diff options
987 files changed, 17778 insertions, 9169 deletions
diff --git a/.github/codecov.yml b/.github/codecov.yml deleted file mode 100644 index fa7b82a..0000000 --- a/.github/codecov.yml +++ /dev/null @@ -1,11 +0,0 @@ -coverage: - status: - project: - default: - informational: true - patch: - default: - informational: true -comment: false -github_checks: - annotations: false diff --git a/.github/workflows/cygwin.yml b/.github/workflows/cygwin.yml index d641b18..2ba1ff2 100644 --- a/.github/workflows/cygwin.yml +++ b/.github/workflows/cygwin.yml @@ -87,7 +87,7 @@ jobs: - name: Run pip run: | export PATH=/usr/bin:/usr/local/bin:$(cygpath ${SYSTEMROOT})/system32 - python3 -m pip --disable-pip-version-check install gcovr fastjsonschema pefile pytest pytest-subtests pytest-xdist coverage + python3 -m pip --disable-pip-version-check install gcovr fastjsonschema pefile pytest pytest-subtests pytest-xdist shell: C:\cygwin\bin\bash.exe --noprofile --norc -o igncr -eo pipefail '{0}' - uses: actions/cache/save@v4 @@ -99,7 +99,7 @@ jobs: - name: Run tests run: | export PATH=/usr/bin:/usr/local/bin:$(cygpath ${SYSTEMROOT})/system32 - python3 ./tools/run_with_cov.py run_tests.py --backend=ninja + python3 ./run_tests.py --backend=ninja env: # Cygwin's static boost installation is broken (some static library # variants such as boost_thread are not present) @@ -112,17 +112,3 @@ jobs: path: meson-test-run.* # test log should be saved on failure if: ${{ !cancelled() }} - - - name: Aggregate coverage reports - run: | - export PATH=/usr/bin:/usr/local/bin:$(cygpath ${SYSTEMROOT})/system32 - ./ci/combine_cov.sh - shell: C:\cygwin\bin\bash.exe --noprofile --norc -o igncr -eo pipefail '{0}' - - - name: Upload coverage report - uses: codecov/codecov-action@v3 - with: - files: .coverage/coverage.xml - name: "${{ matrix.NAME }}" - fail_ci_if_error: false - verbose: true diff --git a/.github/workflows/file_format.yml b/.github/workflows/file_format.yml index d61d634..a8d4ce2 100644 --- a/.github/workflows/file_format.yml +++ b/.github/workflows/file_format.yml @@ -18,3 +18,4 @@ jobs: with: python-version: '3.x' - run: python3 ./run_format_tests.py + - run: python3 ./run_shell_checks.py diff --git a/.github/workflows/images.yml b/.github/workflows/images.yml index d20f7e5..369c91e 100644 --- a/.github/workflows/images.yml +++ b/.github/workflows/images.yml @@ -37,13 +37,14 @@ jobs: fail-fast: false matrix: cfg: - - { name: Arch Linux, id: arch } - - { name: CUDA (on Arch), id: cuda } - - { name: Fedora, id: fedora } - - { name: Gentoo, id: gentoo } - - { name: OpenSUSE, id: opensuse } - - { name: Ubuntu Bionic, id: bionic } - - { name: Ubuntu Rolling, id: ubuntu-rolling } + - { name: Arch Linux, id: arch } + - { name: CUDA (on Arch), id: cuda } + - { name: CUDA Cross (on Ubuntu Jammy), id: cuda-cross } + - { name: Fedora, id: fedora } + - { name: Gentoo, id: gentoo } + - { name: OpenSUSE, id: opensuse } + - { name: Ubuntu Bionic, id: bionic } + - { name: Ubuntu Rolling, id: ubuntu-rolling } steps: # Need v3 because of bionic - uses: actions/checkout@v3 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 5588034..7161b61 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -31,7 +31,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: '3.x' + python-version: '3.13' - run: python -m pip install pylint - run: pylint --output-format colorized mesonbuild @@ -53,7 +53,7 @@ jobs: with: python-version: '3.x' # Pin mypy to version 1.8, so we retain the ability to lint for Python 3.7 - - run: python -m pip install "mypy==1.8" coverage strictyaml types-PyYAML types-tqdm types-chevron + - run: python -m pip install "mypy==1.8" strictyaml truststore types-PyYAML types-tqdm types-chevron - run: python run_mypy.py --allver env: PYTHONUNBUFFERED: 1 diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 2d2ea39..3afb4ba 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -35,7 +35,7 @@ jobs: - run: | export PATH="$HOME/Library/Python/3.9/bin:$PATH" /usr/bin/python3 -m pip install --upgrade pip - /usr/bin/python3 -m pip install pytest pytest-xdist pytest-subtests fastjsonschema coverage + /usr/bin/python3 -m pip install pytest pytest-xdist pytest-subtests fastjsonschema - run: brew install pkg-config ninja llvm qt@5 - env: CPPFLAGS: "-I/opt/homebrew/include" @@ -48,20 +48,7 @@ jobs: export SDKROOT="$(xcodebuild -version -sdk macosx Path)" export PATH="$HOME/Library/Python/3.9/bin:$HOME/tools:/opt/homebrew/opt/qt@5/bin:/opt/homebrew/opt/llvm/bin:$PATH" export PKG_CONFIG_PATH="/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/Current/lib/pkgconfig:/opt/homebrew/opt/qt@5/lib/pkgconfig:$PKG_CONFIG_PATH" - /usr/bin/python3 ./tools/run_with_cov.py ./run_unittests.py - - - name: Aggregate coverage reports - run: | - export PATH="$HOME/Library/Python/3.9/bin:$PATH" - ./ci/combine_cov.sh - - - name: Upload coverage report - uses: codecov/codecov-action@v3 - with: - files: .coverage/coverage.xml - name: "appleclang [unit tests]" - fail_ci_if_error: false - verbose: true + /usr/bin/python3 ./run_unittests.py project-tests-appleclang: @@ -98,9 +85,7 @@ jobs: # https://github.com/actions/setup-python/issues/58 - run: brew install pkg-config ninja llvm qt@5 boost ldc hdf5 openmpi lapack scalapack sdl2 boost-python3 gtk-doc zstd ncurses objfw libomp - run: | - python3 -m pip install --upgrade setuptools - python3 -m pip install --upgrade pip - python3 -m pip install cython coverage + python3 -m pip install cython - env: CPPFLAGS: "-I/opt/homebrew/include" LDFLAGS: "-L/opt/homebrew/lib" @@ -114,18 +99,7 @@ jobs: # We need this to avoid objfw test failures. export PATH="$HOME/tools:/opt/homebrew/opt/qt@5/bin:/opt/homebrew/opt/ncurses/bin:$PATH:/opt/homebrew/opt/llvm/bin" export PKG_CONFIG_PATH="/opt/homebrew/opt/qt@5/lib/pkgconfig:/opt/homebrew/opt/lapack/lib/pkgconfig:/opt/homebrew/opt/ncurses/lib/pkgconfig:$PKG_CONFIG_PATH" - ./tools/run_with_cov.py ./run_project_tests.py --backend=ninja - - - name: Aggregate coverage reports - run: ./ci/combine_cov.sh - - - name: Upload coverage report - uses: codecov/codecov-action@v3 - with: - files: .coverage/coverage.xml - name: "appleclang [project tests; unity=${{ matrix.unity }}]" - fail_ci_if_error: false - verbose: true + ./run_project_tests.py --backend=ninja Qt4macos: # This job only works on Intel Macs, because OpenSSL 1.0 doesn't build on diff --git a/.github/workflows/msys2.yml b/.github/workflows/msys2.yml index 9101e6b..b926d18 100644 --- a/.github/workflows/msys2.yml +++ b/.github/workflows/msys2.yml @@ -29,7 +29,7 @@ permissions: jobs: test: - runs-on: windows-2019 + runs-on: windows-2022 name: ${{ matrix.NAME }} strategy: fail-fast: false @@ -85,11 +85,12 @@ jobs: mingw-w64-${{ matrix.MSYS2_ARCH }}-python-pip mingw-w64-${{ matrix.MSYS2_ARCH }}-python-fastjsonschema mingw-w64-${{ matrix.MSYS2_ARCH }}-objfw + mingw-w64-${{ matrix.MSYS2_ARCH }}-llvm mingw-w64-${{ matrix.MSYS2_ARCH }}-${{ matrix.TOOLCHAIN }} - name: Install dependencies run: | - python3 -m pip --disable-pip-version-check install gcovr pefile pytest pytest-subtests pytest-xdist coverage + python3 -m pip --disable-pip-version-check install gcovr pefile pytest pytest-subtests pytest-xdist - name: Install pypy3 on x86_64 run: | @@ -124,20 +125,9 @@ jobs: pacman --noconfirm --needed -S mingw-w64-${{ matrix.MSYS2_ARCH }}-${{ matrix.MSYS2_CURSES }} fi - MSYSTEM= python3 ./tools/run_with_cov.py run_tests.py --backend=ninja + MSYSTEM= python3 ./run_tests.py --backend=ninja - uses: actions/upload-artifact@v4 with: name: ${{ matrix.NAME }} path: meson-test-run.* - - - name: Aggregate coverage reports - run: ./ci/combine_cov.sh - - - name: Upload coverage report - uses: codecov/codecov-action@v3 - with: - files: .coverage/coverage.xml - name: "${{ matrix.NAME }}" - fail_ci_if_error: false - verbose: true diff --git a/.github/workflows/nonnative.yml b/.github/workflows/nonnative.yml index 2712d10..c616f51 100644 --- a/.github/workflows/nonnative.yml +++ b/.github/workflows/nonnative.yml @@ -36,18 +36,16 @@ jobs: - run: | apt-get -y purge clang gcc gdc apt-get -y autoremove - python3 -m pip install coverage - uses: actions/checkout@v4 - name: Run tests - run: bash -c 'source /ci/env_vars.sh; cd $GITHUB_WORKSPACE; ./tools/run_with_cov.py ./run_tests.py $CI_ARGS --cross ubuntu-armhf.json --cross-only' + run: bash -c 'source /ci/env_vars.sh; cd $GITHUB_WORKSPACE; ./run_tests.py $CI_ARGS --cross ubuntu-armhf.json --cross-only' - - name: Aggregate coverage reports - run: ./ci/combine_cov.sh - - - name: Upload coverage report - uses: codecov/codecov-action@v3 - with: - files: .coverage/coverage.xml - name: "Ubuntu nonnative" - fail_ci_if_error: false - verbose: true + cross-cuda: + runs-on: ubuntu-latest + container: mesonbuild/cuda-cross:latest + env: + MESON_CI_JOBNAME: cuda-cross-${{ github.job }} + steps: + - uses: actions/checkout@v4 + - name: Run tests + run: bash -c 'source /ci/env_vars.sh; cd $GITHUB_WORKSPACE; ./run_tests.py $CI_ARGS --cross cuda-cross.json --cross-only' diff --git a/.github/workflows/os_comp.yml b/.github/workflows/os_comp.yml index 0912a75..4b9b7a4 100644 --- a/.github/workflows/os_comp.yml +++ b/.github/workflows/os_comp.yml @@ -72,18 +72,7 @@ jobs: source /ci/env_vars.sh cd $GITHUB_WORKSPACE - ./tools/run_with_cov.py ./run_tests.py $CI_ARGS - - - name: Aggregate coverage reports - run: ./ci/combine_cov.sh - - - name: Upload coverage report - uses: codecov/codecov-action@v3 - with: - files: .coverage/coverage.xml - name: "OS Comp [${{ matrix.cfg.name }}]" - fail_ci_if_error: false - verbose: true + ./run_tests.py $CI_ARGS pypy: name: 'Arch / PyPy' @@ -172,15 +161,4 @@ jobs: update-alternatives --set i686-w64-mingw32-gcc /usr/bin/i686-w64-mingw32-gcc-posix update-alternatives --set i686-w64-mingw32-g++ /usr/bin/i686-w64-mingw32-g++-posix - ./tools/run_with_cov.py ./run_tests.py $RUN_TESTS_ARGS -- $MESON_ARGS - - - name: Aggregate coverage reports - run: ./ci/combine_cov.sh - - - name: Upload coverage report - uses: codecov/codecov-action@v3 - with: - files: .coverage/coverage.xml - name: "Ubuntu [${{ matrix.cfg.CC }} ${{ matrix.cfg.RUN_TESTS_ARGS }} ${{ matrix.cfg.MESON_ARGS }}]" - fail_ci_if_error: false - verbose: true + ./run_tests.py $RUN_TESTS_ARGS -- $MESON_ARGS diff --git a/.github/workflows/unusedargs_missingreturn.yml b/.github/workflows/unusedargs_missingreturn.yml index d6f1246..4367ce5 100644 --- a/.github/workflows/unusedargs_missingreturn.yml +++ b/.github/workflows/unusedargs_missingreturn.yml @@ -52,22 +52,10 @@ jobs: run: | sudo apt update -yq sudo apt install -yq --no-install-recommends g++ gfortran ninja-build gobjc gobjc++ - python -m pip install coverage - - run: ./tools/run_with_cov.py run_project_tests.py --only cmake common fortran platform-linux "objective c" "objective c++" + - run: ./run_project_tests.py --only cmake common fortran platform-linux "objective c" "objective c++" env: MESON_CI_JOBNAME: linux-ubuntu-gcc-werror - - name: Aggregate coverage reports - run: ./ci/combine_cov.sh - - - name: Upload coverage report - uses: codecov/codecov-action@v3 - with: - files: .coverage/coverage.xml - name: "UnusedMissingReturn" - fail_ci_if_error: false - verbose: true - windows: runs-on: windows-latest steps: @@ -76,23 +64,11 @@ jobs: with: python-version: '3.x' - - run: pip install ninja pefile coverage + - run: pip install ninja pefile - - run: python ./tools/run_with_cov.py run_project_tests.py --only platform-windows + - run: python ./run_project_tests.py --only platform-windows env: CC: gcc CXX: g++ FC: gfortran MESON_CI_JOBNAME: msys2-gcc-werror - - - name: Aggregate coverage reports - run: ./ci/combine_cov.sh - shell: C:\msys64\usr\bin\bash.exe --noprofile --norc -o igncr -eo pipefail '{0}' - - - name: Upload coverage report - uses: codecov/codecov-action@v3 - with: - files: .coverage/coverage.xml - name: "UnusedMissingReturn Windows" - fail_ci_if_error: false - verbose: true @@ -22,6 +22,7 @@ __pycache__ *~ *.swp packagecache +.wraplock /MANIFEST /build /dist @@ -1,7 +1,8 @@ Alexandre Foley <Alexandre.foley@usherbrooke.ca> AlexandreFoley <alexandre.foley@usherbrooke.ca> Igor Gnatenko <i.gnatenko.brain@gmail.com> Igor Gnatenko <ignatenko@redhat.com> -Jussi Pakkanen <jpakkane@gmail.com> Jussi Pakkanen <jpakkane@brash.local> -Jussi Pakkanen <jpakkane@gmail.com> jpakkane <jpakkane@gmail.com> +Jussi Pakkanen <jussi.pakkanen@mailbox.org> Jussi Pakkanen <jpakkane@brash.local> +Jussi Pakkanen <jussi.pakkanen@mailbox.org> Jussi Pakkanen <jpakkane@gmail.com> +Jussi Pakkanen <jussi.pakkanen@mailbox.org> jpakkane <jpakkane@gmail.com> Liam Beguin <liambeguin@gmail.com> Liam Beguin <lvb@xiphos.com> Nirbheek Chauhan <nirbheek@centricular.com> Nirbheek Chauhan <nirbheek.chauhan@gmail.com> Nicolas Schneider <nioncode+git@gmail.com> Nicolas Schneider <nioncode+github@gmail.com> @@ -22,3 +22,7 @@ check_untyped_defs = True # disallow_any_explicit = True # disallow_any_generics = True # disallow_subclassing_any = True + +# future default behaviors +allow_redefinition_new = True +local_partial_types = True diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ea511f3..4488648 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -54,28 +54,28 @@ variables: jobs: -- job: vs2019 +- job: vs2022 timeoutInMinutes: 120 pool: - vmImage: windows-2019 + vmImage: windows-2022 strategy: matrix: - vc2019x64ninja: + vc2022x64ninja: arch: x64 - compiler: msvc2019 + compiler: msvc2022 backend: ninja ifort: true - vc2019x64vs: + vc2022x64vs: arch: x64 - compiler: msvc2019 - backend: vs2019 + compiler: msvc2022 + backend: vs2022 # mysteriously, several tests fail because vs cannot find # executables such as cmd.exe ??? ifort: false - vc2019arm64ninjacross: + vc2022arm64ninjacross: arch: arm64 - compiler: msvc2019 + compiler: msvc2022 backend: ninja extraargs: --cross arm64cl.txt --cross-only # ifort doesn't support arm64 @@ -105,7 +105,7 @@ jobs: displayName: insert ifort into environment inputs: filename: ci/intel-scripts/activate_windows.bat - arguments: vs2019 + arguments: vs2022 modifyEnvironment: True condition: eq(variables.ifort, 'true') - task: PowerShell@2 @@ -114,8 +114,3 @@ jobs: filePath: .\ci\run.ps1 env: MESON_CI_JOBNAME: azure-$(System.JobName) - - task: PowerShell@2 - displayName: Gathering coverage report - inputs: - targetType: 'filePath' - filePath: .\ci\coverage.ps1 diff --git a/ci/ciimage/arch/install.sh b/ci/ciimage/arch/install.sh index de43cb2..402bb04 100755 --- a/ci/ciimage/arch/install.sh +++ b/ci/ciimage/arch/install.sh @@ -9,12 +9,13 @@ source /ci/common.sh pkgs=( python python-pip pypy3 ninja make git sudo fakeroot autoconf automake patch - libelf gcc gcc-fortran gcc-objc vala rust bison flex cython go dlang-dmd + libelf gcc gcc-fortran gcc-objc vala rust byacc flex cython go dlang-dmd mono boost qt5-base gtkmm3 gtest gmock protobuf gobject-introspection itstool glib2-devel gtk3 java-environment=8 gtk-doc llvm clang sdl2 graphviz - doxygen vulkan-headers vulkan-icd-loader vulkan-validation-layers openssh mercurial gtk-sharp-2 qt5-tools + doxygen vulkan-headers vulkan-icd-loader vulkan-validation-layers openssh mercurial gtk-sharp-3 qt5-tools libwmf cmake netcdf-fortran openmpi nasm gnustep-base gettext python-lxml hotdoc rust-bindgen qt6-base qt6-tools qt6-declarative wayland wayland-protocols + intel-oneapi-mkl # cuda ) diff --git a/ci/ciimage/cuda-cross/image.json b/ci/ciimage/cuda-cross/image.json new file mode 100644 index 0000000..062322e --- /dev/null +++ b/ci/ciimage/cuda-cross/image.json @@ -0,0 +1,8 @@ +{ + "base_image": "ubuntu:22.04", + "args": ["--only", "cuda", "--cross", "cuda-cross.json"], + "env": { + "CI": "1", + "MESON_CI_JOBNAME": "linux-cuda-cross" + } +} diff --git a/ci/ciimage/cuda-cross/install.sh b/ci/ciimage/cuda-cross/install.sh new file mode 100755 index 0000000..6b5fe7f --- /dev/null +++ b/ci/ciimage/cuda-cross/install.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +set -e + +source /ci/common.sh + +export DEBIAN_FRONTEND=noninteractive +export LANG='C.UTF-8' + +apt-get -y update +apt-get -y upgrade +apt-get -y install wget + +# Cuda repo + keyring. +wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/cuda-keyring_1.1-1_all.deb +apt-get -y install ./cuda-keyring_1.1-1_all.deb + +# Cuda cross repo. +echo "deb [signed-by=/usr/share/keyrings/cuda-archive-keyring.gpg] https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/cross-linux-aarch64/ /" \ + > /etc/apt/sources.list.d/cuda-ubuntu2204-cross-linux-aarch64.list +apt-get -y update + +pkgs=( + clang cmake crossbuild-essential-arm64 cuda-cross-aarch64 + cuda-nvcc-12-9 git libglib2.0-dev ninja-build pkg-config python3-pip +) + +apt-get -y install "${pkgs[@]}" + +install_minimal_python_packages + +# Tests need nvcc in PATH in order to run cuda tests. +echo "export PATH=\$PATH:/usr/local/cuda/bin" >> /ci/env_vars.sh + +# cleanup +apt-get -y clean +apt-get -y autoclean +rm cuda-keyring_1.1-1_all.deb diff --git a/ci/ciimage/fedora/install.sh b/ci/ciimage/fedora/install.sh index aa87655..62952dc 100755 --- a/ci/ciimage/fedora/install.sh +++ b/ci/ciimage/fedora/install.sh @@ -12,11 +12,13 @@ pkgs=( boost-python3-devel itstool gtk3-devel java-latest-openjdk-devel gtk-doc llvm-devel clang-devel SDL2-devel graphviz-devel zlib zlib-devel zlib-static #hdf5-openmpi-devel hdf5-devel netcdf-openmpi-devel netcdf-devel netcdf-fortran-openmpi-devel netcdf-fortran-devel scalapack-openmpi-devel - doxygen vulkan-devel vulkan-validation-layers-devel openssh lksctp-tools-devel objfw mercurial gtk-sharp2-devel libpcap-devel gpgme-devel + doxygen vulkan-devel vulkan-validation-layers-devel openssh lksctp-tools-devel objfw mercurial gtk-sharp3-devel libpcap-devel gpgme-devel qt5-qtbase-devel qt5-qttools-devel qt5-linguist qt5-qtbase-private-devel qt6-qtdeclarative-devel qt6-qtbase-devel qt6-qttools-devel qt6-linguist qt6-qtbase-private-devel libwmf-devel valgrind cmake openmpi-devel nasm gnustep-base-devel gettext-devel ncurses-devel libxml2-devel libxslt-devel libyaml-devel glib2-devel json-glib-devel libgcrypt-devel wayland-devel wayland-protocols-devel + # HACK: remove npm once we switch back to hotdoc sdist + nodejs-npm ) # Sys update @@ -24,7 +26,12 @@ dnf -y upgrade # Install deps dnf -y install "${pkgs[@]}" -install_python_packages hotdoc +# HACK: build hotdoc from git repo since current sdist is broken on modern compilers +# change back to 'hotdoc' once it's fixed +install_python_packages git+https://github.com/hotdoc/hotdoc + +# HACK: uninstall npm after building hotdoc, remove when we remove npm +dnf -y remove nodejs-npm # Cleanup dnf -y clean all diff --git a/ci/ciimage/gentoo/image.json b/ci/ciimage/gentoo/image.json index e59eee5..f7ecd64 100644 --- a/ci/ciimage/gentoo/image.json +++ b/ci/ciimage/gentoo/image.json @@ -1,5 +1,5 @@ { - "base_image": "gentoo/stage3:desktop", + "base_image": "gentoo/stage3:latest", "env": { "CI": "1", "MESON_CI_JOBNAME": "linux-gentoo-gcc", diff --git a/ci/ciimage/gentoo/install.sh b/ci/ciimage/gentoo/install.sh index 30b0299..c1e15ef 100755 --- a/ci/ciimage/gentoo/install.sh +++ b/ci/ciimage/gentoo/install.sh @@ -23,12 +23,19 @@ pkgs_stable=( dev-util/bindgen dev-libs/elfutils + dev-libs/protobuf + + # modules dev-util/gdbus-codegen + dev-util/glib-utils dev-libs/gobject-introspection dev-util/itstool - dev-libs/protobuf + dev-util/wayland-scanner + dev-libs/wayland-protocols + dev-libs/wayland # custom deps + dev-libs/boost net-libs/libpcap dev-util/gtk-doc media-libs/libwmf @@ -38,14 +45,22 @@ pkgs_stable=( dev-cpp/gtest sci-libs/hdf5 dev-qt/linguist-tools - sys-devel/llvm + dev-qt/qtwidgets:5 + llvm-core/llvm + dev-qt/qtdeclarative:6 dev-qt/qttools + net-print/cups + dev-util/vulkan-headers + media-libs/vulkan-loader # misc app-admin/sudo app-text/doxygen sys-devel/bison + sys-devel/reflex sys-devel/gettext + # needed by vala + x11-libs/gtk+ # TODO: vulkan-validation-layers # TODO: cuda @@ -58,21 +73,14 @@ pkgs_stable=( #dev-libs/wayland #dev-libs/wayland-protocols #dev-python/pypy3 - #dev-qt/qtbase:6 - #dev-qt/qtcore:5 - #dev-qt/qttools:6 #dev-vcs/mercurial #gnustep-base/gnustep-base #media-gfx/graphviz #sci-libs/netcdf-fortran - #sys-devel/clang + #llvm-core/clang #x11-libs/gtk+:3 ) pkgs_latest=( - # ~arch boost needed for py3.12 for now (needs 1.84) - dev-build/b2 - dev-libs/boost - dev-build/autoconf dev-build/automake @@ -94,7 +102,6 @@ printf "%s\n" ${pkgs_latest[@]} >> /etc/portage/package.accept_keywords/meson cat /etc/portage/package.accept_keywords/meson cat <<-EOF > /etc/portage/package.accept_keywords/misc - dev-lang/python-exec dev-lang/python EOF @@ -102,13 +109,21 @@ mkdir /etc/portage/binrepos.conf || true mkdir /etc/portage/profile || true cat <<-EOF > /etc/portage/package.use/ci dev-cpp/gtkmm X + media-libs/libglvnd X + media-libs/freetype harfbuzz + x11-libs/cairo X + x11-libs/libxkbcommon X dev-lang/rust clippy rustfmt dev-lang/rust-bin clippy rustfmt dev-libs/boost python + sci-libs/hdf5 cxx + + # slimmed binpkg, nomesa + media-libs/libsdl2 -opengl -wayland -alsa -dbus -gles2 -udev -vulkan # Some of these settings are needed just to get the binpkg but # aren't negative to have anyway - sys-devel/gcc ada d + sys-devel/gcc ada d jit >=sys-devel/gcc-13 ada objc objc++ sys-devel/gcc pgo lto @@ -133,17 +148,20 @@ cat <<-EOF >> /etc/portage/make.conf FEATURES="\${FEATURES} -ipc-sandbox -network-sandbox -pid-sandbox" EOF -# TODO: Enable all Pythons / add multiple jobs with diff. Python impls? +# Maybe we could enable all Pythons / add multiple jobs with diff. Python impls? #echo '*/* PYTHON_TARGETS: python3_10 python3_11 python3_12' >> /etc/portage/package.use/python -echo '*/* PYTHON_TARGETS: python3_12' >> /etc/portage/package.use/python -cat <<-EOF >> /etc/portage/profile/use.mask --python_targets_python3_12 --python_single_target_python3_12 -EOF -cat <<-EOF >> /etc/portage/profile/use.stable.mask --python_targets_python3_12 --python_single_target_python3_12 -EOF + +# The below is for cases where we want non-default Python (either to get +# better coverage from something older, or something newer) +#echo '*/* PYTHON_TARGETS: python3_12' >> /etc/portage/package.use/python +#cat <<-EOF >> /etc/portage/profile/use.mask +#-python_targets_python3_12 +#-python_single_target_python3_12 +#EOF +#cat <<-EOF >> /etc/portage/profile/use.stable.mask +#-python_targets_python3_12 +#-python_single_target_python3_12 +#EOF echo 'dev-lang/python ensurepip' >> /etc/portage/package.use/python @@ -164,6 +182,15 @@ python3 -m pip install "${base_python_pkgs[@]}" echo "source /etc/profile" >> /ci/env_vars.sh +# For inexplicable reasons, Gentoo only packages valac as valac-$(version) so +# no software can locate it. Parse the installed version out of portage and +# export it to meson. +VALA_VER=$(portageq best_version / dev-lang/vala) +VALA_VER=${VALA_VER#dev-lang/vala-} +VALA_VER=${VALA_VER%.*} +echo "export VALAC=/usr/bin/valac-${VALA_VER}" >> /ci/env_vars.sh +echo "export VAPIGEN=/usr/bin/vapigen-${VALA_VER}" >> /ci/env_vars.sh + # Cleanup to avoid including large contents in the docker image. # We don't need cache files that are side artifacts of installing packages. # We also don't need the gentoo tree -- the official docker image doesn't diff --git a/ci/ciimage/opensuse/install.sh b/ci/ciimage/opensuse/install.sh index 7a76071..7adb3fd 100755 --- a/ci/ciimage/opensuse/install.sh +++ b/ci/ciimage/opensuse/install.sh @@ -16,9 +16,11 @@ pkgs=( qt6-declarative-devel qt6-base-devel qt6-tools qt6-tools-linguist qt6-declarative-tools qt6-core-private-devel libwmf-devel valgrind cmake nasm gnustep-base-devel gettext-tools gettext-runtime gettext-csharp ncurses-devel libxml2-devel libxslt-devel libyaml-devel glib2-devel json-glib-devel - boost-devel libboost_date_time-devel libboost_filesystem-devel libboost_locale-devel libboost_system-devel - libboost_test-devel libboost_log-devel libboost_regex-devel + boost-devel libboost_date_time-devel libboost_filesystem-devel libboost_locale-devel + libboost_headers-devel libboost_test-devel libboost_log-devel libboost_regex-devel libboost_python3-devel libboost_regex-devel + # HACK: remove npm once we switch back to hotdoc sdist + npm ) # Sys update @@ -27,7 +29,12 @@ zypper --non-interactive update # Install deps zypper install -y "${pkgs[@]}" -install_python_packages hotdoc +# HACK: build hotdoc from git repo since current sdist is broken on modern compilers +# change back to 'hotdoc' once it's fixed +install_python_packages git+https://github.com/hotdoc/hotdoc + +# HACK: uninstall npm after building hotdoc, remove when we remove npm +zypper remove -y -u npm echo 'export PKG_CONFIG_PATH="/usr/lib64/mpi/gcc/openmpi3/lib64/pkgconfig:$PKG_CONFIG_PATH"' >> /ci/env_vars.sh diff --git a/ci/ciimage/ubuntu-rolling/install.sh b/ci/ciimage/ubuntu-rolling/install.sh index 1c0891c..3460be4 100755 --- a/ci/ciimage/ubuntu-rolling/install.sh +++ b/ci/ciimage/ubuntu-rolling/install.sh @@ -43,7 +43,9 @@ eatmydata apt-get -y build-dep meson eatmydata apt-get -y install "${pkgs[@]}" eatmydata apt-get -y install --no-install-recommends wine-stable # Wine is special -install_python_packages hotdoc +# HACK: build hotdoc from git repo since current sdist is broken on modern compilers +# change back to 'hotdoc' once it's fixed +install_python_packages git+https://github.com/hotdoc/hotdoc # Lower ulimit before running dub, otherwise there's a very high chance it will OOM. # See: https://github.com/dlang/phobos/pull/9048 and https://github.com/dlang/phobos/pull/8990 @@ -69,7 +71,7 @@ rustup target add arm-unknown-linux-gnueabihf # Use the GitHub API to get the latest release information LATEST_RELEASE=$(wget -qO- "https://api.github.com/repos/ziglang/zig/releases/latest") ZIGVER=$(echo "$LATEST_RELEASE" | jq -r '.tag_name') -ZIG_BASE="zig-linux-x86_64-$ZIGVER" +ZIG_BASE="zig-x86_64-linux-$ZIGVER" wget "https://ziglang.org/download/$ZIGVER/$ZIG_BASE.tar.xz" tar xf "$ZIG_BASE.tar.xz" rm -rf "$ZIG_BASE.tar.xz" diff --git a/ci/combine_cov.sh b/ci/combine_cov.sh deleted file mode 100755 index 99a503b..0000000 --- a/ci/combine_cov.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -echo "Combining coverage reports..." -coverage combine - -echo "Generating XML report..." -coverage xml - -echo "Printing report" -coverage report diff --git a/ci/coverage.ps1 b/ci/coverage.ps1 deleted file mode 100644 index ebd7cd4..0000000 --- a/ci/coverage.ps1 +++ /dev/null @@ -1,14 +0,0 @@ -echo "" -echo "" -echo "=== Gathering coverage report ===" -echo "" - -python3 -m coverage combine -python3 -m coverage xml -python3 -m coverage report - -# Currently codecov.py does not handle Azure, use this fork of a fork to get it -# working without requiring a token -git clone https://github.com/mensinda/codecov-python -python3 -m pip install --ignore-installed ./codecov-python -python3 -m codecov -f .coverage/coverage.xml -n "VS$env:compiler $env:arch $env:backend" -c $env:SOURCE_VERSION @@ -49,7 +49,7 @@ function DownloadFile([String] $Source, [String] $Destination) { if (($env:backend -eq 'ninja') -and ($env:arch -ne 'arm64')) { $dmd = $true } else { $dmd = $false } -DownloadFile -Source https://github.com/mesonbuild/cidata/releases/download/ci5/ci_data.zip -Destination $env:AGENT_WORKFOLDER\ci_data.zip +DownloadFile -Source https://github.com/mesonbuild/cidata/releases/download/ci7/ci_data.zip -Destination $env:AGENT_WORKFOLDER\ci_data.zip echo "Extracting ci_data.zip" Expand-Archive $env:AGENT_WORKFOLDER\ci_data.zip -DestinationPath $env:AGENT_WORKFOLDER\ci_data & "$env:AGENT_WORKFOLDER\ci_data\install.ps1" -Arch $env:arch -Compiler $env:compiler -Boost $true -DMD $dmd @@ -92,7 +92,7 @@ python --version # Needed for running unit tests in parallel. echo "" -python -m pip --disable-pip-version-check install --upgrade pefile pytest-xdist pytest-subtests fastjsonschema coverage +python -m pip --disable-pip-version-check install --upgrade pefile pytest-xdist pytest-subtests fastjsonschema # Needed for running the Cython tests python -m pip --disable-pip-version-check install cython @@ -102,6 +102,6 @@ echo "=== Start running tests ===" # Starting from VS2019 Powershell(?) will fail the test run # if it prints anything to stderr. Python's test runner # does that by default so we need to forward it. -cmd /c "python 2>&1 ./tools/run_with_cov.py run_tests.py --backend $env:backend $env:extraargs" +cmd /c "python 2>&1 run_tests.py --backend $env:backend $env:extraargs" exit $LastExitCode diff --git a/ci/usercustomize.py b/ci/usercustomize.py deleted file mode 100644 index d72c6ad..0000000 --- a/ci/usercustomize.py +++ /dev/null @@ -1,5 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# Copyright 2021 The Meson development team - -import coverage -coverage.process_startup() diff --git a/cross/cuda-cross.json b/cross/cuda-cross.json new file mode 100644 index 0000000..f2d0086 --- /dev/null +++ b/cross/cuda-cross.json @@ -0,0 +1,5 @@ +{ + "file": "cuda-cross.txt", + "tests": ["cuda"], + "env": {} +} diff --git a/cross/cuda-cross.txt b/cross/cuda-cross.txt new file mode 100644 index 0000000..7e81463 --- /dev/null +++ b/cross/cuda-cross.txt @@ -0,0 +1,17 @@ +[binaries] +c = ['/usr/bin/aarch64-linux-gnu-gcc'] +cpp = ['/usr/bin/aarch64-linux-gnu-g++'] +cuda = ['/usr/local/cuda/bin/nvcc'] +ar = '/usr/bin/aarch64-linux-gnu-ar' +strip = '/usr/bin/aarch64-linux-gnu-strip' +ld = '/usr/bin/aarch64-linux-gnu-ld' + +[host_machine] +system = 'linux' +cpu_family = 'aarch64' +cpu = 'aarch64' +endian = 'little' + +[built-in options] +cuda_link_args = ['-lstdc++'] +cuda_ccbindir = '/usr/bin/aarch64-linux-gnu-gcc' diff --git a/cross/ubuntu-armhf.txt b/cross/ubuntu-armhf.txt index 6409e39..97a1c21 100644 --- a/cross/ubuntu-armhf.txt +++ b/cross/ubuntu-armhf.txt @@ -4,6 +4,7 @@ c = ['/usr/bin/arm-linux-gnueabihf-gcc'] cpp = ['/usr/bin/arm-linux-gnueabihf-g++'] rust = ['rustc', '--target', 'arm-unknown-linux-gnueabihf', '-C', 'linker=/usr/bin/arm-linux-gnueabihf-gcc-7'] +rustdoc = ['rustdoc', '--target', 'arm-unknown-linux-gnueabihf', '-C', 'linker=/usr/bin/arm-linux-gnueabihf-gcc-7'] ar = '/usr/arm-linux-gnueabihf/bin/ar' strip = '/usr/arm-linux-gnueabihf/bin/strip' pkg-config = '/usr/bin/arm-linux-gnueabihf-pkg-config' diff --git a/cross/xc32.txt b/cross/xc32.txt new file mode 100644 index 0000000..dd3d026 --- /dev/null +++ b/cross/xc32.txt @@ -0,0 +1,17 @@ +# This file assumes that path to the Microchip XC32 toolchain is added +# to the environment(PATH) variable, so that Meson can find XC32 while building. + +[binaries] +c = 'xc32-gcc' +cpp = 'xc32-g++' +ar = 'xc32-ar' +bin2hex = 'xc32-bin2hex' + +[host_machine] +system = 'baremetal' +cpu_family = 'pic32' +cpu = 'pic32' +endian = 'little' + +[properties] +needs_exe_wrapper = true diff --git a/data/macros.meson b/data/macros.meson index 5977410..c2d1adc 100644 --- a/data/macros.meson +++ b/data/macros.meson @@ -29,14 +29,14 @@ %{shrink:%{__meson} compile \ -C %{_vpath_builddir} \ -j %{_smp_build_ncpus} \ - %{?__meson_verbose:--verbose} \ + %[ 0%{?__meson_verbose} ? "--verbose" : "" ] \ %{nil}} %meson_install \ %{shrink:DESTDIR=%{buildroot} %{__meson} install \ -C %{_vpath_builddir} \ --no-rebuild \ - %{!?__meson_verbose:--quiet} \ + %[ ! 0%{?__meson_verbose} ? "--quiet" : "" ] \ %{nil}} %meson_test \ diff --git a/data/syntax-highlighting/vim/syntax/meson.vim b/data/syntax-highlighting/vim/syntax/meson.vim index a1679e0..905e9fe 100644 --- a/data/syntax-highlighting/vim/syntax/meson.vim +++ b/data/syntax-highlighting/vim/syntax/meson.vim @@ -27,7 +27,7 @@ endif let s:cpo_save = &cpo set cpo&vim -" http://mesonbuild.com/Syntax.html +" https://mesonbuild.com/Syntax.html syn keyword mesonConditional elif else if endif syn keyword mesonRepeat foreach endforeach syn keyword mesonOperator and not or in @@ -54,7 +54,7 @@ syn match mesonEscape "\\N{\a\+\%(\s\a\+\)*}" contained syn match mesonEscape "\\$" " Meson only supports integer numbers -" http://mesonbuild.com/Syntax.html#numbers +" https://mesonbuild.com/Syntax.html#numbers syn match mesonNumber "\<\d\+\>" syn match mesonNumber "\<0x\x\+\>" syn match mesonNumber "\<0o\o\+\>" diff --git a/docs/extensions/refman_links.py b/docs/extensions/refman_links.py index 5c22a0a..865668b 100644 --- a/docs/extensions/refman_links.py +++ b/docs/extensions/refman_links.py @@ -1,11 +1,13 @@ from pathlib import Path from json import loads +import os import re from hotdoc.core.exceptions import HotdocSourceException from hotdoc.core.extension import Extension from hotdoc.core.tree import Page from hotdoc.core.project import Project +from hotdoc.core.symbols import * from hotdoc.run_hotdoc import Application from hotdoc.core.formatter import Formatter from hotdoc.utils.loggable import Logger, warn, info @@ -52,6 +54,35 @@ class RefmanLinksExtension(Extension): with valid links to the correct URL. To reference objects / types use the [[@object]] syntax. ''' + for key, value in self._data.items(): + path = os.path.relpath(value, self.app.config.get_invoke_dir()).split('#')[0] + if path == page.link.ref: + if key.startswith('@'): + res = self.create_symbol( + ClassSymbol, + display_name=key[1:], + filename=path, + unique_name=key) + res.link = Link(value, res.display_name, res.unique_name) + elif '.' in key: + res = self.create_symbol( + MethodSymbol, + parameters=[], + display_name=key.split('.')[-1], + parent_name=f'@{key.split(".")[-2]}', + filename=path, + unique_name=key) + res.link = Link(value, key, res.unique_name) + else: + res = self.create_symbol( + FunctionSymbol, + parameters=[], + display_name=key, + filename=path, + unique_name=key) + res.link = Link(value, res.display_name, res.unique_name) + page.symbols.append(res) + link_regex = re.compile(r'(\[\[#?@?([ \n\t]*[a-zA-Z0-9_]+[ \n\t]*\.)*[ \n\t]*[a-zA-Z0-9_]+[ \n\t]*\]\])(.)?', re.MULTILINE) for m in link_regex.finditer(page.formatted_contents): i = m.group(1) @@ -103,6 +134,10 @@ class RefmanLinksExtension(Extension): ext.formatter.formatting_page_signal.connect(self._formatting_page_cb) info('Meson refman extension LOADED') + def create_symbol(self, *args, **kwargs): + kwargs['language'] = 'meson' + return super(RefmanLinksExtension, self).create_symbol(*args, **kwargs) + @staticmethod def get_dependencies() -> T.List[T.Type[Extension]]: return [] # In case this extension has dependencies on other extensions diff --git a/docs/markdown/Adding-new-projects-to-wrapdb.md b/docs/markdown/Adding-new-projects-to-wrapdb.md index 7c28b49..e690a8b 100644 --- a/docs/markdown/Adding-new-projects-to-wrapdb.md +++ b/docs/markdown/Adding-new-projects-to-wrapdb.md @@ -51,6 +51,37 @@ If the project name is too generic or ambiguous (e.g. `benchmark`), consider using `organization-project` naming format (e.g. `google-benchmark`). +## Overriding dependencies in the submitted project + +Ideally the project you submit should make a call to `meson.override_dependency` +for each dependency you would like to expose, with the first argument +matching the pkg-config file name. This abstracts away the +need to know and keep track of the variable names downstream. + +For instance, the Apache Arrow project exposes multiple dependencies like +its base `arrow` library, along with an `arrow-compute` library. The +project generates `arrow.pc` and `arrow-compute.pc` files for pkg-config +respectively, so internally the project also calls: + +```meson +arrow_dep = declare_dependency(...) +meson.override_dependency('arrow', arrow_dep) + +arrow_compute_dep = declare_dependency(...) +meson.override_dependency('arrow-compute', arrow_compute_dep) +``` +Note that `meson.override_dependency` was introduced in Meson version +0.54.0. If your project supports older versions of Meson, you should +add a condition to only call that function in versions where it is +available: + +```meson +if meson.version().version_compare('>=0.54.0') + meson.override_dependency('arrow', arrow_dep) + meson.override_dependency('arrow-compute', arrow_compute_dep) +endif +``` + ## How to contribute a new wrap If the project already uses Meson build system, then only a wrap file @@ -83,22 +114,41 @@ 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: +Meson's automatic dependency resolver work. + +Assuming the project that you are creating +a wrap file for has called `meson.override_dependency`, then you +can declare those overridden dependencies in the `provide` section +of the wrap file: + +```ini +[provide] +dependency_names = arrow, arrow_compute +``` + +In the case that you do not control the upstream Meson configuration +and it does not already make a call to `meson.override_dependency`, +then you can still expose dependency variables in the wrap file, using +a syntax like: ```ini [provide] -ogg = ogg_dep +arrow = arrow_dep +arrow_compute = arrow_compute_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). +The `arrow` and `arrow_compute` parts on the left refer to the dependency +names, which should be the same as their Pkg-Config name. `arrow_dep` and +`arrow_compute_dep` on the right refer to the variables in the build +definition that provide the dependencies. Most commonly, they hold 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). + +However, it is strongly advised in such cases to request that the upstream +repository use `meson.override_dependency` for its next release, so that +the variable names chosen in the upstream configuration file can be +decoupled from the wrap file contents. Now you can create the build files, if the upstream project does not contain any, and work on them until the project builds correctly. diff --git a/docs/markdown/Build-options.md b/docs/markdown/Build-options.md index 190705d..ee12f55 100644 --- a/docs/markdown/Build-options.md +++ b/docs/markdown/Build-options.md @@ -244,6 +244,10 @@ project which also has an option called `some_option`, then calling `yield` is `false`, `get_option` returns the value of the subproject's option. +*Since 1.8.0* `-Dsub:some_option=anothervalue`, when used with a +yielding option, sets the value separately from the option +it yields to. + ## Built-in build options diff --git a/docs/markdown/Builtin-options.md b/docs/markdown/Builtin-options.md index faf7a60..ee576c3 100644 --- a/docs/markdown/Builtin-options.md +++ b/docs/markdown/Builtin-options.md @@ -72,31 +72,33 @@ Options that are labeled "per machine" in the table are set per machine. See the [specifying options per machine](#specifying-options-per-machine) section for details. -| Option | Default value | Description | Is per machine | Is per subproject | -| -------------------------------------- | ------------- | ----------- | -------------- | ----------------- | +| Option | Default value | Description | Is per machine | Per subproject (since) | +| -------------------------------------- | ------------- | ----------- | -------------- | ---------------------- | | auto_features {enabled, disabled, auto} | auto | Override value of all 'auto' features | no | no | | backend {ninja, vs,<br>vs2010, vs2012, vs2013, vs2015, vs2017, vs2019, vs2022, xcode, none} | ninja | Backend to use | no | no | | genvslite {vs2022} | vs2022 | Setup multi-buildtype ninja build directories and Visual Studio solution | no | no | -| buildtype {plain, debug,<br>debugoptimized, release, minsize, custom} | debug | Build type to use | no | no | -| debug | true | Enable debug symbols and other information | no | no | -| default_both_libraries {shared, static, auto} | shared | Default library type for both_libraries | no | no | -| default_library {shared, static, both} | shared | Default library type | no | yes | +| buildtype {plain, debug,<br>debugoptimized, release, minsize, custom} | debug | Build type to use | no | 1.8.0 | +| debug | true | Enable debug symbols and other information | no | 1.8.0 | +| default_both_libraries {shared, static, auto} | shared | Default library type for both_libraries | no | 1.8.0 | +| default_library {shared, static, both} | shared | Default library type | no | 0.54.0 | | errorlogs | true | Whether to print the logs from failing tests. | no | no | | install_umask {preserve, 0000-0777} | 022 | Default umask to apply on permissions of installed files | no | no | | layout {mirror,flat} | mirror | Build directory layout | no | no | -| optimization {plain, 0, g, 1, 2, 3, s} | 0 | Optimization level | no | no | +| namingscheme {platform, classic} | classic | Library naming scheme to use | no | 1.10.0 | +| optimization {plain, 0, g, 1, 2, 3, s} | 0 | Optimization level | no | 1.8.0 | | pkg_config_path {OS separated path} | '' | Additional paths for pkg-config to search before builtin paths | yes | no | | prefer_static | false | Whether to try static linking before shared linking | no | no | | cmake_prefix_path | [] | Additional prefixes for cmake to search before builtin paths | yes | no | | stdsplit | true | Split stdout and stderr in test logs | no | no | -| strip | false | Strip targets on install | no | no | -| unity {on, off, subprojects} | off | Unity build | no | no | -| unity_size {>=2} | 4 | Unity file block size | no | no | -| warning_level {0, 1, 2, 3, everything} | 1 | Set the warning level. From 0 = compiler default to everything = highest | no | yes | -| werror | false | Treat warnings as errors | no | yes | +| strip | false | Strip targets on install | no | 1.8.0 | +| unity {on, off, subprojects} | off | Unity build | no | 1.8.0 | +| unity_size {>=2} | 4 | Unity file block size | no | 1.8.0 | +| warning_level {0, 1, 2, 3, everything} | 1 | Set the warning level. From 0 = compiler default to everything = highest | no | 0.56.0 | +| werror | false | Treat warnings as errors | no | 0.54.0 | | wrap_mode {default, nofallback,<br>nodownload, forcefallback, nopromote} | default | Wrap mode to use | no | no | | force_fallback_for | [] | Force fallback for those dependencies | no | no | | vsenv | false | Activate Visual Studio environment | no | no | +| os2_emxomf | false | Use OMF format on OS/2 | no | no | (For the Rust language only, `warning_level=0` disables all warnings). @@ -150,6 +152,10 @@ the two-way mapping: All other combinations of `debug` and `optimization` set `buildtype` to `'custom'`. +Note that `-Ddebug=false` does not cause the compiler preprocessor macro +`NDEBUG` to be defined. +The macro can be defined using the base option `b_ndebug`, described below. + #### Details for `warning_level` Exact flags per warning level is compiler specific, but there is an approximate @@ -198,6 +204,16 @@ When `default_both_libraries` is 'auto', passing a [[@both_libs]] dependency in [[both_libraries]] will link the static dependency with the static lib, and the shared dependency with the shared lib. +#### Details for `os2_emxomf` + +The `--os2-emxomf` argument is supported since `1.10.0`, `-Dos2_emxomf=true` +syntax is supported since `1.10.0`. + +Setting the `os2_emxomf` option to `true` forces to use emxomf toolchains in +order to generate OMF files instead of aout toolchains. + +`os2_emxomf` is `false` by default. + ## Base options These are set in the same way as universal options, either by @@ -298,7 +314,10 @@ or compiler being used: | cpp_rtti | true | true, false | Whether to enable RTTI (runtime type identification) | | cpp_thread_count | 4 | integer value ≥ 0 | Number of threads to use with emcc when using threads | | cpp_winlibs | see below | free-form comma-separated list | Standard Windows libs to link against | +| cpp_importstd | false | true or false | Whether to use `import std` | | fortran_std | none | [none, legacy, f95, f2003, f2008, f2018] | Fortran language standard to use | +| rust_dynamic_std | false | true, false | Whether to link dynamically to the Rust standard library *(Added in 1.9.0)* | +| rust_nightly | auto | enabled, disabled, auto | Nightly Rust compiler (enabled=required, disabled=don't use nightly feature, auto=use nightly feature if available) *(Added in 1.10.0)* | | cuda_ccbindir | | filesystem path | CUDA non-default toolchain directory to use (-ccbin) *(Added in 0.57.1)* | The default values of `c_winlibs` and `cpp_winlibs` are in @@ -366,11 +385,10 @@ allowing differences in behavior to crop out. ## Specifying options per subproject -Since *0.54.0* `default_library` and `werror` built-in options can be -defined per subproject. This is useful, for example, when building -shared libraries in the main project and statically linking a subproject, -or when the main project must build with no warnings but some subprojects -cannot. +Several built-in options and all compiler options can be defined per subproject. +This is useful, for example, when building shared libraries in the main project +and statically linking a subproject, or when the main project must build +with no warnings but some subprojects cannot. Most of the time, this would be used either in the parent project by setting subproject's default_options (e.g. `subproject('foo', @@ -378,12 +396,35 @@ default_options: 'default_library=static')`), or by the user through the command line: `-Dfoo:default_library=static`. The value is overridden in this order: -- Value from parent project -- Value from subproject's default_options if set -- Value from subproject() default_options if set -- Value from command line if set - -Since *0.56.0* `warning_level` can also be defined per subproject. +- `opt=value` from parent project's `default_options` +- `opt=value` from subproject's `default_options` +- `opt=value` from machine file +- `opt=value` from command line +- `subp:opt=value` from parent project's default options +- `opt=value` from `subproject()` `default_options` +- `subp:opt=value` from machine file +- `subp:opt=value` from command line + +### Old behavior + +Between *0.54.0* and *1.7.x* only a few options could be defined per subproject: +* `default_library` and `werror` since *0.54.0*; +* `warning_level` since *0.56.0*; +* compiler options since *0.63.0* + +The value was overridden in this order: + +- `opt=value` from parent project's `default_options` +- `opt=value` from machine file +- `opt=value` from command line +- `opt=value` from subproject's `default_options` +- `subp:opt=value` from parent project's default options +- `opt=value` from `subproject()` `default_options` +- `subp:opt=value` from machine file +- `subp:opt=value` from command line + +In other word, the subproject's `default_options` had a *higher* priority +than `opt=value` from machine file or command line. ## Module options @@ -422,6 +463,7 @@ install prefix. For example: if the install prefix is `/usr` and the | platlibdir | | Directory path | Directory for site-specific, platform-specific files (Since 0.60.0) | | purelibdir | | Directory path | Directory for site-specific, non-platform-specific files (Since 0.60.0) | | allow_limited_api | true | true, false | Disables project-wide use of the Python Limited API (Since 1.3.0) | +| build_config | | File path | Specifies the Python build configuration file (PEP 739) (Since 1.10.0) | *Since 0.60.0* The `python.platlibdir` and `python.purelibdir` options are used by the python module methods `python.install_sources()` and diff --git a/docs/markdown/CMake-module.md b/docs/markdown/CMake-module.md index f8275c9..982fa35 100644 --- a/docs/markdown/CMake-module.md +++ b/docs/markdown/CMake-module.md @@ -138,8 +138,8 @@ and supports the following methods: `include_type` kwarg *(new in 0.56.0)* controls the include type of the returned dependency object similar to the same kwarg in the [[dependency]] function. - - `include_directories(target)` returns a Meson [[@inc]] - object for the specified target. Using this method is not necessary + - `include_directories(target)` returns an array of Meson [[@inc]] + objects for the specified target. Using this method is not necessary if the dependency object is used. - `target(target)` returns the raw build target. - `target_type(target)` returns the type of the target as a string diff --git a/docs/markdown/Codegen-module.md b/docs/markdown/Codegen-module.md new file mode 100644 index 0000000..0f6d524 --- /dev/null +++ b/docs/markdown/Codegen-module.md @@ -0,0 +1,144 @@ +--- +short-description: Common Code Generators Module +authors: + - name: Dylan Baker + email: dylan@pnwbakers.com + years: [2024, 2025] +... + +# Codegen Module + +*(New in 1.10.0)* + +This module provides wrappers around common code generators, such as flex/lex and yacc/bison. This purpose of this is to make it easier and more pleasant to use *common* code generators in a mesonic way. + +## Functions + +### lex() + +```meson +lex_gen = codegen.lex(implementations : ['flex', 'reflex'], flex_version : ['>= 2.6', '< 3'], reflex_version : '!= 1.4.2') +``` + +This function provides fine grained control over what implementation(s) and version(s) of lex are acceptable for a given project (These are set per-subproject). It returns a [new object](#lexgenerator), which can be used to generate code. + +It accepts the following keyword arguments: + + - `implementations`: a string array of acceptable implementations to use. May include: `lex`, `flex`, `reflex`, or `win_flex`. + - `lex_version`: a string array of version constraints to apply to the `lex` binary + - `flex_version`: a string array of version constraints to apply to the `flex` binary + - `reflex_version`: a string array of version constraints to apply to the `relex` binary + - `win_flex_version`: a string array of version constraints to apply to the `win_flex` binary + - `required`: A boolean or feature option + - `disabler`: Return a disabler if not found + - `native`: Is this generator for the host or build machine? + +### yacc() + +```meson +yacc = codegen.yacc(implementations : ['bison', 'win_bison']) +``` + +This function provides fine grained controls over which implementation(s) and version(s) of the parser generator to use. + +Accepts the following keyword arguments: + + - `implementations`: a string array of acceptable implementations to use. May include: `yacc`, `byacc`, `bison`, or `win_bison`. + - `yacc_version`: a string array of version constraints to apply to the `yacc` binary + - `byacc_version`: a string array of version constraints to apply to the `byacc` binary + - `bison_version`: a string array of version constraints to apply to the `bison` binary + - `win_bison_version`: a string array of version constraints to apply to the `win_bison` binary + - `required`: A boolean or feature option + - `disabler`: Return a disabler if not found + - `native`: Is this generator for the host or build machine? + +## Returned Objects + +### LexGenerator + +#### lex.implementation + +```meson +lex = codegen.lex() +impl = lex.implementation() +``` + +Returns the string name of the lex implementation chosen. May be one of: + +- lex +- flex +- reflex +- win_flex + +#### lex.generate + +```meson +lex = codegen.lex() +lex.generate('lexer.l') +``` + +This function wraps flex, lex, reflex (but not RE/flex), and win_flex (on Windows). When using win_flex it will automatically add the `--wincompat` argument. + +This requires an input file, which may be a string, File, or generated source. It additionally takes the following options keyword arguments: + +- `args`: An array of extra arguments to pass the lexer +- `plainname`: If set to true then `@PLAINNAME@` will be used as the source base, otherwise `@BASENAME@`. +- `source`: the name of the source output. If this is unset Meson will use `{base}.{ext}` with an extension of `cpp` if the input has an extension of `.ll`, or `c` otherwise, with base being determined by the `plainname` argument. +- `header`: The optional output name for a header file. If this is unset no header is added +- `table`: The optional output name for a table file. If this is unset no table will be generated + +The outputs will be in the form `source [header] [table]`, which means those can be accessed by indexing the output of the `lex` call: + +```meson +lex = codegen.lex() +l1 = lex.generate('lexer.l', header : '@BASENAME@.h', table : '@BASENAME@.tab.h') +headers = [l1[1], l1[2]] # [header, table] + +l2 = lex.generate('lexer.l', table : '@BASENAME@.tab.h') +table = l2[1] +``` + +### YaccGenerator + +#### yacc.implementation + +```meson +yacc = codegen.yacc() +impl = yacc.implementation() +``` + +Returns the string name of the yacc implementation chosen. May be one of: + +- yacc +- bison +- byacc +- win_bison + +#### yacc.generate + +```meson +yacc = codegen.yacc() +yacc.generate('parser.y') +``` + +This function wraps bison, yacc, byacc, and win_bison (on Windows), and attempts to abstract away the differences between them + +This requires an input file, which may be a string, File, or generated source. It additionally takes the following options keyword arguments: + +- `version`: Version constraints on the lexer +- `args`: An array of extra arguments to pass the lexer +- `plainname`: If set to true then `@PLAINNAME@` will be used as the source base, otherwise `@BASENAME@`. +- `source`: the name of the source output. If this is unset Meson will use `{base}.{ext}` with an extension of `cpp` if the input has an extension of `.yy` or `c` otherwise, with base being determined by the `plainname` argument. +- `header`: the name of the header output. If this is unset Meson will use `{base}.{ext}` with an extension of `hpp` if the input has an extension of `.yy` or `h` otherwise, with base being determined by the `plainname` argument. +- `locations`: The name of the locations file, if one is generated. Due to the way yacc works this must be duplicated in the file and in the command. + +The outputs will be in the form `source header [locations]`, which means those can be accessed by indexing the output of the `yacc` call: + +```meson +yacc = codegen.yacc() +p1 = yacc.generate('parser.y', header : '@BASENAME@.h', locations : 'locations.h') +headers = [p1[1], p1[2]] # [header, locations] + +p2 = yacc.generate('parser.yy', locations : 'locations.hpp') +locations = p2[1] +``` diff --git a/docs/markdown/Commands.md b/docs/markdown/Commands.md index 247f2d7..339dd93 100644 --- a/docs/markdown/Commands.md +++ b/docs/markdown/Commands.md @@ -493,6 +493,12 @@ or `--check-only` option). input instead of reading it from a file. This cannot be used with `--recursive` or `--inline` arguments. +*Since 1.9.0* Using `-` as source file with `--editor-config` now requires +`--source-file-path` argument to ensure consistent results. + +*Since 1.10.0* When `--check-diff` is specified, instead of silently exiting +with an error code, `meson format` will print a diff of the formatting changes. + #### Differences with `muon fmt` diff --git a/docs/markdown/Creating-releases.md b/docs/markdown/Creating-releases.md index 44c127e..7b97aec 100644 --- a/docs/markdown/Creating-releases.md +++ b/docs/markdown/Creating-releases.md @@ -104,7 +104,7 @@ following example. `meson.build`: ```meson project('tig', 'c', - version : run_command('version.sh', 'get-vcs').stdout.strip()) + version : run_command('version.sh', 'get-vcs').stdout().strip()) meson.add_dist_script('version.sh', 'set-dist', meson.project_version()) ``` diff --git a/docs/markdown/Cross-compilation.md b/docs/markdown/Cross-compilation.md index 64196f3..65daa22 100644 --- a/docs/markdown/Cross-compilation.md +++ b/docs/markdown/Cross-compilation.md @@ -139,6 +139,22 @@ of a wrapper, these lines are all you need to write. Meson will automatically use the given wrapper when it needs to run host binaries. This happens e.g. when running the project's test suite. +Note that `exe_wrapper` in the cross file is handled separately +from the `exe_wrapper` argument in +[`add_test_setup`](Reference-manual_functions.md#add_test_setup_exe_wrapper) +and [`meson test --wrapper`](Unit-tests.md#other-test-options) +command line argument. Meson must have `exe_wrapper` specified in the +cross file or else it will skip tests that attempt to run cross +compiled binaries. Only the cross file `exe_wrapper` value will be +stored in the `MESON_EXE_WRAPPER` environment variable. If another +wrapper is given in the test setup with `exe_wrapper` or as a +`meson test --wrapper` command line argument, then meson will prepend +the additional wrapper before the cross file wrapper like the +following command: +``` +[prepend_wrapper] <cross_file_wrapper> <exe_binary> <args...> +``` + ### Properties In addition to the properties allowed in [all machine @@ -173,12 +189,12 @@ remember to specify the args as an array and not as a single string *Since 0.52.0* The `sys_root` property may point to the root of the host system path (the system that will run the compiled binaries). -This is used internally by Meson to set the PKG_CONFIG_SYSROOT_DIR +This is used internally by Meson to set the `PKG_CONFIG_SYSROOT_DIR` environment variable for pkg-config. If this is unset the host system is assumed to share a root with the build system. -*Since 0.54.0* The pkg_config_libdir property may point to a list of -path used internally by Meson to set the PKG_CONFIG_LIBDIR environment +*Since 0.54.0* The `pkg_config_libdir` property may point to a list of +path used internally by Meson to set the `PKG_CONFIG_LIBDIR` environment variable for pkg-config. This prevents pkg-config from searching cross dependencies in system directories. @@ -357,7 +373,7 @@ myvar = meson.get_external_property('somekey') As of version 0.44.0 Meson supports loading cross files from system locations (except on Windows). This will be -$XDG_DATA_DIRS/meson/cross, or if XDG_DATA_DIRS is undefined, then +`$XDG_DATA_DIRS/meson/cross`, or if `XDG_DATA_DIRS` is undefined, then /usr/local/share/meson/cross and /usr/share/meson/cross will be tried in that order, for system wide cross files. User local files can be put in $XDG_DATA_HOME/meson/cross, or ~/.local/share/meson/cross if diff --git a/docs/markdown/Dependencies.md b/docs/markdown/Dependencies.md index f7bf901..4d94d58 100644 --- a/docs/markdown/Dependencies.md +++ b/docs/markdown/Dependencies.md @@ -388,6 +388,9 @@ additional toolkit libraries that need to be explicitly linked to. If the CUDA Toolkit cannot be found in the default paths on your system, you can set the path using `CUDA_PATH` explicitly. +Cuda does not honor the `prefer_static` option, and will link statically unless +the `static` keyword argument is set to `false`. + ## CUPS `method` may be `auto`, `config-tool`, `pkg-config`, `cmake` or `extraframework`. diff --git a/docs/markdown/Design-rationale.md b/docs/markdown/Design-rationale.md index 4133979..c520773 100644 --- a/docs/markdown/Design-rationale.md +++ b/docs/markdown/Design-rationale.md @@ -34,9 +34,9 @@ may not work. In some cases the executable file is a binary whereas at other times it is a wrapper shell script that invokes the real binary which resides in a hidden subdirectory. GDB invocation fails if the binary is a script but succeeds if it is not. The user has to remember -the type of each one of his executables (which is an implementation -detail of the build system) just to be able to debug them. Several -other such pain points can be found in [this blog +the type of each executable (which is an implementation detail of the +build system) just to be able to debug them. Several other such pain +points can be found in [this blog post](http://voices.canonical.com/jussi.pakkanen/2011/09/13/autotools/). Given these idiosyncrasies it is no wonder that most people don't want @@ -132,7 +132,7 @@ and so on. Sometimes you just have to compile files with only given compiler flags and no others, or install files in weird places. The system must -allow the user to do this if he really wants to. +allow the user to do this. Overview of the solution -- @@ -151,7 +151,7 @@ passing around compiler flags and linker flags. In the proposed system the user just declares that a given build target uses a given external dependency. The build system then takes care of passing all flags and settings to their proper locations. This means that the user can focus -on his own code rather than marshalling command line arguments from +on their own code rather than marshalling command line arguments from one place to another. A DSL is more work than the approach taken by SCons, which is to diff --git a/docs/markdown/Fs-module.md b/docs/markdown/Fs-module.md index 7ba4832..91c706e 100644 --- a/docs/markdown/Fs-module.md +++ b/docs/markdown/Fs-module.md @@ -206,13 +206,26 @@ fs.name('foo/bar/baz.dll.a') # baz.dll.a *since 0.54.0* Returns the last component of the path, dropping the last part of the -suffix +suffix. ```meson fs.stem('foo/bar/baz.dll') # baz fs.stem('foo/bar/baz.dll.a') # baz.dll ``` +### suffix + +*since 1.9.0* + +Returns the last dot-separated portion of the final component of the path +including the dot, if any. + +```meson +fs.suffix('foo/bar/baz.dll') # .dll +fs.suffix('foo/bar/baz.dll.a') # .a +fs.suffix('foo/bar') # (empty) +``` + ### read - `read(path, encoding: 'utf-8')` *(since 0.57.0)*: return a [string](Syntax.md#strings) with the contents of the given `path`. diff --git a/docs/markdown/Getting-meson_zh.md b/docs/markdown/Getting-meson_zh.md index 4a4cb34..da0bdd5 100644 --- a/docs/markdown/Getting-meson_zh.md +++ b/docs/markdown/Getting-meson_zh.md @@ -1,6 +1,6 @@ # 获取Meson -Meson基于Python3运行,要求Python版本3.5以上。 如果你的操作系统提供包管理器, 你应该用包管理器安装meson;如果没有包管理器,你应该在[Python主页]下载合适的Python3。相关请参阅[特殊平台的安装特例](#特殊平台的安装特例). +Meson基于Python3运行,要求Python版本3.7以上。 如果你的操作系统提供包管理器, 你应该用包管理器安装python;如果没有包管理器,你应该在[Python主页]下载合适的Python3。相关请参阅[特殊平台的安装特例](#特殊平台的安装特例). ## 下载Meson diff --git a/docs/markdown/Gnome-module.md b/docs/markdown/Gnome-module.md index e8953ef..84bcc61 100644 --- a/docs/markdown/Gnome-module.md +++ b/docs/markdown/Gnome-module.md @@ -280,6 +280,8 @@ one XML file. * `object_manager`: *(Added 0.40.0)* if true generates object manager code * `annotations`: *(Added 0.43.0)* list of lists of 3 strings for the annotation for `'ELEMENT', 'KEY', 'VALUE'` * `docbook`: *(Added 0.43.0)* prefix to generate `'PREFIX'-NAME.xml` docbooks +* `rst`: *(Added 1.9.0)* prefix to generate `'PREFIX'-NAME.rst` reStructuredTexts +* `markdown`: *(Added 1.9.0)* prefix to generate `'PREFIX'-NAME.md` markdowns * `build_by_default`: causes, when set to true, to have this target be built by default, that is, when invoking plain `meson compile`, the default value is true for all built target types @@ -289,8 +291,9 @@ one XML file. Starting *0.46.0*, this function returns a list of at least two custom targets (in order): one for the source code and one for the header. -The list will contain a third custom target for the generated docbook -files if that keyword argument is passed. +The list can then contain other custom targets for the generated documentation +files depending if the keyword argument is passed (in order): the docbook +target, the reStructuredText target and the markdown target. Earlier versions return a single custom target representing all the outputs. Generally, you should just add this list of targets to a top diff --git a/docs/markdown/Installing.md b/docs/markdown/Installing.md index 0a7ca9f..5c34eba 100644 --- a/docs/markdown/Installing.md +++ b/docs/markdown/Installing.md @@ -170,6 +170,7 @@ time, please help extending the list of well known categories. * `pkgconfig.generate()`, * `gnome.generate_gir()` - `.gir` file, * `gnome.generate_vapi()` - `.vapi` file (*Since 0.64.0*), + * `gnome.generate_vapi()` - `.deps` file (*Since 1.9.1*), * Files installed into `libdir` and with `.a` or `.pc` extension, * File installed into `includedir`, * Generated header files installed with `gnome.compile_resources()`, diff --git a/docs/markdown/Overview.md b/docs/markdown/Overview.md index 7bee937..66758fd 100644 --- a/docs/markdown/Overview.md +++ b/docs/markdown/Overview.md @@ -6,11 +6,11 @@ short-description: Overview of the Meson build system Meson is a build system that is designed to be as user-friendly as possible without sacrificing performance. The main tool for this is a -custom language that the user uses to describe the structure of his -build. The main design goals of this language has been simplicity, -clarity and conciseness. Much inspiration was drawn from the Python -programming language, which is considered very readable, even to -people who have not programmed in Python before. +custom language used to describe the structure of the build. The main +design goals of this language have been simplicity, clarity and +conciseness. Much inspiration was drawn from the Python programming +language, which is considered very readable, even to people who have +not programmed in Python before. Another main idea has been to provide first class support for modern programming tools and best practices. These include features as varied diff --git a/docs/markdown/Pkgconfig-module.md b/docs/markdown/Pkgconfig-module.md index 80882cb..eb41026 100644 --- a/docs/markdown/Pkgconfig-module.md +++ b/docs/markdown/Pkgconfig-module.md @@ -44,9 +44,12 @@ keyword arguments. `${PREFIX}/include/foobar-1`, the correct value for this argument would be `foobar-1` - `requires` list of strings, pkgconfig-dependencies or libraries that - `pkgconfig.generate()` was used on to put in the `Requires` field + `pkgconfig.generate()` was used on to put in the `Requires` field. + *Since 1.9.0* internal dependencies are supported if `pkgconfig.generate()` + was used on the underlying library. - `requires_private` the same as `requires` but for the `Requires.private` field - `url` a string with a url for the library +- `license` (*Since 1.9.0*) a string with a SPDX license to add to the generated file. - `variables` a list of strings with custom variables to add to the generated file. The strings must be in the form `name=value` and may reference other pkgconfig variables, @@ -90,6 +93,9 @@ application. That will cause pkg-config to prefer those builddir. This is an experimental feature provided on a best-effort basis, it might not work in all use-cases. +*Since 1.9.0* you can specify a license identifier. To use the current project +licence, simply use `license: meson.project_license()` as argument to `generate()`. + ### Implicit dependencies The exact rules followed to find dependencies that are implicitly diff --git a/docs/markdown/Qt6-module.md b/docs/markdown/Qt6-module.md index ebe42a5..972a6a9 100644 --- a/docs/markdown/Qt6-module.md +++ b/docs/markdown/Qt6-module.md @@ -134,7 +134,7 @@ lrelease, it takes no positional arguments, and the following keyword arguments: - `qresource` string: rcc source file to extract ts_files from; cannot be used with ts_files kwarg. - `rcc_extra_arguments` string[]: any additional arguments to `rcc` (optional), - when used with `qresource. + when used with `qresource`. Returns either: a list of custom targets for the compiled translations, or, if using a `qresource` file, a single custom target diff --git a/docs/markdown/Reference-tables.md b/docs/markdown/Reference-tables.md index a5d2785..4a3bf28 100644 --- a/docs/markdown/Reference-tables.md +++ b/docs/markdown/Reference-tables.md @@ -42,6 +42,7 @@ These are return values of the `get_id` (Compiler family) and | ti | Texas Instruments C/C++ Compiler | | | valac | Vala compiler | | | xc16 | Microchip XC16 C compiler | | +| xc32-gcc | Microchip XC32 C/C++ compiler | gcc | | cython | The Cython compiler | | | nasm | The NASM compiler (Since 0.64.0) | | | yasm | The YASM compiler (Since 0.64.0) | | @@ -58,6 +59,7 @@ These are return values of the `get_linker_id` method in a compiler object. | Value | Linker family | | ----- | --------------- | | ld.bfd | The GNU linker | +| ld.eld | Qualcomm's embedded linker | | ld.gold | The GNU gold linker | | ld.lld | The LLVM linker, with the GNU interface | | ld.mold | The fast MOLD linker | @@ -72,6 +74,7 @@ These are return values of the `get_linker_id` method in a compiler object. | optlink | optlink (used with DMD) | | rlink | The Renesas linker, used with CCrx only | | xc16-ar | The Microchip linker, used with XC16 only | +| ld.xc32 | The Microchip linker, used with XC32 only | | ar2000 | The Texas Instruments linker, used with C2000 only | | ti-ar | The Texas Instruments linker | | ar6000 | The Texas Instruments linker, used with C6000 only | @@ -125,6 +128,7 @@ set in the cross file. | msp430 | 16 bit MSP430 processor | | parisc | HP PA-RISC processor | | pic24 | 16 bit Microchip PIC24 | +| pic32 | 32 bit Microchip PIC32 | | ppc | 32 bit PPC processors | | ppc64 | 64 bit PPC processors | | riscv32 | 32 bit RISC-V Open ISA | @@ -171,6 +175,7 @@ These are provided by the `.system()` method call. | openbsd | | | windows | Native Windows (not Cygwin or MSYS2) | | sunos | illumos and Solaris | +| os/2 | OS/2 | Any string not listed above is not guaranteed to remain stable in future releases. @@ -292,6 +297,7 @@ which are supported by GCC, Clang, and other compilers. | const | | constructor | | constructor_priority | +| counted_by⁸ | | deprecated | | destructor | | error | @@ -347,6 +353,8 @@ which are supported by GCC, Clang, and other compilers. ⁷ *New in 1.5.0* +⁸ *New in 1.10.0* + ### MSVC __declspec These values are supported using the MSVC style `__declspec` annotation, diff --git a/docs/markdown/Release-notes-for-0.38.0.md b/docs/markdown/Release-notes-for-0.38.0.md index 5aaca0a..7be99ab 100644 --- a/docs/markdown/Release-notes-for-0.38.0.md +++ b/docs/markdown/Release-notes-for-0.38.0.md @@ -106,7 +106,7 @@ the check is now ~40% faster. ## Array indexing now supports fallback values The second argument to the array -[[list.get]] function is now returned +[[array.get]] function is now returned if the specified index could not be found ```meson diff --git a/docs/markdown/Release-notes-for-1.10.0.md b/docs/markdown/Release-notes-for-1.10.0.md new file mode 100644 index 0000000..aa4d133 --- /dev/null +++ b/docs/markdown/Release-notes-for-1.10.0.md @@ -0,0 +1,289 @@ +--- +title: Release 1.10.0 +short-description: Release notes for 1.10.0 +... + +# New features + +Meson 1.10.0 was released on 08 December 2025 +## Support for the `counted_by` attribute + +`compiler.has_function_attribute()` now supports for the new `counted_by` +attribute. + +## Added a `values()` method for dictionaries + +Mesons built-in [[@dict]] type now supports the [[dict.values]] method +to retrieve the dictionary values as an array, analogous to the +[[dict.keys]] method. + +```meson +dict = { 'b': 'world', 'a': 'hello' } + +[[#dict.keys]] # Returns ['a', 'b'] +[[#dict.values]] # Returns ['hello', 'world'] +``` + +## Add cmd_array method to ExternalProgram + +Added a new `cmd_array()` method to the `ExternalProgram` object that returns +an array containing the command(s) for the program. This is particularly useful +in cases like pyInstaller where the Python command is `meson.exe runpython`, +and the full path should not be used but rather the command array. + +The method returns a list of strings representing the complete command needed +to execute the external program, which may differ from just the full path +returned by `full_path()` in cases where wrapper commands or interpreters are +involved. + +## Microchip XC32 compiler support + +The Microchip XC32 compiler is now supported. + +## Added OS/2 support + +Meson now supports OS/2 system. Especially, `shortname` kwarg and +`os2_emxomf` builtin option are introduced. + +`shortname` is used to specify a short DLL name fitting to a 8.3 rule. + +```meson +lib = library('foo_library', + ... + shortname: 'foo', + ... +) +``` + +This will generate `foo.dll` not `foo_library.dll` on OS/2. If +`shortname` is not used, `foo_libr.dll` which is truncated up to 8 +characters is generated. + +`os2_emxomf` is used to generate OMF files with OMF tool-chains. + +``` +meson setup --os2-emxomf builddir +``` + +This will generate OMF object files and `.lib` library files. If +`--os2-emxomf` is not used, AOUT object files and `.a` library files are +generated. + +## Android cross file generator + +The `env2mfile` command now supports a `--android` argument. When +specified, it tells the cross file generator to create cross files for +all Android toolchains located on the current machines. + +This typically creates many files, so the `-o` argument specifies the +output directory. A typical use case goes like this: + +``` +meson env2mfile --android -o androidcross +meson setup --cross-file \ + androidcross/android-29.0.14033849-android35-aarch64-cross.txt \ + builddir +``` + +## Array `.slice()` method + +Arrays now have a `.slice()` method which allows for subsetting of arrays. + +## `-Db_vscrt` on clang + +`-Db_vscrt` will now link the appropriate runtime library, and set +the appropriate preprocessor symbols, also when the compiler is clang. +This is only supported when using `link.exe` or `lld-link.exe` as the +linker. + +## Added `build_subdir` arg to various targets + +`custom_target()`, `build_target()` and `configure_file()` now support +the `build_subdir` argument. This directs Meson to place the build +result within the specified sub-directory path of the build directory. + +```meson +configure_file(input : files('config.h.in'), + output : 'config.h', + build_subdir : 'config-subdir', + install_dir : 'share/appdir', + configuration : conf) +``` + +This places the build result, `config.h`, in a sub-directory named +`config-subdir`, creating it if necessary. To prevent collisions +within the build directory, `build_subdir` is not allowed to match a +file or directory in the source directory nor contain '..' to refer to +the parent of the build directory. `build_subdir` does not affect the +install directory path at all; `config.h` will be installed as +`share/appdir/config.h`. `build_subdir` may contain multiple levels of +directory names. + +This allows construction of files within the build system that have +any required trailing path name components as well as building +multiple files with the same basename from the same source directory. + +## Support for Cargo workspaces + +When parsing `Cargo.toml` files, Meson now recognizes workspaces +and will process all the required members and any requested optional +members of the workspace. + +For the time being it is recommended to regroup all Cargo dependencies inside a +single workspace invoked from the main Meson project. When invoking multiple +different Cargo subprojects from Meson, feature resolution of common +dependencies might be wrong. + +## Experimental Codegen module + +A new module wrapping some common code generators has been added. Currently it supports lex/flex and yacc/bison. + +## Methods from compiler object now accept strings for include_directories + +The various [[@compiler]] methods with a `include_directories` keyword argument +now accept strings or array of strings, in addition to [[@inc]] objects +generated from [[include_directories]] function, as it was already the case for +[[build_target]] family of functions. + +## `meson format` has a new `--check-diff` option + +When using `meson format --check-only` to verify formatting in CI, it would +previously silently exit with an error code if the code was not formatted +correctly. + +A new `--check-diff` option has been added which will instead print a diff of +the required changes and then exit with an error code. + +## `-Db_thinlto_cache` now supported for GCC + +`-Db_thinlto_cache` is now supported for GCC 15's incremental LTO, as is +`-Db_thinlto_cache_dir`. + +## Using `meson.get_compiler()` to get a language from another project is marked broken + +Meson currently will return a compiler instance from the `meson.get_compiler()` +call, if that language has been initialized in any project. This can result in +situations where a project can only work as a subproject, or if a dependency is +provided by a subproject rather than by a pre-built dependency. + +## Experimental C++ import std support + +**Note**: this feature is experimental and not guaranteed to be + backwards compatible or even exist at all in future Meson releases. + +Meson now supports `import std`, a new, modular way of using the C++ +standard library. This support is enabled with the new `cpp_importstd` +option. It defaults to `false`, but you can set it to `true` either +globally or per-target using `override_options` in the usual way. + +The implementation has many limitations. The biggest one is that the +same module file is used on _all_ targets. That means you can not mix +multiple different C++ standards versions as the compiled module file +can only be used with the same compiler options as were used to build +it. This feature only works with the Ninja backend. + +As `import std` is a major new feature in compilers, expect to +encounter toolchain issues when using it. For an example [see +here](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=122614). + +## Common `Cargo.lock` for all Cargo subprojects + +Whenever Meson finds a `Cargo.lock` file in the toplevel directory +of the project, it will use it to resolve the versions of Cargo +subprojects in preference to per-subproject `Cargo.lock` files. +Per-subproject lock files are only used if the invoking project +did not have a `Cargo.lock` file itself. + +If you wish to experiment with Cargo subprojects, it is recommended +to use `cargo` to set up `Cargo.lock` and `Cargo.toml` files, +encompassing all Rust targets, in the toplevel source directory. +Cargo subprojects remain unstable and subject to change. + +## Add a configure log in meson-logs + +Add a second log file `meson-setup.txt` which contains the configure logs +displayed on stdout during the meson `setup` stage. +It allows user to navigate through the setup logs without searching in the terminal +or the extensive information of `meson-log.txt`. + +## Added new `namingscheme` option + +Traditionally Meson has named output targets so that they don't clash +with each other. This has meant, among other things, that on Windows +Meson uses a nonstandard `.a` suffix for static libraries because both +static libraries and import libraries have the suffix `.lib`. + +There is now an option `namingscheme` that can be set to +`platform`. This new platform native naming scheme that replicates +what Rust does. That is, shared libraries on Windows get a suffix +`.dll`, static libraries get `.lib` and import libraries have the name +`.dll.lib`. + +We expect to change the default value of this option to `platform` in +a future major version. Until that happens we reserve the right to +alter how `platform` actually names its output files. + +## Rewriter improvements + +The [rewriter](Rewriter.md) added support for writing the `project()` +`license_files` argument and for reading dict-valued kwargs. + +It also removed the unused but mandatory `value` arguments to the +`default-options delete` and `kwargs delete` CLI subcommands. To allow +scripts to continue supporting previous releases, these arguments are +still accepted (with a warning) if they're all equal to the empty string. + +## Passing `-C default-linker-libraries` to rustc + +When calling rustc, Meson now passes the `-C default-linker-libraries` option. +While rustc passes the necessary libraries for Rust programs, they are rarely +enough for mixed Rust/C programs. + +## `rustc` will receive `-C embed-bitcode=no` and `-C lto` command line options + +With this release, Meson passes command line arguments to `rustc` to +enable LTO when the `b_lto` built-in option is set to true. As an +optimization, Meson also passes `-C embed-bitcode=no` when LTO is +disabled; note that this is incompatible with passing `-C lto` via +`rust_args`. + +## New method to handle GNU and Windows symbol visibility for C/C++/ObjC/ObjC++ + +Defining public API of a cross platform C/C++/ObjC/ObjC++ library is often +painful and requires copying macro snippets into every projects, typically using +`__declspec(dllexport)`, `__declspec(dllimport)` or +`__attribute__((visibility("default")))`. + +Meson can now generate a header file that defines exactly what's needed for +all supported platforms: +[`snippets.symbol_visibility_header()`](Snippets-module.md#symbol_visibility_header). + +## Vala BuildTarget dependency enhancements + +A BuildTarget that has Vala sources can now get a File dependency for its +generated header, vapi, and gir files. + +```meson +lib = library('foo', 'foo.vala') +lib_h = lib.vala_header() +lib_s = static_lib('static', 'static.c', lib_h) + +lib_vapi = lib.vala_vapi() + +custom_target( + 'foo-typelib', + command : ['g-ir-compiler', '--output', '@OUTPUT@', '@INPUT@'], + input : lib.vala_gir(), + output : 'Foo-1.0.typelib' +) +``` + +`static.c` will not start compilation until `lib.h` is generated. + +## `i18n.xgettext` now accepts CustomTarget and CustomTargetIndex as sources + +Previously, [[@custom_tgt]] were accepted but silently ignored, and +[[@custom_idx]] were not accepted. + +Now, they both can be used, and the generated outputs will be scanned to extract +translation strings. diff --git a/docs/markdown/Release-notes-for-1.9.0.md b/docs/markdown/Release-notes-for-1.9.0.md new file mode 100644 index 0000000..aad105f --- /dev/null +++ b/docs/markdown/Release-notes-for-1.9.0.md @@ -0,0 +1,138 @@ +--- +title: Release 1.9.0 +short-description: Release notes for 1.9.0 +... + +# New features + +Meson 1.9.0 was released on 24 August 2025 +## Array `.flatten()` method + +Arrays now have a `.flatten()` method, which turns nested arrays into a single +flat array. This provides the same effect that Meson often does to arrays +internally, such as when passed to most function arguments. + +## `clang-tidy`'s auto-generated targets correctly select source files + +In previous versions, the target would run `clang-tidy` on _every_ C-like source files (.c, .h, .cpp, .hpp). It did not work correctly because some files, especially headers, are not intended to be consumed as is. + +It will now run only on source files participating in targets. + +## Added Qualcomm's embedded linker, eld + +Qualcomm recently open-sourced their embedded linker. +https://github.com/qualcomm/eld + +Meson users can now use this linker. + +## Added suffix function to the FS module + +The basename and stem were already available. For completeness, expose also the +suffix. + +## Support response files for custom targets + +When using the Ninja backend, Meson can now pass arguments to supported tools +through response files. + +In this release it's enabled only for the Gnome module, fixing calling +`gnome.mkenums()` with a large set of files on Windows (requires +Glib 2.59 or higher). + +## meson format now has a --source-file-path argument when reading from stdin + +This argument is mandatory to mix stdin reading with the use of editor config. +It allows to know where to look for the .editorconfig, and to use the right +section of .editorconfig based on the parsed file name. + +## Added license keyword to pkgconfig.generate + +When specified, it will add a `License:` attribute to the generated .pc file. + +## pkgconfig.generate supports internal dependencies in `requires` + +Internal dependencies can now be specified to `requires` if +pkgconfig.generate was called on the underlying library. + +## New experimental option `rust_dynamic_std` + +A new option `rust_dynamic_std` can be used to link Rust programs so +that they use a dynamic library for the Rust `libstd`. + +Right now, `staticlib` crates cannot be produced if `rust_dynamic_std` is +true, but this may change in the future. + +## Rust and non-Rust sources in the same target + +Meson now supports creating a single target with Rust and non Rust +sources mixed together. In this case, if specified, `link_language` +must be set to `rust`. + +## Explicitly setting Swift module name is now supported + +It is now possible to set the Swift module name for a target via the +*swift_module_name* target kwarg, overriding the default inferred from the +target name. + +```meson +lib = library('foo', 'foo.swift', swift_module_name: 'Foo') +``` + +## Top-level statement handling in Swift libraries + +The Swift compiler normally treats modules with a single source +file (and files named main.swift) to run top-level code at program +start. This emits a main symbol which is usually undesirable in a +library target. Meson now automatically passes the *-parse-as-library* +flag to the Swift compiler in case of single-file library targets to +disable this behavior unless the source file is called main.swift. + +## Swift compiler receives select C family compiler options + +Meson now passes select few C family (C/C++/Obj-C/Obj-C++) compiler +options to the Swift compiler, notably *-std=*, in order to improve +the compatibility of C code as interpreted by the C compiler and the +Swift compiler. + +NB: This does not include any of the options set in the target's +c_flags. + +## Swift/C++ interoperability is now supported + +It is now possible to create Swift executables that can link to C++ or +Objective-C++ libraries. To enable this feature, set the target kwarg +_swift\_interoperability\_mode_ to 'cpp'. + +To import C++ code, specify a bridging header in the Swift target's +sources, or use another way such as adding a directory containing a +Clang module map to its include path. + +Note: Enabling C++ interoperability in a library target is a breaking +change. Swift libraries that enable it need their consumers to enable +it as well, as per [the Swift documentation][1]. + +Swift 5.9 is required to use this feature. Xcode 15 is required if the +Xcode backend is used. + +```meson +lib = static_library('mylib', 'mylib.cpp') +exe = executable('prog', 'main.swift', 'mylib.h', link_with: lib, swift_interoperability_mode: 'cpp') +``` + +[1]: https://www.swift.org/documentation/cxx-interop/project-build-setup/#vending-packages-that-enable-c-interoperability + +## Support for MASM in Visual Studio backends + +Previously, assembling `.masm` files with Microsoft's Macro Assembler is only +available on the Ninja backend. This now also works on Visual Studio backends. + +Note that building ARM64EC code using `ml64.exe` is currently unimplemented in +both of the backends. If you need mixing x64 and Arm64 in your project, please +file an issue on GitHub. + +## Limited support for WrapDB v1 + +WrapDB v1 has been discontinued for several years, Meson will now print a +deprecation warning if a v1 URL is still being used. Wraps can be updated to +latest version using `meson wrap update` command. + diff --git a/docs/markdown/Rewriter.md b/docs/markdown/Rewriter.md index 82f8635..546666f 100644 --- a/docs/markdown/Rewriter.md +++ b/docs/markdown/Rewriter.md @@ -94,7 +94,8 @@ It is also possible to set kwargs of specific functions with the rewriter. The general command for setting or removing kwargs is: ```bash -meson rewrite kwargs {set/delete} <function type> <function ID> <key1> <value1> <key2> <value2> ... +meson rewrite kwargs set <function type> <function ID> <key1> <value1> <key2> <value2> ... +meson rewrite kwargs delete <function type> <function ID> <key1> <key2> ... ``` For instance, setting the project version can be achieved with this command: @@ -116,14 +117,23 @@ converted to `/` by msys bash but in order to keep usage shell-agnostic, the rewrite command also allows `//` as the function ID such that it will work in both msys bash and other shells. +*Before 1.10.0*, the `delete` command expected `<key> <value>` pairs as +in `set`; the `<value>` was ignored. For backward compatibility, Meson +accepts this syntax with a warning if all `<value>`s are the empty string. + ### Setting the project default options For setting and deleting default options, use the following command: ```bash -meson rewrite default-options {set/delete} <opt1> <value1> <opt2> <value2> ... +meson rewrite default-options set <opt1> <value1> <opt2> <value2> ... +meson rewrite default-options delete <opt1> <opt2> ... ``` +*Before 1.10.0*, the `delete` command expected `<opt> <value>` pairs as +in `set`; the `<value>` was ignored. For backward compatibility, Meson +accepts this syntax with a warning if all `<value>`s are the empty string. + ## Limitations Rewriting a Meson file is not guaranteed to keep the indentation of @@ -229,6 +239,9 @@ The format for the type `target` is defined as follows: } ``` +For operation `delete`, the values of the `options` can be anything +(including `null`). + ### Default options modification format The format for the type `default_options` is defined as follows: @@ -246,7 +259,7 @@ The format for the type `default_options` is defined as follows: ``` For operation `delete`, the values of the `options` can be anything -(including `null`) +(including `null`). ## Extracting information diff --git a/docs/markdown/Rust-module.md b/docs/markdown/Rust-module.md index 35eaf39..5ce0fdc 100644 --- a/docs/markdown/Rust-module.md +++ b/docs/markdown/Rust-module.md @@ -137,7 +137,7 @@ were never turned on by Meson. ```ini [properties] -bindgen_clang_arguments = ['-target', 'x86_64-linux-gnu'] +bindgen_clang_arguments = ['--target', 'x86_64-linux-gnu'] ``` ### proc_macro() diff --git a/docs/markdown/Rust.md b/docs/markdown/Rust.md index 08580cd..67bbdec 100644 --- a/docs/markdown/Rust.md +++ b/docs/markdown/Rust.md @@ -27,14 +27,20 @@ feature is stabilized. ## Mixing Rust and non-Rust sources -Meson currently does not support creating a single target with Rust and non Rust -sources mixed together, therefore one must compile multiple libraries and link -them. +*(Since 1.9.0)* Rust supports mixed targets, but only supports using +`rustc` as the linker for such targets. If you need to use a non-Rust +linker, or support Meson < 1.9.0, see below. + +Until Meson 1.9.0, Meson did not support creating a single target with +Rust and non Rust sources mixed together. One had to compile a separate +Rust `static_library` or `shared_library`, and link it into the C build +target (e.g., a library or an executable). ```meson rust_lib = static_library( 'rust_lib', sources : 'lib.rs', + rust_abi: 'c', ... ) @@ -44,7 +50,6 @@ c_lib = static_library( link_with : rust_lib, ) ``` -This is an implementation detail of Meson, and is subject to change in the future. ## Mixing Generated and Static sources @@ -93,3 +98,11 @@ target is a proc macro or dylib, or it depends on a dylib, in which case [`-C prefer-dynamic`](https://doc.rust-lang.org/rustc/codegen-options/index.html#prefer-dynamic) will be passed to the Rust compiler, and the standard libraries will be dynamically linked. + +## Multiple targets for the same crate name + +For library targets that have `rust_abi: 'rust'`, the crate name is derived from the +target name. First, dashes, spaces and dots are replaced with underscores. Second, +*since 1.10.0* anything after the first `+` is dropped. This allows creating multiple +targets for the same crate name, for example when the same crate is built multiple +times with different features, or for both the build and the host machine. diff --git a/docs/markdown/Snippets-module.md b/docs/markdown/Snippets-module.md new file mode 100644 index 0000000..15d5e9f --- /dev/null +++ b/docs/markdown/Snippets-module.md @@ -0,0 +1,111 @@ +--- +short-description: Code snippets module +... + +# Snippets module + +*(new in 1.10.0)* + +This module provides helpers to generate commonly useful code snippets. + +## Functions + +### symbol_visibility_header() + +```meson +snippets.symbol_visibility_header(header_name, + namespace: str + api: str + compilation: str + static_compilation: str + static_only: bool +) +``` + +Generate a header file that defines macros to be used to mark all public APIs +of a library. Depending on the platform, this will typically use +`__declspec(dllexport)`, `__declspec(dllimport)` or +`__attribute__((visibility("default")))`. It is compatible with C, C++, +ObjC and ObjC++ languages. The content of the header is static regardless +of the compiler used. + +The first positional argument is the name of the header file to be generated. +It also takes the following keyword arguments: + +- `namespace`: Prefix for generated macros, defaults to the current project name. + It will be converted to upper case with all non-alphanumeric characters replaced + by an underscore `_`. It is only used for `api`, `compilation` and + `static_compilation` default values. +- `api`: Name of the macro used to mark public APIs. Defaults to `<NAMESPACE>_API`. +- `compilation`: Name of a macro defined only when compiling the library. + Defaults to `<NAMESPACE>_COMPILATION`. +- `static_compilation`: Name of a macro defined only when compiling or using + static library. Defaults to `<NAMESPACE>_STATIC_COMPILATION`. +- `static_only`: If set to true, `<NAMESPACE>_STATIC_COMPILATION` is defined + inside the generated header. In that case the header can only be used for + building a static library. By default it is `true` when `default_library=static`, + and `false` otherwise. [See below for more information](#static_library) + +Projects that define multiple shared libraries should typically have +one header per library, with a different namespace. + +The generated header file should be installed using `install_headers()`. + +`meson.build`: +```meson +project('mylib', 'c') +subdir('mylib') +``` + +`mylib/meson.build`: +```meson +snippets = import('snippets') +apiheader = snippets.symbol_visibility_header('apiconfig.h') +install_headers(apiheader, 'lib.h', subdir: 'mylib') +lib = library('mylib', 'lib.c', + gnu_symbol_visibility: 'hidden', + c_args: ['-DMYLIB_COMPILATION'], +) +``` + +`mylib/lib.h` +```c +#include <mylib/apiconfig.h> +MYLIB_API int do_stuff(); +``` + +`mylib/lib.c` +```c +#include "lib.h" +int do_stuff() { + return 0; +} +``` + +#### Static library + +When building both static and shared libraries on Windows (`default_library=both`), +`-D<NAMESPACE>_STATIC_COMPILATION` must be defined only for the static library, +using `c_static_args`. This causes Meson to compile the library twice. + +```meson +if host_system == 'windows' + static_arg = ['-DMYLIB_STATIC_COMPILATION'] +else + static_arg = [] +endif +lib = library('mylib', 'lib.c', + gnu_symbol_visibility: 'hidden', + c_args: ['-DMYLIB_COMPILATION'], + c_static_args: static_arg +) +``` + +`-D<NAMESPACE>_STATIC_COMPILATION` C-flag must be defined when compiling +applications that use the library API. It typically should be defined in +`declare_dependency(..., compile_args: [])` and +`pkgconfig.generate(..., extra_cflags: [])`. + +Note that when building both shared and static libraries on Windows, +applications cannot currently rely on `pkg-config` to define this macro. +See https://github.com/mesonbuild/meson/pull/14829. diff --git a/docs/markdown/Syntax.md b/docs/markdown/Syntax.md index 05f5038..f4a2e17 100644 --- a/docs/markdown/Syntax.md +++ b/docs/markdown/Syntax.md @@ -566,7 +566,7 @@ executable('exe1', 'foo.c', 'bar.c', 'foobar.c') Because of an internal implementation detail, the following syntax is currently also supported, even though the first argument of -[[executable]] is a single [[@str]] and not a [[@list]]: +[[executable]] is a single [[@str]] and not a [[@array]]: ```meson # WARNING: This example is only valid because of an internal diff --git a/docs/markdown/Users.md b/docs/markdown/Users.md index a515b24..5b9821b 100644 --- a/docs/markdown/Users.md +++ b/docs/markdown/Users.md @@ -2,7 +2,7 @@ title: Users ... -# Notable projects using Meson +# Notable projects and organizations using Meson If you're aware of a notable project that uses Meson, please [file a pull-request](https://github.com/mesonbuild/meson/edit/master/docs/markdown/Users.md) @@ -11,181 +11,47 @@ for it. For other projects using Meson, you may be interested in this Some additional projects are listed in the [`meson` GitHub topic](https://github.com/topics/meson). - - [2048.cpp](https://github.com/plibither8/2048.cpp), a fully featured terminal version of the game "2048" written in C++ - - [aawordsearch](https://github.com/theimpossibleastronaut/aawordsearch), generate wordsearch puzzles using random words in different languages - - [Adwaita Manager](https://github.com/AdwCustomizerTeam/AdwCustomizer), change the look of Adwaita, with ease - - [Aravis](https://github.com/AravisProject/aravis), a glib/gobject based library for video acquisition using Genicam cameras - - [Akira](https://github.com/akiraux/Akira), a native Linux app for UI and UX design built in Vala and Gtk - - [AQEMU](https://github.com/tobimensch/aqemu), a Qt GUI for QEMU virtual machines, since version 0.9.3 - - [Arduino sample project](https://github.com/jpakkane/mesonarduino) - - [Asteria](https://github.com/lhmouse/asteria), another scripting language - - [Audacious](https://github.com/audacious-media-player), a lightweight and versatile audio player - - [bolt](https://gitlab.freedesktop.org/bolt/bolt), userspace daemon to enable security levels for Thunderbolt™ 3 on Linux - - [bsdutils](https://github.com/dcantrell/bsdutils), alternative to GNU coreutils using software from FreeBSD - - [Bubblewrap](https://github.com/containers/bubblewrap), unprivileged sandboxing tool - - [Budgie Desktop](https://github.com/budgie-desktop/budgie-desktop), a desktop environment built on GNOME technologies - - [Bzip2](https://gitlab.com/federicomenaquintero/bzip2), the bzip2 compressor/decompressor - - [Cage](https://github.com/Hjdskes/cage), a Wayland kiosk - - [canfigger](https://github.com/andy5995/canfigger), simple configuration file parser library - - [casync](https://github.com/systemd/casync), Content-Addressable Data Synchronization Tool - - [cglm](https://github.com/recp/cglm), a highly optimized graphics math library for C - - [cinnamon-desktop](https://github.com/linuxmint/cinnamon-desktop), the cinnamon desktop library - - [Cozy](https://github.com/geigi/cozy), a modern audio book player for Linux and macOS using GTK+ 3 - - [Criterion](https://github.com/Snaipe/Criterion), extensible cross-platform C and C++ unit testing framework - - [dav1d](https://code.videolan.org/videolan/dav1d), an AV1 decoder - - [dbus-broker](https://github.com/bus1/dbus-broker), Linux D-Bus Message Broker - - [DOSBox Staging](https://github.com/dosbox-staging/dosbox-staging), DOS/x86 emulator - - [DPDK](http://dpdk.org/browse/dpdk), Data Plane Development Kit, a set of libraries and drivers for fast packet processing - - [DXVK](https://github.com/doitsujin/dxvk), a Vulkan-based Direct3D 11 implementation for Linux using Wine +## [Xorg](https://www.x.org) + + - [Xserver](https://gitlab.freedesktop.org/xorg/xserver), the X.org display server + +## [Gnome](https://www.gnome.org) + + - [GTK](https://gitlab.gnome.org/GNOME/gtk), the multi-platform toolkit used by GNOME + - [GLib](https://gitlab.gnome.org/GNOME/glib), cross-platform C library used by GTK+ and GStreamer + +## [Enlightenment](https://www.enlightenment.org/) + - [EFL](https://www.enlightenment.org/about-efl), multiplatform set of libraries, used by the Enlightenment windows manager and other projects - - [Enlightenment](https://www.enlightenment.org/), windows manager, compositor and minimal desktop for Linux - - [elementary OS](https://github.com/elementary/), Linux desktop oriented distribution - - [Emeus](https://github.com/ebassi/emeus), constraint based layout manager for GTK+ - - [Entangle](https://entangle-photo.org/), tethered camera control and capture desktop application - - [ESP8266 Arduino sample project](https://github.com/trilader/arduino-esp8266-meson), sample project for using the ESP8266 Arduino port with Meson - - [FeedReader](https://github.com/jangernert/FeedReader), a modern desktop application designed to complement existing web-based RSS accounts - - [Flecs](https://github.com/SanderMertens/flecs), a Fast and Lightweight ECS (Entity Component System) C library - - [Foliate](https://github.com/johnfactotum/foliate), a simple and modern GTK eBook reader, built with GJS and Epub.js - - [Fractal](https://wiki.gnome.org/Apps/Fractal/), a Matrix messaging client for GNOME - - [Frida](https://github.com/frida/frida-core), a dynamic binary instrumentation toolkit - - [fwupd](https://github.com/hughsie/fwupd), a simple daemon to allow session software to update firmware - - [GameMode](https://github.com/FeralInteractive/gamemode), a daemon/lib combo for Linux that allows games to request a set of optimisations be temporarily applied to the host OS - - [Geary](https://wiki.gnome.org/Apps/Geary), an email application built around conversations, for the GNOME 3 desktop + +## [Elementary OS](https://github.com/elementary/) + +## [cinnamon-desktop](https://github.com/linuxmint/cinnamon-desktop) + + - [Nemo](https://github.com/linuxmint/nemo), the file manager for the Cinnamon desktop environment + +## [Budgie Desktop](https://github.com/budgie-desktop/budgie-desktop) + +## Other Notable projects + + - [FreeType](https://freetype.org/), widely used open source font rendering engine - [GIMP](https://gitlab.gnome.org/GNOME/gimp), an image manipulation program (master branch) - - [Git](https://git-scm.com/), ["the information manager from hell"](https://github.com/git/git/commit/e83c5163316f89bfbde7d9ab23ca2e25604af290) - - [GLib](https://gitlab.gnome.org/GNOME/glib), cross-platform C library used by GTK+ and GStreamer - - [Glorytun](https://github.com/angt/glorytun), a multipath UDP tunnel - - [GNOME Boxes](https://gitlab.gnome.org/GNOME/gnome-boxes), a GNOME hypervisor - - [GNOME Builder](https://gitlab.gnome.org/GNOME/gnome-builder), an IDE for the GNOME platform - - [GNOME MPV](https://github.com/gnome-mpv/gnome-mpv), GNOME frontend to the mpv video player - - [GNOME Recipes](https://gitlab.gnome.org/GNOME/recipes), application for cooking recipes - - [GNOME Software](https://gitlab.gnome.org/GNOME/gnome-software), an app store for GNOME - - [GNOME Twitch](https://github.com/vinszent/gnome-twitch), an app for viewing Twitch streams on GNOME desktop - - [GNOME Usage](https://gitlab.gnome.org/GNOME/gnome-usage), a GNOME application for visualizing system resources - - [GNOME Web](https://gitlab.gnome.org/GNOME/epiphany), a browser for a simple, clean, beautiful view of the web - - [GNU FriBidi](https://github.com/fribidi/fribidi), the open source implementation of the Unicode Bidirectional Algorithm - - [Graphene](https://ebassi.github.io/graphene/), a thin type library for graphics - - [Grilo](https://git.gnome.org/browse/grilo) and [Grilo plugins](https://git.gnome.org/browse/grilo-plugins), the Grilo multimedia framework - [GStreamer](https://gitlab.freedesktop.org/gstreamer/gstreamer), multimedia framework - - [GTK+](https://gitlab.gnome.org/GNOME/gtk), the multi-platform toolkit used by GNOME - - [GtkDApp](https://gitlab.com/csoriano/GtkDApp), an application template for developing Flatpak apps with Gtk+ and D - - [GVfs](https://git.gnome.org/browse/gvfs/), a userspace virtual filesystem designed to work with the I/O abstraction of GIO - - [Hardcode-Tray](https://github.com/bil-elmoussaoui/Hardcode-Tray), fixes hardcoded tray icons in Linux + - [Git](https://git-scm.com/), ["the information manager from hell"](https://github.com/git/git/commit/e83c5163316f89bfbde7d9ab23ca2e25604af290) - [HarfBuzz](https://github.com/harfbuzz/harfbuzz), a text shaping engine - - [HelenOS](http://helenos.org), a portable microkernel-based multiserver operating system - [HexChat](https://github.com/hexchat/hexchat), a cross-platform IRC client in C - - [IGT](https://gitlab.freedesktop.org/drm/igt-gpu-tools), Linux kernel graphics driver test suite - - [i3](https://i3wm.org), improved tiling window manager - - [inih](https://github.com/benhoyt/inih) (INI Not Invented Here), a small and simple .INI file parser written in C - - [Irssi](https://github.com/irssi/irssi), a terminal chat client in C - - [iSH](https://github.com/tbodt/ish), Linux shell for iOS - - [Janet](https://github.com/janet-lang/janet), a functional and imperative programming language and bytecode interpreter - - [json](https://github.com/nlohmann/json), JSON for Modern C++ - - [JsonCpp](https://github.com/open-source-parsers/jsoncpp), a C++ library for interacting with JSON - - [Json-glib](https://gitlab.gnome.org/GNOME/json-glib), GLib-based JSON manipulation library - - [Kiwix libraries](https://github.com/kiwix/kiwix-lib) - - [Knot Resolver](https://gitlab.labs.nic.cz/knot/knot-resolver), Full caching DNS resolver implementation - - [Ksh](https://github.com/att/ast), a Korn Shell - - [Lc0](https://github.com/LeelaChessZero/lc0), LeelaChessZero is a UCI-compliant chess engine designed to play chess via neural network - - [Le](https://github.com/kirushyk/le), machine learning framework - - [libcamera](https://git.linuxtv.org/libcamera.git/), a library to handle complex cameras on Linux, ChromeOS and Android - - [Libdrm](https://gitlab.freedesktop.org/mesa/drm), a library for abstracting DRM kernel interfaces - - [libdwarf](https://www.prevanders.net/dwarf.html), a multiplatform DWARF parser library - - [libeconf](https://github.com/openSUSE/libeconf), Enhanced config file parsing library, which merges config files placed in several locations into one - - [Libepoxy](https://github.com/anholt/libepoxy/), a library for handling OpenGL function pointer management - - [libfuse](https://github.com/libfuse/libfuse), the reference implementation of the Linux FUSE (Filesystem in Userspace) interface - - [Libgit2-glib](https://git.gnome.org/browse/libgit2-glib), a GLib wrapper for libgit2 - - [libglvnd](https://gitlab.freedesktop.org/glvnd/libglvnd), Vendor neutral OpenGL dispatch library for Unix-like OSes - - [Libhttpseverywhere](https://git.gnome.org/browse/libhttpseverywhere), a library to enable httpseverywhere on any desktop app - - [libmodulemd](https://github.com/fedora-modularity/libmodulemd), a GObject Introspected library for managing [Fedora Project](https://getfedora.org/) module metadata - - [Libosmscout](https://github.com/Framstag/libosmscout), a C++ library for offline map rendering, routing and location -lookup based on OpenStreetMap data - - [libratbag](https://github.com/libratbag/libratbag), provides a DBus daemon to configure input devices, mainly gaming mice - - [libspng](https://github.com/randy408/libspng), a C library for reading and writing Portable Network Graphics (PNG) -format files - - [libSRTP](https://github.com/cisco/libsrtp) (from Cisco Systems), a library for SRTP (Secure Realtime Transport Protocol) - - [libui](https://github.com/andlabs/libui), a simple and portable (but not inflexible) GUI library in C that uses the native GUI technologies of each platform it supports - - [Libva](https://github.com/intel/libva), an implementation for the VA (VIdeo Acceleration) API - - [libvips](https://github.com/libvips/libvips), a fast image processing library with low memory needs - - [Libvirt](https://libvirt.org), a toolkit to manage virtualization platforms - - [Libzim](https://github.com/openzim/libzim), the reference implementation for the ZIM file format - - [Linux PAM](https://github.com/linux-pam/linux-pam), The Pluggable Authentication Modules project for Linux - [LXC](https://github.com/lxc/lxc), Linux container runtime - - [Marker](https://github.com/fabiocolacio/Marker), a GTK-3 markdown editor - - [mcfgthread](https://github.com/lhmouse/mcfgthread), cornerstone library for C++11 threading on mingw-w64 + - [Linux PAM](https://github.com/linux-pam/linux-pam), The Pluggable Authentication Modules project for Linux - [Mesa](https://mesa3d.org/), an open source graphics driver project - - [Miniz](https://github.com/richgel999/miniz), a zlib replacement library - - [MiracleCast](https://github.com/albfan/miraclecast), connect external monitors to your system via WiFi-Display specification aka Miracast - [mpv](https://github.com/mpv-player/mpv), a free, open source, and cross-platform media player - - [mrsh](https://github.com/emersion/mrsh), a minimal POSIX shell - - [Nautilus](https://gitlab.gnome.org/GNOME/nautilus), the GNOME file manager - - [Nemo](https://github.com/linuxmint/nemo), the file manager for the Cinnamon desktop environment - - [netatalk](https://netatalk.io/), a free and open source AFP file server for Mac and Apple II - - [NetPanzer](https://github.com/netpanzer/netpanzer), a 2D online multiplayer tactical warfare game designed for fast action combat - [NumPy](https://numpy.org/), a Python package for scientific computing - - [nvme-cli](https://github.com/linux-nvme/nvme-cli), NVMe management command line interface - - [oomd](https://github.com/facebookincubator/oomd), a userspace Out-Of-Memory (OOM) killer for Linux systems - [OpenH264](https://github.com/cisco/openh264), open source H.264 codec - - [OpenHMD](https://github.com/OpenHMD/OpenHMD), a free and open source API and drivers for immersive technology, such as head mounted displays with built in head tracking - [OpenRC](https://github.com/OpenRC/openrc), an init system for Unix-like operating systems - - [OpenTitan](https://github.com/lowRISC/opentitan), an open source silicon Root of Trust (RoT) project - - [Orc](https://gitlab.freedesktop.org/gstreamer/orc), the Optimized Inner Loop Runtime Compiler - - [OTS](https://github.com/khaledhosny/ots), the OpenType Sanitizer, parses and serializes OpenType files (OTF, TTF) and WOFF and WOFF2 font files, validating and sanitizing them as it goes. Used by Chromium and Firefox - - [Outlier](https://github.com/kerolasa/outlier), a small Hello World style Meson example project - - [p11-kit](https://github.com/p11-glue/p11-kit), PKCS#11 module aggregator - [Pacman](https://gitlab.archlinux.org/pacman/pacman.git), a package manager for Arch Linux - - [Pango](https://git.gnome.org/browse/pango/), an Internationalized text layout and rendering library - - [Parzip](https://github.com/jpakkane/parzip), a multithreaded reimplementation of Zip - - [Peek](https://github.com/phw/peek), simple animated GIF screen recorder with an easy to use interface - [PicoLibc](https://github.com/keith-packard/picolibc), a standard C library for small embedded systems with limited RAM - [PipeWire](https://github.com/PipeWire/pipewire), a framework for video and audio for containerized applications - - [Pistache](https://github.com/pistacheio/pistache), a high performance REST toolkit written in C++ - - [Pithos](https://github.com/pithos/pithos), a Pandora Radio client - - [Pitivi](https://github.com/pitivi/pitivi/), a nonlinear video editor - - [Planner](https://github.com/alainm23/planner), task manager with Todoist support designed for GNU/Linux - - [Playerctl](https://github.com/acrisci/playerctl), mpris command-line controller and library for spotify, vlc, audacious, bmp, cmus, and others - - [Polari](https://gitlab.gnome.org/GNOME/polari), an IRC client - [PostgreSQL](https://www.postgresql.org/), an advanced open source relational database - - [qboot](https://github.com/bonzini/qboot), a minimal x86 firmware for booting Linux kernels - [QEMU](https://qemu.org), a processor emulator and virtualizer - - [radare2](https://github.com/radare/radare2), unix-like reverse engineering framework and commandline tools (not the default) - - [refivar](https://github.com/nvinson/refivar), A reimplementation of efivar in Rust - - [Rizin](https://rizin.re), Free and Open Source Reverse Engineering Framework - - [rmw](https://theimpossibleastronaut.com/rmw-website/), safe-remove utility for the command line - - [RxDock](https://gitlab.com/rxdock/rxdock), a protein-ligand docking software designed for high throughput virtual screening (fork of rDock) - [SciPy](https://scipy.org/), an open-source software for mathematics, science, and engineering - - [scrcpy](https://github.com/Genymobile/scrcpy), a cross platform application that provides display and control of Android devices connected on USB or over TCP/IP - - [Sequeler](https://github.com/Alecaddd/sequeler), a friendly SQL client for Linux, built with Vala and Gtk - - [Siril](https://gitlab.com/free-astro/siril), an image processing software for amateur astronomy - - [slapt-get](https://github.com/jaos/slapt-get), an APT like system for Slackware package management - - [Spot](https://github.com/xou816/spot), Rust based Spotify client for the GNOME desktop - - [SSHFS](https://github.com/libfuse/sshfs), allows you to mount a remote filesystem using SFTP - - [sway](https://github.com/swaywm/sway), i3-compatible Wayland compositor - - [Sysprof](https://git.gnome.org/browse/sysprof), a profiling tool - [systemd](https://github.com/systemd/systemd), the init system - - [szl](https://github.com/dimkr/szl), a lightweight, embeddable scripting language - - [Taisei Project](https://taisei-project.org/), an open-source Touhou Project clone and fangame - - [Terminology](https://github.com/billiob/terminology), a terminal emulator based on the Enlightenment Foundation Libraries - - [ThorVG](https://www.thorvg.org/), vector-based scenes and animations library - - [Tilix](https://github.com/gnunn1/tilix), a tiling terminal emulator for Linux using GTK+ 3 - - [Tizonia](https://github.com/tizonia/tizonia-openmax-il), a command-line cloud music player for Linux with support for Spotify, Google Play Music, YouTube, SoundCloud, TuneIn, Plex servers and Chromecast devices - - [Fossil Logic](https://github.com/fossillogic), Fossil Logic is a cutting-edge software development company specializing in C/C++, Python, programming, Android development using Kotlin, and SQL solutions - - [UFJF-MLTK](https://github.com/mateus558/UFJF-Machine-Learning-Toolkit), A C++ cross-platform framework for machine learning algorithms development and testing - - [Vala Language Server](https://github.com/benwaffle/vala-language-server), code intelligence engine for the Vala and Genie programming languages - - [Valum](https://github.com/valum-framework/valum), a micro web framework written in Vala - - [Venom](https://github.com/naxuroqa/Venom), a modern Tox client for the GNU/Linux desktop - - [vkQuake](https://github.com/Novum/vkQuake), a port of id Software's Quake using Vulkan instead of OpenGL for rendering - - [VMAF](https://github.com/Netflix/vmaf) (by Netflix), a perceptual video quality assessment based on multi-method fusion - - [Wayland](https://gitlab.freedesktop.org/wayland/wayland) and [Weston](https://gitlab.freedesktop.org/wayland/weston), a next generation display server - - [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots), a modular Wayland compositor library - - [xi-gtk](https://github.com/eyelash/xi-gtk), a GTK+ front-end for the Xi editor - - [Xorg](https://gitlab.freedesktop.org/xorg/xserver), the X.org display server (not the default yet) - - [X Test Suite](https://gitlab.freedesktop.org/xorg/test/xts), The X.org test suite - - [zathura](https://github.com/pwmt/zathura), a highly customizable and functional document viewer based on the -girara user interface library and several document libraries - - [Zrythm](https://git.sr.ht/~alextee/zrythm), a cross-platform digital audio workstation written in C using GTK4 - - [ZStandard](https://github.com/facebook/zstd/commit/4dca56ed832c6a88108a2484a8f8ff63d8d76d91), a compression algorithm developed at Facebook (not used by default) - -Note that a more up-to-date list of GNOME projects that use Meson can -be found -[here](https://wiki.gnome.org/Initiatives/GnomeGoals/MesonPorting). + - [Wayland](https://gitlab.freedesktop.org/wayland/wayland), common display protocol diff --git a/docs/markdown/Vala.md b/docs/markdown/Vala.md index fdc4fba..ff7a7c7 100644 --- a/docs/markdown/Vala.md +++ b/docs/markdown/Vala.md @@ -304,6 +304,40 @@ In this example, the second and third elements of the `install_dir` array indicate the destination with `true` to use default directories (i.e. `include` and `share/vala/vapi`). +### Depending on C header + +*(since 1.10.0)* + +Given the previous example, + +```meson +foo_lib = shared_library(...) +foo_h = foo_lib.vala_header() +``` + +This header can now be used like any other generated header to create an +order-only dependency. + + +### Depending on VAPI header + +*(since 1.10.0)* + +Given the previous example, + +```meson +foo_lib = shared_library(...) +foo_vapi = foo_lib.vala_vapi() +``` + +### Depending on generated GIR + +*(since 1.10.0)* + +```meson +foo_lib = shared_library(..., vala_gir : 'foo.gir') +foo_gir = foo_lib.vala_gir() +``` ### GObject Introspection and language bindings @@ -339,6 +373,21 @@ directory (i.e. `share/gir-1.0` for GIRs). The fourth element in the To then generate a typelib file use a custom target with the `g-ir-compiler` program and a dependency on the library: +*Since Meson 1.10*, use the `.vala_gir()` method to get a handle to the generated `.gir` file: + +```meson +g_ir_compiler = find_program('g-ir-compiler') +custom_target('foo typelib', command: [g_ir_compiler, '--output', '@OUTPUT@', '@INPUT@'], + input: foo_lib.vala_gir(), + output: 'Foo-1.0.typelib', + install: true, + install_dir: get_option('libdir') / 'girepository-1.0') +``` + + +*Before Meson 1.10*, calculating the path to the input is required, as is adding a +manual dependency to the vala target: + ```meson g_ir_compiler = find_program('g-ir-compiler') custom_target('foo typelib', command: [g_ir_compiler, '--output', '@OUTPUT@', '@INPUT@'], diff --git a/docs/markdown/Wrap-dependency-system-manual.md b/docs/markdown/Wrap-dependency-system-manual.md index 73358e7..4a7250d 100644 --- a/docs/markdown/Wrap-dependency-system-manual.md +++ b/docs/markdown/Wrap-dependency-system-manual.md @@ -322,8 +322,8 @@ foo-bar-1.0 = foo_bar_dep **Note**: This is experimental and has no backwards or forwards compatibility guarantees. See [Meson's rules on mixing build systems](Mixing-build-systems.md). -Cargo subprojects automatically override the `<package_name>-<version>-rs` dependency -name: +Cargo subprojects automatically call `override_dependency` with the name +`<package_name>-<version>-<suffix>`, where every part is defeined as follows: - `package_name` is defined in `[package] name = ...` section of the `Cargo.toml`. - `version` is the API version deduced from `[package] version = ...` as follow: * `x.y.z` -> 'x' @@ -331,9 +331,9 @@ name: * `0.0.x` -> '0' It allows to make different dependencies for incompatible versions of the same crate. -- `-rs` suffix is added to distinguish from regular system dependencies, for - example `gstreamer-1.0` is a system pkg-config dependency and `gstreamer-0.22-rs` - is a Cargo dependency. +- the suffix is `-rs` for `rlib` and `dylib` crate types. The suffix is added to + distinguish Rust crates from C-ABI dependencies; for example `gstreamer-1.0` + is a system pkg-config dependency and `gstreamer-0.22-rs` is a Cargo dependency. That means the `.wrap` file should have `dependency_names = foo-1-rs` in their `[provide]` section when `Cargo.toml` has package name `foo` and version `1.2`. @@ -361,10 +361,21 @@ Some naming conventions need to be respected: This is typically used as `extra_args += ['--cfg', 'foo']`. - The `extra_deps` variable is pre-defined and can be used to add extra dependencies. This is typically used as `extra_deps += dependency('foo')`. - -Since *1.5.0* Cargo wraps can also be provided with `Cargo.lock` file at the root -of (sub)project source tree. Meson will automatically load that file and convert -it into a series of wraps definitions. +- The `features` variable is pre-defined and contains the list of features enabled + on this crate. + +Since *1.5.0* Cargo wraps can also be provided with `Cargo.lock` file at the +root of (sub)project source tree. Meson will automatically load that file, looking +for crates found on `crates.io` or in a git repository, and will convert those +crates into a series of wraps definitions. Since *1.11.0* the overlay directory +(`patch_directory`) is automatically detected, using the same directory as the +dependency name for `crates.io` URLs, and the final component of the URL +(possibly with the `.git` suffix removed) for "git" URLs. + +Since *1.10.0* Workspace Cargo.toml are supported. For the time being it is +recommended to regroup all Cargo dependencies inside a single workspace invoked +from the main Meson project. When invoking multiple different Cargo subprojects +from Meson, feature resolution of common dependencies might be wrong. ## Using wrapped projects diff --git a/docs/markdown/Yaml-RefMan.md b/docs/markdown/Yaml-RefMan.md index 2872791..cce844b 100644 --- a/docs/markdown/Yaml-RefMan.md +++ b/docs/markdown/Yaml-RefMan.md @@ -150,9 +150,9 @@ optargs: ... varargs: - name: Some name # [required] - type: str | list[str | int] # [required] - description: Some helpful text # [required] + name: Some name # [required] + type: str | array[str | int] # [required] + description: Some helpful text # [required] since: 0.42.0 deprecated: 100.99.0 min_varargs: 1 diff --git a/docs/markdown/_Sidebar.md b/docs/markdown/_Sidebar.md index ce73d5a..eb6afb6 100644 --- a/docs/markdown/_Sidebar.md +++ b/docs/markdown/_Sidebar.md @@ -9,6 +9,7 @@ ### [Modules](Module-reference.md) +* [codegen](Codegen-module.md) * [gnome](Gnome-module.md) * [i18n](i18n-module.md) * [pkgconfig](Pkgconfig-module.md) diff --git a/docs/markdown/i18n-module.md b/docs/markdown/i18n-module.md index da6fce7..767e0c8 100644 --- a/docs/markdown/i18n-module.md +++ b/docs/markdown/i18n-module.md @@ -83,14 +83,14 @@ i18n.xgettext(name, sources..., args: [...], recursive: false) ``` Invokes the `xgettext` program on given sources, to generate a `.pot` file. -This function is to be used when the `gettext` function workflow it not suitable +This function is to be used when the `gettext` function workflow is not suitable for your project. For example, it can be used to produce separate `.pot` files for each executable. Positional arguments are the following: * name `str`: the name of the resulting pot file. -* sources `list[str|File|build_tgt|custom_tgt]`: +* sources `array[str|File|build_tgt|custom_tgt|custom_idx]`: source files or targets. May be a list of `string`, `File`, [[@build_tgt]], or [[@custom_tgt]] returned from other calls to this function. @@ -120,4 +120,7 @@ included to generate the final pot file. Therefore, adding a dependency to source target will automatically add the translations of that dependency to the needed translations for that source target. +*New in 1.10.0* sources can be result of [[@custom_tgt]] or [[@custom_idx]]. +Before 1.10.0, custom targets were silently ignored. + *Added 1.8.0* diff --git a/docs/markdown/index.md b/docs/markdown/index.md index cbb7901..ea168c2 100644 --- a/docs/markdown/index.md +++ b/docs/markdown/index.md @@ -51,9 +51,7 @@ a web chat. The channel to use is `#mesonbuild` either via Matrix ([web interface](https://app.element.io/#/room/#mesonbuild:matrix.org)) or [OFTC IRC](https://www.oftc.net/). -Other methods of communication include the [mailing -list](https://groups.google.com/forum/#!forum/mesonbuild) (hosted by -Google Groups) and the +For general discussion, please use the [Discussions](https://github.com/mesonbuild/meson/discussions) section of the Meson GitHub repository. diff --git a/docs/markdown/snippets/update_xc32_for_v5.md b/docs/markdown/snippets/update_xc32_for_v5.md new file mode 100644 index 0000000..1263948 --- /dev/null +++ b/docs/markdown/snippets/update_xc32_for_v5.md @@ -0,0 +1,4 @@ +## XC32 support now aware of v5.00 features + +XC32 features introduced in v5.00 can now be used. This includes +support for LTO auto and the C2x and CPP23 standards. diff --git a/docs/meson.build b/docs/meson.build index c476b59..f79f5f9 100644 --- a/docs/meson.build +++ b/docs/meson.build @@ -135,6 +135,8 @@ documentation = hotdoc.generate_doc(meson.project_name(), extra_extension: meson.current_source_dir() / 'extensions' / 'refman_links.py', refman_data_file: refman_md[1], fatal_warnings: true, + devhelp_activate: true, + devhelp_online: 'https://mesonbuild.com/', ) run_target('upload', @@ -146,4 +148,4 @@ run_target('upload', depends: documentation, ) -test('validate_links', find_program('./validatelinks.py'), args: meson.current_source_dir() / 'markdown' / 'Users.md') +test('validate_links', find_program('./validatelinks.py'), args: meson.current_source_dir() / 'markdown' / 'Users.md', timeout: 65) diff --git a/docs/refman/generatormd.py b/docs/refman/generatormd.py index 854712d..ebf57fa 100644 --- a/docs/refman/generatormd.py +++ b/docs/refman/generatormd.py @@ -44,6 +44,7 @@ _OBJ_ID_MAP = { ObjectType.ELEMENTARY: 'elementary', ObjectType.BUILTIN: 'builtin', ObjectType.MODULE: 'module', + ObjectType.FUNCTIONS: 'functions', ObjectType.RETURNED: 'returned', } @@ -299,7 +300,7 @@ class GeneratorMD(GeneratorBase): def _write_functions(self) -> None: data = {'functions': [self._gen_func_or_method(x) for x in self.functions]} - self._write_template(data, 'root.functions') + self._write_template(data, f'root.{_OBJ_ID_MAP[ObjectType.FUNCTIONS]}') def _root_refman_docs(self) -> None: def gen_obj_links(objs: T.List[Object]) -> T.List[T.Dict[str, str]]: @@ -330,6 +331,7 @@ class GeneratorMD(GeneratorBase): self._write_template(data, 'root') self._write_template({**dummy, 'name': 'Elementary types'}, f'root.{_OBJ_ID_MAP[ObjectType.ELEMENTARY]}', 'dummy') self._write_template({**dummy, 'name': 'Builtin objects'}, f'root.{_OBJ_ID_MAP[ObjectType.BUILTIN]}', 'dummy') + self._write_functions() self._write_template({**dummy, 'name': 'Returned objects'}, f'root.{_OBJ_ID_MAP[ObjectType.RETURNED]}', 'dummy') if self.enable_modules: @@ -339,7 +341,8 @@ class GeneratorMD(GeneratorBase): def generate(self) -> None: mlog.log('Generating markdown files...') with mlog.nested(): - self._write_functions() + for fun in self.functions: + self._write_template(self._gen_func_or_method(fun), f'root.{_OBJ_ID_MAP[ObjectType.FUNCTIONS]}.{fun.name}', 'func') for obj in self.objects: if not self.enable_modules and (obj.obj_type == ObjectType.MODULE or obj.defined_by_module is not None): continue @@ -383,7 +386,7 @@ class GeneratorMD(GeneratorBase): data[f'{obj.name}.{m.name}'] = f'{obj_file}#{obj.name}{m.name}' # Functions - funcs_file = self._gen_filename('root.functions', extension='html') + funcs_file = self._gen_filename(f'root.{_OBJ_ID_MAP[ObjectType.FUNCTIONS]}', extension='html') for fn in self.functions: data[fn.name] = f'{funcs_file}#{fn.name}' diff --git a/docs/refman/model.py b/docs/refman/model.py index 51d4ca2..1360d89 100644 --- a/docs/refman/model.py +++ b/docs/refman/model.py @@ -78,7 +78,8 @@ class ObjectType(Enum): ELEMENTARY = 0 BUILTIN = 1 MODULE = 2 - RETURNED = 3 + FUNCTIONS = 3 + RETURNED = 4 @dataclass class Object(NamedObject, FetureCheck): diff --git a/docs/refman/templates/meson.vim.mustache b/docs/refman/templates/meson.vim.mustache index d8f009e..a0c0513 100644 --- a/docs/refman/templates/meson.vim.mustache +++ b/docs/refman/templates/meson.vim.mustache @@ -27,7 +27,7 @@ endif let s:cpo_save = &cpo set cpo&vim -" http://mesonbuild.com/Syntax.html +" https://mesonbuild.com/Syntax.html syn keyword mesonConditional elif else if endif syn keyword mesonRepeat foreach endforeach syn keyword mesonOperator and not or in @@ -54,7 +54,7 @@ syn match mesonEscape "\\N{\a\+\%(\s\a\+\)*}" contained syn match mesonEscape "\\$" " Meson only supports integer numbers -" http://mesonbuild.com/Syntax.html#numbers +" https://mesonbuild.com/Syntax.html#numbers syn match mesonNumber "\<\d\+\>" syn match mesonNumber "\<0x\x\+\>" syn match mesonNumber "\<0o\o\+\>" diff --git a/docs/sitemap.txt b/docs/sitemap.txt index fd37185..a71e954 100644 --- a/docs/sitemap.txt +++ b/docs/sitemap.txt @@ -38,6 +38,7 @@ index.md Code-formatting.md Modules.md CMake-module.md + Codegen-module.md Cuda-module.md Dlang-module.md External-Project-module.md @@ -55,6 +56,7 @@ index.md Qt6-module.md Rust-module.md Simd-module.md + Snippets-module.md SourceSet-module.md Windows-module.md i18n-module.md @@ -88,6 +90,8 @@ index.md Wrap-best-practices-and-tips.md Shipping-prebuilt-binaries-as-wraps.md Release-notes.md + Release-notes-for-1.10.0.md + Release-notes-for-1.9.0.md Release-notes-for-1.8.0.md Release-notes-for-1.7.0.md Release-notes-for-1.6.0.md diff --git a/docs/theme/extra/templates/navbar_links.html b/docs/theme/extra/templates/navbar_links.html index 65a21a2..ac4a6fe 100644 --- a/docs/theme/extra/templates/navbar_links.html +++ b/docs/theme/extra/templates/navbar_links.html @@ -7,6 +7,7 @@ <ul class="dropdown-menu" id="modules-menu"> @for tup in [ \ ("CMake-module.html","CMake"), \ + ("Codegen-module.html","Codegen"), \ ("Cuda-module.html","CUDA"), \ ("Dlang-module.html","Dlang"), \ ("External-Project-module.html","External Project"), \ @@ -25,6 +26,7 @@ ("Qt6-module.html","Qt6"), \ ("Rust-module.html","Rust"), \ ("Simd-module.html","Simd"), \ + ("Snippets-module.html","Snippets"), \ ("SourceSet-module.html","SourceSet"), \ ("Wayland-module.html","Wayland"), \ ("Windows-module.html","Windows")]: diff --git a/docs/yaml/builtins/meson.yaml b/docs/yaml/builtins/meson.yaml index 516e41c..eac8368 100644 --- a/docs/yaml/builtins/meson.yaml +++ b/docs/yaml/builtins/meson.yaml @@ -139,7 +139,7 @@ methods: to install only a subset of the files. By default the script has no install tag which means it is not being run when `meson install --tags` argument is specified. - + dry_run: type: bool since: 1.1.0 @@ -447,12 +447,12 @@ methods: description: Returns the version string specified in [[project]] function call. - name: project_license - returns: list[str] + returns: array[str] since: 0.45.0 description: Returns the array of licenses specified in [[project]] function call. - name: project_license_files - returns: list[file] + returns: array[file] since: 1.1.0 description: Returns the array of license files specified in the [[project]] function call. @@ -498,10 +498,10 @@ methods: posargs: env: - type: env | str | list[str] | dict[str] | dict[list[str]] + type: env | str | array[str] | dict[str] | dict[array[str]] description: | The [[@env]] object to add. - Since *0.62.0* list of strings is allowed in dictionary values. In that + Since *0.62.0* array of strings is allowed in dictionary values. In that case values are joined using the separator. kwargs: separator: diff --git a/docs/yaml/elementary/array.yml b/docs/yaml/elementary/array.yml new file mode 100644 index 0000000..7183d1a --- /dev/null +++ b/docs/yaml/elementary/array.yml @@ -0,0 +1,74 @@ +name: array +long_name: Array +description: An array of elements. See [arrays](Syntax.md#arrays). +is_container: true + +methods: +- name: contains + returns: bool + description: | + Returns `true` if the array contains the object + given as argument, `false` otherwise + + arg_flattening: false + + posargs: + item: + type: any + description: The item to check + +- name: get + returns: any + description: | + returns the object at the given index, + negative indices count from the back of the array, indexing out of + bounds returns the `fallback` value *(since 0.38.0)* or, if it is + not specified, causes a fatal error + + arg_flattening: false + + posargs: + index: + type: int + description: Index of the array position to query. Negative values start at the end of the array + + optargs: + fallback: + type: any + description: Fallback value that is returned if the index is out of range. + +- name: slice + returns: array[any] + since: 1.10.0 + description: | + Return a selection of the elements of the array starting at index `start` + and continuing with `step` size jumps until `stop`. Negative indices count + from the back of the array. The step size cannot be zero, but may be + negative. If it is negative, `start` and `stop` default to the end and + beginning of the array, respectively. If `step` is positive, `start` + defaults to 0 and `stop` defaults to the length of the array. Either both + or none of `start` and `stop` must be provided to prevent ambiguity. + + optargs: + start: + type: int + description: The lower bound of the slice + + stop: + type: int + description: The upper bound of the slice + + kwargs: + step: + type: int + default: 1 + description: The step size + +- name: length + returns: int + description: Returns the current size of the array. + +- name: flatten + returns: array[any] + since: 1.9.0 + description: Returns a flattened copy of the array, with all nested arrays removed. diff --git a/docs/yaml/elementary/dict.yml b/docs/yaml/elementary/dict.yml index 19263df..525fbee 100644 --- a/docs/yaml/elementary/dict.yml +++ b/docs/yaml/elementary/dict.yml @@ -44,5 +44,12 @@ methods: description: Fallback value that is returned if the key is not in the [[@dict]]. - name: keys - returns: list[str] - description: Returns an array of keys in the dictionary. + returns: array[str] + description: Returns an array of keys in the dictionary, sorted in ascending order. + +- name: values + returns: array[any] + since: 1.10.0 + description: | + Returns an array of values in the dictionary, sorted by the + corresponding keys in ascending order. diff --git a/docs/yaml/elementary/list.yml b/docs/yaml/elementary/list.yml deleted file mode 100644 index 1ffb6d2..0000000 --- a/docs/yaml/elementary/list.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: list -long_name: List -description: An array of elements. See [arrays](Syntax.md#arrays). -is_container: true - -methods: -- name: contains - returns: bool - description: | - Returns `true` if the array contains the object - given as argument, `false` otherwise - - arg_flattening: false - - posargs: - item: - type: any - description: The item to check - -- name: get - returns: any - description: | - returns the object at the given index, - negative indices count from the back of the array, indexing out of - bounds returns the `fallback` value *(since 0.38.0)* or, if it is - not specified, causes a fatal error - - arg_flattening: false - - posargs: - index: - type: int - description: Index of the list position to query. Negative values start at the end of the list - - optargs: - fallback: - type: any - description: Fallback value that is returned if the index is out of range. - -- name: length - returns: int - description: Returns the current size of the array / list. diff --git a/docs/yaml/elementary/str.yml b/docs/yaml/elementary/str.yml index 44aa742..c2b6c35 100644 --- a/docs/yaml/elementary/str.yml +++ b/docs/yaml/elementary/str.yml @@ -203,7 +203,7 @@ methods: # str.split - name: split - returns: list[str] + returns: array[str] description: | Splits the string at the specified character (or whitespace if not set) and returns the parts in an @@ -224,7 +224,7 @@ methods: description: Specifies the character / substring where to split the string. - name: splitlines - returns: list[str] + returns: array[str] since: 1.2.0 description: | Splits the string into an array of lines. @@ -250,7 +250,7 @@ methods: returns: str description: | The opposite of split, - for example `'.'.join(['a', 'b', 'c']` yields `'a.b.c'`. + for example `'.'.join(['a', 'b', 'c'])` yields `'a.b.c'`. example: | ```meson @@ -270,7 +270,7 @@ methods: The strings to join with the current string. Before Meson *0.60.0* this function only accepts a single positional - argument of the type [[list[str]]]. + argument of the type [[array[str]]]. # str.underscorify - name: underscorify diff --git a/docs/yaml/functions/_build_target_base.yaml b/docs/yaml/functions/_build_target_base.yaml index 1721b29..1129533 100644 --- a/docs/yaml/functions/_build_target_base.yaml +++ b/docs/yaml/functions/_build_target_base.yaml @@ -43,13 +43,13 @@ kwargs: description: precompiled header file to use for the given language <lang>_args: - type: list[str] + type: array[str] description: | compiler flags to use for the given language; eg: `cpp_args` for C++ vala_args: - type: list[str | file] + type: array[str | file] description: | Compiler flags for Vala. Unlike other languages this may contain Files @@ -74,7 +74,7 @@ kwargs: but which will be removed on install dependencies: - type: list[dep] + type: array[dep] description: | one or more dependency objects created with @@ -83,10 +83,12 @@ kwargs: (for deps built by the project) extra_files: - type: str | file | custom_tgt | custom_idx + type: str | file description: | Not used for the build itself but are shown as source files in IDEs - that group files by targets (such as Visual Studio) + that group files by targets (such as Visual Studio). + + These may only be static sources. gui_app: type: bool @@ -98,7 +100,7 @@ kwargs: 0.56.0, use `win_subsystem` instead. link_args: - type: list[str] + type: array[str] description: | Flags to use during linking. You can use UNIX-style flags here for all platforms. @@ -125,7 +127,7 @@ kwargs: *(broken until 0.55.0)* link_whole: - type: list[lib | custom_tgt | custom_idx] + type: array[lib | custom_tgt | custom_idx] since: 0.40.0 description: | Links all contents of the given static libraries whether they are used or @@ -133,18 +135,18 @@ kwargs: '/WHOLEARCHIVE' MSVC linker option. This allows the linked target to re-export symbols from all objects in the static libraries. - *(since 0.41.0)* If passed a list that list will be flattened. + *(since 0.41.0)* If passed an array that array will be flattened. *(since 0.51.0)* This argument also accepts outputs produced by custom targets. The user must ensure that the output is a library in the correct format. link_with: - type: list[lib | custom_tgt | custom_idx] + type: array[lib | custom_tgt | custom_idx] description: | One or more shared or static libraries - (built by this project) that this target should be linked with. *(since 0.41.0)* If passed a - list this list will be flattened. *(since 0.51.0)* The arguments can also be custom targets. + (built by this project) that this target should be linked with. *(since 0.41.0)* If passed an + array that array will be flattened. *(since 0.51.0)* The arguments can also be custom targets. In this case Meson will assume that merely adding the output file in the linker command line is sufficient to make linking work. If this is not sufficient, then the build system writer must write all other steps manually. @@ -156,7 +158,7 @@ kwargs: description: Controls whether Meson adds the current source and build directories to the include path include_directories: - type: list[inc | str] + type: array[inc | str] description: | one or more objects created with the [[include_directories]] function, or *(since 0.50.0)* strings, which will be transparently expanded to include directory objects @@ -175,7 +177,7 @@ kwargs: something like this: `install_dir : get_option('libdir') / 'projectname-1.0'`. install_mode: - type: list[str | int] + type: array[str | int] since: 0.47.0 description: | Specify the file mode in symbolic format @@ -198,7 +200,7 @@ kwargs: (but *not* before that). On Windows, this argument has no effect. objects: - type: list[extracted_obj | file | str] + type: array[extracted_obj | file | str] description: | List of object files that should be linked in this target. @@ -208,7 +210,7 @@ kwargs: object files had to be placed in `sources`. name_prefix: - type: str | list[void] + type: str | array[void] description: | The string that will be used as the prefix for the target output filename by overriding the default (only used for @@ -219,7 +221,7 @@ kwargs: Set this to `[]`, or omit the keyword argument for the default behaviour. name_suffix: - type: str | list[void] + type: str | array[void] description: | The string that will be used as the extension for the target by overriding the default. By default on Windows this is @@ -235,7 +237,7 @@ kwargs: Set this to `[]`, or omit the keyword argument for the default behaviour. override_options: - type: list[str] | dict[str | bool | int | list[str]] + type: array[str] | dict[str | bool | int | array[str]] since: 0.40.0 description: | takes an array of strings in the same format as `project`'s `default_options` @@ -253,10 +255,12 @@ kwargs: `default`, `internal`, `hidden`, `protected` or `inlineshidden`, which is the same as `hidden` but also includes things like C++ implicit constructors as specified in the GCC manual. Ignored on compilers that - do not support GNU visibility arguments. + do not support GNU visibility arguments. See also + [`snippets.symbol_visibility_header()`](Snippets-module.md#symbol_visibility_header) + method to help with defining public API. d_import_dirs: - type: list[inc | str] + type: array[inc | str] since: 0.62.0 description: | the directories to add to the string search path (i.e. `-J` switch for DMD). @@ -268,11 +272,11 @@ kwargs: description: When set to true, the D modules are compiled in debug mode. d_module_versions: - type: list[str | int] + type: array[str | int] description: List of module version identifiers set when compiling D sources. d_debug: - type: list[str] + type: array[str] description: | The [D version identifiers](https://dlang.org/spec/version.html#version) to add during the compilation of D source files. @@ -323,8 +327,50 @@ kwargs: type: dict[str] since: 1.2.0 description: | - On rust targets this provides a map of library names to the crate name - with which it would be available inside the rust code. + On rust targets this allows giving custom names to the crates that are + linked into the target. For example, passing a dependency map of + `{ 'gtk4': 'gtk' }` has the same effect of writing `gtk.package = "gtk4"` + in the `dependencies` section of a Cargo.toml file, or + `extern crate gtk4 as gtk` inside Rust code. + + *Since 1.10.0*, the keys can be either crate names or target names. + + vala_header: + type: str + description: | + On Vala targets, this provides a way to override the name of the generated + C compatible header for targets that generate them. - This allows renaming similar to the dependency renaming feature of cargo - or `extern crate foo as bar` inside rust code. + If it is not set the default will be calculated from the target's name. + + vala_vapi: + type: str + description: | + On Vala targets, this provides a way to override the name of the generated + VAPI file. + + If it is not set the default will be calculated from the target's name. + + vala_gir: + type: str + description: | + If set, generates a GIR file with the given name. If this is unset then + no GIR file will be generated. + + build_subdir: + type: str + since: 1.10.0 + description: + Places the build results in a subdirectory of the given name + rather than directly into the build directory. This does not + affect the install directory, which uses install_dir. + + This allows inserting a directory name into the build path, + either when needed to use the build result while building other + targets or as a way to support multiple targets with the same + basename by using unique build_subdir values for each one. + + To prevent collisions within the build directory, build_subdir + is not allowed to match a file or directory in the source + directory, nor contain '..' to refer to the parent of the build + directory. diff --git a/docs/yaml/functions/add_global_arguments.yaml b/docs/yaml/functions/add_global_arguments.yaml index 3b26d10..25e4eef 100644 --- a/docs/yaml/functions/add_global_arguments.yaml +++ b/docs/yaml/functions/add_global_arguments.yaml @@ -15,11 +15,11 @@ varargs: kwargs: language: - type: list[str] + type: array[str] required: true description: | Specifies the language(s) that the arguments should be - applied to. If a list of languages is given, the arguments are added + applied to. If an array of languages is given, the arguments are added to each of the corresponding compiler command lines. Note that there is no way to remove an argument set in this way. If you have an argument that is only used in a subset of targets, you have to specify diff --git a/docs/yaml/functions/add_test_setup.yaml b/docs/yaml/functions/add_test_setup.yaml index 3d666c7..a29b137 100644 --- a/docs/yaml/functions/add_test_setup.yaml +++ b/docs/yaml/functions/add_test_setup.yaml @@ -18,7 +18,7 @@ posargs: kwargs: env: - type: env | list[str] | dict[str] + type: env | array[str] | dict[str] description: | environment variables to set , such as `['NAME1=value1', 'NAME2=value2']`, @@ -26,7 +26,7 @@ kwargs: environment juggling. *(Since 0.52.0)* A dictionary is also accepted. exe_wrapper: - type: list[str | external_program] + type: array[str | external_program] description: The command or script followed by the arguments to it gdb: @@ -52,9 +52,9 @@ kwargs: without the `--setup` option. exclude_suites: - type: list[str] + type: array[str] since: 0.57.0 description: - A list of test suites that should be excluded when using this setup. + An array of test suites that should be excluded when using this setup. Suites specified in the `--suite` option - to `meson test` will always run, overriding `add_test_setup` if necessary.
\ No newline at end of file + to `meson test` will always run, overriding `add_test_setup` if necessary. diff --git a/docs/yaml/functions/benchmark.yaml b/docs/yaml/functions/benchmark.yaml index 7a555a4..fe69f28 100644 --- a/docs/yaml/functions/benchmark.yaml +++ b/docs/yaml/functions/benchmark.yaml @@ -28,11 +28,11 @@ posargs: kwargs: args: - type: list[str | file | tgt | external_program] + type: array[str | file | tgt | external_program] description: Arguments to pass to the executable env: - type: env | list[str] | dict[str] + type: env | array[str] | dict[str] description: | environment variables to set, such as `['NAME1=value1', 'NAME2=value2']`, or an [[@env]] object which allows more sophisticated @@ -46,11 +46,11 @@ kwargs: executable returns a non-zero return value (i.e. reports an error) suite: - type: str | list[str] + type: str | array[str] description: | - `'label'` (or list of labels `['label1', 'label2']`) + `'label'` (or array of labels `['label1', 'label2']`) attached to this test. The suite name is qualified by a (sub)project - name resulting in `(sub)project_name:label`. In the case of a list + name resulting in `(sub)project_name:label`. In the case of an array of strings, the suite names will be `(sub)project_name:label1`, `(sub)project_name:label2`, etc. @@ -70,7 +70,7 @@ kwargs: for the test depends: - type: list[build_tgt | custom_tgt] + type: array[build_tgt | custom_tgt] since: 0.46.0 description: | specifies that this test depends on the specified diff --git a/docs/yaml/functions/build_target.yaml b/docs/yaml/functions/build_target.yaml index a56fe75..f883e87 100644 --- a/docs/yaml/functions/build_target.yaml +++ b/docs/yaml/functions/build_target.yaml @@ -26,9 +26,9 @@ description: | build_target(<arguments and keyword arguments>, target_type : 'executable') ``` - The lists for the kwargs (such as `sources`, `objects`, and `dependencies`) are - always flattened, which means you can freely nest and add lists while - creating the final list. + The arrays for the kwargs (such as `sources`, `objects`, and `dependencies`) are + always flattened, which means you can freely nest and add arrays while + creating the final array. The returned object also has methods that are documented in [[@build_tgt]]. diff --git a/docs/yaml/functions/configure_file.yaml b/docs/yaml/functions/configure_file.yaml index 20b96aa..32cb559 100644 --- a/docs/yaml/functions/configure_file.yaml +++ b/docs/yaml/functions/configure_file.yaml @@ -12,10 +12,12 @@ description: | A dictionary can be passed instead of a [[@cfg_data]] object. - When a list of strings is passed to the `command:` keyword argument, + When an array of strings is passed to the `command:` keyword argument, it takes any source or configured file as the `input:` and assumes that the `output:` is produced when the specified command is run. + You can install the outputted file with the `install_dir:` kwarg, see below. + *(since 0.47.0)* When the `copy:` keyword argument is set to `true`, this function will copy the file provided in `input:` to a file in the build directory with the name `output:` in the current directory. @@ -34,7 +36,7 @@ kwargs: file specified as `output`. command: - type: list[str | file] + type: array[str | file] description: | As explained above, if specified, Meson does not create the file itself but rather runs the specified command, which allows @@ -103,7 +105,7 @@ kwargs: string, the file is not installed. install_mode: - type: list[str | int] + type: array[str | int] since: 0.47.0 description: | Specify the file mode in symbolic format @@ -153,3 +155,21 @@ kwargs: description: | When specified, macro guards will be used instead of '#pragma once'. The macro guard name will be the specified name. + + build_subdir: + type: str + since: 1.10.0 + description: + Places the build results in a subdirectory of the given name + rather than directly into the build directory. This does not + affect the install directory, which uses install_dir. + + This allows inserting a directory name into the build path, + either when needed to use the build result while building other + targets or as a way to support multiple targets with the same + basename by using unique build_subdir values for each one. + + To prevent collisions within the build directory, build_subdir + is not allowed to match a file or directory in the source + directory, nor contain '..' to refer to the parent of the build + directory. diff --git a/docs/yaml/functions/custom_target.yaml b/docs/yaml/functions/custom_target.yaml index 585d260..5836bb7 100644 --- a/docs/yaml/functions/custom_target.yaml +++ b/docs/yaml/functions/custom_target.yaml @@ -12,10 +12,16 @@ description: | custom_target('foo', output: 'file.txt', ...) ``` + The files passed to `output:` cannot contain path separators. See the + [manual on custom build targets](Custom-build-targets.md#details-on-command-invocation) for + an explanation on where output files should be placed. + + You can install the outputted files with the `install_dir:` kwarg, see below. + *Since 0.60.0* the name argument is optional and defaults to the basename of the first output (`file.txt` in the example above). - The list of strings passed to the `command` keyword argument accept + The array of strings passed to the `command` keyword argument accept the following special string substitutions: - `@INPUT@`: the full path to the input passed to `input`. If more than @@ -103,7 +109,7 @@ kwargs: There are some compilers that can't be told to write their output to a file but instead write it to standard output. When this argument is set to true, Meson captures `stdout` and writes it - to the target file. Note that your command argument list may not + to the target file. Note that your command argument array may not contain `@OUTPUT@` when capture mode is active. console: @@ -118,7 +124,7 @@ kwargs: serializing all targets in this pool. command: - type: list[str | file | exe | external_program] + type: array[str | file | exe | external_program] description: | Command to run to create outputs from inputs. The command may be strings or the return value of functions that return file-like @@ -132,7 +138,7 @@ kwargs: -arg2'` as the latter will *not* work. depend_files: - type: list[str | file] + type: array[str | file] description: | files ([[@str]], [[@file]], or the return value of [[configure_file]] that @@ -140,7 +146,7 @@ kwargs: argument. Useful for adding regen dependencies. depends: - type: list[build_tgt | custom_tgt | custom_idx] + type: array[build_tgt | custom_tgt | custom_idx] description: | Specifies that this target depends on the specified target(s), even though it does not take any of them as a command @@ -162,8 +168,8 @@ kwargs: are also accepted. input: - type: list[str | file] - description: List of source files. *(since 0.41.0)* the list is flattened. + type: array[str | file] + description: Array of source files. *(since 0.41.0)* the array is flattened. install: type: bool @@ -171,7 +177,7 @@ kwargs: the install step (see `install_dir` for details). install_dir: - type: str | list[str | bool] + type: str | array[str | bool] description: | If only one install_dir is provided, all outputs are installed there. *Since 0.40.0* Allows you to specify the installation directory for each @@ -195,17 +201,17 @@ kwargs: This would install `second.file` to `otherdir` and not install `first.file`. install_mode: - type: list[str | int] + type: array[str | int] since: 0.47.0 description: | The file mode and optionally the owner/uid and group/gid. See the `install_mode` kwarg of [[install_data]] for more information. install_tag: - type: list[str] + type: array[str] since: 0.60.0 description: | - A list of strings, one per output, used by the `meson install --tags` command + An array of strings, one per output, used by the `meson install --tags` command to install only a subset of the files. By default all outputs have no install tag which means they are not being @@ -214,12 +220,13 @@ kwargs: outputs that have no tag or are not installed. output: - type: list[str] - description: List of output files. + type: array[str] + description: | + Array of output files. These cannot contain path separators. env: since: 0.57.0 - type: env | list[str] | dict[str] + type: env | array[str] | dict[str] description: | environment variables to set, such as `{'NAME1': 'value1', 'NAME2': 'value2'}` or `['NAME1=value1', 'NAME2=value2']`, @@ -234,4 +241,4 @@ kwargs: There are some compilers that can't be told to read their input from a file and instead read it from standard input. When this argument is set to `true`, Meson feeds the input file to `stdin`. Note that - your argument list may not contain `@INPUT@` when feed mode is active. + your argument array may not contain `@INPUT@` when feed mode is active. diff --git a/docs/yaml/functions/debug.yaml b/docs/yaml/functions/debug.yaml index a64bd92..4a4508c 100644 --- a/docs/yaml/functions/debug.yaml +++ b/docs/yaml/functions/debug.yaml @@ -5,10 +5,10 @@ description: Write the argument string to the meson build log. posargs: message: - type: str | int | bool | list[str | int | bool] | dict[str | int | bool] + type: str | int | bool | array[str | int | bool] | dict[str | int | bool] description: The message to print varargs: name: msg - type: str | int | bool | list[str | int | bool] | dict[str | int | bool] + type: str | int | bool | array[str | int | bool] | dict[str | int | bool] description: Additional parameters will be separated by spaces diff --git a/docs/yaml/functions/declare_dependency.yaml b/docs/yaml/functions/declare_dependency.yaml index 848082d..15d1606 100644 --- a/docs/yaml/functions/declare_dependency.yaml +++ b/docs/yaml/functions/declare_dependency.yaml @@ -12,41 +12,41 @@ description: | kwargs: compile_args: - type: list[str] + type: array[str] description: Compile arguments to use. dependencies: - type: list[dep] + type: array[dep] description: Other dependencies needed to use this dependency. include_directories: - type: list[inc | str] + type: array[inc | str] description: | the directories to add to header search path, must be [[@inc]] objects or *(since 0.50.0)* plain strings. link_args: - type: list[str] + type: array[str] description: Link arguments to use. link_with: - type: list[lib] + type: array[lib] description: Libraries to link against. link_whole: - type: list[lib] + type: array[lib] since: 0.46.0 description: Libraries to link fully, same as [[executable]]. sources: - type: list[str | file | custom_tgt | custom_idx | generated_list] + type: array[str | file | custom_tgt | custom_idx | generated_list] description: | sources to add to targets (or generated header files that should be built before sources including them are built) extra_files: - type: list[str | file] + type: array[str | file] since: 1.2.0 description: | extra files to add to targets. @@ -59,31 +59,31 @@ kwargs: such as `1.2.3`. Defaults to the project version. variables: - type: dict[str] | list[str] + type: dict[str] | array[str] since: 0.54.0 description: | a dictionary of arbitrary strings, this is meant to be used in subprojects where special variables would be provided via cmake or - pkg-config. *since 0.56.0* it can also be a list of `'key=value'` strings. + pkg-config. *since 0.56.0* it can also be an array of `'key=value'` strings. d_module_versions: - type: str | int | list[str | int] + type: str | int | array[str | int] since: 0.62.0 description: | The [D version identifiers](https://dlang.org/spec/version.html#version) to add during the compilation of D source files. d_import_dirs: - type: list[inc | str] + type: array[inc | str] since: 0.62.0 description: | the directories to add to the string search path (i.e. `-J` switch for DMD). Must be [[@inc]] objects or plain strings. objects: - type: list[extracted_obj] + type: array[extracted_obj] since: 1.1.0 description: | - a list of object files, to be linked directly into the targets that use the + an array of object files, to be linked directly into the targets that use the dependency. diff --git a/docs/yaml/functions/dependency.yaml b/docs/yaml/functions/dependency.yaml index a19deab..c337769 100644 --- a/docs/yaml/functions/dependency.yaml +++ b/docs/yaml/functions/dependency.yaml @@ -78,7 +78,7 @@ varargs: kwargs: default_options: - type: list[str] | dict[str | bool | int | list[str]] + type: array[str] | dict[str | bool | int | array[str]] since: 0.38.0 description: | An array of default option values @@ -103,7 +103,7 @@ kwargs: for more details. fallback: - type: list[str] | str + type: array[str] | str description: | Manually specifies a subproject fallback to use in case the dependency is not found in the system. @@ -120,7 +120,7 @@ kwargs: in this case the subproject must use `meson.override_dependency('dependency_name', subproj_dep)` to specify the dependency object used in the superproject. - If the value is an empty list, it has the same effect as + If the value is an empty array it has the same effect as `allow_fallback: false`. language: @@ -174,21 +174,26 @@ kwargs: libraries instead of dynamic ones (note that this is not supported by all dependency backends) + Leaving this value unset will result in an implementation defined default + value. For most dependencies this means the value of the `prefer_static` + option, but some custom dependencies have their own method for determining + the most useful default option. + *Since 0.60.0* it also sets `default_library` option accordingly on the fallback subproject if it was not set explicitly in `default_options` keyword argument. - *Since 0.63.0* when the `prefer_static` option is set to `true` the default - value is `true` otherwise the default value is `false`. + *Since 0.63.0* when the `prefer_static` option is used to calculate the + default value. version: - type: list[str] | str + type: array[str] | str since: 0.37.0 description: | Specifies the required version, a string containing a comparison operator followed by the version string, examples include `>1.0.0`, `<=2.3.5` or `3.1.4` for exact matching. - You can also specify multiple restrictions by passing a list to this + You can also specify multiple restrictions by passing an array to this keyword argument, such as: `['>=3.14.0', '<=4.1.0']`. These requirements are never met if the version is unknown. diff --git a/docs/yaml/functions/environment.yaml b/docs/yaml/functions/environment.yaml index 4c18ffc..1f6e198 100644 --- a/docs/yaml/functions/environment.yaml +++ b/docs/yaml/functions/environment.yaml @@ -7,12 +7,12 @@ arg_flattening: false optargs: env: - type: str | list[str] | dict[str] | dict[list[str]] + type: str | array[str] | dict[str] | dict[array[str]] since: 0.52.0 description: | If provided, each key/value pair is added into the [[@env]] object as if [[env.set]] method was called for each of them. - Since *0.62.0* list of strings is allowed in dictionary values. In that + Since *0.62.0* arrays of strings are allowed in dictionary values. In that case values are joined using the separator. kwargs: diff --git a/docs/yaml/functions/executable.yaml b/docs/yaml/functions/executable.yaml index df71b79..c819a38 100644 --- a/docs/yaml/functions/executable.yaml +++ b/docs/yaml/functions/executable.yaml @@ -4,9 +4,9 @@ description: | Creates a new executable. The first argument specifies its name and the remaining positional arguments define the input files to use. - The lists for the kwargs (such as `sources`, `objects`, and `dependencies`) are - always flattened, which means you can freely nest and add lists while - creating the final list. + The arrays for the kwargs (such as `sources`, `objects`, and `dependencies`) are + always flattened, which means you can freely nest and add arrays while + creating the final array. The returned object also has methods that are documented in [[@exe]]. @@ -37,15 +37,18 @@ kwargs: since: 0.45.0 description: | when set to true causes the target's symbols to be - dynamically exported, allowing modules built using the - [[shared_module]] function to refer to functions, - variables and other symbols defined in the executable itself. Implies - the `implib` argument. + dynamically exported, allowing modules built using the + [[shared_module]] function to refer to functions, + variables and other symbols defined in the executable itself. implib: type: bool | str since: 0.42.0 description: | + When set to a string, that will be the name of a generated import library. + + *Since 1.10.0* passing a boolean value is deprecated. + When set to true, an import library is generated for the executable (the name of the import library is based on *exe_name*). Alternatively, when set to a string, that gives the base name for diff --git a/docs/yaml/functions/files.yaml b/docs/yaml/functions/files.yaml index ca72745..4d701e1 100644 --- a/docs/yaml/functions/files.yaml +++ b/docs/yaml/functions/files.yaml @@ -1,5 +1,5 @@ name: files -returns: list[file] +returns: array[file] description: | This command takes the strings given to it in arguments and returns corresponding File objects that you can use as sources for build diff --git a/docs/yaml/functions/find_program.yaml b/docs/yaml/functions/find_program.yaml index 1899941..110f5df 100644 --- a/docs/yaml/functions/find_program.yaml +++ b/docs/yaml/functions/find_program.yaml @@ -24,7 +24,7 @@ description: | (because the command invocator will reject the command otherwise) and Unixes (if the script file does not have the executable bit set). Hence, you *must not* manually add the interpreter while using this - script as part of a list of commands. Since *0.50.0* if the "python3" + script as part of an array of commands. Since *0.50.0* if the "python3" program is requested and it is not found in the system, Meson will return its current interpreter. @@ -98,7 +98,7 @@ kwargs: instead of a not-found object. version: - type: str | list[str] + type: str | array[str] since: 0.52.0 description: | Specifies the required version, see @@ -117,12 +117,12 @@ kwargs: If this is unspecified, `program_name --version` will be used. dirs: - type: list[str] + type: array[str] since: 0.53.0 - description: extra list of absolute paths where to look for program names. + description: extra array of absolute paths where to look for program names. default_options: - type: list[str] | dict[str | bool | int | list[str]] + type: array[str] | dict[str | bool | int | array[str]] since: 1.3.0 description: | An array of default option values diff --git a/docs/yaml/functions/generator.yaml b/docs/yaml/functions/generator.yaml index 6079d30..c505394 100644 --- a/docs/yaml/functions/generator.yaml +++ b/docs/yaml/functions/generator.yaml @@ -45,12 +45,12 @@ posargs: kwargs: arguments: - type: list[str] - description: A list of template strings that will be the command line arguments passed to the executable. + type: array[str] + description: An array of template strings that will be the command line arguments passed to the executable. depends: # Not sure why this is not just `target` - type: list[build_tgt | custom_tgt | custom_idx] + type: array[build_tgt | custom_tgt | custom_idx] since: 0.51.0 description: | An array of build targets that must be built before @@ -68,9 +68,9 @@ kwargs: recompilation, output: - type: list[str] + type: array[str] description: | - Template string (or list of template strings) defining + Template string (or array of template strings) defining how an output file name is (or multiple output names are) generated from a single source file name. diff --git a/docs/yaml/functions/get_option.yaml b/docs/yaml/functions/get_option.yaml index 0934758..e23e380 100644 --- a/docs/yaml/functions/get_option.yaml +++ b/docs/yaml/functions/get_option.yaml @@ -1,5 +1,5 @@ name: get_option -returns: str | int | bool | feature | list[str | int | bool] +returns: str | int | bool | feature | array[str | int | bool] description: | Obtains the value of the [project build option](Build-options.md) specified in the positional argument. @@ -20,6 +20,11 @@ description: | See [`feature` options](Build-options.md#features) documentation for more details. + For options that are [specified + per-machine](Builtin-options.md#specifying-options-per-machine) + `get_option()` retrieves the value of the option for the + build machine if the argument starts with `build.`. + posargs: option_name: type: str diff --git a/docs/yaml/functions/install_data.yaml b/docs/yaml/functions/install_data.yaml index 9ed09a7..516ca14 100644 --- a/docs/yaml/functions/install_data.yaml +++ b/docs/yaml/functions/install_data.yaml @@ -2,6 +2,9 @@ name: install_data returns: void description: | Installs files from the source tree that are listed as positional arguments. + Please note that this can only install static files from the source tree. + Generated files are installed via the `install_dir:` kwarg on the respective + generators, such as `custom_target()` or `configure_file()`. See [Installing](Installing.md) for more examples. @@ -25,7 +28,7 @@ kwargs: If omitted, the directory defaults to `{datadir}/{projectname}` *(since 0.45.0)*. install_mode: - type: list[str | int] + type: array[str | int] since: 0.38.0 description: | specify the file mode in symbolic format and @@ -58,16 +61,16 @@ kwargs: This is equivalent to GNU Automake's `nobase` option. rename: - type: list[str] + type: array[str] since: 0.46.0 description: | - If specified renames each source file into corresponding file from `rename` list. + If specified renames each source file into corresponding file from `rename` array. Nested paths are allowed and they are - joined with `install_dir`. Length of `rename` list must be equal to + joined with `install_dir`. Length of `rename` array must be equal to the number of sources. sources: - type: list[file | str] + type: array[file | str] description: Additional files to install. follow_symlinks: diff --git a/docs/yaml/functions/install_emptydir.yaml b/docs/yaml/functions/install_emptydir.yaml index df84f60..4f77f59 100644 --- a/docs/yaml/functions/install_emptydir.yaml +++ b/docs/yaml/functions/install_emptydir.yaml @@ -16,7 +16,7 @@ varargs: kwargs: install_mode: - type: list[str | int] + type: array[str | int] description: | Specify the file mode in symbolic format and optionally the owner/uid and group/gid for the created directory. diff --git a/docs/yaml/functions/install_headers.yaml b/docs/yaml/functions/install_headers.yaml index 0ac4fc5..42f6462 100644 --- a/docs/yaml/functions/install_headers.yaml +++ b/docs/yaml/functions/install_headers.yaml @@ -9,6 +9,10 @@ description: | argument. As an example if this has the value `myproj` then the headers would be installed to `/{prefix}/include/myproj`. + Please note that this can only install static files from the source tree. + Generated files are installed via the `install_dir:` kwarg on the respective + generators, such as `custom_target()` or `configure_file(). + example: | For example, this will install `common.h` and `kola.h` into `/{prefix}/include`: @@ -57,7 +61,7 @@ kwargs: Incompatible with the `install_dir` kwarg. install_mode: - type: list[str | int] + type: array[str | int] since: 0.47.0 description: | Specify the file mode in symbolic format diff --git a/docs/yaml/functions/install_man.yaml b/docs/yaml/functions/install_man.yaml index 8d9ba60..1deef59 100644 --- a/docs/yaml/functions/install_man.yaml +++ b/docs/yaml/functions/install_man.yaml @@ -20,7 +20,7 @@ warnings: kwargs: install_mode: - type: list[str | int] + type: array[str | int] since: 0.47.0 description: | Specify the file mode in symbolic format diff --git a/docs/yaml/functions/install_subdir.yaml b/docs/yaml/functions/install_subdir.yaml index 19abee3..3420f5e 100644 --- a/docs/yaml/functions/install_subdir.yaml +++ b/docs/yaml/functions/install_subdir.yaml @@ -66,7 +66,7 @@ posargs: kwargs: install_mode: - type: list[str | int] + type: array[str | int] since: 0.47.0 description: | Specify the file mode in symbolic format @@ -83,16 +83,16 @@ kwargs: tag which means they are not being installed when `--tags` argument is specified. exclude_files: - type: list[str] + type: array[str] description: | - A list of file names that should not be installed. + An array of file names that should not be installed. Names are interpreted as paths relative to the `subdir_name` location. exclude_directories: - type: list[str] + type: array[str] since: 0.47.0 description: | - A list of directory names that should not be installed. + An array of directory names that should not be installed. Names are interpreted as paths relative to the `subdir_name` location. install_dir: diff --git a/docs/yaml/functions/library.yaml b/docs/yaml/functions/library.yaml index 1d406f1..4a4611c 100644 --- a/docs/yaml/functions/library.yaml +++ b/docs/yaml/functions/library.yaml @@ -40,13 +40,13 @@ kwargs: type being build. <lang>_static_args: - type: list[str] + type: array[str] since: 1.3.0 description: Arguments that are only passed to a static library vala_static_args: - type: list[str | file] + type: array[str | file] since: 1.3.0 description: Arguments that are only passed to a static library @@ -54,13 +54,13 @@ kwargs: Like `vala_args`, [[files]] is allowed in addition to string <lang>_shared_args: - type: list[str] + type: array[str] since: 1.3.0 description: Arguments that are only passed to a shared library vala_shared_args: - type: list[str | file] + type: array[str | file] since: 1.3.0 description: Arguments that are only passed to a shared library diff --git a/docs/yaml/functions/message.yaml b/docs/yaml/functions/message.yaml index e480457..339dfc4 100644 --- a/docs/yaml/functions/message.yaml +++ b/docs/yaml/functions/message.yaml @@ -6,11 +6,11 @@ arg_flattening: false posargs: text: - type: str | int | bool | list[str | int | bool] | dict[str | int | bool] + type: str | int | bool | array[str | int | bool] | dict[str | int | bool] description: The message to print. varargs: name: more_text since: 0.54.0 - type: str | int | bool | list[str | int | bool] | dict[str | int | bool] + type: str | int | bool | array[str | int | bool] | dict[str | int | bool] description: Additional text that will be printed separated by spaces. diff --git a/docs/yaml/functions/project.yaml b/docs/yaml/functions/project.yaml index 5be8cac..0db8c03 100644 --- a/docs/yaml/functions/project.yaml +++ b/docs/yaml/functions/project.yaml @@ -13,9 +13,9 @@ description: | would probably want to use the name _libfoobar_ instead of _The Foobar Library_. - It may be followed by the list of programming languages that the project uses. + It may be followed by the array of programming languages that the project uses. - *(since 0.40.0)* The list of languages is optional. + *(since 0.40.0)* The array of languages is optional. These languages may be used both for `native: false` (the default) (host machine) targets and for `native: true` (build machine) targets. @@ -24,7 +24,7 @@ description: | Supported values for languages are `c`, `cpp` (for `C++`), `cuda`, `cython`, `d`, `objc`, `objcpp`, `fortran`, `java`, `cs` (for `C#`), - `vala` and `rust`. + `swift`, `nasm`, `masm`, `linearasm`, `vala` and `rust`. posargs: project_name: @@ -38,22 +38,26 @@ varargs: kwargs: default_options: - type: list[str] | dict[str | bool | int | list[str]] + type: array[str] | dict[str | bool | int | array[str]] description: | Accepts strings in the form `key=value` which have the same format as options to `meson configure`. For example to set the default project type you would set this: `default_options : ['buildtype=debugoptimized']`. Note that these settings are only used when running Meson for the first - time. Global options such as `buildtype` can only be specified in - the master project, settings in subprojects are ignored. Project - specific options are used normally even in subprojects. + time. Note that some options can override the default behavior; for example, using `c_args` here means that the `CFLAGS` environment variable is not used. Consider using [[add_project_arguments()]] instead. + Also note that not all options are taken into account when + building as a subproject, and the exact set of options + that are per-subproject has increased over time; for more + information, see [core options](Builtin-options.md#core-options) + and [compiler options](Builtin-options.md#compiler-options). + *(since 1.2.0)*: A dictionary may now be passed. version: @@ -72,7 +76,7 @@ kwargs: Usually something like `>=0.28.0`. license: - type: str | list[str] + type: str | array[str] description: | Takes a string or array of strings describing the license(s) the code is under. @@ -94,7 +98,7 @@ kwargs: value in your Meson build files with `meson.project_license()`. license_files: - type: str | list[str] + type: str | array[str] since: 1.1.0 description: | Takes a string or array of strings with the paths to the license file(s) diff --git a/docs/yaml/functions/run_command.yaml b/docs/yaml/functions/run_command.yaml index 5803f82..1c9cc53 100644 --- a/docs/yaml/functions/run_command.yaml +++ b/docs/yaml/functions/run_command.yaml @@ -40,7 +40,7 @@ kwargs: empty string. env: - type: env | list[str] | dict[str] + type: env | array[str] | dict[str] since: 0.50.0 description: | environment variables to set, diff --git a/docs/yaml/functions/run_target.yaml b/docs/yaml/functions/run_target.yaml index 3ede1c9..bf670d8 100644 --- a/docs/yaml/functions/run_target.yaml +++ b/docs/yaml/functions/run_target.yaml @@ -31,25 +31,25 @@ posargs: kwargs: command: - type: list[exe| external_program | custom_tgt | file | str] + type: array[exe| external_program | custom_tgt | file | str] description: | - A list containing the command to run and the arguments - to pass to it. Each list item may be a string or a target. For + An array containing the command to run and the arguments + to pass to it. Each array element may be a string or a target. For instance, passing the return value of [[executable]] as the first item will run that executable, or passing a string as the first item will find that command in `PATH` and run it. depends: - type: list[build_tgt | custom_tgt | custom_idx] + type: array[build_tgt | custom_tgt | custom_idx] description: | - A list of targets that this target depends on but which + An array of targets that this target depends on but which are not listed in the command array (because, for example, the script does file globbing internally, custom_idx was not possible as a type between 0.60 and 1.4.0). env: since: 0.57.0 - type: env | list[str] | dict[str] + type: env | array[str] | dict[str] description: | environment variables to set, such as `{'NAME1': 'value1', 'NAME2': 'value2'}` or `['NAME1=value1', 'NAME2=value2']`, diff --git a/docs/yaml/functions/shared_library.yaml b/docs/yaml/functions/shared_library.yaml index f633aca..c3ea77a 100644 --- a/docs/yaml/functions/shared_library.yaml +++ b/docs/yaml/functions/shared_library.yaml @@ -29,13 +29,13 @@ kwargs: `soversion` is not defined, it is set to `3`. darwin_versions: - type: str | int | list[str] + type: str | int | array[str] since: 0.48.0 description: | Defines the `compatibility version` and `current version` for the dylib on macOS. - If a list is specified, it must be + If an array is specified, it must be either zero, one, or two elements. If only one element is specified - or if it's not a list, the specified value will be used for setting + or if it's not an array the specified value will be used for setting both compatibility version and current version. If unspecified, the `soversion` will be used as per the aforementioned rules. @@ -54,3 +54,9 @@ kwargs: Set the specific ABI to compile (when compiling rust). - 'rust' (default): Create a "dylib" crate. - 'c': Create a "cdylib" crate. + + shortname: + type: str + since: 1.10.0 + description: | + A string specifying a DLL name fitting to 8.3 limit on OS/2 of this shared library. diff --git a/docs/yaml/functions/structured_sources.yaml b/docs/yaml/functions/structured_sources.yaml index a5f0a83..0dbf585 100644 --- a/docs/yaml/functions/structured_sources.yaml +++ b/docs/yaml/functions/structured_sources.yaml @@ -10,7 +10,7 @@ description: | posargs: root: - type: list[str | file | custom_tgt | custom_idx | generated_list] + type: array[str | file | custom_tgt | custom_idx | generated_list] description: Sources to put at the root of the generated structure optargs: diff --git a/docs/yaml/functions/subdir.yaml b/docs/yaml/functions/subdir.yaml index 428563b..96e17ba 100644 --- a/docs/yaml/functions/subdir.yaml +++ b/docs/yaml/functions/subdir.yaml @@ -21,6 +21,6 @@ posargs: kwargs: if_found: - type: list[dep] + type: array[dep] since: 0.44.0 description: Only enter the subdir if all [[dep.found]] methods return `true`. diff --git a/docs/yaml/functions/subproject.yaml b/docs/yaml/functions/subproject.yaml index bccac79..508837c 100644 --- a/docs/yaml/functions/subproject.yaml +++ b/docs/yaml/functions/subproject.yaml @@ -42,7 +42,7 @@ posargs: kwargs: default_options: - type: list[str] | dict[str | bool | int | list[str]] + type: array[str] | dict[str | bool | int | array[str]] since: 0.37.0 description: | An array of default option values diff --git a/docs/yaml/functions/summary.yaml b/docs/yaml/functions/summary.yaml index cf63dcd..b531282 100644 --- a/docs/yaml/functions/summary.yaml +++ b/docs/yaml/functions/summary.yaml @@ -16,7 +16,7 @@ description: | - an integer, boolean or string - *since 0.57.0* an external program or a dependency - *since 0.58.0* a feature option - - a list of those. + - an array of those. Instead of calling summary as `summary(key, value)`, it is also possible to directly pass a dictionary to the [[summary]] function, as seen in the example @@ -38,7 +38,7 @@ example: | summary({'Some boolean': false, 'Another boolean': true, 'Some string': 'Hello World', - 'A list': ['string', 1, true], + 'An array': ['string', 1, true], }, section: 'Configuration') ``` @@ -56,7 +56,7 @@ example: | Some boolean : False Another boolean: True Some string : Hello World - A list : string + An array : string 1 True ``` @@ -65,7 +65,7 @@ arg_flattening: false posargs: key_or_dict: - type: str | dict[str | bool | int | dep | external_program | list[str | bool | int | dep | external_program]] + type: str | dict[str | bool | int | dep | external_program | array[str | bool | int | dep | external_program]] description: | The name of the new entry, or a dict containing multiple entries. If a dict is passed it is equivalent to calling summary() once for each @@ -74,7 +74,7 @@ posargs: optargs: value: - type: str | bool | int | dep | external_program | list[str | bool | int | dep | external_program] + type: str | bool | int | dep | external_program | array[str | bool | int | dep | external_program] description: | The value to print for the `key`. Only valid if `key_or_dict` is a str. @@ -92,5 +92,5 @@ kwargs: type: str since: 0.54.0 description: | - The separator to use when printing list values in this summary. If no - separator is given, each list item will be printed on its own line. + The separator to use when printing array values in this summary. If no + separator is given, each array item will be printed on its own line. diff --git a/docs/yaml/functions/vcs_tag.yaml b/docs/yaml/functions/vcs_tag.yaml index 3a35684..bf223e7 100644 --- a/docs/yaml/functions/vcs_tag.yaml +++ b/docs/yaml/functions/vcs_tag.yaml @@ -22,7 +22,7 @@ description: | kwargs: command: - type: list[exe | external_program | custom_tgt | file | str] + type: array[exe | external_program | custom_tgt | file | str] description: | The command to execute, see [[custom_target]] for details on how this command must be specified. @@ -71,7 +71,7 @@ kwargs: The subdirectory to install the generated file to (e.g. `share/myproject`). install_mode: - type: list[str | int] + type: array[str | int] since: 1.7.0 description: | Specify the file mode in symbolic format diff --git a/docs/yaml/objects/build_tgt.yaml b/docs/yaml/objects/build_tgt.yaml index 73b9b5d..3fed56c 100644 --- a/docs/yaml/objects/build_tgt.yaml +++ b/docs/yaml/objects/build_tgt.yaml @@ -77,3 +77,27 @@ methods: objects feature compatible with [[@external_program]] objects. This simplifies use-cases where an executable is used instead of an [[@external_program]]. + +- name: vala_header + returns: file + since: 1.10.0 + description: | + Returns a [[@file]] object pointing to a C compatible header generated by + the vala compiler, if this target does not generate a vala header then it is + an error to call this method. + +- name: vala_vapi + returns: file + since: 1.10.0 + description: | + Returns a [[@file]] object pointing to a VAPI header generated by the vala + compiler, if this target does not generate a VAPI file then it is an error + to call this method. + +- name: vala_gir + returns: file + since: 1.10.0 + description: | + Returns a [[@file]] object pointing to a GIR file generated by the vala + compiler, if this target does not generate a GIR file then it is an error + to call this method. diff --git a/docs/yaml/objects/cfg_data.yaml b/docs/yaml/objects/cfg_data.yaml index 03abb17..36f9755 100644 --- a/docs/yaml/objects/cfg_data.yaml +++ b/docs/yaml/objects/cfg_data.yaml @@ -114,7 +114,7 @@ methods: description: The name of the variable to query - name: keys - returns: list[str] + returns: array[str] since: 0.57.0 description: | Returns an array of keys of diff --git a/docs/yaml/objects/compiler.yaml b/docs/yaml/objects/compiler.yaml index 43831d2..c676180 100644 --- a/docs/yaml/objects/compiler.yaml +++ b/docs/yaml/objects/compiler.yaml @@ -45,9 +45,9 @@ methods: description: You have found a bug if you can see this! kwargs: args: - type: list[str] + type: array[str] description: | - Used to pass a list of compiler arguments. + Used to pass an array of compiler arguments. Defining include paths for headers not in the default include path via `-Isome/path/to/header` is generally supported, however, usually not recommended. @@ -61,16 +61,19 @@ methods: description: You have found a bug if you can see this! kwargs: include_directories: - type: inc | list[inc] + type: array[inc | str] since: 0.38.0 - description: Extra directories for header searches. + description: | + Extra directories for header searches, created with the + [[include_directories]] function. + *(Since 1.10.0)* Strings are also accepted. - name: _dependencies returns: void description: You have found a bug if you can see this! kwargs: dependencies: - type: dep | list[dep] + type: dep | array[dep] description: Additionally dependencies required for compiling and / or linking. - name: _prefix @@ -78,7 +81,7 @@ methods: description: You have found a bug if you can see this! kwargs: prefix: - type: str | list[str] + type: str | array[str] description: | Used to add `#include`s and other things that are required for the symbol to be declared. Since 1.0.0 an array is accepted @@ -184,7 +187,7 @@ methods: description: Returns the compiler's version number as a string. - name: cmd_array - returns: list[str] + returns: array[str] description: Returns an array containing the command(s) for the compiler. @@ -441,10 +444,10 @@ methods: *(since 0.47.0)* The value of a `feature` option can also be passed here. has_headers: - type: list[str] + type: array[str] since: 0.50.0 description: | - List of headers that must be found as well. + An array of headers that must be found as well. This check is equivalent to checking each header with a [[compiler.has_header]] call. @@ -468,7 +471,7 @@ methods: description: If `true`, this method will return a [[@disabler]] on a failed check. dirs: - type: list[str] + type: array[str] description: | Additional directories to search in. @@ -478,19 +481,20 @@ methods: # does not work, since all _common kwargs need to be prefixed `header_` here # kwargs_inherit: compiler._common header_args: - type: list[str] + type: array[str] since: 0.51.0 description: | When the `has_headers` kwarg is also used, this argument is passed to [[compiler.has_header]] as `args`. header_include_directories: - type: inc | list[inc] + type: array[inc | str] since: 0.51.0 description: | When the `has_headers` kwarg is also used, this argument is passed to [[compiler.has_header]] as `include_directories`. + *(Since 1.10.0)* Strings are also accepted. header_dependencies: - type: dep | list[dep] + type: dep | array[dep] since: 0.51.0 description: | When the `has_headers` kwarg is also used, this argument is passed to @@ -539,7 +543,7 @@ methods: - compiler._required - name: get_supported_arguments - returns: list[str] + returns: array[str] since: 0.43.0 varargs_inherit: compiler.has_multi_arguments description: | @@ -558,11 +562,11 @@ methods: - `'require'`: Abort if at least one argument is not supported - name: first_supported_argument - returns: list[str] + returns: array[str] since: 0.43.0 varargs_inherit: compiler.has_multi_arguments description: | - Given a list of strings, returns a single-element list containing the first + Given an array of strings, returns a single-element array containing the first argument that passes the [[compiler.has_argument]] test or an empty array if none pass. @@ -601,7 +605,7 @@ methods: - compiler._required - name: get_supported_link_arguments - returns: list[str] + returns: array[str] since: 0.46.0 varargs_inherit: compiler.has_multi_link_arguments description: | @@ -621,11 +625,11 @@ methods: # - `'require'`: Abort if at least one argument is not supported - name: first_supported_link_argument - returns: list[str] + returns: array[str] since: 0.46.0 varargs_inherit: compiler.has_multi_link_arguments description: | - Given a list of strings, returns the first argument that passes the + Given an array of strings, returns the first argument that passes the [[compiler.has_link_argument]] test or an empty array if none pass. - name: has_function_attribute @@ -645,7 +649,7 @@ methods: - compiler._required - name: get_supported_function_attributes - returns: list[str] + returns: array[str] since: 0.48.0 description: | Returns an array containing any names that are supported GCC style attributes. @@ -666,10 +670,10 @@ methods: operating systems. - name: preprocess - returns: list[custom_idx] + returns: array[custom_idx] since: 0.64.0 description: | - Preprocess a list of source files but do not compile them. The preprocessor + Preprocess an array of source files but do not compile them. The preprocessor will receive the same arguments (include directories, defines, etc) as with normal compilation. That includes for example args added with `add_project_arguments()`, or on the command line with `-Dc_args=-DFOO`. @@ -694,15 +698,15 @@ methods: the source filename and `@BASENAME@` is replaced by the source filename without its extension. compile_args: - type: list[str] + type: array[str] description: | Extra flags to pass to the preprocessor dependencies: - type: dep | list[dep] + type: dep | array[dep] description: Additionally dependencies required. since: 1.1.0 depends: - type: list[build_tgt | custom_tgt] + type: array[build_tgt | custom_tgt] description: | Specifies that this target depends on the specified target(s). These targets should be built before starting diff --git a/docs/yaml/objects/custom_tgt.yaml b/docs/yaml/objects/custom_tgt.yaml index 5102ab9..60e067c 100644 --- a/docs/yaml/objects/custom_tgt.yaml +++ b/docs/yaml/objects/custom_tgt.yaml @@ -25,9 +25,9 @@ methods: custom target's output argument. - name: to_list - returns: list[custom_idx] + returns: array[custom_idx] since: 0.54.0 description: | - Returns a list of opaque objects that references this target, + Returns an array of opaque objects that references this target, and can be used as a source in other targets. This can be used to iterate outputs with `foreach` loop. diff --git a/docs/yaml/objects/dep.yaml b/docs/yaml/objects/dep.yaml index ffd19f7..fdf5fe5 100644 --- a/docs/yaml/objects/dep.yaml +++ b/docs/yaml/objects/dep.yaml @@ -34,11 +34,11 @@ methods: kwargs: define_variable: - type: list[str] + type: array[str] since: 0.44.0 description: | You can also redefine a - variable by passing a list to this kwarg + variable by passing an array to this kwarg that can affect the retrieved variable: `['prefix', '/'])`. *(Since 1.3.0)* Multiple variables can be specified in pairs. @@ -224,7 +224,7 @@ methods: description: The default value to return when the variable does not exist pkgconfig_define: - type: list[str] + type: array[str] description: See [[dep.get_pkgconfig_variable]] - name: as_static @@ -250,4 +250,3 @@ methods: recursive: type: bool description: If true, this is recursively applied to dependencies -
\ No newline at end of file diff --git a/docs/yaml/objects/external_program.yaml b/docs/yaml/objects/external_program.yaml index 4c24497..db5d39f 100644 --- a/docs/yaml/objects/external_program.yaml +++ b/docs/yaml/objects/external_program.yaml @@ -56,3 +56,8 @@ methods: ```meson run_command(find_program('foo'), 'arg1', 'arg2') ``` + +- name: cmd_array + returns: array[str] + description: Returns an array containing the command(s) for the program. + since: 1.10.0 diff --git a/docs/yaml/objects/generator.yaml b/docs/yaml/objects/generator.yaml index fbef95f..3dbcdd5 100644 --- a/docs/yaml/objects/generator.yaml +++ b/docs/yaml/objects/generator.yaml @@ -9,20 +9,20 @@ methods: - name: process returns: generated_list description: | - Takes a list of files, causes them to be processed and returns an object containing the result + Takes an array of files, causes them to be processed and returns an object containing the result which can then, for example, be passed into a build target definition. varargs: name: source min_varargs: 1 type: str | file | custom_tgt | custom_idx | generated_list - description: List of sources to process. + description: sources to process. kwargs: extra_args: - type: list[str] + type: array[str] description: | - If present, will be used to replace an entry `@EXTRA_ARGS@` in the argument list. + If present, will be used to replace an entry `@EXTRA_ARGS@` in the argument array. preserve_path_from: type: str @@ -36,7 +36,7 @@ methods: directory}/one.out`. env: - type: env | list[str] | dict[str] + type: env | array[str] | dict[str] since: 1.3.0 description: | environment variables to set, such as diff --git a/man/meson.1 b/man/meson.1 index b78cf96..f529784 100644 --- a/man/meson.1 +++ b/man/meson.1 @@ -1,4 +1,4 @@ -.TH MESON "1" "April 2025" "meson 1.8.0" "User Commands" +.TH MESON "1" "December 2025" "meson 1.10.0" "User Commands" .SH NAME meson - a high productivity build system .SH DESCRIPTION @@ -679,6 +679,8 @@ could not rebuild the required targets. .SH SEE ALSO -http://mesonbuild.com/ +https://mesonbuild.com/ https://wrapdb.mesonbuild.com/ + +.MR meson-reference 3 diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py index cd8156a..d3afb4d 100644 --- a/mesonbuild/ast/interpreter.py +++ b/mesonbuild/ast/interpreter.py @@ -8,8 +8,12 @@ from __future__ import annotations import os import sys import typing as T +from collections import defaultdict +from dataclasses import dataclass +import itertools +from pathlib import Path -from .. import mparser, mesonlib +from .. import mparser, mesonlib, mlog from .. import environment from ..interpreterbase import ( @@ -20,8 +24,14 @@ from ..interpreterbase import ( ContinueRequest, Disabler, default_resolve_key, + is_disabled, + UnknownValue, + UndefinedVariable, + InterpreterObject, ) +from ..interpreterbase.helpers import flatten + from ..interpreter import ( StringHolder, BooleanHolder, @@ -36,19 +46,21 @@ from ..mparser import ( ArrayNode, AssignmentNode, BaseNode, - ElementaryNode, EmptyNode, IdNode, MethodNode, NotNode, PlusAssignmentNode, TernaryNode, + SymbolNode, + Token, + FunctionNode, ) if T.TYPE_CHECKING: from .visitor import AstVisitor from ..interpreter import Interpreter - from ..interpreterbase import SubProject, TYPE_nkwargs, TYPE_var + from ..interpreterbase import SubProject, TYPE_var, TYPE_nvar from ..mparser import ( AndNode, ComparisonNode, @@ -60,38 +72,128 @@ if T.TYPE_CHECKING: UMinusNode, ) -class DontCareObject(MesonInterpreterObject): - pass - -class MockExecutable(MesonInterpreterObject): - pass - -class MockStaticLibrary(MesonInterpreterObject): - pass - -class MockSharedLibrary(MesonInterpreterObject): - pass - -class MockCustomTarget(MesonInterpreterObject): - pass - -class MockRunTarget(MesonInterpreterObject): - pass - -ADD_SOURCE = 0 -REMOVE_SOURCE = 1 - _T = T.TypeVar('_T') _V = T.TypeVar('_V') +def _symbol(val: str) -> SymbolNode: + return SymbolNode(Token('', '', 0, 0, 0, (0, 0), val)) + +# `IntrospectionFile` is to the `IntrospectionInterpreter` what `File` is to the normal `Interpreter`. +@dataclass +class IntrospectionFile: + subdir: str + rel: str + + def to_abs_path(self, root_dir: Path) -> Path: + return (root_dir / self.subdir / self.rel).resolve() + + def __hash__(self) -> int: + return hash((self.__class__.__name__, self.subdir, self.rel)) + +# `IntrospectionDependency` is to the `IntrospectionInterpreter` what `Dependency` is to the normal `Interpreter`. +@dataclass +class IntrospectionDependency(MesonInterpreterObject): + name: T.Union[str, UnknownValue] + required: T.Union[bool, UnknownValue] + version: T.Union[T.List[str], UnknownValue] + has_fallback: bool + conditional: bool + node: FunctionNode + +# `IntrospectionBuildTarget` is to the `IntrospectionInterpreter` what `BuildTarget` is to the normal `Interpreter`. +@dataclass +class IntrospectionBuildTarget(MesonInterpreterObject): + name: str + machine: str + id: str + typename: str + defined_in: str + subdir: str + build_by_default: T.Union[bool, UnknownValue] + installed: T.Union[bool, UnknownValue] + outputs: T.List[str] + source_nodes: T.List[BaseNode] + extra_files: BaseNode + kwargs: T.Dict[str, TYPE_var] + node: FunctionNode + +def is_ignored_edge(src: T.Union[BaseNode, UnknownValue]) -> bool: + return (isinstance(src, FunctionNode) and src.func_name.value not in {'files', 'get_variable'}) or isinstance(src, MethodNode) + +class DataflowDAG: + src_to_tgts: T.DefaultDict[T.Union[BaseNode, UnknownValue], T.Set[T.Union[BaseNode, UnknownValue]]] + tgt_to_srcs: T.DefaultDict[T.Union[BaseNode, UnknownValue], T.Set[T.Union[BaseNode, UnknownValue]]] + + def __init__(self) -> None: + self.src_to_tgts = defaultdict(set) + self.tgt_to_srcs = defaultdict(set) + + def add_edge(self, source: T.Union[BaseNode, UnknownValue], target: T.Union[BaseNode, UnknownValue]) -> None: + self.src_to_tgts[source].add(target) + self.tgt_to_srcs[target].add(source) + + # Returns all nodes in the DAG that are reachable from a node in `srcs`. + # In other words, A node `a` is part of the returned set exactly if data + # from `srcs` flows into `a`, directly or indirectly. + # Certain edges are ignored. + def reachable(self, srcs: T.Set[T.Union[BaseNode, UnknownValue]], reverse: bool) -> T.Set[T.Union[BaseNode, UnknownValue]]: + reachable = srcs.copy() + active = srcs.copy() + while active: + new: T.Set[T.Union[BaseNode, UnknownValue]] = set() + if reverse: + for tgt in active: + new.update(src for src in self.tgt_to_srcs[tgt] if not is_ignored_edge(src)) + else: + for src in active: + if is_ignored_edge(src): + continue + new.update(tgt for tgt in self.src_to_tgts[src]) + reachable.update(new) + active = new + return reachable + + # Returns all paths from src to target. + # Certain edges are ignored. + def find_all_paths(self, src: T.Union[BaseNode, UnknownValue], target: T.Union[BaseNode, UnknownValue]) -> T.List[T.List[T.Union[BaseNode, UnknownValue]]]: + queue = [(src, [src])] + paths = [] + while queue: + cur, path = queue.pop() + if cur == target: + paths.append(path) + if is_ignored_edge(cur): + continue + queue.extend((tgt, path + [tgt]) for tgt in self.src_to_tgts[cur]) + return paths class AstInterpreter(InterpreterBase): def __init__(self, source_root: str, subdir: str, subproject: SubProject, subproject_dir: str, env: environment.Environment, visitors: T.Optional[T.List[AstVisitor]] = None): super().__init__(source_root, subdir, subproject, subproject_dir, env) self.visitors = visitors if visitors is not None else [] - self.assignments: T.Dict[str, BaseNode] = {} - self.assign_vals: T.Dict[str, T.Any] = {} - self.reverse_assignment: T.Dict[str, BaseNode] = {} + self.nesting: T.List[int] = [] + self.cur_assignments: T.DefaultDict[str, T.List[T.Tuple[T.List[int], T.Union[BaseNode, UnknownValue]]]] = defaultdict(list) + self.all_assignment_nodes: T.DefaultDict[str, T.List[AssignmentNode]] = defaultdict(list) + # dataflow_dag is an acyclic directed graph that contains an edge + # from one instance of `BaseNode` to another instance of `BaseNode` if + # data flows directly from one to the other. Example: If meson.build + # contains this: + # var = 'foo' + '123' + # executable(var, 'src.c') + # var = 'bar' + # dataflow_dag will contain an edge from the IdNode corresponding to + # 'var' in line 2 to the ArithmeticNode corresponding to 'foo' + '123'. + # This graph is crucial for e.g. node_to_runtime_value because we have + # to know that 'var' in line2 is 'foo123' and not 'bar'. + self.dataflow_dag = DataflowDAG() + self.funcvals: T.Dict[BaseNode, T.Any] = {} + self.tainted = False + self.predefined_vars = { + 'meson': UnknownValue(), + 'host_machine': UnknownValue(), + 'build_machine': UnknownValue(), + 'target_machine': UnknownValue() + } self.funcs.update({'project': self.func_do_nothing, 'test': self.func_do_nothing, 'benchmark': self.func_do_nothing, @@ -124,7 +226,7 @@ class AstInterpreter(InterpreterBase): 'vcs_tag': self.func_do_nothing, 'add_languages': self.func_do_nothing, 'declare_dependency': self.func_do_nothing, - 'files': self.func_do_nothing, + 'files': self.func_files, 'executable': self.func_do_nothing, 'static_library': self.func_do_nothing, 'shared_library': self.func_do_nothing, @@ -133,9 +235,9 @@ class AstInterpreter(InterpreterBase): 'custom_target': self.func_do_nothing, 'run_target': self.func_do_nothing, 'subdir': self.func_subdir, - 'set_variable': self.func_do_nothing, - 'get_variable': self.func_do_nothing, - 'unset_variable': self.func_do_nothing, + 'set_variable': self.func_set_variable, + 'get_variable': self.func_get_variable, + 'unset_variable': self.func_unset_variable, 'is_disabler': self.func_do_nothing, 'is_variable': self.func_do_nothing, 'disabler': self.func_do_nothing, @@ -153,14 +255,14 @@ class AstInterpreter(InterpreterBase): 'debug': self.func_do_nothing, }) - def _unholder_args(self, args: _T, kwargs: _V) -> T.Tuple[_T, _V]: + def _unholder_args(self, args: T.Any, kwargs: T.Any) -> T.Tuple[T.Any, T.Any]: return args, kwargs - def _holderify(self, res: _T) -> _T: + def _holderify(self, res: T.Any) -> T.Any: return res - def func_do_nothing(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> bool: - return True + def func_do_nothing(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> UnknownValue: + return UnknownValue() def load_root_meson_file(self) -> None: super().load_root_meson_file() @@ -182,24 +284,50 @@ class AstInterpreter(InterpreterBase): buildfilename = os.path.join(subdir, environment.build_filename) sys.stderr.write(f'Unable to find build file {buildfilename} --> Skipping\n') - def method_call(self, node: BaseNode) -> bool: - return True + def inner_method_call(self, obj: BaseNode, method_name: str, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Any: + for arg in itertools.chain(args, kwargs.values()): + if isinstance(arg, UnknownValue): + return UnknownValue() + + if isinstance(obj, str): + result = StringHolder(obj, T.cast('Interpreter', self)).method_call(method_name, args, kwargs) + elif isinstance(obj, bool): + result = BooleanHolder(obj, T.cast('Interpreter', self)).method_call(method_name, args, kwargs) + elif isinstance(obj, int): + result = IntegerHolder(obj, T.cast('Interpreter', self)).method_call(method_name, args, kwargs) + elif isinstance(obj, list): + result = ArrayHolder(obj, T.cast('Interpreter', self)).method_call(method_name, args, kwargs) + elif isinstance(obj, dict): + result = DictHolder(obj, T.cast('Interpreter', self)).method_call(method_name, args, kwargs) + else: + return UnknownValue() + return result - def evaluate_fstring(self, node: mparser.StringNode) -> str: - assert isinstance(node, mparser.StringNode) - return node.value + def method_call(self, node: mparser.MethodNode) -> None: + invocable = node.source_object + self.evaluate_statement(invocable) + obj = self.node_to_runtime_value(invocable) + method_name = node.name.value + (args, kwargs) = self.reduce_arguments(node.args) + if is_disabled(args, kwargs): + res = Disabler() + else: + res = self.inner_method_call(obj, method_name, args, kwargs) + self.funcvals[node] = res - def evaluate_arraystatement(self, cur: mparser.ArrayNode) -> TYPE_var: - return self.reduce_arguments(cur.args)[0] + def evaluate_fstring(self, node: mparser.StringNode) -> None: + pass + + def evaluate_arraystatement(self, cur: mparser.ArrayNode) -> None: + for arg in cur.args.arguments: + self.evaluate_statement(arg) - def evaluate_arithmeticstatement(self, cur: ArithmeticNode) -> int: + def evaluate_arithmeticstatement(self, cur: ArithmeticNode) -> None: self.evaluate_statement(cur.left) self.evaluate_statement(cur.right) - return 0 - def evaluate_uminusstatement(self, cur: UMinusNode) -> int: + def evaluate_uminusstatement(self, cur: UMinusNode) -> None: self.evaluate_statement(cur.value) - return 0 def evaluate_ternary(self, node: TernaryNode) -> None: assert isinstance(node, TernaryNode) @@ -207,42 +335,27 @@ class AstInterpreter(InterpreterBase): self.evaluate_statement(node.trueblock) self.evaluate_statement(node.falseblock) - def evaluate_dictstatement(self, node: mparser.DictNode) -> TYPE_nkwargs: - def resolve_key(node: mparser.BaseNode) -> str: - if isinstance(node, mparser.StringNode): - return node.value - return '__AST_UNKNOWN__' - arguments, kwargs = self.reduce_arguments(node.args, key_resolver=resolve_key) - assert not arguments - self.argument_depth += 1 - for key, value in kwargs.items(): - if isinstance(key, BaseNode): - self.evaluate_statement(key) - self.argument_depth -= 1 - return {} + def evaluate_dictstatement(self, node: mparser.DictNode) -> None: + for k, v in node.args.kwargs.items(): + self.evaluate_statement(k) + self.evaluate_statement(v) - def evaluate_plusassign(self, node: PlusAssignmentNode) -> None: - assert isinstance(node, PlusAssignmentNode) - # Cheat by doing a reassignment - self.assignments[node.var_name.value] = node.value # Save a reference to the value node - if node.value.ast_id: - self.reverse_assignment[node.value.ast_id] = node - self.assign_vals[node.var_name.value] = self.evaluate_statement(node.value) - - def evaluate_indexing(self, node: IndexNode) -> int: - return 0 - - def unknown_function_called(self, func_name: str) -> None: - pass + def evaluate_indexing(self, node: IndexNode) -> None: + self.evaluate_statement(node.iobject) + self.evaluate_statement(node.index) def reduce_arguments( self, args: mparser.ArgumentNode, key_resolver: T.Callable[[mparser.BaseNode], str] = default_resolve_key, duplicate_key_error: T.Optional[str] = None, - ) -> T.Tuple[T.List[TYPE_var], TYPE_nkwargs]: + ) -> T.Tuple[T.List[T.Any], T.Any]: + for arg in args.arguments: + self.evaluate_statement(arg) + for value in args.kwargs.values(): + self.evaluate_statement(value) if isinstance(args, ArgumentNode): - kwargs: T.Dict[str, TYPE_var] = {} + kwargs = {} for key, val in args.kwargs.items(): kwargs[key_resolver(key)] = val if args.incorrect_order(): @@ -251,158 +364,417 @@ class AstInterpreter(InterpreterBase): else: return self.flatten_args(args), {} - def evaluate_comparison(self, node: ComparisonNode) -> bool: + def evaluate_comparison(self, node: ComparisonNode) -> None: self.evaluate_statement(node.left) self.evaluate_statement(node.right) - return False - def evaluate_andstatement(self, cur: AndNode) -> bool: + def evaluate_andstatement(self, cur: AndNode) -> None: self.evaluate_statement(cur.left) self.evaluate_statement(cur.right) - return False - def evaluate_orstatement(self, cur: OrNode) -> bool: + def evaluate_orstatement(self, cur: OrNode) -> None: self.evaluate_statement(cur.left) self.evaluate_statement(cur.right) - return False - def evaluate_notstatement(self, cur: NotNode) -> bool: + def evaluate_notstatement(self, cur: NotNode) -> None: self.evaluate_statement(cur.value) - return False + + def find_potential_writes(self, node: BaseNode) -> T.Set[str]: + if isinstance(node, mparser.ForeachClauseNode): + return {el.value for el in node.varnames} | self.find_potential_writes(node.block) + elif isinstance(node, mparser.CodeBlockNode): + ret = set() + for line in node.lines: + ret.update(self.find_potential_writes(line)) + return ret + elif isinstance(node, (AssignmentNode, PlusAssignmentNode)): + return set([node.var_name.value]) | self.find_potential_writes(node.value) + elif isinstance(node, IdNode): + return set() + elif isinstance(node, ArrayNode): + ret = set() + for arg in node.args.arguments: + ret.update(self.find_potential_writes(arg)) + return ret + elif isinstance(node, mparser.DictNode): + ret = set() + for k, v in node.args.kwargs.items(): + ret.update(self.find_potential_writes(k)) + ret.update(self.find_potential_writes(v)) + return ret + elif isinstance(node, FunctionNode): + ret = set() + for arg in node.args.arguments: + ret.update(self.find_potential_writes(arg)) + for arg in node.args.kwargs.values(): + ret.update(self.find_potential_writes(arg)) + return ret + elif isinstance(node, MethodNode): + ret = self.find_potential_writes(node.source_object) + for arg in node.args.arguments: + ret.update(self.find_potential_writes(arg)) + for arg in node.args.kwargs.values(): + ret.update(self.find_potential_writes(arg)) + return ret + elif isinstance(node, ArithmeticNode): + return self.find_potential_writes(node.left) | self.find_potential_writes(node.right) + elif isinstance(node, (mparser.NumberNode, mparser.StringNode, mparser.BreakNode, mparser.BooleanNode, mparser.ContinueNode)): + return set() + elif isinstance(node, mparser.IfClauseNode): + if isinstance(node.elseblock, EmptyNode): + ret = set() + else: + ret = self.find_potential_writes(node.elseblock.block) + for i in node.ifs: + ret.update(self.find_potential_writes(i)) + return ret + elif isinstance(node, mparser.IndexNode): + return self.find_potential_writes(node.iobject) | self.find_potential_writes(node.index) + elif isinstance(node, mparser.IfNode): + return self.find_potential_writes(node.condition) | self.find_potential_writes(node.block) + elif isinstance(node, (mparser.ComparisonNode, mparser.OrNode, mparser.AndNode)): + return self.find_potential_writes(node.left) | self.find_potential_writes(node.right) + elif isinstance(node, mparser.NotNode): + return self.find_potential_writes(node.value) + elif isinstance(node, mparser.TernaryNode): + return self.find_potential_writes(node.condition) | self.find_potential_writes(node.trueblock) | self.find_potential_writes(node.falseblock) + elif isinstance(node, mparser.UMinusNode): + return self.find_potential_writes(node.value) + elif isinstance(node, mparser.ParenthesizedNode): + return self.find_potential_writes(node.inner) + raise mesonlib.MesonBugException('Unhandled node type') def evaluate_foreach(self, node: ForeachClauseNode) -> None: + asses = self.find_potential_writes(node) + for ass in asses: + self.cur_assignments[ass].append((self.nesting.copy(), UnknownValue())) try: self.evaluate_codeblock(node.block) except ContinueRequest: pass except BreakRequest: pass + for ass in asses: + self.cur_assignments[ass].append((self.nesting.copy(), UnknownValue())) # In case the foreach loops 0 times. def evaluate_if(self, node: IfClauseNode) -> None: + self.nesting.append(0) for i in node.ifs: self.evaluate_codeblock(i.block) + self.nesting[-1] += 1 if not isinstance(node.elseblock, EmptyNode): self.evaluate_codeblock(node.elseblock.block) - - def get_variable(self, varname: str) -> int: - return 0 - - def assignment(self, node: AssignmentNode) -> None: - assert isinstance(node, AssignmentNode) - self.assignments[node.var_name.value] = node.value # Save a reference to the value node - if node.value.ast_id: - self.reverse_assignment[node.value.ast_id] = node - self.assign_vals[node.var_name.value] = self.evaluate_statement(node.value) # Evaluate the value just in case - - def resolve_node(self, node: BaseNode, include_unknown_args: bool = False, id_loop_detect: T.Optional[T.List[str]] = None) -> T.Optional[T.Any]: - def quick_resolve(n: BaseNode, loop_detect: T.Optional[T.List[str]] = None) -> T.Any: - if loop_detect is None: - loop_detect = [] - if isinstance(n, IdNode): - assert isinstance(n.value, str) - if n.value in loop_detect or n.value not in self.assignments: - return [] - return quick_resolve(self.assignments[n.value], loop_detect = loop_detect + [n.value]) - elif isinstance(n, ElementaryNode): - return n.value + self.nesting.pop() + for var_name in self.cur_assignments: + potential_values = [] + oldval = self.get_cur_value_if_defined(var_name) + if not isinstance(oldval, UndefinedVariable): + potential_values.append(oldval) + for nesting, value in self.cur_assignments[var_name]: + if len(nesting) > len(self.nesting): + potential_values.append(value) + self.cur_assignments[var_name] = [(nesting, v) for (nesting, v) in self.cur_assignments[var_name] if len(nesting) <= len(self.nesting)] + if len(potential_values) > 1 or (len(potential_values) > 0 and isinstance(oldval, UndefinedVariable)): + uv = UnknownValue() + for pv in potential_values: + self.dataflow_dag.add_edge(pv, uv) + self.cur_assignments[var_name].append((self.nesting.copy(), uv)) + + def func_files(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Any: + ret: T.List[T.Union[IntrospectionFile, UnknownValue]] = [] + for arg in args: + if isinstance(arg, str): + ret.append(IntrospectionFile(self.subdir, arg)) + elif isinstance(arg, UnknownValue): + ret.append(UnknownValue()) else: - return n - - if id_loop_detect is None: - id_loop_detect = [] - result = None - - if not isinstance(node, BaseNode): - return None - - assert node.ast_id - if node.ast_id in id_loop_detect: - return None # Loop detected - id_loop_detect += [node.ast_id] - - # Try to evaluate the value of the node - if isinstance(node, IdNode): - result = quick_resolve(node) - - elif isinstance(node, ElementaryNode): - result = node.value - - elif isinstance(node, NotNode): - result = self.resolve_node(node.value, include_unknown_args, id_loop_detect) - if isinstance(result, bool): - result = not result - + raise TypeError + return ret + + def get_cur_value_if_defined(self, var_name: str) -> T.Union[BaseNode, UnknownValue, UndefinedVariable]: + if var_name in self.predefined_vars: + return self.predefined_vars[var_name] + ret: T.Union[BaseNode, UnknownValue, UndefinedVariable] = UndefinedVariable() + for nesting, value in reversed(self.cur_assignments[var_name]): + if len(self.nesting) >= len(nesting) and self.nesting[:len(nesting)] == nesting: + ret = value + break + if isinstance(ret, UndefinedVariable) and self.tainted: + return UnknownValue() + return ret + + def get_cur_value(self, var_name: str) -> T.Union[BaseNode, UnknownValue]: + ret = self.get_cur_value_if_defined(var_name) + if isinstance(ret, UndefinedVariable): + path = mlog.get_relative_path(Path(self.current_node.filename), Path(os.getcwd())) + mlog.warning(f"{path}:{self.current_node.lineno}:{self.current_node.colno} will always crash if executed, since a variable named `{var_name}` is not defined") + # We could add more advanced analysis of code referencing undefined + # variables, but it is probably not worth the effort and the + # complexity. So we do the simplest thing, returning an + # UnknownValue. + return UnknownValue() + return ret + + # The function `node_to_runtime_value` takes a node of the ast as an + # argument and tries to return the same thing that would be passed to e.g. + # `func_message` if you put `message(node)` in your `meson.build` file and + # run `meson setup`. If this is not possible, `UnknownValue()` is returned. + # There are 3 Reasons why this is sometimes impossible: + # 1. Because the meson rewriter is imperfect and has not implemented everything yet + # 2. Because the value is different on different machines, example: + # ```meson + # node = somedep.found() + # message(node) + # ``` + # will print `true` on some machines and `false` on others, so + # `node_to_runtime_value` does not know whether to return `true` or + # `false` and will return `UnknownValue()`. + # 3. Here: + # ```meson + # foreach x : [1, 2] + # node = x + # message(node) + # endforeach + # ``` + # `node_to_runtime_value` does not know whether to return `1` or `2` and + # will return `UnknownValue()`. + # + # If you have something like + # ``` + # node = [123, somedep.found()] + # ``` + # `node_to_runtime_value` will return `[123, UnknownValue()]`. + def node_to_runtime_value(self, node: T.Union[UnknownValue, BaseNode, TYPE_var]) -> T.Any: + if isinstance(node, (mparser.StringNode, mparser.BooleanNode, mparser.NumberNode)): + return node.value + elif isinstance(node, mparser.StringNode): + if node.is_fstring: + return UnknownValue() + else: + return node.value + elif isinstance(node, list): + return [self.node_to_runtime_value(x) for x in node] elif isinstance(node, ArrayNode): - result = node.args.arguments.copy() + return [self.node_to_runtime_value(x) for x in node.args.arguments] + elif isinstance(node, mparser.DictNode): + return {self.node_to_runtime_value(k): self.node_to_runtime_value(v) for k, v in node.args.kwargs.items()} + elif isinstance(node, IdNode): + assert len(self.dataflow_dag.tgt_to_srcs[node]) == 1 + val = next(iter(self.dataflow_dag.tgt_to_srcs[node])) + return self.node_to_runtime_value(val) + elif isinstance(node, (MethodNode, FunctionNode)): + funcval = self.funcvals[node] + if isinstance(funcval, (dict, str)): + return funcval + else: + return self.node_to_runtime_value(funcval) + elif isinstance(node, ArithmeticNode): + left = self.node_to_runtime_value(node.left) + right = self.node_to_runtime_value(node.right) + if isinstance(left, list) and isinstance(right, UnknownValue): + return left + [right] + if isinstance(right, list) and isinstance(left, UnknownValue): + return [left] + right + if isinstance(left, UnknownValue) or isinstance(right, UnknownValue): + return UnknownValue() + if node.operation == '+': + if isinstance(left, dict) and isinstance(right, dict): + ret = left.copy() + for k, v in right.items(): + ret[k] = v + return ret + if isinstance(left, list): + if not isinstance(right, list): + right = [right] + return left + right + return left + right + elif node.operation == '-': + return left - right + elif node.operation == '*': + return left * right + elif node.operation == '/': + if isinstance(left, int) and isinstance(right, int): + return left // right + elif isinstance(left, str) and isinstance(right, str): + return os.path.join(left, right).replace('\\', '/') + elif node.operation == '%': + if isinstance(left, int) and isinstance(right, int): + return left % right + elif isinstance(node, (UnknownValue, IntrospectionBuildTarget, IntrospectionFile, IntrospectionDependency, str, bool, int)): + return node + elif isinstance(node, mparser.IndexNode): + iobject = self.node_to_runtime_value(node.iobject) + index = self.node_to_runtime_value(node.index) + if isinstance(iobject, UnknownValue) or isinstance(index, UnknownValue): + return UnknownValue() + return iobject[index] + elif isinstance(node, mparser.ComparisonNode): + left = self.node_to_runtime_value(node.left) + right = self.node_to_runtime_value(node.right) + if isinstance(left, UnknownValue) or isinstance(right, UnknownValue): + return UnknownValue() + if node.ctype == '==': + return left == right + elif node.ctype == '!=': + return left != right + elif node.ctype == 'in': + return left in right + elif node.ctype == 'not in': + return left not in right + elif isinstance(node, mparser.TernaryNode): + cond = self.node_to_runtime_value(node.condition) + if isinstance(cond, UnknownValue): + return UnknownValue() + if cond is True: + return self.node_to_runtime_value(node.trueblock) + if cond is False: + return self.node_to_runtime_value(node.falseblock) + elif isinstance(node, mparser.OrNode): + left = self.node_to_runtime_value(node.left) + right = self.node_to_runtime_value(node.right) + if isinstance(left, UnknownValue) or isinstance(right, UnknownValue): + return UnknownValue() + return left or right + elif isinstance(node, mparser.AndNode): + left = self.node_to_runtime_value(node.left) + right = self.node_to_runtime_value(node.right) + if isinstance(left, UnknownValue) or isinstance(right, UnknownValue): + return UnknownValue() + return left and right + elif isinstance(node, mparser.UMinusNode): + val = self.node_to_runtime_value(node.value) + if isinstance(val, UnknownValue): + return val + if isinstance(val, (int, float)): + return -val + elif isinstance(node, mparser.NotNode): + val = self.node_to_runtime_value(node.value) + if isinstance(val, UnknownValue): + return val + if isinstance(val, bool): + return not val + elif isinstance(node, mparser.ParenthesizedNode): + return self.node_to_runtime_value(node.inner) + raise mesonlib.MesonBugException('Unhandled node type') - elif isinstance(node, ArgumentNode): - result = node.arguments.copy() + def assignment(self, node: AssignmentNode) -> None: + assert isinstance(node, AssignmentNode) + self.evaluate_statement(node.value) + self.cur_assignments[node.var_name.value].append((self.nesting.copy(), node.value)) + self.all_assignment_nodes[node.var_name.value].append(node) - elif isinstance(node, ArithmeticNode): - if node.operation != 'add': - return None # Only handle string and array concats - l = self.resolve_node(node.left, include_unknown_args, id_loop_detect) - r = self.resolve_node(node.right, include_unknown_args, id_loop_detect) - if isinstance(l, str) and isinstance(r, str): - result = l + r # String concatenation detected + def evaluate_plusassign(self, node: PlusAssignmentNode) -> None: + assert isinstance(node, PlusAssignmentNode) + self.evaluate_statement(node.value) + lhs = self.get_cur_value(node.var_name.value) + newval: T.Union[UnknownValue, ArithmeticNode] + if isinstance(lhs, UnknownValue): + newval = UnknownValue() + else: + newval = mparser.ArithmeticNode(operation='+', left=lhs, operator=_symbol('+'), right=node.value) + self.cur_assignments[node.var_name.value].append((self.nesting.copy(), newval)) + self.all_assignment_nodes[node.var_name.value].append(node) + + self.dataflow_dag.add_edge(lhs, newval) + self.dataflow_dag.add_edge(node.value, newval) + + def func_set_variable(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> None: + assert isinstance(node, FunctionNode) + if bool(node.args.kwargs): + raise InvalidArguments('set_variable accepts no keyword arguments') + if len(node.args.arguments) != 2: + raise InvalidArguments('set_variable requires exactly two positional arguments') + var_name = args[0] + value = node.args.arguments[1] + if isinstance(var_name, UnknownValue): + self.evaluate_statement(value) + self.tainted = True + return + assert isinstance(var_name, str) + equiv = AssignmentNode(var_name=IdNode(Token('', '', 0, 0, 0, (0, 0), var_name)), value=value, operator=_symbol('=')) + equiv.ast_id = str(id(equiv)) + self.evaluate_statement(equiv) + + def func_get_variable(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Any: + assert isinstance(node, FunctionNode) + var_name = args[0] + if isinstance(var_name, UnknownValue): + val: T.Union[UnknownValue, BaseNode] = UnknownValue() + else: + assert isinstance(var_name, str) + val = self.get_cur_value(var_name) + self.dataflow_dag.add_edge(val, node) + return val + + def func_unset_variable(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> None: + assert isinstance(node, FunctionNode) + if bool(node.args.kwargs): + raise InvalidArguments('unset_variable accepts no keyword arguments') + if len(node.args.arguments) != 1: + raise InvalidArguments('unset_variable requires exactly one positional arguments') + var_name = args[0] + assert isinstance(var_name, str) + self.cur_assignments[var_name].append((self.nesting.copy(), node)) + + def nodes_to_pretty_filelist(self, root_path: Path, subdir: str, nodes: T.List[BaseNode]) -> T.List[T.Union[str, UnknownValue]]: + def src_to_abs(src: T.Union[str, IntrospectionFile, UnknownValue]) -> T.Union[str, UnknownValue]: + if isinstance(src, str): + return os.path.normpath(os.path.join(root_path, subdir, src)) + elif isinstance(src, IntrospectionFile): + return str(src.to_abs_path(root_path)) + elif isinstance(src, UnknownValue): + return src else: - result = self.flatten_args(l, include_unknown_args, id_loop_detect) + self.flatten_args(r, include_unknown_args, id_loop_detect) - - elif isinstance(node, MethodNode): - src = quick_resolve(node.source_object) - margs = self.flatten_args(node.args.arguments, include_unknown_args, id_loop_detect) - mkwargs: T.Dict[str, TYPE_var] = {} - method_name = node.name.value - try: - if isinstance(src, str): - result = StringHolder(src, T.cast('Interpreter', self)).method_call(method_name, margs, mkwargs) - elif isinstance(src, bool): - result = BooleanHolder(src, T.cast('Interpreter', self)).method_call(method_name, margs, mkwargs) - elif isinstance(src, int): - result = IntegerHolder(src, T.cast('Interpreter', self)).method_call(method_name, margs, mkwargs) - elif isinstance(src, list): - result = ArrayHolder(src, T.cast('Interpreter', self)).method_call(method_name, margs, mkwargs) - elif isinstance(src, dict): - result = DictHolder(src, T.cast('Interpreter', self)).method_call(method_name, margs, mkwargs) - except mesonlib.MesonException: - return None - - # Ensure that the result is fully resolved (no more nodes) - if isinstance(result, BaseNode): - result = self.resolve_node(result, include_unknown_args, id_loop_detect) - elif isinstance(result, list): - new_res: T.List[TYPE_var] = [] - for i in result: - if isinstance(i, BaseNode): - resolved = self.resolve_node(i, include_unknown_args, id_loop_detect) - if resolved is not None: - new_res += self.flatten_args(resolved, include_unknown_args, id_loop_detect) - else: - new_res += [i] - result = new_res + raise TypeError - return result + rtvals: T.List[T.Any] = flatten([self.node_to_runtime_value(sn) for sn in nodes]) + return [src_to_abs(x) for x in rtvals] - def flatten_args(self, args_raw: T.Union[TYPE_var, T.Sequence[TYPE_var]], include_unknown_args: bool = False, id_loop_detect: T.Optional[T.List[str]] = None) -> T.List[TYPE_var]: + def flatten_args(self, args_raw: T.Union[TYPE_nvar, T.Sequence[TYPE_nvar]], include_unknown_args: bool = False) -> T.List[TYPE_var]: # Make sure we are always dealing with lists if isinstance(args_raw, list): args = args_raw else: args = [args_raw] - flattened_args: T.List[TYPE_var] = [] + # BaseNode resolves to Any. :/ + flattened_args: T.List[T.Union[TYPE_var, T.Any]] = [] # Resolve the contents of args for i in args: if isinstance(i, BaseNode): - resolved = self.resolve_node(i, include_unknown_args, id_loop_detect) + resolved = self.node_to_runtime_value(i) if resolved is not None: if not isinstance(resolved, list): resolved = [resolved] flattened_args += resolved - elif isinstance(i, (str, bool, int, float)) or include_unknown_args: + elif isinstance(i, (str, bool, int, float, UnknownValue, IntrospectionFile)) or include_unknown_args: flattened_args += [i] + else: + raise NotImplementedError return flattened_args def evaluate_testcase(self, node: TestCaseClauseNode) -> Disabler | None: return Disabler(subproject=self.subproject) + + def evaluate_statement(self, cur: mparser.BaseNode) -> T.Optional[InterpreterObject]: + if hasattr(cur, 'args'): + for arg in cur.args.arguments: + self.dataflow_dag.add_edge(arg, cur) + for k, v in cur.args.kwargs.items(): + self.dataflow_dag.add_edge(v, cur) + for attr in ['source_object', 'left', 'right', 'items', 'iobject', 'index', 'condition']: + if hasattr(cur, attr): + assert isinstance(getattr(cur, attr), mparser.BaseNode) + self.dataflow_dag.add_edge(getattr(cur, attr), cur) + if isinstance(cur, mparser.IdNode): + self.dataflow_dag.add_edge(self.get_cur_value(cur.value), cur) + return None + else: + return super().evaluate_statement(cur) + + def function_call(self, node: mparser.FunctionNode) -> T.Any: + ret = super().function_call(node) + if ret is not None: + self.funcvals[node] = ret + return ret diff --git a/mesonbuild/ast/introspection.py b/mesonbuild/ast/introspection.py index 4eb3fec..360edae 100644 --- a/mesonbuild/ast/introspection.py +++ b/mesonbuild/ast/introspection.py @@ -6,22 +6,20 @@ # or an interpreter-based tool from __future__ import annotations -import copy import os import typing as T from .. import compilers, environment, mesonlib, options -from .. import coredata as cdata from ..build import Executable, Jar, SharedLibrary, SharedModule, StaticLibrary from ..compilers import detect_compiler_for -from ..interpreterbase import InvalidArguments, SubProject +from ..interpreterbase import InvalidArguments, SubProject, UnknownValue from ..mesonlib import MachineChoice from ..options import OptionKey -from ..mparser import BaseNode, ArithmeticNode, ArrayNode, ElementaryNode, IdNode, FunctionNode, StringNode -from .interpreter import AstInterpreter +from ..mparser import BaseNode, ArrayNode, ElementaryNode, IdNode, FunctionNode, StringNode +from .interpreter import AstInterpreter, IntrospectionBuildTarget, IntrospectionDependency if T.TYPE_CHECKING: - from ..build import BuildTarget + from ..build import BuildTarget, BuildTargetKeywordArguments from ..interpreterbase import TYPE_var from .visitor import AstVisitor @@ -44,8 +42,11 @@ class IntrospectionHelper: return NotImplemented class IntrospectionInterpreter(AstInterpreter): - # Interpreter to detect the options without a build directory - # Most of the code is stolen from interpreter.Interpreter + # If you run `meson setup ...` the `Interpreter`-class walks over the AST. + # If you run `meson rewrite ...` and `meson introspect meson.build ...`, + # the `AstInterpreter`-class walks over the AST. + # Works without a build directory. + # Most of the code is stolen from interpreter.Interpreter . def __init__(self, source_root: str, subdir: str, @@ -61,11 +62,10 @@ class IntrospectionInterpreter(AstInterpreter): self.cross_file = cross_file self.backend = backend - self.default_options = {OptionKey('backend'): self.backend} self.project_data: T.Dict[str, T.Any] = {} - self.targets: T.List[T.Dict[str, T.Any]] = [] - self.dependencies: T.List[T.Dict[str, T.Any]] = [] - self.project_node: BaseNode = None + self.targets: T.List[IntrospectionBuildTarget] = [] + self.dependencies: T.List[IntrospectionDependency] = [] + self.project_node: FunctionNode = None self.funcs.update({ 'add_languages': self.func_add_languages, @@ -83,6 +83,7 @@ class IntrospectionInterpreter(AstInterpreter): def func_project(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> None: if self.project_node: raise InvalidArguments('Second call to project()') + assert isinstance(node, FunctionNode) self.project_node = node if len(args) < 1: raise InvalidArguments('Not enough arguments to project(). Needs at least the project name.') @@ -114,25 +115,6 @@ class IntrospectionInterpreter(AstInterpreter): self._load_option_file() - def_opts = self.flatten_args(kwargs.get('default_options', [])) - _project_default_options = mesonlib.stringlistify(def_opts) - string_dict = cdata.create_options_dict(_project_default_options, self.subproject) - self.project_default_options = {OptionKey(s): v for s, v in string_dict.items()} - self.default_options.update(self.project_default_options) - if self.environment.first_invocation or (self.subproject != '' and self.subproject not in self.coredata.initialized_subprojects): - if self.subproject == '': - self.coredata.optstore.initialize_from_top_level_project_call( - T.cast('T.Dict[T.Union[OptionKey, str], str]', string_dict), - {}, # TODO: not handled by this Interpreter. - self.environment.options) - else: - self.coredata.optstore.initialize_from_subproject_call( - self.subproject, - {}, # TODO: this isn't handled by the introspection interpreter... - T.cast('T.Dict[T.Union[OptionKey, str], str]', string_dict), - {}) # TODO: this isn't handled by the introspection interpreter... - self.coredata.initialized_subprojects.add(self.subproject) - if not self.is_subproject() and 'subproject_dir' in kwargs: spdirname = kwargs['subproject_dir'] if isinstance(spdirname, StringNode): @@ -146,10 +128,8 @@ class IntrospectionInterpreter(AstInterpreter): if os.path.isdir(os.path.join(subprojects_dir, i)): self.do_subproject(SubProject(i)) - self.coredata.init_backend_options(self.backend) - options = {k: v for k, v in self.environment.options.items() if self.environment.coredata.optstore.is_backend_option(k)} + self.environment.init_backend_options(self.backend) - self.coredata.set_options(options) self._add_languages(proj_langs, True, MachineChoice.HOST) self._add_languages(proj_langs, True, MachineChoice.BUILD) @@ -164,10 +144,10 @@ class IntrospectionInterpreter(AstInterpreter): except (mesonlib.MesonException, RuntimeError): pass - def func_add_languages(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> None: + def func_add_languages(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> UnknownValue: kwargs = self.flatten_kwargs(kwargs) required = kwargs.get('required', True) - assert isinstance(required, (bool, options.UserFeatureOption)), 'for mypy' + assert isinstance(required, (bool, options.UserFeatureOption, UnknownValue)), 'for mypy' if isinstance(required, options.UserFeatureOption): required = required.is_enabled() if 'native' in kwargs: @@ -176,8 +156,9 @@ class IntrospectionInterpreter(AstInterpreter): else: for for_machine in [MachineChoice.BUILD, MachineChoice.HOST]: self._add_languages(args, required, for_machine) + return UnknownValue() - def _add_languages(self, raw_langs: T.List[TYPE_var], required: bool, for_machine: MachineChoice) -> None: + def _add_languages(self, raw_langs: T.List[TYPE_var], required: T.Union[bool, UnknownValue], for_machine: MachineChoice) -> None: langs: T.List[str] = [] for l in self.flatten_args(raw_langs): if isinstance(l, str): @@ -192,48 +173,49 @@ class IntrospectionInterpreter(AstInterpreter): comp = detect_compiler_for(self.environment, lang, for_machine, True, self.subproject) except mesonlib.MesonException: # do we even care about introspecting this language? - if required: + if isinstance(required, UnknownValue) or required: raise else: continue - if self.subproject: - options = {} - for k in comp.get_options(): - v = copy.copy(self.coredata.optstore.get_value_object(k)) - k = k.evolve(subproject=self.subproject) - options[k] = v - self.coredata.add_compiler_options(options, lang, for_machine, self.environment, self.subproject) - - def func_dependency(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> None: + if comp: + self.coredata.process_compiler_options(lang, comp, self.subproject) + + def func_dependency(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Optional[IntrospectionDependency]: + assert isinstance(node, FunctionNode) args = self.flatten_args(args) kwargs = self.flatten_kwargs(kwargs) if not args: - return + return None name = args[0] + assert isinstance(name, (str, UnknownValue)) has_fallback = 'fallback' in kwargs required = kwargs.get('required', True) version = kwargs.get('version', []) if not isinstance(version, list): version = [version] - if isinstance(required, ElementaryNode): - required = required.value - if not isinstance(required, bool): - required = False - self.dependencies += [{ - 'name': name, - 'required': required, - 'version': version, - 'has_fallback': has_fallback, - 'conditional': node.condition_level > 0, - 'node': node - }] - - def build_target(self, node: BaseNode, args: T.List[TYPE_var], kwargs_raw: T.Dict[str, TYPE_var], targetclass: T.Type[BuildTarget]) -> T.Optional[T.Dict[str, T.Any]]: + if any(isinstance(el, UnknownValue) for el in version): + version = UnknownValue() + else: + assert all(isinstance(el, str) for el in version) + version = T.cast(T.List[str], version) + assert isinstance(required, (bool, UnknownValue)) + newdep = IntrospectionDependency( + name=name, + required=required, + version=version, + has_fallback=has_fallback, + conditional=node.condition_level > 0, + node=node) + self.dependencies += [newdep] + return newdep + + def build_target(self, node: BaseNode, args: T.List[TYPE_var], kwargs_raw: T.Dict[str, TYPE_var], targetclass: T.Type[BuildTarget]) -> T.Union[IntrospectionBuildTarget, UnknownValue]: + assert isinstance(node, FunctionNode) args = self.flatten_args(args) if not args or not isinstance(args[0], str): - return None + return UnknownValue() name = args[0] - srcqueue = [node] + srcqueue: T.List[BaseNode] = [node] extra_queue = [] # Process the sources BEFORE flattening the kwargs, to preserve the original nodes @@ -245,45 +227,26 @@ class IntrospectionInterpreter(AstInterpreter): kwargs = self.flatten_kwargs(kwargs_raw, True) - def traverse_nodes(inqueue: T.List[BaseNode]) -> T.List[BaseNode]: - res: T.List[BaseNode] = [] - while inqueue: - curr = inqueue.pop(0) - arg_node = None - assert isinstance(curr, BaseNode) - if isinstance(curr, FunctionNode): - arg_node = curr.args - elif isinstance(curr, ArrayNode): - arg_node = curr.args - elif isinstance(curr, IdNode): - # Try to resolve the ID and append the node to the queue - assert isinstance(curr.value, str) - var_name = curr.value - if var_name in self.assignments: - tmp_node = self.assignments[var_name] - if isinstance(tmp_node, (ArrayNode, IdNode, FunctionNode)): - inqueue += [tmp_node] - elif isinstance(curr, ArithmeticNode): - inqueue += [curr.left, curr.right] - if arg_node is None: - continue - arg_nodes = arg_node.arguments.copy() - # Pop the first element if the function is a build target function - if isinstance(curr, FunctionNode) and curr.func_name.value in BUILD_TARGET_FUNCTIONS: - arg_nodes.pop(0) - elementary_nodes = [x for x in arg_nodes if isinstance(x, (str, StringNode))] - inqueue += [x for x in arg_nodes if isinstance(x, (FunctionNode, ArrayNode, IdNode, ArithmeticNode))] - if elementary_nodes: - res += [curr] - return res - - source_nodes = traverse_nodes(srcqueue) - extraf_nodes = traverse_nodes(extra_queue) + oldlen = len(node.args.arguments) + source_nodes = node.args.arguments[1:] + for k, v in node.args.kwargs.items(): + assert isinstance(k, IdNode) + if k.value == 'sources': + source_nodes.append(v) + assert oldlen == len(node.args.arguments) + + extraf_nodes = None + for k, v in node.args.kwargs.items(): + assert isinstance(k, IdNode) + if k.value == 'extra_files': + assert extraf_nodes is None + extraf_nodes = v # Make sure nothing can crash when creating the build class - kwargs_reduced = {k: v for k, v in kwargs.items() if k in targetclass.known_kwargs and k in {'install', 'build_by_default', 'build_always'}} - kwargs_reduced = {k: v.value if isinstance(v, ElementaryNode) else v for k, v in kwargs_reduced.items()} - kwargs_reduced = {k: v for k, v in kwargs_reduced.items() if not isinstance(v, BaseNode)} + _kwargs_reduced = {k: v for k, v in kwargs.items() if k in targetclass.known_kwargs and k in {'install', 'build_by_default', 'build_always', 'name_prefix'}} + _kwargs_reduced = {k: v.value if isinstance(v, ElementaryNode) else v for k, v in _kwargs_reduced.items()} + _kwargs_reduced = {k: v for k, v in _kwargs_reduced.items() if not isinstance(v, (BaseNode, UnknownValue))} + kwargs_reduced = T.cast('BuildTargetKeywordArguments', _kwargs_reduced) for_machine = MachineChoice.BUILD if kwargs.get('native', False) else MachineChoice.HOST objects: T.List[T.Any] = [] empty_sources: T.List[T.Any] = [] @@ -293,27 +256,34 @@ class IntrospectionInterpreter(AstInterpreter): self.environment, self.coredata.compilers[for_machine], kwargs_reduced) target.process_compilers_late() - new_target = { - 'name': target.get_basename(), - 'machine': target.for_machine.get_lower_case_name(), - 'id': target.get_id(), - 'type': target.get_typename(), - 'defined_in': os.path.normpath(os.path.join(self.source_root, self.subdir, environment.build_filename)), - 'subdir': self.subdir, - 'build_by_default': target.build_by_default, - 'installed': target.should_install(), - 'outputs': target.get_outputs(), - 'sources': source_nodes, - 'extra_files': extraf_nodes, - 'kwargs': kwargs, - 'node': node, - } + build_by_default: T.Union[UnknownValue, bool] = target.build_by_default + if 'build_by_default' in kwargs and isinstance(kwargs['build_by_default'], UnknownValue): + build_by_default = kwargs['build_by_default'] + + install: T.Union[UnknownValue, bool] = target.should_install() + if 'install' in kwargs and isinstance(kwargs['install'], UnknownValue): + install = kwargs['install'] + + new_target = IntrospectionBuildTarget( + name=target.get_basename(), + machine=target.for_machine.get_lower_case_name(), + id=target.get_id(), + typename=target.get_typename(), + defined_in=os.path.normpath(os.path.join(self.source_root, self.subdir, environment.build_filename)), + subdir=self.subdir, + build_by_default=build_by_default, + installed=install, + outputs=target.get_outputs(), + source_nodes=source_nodes, + extra_files=extraf_nodes, + kwargs=kwargs, + node=node) self.targets += [new_target] return new_target - def build_library(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Optional[T.Dict[str, T.Any]]: - default_library = self.coredata.optstore.get_value_for(OptionKey('default_library')) + def build_library(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Union[IntrospectionBuildTarget, UnknownValue]: + default_library = self.coredata.optstore.get_value_for(OptionKey('default_library', subproject=self.subproject)) if default_library == 'shared': return self.build_target(node, args, kwargs, SharedLibrary) elif default_library == 'static': @@ -322,28 +292,28 @@ class IntrospectionInterpreter(AstInterpreter): return self.build_target(node, args, kwargs, SharedLibrary) return None - def func_executable(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Optional[T.Dict[str, T.Any]]: + def func_executable(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Union[IntrospectionBuildTarget, UnknownValue]: return self.build_target(node, args, kwargs, Executable) - def func_static_lib(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Optional[T.Dict[str, T.Any]]: + def func_static_lib(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Union[IntrospectionBuildTarget, UnknownValue]: return self.build_target(node, args, kwargs, StaticLibrary) - def func_shared_lib(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Optional[T.Dict[str, T.Any]]: + def func_shared_lib(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Union[IntrospectionBuildTarget, UnknownValue]: return self.build_target(node, args, kwargs, SharedLibrary) - def func_both_lib(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Optional[T.Dict[str, T.Any]]: + def func_both_lib(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Union[IntrospectionBuildTarget, UnknownValue]: return self.build_target(node, args, kwargs, SharedLibrary) - def func_shared_module(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Optional[T.Dict[str, T.Any]]: + def func_shared_module(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Union[IntrospectionBuildTarget, UnknownValue]: return self.build_target(node, args, kwargs, SharedModule) - def func_library(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Optional[T.Dict[str, T.Any]]: + def func_library(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Union[IntrospectionBuildTarget, UnknownValue]: return self.build_library(node, args, kwargs) - def func_jar(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Optional[T.Dict[str, T.Any]]: + def func_jar(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Union[IntrospectionBuildTarget, UnknownValue]: return self.build_target(node, args, kwargs, Jar) - def func_build_target(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Optional[T.Dict[str, T.Any]]: + def func_build_target(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> T.Union[IntrospectionBuildTarget, UnknownValue]: if 'target_type' not in kwargs: return None target_type = kwargs.pop('target_type') @@ -395,7 +365,7 @@ class IntrospectionInterpreter(AstInterpreter): flattened_kwargs = {} for key, val in kwargs.items(): if isinstance(val, BaseNode): - resolved = self.resolve_node(val, include_unknown_args) + resolved = self.node_to_runtime_value(val) if resolved is not None: flattened_kwargs[key] = resolved elif isinstance(val, (str, bool, int, float)) or include_unknown_args: diff --git a/mesonbuild/ast/printer.py b/mesonbuild/ast/printer.py index 4ce3b3f..024b62b 100644 --- a/mesonbuild/ast/printer.py +++ b/mesonbuild/ast/printer.py @@ -7,12 +7,46 @@ from __future__ import annotations from .. import mparser from .visitor import AstVisitor, FullAstVisitor +from ..mesonlib import MesonBugException import re import typing as T +# Also known as "order of operations" or "binding power". +# This is the counterpart to Parser.e1, Parser.e2, Parser.e3, Parser.e4, Parser.e5, Parser.e6, Parser.e7, Parser.e8, Parser.e9, Parser.e10 +def precedence_level(node: mparser.BaseNode) -> int: + if isinstance(node, (mparser.PlusAssignmentNode, mparser.AssignmentNode, mparser.TernaryNode)): + return 1 + elif isinstance(node, mparser.OrNode): + return 2 + elif isinstance(node, mparser.AndNode): + return 3 + elif isinstance(node, mparser.ComparisonNode): + return 4 + elif isinstance(node, mparser.ArithmeticNode): + if node.operation in {'+', '-'}: + return 5 + elif node.operation in {'%', '*', '/'}: + return 6 + elif isinstance(node, (mparser.NotNode, mparser.UMinusNode)): + return 7 + elif isinstance(node, mparser.FunctionNode): + return 8 + elif isinstance(node, (mparser.ArrayNode, mparser.DictNode)): + return 9 + elif isinstance(node, (mparser.BooleanNode, mparser.IdNode, mparser.NumberNode, mparser.StringNode, mparser.EmptyNode)): + return 10 + elif isinstance(node, mparser.ParenthesizedNode): + # Parenthesize have the highest binding power, but since the AstPrinter + # ignores ParanthesizedNode, the binding power of the inner node is + # relevant. + return precedence_level(node.inner) + raise MesonBugException('Unhandled node type') + class AstPrinter(AstVisitor): + escape_trans: T.Dict[int, str] = str.maketrans({'\\': '\\\\', "'": "\'"}) + def __init__(self, indent: int = 2, arg_newline_cutoff: int = 5, update_ast_line_nos: bool = False): self.result = '' self.indent = indent @@ -57,7 +91,7 @@ class AstPrinter(AstVisitor): node.lineno = self.curr_line or node.lineno def escape(self, val: str) -> str: - return val.replace('\\', '\\\\').replace("'", "\'") + return val.translate(self.escape_trans) def visit_StringNode(self, node: mparser.StringNode) -> None: assert isinstance(node.value, str) @@ -104,15 +138,25 @@ class AstPrinter(AstVisitor): def visit_ComparisonNode(self, node: mparser.ComparisonNode) -> None: node.left.accept(self) - self.append_padded(node.ctype if node.ctype != 'notin' else 'not in', node) + self.append_padded(node.ctype, node) node.lineno = self.curr_line or node.lineno node.right.accept(self) + def maybe_parentheses(self, outer: mparser.BaseNode, inner: mparser.BaseNode, parens: bool) -> None: + if parens: + self.append('(', inner) + inner.accept(self) + if parens: + self.append(')', inner) + def visit_ArithmeticNode(self, node: mparser.ArithmeticNode) -> None: - node.left.accept(self) + prec = precedence_level(node) + prec_left = precedence_level(node.left) + prec_right = precedence_level(node.right) + self.maybe_parentheses(node, node.left, prec > prec_left) self.append_padded(node.operator.value, node) node.lineno = self.curr_line or node.lineno - node.right.accept(self) + self.maybe_parentheses(node, node.right, prec > prec_right or (prec == prec_right and node.operation in {'sub', 'div', 'mod'})) def visit_NotNode(self, node: mparser.NotNode) -> None: node.lineno = self.curr_line or node.lineno diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 3dfa2fb..dddcf67 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -24,12 +24,13 @@ from .. import dependencies from .. import programs from .. import mesonlib from .. import mlog -from ..compilers import LANGUAGES_USING_LDFLAGS, detect, lang_suffixes +from .. import compilers +from ..compilers import detect, lang_suffixes from ..mesonlib import ( File, MachineChoice, MesonException, MesonBugException, OrderedSet, ExecutableSerialisation, EnvironmentException, classify_unity_sources, get_compiler_for_source, - is_parent_path, + get_rsp_threshold, unique_list ) from ..options import OptionKey @@ -39,14 +40,14 @@ if T.TYPE_CHECKING: from ..arglist import CompilerArgs from ..compilers import Compiler from ..environment import Environment - from ..interpreter import Interpreter, Test + from ..interpreter import Test from ..linkers.linkers import StaticLinker from ..mesonlib import FileMode, FileOrString from ..options import ElementaryOptionValues from typing_extensions import TypedDict, NotRequired - _ALL_SOURCES_TYPE = T.List[T.Union[File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList]] + _ALL_SOURCES_TYPE = T.List[T.Union[File, build.GeneratedTypes]] class TargetIntrospectionData(TypedDict): @@ -61,7 +62,7 @@ if T.TYPE_CHECKING: # Languages that can mix with C or C++ but don't support unity builds yet # because the syntax we use for unity builds is specific to C/++/ObjC/++. # Assembly files cannot be unitified and neither can LLVM IR files -LANGS_CANT_UNITY = ('d', 'fortran', 'vala') +LANGS_CANT_UNITY = ('d', 'fortran', 'vala', 'rust') @dataclass(eq=False) class RegenInfo: @@ -177,7 +178,6 @@ class InstallSymlinkData: install_path: str subproject: str tag: T.Optional[str] = None - allow_missing: bool = False # cannot use dataclass here because "exclude" is out of order class SubdirInstallData(InstallDataBase): @@ -218,47 +218,50 @@ class TestSerialisation: assert isinstance(self.exe_wrapper, programs.ExternalProgram) -def get_backend_from_name(backend: str, build: T.Optional[build.Build] = None, interpreter: T.Optional['Interpreter'] = None) -> T.Optional['Backend']: +def get_backend_from_name(backend: str, build: T.Optional[build.Build] = None) -> T.Optional['Backend']: if backend == 'ninja': from . import ninjabackend - return ninjabackend.NinjaBackend(build, interpreter) + return ninjabackend.NinjaBackend(build) elif backend == 'vs': from . import vs2010backend - return vs2010backend.autodetect_vs_version(build, interpreter) + return vs2010backend.autodetect_vs_version(build) elif backend == 'vs2010': from . import vs2010backend - return vs2010backend.Vs2010Backend(build, interpreter) + return vs2010backend.Vs2010Backend(build) elif backend == 'vs2012': from . import vs2012backend - return vs2012backend.Vs2012Backend(build, interpreter) + return vs2012backend.Vs2012Backend(build) elif backend == 'vs2013': from . import vs2013backend - return vs2013backend.Vs2013Backend(build, interpreter) + return vs2013backend.Vs2013Backend(build) elif backend == 'vs2015': from . import vs2015backend - return vs2015backend.Vs2015Backend(build, interpreter) + return vs2015backend.Vs2015Backend(build) elif backend == 'vs2017': from . import vs2017backend - return vs2017backend.Vs2017Backend(build, interpreter) + return vs2017backend.Vs2017Backend(build) elif backend == 'vs2019': from . import vs2019backend - return vs2019backend.Vs2019Backend(build, interpreter) + return vs2019backend.Vs2019Backend(build) elif backend == 'vs2022': from . import vs2022backend - return vs2022backend.Vs2022Backend(build, interpreter) + return vs2022backend.Vs2022Backend(build) + elif backend == 'vs2026': + from . import vs2026backend + return vs2026backend.Vs2026Backend(build) elif backend == 'xcode': from . import xcodebackend - return xcodebackend.XCodeBackend(build, interpreter) + return xcodebackend.XCodeBackend(build) elif backend == 'none': from . import nonebackend - return nonebackend.NoneBackend(build, interpreter) + return nonebackend.NoneBackend(build) return None -def get_genvslite_backend(genvsname: str, build: T.Optional[build.Build] = None, interpreter: T.Optional['Interpreter'] = None) -> T.Optional['Backend']: +def get_genvslite_backend(genvsname: str, build: T.Optional[build.Build] = None) -> T.Optional['Backend']: if genvsname == 'vs2022': from . import vs2022backend - return vs2022backend.Vs2022Backend(build, interpreter, gen_lite = True) + return vs2022backend.Vs2022Backend(build, gen_lite = True) return None # This class contains the basic functionality that is needed by all backends. @@ -268,14 +271,13 @@ class Backend: environment: T.Optional['Environment'] name = '<UNKNOWN>' - def __init__(self, build: T.Optional[build.Build], interpreter: T.Optional['Interpreter']): + def __init__(self, build: T.Optional[build.Build]): # Make it possible to construct a dummy backend # This is used for introspection without a build directory if build is None: self.environment = None return self.build = build - self.interpreter = interpreter self.environment = build.environment self.processed_targets: T.Set[str] = set() self.build_dir = self.environment.get_build_dir() @@ -296,7 +298,7 @@ class Backend: def generate(self, capture: bool = False, vslite_ctx: T.Optional[T.Dict] = None) -> T.Optional[T.Dict]: raise RuntimeError(f'generate is not implemented in {type(self).__name__}') - def get_target_filename(self, t: T.Union[build.Target, build.CustomTargetIndex], *, warn_multi_output: bool = True) -> str: + def get_target_filename(self, t: build.AnyTargetType, *, warn_multi_output: bool = True) -> str: if isinstance(t, build.CustomTarget): if warn_multi_output and len(t.get_outputs()) != 1: mlog.warning(f'custom_target {t.name!r} has more than one output! ' @@ -309,7 +311,7 @@ class Backend: filename = t.get_filename() return os.path.join(self.get_target_dir(t), filename) - def get_target_filename_abs(self, target: T.Union[build.Target, build.CustomTargetIndex]) -> str: + def get_target_filename_abs(self, target: build.AnyTargetType) -> str: return os.path.join(self.environment.get_build_dir(), self.get_target_filename(target)) def get_target_debug_filename(self, target: build.BuildTarget) -> T.Optional[str]: @@ -337,14 +339,14 @@ class Backend: def get_build_dir_include_args(self, target: build.BuildTarget, compiler: 'Compiler', *, absolute_path: bool = False) -> T.List[str]: if absolute_path: - curdir = os.path.join(self.build_dir, target.get_subdir()) + curdir = os.path.join(self.build_dir, target.get_builddir()) else: - curdir = target.get_subdir() + curdir = target.get_builddir() if curdir == '': curdir = '.' return compiler.get_include_args(curdir, False) - def get_target_filename_for_linking(self, target: T.Union[build.Target, build.CustomTargetIndex]) -> T.Optional[str]: + def get_target_filename_for_linking(self, target: build.AnyTargetType) -> T.Optional[str]: # On some platforms (msvc for instance), the file that is used for # dynamic linking is not the same as the dynamic library itself. This # file is called an import library, and we want to link against that. @@ -369,17 +371,23 @@ class Backend: raise AssertionError(f'BUG: Tried to link to {target!r} which is not linkable') @lru_cache(maxsize=None) - def get_target_dir(self, target: T.Union[build.Target, build.CustomTargetIndex]) -> str: + def get_target_dir(self, target: build.AnyTargetType) -> str: if isinstance(target, build.RunTarget): # this produces no output, only a dummy top-level name dirname = '' elif self.environment.coredata.optstore.get_value_for(OptionKey('layout')) == 'mirror': - dirname = target.get_subdir() + dirname = target.get_builddir() else: dirname = 'meson-out' + build_subdir = target.get_build_subdir() + if build_subdir: + dirname = os.path.join(dirname, build_subdir) return dirname - def get_target_dir_relative_to(self, t: build.Target, o: build.Target) -> str: + def get_target_dir_relative_to(self, + t: T.Union[build.Target, build.CustomTargetIndex], + o: T.Union[build.Target, build.CustomTargetIndex], + ) -> str: '''Get a target dir relative to another target's directory''' target_dir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(t)) othert_dir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(o)) @@ -392,16 +400,16 @@ class Backend: return os.path.join(self.build_to_src, target_dir) return self.build_to_src - def get_target_private_dir(self, target: T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex]) -> str: + def get_target_private_dir(self, target: build.BuildTargetTypes) -> str: return os.path.join(self.get_target_filename(target, warn_multi_output=False) + '.p') - def get_target_private_dir_abs(self, target: T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex]) -> str: + def get_target_private_dir_abs(self, target: build.BuildTargetTypes) -> str: return os.path.join(self.environment.get_build_dir(), self.get_target_private_dir(target)) @lru_cache(maxsize=None) def get_target_generated_dir( - self, target: T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex], - gensrc: T.Union[build.CustomTarget, build.CustomTargetIndex, build.GeneratedList], + self, target: build.BuildTargetTypes, + gensrc: build.GeneratedTypes, src: str) -> str: """ Takes a BuildTarget, a generator source (CustomTarget or GeneratedList), @@ -415,7 +423,7 @@ class Backend: # target that the GeneratedList is used in return os.path.join(self.get_target_private_dir(target), src) - def get_unity_source_file(self, target: T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex], + def get_unity_source_file(self, target: build.BuildTargetTypes, suffix: str, number: int) -> mesonlib.File: # There is a potential conflict here, but it is unlikely that # anyone both enables unity builds and has a file called foo-unity.cpp. @@ -472,11 +480,11 @@ class Backend: def flatten_object_list(self, target: build.BuildTarget, proj_dir_to_build_root: str = '' ) -> T.Tuple[T.List[str], T.List[build.BuildTargetTypes]]: obj_list, deps = self._flatten_object_list(target, target.get_objects(), proj_dir_to_build_root) - return list(dict.fromkeys(obj_list)), deps + return unique_list(obj_list), deps def determine_ext_objs(self, objects: build.ExtractedObjects) -> T.List[str]: obj_list, _ = self._flatten_object_list(objects.target, [objects], '') - return list(dict.fromkeys(obj_list)) + return unique_list(obj_list) def _flatten_object_list(self, target: build.BuildTarget, objects: T.Sequence[T.Union[str, 'File', build.ExtractedObjects]], @@ -486,7 +494,7 @@ class Backend: for obj in objects: if isinstance(obj, str): o = os.path.join(proj_dir_to_build_root, - self.build_to_src, target.get_subdir(), obj) + self.build_to_src, target.get_builddir(), obj) obj_list.append(o) elif isinstance(obj, mesonlib.File): if obj.is_built: @@ -533,6 +541,7 @@ class Backend: capture: T.Optional[str] = None, feed: T.Optional[str] = None, env: T.Optional[mesonlib.EnvironmentVariables] = None, + can_use_rsp_file: bool = False, tag: T.Optional[str] = None, verbose: bool = False, installdir_map: T.Optional[T.Dict[str, str]] = None) -> 'ExecutableSerialisation': @@ -563,9 +572,7 @@ class Backend: cmd_args: T.List[str] = [] for c in raw_cmd_args: if isinstance(c, programs.ExternalProgram): - p = c.get_path() - assert isinstance(p, str) - cmd_args.append(p) + cmd_args += c.get_command() elif isinstance(c, (build.BuildTarget, build.CustomTarget)): cmd_args.append(self.get_target_filename_abs(c)) elif isinstance(c, mesonlib.File): @@ -589,11 +596,26 @@ class Backend: else: if exe_cmd[0].endswith('.jar'): exe_cmd = ['java', '-jar'] + exe_cmd - elif exe_cmd[0].endswith('.exe') and not (mesonlib.is_windows() or mesonlib.is_cygwin() or mesonlib.is_wsl()): + elif exe_cmd[0].endswith('.exe') and not (mesonlib.is_windows() or mesonlib.is_cygwin() or mesonlib.is_wsl() or machine.is_os2()): exe_cmd = ['mono'] + exe_cmd exe_wrapper = None workdir = workdir or self.environment.get_build_dir() + + # Must include separators as well + needs_rsp_file = can_use_rsp_file and sum(len(i) + 1 for i in cmd_args) >= get_rsp_threshold() + + if needs_rsp_file: + hasher = hashlib.sha1() + args = ' '.join(mesonlib.quote_arg(arg) for arg in cmd_args) + hasher.update(args.encode(encoding='utf-8', errors='ignore')) + digest = hasher.hexdigest() + scratch_file = f'meson_rsp_{digest}.rsp' + rsp_file = os.path.join(self.environment.get_scratch_dir(), scratch_file) + with open(rsp_file, 'w', encoding='utf-8', newline='\n') as f: + f.write(args) + cmd_args = [f'@{rsp_file}'] + return ExecutableSerialisation(exe_cmd + cmd_args, env, exe_wrapper, workdir, extra_paths, capture, feed, tag, verbose, installdir_map) @@ -606,6 +628,7 @@ class Backend: feed: T.Optional[str] = None, force_serialize: bool = False, env: T.Optional[mesonlib.EnvironmentVariables] = None, + can_use_rsp_file: bool = False, verbose: bool = False) -> T.Tuple[T.List[str], str]: ''' Serialize an executable for running with a generator or a custom target @@ -613,7 +636,7 @@ class Backend: cmd: T.List[T.Union[str, mesonlib.File, build.BuildTarget, build.CustomTarget, programs.ExternalProgram]] = [] cmd.append(exe) cmd.extend(cmd_args) - es = self.get_executable_serialisation(cmd, workdir, extra_bdeps, capture, feed, env, verbose=verbose) + es = self.get_executable_serialisation(cmd, workdir, extra_bdeps, capture, feed, env, can_use_rsp_file, verbose=verbose) reasons: T.List[str] = [] if es.extra_paths: reasons.append('to set PATH') @@ -653,6 +676,9 @@ class Backend: envlist.append(f'{k}={v}') return ['env'] + envlist + es.cmd_args, ', '.join(reasons) + if any(a.startswith('@') for a in es.cmd_args): + reasons.append('because command is too long') + if not force_serialize: if not capture and not feed: return es.cmd_args, '' @@ -715,118 +741,6 @@ class Backend: return l, stdlib_args @staticmethod - def _libdir_is_system(libdir: str, compilers: T.Mapping[str, 'Compiler'], env: 'Environment') -> bool: - libdir = os.path.normpath(libdir) - for cc in compilers.values(): - if libdir in cc.get_library_dirs(env): - return True - return False - - def get_external_rpath_dirs(self, target: build.BuildTarget) -> T.Set[str]: - args: T.List[str] = [] - for lang in LANGUAGES_USING_LDFLAGS: - try: - e = self.environment.coredata.get_external_link_args(target.for_machine, lang) - if isinstance(e, str): - args.append(e) - else: - args.extend(e) - except Exception: - pass - return self.get_rpath_dirs_from_link_args(args) - - @staticmethod - def get_rpath_dirs_from_link_args(args: T.List[str]) -> T.Set[str]: - dirs: T.Set[str] = set() - # Match rpath formats: - # -Wl,-rpath= - # -Wl,-rpath, - rpath_regex = re.compile(r'-Wl,-rpath[=,]([^,]+)') - # Match solaris style compat runpath formats: - # -Wl,-R - # -Wl,-R, - runpath_regex = re.compile(r'-Wl,-R[,]?([^,]+)') - # Match symbols formats: - # -Wl,--just-symbols= - # -Wl,--just-symbols, - symbols_regex = re.compile(r'-Wl,--just-symbols[=,]([^,]+)') - for arg in args: - rpath_match = rpath_regex.match(arg) - if rpath_match: - for dir in rpath_match.group(1).split(':'): - dirs.add(dir) - runpath_match = runpath_regex.match(arg) - if runpath_match: - for dir in runpath_match.group(1).split(':'): - # The symbols arg is an rpath if the path is a directory - if Path(dir).is_dir(): - dirs.add(dir) - symbols_match = symbols_regex.match(arg) - if symbols_match: - for dir in symbols_match.group(1).split(':'): - # Prevent usage of --just-symbols to specify rpath - if Path(dir).is_dir(): - raise MesonException(f'Invalid arg for --just-symbols, {dir} is a directory.') - return dirs - - @lru_cache(maxsize=None) - def rpaths_for_non_system_absolute_shared_libraries(self, target: build.BuildTarget, exclude_system: bool = True) -> 'ImmutableListProtocol[str]': - paths: OrderedSet[str] = OrderedSet() - srcdir = self.environment.get_source_dir() - - for dep in target.external_deps: - if dep.type_name not in {'library', 'pkgconfig', 'cmake'}: - continue - for libpath in dep.link_args: - # For all link args that are absolute paths to a library file, add RPATH args - if not os.path.isabs(libpath): - continue - libdir = os.path.dirname(libpath) - if exclude_system and self._libdir_is_system(libdir, target.compilers, self.environment): - # No point in adding system paths. - continue - # Don't remove rpaths specified in LDFLAGS. - if libdir in self.get_external_rpath_dirs(target): - continue - # Windows doesn't support rpaths, but we use this function to - # emulate rpaths by setting PATH - # .dll is there for mingw gcc - # .so's may be extended with version information, e.g. libxyz.so.1.2.3 - if not ( - os.path.splitext(libpath)[1] in {'.dll', '.lib', '.so', '.dylib'} - or re.match(r'.+\.so(\.|$)', os.path.basename(libpath)) - ): - continue - - if is_parent_path(srcdir, libdir): - rel_to_src = libdir[len(srcdir) + 1:] - assert not os.path.isabs(rel_to_src), f'rel_to_src: {rel_to_src} is absolute' - paths.add(os.path.join(self.build_to_src, rel_to_src)) - else: - paths.add(libdir) - # Don't remove rpaths specified by the dependency - paths.difference_update(self.get_rpath_dirs_from_link_args(dep.link_args)) - for i in chain(target.link_targets, target.link_whole_targets): - if isinstance(i, build.BuildTarget): - paths.update(self.rpaths_for_non_system_absolute_shared_libraries(i, exclude_system)) - return list(paths) - - # This may take other types - def determine_rpath_dirs(self, target: T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex] - ) -> T.Tuple[str, ...]: - result: OrderedSet[str] - if self.environment.coredata.optstore.get_value_for(OptionKey('layout')) == 'mirror': - # Need a copy here - result = OrderedSet(target.get_link_dep_subdirs()) - else: - result = OrderedSet() - result.add('meson-out') - if isinstance(target, build.BuildTarget): - result.update(self.rpaths_for_non_system_absolute_shared_libraries(target)) - target.rpath_dirs_to_remove.update([d.encode('utf-8') for d in result]) - return tuple(result) - - @staticmethod @lru_cache(maxsize=None) def canonicalize_filename(fname: str) -> str: if os.path.altsep is not None: @@ -905,18 +819,19 @@ class Backend: # Filter out headers and all non-source files sources: T.List['FileOrString'] = [] for s in raw_sources: - if self.environment.is_source(s): + if compilers.is_source(s): sources.append(s) - elif self.environment.is_object(s): + elif compilers.is_object(s): result.append(s.relative_name()) # MSVC generate an object file for PCH if extobj.pch and self.target_uses_pch(extobj.target): for lang, pch in extobj.target.pch.items(): - compiler = extobj.target.compilers[lang] - if compiler.get_argument_syntax() == 'msvc': - objname = self.get_msvc_pch_objname(lang, pch) - result.append(os.path.join(targetdir, objname)) + if pch: + compiler = extobj.target.compilers[lang] + if compiler.get_argument_syntax() == 'msvc': + objname = self.get_msvc_pch_objname(lang, pch) + result.append(os.path.join(targetdir, objname)) # extobj could contain only objects and no sources if not sources: @@ -951,13 +866,13 @@ class Backend: args: T.List[str] = [] pchpath = self.get_target_private_dir(target) includeargs = compiler.get_include_args(pchpath, False) - p = target.get_pch(compiler.get_language()) + p = target.pch.get(compiler.get_language()) if p: args += compiler.get_pch_use_args(pchpath, p[0]) return includeargs + args - def get_msvc_pch_objname(self, lang: str, pch: T.List[str]) -> str: - if len(pch) == 1: + def get_msvc_pch_objname(self, lang: str, pch: T.Tuple[str, T.Optional[str]]) -> str: + if pch[1] is None: # Same name as in create_msvc_pch_implementation() below. return f'meson_pch-{lang}.obj' return os.path.splitext(pch[1])[0] + '.obj' @@ -1023,8 +938,8 @@ class Backend: commands += compiler.get_werror_args() # Add compile args for c_* or cpp_* build options set on the # command-line or default_options inside project(). - commands += compiler.get_option_compile_args(target, self.environment, target.subproject) - commands += compiler.get_option_std_args(target, self.environment, target.subproject) + commands += compiler.get_option_compile_args(target, target.subproject) + commands += compiler.get_option_std_args(target, target.subproject) optimization = self.get_target_option(target, 'optimization') assert isinstance(optimization, str), 'for mypy' @@ -1067,11 +982,6 @@ class Backend: if compiler.language == 'vala': if dep.type_name == 'pkgconfig': assert isinstance(dep, dependencies.ExternalDependency) - if dep.name == 'glib-2.0' and dep.version_reqs is not None: - for req in dep.version_reqs: - if req.startswith(('>=', '==')): - commands += ['--target-glib', req[2:]] - break commands += ['--pkg', dep.name] elif isinstance(dep, dependencies.ExternalLibrary): commands += dep.get_link_args('vala') @@ -1083,6 +993,32 @@ class Backend: commands += dep.get_exe_args(compiler) # For 'automagic' deps: Boost and GTest. Also dependency('threads'). # pkg-config puts the thread flags itself via `Cflags:` + if compiler.language == 'vala': + # Vala wants to know the minimum glib version + for dep in target.added_deps: + if dep.name == 'glib-2.0': + if dep.type_name == 'pkgconfig': + assert isinstance(dep, dependencies.ExternalDependency) + if dep.version_reqs is not None: + for req in dep.version_reqs: + if req.startswith(('>=', '==')): + commands += ['--target-glib', req[2:].strip()] + break + elif isinstance(dep, dependencies.InternalDependency) and dep.version is not None: + glib_version = dep.version.split('.') + if len(glib_version) != 3: + mlog.warning(f'GLib version has unexpected format: {dep.version}') + break + try: + # If GLib version is a development version, downgrade + # --target-glib to the previous version, as valac will + # complain about non-even minor versions + glib_version[1] = str((int(glib_version[1]) // 2) * 2) + except ValueError: + mlog.warning(f'GLib version has unexpected format: {dep.version}') + break + commands += ['--target-glib', f'{glib_version[0]}.{glib_version[1]}'] + # Fortran requires extra include directives. if compiler.language == 'fortran': for lt in chain(target.link_targets, target.link_whole_targets): @@ -1127,8 +1063,8 @@ class Backend: # Get program and library dirs from all target compilers if isinstance(target, build.BuildTarget): for cc in target.compilers.values(): - paths.update(cc.get_program_dirs(self.environment)) - paths.update(cc.get_library_dirs(self.environment)) + paths.update(cc.get_program_dirs()) + paths.update(cc.get_library_dirs()) return list(paths) @staticmethod @@ -1188,8 +1124,8 @@ class Backend: return results def determine_windows_extra_paths( - self, target: T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex, programs.ExternalProgram, mesonlib.File, str], - extra_bdeps: T.Sequence[T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex]]) -> T.List[str]: + self, target: T.Union[build.BuildTargetTypes, programs.ExternalProgram, mesonlib.File, str], + extra_bdeps: T.Sequence[build.BuildTargetTypes]) -> T.List[str]: """On Windows there is no such thing as an rpath. We must determine all locations of DLLs that this exe @@ -1255,15 +1191,12 @@ class Backend: exe_wrapper = self.environment.get_exe_wrapper() machine = self.environment.machines[exe.for_machine] if machine.is_windows() or machine.is_cygwin(): - extra_bdeps: T.List[T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex]] = [] + extra_bdeps: T.List[build.BuildTargetTypes] = [] if isinstance(exe, build.CustomTarget): extra_bdeps = list(exe.get_transitive_build_target_deps()) + extra_bdeps.extend(t.depends) + extra_bdeps.extend(a for a in t.cmd_args if isinstance(a, build.BuildTarget)) extra_paths = self.determine_windows_extra_paths(exe, extra_bdeps) - for a in t.cmd_args: - if isinstance(a, build.BuildTarget): - for p in self.determine_windows_extra_paths(a, []): - if p not in extra_paths: - extra_paths.append(p) else: extra_paths = [] @@ -1289,8 +1222,12 @@ class Backend: else: raise MesonException('Bad object in test command.') + # set LD_LIBRARY_PATH for + # a) dependencies, as relying on rpath is not very safe: + # https://github.com/mesonbuild/meson/pull/11119 + # b) depends and targets passed via args. t_env = copy.deepcopy(t.env) - if not machine.is_windows() and not machine.is_cygwin() and not machine.is_darwin(): + if not machine.is_windows() and not machine.is_cygwin(): ld_lib_path_libs: T.Set[build.SharedLibrary] = set() for d in depends: if isinstance(d, build.BuildTarget): @@ -1299,10 +1236,12 @@ class Backend: ld_lib_path_libs.add(l) env_build_dir = self.environment.get_build_dir() - ld_lib_path: T.Set[str] = set(os.path.join(env_build_dir, l.get_subdir()) for l in ld_lib_path_libs) + ld_lib_path: T.Set[str] = set(os.path.join(env_build_dir, l.get_builddir()) for l in ld_lib_path_libs) if ld_lib_path: t_env.prepend('LD_LIBRARY_PATH', list(ld_lib_path), ':') + if machine.is_darwin(): + t_env.prepend('DYLD_LIBRARY_PATH', list(ld_lib_path), ':') ts = TestSerialisation(t.get_name(), t.project_name, t.suite, cmd, is_cross, exe_wrapper, self.environment.need_exe_wrapper(), @@ -1320,7 +1259,7 @@ class Backend: def write_test_serialisation(self, tests: T.List['Test'], datafile: T.BinaryIO) -> None: pickle.dump(self.create_test_serialisation(tests), datafile) - def construct_target_rel_paths(self, t: T.Union[build.Target, build.CustomTargetIndex], workdir: T.Optional[str]) -> T.List[str]: + def construct_target_rel_paths(self, t: build.AnyTargetType, workdir: T.Optional[str]) -> T.List[str]: target_dir = self.get_target_dir(t) # ensure that test executables can be run when passed as arguments if isinstance(t, build.Executable) and workdir is None: @@ -1371,7 +1310,7 @@ class Backend: '''List of all files whose alteration means that the build definition needs to be regenerated.''' deps = OrderedSet([str(Path(self.build_to_src) / df) - for df in self.interpreter.get_build_def_files()]) + for df in self.build.def_files]) if self.environment.is_cross_build(): deps.update(self.environment.coredata.cross_files) deps.update(self.environment.coredata.config_files) @@ -1442,27 +1381,31 @@ class Backend: result[name] = b return result - def get_testlike_targets(self, benchmark: bool = False) -> T.OrderedDict[str, T.Union[build.BuildTarget, build.CustomTarget]]: - result: T.OrderedDict[str, T.Union[build.BuildTarget, build.CustomTarget]] = OrderedDict() + def get_testlike_targets(self, benchmark: bool = False) -> T.Iterable[T.Union[build.BuildTarget, build.CustomTarget]]: targets = self.build.get_benchmarks() if benchmark else self.build.get_tests() for t in targets: exe = t.exe - if isinstance(exe, (build.CustomTarget, build.BuildTarget)): - result[exe.get_id()] = exe + if isinstance(exe, build.CustomTargetIndex): + yield exe.target + elif isinstance(exe, (build.CustomTarget, build.BuildTarget)): + yield exe for arg in t.cmd_args: - if not isinstance(arg, (build.CustomTarget, build.BuildTarget)): - continue - result[arg.get_id()] = arg + if isinstance(arg, build.CustomTargetIndex): + yield arg.target + elif isinstance(arg, (build.CustomTarget, build.BuildTarget)): + yield arg for dep in t.depends: assert isinstance(dep, (build.CustomTarget, build.BuildTarget, build.CustomTargetIndex)) - result[dep.get_id()] = dep - return result + if isinstance(dep, build.CustomTargetIndex): + yield dep.target + else: + yield dep @lru_cache(maxsize=None) def get_custom_target_provided_by_generated_source(self, generated_source: build.CustomTarget) -> 'ImmutableListProtocol[str]': libs: T.List[str] = [] for f in generated_source.get_outputs(): - if self.environment.is_library(f): + if compilers.is_library(f): libs.append(os.path.join(self.get_target_dir(generated_source), f)) return libs @@ -1520,7 +1463,7 @@ class Backend: deps.append(os.path.join(self.build_to_src, target.subdir, i)) return deps - def get_custom_target_output_dir(self, target: T.Union[build.Target, build.CustomTargetIndex]) -> str: + def get_custom_target_output_dir(self, target: build.AnyTargetType) -> str: # The XCode backend is special. A target foo/bar does # not go to ${BUILDDIR}/foo/bar but instead to # ${BUILDDIR}/${BUILDTYPE}/foo/bar. @@ -1562,7 +1505,7 @@ class Backend: def eval_custom_target_command( self, target: build.CustomTarget, absolute_outputs: bool = False) -> \ - T.Tuple[T.List[str], T.List[str], T.List[str]]: + T.Tuple[T.List[str], T.List[str], T.List[str | programs.ExternalProgram]]: # We want the outputs to be absolute only when using the VS backend # XXX: Maybe allow the vs backend to use relative paths too? source_root = self.build_to_src @@ -1575,7 +1518,7 @@ class Backend: outputs = [os.path.join(outdir, i) for i in target.get_outputs()] inputs = self.get_custom_target_sources(target) # Evaluate the command list - cmd: T.List[str] = [] + cmd: T.List[str | programs.ExternalProgram] = [] for i in target.command: if isinstance(i, build.BuildTarget): cmd += self.build_target_to_cmd_array(i) @@ -1611,6 +1554,9 @@ class Backend: if not target.absolute_paths: pdir = self.get_target_private_dir(target) i = i.replace('@PRIVATE_DIR@', pdir) + elif isinstance(i, programs.ExternalProgram): + # Let it pass and be extended elsewhere + pass else: raise RuntimeError(f'Argument {i} is of unknown type {type(i)}') cmd.append(i) @@ -1635,9 +1581,18 @@ class Backend: # fixed. # # https://github.com/mesonbuild/meson/pull/737 - cmd = [i.replace('\\', '/') for i in cmd] + cmd = [i.replace('\\', '/') if isinstance(i, str) else i for i in cmd] return inputs, outputs, cmd + def transform_link_args(self, target: build.BuildTarget, args: list[str]) -> list[str]: + resolved_args = [] + for i in args: + if '@PRIVATE_DIR@' in i: + pdir = self.get_target_private_dir(target) + i = i.replace('@PRIVATE_DIR@', pdir) + resolved_args.append(i) + return resolved_args + def get_introspect_command(self) -> str: return ' '.join(shlex.quote(x) for x in self.environment.get_build_command() + ['introspect']) @@ -1777,7 +1732,7 @@ class Backend: for alias, to, tag in t.get_aliases(): alias = os.path.join(first_outdir, alias) - s = InstallSymlinkData(to, alias, first_outdir, t.subproject, tag, allow_missing=True) + s = InstallSymlinkData(to, alias, first_outdir, t.subproject, tag) d.symlinks.append(s) if isinstance(t, (build.SharedLibrary, build.SharedModule, build.Executable)): @@ -1996,6 +1951,8 @@ class Backend: compiler += [j] elif isinstance(j, (build.BuildTarget, build.CustomTarget)): compiler += j.get_outputs() + elif isinstance(j, programs.ExternalProgram): + compiler += j.get_command() else: raise RuntimeError(f'Type "{type(j).__name__}" is not supported in get_introspection_data. This is a bug') @@ -2079,7 +2036,7 @@ class Backend: compiler: 'Compiler', sources: _ALL_SOURCES_TYPE, output_templ: str, - depends: T.Optional[T.List[T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex]]] = None, + depends: T.Optional[T.List[build.BuildTargetTypes]] = None, ) -> build.GeneratedList: ''' Some backends don't support custom compilers. This is a convenience @@ -2088,10 +2045,11 @@ class Backend: exe = programs.ExternalProgram(compiler.get_exe()) args = compiler.get_exe_args() commands = self.compiler_to_generator_args(target, compiler) - generator = build.Generator(exe, args + commands.to_native(), + generator = build.Generator(self.environment, + exe, args + commands.to_native(), [output_templ], depfile='@PLAINNAME@.d', depends=depends) - return generator.process_files(sources, self.interpreter) + return generator.process_files(sources) def compile_target_to_generator(self, target: build.CompileTarget) -> build.GeneratedList: all_sources = T.cast('_ALL_SOURCES_TYPE', target.sources) + T.cast('_ALL_SOURCES_TYPE', target.generated) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index d7de987..086d195 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -20,16 +20,17 @@ import typing as T from . import backends from .. import modules -from .. import environment, mesonlib +from .. import mesonlib from .. import build from .. import mlog from .. import compilers +from .. import tooldetect from ..arglist import CompilerArgs -from ..compilers import Compiler +from ..compilers import Compiler, is_library from ..linkers import ArLikeLinker, RSPFileSyntax from ..mesonlib import ( File, LibType, MachineChoice, MesonBugException, MesonException, OrderedSet, PerMachine, - ProgressBar, quote_arg + ProgressBar, quote_arg, unique_list ) from ..mesonlib import get_compiler_for_source, has_path_sep, is_parent_path from ..options import OptionKey @@ -41,7 +42,6 @@ if T.TYPE_CHECKING: from .._typing import ImmutableListProtocol from ..build import ExtractedObjects, LibTypes - from ..interpreter import Interpreter from ..linkers.linkers import DynamicLinker, StaticLinker from ..compilers.cs import CsCompiler from ..compilers.fortran import FortranCompiler @@ -477,10 +477,16 @@ class RustCrate: return ret +@dataclass +class ImportStdInfo: + gen_target: NinjaBuildElement + gen_module_file: str + gen_objects: T.List[str] + class NinjaBackend(backends.Backend): - def __init__(self, build: T.Optional[build.Build], interpreter: T.Optional[Interpreter]): - super().__init__(build, interpreter) + def __init__(self, build: T.Optional[build.Build]): + super().__init__(build) self.name = 'ninja' self.ninja_filename = 'build.ninja' self.fortran_deps: T.Dict[str, T.Dict[str, File]] = {} @@ -500,11 +506,7 @@ class NinjaBackend(backends.Backend): # - https://github.com/mesonbuild/meson/pull/9453 # - https://github.com/mesonbuild/meson/issues/9479#issuecomment-953485040 self.allow_thin_archives = PerMachine[bool](True, True) - if self.environment: - for for_machine in MachineChoice: - if 'cuda' in self.environment.coredata.compilers[for_machine]: - mlog.debug('cuda enabled globally, disabling thin archives for {}, since nvcc/nvlink cannot handle thin archives natively'.format(for_machine)) - self.allow_thin_archives[for_machine] = False + self.import_std: T.Optional[ImportStdInfo] = None def create_phony_target(self, dummy_outfile: str, rulename: str, phony_infilename: str) -> NinjaBuildElement: ''' @@ -523,7 +525,7 @@ class NinjaBackend(backends.Backend): return NinjaBuildElement(self.all_outputs, to_name, rulename, phony_infilename) - def detect_vs_dep_prefix(self, tempfilename): + def detect_vs_dep_prefix(self, tempfilename: str) -> T.TextIO: '''VS writes its dependency in a locale dependent format. Detect the search prefix to use.''' # TODO don't hard-code host @@ -573,7 +575,7 @@ class NinjaBackend(backends.Backend): # \MyDir\include\stdio.h. matchre = re.compile(rb"^(.*\s)([a-zA-Z]:[\\/]|[\\\/]).*stdio.h$") - def detect_prefix(out): + def detect_prefix(out: bytes) -> T.TextIO: for line in re.split(rb'\r?\n', out): match = matchre.match(line) if match: @@ -595,7 +597,13 @@ class NinjaBackend(backends.Backend): # We don't yet have a use case where we'd expect to make use of this, # so no harm in catching and reporting something unexpected. raise MesonBugException('We do not expect the ninja backend to be given a valid \'vslite_ctx\'') - ninja = environment.detect_ninja_command_and_version(log=True) + if self.environment: + for for_machine in MachineChoice: + if 'cuda' in self.environment.coredata.compilers[for_machine]: + mlog.debug('cuda enabled globally, disabling thin archives for {}, since nvcc/nvlink cannot handle thin archives natively'.format(for_machine)) + self.allow_thin_archives[for_machine] = False + + ninja = tooldetect.detect_ninja_command_and_version(log=True) if self.environment.coredata.optstore.get_value_for(OptionKey('vsenv')): builddir = Path(self.environment.get_build_dir()) try: @@ -656,7 +664,7 @@ class NinjaBackend(backends.Backend): key = OptionKey('b_coverage') if key in self.environment.coredata.optstore and\ self.environment.coredata.optstore.get_value_for('b_coverage'): - gcovr_exe, gcovr_version, lcov_exe, lcov_version, genhtml_exe, llvm_cov_exe = environment.find_coverage_tools(self.environment.coredata) + gcovr_exe, gcovr_version, lcov_exe, lcov_version, genhtml_exe, llvm_cov_exe = tooldetect.find_coverage_tools(self.environment.coredata) mlog.debug(f'Using {gcovr_exe} ({gcovr_version}), {lcov_exe} and {llvm_cov_exe} for code coverage') if gcovr_exe or (lcov_exe and genhtml_exe): self.add_build_comment(NinjaComment('Coverage rules')) @@ -746,9 +754,9 @@ class NinjaBackend(backends.Backend): if isinstance(genlist, (build.CustomTarget, build.CustomTargetIndex)): continue for src in genlist.get_outputs(): - if self.environment.is_header(src): + if compilers.is_header(src): header_deps.append(self.get_target_generated_dir(target, genlist, src)) - if 'vala' in target.compilers and not isinstance(target, build.Executable): + if target.vala_header: vala_header = File.from_built_file(self.get_target_dir(target), target.vala_header) header_deps.append(vala_header) # Recurse and find generated headers @@ -785,11 +793,11 @@ class NinjaBackend(backends.Backend): srcs[f] = s return srcs - def get_target_source_can_unity(self, target, source): + def get_target_source_can_unity(self, target, source: FileOrString) -> bool: if isinstance(source, File): source = source.fname - if self.environment.is_llvm_ir(source) or \ - self.environment.is_assembly(source): + if compilers.is_llvm_ir(source) or \ + compilers.is_assembly(source): return False suffix = os.path.splitext(source)[1][1:].lower() for lang in backends.LANGS_CANT_UNITY: @@ -870,13 +878,15 @@ class NinjaBackend(backends.Backend): } tgt[lnk_hash] = lnk_block - def generate_target(self, target) -> None: + def generate_target(self, target: T.Union[build.BuildTarget, build.CustomTarget, build.RunTarget]) -> None: if isinstance(target, build.BuildTarget): os.makedirs(self.get_target_private_dir_abs(target), exist_ok=True) if isinstance(target, build.CustomTarget): self.generate_custom_target(target) + return if isinstance(target, build.RunTarget): self.generate_run_target(target) + return compiled_sources: T.List[str] = [] source2object: T.Dict[str, str] = {} name = target.get_id() @@ -890,14 +900,14 @@ class NinjaBackend(backends.Backend): self.generate_shlib_aliases(target, self.get_target_dir(target)) + # Generate rules for GeneratedLists + self.generate_generator_list_rules(target) + # If target uses a language that cannot link to C objects, # just generate for that language and return. if isinstance(target, build.Jar): self.generate_jar_target(target) return - if target.uses_rust(): - self.generate_rust_target(target) - return if 'cs' in target.compilers: self.generate_cs_target(target) return @@ -921,7 +931,7 @@ class NinjaBackend(backends.Backend): # a language that is handled below, such as C or C++ transpiled_sources: T.List[str] - if 'vala' in target.compilers: + if target.uses_vala(): # Sources consumed by valac are filtered out. These only contain # C/C++ sources, objects, generated libs, and unknown sources now. target_sources, generated_sources, \ @@ -934,8 +944,6 @@ class NinjaBackend(backends.Backend): generated_sources = self.get_target_generated_sources(target) transpiled_sources = [] self.scan_fortran_module_outputs(target) - # Generate rules for GeneratedLists - self.generate_generator_list_rules(target) # Generate rules for building the remaining source files in this target outname = self.get_target_filename(target) @@ -964,16 +972,16 @@ class NinjaBackend(backends.Backend): generated_source_files: T.List[File] = [] for rel_src in generated_sources.keys(): raw_src = File.from_built_relative(rel_src) - if self.environment.is_source(rel_src): + if compilers.is_source(rel_src): if is_unity and self.get_target_source_can_unity(target, rel_src): unity_deps.append(raw_src) abs_src = os.path.join(self.environment.get_build_dir(), rel_src) unity_src.append(abs_src) else: generated_source_files.append(raw_src) - elif self.environment.is_object(rel_src): + elif compilers.is_object(rel_src): obj_list.append(rel_src) - elif self.environment.is_library(rel_src) or modules.is_module_library(rel_src): + elif compilers.is_library(rel_src) or modules.is_module_library(rel_src): pass elif is_compile_target: generated_source_files.append(raw_src) @@ -991,7 +999,9 @@ class NinjaBackend(backends.Backend): # this target. We create the Ninja build file elements for this here # because we need `header_deps` to be fully generated in the above loop. for src in generated_source_files: - if self.environment.is_llvm_ir(src): + if not compilers.is_separate_compile(src): + continue + if compilers.is_llvm_ir(src): o, s = self.generate_llvm_ir_compile(target, src) else: o, s = self.generate_single_compile(target, src, True, order_deps=header_deps) @@ -1039,7 +1049,7 @@ class NinjaBackend(backends.Backend): # compile we get precise dependency info from dep files. # This should work in all cases. If it does not, then just # move them from orderdeps to proper deps. - if self.environment.is_header(src): + if compilers.is_header(src): header_deps.append(raw_src) else: transpiled_source_files.append(raw_src) @@ -1049,21 +1059,24 @@ class NinjaBackend(backends.Backend): # Generate compile targets for all the preexisting sources for this target for src in target_sources.values(): - if not self.environment.is_header(src) or is_compile_target: - if self.environment.is_llvm_ir(src): - o, s = self.generate_llvm_ir_compile(target, src) - obj_list.append(o) - elif is_unity and self.get_target_source_can_unity(target, src): - abs_src = os.path.join(self.environment.get_build_dir(), - src.rel_to_builddir(self.build_to_src)) - unity_src.append(abs_src) - else: - o, s = self.generate_single_compile(target, src, False, [], - header_deps + d_generated_deps + fortran_order_deps, - fortran_inc_args) - obj_list.append(o) - compiled_sources.append(s) - source2object[s] = o + if not compilers.is_separate_compile(src): + continue + if compilers.is_header(src) and not is_compile_target: + continue + if compilers.is_llvm_ir(src): + o, s = self.generate_llvm_ir_compile(target, src) + obj_list.append(o) + elif is_unity and self.get_target_source_can_unity(target, src): + abs_src = os.path.join(self.environment.get_build_dir(), + src.rel_to_builddir(self.build_to_src)) + unity_src.append(abs_src) + else: + o, s = self.generate_single_compile(target, src, False, [], + header_deps + d_generated_deps + fortran_order_deps, + fortran_inc_args) + obj_list.append(o) + compiled_sources.append(s) + source2object[s] = o if is_unity: for src in self.generate_unity_files(target, unity_src): @@ -1083,8 +1096,14 @@ class NinjaBackend(backends.Backend): final_obj_list = self.generate_prelink(target, obj_list) else: final_obj_list = obj_list - elem = self.generate_link(target, outname, final_obj_list, linker, pch_objects, stdlib_args=stdlib_args) + self.generate_dependency_scan_target(target, compiled_sources, source2object, fortran_order_deps) + + if target.uses_rust(): + self.generate_rust_target(target, outname, final_obj_list, fortran_order_deps) + return + + elem = self.generate_link(target, outname, final_obj_list, linker, pch_objects, stdlib_args=stdlib_args) self.add_build(elem) #In AIX, we archive shared libraries. If the instance is a shared library, we add a command to archive the shared library #object and create the build element. @@ -1189,7 +1208,7 @@ class NinjaBackend(backends.Backend): if isinstance(s, build.GeneratedList): self.generate_genlist_for_target(s, target) - def unwrap_dep_list(self, target): + def unwrap_dep_list(self, target: T.Union[build.CustomTarget, build.RunTarget]) -> T.List[str]: deps = [] for i in target.get_dependencies(): # FIXME, should not grab element at zero but rather expand all. @@ -1223,6 +1242,7 @@ class NinjaBackend(backends.Backend): capture=ofilenames[0] if target.capture else None, feed=srcs[0] if target.feed else None, env=target.env, + can_use_rsp_file=target.rspable, verbose=target.console) if reason: cmd_type = f' (wrapped by meson {reason})' @@ -1242,7 +1262,7 @@ class NinjaBackend(backends.Backend): self.add_build(elem) self.processed_targets.add(target.get_id()) - def build_run_target_name(self, target) -> str: + def build_run_target_name(self, target: build.RunTarget) -> str: if target.subproject != '': subproject_prefix = f'{target.subproject}@@' else: @@ -1554,7 +1574,6 @@ class NinjaBackend(backends.Backend): elem.add_item('ARGS', commands) self.add_build(elem) - self.generate_generator_list_rules(target) self.create_target_source_introspection(target, compiler, commands, rel_srcs, generated_rel_srcs) def determine_java_compile_args(self, target, compiler) -> T.List[str]: @@ -1572,7 +1591,7 @@ class NinjaBackend(backends.Backend): args += ['-sourcepath', sourcepath] return args - def generate_java_compile(self, srcs, target, compiler, args): + def generate_java_compile(self, srcs, target, compiler, args) -> str: deps = [os.path.join(self.get_target_dir(l), l.get_filename()) for l in target.link_targets] generated_sources = self.get_target_generated_sources(target) for rel_src in generated_sources.keys(): @@ -1603,25 +1622,24 @@ class NinjaBackend(backends.Backend): description = 'Creating JAR $out' self.add_rule(NinjaRule(rule, command, [], description)) - def determine_dep_vapis(self, target) -> T.List[str]: + def determine_dep_vapis(self, target: build.BuildTarget) -> T.List[str]: """ Peek into the sources of BuildTargets we're linking with, and if any of them was built with Vala, assume that it also generated a .vapi file of the same name as the BuildTarget and return the path to it relative to the build directory. """ - result = OrderedSet() + result: OrderedSet[str] = OrderedSet() for dep in itertools.chain(target.link_targets, target.link_whole_targets): - if not dep.is_linkable_target(): + if not (isinstance(dep, build.BuildTarget) and dep.is_linkable_target()): continue for i in dep.sources: - if hasattr(i, 'fname'): - i = i.fname if i.split('.')[-1] in compilers.lang_suffixes['vala']: - vapiname = dep.vala_vapi - fullname = os.path.join(self.get_target_dir(dep), vapiname) - result.add(fullname) - break + if dep.vala_vapi is not None: + vapiname = dep.vala_vapi + fullname = os.path.join(self.get_target_dir(dep), vapiname) + result.add(fullname) + break return list(result) def split_vala_sources(self, t: build.BuildTarget) -> \ @@ -1740,31 +1758,36 @@ class NinjaBackend(backends.Backend): # Library name args += ['--library', target.name] # Outputted header - hname = os.path.join(self.get_target_dir(target), target.vala_header) - args += ['--header', hname] - if self.is_unity(target): - # Without this the declarations will get duplicated in the .c - # files and cause a build failure when all of them are - # #include-d in one .c file. - # https://github.com/mesonbuild/meson/issues/1969 - args += ['--use-header'] - valac_outputs.append(hname) + if target.vala_header is not None: + hname = os.path.join(self.get_target_dir(target), target.vala_header) + args += ['--header', hname] + if self.is_unity(target): + # Without this the declarations will get duplicated in the .c + # files and cause a build failure when all of them are + # #include-d in one .c file. + # https://github.com/mesonbuild/meson/issues/1969 + args += ['--use-header'] + valac_outputs.append(hname) # Outputted vapi file - vapiname = os.path.join(self.get_target_dir(target), target.vala_vapi) - # Force valac to write the vapi and gir files in the target build dir. - # Without this, it will write it inside c_out_dir - args += ['--vapi', os.path.join('..', target.vala_vapi)] - valac_outputs.append(vapiname) + if target.vala_vapi is not None: + vapiname = os.path.join(self.get_target_dir(target), target.vala_vapi) + # Force valac to write the vapi and gir files in the target build dir. + # Without this, it will write it inside c_out_dir + args += ['--vapi', os.path.join('..', target.vala_vapi)] + valac_outputs.append(vapiname) # Install header and vapi to default locations if user requests this if len(target.install_dir) > 1 and target.install_dir[1] is True: target.install_dir[1] = self.environment.get_includedir() if len(target.install_dir) > 2 and target.install_dir[2] is True: target.install_dir[2] = os.path.join(self.environment.get_datadir(), 'vala', 'vapi') # Generate GIR if requested - if isinstance(target.vala_gir, str): + if target.vala_gir is not None: girname = os.path.join(self.get_target_dir(target), target.vala_gir) args += ['--gir', os.path.join('..', target.vala_gir)] valac_outputs.append(girname) + shared_target = target.get('shared') + if isinstance(shared_target, build.SharedLibrary): + args += ['--shared-library', shared_target.get_filename()] # Install GIR to default location if requested by user if len(target.install_dir) > 3 and target.install_dir[3] is True: target.install_dir[3] = os.path.join(self.environment.get_datadir(), 'gir-1.0') @@ -1775,7 +1798,7 @@ class NinjaBackend(backends.Backend): gres_xml, = self.get_custom_target_sources(gensrc) args += ['--gresources=' + gres_xml] for source_dir in gensrc.source_dirs: - gres_dirs += [os.path.join(self.get_target_dir(gensrc), source_dir)] + gres_dirs += [source_dir] # Ensure that resources are built before vala sources # This is required since vala code using [GtkTemplate] effectively depends on .ui files # GResourceHeaderTarget is not suitable due to lacking depfile @@ -1791,6 +1814,8 @@ class NinjaBackend(backends.Backend): self.compiler_to_rule_name(valac), all_files + dependency_vapis) element.add_item('ARGS', args) + depfile = valac.depfile_for_object(os.path.join(self.get_target_dir(target), target.name)) + element.add_item('DEPFILE', depfile) element.add_dep(extra_dep_files) self.add_build(element) self.create_target_source_introspection(target, valac, args, all_files, []) @@ -1810,8 +1835,8 @@ class NinjaBackend(backends.Backend): args += cython.get_always_args() args += cython.get_debug_args(self.get_target_option(target, 'debug')) args += cython.get_optimization_args(self.get_target_option(target, 'optimization')) - args += cython.get_option_compile_args(target, self.environment, target.subproject) - args += cython.get_option_std_args(target, self.environment, target.subproject) + args += cython.get_option_compile_args(target, target.subproject) + args += cython.get_option_std_args(target, target.subproject) args += self.build.get_global_args(cython, target.for_machine) args += self.build.get_project_args(cython, target.subproject, target.for_machine) args += target.get_extra_args('cython') @@ -1857,9 +1882,9 @@ class NinjaBackend(backends.Backend): generated_sources[ssrc] = mesonlib.File.from_built_file(gen.get_subdir(), ssrc) # Following logic in L883-900 where we determine whether to add generated source # as a header(order-only) dep to the .so compilation rule - if not self.environment.is_source(ssrc) and \ - not self.environment.is_object(ssrc) and \ - not self.environment.is_library(ssrc) and \ + if not compilers.is_source(ssrc) and \ + not compilers.is_object(ssrc) and \ + not compilers.is_library(ssrc) and \ not modules.is_module_library(ssrc): header_deps.append(ssrc) for source in pyx_sources: @@ -1877,24 +1902,28 @@ class NinjaBackend(backends.Backend): elem.add_orderdep(instr) self.add_build(elem) - def __generate_sources_structure(self, root: Path, structured_sources: build.StructuredSources) -> T.Tuple[T.List[str], T.Optional[str]]: + def __generate_sources_structure(self, root: Path, structured_sources: build.StructuredSources, + main_file_ext: T.Union[str, T.Tuple[str, ...]] = tuple(), + ) -> T.Tuple[T.List[str], T.Optional[str]]: first_file: T.Optional[str] = None orderdeps: T.List[str] = [] for path, files in structured_sources.sources.items(): for file in files: if isinstance(file, File): out = root / path / Path(file.fname).name - orderdeps.append(str(out)) self._generate_copy_target(file, out) - if first_file is None: - first_file = str(out) + out_s = str(out) + orderdeps.append(out_s) + if first_file is None and out_s.endswith(main_file_ext): + first_file = out_s else: for f in file.get_outputs(): out = root / path / f - orderdeps.append(str(out)) + out_s = str(out) + orderdeps.append(out_s) self._generate_copy_target(str(Path(file.subdir) / f), out) - if first_file is None: - first_file = str(out) + if first_file is None and out_s.endswith(main_file_ext): + first_file = out_s return orderdeps, first_file def _add_rust_project_entry(self, name: str, main_rust_file: str, args: CompilerArgs, @@ -1914,7 +1943,7 @@ class NinjaBackend(backends.Backend): crate = RustCrate( len(self.rust_crates), - name, + self._get_rust_crate_name(name), main_rust_file, crate_type, target_name, @@ -1928,11 +1957,20 @@ class NinjaBackend(backends.Backend): self.rust_crates[name] = crate - def _get_rust_dependency_name(self, target: build.BuildTarget, dependency: LibTypes) -> str: - # Convert crate names with dashes to underscores by default like - # cargo does as dashes can't be used as parts of identifiers - # in Rust - return target.rust_dependency_map.get(dependency.name, dependency.name).replace('-', '_') + @staticmethod + def _get_rust_crate_name(target_name: str) -> str: + # Rustc replaces - with _. spaces or dots are not allowed, so we replace them with underscores + # Also +SUFFIX is dropped, which can be used to distinguish host from build crates + crate_name = target_name.replace('-', '_').replace(' ', '_').replace('.', '_') + return crate_name.split('+', 1)[0] + + @staticmethod + def _get_rust_dependency_name(target: build.BuildTarget, dependency: LibTypes) -> str: + crate_name_raw = target.rust_dependency_map.get(dependency.name, None) + if crate_name_raw is None: + dependency_crate_name = NinjaBackend._get_rust_crate_name(dependency.name) + crate_name_raw = target.rust_dependency_map.get(dependency_crate_name, dependency.name) + return NinjaBackend._get_rust_crate_name(crate_name_raw) def generate_rust_sources(self, target: build.BuildTarget) -> T.Tuple[T.List[str], str]: orderdeps: T.List[str] = [] @@ -1940,23 +1978,33 @@ class NinjaBackend(backends.Backend): # Rust compiler takes only the main file as input and # figures out what other files are needed via import # statements and magic. - main_rust_file = None + main_rust_file: T.Optional[str] = None if target.structured_sources: if target.structured_sources.needs_copy(): _ods, main_rust_file = self.__generate_sources_structure(Path( - self.get_target_private_dir(target)) / 'structured', target.structured_sources) + self.get_target_private_dir(target)) / 'structured', target.structured_sources, '.rs') + if main_rust_file is None: + raise MesonException('Could not find a rust file to treat as the main file for ', target.name) else: # The only way to get here is to have only files in the "root" # positional argument, which are all generated into the same # directory - g = target.structured_sources.first_file() - - if isinstance(g, File): - main_rust_file = g.rel_to_builddir(self.build_to_src) - elif isinstance(g, GeneratedList): - main_rust_file = os.path.join(self.get_target_private_dir(target), g.get_outputs()[0]) - else: - main_rust_file = os.path.join(g.get_subdir(), g.get_outputs()[0]) + for g in target.structured_sources.sources['']: + if isinstance(g, File): + if g.endswith('.rs'): + main_rust_file = g.rel_to_builddir(self.build_to_src) + elif isinstance(g, GeneratedList): + for h in g.get_outputs(): + if h.endswith('.rs'): + main_rust_file = os.path.join(self.get_target_private_dir(target), h) + break + else: + for h in g.get_outputs(): + if h.endswith('.rs'): + main_rust_file = os.path.join(g.get_subdir(), h) + break + if main_rust_file is not None: + break _ods = [] for f in target.structured_sources.as_list(): @@ -1967,9 +2015,10 @@ class NinjaBackend(backends.Backend): for s in f.get_outputs()]) self.all_structured_sources.update(_ods) orderdeps.extend(_ods) + return orderdeps, main_rust_file for i in target.get_sources(): - if main_rust_file is None: + if main_rust_file is None and i.endswith('.rs'): main_rust_file = i.rel_to_builddir(self.build_to_src) for g in target.get_generated_sources(): for i in g.get_outputs(): @@ -1977,7 +2026,7 @@ class NinjaBackend(backends.Backend): fname = os.path.join(self.get_target_private_dir(target), i) else: fname = os.path.join(g.get_subdir(), i) - if main_rust_file is None: + if main_rust_file is None and fname.endswith('.rs'): main_rust_file = fname orderdeps.append(fname) @@ -1992,12 +2041,12 @@ class NinjaBackend(backends.Backend): args.extend(['--crate-type', src_crate_type]) # If we're dynamically linking, add those arguments - if target.rust_crate_type in {'bin', 'dylib'}: + if target.rust_crate_type in {'bin', 'dylib', 'cdylib'}: args.extend(rustc.get_linker_always_args()) + args += compilers.get_base_link_args(target, rustc, self.environment) args += self.generate_basic_compiler_args(target, rustc) - # Rustc replaces - with _. spaces or dots are not allowed, so we replace them with underscores - args += ['--crate-name', target.name.replace('-', '_').replace(' ', '_').replace('.', '_')] + args += ['--crate-name', self._get_rust_crate_name(target.name)] if depfile: args += rustc.get_dependency_gen_args(target_name, depfile) args += rustc.get_output_args(target_name) @@ -2005,48 +2054,46 @@ class NinjaBackend(backends.Backend): args += target.get_extra_args('rust') return args - def get_rust_compiler_deps_and_args(self, target: build.BuildTarget, rustc: Compiler) -> T.Tuple[T.List[str], T.List[str], T.List[RustDep], T.List[str]]: + def get_rust_compiler_deps_and_args(self, target: build.BuildTarget, rustc: Compiler, + obj_list: T.List[str]) -> T.Tuple[T.List[str], T.List[RustDep], T.List[str]]: deps: T.List[str] = [] project_deps: T.List[RustDep] = [] args: T.List[str] = [] - # Rustc always use non-debug Windows runtime. Inject the one selected - # by Meson options instead. - # https://github.com/rust-lang/rust/issues/39016 - if not isinstance(target, build.StaticLibrary): - try: - buildtype = self.get_target_option(target, 'buildtype') - crt = self.get_target_option(target, 'b_vscrt') - args += rustc.get_crt_link_args(crt, buildtype) - except (KeyError, AttributeError): - pass - - if mesonlib.version_compare(rustc.version, '>= 1.67.0'): - verbatim = '+verbatim' - else: - verbatim = '' - - def _link_library(libname: str, static: bool, bundle: bool = False): + def _link_library(libname: str, static: bool, bundle: bool = False) -> None: + orig_libname = libname type_ = 'static' if static else 'dylib' modifiers = [] + # Except with -Clink-arg, search is limited to the -L search paths + dir_, libname = os.path.split(libname) + linkdirs.add(dir_) if not bundle and static: modifiers.append('-bundle') - if verbatim: - modifiers.append(verbatim) + if rustc.has_verbatim(): + modifiers.append('+verbatim') + else: + libname = rustc.lib_file_to_l_arg(libname) + if libname is None: + raise MesonException(f"rustc does not implement '-l{type_}:+verbatim'; cannot link to '{orig_libname}' due to nonstandard name") + if modifiers: type_ += ':' + ','.join(modifiers) args.append(f'-l{type_}={libname}') - objs, od = self.flatten_object_list(target) - for o in objs: + for o in obj_list: args.append(f'-Clink-arg={o}') deps.append(o) - fortran_order_deps = self.get_fortran_order_deps(od) linkdirs = mesonlib.OrderedSet() external_deps = target.external_deps.copy() target_deps = target.get_dependencies() for d in target_deps: + # rlibs only store -l flags, not -L; help out rustc and always + # add the -L flag, in case it's needed to find non-bundled + # dependencies of an rlib. At this point we don't have + # information on whether this is a direct dependency (which + # might use -Clink-arg= below) or an indirect one, so always + # add to linkdirs. linkdirs.add(d.subdir) deps.append(self.get_dependency_filename(d)) if isinstance(d, build.StaticLibrary): @@ -2079,8 +2126,7 @@ class NinjaBackend(backends.Backend): link_whole = d in target.link_whole_targets if isinstance(target, build.StaticLibrary) or (isinstance(target, build.Executable) and rustc.get_crt_static()): static = isinstance(d, build.StaticLibrary) - libname = os.path.basename(lib) if verbatim else d.name - _link_library(libname, static, bundle=link_whole) + _link_library(lib, static, bundle=link_whole) elif link_whole: link_whole_args = rustc.linker.get_link_whole_for([lib]) args += [f'-Clink-arg={a}' for a in link_whole_args] @@ -2089,22 +2135,19 @@ class NinjaBackend(backends.Backend): for e in external_deps: for a in e.get_link_args(): - if a in rustc.native_static_libs: - # Exclude link args that rustc already add by default - pass - elif a.startswith('-L'): + if a.startswith('-L'): args.append(a) - elif a.endswith(('.dll', '.so', '.dylib', '.a', '.lib')) and isinstance(target, build.StaticLibrary): - dir_, lib = os.path.split(a) + continue + elif is_library(a): + if isinstance(target, build.StaticLibrary): + static = a.endswith(('.a', '.lib')) + _link_library(a, static) + continue + + dir_, _ = os.path.split(a) linkdirs.add(dir_) - if not verbatim: - lib, ext = os.path.splitext(lib) - if lib.startswith('lib'): - lib = lib[3:] - static = a.endswith(('.a', '.lib')) - _link_library(lib, static) - else: - args.append(f'-Clink-arg={a}') + + args.append(f'-Clink-arg={a}') for d in linkdirs: d = d or '.' @@ -2119,40 +2162,44 @@ class NinjaBackend(backends.Backend): and dep.rust_crate_type == 'dylib' for dep in target_deps) - if target.rust_crate_type in {'dylib', 'proc-macro'} or has_rust_shared_deps: - # add prefer-dynamic if any of the Rust libraries we link + if target.rust_crate_type in {'dylib', 'proc-macro'}: + # also add prefer-dynamic if any of the Rust libraries we link # against are dynamic or this is a dynamic library itself, # otherwise we'll end up with multiple implementations of libstd. + has_rust_shared_deps = True + elif self.get_target_option(target, 'rust_dynamic_std'): + if target.rust_crate_type == 'staticlib': + # staticlib crates always include a copy of the Rust libstd, + # therefore it is not possible to also link it dynamically. + # The options to avoid this (-Z staticlib-allow-rdylib-deps and + # -Z staticlib-prefer-dynamic) are not yet stable; alternatively, + # one could use "--emit obj" (implemented in the pull request at + # https://github.com/mesonbuild/meson/pull/11213) or "--emit rlib" + # (officially not recommended for linking with C programs). + raise MesonException('rust_dynamic_std does not support staticlib crates yet') + # want libstd as a shared dep + has_rust_shared_deps = True + + if has_rust_shared_deps: args += ['-C', 'prefer-dynamic'] - - if isinstance(target, build.SharedLibrary) or has_shared_deps: + if has_shared_deps or has_rust_shared_deps: args += self.get_build_rpath_args(target, rustc) - return deps, fortran_order_deps, project_deps, args - - def generate_rust_target(self, target: build.BuildTarget) -> None: - rustc = T.cast('RustCompiler', target.compilers['rust']) - self.generate_generator_list_rules(target) - - for i in target.get_sources(): - if not rustc.can_compile(i): - raise InvalidArguments(f'Rust target {target.get_basename()} contains a non-rust source file.') - for g in target.get_generated_sources(): - for i in g.get_outputs(): - if not rustc.can_compile(i): - raise InvalidArguments(f'Rust target {target.get_basename()} contains a non-rust source file.') + return deps, project_deps, args + def generate_rust_target(self, target: build.BuildTarget, target_name: str, obj_list: T.List[str], + fortran_order_deps: T.List[str]) -> None: orderdeps, main_rust_file = self.generate_rust_sources(target) - target_name = self.get_target_filename(target) if main_rust_file is None: raise RuntimeError('A Rust target has no Rust sources. This is weird. Also a bug. Please report') + rustc = T.cast('RustCompiler', target.compilers['rust']) args = rustc.compiler_args() depfile = os.path.join(self.get_target_private_dir(target), target.name + '.d') args += self.get_rust_compiler_args(target, rustc, target.rust_crate_type, depfile) - deps, fortran_order_deps, project_deps, deps_args = self.get_rust_compiler_deps_and_args(target, rustc) + deps, project_deps, deps_args = self.get_rust_compiler_deps_and_args(target, rustc, obj_list) args += deps_args proc_macro_dylib_path = None @@ -2184,10 +2231,14 @@ class NinjaBackend(backends.Backend): if target.doctests: assert target.doctests.target is not None - rustdoc = rustc.get_rustdoc(self.environment) + rustdoc = rustc.get_rustdoc() args = rustdoc.get_exe_args() args += self.get_rust_compiler_args(target.doctests.target, rustdoc, target.rust_crate_type) - _, _, _, deps_args = self.get_rust_compiler_deps_and_args(target.doctests.target, rustdoc) + o, _ = self.flatten_object_list(target.doctests.target) + obj_list = unique_list(obj_list + o) + # Rustc does not add files in the obj_list to Rust rlibs, + # and is added by Meson to all of the dependencies, including here. + _, _, deps_args = self.get_rust_compiler_deps_and_args(target.doctests.target, rustdoc, obj_list) args += deps_args target.doctests.cmd_args = args.to_native() + [main_rust_file] + target.doctests.cmd_args @@ -2207,21 +2258,18 @@ class NinjaBackend(backends.Backend): def compiler_to_pch_rule_name(cls, compiler: Compiler) -> str: return cls.get_compiler_rule_name(compiler.get_language(), compiler.for_machine, 'PCH') - def swift_module_file_name(self, target): + def swift_module_file_name(self, target) -> str: return os.path.join(self.get_target_private_dir(target), - self.target_swift_modulename(target) + '.swiftmodule') - - def target_swift_modulename(self, target): - return target.name + target.swift_module_name + '.swiftmodule') - def determine_swift_dep_modules(self, target): + def determine_swift_dep_modules(self, target) -> T.List[str]: result = [] for l in target.link_targets: if self.is_swift_target(l): result.append(self.swift_module_file_name(l)) return result - def get_swift_link_deps(self, target): + def get_swift_link_deps(self, target) -> T.List[str]: result = [] for l in target.link_targets: result.append(self.get_target_filename(l)) @@ -2239,19 +2287,33 @@ class NinjaBackend(backends.Backend): return srcs, others def generate_swift_target(self, target) -> None: - module_name = self.target_swift_modulename(target) + module_name = target.swift_module_name swiftc = target.compilers['swift'] abssrc = [] relsrc = [] abs_headers = [] header_imports = [] + + if not target.uses_swift_cpp_interop(): + cpp_targets = [t for t in target.link_targets if t.uses_swift_cpp_interop()] + if cpp_targets != []: + target_word = 'targets' if len(cpp_targets) > 1 else 'target' + first = ', '.join(repr(t.name) for t in cpp_targets[:-1]) + and_word = ' and ' if len(cpp_targets) > 1 else '' + last = repr(cpp_targets[-1].name) + enable_word = 'enable' if len(cpp_targets) > 1 else 'enables' + raise MesonException('Swift target {0} links against {1} {2}{3}{4} which {5} C++ interoperability. ' + 'This requires {0} to also have it enabled. ' + 'Add "swift_interoperability_mode: \'cpp\'" to the definition of {0}.' + .format(repr(target.name), target_word, first, and_word, last, enable_word)) + for i in target.get_sources(): if swiftc.can_compile(i): rels = i.rel_to_builddir(self.build_to_src) abss = os.path.normpath(os.path.join(self.environment.get_build_dir(), rels)) relsrc.append(rels) abssrc.append(abss) - elif self.environment.is_header(i): + elif compilers.is_header(i): relh = i.rel_to_builddir(self.build_to_src) absh = os.path.normpath(os.path.join(self.environment.get_build_dir(), relh)) abs_headers.append(absh) @@ -2261,6 +2323,16 @@ class NinjaBackend(backends.Backend): os.makedirs(self.get_target_private_dir_abs(target), exist_ok=True) compile_args = self.generate_basic_compiler_args(target, swiftc) compile_args += swiftc.get_module_args(module_name) + compile_args += swiftc.get_cxx_interoperability_args(target) + compile_args += self.build.get_project_args(swiftc, target.subproject, target.for_machine) + compile_args += self.build.get_global_args(swiftc, target.for_machine) + if isinstance(target, (build.StaticLibrary, build.SharedLibrary)): + # swiftc treats modules with a single source file, and the main.swift file in multi-source file modules + # as top-level code. This is undesirable in library targets since it emits a main function. Add the + # -parse-as-library option as necessary to prevent emitting the main function while keeping files explicitly + # named main.swift treated as the entrypoint of the module in case this is desired. + if len(abssrc) == 1 and os.path.basename(abssrc[0]) != 'main.swift': + compile_args += swiftc.get_library_args() for i in reversed(target.get_include_dirs()): basedir = i.get_curdir() for d in i.get_incdirs(): @@ -2419,6 +2491,13 @@ class NinjaBackend(backends.Backend): args = [] options = {} self.add_rule(NinjaRule(rule, cmdlist, args, description, **options, extra=None)) + if self.environment.machines[for_machine].is_os2() and complist: + rule = 'IMPORTLIB{}'.format(self.get_rule_suffix(for_machine)) + description = 'Generating import library $out' + command = ['emximp'] + args = ['-o', '$out', '$in'] + options = {} + self.add_rule(NinjaRule(rule, command, args, description, **options, extra=None)) args = self.environment.get_build_command() + \ ['--internal', @@ -2450,9 +2529,19 @@ class NinjaBackend(backends.Backend): def generate_vala_compile_rules(self, compiler) -> None: rule = self.compiler_to_rule_name(compiler) - command = compiler.get_exelist() + ['$ARGS', '$in'] + command = compiler.get_exelist() description = 'Compiling Vala source $in' - self.add_rule(NinjaRule(rule, command, [], description, extra='restat = 1')) + + depargs = compiler.get_dependency_gen_args('$out', '$DEPFILE') + depfile = '$DEPFILE' if depargs else None + depstyle = 'gcc' if depargs else None + + args = depargs + ['$ARGS', '$in'] + + self.add_rule(NinjaRule(rule, command + args, [], description, + depfile=depfile, + deps=depstyle, + extra='restat = 1')) def generate_cython_compile_rules(self, compiler: 'Compiler') -> None: rule = self.compiler_to_rule_name(compiler) @@ -2544,7 +2633,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) command = compiler.get_exelist() args = ['$ARGS'] + depargs + NinjaCommandArg.list(compiler.get_output_args('$out'), Quoting.none) + ['-cm', '$in'] description = 'Compiling to C object $in' - if compiler.get_argument_syntax() == 'msvc': + if compiler.get_depfile_format() == 'msvc': deps = 'msvc' depfile = None else: @@ -2602,7 +2691,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) command = compiler.get_exelist() args = ['$ARGS'] + depargs + NinjaCommandArg.list(compiler.get_output_args('$out'), Quoting.none) + compiler.get_compile_only_args() + ['$in'] description = f'Compiling {compiler.get_display_language()} object $out' - if compiler.get_argument_syntax() == 'msvc': + if compiler.get_depfile_format() == 'msvc': deps = 'msvc' depfile = None else: @@ -2628,7 +2717,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) else: command = compiler.get_exelist() + ['$ARGS'] + depargs + output + compiler.get_compile_only_args() + ['$in'] description = 'Precompiling header $in' - if compiler.get_argument_syntax() == 'msvc': + if compiler.get_depfile_format() == 'msvc': deps = 'msvc' depfile = None else: @@ -2680,7 +2769,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) continue self.generate_genlist_for_target(genlist, target) - def replace_paths(self, target, args, override_subdir=None): + def replace_paths(self, target, args: T.List[str], override_subdir=None) -> T.List[str]: if override_subdir: source_target_dir = os.path.join(self.build_to_src, override_subdir) else: @@ -2822,12 +2911,12 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) mod_files = _scan_fortran_file_deps(src, srcdir, dirname, tdeps, compiler) return mod_files - def get_no_stdlib_link_args(self, target, linker): + def get_no_stdlib_link_args(self, target, linker) -> T.List[str]: if hasattr(linker, 'language') and linker.language in self.build.stdlibs[target.for_machine]: return linker.get_no_stdlib_link_args() return [] - def get_compile_debugfile_args(self, compiler, target, objfile): + def get_compile_debugfile_args(self, compiler, target, objfile) -> T.List[str]: # The way MSVC uses PDB files is documented exactly nowhere so # the following is what we have been able to decipher via # reverse engineering. @@ -2896,7 +2985,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) return linker.get_link_debugfile_args(filename) return [] - def generate_llvm_ir_compile(self, target, src: FileOrString): + def generate_llvm_ir_compile(self, target, src: FileOrString) -> T.Tuple[str, str]: compiler = get_compiler_for_source(target.compilers.values(), src) commands = compiler.compiler_args() # Compiler args for compiling this target @@ -3110,11 +3199,11 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) # Add MSVC debug file generation compile flags: /Fd /FS commands += self.get_compile_debugfile_args(compiler, target, rel_obj) - # PCH handling - if self.target_uses_pch(target): - pchlist = target.get_pch(compiler.language) + # PCH handling. We only support PCH for C and C++ + if compiler.language in {'c', 'cpp'} and target.has_pch() and self.target_uses_pch(target): + pchlist = target.pch[compiler.language] else: - pchlist = [] + pchlist = None if not pchlist: pch_dep = [] elif compiler.id == 'intel': @@ -3127,9 +3216,9 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) # If TASKING compiler family is used and MIL linking is enabled for the target, # then compilation rule name is a special one to output MIL files # instead of object files for .c files - key = OptionKey('b_lto') if compiler.get_id() == 'tasking': - if ((isinstance(target, build.StaticLibrary) and target.prelink) or target.get_option(key)) and src.rsplit('.', 1)[1] in compilers.lang_suffixes['c']: + target_lto = self.get_target_option(target, OptionKey('b_lto', machine=target.for_machine, subproject=target.subproject)) + if ((isinstance(target, build.StaticLibrary) and target.prelink) or target_lto) and src.rsplit('.', 1)[1] in compilers.lang_suffixes['c']: compiler_name = self.get_compiler_rule_name('tasking_mil_compile', compiler.for_machine) else: compiler_name = self.compiler_to_rule_name(compiler) @@ -3159,6 +3248,11 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) rel_obj) self.add_build(depelem) commands += compiler.get_module_outdir_args(self.get_target_private_dir(target)) + + # C++ import std is complicated enough to get its own method. + istd_args, istd_dep = self.handle_cpp_import_std(target, compiler) + commands.extend(istd_args) + header_deps += istd_dep if extra_args is not None: commands.extend(extra_args) @@ -3208,6 +3302,58 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) assert isinstance(rel_src, str) return (rel_obj, rel_src.replace('\\', '/')) + def target_uses_import_std(self, target: build.BuildTarget) -> bool: + if 'cpp' not in target.compilers: + return False + if 'cpp_importstd' not in self.environment.coredata.optstore: + return False + if self.environment.coredata.get_option_for_target(target, 'cpp_importstd') == 'false': + return False + return True + + def handle_cpp_import_std(self, target: build.BuildTarget, compiler): + istd_args = [] + istd_dep = [] + if not self.target_uses_import_std(target): + return istd_args, istd_dep + mlog.warning('Import std support is experimental and might break compatibility in the future.') + # At the time of writing, all three major compilers work + # wildly differently. Keep this isolated here until things + # consolidate. + if compiler.id == 'gcc': + if self.import_std is None: + mod_file = 'gcm.cache/std.gcm' + mod_obj_file = 'std.o' + elem = NinjaBuildElement(self.all_outputs, [mod_file, mod_obj_file], 'CUSTOM_COMMAND', []) + compile_args = compiler.get_option_compile_args(target, self.environment) + compile_args += compiler.get_option_std_args(target, self.environment) + compile_args += ['-c', '-fmodules', '-fsearch-include-path', 'bits/std.cc'] + elem.add_item('COMMAND', compiler.exelist + compile_args) + self.add_build(elem) + self.import_std = ImportStdInfo(elem, mod_file, [mod_obj_file]) + istd_args = ['-fmodules'] + istd_dep = [File(True, '', self.import_std.gen_module_file)] + return istd_args, istd_dep + elif compiler.id == 'msvc': + if self.import_std is None: + mod_file = 'std.ifc' + mod_obj_file = 'std.obj' + in_file = Path(os.environ['VCToolsInstallDir']) / 'modules/std.ixx' + if not in_file.is_file(): + raise SystemExit('VS std import header could not be located.') + in_file_str = str(in_file) + elem = NinjaBuildElement(self.all_outputs, [mod_file, mod_obj_file], 'CUSTOM_COMMAND', [in_file_str]) + compile_args = compiler.get_option_compile_args(target, self.environment) + compile_args += compiler.get_option_std_args(target, self.environment) + compile_args += ['/nologo', '/c', '/O2', in_file_str] + elem.add_item('COMMAND', compiler.exelist + compile_args) + self.add_build(elem) + self.import_std = ImportStdInfo(elem, mod_file, [mod_obj_file]) + istd_dep = [File(True, '', self.import_std.gen_module_file)] + return istd_args, istd_dep + else: + raise MesonException(f'Import std not supported on compiler {compiler.id} yet.') + def add_dependency_scanner_entries_to_element(self, target: build.BuildTarget, compiler, element, src) -> None: if not self.should_use_dyndeps_for_target(target): return @@ -3226,7 +3372,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) priv = self.get_target_private_dir(target) return os.path.join(priv, 'depscan.json'), os.path.join(priv, 'depscan.dd') - def add_header_deps(self, target, ninja_element, header_deps): + def add_header_deps(self, target, ninja_element, header_deps) -> None: for d in header_deps: if isinstance(d, File): d = d.rel_to_builddir(self.build_to_src) @@ -3258,7 +3404,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) for lt in itertools.chain(target.link_targets, target.link_whole_targets) ] - def generate_msvc_pch_command(self, target, compiler, pch): + def generate_msvc_pch_command(self, target, compiler, pch: T.Tuple[str, T.Optional[str]]): header = pch[0] pchname = compiler.get_pch_name(header) dst = os.path.join(self.get_target_private_dir(target), pchname) @@ -3266,7 +3412,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) commands = [] commands += self.generate_basic_compiler_args(target, compiler) - if len(pch) == 1: + if pch[1] is None: # Auto generate PCH. source = self.create_msvc_pch_implementation(target, compiler.get_language(), pch[0]) pch_header_dir = os.path.dirname(os.path.join(self.build_to_src, target.get_source_subdir(), header)) @@ -3285,7 +3431,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) return commands, dep, dst, link_objects, source - def generate_gcc_pch_command(self, target, compiler, pch): + def generate_gcc_pch_command(self, target, compiler, pch: str): commands = self._generate_single_compile(target, compiler) if pch.split('.')[-1] == 'h' and compiler.language == 'cpp': # Explicitly compile pch headers as C++. If Clang is invoked in C++ mode, it actually warns if @@ -3296,24 +3442,20 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) dep = dst + '.' + compiler.get_depfile_suffix() return commands, dep, dst, [] # Gcc does not create an object file during pch generation. - def generate_mwcc_pch_command(self, target, compiler, pch): + def generate_mwcc_pch_command(self, target, compiler, pch: str): commands = self._generate_single_compile(target, compiler) dst = os.path.join(self.get_target_private_dir(target), os.path.basename(pch) + '.' + compiler.get_pch_suffix()) dep = os.path.splitext(dst)[0] + '.' + compiler.get_depfile_suffix() return commands, dep, dst, [] # mwcc compilers do not create an object file during pch generation. - def generate_pch(self, target, header_deps=None): + def generate_pch(self, target: build.BuildTarget, header_deps=None): header_deps = header_deps if header_deps is not None else [] pch_objects = [] for lang in ['c', 'cpp']: - pch = target.get_pch(lang) + pch = target.pch[lang] if not pch: continue - if not has_path_sep(pch[0]) or not has_path_sep(pch[-1]): - msg = f'Precompiled header of {target.get_basename()!r} must not be in the same ' \ - 'directory as source, please put it in a subdirectory.' - raise InvalidArguments(msg) compiler: Compiler = target.compilers[lang] if compiler.get_argument_syntax() == 'msvc': (commands, dep, dst, objs, src) = self.generate_msvc_pch_command(target, compiler, pch) @@ -3341,13 +3483,18 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) self.all_pch[compiler.id].update(objs + [dst]) return pch_objects - def get_target_shsym_filename(self, target): + def get_target_shsym_filename(self, target) -> str: # Always name the .symbols file after the primary build output because it always exists targetdir = self.get_target_private_dir(target) return os.path.join(targetdir, target.get_filename() + '.symbols') def generate_shsym(self, target) -> None: - target_file = self.get_target_filename(target) + # On OS/2, an import library is generated after linking a DLL, so + # if a DLL is used as a target, import library is not generated. + if self.environment.machines[target.for_machine].is_os2(): + target_file = self.get_target_filename_for_linking(target) + else: + target_file = self.get_target_filename(target) if isinstance(target, build.SharedLibrary) and target.aix_so_archive: if self.environment.machines[target.for_machine].is_aix(): linker, stdlib_args = target.get_clink_dynamic_linker_and_stdlibs() @@ -3365,20 +3512,20 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) def get_import_filename(self, target) -> str: return os.path.join(self.get_target_dir(target), target.import_filename) - def get_target_type_link_args(self, target, linker): - commands = [] + def get_target_type_link_args(self, target: build.BuildTarget, linker: Compiler): + commands: T.List[str] = [] if isinstance(target, build.Executable): # Currently only used with the Swift compiler to add '-emit-executable' commands += linker.get_std_exe_link_args() # If export_dynamic, add the appropriate linker arguments if target.export_dynamic: - commands += linker.gen_export_dynamic_link_args(self.environment) + commands += linker.gen_export_dynamic_link_args() # If implib, and that's significant on this platform (i.e. Windows using either GCC or Visual Studio) if target.import_filename: commands += linker.gen_import_library_args(self.get_import_filename(target)) if target.pie: commands += linker.get_pie_link_args() - if target.vs_module_defs and hasattr(linker, 'gen_vs_module_defs_args'): + if target.vs_module_defs: commands += linker.gen_vs_module_defs_args(target.vs_module_defs.rel_to_builddir(self.build_to_src)) elif isinstance(target, build.SharedLibrary): if isinstance(target, build.SharedModule): @@ -3390,10 +3537,9 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) if not isinstance(target, build.SharedModule) or target.force_soname: # Add -Wl,-soname arguments on Linux, -install_name on OS X commands += linker.get_soname_args( - self.environment, target.prefix, target.name, target.suffix, + target.prefix, target.name, target.suffix, target.soversion, target.darwin_versions) - # This is only visited when building for Windows using either GCC or Visual Studio - if target.vs_module_defs and hasattr(linker, 'gen_vs_module_defs_args'): + if target.vs_module_defs: commands += linker.gen_vs_module_defs_args(target.vs_module_defs.rel_to_builddir(self.build_to_src)) # This is only visited when building for Windows using either GCC or Visual Studio if target.import_filename: @@ -3491,15 +3637,15 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) mlog.warning("Generated linker command has '-l' argument without following library name") break libs.add(lib) - elif os.path.isabs(item) and self.environment.is_library(item) and os.path.isfile(item): + elif os.path.isabs(item) and compilers.is_library(item) and os.path.isfile(item): absolute_libs.append(item) guessed_dependencies = [] # TODO The get_library_naming requirement currently excludes link targets that use d or fortran as their main linker try: - static_patterns = linker.get_library_naming(self.environment, LibType.STATIC, strict=True) - shared_patterns = linker.get_library_naming(self.environment, LibType.SHARED, strict=True) - search_dirs = tuple(search_dirs) + tuple(linker.get_library_dirs(self.environment)) + static_patterns = linker.get_library_naming(LibType.STATIC, strict=True) + shared_patterns = linker.get_library_naming(LibType.SHARED, strict=True) + search_dirs = tuple(search_dirs) + tuple(linker.get_library_dirs()) for libname in libs: # be conservative and record most likely shared and static resolution, because we don't know exactly # which one the linker will prefer @@ -3517,7 +3663,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) return guessed_dependencies + absolute_libs - def generate_prelink(self, target, obj_list): + def generate_prelink(self, target, obj_list) -> T.List[str]: assert isinstance(target, build.StaticLibrary) prelink_name = os.path.join(self.get_target_private_dir(target), target.name + '-prelink.o') elem = NinjaBuildElement(self.all_outputs, [prelink_name], 'CUSTOM_COMMAND', obj_list) @@ -3548,12 +3694,9 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) else: target_slashname_workaround_dir = self.get_target_dir(target) (rpath_args, target.rpath_dirs_to_remove) = ( - linker.build_rpath_args(self.environment, - self.environment.get_build_dir(), + linker.build_rpath_args(self.environment.get_build_dir(), target_slashname_workaround_dir, - self.determine_rpath_dirs(target), - target.build_rpath, - target.install_rpath)) + target)) return rpath_args def generate_link(self, target: build.BuildTarget, outname, obj_list, linker: T.Union['Compiler', 'StaticLinker'], extra_args=None, stdlib_args=None): @@ -3566,6 +3709,11 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) linker_base = linker.get_language() # Fixme. if isinstance(target, build.SharedLibrary): self.generate_shsym(target) + if self.environment.machines[target.for_machine].is_os2(): + target_file = self.get_target_filename(target) + import_name = self.get_import_filename(target) + elem = NinjaBuildElement(self.all_outputs, import_name, 'IMPORTLIB', target_file) + self.add_build(elem) crstr = self.get_rule_suffix(target.for_machine) linker_rule = linker_base + '_LINKER' + crstr # Create an empty commands list, and start adding link arguments from @@ -3581,11 +3729,12 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) # options passed on the command-line, in default_options, etc. # These have the lowest priority. if isinstance(target, build.StaticLibrary): - commands += linker.get_base_link_args(target, linker, self.environment) + base_link_args = linker.get_base_link_args(target, linker, self.environment) else: - commands += compilers.get_base_link_args(target, - linker, - self.environment) + base_link_args = compilers.get_base_link_args(target, + linker, + self.environment) + commands += self.transform_link_args(target, base_link_args) # Add -nostdlib if needed; can't be overridden commands += self.get_no_stdlib_link_args(target, linker) # Add things like /NOLOGO; usually can't be overridden @@ -3672,11 +3821,13 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) # # We shouldn't check whether we are making a static library, because # in the LTO case we do use a real compiler here. - commands += linker.get_option_link_args(target, self.environment) + commands += linker.get_option_link_args(target) dep_targets = [] dep_targets.extend(self.guess_external_link_dependencies(linker, target, commands, internal)) + obj_list += self.get_import_std_object(target) + # Add libraries generated by custom targets custom_target_libraries = self.get_custom_target_provided_libraries(target) commands += extra_args @@ -3688,7 +3839,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) elem = NinjaBuildElement(self.all_outputs, outname, linker_rule, obj_list, implicit_outs=implicit_outs) elem.add_dep(dep_targets + custom_target_libraries) if linker.get_id() == 'tasking': - if len([x for x in dep_targets + custom_target_libraries if x.endswith('.ma')]) > 0 and not target.get_option(OptionKey('b_lto')): + if len([x for x in dep_targets + custom_target_libraries if x.endswith('.ma')]) > 0 and not self.get_target_option(target, OptionKey('b_lto', target.subproject, target.for_machine)): raise MesonException(f'Tried to link the target named \'{target.name}\' with a MIL archive without LTO enabled! This causes the compiler to ignore the archive.') # Compiler args must be included in TI C28x linker commands. @@ -3705,9 +3856,17 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) self.create_target_linker_introspection(target, linker, commands) return elem - def get_dependency_filename(self, t): + def get_import_std_object(self, target: build.BuildTarget) -> T.List[File]: + if not self.target_uses_import_std(target): + return [] + return self.import_std.gen_objects + + def get_dependency_filename(self, t) -> str: if isinstance(t, build.SharedLibrary): - return self.get_target_shsym_filename(t) + if t.uses_rust() and t.rust_crate_type == 'proc-macro': + return self.get_target_filename(t) + else: + return self.get_target_shsym_filename(t) elif isinstance(t, mesonlib.File): if t.is_built: return t.relative_name() @@ -3756,9 +3915,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) gcda_elem.add_item('description', 'Deleting gcda files') self.add_build(gcda_elem) - def get_user_option_args(self, shut_up_pylint: bool = True) -> T.List[str]: - if shut_up_pylint: - return [] + def get_user_option_args(self) -> T.List[str]: cmds = [] for k, v in self.environment.coredata.optstore.items(): if self.environment.coredata.optstore.is_project_option(k): @@ -3806,7 +3963,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) self.add_build(elem) def generate_scanbuild(self) -> None: - if not environment.detect_scanbuild(): + if not tooldetect.detect_scanbuild(): return if 'scan-build' in self.all_outputs: return @@ -3824,8 +3981,9 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) if extra_arg: target_name += f'-{extra_arg}' extra_args.append(f'--{extra_arg}') - colorout = self.environment.coredata.optstore.get_value('b_colorout') \ + colorout = self.environment.coredata.optstore.get_value_for('b_colorout') \ if OptionKey('b_colorout') in self.environment.coredata.optstore else 'always' + assert isinstance(colorout, str), 'for mypy' extra_args.extend(['--color', colorout]) if not os.path.exists(os.path.join(self.environment.source_dir, '.clang-' + name)) and \ not os.path.exists(os.path.join(self.environment.source_dir, '_clang-' + name)): @@ -3846,16 +4004,16 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) self.add_build(elem) def generate_clangformat(self) -> None: - if not environment.detect_clangformat(): + if not tooldetect.detect_clangformat(): return self.generate_clangtool('format') self.generate_clangtool('format', 'check') def generate_clangtidy(self) -> None: - if not environment.detect_clangtidy(): + if not tooldetect.detect_clangtidy(): return self.generate_clangtool('tidy', need_pch=True) - if not environment.detect_clangapply(): + if not tooldetect.detect_clangapply(): return self.generate_clangtool('tidy', 'fix', need_pch=True) @@ -3890,11 +4048,11 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) def generate_ending(self) -> None: for targ, deps in [ - ('all', self.get_build_by_default_targets()), + ('all', self.get_build_by_default_targets().values()), ('meson-test-prereq', self.get_testlike_targets()), ('meson-benchmark-prereq', self.get_testlike_targets(True))]: targetlist = [] - for t in deps.values(): + for t in deps: # Add the first output of each target to the 'all' target so that # they are all built #Add archive file if shared library in AIX for build all. diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index 283f9f0..cc7ce1a 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -28,11 +28,10 @@ from .. import coredata if T.TYPE_CHECKING: from ..arglist import CompilerArgs - from ..interpreter import Interpreter Project = T.Tuple[str, Path, str, MachineChoice] -def autodetect_vs_version(build: T.Optional[build.Build], interpreter: T.Optional[Interpreter]) -> backends.Backend: +def autodetect_vs_version(build: T.Optional[build.Build]) -> backends.Backend: vs_version = os.getenv('VisualStudioVersion', None) vs_install_dir = os.getenv('VSINSTALLDIR', None) if not vs_install_dir: @@ -42,27 +41,31 @@ def autodetect_vs_version(build: T.Optional[build.Build], interpreter: T.Optiona # vcvarsall.bat doesn't set it, so also use VSINSTALLDIR if vs_version == '11.0' or 'Visual Studio 11' in vs_install_dir: from mesonbuild.backend.vs2012backend import Vs2012Backend - return Vs2012Backend(build, interpreter) + return Vs2012Backend(build) if vs_version == '12.0' or 'Visual Studio 12' in vs_install_dir: from mesonbuild.backend.vs2013backend import Vs2013Backend - return Vs2013Backend(build, interpreter) + return Vs2013Backend(build) if vs_version == '14.0' or 'Visual Studio 14' in vs_install_dir: from mesonbuild.backend.vs2015backend import Vs2015Backend - return Vs2015Backend(build, interpreter) + return Vs2015Backend(build) if vs_version == '15.0' or 'Visual Studio 17' in vs_install_dir or \ 'Visual Studio\\2017' in vs_install_dir: from mesonbuild.backend.vs2017backend import Vs2017Backend - return Vs2017Backend(build, interpreter) + return Vs2017Backend(build) if vs_version == '16.0' or 'Visual Studio 19' in vs_install_dir or \ 'Visual Studio\\2019' in vs_install_dir: from mesonbuild.backend.vs2019backend import Vs2019Backend - return Vs2019Backend(build, interpreter) + return Vs2019Backend(build) if vs_version == '17.0' or 'Visual Studio 22' in vs_install_dir or \ 'Visual Studio\\2022' in vs_install_dir: from mesonbuild.backend.vs2022backend import Vs2022Backend - return Vs2022Backend(build, interpreter) + return Vs2022Backend(build) + if vs_version == '18.0' or 'Visual Studio 26' in vs_install_dir or \ + 'Visual Studio\\2026' in vs_install_dir: + from mesonbuild.backend.vs2026backend import Vs2026Backend + return Vs2026Backend(build) if 'Visual Studio 10.0' in vs_install_dir: - return Vs2010Backend(build, interpreter) + return Vs2010Backend(build) raise MesonException('Could not detect Visual Studio using VisualStudioVersion: {!r} or VSINSTALLDIR: {!r}!\n' 'Please specify the exact backend to use.'.format(vs_version, vs_install_dir)) @@ -135,8 +138,8 @@ class Vs2010Backend(backends.Backend): name = 'vs2010' - def __init__(self, build: T.Optional[build.Build], interpreter: T.Optional[Interpreter], gen_lite: bool = False): - super().__init__(build, interpreter) + def __init__(self, build: T.Optional[build.Build], gen_lite: bool = False): + super().__init__(build) self.project_file_version = '10.0.30319.1' self.sln_file_version = '11.00' self.sln_version_comment = '2010' @@ -147,10 +150,13 @@ class Vs2010Backend(backends.Backend): self.handled_target_deps = {} self.gen_lite = gen_lite # Synonymous with generating the simpler makefile-style multi-config projects that invoke 'meson compile' builds, avoiding native MSBuild complications + def detect_toolset(self) -> None: + pass + def get_target_private_dir(self, target): return os.path.join(self.get_target_dir(target), target.get_id()) - def generate_genlist_for_target(self, genlist: T.Union[build.GeneratedList, build.CustomTarget, build.CustomTargetIndex], target: build.BuildTarget, parent_node: ET.Element, generator_output_files: T.List[str], custom_target_include_dirs: T.List[str], custom_target_output_files: T.List[str]) -> None: + def generate_genlist_for_target(self, genlist: build.GeneratedTypes, target: build.BuildTarget, parent_node: ET.Element, generator_output_files: T.List[str], custom_target_include_dirs: T.List[str], custom_target_output_files: T.List[str]) -> None: if isinstance(genlist, build.GeneratedList): for x in genlist.depends: self.generate_genlist_for_target(x, target, parent_node, [], [], []) @@ -227,6 +233,7 @@ class Vs2010Backend(backends.Backend): # Check for (currently) unexpected capture arg use cases - if capture: raise MesonBugException('We do not expect any vs backend to generate with \'capture = True\'') + self.detect_toolset() host_machine = self.environment.machines.host.cpu_family if host_machine in {'64', 'x86_64'}: # amd64 or x86_64 @@ -326,7 +333,7 @@ class Vs2010Backend(backends.Backend): result[o.target.get_id()] = o.target return result.items() - def get_target_deps(self, t: T.Dict[T.Any, T.Union[build.Target, build.CustomTargetIndex]], recursive=False): + def get_target_deps(self, t: T.Dict[T.Any, build.AnyTargetType], recursive=False): all_deps: T.Dict[str, build.Target] = {} for target in t.values(): if isinstance(target, build.CustomTargetIndex): @@ -413,7 +420,8 @@ class Vs2010Backend(backends.Backend): def generate_solution(self, sln_filename: str, projlist: T.List[Project]) -> None: default_projlist = self.get_build_by_default_targets() - default_projlist.update(self.get_testlike_targets()) + for t in self.get_testlike_targets(): + default_projlist[t.get_id()] = t sln_filename_tmp = sln_filename + '~' # Note using the utf-8 BOM requires the blank line, otherwise Visual Studio Version Selector fails. # Without the BOM, VSVS fails if there is a blank line. @@ -534,7 +542,7 @@ class Vs2010Backend(backends.Backend): replace_if_different(sln_filename, sln_filename_tmp) def generate_projects(self, vslite_ctx: dict = None) -> T.List[Project]: - startup_project = self.environment.coredata.optstore.get_value('backend_startup_project') + startup_project = self.environment.coredata.optstore.get_value_for('backend_startup_project') projlist: T.List[Project] = [] startup_idx = 0 for (i, (name, target)) in enumerate(self.build.targets.items()): @@ -566,16 +574,16 @@ class Vs2010Backend(backends.Backend): objects = [] languages = [] for i in srclist: - if self.environment.is_header(i): + if compilers.is_header(i): headers.append(i) - elif self.environment.is_object(i): + elif compilers.is_object(i): objects.append(i) - elif self.environment.is_source(i): + elif compilers.is_source(i): sources.append(i) lang = self.lang_from_source_file(i) if lang not in languages: languages.append(lang) - elif self.environment.is_library(i): + elif compilers.is_library(i): pass else: # Everything that is not an object or source file is considered a header. @@ -619,7 +627,8 @@ class Vs2010Backend(backends.Backend): conftype='Utility', target_ext=None, target_platform=None, - gen_manifest=True) -> T.Tuple[ET.Element, ET.Element]: + gen_manifest=True, + masm_type: T.Optional[T.Literal['masm', 'marmasm']] = None) -> T.Tuple[ET.Element, ET.Element]: root = ET.Element('Project', {'DefaultTargets': "Build", 'ToolsVersion': '4.0', 'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'}) @@ -657,6 +666,13 @@ class Vs2010Backend(backends.Backend): # "The build tools for v142 (Platform Toolset = 'v142') cannot be found. ... please install v142 build tools." # This is extremely unhelpful and misleading since the v14x build tools ARE installed. ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.props') + ext_settings_grp = ET.SubElement(root, 'ImportGroup', Label='ExtensionSettings') + if masm_type: + ET.SubElement( + ext_settings_grp, + 'Import', + Project=rf'$(VCTargetsPath)\BuildCustomizations\{masm_type}.props', + ) # This attribute makes sure project names are displayed as expected in solution files even when their project file names differ pname = ET.SubElement(globalgroup, 'ProjectName') @@ -692,9 +708,11 @@ class Vs2010Backend(backends.Backend): if target_ext: ET.SubElement(direlem, 'TargetExt').text = target_ext - ET.SubElement(direlem, 'EmbedManifest').text = 'false' - if not gen_manifest: - ET.SubElement(direlem, 'GenerateManifest').text = 'false' + # Fix weird mt.exe error: + # mt.exe is trying to compile a non-existent .generated.manifest file and link it + # with the target. This does not happen without masm props. + ET.SubElement(direlem, 'EmbedManifest').text = 'true' if masm_type or gen_manifest == 'embed' else 'false' + ET.SubElement(direlem, 'GenerateManifest').text = 'true' if gen_manifest else 'false' return (root, type_config) @@ -775,12 +793,19 @@ class Vs2010Backend(backends.Backend): platform = self.build_platform else: platform = self.platform + + masm = self.get_masm_type(target) + (root, type_config) = self.create_basic_project(target.name, temp_dir=target.get_id(), guid=guid, target_platform=platform, - gen_manifest=self.get_gen_manifest(target)) + gen_manifest=self.get_gen_manifest(target), + masm_type=masm) ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.targets') + ext_tgt_grp = ET.SubElement(root, 'ImportGroup', Label='ExtensionTargets') + if masm: + ET.SubElement(ext_tgt_grp, 'Import', Project=rf'$(VCTargetsPath)\BuildCustomizations\{masm}.targets') target.generated = [self.compile_target_to_generator(target)] target.sources = [] self.generate_custom_generator_commands(target, root) @@ -795,25 +820,31 @@ class Vs2010Backend(backends.Backend): return 'c' if ext in compilers.cpp_suffixes: return 'cpp' + if ext in compilers.lang_suffixes['masm']: + return 'masm' raise MesonException(f'Could not guess language from source file {src}.') - def add_pch(self, pch_sources, lang, inc_cl): + def add_pch(self, pch_sources: T.Dict[str, T.Tuple[str, T.Optional[str], str, T.Optional[str]]], + lang, inc_cl) -> None: if lang in pch_sources: self.use_pch(pch_sources, lang, inc_cl) - def create_pch(self, pch_sources, lang, inc_cl): + def create_pch(self, pch_sources: T.Dict[str, T.Tuple[str, T.Optional[str], str, T.Optional[str]]], + lang, inc_cl) -> None: pch = ET.SubElement(inc_cl, 'PrecompiledHeader') pch.text = 'Create' self.add_pch_files(pch_sources, lang, inc_cl) - def use_pch(self, pch_sources, lang, inc_cl): + def use_pch(self, pch_sources: T.Dict[str, T.Tuple[str, T.Optional[str], str, T.Optional[str]]], + lang, inc_cl) -> None: pch = ET.SubElement(inc_cl, 'PrecompiledHeader') pch.text = 'Use' header = self.add_pch_files(pch_sources, lang, inc_cl) pch_include = ET.SubElement(inc_cl, 'ForcedIncludeFiles') pch_include.text = header + ';%(ForcedIncludeFiles)' - def add_pch_files(self, pch_sources, lang, inc_cl): + def add_pch_files(self, pch_sources: T.Dict[str, T.Tuple[str, T.Optional[str], str, T.Optional[str]]], + lang, inc_cl) -> str: header = os.path.basename(pch_sources[lang][0]) pch_file = ET.SubElement(inc_cl, 'PrecompiledHeaderFile') # When USING PCHs, MSVC will not do the regular include @@ -842,6 +873,8 @@ class Vs2010Backend(backends.Backend): # FIXME add args as needed. if entry[1:].startswith('fsanitize'): return True + if entry[1:] in frozenset(['Zi', 'Z7', 'ZI']): + return True return entry[1:].startswith('M') def add_additional_options(self, lang, parent_node, file_args): @@ -956,13 +989,13 @@ class Vs2010Backend(backends.Backend): other.append(arg) return lpaths, libs, other - def _get_cl_compiler(self, target): + def _get_cl_compiler(self, target: build.BuildTarget): for lang, c in target.compilers.items(): if lang in {'c', 'cpp'}: return c - # No source files, only objects, but we still need a compiler, so + # No C/C++ source files, only objects/assembly source, but we still need a compiler, so # return a found compiler - if len(target.objects) > 0: + if len(target.objects) > 0 or len(target.sources) > 0: for lang, c in self.environment.coredata.compilers[target.for_machine].items(): if lang in {'c', 'cpp'}: return c @@ -999,9 +1032,9 @@ class Vs2010Backend(backends.Backend): file_args[l] += compilers.get_base_compile_args( target, comp, self.environment) file_args[l] += comp.get_option_compile_args( - target, self.environment, target.subproject) + target, target.subproject) file_args[l] += comp.get_option_std_args( - target, self.environment, target.subproject) + target, target.subproject) # Add compile args added using add_project_arguments() for l, args in self.build.projects_args[target.for_machine].get(target.subproject, {}).items(): @@ -1119,11 +1152,10 @@ class Vs2010Backend(backends.Backend): return (target_args, file_args), (target_defines, file_defines), (target_inc_dirs, file_inc_dirs) - @staticmethod - def get_build_args(compiler, optimization_level: str, debug: bool, sanitize: str) -> T.List[str]: + def get_build_args(self, target: build.BuildTarget, compiler, optimization_level: str, debug: bool, sanitize: str) -> T.List[str]: build_args = compiler.get_optimization_args(optimization_level) build_args += compiler.get_debug_args(debug) - build_args += compiler.sanitizer_compile_args(sanitize) + build_args += compiler.sanitizer_compile_args(target, sanitize) return build_args @@ -1325,11 +1357,11 @@ class Vs2010Backend(backends.Backend): if '/fsanitize=address' in build_args: ET.SubElement(type_config, 'EnableASAN').text = 'true' # Debug format - if '/ZI' in build_args: + if '/ZI' in build_args or '-ZI' in build_args: ET.SubElement(clconf, 'DebugInformationFormat').text = 'EditAndContinue' - elif '/Zi' in build_args: + elif '/Zi' in build_args or '-Zi' in build_args: ET.SubElement(clconf, 'DebugInformationFormat').text = 'ProgramDatabase' - elif '/Z7' in build_args: + elif '/Z7' in build_args or '-Z7' in build_args: ET.SubElement(clconf, 'DebugInformationFormat').text = 'OldStyle' else: ET.SubElement(clconf, 'DebugInformationFormat').text = 'None' @@ -1438,7 +1470,7 @@ class Vs2010Backend(backends.Backend): # to be after all internal and external libraries so that unresolved # symbols from those can be found here. This is needed when the # *_winlibs that we want to link to are static mingw64 libraries. - extra_link_args += compiler.get_option_link_args(target, self.environment, target.subproject) + extra_link_args += compiler.get_option_link_args(target, target.subproject) (additional_libpaths, additional_links, extra_link_args) = self.split_link_args(extra_link_args.to_native()) # Add more libraries to be linked if needed @@ -1457,13 +1489,13 @@ class Vs2010Backend(backends.Backend): # Unfortunately, we can't use self.object_filename_from_source() for gen in l.genlist: for src in gen.get_outputs(): - if self.environment.is_source(src): + if compilers.is_source(src): path = self.get_target_generated_dir(t, gen, src) gen_src_ext = '.' + os.path.splitext(path)[1][1:] extra_link_args.append(path[:-len(gen_src_ext)] + '.obj') for src in l.srclist: - if self.environment.is_source(src): + if compilers.is_source(src): target_private_dir = self.relpath(self.get_target_private_dir(t), self.get_target_dir(t)) rel_obj = self.object_filename_from_source(t, compiler, src, target_private_dir) @@ -1493,8 +1525,9 @@ class Vs2010Backend(backends.Backend): additional_links.append(self.relpath(lib, self.get_target_dir(target))) if len(extra_link_args) > 0: - extra_link_args.append('%(AdditionalOptions)') - ET.SubElement(link, "AdditionalOptions").text = ' '.join(extra_link_args) + args = [self.escape_additional_option(arg) for arg in extra_link_args] + args.append('%(AdditionalOptions)') + ET.SubElement(link, "AdditionalOptions").text = ' '.join(args) if len(additional_libpaths) > 0: additional_libpaths.insert(0, '%(AdditionalLibraryDirectories)') ET.SubElement(link, 'AdditionalLibraryDirectories').text = ';'.join(additional_libpaths) @@ -1551,7 +1584,7 @@ class Vs2010Backend(backends.Backend): # once a build/compile has generated these sources. # # This modifies the paths in 'gen_files' in place, as opposed to returning a new list of modified paths. - def relocate_generated_file_paths_to_concrete_build_dir(self, gen_files: T.List[str], target: T.Union[build.Target, build.CustomTargetIndex]) -> None: + def relocate_generated_file_paths_to_concrete_build_dir(self, gen_files: T.List[str], target: build.AnyTargetType) -> None: (_, build_dir_tail) = os.path.split(self.src_to_build) meson_build_dir_for_buildtype = build_dir_tail[:-2] + coredata.get_genvs_default_buildtype_list()[0] # Get the first buildtype suffixed dir (i.e. '[builddir]_debug') from '[builddir]_vs' # Relative path from this .vcxproj to the directory containing the set of '..._[debug/debugoptimized/release]' setup meson build dirs. @@ -1607,6 +1640,8 @@ class Vs2010Backend(backends.Backend): else: platform = self.platform + masm = self.get_masm_type(target) + tfilename = os.path.splitext(target.get_filename()) (root, type_config) = self.create_basic_project(tfilename[0], @@ -1615,7 +1650,8 @@ class Vs2010Backend(backends.Backend): conftype=conftype, target_ext=tfilename[1], target_platform=platform, - gen_manifest=self.get_gen_manifest(target)) + gen_manifest=self.get_gen_manifest(target), + masm_type=masm) generated_files, custom_target_output_files, generated_files_include_dirs = self.generate_custom_generator_commands( target, root) @@ -1625,7 +1661,7 @@ class Vs2010Backend(backends.Backend): gen_hdrs += custom_hdrs compiler = self._get_cl_compiler(target) - build_args = Vs2010Backend.get_build_args(compiler, self.optimization, self.debug, self.sanitize) + build_args = self.get_build_args(target, compiler, self.optimization, self.debug, self.sanitize) assert isinstance(target, (build.Executable, build.SharedLibrary, build.StaticLibrary, build.SharedModule)), 'for mypy' # Prefix to use to access the build root from the vcxproj dir @@ -1658,25 +1694,25 @@ class Vs2010Backend(backends.Backend): else: return False - pch_sources = {} + pch_sources: T.Dict[str, T.Tuple[str, T.Optional[str], str, T.Optional[str]]] = {} if self.target_uses_pch(target): for lang in ['c', 'cpp']: - pch = target.get_pch(lang) + pch = target.pch[lang] if not pch: continue if compiler.id == 'msvc': - if len(pch) == 1: + if pch[1] is None: # Auto generate PCH. src = os.path.join(proj_to_build_root, self.create_msvc_pch_implementation(target, lang, pch[0])) pch_header_dir = os.path.dirname(os.path.join(proj_to_src_dir, pch[0])) else: src = os.path.join(proj_to_src_dir, pch[1]) pch_header_dir = None - pch_sources[lang] = [pch[0], src, lang, pch_header_dir] + pch_sources[lang] = (pch[0], src, lang, pch_header_dir) else: # I don't know whether its relevant but let's handle other compilers # used with a vs backend - pch_sources[lang] = [pch[0], None, lang, None] + pch_sources[lang] = (pch[0], None, lang, None) previous_includes = [] if len(headers) + len(gen_hdrs) + len(target.extra_files) + len(pch_sources) > 0: @@ -1719,12 +1755,17 @@ class Vs2010Backend(backends.Backend): for s in sources: relpath = os.path.join(proj_to_build_root, s.rel_to_builddir(self.build_to_src)) if path_normalize_add(relpath, previous_sources): - inc_cl = ET.SubElement(inc_src, 'CLCompile', Include=relpath) + lang = Vs2010Backend.lang_from_source_file(s) + if lang == 'masm' and masm: + inc_cl = ET.SubElement(inc_src, masm.upper(), Include=relpath) + else: + inc_cl = ET.SubElement(inc_src, 'CLCompile', Include=relpath) + if self.gen_lite: self.add_project_nmake_defs_incs_and_opts(inc_cl, relpath, defs_paths_opts_per_lang_and_buildtype, platform) else: - lang = Vs2010Backend.lang_from_source_file(s) - self.add_pch(pch_sources, lang, inc_cl) + if lang != 'masm': + self.add_pch(pch_sources, lang, inc_cl) self.add_additional_options(lang, inc_cl, file_args) self.add_preprocessor_defines(lang, inc_cl, file_defines) self.add_include_dirs(lang, inc_cl, file_inc_dirs) @@ -1732,12 +1773,17 @@ class Vs2010Backend(backends.Backend): self.object_filename_from_source(target, compiler, s) for s in gen_src: if path_normalize_add(s, previous_sources): - inc_cl = ET.SubElement(inc_src, 'CLCompile', Include=s) + lang = Vs2010Backend.lang_from_source_file(s) + if lang == 'masm' and masm: + inc_cl = ET.SubElement(inc_src, masm.upper(), Include=s) + else: + inc_cl = ET.SubElement(inc_src, 'CLCompile', Include=s) + if self.gen_lite: self.add_project_nmake_defs_incs_and_opts(inc_cl, s, defs_paths_opts_per_lang_and_buildtype, platform) else: - lang = Vs2010Backend.lang_from_source_file(s) - self.add_pch(pch_sources, lang, inc_cl) + if lang != 'masm': + self.add_pch(pch_sources, lang, inc_cl) self.add_additional_options(lang, inc_cl, file_args) self.add_preprocessor_defines(lang, inc_cl, file_defines) self.add_include_dirs(lang, inc_cl, file_inc_dirs) @@ -1786,6 +1832,9 @@ class Vs2010Backend(backends.Backend): ET.SubElement(inc_objs, 'Object', Include=s) ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.targets') + ext_tgt_grp = ET.SubElement(root, 'ImportGroup', Label='ExtensionTargets') + if masm: + ET.SubElement(ext_tgt_grp, 'Import', Project=rf'$(VCTargetsPath)\BuildCustomizations\{masm}.targets') self.add_regen_dependency(root) if not self.gen_lite: # Injecting further target dependencies into this vcxproj implies and forces a Visual Studio BUILD dependency, @@ -2096,7 +2145,8 @@ class Vs2010Backend(backends.Backend): pass # Returns if a target generates a manifest or not. - def get_gen_manifest(self, target): + # Returns 'embed' if the generated manifest is embedded. + def get_gen_manifest(self, target: T.Optional[build.BuildTarget]): if not isinstance(target, build.BuildTarget): return True @@ -2113,6 +2163,31 @@ class Vs2010Backend(backends.Backend): arg = arg.upper() if arg == '/MANIFEST:NO': return False + if arg.startswith('/MANIFEST:EMBED'): + return 'embed' if arg == '/MANIFEST' or arg.startswith('/MANIFEST:'): break return True + + # FIXME: add a way to distinguish between arm64ec+marmasm (written in ARM assembly) + # and arm64ec+masm (written in x64 assembly). + # + # For now, assume it's the native ones. (same behavior as ninja backend) + def get_masm_type(self, target: build.BuildTarget): + if not isinstance(target, build.BuildTarget): + return None + + if 'masm' not in target.compilers: + return None + + if target.for_machine == MachineChoice.BUILD: + platform = self.build_platform + elif target.for_machine == MachineChoice.HOST: + platform = self.platform + else: + return None + + if platform in {'ARM', 'arm64', 'arm64ec'}: + return 'marmasm' + else: + return 'masm' diff --git a/mesonbuild/backend/vs2012backend.py b/mesonbuild/backend/vs2012backend.py index 307964b..5ed31d9 100644 --- a/mesonbuild/backend/vs2012backend.py +++ b/mesonbuild/backend/vs2012backend.py @@ -10,17 +10,18 @@ from ..mesonlib import MesonException if T.TYPE_CHECKING: from ..build import Build - from ..interpreter import Interpreter class Vs2012Backend(Vs2010Backend): name = 'vs2012' - def __init__(self, build: T.Optional[Build], interpreter: T.Optional[Interpreter]): - super().__init__(build, interpreter) + def __init__(self, build: T.Optional[Build]): + super().__init__(build) self.vs_version = '2012' self.sln_file_version = '12.00' self.sln_version_comment = '2012' + + def detect_toolset(self) -> None: if self.environment is not None: # TODO: we assume host == build comps = self.environment.coredata.compilers.host diff --git a/mesonbuild/backend/vs2013backend.py b/mesonbuild/backend/vs2013backend.py index ae0b68b..c53e64c 100644 --- a/mesonbuild/backend/vs2013backend.py +++ b/mesonbuild/backend/vs2013backend.py @@ -9,17 +9,18 @@ import typing as T if T.TYPE_CHECKING: from ..build import Build - from ..interpreter import Interpreter class Vs2013Backend(Vs2010Backend): name = 'vs2013' - def __init__(self, build: T.Optional[Build], interpreter: T.Optional[Interpreter]): - super().__init__(build, interpreter) + def __init__(self, build: T.Optional[Build]): + super().__init__(build) self.vs_version = '2013' self.sln_file_version = '12.00' self.sln_version_comment = '2013' + + def detect_toolset(self) -> None: if self.environment is not None: # TODO: we assume host == build comps = self.environment.coredata.compilers.host diff --git a/mesonbuild/backend/vs2015backend.py b/mesonbuild/backend/vs2015backend.py index 4c515cc..f5a468f 100644 --- a/mesonbuild/backend/vs2015backend.py +++ b/mesonbuild/backend/vs2015backend.py @@ -10,17 +10,17 @@ from ..mesonlib import MesonException if T.TYPE_CHECKING: from ..build import Build - from ..interpreter import Interpreter class Vs2015Backend(Vs2010Backend): name = 'vs2015' - def __init__(self, build: T.Optional[Build], interpreter: T.Optional[Interpreter]): - super().__init__(build, interpreter) + def __init__(self, build: T.Optional[Build]): self.vs_version = '2015' self.sln_file_version = '12.00' self.sln_version_comment = '14' + + def detect_toolset(self) -> None: if self.environment is not None: # TODO: we assume host == build comps = self.environment.coredata.compilers.host diff --git a/mesonbuild/backend/vs2017backend.py b/mesonbuild/backend/vs2017backend.py index 393544f..bf6c337 100644 --- a/mesonbuild/backend/vs2017backend.py +++ b/mesonbuild/backend/vs2017backend.py @@ -12,18 +12,19 @@ from ..mesonlib import MesonException if T.TYPE_CHECKING: from ..build import Build - from ..interpreter import Interpreter class Vs2017Backend(Vs2010Backend): name = 'vs2017' - def __init__(self, build: T.Optional[Build], interpreter: T.Optional[Interpreter]): - super().__init__(build, interpreter) + def __init__(self, build: T.Optional[Build]): + super().__init__(build) self.vs_version = '2017' self.sln_file_version = '12.00' self.sln_version_comment = '15' + + def detect_toolset(self) -> None: # We assume that host == build if self.environment is not None: comps = self.environment.coredata.compilers.host diff --git a/mesonbuild/backend/vs2019backend.py b/mesonbuild/backend/vs2019backend.py index 4d6e226..bf86a47 100644 --- a/mesonbuild/backend/vs2019backend.py +++ b/mesonbuild/backend/vs2019backend.py @@ -11,17 +11,18 @@ from .vs2010backend import Vs2010Backend if T.TYPE_CHECKING: from ..build import Build - from ..interpreter import Interpreter class Vs2019Backend(Vs2010Backend): name = 'vs2019' - def __init__(self, build: T.Optional[Build], interpreter: T.Optional[Interpreter]): - super().__init__(build, interpreter) + def __init__(self, build: T.Optional[Build]): + super().__init__(build) self.sln_file_version = '12.00' self.sln_version_comment = 'Version 16' + + def detect_toolset(self) -> None: if self.environment is not None: comps = self.environment.coredata.compilers.host if comps and all(c.id == 'clang-cl' for c in comps.values()): diff --git a/mesonbuild/backend/vs2022backend.py b/mesonbuild/backend/vs2022backend.py index 27e0438..d7de4be 100644 --- a/mesonbuild/backend/vs2022backend.py +++ b/mesonbuild/backend/vs2022backend.py @@ -11,17 +11,18 @@ from .vs2010backend import Vs2010Backend if T.TYPE_CHECKING: from ..build import Build - from ..interpreter import Interpreter class Vs2022Backend(Vs2010Backend): name = 'vs2022' - def __init__(self, build: T.Optional[Build], interpreter: T.Optional[Interpreter], gen_lite: bool = False): - super().__init__(build, interpreter, gen_lite=gen_lite) + def __init__(self, build: T.Optional[Build], gen_lite: bool = False): + super().__init__(build, gen_lite=gen_lite) self.sln_file_version = '12.00' self.sln_version_comment = 'Version 17' + + def detect_toolset(self) -> None: if self.environment is not None: comps = self.environment.coredata.compilers.host if comps and all(c.id == 'clang-cl' for c in comps.values()): diff --git a/mesonbuild/backend/vs2026backend.py b/mesonbuild/backend/vs2026backend.py new file mode 100644 index 0000000..ba24d8a --- /dev/null +++ b/mesonbuild/backend/vs2026backend.py @@ -0,0 +1,55 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2025 The Meson development team + +from __future__ import annotations + +import os +import typing as T +import xml.etree.ElementTree as ET + +from .vs2010backend import Vs2010Backend + +if T.TYPE_CHECKING: + from ..build import Build + + +class Vs2026Backend(Vs2010Backend): + + name = 'vs2026' + + def __init__(self, build: T.Optional[Build], gen_lite: bool = False): + super().__init__(build, gen_lite=gen_lite) + self.sln_file_version = '12.00' + self.sln_version_comment = 'Version 18' + + def detect_toolset(self) -> None: + if self.environment is not None: + comps = self.environment.coredata.compilers.host + if comps and all(c.id == 'clang-cl' for c in comps.values()): + self.platform_toolset = 'ClangCL' + elif comps and all(c.id == 'intel-cl' for c in comps.values()): + c = list(comps.values())[0] + if c.version.startswith('19'): + self.platform_toolset = 'Intel C++ Compiler 19.0' + # We don't have support for versions older than 2022 right now. + if not self.platform_toolset: + self.platform_toolset = 'v145' + self.vs_version = '2026' + # WindowsSDKVersion should be set by command prompt. + sdk_version = os.environ.get('WindowsSDKVersion', None) + if sdk_version: + self.windows_target_platform_version = sdk_version.rstrip('\\') + + def generate_debug_information(self, link): + # valid values for vs2026 is 'false', 'true', 'DebugFastLink', 'DebugFull' + ET.SubElement(link, 'GenerateDebugInformation').text = 'DebugFull' + + def generate_lang_standard_info(self, file_args, clconf): + if 'cpp' in file_args: + optargs = [x for x in file_args['cpp'] if x.startswith('/std:c++')] + if optargs: + ET.SubElement(clconf, 'LanguageStandard').text = optargs[0].replace("/std:c++", "stdcpp") + if 'c' in file_args: + optargs = [x for x in file_args['c'] if x.startswith('/std:c')] + if optargs: + ET.SubElement(clconf, 'LanguageStandard_C').text = optargs[0].replace("/std:c", "stdc") diff --git a/mesonbuild/backend/xcodebackend.py b/mesonbuild/backend/xcodebackend.py index 587404a..b4f43f3 100644 --- a/mesonbuild/backend/xcodebackend.py +++ b/mesonbuild/backend/xcodebackend.py @@ -8,6 +8,7 @@ import typing as T from . import backends from .. import build +from .. import compilers from .. import mesonlib from .. import mlog from ..arglist import CompilerArgs @@ -17,7 +18,6 @@ from ..options import OptionKey if T.TYPE_CHECKING: from ..build import BuildTarget from ..compilers import Compiler - from ..interpreter import Interpreter INDENT = '\t' XCODETYPEMAP = {'c': 'sourcecode.c.c', @@ -176,6 +176,15 @@ class PbxDict: self.keys.add(key) self.items.append(item) + def get_item(self, key: str) -> PbxDictItem: + assert key in self.keys + for item in self.items: + if not isinstance(item, PbxDictItem): + continue + if item.key == key: + return item + return None + def has_item(self, key: str) -> bool: return key in self.keys @@ -227,8 +236,8 @@ class XCodeBackend(backends.Backend): name = 'xcode' - def __init__(self, build: T.Optional[build.Build], interpreter: T.Optional[Interpreter]): - super().__init__(build, interpreter) + def __init__(self, build: T.Optional[build.Build]): + super().__init__(build) self.project_uid = self.environment.coredata.lang_guids['default'].replace('-', '')[:24] self.buildtype = T.cast('str', self.environment.coredata.optstore.get_value_for(OptionKey('buildtype'))) self.project_conflist = self.gen_id() @@ -260,7 +269,7 @@ class XCodeBackend(backends.Backend): # that is used in two targets gets a total of four unique ID numbers. self.fileref_ids = {} - def write_pbxfile(self, top_level_dict, ofilename) -> None: + def write_pbxfile(self, top_level_dict: PbxDict, ofilename: str) -> None: tmpname = ofilename + '.tmp' with open(tmpname, 'w', encoding='utf-8') as ofile: ofile.write('// !$*UTF8*$!\n') @@ -271,12 +280,12 @@ class XCodeBackend(backends.Backend): return str(uuid.uuid4()).upper().replace('-', '')[:24] @functools.lru_cache(maxsize=None) - def get_target_dir(self, target: T.Union[build.Target, build.CustomTargetIndex]) -> str: + def get_target_dir(self, target: build.AnyTargetType) -> str: dirname = os.path.join(target.get_subdir(), T.cast('str', self.environment.coredata.optstore.get_value_for(OptionKey('buildtype')))) #os.makedirs(os.path.join(self.environment.get_build_dir(), dirname), exist_ok=True) return dirname - def get_custom_target_output_dir(self, target: T.Union[build.Target, build.CustomTargetIndex]) -> str: + def get_custom_target_output_dir(self, target: build.AnyTargetType) -> str: dirname = target.get_subdir() os.makedirs(os.path.join(self.environment.get_build_dir(), dirname), exist_ok=True) return dirname @@ -396,10 +405,23 @@ class XCodeBackend(backends.Backend): def generate_filemap(self) -> None: self.filemap = {} # Key is source file relative to src root. + self.foldermap = {} self.target_filemap = {} for name, t in self.build_targets.items(): for s in t.sources: if isinstance(s, mesonlib.File): + if '/' in s.fname: + # From the top level down, add the folders containing the source file. + folder = os.path.split(os.path.dirname(s.fname)) + while folder: + fpath = os.path.join(*folder) + # Multiple targets might use the same folders, so store their targets with them. + # Otherwise, folders and their source files will appear in the wrong places in Xcode. + if (fpath, t) not in self.foldermap: + self.foldermap[(fpath, t)] = self.gen_id() + else: + break + folder = folder[:-1] s = os.path.join(s.subdir, s.fname) self.filemap[s] = self.gen_id() for o in t.objects: @@ -469,7 +491,7 @@ class XCodeBackend(backends.Backend): self.build_rules[name] = languages def generate_custom_target_map(self) -> None: - self.shell_targets = {} + self.shell_targets: T.Dict[T.Union[str, T.Tuple[str, int]], str] = {} self.custom_target_output_buildfile = {} self.custom_target_output_fileref = {} for tname, t in self.custom_targets.items(): @@ -502,7 +524,8 @@ class XCodeBackend(backends.Backend): self.gen_single_target_map(genlist, tname, t, generator_id) generator_id += 1 - def gen_single_target_map(self, genlist, tname, t, generator_id) -> None: + def gen_single_target_map(self, genlist: build.GeneratedList, tname: str, + t: build.AnyTargetType, generator_id: int) -> None: k = (tname, generator_id) assert k not in self.shell_targets self.shell_targets[k] = self.gen_id() @@ -532,7 +555,7 @@ class XCodeBackend(backends.Backend): self.native_frameworks_fileref[f] = self.gen_id() def generate_target_dependency_map(self) -> None: - self.target_dependency_map = {} + self.target_dependency_map: T.Dict[T.Union[str, T.Tuple[str, str]], str] = {} for tname, t in self.build_targets.items(): for target in t.link_targets: if isinstance(target, build.CustomTargetIndex): @@ -606,7 +629,7 @@ class XCodeBackend(backends.Backend): self.fileref_ids[k] = self.gen_id() def generate_build_file_maps(self) -> None: - for buildfile in self.interpreter.get_build_def_files(): + for buildfile in self.build.def_files: assert isinstance(buildfile, str) self.buildfile_ids[buildfile] = self.gen_id() self.fileref_ids[buildfile] = self.gen_id() @@ -767,7 +790,7 @@ class XCodeBackend(backends.Backend): self.create_generator_shellphase(objects_dict, tname, generator_id) generator_id += 1 - def create_generator_shellphase(self, objects_dict, tname, generator_id) -> None: + def create_generator_shellphase(self, objects_dict: PbxDict, tname: str, generator_id: int) -> None: file_ids = self.generator_buildfile_ids[(tname, generator_id)] ref_ids = self.generator_fileref_ids[(tname, generator_id)] assert len(ref_ids) == len(file_ids) @@ -1002,7 +1025,7 @@ class XCodeBackend(backends.Backend): custom_dict.add_item('sourceTree', 'SOURCE_ROOT') objects_dict.add_item(self.custom_target_output_fileref[o], custom_dict) - for buildfile in self.interpreter.get_build_def_files(): + for buildfile in self.build.def_files: basename = os.path.split(buildfile)[1] buildfile_dict = PbxDict() typestr = self.get_xcodetype(buildfile) @@ -1052,6 +1075,24 @@ class XCodeBackend(backends.Backend): main_children.add_item(frameworks_id, 'Frameworks') main_dict.add_item('sourceTree', '<group>') + # Define each folder as a group in Xcode. That way, it can build the file tree correctly. + # This must be done before the project tree group is generated, as source files are added during that phase. + for (path, target), id in self.foldermap.items(): + folder_dict = PbxDict() + objects_dict.add_item(id, folder_dict, path) + folder_dict.add_item('isa', 'PBXGroup') + folder_children = PbxArray() + folder_dict.add_item('children', folder_children) + folder_dict.add_item('name', '"{}"'.format(path.rsplit('/', 1)[-1])) + folder_dict.add_item('path', f'"{path}"') + folder_dict.add_item('sourceTree', 'SOURCE_ROOT') + + # Add any detected subdirectories (not declared as subdir()) here, but only one level higher. + # Example: In "root", add "root/sub", but not "root/sub/subtwo". + for path_dep, target_dep in self.foldermap: + if path_dep.startswith(path) and path_dep.split('/', 1)[0] == path.split('/', 1)[0] and path_dep != path and path_dep.count('/') == path.count('/') + 1 and target == target_dep: + folder_children.add_item(self.foldermap[(path_dep, target)], path_dep) + self.add_projecttree(objects_dict, projecttree_id) resource_dict = PbxDict() @@ -1117,10 +1158,11 @@ class XCodeBackend(backends.Backend): product_dict.add_item('name', 'Products') product_dict.add_item('sourceTree', '<group>') - def write_group_target_entry(self, objects_dict, t): + def write_group_target_entry(self, objects_dict: PbxDict, t) -> str: tid = t.get_id() group_id = self.gen_id() target_dict = PbxDict() + folder_ids = set() objects_dict.add_item(group_id, target_dict, tid) target_dict.add_item('isa', 'PBXGroup') target_children = PbxArray() @@ -1130,6 +1172,18 @@ class XCodeBackend(backends.Backend): source_files_dict = PbxDict() for s in t.sources: if isinstance(s, mesonlib.File): + # If the file is in a folder, add it to the group representing that folder. + if '/' in s.fname: + folder = '/'.join(s.fname.split('/')[:-1]) + folder_dict = objects_dict.get_item(self.foldermap[(folder, t)]).value.get_item('children').value + temp = os.path.join(s.subdir, s.fname) + folder_dict.add_item(self.fileref_ids[(tid, temp)], temp) + if self.foldermap[(folder, t)] in folder_ids: + continue + if len(folder.split('/')) == 1: + target_children.add_item(self.foldermap[(folder, t)], folder) + folder_ids.add(self.foldermap[(folder, t)]) + continue s = os.path.join(s.subdir, s.fname) elif isinstance(s, str): s = os.path.join(t.subdir, s) @@ -1157,7 +1211,7 @@ class XCodeBackend(backends.Backend): source_files_dict.add_item('sourceTree', '<group>') return group_id - def add_projecttree(self, objects_dict, projecttree_id) -> None: + def add_projecttree(self, objects_dict: PbxDict, projecttree_id: str) -> None: root_dict = PbxDict() objects_dict.add_item(projecttree_id, root_dict, "Root of project tree") root_dict.add_item('isa', 'PBXGroup') @@ -1169,7 +1223,7 @@ class XCodeBackend(backends.Backend): project_tree = self.generate_project_tree() self.write_tree(objects_dict, project_tree, target_children, '') - def write_tree(self, objects_dict, tree_node, children_array, current_subdir) -> None: + def write_tree(self, objects_dict: PbxDict, tree_node: FileTreeEntry, children_array: PbxArray, current_subdir: str) -> None: for subdir_name, subdir_node in tree_node.subdirs.items(): subdir_dict = PbxDict() subdir_children = PbxArray() @@ -1385,7 +1439,8 @@ class XCodeBackend(backends.Backend): self.generate_single_generator_phase(tname, t, genlist, generator_id, objects_dict) generator_id += 1 - def generate_single_generator_phase(self, tname, t, genlist, generator_id, objects_dict) -> None: + def generate_single_generator_phase(self, tname: str, t: build.AnyTargetType, + genlist: build.GeneratedList, generator_id: int, objects_dict: PbxDict) -> None: # TODO: this should be rewritten to use the meson wrapper, like the other generators do # Currently it doesn't handle a host binary that requires an exe wrapper correctly. generator = genlist.get_generator() @@ -1458,7 +1513,7 @@ class XCodeBackend(backends.Backend): phase_dict.add_item('files', file_arr) for s in self.build_targets[name].sources: s = os.path.join(s.subdir, s.fname) - if not self.environment.is_header(s): + if not compilers.is_header(s): file_arr.add_item(self.buildfile_ids[(name, s)], os.path.join(self.environment.get_source_dir(), s)) generator_id = 0 for gt in t.generated: @@ -1564,7 +1619,7 @@ class XCodeBackend(backends.Backend): settings_dict.add_item('SDKROOT', 'macosx') bt_dict.add_item('name', buildtype) - def determine_internal_dep_link_args(self, target, buildtype): + def determine_internal_dep_link_args(self, target: build.BuildTarget, buildtype: str) -> T.Tuple[T.List[str], bool]: links_dylib = False dep_libs = [] for l in target.link_targets: @@ -1589,13 +1644,14 @@ class XCodeBackend(backends.Backend): links_dylib = links_dylib or sub_links_dylib return (dep_libs, links_dylib) - def generate_single_build_target(self, objects_dict, target_name, target) -> None: + def generate_single_build_target(self, objects_dict: PbxDict, target_name: str, target: build.BuildTarget) -> None: for buildtype in self.buildtypes: - dep_libs = [] + dep_libs: T.List[str] = [] links_dylib = False headerdirs = [] bridging_header = "" is_swift = self.is_swift_target(target) + langs = set() for d in target.include_dirs: for sd in d.incdirs: cd = os.path.join(d.curdir, sd) @@ -1606,7 +1662,7 @@ class XCodeBackend(backends.Backend): # Swift can import declarations from C-based code using bridging headers. # There can only be one header, and it must be included as a source file. for i in target.get_sources(): - if self.environment.is_header(i) and is_swift: + if compilers.is_header(i) and is_swift: relh = i.rel_to_builddir(self.build_to_src) bridging_header = os.path.normpath(os.path.join(self.environment.get_build_dir(), relh)) break @@ -1678,7 +1734,7 @@ class XCodeBackend(backends.Backend): ldargs += linker.get_std_shared_lib_link_args() ldstr = ' '.join(ldargs) valid = self.buildconfmap[target_name][buildtype] - langargs = {} + langargs: T.Dict[str, T.List[str]] = {} for lang in self.environment.coredata.compilers[target.for_machine]: if lang not in LANGNAMEMAP: continue @@ -1687,8 +1743,8 @@ class XCodeBackend(backends.Backend): continue # Start with warning args warn_args = compiler.get_warn_args(self.get_target_option(target, 'warning_level')) - std_args = compiler.get_option_compile_args(target, self.environment, target.subproject) - std_args += compiler.get_option_std_args(target, self.environment, target.subproject) + std_args = compiler.get_option_compile_args(target, target.subproject) + std_args += compiler.get_option_std_args(target, target.subproject) # Add compile args added using add_project_arguments() pargs = self.build.projects_args[target.for_machine].get(target.subproject, {}).get(lang, []) # Add compile args added using add_global_arguments() @@ -1715,6 +1771,7 @@ class XCodeBackend(backends.Backend): lang = 'c' elif lang == 'objcpp': lang = 'cpp' + langs.add(lang) langname = LANGNAMEMAP[lang] langargs.setdefault(langname, []) langargs[langname] = cargs + cti_args + args @@ -1745,9 +1802,7 @@ class XCodeBackend(backends.Backend): # Xcode uses GCC_PREFIX_HEADER which only allows one file per target/executable. Precompiling various header files and # applying a particular pch to each source file will require custom scripts (as a build phase) and build flags per each # file. Since Xcode itself already discourages precompiled headers in favor of modules we don't try much harder here. - pchs = target.get_pch('c') + target.get_pch('cpp') + target.get_pch('objc') + target.get_pch('objcpp') - # Make sure to use headers (other backends require implementation files like *.c *.cpp, etc; these should not be used here) - pchs = [pch for pch in pchs if pch.endswith('.h') or pch.endswith('.hh') or pch.endswith('hpp')] + pchs = [t[0] for t in [target.pch['c'], target.pch['cpp']] if t is not None] if pchs: if len(pchs) > 1: mlog.warning(f'Unsupported Xcode configuration: More than 1 precompiled header found "{pchs!s}". Target "{target.name}" might not compile correctly.') @@ -1776,6 +1831,8 @@ class XCodeBackend(backends.Backend): settings_dict.add_item('SECTORDER_FLAGS', '') if is_swift and bridging_header: settings_dict.add_item('SWIFT_OBJC_BRIDGING_HEADER', bridging_header) + if self.objversion >= 60 and target.uses_swift_cpp_interop(): + settings_dict.add_item('SWIFT_OBJC_INTEROP_MODE', 'objcxx') settings_dict.add_item('BUILD_DIR', symroot) settings_dict.add_item('OBJROOT', f'{symroot}/build') sysheader_arr = PbxArray() @@ -1789,7 +1846,7 @@ class XCodeBackend(backends.Backend): warn_array.add_item('"$(inherited)"') bt_dict.add_item('name', buildtype) - def normalize_header_search_paths(self, header_dirs) -> PbxArray: + def normalize_header_search_paths(self, header_dirs: T.List[str]) -> PbxArray: header_arr = PbxArray() for i in header_dirs: np = os.path.normpath(i) @@ -1798,7 +1855,7 @@ class XCodeBackend(backends.Backend): header_arr.add_item(item) return header_arr - def add_otherargs(self, settings_dict, langargs): + def add_otherargs(self, settings_dict: PbxDict, langargs: T.Dict[str, T.List[str]]) -> None: for langname, args in langargs.items(): if args: quoted_args = [] diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 7320b88..906f552 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -22,23 +22,23 @@ from . import programs from .mesonlib import ( HoldableObject, SecondLevelHolder, File, MesonException, MachineChoice, PerMachine, OrderedSet, listify, - extract_as_list, typeslistify, stringlistify, classify_unity_sources, + extract_as_list, typeslistify, classify_unity_sources, get_filenames_templates_dict, substitute_values, has_path_sep, - is_parent_path, PerMachineDefaultable, + is_parent_path, relpath, PerMachineDefaultable, MesonBugException, EnvironmentVariables, pickle_load, lazy_property, ) from .options import OptionKey from .compilers import ( is_header, is_object, is_source, clink_langs, sort_clink, all_languages, - is_known_suffix, detect_static_linker + is_known_suffix, detect_static_linker, LANGUAGES_USING_LDFLAGS ) from .interpreterbase import FeatureNew, FeatureDeprecated if T.TYPE_CHECKING: - from typing_extensions import Literal, TypedDict + from typing_extensions import Literal, TypeAlias, TypedDict - from . import environment + from .environment import Environment from ._typing import ImmutableListProtocol from .backend.backends import Backend from .compilers import Compiler @@ -47,13 +47,14 @@ if T.TYPE_CHECKING: from .interpreterbase import SubProject from .linkers.linkers import StaticLinker from .mesonlib import ExecutableSerialisation, FileMode, FileOrString - from .modules import ModuleState from .mparser import BaseNode - GeneratedTypes = T.Union['CustomTarget', 'CustomTargetIndex', 'GeneratedList'] - LibTypes = T.Union['SharedLibrary', 'StaticLibrary', 'CustomTarget', 'CustomTargetIndex'] - BuildTargetTypes = T.Union['BuildTarget', 'CustomTarget', 'CustomTargetIndex'] - ObjectTypes = T.Union[str, 'File', 'ExtractedObjects', 'GeneratedTypes'] + GeneratedTypes: TypeAlias = T.Union['CustomTarget', 'CustomTargetIndex', 'GeneratedList'] + LibTypes: TypeAlias = T.Union['SharedLibrary', 'StaticLibrary', 'CustomTarget', 'CustomTargetIndex'] + BuildTargetTypes: TypeAlias = T.Union['BuildTarget', 'CustomTarget', 'CustomTargetIndex'] + ObjectTypes: TypeAlias = T.Union[str, 'File', 'ExtractedObjects', 'GeneratedTypes'] + AnyTargetType: TypeAlias = T.Union['Target', 'CustomTargetIndex'] + RustCrateType: TypeAlias = Literal['bin', 'lib', 'rlib', 'dylib', 'cdylib', 'staticlib', 'proc-macro'] class DFeatures(TypedDict): @@ -62,6 +63,89 @@ if T.TYPE_CHECKING: import_dirs: T.List[IncludeDirs] versions: T.List[T.Union[str, int]] + class BuildTargetKeywordArguments(TypedDict, total=False): + + # The use of Sequence[str] here is intentional to get the generally + # undesirable behavior of Sequence[str] + + build_by_default: bool + build_rpath: str + c_pch: T.Optional[T.Tuple[str, T.Optional[str]]] + cpp_pch: T.Optional[T.Tuple[str, T.Optional[str]]] + d_debug: T.List[T.Union[str, int]] + d_import_dirs: T.List[IncludeDirs] + d_module_versions: T.List[T.Union[str, int]] + d_unittest: bool + dependencies: T.List[dependencies.Dependency] + extra_files: T.List[File] + gnu_symbol_visibility: Literal['default', 'internal', 'hidden', 'protected', 'inlineshidden', ''] + implicit_include_directories: bool + include_directories: T.List[IncludeDirs] + install: bool + install_dir: T.List[T.Union[str, Literal[False]]] + install_mode: FileMode + install_rpath: str + install_tag: T.List[T.Optional[str]] + language_args: T.DefaultDict[str, T.List[str]] + link_args: T.List[str] + link_depends: T.List[T.Union[str, File, CustomTarget, CustomTargetIndex]] + link_language: str + link_whole: T.List[T.Union[StaticLibrary, CustomTarget, CustomTargetIndex]] + link_with: T.List[BuildTargetTypes] + name_prefix: T.Optional[str] + name_suffix: T.Optional[str] + native: MachineChoice + override_options: T.Dict[OptionKey, str] + resources: T.List[str] + swift_interoperability_mode: Literal['c', 'cpp'] + swift_module_name: str + rust_crate_type: RustCrateType + rust_dependency_map: T.Dict[str, str] + vala_gir: T.Optional[str] + vala_header: T.Optional[str] + vala_vapi: T.Optional[str] + win_subsystem: str + + _allow_no_sources: bool + + class ExecutableKeywordArguments(BuildTargetKeywordArguments, total=False): + + android_exe_type: T.Optional[Literal['application', 'executable']] + implib: T.Optional[str] + export_dynamic: bool + pie: bool + vs_module_defs: T.Union[str, File, CustomTarget, CustomTargetIndex] + + class SharedModuleKeywordArguments(BuildTargetKeywordArguments, total=False): + + vs_module_defs: T.Union[str, File, CustomTarget, CustomTargetIndex] + + class SharedLibraryKeywordArguments(SharedModuleKeywordArguments, total=False): + + version: str + soversion: str + darwin_versions: T.Tuple[str, str] + shortname: str + + class StaticLibraryKeywordArguments(BuildTargetKeywordArguments, total=False): + + pic: bool + prelink: bool + +DEFAULT_STATIC_LIBRARY_NAMES: T.Mapping[str, T.Tuple[str, str]] = { + 'unix': ('lib', 'a'), + 'windows': ('', 'lib'), + 'darwin': ('lib', 'a'), + 'cygwin': ('lib', 'a'), +} + +DEFAULT_SHARED_LIBRARY_NAMES: T.Mapping[str, T.Tuple[str, str, str]] = { + 'unix': ('lib', 'so', ''), + 'windows': ('', 'dll', 'dll.lib'), + 'darwin': ('lib', 'dylib', ''), + 'cygwin': ('cyg', 'dll', 'dll.a'), +} + pch_kwargs = {'c_pch', 'cpp_pch'} lang_arg_kwargs = {f'{lang}_args' for lang in all_languages} @@ -75,10 +159,12 @@ lang_arg_kwargs |= { vala_kwargs = {'vala_header', 'vala_gir', 'vala_vapi'} rust_kwargs = {'rust_crate_type', 'rust_dependency_map'} cs_kwargs = {'resources', 'cs_args'} +swift_kwargs = {'swift_interoperability_mode', 'swift_module_name'} buildtarget_kwargs = { 'build_by_default', 'build_rpath', + 'build_subdir', 'dependencies', 'extra_files', 'gui_app', @@ -110,10 +196,11 @@ known_build_target_kwargs = ( pch_kwargs | vala_kwargs | rust_kwargs | - cs_kwargs) + cs_kwargs | + swift_kwargs) known_exe_kwargs = known_build_target_kwargs | {'implib', 'export_dynamic', 'pie', 'vs_module_defs', 'android_exe_type'} -known_shlib_kwargs = known_build_target_kwargs | {'version', 'soversion', 'vs_module_defs', 'darwin_versions', 'rust_abi'} +known_shlib_kwargs = known_build_target_kwargs | {'version', 'soversion', 'vs_module_defs', 'darwin_versions', 'rust_abi', 'shortname'} known_shmod_kwargs = known_build_target_kwargs | {'vs_module_defs', 'rust_abi'} known_stlib_kwargs = known_build_target_kwargs | {'pic', 'prelink', 'rust_abi'} known_jar_kwargs = known_exe_kwargs | {'main_class', 'java_resources'} @@ -244,12 +331,13 @@ class Build: all dependencies and so on. """ - def __init__(self, environment: environment.Environment): + def __init__(self, environment: Environment): self.version = coredata.version + self._def_files: T.Optional[T.List[str]] = None self.project_name = 'name of master project' self.project_version: T.Optional[str] = None self.environment = environment - self.projects = {} + self.projects: T.Dict[str, str] = {} self.targets: 'T.OrderedDict[str, T.Union[CustomTarget, BuildTarget]]' = OrderedDict() self.targetnames: T.Set[T.Tuple[str, str]] = set() # Set of executable names and their subdir self.global_args: PerMachine[T.Dict[str, T.List[str]]] = PerMachine({}, {}) @@ -275,7 +363,7 @@ class Build: self.stdlibs = PerMachine({}, {}) self.test_setups: T.Dict[str, TestSetup] = {} self.test_setup_default_name = None - self.find_overrides: T.Dict[str, T.Union['Executable', programs.ExternalProgram, programs.OverrideProgram]] = {} + self.find_overrides: T.Dict[str, T.Union['OverrideExecutable', programs.ExternalProgram, programs.OverrideProgram]] = {} self.searched_programs: T.Set[str] = set() # The list of all programs that have been searched for. # If we are doing a cross build we need two caches, if we're doing a @@ -289,14 +377,26 @@ class Build: Needed for tracking whether a modules options needs to be exposed to the user. """ - def get_build_targets(self): + @property + def def_files(self) -> T.List[str]: + if self._def_files is None: + raise MesonBugException('build.def_files has not been set yet') + return self._def_files + + @def_files.setter + def def_files(self, value: T.List[str]): + if self._def_files is not None: + raise MesonBugException('build.def_files already set') + self._def_files = value + + def get_build_targets(self) -> OrderedDict[str, BuildTarget]: build_targets = OrderedDict() for name, t in self.targets.items(): if isinstance(t, BuildTarget): build_targets[name] = t return build_targets - def get_custom_targets(self): + def get_custom_targets(self) -> OrderedDict[str, CustomTarget]: custom_targets = OrderedDict() for name, t in self.targets.items(): if isinstance(t, CustomTarget): @@ -320,7 +420,7 @@ class Build: if self.static_linker[compiler.for_machine] is None and compiler.needs_static_linker(): self.static_linker[compiler.for_machine] = detect_static_linker(self.environment, compiler) - def get_project(self): + def get_project(self) -> T.Dict[str, str]: return self.projects[''] def get_subproject_dir(self): @@ -477,7 +577,7 @@ class StructuredSources(HoldableObject): represent the required filesystem layout. """ - sources: T.DefaultDict[str, T.List[T.Union[File, CustomTarget, CustomTargetIndex, GeneratedList]]] = field( + sources: T.DefaultDict[str, T.List[T.Union[File, GeneratedTypes]]] = field( default_factory=lambda: defaultdict(list)) def __add__(self, other: StructuredSources) -> StructuredSources: @@ -489,14 +589,7 @@ class StructuredSources(HoldableObject): def __bool__(self) -> bool: return bool(self.sources) - def first_file(self) -> T.Union[File, CustomTarget, CustomTargetIndex, GeneratedList]: - """Get the first source in the root - - :return: The first source in the root - """ - return self.sources[''][0] - - def as_list(self) -> T.List[T.Union[File, CustomTarget, CustomTargetIndex, GeneratedList]]: + def as_list(self) -> T.List[T.Union[File, GeneratedTypes]]: return list(itertools.chain.from_iterable(self.sources.values())) def needs_copy(self) -> bool: @@ -524,10 +617,11 @@ class Target(HoldableObject, metaclass=abc.ABCMeta): subproject: 'SubProject' build_by_default: bool for_machine: MachineChoice - environment: environment.Environment + environment: Environment install: bool = False build_always_stale: bool = False extra_files: T.List[File] = field(default_factory=list) + build_subdir: str = '' @abc.abstractproperty def typename(self) -> str: @@ -545,6 +639,9 @@ class Target(HoldableObject, metaclass=abc.ABCMeta): Target "{self.name}" has a path separator in its name. This is not supported, it can cause unexpected failures and will become a hard error in the future.''')) + self.builddir = self.subdir + if self.build_subdir: + self.builddir = os.path.join(self.subdir, self.build_subdir) # dataclass comparators? def __lt__(self, other: object) -> bool: @@ -605,6 +702,12 @@ class Target(HoldableObject, metaclass=abc.ABCMeta): def get_typename(self) -> str: return self.typename + def get_build_subdir(self) -> str: + return self.build_subdir + + def get_builddir(self) -> str: + return self.builddir + @staticmethod def _get_id_hash(target_id: str) -> str: # We don't really need cryptographic security here. @@ -640,45 +743,14 @@ class Target(HoldableObject, metaclass=abc.ABCMeta): if getattr(self, 'name_suffix_set', False): name += '.' + self.suffix return self.construct_id_from_path( - self.subdir, name, self.type_suffix()) + self.builddir, name, self.type_suffix()) def get_id(self) -> str: return self.id - def process_kwargs_base(self, kwargs: T.Dict[str, T.Any]) -> None: - if 'build_by_default' in kwargs: - self.build_by_default = kwargs['build_by_default'] - if not isinstance(self.build_by_default, bool): - raise InvalidArguments('build_by_default must be a boolean value.') - - if not self.build_by_default and kwargs.get('install', False): - # For backward compatibility, if build_by_default is not explicitly - # set, use the value of 'install' if it's enabled. - self.build_by_default = True - - self.raw_overrides = self.parse_overrides(kwargs) - def get_override(self, name: str) -> T.Optional[str]: return self.raw_overrides.get(name, None) - @staticmethod - def parse_overrides(kwargs: T.Dict[str, T.Any]) -> T.Dict[str, str]: - opts = kwargs.get('override_options', []) - - # In this case we have an already parsed and ready to go dictionary - # provided by typed_kwargs - if isinstance(opts, dict): - return T.cast('T.Dict[OptionKey, str]', opts) - - result: T.Dict[str, str] = {} - overrides = stringlistify(opts) - for o in overrides: - if '=' not in o: - raise InvalidArguments('Overrides must be of form "key=value"') - k, v = o.split('=', 1) - result[k] = v - return result - def is_linkable_target(self) -> bool: return False @@ -692,6 +764,7 @@ class BuildTarget(Target): known_kwargs = known_build_target_kwargs install_dir: T.List[T.Union[str, Literal[False]]] + rust_crate_type: RustCrateType # This set contains all the languages a linker can link natively # without extra flags. For instance, nvcc (cuda) can link C++ @@ -711,10 +784,10 @@ class BuildTarget(Target): sources: T.List['SourceOutputs'], structured_sources: T.Optional[StructuredSources], objects: T.List[ObjectTypes], - environment: environment.Environment, + environment: Environment, compilers: T.Dict[str, 'Compiler'], - kwargs: T.Dict[str, T.Any]): - super().__init__(name, subdir, subproject, True, for_machine, environment, install=kwargs.get('install', False)) + kwargs: BuildTargetKeywordArguments): + super().__init__(name, subdir, subproject, True, for_machine, environment, install=kwargs.get('install', False), build_subdir=kwargs.get('build_subdir', '')) self.all_compilers = compilers self.compilers: OrderedDict[str, Compiler] = OrderedDict() self.objects: T.List[ObjectTypes] = [] @@ -725,7 +798,7 @@ class BuildTarget(Target): self.link_targets: T.List[LibTypes] = [] self.link_whole_targets: T.List[T.Union[StaticLibrary, CustomTarget, CustomTargetIndex]] = [] self.depend_files: T.List[File] = [] - self.link_depends = [] + self.link_depends: T.List[T.Union[File, BuildTargetTypes]] = [] self.added_deps = set() self.name_prefix_set = False self.name_suffix_set = False @@ -736,7 +809,7 @@ class BuildTarget(Target): # The list of all files outputted by this target. Useful in cases such # as Vala which generates .vapi and .h besides the compiled output. self.outputs = [self.filename] - self.pch: T.Dict[str, T.List[str]] = {} + self.pch: T.Dict[str, T.Optional[T.Tuple[str, T.Optional[str]]]] = {} self.extra_args: T.DefaultDict[str, T.List[str]] = kwargs.get('language_args', defaultdict(list)) self.sources: T.List[File] = [] # If the same source is defined multiple times, use it only once. @@ -754,6 +827,9 @@ class BuildTarget(Target): self.both_lib: T.Optional[T.Union[StaticLibrary, SharedLibrary]] = None # Track build_rpath entries so we can remove them at install time self.rpath_dirs_to_remove: T.Set[bytes] = set() + self.vala_header: T.Optional[str] = None + self.vala_vapi: T.Optional[str] = None + self.vala_gir: T.Optional[str] = None self.process_sourcelist(sources) # Objects can be: # 1. Preexisting objects provided by the user with the `objects:` kwarg @@ -773,9 +849,10 @@ class BuildTarget(Target): self.link_whole_targets.clear() self.link(link_targets) self.link_whole(link_whole_targets) + self._set_vala_args(kwargs) - if not any([self.sources, self.generated, self.objects, self.link_whole_targets, self.structured_sources, - kwargs.pop('_allow_no_sources', False)]): + if not any([[src for src in self.sources if not is_header(src)], self.generated, self.objects, + self.link_whole_targets, self.structured_sources, kwargs.pop('_allow_no_sources', False)]): mlog.warning(f'Build target {name} has no sources. ' 'This was never supposed to be allowed but did because of a bug, ' 'support will be removed in a future release of Meson') @@ -783,22 +860,41 @@ class BuildTarget(Target): self.validate_install() self.check_module_linking() + def _set_vala_args(self, kwargs: BuildTargetKeywordArguments) -> None: + if self.uses_vala(): + self.vala_header = kwargs.get('vala_header') or self.name + '.h' + self.vala_vapi = kwargs.get('vala_vapi') or self.name + '.vapi' + self.vala_gir = kwargs.get('vala_gir') + def post_init(self) -> None: ''' Initialisations and checks requiring the final list of compilers to be known ''' self.validate_sources() - if self.structured_sources and any([self.sources, self.generated]): - raise MesonException('cannot mix structured sources and unstructured sources') - if self.structured_sources and 'rust' not in self.compilers: - raise MesonException('structured sources are only supported in Rust targets') if self.uses_rust(): + if self.link_language and self.link_language != 'rust': + raise MesonException('cannot build Rust sources with a different link_language') + if self.structured_sources: + # TODO: the interpreter should be able to generate a better error message? + if any((s.endswith('.rs') for s in self.sources)) or \ + any(any((s.endswith('.rs') for s in g.get_outputs())) for g in self.generated): + raise MesonException('cannot mix Rust structured sources and unstructured sources') + # relocation-model=pic is rustc's default and Meson does not # currently have a way to disable PIC. self.pic = True - if 'vala' in self.compilers and self.is_linkable_target(): - self.outputs += [self.vala_header, self.vala_vapi] - self.install_tag += ['devel', 'devel'] - if self.vala_gir: + self.pie = True + else: + if self.structured_sources: + raise MesonException('structured sources are only supported in Rust targets') + + if self.is_linkable_target(): + if self.vala_header is not None: + self.outputs.append(self.vala_header) + self.install_tag.append('devel') + if self.vala_vapi is not None: + self.outputs.append(self.vala_vapi) + self.install_tag.append('devel') + if self.vala_gir is not None: self.outputs.append(self.vala_gir) self.install_tag.append('devel') @@ -816,12 +912,12 @@ class BuildTarget(Target): else: mlog.warning('Installing target build for the build machine. This will fail in a cross build.') - def check_unknown_kwargs(self, kwargs): + def check_unknown_kwargs(self, kwargs: BuildTargetKeywordArguments) -> None: # Override this method in derived classes that have more # keywords. self.check_unknown_kwargs_int(kwargs, self.known_kwargs) - def check_unknown_kwargs_int(self, kwargs, known_kwargs): + def check_unknown_kwargs_int(self, kwargs: BuildTargetKeywordArguments, known_kwargs: T.Set[str]) -> None: unknowns = [] for k in kwargs: if k == 'language_args': @@ -889,6 +985,11 @@ class BuildTarget(Target): # did user override clink_langs for this target? link_langs = [self.link_language] if self.link_language else clink_langs + if self.link_language: + if self.link_language not in self.all_compilers: + m = f'Target {self.name} requires {self.link_language} compiler not part of the project' + raise MesonException(m) + # If this library is linked against another library we need to consider # the languages of those libraries as well. if self.link_targets or self.link_whole_targets: @@ -896,6 +997,10 @@ class BuildTarget(Target): if isinstance(t, (CustomTarget, CustomTargetIndex)): continue # We can't know anything about these. for name, compiler in t.compilers.items(): + if name == 'rust': + # Rust is always linked through a C-ABI target, so do not add + # the compiler here + continue if name in link_langs and name not in self.compilers: self.compilers[name] = compiler @@ -903,7 +1008,7 @@ class BuildTarget(Target): # No source files or parent targets, target consists of only object # files of unknown origin. Just add the first clink compiler # that we have and hope that it can link these objects - for lang in link_langs: + for lang in reversed(link_langs): if lang in self.all_compilers: self.compilers[lang] = self.all_compilers[lang] break @@ -981,7 +1086,7 @@ class BuildTarget(Target): self.compilers[lang] = compiler break else: - if is_known_suffix(s): + if is_known_suffix(s) and not is_header(s): path = pathlib.Path(str(s)).as_posix() m = f'No {self.for_machine.get_lower_case_name()} machine compiler for {path!r}' raise MesonException(m) @@ -1011,7 +1116,7 @@ class BuildTarget(Target): langs = ', '.join(self.compilers.keys()) raise InvalidArguments(f'Cannot mix those languages into a target: {langs}') - def process_link_depends(self, sources): + def process_link_depends(self, sources: T.Iterable[T.Union[str, File, BuildTargetTypes]]) -> None: """Process the link_depends keyword argument. This is designed to handle strings, Files, and the output of Custom @@ -1020,19 +1125,14 @@ class BuildTarget(Target): generated twice, since the output needs to be passed to the ld_args and link_depends. """ - sources = listify(sources) for s in sources: if isinstance(s, File): self.link_depends.append(s) elif isinstance(s, str): self.link_depends.append( File.from_source_file(self.environment.source_dir, self.subdir, s)) - elif hasattr(s, 'get_outputs'): - self.link_depends.append(s) else: - raise InvalidArguments( - 'Link_depends arguments must be strings, Files, ' - 'or a Custom Target, or lists thereof.') + self.link_depends.append(s) def extract_objects(self, srclist: T.List[T.Union['FileOrString', 'GeneratedTypes']], is_unity: bool) -> ExtractedObjects: sources_set = set(self.sources) @@ -1074,20 +1174,23 @@ class BuildTarget(Target): at link time, see get_dependencies() for that. """ result: OrderedSet[BuildTargetTypes] = OrderedSet() + nonresults: T.Set[BuildTargetTypes] = set() stack: T.Deque[BuildTargetTypes] = deque() stack.appendleft(self) while stack: t = stack.pop() - if t in result: + if t in result or t in nonresults: continue if isinstance(t, CustomTargetIndex): stack.appendleft(t.target) continue if isinstance(t, SharedLibrary): result.add(t) + else: + nonresults.add(t) if isinstance(t, BuildTarget): - stack.extendleft(t.link_targets) - stack.extendleft(t.link_whole_targets) + stack.extendleft((t2 for t2 in t.link_targets if t2 not in nonresults)) + stack.extendleft((t2 for t2 in t.link_whole_targets if t2 not in nonresults)) return list(result) @lru_cache(maxsize=None) @@ -1155,22 +1258,23 @@ class BuildTarget(Target): def get_custom_install_mode(self) -> T.Optional['FileMode']: return self.install_mode - def process_kwargs(self, kwargs): - self.process_kwargs_base(kwargs) + def process_kwargs(self, kwargs: BuildTargetKeywordArguments) -> None: self.original_kwargs = kwargs - self.add_pch('c', extract_as_list(kwargs, 'c_pch')) - self.add_pch('cpp', extract_as_list(kwargs, 'cpp_pch')) + if 'build_by_default' in kwargs: + self.build_by_default = kwargs['build_by_default'] + + if not self.build_by_default and kwargs.get('install', False): + # For backward compatibility, if build_by_default is not explicitly + # set, use the value of 'install' if it's enabled. + self.build_by_default = True + + self.raw_overrides = kwargs.get('override_options', {}) - if not isinstance(self, Executable) or kwargs.get('export_dynamic', False): - self.vala_header = kwargs.get('vala_header', self.name + '.h') - self.vala_vapi = kwargs.get('vala_vapi', self.name + '.vapi') - self.vala_gir = kwargs.get('vala_gir', None) + self.pch['c'] = kwargs.get('c_pch') + self.pch['cpp'] = kwargs.get('cpp_pch') - self.link_args = extract_as_list(kwargs, 'link_args') - for i in self.link_args: - if not isinstance(i, str): - raise InvalidArguments('Link_args arguments must be strings.') + self.link_args = kwargs.get('link_args', []) for l in self.link_args: if '-Wl,-rpath' in l or l.startswith('-rpath'): mlog.warning(textwrap.dedent('''\ @@ -1191,94 +1295,48 @@ class BuildTarget(Target): self.install_dir = typeslistify(kwargs.get('install_dir', []), (str, bool)) self.install_mode = kwargs.get('install_mode', None) - self.install_tag = stringlistify(kwargs.get('install_tag', [None])) - if not isinstance(self, Executable): - # build_target will always populate these as `None`, which is fine - if kwargs.get('gui_app') is not None: - raise InvalidArguments('Argument gui_app can only be used on executables.') - if kwargs.get('win_subsystem') is not None: - raise InvalidArguments('Argument win_subsystem can only be used on executables.') - extra_files = extract_as_list(kwargs, 'extra_files') - for i in extra_files: - assert isinstance(i, File) - if i in self.extra_files: - continue - trial = os.path.join(self.environment.get_source_dir(), i.subdir, i.fname) - if not os.path.isfile(trial): - raise InvalidArguments(f'Tried to add non-existing extra file {i}.') - self.extra_files.append(i) + self.install_tag: T.List[T.Optional[str]] = kwargs.get('install_tag') or [None] + self.extra_files = kwargs.get('extra_files', []) self.install_rpath: str = kwargs.get('install_rpath', '') - if not isinstance(self.install_rpath, str): - raise InvalidArguments('Install_rpath is not a string.') self.build_rpath = kwargs.get('build_rpath', '') - if not isinstance(self.build_rpath, str): - raise InvalidArguments('Build_rpath is not a string.') - resources = extract_as_list(kwargs, 'resources') - for r in resources: - if not isinstance(r, str): - raise InvalidArguments('Resource argument is not a string.') - trial = os.path.join(self.environment.get_source_dir(), self.subdir, r) - if not os.path.isfile(trial): - raise InvalidArguments(f'Tried to add non-existing resource {r}.') - self.resources = resources - if kwargs.get('name_prefix') is not None: - name_prefix = kwargs['name_prefix'] - if isinstance(name_prefix, list): - if name_prefix: - raise InvalidArguments('name_prefix array must be empty to signify default.') - else: - if not isinstance(name_prefix, str): - raise InvalidArguments('name_prefix must be a string.') - self.prefix = name_prefix - self.name_prefix_set = True - if kwargs.get('name_suffix') is not None: - name_suffix = kwargs['name_suffix'] - if isinstance(name_suffix, list): - if name_suffix: - raise InvalidArguments('name_suffix array must be empty to signify default.') - else: - if not isinstance(name_suffix, str): - raise InvalidArguments('name_suffix must be a string.') - if name_suffix == '': - raise InvalidArguments('name_suffix should not be an empty string. ' - 'If you want meson to use the default behaviour ' - 'for each platform pass `[]` (empty array)') - self.suffix = name_suffix - self.name_suffix_set = True - if isinstance(self, StaticLibrary): - # You can't disable PIC on OS X. The compiler ignores -fno-PIC. - # PIC is always on for Windows (all code is position-independent - # since library loading is done differently) - m = self.environment.machines[self.for_machine] - if m.is_darwin() or m.is_windows(): - self.pic = True - else: - self.pic = self._extract_pic_pie(kwargs, 'pic', 'b_staticpic') - if isinstance(self, Executable) or (isinstance(self, StaticLibrary) and not self.pic): - # Executables must be PIE on Android - if self.environment.machines[self.for_machine].is_android(): - self.pie = True - else: - self.pie = self._extract_pic_pie(kwargs, 'pie', 'b_pie') + self.resources = kwargs.get('resources', []) + name_prefix = kwargs.get('name_prefix') + if name_prefix is not None: + self.prefix = name_prefix + self.name_prefix_set = True + name_suffix = kwargs.get('name_suffix') + if name_suffix is not None: + self.suffix = name_suffix + self.name_suffix_set = True self.implicit_include_directories = kwargs.get('implicit_include_directories', True) - if not isinstance(self.implicit_include_directories, bool): - raise InvalidArguments('Implicit_include_directories must be a boolean.') self.gnu_symbol_visibility = kwargs.get('gnu_symbol_visibility', '') - if not isinstance(self.gnu_symbol_visibility, str): - raise InvalidArguments('GNU symbol visibility must be a string.') - if self.gnu_symbol_visibility != '': - permitted = ['default', 'internal', 'hidden', 'protected', 'inlineshidden'] - if self.gnu_symbol_visibility not in permitted: - raise InvalidArguments('GNU symbol visibility arg {} not one of: {}'.format(self.gnu_symbol_visibility, ', '.join(permitted))) - - rust_dependency_map = kwargs.get('rust_dependency_map', {}) - if not isinstance(rust_dependency_map, dict): - raise InvalidArguments(f'Invalid rust_dependency_map "{rust_dependency_map}": must be a dictionary.') - if any(not isinstance(v, str) for v in rust_dependency_map.values()): - raise InvalidArguments(f'Invalid rust_dependency_map "{rust_dependency_map}": must be a dictionary with string values.') - self.rust_dependency_map = rust_dependency_map - - def _extract_pic_pie(self, kwargs: T.Dict[str, T.Any], arg: str, option: str) -> bool: + self.rust_dependency_map = kwargs.get('rust_dependency_map', {}) + + self.swift_interoperability_mode = kwargs.get('swift_interoperability_mode', 'c') + self.swift_module_name = kwargs.get('swift_module_name') or self.name + + @T.overload + def _extract_pic_pie(self, kwargs: StaticLibraryKeywordArguments, arg: Literal['pic'], + option: Literal['b_staticpic']) -> bool: ... + + @T.overload + def _extract_pic_pie(self, kwargs: T.Union[StaticLibraryKeywordArguments, ExecutableKeywordArguments], + arg: Literal['pie'], option: Literal['b_pie']) -> bool: ... + + def _extract_pic_pie(self, kwargs: T.Union[StaticLibraryKeywordArguments, ExecutableKeywordArguments], + arg: Literal['pic', 'pie'], option: Literal['b_staticpic', 'b_pie']) -> bool: + # You can't disable PIC on OS X. The compiler ignores -fno-PIC. + # PIC is always on for Windows (all code is position-independent + # since library loading is done differently) + m = self.environment.machines[self.for_machine] + assert m is not None, 'for mypy' + if arg == 'pic' and (m.is_darwin() or m.is_windows()): + return True + + # Executables must be PIE on Android + if arg == 'pie' and m.is_android(): + return True + # Check if we have -fPIC, -fpic, -fPIE, or -fpie in cflags all_flags = self.extra_args['c'] + self.extra_args['cpp'] if '-f' + arg.lower() in all_flags or '-f' + arg.upper() in all_flags: @@ -1287,15 +1345,12 @@ class BuildTarget(Target): k = OptionKey(option) if kwargs.get(arg) is not None: - val = T.cast('bool', kwargs[arg]) + return kwargs[arg] elif k in self.environment.coredata.optstore: - val = self.environment.coredata.optstore.get_value_for(k.name, k.subproject) - else: - val = False - - if not isinstance(val, bool): - raise InvalidArguments(f'Argument {arg} to {self.name!r} must be boolean') - return val + val = self.environment.coredata.get_option_for_target(self, k) + assert isinstance(val, bool), 'for mypy' + return val + return False def get_filename(self) -> str: return self.filename @@ -1363,10 +1418,7 @@ class BuildTarget(Target): return self.install def has_pch(self) -> bool: - return bool(self.pch) - - def get_pch(self, language: str) -> T.List[str]: - return self.pch.get(language, []) + return any(x is not None for x in self.pch.values()) def get_include_dirs(self) -> T.List['IncludeDirs']: return self.include_dirs @@ -1375,6 +1427,10 @@ class BuildTarget(Target): deps = listify(deps) for dep in deps: if dep in self.added_deps: + # Prefer to add dependencies to added_deps which have a name + if dep.is_named(): + self.added_deps.remove(dep) + self.added_deps.add(dep) continue if isinstance(dep, dependencies.InternalDependency): @@ -1533,37 +1589,6 @@ class BuildTarget(Target): else: mlog.warning(msg + ' This will fail in cross build.') - def add_pch(self, language: str, pchlist: T.List[str]) -> None: - if not pchlist: - return - elif len(pchlist) == 1: - if not is_header(pchlist[0]): - raise InvalidArguments(f'PCH argument {pchlist[0]} is not a header.') - elif len(pchlist) == 2: - if is_header(pchlist[0]): - if not is_source(pchlist[1]): - raise InvalidArguments('PCH definition must contain one header and at most one source.') - elif is_source(pchlist[0]): - if not is_header(pchlist[1]): - raise InvalidArguments('PCH definition must contain one header and at most one source.') - pchlist = [pchlist[1], pchlist[0]] - else: - raise InvalidArguments(f'PCH argument {pchlist[0]} is of unknown type.') - - if os.path.dirname(pchlist[0]) != os.path.dirname(pchlist[1]): - raise InvalidArguments('PCH files must be stored in the same folder.') - - FeatureDeprecated.single_use('PCH source files', '0.50.0', self.subproject, - 'Only a single header file should be used.') - elif len(pchlist) > 2: - raise InvalidArguments('PCH definition may have a maximum of 2 files.') - for f in pchlist: - if not isinstance(f, str): - raise MesonException('PCH arguments must be strings.') - if not os.path.isfile(os.path.join(self.environment.source_dir, self.subdir, f)): - raise MesonException(f'File {f} does not exist.') - self.pch[language] = pchlist - def add_include_dirs(self, args: T.Sequence['IncludeDirs'], set_is_system: T.Optional[str] = None) -> None: ids: T.List['IncludeDirs'] = [] for a in args: @@ -1603,12 +1628,15 @@ class BuildTarget(Target): if isinstance(link_target, (CustomTarget, CustomTargetIndex)): continue for language in link_target.compilers: + if language == 'rust' and not link_target.uses_rust_abi(): + # All Rust dependencies must go through a C-ABI dependency, so ignore it + continue if language not in langs: langs.append(language) return langs - def get_prelinker(self): + def get_prelinker(self) -> Compiler: if self.link_language: comp = self.all_compilers[self.link_language] return comp @@ -1637,7 +1665,7 @@ class BuildTarget(Target): # If the user set the link_language, just return that. if self.link_language: comp = self.all_compilers[self.link_language] - return comp, comp.language_stdlib_only_link_flags(self.environment) + return comp, comp.language_stdlib_only_link_flags() # Since dependencies could come from subprojects, they could have # languages we don't have in self.all_compilers. Use the global list of @@ -1667,7 +1695,7 @@ class BuildTarget(Target): for l in clink_langs: try: comp = self.all_compilers[l] - return comp, comp.language_stdlib_only_link_flags(self.environment) + return comp, comp.language_stdlib_only_link_flags() except KeyError: pass @@ -1682,7 +1710,7 @@ class BuildTarget(Target): # We need to use all_compilers here because # get_langs_used_by_deps could return a language from a # subproject - stdlib_args.extend(all_compilers[dl].language_stdlib_only_link_flags(self.environment)) + stdlib_args.extend(all_compilers[dl].language_stdlib_only_link_flags()) return stdlib_args def uses_rust(self) -> bool: @@ -1694,6 +1722,12 @@ class BuildTarget(Target): def uses_fortran(self) -> bool: return 'fortran' in self.compilers + def uses_vala(self) -> bool: + return 'vala' in self.compilers + + def uses_swift_cpp_interop(self) -> bool: + return self.swift_interoperability_mode == 'cpp' and 'swift' in self.compilers + def get_using_msvc(self) -> bool: ''' Check if the dynamic linker is MSVC. Used by Executable, StaticLibrary, @@ -1744,11 +1778,11 @@ class BuildTarget(Target): 'use shared_library() with `override_options: [\'b_lundef=false\']` instead.') link_target.force_soname = True - def process_vs_module_defs_kw(self, kwargs: T.Dict[str, T.Any]) -> None: - if kwargs.get('vs_module_defs') is None: + def process_vs_module_defs_kw(self, kwargs: ExecutableKeywordArguments) -> None: + path = kwargs.get('vs_module_defs') + if path is None: return - path: T.Union[str, File, CustomTarget, CustomTargetIndex] = kwargs['vs_module_defs'] if isinstance(path, str): if os.path.isabs(path): self.vs_module_defs = File.from_absolute_file(path) @@ -1757,16 +1791,12 @@ class BuildTarget(Target): elif isinstance(path, File): # When passing a generated file. self.vs_module_defs = path - elif isinstance(path, (CustomTarget, CustomTargetIndex)): + else: # When passing output of a Custom Target self.vs_module_defs = File.from_built_file(path.get_subdir(), path.get_filename()) - else: - raise InvalidArguments( - 'vs_module_defs must be either a string, ' - 'a file object, a Custom Target, or a Custom Target Index') - self.process_link_depends(path) + self.process_link_depends([path]) - def extract_targets_as_list(self, kwargs: T.Dict[str, T.Union[LibTypes, T.Sequence[LibTypes]]], key: T.Literal['link_with', 'link_whole']) -> T.List[LibTypes]: + def extract_targets_as_list(self, kwargs: BuildTargetKeywordArguments, key: T.Literal['link_with', 'link_whole']) -> T.List[LibTypes]: bl_type = self.environment.coredata.optstore.get_value_for(OptionKey('default_both_libraries')) if bl_type == 'auto': if isinstance(self, StaticLibrary): @@ -1788,6 +1818,132 @@ class BuildTarget(Target): """Base case used by BothLibraries""" return self + def determine_rpath_dirs(self) -> T.Tuple[str, ...]: + result: OrderedSet[str] + if self.environment.coredata.optstore.get_value_for(OptionKey('layout')) == 'mirror': + # Need a copy here + result = OrderedSet(self.get_link_dep_subdirs()) + else: + result = OrderedSet() + result.add('meson-out') + result.update(self.rpaths_for_non_system_absolute_shared_libraries()) + self.rpath_dirs_to_remove.update([d.encode('utf-8') for d in result]) + return tuple(result) + + @lru_cache(maxsize=None) + def rpaths_for_non_system_absolute_shared_libraries(self, exclude_system: bool = True) -> ImmutableListProtocol[str]: + paths: OrderedSet[str] = OrderedSet() + srcdir = self.environment.get_source_dir() + + system_dirs = set() + if exclude_system: + for cc in self.compilers.values(): + system_dirs.update(cc.get_library_dirs()) + + external_rpaths = self.get_external_rpath_dirs() + build_to_src = relpath(self.environment.get_source_dir(), + self.environment.get_build_dir()) + + for dep in self.external_deps: + if dep.type_name not in {'library', 'pkgconfig', 'cmake'}: + continue + for libpath in dep.link_args: + if libpath.startswith('-'): + continue + # For all link args that are absolute paths to a library file, add RPATH args + if not os.path.isabs(libpath): + continue + libdir, libname = os.path.split(libpath) + # Windows doesn't support rpaths, but we use this function to + # emulate rpaths by setting PATH + # .dll is there for mingw gcc + # .so's may be extended with version information, e.g. libxyz.so.1.2.3 + if not ( + libname.endswith(('.dll', '.lib', '.so', '.dylib')) + or '.so.' in libname + ): + continue + + # Don't remove rpaths specified in LDFLAGS. + if libdir in external_rpaths: + continue + if system_dirs and os.path.normpath(libdir) in system_dirs: + # No point in adding system paths. + continue + + if is_parent_path(srcdir, libdir): + rel_to_src = libdir[len(srcdir) + 1:] + assert not os.path.isabs(rel_to_src), f'rel_to_src: {rel_to_src} is absolute' + paths.add(os.path.join(build_to_src, rel_to_src)) + else: + paths.add(libdir) + # Don't remove rpaths specified by the dependency + paths.difference_update(self.get_rpath_dirs_from_link_args(dep.link_args)) + for i in itertools.chain(self.link_targets, self.link_whole_targets): + if isinstance(i, BuildTarget): + paths.update(i.rpaths_for_non_system_absolute_shared_libraries(exclude_system)) + return list(paths) + + def get_external_rpath_dirs(self) -> T.Set[str]: + args: T.List[str] = [] + for lang in LANGUAGES_USING_LDFLAGS: + try: + args += self.environment.coredata.get_external_link_args(self.for_machine, lang) + except KeyError: + pass + return self.get_rpath_dirs_from_link_args(args) + + # Match rpath formats: + # -Wl,-rpath= + # -Wl,-rpath, + _rpath_regex = re.compile(r'-Wl,-rpath[=,]([^,]+)') + # Match solaris style compat runpath formats: + # -Wl,-R + # -Wl,-R, + _runpath_regex = re.compile(r'-Wl,-R[,]?([^,]+)') + # Match symbols formats: + # -Wl,--just-symbols= + # -Wl,--just-symbols, + _symbols_regex = re.compile(r'-Wl,--just-symbols[=,]([^,]+)') + + @classmethod + def get_rpath_dirs_from_link_args(cls, args: T.List[str]) -> T.Set[str]: + dirs: T.Set[str] = set() + + for arg in args: + if not arg.startswith('-Wl,'): + continue + + rpath_match = cls._rpath_regex.match(arg) + if rpath_match: + for dir in rpath_match.group(1).split(':'): + dirs.add(dir) + runpath_match = cls._runpath_regex.match(arg) + if runpath_match: + for dir in runpath_match.group(1).split(':'): + # The symbols arg is an rpath if the path is a directory + if os.path.isdir(dir): + dirs.add(dir) + symbols_match = cls._symbols_regex.match(arg) + if symbols_match: + for dir in symbols_match.group(1).split(':'): + # Prevent usage of --just-symbols to specify rpath + if os.path.isdir(dir): + raise MesonException(f'Invalid arg for --just-symbols, {dir} is a directory.') + return dirs + + def get_platform_scheme_name(self) -> str: + m = self.environment.machines[self.for_machine] + if m.is_cygwin(): + return 'cygwin' + elif m.is_windows(): + return 'windows' + elif m.is_darwin(): + return 'darwin' + else: + return 'unix' + + class FileInTargetPrivateDir: """Represents a file with the path '/path/to/build/target_private_dir/fname'. target_private_dir is the return value of get_target_private_dir which is e.g. 'subdir/target.p'. @@ -1823,19 +1979,21 @@ class FileMaybeInTargetPrivateDir: return self.fname class Generator(HoldableObject): - def __init__(self, exe: T.Union['Executable', programs.ExternalProgram], + def __init__(self, env: Environment, + exe: T.Union['Executable', programs.ExternalProgram], arguments: T.List[str], output: T.List[str], # how2dataclass *, depfile: T.Optional[str] = None, capture: bool = False, - depends: T.Optional[T.List[T.Union[BuildTarget, 'CustomTarget', 'CustomTargetIndex']]] = None, + depends: T.Optional[T.List[BuildTargetTypes]] = None, name: str = 'Generator'): + self.environment = env self.exe = exe self.depfile = depfile self.capture = capture - self.depends: T.List[T.Union[BuildTarget, 'CustomTarget', 'CustomTargetIndex']] = depends or [] + self.depends: T.List[BuildTargetTypes] = depends or [] self.arglist = arguments self.outputs = output self.name = name @@ -1865,14 +2023,14 @@ class Generator(HoldableObject): basename = os.path.splitext(plainname)[0] return [x.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname) for x in self.arglist] - def process_files(self, files: T.Iterable[T.Union[str, File, 'CustomTarget', 'CustomTargetIndex', 'GeneratedList']], - state: T.Union['Interpreter', 'ModuleState'], + def process_files(self, files: T.Iterable[T.Union[str, File, GeneratedTypes]], + subdir: str = '', preserve_path_from: T.Optional[str] = None, extra_args: T.Optional[T.List[str]] = None, env: T.Optional[EnvironmentVariables] = None) -> 'GeneratedList': output = GeneratedList( self, - state.subdir, + subdir, preserve_path_from, extra_args=extra_args if extra_args is not None else [], env=env if env is not None else EnvironmentVariables()) @@ -1887,17 +2045,17 @@ class Generator(HoldableObject): output.depends.add(e) fs = [FileInTargetPrivateDir(f) for f in e.get_outputs()] elif isinstance(e, str): - fs = [File.from_source_file(state.environment.source_dir, state.subdir, e)] + fs = [File.from_source_file(self.environment.source_dir, subdir, e)] else: fs = [e] for f in fs: if preserve_path_from: - abs_f = f.absolute_path(state.environment.source_dir, state.environment.build_dir) + abs_f = f.absolute_path(self.environment.source_dir, self.environment.build_dir) if not is_parent_path(preserve_path_from, abs_f): raise InvalidArguments('generator.process: When using preserve_path_from, all input files must be in a subdirectory of the given dir.') f = FileMaybeInTargetPrivateDir(f) - output.add_file(f, state) + output.add_file(f, self.environment) return output @@ -1936,9 +2094,9 @@ class GeneratedList(HoldableObject): # know the absolute path of self.depend_files.append(File.from_absolute_file(path)) - def add_preserved_path_segment(self, infile: FileMaybeInTargetPrivateDir, outfiles: T.List[str], state: T.Union['Interpreter', 'ModuleState']) -> T.List[str]: + def add_preserved_path_segment(self, infile: FileMaybeInTargetPrivateDir, outfiles: T.List[str], environment: Environment) -> T.List[str]: result: T.List[str] = [] - in_abs = infile.absolute_path(state.environment.source_dir, state.environment.build_dir) + in_abs = infile.absolute_path(environment.source_dir, environment.build_dir) assert os.path.isabs(self.preserve_path_from) rel = os.path.relpath(in_abs, self.preserve_path_from) path_segment = os.path.dirname(rel) @@ -1946,11 +2104,11 @@ class GeneratedList(HoldableObject): result.append(os.path.join(path_segment, of)) return result - def add_file(self, newfile: FileMaybeInTargetPrivateDir, state: T.Union['Interpreter', 'ModuleState']) -> None: + def add_file(self, newfile: FileMaybeInTargetPrivateDir, environment: Environment) -> None: self.infilelist.append(newfile) outfiles = self.generator.get_base_outnames(newfile.fname) if self.preserve_path_from: - outfiles = self.add_preserved_path_segment(newfile, outfiles, state) + outfiles = self.add_preserved_path_segment(newfile, outfiles, environment) self.outfilelist += outfiles self.outmap[newfile] = outfiles @@ -1987,23 +2145,17 @@ class Executable(BuildTarget): sources: T.List['SourceOutputs'], structured_sources: T.Optional[StructuredSources], objects: T.List[ObjectTypes], - environment: environment.Environment, + environment: Environment, compilers: T.Dict[str, 'Compiler'], - kwargs): - key = OptionKey('b_pie') - if 'pie' not in kwargs and key in environment.coredata.optstore: - kwargs['pie'] = environment.coredata.optstore.get_value_for(key) + kwargs: ExecutableKeywordArguments): + self.export_dynamic = kwargs.get('export_dynamic', False) + self.rust_crate_type = kwargs.get('rust_crate_type', 'bin') super().__init__(name, subdir, subproject, for_machine, sources, structured_sources, objects, environment, compilers, kwargs) self.win_subsystem = kwargs.get('win_subsystem') or 'console' - assert kwargs.get('android_exe_type') is None or kwargs.get('android_exe_type') in {'application', 'executable'} + self.pie = self._extract_pic_pie(kwargs, 'pie', 'b_pie') # Check for export_dynamic - self.export_dynamic = kwargs.get('export_dynamic', False) - if not isinstance(self.export_dynamic, bool): - raise InvalidArguments('"export_dynamic" keyword argument must be a boolean') - self.implib = kwargs.get('implib') - if not isinstance(self.implib, (bool, str, type(None))): - raise InvalidArguments('"export_dynamic" keyword argument must be a boolean or string') + self.implib_name = kwargs.get('implib') # Only linkwithable if using export_dynamic self.is_linkwithable = self.export_dynamic # Remember that this exe was returned by `find_program()` through an override @@ -2012,6 +2164,12 @@ class Executable(BuildTarget): self.vs_module_defs: T.Optional[File] = None self.process_vs_module_defs_kw(kwargs) + def _set_vala_args(self, kwargs: BuildTargetKeywordArguments) -> None: + # These don't get generated if the executable doesn't have + # export_dynamic set to true. + if self.export_dynamic: + super()._set_vala_args(kwargs) + def post_init(self) -> None: super().post_init() machine = self.environment.machines[self.for_machine] @@ -2057,9 +2215,7 @@ class Executable(BuildTarget): # If using export_dynamic, set the import library name if self.export_dynamic: - implib_basename = self.name + '.exe' - if isinstance(self.implib, str): - implib_basename = self.implib + implib_basename = self.implib_name or self.name + '.exe' if machine.is_windows() or machine.is_cygwin(): if self.get_using_msvc(): self.import_filename = f'{implib_basename}.lib' @@ -2082,13 +2238,6 @@ class Executable(BuildTarget): name += '_' + self.suffix self.debug_filename = name + '.pdb' - def process_kwargs(self, kwargs): - super().process_kwargs(kwargs) - - self.rust_crate_type = kwargs.get('rust_crate_type') or 'bin' - if self.rust_crate_type != 'bin': - raise InvalidArguments('Invalid rust_crate_type: must be "bin" for executables.') - def get_default_install_dir(self) -> T.Union[T.Tuple[str, str], T.Tuple[None, None]]: return self.environment.get_bindir(), '{bindir}' @@ -2115,7 +2264,7 @@ class Executable(BuildTarget): """ return self.debug_filename - def is_linkable_target(self): + def is_linkable_target(self) -> bool: return self.is_linkwithable def get_command(self) -> 'ImmutableListProtocol[str]': @@ -2148,12 +2297,16 @@ class StaticLibrary(BuildTarget): sources: T.List['SourceOutputs'], structured_sources: T.Optional[StructuredSources], objects: T.List[ObjectTypes], - environment: environment.Environment, + environment: Environment, compilers: T.Dict[str, 'Compiler'], - kwargs): - self.prelink = T.cast('bool', kwargs.get('prelink', False)) + kwargs: StaticLibraryKeywordArguments): + self.prelink = kwargs.get('prelink', False) + self.rust_crate_type = kwargs.get('rust_crate_type', 'rlib') super().__init__(name, subdir, subproject, for_machine, sources, structured_sources, objects, environment, compilers, kwargs) + self.pic = self._extract_pic_pie(kwargs, 'pic', 'b_staticpic') + if not self.pic: + self.pie = self._extract_pic_pie(kwargs, 'pie', 'b_pie') def post_init(self) -> None: super().post_init() @@ -2174,11 +2327,30 @@ class StaticLibrary(BuildTarget): # and thus, machine_info kernel should be set to 'none'. # In that case, native_static_libs list is empty. rustc = self.compilers['rust'] - d = dependencies.InternalDependency('undefined', [], [], - rustc.native_static_libs, + link_args = ['-L' + rustc.get_target_libdir() + '/self-contained'] + link_args += rustc.native_static_libs + d = dependencies.InternalDependency('undefined', [], [], link_args, [], [], [], [], [], {}, [], [], [], '_rust_native_static_libs') self.external_deps.append(d) + + default_prefix, default_suffix = self.determine_default_prefix_and_suffix() + if not self.name_prefix_set: + self.prefix = default_prefix + if not self.name_suffix_set: + self.suffix = default_suffix + self.filename = self.prefix + self.name + '.' + self.suffix + self.outputs[0] = self.filename + + def determine_default_prefix_and_suffix(self) -> T.Tuple[str, str]: + scheme = self.environment.coredata.get_option_for_target(self, 'namingscheme') + assert isinstance(scheme, str), 'for mypy' + if scheme == 'platform': + schemename = self.get_platform_scheme_name() + prefix, suffix = DEFAULT_STATIC_LIBRARY_NAMES[schemename] + else: + prefix = '' + suffix = '' # By default a static library is named libfoo.a even on Windows because # MSVC does not have a consistent convention for what static libraries # are called. The MSVC CRT uses libfoo.lib syntax but nothing else uses @@ -2190,21 +2362,25 @@ class StaticLibrary(BuildTarget): # See our FAQ for more detailed rationale: # https://mesonbuild.com/FAQ.html#why-does-building-my-project-with-msvc-output-static-libraries-called-libfooa if not hasattr(self, 'prefix'): - self.prefix = 'lib' + prefix = 'lib' if not hasattr(self, 'suffix'): if self.uses_rust(): if self.rust_crate_type == 'rlib': # default Rust static library suffix - self.suffix = 'rlib' + suffix = 'rlib' elif self.rust_crate_type == 'staticlib': - self.suffix = 'a' + suffix = 'a' + elif self.environment.machines[self.for_machine].is_os2() and self.environment.coredata.optstore.get_value_for(OptionKey('os2_emxomf')): + suffix = 'lib' else: - if 'c' in self.compilers and self.compilers['c'].get_id() == 'tasking': - self.suffix = 'ma' if self.options.get_value('b_lto') and not self.prelink else 'a' - else: - self.suffix = 'a' - self.filename = self.prefix + self.name + '.' + self.suffix - self.outputs[0] = self.filename + suffix = 'a' + if 'c' in self.compilers and self.compilers['c'].get_id() == 'tasking' and not self.prelink: + key = OptionKey('b_lto', self.subproject, self.for_machine) + v = self.environment.coredata.get_option_for_target(self, key) + assert isinstance(v, bool), 'for mypy' + if v: + suffix = 'ma' + return (prefix, suffix) def get_link_deps_mapping(self, prefix: str) -> T.Mapping[str, str]: return {} @@ -2213,26 +2389,9 @@ class StaticLibrary(BuildTarget): return self.environment.get_static_lib_dir(), '{libdir_static}' def type_suffix(self): - return "@sta" + return "@rlib" if self.uses_rust_abi() else "@sta" - def process_kwargs(self, kwargs): - super().process_kwargs(kwargs) - - rust_abi = kwargs.get('rust_abi') - rust_crate_type = kwargs.get('rust_crate_type') - if rust_crate_type: - if rust_abi: - raise InvalidArguments('rust_abi and rust_crate_type are mutually exclusive.') - if rust_crate_type == 'lib': - self.rust_crate_type = 'rlib' - elif rust_crate_type in {'rlib', 'staticlib'}: - self.rust_crate_type = rust_crate_type - else: - raise InvalidArguments(f'Crate type {rust_crate_type!r} invalid for static libraries; must be "rlib" or "staticlib"') - else: - self.rust_crate_type = 'staticlib' if rust_abi == 'c' else 'rlib' - - def is_linkable_target(self): + def is_linkable_target(self) -> bool: return True def is_internal(self) -> bool: @@ -2267,7 +2426,7 @@ class SharedLibrary(BuildTarget): sources: T.List['SourceOutputs'], structured_sources: T.Optional[StructuredSources], objects: T.List[ObjectTypes], - environment: environment.Environment, + environment: Environment, compilers: T.Dict[str, 'Compiler'], kwargs): self.soversion: T.Optional[str] = None @@ -2275,12 +2434,14 @@ class SharedLibrary(BuildTarget): # Max length 2, first element is compatibility_version, second is current_version self.darwin_versions: T.Optional[T.Tuple[str, str]] = None self.vs_module_defs = None + self.shortname: T.Optional[str] = None # The import library this target will generate self.import_filename = None # The debugging information file this target will generate self.debug_filename = None # Use by the pkgconfig module self.shared_library_only = False + self.rust_crate_type = kwargs.get('rust_crate_type', 'dylib') super().__init__(name, subdir, subproject, for_machine, sources, structured_sources, objects, environment, compilers, kwargs) @@ -2315,23 +2476,18 @@ class SharedLibrary(BuildTarget): def get_default_install_dir(self) -> T.Union[T.Tuple[str, str], T.Tuple[None, None]]: return self.environment.get_shared_lib_dir(), '{libdir_shared}' - def determine_filenames(self): - """ - See https://github.com/mesonbuild/meson/pull/417 for details. - - First we determine the filename template (self.filename_tpl), then we - set the output filename (self.filename). - - The template is needed while creating aliases (self.get_aliases), - which are needed while generating .so shared libraries for Linux. - - Besides this, there's also the import library name (self.import_filename), - which is only used on Windows since on that platform the linker uses a - separate library called the "import library" during linking instead of - the shared library (DLL). - """ - prefix = '' - suffix = '' + def determine_naming_info(self) -> T.Tuple[str, str, str, str, bool]: + scheme = self.environment.coredata.get_option_for_target(self, 'namingscheme') + assert isinstance(scheme, str), 'for mypy' + if scheme == 'platform': + schemename = self.get_platform_scheme_name() + prefix, suffix, import_suffix = DEFAULT_SHARED_LIBRARY_NAMES[schemename] + else: + prefix = None + suffix = None + import_suffix = None + filename_tpl = self.basic_filename_tpl + create_debug_file = False create_debug_file = False self.filename_tpl = self.basic_filename_tpl import_filename_tpl = None @@ -2340,82 +2496,125 @@ class SharedLibrary(BuildTarget): if 'cs' in self.compilers: prefix = '' suffix = 'dll' - self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' + filename_tpl = '{0.prefix}{0.name}.{0.suffix}' create_debug_file = True # C, C++, Swift, Vala - # Only Windows uses a separate import library for linking + # Only Windows and OS/2 uses a separate import library for linking # For all other targets/platforms import_filename stays None elif self.environment.machines[self.for_machine].is_windows(): - suffix = 'dll' + suffix = suffix if suffix is not None else 'dll' if self.uses_rust(): # Shared library is of the form foo.dll - prefix = '' + prefix = prefix if prefix is not None else '' # Import library is called foo.dll.lib import_filename_tpl = '{0.prefix}{0.name}.dll.lib' # .pdb file is only created when debug symbols are enabled create_debug_file = self.environment.coredata.optstore.get_value_for(OptionKey("debug")) elif self.get_using_msvc(): # Shared library is of the form foo.dll - prefix = '' + prefix = prefix if prefix is not None else '' # Import library is called foo.lib - import_filename_tpl = '{0.prefix}{0.name}.lib' + import_suffix = import_suffix if import_suffix is not None else 'lib' + import_filename_tpl = '{0.prefix}{0.name}.' + import_suffix # .pdb file is only created when debug symbols are enabled create_debug_file = self.environment.coredata.optstore.get_value_for(OptionKey("debug")) # Assume GCC-compatible naming else: # Shared library is of the form libfoo.dll - prefix = 'lib' + prefix = prefix if prefix is not None else 'lib' # Import library is called libfoo.dll.a - import_filename_tpl = '{0.prefix}{0.name}.dll.a' + import_suffix = import_suffix if import_suffix is not None else '.dll.a' + import_filename_tpl = '{0.prefix}{0.name}' + import_suffix # Shared library has the soversion if it is defined if self.soversion: - self.filename_tpl = '{0.prefix}{0.name}-{0.soversion}.{0.suffix}' + filename_tpl = '{0.prefix}{0.name}-{0.soversion}.{0.suffix}' else: - self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' + filename_tpl = '{0.prefix}{0.name}.{0.suffix}' elif self.environment.machines[self.for_machine].is_cygwin(): suffix = 'dll' # Shared library is of the form cygfoo.dll # (ld --dll-search-prefix=cyg is the default) prefix = 'cyg' # Import library is called libfoo.dll.a + import_suffix = import_suffix if import_suffix is not None else '.dll.a' import_prefix = self.prefix if self.prefix is not None else 'lib' - import_filename_tpl = import_prefix + '{0.name}.dll.a' + import_filename_tpl = import_prefix + '{0.name}' + import_suffix if self.soversion: - self.filename_tpl = '{0.prefix}{0.name}-{0.soversion}.{0.suffix}' + filename_tpl = '{0.prefix}{0.name}-{0.soversion}.{0.suffix}' else: - self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' + filename_tpl = '{0.prefix}{0.name}.{0.suffix}' elif self.environment.machines[self.for_machine].is_darwin(): - prefix = 'lib' - suffix = 'dylib' + prefix = prefix if prefix is not None else 'lib' + suffix = suffix if suffix is not None else 'dylib' # On macOS, the filename can only contain the major version if self.soversion: # libfoo.X.dylib - self.filename_tpl = '{0.prefix}{0.name}.{0.soversion}.{0.suffix}' + filename_tpl = '{0.prefix}{0.name}.{0.soversion}.{0.suffix}' else: # libfoo.dylib - self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' + filename_tpl = '{0.prefix}{0.name}.{0.suffix}' elif self.environment.machines[self.for_machine].is_android(): - prefix = 'lib' - suffix = 'so' + prefix = prefix if prefix is not None else 'lib' + suffix = suffix if suffix is not None else 'so' # Android doesn't support shared_library versioning - self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' + filename_tpl = '{0.prefix}{0.name}.{0.suffix}' + elif self.environment.machines[self.for_machine].is_os2(): + # Shared library is of the form foo.dll + prefix = prefix if prefix is not None else '' + suffix = suffix if suffix is not None else 'dll' + # Import library is called foo_dll.a or foo_dll.lib + if import_suffix is None: + import_suffix = '_dll.lib' if self.environment.coredata.optstore.get_value_for(OptionKey('os2_emxomf')) else '_dll.a' + import_filename_tpl = '{0.prefix}{0.name}' + import_suffix + filename_tpl = '{0.shortname}' if self.shortname else '{0.prefix}{0.name}' + if self.soversion: + # fooX.dll + filename_tpl += '{0.soversion}' + filename_tpl += '.{0.suffix}' else: - prefix = 'lib' - suffix = 'so' + prefix = prefix if prefix is not None else 'lib' + suffix = suffix if suffix is not None else 'so' if self.ltversion: # libfoo.so.X[.Y[.Z]] (.Y and .Z are optional) - self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}.{0.ltversion}' + filename_tpl = '{0.prefix}{0.name}.{0.suffix}.{0.ltversion}' elif self.soversion: # libfoo.so.X - self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}.{0.soversion}' + filename_tpl = '{0.prefix}{0.name}.{0.suffix}.{0.soversion}' else: # No versioning, libfoo.so - self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' + filename_tpl = '{0.prefix}{0.name}.{0.suffix}' + return (prefix, suffix, filename_tpl, import_filename_tpl, create_debug_file) + + def determine_filenames(self): + """ + See https://github.com/mesonbuild/meson/pull/417 for details. + + First we determine the filename template (self.filename_tpl), then we + set the output filename (self.filename). + + The template is needed while creating aliases (self.get_aliases), + which are needed while generating .so shared libraries for Linux. + + Besides this, there's also the import library name (self.import_filename), + which is only used on Windows and OS/2 since on that platform the linker uses a + separate library called the "import library" during linking instead of + the shared library (DLL). + """ + prefix, suffix, filename_tpl, import_filename_tpl, create_debug_file = self.determine_naming_info() if self.prefix is None: self.prefix = prefix if self.suffix is None: self.suffix = suffix + self.filename_tpl = filename_tpl self.filename = self.filename_tpl.format(self) + if self.environment.machines[self.for_machine].is_os2(): + # OS/2 does not allow a longer DLL name than 8 chars + name = os.path.splitext(self.filename)[0] + if len(name) > 8: + name = name[:8] + if self.soversion: + name = name[:-len(self.soversion)] + self.soversion + self.filename = '{}.{}'.format(name, self.suffix) if import_filename_tpl: self.import_filename = import_filename_tpl.format(self) # There may have been more outputs added by the time we get here, so @@ -2424,20 +2623,20 @@ class SharedLibrary(BuildTarget): if create_debug_file: self.debug_filename = os.path.splitext(self.filename)[0] + '.pdb' - def process_kwargs(self, kwargs): + def process_kwargs(self, kwargs: SharedLibraryKeywordArguments) -> None: super().process_kwargs(kwargs) if not self.environment.machines[self.for_machine].is_android(): # Shared library version - self.ltversion = T.cast('T.Optional[str]', kwargs.get('version')) - self.soversion = T.cast('T.Optional[str]', kwargs.get('soversion')) + self.ltversion = kwargs.get('version') + self.soversion = kwargs.get('soversion') if self.soversion is None and self.ltversion is not None: # library version is defined, get the soversion from that # We replicate what Autotools does here and take the first # number of the version by default. self.soversion = self.ltversion.split('.')[0] # macOS, iOS and tvOS dylib compatibility_version and current_version - self.darwin_versions = T.cast('T.Optional[T.Tuple[str, str]]', kwargs.get('darwin_versions')) + self.darwin_versions = kwargs.get('darwin_versions') if self.darwin_versions is None and self.soversion is not None: # If unspecified, pick the soversion self.darwin_versions = (self.soversion, self.soversion) @@ -2445,19 +2644,8 @@ class SharedLibrary(BuildTarget): # Visual Studio module-definitions file self.process_vs_module_defs_kw(kwargs) - rust_abi = kwargs.get('rust_abi') - rust_crate_type = kwargs.get('rust_crate_type') - if rust_crate_type: - if rust_abi: - raise InvalidArguments('rust_abi and rust_crate_type are mutually exclusive.') - if rust_crate_type == 'lib': - self.rust_crate_type = 'dylib' - elif rust_crate_type in {'dylib', 'cdylib', 'proc-macro'}: - self.rust_crate_type = rust_crate_type - else: - raise InvalidArguments(f'Crate type {rust_crate_type!r} invalid for shared libraries; must be "dylib", "cdylib" or "proc-macro"') - else: - self.rust_crate_type = 'cdylib' if rust_abi == 'c' else 'dylib' + # OS/2 uses a 8.3 name for a DLL + self.shortname = kwargs.get('shortname') def get_import_filename(self) -> T.Optional[str]: """ @@ -2510,7 +2698,7 @@ class SharedLibrary(BuildTarget): def type_suffix(self): return "@sha" - def is_linkable_target(self): + def is_linkable_target(self) -> bool: return True def set_static(self, static_library: StaticLibrary) -> None: @@ -2544,7 +2732,7 @@ class SharedModule(SharedLibrary): sources: T.List['SourceOutputs'], structured_sources: T.Optional[StructuredSources], objects: T.List[ObjectTypes], - environment: environment.Environment, + environment: Environment, compilers: T.Dict[str, 'Compiler'], kwargs): if 'version' in kwargs: @@ -2594,7 +2782,7 @@ class CommandBase: subproject: str def flatten_command(self, cmd: T.Sequence[T.Union[str, File, programs.ExternalProgram, BuildTargetTypes]]) -> \ - T.List[T.Union[str, File, BuildTarget, 'CustomTarget']]: + T.List[T.Union[str, File, BuildTarget, CustomTarget, programs.ExternalProgram]]: cmd = listify(cmd) final_cmd: T.List[T.Union[str, File, BuildTarget, 'CustomTarget']] = [] for c in cmd: @@ -2611,7 +2799,8 @@ class CommandBase: # Can only add a dependency on an external program which we # know the absolute path of self.depend_files.append(File.from_absolute_file(path)) - final_cmd += c.get_command() + # Do NOT flatten -- it is needed for later parsing + final_cmd.append(c) elif isinstance(c, (BuildTarget, CustomTarget)): self.dependencies.append(c) final_cmd.append(c) @@ -2659,7 +2848,7 @@ class CustomTarget(Target, CustomTargetBase, CommandBase): name: T.Optional[str], subdir: str, subproject: str, - environment: environment.Environment, + environment: Environment, command: T.Sequence[T.Union[ str, BuildTargetTypes, GeneratedList, programs.ExternalProgram, File]], @@ -2681,13 +2870,15 @@ class CustomTarget(Target, CustomTargetBase, CommandBase): install_dir: T.Optional[T.List[T.Union[str, Literal[False]]]] = None, install_mode: T.Optional[FileMode] = None, install_tag: T.Optional[T.List[T.Optional[str]]] = None, + rspable: bool = False, absolute_paths: bool = False, backend: T.Optional['Backend'] = None, description: str = 'Generating {} with a custom command', + build_subdir: str = '', ): # TODO expose keyword arg to make MachineChoice.HOST configurable super().__init__(name, subdir, subproject, False, MachineChoice.HOST, environment, - install, build_always_stale) + install, build_always_stale, build_subdir = build_subdir) self.sources = list(sources) self.outputs = substitute_values( outputs, get_filenames_templates_dict( @@ -2713,6 +2904,9 @@ class CustomTarget(Target, CustomTargetBase, CommandBase): # Whether to use absolute paths for all files on the commandline self.absolute_paths = absolute_paths + # Whether to enable using response files for the underlying tool + self.rspable = rspable + def get_default_install_dir(self) -> T.Union[T.Tuple[str, str], T.Tuple[None, None]]: return None, None @@ -2866,7 +3060,7 @@ class CompileTarget(BuildTarget): name: str, subdir: str, subproject: str, - environment: environment.Environment, + environment: Environment, sources: T.List['SourceOutputs'], output_templ: str, compiler: Compiler, @@ -2874,7 +3068,7 @@ class CompileTarget(BuildTarget): compile_args: T.List[str], include_directories: T.List[IncludeDirs], dependencies: T.List[dependencies.Dependency], - depends: T.List[T.Union[BuildTarget, CustomTarget, CustomTargetIndex]]): + depends: T.List[BuildTargetTypes]): compilers = {compiler.get_language(): compiler} kwargs = { 'build_by_default': False, @@ -2919,10 +3113,10 @@ class RunTarget(Target, CommandBase): def __init__(self, name: str, command: T.Sequence[T.Union[str, File, BuildTargetTypes, programs.ExternalProgram]], - dependencies: T.Sequence[T.Union[Target, CustomTargetIndex]], + dependencies: T.Sequence[AnyTargetType], subdir: str, subproject: str, - environment: environment.Environment, + environment: Environment, env: T.Optional[EnvironmentVariables] = None, default_env: bool = True): # These don't produce output artifacts @@ -2938,7 +3132,7 @@ class RunTarget(Target, CommandBase): repr_str = "<{0} {1}: {2}>" return repr_str.format(self.__class__.__name__, self.get_id(), self.command[0]) - def get_dependencies(self) -> T.List[T.Union[BuildTarget, CustomTarget, CustomTargetIndex]]: + def get_dependencies(self) -> T.List[BuildTargetTypes]: return self.dependencies def get_generated_sources(self) -> T.List[GeneratedTypes]: @@ -2969,7 +3163,7 @@ class AliasTarget(RunTarget): typename = 'alias' def __init__(self, name: str, dependencies: T.Sequence[Target], - subdir: str, subproject: str, environment: environment.Environment): + subdir: str, subproject: str, environment: Environment): super().__init__(name, [], dependencies, subdir, subproject, environment) def __repr__(self): @@ -2980,10 +3174,11 @@ class Jar(BuildTarget): known_kwargs = known_jar_kwargs typename = 'jar' + rust_crate_type = '' # type: ignore[assignment] def __init__(self, name: str, subdir: str, subproject: str, for_machine: MachineChoice, sources: T.List[SourceOutputs], structured_sources: T.Optional['StructuredSources'], - objects, environment: environment.Environment, compilers: T.Dict[str, 'Compiler'], + objects, environment: Environment, compilers: T.Dict[str, 'Compiler'], kwargs): super().__init__(name, subdir, subproject, for_machine, sources, structured_sources, objects, environment, compilers, kwargs) @@ -3001,10 +3196,10 @@ class Jar(BuildTarget): self.main_class = kwargs.get('main_class', '') self.java_resources: T.Optional[StructuredSources] = kwargs.get('java_resources', None) - def get_main_class(self): + def get_main_class(self) -> str: return self.main_class - def type_suffix(self): + def type_suffix(self) -> str: return "@jar" def get_java_args(self): @@ -3020,7 +3215,7 @@ class Jar(BuildTarget): def is_linkable_target(self): return True - def get_classpath_args(self): + def get_classpath_args(self) -> T.List[str]: cp_paths = [os.path.join(l.get_subdir(), l.get_filename()) for l in self.link_targets] cp_string = os.pathsep.join(cp_paths) if cp_string: @@ -3051,6 +3246,14 @@ class CustomTargetIndex(CustomTargetBase, HoldableObject): def name(self) -> str: return f'{self.target.name}[{self.output}]' + @property + def depend_files(self) -> T.List[File]: + return self.target.depend_files + + @property + def subdir(self) -> str: + return self.target.subdir + def __repr__(self): return '<CustomTargetIndex: {!r}[{}]>'.format(self.target, self.output) @@ -3060,6 +3263,12 @@ class CustomTargetIndex(CustomTargetBase, HoldableObject): def get_subdir(self) -> str: return self.target.get_subdir() + def get_build_subdir(self) -> str: + return self.target.get_build_subdir() + + def get_builddir(self) -> str: + return self.target.get_builddir() + def get_filename(self) -> str: return self.output @@ -3104,6 +3313,10 @@ class CustomTargetIndex(CustomTargetBase, HoldableObject): def get_custom_install_dir(self) -> T.List[T.Union[str, Literal[False]]]: return self.target.get_custom_install_dir() + def get_basename(self) -> str: + return self.target.get_basename() + + class ConfigurationData(HoldableObject): def __init__(self, initial_values: T.Optional[T.Union[ T.Dict[str, T.Tuple[T.Union[str, int, bool], T.Optional[str]]], @@ -3129,6 +3342,18 @@ class ConfigurationData(HoldableObject): def keys(self) -> T.Iterator[str]: return self.values.keys() +class OverrideExecutable(Executable): + def __init__(self, executable: Executable, version: str): + self._executable = executable + self._version = version + + def __getattr__(self, name: str) -> T.Any: + _executable = object.__getattribute__(self, '_executable') + return getattr(_executable, name) + + def get_version(self, interpreter: T.Optional[Interpreter] = None) -> str: + return self._version + # A bit poorly named, but this represents plain data files to copy # during install. @dataclass(eq=False) diff --git a/mesonbuild/cargo/__init__.py b/mesonbuild/cargo/__init__.py index 0a4d5f2..c5b157f 100644 --- a/mesonbuild/cargo/__init__.py +++ b/mesonbuild/cargo/__init__.py @@ -1,6 +1,7 @@ __all__ = [ 'Interpreter', - 'load_wraps', + 'TomlImplementationMissing', ] -from .interpreter import Interpreter, load_wraps +from .interpreter import Interpreter +from .toml import TomlImplementationMissing diff --git a/mesonbuild/cargo/builder.py b/mesonbuild/cargo/builder.py index 112c7c5..16abe26 100644 --- a/mesonbuild/cargo/builder.py +++ b/mesonbuild/cargo/builder.py @@ -44,7 +44,7 @@ class Builder: :param value: the value of the string :return: A StringNode """ - return mparser.StringNode(self._token('string', value)) + return mparser.StringNode(self._token('string', value), escape=False) def number(self, value: int) -> mparser.NumberNode: """Build A NumberNode @@ -162,7 +162,7 @@ class Builder: :param rhs: the right hand side of the "not in" :return: A comparison node """ - return mparser.ComparisonNode('notin', lhs, self._symbol('not in'), rhs) + return mparser.ComparisonNode('not in', lhs, self._symbol('not in'), rhs) def or_(self, lhs: mparser.BaseNode, rhs: mparser.BaseNode) -> mparser.OrNode: """Create and OrNode @@ -202,7 +202,7 @@ class Builder: :param rhs: The right of the addition :return: The ArithmeticNode """ - return mparser.ArithmeticNode('add', lhs, self._symbol('+'), rhs) + return mparser.ArithmeticNode('+', lhs, self._symbol('+'), rhs) def plusassign(self, value: mparser.BaseNode, varname: str) -> mparser.PlusAssignmentNode: """Create a "+=" node diff --git a/mesonbuild/cargo/cfg.py b/mesonbuild/cargo/cfg.py index 0d49527..a0ee6e2 100644 --- a/mesonbuild/cargo/cfg.py +++ b/mesonbuild/cargo/cfg.py @@ -4,6 +4,7 @@ """Rust CFG parser. Rust uses its `cfg()` format in cargo. +https://doc.rust-lang.org/reference/conditional-compilation.html This may have the following functions: - all() @@ -22,18 +23,15 @@ so you could have examples like: from __future__ import annotations import dataclasses import enum -import functools import typing as T -from . import builder -from .. import mparser from ..mesonlib import MesonBugException if T.TYPE_CHECKING: _T = T.TypeVar('_T') _LEX_TOKEN = T.Tuple['TokenType', T.Optional[str]] - _LEX_STREAM = T.Iterable[_LEX_TOKEN] + _LEX_STREAM = T.Iterator[_LEX_TOKEN] _LEX_STREAM_AH = T.Iterator[T.Tuple[_LEX_TOKEN, T.Optional[_LEX_TOKEN]]] @@ -48,6 +46,7 @@ class TokenType(enum.Enum): NOT = enum.auto() COMMA = enum.auto() EQUAL = enum.auto() + CFG = enum.auto() def lexer(raw: str) -> _LEX_STREAM: @@ -56,45 +55,41 @@ def lexer(raw: str) -> _LEX_STREAM: :param raw: The raw cfg() expression :return: An iterable of tokens """ - buffer: T.List[str] = [] + start: int = 0 is_string: bool = False - for s in raw: - if s.isspace() or s in {')', '(', ',', '='} or (s == '"' and buffer): - val = ''.join(buffer) - buffer.clear() - if is_string: + for i, s in enumerate(raw): + if s.isspace() or s in {')', '(', ',', '=', '"'}: + val = raw[start:i] + start = i + 1 + if s == '"' and is_string: yield (TokenType.STRING, val) + is_string = False + continue elif val == 'any': yield (TokenType.ANY, None) elif val == 'all': yield (TokenType.ALL, None) elif val == 'not': yield (TokenType.NOT, None) + elif val == 'cfg': + yield (TokenType.CFG, None) elif val: yield (TokenType.IDENTIFIER, val) if s == '(': yield (TokenType.LPAREN, None) - continue elif s == ')': yield (TokenType.RPAREN, None) - continue elif s == ',': yield (TokenType.COMMA, None) - continue elif s == '=': yield (TokenType.EQUAL, None) - continue - elif s.isspace(): - continue - - if s == '"': - is_string = not is_string - else: - buffer.append(s) - if buffer: + elif s == '"': + is_string = True + val = raw[start:] + if val: # This should always be an identifier - yield (TokenType.IDENTIFIER, ''.join(buffer)) + yield (TokenType.IDENTIFIER, val) def lookahead(iter: T.Iterator[_T]) -> T.Iterator[T.Tuple[_T, T.Optional[_T]]]: @@ -146,8 +141,8 @@ class Identifier(IR): @dataclasses.dataclass class Equal(IR): - lhs: IR - rhs: IR + lhs: Identifier + rhs: String @dataclasses.dataclass @@ -175,41 +170,40 @@ def _parse(ast: _LEX_STREAM_AH) -> IR: else: ntoken, _ = (None, None) - stream: T.List[_LEX_TOKEN] if token is TokenType.IDENTIFIER: + assert value + id_ = Identifier(value) if ntoken is TokenType.EQUAL: - return Equal(Identifier(value), _parse(ast)) - if token is TokenType.STRING: - return String(value) - if token is TokenType.EQUAL: - # In this case the previous caller already has handled the equal - return _parse(ast) - if token in {TokenType.ANY, TokenType.ALL}: + next(ast) + (token, value), _ = next(ast) + assert token is TokenType.STRING + assert value is not None + return Equal(id_, String(value)) + return id_ + elif token in {TokenType.ANY, TokenType.ALL}: type_ = All if token is TokenType.ALL else Any - assert ntoken is TokenType.LPAREN - next(ast) # advance the iterator to get rid of the LPAREN - stream = [] args: T.List[IR] = [] - while token is not TokenType.RPAREN: + (token, value), n_stream = next(ast) + assert token is TokenType.LPAREN + if n_stream and n_stream[0] == TokenType.RPAREN: + return type_(args) + while True: + args.append(_parse(ast)) (token, value), _ = next(ast) - if token is TokenType.COMMA: - args.append(_parse(lookahead(iter(stream)))) - stream.clear() - else: - stream.append((token, value)) - if stream: - args.append(_parse(lookahead(iter(stream)))) + if token is TokenType.RPAREN: + break + assert token is TokenType.COMMA return type_(args) - if token is TokenType.NOT: - next(ast) # advance the iterator to get rid of the LPAREN - stream = [] - # Mypy can't figure out that token is overridden inside the while loop - while token is not TokenType.RPAREN: # type: ignore - (token, value), _ = next(ast) - stream.append((token, value)) - return Not(_parse(lookahead(iter(stream)))) - - raise MesonBugException(f'Unhandled Cargo token: {token}') + elif token in {TokenType.NOT, TokenType.CFG}: + is_not = token is TokenType.NOT + (token, value), _ = next(ast) + assert token is TokenType.LPAREN + arg = _parse(ast) + (token, value), _ = next(ast) + assert token is TokenType.RPAREN + return Not(arg) if is_not else arg + else: + raise MesonBugException(f'Unhandled Cargo token:{token} {value}') def parse(ast: _LEX_STREAM) -> IR: @@ -218,57 +212,24 @@ def parse(ast: _LEX_STREAM) -> IR: :param ast: An iterable of Tokens :return: An mparser Node to be used as a conditional """ - ast_i: _LEX_STREAM_AH = lookahead(iter(ast)) + ast_i: _LEX_STREAM_AH = lookahead(ast) return _parse(ast_i) -@functools.singledispatch -def ir_to_meson(ir: T.Any, build: builder.Builder) -> mparser.BaseNode: - raise NotImplementedError - - -@ir_to_meson.register -def _(ir: String, build: builder.Builder) -> mparser.BaseNode: - return build.string(ir.value) - - -@ir_to_meson.register -def _(ir: Identifier, build: builder.Builder) -> mparser.BaseNode: - host_machine = build.identifier('host_machine') - if ir.value == "target_arch": - return build.method('cpu_family', host_machine) - elif ir.value in {"target_os", "target_family"}: - return build.method('system', host_machine) - elif ir.value == "target_endian": - return build.method('endian', host_machine) - raise MesonBugException(f"Unhandled Cargo identifier: {ir.value}") - - -@ir_to_meson.register -def _(ir: Equal, build: builder.Builder) -> mparser.BaseNode: - return build.equal(ir_to_meson(ir.lhs, build), ir_to_meson(ir.rhs, build)) - - -@ir_to_meson.register -def _(ir: Not, build: builder.Builder) -> mparser.BaseNode: - return build.not_(ir_to_meson(ir.value, build)) - - -@ir_to_meson.register -def _(ir: Any, build: builder.Builder) -> mparser.BaseNode: - args = iter(reversed(ir.args)) - last = next(args) - cur = build.or_(ir_to_meson(next(args), build), ir_to_meson(last, build)) - for a in args: - cur = build.or_(ir_to_meson(a, build), cur) - return cur +def _eval_cfg(ir: IR, cfgs: T.Dict[str, str]) -> bool: + if isinstance(ir, Identifier): + return ir.value in cfgs + elif isinstance(ir, Equal): + return cfgs.get(ir.lhs.value) == ir.rhs.value + elif isinstance(ir, Not): + return not _eval_cfg(ir.value, cfgs) + elif isinstance(ir, Any): + return any(_eval_cfg(i, cfgs) for i in ir.args) + elif isinstance(ir, All): + return all(_eval_cfg(i, cfgs) for i in ir.args) + else: + raise MesonBugException(f'Unhandled Cargo cfg IR: {ir}') -@ir_to_meson.register -def _(ir: All, build: builder.Builder) -> mparser.BaseNode: - args = iter(reversed(ir.args)) - last = next(args) - cur = build.and_(ir_to_meson(next(args), build), ir_to_meson(last, build)) - for a in args: - cur = build.and_(ir_to_meson(a, build), cur) - return cur +def eval_cfg(raw: str, cfgs: T.Dict[str, str]) -> bool: + return _eval_cfg(parse(lexer(raw)), cfgs) diff --git a/mesonbuild/cargo/interpreter.py b/mesonbuild/cargo/interpreter.py index af272a8..e262986 100644 --- a/mesonbuild/cargo/interpreter.py +++ b/mesonbuild/cargo/interpreter.py @@ -11,460 +11,175 @@ port will be required. from __future__ import annotations import dataclasses -import importlib -import json +import functools import os -import shutil +import pathlib import collections import urllib.parse -import itertools import typing as T +from pathlib import PurePath -from . import builder -from . import version -from ..mesonlib import MesonException, Popen_safe +from . import builder, version +from .cfg import eval_cfg +from .toml import load_toml +from .manifest import Manifest, CargoLock, CargoLockPackage, Workspace, fixup_meson_varname +from ..mesonlib import is_parent_path, MesonException, MachineChoice, version_compare from .. import coredata, mlog from ..wrap.wrap import PackageDefinition if T.TYPE_CHECKING: - from types import ModuleType - - from typing_extensions import Protocol, Self - - from . import manifest + from . import raw from .. import mparser + from .manifest import Dependency, SystemDependency from ..environment import Environment from ..interpreterbase import SubProject + from ..compilers.rust import RustCompiler - # Copied from typeshed. Blarg that they don't expose this - class DataclassInstance(Protocol): - __dataclass_fields__: T.ClassVar[dict[str, dataclasses.Field[T.Any]]] - - _UnknownKeysT = T.TypeVar('_UnknownKeysT', manifest.FixedPackage, - manifest.FixedDependency, manifest.FixedLibTarget, - manifest.FixedBuildTarget) - - -# tomllib is present in python 3.11, before that it is a pypi module called tomli, -# we try to import tomllib, then tomli, -# TODO: add a fallback to toml2json? -tomllib: T.Optional[ModuleType] = None -toml2json: T.Optional[str] = None -for t in ['tomllib', 'tomli']: - try: - tomllib = importlib.import_module(t) - break - except ImportError: - pass -else: - # TODO: it would be better to use an Executable here, which could be looked - # up in the cross file or provided by a wrap. However, that will have to be - # passed in externally, since we don't have (and I don't think we should), - # have access to the `Environment` for that in this module. - toml2json = shutil.which('toml2json') - - -_EXTRA_KEYS_WARNING = ( - "This may (unlikely) be an error in the cargo manifest, or may be a missing " - "implementation in Meson. If this issue can be reproduced with the latest " - "version of Meson, please help us by opening an issue at " - "https://github.com/mesonbuild/meson/issues. Please include the crate and " - "version that is generating this warning if possible." -) - -class TomlImplementationMissing(MesonException): - pass - - -def load_toml(filename: str) -> T.Dict[object, object]: - if tomllib: - with open(filename, 'rb') as f: - raw = tomllib.load(f) - else: - if toml2json is None: - raise TomlImplementationMissing('Could not find an implementation of tomllib, nor toml2json') - - p, out, err = Popen_safe([toml2json, filename]) - if p.returncode != 0: - raise MesonException('toml2json failed to decode output\n', err) - - raw = json.loads(out) - - if not isinstance(raw, dict): - raise MesonException("Cargo.toml isn't a dictionary? How did that happen?") - - return raw - - -def fixup_meson_varname(name: str) -> str: - """Fixup a meson variable name - - :param name: The name to fix - :return: the fixed name - """ - return name.replace('-', '_') - - -# Pylance can figure out that these do not, in fact, overlap, but mypy can't -@T.overload -def _fixup_raw_mappings(d: manifest.BuildTarget) -> manifest.FixedBuildTarget: ... # type: ignore - -@T.overload -def _fixup_raw_mappings(d: manifest.LibTarget) -> manifest.FixedLibTarget: ... # type: ignore - -@T.overload -def _fixup_raw_mappings(d: manifest.Dependency) -> manifest.FixedDependency: ... - -def _fixup_raw_mappings(d: T.Union[manifest.BuildTarget, manifest.LibTarget, manifest.Dependency] - ) -> T.Union[manifest.FixedBuildTarget, manifest.FixedLibTarget, - manifest.FixedDependency]: - """Fixup raw cargo mappings to ones more suitable for python to consume. - - This does the following: - * replaces any `-` with `_`, cargo likes the former, but python dicts make - keys with `-` in them awkward to work with - * Convert Dependency versions from the cargo format to something meson - understands - - :param d: The mapping to fix - :return: the fixed string - """ - raw = {fixup_meson_varname(k): v for k, v in d.items()} - if 'version' in raw: - assert isinstance(raw['version'], str), 'for mypy' - raw['version'] = version.convert(raw['version']) - return T.cast('T.Union[manifest.FixedBuildTarget, manifest.FixedLibTarget, manifest.FixedDependency]', raw) - - -def _handle_unknown_keys(data: _UnknownKeysT, cls: T.Union[DataclassInstance, T.Type[DataclassInstance]], - msg: str) -> _UnknownKeysT: - """Remove and warn on keys that are coming from cargo, but are unknown to - our representations. - - This is intended to give users the possibility of things proceeding when a - new key is added to Cargo.toml that we don't yet handle, but to still warn - them that things might not work. - - :param data: The raw data to look at - :param cls: The Dataclass derived type that will be created - :param msg: the header for the error message. Usually something like "In N structure". - :return: The original data structure, but with all unknown keys removed. - """ - unexpected = set(data) - {x.name for x in dataclasses.fields(cls)} - if unexpected: - mlog.warning(msg, 'has unexpected keys', '"{}".'.format(', '.join(sorted(unexpected))), - _EXTRA_KEYS_WARNING) - for k in unexpected: - # Mypy and Pyright can't prove that this is okay - del data[k] # type: ignore[misc] - return data - - -@dataclasses.dataclass -class Package: - - """Representation of a Cargo Package entry, with defaults filled in.""" - - name: str - version: str - description: T.Optional[str] = None - resolver: T.Optional[str] = None - authors: T.List[str] = dataclasses.field(default_factory=list) - edition: manifest.EDITION = '2015' - rust_version: T.Optional[str] = None - documentation: T.Optional[str] = None - readme: T.Optional[str] = None - homepage: T.Optional[str] = None - repository: T.Optional[str] = None - license: T.Optional[str] = None - license_file: T.Optional[str] = None - keywords: T.List[str] = dataclasses.field(default_factory=list) - categories: T.List[str] = dataclasses.field(default_factory=list) - workspace: T.Optional[str] = None - build: T.Optional[str] = None - links: T.Optional[str] = None - exclude: T.List[str] = dataclasses.field(default_factory=list) - include: T.List[str] = dataclasses.field(default_factory=list) - publish: bool = True - metadata: T.Dict[str, T.Any] = dataclasses.field(default_factory=dict) - default_run: T.Optional[str] = None - autolib: bool = True - autobins: bool = True - autoexamples: bool = True - autotests: bool = True - autobenches: bool = True - api: str = dataclasses.field(init=False) - - def __post_init__(self) -> None: - self.api = _version_to_api(self.version) - - @classmethod - def from_raw(cls, raw: manifest.Package) -> Self: - pkg = T.cast('manifest.FixedPackage', - {fixup_meson_varname(k): v for k, v in raw.items()}) - pkg = _handle_unknown_keys(pkg, cls, f'Package entry {pkg["name"]}') - return cls(**pkg) - -@dataclasses.dataclass -class SystemDependency: - - """ Representation of a Cargo system-deps entry - https://docs.rs/system-deps/latest/system_deps - """ - - name: str - version: T.List[str] - optional: bool = False - feature: T.Optional[str] = None - feature_overrides: T.Dict[str, T.Dict[str, str]] = dataclasses.field(default_factory=dict) - - @classmethod - def from_raw(cls, name: str, raw: T.Any) -> SystemDependency: - if isinstance(raw, str): - return cls(name, SystemDependency.convert_version(raw)) - name = raw.get('name', name) - version = SystemDependency.convert_version(raw.get('version')) - optional = raw.get('optional', False) - feature = raw.get('feature') - # Everything else are overrides when certain features are enabled. - feature_overrides = {k: v for k, v in raw.items() if k not in {'name', 'version', 'optional', 'feature'}} - return cls(name, version, optional, feature, feature_overrides) - - @staticmethod - def convert_version(version: T.Optional[str]) -> T.List[str]: - vers = version.split(',') if version is not None else [] - result: T.List[str] = [] - for v in vers: - v = v.strip() - if v[0] not in '><=': - v = f'>={v}' - result.append(v) - return result - - def enabled(self, features: T.Set[str]) -> bool: - return self.feature is None or self.feature in features - -@dataclasses.dataclass -class Dependency: - - """Representation of a Cargo Dependency Entry.""" - - name: dataclasses.InitVar[str] - version: T.List[str] - registry: T.Optional[str] = None - git: T.Optional[str] = None - branch: T.Optional[str] = None - rev: T.Optional[str] = None - path: T.Optional[str] = None - optional: bool = False - package: str = '' - default_features: bool = True - features: T.List[str] = dataclasses.field(default_factory=list) - api: str = dataclasses.field(init=False) - - def __post_init__(self, name: str) -> None: - self.package = self.package or name - # Extract wanted API version from version constraints. - api = set() - for v in self.version: - if v.startswith(('>=', '==')): - api.add(_version_to_api(v[2:].strip())) - elif v.startswith('='): - api.add(_version_to_api(v[1:].strip())) - if not api: - self.api = '0' - elif len(api) == 1: - self.api = api.pop() - else: - raise MesonException(f'Cannot determine minimum API version from {self.version}.') - - @classmethod - def from_raw(cls, name: str, raw: manifest.DependencyV) -> Dependency: - """Create a dependency from a raw cargo dictionary""" - if isinstance(raw, str): - return cls(name, version.convert(raw)) - fixed = _handle_unknown_keys(_fixup_raw_mappings(raw), cls, f'Dependency entry {name}') - return cls(name, **fixed) - - -@dataclasses.dataclass -class BuildTarget: - - name: str - crate_type: T.List[manifest.CRATE_TYPE] = dataclasses.field(default_factory=lambda: ['lib']) - path: dataclasses.InitVar[T.Optional[str]] = None - - # https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-test-field - # True for lib, bin, test - test: bool = True - - # https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-doctest-field - # True for lib - doctest: bool = False - - # https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-bench-field - # True for lib, bin, benchmark - bench: bool = True - - # https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-doc-field - # True for libraries and binaries - doc: bool = False - - harness: bool = True - edition: manifest.EDITION = '2015' - required_features: T.List[str] = dataclasses.field(default_factory=list) - plugin: bool = False - - @classmethod - def from_raw(cls, raw: manifest.BuildTarget) -> Self: - name = raw.get('name', '<anonymous>') - build = _handle_unknown_keys(_fixup_raw_mappings(raw), cls, f'Binary entry {name}') - return cls(**build) - -@dataclasses.dataclass -class Library(BuildTarget): + from typing_extensions import Literal - """Representation of a Cargo Library Entry.""" +def _dependency_name(package_name: str, api: str, suffix: str = '-rs') -> str: + basename = package_name[:-len(suffix)] if suffix and package_name.endswith(suffix) else package_name + return f'{basename}-{api}{suffix}' - doctest: bool = True - doc: bool = True - path: str = os.path.join('src', 'lib.rs') - proc_macro: bool = False - crate_type: T.List[manifest.CRATE_TYPE] = dataclasses.field(default_factory=lambda: ['lib']) - doc_scrape_examples: bool = True - @classmethod - def from_raw(cls, raw: manifest.LibTarget, fallback_name: str) -> Self: # type: ignore[override] - fixed = _fixup_raw_mappings(raw) +def _dependency_varname(dep: Dependency) -> str: + return f'{fixup_meson_varname(dep.package)}_{(dep.api.replace(".", "_"))}_dep' - # We need to set the name field if it's not set manually, including if - # other fields are set in the lib section - if 'name' not in fixed: - fixed['name'] = fallback_name - fixed = _handle_unknown_keys(fixed, cls, f'Library entry {fixed["name"]}') - return cls(**fixed) +def _library_name(name: str, api: str, lib_type: Literal['rust', 'c', 'proc-macro'] = 'rust') -> str: + # Add the API version to the library name to avoid conflicts when multiple + # versions of the same crate are used. The Ninja backend removed everything + # after the + to form the crate name. + if lib_type == 'c': + return name + return f'{name}+{api.replace(".", "_")}' -@dataclasses.dataclass -class Binary(BuildTarget): - - """Representation of a Cargo Bin Entry.""" - - doc: bool = True - - -@dataclasses.dataclass -class Test(BuildTarget): +def _extra_args_varname() -> str: + return 'extra_args' - """Representation of a Cargo Test Entry.""" - bench: bool = True +def _extra_deps_varname() -> str: + return 'extra_deps' @dataclasses.dataclass -class Benchmark(BuildTarget): - - """Representation of a Cargo Benchmark Entry.""" - - test: bool = True +class PackageConfiguration: + """Configuration for a package during dependency resolution.""" + features: T.Set[str] = dataclasses.field(default_factory=set) + required_deps: T.Set[str] = dataclasses.field(default_factory=set) + optional_deps_features: T.Dict[str, T.Set[str]] = dataclasses.field(default_factory=lambda: collections.defaultdict(set)) + # Cache of resolved dependency packages + dep_packages: T.Dict[PackageKey, PackageState] = dataclasses.field(default_factory=dict) + + def get_features_args(self) -> T.List[str]: + """Get feature configuration arguments.""" + args: T.List[str] = [] + for feature in self.features: + args.extend(['--cfg', f'feature="{feature}"']) + return args + + def get_dependency_map(self, manifest: Manifest) -> T.Dict[str, str]: + """Get the rust dependency mapping for this package configuration.""" + dependency_map: T.Dict[str, str] = {} + for name in self.required_deps: + dep = manifest.dependencies[name] + dep_key = PackageKey(dep.package, dep.api) + dep_pkg = self.dep_packages[dep_key] + dep_lib_name = _library_name(dep_pkg.manifest.lib.name, dep_pkg.manifest.package.api) + dep_crate_name = name if name != dep.package else dep_pkg.manifest.lib.name + dependency_map[dep_lib_name] = dep_crate_name + return dependency_map @dataclasses.dataclass -class Example(BuildTarget): +class PackageState: + manifest: Manifest + downloaded: bool = False + # If this package is member of a workspace. + ws_subdir: T.Optional[str] = None + ws_member: T.Optional[str] = None + # Package configuration state + cfg: T.Optional[PackageConfiguration] = None + + def get_env_dict(self, environment: Environment, subdir: str) -> T.Dict[str, str]: + """Get environment variables for this package.""" + # Common variables for build.rs and crates + # https://doc.rust-lang.org/cargo/reference/environment-variables.html + # OUT_DIR is the directory where build.rs generate files. In our case, + # it's the directory where meson/meson.build places generated files. + out_dir = os.path.join(environment.build_dir, subdir, 'meson') + os.makedirs(out_dir, exist_ok=True) + version_arr = self.manifest.package.version.split('.') + version_arr += [''] * (4 - len(version_arr)) + + return { + 'OUT_DIR': out_dir, + 'CARGO_MANIFEST_DIR': os.path.join(environment.source_dir, subdir), + 'CARGO_MANIFEST_PATH': os.path.join(environment.source_dir, subdir, 'Cargo.toml'), + 'CARGO_PKG_VERSION': self.manifest.package.version, + 'CARGO_PKG_VERSION_MAJOR': version_arr[0], + 'CARGO_PKG_VERSION_MINOR': version_arr[1], + 'CARGO_PKG_VERSION_PATCH': version_arr[2], + 'CARGO_PKG_VERSION_PRE': version_arr[3], + 'CARGO_PKG_AUTHORS': ','.join(self.manifest.package.authors), + 'CARGO_PKG_NAME': self.manifest.package.name, + # FIXME: description can contain newlines which breaks ninja. + #'CARGO_PKG_DESCRIPTION': self.manifest.package.description or '', + 'CARGO_PKG_HOMEPAGE': self.manifest.package.homepage or '', + 'CARGO_PKG_REPOSITORY': self.manifest.package.repository or '', + 'CARGO_PKG_LICENSE': self.manifest.package.license or '', + 'CARGO_PKG_LICENSE_FILE': self.manifest.package.license_file or '', + 'CARGO_PKG_RUST_VERSION': self.manifest.package.rust_version or '', + 'CARGO_PKG_README': self.manifest.package.readme or '', + 'CARGO_CRATE_NAME': fixup_meson_varname(self.manifest.package.name), + } - """Representation of a Cargo Example Entry.""" + def get_lint_args(self, rustc: RustCompiler) -> T.List[str]: + """Get lint arguments for this package.""" + args: T.List[str] = [] + has_check_cfg = rustc.has_check_cfg - crate_type: T.List[manifest.CRATE_TYPE] = dataclasses.field(default_factory=lambda: ['bin']) + for lint in self.manifest.lints: + args.extend(lint.to_arguments(has_check_cfg)) + if has_check_cfg: + for feature in self.manifest.features: + if feature != 'default': + args.append('--check-cfg') + args.append(f'cfg(feature,values("{feature}"))') + for name in self.manifest.system_dependencies: + args.append('--check-cfg') + args.append(f'cfg(system_deps_have_{fixup_meson_varname(name)})') -@dataclasses.dataclass -class Manifest: - - """Cargo Manifest definition. - - Most of these values map up to the Cargo Manifest, but with default values - if not provided. - - Cargo subprojects can contain what Meson wants to treat as multiple, - interdependent, subprojects. - - :param path: the path within the cargo subproject. - """ - - package: Package - dependencies: T.Dict[str, Dependency] - dev_dependencies: T.Dict[str, Dependency] - build_dependencies: T.Dict[str, Dependency] - system_dependencies: T.Dict[str, SystemDependency] = dataclasses.field(init=False) - lib: Library - bin: T.List[Binary] - test: T.List[Test] - bench: T.List[Benchmark] - example: T.List[Example] - features: T.Dict[str, T.List[str]] - target: T.Dict[str, T.Dict[str, Dependency]] - path: str = '' - - def __post_init__(self) -> None: - self.features.setdefault('default', []) - self.system_dependencies = {k: SystemDependency.from_raw(k, v) for k, v in self.package.metadata.get('system-deps', {}).items()} - - -def _convert_manifest(raw_manifest: manifest.Manifest, subdir: str, path: str = '') -> Manifest: - return Manifest( - Package.from_raw(raw_manifest['package']), - {k: Dependency.from_raw(k, v) for k, v in raw_manifest.get('dependencies', {}).items()}, - {k: Dependency.from_raw(k, v) for k, v in raw_manifest.get('dev-dependencies', {}).items()}, - {k: Dependency.from_raw(k, v) for k, v in raw_manifest.get('build-dependencies', {}).items()}, - Library.from_raw(raw_manifest.get('lib', {}), raw_manifest['package']['name']), - [Binary.from_raw(b) for b in raw_manifest.get('bin', {})], - [Test.from_raw(b) for b in raw_manifest.get('test', {})], - [Benchmark.from_raw(b) for b in raw_manifest.get('bench', {})], - [Example.from_raw(b) for b in raw_manifest.get('example', {})], - raw_manifest.get('features', {}), - {k: {k2: Dependency.from_raw(k2, v2) for k2, v2 in v.get('dependencies', {}).items()} - for k, v in raw_manifest.get('target', {}).items()}, - path, - ) - - -def _version_to_api(version: str) -> str: - # x.y.z -> x - # 0.x.y -> 0.x - # 0.0.x -> 0 - vers = version.split('.') - if int(vers[0]) != 0: - return vers[0] - elif len(vers) >= 2 and int(vers[1]) != 0: - return f'0.{vers[1]}' - return '0' - - -def _dependency_name(package_name: str, api: str) -> str: - basename = package_name[:-3] if package_name.endswith('-rs') else package_name - return f'{basename}-{api}-rs' - - -def _dependency_varname(package_name: str) -> str: - return f'{fixup_meson_varname(package_name)}_dep' + return args + def get_env_args(self, rustc: RustCompiler, environment: Environment, subdir: str) -> T.List[str]: + """Get environment variable arguments for rustc.""" + enable_env_set_args = rustc.enable_env_set_args() + if enable_env_set_args is None: + return [] -def _extra_args_varname() -> str: - return 'extra_args' + env_dict = self.get_env_dict(environment, subdir) + env_args = list(enable_env_set_args) + for k, v in env_dict.items(): + env_args.extend(['--env-set', f'{k}={v}']) + return env_args + def get_rustc_args(self, environment: Environment, subdir: str, machine: MachineChoice) -> T.List[str]: + """Get rustc arguments for this package.""" + if not environment.is_cross_build(): + machine = MachineChoice.HOST -def _extra_deps_varname() -> str: - return 'extra_deps' + rustc = T.cast('RustCompiler', environment.coredata.compilers[machine]['rust']) + cfg = self.cfg -class PackageState: - def __init__(self, manifest: Manifest, downloaded: bool) -> None: - self.manifest = manifest - self.downloaded = downloaded - self.features: T.Set[str] = set() - self.required_deps: T.Set[str] = set() - self.optional_deps_features: T.Dict[str, T.Set[str]] = collections.defaultdict(set) + args: T.List[str] = [] + args.extend(self.get_lint_args(rustc)) + args.extend(cfg.get_features_args()) + args.extend(self.get_env_args(rustc, environment, subdir)) + return args @dataclasses.dataclass(frozen=True) @@ -473,103 +188,315 @@ class PackageKey: api: str +@dataclasses.dataclass +class WorkspaceState: + workspace: Workspace + subdir: str + downloaded: bool = False + # member path -> PackageState, for all members of this workspace + packages: T.Dict[str, PackageState] = dataclasses.field(default_factory=dict) + # package name to member path, for all members of this workspace + packages_to_member: T.Dict[str, str] = dataclasses.field(default_factory=dict) + # member paths that are required to be built + required_members: T.List[str] = dataclasses.field(default_factory=list) + + class Interpreter: - def __init__(self, env: Environment) -> None: + def __init__(self, env: Environment, subdir: str, subprojects_dir: str) -> None: self.environment = env + self.subprojects_dir = subprojects_dir # Map Cargo.toml's subdir to loaded manifest. - self.manifests: T.Dict[str, Manifest] = {} + self.manifests: T.Dict[str, T.Union[Manifest, Workspace]] = {} # Map of cargo package (name + api) to its state self.packages: T.Dict[PackageKey, PackageState] = {} - - def interpret(self, subdir: str) -> mparser.CodeBlockNode: - manifest = self._load_manifest(subdir) - pkg, cached = self._fetch_package(manifest.package.name, manifest.package.api) - if not cached: - # This is an entry point, always enable the 'default' feature. - # FIXME: We should have a Meson option similar to `cargo build --no-default-features` + # Map subdir to workspace + self.workspaces: T.Dict[str, WorkspaceState] = {} + # Files that should trigger a reconfigure if modified + self.build_def_files: T.List[str] = [] + # Cargo packages + filename = os.path.join(self.environment.get_source_dir(), subdir, 'Cargo.lock') + subprojects_dir = os.path.join(self.environment.get_source_dir(), subprojects_dir) + self.cargolock = load_cargo_lock(filename, subprojects_dir) + if self.cargolock: + self.environment.wrap_resolver.merge_wraps(self.cargolock.wraps) + self.build_def_files.append(filename) + + def get_build_def_files(self) -> T.List[str]: + return self.build_def_files + + def _prepare_entry_point(self, ws: WorkspaceState) -> None: + pkgs = [self._require_workspace_member(ws, m) for m in ws.workspace.default_members] + for pkg in pkgs: + self._prepare_package(pkg) self._enable_feature(pkg, 'default') - # Build an AST for this package + def interpret(self, subdir: str, project_root: T.Optional[str] = None) -> mparser.CodeBlockNode: + manifest, cached = self._load_manifest(subdir) filename = os.path.join(self.environment.source_dir, subdir, 'Cargo.toml') build = builder.Builder(filename) - ast = self._create_project(pkg, build) - ast += [ - build.assign(build.function('import', [build.string('rust')]), 'rust'), + if project_root: + # this is a subdir() + assert isinstance(manifest, Manifest) + return self.interpret_package(manifest, build, subdir, project_root) + + ws = self._get_workspace(manifest, subdir, downloaded=False) + if not cached: + self._prepare_entry_point(ws) + return self.interpret_workspace(ws, build, subdir) + + def interpret_package(self, manifest: Manifest, build: builder.Builder, subdir: str, project_root: str) -> mparser.CodeBlockNode: + # Build an AST for this package + ws = self.workspaces[project_root] + member = ws.packages_to_member[manifest.package.name] + pkg = ws.packages[member] + ast = self._create_package(pkg, build, subdir) + return build.block(ast) + + def _create_package(self, pkg: PackageState, build: builder.Builder, subdir: str) -> T.List[mparser.BaseNode]: + cfg = pkg.cfg + ast: T.List[mparser.BaseNode] = [ + build.assign(build.array([build.string(f) for f in cfg.features]), 'features'), build.function('message', [ build.string('Enabled features:'), - build.array([build.string(f) for f in pkg.features]), + build.identifier('features'), ]), ] ast += self._create_dependencies(pkg, build) ast += self._create_meson_subdir(build) - # Libs are always auto-discovered and there's no other way to handle them, - # which is unfortunate for reproducability - if os.path.exists(os.path.join(self.environment.source_dir, subdir, pkg.manifest.path, pkg.manifest.lib.path)): - for crate_type in pkg.manifest.lib.crate_type: - ast.extend(self._create_lib(pkg, build, crate_type)) + if pkg.manifest.lib: + crate_type = pkg.manifest.lib.crate_type + if 'dylib' in crate_type and 'cdylib' in crate_type: + raise MesonException('Cannot build both dylib and cdylib due to file name conflict') + if 'proc-macro' in crate_type: + ast.extend(self._create_lib(pkg, build, subdir, 'proc-macro', shared=True)) + if any(x in crate_type for x in ['lib', 'rlib', 'dylib']): + ast.extend(self._create_lib(pkg, build, subdir, 'rust', + static=('lib' in crate_type or 'rlib' in crate_type), + shared='dylib' in crate_type)) + if any(x in crate_type for x in ['staticlib', 'cdylib']): + ast.extend(self._create_lib(pkg, build, subdir, 'c', + static='staticlib' in crate_type, + shared='cdylib' in crate_type)) + + return ast + + def interpret_workspace(self, ws: WorkspaceState, build: builder.Builder, subdir: str) -> mparser.CodeBlockNode: + name = os.path.dirname(subdir) + subprojects_dir = os.path.join(subdir, 'subprojects') + self.environment.wrap_resolver.load_and_merge(subprojects_dir, T.cast('SubProject', name)) + ast: T.List[mparser.BaseNode] = [] + + # Call subdir() for each required member of the workspace. The order is + # important, if a member depends on another member, that member must be + # processed first. + processed_members: T.Dict[str, PackageState] = {} + def _process_member(member: str) -> None: + if member in processed_members: + return + pkg = ws.packages[member] + cfg = pkg.cfg + for depname in cfg.required_deps: + dep = pkg.manifest.dependencies[depname] + if dep.path: + dep_member = os.path.normpath(os.path.join(pkg.ws_member, dep.path)) + _process_member(dep_member) + if member == '.': + ast.extend(self._create_package(pkg, build, subdir)) + elif is_parent_path(self.subprojects_dir, member): + depname = _dependency_name(pkg.manifest.package.name, pkg.manifest.package.api) + ast.append(build.function('subproject', [build.string(depname)])) + else: + ast.append(build.function('subdir', [build.string(member)])) + processed_members[member] = pkg + + ast.append(build.assign(build.function('import', [build.string('rust')]), 'rust')) + for member in ws.required_members: + _process_member(member) + ast = self._create_project(name, processed_members.get('.'), build) + ast return build.block(ast) - def _fetch_package(self, package_name: str, api: str) -> T.Tuple[PackageState, bool]: + def _load_workspace_member(self, ws: WorkspaceState, m: str) -> None: + m = os.path.normpath(m) + if m in ws.packages: + return + # Load member's manifest + m_subdir = os.path.join(ws.subdir, m) + manifest_, _ = self._load_manifest(m_subdir, ws.workspace, m) + assert isinstance(manifest_, Manifest) + self._add_workspace_member(manifest_, ws, m) + + def _add_workspace_member(self, manifest_: Manifest, ws: WorkspaceState, m: str) -> None: + key = PackageKey(manifest_.package.name, manifest_.package.api) + ws.packages_to_member[manifest_.package.name] = m + if key in self.packages: + ws.packages[m] = self.packages[key] + self._require_workspace_member(ws, m) + else: + ws.packages[m] = PackageState(manifest_, ws_subdir=ws.subdir, ws_member=m, downloaded=ws.downloaded) + + def _get_workspace(self, manifest: T.Union[Workspace, Manifest], subdir: str, downloaded: bool) -> WorkspaceState: + ws = self.workspaces.get(subdir) + if ws: + return ws + workspace = manifest if isinstance(manifest, Workspace) else \ + Workspace(root_package=manifest, members=['.'], default_members=['.']) + ws = WorkspaceState(workspace, subdir, downloaded=downloaded) + if workspace.root_package: + self._add_workspace_member(workspace.root_package, ws, '.') + for m in workspace.members: + self._load_workspace_member(ws, m) + self.workspaces[subdir] = ws + return ws + + def _record_package(self, pkg: PackageState) -> None: + key = PackageKey(pkg.manifest.package.name, pkg.manifest.package.api) + if key not in self.packages: + self.packages[key] = pkg + + def _require_workspace_member(self, ws: WorkspaceState, member: str) -> PackageState: + member = os.path.normpath(member) + pkg = ws.packages[member] + if member not in ws.required_members: + self._record_package(pkg) + ws.required_members.append(member) + return pkg + + def _fetch_package(self, package_name: str, api: str) -> PackageState: key = PackageKey(package_name, api) pkg = self.packages.get(key) if pkg: - return pkg, True + return pkg meson_depname = _dependency_name(package_name, api) - subdir, _ = self.environment.wrap_resolver.resolve(meson_depname) + return self._fetch_package_from_subproject(package_name, meson_depname) + + def _resolve_package(self, package_name: str, version_constraints: T.List[str]) -> T.Optional[CargoLockPackage]: + """From all available versions from Cargo.lock, pick the most recent + satisfying the constraints and return it.""" + if self.cargolock: + cargo_lock_pkgs = self.cargolock.named(package_name) + else: + cargo_lock_pkgs = [] + for cargo_pkg in cargo_lock_pkgs: + if all(version_compare(cargo_pkg.version, v) for v in version_constraints): + return cargo_pkg + + if not version_constraints: + raise MesonException(f'Cannot determine version of cargo package {package_name}') + return None + + def _fetch_package_from_subproject(self, package_name: str, meson_depname: str) -> PackageState: + subp_name, _ = self.environment.wrap_resolver.find_dep_provider(meson_depname) + if subp_name is None: + if self.cargolock is None: + raise MesonException(f'Dependency {meson_depname!r} not found in any wrap files.') + # If Cargo.lock has a different version, this could be a resolution + # bug, but maybe also a version mismatch? I am not sure yet... + similar_deps = [pkg.subproject + for pkg in self.cargolock.named(package_name)] + if similar_deps: + similar_msg = f'Cargo.lock provides: {", ".join(similar_deps)}.' + else: + similar_msg = 'Cargo.lock does not contain this crate name.' + raise MesonException(f'Dependency {meson_depname!r} not found in any wrap files or Cargo.lock; {similar_msg} This could be a Meson bug, please report it.') + + subdir, _ = self.environment.wrap_resolver.resolve(subp_name) subprojects_dir = os.path.join(subdir, 'subprojects') self.environment.wrap_resolver.load_and_merge(subprojects_dir, T.cast('SubProject', meson_depname)) - manifest = self._load_manifest(subdir) + manifest, _ = self._load_manifest(subdir) downloaded = \ - meson_depname in self.environment.wrap_resolver.wraps and \ - self.environment.wrap_resolver.wraps[meson_depname].type is not None - pkg = PackageState(manifest, downloaded) - self.packages[key] = pkg + subp_name in self.environment.wrap_resolver.wraps and \ + self.environment.wrap_resolver.wraps[subp_name].type is not None + + ws = self._get_workspace(manifest, subdir, downloaded=downloaded) + member = ws.packages_to_member[package_name] + pkg = self._require_workspace_member(ws, member) + return pkg + + def _prepare_package(self, pkg: PackageState) -> None: + key = PackageKey(pkg.manifest.package.name, pkg.manifest.package.api) + assert key in self.packages + if pkg.cfg: + return + + pkg.cfg = PackageConfiguration() + # Merge target specific dependencies that are enabled + cfgs = self._get_cfgs(MachineChoice.HOST) + for condition, dependencies in pkg.manifest.target.items(): + if eval_cfg(condition, cfgs): + pkg.manifest.dependencies.update(dependencies) # Fetch required dependencies recursively. - for depname, dep in manifest.dependencies.items(): + for depname, dep in pkg.manifest.dependencies.items(): if not dep.optional: self._add_dependency(pkg, depname) - return pkg, False - def _dep_package(self, dep: Dependency) -> PackageState: - return self.packages[PackageKey(dep.package, dep.api)] + def _dep_package(self, pkg: PackageState, dep: Dependency) -> PackageState: + if dep.path: + ws = self.workspaces[pkg.ws_subdir] + dep_member = os.path.normpath(os.path.join(pkg.ws_member, dep.path)) + if is_parent_path(self.subprojects_dir, dep_member): + if len(pathlib.PurePath(dep_member).parts) != 2: + raise MesonException('found "{self.subprojects_dir}" in path but it is not a valid subproject path') + self._load_workspace_member(ws, dep_member) + dep_pkg = self._require_workspace_member(ws, dep_member) + elif dep.git: + _, _, directory = _parse_git_url(dep.git, dep.branch) + dep_pkg = self._fetch_package_from_subproject(dep.package, directory) + else: + cargo_pkg = self._resolve_package(dep.package, dep.meson_version) + if cargo_pkg: + dep.update_version(f'={cargo_pkg.version}') + dep_pkg = self._fetch_package(dep.package, dep.api) + + if not dep.version: + dep.update_version(f'={dep_pkg.manifest.package.version}') + + dep_key = PackageKey(dep.package, dep.api) + pkg.cfg.dep_packages.setdefault(dep_key, dep_pkg) + assert pkg.cfg.dep_packages[dep_key] == dep_pkg + return dep_pkg - def _load_manifest(self, subdir: str) -> Manifest: + def _load_manifest(self, subdir: str, workspace: T.Optional[Workspace] = None, member_path: str = '') -> T.Tuple[T.Union[Manifest, Workspace], bool]: manifest_ = self.manifests.get(subdir) - if not manifest_: - filename = os.path.join(self.environment.source_dir, subdir, 'Cargo.toml') - raw = load_toml(filename) - if 'package' in raw: - raw_manifest = T.cast('manifest.Manifest', raw) - manifest_ = _convert_manifest(raw_manifest, subdir) - self.manifests[subdir] = manifest_ - else: - raise MesonException(f'{subdir}/Cargo.toml does not have [package] section') - return manifest_ + if manifest_: + return manifest_, True + path = os.path.join(self.environment.source_dir, subdir) + filename = os.path.join(path, 'Cargo.toml') + self.build_def_files.append(filename) + raw_manifest = T.cast('raw.Manifest', load_toml(filename)) + if 'workspace' in raw_manifest: + manifest_ = Workspace.from_raw(raw_manifest, path) + elif 'package' in raw_manifest: + manifest_ = Manifest.from_raw(raw_manifest, path, workspace, member_path) + else: + raise MesonException(f'{subdir}/Cargo.toml does not have [package] or [workspace] section') + self.manifests[subdir] = manifest_ + return manifest_, False def _add_dependency(self, pkg: PackageState, depname: str) -> None: - if depname in pkg.required_deps: + cfg = pkg.cfg + if depname in cfg.required_deps: return dep = pkg.manifest.dependencies.get(depname) if not dep: - if depname in itertools.chain(pkg.manifest.dev_dependencies, pkg.manifest.build_dependencies): - # FIXME: Not supported yet - return - raise MesonException(f'Dependency {depname} not defined in {pkg.manifest.package.name} manifest') - pkg.required_deps.add(depname) - dep_pkg, _ = self._fetch_package(dep.package, dep.api) + # It could be build/dev/target dependency. Just ignore it. + return + cfg.required_deps.add(depname) + dep_pkg = self._dep_package(pkg, dep) + self._prepare_package(dep_pkg) if dep.default_features: self._enable_feature(dep_pkg, 'default') for f in dep.features: self._enable_feature(dep_pkg, f) - for f in pkg.optional_deps_features[depname]: + for f in cfg.optional_deps_features[depname]: self._enable_feature(dep_pkg, f) def _enable_feature(self, pkg: PackageState, feature: str) -> None: - if feature in pkg.features: + cfg = pkg.cfg + if feature in cfg.features: return - pkg.features.add(feature) + cfg.features.add(feature) # A feature can also be a dependency. if feature in pkg.manifest.dependencies: self._add_dependency(pkg, feature) @@ -580,50 +507,78 @@ class Interpreter: depname, dep_f = f.split('/', 1) if depname[-1] == '?': depname = depname[:-1] - if depname in pkg.required_deps: - dep = pkg.manifest.dependencies[depname] - dep_pkg = self._dep_package(dep) - self._enable_feature(dep_pkg, dep_f) - else: - # This feature will be enabled only if that dependency - # is later added. - pkg.optional_deps_features[depname].add(dep_f) else: self._add_dependency(pkg, depname) - dep = pkg.manifest.dependencies.get(depname) - if dep: - dep_pkg = self._dep_package(dep) - self._enable_feature(dep_pkg, dep_f) + if depname in cfg.required_deps: + dep = pkg.manifest.dependencies[depname] + dep_pkg = self._dep_package(pkg, dep) + self._enable_feature(dep_pkg, dep_f) + else: + # This feature will be enabled only if that dependency + # is later added. + cfg.optional_deps_features[depname].add(dep_f) elif f.startswith('dep:'): self._add_dependency(pkg, f[4:]) else: self._enable_feature(pkg, f) - def _create_project(self, pkg: PackageState, build: builder.Builder) -> T.List[mparser.BaseNode]: + def has_check_cfg(self, machine: MachineChoice) -> bool: + if not self.environment.is_cross_build(): + machine = MachineChoice.HOST + rustc = T.cast('RustCompiler', self.environment.coredata.compilers[machine]['rust']) + return rustc.has_check_cfg + + @functools.lru_cache(maxsize=None) + def _get_cfgs(self, machine: MachineChoice) -> T.Dict[str, str]: + if not self.environment.is_cross_build(): + machine = MachineChoice.HOST + rustc = T.cast('RustCompiler', self.environment.coredata.compilers[machine]['rust']) + cfgs = rustc.get_cfgs().copy() + rustflags = self.environment.coredata.get_external_args(machine, 'rust') + rustflags_i = iter(rustflags) + for i in rustflags_i: + if i == '--cfg': + cfgs.append(next(rustflags_i)) + return dict(self._split_cfg(i) for i in cfgs) + + @staticmethod + def _split_cfg(cfg: str) -> T.Tuple[str, str]: + pair = cfg.split('=', maxsplit=1) + value = pair[1] if len(pair) > 1 else '' + if value and value[0] == '"': + value = value[1:-1] + return pair[0], value + + def _create_project(self, name: str, pkg: T.Optional[PackageState], build: builder.Builder) -> T.List[mparser.BaseNode]: """Create the project() function call :param pkg: The package to generate from :param build: The AST builder :return: a list nodes """ - default_options: T.List[mparser.BaseNode] = [] - default_options.append(build.string(f'rust_std={pkg.manifest.package.edition}')) - if pkg.downloaded: - default_options.append(build.string('warning_level=0')) - - args: T.List[mparser.BaseNode] = [] - args.extend([ - build.string(pkg.manifest.package.name), + args: T.List[mparser.BaseNode] = [ + build.string(name), build.string('rust'), - ]) + ] kwargs: T.Dict[str, mparser.BaseNode] = { - 'version': build.string(pkg.manifest.package.version), # Always assume that the generated meson is using the latest features # This will warn when when we generate deprecated code, which is helpful # for the upkeep of the module 'meson_version': build.string(f'>= {coredata.stable_version}'), - 'default_options': build.array(default_options), } + if not pkg: + return [ + build.function('project', args, kwargs), + ] + + default_options: T.Dict[str, mparser.BaseNode] = {} + if pkg.downloaded: + default_options['warning_level'] = build.string('0') + + kwargs.update({ + 'version': build.string(pkg.manifest.package.version), + 'default_options': build.dict({build.string(k): v for k, v in default_options.items()}), + }) if pkg.manifest.package.license: kwargs['license'] = build.string(pkg.manifest.package.license) elif pkg.manifest.package.license_file: @@ -632,19 +587,23 @@ class Interpreter: return [build.function('project', args, kwargs)] def _create_dependencies(self, pkg: PackageState, build: builder.Builder) -> T.List[mparser.BaseNode]: + cfg = pkg.cfg ast: T.List[mparser.BaseNode] = [] - for depname in pkg.required_deps: + for depname in cfg.required_deps: dep = pkg.manifest.dependencies[depname] - ast += self._create_dependency(dep, build) + dep_pkg = self._dep_package(pkg, dep) + if dep_pkg.manifest.lib: + ast += self._create_dependency(dep_pkg, dep, build) ast.append(build.assign(build.array([]), 'system_deps_args')) for name, sys_dep in pkg.manifest.system_dependencies.items(): - if sys_dep.enabled(pkg.features): + if sys_dep.enabled(cfg.features): ast += self._create_system_dependency(name, sys_dep, build) return ast def _create_system_dependency(self, name: str, dep: SystemDependency, build: builder.Builder) -> T.List[mparser.BaseNode]: + # TODO: handle feature_overrides kw = { - 'version': build.array([build.string(s) for s in dep.version]), + 'version': build.array([build.string(s) for s in dep.meson_version]), 'required': build.bool(not dep.optional), } varname = f'{fixup_meson_varname(name)}_system_dep' @@ -668,10 +627,11 @@ class Interpreter: ), ] - def _create_dependency(self, dep: Dependency, build: builder.Builder) -> T.List[mparser.BaseNode]: - pkg = self._dep_package(dep) + def _create_dependency(self, pkg: PackageState, dep: Dependency, build: builder.Builder) -> T.List[mparser.BaseNode]: + cfg = pkg.cfg + version_ = dep.meson_version or [pkg.manifest.package.version] kw = { - 'version': build.array([build.string(s) for s in dep.version]), + 'version': build.array([build.string(s) for s in version_]), } # Lookup for this dependency with the features we want in default_options kwarg. # @@ -692,7 +652,7 @@ class Interpreter: [build.string(_dependency_name(dep.package, dep.api))], kw, ), - _dependency_varname(dep.package), + _dependency_varname(dep), ), # actual_features = xxx_dep.get_variable('features', default_value : '').split(',') build.assign( @@ -700,7 +660,7 @@ class Interpreter: 'split', build.method( 'get_variable', - build.identifier(_dependency_varname(dep.package)), + build.identifier(_dependency_varname(dep)), [build.string('features')], {'default_value': build.string('')} ), @@ -714,7 +674,7 @@ class Interpreter: # error() # endif # endforeach - build.assign(build.array([build.string(f) for f in pkg.features]), 'needed_features'), + build.assign(build.array([build.string(f) for f in cfg.features]), 'needed_features'), build.foreach(['f'], build.identifier('needed_features'), build.block([ build.if_(build.not_in(build.identifier('f'), build.identifier('actual_features')), build.block([ build.function('error', [ @@ -747,30 +707,37 @@ class Interpreter: build.block([build.function('subdir', [build.string('meson')])])) ] - def _create_lib(self, pkg: PackageState, build: builder.Builder, crate_type: manifest.CRATE_TYPE) -> T.List[mparser.BaseNode]: + def _create_lib(self, pkg: PackageState, build: builder.Builder, subdir: str, + lib_type: Literal['rust', 'c', 'proc-macro'], + static: bool = False, shared: bool = False) -> T.List[mparser.BaseNode]: + cfg = pkg.cfg dependencies: T.List[mparser.BaseNode] = [] - dependency_map: T.Dict[mparser.BaseNode, mparser.BaseNode] = {} - for name in pkg.required_deps: + for name in cfg.required_deps: dep = pkg.manifest.dependencies[name] - dependencies.append(build.identifier(_dependency_varname(dep.package))) - if name != dep.package: - dep_pkg = self._dep_package(dep) - dep_lib_name = dep_pkg.manifest.lib.name - dependency_map[build.string(fixup_meson_varname(dep_lib_name))] = build.string(name) + dependencies.append(build.identifier(_dependency_varname(dep))) + + dependency_map: T.Dict[mparser.BaseNode, mparser.BaseNode] = { + build.string(k): build.string(v) for k, v in cfg.get_dependency_map(pkg.manifest).items()} + for name, sys_dep in pkg.manifest.system_dependencies.items(): - if sys_dep.enabled(pkg.features): + if sys_dep.enabled(cfg.features): dependencies.append(build.identifier(f'{fixup_meson_varname(name)}_system_dep')) - rust_args: T.List[mparser.BaseNode] = [ - build.identifier('features_args'), - build.identifier(_extra_args_varname()), - build.identifier('system_deps_args'), - ] + rustc_args_list = pkg.get_rustc_args(self.environment, subdir, MachineChoice.HOST) + extra_args_ref = build.identifier(_extra_args_varname()) + system_deps_args_ref = build.identifier('system_deps_args') + rust_args: T.List[mparser.BaseNode] = [build.string(a) for a in rustc_args_list] + rust_args.append(extra_args_ref) + rust_args.append(system_deps_args_ref) dependencies.append(build.identifier(_extra_deps_varname())) + override_options: T.Dict[mparser.BaseNode, mparser.BaseNode] = { + build.string('rust_std'): build.string(pkg.manifest.package.edition), + } + posargs: T.List[mparser.BaseNode] = [ - build.string(fixup_meson_varname(pkg.manifest.lib.name)), + build.string(_library_name(pkg.manifest.lib.name, pkg.manifest.package.api, lib_type)), build.string(pkg.manifest.lib.path), ] @@ -778,32 +745,28 @@ class Interpreter: 'dependencies': build.array(dependencies), 'rust_dependency_map': build.dict(dependency_map), 'rust_args': build.array(rust_args), + 'override_options': build.dict(override_options), } + depname_suffix = '' if lib_type == 'c' else '-rs' + depname = _dependency_name(pkg.manifest.package.name, pkg.manifest.package.api, depname_suffix) + lib: mparser.BaseNode - if pkg.manifest.lib.proc_macro or crate_type == 'proc-macro': + if lib_type == 'proc-macro': lib = build.method('proc_macro', build.identifier('rust'), posargs, kwargs) else: - if crate_type in {'lib', 'rlib', 'staticlib'}: - target_type = 'static_library' - elif crate_type in {'dylib', 'cdylib'}: - target_type = 'shared_library' + if static and shared: + target_type = 'both_libraries' else: - raise MesonException(f'Unsupported crate type {crate_type}') - if crate_type in {'staticlib', 'cdylib'}: - kwargs['rust_abi'] = build.string('c') - lib = build.function(target_type, posargs, kwargs) + target_type = 'shared_library' if shared else 'static_library' - features_args: T.List[mparser.BaseNode] = [] - for f in pkg.features: - features_args += [build.string('--cfg'), build.string(f'feature="{f}"')] + kwargs['rust_abi'] = build.string(lib_type) + lib = build.function(target_type, posargs, kwargs) - # features_args = ['--cfg', 'feature="f1"', ...] # lib = xxx_library() # dep = declare_dependency() # meson.override_dependency() return [ - build.assign(build.array(features_args), 'features_args'), build.assign(lib, 'lib'), build.assign( build.function( @@ -811,8 +774,9 @@ class Interpreter: kw={ 'link_with': build.identifier('lib'), 'variables': build.dict({ - build.string('features'): build.string(','.join(pkg.features)), - }) + build.string('features'): build.string(','.join(cfg.features)), + }), + 'version': build.string(pkg.manifest.package.version), }, ), 'dep' @@ -821,57 +785,79 @@ class Interpreter: 'override_dependency', build.identifier('meson'), [ - build.string(_dependency_name(pkg.manifest.package.name, pkg.manifest.package.api)), + build.string(depname), build.identifier('dep'), ], ), ] -def load_wraps(source_dir: str, subproject_dir: str) -> T.List[PackageDefinition]: +def _parse_git_url(url: str, branch: T.Optional[str] = None) -> T.Tuple[str, str, str]: + if url.startswith('git+'): + url = url[4:] + parts = urllib.parse.urlparse(url) + query = urllib.parse.parse_qs(parts.query) + query_branch = query['branch'][0] if 'branch' in query else '' + branch = branch or query_branch + revision = parts.fragment or branch + directory = PurePath(parts.path).name + if directory.endswith('.git'): + directory = directory[:-4] + if branch: + directory += f'-{branch}' + url = urllib.parse.urlunparse(parts._replace(params='', query='', fragment='')) + return url, revision, directory + + +def load_cargo_lock(filename: str, subproject_dir: str) -> T.Optional[CargoLock]: """ Convert Cargo.lock into a list of wraps """ - wraps: T.List[PackageDefinition] = [] - filename = os.path.join(source_dir, 'Cargo.lock') + # Map directory -> PackageDefinition, to avoid duplicates. Multiple packages + # can have the same source URL, in that case we have a single wrap that + # provides multiple dependency names. if os.path.exists(filename): - try: - cargolock = T.cast('manifest.CargoLock', load_toml(filename)) - except TomlImplementationMissing as e: - mlog.warning('Failed to load Cargo.lock:', str(e), fatal=False) - return wraps - for package in cargolock['package']: - name = package['name'] - version = package['version'] - subp_name = _dependency_name(name, _version_to_api(version)) - source = package.get('source') - if source is None: + toml = load_toml(filename) + raw_cargolock = T.cast('raw.CargoLock', toml) + cargolock = CargoLock.from_raw(raw_cargolock) + packagefiles_dir = os.path.join(subproject_dir, 'packagefiles') + wraps: T.Dict[str, PackageDefinition] = {} + for package in cargolock.package: + meson_depname = _dependency_name(package.name, version.api(package.version)) + if package.source is None: # This is project's package, or one of its workspace members. pass - elif source == 'registry+https://github.com/rust-lang/crates.io-index': - checksum = package.get('checksum') + elif package.source == 'registry+https://github.com/rust-lang/crates.io-index': + checksum = package.checksum if checksum is None: - checksum = cargolock['metadata'][f'checksum {name} {version} ({source})'] - url = f'https://crates.io/api/v1/crates/{name}/{version}/download' - directory = f'{name}-{version}' - wraps.append(PackageDefinition.from_values(subp_name, subproject_dir, 'file', { + checksum = cargolock.metadata[f'checksum {package.name} {package.version} ({package.source})'] + url = f'https://crates.io/api/v1/crates/{package.name}/{package.version}/download' + directory = f'{package.name}-{package.version}' + name = meson_depname + wrap_type = 'file' + cfg = { 'directory': directory, 'source_url': url, 'source_filename': f'{directory}.tar.gz', 'source_hash': checksum, 'method': 'cargo', - })) - elif source.startswith('git+'): - parts = urllib.parse.urlparse(source[4:]) - query = urllib.parse.parse_qs(parts.query) - branch = query['branch'][0] if 'branch' in query else '' - revision = parts.fragment or branch - url = urllib.parse.urlunparse(parts._replace(params='', query='', fragment='')) - wraps.append(PackageDefinition.from_values(subp_name, subproject_dir, 'git', { - 'directory': name, + } + elif package.source.startswith('git+'): + url, revision, directory = _parse_git_url(package.source) + name = directory + wrap_type = 'git' + cfg = { 'url': url, 'revision': revision, 'method': 'cargo', - })) + } else: - mlog.warning(f'Unsupported source URL in {filename}: {source}') - return wraps + mlog.warning(f'Unsupported source URL in {filename}: {package.source}') + continue + if os.path.isdir(os.path.join(packagefiles_dir, name)): + cfg['patch_directory'] = name + if directory not in wraps: + wraps[directory] = PackageDefinition.from_values(name, subproject_dir, wrap_type, cfg) + wraps[directory].add_provided_dep(meson_depname) + cargolock.wraps = {w.name: w for w in wraps.values()} + return cargolock + return None diff --git a/mesonbuild/cargo/manifest.py b/mesonbuild/cargo/manifest.py index d95df7f..ec84e4b 100644 --- a/mesonbuild/cargo/manifest.py +++ b/mesonbuild/cargo/manifest.py @@ -4,244 +4,622 @@ """Type definitions for cargo manifest files.""" from __future__ import annotations + +import collections +import dataclasses +import os import typing as T -from typing_extensions import Literal, TypedDict, Required - -EDITION = Literal['2015', '2018', '2021'] -CRATE_TYPE = Literal['bin', 'lib', 'dylib', 'staticlib', 'cdylib', 'rlib', 'proc-macro'] - -Package = TypedDict( - 'Package', - { - 'name': Required[str], - 'version': Required[str], - 'authors': T.List[str], - 'edition': EDITION, - 'rust-version': str, - 'description': str, - 'readme': str, - 'license': str, - 'license-file': str, - 'keywords': T.List[str], - 'categories': T.List[str], - 'workspace': str, - 'build': str, - 'links': str, - 'include': T.List[str], - 'exclude': T.List[str], - 'publish': bool, - 'metadata': T.Dict[str, T.Dict[str, str]], - 'default-run': str, - 'autolib': bool, - 'autobins': bool, - 'autoexamples': bool, - 'autotests': bool, - 'autobenches': bool, - }, - total=False, -) -"""A description of the Package Dictionary.""" -class FixedPackage(TypedDict, total=False): +from . import version +from ..mesonlib import MesonException, lazy_property, Version +from .. import mlog - """A description of the Package Dictionary, fixed up.""" +if T.TYPE_CHECKING: + from typing_extensions import Protocol, Self - name: Required[str] - version: Required[str] - authors: T.List[str] - edition: EDITION - rust_version: str - description: str - readme: str - license: str - license_file: str - keywords: T.List[str] - categories: T.List[str] - workspace: str - build: str - links: str - include: T.List[str] - exclude: T.List[str] - publish: bool - metadata: T.Dict[str, T.Dict[str, str]] - default_run: str - autolib: bool - autobins: bool - autoexamples: bool - autotests: bool - autobenches: bool - - -class Badge(TypedDict): - - """An entry in the badge section.""" - - status: Literal['actively-developed', 'passively-developed', 'as-is', 'experimental', 'deprecated', 'none'] - - -Dependency = TypedDict( - 'Dependency', - { - 'version': str, - 'registry': str, - 'git': str, - 'branch': str, - 'rev': str, - 'path': str, - 'optional': bool, - 'package': str, - 'default-features': bool, - 'features': T.List[str], - }, - total=False, + from . import raw + from .raw import EDITION, CRATE_TYPE, LINT_LEVEL + from ..wrap.wrap import PackageDefinition + + # Copied from typeshed. Blarg that they don't expose this + class DataclassInstance(Protocol): + __dataclass_fields__: T.ClassVar[dict[str, dataclasses.Field[T.Any]]] + +_DI = T.TypeVar('_DI', bound='DataclassInstance') + +_EXTRA_KEYS_WARNING = ( + "This may (unlikely) be an error in the cargo manifest, or may be a missing " + "implementation in Meson. If this issue can be reproduced with the latest " + "version of Meson, please help us by opening an issue at " + "https://github.com/mesonbuild/meson/issues. Please include the crate and " + "version that is generating this warning if possible." ) -"""An entry in the *dependencies sections.""" -class FixedDependency(TypedDict, total=False): +def fixup_meson_varname(name: str) -> str: + """Fixup a meson variable name - """An entry in the *dependencies sections, fixed up.""" + :param name: The name to fix + :return: the fixed name + """ + return name.replace('-', '_') - version: T.List[str] - registry: str - git: str - branch: str - rev: str - path: str - optional: bool - package: str - default_features: bool - features: T.List[str] - - -DependencyV = T.Union[Dependency, str] -"""A Dependency entry, either a string or a Dependency Dict.""" - - -_BaseBuildTarget = TypedDict( - '_BaseBuildTarget', - { - 'path': str, - 'test': bool, - 'doctest': bool, - 'bench': bool, - 'doc': bool, - 'plugin': bool, - 'proc-macro': bool, - 'harness': bool, - 'edition': EDITION, - 'crate-type': T.List[CRATE_TYPE], - 'required-features': T.List[str], - }, - total=False, -) + +class DefaultValue: + """Base class to converts a raw value from cargo manifest to a meson value + + It returns the value from current manifest, or fallback to the + workspace value. If both are None, its default value is used. Subclasses can + override the convert() method to implement custom conversion logic. + """ + + def __init__(self, default: object = None) -> None: + self.default = default + + def convert(self, v: T.Any, ws_v: T.Any) -> object: + return v if v is not None else ws_v + + +class MergeValue(DefaultValue): + def __init__(self, func: T.Callable[[T.Any, T.Any], object], default: object = None) -> None: + super().__init__(default) + self.func = func + + def convert(self, v: T.Any, ws_v: T.Any) -> object: + return self.func(v, ws_v) + + +class ConvertValue(DefaultValue): + def __init__(self, func: T.Callable[[T.Any], object], default: object = None) -> None: + super().__init__(default) + self.func = func + + def convert(self, v: T.Any, ws_v: T.Any) -> object: + return self.func(v if v is not None else ws_v) -class BuildTarget(_BaseBuildTarget, total=False): +def _raw_to_dataclass(raw: T.Mapping[str, object], cls: T.Type[_DI], msg: str, + raw_from_workspace: T.Optional[T.Mapping[str, object]] = None, + ignored_fields: T.Optional[T.List[str]] = None, + **kwargs: DefaultValue) -> _DI: + """Fixup raw cargo mappings to a dataclass. - name: Required[str] + * Inherit values from the workspace. + * Replaces any `-` with `_` in the keys. + * Optionally pass values through the functions in kwargs, in order to do + recursive conversions. + * Remove and warn on keys that are coming from cargo, but are unknown to + our representations. -class LibTarget(_BaseBuildTarget, total=False): + This is intended to give users the possibility of things proceeding when a + new key is added to Cargo.toml that we don't yet handle, but to still warn + them that things might not work. + + :param raw: The raw data to look at + :param cls: The Dataclass derived type that will be created + :param msg: the header for the error message. Usually something like "In N structure". + :param raw_from_workspace: If inheriting from a workspace, the raw data from the workspace. + :param kwargs: DefaultValue instances to convert values. + :return: A @cls instance. + """ + new_dict = {} + unexpected = set() + fields = {x.name for x in dataclasses.fields(cls)} + raw_from_workspace = raw_from_workspace or {} + ignored_fields = ignored_fields or [] + inherit = raw.get('workspace', False) + + for orig_k, v in raw.items(): + if orig_k == 'workspace': + continue + ws_v = None + if isinstance(v, dict) and v.get('workspace', False): + # foo.workspace = true, take value from workspace. + ws_v = raw_from_workspace[orig_k] + v = None + elif inherit: + # foo = {}, give the workspace value, if any, to the converter + # function in the case it wants to merge values. + ws_v = raw_from_workspace.get(orig_k) + k = fixup_meson_varname(orig_k) + if k not in fields: + if orig_k not in ignored_fields: + unexpected.add(orig_k) + continue + if k in kwargs: + new_dict[k] = kwargs[k].convert(v, ws_v) + else: + new_dict[k] = v if v is not None else ws_v + + if inherit: + # Inherit any keys from the workspace that we don't have yet. + for orig_k, ws_v in raw_from_workspace.items(): + k = fixup_meson_varname(orig_k) + if k not in fields: + if orig_k not in ignored_fields: + unexpected.add(orig_k) + continue + if k in new_dict: + continue + if k in kwargs: + new_dict[k] = kwargs[k].convert(None, ws_v) + else: + new_dict[k] = ws_v + + # Finally, set default values. + for k, convertor in kwargs.items(): + if k not in new_dict and convertor.default is not None: + new_dict[k] = convertor.default + + if unexpected: + mlog.warning(msg, 'has unexpected keys', '"{}".'.format(', '.join(sorted(unexpected))), + _EXTRA_KEYS_WARNING) + + return cls(**new_dict) + + +@dataclasses.dataclass +class Package: + + """Representation of a Cargo Package entry, with defaults filled in.""" name: str + version: str = "0" + description: T.Optional[str] = None + resolver: T.Optional[str] = None + authors: T.List[str] = dataclasses.field(default_factory=list) + edition: EDITION = '2015' + rust_version: T.Optional[str] = None + documentation: T.Optional[str] = None + readme: T.Optional[str] = None + homepage: T.Optional[str] = None + repository: T.Optional[str] = None + license: T.Optional[str] = None + license_file: T.Optional[str] = None + keywords: T.List[str] = dataclasses.field(default_factory=list) + categories: T.List[str] = dataclasses.field(default_factory=list) + workspace: T.Optional[str] = None + build: T.Optional[str] = None + links: T.Optional[str] = None + exclude: T.List[str] = dataclasses.field(default_factory=list) + include: T.List[str] = dataclasses.field(default_factory=list) + publish: bool = True + metadata: T.Dict[str, T.Any] = dataclasses.field(default_factory=dict) + default_run: T.Optional[str] = None + autolib: bool = True + autobins: bool = True + autoexamples: bool = True + autotests: bool = True + autobenches: bool = True + + @lazy_property + def api(self) -> str: + return version.api(self.version) + + @classmethod + def from_raw(cls, raw_pkg: raw.Package, workspace: T.Optional[Workspace] = None) -> Self: + raw_ws_pkg = workspace.package if workspace else None + return _raw_to_dataclass(raw_pkg, cls, f'Package entry {raw_pkg["name"]}', raw_ws_pkg) + + +@dataclasses.dataclass +class SystemDependency: + + """ Representation of a Cargo system-deps entry + https://docs.rs/system-deps/latest/system_deps + """ + name: str + version: str = '' + optional: bool = False + feature: T.Optional[str] = None + # TODO: convert values to dataclass + feature_overrides: T.Dict[str, T.Dict[str, str]] = dataclasses.field(default_factory=dict) + + @lazy_property + def meson_version(self) -> T.List[str]: + vers = self.version.split(',') if self.version else [] + result: T.List[str] = [] + for v in vers: + v = v.strip() + if v[0] not in '><=': + v = f'>={v}' + result.append(v) + return result + + def enabled(self, features: T.Set[str]) -> bool: + return self.feature is None or self.feature in features + + @classmethod + def from_raw(cls, name: str, raw: T.Union[T.Dict[str, T.Any], str]) -> Self: + if isinstance(raw, str): + raw = {'version': raw} + name = raw.get('name', name) + version = raw.get('version', '') + optional = raw.get('optional', False) + feature = raw.get('feature') + # Everything else are overrides when certain features are enabled. + feature_overrides = {k: v for k, v in raw.items() if k not in {'name', 'version', 'optional', 'feature'}} + return cls(name, version, optional, feature, feature_overrides) + + +@dataclasses.dataclass +class Dependency: + + """Representation of a Cargo Dependency Entry.""" -class _BaseFixedBuildTarget(TypedDict, total=False): + package: str + version: str = '' + registry: T.Optional[str] = None + git: T.Optional[str] = None + branch: T.Optional[str] = None + rev: T.Optional[str] = None + path: T.Optional[str] = None + optional: bool = False + default_features: bool = True + features: T.List[str] = dataclasses.field(default_factory=list) + + @lazy_property + def meson_version(self) -> T.List[str]: + return version.convert(self.version) + + @lazy_property + def api(self) -> str: + # Extract wanted API version from version constraints. + api = set() + for v in self.meson_version: + if v.startswith(('>=', '==')): + api.add(version.api(v[2:].strip())) + elif v.startswith('='): + api.add(version.api(v[1:].strip())) + if not api: + return '' + elif len(api) == 1: + return api.pop() + else: + raise MesonException(f'Cannot determine minimum API version from {self.version}.') + + def update_version(self, v: str) -> None: + self.version = v + try: + delattr(self, 'api') + except AttributeError: + pass + try: + delattr(self, 'meson_version') + except AttributeError: + pass + + @T.overload + @staticmethod + def _depv_to_dep(depv: raw.FromWorkspace) -> raw.FromWorkspace: ... + + @T.overload + @staticmethod + def _depv_to_dep(depv: raw.DependencyV) -> raw.Dependency: ... + + @staticmethod + def _depv_to_dep(depv: T.Union[raw.FromWorkspace, raw.DependencyV]) -> T.Union[raw.FromWorkspace, raw.Dependency]: + return {'version': depv} if isinstance(depv, str) else depv + + @classmethod + def from_raw(cls, name: str, raw_depv: T.Union[raw.FromWorkspace, raw.DependencyV], member_path: str = '', workspace: T.Optional[Workspace] = None) -> Self: + """Create a dependency from a raw cargo dictionary or string""" + raw_ws_dep = workspace.dependencies.get(name) if workspace else None + raw_ws_dep = cls._depv_to_dep(raw_ws_dep or {}) + raw_dep = cls._depv_to_dep(raw_depv) + + def path_convertor(path: T.Optional[str], ws_path: T.Optional[str]) -> T.Optional[str]: + if path: + return path + if ws_path: + return os.path.relpath(ws_path, member_path) + return None + + return _raw_to_dataclass(raw_dep, cls, f'Dependency entry {name}', raw_ws_dep, + package=DefaultValue(name), + path=MergeValue(path_convertor), + features=MergeValue(lambda features, ws_features: (features or []) + (ws_features or []))) + + +@dataclasses.dataclass +class BuildTarget: + + # https://doc.rust-lang.org/cargo/reference/cargo-targets.html + # Some default values are overridden in subclasses + name: str path: str - test: bool - doctest: bool - bench: bool - doc: bool - plugin: bool - harness: bool edition: EDITION - crate_type: T.List[CRATE_TYPE] - required_features: T.List[str] + test: bool = True + doctest: bool = True + bench: bool = True + doc: bool = True + harness: bool = True + crate_type: T.List[CRATE_TYPE] = dataclasses.field(default_factory=lambda: ['bin']) + required_features: T.List[str] = dataclasses.field(default_factory=list) + plugin: bool = False -class FixedBuildTarget(_BaseFixedBuildTarget, total=False): +@dataclasses.dataclass +class Library(BuildTarget): - name: str + """Representation of a Cargo Library Entry.""" -class FixedLibTarget(_BaseFixedBuildTarget, total=False): + @classmethod + def from_raw(cls, raw: raw.LibTarget, pkg: Package) -> Self: + name = raw.get('name', fixup_meson_varname(pkg.name)) + # If proc_macro is True, it takes precedence and sets crate_type to proc-macro + proc_macro = raw.get('proc-macro', False) + return _raw_to_dataclass(raw, cls, f'Library entry {name}', + ignored_fields=['proc-macro'], + name=DefaultValue(name), + path=DefaultValue('src/lib.rs'), + edition=DefaultValue(pkg.edition), + crate_type=ConvertValue(lambda x: ['proc-macro'] if proc_macro else x, + ['proc-macro'] if proc_macro else ['lib'])) - name: Required[str] - proc_macro: bool +@dataclasses.dataclass +class Binary(BuildTarget): -class Target(TypedDict): + """Representation of a Cargo Bin Entry.""" - """Target entry in the Manifest File.""" + @classmethod + def from_raw(cls, raw: raw.BuildTarget, pkg: Package) -> Self: + name = raw["name"] + return _raw_to_dataclass(raw, cls, f'Binary entry {name}', + path=DefaultValue('src/main.rs'), + edition=DefaultValue(pkg.edition)) - dependencies: T.Dict[str, DependencyV] +@dataclasses.dataclass +class Test(BuildTarget): -class Workspace(TypedDict): + """Representation of a Cargo Test Entry.""" - """The representation of a workspace. + @classmethod + def from_raw(cls, raw: raw.BuildTarget, pkg: Package) -> Self: + name = raw["name"] + return _raw_to_dataclass(raw, cls, f'Test entry {name}', + path=DefaultValue(f'tests/{name}.rs'), + edition=DefaultValue(pkg.edition), + bench=DefaultValue(False), + doc=DefaultValue(False)) - In a vritual manifest the :attribute:`members` is always present, but in a - project manifest, an empty workspace may be provided, in which case the - workspace is implicitly filled in by values from the path based dependencies. - the :attribute:`exclude` is always optional - """ +@dataclasses.dataclass +class Benchmark(BuildTarget): + + """Representation of a Cargo Benchmark Entry.""" + + @classmethod + def from_raw(cls, raw: raw.BuildTarget, pkg: Package) -> Self: + name = raw["name"] + return _raw_to_dataclass(raw, cls, f'Benchmark entry {name}', + path=DefaultValue(f'benches/{name}.rs'), + edition=DefaultValue(pkg.edition), + test=DefaultValue(False), + doc=DefaultValue(False)) - members: T.List[str] - exclude: T.List[str] - - -Manifest = TypedDict( - 'Manifest', - { - 'package': Required[Package], - 'badges': T.Dict[str, Badge], - 'dependencies': T.Dict[str, DependencyV], - 'dev-dependencies': T.Dict[str, DependencyV], - 'build-dependencies': T.Dict[str, DependencyV], - 'lib': LibTarget, - 'bin': T.List[BuildTarget], - 'test': T.List[BuildTarget], - 'bench': T.List[BuildTarget], - 'example': T.List[BuildTarget], - 'features': T.Dict[str, T.List[str]], - 'target': T.Dict[str, Target], - 'workspace': Workspace, - - # TODO: patch? - # TODO: replace? - }, - total=False, -) -"""The Cargo Manifest format.""" +@dataclasses.dataclass +class Example(BuildTarget): -class VirtualManifest(TypedDict): + """Representation of a Cargo Example Entry.""" - """The Representation of a virtual manifest. + @classmethod + def from_raw(cls, raw: raw.BuildTarget, pkg: Package) -> Self: + name = raw["name"] + return _raw_to_dataclass(raw, cls, f'Example entry {name}', + path=DefaultValue(f'examples/{name}.rs'), + edition=DefaultValue(pkg.edition), + test=DefaultValue(False), + bench=DefaultValue(False), + doc=DefaultValue(False)) - Cargo allows a root manifest that contains only a workspace, this is called - a virtual manifest. This doesn't really map 1:1 with any meson concept, - except perhaps the proposed "meta project". + +@dataclasses.dataclass +class Lint: + + """Cargo Lint definition. + """ + + name: str + level: LINT_LEVEL + priority: int + check_cfg: T.Optional[T.List[str]] + + @classmethod + def from_raw(cls, r: T.Union[raw.FromWorkspace, T.Dict[str, T.Dict[str, raw.LintV]]]) -> T.List[Lint]: + r = T.cast('T.Dict[str, T.Dict[str, raw.LintV]]', r) + lints: T.Dict[str, Lint] = {} + for tool, raw_lints in r.items(): + prefix = '' if tool == 'rust' else f'{tool}::' + for name, settings in raw_lints.items(): + name = prefix + name + if isinstance(settings, str): + settings = T.cast('raw.Lint', {'level': settings}) + check_cfg = None + if name == 'unexpected_cfgs': + # 'cfg(test)' is added automatically by cargo + check_cfg = ['cfg(test)'] + settings.get('check-cfg', []) + lints[name] = Lint(name=name, + level=settings['level'], + priority=settings.get('priority', 0), + check_cfg=check_cfg) + + lints_final = list(lints.values()) + lints_final.sort(key=lambda x: x.priority) + return lints_final + + def to_arguments(self, check_cfg: bool) -> T.List[str]: + if self.level == "deny": + flag = "-D" + elif self.level == "allow": + flag = "-A" + elif self.level == "warn": + flag = "-W" + elif self.level == "forbid": + flag = "-F" + else: + raise MesonException(f"invalid level {self.level!r} for {self.name}") + args = [flag, self.name] + if check_cfg and self.check_cfg: + for arg in self.check_cfg: + args.append('--check-cfg') + args.append(arg) + return args + + +@dataclasses.dataclass +class Manifest: + + """Cargo Manifest definition. + + Most of these values map up to the Cargo Manifest, but with default values + if not provided. + + Cargo subprojects can contain what Meson wants to treat as multiple, + interdependent, subprojects. + + :param path: the path within the cargo subproject. """ - workspace: Workspace + package: Package + dependencies: T.Dict[str, Dependency] = dataclasses.field(default_factory=dict) + dev_dependencies: T.Dict[str, Dependency] = dataclasses.field(default_factory=dict) + build_dependencies: T.Dict[str, Dependency] = dataclasses.field(default_factory=dict) + lib: T.Optional[Library] = None + bin: T.List[Binary] = dataclasses.field(default_factory=list) + test: T.List[Test] = dataclasses.field(default_factory=list) + bench: T.List[Benchmark] = dataclasses.field(default_factory=list) + example: T.List[Example] = dataclasses.field(default_factory=list) + features: T.Dict[str, T.List[str]] = dataclasses.field(default_factory=dict) + target: T.Dict[str, T.Dict[str, Dependency]] = dataclasses.field(default_factory=dict) + lints: T.List[Lint] = dataclasses.field(default_factory=list) + + # missing: profile + + def __post_init__(self) -> None: + self.features.setdefault('default', []) + + @lazy_property + def system_dependencies(self) -> T.Dict[str, SystemDependency]: + return {k: SystemDependency.from_raw(k, v) for k, v in self.package.metadata.get('system-deps', {}).items()} + + @classmethod + def from_raw(cls, raw: raw.Manifest, path: str, workspace: T.Optional[Workspace] = None, member_path: str = '') -> Self: + pkg = Package.from_raw(raw['package'], workspace) + + autolib = None + if pkg.autolib and os.path.exists(os.path.join(path, 'src/lib.rs')): + autolib = Library.from_raw({}, pkg) + + def dependencies_from_raw(x: T.Dict[str, T.Any]) -> T.Dict[str, Dependency]: + return {k: Dependency.from_raw(k, v, member_path, workspace) for k, v in x.items()} + + return _raw_to_dataclass(raw, cls, f'Cargo.toml package {pkg.name}', + raw_from_workspace=workspace.inheritable if workspace else None, + ignored_fields=['badges', 'workspace'], + package=ConvertValue(lambda _: pkg), + dependencies=ConvertValue(dependencies_from_raw), + dev_dependencies=ConvertValue(dependencies_from_raw), + build_dependencies=ConvertValue(dependencies_from_raw), + lints=ConvertValue(Lint.from_raw), + lib=ConvertValue(lambda x: Library.from_raw(x, pkg), default=autolib), + bin=ConvertValue(lambda x: [Binary.from_raw(b, pkg) for b in x]), + test=ConvertValue(lambda x: [Test.from_raw(b, pkg) for b in x]), + bench=ConvertValue(lambda x: [Benchmark.from_raw(b, pkg) for b in x]), + example=ConvertValue(lambda x: [Example.from_raw(b, pkg) for b in x]), + target=ConvertValue(lambda x: {k: dependencies_from_raw(v.get('dependencies', {})) for k, v in x.items()})) + + +@dataclasses.dataclass +class Workspace: + + """Cargo Workspace definition. + """ -class CargoLockPackage(TypedDict, total=False): + resolver: str = dataclasses.field(default_factory=lambda: '2') + members: T.List[str] = dataclasses.field(default_factory=list) + exclude: T.List[str] = dataclasses.field(default_factory=list) + default_members: T.List[str] = dataclasses.field(default_factory=list) + + # inheritable settings are kept in raw format, for use with _raw_to_dataclass + package: T.Optional[raw.Package] = None + dependencies: T.Dict[str, raw.Dependency] = dataclasses.field(default_factory=dict) + lints: T.Dict[str, T.Dict[str, raw.LintV]] = dataclasses.field(default_factory=dict) + metadata: T.Dict[str, T.Any] = dataclasses.field(default_factory=dict) + + # A workspace can also have a root package. + root_package: T.Optional[Manifest] = None + + @lazy_property + def inheritable(self) -> T.Dict[str, object]: + # the whole lints table is inherited. Do not add package, dependencies + # etc. because they can only be inherited a field at a time. + return { + 'lints': self.lints, + } + + @classmethod + def from_raw(cls, raw: raw.Manifest, path: str) -> Self: + ws = _raw_to_dataclass(raw['workspace'], cls, 'Workspace') + if 'package' in raw: + ws.root_package = Manifest.from_raw(raw, path, ws, '.') + if not ws.default_members: + ws.default_members = ['.'] if ws.root_package else ws.members + return ws + + +@dataclasses.dataclass +class CargoLockPackage: """A description of a package in the Cargo.lock file format.""" name: str version: str - source: str - checksum: str + source: T.Optional[str] = None + checksum: T.Optional[str] = None + dependencies: T.List[str] = dataclasses.field(default_factory=list) + + @lazy_property + def api(self) -> str: + return version.api(self.version) + + @lazy_property + def subproject(self) -> str: + return f'{self.name}-{self.api}-rs' + @classmethod + def from_raw(cls, raw: raw.CargoLockPackage) -> Self: + return _raw_to_dataclass(raw, cls, 'Cargo.lock package') -class CargoLock(TypedDict, total=False): + +@dataclasses.dataclass +class CargoLock: """A description of the Cargo.lock file format.""" - version: str - package: T.List[CargoLockPackage] - metadata: T.Dict[str, str] + version: int = 1 + package: T.List[CargoLockPackage] = dataclasses.field(default_factory=list) + metadata: T.Dict[str, str] = dataclasses.field(default_factory=dict) + wraps: T.Dict[str, PackageDefinition] = dataclasses.field(default_factory=dict) + + def named(self, name: str) -> T.Sequence[CargoLockPackage]: + return self._versions[name] + + @lazy_property + def _versions(self) -> T.Dict[str, T.List[CargoLockPackage]]: + versions = collections.defaultdict(list) + for pkg in self.package: + versions[pkg.name].append(pkg) + for pkg_versions in versions.values(): + pkg_versions.sort(reverse=True, key=lambda pkg: Version(pkg.version)) + return versions + + @classmethod + def from_raw(cls, raw: raw.CargoLock) -> Self: + return _raw_to_dataclass(raw, cls, 'Cargo.lock', + package=ConvertValue(lambda x: [CargoLockPackage.from_raw(p) for p in x])) diff --git a/mesonbuild/cargo/raw.py b/mesonbuild/cargo/raw.py new file mode 100644 index 0000000..b683f06 --- /dev/null +++ b/mesonbuild/cargo/raw.py @@ -0,0 +1,204 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright © 2022-2024 Intel Corporation + +"""Type definitions for cargo manifest files.""" + +from __future__ import annotations +import typing as T + +from typing_extensions import Literal, TypedDict, Required + +EDITION = Literal['2015', '2018', '2021'] +CRATE_TYPE = Literal['bin', 'lib', 'dylib', 'staticlib', 'cdylib', 'rlib', 'proc-macro'] +LINT_LEVEL = Literal['allow', 'deny', 'forbid', 'warn'] + + +class FromWorkspace(TypedDict): + + """An entry or section that is copied from the workspace.""" + + workspace: bool + + +Package = TypedDict( + 'Package', + { + 'name': Required[str], + 'version': Required[T.Union[FromWorkspace, str]], + 'authors': T.Union[FromWorkspace, T.List[str]], + 'edition': T.Union[FromWorkspace, EDITION], + 'rust-version': T.Union[FromWorkspace, str], + 'description': T.Union[FromWorkspace, str], + 'readme': T.Union[FromWorkspace, str], + 'license': T.Union[FromWorkspace, str], + 'license-file': T.Union[FromWorkspace, str], + 'keywords': T.Union[FromWorkspace, T.List[str]], + 'categories': T.Union[FromWorkspace, T.List[str]], + 'homepage': T.Union[FromWorkspace, str], + 'repository': T.Union[FromWorkspace, str], + 'documentation': T.Union[FromWorkspace, str], + 'workspace': str, + 'build': str, + 'links': str, + 'include': T.Union[FromWorkspace, T.List[str]], + 'exclude': T.Union[FromWorkspace, T.List[str]], + 'publish': T.Union[FromWorkspace, bool], + 'metadata': T.Dict[str, T.Dict[str, str]], + 'default-run': str, + 'autolib': bool, + 'autobins': bool, + 'autoexamples': bool, + 'autotests': bool, + 'autobenches': bool, + }, + total=False, +) +"""A description of the Package Dictionary.""" + +class Badge(TypedDict): + + """An entry in the badge section.""" + + status: Literal['actively-developed', 'passively-developed', 'as-is', 'experimental', 'deprecated', 'none'] + repository: str + + +Dependency = TypedDict( + 'Dependency', + { + 'version': str, + 'registry': str, + 'git': str, + 'branch': str, + 'rev': str, + 'path': str, + 'optional': bool, + 'package': str, + 'default-features': bool, + 'features': T.List[str], + }, + total=False, +) +"""An entry in the *dependencies sections.""" + + +DependencyV = T.Union[Dependency, str] +"""A Dependency entry, either a string or a Dependency Dict.""" + + +_BaseBuildTarget = TypedDict( + '_BaseBuildTarget', + { + 'path': str, + 'test': bool, + 'doctest': bool, + 'bench': bool, + 'doc': bool, + 'plugin': bool, + 'proc-macro': bool, + 'harness': bool, + 'edition': EDITION, + 'crate-type': T.List[CRATE_TYPE], + 'required-features': T.List[str], + }, + total=False, +) + + +class BuildTarget(_BaseBuildTarget, total=False): + + name: Required[str] + + +class LibTarget(_BaseBuildTarget, total=False): + + name: str + + +class Target(TypedDict): + + """Target entry in the Manifest File.""" + + dependencies: T.Dict[str, T.Union[FromWorkspace, DependencyV]] + + +Lint = TypedDict( + 'Lint', + { + 'level': Required[LINT_LEVEL], + 'priority': int, + 'check-cfg': T.List[str], + }, + total=True, +) +"""The representation of a linter setting. + +This does not include the name or tool, since those are the keys of the +dictionaries that point to Lint. +""" + + +LintV = T.Union[Lint, str] +"""A Lint entry, either a string or a Lint Dict.""" + + +class Workspace(TypedDict): + + """The representation of a workspace. + + In a vritual manifest the :attribute:`members` is always present, but in a + project manifest, an empty workspace may be provided, in which case the + workspace is implicitly filled in by values from the path based dependencies. + + the :attribute:`exclude` is always optional + """ + + members: T.List[str] + exclude: T.List[str] + package: Package + dependencies: T.Dict[str, DependencyV] + + +Manifest = TypedDict( + 'Manifest', + { + 'package': Package, + 'badges': T.Dict[str, Badge], + 'dependencies': T.Dict[str, T.Union[FromWorkspace, DependencyV]], + 'dev-dependencies': T.Dict[str, T.Union[FromWorkspace, DependencyV]], + 'build-dependencies': T.Dict[str, T.Union[FromWorkspace, DependencyV]], + 'lib': LibTarget, + 'bin': T.List[BuildTarget], + 'test': T.List[BuildTarget], + 'bench': T.List[BuildTarget], + 'example': T.List[BuildTarget], + 'features': T.Dict[str, T.List[str]], + 'target': T.Dict[str, Target], + 'workspace': Workspace, + 'lints': T.Union[FromWorkspace, T.Dict[str, T.Dict[str, LintV]]], + + # TODO: patch? + # TODO: replace? + }, + total=False, +) +"""The Cargo Manifest format.""" + + +class CargoLockPackage(TypedDict, total=False): + + """A description of a package in the Cargo.lock file format.""" + + name: str + version: str + source: str + checksum: str + + +class CargoLock(TypedDict, total=False): + + """A description of the Cargo.lock file format.""" + + version: int + package: T.List[CargoLockPackage] + metadata: T.Dict[str, str] diff --git a/mesonbuild/cargo/toml.py b/mesonbuild/cargo/toml.py new file mode 100644 index 0000000..601510e --- /dev/null +++ b/mesonbuild/cargo/toml.py @@ -0,0 +1,49 @@ +from __future__ import annotations + +import importlib +import shutil +import json +import typing as T + +from ..mesonlib import MesonException, Popen_safe +if T.TYPE_CHECKING: + from types import ModuleType + + +# tomllib is present in python 3.11, before that it is a pypi module called tomli, +# we try to import tomllib, then tomli, +tomllib: T.Optional[ModuleType] = None +toml2json: T.Optional[str] = None +for t in ['tomllib', 'tomli']: + try: + tomllib = importlib.import_module(t) + break + except ImportError: + pass +else: + # TODO: it would be better to use an Executable here, which could be looked + # up in the cross file or provided by a wrap. However, that will have to be + # passed in externally, since we don't have (and I don't think we should), + # have access to the `Environment` for that in this module. + toml2json = shutil.which('toml2json') + +class TomlImplementationMissing(MesonException): + pass + + +def load_toml(filename: str) -> T.Dict[str, object]: + if tomllib: + with open(filename, 'rb') as f: + raw = tomllib.load(f) + else: + if toml2json is None: + raise TomlImplementationMissing('Could not find an implementation of tomllib, nor toml2json') + + p, out, err = Popen_safe([toml2json, filename]) + if p.returncode != 0: + raise MesonException('toml2json failed to decode output\n', err) + + raw = json.loads(out) + + # tomllib.load() returns T.Dict[str, T.Any] but not other implementations. + return T.cast('T.Dict[str, object]', raw) diff --git a/mesonbuild/cargo/version.py b/mesonbuild/cargo/version.py index cde7a83..ce58945 100644 --- a/mesonbuild/cargo/version.py +++ b/mesonbuild/cargo/version.py @@ -7,6 +7,18 @@ from __future__ import annotations import typing as T +def api(version: str) -> str: + # x.y.z -> x + # 0.x.y -> 0.x + # 0.0.x -> 0 + vers = version.split('.') + if int(vers[0]) != 0: + return vers[0] + elif len(vers) >= 2 and int(vers[1]) != 0: + return f'0.{vers[1]}' + return '0' + + def convert(cargo_ver: str) -> T.List[str]: """Convert a Cargo compatible version into a Meson compatible one. @@ -15,6 +27,8 @@ def convert(cargo_ver: str) -> T.List[str]: """ # Cleanup, just for safety cargo_ver = cargo_ver.strip() + if not cargo_ver: + return [] cargo_vers = [c.strip() for c in cargo_ver.split(',')] out: T.List[str] = [] diff --git a/mesonbuild/cmake/common.py b/mesonbuild/cmake/common.py index 7644c0b..b7ab1ba 100644 --- a/mesonbuild/cmake/common.py +++ b/mesonbuild/cmake/common.py @@ -19,6 +19,7 @@ language_map = { 'cuda': 'CUDA', 'objc': 'OBJC', 'objcpp': 'OBJCXX', + 'nasm': 'ASM_NASM', 'cs': 'CSharp', 'java': 'Java', 'fortran': 'Fortran', diff --git a/mesonbuild/cmake/generator.py b/mesonbuild/cmake/generator.py index a617f8a..fcc37cd 100644 --- a/mesonbuild/cmake/generator.py +++ b/mesonbuild/cmake/generator.py @@ -170,6 +170,8 @@ def parse_generator_expressions( # Evaluate the function if func in supported: res = supported[func](args) + else: + mlog.warning(f"Unknown generator expression '$<{func}:{args}>'.", once=True, fatal=False) return res diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index 9296276..bdaa6d5 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -125,7 +125,7 @@ TRANSFER_DEPENDENCIES_FROM: T.Collection[str] = ['header_only'] _cmake_name_regex = re.compile(r'[^_a-zA-Z0-9]') def _sanitize_cmake_name(name: str) -> str: name = _cmake_name_regex.sub('_', name) - if name in FORBIDDEN_TARGET_NAMES or name.startswith('meson'): + if name in FORBIDDEN_TARGET_NAMES or name.startswith('meson') or name[0].isdigit(): name = 'cm_' + name return name @@ -223,6 +223,7 @@ class ConverterTarget: self.install = target.install self.install_dir: T.Optional[Path] = None self.link_libraries = target.link_libraries + self.link_targets: T.List[str] = [] self.link_flags = target.link_flags + target.link_lang_flags self.public_link_flags: T.List[str] = [] self.depends_raw: T.List[str] = [] @@ -242,6 +243,8 @@ class ConverterTarget: self.compile_opts: T.Dict[str, T.List[str]] = {} self.public_compile_opts: T.List[str] = [] self.pie = False + self.version: T.Optional[str] = None + self.soversion: T.Optional[str] = None # Project default override options (c_std, cpp_std, etc.) self.override_options: T.List[str] = [] @@ -356,6 +359,8 @@ class ConverterTarget: tgt = trace.targets.get(self.cmake_name) if tgt: self.depends_raw = trace.targets[self.cmake_name].depends + self.version = trace.targets[self.cmake_name].properties.get('VERSION', [None])[0] + self.soversion = trace.targets[self.cmake_name].properties.get('SOVERSION', [None])[0] rtgt = resolve_cmake_trace_targets(self.cmake_name, trace, self.env, clib_compiler=self.clib_compiler) self.includes += [Path(x) for x in rtgt.include_directories] @@ -363,6 +368,8 @@ class ConverterTarget: self.public_link_flags += rtgt.public_link_flags self.public_compile_opts += rtgt.public_compile_opts self.link_libraries += rtgt.libraries + self.depends_raw += rtgt.target_dependencies + self.link_targets += rtgt.target_dependencies elif self.type.upper() not in ['EXECUTABLE', 'OBJECT_LIBRARY']: mlog.warning('CMake: Target', mlog.bold(self.cmake_name), 'not found in CMake trace. This can lead to build errors') @@ -779,12 +786,12 @@ class ConverterCustomTarget: mlog.log(' -- depends: ', mlog.bold(str(self.depends))) class CMakeInterpreter: - def __init__(self, subdir: Path, install_prefix: Path, env: 'Environment', backend: 'Backend'): + def __init__(self, subdir: Path, env: 'Environment', backend: 'Backend'): self.subdir = subdir self.src_dir = Path(env.get_source_dir(), subdir) self.build_dir_rel = subdir / '__CMake_build' self.build_dir = Path(env.get_build_dir()) / self.build_dir_rel - self.install_prefix = install_prefix + self.install_prefix = Path(T.cast('str', env.coredata.optstore.get_value_for(OptionKey('prefix')))) self.env = env self.for_machine = MachineChoice.HOST # TODO make parameter self.backend_name = backend.name @@ -835,6 +842,8 @@ class CMakeInterpreter: cmake_args = [] cmake_args += cmake_get_generator_args(self.env) cmake_args += [f'-DCMAKE_INSTALL_PREFIX={self.install_prefix}'] + libdir = self.env.coredata.optstore.get_value_for(OptionKey('libdir')) + cmake_args += [f'-DCMAKE_INSTALL_LIBDIR={libdir}'] cmake_args += extra_cmake_options if not any(arg.startswith('-DCMAKE_BUILD_TYPE=') for arg in cmake_args): # Our build type is favored over any CMAKE_BUILD_TYPE environment variable @@ -844,6 +853,10 @@ class CMakeInterpreter: trace_args = self.trace.trace_args() cmcmp_args = [f'-DCMAKE_POLICY_WARNING_{x}=OFF' for x in DISABLE_POLICY_WARNINGS] + if mesonlib.version_compare(cmake_exe.version(), '>= 3.25'): + # Enable MSVC debug information variable + cmcmp_args += ['-DCMAKE_POLICY_CMP0141=NEW'] + self.fileapi.setup_request() # Run CMake @@ -957,17 +970,27 @@ class CMakeInterpreter: object_libs += [tgt] self.languages += [x for x in tgt.languages if x not in self.languages] - # Second pass: Detect object library dependencies + # Second pass: Populate link_with project internal targets + for tgt in self.targets: + for i in tgt.link_targets: + # Handle target-based link libraries + link_with = self.output_target_map.target(i) + if not link_with or isinstance(link_with, ConverterCustomTarget): + # Generated file etc. + continue + tgt.link_with.append(link_with) + + # Third pass: Detect object library dependencies for tgt in self.targets: tgt.process_object_libs(object_libs, self._object_lib_workaround) - # Third pass: Reassign dependencies to avoid some loops + # Fourth pass: Reassign dependencies to avoid some loops for tgt in self.targets: tgt.process_inter_target_dependencies() for ctgt in self.custom_targets: ctgt.process_inter_target_dependencies() - # Fourth pass: Remove rassigned dependencies + # Fifth pass: Remove reassigned dependencies for tgt in self.targets: tgt.cleanup_dependencies() @@ -1159,6 +1182,12 @@ class CMakeInterpreter: 'objects': [method(x, 'extract_all_objects') for x in objec_libs], } + # Only set version if we know it + if tgt.version: + tgt_kwargs['version'] = tgt.version + if tgt.soversion: + tgt_kwargs['soversion'] = tgt.soversion + # Only set if installed and only override if it is set if install_tgt and tgt.install_dir: tgt_kwargs['install_dir'] = tgt.install_dir diff --git a/mesonbuild/cmake/toolchain.py b/mesonbuild/cmake/toolchain.py index d410886..902d6b0 100644 --- a/mesonbuild/cmake/toolchain.py +++ b/mesonbuild/cmake/toolchain.py @@ -174,8 +174,23 @@ class CMakeToolchain: return p # Set the compiler variables + comp_obj = self.compilers.get('c', self.compilers.get('cpp', None)) + if comp_obj and comp_obj.get_id() == 'msvc': + debug_args = comp_obj.get_debug_args(True) + if '/Z7' in debug_args: + defaults['CMAKE_MSVC_DEBUG_INFORMATION_FORMAT'] = ['Embedded'] + elif '/Zi' in debug_args: + defaults['CMAKE_MSVC_DEBUG_INFORMATION_FORMAT'] = ['ProgramDatabase'] + elif '/ZI' in debug_args: + defaults['CMAKE_MSVC_DEBUG_INFORMATION_FORMAT'] = ['EditAndContinue'] + for lang, comp_obj in self.compilers.items(): - prefix = 'CMAKE_{}_'.format(language_map.get(lang, lang.upper())) + language = language_map.get(lang, None) + + if not language: + continue # unsupported language + + prefix = 'CMAKE_{}_'.format(language) exe_list = comp_obj.get_exelist() if not exe_list: @@ -211,7 +226,7 @@ class CMakeToolchain: # Generate the CMakeLists.txt mlog.debug('CMake Toolchain: Calling CMake once to generate the compiler state') languages = list(self.compilers.keys()) - lang_ids = [language_map.get(x, x.upper()) for x in languages] + lang_ids = [language_map.get(x) for x in languages if x in language_map] cmake_content = dedent(f''' cmake_minimum_required(VERSION 3.10) project(CompInfo {' '.join(lang_ids)}) diff --git a/mesonbuild/cmake/tracetargets.py b/mesonbuild/cmake/tracetargets.py index 2cc0c17..b1ecabd 100644 --- a/mesonbuild/cmake/tracetargets.py +++ b/mesonbuild/cmake/tracetargets.py @@ -45,6 +45,7 @@ class ResolvedTarget: self.public_link_flags: T.List[str] = [] self.public_compile_opts: T.List[str] = [] self.libraries: T.List[str] = [] + self.target_dependencies: T.List[str] = [] def resolve_cmake_trace_targets(target_name: str, trace: 'CMakeTraceParser', @@ -86,6 +87,7 @@ def resolve_cmake_trace_targets(target_name: str, curr_path = Path(*path_to_framework) framework_path = curr_path.parent framework_name = curr_path.stem + res.public_compile_opts += [f"-F{framework_path}"] res.libraries += [f'-F{framework_path}', '-framework', framework_name] else: res.libraries += [curr] @@ -94,7 +96,7 @@ def resolve_cmake_trace_targets(target_name: str, # CMake brute-forces a combination of prefix/suffix combinations to find the # right library. Assume any bare argument passed which is not also a CMake # target must be a system library we should try to link against. - flib = clib_compiler.find_library(curr, env, []) + flib = clib_compiler.find_library(curr, []) if flib is not None: res.libraries += flib else: @@ -144,9 +146,13 @@ def resolve_cmake_trace_targets(target_name: str, targets += [x for x in tgt.properties['IMPORTED_LOCATION'] if x] if 'LINK_LIBRARIES' in tgt.properties: - targets += [x for x in tgt.properties['LINK_LIBRARIES'] if x] + link_libraries = [x for x in tgt.properties['LINK_LIBRARIES'] if x] + targets += link_libraries + res.target_dependencies += link_libraries if 'INTERFACE_LINK_LIBRARIES' in tgt.properties: - targets += [x for x in tgt.properties['INTERFACE_LINK_LIBRARIES'] if x] + link_libraries = [x for x in tgt.properties['INTERFACE_LINK_LIBRARIES'] if x] + targets += link_libraries + res.target_dependencies += link_libraries if f'IMPORTED_LINK_DEPENDENT_LIBRARIES_{cfg}' in tgt.properties: targets += [x for x in tgt.properties[f'IMPORTED_LINK_DEPENDENT_LIBRARIES_{cfg}'] if x] diff --git a/mesonbuild/cmdline.py b/mesonbuild/cmdline.py new file mode 100644 index 0000000..04f7496 --- /dev/null +++ b/mesonbuild/cmdline.py @@ -0,0 +1,182 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2013-2025 The Meson development team + +from __future__ import annotations + +import argparse +import ast +import configparser +import os +import shlex +import typing as T +from itertools import chain + +from . import options +from .mesonlib import MesonException +from .options import OptionKey + +if T.TYPE_CHECKING: + from typing_extensions import Protocol + + # typeshed + StrOrBytesPath = T.Union[str, bytes, os.PathLike[str], os.PathLike[bytes]] + + class SharedCMDOptions(Protocol): + """Representation of command line options from Meson setup, configure, + and dist. + + :param cmd_line_options: command line options parsed into an OptionKey: + str mapping + """ + + cmd_line_options: T.Dict[OptionKey, T.Optional[str]] + cross_file: T.List[str] + native_file: T.List[str] + + +class CmdLineFileParser(configparser.ConfigParser): + def __init__(self) -> None: + # We don't want ':' as key delimiter, otherwise it would break when + # storing subproject options like "subproject:option=value" + super().__init__(delimiters=['='], interpolation=None) + + def read(self, filenames: T.Union['StrOrBytesPath', T.Iterable['StrOrBytesPath']], encoding: T.Optional[str] = 'utf-8') -> T.List[str]: + return super().read(filenames, encoding) + + def optionxform(self, optionstr: str) -> str: + # Don't call str.lower() on keys + return optionstr + + +def get_cmd_line_file(build_dir: str) -> str: + return os.path.join(build_dir, 'meson-private', 'cmd_line.txt') + +def read_cmd_line_file(build_dir: str, options: SharedCMDOptions) -> None: + filename = get_cmd_line_file(build_dir) + if not os.path.isfile(filename): + return + + config = CmdLineFileParser() + config.read(filename) + + # Do a copy because config is not really a dict. options.cmd_line_options + # overrides values from the file. + d = {OptionKey.from_string(k): v for k, v in config['options'].items()} + d.update(options.cmd_line_options) + options.cmd_line_options = d + + properties = config['properties'] + if not options.cross_file: + options.cross_file = ast.literal_eval(properties.get('cross_file', '[]')) + if not options.native_file: + # This will be a string in the form: "['first', 'second', ...]", use + # literal_eval to get it into the list of strings. + options.native_file = ast.literal_eval(properties.get('native_file', '[]')) + +def write_cmd_line_file(build_dir: str, options: SharedCMDOptions) -> None: + filename = get_cmd_line_file(build_dir) + config = CmdLineFileParser() + + properties: T.Dict[str, T.List[str]] = {} + if options.cross_file: + properties['cross_file'] = options.cross_file + if options.native_file: + properties['native_file'] = options.native_file + + config['options'] = {str(k): str(v) for k, v in options.cmd_line_options.items()} + config['properties'] = {k: repr(v) for k, v in properties.items()} + with open(filename, 'w', encoding='utf-8') as f: + config.write(f) + +def update_cmd_line_file(build_dir: str, options: SharedCMDOptions) -> None: + filename = get_cmd_line_file(build_dir) + config = CmdLineFileParser() + config.read(filename) + for k, v in options.cmd_line_options.items(): + keystr = str(k) + if v is not None: + config['options'][keystr] = str(v) + elif keystr in config['options']: + del config['options'][keystr] + + with open(filename, 'w', encoding='utf-8') as f: + config.write(f) + +def format_cmd_line_options(options: SharedCMDOptions) -> str: + cmdline = ['-D{}={}'.format(str(k), v) for k, v in options.cmd_line_options.items()] + if options.cross_file: + cmdline += [f'--cross-file={f}' for f in options.cross_file] + if options.native_file: + cmdline += [f'--native-file={f}' for f in options.native_file] + return ' '.join([shlex.quote(x) for x in cmdline]) + + +class KeyNoneAction(argparse.Action): + """ + Custom argparse Action that stores values in a dictionary as keys with value None. + """ + + def __init__(self, option_strings: str, dest: str, nargs: T.Optional[T.Union[int, str]] = None, **kwargs: T.Any) -> None: + assert nargs is None or nargs == 1 + super().__init__(option_strings, dest, nargs=1, **kwargs) + + def __call__(self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, + arg: T.List[str], option_string: str = None) -> None: # type: ignore[override] + current_dict = getattr(namespace, self.dest) + if current_dict is None: + current_dict = {} + setattr(namespace, self.dest, current_dict) + + key = OptionKey.from_string(arg[0]) + current_dict[key] = None + + +class KeyValueAction(argparse.Action): + """ + Custom argparse Action that parses KEY=VAL arguments and stores them in a dictionary. + """ + + def __init__(self, option_strings: str, dest: str, nargs: T.Optional[T.Union[int, str]] = None, **kwargs: T.Any) -> None: + assert nargs is None or nargs == 1 + super().__init__(option_strings, dest, nargs=1, **kwargs) + + def __call__(self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, + arg: T.List[str], option_string: str = None) -> None: # type: ignore[override] + current_dict = getattr(namespace, self.dest) + if current_dict is None: + current_dict = {} + setattr(namespace, self.dest, current_dict) + + try: + keystr, value = arg[0].split('=', 1) + key = OptionKey.from_string(keystr) + current_dict[key] = value + except ValueError: + parser.error(f'The argument for option {option_string!r} must be in OPTION=VALUE format.') + + +def register_builtin_arguments(parser: argparse.ArgumentParser) -> None: + for n, b in options.BUILTIN_OPTIONS.items(): + options.option_to_argparse(b, n, parser, '') + for n, b in options.BUILTIN_OPTIONS_PER_MACHINE.items(): + options.option_to_argparse(b, n, parser, ' (just for host machine)') + options.option_to_argparse(b, n.as_build(), parser, ' (just for build machine)') + parser.add_argument('-D', action=KeyValueAction, dest='cmd_line_options', default={}, metavar="option=value", + help='Set the value of an option, can be used several times to set multiple options.') + +def parse_cmd_line_options(args: SharedCMDOptions) -> None: + # Merge builtin options set with --option into the dict. + for key in chain( + options.BUILTIN_OPTIONS.keys(), + (k.as_build() for k in options.BUILTIN_OPTIONS_PER_MACHINE.keys()), + options.BUILTIN_OPTIONS_PER_MACHINE.keys(), + ): + name = str(key) + value = getattr(args, name, None) + if value is not None: + if key in args.cmd_line_options: + cmdline_name = options.argparse_name_to_arg(name) + raise MesonException( + f'Got argument {name} as both -D{name} and {cmdline_name}. Pick one.') + args.cmd_line_options[key] = value + delattr(args, name) diff --git a/mesonbuild/compilers/__init__.py b/mesonbuild/compilers/__init__.py index aab761a..f645090 100644 --- a/mesonbuild/compilers/__init__.py +++ b/mesonbuild/compilers/__init__.py @@ -18,6 +18,7 @@ __all__ = [ 'is_library', 'is_llvm_ir', 'is_object', + 'is_separate_compile', 'is_source', 'is_java', 'is_known_suffix', @@ -62,6 +63,7 @@ from .compilers import ( is_object, is_library, is_known_suffix, + is_separate_compile, lang_suffixes, LANGUAGES_USING_LDFLAGS, sort_clink, diff --git a/mesonbuild/compilers/asm.py b/mesonbuild/compilers/asm.py index d358ca9..c298933 100644 --- a/mesonbuild/compilers/asm.py +++ b/mesonbuild/compilers/asm.py @@ -6,14 +6,14 @@ import typing as T from ..mesonlib import EnvironmentException, get_meson_command from ..options import OptionKey from .compilers import Compiler +from ..linkers.linkers import VisualStudioLikeLinkerMixin from .mixins.metrowerks import MetrowerksCompiler, mwasmarm_instruction_set_args, mwasmeppc_instruction_set_args from .mixins.ti import TICompiler if T.TYPE_CHECKING: - from ..environment import Environment from ..linkers.linkers import DynamicLinker from ..mesonlib import MachineChoice - from ..envconfig import MachineInfo + from ..environment import Environment nasm_optimization_args: T.Dict[str, T.List[str]] = { 'plain': [], @@ -26,7 +26,26 @@ nasm_optimization_args: T.Dict[str, T.List[str]] = { } -class NasmCompiler(Compiler): +class ASMCompiler(Compiler): + + """Shared base class for all ASM Compilers (Assemblers)""" + + _SUPPORTED_ARCHES: T.Set[str] = set() + + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, + for_machine: MachineChoice, env: Environment, + linker: T.Optional[DynamicLinker] = None, + full_version: T.Optional[str] = None): + info = env.machines[for_machine] + if self._SUPPORTED_ARCHES and info.cpu_family not in self._SUPPORTED_ARCHES: + raise EnvironmentException(f'ASM Compiler {self.id} does not support building for {info.cpu_family} CPU family.') + super().__init__(ccache, exelist, version, for_machine, env, linker, full_version) + + def sanity_check(self, work_dir: str) -> None: + return None + + +class NasmCompiler(ASMCompiler): language = 'nasm' id = 'nasm' @@ -39,15 +58,15 @@ class NasmCompiler(Compiler): 'mtd': ['/DEFAULTLIB:libucrtd.lib', '/DEFAULTLIB:libvcruntimed.lib', '/DEFAULTLIB:libcmtd.lib'], } + _SUPPORTED_ARCHES = {'x86', 'x86_64'} + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, - for_machine: 'MachineChoice', info: 'MachineInfo', + for_machine: 'MachineChoice', env: Environment, linker: T.Optional['DynamicLinker'] = None, - full_version: T.Optional[str] = None, is_cross: bool = False): - super().__init__(ccache, exelist, version, for_machine, info, linker, full_version, is_cross) - self.links_with_msvc = False - if 'link' in self.linker.id: + full_version: T.Optional[str] = None): + super().__init__(ccache, exelist, version, for_machine, env, linker, full_version) + if isinstance(self.linker, VisualStudioLikeLinkerMixin): self.base_options.add(OptionKey('b_vscrt')) - self.links_with_msvc = True def needs_static_linker(self) -> bool: return True @@ -96,10 +115,6 @@ class NasmCompiler(Compiler): def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]: return ['-MD', outfile, '-MQ', outtarget] - def sanity_check(self, work_dir: str, environment: 'Environment') -> None: - if self.info.cpu_family not in {'x86', 'x86_64'}: - raise EnvironmentException(f'ASM compiler {self.id!r} does not support {self.info.cpu_family} CPU family') - def get_pic_args(self) -> T.List[str]: return [] @@ -122,7 +137,7 @@ class NasmCompiler(Compiler): # require this, otherwise it'll fail to find # _WinMain or _DllMainCRTStartup. def get_crt_link_args(self, crt_val: str, buildtype: str) -> T.List[str]: - if not self.info.is_windows(): + if not isinstance(self.linker, VisualStudioLikeLinkerMixin): return [] return self.crt_args[self.get_crt_val(crt_val, buildtype)] @@ -140,7 +155,7 @@ class YasmCompiler(NasmCompiler): def get_debug_args(self, is_debug: bool) -> T.List[str]: if is_debug: - if self.info.is_windows() and self.links_with_msvc: + if isinstance(self.linker, VisualStudioLikeLinkerMixin): return ['-g', 'cv8'] elif self.info.is_darwin(): return ['-g', 'null'] @@ -152,10 +167,12 @@ class YasmCompiler(NasmCompiler): return ['--depfile', outfile] # https://learn.microsoft.com/en-us/cpp/assembler/masm/ml-and-ml64-command-line-reference -class MasmCompiler(Compiler): +class MasmCompiler(ASMCompiler): language = 'masm' id = 'ml' + _SUPPORTED_ARCHES = {'x86', 'x86_64'} + def get_compile_only_args(self) -> T.List[str]: return ['/c'] @@ -183,10 +200,6 @@ class MasmCompiler(Compiler): return ['/Zi'] return [] - def sanity_check(self, work_dir: str, environment: 'Environment') -> None: - if self.info.cpu_family not in {'x86', 'x86_64'}: - raise EnvironmentException(f'ASM compiler {self.id!r} does not support {self.info.cpu_family} CPU family') - def get_pic_args(self) -> T.List[str]: return [] @@ -210,9 +223,10 @@ class MasmCompiler(Compiler): # https://learn.microsoft.com/en-us/cpp/assembler/arm/arm-assembler-command-line-reference -class MasmARMCompiler(Compiler): +class MasmARMCompiler(ASMCompiler): language = 'masm' id = 'armasm' + _SUPPORTED_ARCHES = {'arm', 'aarch64'} def needs_static_linker(self) -> bool: return True @@ -234,10 +248,6 @@ class MasmARMCompiler(Compiler): return ['-g'] return [] - def sanity_check(self, work_dir: str, environment: 'Environment') -> None: - if self.info.cpu_family not in {'arm', 'aarch64'}: - raise EnvironmentException(f'ASM compiler {self.id!r} does not support {self.info.cpu_family} CPU family') - def get_pic_args(self) -> T.List[str]: return [] @@ -256,19 +266,23 @@ class MasmARMCompiler(Compiler): def get_crt_compile_args(self, crt_val: str, buildtype: str) -> T.List[str]: return [] + def get_depfile_format(self) -> str: + return 'msvc' + def depfile_for_object(self, objfile: str) -> T.Optional[str]: return None # https://downloads.ti.com/docs/esd/SPRUI04/ -class TILinearAsmCompiler(TICompiler, Compiler): +class TILinearAsmCompiler(TICompiler, ASMCompiler): language = 'linearasm' + _SUPPORTED_ARCHES = {'c6000'} def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, - for_machine: MachineChoice, info: MachineInfo, + for_machine: MachineChoice, env: Environment, linker: T.Optional[DynamicLinker] = None, - full_version: T.Optional[str] = None, is_cross: bool = False): - Compiler.__init__(self, ccache, exelist, version, for_machine, info, linker, full_version, is_cross) + full_version: T.Optional[str] = None): + ASMCompiler.__init__(self, ccache, exelist, version, for_machine, env, linker, full_version) TICompiler.__init__(self) def needs_static_linker(self) -> bool: @@ -280,22 +294,18 @@ class TILinearAsmCompiler(TICompiler, Compiler): def get_crt_compile_args(self, crt_val: str, buildtype: str) -> T.List[str]: return [] - def sanity_check(self, work_dir: str, environment: Environment) -> None: - if self.info.cpu_family not in {'c6000'}: - raise EnvironmentException(f'TI Linear ASM compiler {self.id!r} does not support {self.info.cpu_family} CPU family') - def get_depfile_suffix(self) -> str: return 'd' -class MetrowerksAsmCompiler(MetrowerksCompiler, Compiler): +class MetrowerksAsmCompiler(MetrowerksCompiler, ASMCompiler): language = 'nasm' def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, - for_machine: 'MachineChoice', info: 'MachineInfo', + for_machine: 'MachineChoice', env: Environment, linker: T.Optional['DynamicLinker'] = None, - full_version: T.Optional[str] = None, is_cross: bool = False): - Compiler.__init__(self, ccache, exelist, version, for_machine, info, linker, full_version, is_cross) + full_version: T.Optional[str] = None): + ASMCompiler.__init__(self, ccache, exelist, version, for_machine, env, linker, full_version) MetrowerksCompiler.__init__(self) self.warn_args: T.Dict[str, T.List[str]] = { @@ -321,21 +331,15 @@ class MetrowerksAsmCompiler(MetrowerksCompiler, Compiler): class MetrowerksAsmCompilerARM(MetrowerksAsmCompiler): id = 'mwasmarm' + _SUPPORTED_ARCHES = {'arm'} def get_instruction_set_args(self, instruction_set: str) -> T.Optional[T.List[str]]: return mwasmarm_instruction_set_args.get(instruction_set, None) - def sanity_check(self, work_dir: str, environment: 'Environment') -> None: - if self.info.cpu_family not in {'arm'}: - raise EnvironmentException(f'ASM compiler {self.id!r} does not support {self.info.cpu_family} CPU family') - class MetrowerksAsmCompilerEmbeddedPowerPC(MetrowerksAsmCompiler): id = 'mwasmeppc' + _SUPPORTED_ARCHES = {'ppc'} def get_instruction_set_args(self, instruction_set: str) -> T.Optional[T.List[str]]: return mwasmeppc_instruction_set_args.get(instruction_set, None) - - def sanity_check(self, work_dir: str, environment: 'Environment') -> None: - if self.info.cpu_family not in {'ppc'}: - raise EnvironmentException(f'ASM compiler {self.id!r} does not support {self.info.cpu_family} CPU family') diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index 7a2fec5..d60ed99 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -15,7 +15,7 @@ from .c_function_attributes import C_FUNC_ATTRIBUTES from .mixins.apple import AppleCompilerMixin, AppleCStdsMixin from .mixins.clike import CLikeCompiler from .mixins.ccrx import CcrxCompiler -from .mixins.xc16 import Xc16Compiler +from .mixins.microchip import Xc16Compiler, Xc32Compiler, Xc32CStds from .mixins.compcert import CompCertCompiler from .mixins.ti import TICompiler from .mixins.arm import ArmCompiler, ArmclangCompiler @@ -39,7 +39,6 @@ from .compilers import ( if T.TYPE_CHECKING: from ..options import MutableKeyedOptionDictType from ..dependencies import Dependency - from ..envconfig import MachineInfo from ..environment import Environment from ..linkers.linkers import DynamicLinker from ..mesonlib import MachineChoice @@ -64,24 +63,22 @@ class CCompiler(CLikeCompiler, Compiler): language = 'c' - def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, - info: 'MachineInfo', - linker: T.Optional['DynamicLinker'] = None, + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): # If a child ObjC or CPP class has already set it, don't set it ourselves - Compiler.__init__(self, ccache, exelist, version, for_machine, info, - is_cross=is_cross, full_version=full_version, linker=linker) + Compiler.__init__(self, ccache, exelist, version, for_machine, env, + full_version=full_version, linker=linker) CLikeCompiler.__init__(self) def get_no_stdinc_args(self) -> T.List[str]: return ['-nostdinc'] - def sanity_check(self, work_dir: str, environment: 'Environment') -> None: + def sanity_check(self, work_dir: str) -> None: code = 'int main(void) { int class=0; return class; }\n' - return self._sanity_check_impl(work_dir, environment, 'sanitycheckc.c', code) + return self._sanity_check_impl(work_dir, 'sanitycheckc.c', code) - def has_header_symbol(self, hname: str, symbol: str, prefix: str, - env: 'Environment', *, + def has_header_symbol(self, hname: str, symbol: str, prefix: str, *, extra_args: T.Union[None, T.List[str], T.Callable[['CompileCheckMode'], T.List[str]]] = None, dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: fargs = {'prefix': prefix, 'header': hname, 'symbol': symbol} @@ -94,7 +91,7 @@ class CCompiler(CLikeCompiler, Compiler): #endif return 0; }}''' - return self.compiles(t.format(**fargs), env, extra_args=extra_args, + return self.compiles(t.format(**fargs), extra_args=extra_args, dependencies=dependencies) def get_options(self) -> 'MutableKeyedOptionDictType': @@ -108,12 +105,11 @@ class CCompiler(CLikeCompiler, Compiler): class ClangCCompiler(ClangCStds, ClangCompiler, CCompiler): - def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, - info: 'MachineInfo', - linker: T.Optional['DynamicLinker'] = None, + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, linker: T.Optional['DynamicLinker'] = None, defines: T.Optional[T.Dict[str, str]] = None, full_version: T.Optional[str] = None): - CCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, info, linker=linker, full_version=full_version) + CCompiler.__init__(self, ccache, exelist, version, for_machine, env, linker=linker, full_version=full_version) ClangCompiler.__init__(self, defines) default_warn_args = ['-Wall', '-Winvalid-pch'] self.warn_args = {'0': [], @@ -132,17 +128,17 @@ class ClangCCompiler(ClangCStds, ClangCompiler, CCompiler): gnu_winlibs) return opts - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: args = [] - std = self.get_compileropt_value('std', env, target, subproject) + std = self.get_compileropt_value('std', target, subproject) assert isinstance(std, str) if std != 'none': args.append('-std=' + std) return args - def get_option_link_args(self, target: 'BuildTarget', env: 'Environment', subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_link_args(self, target: 'BuildTarget', subproject: T.Optional[str] = None) -> T.List[str]: if self.info.is_windows() or self.info.is_cygwin(): - retval = self.get_compileropt_value('winlibs', env, target, subproject) + retval = self.get_compileropt_value('winlibs', target, subproject) assert isinstance(retval, list) libs: T.List[str] = retval.copy() for l in libs: @@ -179,18 +175,16 @@ class EmscriptenCCompiler(EmscriptenMixin, ClangCCompiler): _C2X_VERSION = '>=1.38.35' # 1.38.35 used Clang 9.0.0 _C23_VERSION = '>=3.1.45' # 3.1.45 used Clang 18.0.0 - def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, - info: 'MachineInfo', - linker: T.Optional['DynamicLinker'] = None, + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, linker: T.Optional['DynamicLinker'] = None, defines: T.Optional[T.Dict[str, str]] = None, full_version: T.Optional[str] = None): - if not is_cross: + if not env.is_cross_build(for_machine): raise MesonException('Emscripten compiler can only be used for cross compilation.') if not version_compare(version, '>=1.39.19'): raise MesonException('Meson requires Emscripten >= 1.39.19') - ClangCCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, - info, linker=linker, - defines=defines, full_version=full_version) + ClangCCompiler.__init__(self, ccache, exelist, version, for_machine, env, + linker=linker, defines=defines, full_version=full_version) class ArmclangCCompiler(ArmclangCompiler, CCompiler): @@ -198,12 +192,11 @@ class ArmclangCCompiler(ArmclangCompiler, CCompiler): Keil armclang ''' - def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, - info: 'MachineInfo', - linker: T.Optional['DynamicLinker'] = None, + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - CCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, - info, linker=linker, full_version=full_version) + CCompiler.__init__(self, ccache, exelist, version, for_machine, + env, linker=linker, full_version=full_version) ArmclangCompiler.__init__(self) default_warn_args = ['-Wall', '-Winvalid-pch'] self.warn_args = {'0': [], @@ -220,15 +213,15 @@ class ArmclangCCompiler(ArmclangCompiler, CCompiler): std_opt.set_versions(['c90', 'c99', 'c11'], gnu=True) return opts - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: args = [] - std = self.get_compileropt_value('std', env, target, subproject) + std = self.get_compileropt_value('std', target, subproject) assert isinstance(std, str) if std != 'none': args.append('-std=' + std) return args - def get_option_link_args(self, target: 'BuildTarget', env: 'Environment', subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_link_args(self, target: 'BuildTarget', subproject: T.Optional[str] = None) -> T.List[str]: return [] @@ -236,12 +229,11 @@ class GnuCCompiler(GnuCStds, GnuCompiler, CCompiler): _INVALID_PCH_VERSION = ">=3.4.0" - def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, - info: 'MachineInfo', - linker: T.Optional['DynamicLinker'] = None, + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, linker: T.Optional['DynamicLinker'] = None, defines: T.Optional[T.Dict[str, str]] = None, full_version: T.Optional[str] = None): - CCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, info, linker=linker, full_version=full_version) + CCompiler.__init__(self, ccache, exelist, version, for_machine, env, linker=linker, full_version=full_version) GnuCompiler.__init__(self, defines) default_warn_args = ['-Wall'] if version_compare(self.version, self._INVALID_PCH_VERSION): @@ -264,19 +256,19 @@ class GnuCCompiler(GnuCStds, GnuCompiler, CCompiler): gnu_winlibs) return opts - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: args = [] key = OptionKey('c_std', machine=self.for_machine) - std = self.get_compileropt_value(key, env, target, subproject) + std = self.get_compileropt_value(key, target, subproject) assert isinstance(std, str) if std != 'none': args.append('-std=' + std) return args - def get_option_link_args(self, target: 'BuildTarget', env: 'Environment', subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_link_args(self, target: 'BuildTarget', subproject: T.Optional[str] = None) -> T.List[str]: if self.info.is_windows() or self.info.is_cygwin(): # without a typeddict mypy can't figure this out - retval = self.get_compileropt_value('winlibs', env, target, subproject) + retval = self.get_compileropt_value('winlibs', target, subproject) assert isinstance(retval, list) libs: T.List[str] = retval.copy() @@ -290,12 +282,11 @@ class GnuCCompiler(GnuCStds, GnuCompiler, CCompiler): class PGICCompiler(PGICompiler, CCompiler): - def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, - info: 'MachineInfo', - linker: T.Optional['DynamicLinker'] = None, + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - CCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, - info, linker=linker, full_version=full_version) + CCompiler.__init__(self, ccache, exelist, version, for_machine, + env, linker=linker, full_version=full_version) PGICompiler.__init__(self) @@ -303,12 +294,11 @@ class NvidiaHPC_CCompiler(PGICompiler, CCompiler): id = 'nvidia_hpc' - def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, - info: 'MachineInfo', - linker: T.Optional['DynamicLinker'] = None, + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - CCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, - info, linker=linker, full_version=full_version) + CCompiler.__init__(self, ccache, exelist, version, for_machine, + env, linker=linker, full_version=full_version) PGICompiler.__init__(self) def get_options(self) -> 'MutableKeyedOptionDictType': @@ -319,15 +309,22 @@ class NvidiaHPC_CCompiler(PGICompiler, CCompiler): std_opt.set_versions(cppstd_choices, gnu=True) return opts + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: + args: T.List[str] = [] + std = self.get_compileropt_value('std', target, subproject) + assert isinstance(std, str) + if std != 'none': + args.append('-std=' + std) + return args + class ElbrusCCompiler(ElbrusCompiler, CCompiler): - def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, - info: 'MachineInfo', - linker: T.Optional['DynamicLinker'] = None, + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, linker: T.Optional['DynamicLinker'] = None, defines: T.Optional[T.Dict[str, str]] = None, full_version: T.Optional[str] = None): - CCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, - info, linker=linker, full_version=full_version) + CCompiler.__init__(self, ccache, exelist, version, for_machine, + env, linker=linker, full_version=full_version) ElbrusCompiler.__init__(self) def get_options(self) -> 'MutableKeyedOptionDictType': @@ -350,24 +347,20 @@ class ElbrusCCompiler(ElbrusCompiler, CCompiler): # Elbrus C compiler does not have lchmod, but there is only linker warning, not compiler error. # So we should explicitly fail at this case. - def has_function(self, funcname: str, prefix: str, env: 'Environment', *, + def has_function(self, funcname: str, prefix: str, *, extra_args: T.Optional[T.List[str]] = None, dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: if funcname == 'lchmod': return False, False - else: - return super().has_function(funcname, prefix, env, - extra_args=extra_args, - dependencies=dependencies) + return super().has_function(funcname, prefix, extra_args=extra_args, dependencies=dependencies) class IntelCCompiler(IntelGnuLikeCompiler, CCompiler): - def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, - info: 'MachineInfo', - linker: T.Optional['DynamicLinker'] = None, + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - CCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, - info, linker=linker, full_version=full_version) + CCompiler.__init__(self, ccache, exelist, version, for_machine, + env, linker=linker, full_version=full_version) IntelGnuLikeCompiler.__init__(self) self.lang_header = 'c-header' default_warn_args = ['-Wall', '-w3'] @@ -388,9 +381,9 @@ class IntelCCompiler(IntelGnuLikeCompiler, CCompiler): std_opt.set_versions(stds, gnu=True) return opts - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: args: T.List[str] = [] - std = self.get_compileropt_value('std', env, target, subproject) + std = self.get_compileropt_value('std', target, subproject) assert isinstance(std, str) if std != 'none': args.append('-std=' + std) @@ -415,8 +408,8 @@ class VisualStudioLikeCCompilerMixin(CompilerMixinBase): msvc_winlibs) return opts - def get_option_link_args(self, target: 'BuildTarget', env: 'Environment', subproject: T.Optional[str] = None) -> T.List[str]: - retval = self.get_compileropt_value('winlibs', env, target, subproject) + def get_option_link_args(self, target: 'BuildTarget', subproject: T.Optional[str] = None) -> T.List[str]: + retval = self.get_compileropt_value('winlibs', target, subproject) assert isinstance(retval, list) libs: T.List[str] = retval.copy() for l in libs: @@ -430,12 +423,11 @@ class VisualStudioCCompiler(MSVCCompiler, VisualStudioLikeCCompilerMixin, CCompi _C17_VERSION = '>=19.28' def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, - is_cross: bool, info: 'MachineInfo', target: str, + env: Environment, target: str, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - CCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, - info, linker=linker, - full_version=full_version) + CCompiler.__init__(self, ccache, exelist, version, for_machine, + env, linker=linker, full_version=full_version) MSVCCompiler.__init__(self, target) def get_options(self) -> 'MutableKeyedOptionDictType': @@ -451,9 +443,9 @@ class VisualStudioCCompiler(MSVCCompiler, VisualStudioLikeCCompilerMixin, CCompi std_opt.set_versions(stds, gnu=True, gnu_deprecated=True) return opts - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: args = [] - std = self.get_compileropt_value('std', env, target, subproject) + std = self.get_compileropt_value('std', target, subproject) # As of MVSC 16.8, /std:c11 and /std:c17 are the only valid C standard options. if std in {'c11'}: @@ -465,16 +457,15 @@ class VisualStudioCCompiler(MSVCCompiler, VisualStudioLikeCCompilerMixin, CCompi 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, + env: Environment, target: str, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - CCompiler.__init__(self, [], exelist, version, for_machine, is_cross, - info, linker=linker, - full_version=full_version) + CCompiler.__init__(self, [], exelist, version, for_machine, + env, linker=linker, full_version=full_version) ClangClCompiler.__init__(self, target) - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: - std = self.get_compileropt_value('std', env, target, subproject) + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: + std = self.get_compileropt_value('std', target, subproject) assert isinstance(std, str) if std != "none": return [f'/clang:-std={std}'] @@ -486,12 +477,11 @@ class IntelClCCompiler(IntelVisualStudioLikeCompiler, VisualStudioLikeCCompilerM """Intel "ICL" compiler abstraction.""" def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, - is_cross: bool, info: 'MachineInfo', target: str, + env: Environment, target: str, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - CCompiler.__init__(self, [], exelist, version, for_machine, is_cross, - info, linker=linker, - full_version=full_version) + CCompiler.__init__(self, [], exelist, version, for_machine, + env, linker=linker, full_version=full_version) IntelVisualStudioLikeCompiler.__init__(self, target) def get_options(self) -> 'MutableKeyedOptionDictType': @@ -502,9 +492,9 @@ class IntelClCCompiler(IntelVisualStudioLikeCompiler, VisualStudioLikeCCompilerM std_opt.set_versions(['c89', 'c99', 'c11']) return opts - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: args: T.List[str] = [] - std = self.get_compileropt_value('winlibs', env, target, subproject) + std = self.get_compileropt_value('std', target, subproject) assert isinstance(std, str) if std == 'c89': mlog.log("ICL doesn't explicitly implement c89, setting the standard to 'none', which is close.", once=True) @@ -520,12 +510,11 @@ class IntelLLVMClCCompiler(IntelClCCompiler): class ArmCCompiler(ArmCompiler, CCompiler): def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, - is_cross: bool, info: 'MachineInfo', + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - CCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, - info, linker=linker, - full_version=full_version) + CCompiler.__init__(self, ccache, exelist, version, for_machine, + env, linker=linker, full_version=full_version) ArmCompiler.__init__(self) def get_options(self) -> 'MutableKeyedOptionDictType': @@ -536,9 +525,9 @@ class ArmCCompiler(ArmCompiler, CCompiler): std_opt.set_versions(['c89', 'c99', 'c11']) return opts - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: args = [] - std = self.get_compileropt_value('std', env, target, subproject) + std = self.get_compileropt_value('std', target, subproject) assert isinstance(std, str) if std != 'none': args.append('--' + std) @@ -547,11 +536,11 @@ class ArmCCompiler(ArmCompiler, CCompiler): class CcrxCCompiler(CcrxCompiler, CCompiler): def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, - is_cross: bool, info: 'MachineInfo', + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - CCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, - info, linker=linker, full_version=full_version) + CCompiler.__init__(self, ccache, exelist, version, for_machine, + env, linker=linker, full_version=full_version) CcrxCompiler.__init__(self) # Override CCompiler.get_always_args @@ -569,9 +558,9 @@ class CcrxCCompiler(CcrxCompiler, CCompiler): def get_no_stdinc_args(self) -> T.List[str]: return [] - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: args = [] - std = self.get_compileropt_value('std', env, target, subproject) + std = self.get_compileropt_value('std', target, subproject) assert isinstance(std, str) if std == 'c89': args.append('-lang=c') @@ -599,11 +588,11 @@ class CcrxCCompiler(CcrxCompiler, CCompiler): class Xc16CCompiler(Xc16Compiler, CCompiler): def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, - is_cross: bool, info: 'MachineInfo', + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - CCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, - info, linker=linker, full_version=full_version) + CCompiler.__init__(self, ccache, exelist, version, for_machine, + env, linker=linker, full_version=full_version) Xc16Compiler.__init__(self) def get_options(self) -> 'MutableKeyedOptionDictType': @@ -617,9 +606,9 @@ class Xc16CCompiler(Xc16Compiler, CCompiler): def get_no_stdinc_args(self) -> T.List[str]: return [] - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: args = [] - std = self.get_compileropt_value('std', env, target, subproject) + std = self.get_compileropt_value('std', target, subproject) assert isinstance(std, str) if std != 'none': args.append('-ansi') @@ -643,13 +632,27 @@ class Xc16CCompiler(Xc16Compiler, CCompiler): path = '.' return ['-I' + path] + +class Xc32CCompiler(Xc32CStds, Xc32Compiler, GnuCCompiler): + + """Microchip XC32 C compiler.""" + + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, linker: T.Optional[DynamicLinker] = None, + defines: T.Optional[T.Dict[str, str]] = None, + full_version: T.Optional[str] = None): + GnuCCompiler.__init__(self, ccache, exelist, version, for_machine, + env, linker=linker, full_version=full_version, defines=defines) + Xc32Compiler.__init__(self) + + class CompCertCCompiler(CompCertCompiler, CCompiler): def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, - is_cross: bool, info: 'MachineInfo', + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - CCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, - info, linker=linker, full_version=full_version) + CCompiler.__init__(self, ccache, exelist, version, for_machine, + env, linker=linker, full_version=full_version) CompCertCompiler.__init__(self) def get_options(self) -> 'MutableKeyedOptionDictType': @@ -676,11 +679,11 @@ class CompCertCCompiler(CompCertCompiler, CCompiler): class TICCompiler(TICompiler, CCompiler): def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, - is_cross: bool, info: 'MachineInfo', + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - CCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, - info, linker=linker, full_version=full_version) + CCompiler.__init__(self, ccache, exelist, version, for_machine, + env, linker=linker, full_version=full_version) TICompiler.__init__(self) # Override CCompiler.get_always_args @@ -698,9 +701,9 @@ class TICCompiler(TICompiler, CCompiler): def get_no_stdinc_args(self) -> T.List[str]: return [] - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: args = [] - std = self.get_compileropt_value('std', env, target, subproject) + std = self.get_compileropt_value('std', target, subproject) assert isinstance(std, str) if std != 'none': args.append('--' + std) @@ -717,11 +720,11 @@ class MetrowerksCCompilerARM(MetrowerksCompiler, CCompiler): id = 'mwccarm' def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, - is_cross: bool, info: 'MachineInfo', + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - CCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, - info, linker=linker, full_version=full_version) + CCompiler.__init__(self, ccache, exelist, version, for_machine, + env, linker=linker, full_version=full_version) MetrowerksCompiler.__init__(self) def get_instruction_set_args(self, instruction_set: str) -> T.Optional[T.List[str]]: @@ -732,9 +735,9 @@ class MetrowerksCCompilerARM(MetrowerksCompiler, CCompiler): self._update_language_stds(opts, ['c99']) return opts - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: args = [] - std = self.get_compileropt_value('std', env, target, subproject) + std = self.get_compileropt_value('std', target, subproject) assert isinstance(std, str) if std != 'none': args.append('-lang') @@ -745,11 +748,11 @@ class MetrowerksCCompilerEmbeddedPowerPC(MetrowerksCompiler, CCompiler): id = 'mwcceppc' def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, - is_cross: bool, info: 'MachineInfo', + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - CCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, - info, linker=linker, full_version=full_version) + CCompiler.__init__(self, ccache, exelist, version, for_machine, + env, linker=linker, full_version=full_version) MetrowerksCompiler.__init__(self) def get_instruction_set_args(self, instruction_set: str) -> T.Optional[T.List[str]]: @@ -760,9 +763,9 @@ class MetrowerksCCompilerEmbeddedPowerPC(MetrowerksCompiler, CCompiler): self._update_language_stds(opts, ['c99']) return opts - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: args = [] - std = self.get_compileropt_value('std', env, target, subproject) + std = self.get_compileropt_value('std', target, subproject) assert isinstance(std, str) if std != 'none': args.append('-lang ' + std) @@ -772,9 +775,9 @@ class TaskingCCompiler(TaskingCompiler, CCompiler): id = 'tasking' def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, - is_cross: bool, info: 'MachineInfo', + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - CCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, - info, linker=linker, full_version=full_version) + CCompiler.__init__(self, ccache, exelist, version, for_machine, + env, linker=linker, full_version=full_version) TaskingCompiler.__init__(self) diff --git a/mesonbuild/compilers/c_function_attributes.py b/mesonbuild/compilers/c_function_attributes.py index a7258a1..e425dbf 100644 --- a/mesonbuild/compilers/c_function_attributes.py +++ b/mesonbuild/compilers/c_function_attributes.py @@ -30,6 +30,13 @@ C_FUNC_ATTRIBUTES = { 'int foo(void) __attribute__((constructor));', 'constructor_priority': 'int foo( void ) __attribute__((__constructor__(65535/2)));', + 'counted_by': + ''' + struct foo { + unsigned int count; + char bar[] __attribute__((counted_by(count))); + }; + ''', 'deprecated': 'int foo(void) __attribute__((deprecated("")));', 'destructor': @@ -139,7 +146,7 @@ CXX_FUNC_ATTRIBUTES = { 'ifunc': ('extern "C" {' 'int my_foo(void) { return 0; }' - 'static int (*resolve_foo(void))(void) { return my_foo; }' + 'int (*resolve_foo(void))(void) { return my_foo; }' '}' 'int foo(void) __attribute__((ifunc("resolve_foo")));'), } diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index ad252a1..2d813eb 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -42,7 +42,7 @@ _T = T.TypeVar('_T') about. To support a new compiler, add its information below. Also add corresponding autodetection code in detect.py.""" -header_suffixes = {'h', 'hh', 'hpp', 'hxx', 'H', 'ipp', 'moc', 'vapi', 'di'} +header_suffixes = {'h', 'hh', 'hpp', 'hxx', 'H', 'ipp', 'moc', 'vapi', 'di', 'pxd', 'pxi'} obj_suffixes = {'o', 'obj', 'res'} # To the emscripten compiler, .js files are libraries lib_suffixes = {'a', 'lib', 'dll', 'dll.a', 'dylib', 'so', 'js'} @@ -84,7 +84,7 @@ clib_langs = ('objcpp', 'cpp', 'objc', 'c', 'nasm', 'fortran') # List of languages that can be linked with C code directly by the linker # used in build.py:process_compilers() and build.py:get_dynamic_linker() # This must be sorted, see sort_clink(). -clink_langs = ('d', 'cuda') + clib_langs +clink_langs = ('rust', 'd', 'cuda') + clib_langs SUFFIX_TO_LANG = dict(itertools.chain(*( [(suffix, lang) for suffix in v] for lang, v in lang_suffixes.items()))) @@ -154,6 +154,9 @@ def is_java(fname: mesonlib.FileOrString) -> bool: suffix = fname.split('.')[-1] return suffix in lang_suffixes['java'] +def is_separate_compile(fname: mesonlib.FileOrString) -> bool: + return not fname.endswith('.rs') + def is_llvm_ir(fname: 'mesonlib.FileOrString') -> bool: if isinstance(fname, mesonlib.File): fname = fname.fname @@ -170,16 +173,19 @@ def is_object(fname: 'mesonlib.FileOrString') -> bool: fname = fname.fname return cached_is_object_by_name(fname) -def is_library(fname: 'mesonlib.FileOrString') -> bool: - if isinstance(fname, mesonlib.File): - fname = fname.fname - +@lru_cache(maxsize=None) +def cached_is_library_by_name(fname: str) -> bool: if soregex.match(fname): return True suffix = fname.split('.')[-1] return suffix in lib_suffixes +def is_library(fname: 'mesonlib.FileOrString') -> bool: + if isinstance(fname, mesonlib.File): + fname = fname.fname + return cached_is_library_by_name(fname) + def is_known_suffix(fname: 'mesonlib.FileOrString') -> bool: if isinstance(fname, mesonlib.File): fname = fname.fname @@ -267,13 +273,16 @@ def are_asserts_disabled_for_subproject(subproject: str, env: 'Environment') -> def get_base_compile_args(target: 'BuildTarget', compiler: 'Compiler', env: 'Environment') -> T.List[str]: args: T.List[str] = [] + lto = False try: if env.coredata.get_option_for_target(target, 'b_lto'): num_threads = get_option_value_for_target(env, target, OptionKey('b_lto_threads'), 0) ltomode = get_option_value_for_target(env, target, OptionKey('b_lto_mode'), 'default') args.extend(compiler.get_lto_compile_args( + target=target, threads=num_threads, mode=ltomode)) + lto = True except (KeyError, AttributeError): pass try: @@ -287,11 +296,11 @@ def get_base_compile_args(target: 'BuildTarget', compiler: 'Compiler', env: 'Env assert isinstance(sanitize, list) if sanitize == ['none']: sanitize = [] - sanitize_args = compiler.sanitizer_compile_args(sanitize) + sanitize_args = compiler.sanitizer_compile_args(target, sanitize) # We consider that if there are no sanitizer arguments returned, then # the language doesn't support them. if sanitize_args: - if not compiler.has_multi_arguments(sanitize_args, env)[0]: + if not compiler.has_multi_arguments(sanitize_args)[0]: raise MesonException(f'Compiler {compiler.name_string()} does not support sanitizer arguments {sanitize_args}') args.extend(sanitize_args) except KeyError: @@ -310,12 +319,12 @@ def get_base_compile_args(target: 'BuildTarget', compiler: 'Compiler', env: 'Env except (KeyError, AttributeError): pass try: - args += compiler.get_assert_args(are_asserts_disabled(target, env), env) + args += compiler.get_assert_args(are_asserts_disabled(target, env)) except KeyError: pass # This does not need a try...except - if option_enabled(compiler.base_options, target, env, 'b_bitcode'): - args.append('-fembed-bitcode') + bitcode = option_enabled(compiler.base_options, target, env, 'b_bitcode') + args.extend(compiler.get_embed_bitcode_args(bitcode, lto)) try: crt_val = env.coredata.get_option_for_target(target, 'b_vscrt') assert isinstance(crt_val, str) @@ -345,12 +354,16 @@ def get_base_link_args(target: 'BuildTarget', thinlto_cache_dir = get_option_value_for_target(env, target, OptionKey('b_thinlto_cache_dir'), '') if thinlto_cache_dir == '': thinlto_cache_dir = os.path.join(build_dir, 'meson-private', 'thinlto-cache') + os.mkdir(thinlto_cache_dir) num_threads = get_option_value_for_target(env, target, OptionKey('b_lto_threads'), 0) lto_mode = get_option_value_for_target(env, target, OptionKey('b_lto_mode'), 'default') args.extend(linker.get_lto_link_args( + target=target, threads=num_threads, mode=lto_mode, thinlto_cache_dir=thinlto_cache_dir)) + obj_cache_path = os.path.join('@PRIVATE_DIR@', "lto.o") + args.extend(linker.get_lto_obj_cache_path(obj_cache_path)) except (KeyError, AttributeError): pass try: @@ -358,11 +371,11 @@ def get_base_link_args(target: 'BuildTarget', assert isinstance(sanitizer, list) if sanitizer == ['none']: sanitizer = [] - sanitizer_args = linker.sanitizer_link_args(sanitizer) + sanitizer_args = linker.sanitizer_link_args(target, sanitizer) # We consider that if there are no sanitizer arguments returned, then # the language doesn't support them. if sanitizer_args: - if not linker.has_multi_link_arguments(sanitizer_args, env)[0]: + if not linker.has_multi_link_arguments(sanitizer_args)[0]: raise MesonException(f'Linker {linker.name_string()} does not support sanitizer arguments {sanitizer_args}') args.extend(sanitizer_args) except KeyError: @@ -461,9 +474,9 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): mode = 'COMPILER' def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, - for_machine: MachineChoice, info: 'MachineInfo', + for_machine: MachineChoice, environment: Environment, linker: T.Optional['DynamicLinker'] = None, - full_version: T.Optional[str] = None, is_cross: bool = False): + full_version: T.Optional[str] = None): self.exelist = ccache + exelist self.exelist_no_ccache = exelist self.file_suffixes = lang_suffixes[self.language] @@ -474,10 +487,20 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): self.for_machine = for_machine self.base_options: T.Set[OptionKey] = set() self.linker = linker - self.info = info - self.is_cross = is_cross + self.environment = environment + self.is_cross = environment.is_cross_build(for_machine) self.modes: T.List[Compiler] = [] + @property + def info(self) -> MachineInfo: + # This must be fetched dynamically because it may be re-evaluated later, + # and we could end up with a stale copy + # see :class:`Interpreter._redetect_machines()` + return self.environment.machines[self.for_machine] + + def init_from_options(self) -> None: + """Initializer compiler attributes that require options to be set.""" + def __repr__(self) -> str: repr_str = "<{0}: v{1} `{2}`>" return repr_str.format(self.__class__.__name__, self.version, @@ -529,14 +552,14 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): def get_default_suffix(self) -> str: return self.default_suffix - def get_define(self, dname: str, prefix: str, env: 'Environment', + def get_define(self, dname: str, prefix: str, extra_args: T.Union[T.List[str], T.Callable[[CompileCheckMode], T.List[str]]], dependencies: T.List['Dependency'], disable_cache: bool = False) -> T.Tuple[str, bool]: raise EnvironmentException('%s does not support get_define ' % self.get_id()) def compute_int(self, expression: str, low: T.Optional[int], high: T.Optional[int], - guess: T.Optional[int], prefix: str, env: 'Environment', *, + guess: T.Optional[int], prefix: str, *, extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]], dependencies: T.Optional[T.List['Dependency']]) -> int: raise EnvironmentException('%s does not support compute_int ' % self.get_id()) @@ -545,18 +568,17 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): build_dir: str) -> T.List[str]: raise EnvironmentException('%s does not support compute_parameters_with_absolute_paths ' % self.get_id()) - def has_members(self, typename: str, membernames: T.List[str], - prefix: str, env: 'Environment', *, + def has_members(self, typename: str, membernames: T.List[str], prefix: str, *, extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: raise EnvironmentException('%s does not support has_member(s) ' % self.get_id()) - def has_type(self, typename: str, prefix: str, env: 'Environment', + def has_type(self, typename: str, prefix: str, extra_args: T.Union[T.List[str], T.Callable[[CompileCheckMode], T.List[str]]], *, dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: raise EnvironmentException('%s does not support has_type ' % self.get_id()) - def symbols_have_underscore_prefix(self, env: 'Environment') -> bool: + def symbols_have_underscore_prefix(self) -> bool: raise EnvironmentException('%s does not support symbols_have_underscore_prefix ' % self.get_id()) def get_exelist(self, ccache: bool = True) -> T.List[str]: @@ -606,24 +628,19 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): def make_option_name(self, key: OptionKey) -> str: return f'{self.language}_{key.name}' - @staticmethod - def update_options(options: MutableKeyedOptionDictType, *args: T.Tuple[OptionKey, options.AnyOptionType]) -> MutableKeyedOptionDictType: - options.update(args) - return options - def get_options(self) -> 'MutableKeyedOptionDictType': return {} - def get_option_compile_args(self, target: 'BuildTarget', env: 'Environment', subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_compile_args(self, target: 'BuildTarget', subproject: T.Optional[str] = None) -> T.List[str]: return [] - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: return [] - def get_option_link_args(self, target: 'BuildTarget', env: 'Environment', subproject: T.Optional[str] = None) -> T.List[str]: - return self.linker.get_option_link_args(target, env, subproject) + def get_option_link_args(self, target: 'BuildTarget', subproject: T.Optional[str] = None) -> T.List[str]: + return self.linker.get_option_link_args(target, subproject) - def check_header(self, hname: str, prefix: str, env: 'Environment', *, + def check_header(self, hname: str, prefix: str, *, extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: """Check that header is usable. @@ -634,7 +651,7 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): """ raise EnvironmentException('Language %s does not support header checks.' % self.get_display_language()) - def has_header(self, hname: str, prefix: str, env: 'Environment', *, + def has_header(self, hname: str, prefix: str, *, extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, dependencies: T.Optional[T.List['Dependency']] = None, disable_cache: bool = False) -> T.Tuple[bool, bool]: @@ -654,26 +671,25 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): """ raise EnvironmentException('Language %s does not support header checks.' % self.get_display_language()) - def has_header_symbol(self, hname: str, symbol: str, prefix: str, - env: 'Environment', *, + def has_header_symbol(self, hname: str, symbol: str, prefix: str, *, extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: raise EnvironmentException('Language %s does not support header symbol checks.' % self.get_display_language()) - def run(self, code: 'mesonlib.FileOrString', env: 'Environment', + def run(self, code: 'mesonlib.FileOrString', extra_args: T.Union[T.List[str], T.Callable[[CompileCheckMode], T.List[str]], None] = None, dependencies: T.Optional[T.List['Dependency']] = None, run_env: T.Optional[T.Dict[str, str]] = None, run_cwd: T.Optional[str] = None) -> RunResult: - need_exe_wrapper = env.need_exe_wrapper(self.for_machine) - if need_exe_wrapper and not env.has_exe_wrapper(): + need_exe_wrapper = self.environment.need_exe_wrapper(self.for_machine) + if need_exe_wrapper and not self.environment.has_exe_wrapper(): raise CrossNoRunException('Can not run test applications in this cross environment.') - with self._build_wrapper(code, env, extra_args, dependencies, mode=CompileCheckMode.LINK, want_output=True) as p: + with self._build_wrapper(code, extra_args, dependencies, mode=CompileCheckMode.LINK, want_output=True) as p: if p.returncode != 0: mlog.debug(f'Could not compile test file {p.input_name}: {p.returncode}\n') return RunResult(False) if need_exe_wrapper: - cmdlist = env.exe_wrapper.get_command() + [p.output_name] + cmdlist = self.environment.exe_wrapper.get_command() + [p.output_name] else: cmdlist = [p.output_name] try: @@ -694,11 +710,11 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): # For now we just accept code as a string, as that's what internal callers # need anyway. If we wanted to accept files, the cache key would need to # include mtime. - def cached_run(self, code: str, env: 'Environment', *, + def cached_run(self, code: str, *, extra_args: T.Union[T.List[str], T.Callable[[CompileCheckMode], T.List[str]], None] = None, dependencies: T.Optional[T.List['Dependency']] = None) -> RunResult: - run_check_cache = env.coredata.run_check_cache - args = self.build_wrapper_args(env, extra_args, dependencies, CompileCheckMode('link')) + run_check_cache = self.environment.coredata.run_check_cache + args = self.build_wrapper_args(extra_args, dependencies, CompileCheckMode('link')) key = (code, tuple(args)) if key in run_check_cache: p = run_check_cache[key] @@ -710,21 +726,21 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): mlog.debug('Cached run stdout:\n', p.stdout) mlog.debug('Cached run stderr:\n', p.stderr) else: - p = self.run(code, env, extra_args=extra_args, dependencies=dependencies) + p = self.run(code, extra_args=extra_args, dependencies=dependencies) run_check_cache[key] = p return p - def sizeof(self, typename: str, prefix: str, env: 'Environment', *, + def sizeof(self, typename: str, prefix: str, *, extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[int, bool]: raise EnvironmentException('Language %s does not support sizeof checks.' % self.get_display_language()) - def alignment(self, typename: str, prefix: str, env: 'Environment', *, + def alignment(self, typename: str, prefix: str, *, extra_args: T.Optional[T.List[str]] = None, dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[int, bool]: raise EnvironmentException('Language %s does not support alignment checks.' % self.get_display_language()) - def has_function(self, funcname: str, prefix: str, env: 'Environment', *, + def has_function(self, funcname: str, prefix: str, *, extra_args: T.Optional[T.List[str]] = None, dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: """See if a function exists. @@ -748,20 +764,19 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): "Always returns a copy that can be independently mutated" return args.copy() - def find_library(self, libname: str, env: 'Environment', extra_dirs: T.List[str], - libtype: LibType = LibType.PREFER_SHARED, lib_prefix_warning: bool = True) -> T.Optional[T.List[str]]: + def find_library(self, libname: str, extra_dirs: T.List[str], libtype: LibType = LibType.PREFER_SHARED, + lib_prefix_warning: bool = True, ignore_system_dirs: bool = False) -> T.Optional[T.List[str]]: raise EnvironmentException(f'Language {self.get_display_language()} does not support library finding.') - def get_library_naming(self, env: 'Environment', libtype: LibType, - strict: bool = False) -> T.Optional[T.Tuple[str, ...]]: + def get_library_naming(self, libtype: LibType, strict: bool = False) -> T.Optional[T.Tuple[str, ...]]: raise EnvironmentException( 'Language {} does not support get_library_naming.'.format( self.get_display_language())) - def get_program_dirs(self, env: 'Environment') -> T.List[str]: + def get_program_dirs(self) -> T.List[str]: return [] - def has_multi_arguments(self, args: T.List[str], env: 'Environment') -> T.Tuple[bool, bool]: + def has_multi_arguments(self, args: T.List[str]) -> T.Tuple[bool, bool]: """Checks if the compiler has all of the arguments. :returns: @@ -772,14 +787,14 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): 'Language {} does not support has_multi_arguments.'.format( self.get_display_language())) - def has_multi_link_arguments(self, args: T.List[str], env: 'Environment') -> T.Tuple[bool, bool]: + def has_multi_link_arguments(self, args: T.List[str]) -> T.Tuple[bool, bool]: """Checks if the linker has all of the arguments. :returns: A tuple of (bool, bool). The first value is whether the check succeeded, and the second is whether it was retrieved from a cache """ - return self.linker.has_multi_arguments(args, env) + return self.linker.has_multi_arguments(args) def _get_compile_output(self, dirname: str, mode: CompileCheckMode) -> str: assert mode != CompileCheckMode.PREPROCESS, 'In pre-processor mode, the output is sent to stdout and discarded' @@ -819,7 +834,7 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): if extra_args is None: extra_args = [] - with TemporaryDirectoryWinProof(dir=temp_dir) as tmpdirname: + with TemporaryDirectoryWinProof(dir=temp_dir if temp_dir else None) as tmpdirname: no_ccache = False if isinstance(code, str): srcname = os.path.join(tmpdirname, @@ -867,7 +882,7 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): yield result @contextlib.contextmanager - def cached_compile(self, code: 'mesonlib.FileOrString', cdata: coredata.CoreData, *, + def cached_compile(self, code: 'mesonlib.FileOrString', *, extra_args: T.Union[None, T.List[str], CompilerArgs] = None, mode: CompileCheckMode = CompileCheckMode.LINK, temp_dir: T.Optional[str] = None) -> T.Iterator[CompileResult]: @@ -878,8 +893,9 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): key: coredata.CompilerCheckCacheKey = (tuple(self.exelist), self.version, code, textra_args, mode) # Check if not cached, and generate, otherwise get from the cache - if key in cdata.compiler_check_cache: - p = cdata.compiler_check_cache[key] + cache = self.environment.coredata.compiler_check_cache + if key in cache: + p = cache[key] p.cached = True mlog.debug('Using cached compile:') mlog.debug('Cached command line: ', ' '.join(p.command), '\n') @@ -889,7 +905,7 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): yield p else: with self.compile(code, extra_args=extra_args, mode=mode, want_output=False, temp_dir=temp_dir) as p: - cdata.compiler_check_cache[key] = p + cache[key] = p yield p def get_colorout_args(self, colortype: str) -> T.List[str]: @@ -933,11 +949,10 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): """ return None - def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, - rpath_paths: T.Tuple[str, ...], build_rpath: str, - install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: - return self.linker.build_rpath_args( - env, build_dir, from_dir, rpath_paths, build_rpath, install_rpath) + def build_rpath_args(self, build_dir: str, from_dir: str, target: BuildTarget, + extra_paths: T.Optional[T.List[str]] = None + ) -> T.Tuple[T.List[str], T.Set[bytes]]: + return self.linker.build_rpath_args(build_dir, from_dir, target, extra_paths) def get_archive_name(self, filename: str) -> str: return self.linker.get_archive_name(filename) @@ -947,19 +962,19 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): return [] return self.linker.get_command_to_archive_shlib() - def thread_flags(self, env: 'Environment') -> T.List[str]: + def thread_flags(self) -> T.List[str]: return [] - def thread_link_flags(self, env: 'Environment') -> T.List[str]: - return self.linker.thread_flags(env) + def thread_link_flags(self) -> T.List[str]: + return self.linker.thread_flags() - def openmp_flags(self, env: Environment) -> T.List[str]: + def openmp_flags(self) -> T.List[str]: raise EnvironmentException('Language %s does not support OpenMP flags.' % self.get_display_language()) - def openmp_link_flags(self, env: Environment) -> T.List[str]: - return self.openmp_flags(env) + def openmp_link_flags(self) -> T.List[str]: + return self.openmp_flags() - def language_stdlib_only_link_flags(self, env: 'Environment') -> T.List[str]: + def language_stdlib_only_link_flags(self) -> T.List[str]: return [] def gnu_symbol_visibility_args(self, vistype: str) -> T.List[str]: @@ -971,7 +986,7 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): # or does not target Windows return self.linker.get_win_subsystem_args(value) - def has_func_attribute(self, name: str, env: 'Environment') -> T.Tuple[bool, bool]: + def has_func_attribute(self, name: str) -> T.Tuple[bool, bool]: raise EnvironmentException( f'Language {self.get_display_language()} does not support function attributes.') @@ -1027,17 +1042,24 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): ret.append(arg) return ret - def get_lto_compile_args(self, *, threads: int = 0, mode: str = 'default') -> T.List[str]: + def get_embed_bitcode_args(self, bitcode: bool, lto: bool) -> T.List[str]: + return [] + + def get_lto_compile_args(self, *, target: T.Optional[BuildTarget] = None, threads: int = 0, + mode: str = 'default') -> T.List[str]: return [] - def get_lto_link_args(self, *, threads: int = 0, mode: str = 'default', - thinlto_cache_dir: T.Optional[str] = None) -> T.List[str]: + def get_lto_link_args(self, *, target: T.Optional[BuildTarget] = None, threads: int = 0, + mode: str = 'default', thinlto_cache_dir: T.Optional[str] = None) -> T.List[str]: return self.linker.get_lto_args() - def sanitizer_compile_args(self, value: T.List[str]) -> T.List[str]: + def get_lto_obj_cache_path(self, path: str) -> T.List[str]: + return self.linker.get_lto_obj_cache_path(path) + + def sanitizer_compile_args(self, target: T.Optional[BuildTarget], value: T.List[str]) -> T.List[str]: return [] - def sanitizer_link_args(self, value: T.List[str]) -> T.List[str]: + def sanitizer_link_args(self, target: T.Optional[BuildTarget], value: T.List[str]) -> T.List[str]: return self.linker.sanitizer_args(value) def get_asneeded_args(self) -> T.List[str]: @@ -1052,11 +1074,10 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): def get_optimization_link_args(self, optimization_level: str) -> T.List[str]: return self.linker.get_optimization_link_args(optimization_level) - def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, - suffix: str, soversion: str, + def get_soname_args(self, prefix: str, shlib_name: str, suffix: str, soversion: str, darwin_versions: T.Tuple[str, str]) -> T.List[str]: return self.linker.get_soname_args( - env, prefix, shlib_name, suffix, soversion, + prefix, shlib_name, suffix, soversion, darwin_versions) def get_target_link_args(self, target: 'BuildTarget') -> T.List[str]: @@ -1080,7 +1101,10 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): def get_coverage_link_args(self) -> T.List[str]: return self.linker.get_coverage_args() - def get_assert_args(self, disable: bool, env: 'Environment') -> T.List[str]: + def gen_vs_module_defs_args(self, defsfile: str) -> T.List[str]: + return self.linker.gen_vs_module_defs_args(defsfile) + + def get_assert_args(self, disable: bool) -> T.List[str]: """Get arguments to enable or disable assertion. :param disable: Whether to disable assertions @@ -1148,27 +1172,24 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): # those features explicitly. return [] - def get_library_dirs(self, env: 'Environment', - elf_class: T.Optional[int] = None) -> T.List[str]: + def get_library_dirs(self, elf_class: T.Optional[int] = None) -> T.List[str]: return [] def get_return_value(self, fname: str, rtype: str, prefix: str, - env: 'Environment', extra_args: T.Optional[T.List[str]], dependencies: T.Optional[T.List['Dependency']]) -> T.Union[str, int]: raise EnvironmentException(f'{self.id} does not support get_return_value') def find_framework(self, name: str, - env: 'Environment', extra_dirs: T.List[str], allow_system: bool = True) -> T.Optional[T.List[str]]: raise EnvironmentException(f'{self.id} does not support find_framework') - def find_framework_paths(self, env: 'Environment') -> T.List[str]: + def find_framework_paths(self) -> T.List[str]: raise EnvironmentException(f'{self.id} does not support find_framework_paths') def attribute_check_func(self, name: str) -> str: @@ -1190,7 +1211,7 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): return ' '.join(self.exelist) @abc.abstractmethod - def sanity_check(self, work_dir: str, environment: 'Environment') -> None: + def sanity_check(self, work_dir: str) -> None: """Check that this compiler actually works. This should provide a simple compile/link test. Something as simple as: @@ -1200,13 +1221,13 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): is good enough here. """ - def run_sanity_check(self, environment: Environment, cmdlist: T.List[str], work_dir: str, use_exe_wrapper_for_cross: bool = True) -> T.Tuple[str, str]: + def run_sanity_check(self, cmdlist: T.List[str], work_dir: str, use_exe_wrapper_for_cross: bool = True) -> T.Tuple[str, str]: # Run sanity check if self.is_cross and use_exe_wrapper_for_cross: - if not environment.has_exe_wrapper(): + if not self.environment.has_exe_wrapper(): # Can't check if the binaries run so we have to assume they do return ('', '') - cmdlist = environment.exe_wrapper.get_command() + cmdlist + cmdlist = self.environment.exe_wrapper.get_command() + cmdlist mlog.debug('Running test binary command: ', mesonlib.join_args(cmdlist)) try: pe, stdo, stde = Popen_safe_logged(cmdlist, 'Sanity check', cwd=work_dir) @@ -1230,6 +1251,9 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): def get_include_args(self, path: str, is_system: bool) -> T.List[str]: return [] + def get_depfile_format(self) -> str: + return 'msvc' if self.get_argument_syntax() == 'msvc' else 'gcc' + def depfile_for_object(self, objfile: str) -> T.Optional[str]: return objfile + '.' + self.get_depfile_suffix() @@ -1275,7 +1299,7 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): """Arguments to the compiler to turn off all optimizations.""" return [] - def build_wrapper_args(self, env: 'Environment', + def build_wrapper_args(self, extra_args: T.Union[None, CompilerArgs, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]], dependencies: T.Optional[T.List['Dependency']], mode: CompileCheckMode = CompileCheckMode.COMPILE) -> CompilerArgs: @@ -1304,16 +1328,16 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): if mode is CompileCheckMode.COMPILE: # Add DFLAGS from the env - args += env.coredata.get_external_args(self.for_machine, self.language) + args += self.environment.coredata.get_external_args(self.for_machine, self.language) elif mode is CompileCheckMode.LINK: # Add LDFLAGS from the env - args += env.coredata.get_external_link_args(self.for_machine, self.language) + args += self.environment.coredata.get_external_link_args(self.for_machine, self.language) # extra_args must override all other arguments, so we add them last args += extra_args return args @contextlib.contextmanager - def _build_wrapper(self, code: 'mesonlib.FileOrString', env: 'Environment', + def _build_wrapper(self, code: 'mesonlib.FileOrString', extra_args: T.Union[None, CompilerArgs, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, dependencies: T.Optional[T.List['Dependency']] = None, mode: CompileCheckMode = CompileCheckMode.COMPILE, want_output: bool = False, @@ -1323,15 +1347,15 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): This method isn't meant to be called externally, it's mean to be wrapped by other methods like compiles() and links(). """ - args = self.build_wrapper_args(env, extra_args, dependencies, mode) + args = self.build_wrapper_args(extra_args, dependencies, mode) if disable_cache or want_output: - with self.compile(code, extra_args=args, mode=mode, want_output=want_output, temp_dir=env.scratch_dir) as r: + with self.compile(code, extra_args=args, mode=mode, want_output=want_output, temp_dir=self.environment.scratch_dir) as r: yield r else: - with self.cached_compile(code, env.coredata, extra_args=args, mode=mode, temp_dir=env.scratch_dir) as r: + with self.cached_compile(code, extra_args=args, mode=mode, temp_dir=self.environment.scratch_dir) as r: yield r - def compiles(self, code: 'mesonlib.FileOrString', env: 'Environment', *, + def compiles(self, code: 'mesonlib.FileOrString', *, extra_args: T.Union[None, T.List[str], CompilerArgs, T.Callable[[CompileCheckMode], T.List[str]]] = None, dependencies: T.Optional[T.List['Dependency']] = None, mode: CompileCheckMode = CompileCheckMode.COMPILE, @@ -1342,21 +1366,21 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): A tuple of (bool, bool). The first value is whether the check succeeded, and the second is whether it was retrieved from a cache """ - with self._build_wrapper(code, env, extra_args, dependencies, mode, disable_cache=disable_cache) as p: + with self._build_wrapper(code, extra_args, dependencies, mode, disable_cache=disable_cache) as p: return p.returncode == 0, p.cached - def links(self, code: 'mesonlib.FileOrString', env: 'Environment', *, + def links(self, code: 'mesonlib.FileOrString', *, compiler: T.Optional['Compiler'] = None, extra_args: T.Union[None, T.List[str], CompilerArgs, T.Callable[[CompileCheckMode], T.List[str]]] = None, dependencies: T.Optional[T.List['Dependency']] = None, disable_cache: bool = False) -> T.Tuple[bool, bool]: if compiler: - with compiler._build_wrapper(code, env, dependencies=dependencies, want_output=True) as r: + with compiler._build_wrapper(code, dependencies=dependencies, want_output=True) as r: objfile = mesonlib.File.from_absolute_file(r.output_name) - return self.compiles(objfile, env, extra_args=extra_args, + return self.compiles(objfile, extra_args=extra_args, dependencies=dependencies, mode=CompileCheckMode.LINK, disable_cache=True) - return self.compiles(code, env, extra_args=extra_args, + return self.compiles(code, extra_args=extra_args, dependencies=dependencies, mode=CompileCheckMode.LINK, disable_cache=disable_cache) def get_feature_args(self, kwargs: DFeatures, build_to_src: str) -> T.List[str]: @@ -1396,16 +1420,15 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): def get_compileropt_value(self, key: T.Union[str, OptionKey], - env: Environment, target: T.Optional[BuildTarget], subproject: T.Optional[str] = None ) -> options.ElementaryOptionValues: if isinstance(key, str): key = self.form_compileropt_key(key) if target: - return env.coredata.get_option_for_target(target, key) + return self.environment.coredata.get_option_for_target(target, key) else: - return env.coredata.optstore.get_value_for(key.evolve(subproject=subproject)) + return self.environment.coredata.optstore.get_value_for(key.evolve(subproject=subproject)) def _update_language_stds(self, opts: MutableKeyedOptionDictType, value: T.List[str]) -> None: key = self.form_compileropt_key('std') @@ -1414,43 +1437,3 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): if 'none' not in value: value = ['none'] + value std.choices = value - - -def get_global_options(lang: str, - comp: T.Type[Compiler], - for_machine: MachineChoice, - env: 'Environment') -> dict[OptionKey, options.AnyOptionType]: - """Retrieve options that apply to all compilers for a given language.""" - description = f'Extra arguments passed to the {lang}' - argkey = OptionKey(f'{lang}_args', machine=for_machine) - largkey = OptionKey(f'{lang}_link_args', machine=for_machine) - envkey = OptionKey(f'{lang}_env_args', machine=for_machine) - - comp_key = argkey if argkey in env.options else envkey - - comp_options = env.options.get(comp_key, []) - link_options = env.options.get(largkey, []) - assert isinstance(comp_options, (str, list)), 'for mypy' - assert isinstance(link_options, (str, list)), 'for mypy' - - cargs = options.UserStringArrayOption( - argkey.name, - description + ' compiler', - comp_options, split_args=True, allow_dups=True) - - largs = options.UserStringArrayOption( - largkey.name, - description + ' linker', - link_options, split_args=True, allow_dups=True) - - if comp.INVOKES_LINKER and comp_key == envkey: - # If the compiler acts as a linker driver, and we're using the - # environment variable flags for both the compiler and linker - # arguments, then put the compiler flags in the linker flags as well. - # This is how autotools works, and the env vars feature is for - # autotools compatibility. - largs.extend_value(comp_options) - - opts: dict[OptionKey, options.AnyOptionType] = {argkey: cargs, largkey: largs} - - return opts diff --git a/mesonbuild/compilers/cpp.py b/mesonbuild/compilers/cpp.py index 01b9bb9..5ed4aea 100644 --- a/mesonbuild/compilers/cpp.py +++ b/mesonbuild/compilers/cpp.py @@ -32,11 +32,11 @@ from .mixins.pgi import PGICompiler from .mixins.emscripten import EmscriptenMixin from .mixins.metrowerks import MetrowerksCompiler from .mixins.metrowerks import mwccarm_instruction_set_args, mwcceppc_instruction_set_args +from .mixins.microchip import Xc32Compiler, Xc32CPPStds if T.TYPE_CHECKING: from ..options import MutableKeyedOptionDictType from ..dependencies import Dependency - from ..envconfig import MachineInfo from ..environment import Environment from ..linkers.linkers import DynamicLinker from ..mesonlib import MachineChoice @@ -66,14 +66,12 @@ class CPPCompiler(CLikeCompiler, Compiler): language = 'cpp' - def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, - info: 'MachineInfo', - linker: T.Optional['DynamicLinker'] = None, + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): # If a child ObjCPP class has already set it, don't set it ourselves - Compiler.__init__(self, ccache, exelist, version, for_machine, info, - is_cross=is_cross, linker=linker, - full_version=full_version) + Compiler.__init__(self, ccache, exelist, version, for_machine, env, + linker=linker, full_version=full_version) CLikeCompiler.__init__(self) @classmethod @@ -86,9 +84,9 @@ class CPPCompiler(CLikeCompiler, Compiler): def get_no_stdlib_link_args(self) -> T.List[str]: return ['-nostdlib++'] - def sanity_check(self, work_dir: str, environment: 'Environment') -> None: + def sanity_check(self, work_dir: str) -> None: code = 'class breakCCompiler;int main(void) { return 0; }\n' - return self._sanity_check_impl(work_dir, environment, 'sanitycheckcpp.cc', code) + return self._sanity_check_impl(work_dir, 'sanitycheckcpp.cc', code) def get_compiler_check_args(self, mode: CompileCheckMode) -> T.List[str]: # -fpermissive allows non-conforming code to compile which is necessary @@ -96,12 +94,11 @@ class CPPCompiler(CLikeCompiler, Compiler): # too strict without this and always fails. return super().get_compiler_check_args(mode) + ['-fpermissive'] - def has_header_symbol(self, hname: str, symbol: str, prefix: str, - env: 'Environment', *, + def has_header_symbol(self, hname: str, symbol: str, prefix: str, *, extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: # Check if it's a C-like symbol - found, cached = super().has_header_symbol(hname, symbol, prefix, env, + found, cached = super().has_header_symbol(hname, symbol, prefix, extra_args=extra_args, dependencies=dependencies) if found: @@ -113,7 +110,7 @@ class CPPCompiler(CLikeCompiler, Compiler): #include <{hname}> using {symbol}; int main(void) {{ return 0; }}''' - return self.compiles(t, env, extra_args=extra_args, + return self.compiles(t, extra_args=extra_args, dependencies=dependencies) def _test_cpp_std_arg(self, cpp_std_value: str) -> bool: @@ -158,7 +155,7 @@ class CPPCompiler(CLikeCompiler, Compiler): } # Currently, remapping is only supported for Clang, Elbrus and GCC - assert self.id in frozenset(['clang', 'lcc', 'gcc', 'emscripten', 'armltdclang', 'intel-llvm']) + assert self.id in frozenset(['clang', 'lcc', 'gcc', 'emscripten', 'armltdclang', 'intel-llvm', 'nvidia_hpc', 'xc32-gcc']) if cpp_std not in CPP_FALLBACKS: # 'c++03' and 'c++98' don't have fallback types @@ -186,13 +183,13 @@ class _StdCPPLibMixin(CompilerMixinBase): def language_stdlib_provider(self, env: Environment) -> str: # https://stackoverflow.com/a/31658120 - header = 'version' if self.has_header('version', '', env)[0] else 'ciso646' - is_libcxx = self.has_header_symbol(header, '_LIBCPP_VERSION', '', env)[0] + header = 'version' if self.has_header('version', '')[0] else 'ciso646' + is_libcxx = self.has_header_symbol(header, '_LIBCPP_VERSION', '')[0] lib = 'c++' if is_libcxx else 'stdc++' return lib @functools.lru_cache(None) - def language_stdlib_only_link_flags(self, env: Environment) -> T.List[str]: + def language_stdlib_only_link_flags(self) -> T.List[str]: """Detect the C++ stdlib and default search dirs As an optimization, this method will cache the value, to avoid building the same values over and over @@ -205,13 +202,10 @@ class _StdCPPLibMixin(CompilerMixinBase): # be passed to a different compiler with a different set of default # search paths, such as when using Clang for C/C++ and gfortran for # fortran. - search_dirs = [f'-L{d}' for d in self.get_compiler_dirs(env, 'libraries')] - - machine = env.machines[self.for_machine] - assert machine is not None, 'for mypy' + search_dirs = [f'-L{d}' for d in self.get_compiler_dirs('libraries')] - lib = self.language_stdlib_provider(env) - if self.find_library(lib, env, []) is not None: + lib = self.language_stdlib_provider(self.environment) + if self.find_library(lib, []) is not None: return search_dirs + [f'-l{lib}'] # TODO: maybe a bug exception? @@ -220,13 +214,12 @@ class _StdCPPLibMixin(CompilerMixinBase): class ClangCPPCompiler(_StdCPPLibMixin, ClangCPPStds, ClangCompiler, CPPCompiler): - def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, - info: 'MachineInfo', - linker: T.Optional['DynamicLinker'] = None, + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, linker: T.Optional['DynamicLinker'] = None, defines: T.Optional[T.Dict[str, str]] = None, full_version: T.Optional[str] = None): - CPPCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, - info, linker=linker, full_version=full_version) + CPPCompiler.__init__(self, ccache, exelist, version, for_machine, + env, linker=linker, full_version=full_version) ClangCompiler.__init__(self, defines) default_warn_args = ['-Wall', '-Winvalid-pch'] self.warn_args = {'0': [], @@ -265,12 +258,12 @@ class ClangCPPCompiler(_StdCPPLibMixin, ClangCPPStds, ClangCompiler, CPPCompiler gnu_winlibs) return opts - def get_option_compile_args(self, target: 'BuildTarget', env: 'Environment', subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_compile_args(self, target: 'BuildTarget', subproject: T.Optional[str] = None) -> T.List[str]: args: T.List[str] = [] - rtti = self.get_compileropt_value('rtti', env, target, subproject) - debugstl = self.get_compileropt_value('debugstl', env, target, subproject) - eh = self.get_compileropt_value('eh', env, target, subproject) + rtti = self.get_compileropt_value('rtti', target, subproject) + debugstl = self.get_compileropt_value('debugstl', target, subproject) + eh = self.get_compileropt_value('eh', target, subproject) assert isinstance(rtti, bool) assert isinstance(eh, str) @@ -285,6 +278,7 @@ class ClangCPPCompiler(_StdCPPLibMixin, ClangCPPStds, ClangCompiler, CPPCompiler # https://discourse.llvm.org/t/building-a-program-with-d-libcpp-debug-1-against-a-libc-that-is-not-itself-built-with-that-define/59176/3 # Note that unlike _GLIBCXX_DEBUG, _MODE_DEBUG doesn't break ABI. It's just slow. if version_compare(self.version, '>=18'): + args.append('-U_LIBCPP_HARDENING_MODE') args.append('-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG') if not rtti: @@ -292,18 +286,18 @@ class ClangCPPCompiler(_StdCPPLibMixin, ClangCPPStds, ClangCompiler, CPPCompiler return args - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: args: T.List[str] = [] - std = self.get_compileropt_value('std', env, target, subproject) + std = self.get_compileropt_value('std', target, subproject) assert isinstance(std, str) if std != 'none': args.append(self._find_best_cpp_std(std)) return args - def get_option_link_args(self, target: 'BuildTarget', env: 'Environment', subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_link_args(self, target: 'BuildTarget', subproject: T.Optional[str] = None) -> T.List[str]: if self.info.is_windows() or self.info.is_cygwin(): # without a typedict mypy can't understand this. - retval = self.get_compileropt_value('winlibs', env, target, subproject) + retval = self.get_compileropt_value('winlibs', target, subproject) assert isinstance(retval, list) libs = retval[:] for l in libs: @@ -311,7 +305,7 @@ class ClangCPPCompiler(_StdCPPLibMixin, ClangCPPStds, ClangCompiler, CPPCompiler return libs return [] - def get_assert_args(self, disable: bool, env: 'Environment') -> T.List[str]: + def get_assert_args(self, disable: bool) -> T.List[str]: if disable: return ['-DNDEBUG'] @@ -320,15 +314,10 @@ class ClangCPPCompiler(_StdCPPLibMixin, ClangCPPStds, ClangCompiler, CPPCompiler if self.defines.get(macro) is not None: return [] - if self.language_stdlib_provider(env) == 'stdc++': + if self.language_stdlib_provider(self.environment) == 'stdc++': return ['-D_GLIBCXX_ASSERTIONS=1'] - else: - if version_compare(self.version, '>=18'): - return ['-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST'] - elif version_compare(self.version, '>=15'): - return ['-D_LIBCPP_ENABLE_ASSERTIONS=1'] - return [] + return ['-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST'] def get_pch_use_args(self, pch_dir: str, header: str) -> T.List[str]: args = super().get_pch_use_args(pch_dir, header) @@ -359,22 +348,20 @@ class EmscriptenCPPCompiler(EmscriptenMixin, ClangCPPCompiler): _CPP23_VERSION = '>=2.0.10' _CPP26_VERSION = '>=3.1.39' - def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, - info: 'MachineInfo', - linker: T.Optional['DynamicLinker'] = None, + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, linker: T.Optional['DynamicLinker'] = None, defines: T.Optional[T.Dict[str, str]] = None, full_version: T.Optional[str] = None): - if not is_cross: + if not env.is_cross_build(for_machine): raise MesonException('Emscripten compiler can only be used for cross compilation.') if not version_compare(version, '>=1.39.19'): raise MesonException('Meson requires Emscripten >= 1.39.19') - ClangCPPCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, - info, linker=linker, - defines=defines, full_version=full_version) + ClangCPPCompiler.__init__(self, ccache, exelist, version, for_machine, env, + linker=linker, defines=defines, full_version=full_version) - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: args: T.List[str] = [] - std = self.get_compileropt_value('std', env, target, subproject) + std = self.get_compileropt_value('std', target, subproject) assert isinstance(std, str) if std != 'none': args.append(self._find_best_cpp_std(std)) @@ -386,12 +373,11 @@ class ArmclangCPPCompiler(ArmclangCompiler, CPPCompiler): Keil armclang ''' - def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, - info: 'MachineInfo', - linker: T.Optional['DynamicLinker'] = None, + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - CPPCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, - info, linker=linker, full_version=full_version) + CPPCompiler.__init__(self, ccache, exelist, version, for_machine, + env, linker=linker, full_version=full_version) ArmclangCompiler.__init__(self) default_warn_args = ['-Wall', '-Winvalid-pch'] self.warn_args = {'0': [], @@ -416,31 +402,30 @@ class ArmclangCPPCompiler(ArmclangCompiler, CPPCompiler): std_opt.set_versions(['c++98', 'c++03', 'c++11', 'c++14', 'c++17'], gnu=True) return opts - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: args: T.List[str] = [] - std = self.get_compileropt_value('std', env, target, subproject) + std = self.get_compileropt_value('std', target, subproject) assert isinstance(std, str) if std != 'none': args.append('-std=' + std) - eh = self.get_compileropt_value('eh', env, target, subproject) + eh = self.get_compileropt_value('eh', target, subproject) assert isinstance(eh, str) non_msvc_eh_options(eh, args) return args - def get_option_link_args(self, target: 'BuildTarget', env: 'Environment', subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_link_args(self, target: 'BuildTarget', subproject: T.Optional[str] = None) -> T.List[str]: return [] class GnuCPPCompiler(_StdCPPLibMixin, GnuCPPStds, GnuCompiler, CPPCompiler): - def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, - info: 'MachineInfo', - linker: T.Optional['DynamicLinker'] = None, + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, linker: T.Optional['DynamicLinker'] = None, defines: T.Optional[T.Dict[str, str]] = None, full_version: T.Optional[str] = None): - CPPCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, - info, linker=linker, full_version=full_version) + CPPCompiler.__init__(self, ccache, exelist, version, for_machine, + env, linker=linker, full_version=full_version) GnuCompiler.__init__(self, defines) default_warn_args = ['-Wall', '-Winvalid-pch'] self.warn_args = {'0': [], @@ -480,14 +465,21 @@ class GnuCPPCompiler(_StdCPPLibMixin, GnuCPPStds, GnuCompiler, CPPCompiler): 'Standard Win libraries to link against', gnu_winlibs) + if version_compare(self.version, '>=15.1'): + key = key.evolve(name='cpp_importstd') + opts[key] = options.UserComboOption(self.make_option_name(key), + 'Use #import std.', + 'false', + choices=['false', 'true']) + return opts - def get_option_compile_args(self, target: 'BuildTarget', env: 'Environment', subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_compile_args(self, target: 'BuildTarget', subproject: T.Optional[str] = None) -> T.List[str]: args: T.List[str] = [] - rtti = self.get_compileropt_value('rtti', env, target, subproject) - debugstl = self.get_compileropt_value('debugstl', env, target, subproject) - eh = self.get_compileropt_value('eh', env, target, subproject) + rtti = self.get_compileropt_value('rtti', target, subproject) + debugstl = self.get_compileropt_value('debugstl', target, subproject) + eh = self.get_compileropt_value('eh', target, subproject) assert isinstance(rtti, bool) assert isinstance(eh, str) @@ -498,22 +490,23 @@ class GnuCPPCompiler(_StdCPPLibMixin, GnuCPPStds, GnuCompiler, CPPCompiler): if not rtti: args.append('-fno-rtti') + # We may want to handle libc++'s debugstl mode here too if debugstl: args.append('-D_GLIBCXX_DEBUG=1') return args - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: args: T.List[str] = [] - std = self.get_compileropt_value('std', env, target, subproject) + std = self.get_compileropt_value('std', target, subproject) assert isinstance(std, str) if std != 'none': args.append(self._find_best_cpp_std(std)) return args - def get_option_link_args(self, target: 'BuildTarget', env: 'Environment', subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_link_args(self, target: 'BuildTarget', subproject: T.Optional[str] = None) -> T.List[str]: if self.info.is_windows() or self.info.is_cygwin(): # without a typedict mypy can't understand this. - retval = self.get_compileropt_value('winlibs', env, target, subproject) + retval = self.get_compileropt_value('winlibs', target, subproject) assert isinstance(retval, list) libs: T.List[str] = retval[:] for l in libs: @@ -521,7 +514,7 @@ class GnuCPPCompiler(_StdCPPLibMixin, GnuCPPStds, GnuCompiler, CPPCompiler): return libs return [] - def get_assert_args(self, disable: bool, env: 'Environment') -> T.List[str]: + def get_assert_args(self, disable: bool) -> T.List[str]: if disable: return ['-DNDEBUG'] @@ -530,13 +523,17 @@ class GnuCPPCompiler(_StdCPPLibMixin, GnuCPPStds, GnuCompiler, CPPCompiler): if self.defines.get(macro) is not None: return [] - if self.language_stdlib_provider(env) == 'stdc++': + # For GCC, we can assume that the libstdc++ version is the same as + # the compiler itself. Anything else isn't supported. + if self.language_stdlib_provider(self.environment) == 'stdc++': return ['-D_GLIBCXX_ASSERTIONS=1'] else: + # One can use -stdlib=libc++ with GCC, it just (as of 2025) requires + # an experimental configure arg to expose that. libc++ supports "multiple" + # versions of GCC (only ever one version of GCC per libc++ version), but + # that is "multiple" for our purposes as we can't assume a mapping. if version_compare(self.version, '>=18'): return ['-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST'] - elif version_compare(self.version, '>=15'): - return ['-D_LIBCPP_ENABLE_ASSERTIONS=1'] return [] @@ -545,12 +542,11 @@ class GnuCPPCompiler(_StdCPPLibMixin, GnuCPPStds, GnuCompiler, CPPCompiler): class PGICPPCompiler(PGICompiler, CPPCompiler): - def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, - info: 'MachineInfo', - linker: T.Optional['DynamicLinker'] = None, + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - CPPCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, - info, linker=linker, full_version=full_version) + CPPCompiler.__init__(self, ccache, exelist, version, for_machine, + env, linker=linker, full_version=full_version) PGICompiler.__init__(self) @@ -558,12 +554,11 @@ class NvidiaHPC_CPPCompiler(PGICompiler, CPPCompiler): id = 'nvidia_hpc' - def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, - info: 'MachineInfo', - linker: T.Optional['DynamicLinker'] = None, + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - CPPCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, - info, linker=linker, full_version=full_version) + CPPCompiler.__init__(self, ccache, exelist, version, for_machine, + env, linker=linker, full_version=full_version) PGICompiler.__init__(self) def get_options(self) -> 'MutableKeyedOptionDictType': @@ -577,15 +572,22 @@ class NvidiaHPC_CPPCompiler(PGICompiler, CPPCompiler): std_opt.set_versions(cppstd_choices) return opts + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: + args: T.List[str] = [] + std = self.get_compileropt_value('std', target, subproject) + assert isinstance(std, str) + if std != 'none': + args.append(self._find_best_cpp_std(std)) + return args + class ElbrusCPPCompiler(ElbrusCompiler, CPPCompiler): - def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, - info: 'MachineInfo', - linker: T.Optional['DynamicLinker'] = None, + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, linker: T.Optional['DynamicLinker'] = None, defines: T.Optional[T.Dict[str, str]] = None, full_version: T.Optional[str] = None): - CPPCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, - info, linker=linker, full_version=full_version) + CPPCompiler.__init__(self, ccache, exelist, version, for_machine, + env, linker=linker, full_version=full_version) ElbrusCompiler.__init__(self) def get_options(self) -> 'MutableKeyedOptionDictType': @@ -628,33 +630,30 @@ class ElbrusCPPCompiler(ElbrusCompiler, CPPCompiler): # Elbrus C++ compiler does not have lchmod, but there is only linker warning, not compiler error. # So we should explicitly fail at this case. - def has_function(self, funcname: str, prefix: str, env: 'Environment', *, + def has_function(self, funcname: str, prefix: str, *, extra_args: T.Optional[T.List[str]] = None, dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: if funcname == 'lchmod': return False, False - else: - return super().has_function(funcname, prefix, env, - extra_args=extra_args, - dependencies=dependencies) + return super().has_function(funcname, prefix, extra_args=extra_args, dependencies=dependencies) # Elbrus C++ compiler does not support RTTI, so don't check for it. - def get_option_compile_args(self, target: 'BuildTarget', env: 'Environment', subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_compile_args(self, target: 'BuildTarget', subproject: T.Optional[str] = None) -> T.List[str]: args: T.List[str] = [] - eh = self.get_compileropt_value('eh', env, target, subproject) + eh = self.get_compileropt_value('eh', target, subproject) assert isinstance(eh, str) non_msvc_eh_options(eh, args) - debugstl = self.get_compileropt_value('debugstl', env, target, subproject) + debugstl = self.get_compileropt_value('debugstl', target, subproject) assert isinstance(debugstl, str) if debugstl: args.append('-D_GLIBCXX_DEBUG=1') return args - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: args: T.List[str] = [] - std = self.get_compileropt_value('std', env, target, subproject) + std = self.get_compileropt_value('std', target, subproject) assert isinstance(std, str) if std != 'none': args.append(self._find_best_cpp_std(std)) @@ -662,12 +661,11 @@ class ElbrusCPPCompiler(ElbrusCompiler, CPPCompiler): class IntelCPPCompiler(IntelGnuLikeCompiler, CPPCompiler): - def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, - info: 'MachineInfo', - linker: T.Optional['DynamicLinker'] = None, + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - CPPCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, - info, linker=linker, full_version=full_version) + CPPCompiler.__init__(self, ccache, exelist, version, for_machine, + env, linker=linker, full_version=full_version) IntelGnuLikeCompiler.__init__(self) self.lang_header = 'c++-header' default_warn_args = ['-Wall', '-w3', '-Wpch-messages'] @@ -718,12 +716,12 @@ class IntelCPPCompiler(IntelGnuLikeCompiler, CPPCompiler): self._update_language_stds(opts, c_stds + g_stds) return opts - def get_option_compile_args(self, target: 'BuildTarget', env: 'Environment', subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_compile_args(self, target: 'BuildTarget', subproject: T.Optional[str] = None) -> T.List[str]: args: T.List[str] = [] - rtti = self.get_compileropt_value('rtti', env, target, subproject) - debugstl = self.get_compileropt_value('debugstl', env, target, subproject) - eh = self.get_compileropt_value('eh', env, target, subproject) + rtti = self.get_compileropt_value('rtti', target, subproject) + debugstl = self.get_compileropt_value('debugstl', target, subproject) + eh = self.get_compileropt_value('eh', target, subproject) assert isinstance(rtti, bool) assert isinstance(eh, str) @@ -731,15 +729,15 @@ class IntelCPPCompiler(IntelGnuLikeCompiler, CPPCompiler): if eh == 'none': args.append('-fno-exceptions') - if rtti: + if not rtti: args.append('-fno-rtti') if debugstl: args.append('-D_GLIBCXX_DEBUG=1') return args - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: args: T.List[str] = [] - std = self.get_compileropt_value('std', env, target, subproject) + std = self.get_compileropt_value('std', target, subproject) assert isinstance(std, str) if std != 'none': remap_cpp03 = { @@ -750,7 +748,7 @@ class IntelCPPCompiler(IntelGnuLikeCompiler, CPPCompiler): return args - def get_option_link_args(self, target: 'BuildTarget', env: 'Environment', subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_link_args(self, target: 'BuildTarget', subproject: T.Optional[str] = None) -> T.List[str]: return [] @@ -777,13 +775,13 @@ class VisualStudioLikeCPPCompilerMixin(CompilerMixinBase): 'c++latest': (False, "latest"), } - def get_option_link_args(self, target: 'BuildTarget', env: 'Environment', subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_link_args(self, target: 'BuildTarget', subproject: T.Optional[str] = None) -> T.List[str]: # need a typeddict for this key = self.form_compileropt_key('winlibs').evolve(subproject=subproject) if target: - value = env.coredata.get_option_for_target(target, key) + value = self.environment.coredata.get_option_for_target(target, key) else: - value = env.coredata.optstore.get_value_for(key) + value = self.environment.coredata.optstore.get_value_for(key) return T.cast('T.List[str]', value)[:] def _get_options_impl(self, opts: 'MutableKeyedOptionDictType', cpp_stds: T.List[str]) -> 'MutableKeyedOptionDictType': @@ -811,13 +809,20 @@ class VisualStudioLikeCPPCompilerMixin(CompilerMixinBase): std_opt = opts[self.form_compileropt_key('std')] assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(cpp_stds) + + if version_compare(self.version, '>=19.44.35219'): + key = self.form_compileropt_key('importstd') + opts[key] = options.UserComboOption(self.make_option_name(key), + 'Use #import std.', + 'false', + choices=['false', 'true']) return opts - def get_option_compile_args(self, target: 'BuildTarget', env: 'Environment', subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_compile_args(self, target: 'BuildTarget', subproject: T.Optional[str] = None) -> T.List[str]: args: T.List[str] = [] - eh = self.get_compileropt_value('eh', env, target, subproject) - rtti = self.get_compileropt_value('rtti', env, target, subproject) + eh = self.get_compileropt_value('eh', target, subproject) + rtti = self.get_compileropt_value('rtti', target, subproject) assert isinstance(rtti, bool) assert isinstance(eh, str) @@ -834,9 +839,9 @@ class VisualStudioLikeCPPCompilerMixin(CompilerMixinBase): return args - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: args: T.List[str] = [] - std = self.get_compileropt_value('std', env, target, subproject) + std = self.get_compileropt_value('std', target, subproject) assert isinstance(std, str) permissive, ver = self.VC_VERSION_MAP[std] @@ -857,23 +862,22 @@ class CPP11AsCPP14Mixin(CompilerMixinBase): This is a limitation of Clang and MSVC that ICL doesn't share. """ - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: # Note: there is no explicit flag for supporting C++11; we attempt to do the best we can # which means setting the C++ standard version to C++14, in compilers that support it # (i.e., after VS2015U3) # if one is using anything before that point, one cannot set the standard. stdkey = self.form_compileropt_key('std').evolve(subproject=subproject) if target is not None: - std = env.coredata.get_option_for_target(target, stdkey) + std = self.environment.coredata.get_option_for_target(target, stdkey) else: - std = env.coredata.optstore.get_value_for(stdkey) + std = self.environment.coredata.optstore.get_value_for(stdkey) if std in {'vc++11', 'c++11'}: mlog.warning(self.id, 'does not support C++11;', 'attempting best effort; setting the standard to C++14', once=True, fatal=False) - original_args = super().get_option_std_args(target, env, subproject) - std_mapping = {'/std:c++11': '/std:c++14', - '/std:c++14': '/std:vc++14'} + original_args = super().get_option_std_args(target, subproject) + std_mapping = {'/std:c++11': '/std:c++14'} processed_args = [std_mapping.get(x, x) for x in original_args] return processed_args @@ -883,11 +887,11 @@ class VisualStudioCPPCompiler(CPP11AsCPP14Mixin, VisualStudioLikeCPPCompilerMixi id = 'msvc' def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, - is_cross: bool, info: 'MachineInfo', target: str, + env: Environment, target: str, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - CPPCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, - info, linker=linker, full_version=full_version) + CPPCompiler.__init__(self, ccache, exelist, version, for_machine, + env, linker=linker, full_version=full_version) MSVCCompiler.__init__(self, target) # By default, MSVC has a broken __cplusplus define that pretends to be c++98: @@ -908,12 +912,12 @@ class VisualStudioCPPCompiler(CPP11AsCPP14Mixin, VisualStudioLikeCPPCompilerMixi cpp_stds.extend(['c++20', 'vc++20']) return self._get_options_impl(super().get_options(), cpp_stds) - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: - std = self.get_compileropt_value('std', env, target, subproject) + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: + std = self.get_compileropt_value('std', target, subproject) if std != 'none' and version_compare(self.version, '<19.00.24210'): mlog.warning('This version of MSVC does not support cpp_std arguments', fatal=False) - args = super().get_option_std_args(target, env, subproject) + args = super().get_option_std_args(target, subproject) if version_compare(self.version, '<19.11'): try: @@ -928,11 +932,11 @@ class ClangClCPPCompiler(CPP11AsCPP14Mixin, VisualStudioLikeCPPCompilerMixin, Cl id = 'clang-cl' def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, - is_cross: bool, info: 'MachineInfo', target: str, + env: Environment, target: str, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - CPPCompiler.__init__(self, [], exelist, version, for_machine, is_cross, - info, linker=linker, full_version=full_version) + CPPCompiler.__init__(self, [], exelist, version, for_machine, + env, linker=linker, full_version=full_version) ClangClCompiler.__init__(self, target) def get_options(self) -> 'MutableKeyedOptionDictType': @@ -943,11 +947,11 @@ class ClangClCPPCompiler(CPP11AsCPP14Mixin, VisualStudioLikeCPPCompilerMixin, Cl class IntelClCPPCompiler(VisualStudioLikeCPPCompilerMixin, IntelVisualStudioLikeCompiler, CPPCompiler): def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, - is_cross: bool, info: 'MachineInfo', target: str, + env: Environment, target: str, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - CPPCompiler.__init__(self, [], exelist, version, for_machine, is_cross, - info, linker=linker, full_version=full_version) + CPPCompiler.__init__(self, [], exelist, version, for_machine, + env, linker=linker, full_version=full_version) IntelVisualStudioLikeCompiler.__init__(self, target) def get_options(self) -> 'MutableKeyedOptionDictType': @@ -971,12 +975,11 @@ class IntelLLVMClCPPCompiler(IntelClCPPCompiler): class ArmCPPCompiler(ArmCompiler, CPPCompiler): - def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, - info: 'MachineInfo', - linker: T.Optional['DynamicLinker'] = None, + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - CPPCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, - info, linker=linker, full_version=full_version) + CPPCompiler.__init__(self, ccache, exelist, version, for_machine, + env, linker=linker, full_version=full_version) ArmCompiler.__init__(self) def get_options(self) -> 'MutableKeyedOptionDictType': @@ -986,9 +989,9 @@ class ArmCPPCompiler(ArmCompiler, CPPCompiler): std_opt.set_versions(['c++03', 'c++11']) return opts - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: args: T.List[str] = [] - std = self.get_compileropt_value('std', env, target, subproject) + std = self.get_compileropt_value('std', target, subproject) assert isinstance(std, str) if std == 'c++11': args.append('--cpp11') @@ -996,7 +999,7 @@ class ArmCPPCompiler(ArmCompiler, CPPCompiler): args.append('--cpp') return args - def get_option_link_args(self, target: 'BuildTarget', env: 'Environment', subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_link_args(self, target: 'BuildTarget', subproject: T.Optional[str] = None) -> T.List[str]: return [] def get_compiler_check_args(self, mode: CompileCheckMode) -> T.List[str]: @@ -1004,12 +1007,11 @@ class ArmCPPCompiler(ArmCompiler, CPPCompiler): class CcrxCPPCompiler(CcrxCompiler, CPPCompiler): - def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, - info: 'MachineInfo', - linker: T.Optional['DynamicLinker'] = None, + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - CPPCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, - info, linker=linker, full_version=full_version) + CPPCompiler.__init__(self, ccache, exelist, version, for_machine, + env, linker=linker, full_version=full_version) CcrxCompiler.__init__(self) # Override CCompiler.get_always_args @@ -1022,19 +1024,18 @@ class CcrxCPPCompiler(CcrxCompiler, CPPCompiler): def get_output_args(self, outputname: str) -> T.List[str]: return [f'-output=obj={outputname}'] - def get_option_link_args(self, target: 'BuildTarget', env: 'Environment', subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_link_args(self, target: 'BuildTarget', subproject: T.Optional[str] = None) -> T.List[str]: return [] def get_compiler_check_args(self, mode: CompileCheckMode) -> T.List[str]: return [] class TICPPCompiler(TICompiler, CPPCompiler): - def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, - info: 'MachineInfo', - linker: T.Optional['DynamicLinker'] = None, + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - CPPCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, - info, linker=linker, full_version=full_version) + CPPCompiler.__init__(self, ccache, exelist, version, for_machine, + env, linker=linker, full_version=full_version) TICompiler.__init__(self) def get_options(self) -> 'MutableKeyedOptionDictType': @@ -1045,9 +1046,9 @@ class TICPPCompiler(TICompiler, CPPCompiler): std_opt.set_versions(['c++03']) return opts - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: args: T.List[str] = [] - std = self.get_compileropt_value('std', env, target, subproject) + std = self.get_compileropt_value('std', target, subproject) assert isinstance(std, str) if std != 'none': args.append('--' + std) @@ -1056,7 +1057,7 @@ class TICPPCompiler(TICompiler, CPPCompiler): def get_always_args(self) -> T.List[str]: return [] - def get_option_link_args(self, target: 'BuildTarget', env: 'Environment', subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_link_args(self, target: 'BuildTarget', subproject: T.Optional[str] = None) -> T.List[str]: return [] class C2000CPPCompiler(TICPPCompiler): @@ -1070,11 +1071,10 @@ class MetrowerksCPPCompilerARM(MetrowerksCompiler, CPPCompiler): id = 'mwccarm' def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, - is_cross: bool, info: 'MachineInfo', - linker: T.Optional['DynamicLinker'] = None, + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - CPPCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, - info, linker=linker, full_version=full_version) + CPPCompiler.__init__(self, ccache, exelist, version, for_machine, + env, linker=linker, full_version=full_version) MetrowerksCompiler.__init__(self) def get_instruction_set_args(self, instruction_set: str) -> T.Optional[T.List[str]]: @@ -1085,9 +1085,9 @@ class MetrowerksCPPCompilerARM(MetrowerksCompiler, CPPCompiler): self._update_language_stds(opts, []) return opts - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: args: T.List[str] = [] - std = self.get_compileropt_value('std', env, target, subproject) + std = self.get_compileropt_value('std', target, subproject) assert isinstance(std, str) if std != 'none': args.append('-lang') @@ -1098,11 +1098,10 @@ class MetrowerksCPPCompilerEmbeddedPowerPC(MetrowerksCompiler, CPPCompiler): id = 'mwcceppc' def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, - is_cross: bool, info: 'MachineInfo', - linker: T.Optional['DynamicLinker'] = None, + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - CPPCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, - info, linker=linker, full_version=full_version) + CPPCompiler.__init__(self, ccache, exelist, version, for_machine, + env, linker=linker, full_version=full_version) MetrowerksCompiler.__init__(self) def get_instruction_set_args(self, instruction_set: str) -> T.Optional[T.List[str]]: @@ -1113,10 +1112,23 @@ class MetrowerksCPPCompilerEmbeddedPowerPC(MetrowerksCompiler, CPPCompiler): self._update_language_stds(opts, []) return opts - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: args: T.List[str] = [] - std = self.get_compileropt_value('std', env, target, subproject) + std = self.get_compileropt_value('std', target, subproject) assert isinstance(std, str) if std != 'none': args.append('-lang ' + std) return args + + +class Xc32CPPCompiler(Xc32CPPStds, Xc32Compiler, GnuCPPCompiler): + + """Microchip XC32 C++ compiler.""" + + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, linker: T.Optional[DynamicLinker] = None, + defines: T.Optional[T.Dict[str, str]] = None, + full_version: T.Optional[str] = None): + GnuCPPCompiler.__init__(self, ccache, exelist, version, for_machine, env, + linker=linker, full_version=full_version, defines=defines) + Xc32Compiler.__init__(self) diff --git a/mesonbuild/compilers/cs.py b/mesonbuild/compilers/cs.py index 4bbddeb..cacd9ee 100644 --- a/mesonbuild/compilers/cs.py +++ b/mesonbuild/compilers/cs.py @@ -15,7 +15,6 @@ from .mixins.islinker import BasicLinkerIsCompilerMixin if T.TYPE_CHECKING: from ..dependencies import Dependency - from ..envconfig import MachineInfo from ..environment import Environment from ..mesonlib import MachineChoice @@ -35,8 +34,8 @@ class CsCompiler(BasicLinkerIsCompilerMixin, Compiler): language = 'cs' def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, - info: 'MachineInfo', runner: T.Optional[str] = None): - super().__init__([], exelist, version, for_machine, info) + env: Environment, runner: T.Optional[str] = None): + super().__init__([], exelist, version, for_machine, env) self.runner = runner @classmethod @@ -83,7 +82,7 @@ class CsCompiler(BasicLinkerIsCompilerMixin, Compiler): def get_pch_name(self, header_name: str) -> str: return '' - def sanity_check(self, work_dir: str, environment: 'Environment') -> None: + def sanity_check(self, work_dir: str) -> None: src = 'sanity.cs' obj = 'sanity.exe' source_name = os.path.join(work_dir, src) @@ -102,7 +101,7 @@ class CsCompiler(BasicLinkerIsCompilerMixin, Compiler): cmdlist = [self.runner, obj] else: cmdlist = [os.path.join(work_dir, obj)] - self.run_sanity_check(environment, cmdlist, work_dir, use_exe_wrapper_for_cross=False) + self.run_sanity_check(cmdlist, work_dir, use_exe_wrapper_for_cross=False) def needs_static_linker(self) -> bool: return False @@ -119,8 +118,8 @@ class MonoCompiler(CsCompiler): id = 'mono' def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, - info: 'MachineInfo'): - super().__init__(exelist, version, for_machine, info, runner='mono') + env: Environment): + super().__init__(exelist, version, for_machine, env, runner='mono') def rsp_file_syntax(self) -> 'RSPFileSyntax': return RSPFileSyntax.GCC diff --git a/mesonbuild/compilers/cuda.py b/mesonbuild/compilers/cuda.py index ab00cf1..cf0fcec 100644 --- a/mesonbuild/compilers/cuda.py +++ b/mesonbuild/compilers/cuda.py @@ -22,7 +22,6 @@ if T.TYPE_CHECKING: from ..options import MutableKeyedOptionDictType from ..dependencies import Dependency from ..environment import Environment # noqa: F401 - from ..envconfig import MachineInfo from ..linkers.linkers import DynamicLinker from ..mesonlib import MachineChoice @@ -183,11 +182,10 @@ class CudaCompiler(Compiler): id = 'nvcc' def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, - is_cross: bool, - host_compiler: Compiler, info: 'MachineInfo', + host_compiler: Compiler, env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - super().__init__(ccache, exelist, version, for_machine, info, linker=linker, full_version=full_version, is_cross=is_cross) + super().__init__(ccache, exelist, version, for_machine, env, linker=linker, full_version=full_version) self.host_compiler = host_compiler self.base_options = host_compiler.base_options # -Wpedantic generates useless churn due to nvcc's dual compilation model producing @@ -198,6 +196,7 @@ class CudaCompiler(Compiler): for level, flags in host_compiler.warn_args.items() } self.host_werror_args = ['-Xcompiler=' + x for x in self.host_compiler.get_werror_args()] + self.debug_macros_available = version_compare(self.version, '>=12.9') @classmethod def _shield_nvcc_list_arg(cls, arg: str, listmode: bool = True) -> str: @@ -495,10 +494,10 @@ class CudaCompiler(Compiler): def needs_static_linker(self) -> bool: return False - def thread_link_flags(self, environment: 'Environment') -> T.List[str]: - return self._to_host_flags(self.host_compiler.thread_link_flags(environment), Phase.LINKER) + def thread_link_flags(self) -> T.List[str]: + return self._to_host_flags(self.host_compiler.thread_link_flags(), Phase.LINKER) - def sanity_check(self, work_dir: str, env: 'Environment') -> None: + def sanity_check(self, work_dir: str) -> None: mlog.debug('Sanity testing ' + self.get_display_language() + ' compiler:', ' '.join(self.exelist)) mlog.debug('Is cross compiler: %s.' % str(self.is_cross)) @@ -551,10 +550,10 @@ class CudaCompiler(Compiler): # Use the -ccbin option, if available, even during sanity checking. # Otherwise, on systems where CUDA does not support the default compiler, # NVCC becomes unusable. - flags += self.get_ccbin_args(None, env, '') + flags += self._get_ccbin_args(None, '') # If cross-compiling, we can't run the sanity check, only compile it. - if self.is_cross and not env.has_exe_wrapper(): + if self.is_cross and not self.environment.has_exe_wrapper(): # Linking cross built apps is painful. You can't really # tell if you should use -nostdlib or not and for example # on OSX the compiler binary is the same but you need @@ -581,7 +580,7 @@ class CudaCompiler(Compiler): cmdlist = self.exelist + ['--run', f'"{binary_name}"'] try: - stdo, stde = self.run_sanity_check(env, cmdlist, work_dir) + stdo, stde = self.run_sanity_check(cmdlist, work_dir) except EnvironmentException: raise EnvironmentException(f'Executables created by {self.language} compiler {self.name_string()} are not runnable.') @@ -591,8 +590,7 @@ class CudaCompiler(Compiler): if stde == '': self.detected_cc = stdo - def has_header_symbol(self, hname: str, symbol: str, prefix: str, - env: 'Environment', *, + def has_header_symbol(self, hname: str, symbol: str, prefix: str, *, extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: if extra_args is None: @@ -608,7 +606,7 @@ class CudaCompiler(Compiler): #endif return 0; }}''' - found, cached = self.compiles(t.format_map(fargs), env, extra_args=extra_args, dependencies=dependencies) + found, cached = self.compiles(t.format_map(fargs), extra_args=extra_args, dependencies=dependencies) if found: return True, cached # Check if it's a class or a template @@ -618,7 +616,7 @@ class CudaCompiler(Compiler): int main(void) {{ return 0; }}''' - return self.compiles(t.format_map(fargs), env, extra_args=extra_args, dependencies=dependencies) + return self.compiles(t.format_map(fargs), extra_args=extra_args, dependencies=dependencies) _CPP14_VERSION = '>=9.0' _CPP17_VERSION = '>=11.0' @@ -650,40 +648,39 @@ class CudaCompiler(Compiler): return opts - def get_option_compile_args(self, target: 'BuildTarget', env: 'Environment', subproject: T.Optional[str] = None) -> T.List[str]: - args = self.get_ccbin_args(target, env, subproject) + def get_option_compile_args(self, target: 'BuildTarget', subproject: T.Optional[str] = None) -> T.List[str]: + args = self._get_ccbin_args(target, subproject) try: - host_compiler_args = self.host_compiler.get_option_compile_args(target, env, subproject) + host_compiler_args = self.host_compiler.get_option_compile_args(target, subproject) except KeyError: host_compiler_args = [] return args + self._to_host_flags(host_compiler_args) - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: # On Windows, the version of the C++ standard used by nvcc is dictated by # 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 = self.get_compileropt_value('std', env, target, subproject) + std = self.get_compileropt_value('std', target, subproject) assert isinstance(std, str) if std != 'none': return ['--std=' + std] try: - host_compiler_args = self.host_compiler.get_option_std_args(target, env, subproject) + host_compiler_args = self.host_compiler.get_option_std_args(target, subproject) except KeyError: host_compiler_args = [] return self._to_host_flags(host_compiler_args) - def get_option_link_args(self, target: 'BuildTarget', env: 'Environment', subproject: T.Optional[str] = None) -> T.List[str]: - args = self.get_ccbin_args(target, env, subproject) - return args + self._to_host_flags(self.host_compiler.get_option_link_args(target, env, subproject), Phase.LINKER) + def get_option_link_args(self, target: 'BuildTarget', subproject: T.Optional[str] = None) -> T.List[str]: + args = self._get_ccbin_args(target, subproject) + return args + self._to_host_flags(self.host_compiler.get_option_link_args(target, subproject), Phase.LINKER) - def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, - suffix: str, soversion: str, + def get_soname_args(self, prefix: str, shlib_name: str, suffix: str, soversion: str, darwin_versions: T.Tuple[str, str]) -> T.List[str]: return self._to_host_flags(self.host_compiler.get_soname_args( - env, prefix, shlib_name, suffix, soversion, darwin_versions), Phase.LINKER) + prefix, shlib_name, suffix, soversion, darwin_versions), Phase.LINKER) def get_compile_only_args(self) -> T.List[str]: return ['-c'] @@ -697,11 +694,11 @@ class CudaCompiler(Compiler): # return self._to_host_flags(self.host_compiler.get_optimization_args(optimization_level)) return cuda_optimization_args[optimization_level] - def sanitizer_compile_args(self, value: T.List[str]) -> T.List[str]: - return self._to_host_flags(self.host_compiler.sanitizer_compile_args(value)) + def sanitizer_compile_args(self, target: T.Optional[BuildTarget], value: T.List[str]) -> T.List[str]: + return self._to_host_flags(self.host_compiler.sanitizer_compile_args(target, value)) - def sanitizer_link_args(self, value: T.List[str]) -> T.List[str]: - return self._to_host_flags(self.host_compiler.sanitizer_link_args(value)) + def sanitizer_link_args(self, target: T.Optional[BuildTarget], value: T.List[str]) -> T.List[str]: + return self._to_host_flags(self.host_compiler.sanitizer_link_args(target, value)) def get_debug_args(self, is_debug: bool) -> T.List[str]: return cuda_debug_args[is_debug] @@ -730,11 +727,11 @@ class CudaCompiler(Compiler): def get_optimization_link_args(self, optimization_level: str) -> T.List[str]: return self._to_host_flags(self.host_compiler.get_optimization_link_args(optimization_level), Phase.LINKER) - def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, - rpath_paths: T.Tuple[str, ...], build_rpath: str, - install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + def build_rpath_args(self, build_dir: str, from_dir: str, target: BuildTarget, + extra_paths: T.Optional[T.List[str]] = None + ) -> T.Tuple[T.List[str], T.Set[bytes]]: (rpath_args, rpath_dirs_to_remove) = self.host_compiler.build_rpath_args( - env, build_dir, from_dir, rpath_paths, build_rpath, install_rpath) + build_dir, from_dir, target, extra_paths) return (self._to_host_flags(rpath_args, Phase.LINKER), rpath_dirs_to_remove) def linker_to_compiler_args(self, args: T.List[str]) -> T.List[str]: @@ -762,9 +759,9 @@ class CudaCompiler(Compiler): def get_std_exe_link_args(self) -> T.List[str]: return self._to_host_flags(self.host_compiler.get_std_exe_link_args(), Phase.LINKER) - def find_library(self, libname: str, env: 'Environment', extra_dirs: T.List[str], - libtype: LibType = LibType.PREFER_SHARED, lib_prefix_warning: bool = True) -> T.Optional[T.List[str]]: - return self.host_compiler.find_library(libname, env, extra_dirs, libtype, lib_prefix_warning) + def find_library(self, libname: str, extra_dirs: T.List[str], libtype: LibType = LibType.PREFER_SHARED, + lib_prefix_warning: bool = True, ignore_system_dirs: bool = False) -> T.Optional[T.List[str]]: + return self.host_compiler.find_library(libname, extra_dirs, libtype, lib_prefix_warning, ignore_system_dirs) def get_crt_compile_args(self, crt_val: str, buildtype: str) -> T.List[str]: return self._to_host_flags(self.host_compiler.get_crt_compile_args(crt_val, buildtype)) @@ -787,15 +784,13 @@ class CudaCompiler(Compiler): def get_dependency_link_args(self, dep: 'Dependency') -> T.List[str]: return self._to_host_flags(super().get_dependency_link_args(dep), Phase.LINKER) - def get_ccbin_args(self, - target: 'T.Optional[BuildTarget]', - env: 'Environment', - subproject: T.Optional[str] = None) -> T.List[str]: + def _get_ccbin_args(self, target: 'T.Optional[BuildTarget]', + subproject: T.Optional[str] = None) -> T.List[str]: key = self.form_compileropt_key('ccbindir').evolve(subproject=subproject) if target: - ccbindir = env.coredata.get_option_for_target(target, key) + ccbindir = self.environment.coredata.get_option_for_target(target, key) else: - ccbindir = env.coredata.optstore.get_value_for(key) + ccbindir = self.environment.coredata.optstore.get_value_for(key) if isinstance(ccbindir, str) and ccbindir != '': return [self._shield_nvcc_list_arg('-ccbin='+ccbindir, False)] else: @@ -807,14 +802,19 @@ class CudaCompiler(Compiler): def get_profile_use_args(self) -> T.List[str]: return ['-Xcompiler=' + x for x in self.host_compiler.get_profile_use_args()] - def get_assert_args(self, disable: bool, env: 'Environment') -> T.List[str]: - return self.host_compiler.get_assert_args(disable, env) + def get_assert_args(self, disable: bool) -> T.List[str]: + cccl_macros = [] + if not disable and self.debug_macros_available: + # https://github.com/NVIDIA/cccl/pull/2382 + cccl_macros = ['-DCCCL_ENABLE_ASSERTIONS=1'] - def has_multi_arguments(self, args: T.List[str], env: Environment) -> T.Tuple[bool, bool]: + return self.host_compiler.get_assert_args(disable) + cccl_macros + + def has_multi_arguments(self, args: T.List[str]) -> T.Tuple[bool, bool]: args = self._to_host_flags(args) - return self.compiles('int main(void) { return 0; }', env, extra_args=args, mode=CompileCheckMode.COMPILE) + return self.compiles('int main(void) { return 0; }', extra_args=args, mode=CompileCheckMode.COMPILE) - def has_multi_link_arguments(self, args: T.List[str], env: Environment) -> T.Tuple[bool, bool]: + def has_multi_link_arguments(self, args: T.List[str]) -> T.Tuple[bool, bool]: args = ['-Xnvlink='+self._shield_nvcc_list_arg(s) for s in self.linker.fatal_warnings()] args += self._to_host_flags(args, phase=Phase.LINKER) - return self.compiles('int main(void) { return 0; }', env, extra_args=args, mode=CompileCheckMode.LINK) + return self.compiles('int main(void) { return 0; }', extra_args=args, mode=CompileCheckMode.LINK) diff --git a/mesonbuild/compilers/cython.py b/mesonbuild/compilers/cython.py index 50bb465..b33147e 100644 --- a/mesonbuild/compilers/cython.py +++ b/mesonbuild/compilers/cython.py @@ -12,7 +12,6 @@ from .compilers import Compiler if T.TYPE_CHECKING: from ..options import MutableKeyedOptionDictType - from ..environment import Environment from ..build import BuildTarget @@ -49,9 +48,9 @@ class CythonCompiler(Compiler): def get_depfile_suffix(self) -> str: return 'dep' - def sanity_check(self, work_dir: str, environment: 'Environment') -> None: + def sanity_check(self, work_dir: str) -> None: code = 'print("hello world")' - with self.cached_compile(code, environment.coredata) as p: + with self.cached_compile(code) as p: if p.returncode != 0: raise EnvironmentException(f'Cython compiler {self.id!r} cannot compile programs') @@ -86,13 +85,13 @@ class CythonCompiler(Compiler): return opts - def get_option_compile_args(self, target: 'BuildTarget', env: 'Environment', subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_compile_args(self, target: 'BuildTarget', subproject: T.Optional[str] = None) -> T.List[str]: args: T.List[str] = [] - version = self.get_compileropt_value('version', env, target, subproject) + version = self.get_compileropt_value('version', target, subproject) assert isinstance(version, str) args.append(f'-{version}') - lang = self.get_compileropt_value('language', env, target, subproject) + lang = self.get_compileropt_value('language', target, subproject) assert isinstance(lang, str) if lang == 'cpp': args.append('--cplus') diff --git a/mesonbuild/compilers/d.py b/mesonbuild/compilers/d.py index 51f2436..dd12094 100644 --- a/mesonbuild/compilers/d.py +++ b/mesonbuild/compilers/d.py @@ -26,7 +26,7 @@ from .mixins.gnu import gnu_common_warning_args if T.TYPE_CHECKING: from . import compilers - from ..build import DFeatures + from ..build import BuildTarget, DFeatures from ..dependencies import Dependency from ..envconfig import MachineInfo from ..environment import Environment @@ -175,9 +175,9 @@ class DmdLikeCompilerMixin(CompilerMixinBase): def gen_import_library_args(self, implibname: str) -> T.List[str]: return self.linker.import_library_args(implibname) - def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, - rpath_paths: T.Tuple[str, ...], build_rpath: str, - install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + def build_rpath_args(self, build_dir: str, from_dir: str, target: BuildTarget, + extra_paths: T.Optional[T.List[str]] = None + ) -> T.Tuple[T.List[str], T.Set[bytes]]: if self.info.is_windows(): return ([], set()) @@ -188,7 +188,7 @@ class DmdLikeCompilerMixin(CompilerMixinBase): # split into two separate arguments both prefaced with the -L=. args: T.List[str] = [] (rpath_args, rpath_dirs_to_remove) = super().build_rpath_args( - env, build_dir, from_dir, rpath_paths, build_rpath, install_rpath) + build_dir, from_dir, target) for r in rpath_args: if ',' in r: a, b = r.split(',', maxsplit=1) @@ -199,7 +199,7 @@ class DmdLikeCompilerMixin(CompilerMixinBase): return (args, rpath_dirs_to_remove) return super().build_rpath_args( - env, build_dir, from_dir, rpath_paths, build_rpath, install_rpath) + build_dir, from_dir, target) @classmethod def _translate_args_to_nongnu(cls, args: T.List[str], info: MachineInfo, link_id: str) -> T.List[str]: @@ -377,11 +377,9 @@ class DmdLikeCompilerMixin(CompilerMixinBase): return [] return self.mscrt_args[self.get_crt_val(crt_val, buildtype)] - def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, - suffix: str, soversion: str, + def get_soname_args(self, prefix: str, shlib_name: str, suffix: str, soversion: str, darwin_versions: T.Tuple[str, str]) -> T.List[str]: - sargs = super().get_soname_args(env, prefix, shlib_name, suffix, - soversion, darwin_versions) + sargs = super().get_soname_args(prefix, shlib_name, suffix, soversion, darwin_versions) # LDC and DMD actually do use a linker, but they proxy all of that with # their own arguments @@ -431,15 +429,14 @@ class DCompiler(Compiler): language = 'd' def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, - info: 'MachineInfo', arch: str, *, + env: Environment, arch: str, *, linker: T.Optional['DynamicLinker'] = None, - full_version: T.Optional[str] = None, - is_cross: bool = False): - super().__init__([], exelist, version, for_machine, info, linker=linker, - full_version=full_version, is_cross=is_cross) + full_version: T.Optional[str] = None): + super().__init__([], exelist, version, for_machine, env, linker=linker, + full_version=full_version) self.arch = arch - def sanity_check(self, work_dir: str, environment: 'Environment') -> None: + def sanity_check(self, work_dir: str) -> None: source_name = os.path.join(work_dir, 'sanity.d') output_name = os.path.join(work_dir, 'dtest') with open(source_name, 'w', encoding='utf-8') as ofile: @@ -448,7 +445,7 @@ class DCompiler(Compiler): compile_cmdlist = self.exelist + self.get_output_args(output_name) + self._get_target_arch_args() + [source_name] # If cross-compiling, we can't run the sanity check, only compile it. - if self.is_cross and not environment.has_exe_wrapper(): + if self.is_cross and not self.environment.has_exe_wrapper(): compile_cmdlist += self.get_compile_only_args() pc = subprocess.Popen(compile_cmdlist, cwd=work_dir) @@ -456,7 +453,7 @@ class DCompiler(Compiler): if pc.returncode != 0: raise EnvironmentException('D compiler %s cannot compile programs.' % self.name_string()) - stdo, stde = self.run_sanity_check(environment, [output_name], work_dir) + stdo, stde = self.run_sanity_check([output_name], work_dir) def needs_static_linker(self) -> bool: return True @@ -539,8 +536,8 @@ class DCompiler(Compiler): def compiler_args(self, args: T.Optional[T.Iterable[str]] = None) -> DCompilerArgs: return DCompilerArgs(self, args) - def has_multi_arguments(self, args: T.List[str], env: 'Environment') -> T.Tuple[bool, bool]: - return self.compiles('int i;\n', env, extra_args=args) + def has_multi_arguments(self, args: T.List[str]) -> T.Tuple[bool, bool]: + return self.compiles('int i;\n', extra_args=args) def _get_target_arch_args(self) -> T.List[str]: # LDC2 on Windows targets to current OS architecture, but @@ -570,15 +567,15 @@ class DCompiler(Compiler): args.append(extra_args) return args - def run(self, code: 'mesonlib.FileOrString', env: 'Environment', + def run(self, code: 'mesonlib.FileOrString', extra_args: T.Union[T.List[str], T.Callable[[CompileCheckMode], T.List[str]], None] = None, dependencies: T.Optional[T.List['Dependency']] = None, run_env: T.Optional[T.Dict[str, str]] = None, run_cwd: T.Optional[str] = None) -> compilers.RunResult: extra_args = self._get_compile_extra_args(extra_args) - return super().run(code, env, extra_args, dependencies, run_env, run_cwd) + return super().run(code, extra_args, dependencies, run_env, run_cwd) - def sizeof(self, typename: str, prefix: str, env: 'Environment', *, + def sizeof(self, typename: str, prefix: str, *, extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[int, bool]: if extra_args is None: @@ -590,7 +587,7 @@ class DCompiler(Compiler): writeln(({typename}).sizeof); }} ''' - res = self.cached_run(t, env, extra_args=extra_args, + res = self.cached_run(t, extra_args=extra_args, dependencies=dependencies) if not res.compiled: return -1, False @@ -598,7 +595,7 @@ class DCompiler(Compiler): raise mesonlib.EnvironmentException('Could not run sizeof test binary.') return int(res.stdout), res.cached - def alignment(self, typename: str, prefix: str, env: 'Environment', *, + def alignment(self, typename: str, prefix: str, *, extra_args: T.Optional[T.List[str]] = None, dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[int, bool]: if extra_args is None: @@ -610,8 +607,7 @@ class DCompiler(Compiler): writeln(({typename}).alignof); }} ''' - res = self.run(t, env, extra_args=extra_args, - dependencies=dependencies) + res = self.run(t, extra_args=extra_args, dependencies=dependencies) if not res.compiled: raise mesonlib.EnvironmentException('Could not compile alignment test.') if res.returncode != 0: @@ -621,7 +617,7 @@ class DCompiler(Compiler): raise mesonlib.EnvironmentException(f'Could not determine alignment of {typename}. Sorry. You might want to file a bug.') return align, res.cached - def has_header(self, hname: str, prefix: str, env: 'Environment', *, + def has_header(self, hname: str, prefix: str, *, extra_args: T.Union[None, T.List[str], T.Callable[['CompileCheckMode'], T.List[str]]] = None, dependencies: T.Optional[T.List['Dependency']] = None, disable_cache: bool = False) -> T.Tuple[bool, bool]: @@ -630,7 +626,7 @@ class DCompiler(Compiler): code = f'''{prefix} import {hname}; ''' - return self.compiles(code, env, extra_args=extra_args, + return self.compiles(code, extra_args=extra_args, dependencies=dependencies, mode=CompileCheckMode.COMPILE, disable_cache=disable_cache) class GnuDCompiler(GnuCompiler, DCompiler): @@ -640,13 +636,11 @@ class GnuDCompiler(GnuCompiler, DCompiler): id = 'gcc' def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, - info: 'MachineInfo', arch: str, *, + env: Environment, arch: str, *, linker: T.Optional['DynamicLinker'] = None, - full_version: T.Optional[str] = None, - is_cross: bool = False): - DCompiler.__init__(self, exelist, version, for_machine, info, arch, - linker=linker, - full_version=full_version, is_cross=is_cross) + full_version: T.Optional[str] = None): + DCompiler.__init__(self, exelist, version, for_machine, env, arch, + linker=linker, full_version=full_version) GnuCompiler.__init__(self, {}) default_warn_args = ['-Wall', '-Wdeprecated'] self.warn_args = {'0': [], @@ -699,7 +693,7 @@ class GnuDCompiler(GnuCompiler, DCompiler): return args return args + ['-shared-libphobos'] - def get_assert_args(self, disable: bool, env: 'Environment') -> T.List[str]: + def get_assert_args(self, disable: bool) -> T.List[str]: if disable: return ['-frelease'] return [] @@ -723,13 +717,12 @@ class LLVMDCompiler(DmdLikeCompilerMixin, DCompiler): id = 'llvm' def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, - info: 'MachineInfo', arch: str, *, + env: Environment, arch: str, *, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None, - is_cross: bool = False, version_output: T.Optional[str] = None): - DCompiler.__init__(self, exelist, version, for_machine, info, arch, - linker=linker, - full_version=full_version, is_cross=is_cross) + version_output: T.Optional[str] = None): + DCompiler.__init__(self, exelist, version, for_machine, env, arch, + linker=linker, full_version=full_version) DmdLikeCompilerMixin.__init__(self, dmd_frontend_version=find_ldc_dmd_frontend_version(version_output)) self.base_options = {OptionKey(o) for o in ['b_coverage', 'b_colorout', 'b_vscrt', 'b_ndebug']} @@ -769,7 +762,7 @@ class LLVMDCompiler(DmdLikeCompilerMixin, DCompiler): return args return args + ['-link-defaultlib-shared'] - def get_assert_args(self, disable: bool, env: 'Environment') -> T.List[str]: + def get_assert_args(self, disable: bool) -> T.List[str]: if disable: return ['--release'] return [] @@ -786,13 +779,11 @@ class DmdDCompiler(DmdLikeCompilerMixin, DCompiler): id = 'dmd' def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, - info: 'MachineInfo', arch: str, *, + env: Environment, arch: str, *, linker: T.Optional['DynamicLinker'] = None, - full_version: T.Optional[str] = None, - is_cross: bool = False): - DCompiler.__init__(self, exelist, version, for_machine, info, arch, - linker=linker, - full_version=full_version, is_cross=is_cross) + full_version: T.Optional[str] = None): + DCompiler.__init__(self, exelist, version, for_machine, env, arch, + linker=linker, full_version=full_version) DmdLikeCompilerMixin.__init__(self, version) self.base_options = {OptionKey(o) for o in ['b_coverage', 'b_colorout', 'b_vscrt', 'b_ndebug']} @@ -855,7 +846,7 @@ class DmdDCompiler(DmdLikeCompilerMixin, DCompiler): return args return args + ['-defaultlib=phobos2', '-debuglib=phobos2'] - def get_assert_args(self, disable: bool, env: 'Environment') -> T.List[str]: + def get_assert_args(self, disable: bool) -> T.List[str]: if disable: return ['-release'] return [] diff --git a/mesonbuild/compilers/detect.py b/mesonbuild/compilers/detect.py index 53bdd85..db2bdf6 100644 --- a/mesonbuild/compilers/detect.py +++ b/mesonbuild/compilers/detect.py @@ -5,9 +5,10 @@ from __future__ import annotations from ..mesonlib import ( MesonException, EnvironmentException, MachineChoice, join_args, - search_version, is_windows, Popen_safe, Popen_safe_logged, windows_proof_rm, + search_version, is_windows, Popen_safe, Popen_safe_logged, version_compare, windows_proof_rm, ) -from ..envconfig import BinaryTable +from ..programs import ExternalProgram +from ..envconfig import BinaryTable, detect_cpu_family, detect_machine_info from .. import mlog from ..linkers import guess_win_linker, guess_nix_linker @@ -22,6 +23,7 @@ import typing as T if T.TYPE_CHECKING: from .compilers import Compiler + from .asm import ASMCompiler from .c import CCompiler from .cpp import CPPCompiler from .fortran import FortranCompiler @@ -46,7 +48,7 @@ if is_windows(): # There is currently no pgc++ for Windows, only for Mac and Linux. defaults['cpp'] = ['icl', 'cl', 'c++', 'g++', 'clang++', 'clang-cl'] # the binary flang-new will be renamed to flang in the foreseeable future - defaults['fortran'] = ['ifort', 'gfortran', 'flang-new', 'flang', 'pgfortran', 'g95'] + defaults['fortran'] = ['ifort', 'ifx', 'gfortran', 'flang-new', 'flang', 'pgfortran', 'g95'] defaults['objc'] = ['clang', 'clang-cl', 'gcc'] defaults['objcpp'] = ['clang++', 'clang-cl', 'g++'] defaults['cs'] = ['csc', 'mcs'] @@ -78,6 +80,7 @@ defaults['clang_cl_static_linker'] = ['llvm-lib'] defaults['cuda_static_linker'] = ['nvlink'] defaults['gcc_static_linker'] = ['gcc-ar'] defaults['clang_static_linker'] = ['llvm-ar'] +defaults['emxomf_static_linker'] = ['emxomfar'] defaults['nasm'] = ['nasm', 'yasm'] @@ -107,9 +110,9 @@ def detect_compiler_for(env: 'Environment', lang: str, for_machine: MachineChoic if comp is None: return comp assert comp.for_machine == for_machine - env.coredata.process_compiler_options(lang, comp, env, subproject) + env.coredata.process_compiler_options(lang, comp, subproject) if not skip_sanity_check: - comp.sanity_check(env.get_scratch_dir(), env) + comp.sanity_check(env.get_scratch_dir()) env.coredata.compilers[comp.for_machine][lang] = comp return comp @@ -118,7 +121,7 @@ def detect_compiler_for(env: 'Environment', lang: str, for_machine: MachineChoic # ======= def _get_compilers(env: 'Environment', lang: str, for_machine: MachineChoice, - allow_build_machine: bool = False) -> T.Tuple[T.List[T.List[str]], T.List[str]]: + allow_build_machine: bool = False) -> T.Tuple[T.List[T.List[str]], T.Union[None, ExternalProgram]]: ''' The list of compilers is detected in the exact same way for C, C++, ObjC, ObjC++, Fortran, CS so consolidate it here. @@ -156,6 +159,7 @@ def _handle_exceptions( def detect_static_linker(env: 'Environment', compiler: Compiler) -> StaticLinker: from . import d from ..linkers import linkers + from ..options import OptionKey linker = env.lookup_binary_entry(compiler.for_machine, 'ar') if linker is not None: trials = [linker] @@ -165,6 +169,8 @@ def detect_static_linker(env: 'Environment', compiler: Compiler) -> StaticLinker trials = [defaults['cuda_static_linker']] + default_linkers elif compiler.get_argument_syntax() == 'msvc': trials = [defaults['vs_static_linker'], defaults['clang_cl_static_linker']] + elif env.machines[compiler.for_machine].is_os2() and env.coredata.optstore.get_value_for(OptionKey('os2_emxomf')): + trials = [defaults['emxomf_static_linker']] + default_linkers elif compiler.id == 'gcc': # Use gcc-ar if available; needed for LTO trials = [defaults['gcc_static_linker']] + default_linkers @@ -207,50 +213,55 @@ def detect_static_linker(env: 'Environment', compiler: Compiler) -> StaticLinker popen_exceptions[join_args(linker + [arg])] = e continue if "xilib: executing 'lib'" in err: - return linkers.IntelVisualStudioLinker(linker, getattr(compiler, 'machine', None)) + return linkers.IntelVisualStudioLinker(linker, env, getattr(compiler, 'machine', None)) if '/OUT:' in out.upper() or '/OUT:' in err.upper(): - return linkers.VisualStudioLinker(linker, getattr(compiler, 'machine', None)) + return linkers.VisualStudioLinker(linker, env, getattr(compiler, 'machine', None)) if 'ar-Error-Unknown switch: --version' in err: - return linkers.PGIStaticLinker(linker) + return linkers.PGIStaticLinker(linker, env) if p.returncode == 0 and 'armar' in linker_name: - return linkers.ArmarLinker(linker) + return linkers.ArmarLinker(linker, env) if 'DMD32 D Compiler' in out or 'DMD64 D Compiler' in out: assert isinstance(compiler, d.DCompiler) - return linkers.DLinker(linker, compiler.arch) + return linkers.DLinker(linker, env, compiler.arch) if 'LDC - the LLVM D compiler' in out: assert isinstance(compiler, d.DCompiler) - return linkers.DLinker(linker, compiler.arch, rsp_syntax=compiler.rsp_file_syntax()) + return linkers.DLinker(linker, env, compiler.arch, rsp_syntax=compiler.rsp_file_syntax()) if 'GDC' in out and ' based on D ' in out: assert isinstance(compiler, d.DCompiler) - return linkers.DLinker(linker, compiler.arch) + return linkers.DLinker(linker, env, compiler.arch) if err.startswith('Renesas') and 'rlink' in linker_name: - return linkers.CcrxLinker(linker) - if out.startswith('GNU ar') and 'xc16-ar' in linker_name: - return linkers.Xc16Linker(linker) + return linkers.CcrxLinker(linker, env) + if out.startswith('GNU ar'): + if 'xc16-ar' in linker_name: + return linkers.Xc16Linker(linker, env) + elif 'xc32-ar' in linker_name: + return linkers.Xc32ArLinker(compiler.for_machine, linker, env) if 'Texas Instruments Incorporated' in out: if 'ar2000' in linker_name: - return linkers.C2000Linker(linker) + return linkers.C2000Linker(linker, env) elif 'ar6000' in linker_name: - return linkers.C6000Linker(linker) + return linkers.C6000Linker(linker, env) else: - return linkers.TILinker(linker) + return linkers.TILinker(linker, env) if out.startswith('The CompCert'): - return linkers.CompCertLinker(linker) + return linkers.CompCertLinker(linker, env) if out.strip().startswith('Metrowerks') or out.strip().startswith('Freescale'): if 'ARM' in out: - return linkers.MetrowerksStaticLinkerARM(linker) + return linkers.MetrowerksStaticLinkerARM(linker, env) else: - return linkers.MetrowerksStaticLinkerEmbeddedPowerPC(linker) + return linkers.MetrowerksStaticLinkerEmbeddedPowerPC(linker, env) if 'TASKING VX-toolset' in err: - return linkers.TaskingStaticLinker(linker) + return linkers.TaskingStaticLinker(linker, env) if p.returncode == 0: - return linkers.ArLinker(compiler.for_machine, linker) + return linkers.ArLinker(compiler.for_machine, linker, env) if p.returncode == 1 and err.startswith('usage'): # OSX - return linkers.AppleArLinker(compiler.for_machine, linker) + return linkers.AppleArLinker(compiler.for_machine, linker, env) if p.returncode == 1 and err.startswith('Usage'): # AIX - return linkers.AIXArLinker(linker) + return linkers.AIXArLinker(linker, env) if p.returncode == 1 and err.startswith('ar: bad option: --'): # Solaris - return linkers.ArLinker(compiler.for_machine, linker) + return linkers.ArLinker(compiler.for_machine, linker, env) + if p.returncode == 1 and err.startswith('emxomfar'): + return linkers.EmxomfArLinker(compiler.for_machine, linker, env) _handle_exceptions(popen_exceptions, trials, 'linker') raise EnvironmentException('Unreachable code (exception to make mypy happy)') @@ -269,11 +280,10 @@ def _detect_c_or_cpp_compiler(env: 'Environment', lang: str, for_machine: Machin from . import c, cpp from ..linkers import linkers popen_exceptions: T.Dict[str, T.Union[Exception, str]] = {} - compilers, ccache = _get_compilers(env, lang, for_machine) + compilers, ccache_exe = _get_compilers(env, lang, for_machine) + ccache = ccache_exe.get_command() if (ccache_exe and ccache_exe.found()) else [] if override_compiler is not None: compilers = [override_compiler] - is_cross = env.is_cross_build(for_machine) - info = env.machines[for_machine] cls: T.Union[T.Type[CCompiler], T.Type[CPPCompiler]] lnk: T.Union[T.Type[StaticLinker], T.Type[DynamicLinker]] @@ -340,7 +350,7 @@ def _detect_c_or_cpp_compiler(env: 'Environment', lang: str, for_machine: Machin guess_gcc_or_lcc = 'gcc' if 'e2k' in out and 'lcc' in out: guess_gcc_or_lcc = 'lcc' - if 'Microchip Technology' in out: + if 'Microchip' in out: # this output has "Free Software Foundation" in its version guess_gcc_or_lcc = None @@ -360,13 +370,13 @@ def _detect_c_or_cpp_compiler(env: 'Environment', lang: str, for_machine: Machin linker = guess_nix_linker(env, compiler, cls, version, for_machine) return cls( - ccache, compiler, version, for_machine, is_cross, - info, defines=defines, full_version=full_version, + ccache, compiler, version, for_machine, + env, defines=defines, full_version=full_version, linker=linker) if 'Emscripten' in out: cls = c.EmscriptenCCompiler if lang == 'c' else cpp.EmscriptenCPPCompiler - env.coredata.add_lang_args(cls.language, cls, for_machine, env) + env.add_lang_args(cls.language, cls, for_machine) # emcc requires a file input in order to pass arguments to the # linker. It'll exit with an error code, but still print the @@ -376,10 +386,10 @@ def _detect_c_or_cpp_compiler(env: 'Environment', lang: str, for_machine: Machin _, o, _ = Popen_safe(cmd) linker = linkers.WASMDynamicLinker( - compiler, for_machine, cls.LINKER_PREFIX, + compiler, env, for_machine, cls.LINKER_PREFIX, [], version=search_version(o)) return cls( - ccache, compiler, version, for_machine, is_cross, info, + ccache, compiler, version, for_machine, env, linker=linker, full_version=full_version) if 'Arm C/C++/Fortran Compiler' in out: @@ -392,7 +402,7 @@ def _detect_c_or_cpp_compiler(env: 'Environment', lang: str, for_machine: Machin cls = cpp.ArmLtdClangCPPCompiler linker = guess_nix_linker(env, compiler, cls, version, for_machine) return cls( - ccache, compiler, version, for_machine, is_cross, info, + ccache, compiler, version, for_machine, env, linker=linker) if 'armclang' in out: # The compiler version is not present in the first line of output, @@ -409,10 +419,10 @@ def _detect_c_or_cpp_compiler(env: 'Environment', lang: str, for_machine: Machin version = search_version(arm_ver_str) full_version = arm_ver_str cls = c.ArmclangCCompiler if lang == 'c' else cpp.ArmclangCPPCompiler - linker = linkers.ArmClangDynamicLinker(for_machine, version=version) - env.coredata.add_lang_args(cls.language, cls, for_machine, env) + linker = linkers.ArmClangDynamicLinker(env, for_machine, version=version) + env.add_lang_args(cls.language, cls, for_machine) return cls( - ccache, compiler, version, for_machine, is_cross, info, + ccache, compiler, version, for_machine, env, full_version=full_version, linker=linker) if 'CL.EXE COMPATIBILITY' in out: # if this is clang-cl masquerading as cl, detect it as cl, not @@ -431,7 +441,7 @@ def _detect_c_or_cpp_compiler(env: 'Environment', lang: str, for_machine: Machin cls = c.ClangClCCompiler if lang == 'c' else cpp.ClangClCPPCompiler linker = guess_win_linker(env, ['lld-link'], cls, version, for_machine) return cls( - compiler, version, for_machine, is_cross, info, target, + compiler, version, for_machine, env, target, linker=linker) # must be detected here before clang because TI compilers contain 'clang' in their output and so that they can be detected as 'clang' @@ -445,10 +455,10 @@ def _detect_c_or_cpp_compiler(env: 'Environment', lang: str, for_machine: Machin if identifier in out: cls = compiler_classes[0] if lang == 'c' else compiler_classes[1] lnk = compiler_classes[2] - env.coredata.add_lang_args(cls.language, cls, for_machine, env) - linker = lnk(compiler, for_machine, version=version) + env.add_lang_args(cls.language, cls, for_machine) + linker = lnk(compiler, env, for_machine, version=version) return cls( - ccache, compiler, version, for_machine, is_cross, info, + ccache, compiler, version, for_machine, env, full_version=full_version, linker=linker) if 'clang' in out or 'Clang' in out: @@ -475,26 +485,26 @@ def _detect_c_or_cpp_compiler(env: 'Environment', lang: str, for_machine: Machin linker = guess_nix_linker(env, compiler, cls, version, for_machine) return cls( - ccache, compiler, version, for_machine, is_cross, info, + ccache, compiler, version, for_machine, env, defines=defines, full_version=full_version, linker=linker) if 'Intel(R) C++ Intel(R)' in err: version = search_version(err) target = 'x86' if 'IA-32' in err else 'x86_64' cls = c.IntelClCCompiler if lang == 'c' else cpp.IntelClCPPCompiler - env.coredata.add_lang_args(cls.language, cls, for_machine, env) - linker = linkers.XilinkDynamicLinker(for_machine, [], version=version) + env.add_lang_args(cls.language, cls, for_machine) + linker = linkers.XilinkDynamicLinker(env, for_machine, [], version=version) return cls( - compiler, version, for_machine, is_cross, info, target, + compiler, version, for_machine, env, target, linker=linker) if 'Intel(R) oneAPI DPC++/C++ Compiler for applications' in err: version = search_version(err) target = 'x86' if 'IA-32' in err else 'x86_64' cls = c.IntelLLVMClCCompiler if lang == 'c' else cpp.IntelLLVMClCPPCompiler - env.coredata.add_lang_args(cls.language, cls, for_machine, env) - linker = linkers.XilinkDynamicLinker(for_machine, [], version=version) + env.add_lang_args(cls.language, cls, for_machine) + linker = linkers.XilinkDynamicLinker(env, for_machine, [], version=version) return cls( - compiler, version, for_machine, is_cross, info, target, + compiler, version, for_machine, env, target, linker=linker) if 'Microsoft' in out or 'Microsoft' in err: # Latest versions of Visual Studio print version @@ -516,67 +526,89 @@ def _detect_c_or_cpp_compiler(env: 'Environment', lang: str, for_machine: Machin raise EnvironmentException(m) cls = c.VisualStudioCCompiler if lang == 'c' else cpp.VisualStudioCPPCompiler linker = guess_win_linker(env, ['link'], cls, version, for_machine) - # As of this writing, CCache does not support MSVC but sccache does. - if 'sccache' not in ccache: - ccache = [] + if ccache_exe and ccache_exe.found(): + if ccache_exe.get_name() == 'ccache' and version_compare(ccache_exe.get_version(), '< 4.6'): + mlog.warning('Visual Studio support requires ccache 4.6 or higher. You have ccache {}. '.format(ccache_exe.get_version()), once=True) + ccache = [] return cls( - ccache, compiler, version, for_machine, is_cross, info, target, + ccache, compiler, version, for_machine, env, target, full_version=cl_signature, linker=linker) if 'PGI Compilers' in out: cls = c.PGICCompiler if lang == 'c' else cpp.PGICPPCompiler - env.coredata.add_lang_args(cls.language, cls, for_machine, env) - linker = linkers.PGIDynamicLinker(compiler, for_machine, cls.LINKER_PREFIX, [], version=version) + env.add_lang_args(cls.language, cls, for_machine) + linker = linkers.PGIDynamicLinker(compiler, env, for_machine, cls.LINKER_PREFIX, [], version=version) return cls( - ccache, compiler, version, for_machine, is_cross, - info, linker=linker) + ccache, compiler, version, for_machine, + env, linker=linker) if 'NVIDIA Compilers and Tools' in out: cls = c.NvidiaHPC_CCompiler if lang == 'c' else cpp.NvidiaHPC_CPPCompiler - env.coredata.add_lang_args(cls.language, cls, for_machine, env) - linker = linkers.NvidiaHPC_DynamicLinker(compiler, for_machine, cls.LINKER_PREFIX, [], version=version) + env.add_lang_args(cls.language, cls, for_machine) + linker = linkers.NvidiaHPC_DynamicLinker(compiler, env, for_machine, cls.LINKER_PREFIX, [], version=version) return cls( - ccache, compiler, version, for_machine, is_cross, - info, linker=linker) + ccache, compiler, version, for_machine, + env, linker=linker) if '(ICC)' in out: cls = c.IntelCCompiler if lang == 'c' else cpp.IntelCPPCompiler l = guess_nix_linker(env, compiler, cls, version, for_machine) return cls( - ccache, compiler, version, for_machine, is_cross, info, + ccache, compiler, version, for_machine, env, full_version=full_version, linker=l) if 'Intel(R) oneAPI' in out: cls = c.IntelLLVMCCompiler if lang == 'c' else cpp.IntelLLVMCPPCompiler l = guess_nix_linker(env, compiler, cls, version, for_machine) return cls( - ccache, compiler, version, for_machine, is_cross, info, + ccache, compiler, version, for_machine, env, full_version=full_version, linker=l) if 'ARM' in out and not ('Metrowerks' in out or 'Freescale' in out): cls = c.ArmCCompiler if lang == 'c' else cpp.ArmCPPCompiler - env.coredata.add_lang_args(cls.language, cls, for_machine, env) - linker = linkers.ArmDynamicLinker(for_machine, version=version) + env.add_lang_args(cls.language, cls, for_machine) + linker = linkers.ArmDynamicLinker(env, for_machine, version=version) return cls( - ccache, compiler, version, for_machine, is_cross, - info, full_version=full_version, linker=linker) + ccache, compiler, version, for_machine, + env, full_version=full_version, linker=linker) if 'RX Family' in out: cls = c.CcrxCCompiler if lang == 'c' else cpp.CcrxCPPCompiler - env.coredata.add_lang_args(cls.language, cls, for_machine, env) - linker = linkers.CcrxDynamicLinker(for_machine, version=version) + env.add_lang_args(cls.language, cls, for_machine) + linker = linkers.CcrxDynamicLinker(env, for_machine, version=version) return cls( - ccache, compiler, version, for_machine, is_cross, info, + ccache, compiler, version, for_machine, env, full_version=full_version, linker=linker) - if 'Microchip Technology' in out: - cls = c.Xc16CCompiler - env.coredata.add_lang_args(cls.language, cls, for_machine, env) - linker = linkers.Xc16DynamicLinker(for_machine, version=version) - return cls( - ccache, compiler, version, for_machine, is_cross, info, - full_version=full_version, linker=linker) + if 'Microchip' in out: + if 'XC32' in out: + # XC32 versions always have the form 'vMAJOR.MINOR' + match = re.search(r'XC32.*v(\d+\.\d+)', out) + if match: + version = match.group(1) + else: + raise EnvironmentException(f'Failed to detect XC32 compiler version: full version was\n{full_version}') + + cls = c.Xc32CCompiler if lang == 'c' else cpp.Xc32CPPCompiler + defines = _get_gnu_compiler_defines(compiler, lang) + cls.gcc_version = _get_gnu_version_from_defines(defines) + + env.add_lang_args(cls.language, cls, for_machine) + linker = linkers.Xc32DynamicLinker(compiler, env, for_machine, cls.LINKER_PREFIX, [], version=version) + + return cls( + ccache, compiler, version, for_machine, + env, defines=defines, full_version=full_version, + linker=linker) + else: + cls = c.Xc16CCompiler + env.add_lang_args(cls.language, cls, for_machine) + linker = linkers.Xc16DynamicLinker(env, for_machine, version=version) + + return cls( + ccache, compiler, version, for_machine, env, + full_version=full_version, linker=linker) if 'CompCert' in out: cls = c.CompCertCCompiler - env.coredata.add_lang_args(cls.language, cls, for_machine, env) - linker = linkers.CompCertDynamicLinker(for_machine, version=version) + env.add_lang_args(cls.language, cls, for_machine) + linker = linkers.CompCertDynamicLinker(env, for_machine, version=version) return cls( - ccache, compiler, version, for_machine, is_cross, info, + ccache, compiler, version, for_machine, env, full_version=full_version, linker=linker) if 'Metrowerks C/C++' in out or 'Freescale C/C++' in out: @@ -591,7 +623,7 @@ def _detect_c_or_cpp_compiler(env: 'Environment', lang: str, for_machine: Machin assert mwcc_ver_match is not None, 'for mypy' # because mypy *should* be complaining that this could be None compiler_version = '.'.join(x for x in mwcc_ver_match.groups() if x is not None) - env.coredata.add_lang_args(cls.language, cls, for_machine, env) + env.add_lang_args(cls.language, cls, for_machine) ld = env.lookup_binary_entry(for_machine, cls.language + '_ld') if ld is not None: @@ -601,12 +633,12 @@ def _detect_c_or_cpp_compiler(env: 'Environment', lang: str, for_machine: Machin assert mwld_ver_match is not None, 'for mypy' # because mypy *should* be complaining that this could be None linker_version = '.'.join(x for x in mwld_ver_match.groups() if x is not None) - linker = lnk(ld, for_machine, version=linker_version) + linker = lnk(ld, env, for_machine, version=linker_version) else: raise EnvironmentException(f'Failed to detect linker for {cls.id!r} compiler. Please update your cross file(s).') return cls( - ccache, compiler, compiler_version, for_machine, is_cross, info, + ccache, compiler, compiler_version, for_machine, env, full_version=full_version, linker=linker) if 'TASKING VX-toolset' in err: cls = c.TaskingCCompiler @@ -616,14 +648,14 @@ def _detect_c_or_cpp_compiler(env: 'Environment', lang: str, for_machine: Machin assert tasking_ver_match is not None, 'for mypy' tasking_version = '.'.join(x for x in tasking_ver_match.groups() if x is not None) - env.coredata.add_lang_args(cls.language, cls, for_machine, env) + env.add_lang_args(cls.language, cls, for_machine) ld = env.lookup_binary_entry(for_machine, cls.language + '_ld') if ld is None: raise MesonException(f'{cls.language}_ld was not properly defined in your cross file') - linker = lnk(ld, for_machine, version=tasking_version) + linker = lnk(ld, env, for_machine, version=tasking_version) return cls( - ccache, compiler, tasking_version, for_machine, is_cross, info, + ccache, compiler, tasking_version, for_machine, env, full_version=full_version, linker=linker) _handle_exceptions(popen_exceptions, compilers) @@ -640,9 +672,8 @@ def detect_cuda_compiler(env: 'Environment', for_machine: MachineChoice) -> Comp from ..options import OptionKey from ..linkers.linkers import CudaLinker popen_exceptions = {} - is_cross = env.is_cross_build(for_machine) - compilers, ccache = _get_compilers(env, 'cuda', for_machine) - info = env.machines[for_machine] + compilers, ccache_exe = _get_compilers(env, 'cuda', for_machine) + ccache = ccache_exe.get_command() if (ccache_exe and ccache_exe.found()) else [] for compiler in compilers: arg = '--version' try: @@ -668,15 +699,17 @@ def detect_cuda_compiler(env: 'Environment', for_machine: MachineChoice) -> Comp version = out.strip().rsplit('V', maxsplit=1)[-1] cpp_compiler = detect_cpp_compiler(env, for_machine) cls = CudaCompiler - env.coredata.add_lang_args(cls.language, cls, for_machine, env) + env.add_lang_args(cls.language, cls, for_machine) key = OptionKey('cuda_link_args', machine=for_machine) + if env.is_cross_build(for_machine): + key = key.as_host() if key in env.options: # To fix LDFLAGS issue val = env.options[key] assert isinstance(val, list) - env.coredata.set_options({key: cls.to_host_flags_base(val, Phase.LINKER)}) - linker = CudaLinker(compiler, for_machine, CudaCompiler.LINKER_PREFIX, [], version=CudaLinker.parse_version()) - return cls(ccache, compiler, version, for_machine, is_cross, host_compiler=cpp_compiler, info=info, linker=linker) + env.coredata.optstore.set_option(key, cls.to_host_flags_base(val, Phase.LINKER)) + linker = CudaLinker(compiler, env, for_machine, CudaCompiler.LINKER_PREFIX, [], version=CudaLinker.parse_version()) + return cls(ccache, compiler, version, for_machine, cpp_compiler, env, linker=linker) raise EnvironmentException(f'Could not find suitable CUDA compiler: "{"; ".join([" ".join(c) for c in compilers])}"') def detect_fortran_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: @@ -684,8 +717,6 @@ def detect_fortran_compiler(env: 'Environment', for_machine: MachineChoice) -> C from ..linkers import linkers popen_exceptions: T.Dict[str, T.Union[Exception, str]] = {} compilers, ccache = _get_compilers(env, 'fortran', for_machine) - is_cross = env.is_cross_build(for_machine) - info = env.machines[for_machine] cls: T.Type[FortranCompiler] for compiler in compilers: # capture help text for possible fallback @@ -721,14 +752,14 @@ def detect_fortran_compiler(env: 'Environment', for_machine: MachineChoice) -> C cls = fortran.ElbrusFortranCompiler linker = guess_nix_linker(env, compiler, cls, version, for_machine) return cls( - compiler, version, for_machine, is_cross, info, + compiler, version, for_machine, env, defines, full_version=full_version, linker=linker) else: version = _get_gnu_version_from_defines(defines) cls = fortran.GnuFortranCompiler linker = guess_nix_linker(env, compiler, cls, version, for_machine) return cls( - compiler, version, for_machine, is_cross, info, + compiler, version, for_machine, env, defines, full_version=full_version, linker=linker) if 'Arm C/C++/Fortran Compiler' in out: @@ -738,13 +769,13 @@ def detect_fortran_compiler(env: 'Environment', for_machine: MachineChoice) -> C version = '.'.join([x for x in arm_ver_match.groups() if x is not None]) linker = guess_nix_linker(env, compiler, cls, version, for_machine) return cls( - compiler, version, for_machine, is_cross, info, + compiler, version, for_machine, env, linker=linker) if 'G95' in out: cls = fortran.G95FortranCompiler linker = guess_nix_linker(env, compiler, cls, version, for_machine) return cls( - compiler, version, for_machine, is_cross, info, + compiler, version, for_machine, env, full_version=full_version, linker=linker) if 'Sun Fortran' in err: @@ -752,64 +783,64 @@ def detect_fortran_compiler(env: 'Environment', for_machine: MachineChoice) -> C cls = fortran.SunFortranCompiler linker = guess_nix_linker(env, compiler, cls, version, for_machine) return cls( - compiler, version, for_machine, is_cross, info, + compiler, version, for_machine, env, full_version=full_version, linker=linker) if 'Intel(R) Fortran Compiler for applications' in err: version = search_version(err) target = 'x86' if 'IA-32' in err else 'x86_64' cls = fortran.IntelLLVMClFortranCompiler - env.coredata.add_lang_args(cls.language, cls, for_machine, env) - linker = linkers.XilinkDynamicLinker(for_machine, [], version=version) + env.add_lang_args(cls.language, cls, for_machine) + linker = linkers.XilinkDynamicLinker(env, for_machine, [], version=version) return cls( - compiler, version, for_machine, is_cross, info, + compiler, version, for_machine, env, target, linker=linker) if 'Intel(R) Visual Fortran' in err or 'Intel(R) Fortran' in err: version = search_version(err) target = 'x86' if 'IA-32' in err else 'x86_64' cls = fortran.IntelClFortranCompiler - env.coredata.add_lang_args(cls.language, cls, for_machine, env) - linker = linkers.XilinkDynamicLinker(for_machine, [], version=version) + env.add_lang_args(cls.language, cls, for_machine) + linker = linkers.XilinkDynamicLinker(env, for_machine, [], version=version) return cls( - compiler, version, for_machine, is_cross, info, + compiler, version, for_machine, env, target, linker=linker) if 'ifort (IFORT)' in out: cls = fortran.IntelFortranCompiler linker = guess_nix_linker(env, compiler, cls, version, for_machine) return cls( - compiler, version, for_machine, is_cross, info, + compiler, version, for_machine, env, full_version=full_version, linker=linker) if 'ifx (IFORT)' in out or 'ifx (IFX)' in out: cls = fortran.IntelLLVMFortranCompiler linker = guess_nix_linker(env, compiler, cls, version, for_machine) return cls( - compiler, version, for_machine, is_cross, info, + compiler, version, for_machine, env, full_version=full_version, linker=linker) if 'PathScale EKOPath(tm)' in err: return fortran.PathScaleFortranCompiler( - compiler, version, for_machine, is_cross, info, + compiler, version, for_machine, env, full_version=full_version) if 'PGI Compilers' in out: cls = fortran.PGIFortranCompiler - env.coredata.add_lang_args(cls.language, cls, for_machine, env) - linker = linkers.PGIDynamicLinker(compiler, for_machine, + env.add_lang_args(cls.language, cls, for_machine) + linker = linkers.PGIDynamicLinker(compiler, env, for_machine, cls.LINKER_PREFIX, [], version=version) return cls( - compiler, version, for_machine, is_cross, info, + compiler, version, for_machine, env, full_version=full_version, linker=linker) if 'NVIDIA Compilers and Tools' in out: cls = fortran.NvidiaHPC_FortranCompiler - env.coredata.add_lang_args(cls.language, cls, for_machine, env) - linker = linkers.PGIDynamicLinker(compiler, for_machine, + env.add_lang_args(cls.language, cls, for_machine) + linker = linkers.PGIDynamicLinker(compiler, env, for_machine, cls.LINKER_PREFIX, [], version=version) return cls( - compiler, version, for_machine, is_cross, info, + compiler, version, for_machine, env, full_version=full_version, linker=linker) def _get_linker_try_windows(cls: T.Type['Compiler']) -> T.Optional['DynamicLinker']: @@ -834,14 +865,14 @@ def detect_fortran_compiler(env: 'Environment', for_machine: MachineChoice) -> C cls = fortran.LlvmFlangFortranCompiler linker = _get_linker_try_windows(cls) return cls( - compiler, version, for_machine, is_cross, info, + compiler, version, for_machine, env, full_version=full_version, linker=linker) if 'flang' in out or 'clang' in out: cls = fortran.ClassicFlangFortranCompiler linker = _get_linker_try_windows(cls) return cls( - compiler, version, for_machine, is_cross, info, + compiler, version, for_machine, env, full_version=full_version, linker=linker) if 'Open64 Compiler Suite' in err: @@ -849,19 +880,19 @@ def detect_fortran_compiler(env: 'Environment', for_machine: MachineChoice) -> C linker = guess_nix_linker(env, compiler, cls, version, for_machine) return cls( - compiler, version, for_machine, is_cross, info, + compiler, version, for_machine, env, full_version=full_version, linker=linker) if 'NAG Fortran' in err: full_version = err.split('\n', 1)[0] version = full_version.split()[-1] cls = fortran.NAGFortranCompiler - env.coredata.add_lang_args(cls.language, cls, for_machine, env) + env.add_lang_args(cls.language, cls, for_machine) linker = linkers.NAGDynamicLinker( - compiler, for_machine, cls.LINKER_PREFIX, [], + compiler, env, for_machine, cls.LINKER_PREFIX, [], version=version) return cls( - compiler, version, for_machine, is_cross, info, + compiler, version, for_machine, env, full_version=full_version, linker=linker) _handle_exceptions(popen_exceptions, compilers) @@ -876,9 +907,8 @@ def detect_objcpp_compiler(env: 'Environment', for_machine: MachineChoice) -> 'C def _detect_objc_or_objcpp_compiler(env: 'Environment', lang: str, for_machine: MachineChoice) -> 'Compiler': from . import objc, objcpp popen_exceptions: T.Dict[str, T.Union[Exception, str]] = {} - compilers, ccache = _get_compilers(env, lang, for_machine) - is_cross = env.is_cross_build(for_machine) - info = env.machines[for_machine] + compilers, ccache_exe = _get_compilers(env, lang, for_machine) + ccache = ccache_exe.get_command() if (ccache_exe and ccache_exe.found()) else [] comp: T.Union[T.Type[objc.ObjCCompiler], T.Type[objcpp.ObjCPPCompiler]] for compiler in compilers: @@ -898,9 +928,9 @@ def _detect_objc_or_objcpp_compiler(env: 'Environment', lang: str, for_machine: comp = objc.GnuObjCCompiler if lang == 'objc' else objcpp.GnuObjCPPCompiler linker = guess_nix_linker(env, compiler, comp, version, for_machine) c = comp( - ccache, compiler, version, for_machine, is_cross, info, + ccache, compiler, version, for_machine, env, defines, linker=linker) - if not c.compiles('int main(void) { return 0; }', env)[0]: + if not c.compiles('int main(void) { return 0; }')[0]: popen_exceptions[join_args(compiler)] = f'GCC was not built with support for {"objective-c" if lang == "objc" else "objective-c++"}' continue return c @@ -925,14 +955,13 @@ def _detect_objc_or_objcpp_compiler(env: 'Environment', lang: str, for_machine: linker = guess_nix_linker(env, compiler, comp, version, for_machine) return comp( ccache, compiler, version, for_machine, - is_cross, info, linker=linker, defines=defines) + env, linker=linker, defines=defines) _handle_exceptions(popen_exceptions, compilers) raise EnvironmentException('Unreachable code (exception to make mypy happy)') def detect_java_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: from .java import JavaCompiler exelist = env.lookup_binary_entry(for_machine, 'java') - info = env.machines[for_machine] if exelist is None: # TODO support fallback exelist = [defaults['java'][0]] @@ -948,15 +977,14 @@ def detect_java_compiler(env: 'Environment', for_machine: MachineChoice) -> Comp if len(parts) > 1: version = parts[1] comp_class = JavaCompiler - env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) - return comp_class(exelist, version, for_machine, info) + env.add_lang_args(comp_class.language, comp_class, for_machine) + return comp_class(exelist, version, for_machine, env) raise EnvironmentException('Unknown compiler: ' + join_args(exelist)) def detect_cs_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: from . import cs compilers, ccache = _get_compilers(env, 'cs', for_machine) popen_exceptions = {} - info = env.machines[for_machine] for comp in compilers: try: p, out, err = Popen_safe_logged(comp + ['--version'], msg='Detecting compiler via') @@ -972,8 +1000,8 @@ def detect_cs_compiler(env: 'Environment', for_machine: MachineChoice) -> Compil cls = cs.VisualStudioCsCompiler else: continue - env.coredata.add_lang_args(cls.language, cls, for_machine, env) - return cls(comp, version, for_machine, info) + env.add_lang_args(cls.language, cls, for_machine) + return cls(comp, version, for_machine, env) _handle_exceptions(popen_exceptions, compilers) raise EnvironmentException('Unreachable code (exception to make mypy happy)') @@ -982,8 +1010,6 @@ def detect_cython_compiler(env: 'Environment', for_machine: MachineChoice) -> Co """Search for a cython compiler.""" from .cython import CythonCompiler compilers, _ = _get_compilers(env, 'cython', MachineChoice.BUILD) - is_cross = env.is_cross_build(for_machine) - info = env.machines[for_machine] popen_exceptions: T.Dict[str, Exception] = {} for comp in compilers: @@ -1002,16 +1028,14 @@ def detect_cython_compiler(env: 'Environment', for_machine: MachineChoice) -> Co version = search_version(err) if version is not None: comp_class = CythonCompiler - env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) - return comp_class([], comp, version, for_machine, info, is_cross=is_cross) + env.add_lang_args(comp_class.language, comp_class, for_machine) + return comp_class([], comp, version, for_machine, env) _handle_exceptions(popen_exceptions, compilers) raise EnvironmentException('Unreachable code (exception to make mypy happy)') def detect_vala_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: from .vala import ValaCompiler exelist = env.lookup_binary_entry(MachineChoice.BUILD, 'vala') - is_cross = env.is_cross_build(for_machine) - info = env.machines[for_machine] if exelist is None: # TODO support fallback exelist = [defaults['vala'][0]] @@ -1023,8 +1047,8 @@ def detect_vala_compiler(env: 'Environment', for_machine: MachineChoice) -> Comp version = search_version(out) if 'Vala' in out: comp_class = ValaCompiler - env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) - return comp_class(exelist, version, for_machine, is_cross, info) + env.add_lang_args(comp_class.language, comp_class, for_machine) + return comp_class(exelist, version, for_machine, env) raise EnvironmentException('Unknown compiler: ' + join_args(exelist)) def detect_rust_compiler(env: 'Environment', for_machine: MachineChoice) -> RustCompiler: @@ -1032,8 +1056,6 @@ def detect_rust_compiler(env: 'Environment', for_machine: MachineChoice) -> Rust from ..linkers import linkers popen_exceptions: T.Dict[str, Exception] = {} compilers, _ = _get_compilers(env, 'rust', for_machine) - is_cross = env.is_cross_build(for_machine) - info = env.machines[for_machine] cc = detect_c_compiler(env, for_machine) is_link_exe = isinstance(cc.linker, linkers.VisualStudioLikeLinkerMixin) @@ -1117,11 +1139,16 @@ def detect_rust_compiler(env: 'Environment', for_machine: MachineChoice) -> Rust # so we can initialize a new copy for the Rust Compiler # TODO rewrite this without type: ignore assert cc.linker is not None, 'for mypy' + linker: DynamicLinker if is_link_exe: - linker = type(cc.linker)(for_machine, always_args, exelist=cc.linker.exelist, # type: ignore - version=cc.linker.version, **extra_args) # type: ignore + # TODO: Due to initializer mismatch we can't use the VisualStudioLikeMixin here + # But all of these ahve the same API so we can just pick one. + linker = T.cast('T.Type[linkers.MSVCDynamicLinker]', type(cc.linker))( + env, for_machine, always_args, + exelist=cc.linker.exelist, version=cc.linker.version, + **extra_args) # type: ignore else: - linker = type(cc.linker)(compiler, for_machine, cc.LINKER_PREFIX, + linker = type(cc.linker)(compiler, env, for_machine, cc.LINKER_PREFIX, always_args=always_args, system=cc.linker.system, version=cc.linker.version, **extra_args) elif 'link' in override[0]: @@ -1145,9 +1172,9 @@ def detect_rust_compiler(env: 'Environment', for_machine: MachineChoice) -> Rust c = linker.exelist[1] if linker.exelist[0].endswith('ccache') else linker.exelist[0] compiler.extend(cls.use_linker_args(c, '')) - env.coredata.add_lang_args(cls.language, cls, for_machine, env) + env.add_lang_args(cls.language, cls, for_machine) return cls( - compiler, version, for_machine, is_cross, info, + compiler, version, for_machine, env, linker=linker, full_version=full_version) _handle_exceptions(popen_exceptions, compilers) @@ -1155,7 +1182,6 @@ def detect_rust_compiler(env: 'Environment', for_machine: MachineChoice) -> Rust def detect_d_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: from . import c, d - info = env.machines[for_machine] # Detect the target architecture, required for proper architecture handling on Windows. # MSVC compiler is required for correct platform detection. @@ -1164,14 +1190,12 @@ def detect_d_compiler(env: 'Environment', for_machine: MachineChoice) -> Compile if not is_msvc: c_compiler = {} - # Import here to avoid circular imports - from ..environment import detect_cpu_family arch = detect_cpu_family(c_compiler) if is_msvc and arch == 'x86': arch = 'x86_mscoff' popen_exceptions = {} - is_cross = env.is_cross_build(for_machine) + info = env.machines[for_machine] compilers, ccache = _get_compilers(env, 'd', for_machine) cls: T.Type[d.DCompiler] for exelist in compilers: @@ -1208,7 +1232,7 @@ def detect_d_compiler(env: 'Environment', for_machine: MachineChoice) -> Compile if info.is_windows() or info.is_cygwin(): objfile = os.path.basename(f)[:-1] + 'obj' extra_args = [f] - if is_cross: + if env.is_cross_build(for_machine): extra_args.append(f'-mtriple={info.cpu}-windows') linker = guess_win_linker(env, @@ -1228,15 +1252,15 @@ def detect_d_compiler(env: 'Environment', for_machine: MachineChoice) -> Compile windows_proof_rm(objfile) return cls( - exelist, version, for_machine, info, arch, + exelist, version, for_machine, env, arch, full_version=full_version, linker=linker, - is_cross=is_cross, version_output=out) + version_output=out) elif 'gdc' in out: cls = d.GnuDCompiler linker = guess_nix_linker(env, exelist, cls, version, for_machine) return cls( - exelist, version, for_machine, info, arch, - is_cross=is_cross, full_version=full_version, linker=linker) + exelist, version, for_machine, env, arch, + full_version=full_version, linker=linker) elif 'The D Language Foundation' in out or 'Digital Mars' in out: cls = d.DmdDCompiler # DMD seems to require a file @@ -1265,7 +1289,7 @@ def detect_d_compiler(env: 'Environment', for_machine: MachineChoice) -> Compile windows_proof_rm(objfile) return cls( - exelist, version, for_machine, info, arch, + exelist, version, for_machine, env, arch, full_version=full_version, linker=linker) raise EnvironmentException('Unknown compiler: ' + join_args(exelist)) @@ -1275,8 +1299,6 @@ def detect_d_compiler(env: 'Environment', for_machine: MachineChoice) -> Compile def detect_swift_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: from .swift import SwiftCompiler exelist = env.lookup_binary_entry(for_machine, 'swift') - is_cross = env.is_cross_build(for_machine) - info = env.machines[for_machine] if exelist is None: # TODO support fallback exelist = [defaults['swift'][0]] @@ -1294,13 +1316,12 @@ def detect_swift_compiler(env: 'Environment', for_machine: MachineChoice) -> Com exelist, cls, version, for_machine, extra_args=[f.name, '-o', '/dev/null']) return cls( - exelist, version, for_machine, is_cross, info, linker=linker) + exelist, version, for_machine, env, linker=linker) raise EnvironmentException('Unknown compiler: ' + join_args(exelist)) def detect_nasm_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: from .asm import NasmCompiler, YasmCompiler, MetrowerksAsmCompilerARM, MetrowerksAsmCompilerEmbeddedPowerPC - is_cross = env.is_cross_build(for_machine) # When cross compiling and nasm is not defined in the cross file we can # fallback to the build machine nasm. @@ -1308,11 +1329,6 @@ def detect_nasm_compiler(env: 'Environment', for_machine: MachineChoice) -> Comp # We need a C compiler to properly detect the machine info and linker cc = detect_c_compiler(env, for_machine) - if not is_cross: - from ..environment import detect_machine_info - info = detect_machine_info({'c': cc}) - else: - info = env.machines[for_machine] popen_exceptions: T.Dict[str, Exception] = {} for comp in compilers: @@ -1327,39 +1343,38 @@ def detect_nasm_compiler(env: 'Environment', for_machine: MachineChoice) -> Comp continue version = search_version(output) + comp_class: T.Type[ASMCompiler] if 'NASM' in output: comp_class = NasmCompiler - env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) - return comp_class([], comp, version, for_machine, info, cc.linker, is_cross=is_cross) + env.add_lang_args(comp_class.language, comp_class, for_machine) + return comp_class([], comp, version, for_machine, env, cc.linker) elif 'yasm' in output: comp_class = YasmCompiler - env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) - return comp_class([], comp, version, for_machine, info, cc.linker, is_cross=is_cross) + env.add_lang_args(comp_class.language, comp_class, for_machine) + return comp_class([], comp, version, for_machine, env, cc.linker) elif 'Metrowerks' in output or 'Freescale' in output: if 'ARM' in output: comp_class_mwasmarm = MetrowerksAsmCompilerARM - env.coredata.add_lang_args(comp_class_mwasmarm.language, comp_class_mwasmarm, for_machine, env) - return comp_class_mwasmarm([], comp, version, for_machine, info, cc.linker, is_cross=is_cross) + env.add_lang_args(comp_class_mwasmarm.language, comp_class_mwasmarm, for_machine) + return comp_class_mwasmarm([], comp, version, for_machine, env, cc.linker) else: comp_class_mwasmeppc = MetrowerksAsmCompilerEmbeddedPowerPC - env.coredata.add_lang_args(comp_class_mwasmeppc.language, comp_class_mwasmeppc, for_machine, env) - return comp_class_mwasmeppc([], comp, version, for_machine, info, cc.linker, is_cross=is_cross) + env.add_lang_args(comp_class_mwasmeppc.language, comp_class_mwasmeppc, for_machine) + return comp_class_mwasmeppc([], comp, version, for_machine, env, cc.linker) _handle_exceptions(popen_exceptions, compilers) raise EnvironmentException('Unreachable code (exception to make mypy happy)') def detect_masm_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler: # We need a C compiler to properly detect the machine info and linker - is_cross = env.is_cross_build(for_machine) cc = detect_c_compiler(env, for_machine) - if not is_cross: - from ..environment import detect_machine_info + if not env.is_cross_build(for_machine): info = detect_machine_info({'c': cc}) else: info = env.machines[for_machine] from .asm import MasmCompiler, MasmARMCompiler - comp_class: T.Type[Compiler] + comp_class: T.Type[ASMCompiler] if info.cpu_family == 'x86': comp = ['ml'] comp_class = MasmCompiler @@ -1383,8 +1398,8 @@ def detect_masm_compiler(env: 'Environment', for_machine: MachineChoice) -> Comp try: output = Popen_safe(comp + [arg])[2] version = search_version(output) - env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) - return comp_class([], comp, version, for_machine, info, cc.linker, is_cross=is_cross) + env.add_lang_args(comp_class.language, comp_class, for_machine) + return comp_class([], comp, version, for_machine, env, cc.linker) except OSError as e: popen_exceptions[' '.join(comp + [arg])] = e _handle_exceptions(popen_exceptions, [comp]) @@ -1393,18 +1408,16 @@ def detect_masm_compiler(env: 'Environment', for_machine: MachineChoice) -> Comp def detect_linearasm_compiler(env: Environment, for_machine: MachineChoice) -> Compiler: from .asm import TILinearAsmCompiler comp = ['cl6x'] - comp_class: T.Type[Compiler] = TILinearAsmCompiler + comp_class: T.Type[ASMCompiler] = TILinearAsmCompiler arg = '-h' - info = env.machines[for_machine] cc = detect_c_compiler(env, for_machine) - is_cross = env.is_cross_build(for_machine) popen_exceptions: T.Dict[str, Exception] = {} try: output = Popen_safe(comp + [arg])[2] version = search_version(output) - env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) - return comp_class([], comp, version, for_machine, info, cc.linker, is_cross=is_cross) + env.add_lang_args(comp_class.language, comp_class, for_machine) + return comp_class([], comp, version, for_machine, env, cc.linker) except OSError as e: popen_exceptions[' '.join(comp + [arg])] = e _handle_exceptions(popen_exceptions, [comp]) diff --git a/mesonbuild/compilers/fortran.py b/mesonbuild/compilers/fortran.py index 5794db0..7654b3f 100644 --- a/mesonbuild/compilers/fortran.py +++ b/mesonbuild/compilers/fortran.py @@ -29,7 +29,6 @@ from mesonbuild.mesonlib import ( if T.TYPE_CHECKING: from ..options import MutableKeyedOptionDictType from ..dependencies import Dependency - from ..envconfig import MachineInfo from ..environment import Environment from ..linkers.linkers import DynamicLinker from ..mesonlib import MachineChoice @@ -40,15 +39,14 @@ class FortranCompiler(CLikeCompiler, Compiler): language = 'fortran' - def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, - info: 'MachineInfo', - linker: T.Optional['DynamicLinker'] = None, + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - Compiler.__init__(self, [], exelist, version, for_machine, info, - is_cross=is_cross, full_version=full_version, linker=linker) + Compiler.__init__(self, [], exelist, version, for_machine, env, + full_version=full_version, linker=linker) CLikeCompiler.__init__(self) - def has_function(self, funcname: str, prefix: str, env: 'Environment', *, + def has_function(self, funcname: str, prefix: str, *, extra_args: T.Optional[T.List[str]] = None, dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: raise MesonException('Fortran does not have "has_function" capability.\n' @@ -56,15 +54,15 @@ class FortranCompiler(CLikeCompiler, Compiler): "meson.get_compiler('fortran').links('block; end block; end program')\n\n" 'that example is to see if the compiler has Fortran 2008 Block element.') - def _get_basic_compiler_args(self, env: 'Environment', mode: CompileCheckMode) -> T.Tuple[T.List[str], T.List[str]]: - cargs = env.coredata.get_external_args(self.for_machine, self.language) - largs = env.coredata.get_external_link_args(self.for_machine, self.language) + def _get_basic_compiler_args(self, mode: CompileCheckMode) -> T.Tuple[T.List[str], T.List[str]]: + cargs = self.environment.coredata.get_external_args(self.for_machine, self.language) + largs = self.environment.coredata.get_external_link_args(self.for_machine, self.language) return cargs, largs - def sanity_check(self, work_dir: str, environment: 'Environment') -> None: + def sanity_check(self, work_dir: str) -> None: source_name = 'sanitycheckf.f' code = ' PROGRAM MAIN\n PRINT *, "Fortran compilation is working."\n END\n' - return self._sanity_check_impl(work_dir, environment, source_name, code) + return self._sanity_check_impl(work_dir, source_name, code) def get_optimization_args(self, optimization_level: str) -> T.List[str]: return gnu_optimization_args[optimization_level] @@ -103,16 +101,16 @@ class FortranCompiler(CLikeCompiler, Compiler): return filename - def find_library(self, libname: str, env: 'Environment', extra_dirs: T.List[str], - libtype: LibType = LibType.PREFER_SHARED, lib_prefix_warning: bool = True) -> T.Optional[T.List[str]]: + def find_library(self, libname: str, extra_dirs: T.List[str], libtype: LibType = LibType.PREFER_SHARED, + lib_prefix_warning: bool = True, ignore_system_dirs: bool = False) -> T.Optional[T.List[str]]: code = 'stop; end program' - return self._find_library_impl(libname, env, extra_dirs, code, libtype, lib_prefix_warning) + return self._find_library_impl(libname, extra_dirs, code, libtype, lib_prefix_warning, ignore_system_dirs) - def has_multi_arguments(self, args: T.List[str], env: 'Environment') -> T.Tuple[bool, bool]: - return self._has_multi_arguments(args, env, 'stop; end program') + def has_multi_arguments(self, args: T.List[str]) -> T.Tuple[bool, bool]: + return self._has_multi_arguments(args, 'stop; end program') - 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 has_multi_link_arguments(self, args: T.List[str]) -> T.Tuple[bool, bool]: + return self._has_multi_link_arguments(args, 'stop; end program') def get_options(self) -> 'MutableKeyedOptionDictType': opts = super().get_options() @@ -126,7 +124,7 @@ class FortranCompiler(CLikeCompiler, Compiler): return opts - def _compile_int(self, expression: str, prefix: str, env: 'Environment', + def _compile_int(self, expression: str, prefix: str, extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]], dependencies: T.Optional[T.List['Dependency']]) -> bool: # Use a trick for emulating a static assert @@ -135,28 +133,27 @@ class FortranCompiler(CLikeCompiler, Compiler): {prefix} real(merge(kind(1.),-1,({expression}))), parameter :: fail = 1. end program test''' - return self.compiles(t, env, extra_args=extra_args, - dependencies=dependencies)[0] + return self.compiles(t, extra_args=extra_args, dependencies=dependencies)[0] - def cross_compute_int(self, expression: str, low: T.Optional[int], high: T.Optional[int], - guess: T.Optional[int], prefix: str, env: 'Environment', - extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, - dependencies: T.Optional[T.List['Dependency']] = None) -> int: + def _cross_compute_int(self, expression: str, low: T.Optional[int], high: T.Optional[int], + guess: T.Optional[int], prefix: str, + extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> int: # This only difference between this implementation and that of CLikeCompiler # is a change in logical conjunction operator (.and. instead of &&) # Try user's guess first if isinstance(guess, int): - if self._compile_int(f'{expression} == {guess}', prefix, env, extra_args, dependencies): + if self._compile_int(f'{expression} == {guess}', prefix, extra_args, dependencies): return guess # If no bounds are given, compute them in the limit of int32 maxint = 0x7fffffff minint = -0x80000000 if not isinstance(low, int) or not isinstance(high, int): - if self._compile_int(f'{expression} >= 0', prefix, env, extra_args, dependencies): + if self._compile_int(f'{expression} >= 0', prefix, extra_args, dependencies): low = cur = 0 - while self._compile_int(f'{expression} > {cur}', prefix, env, extra_args, dependencies): + while self._compile_int(f'{expression} > {cur}', prefix, extra_args, dependencies): low = cur + 1 if low > maxint: raise mesonlib.EnvironmentException('Cross-compile check overflowed') @@ -164,7 +161,7 @@ class FortranCompiler(CLikeCompiler, Compiler): high = cur else: high = cur = -1 - while self._compile_int(f'{expression} < {cur}', prefix, env, extra_args, dependencies): + while self._compile_int(f'{expression} < {cur}', prefix, extra_args, dependencies): high = cur - 1 if high < minint: raise mesonlib.EnvironmentException('Cross-compile check overflowed') @@ -175,13 +172,13 @@ class FortranCompiler(CLikeCompiler, Compiler): if high < low: raise mesonlib.EnvironmentException('high limit smaller than low limit') condition = f'{expression} <= {high} .and. {expression} >= {low}' - if not self._compile_int(condition, prefix, env, extra_args, dependencies): + if not self._compile_int(condition, prefix, extra_args, dependencies): raise mesonlib.EnvironmentException('Value out of given range') # Binary search while low != high: cur = low + int((high - low) / 2) - if self._compile_int(f'{expression} <= {cur}', prefix, env, extra_args, dependencies): + if self._compile_int(f'{expression} <= {cur}', prefix, extra_args, dependencies): high = cur else: low = cur + 1 @@ -189,29 +186,28 @@ class FortranCompiler(CLikeCompiler, Compiler): return low def compute_int(self, expression: str, low: T.Optional[int], high: T.Optional[int], - guess: T.Optional[int], prefix: str, env: 'Environment', *, + guess: T.Optional[int], prefix: str, *, extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]], dependencies: T.Optional[T.List['Dependency']] = None) -> int: if extra_args is None: extra_args = [] if self.is_cross: - return self.cross_compute_int(expression, low, high, guess, prefix, env, extra_args, dependencies) + return self._cross_compute_int(expression, low, high, guess, prefix, extra_args, dependencies) t = f'''program test {prefix} print '(i0)', {expression} end program test ''' - res = self.run(t, env, extra_args=extra_args, - dependencies=dependencies) + res = self.run(t, extra_args=extra_args, dependencies=dependencies) if not res.compiled: return -1 if res.returncode != 0: raise mesonlib.EnvironmentException('Could not run compute_int test binary.') return int(res.stdout) - def cross_sizeof(self, typename: str, prefix: str, env: 'Environment', *, - extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, - dependencies: T.Optional[T.List['Dependency']] = None) -> int: + def _cross_sizeof(self, typename: str, prefix: str, *, + extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> int: if extra_args is None: extra_args = [] t = f'''program test @@ -220,19 +216,19 @@ class FortranCompiler(CLikeCompiler, Compiler): {typename} :: something end program test ''' - if not self.compiles(t, env, extra_args=extra_args, + if not self.compiles(t, extra_args=extra_args, dependencies=dependencies)[0]: return -1 - return self.cross_compute_int('c_sizeof(x)', None, None, None, prefix + '\nuse iso_c_binding\n' + typename + ' :: x', env, extra_args, dependencies) + return self._cross_compute_int('c_sizeof(x)', None, None, None, prefix + '\nuse iso_c_binding\n' + typename + ' :: x', extra_args, dependencies) - def sizeof(self, typename: str, prefix: str, env: 'Environment', *, + def sizeof(self, typename: str, prefix: str, *, extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[int, bool]: if extra_args is None: extra_args = [] if self.is_cross: - r = self.cross_sizeof(typename, prefix, env, extra_args=extra_args, - dependencies=dependencies) + r = self._cross_sizeof(typename, prefix, extra_args=extra_args, + dependencies=dependencies) return r, False t = f'''program test use iso_c_binding @@ -241,7 +237,7 @@ class FortranCompiler(CLikeCompiler, Compiler): print '(i0)', c_sizeof(x) end program test ''' - res = self.cached_run(t, env, extra_args=extra_args, + res = self.cached_run(t, extra_args=extra_args, dependencies=dependencies) if not res.compiled: return -1, False @@ -250,23 +246,22 @@ class FortranCompiler(CLikeCompiler, Compiler): return int(res.stdout), res.cached @functools.lru_cache() - def output_is_64bit(self, env: 'Environment') -> bool: + def output_is_64bit(self) -> bool: ''' returns true if the output produced is 64-bit, false if 32-bit ''' - return self.sizeof('type(c_ptr)', '', env)[0] == 8 + return self.sizeof('type(c_ptr)', '')[0] == 8 class GnuFortranCompiler(GnuCompiler, FortranCompiler): - def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, - info: 'MachineInfo', + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, defines: T.Optional[T.Dict[str, str]] = None, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): FortranCompiler.__init__(self, exelist, version, for_machine, - is_cross, info, linker=linker, - full_version=full_version) + env, linker=linker, full_version=full_version) GnuCompiler.__init__(self, defines) default_warn_args = ['-Wall'] self.warn_args = {'0': [], @@ -285,9 +280,9 @@ class GnuFortranCompiler(GnuCompiler, FortranCompiler): self._update_language_stds(opts, fortran_stds) return opts - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: args: T.List[str] = [] - std = self.get_compileropt_value('std', env, target, subproject) + std = self.get_compileropt_value('std', target, subproject) assert isinstance(std, str) if std != 'none': args.append('-std=' + std) @@ -302,17 +297,17 @@ class GnuFortranCompiler(GnuCompiler, FortranCompiler): def get_module_outdir_args(self, path: str) -> T.List[str]: return ['-J' + path] - def language_stdlib_only_link_flags(self, env: 'Environment') -> T.List[str]: + def language_stdlib_only_link_flags(self) -> T.List[str]: # We need to apply the search prefix here, as these link arguments may # be passed to a different compiler with a different set of default # search paths, such as when using Clang for C/C++ and gfortran for # fortran, search_dirs: T.List[str] = [] - for d in self.get_compiler_dirs(env, 'libraries'): + for d in self.get_compiler_dirs('libraries'): search_dirs.append(f'-L{d}') return search_dirs + ['-lgfortran', '-lm'] - def has_header(self, hname: str, prefix: str, env: 'Environment', *, + def has_header(self, hname: str, prefix: str, *, extra_args: T.Union[None, T.List[str], T.Callable[['CompileCheckMode'], T.List[str]]] = None, dependencies: T.Optional[T.List['Dependency']] = None, disable_cache: bool = False) -> T.Tuple[bool, bool]: @@ -322,18 +317,18 @@ class GnuFortranCompiler(GnuCompiler, FortranCompiler): https://github.com/mesonbuild/meson/issues/7017 ''' code = f'{prefix}\n#include <{hname}>' - return self.compiles(code, env, extra_args=extra_args, + return self.compiles(code, extra_args=extra_args, dependencies=dependencies, mode=CompileCheckMode.PREPROCESS, disable_cache=disable_cache) class ElbrusFortranCompiler(ElbrusCompiler, FortranCompiler): - def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, - info: 'MachineInfo', + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, defines: T.Optional[T.Dict[str, str]] = None, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - FortranCompiler.__init__(self, exelist, version, for_machine, is_cross, - info, linker=linker, full_version=full_version) + FortranCompiler.__init__(self, exelist, version, for_machine, + env, linker=linker, full_version=full_version) ElbrusCompiler.__init__(self) def get_options(self) -> 'MutableKeyedOptionDictType': @@ -350,13 +345,11 @@ class G95FortranCompiler(FortranCompiler): LINKER_PREFIX = '-Wl,' id = 'g95' - def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, - info: 'MachineInfo', - linker: T.Optional['DynamicLinker'] = None, + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): FortranCompiler.__init__(self, exelist, version, for_machine, - is_cross, info, linker=linker, - full_version=full_version) + env, linker=linker, full_version=full_version) default_warn_args = ['-Wall'] self.warn_args = {'0': [], '1': default_warn_args, @@ -388,7 +381,7 @@ class SunFortranCompiler(FortranCompiler): def get_module_outdir_args(self, path: str) -> T.List[str]: return ['-moddir=' + path] - def openmp_flags(self, env: Environment) -> T.List[str]: + def openmp_flags(self) -> T.List[str]: return ['-xopenmp'] @@ -396,13 +389,11 @@ class IntelFortranCompiler(IntelGnuLikeCompiler, FortranCompiler): id = 'intel' - def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, - info: 'MachineInfo', - linker: T.Optional['DynamicLinker'] = None, + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): FortranCompiler.__init__(self, exelist, version, for_machine, - is_cross, info, linker=linker, - full_version=full_version) + env, linker=linker, full_version=full_version) # FIXME: Add support for OS X and Windows in detect_fortran_compiler so # we are sent the type of compiler IntelGnuLikeCompiler.__init__(self) @@ -419,9 +410,9 @@ class IntelFortranCompiler(IntelGnuLikeCompiler, FortranCompiler): self._update_language_stds(opts, ['none', 'legacy', 'f95', 'f2003', 'f2008', 'f2018']) return opts - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: args: T.List[str] = [] - std = self.get_compileropt_value('std', env, target, subproject) + std = self.get_compileropt_value('std', target, subproject) stds = {'legacy': 'none', 'f95': 'f95', 'f2003': 'f03', 'f2008': 'f08', 'f2018': 'f18'} assert isinstance(std, str) if std != 'none': @@ -434,7 +425,7 @@ class IntelFortranCompiler(IntelGnuLikeCompiler, FortranCompiler): def get_werror_args(self) -> T.List[str]: return ['-warn', 'errors'] - def language_stdlib_only_link_flags(self, env: 'Environment') -> T.List[str]: + def language_stdlib_only_link_flags(self) -> T.List[str]: # TODO: needs default search path added return ['-lifcore', '-limf'] @@ -446,18 +437,22 @@ class IntelLLVMFortranCompiler(IntelFortranCompiler): id = 'intel-llvm' + def get_preprocess_only_args(self) -> T.List[str]: + return ['-preprocess-only'] + + def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]: + return [] class IntelClFortranCompiler(IntelVisualStudioLikeCompiler, FortranCompiler): always_args = ['/nologo'] def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, - is_cross: bool, info: 'MachineInfo', target: str, + env: Environment, target: str, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): FortranCompiler.__init__(self, exelist, version, for_machine, - is_cross, info, linker=linker, - full_version=full_version) + env, linker=linker, full_version=full_version) IntelVisualStudioLikeCompiler.__init__(self, target) self.file_suffixes = ('f90', 'f', 'for', 'ftn', 'fpp', ) @@ -473,9 +468,9 @@ class IntelClFortranCompiler(IntelVisualStudioLikeCompiler, FortranCompiler): self._update_language_stds(opts, ['none', 'legacy', 'f95', 'f2003', 'f2008', 'f2018']) return opts - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: args: T.List[str] = [] - std = self.get_compileropt_value('std', env, target, subproject) + std = self.get_compileropt_value('std', target, subproject) stds = {'legacy': 'none', 'f95': 'f95', 'f2003': 'f03', 'f2008': 'f08', 'f2018': 'f18'} assert isinstance(std, str) if std != 'none': @@ -497,13 +492,11 @@ class PathScaleFortranCompiler(FortranCompiler): id = 'pathscale' - def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, - info: 'MachineInfo', - linker: T.Optional['DynamicLinker'] = None, + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): FortranCompiler.__init__(self, exelist, version, for_machine, - is_cross, info, linker=linker, - full_version=full_version) + env, linker=linker, full_version=full_version) default_warn_args = ['-fullwarn'] self.warn_args = {'0': [], '1': default_warn_args, @@ -511,19 +504,17 @@ class PathScaleFortranCompiler(FortranCompiler): '3': default_warn_args, 'everything': default_warn_args} - def openmp_flags(self, env: Environment) -> T.List[str]: + def openmp_flags(self) -> T.List[str]: return ['-mp'] class PGIFortranCompiler(PGICompiler, FortranCompiler): - def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, - info: 'MachineInfo', - linker: T.Optional['DynamicLinker'] = None, + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): FortranCompiler.__init__(self, exelist, version, for_machine, - is_cross, info, linker=linker, - full_version=full_version) + env, linker=linker, full_version=full_version) PGICompiler.__init__(self) default_warn_args = ['-Minform=inform'] @@ -533,7 +524,7 @@ class PGIFortranCompiler(PGICompiler, FortranCompiler): '3': default_warn_args + ['-Mdclchk'], 'everything': default_warn_args + ['-Mdclchk']} - def language_stdlib_only_link_flags(self, env: 'Environment') -> T.List[str]: + def language_stdlib_only_link_flags(self) -> T.List[str]: # TODO: needs default search path added return ['-lpgf90rtl', '-lpgf90', '-lpgf90_rpm1', '-lpgf902', '-lpgf90rtl', '-lpgftnrtl', '-lrt'] @@ -543,13 +534,11 @@ class NvidiaHPC_FortranCompiler(PGICompiler, FortranCompiler): id = 'nvidia_hpc' - def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, - info: 'MachineInfo', - linker: T.Optional['DynamicLinker'] = None, + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): FortranCompiler.__init__(self, exelist, version, for_machine, - is_cross, info, linker=linker, - full_version=full_version) + env, linker=linker, full_version=full_version) PGICompiler.__init__(self) default_warn_args = ['-Minform=inform'] @@ -564,13 +553,11 @@ class ClassicFlangFortranCompiler(ClangCompiler, FortranCompiler): id = 'flang' - def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, - info: 'MachineInfo', - linker: T.Optional['DynamicLinker'] = None, + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): FortranCompiler.__init__(self, exelist, version, for_machine, - is_cross, info, linker=linker, - full_version=full_version) + env, linker=linker, full_version=full_version) ClangCompiler.__init__(self, {}) default_warn_args = ['-Minform=inform'] self.warn_args = {'0': [], @@ -579,14 +566,14 @@ class ClassicFlangFortranCompiler(ClangCompiler, FortranCompiler): '3': default_warn_args, 'everything': default_warn_args} - def language_stdlib_only_link_flags(self, env: 'Environment') -> T.List[str]: + def language_stdlib_only_link_flags(self) -> T.List[str]: # We need to apply the search prefix here, as these link arguments may # be passed to a different compiler with a different set of default # search paths, such as when using Clang for C/C++ and gfortran for # fortran, # XXX: Untested.... search_dirs: T.List[str] = [] - for d in self.get_compiler_dirs(env, 'libraries'): + for d in self.get_compiler_dirs('libraries'): search_dirs.append(f'-L{d}') return search_dirs + ['-lflang', '-lpgmath'] @@ -600,12 +587,11 @@ class LlvmFlangFortranCompiler(ClangCompiler, FortranCompiler): id = 'llvm-flang' - def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, - info: 'MachineInfo', linker: T.Optional['DynamicLinker'] = None, + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): FortranCompiler.__init__(self, exelist, version, for_machine, - is_cross, info, linker=linker, - full_version=full_version) + env, linker=linker, full_version=full_version) ClangCompiler.__init__(self, {}) default_warn_args = ['-Wall'] self.warn_args = {'0': [], @@ -632,10 +618,10 @@ class LlvmFlangFortranCompiler(ClangCompiler, FortranCompiler): # https://github.com/llvm/llvm-project/issues/92459 return [] - def language_stdlib_only_link_flags(self, env: 'Environment') -> T.List[str]: + def language_stdlib_only_link_flags(self) -> T.List[str]: # matching setup from ClassicFlangFortranCompiler search_dirs: T.List[str] = [] - for d in self.get_compiler_dirs(env, 'libraries'): + for d in self.get_compiler_dirs('libraries'): search_dirs.append(f'-L{d}') # does not automatically link to Fortran_main anymore after # https://github.com/llvm/llvm-project/commit/9d6837d595719904720e5ff68ec1f1a2665bdc2f @@ -643,20 +629,22 @@ class LlvmFlangFortranCompiler(ClangCompiler, FortranCompiler): # https://github.com/llvm/llvm-project/commit/8d5386669ed63548daf1bee415596582d6d78d7d; # it seems flang 18 doesn't work if something accidentally includes a program unit, see # https://github.com/llvm/llvm-project/issues/92496 - return search_dirs + ['-lFortranRuntime', '-lFortranDecimal'] + # Only link FortranRuntime and FortranDecimal for flang < 19, see + # https://github.com/scipy/scipy/issues/21562#issuecomment-2942938509 + if version_compare(self.version, '<19'): + search_dirs += ['-lFortranRuntime', '-lFortranDecimal'] + return search_dirs class Open64FortranCompiler(FortranCompiler): id = 'open64' - def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, - info: 'MachineInfo', - linker: T.Optional['DynamicLinker'] = None, + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): FortranCompiler.__init__(self, exelist, version, for_machine, - is_cross, info, linker=linker, - full_version=full_version) + env, linker=linker, full_version=full_version) default_warn_args = ['-fullwarn'] self.warn_args = {'0': [], '1': default_warn_args, @@ -664,7 +652,7 @@ class Open64FortranCompiler(FortranCompiler): '3': default_warn_args, 'everything': default_warn_args} - def openmp_flags(self, env: Environment) -> T.List[str]: + def openmp_flags(self) -> T.List[str]: return ['-mp'] @@ -672,13 +660,11 @@ class NAGFortranCompiler(FortranCompiler): id = 'nagfor' - def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, - info: 'MachineInfo', - linker: T.Optional['DynamicLinker'] = None, + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): FortranCompiler.__init__(self, exelist, version, for_machine, - is_cross, info, linker=linker, - full_version=full_version) + env, linker=linker, full_version=full_version) # Warnings are on by default; -w disables (by category): self.warn_args = { '0': ['-w=all'], @@ -707,5 +693,5 @@ class NAGFortranCompiler(FortranCompiler): def get_std_exe_link_args(self) -> T.List[str]: return self.get_always_args() - def openmp_flags(self, env: Environment) -> T.List[str]: + def openmp_flags(self) -> T.List[str]: return ['-openmp'] diff --git a/mesonbuild/compilers/java.py b/mesonbuild/compilers/java.py index 47d2ac9..f60bc6b 100644 --- a/mesonbuild/compilers/java.py +++ b/mesonbuild/compilers/java.py @@ -15,7 +15,6 @@ from .compilers import Compiler from .mixins.islinker import BasicLinkerIsCompilerMixin if T.TYPE_CHECKING: - from ..envconfig import MachineInfo from ..environment import Environment from ..mesonlib import MachineChoice @@ -38,8 +37,8 @@ class JavaCompiler(BasicLinkerIsCompilerMixin, Compiler): } def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, - info: 'MachineInfo', full_version: T.Optional[str] = None): - super().__init__([], exelist, version, for_machine, info, full_version=full_version) + env: Environment, full_version: T.Optional[str] = None): + super().__init__([], exelist, version, for_machine, env, full_version=full_version) self.javarunner = 'java' def get_warn_args(self, level: str) -> T.List[str]: @@ -72,7 +71,7 @@ class JavaCompiler(BasicLinkerIsCompilerMixin, Compiler): return parameter_list - def sanity_check(self, work_dir: str, environment: 'Environment') -> None: + def sanity_check(self, work_dir: str) -> None: src = 'SanityCheck.java' obj = 'SanityCheck' source_name = os.path.join(work_dir, src) @@ -91,7 +90,7 @@ class JavaCompiler(BasicLinkerIsCompilerMixin, Compiler): runner = shutil.which(self.javarunner) if runner: cmdlist = [runner, '-cp', '.', obj] - self.run_sanity_check(environment, cmdlist, work_dir, use_exe_wrapper_for_cross=False) + self.run_sanity_check(cmdlist, work_dir, use_exe_wrapper_for_cross=False) else: m = "Java Virtual Machine wasn't found, but it's needed by Meson. " \ "Please install a JRE.\nIf you have specific needs where this " \ diff --git a/mesonbuild/compilers/mixins/apple.py b/mesonbuild/compilers/mixins/apple.py index 2a09393..4ed2561 100644 --- a/mesonbuild/compilers/mixins/apple.py +++ b/mesonbuild/compilers/mixins/apple.py @@ -10,7 +10,7 @@ from ...mesonlib import MesonException if T.TYPE_CHECKING: from ..._typing import ImmutableListProtocol - from ...environment import Environment + from ...envconfig import MachineInfo from ..compilers import Compiler else: # This is a bit clever, for mypy we pretend that these mixins descend from @@ -26,7 +26,11 @@ class AppleCompilerMixin(Compiler): __BASE_OMP_FLAGS: ImmutableListProtocol[str] = ['-Xpreprocessor', '-fopenmp'] - def openmp_flags(self, env: Environment) -> T.List[str]: + if T.TYPE_CHECKING: + # Older versions of mypy can't figure this out + info: MachineInfo + + def openmp_flags(self) -> T.List[str]: """Flags required to compile with OpenMP on Apple. The Apple Clang Compiler doesn't have builtin support for OpenMP, it @@ -35,23 +39,19 @@ class AppleCompilerMixin(Compiler): :return: A list of arguments """ - m = env.machines[self.for_machine] - assert m is not None, 'for mypy' - if m.cpu_family.startswith('x86'): + if self.info.cpu_family.startswith('x86'): root = '/usr/local' else: root = '/opt/homebrew' return self.__BASE_OMP_FLAGS + [f'-I{root}/opt/libomp/include'] - def openmp_link_flags(self, env: Environment) -> T.List[str]: - m = env.machines[self.for_machine] - assert m is not None, 'for mypy' - if m.cpu_family.startswith('x86'): + def openmp_link_flags(self) -> T.List[str]: + if self.info.cpu_family.startswith('x86'): root = '/usr/local' else: root = '/opt/homebrew' - link = self.find_library('omp', env, [f'{root}/opt/libomp/lib']) + link = self.find_library('omp', [f'{root}/opt/libomp/lib']) if not link: raise MesonException("Couldn't find libomp") return self.__BASE_OMP_FLAGS + link @@ -67,7 +67,9 @@ class AppleCStdsMixin(Compiler): _C17_VERSION = '>=10.0.0' _C18_VERSION = '>=11.0.0' - _C2X_VERSION = '>=11.0.0' + _C2X_VERSION = '>=11.0.3' + _C23_VERSION = '>=17.0.0' + _C2Y_VERSION = '>=17.0.0' class AppleCPPStdsMixin(Compiler): diff --git a/mesonbuild/compilers/mixins/arm.py b/mesonbuild/compilers/mixins/arm.py index a70ec4f..e1212a2 100644 --- a/mesonbuild/compilers/mixins/arm.py +++ b/mesonbuild/compilers/mixins/arm.py @@ -15,7 +15,6 @@ from ..compilers import clike_debug_args from .clang import clang_color_args if T.TYPE_CHECKING: - from ...environment import Environment from ...compilers.compilers import Compiler else: # This is a bit clever, for mypy we pretend that these mixins descend from @@ -90,7 +89,7 @@ class ArmCompiler(Compiler): # PCH files." return 'pch' - def thread_flags(self, env: 'Environment') -> T.List[str]: + def thread_flags(self,) -> T.List[str]: return [] def get_coverage_args(self) -> T.List[str]: diff --git a/mesonbuild/compilers/mixins/ccrx.py b/mesonbuild/compilers/mixins/ccrx.py index d1badaa..a040618 100644 --- a/mesonbuild/compilers/mixins/ccrx.py +++ b/mesonbuild/compilers/mixins/ccrx.py @@ -12,7 +12,6 @@ from ...mesonlib import EnvironmentException if T.TYPE_CHECKING: from ...envconfig import MachineInfo - from ...environment import Environment from ...compilers.compilers import Compiler else: # This is a bit clever, for mypy we pretend that these mixins descend from @@ -67,7 +66,7 @@ class CcrxCompiler(Compiler): def get_pch_use_args(self, pch_dir: str, header: str) -> T.List[str]: return [] - def thread_flags(self, env: 'Environment') -> T.List[str]: + def thread_flags(self) -> T.List[str]: return [] def get_coverage_args(self) -> T.List[str]: diff --git a/mesonbuild/compilers/mixins/clang.py b/mesonbuild/compilers/mixins/clang.py index ae5ab63..851b22f 100644 --- a/mesonbuild/compilers/mixins/clang.py +++ b/mesonbuild/compilers/mixins/clang.py @@ -11,16 +11,16 @@ import typing as T from ... import mesonlib from ... import options -from ...linkers.linkers import AppleDynamicLinker, ClangClDynamicLinker, LLVMDynamicLinker, GnuGoldDynamicLinker, \ - MoldDynamicLinker, MSVCDynamicLinker +from ...linkers.linkers import AppleDynamicLinker, ClangClDynamicLinker, LLVMDynamicLinker, \ + GnuBFDDynamicLinker, GnuGoldDynamicLinker, MoldDynamicLinker, VisualStudioLikeLinkerMixin from ...options import OptionKey from ..compilers import CompileCheckMode from .gnu import GnuLikeCompiler if T.TYPE_CHECKING: from ...options import MutableKeyedOptionDictType - from ...environment import Environment from ...dependencies import Dependency # noqa: F401 + from ...build import BuildTarget from ..compilers import Compiler CompilerMixinBase = Compiler @@ -54,6 +54,26 @@ class ClangCompiler(GnuLikeCompiler): id = 'clang' + # -fms-runtime-lib is a compilation option which sets up an automatic dependency + # from the .o files to the final link product + CRT_D_ARGS: T.Dict[str, T.List[str]] = { + 'none': [], + 'md': ['-fms-runtime-lib=dll'], + 'mdd': ['-fms-runtime-lib=dll_dbg'], + 'mt': ['-fms-runtime-lib=static'], + 'mtd': ['-fms-runtime-lib=static_dbg'], + } + + # disable libcmt to avoid warnings, as that is the default and clang + # adds it by default. + CRT_ARGS: T.Dict[str, T.List[str]] = { + 'none': [], + 'md': ['-Wl,/nodefaultlib:libcmt'], + 'mdd': ['-Wl,/nodefaultlib:libcmt'], + 'mt': [], + 'mtd': ['-Wl,/nodefaultlib:libcmt'], + } + def __init__(self, defines: T.Optional[T.Dict[str, str]]): super().__init__() self.defines = defines or {} @@ -65,11 +85,23 @@ class ClangCompiler(GnuLikeCompiler): # linkers don't have base_options. if isinstance(self.linker, AppleDynamicLinker): self.base_options.add(OptionKey('b_bitcode')) - elif isinstance(self.linker, MSVCDynamicLinker): + elif isinstance(self.linker, VisualStudioLikeLinkerMixin): self.base_options.add(OptionKey('b_vscrt')) # All Clang backends can also do LLVM IR self.can_compile_suffixes.add('ll') + def get_crt_compile_args(self, crt_val: str, buildtype: str) -> T.List[str]: + if not isinstance(self.linker, VisualStudioLikeLinkerMixin): + return [] + crt_val = self.get_crt_val(crt_val, buildtype) + return self.CRT_D_ARGS[crt_val] + + def get_crt_link_args(self, crt_val: str, buildtype: str) -> T.List[str]: + if not isinstance(self.linker, VisualStudioLikeLinkerMixin): + return [] + crt_val = self.get_crt_val(crt_val, buildtype) + return self.CRT_ARGS[crt_val] + def get_colorout_args(self, colortype: str) -> T.List[str]: return clang_color_args[colortype][:] @@ -117,7 +149,7 @@ class ClangCompiler(GnuLikeCompiler): myargs.append('-Werror=ignored-optimization-argument') return super().get_compiler_check_args(mode) + myargs - def has_function(self, funcname: str, prefix: str, env: 'Environment', *, + def has_function(self, funcname: str, prefix: str, *, extra_args: T.Optional[T.List[str]] = None, dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: if extra_args is None: @@ -129,10 +161,10 @@ class ClangCompiler(GnuLikeCompiler): # TODO: this really should be communicated by the linker if isinstance(self.linker, AppleDynamicLinker) and mesonlib.version_compare(self.version, '>=8.0'): extra_args.append('-Wl,-no_weak_imports') - return super().has_function(funcname, prefix, env, extra_args=extra_args, + return super().has_function(funcname, prefix, extra_args=extra_args, dependencies=dependencies) - def openmp_flags(self, env: Environment) -> T.List[str]: + def openmp_flags(self) -> T.List[str]: if mesonlib.version_compare(self.version, '>=3.8.0'): return ['-fopenmp'] elif mesonlib.version_compare(self.version, '>=3.7.0'): @@ -141,13 +173,6 @@ class ClangCompiler(GnuLikeCompiler): # Shouldn't work, but it'll be checked explicitly in the OpenMP dependency. return [] - def gen_vs_module_defs_args(self, defsfile: str) -> T.List[str]: - if isinstance(self.linker, (ClangClDynamicLinker, MSVCDynamicLinker)): - # With MSVC, DLLs only export symbols that are explicitly exported, - # so if a module defs file is specified, we use that to export symbols - return ['-Wl,/DEF:' + defsfile] - return super().gen_vs_module_defs_args(defsfile) - @classmethod def use_linker_args(cls, linker: str, version: str) -> T.List[str]: # Clang additionally can use a linker specified as a path, which GCC @@ -155,7 +180,10 @@ class ClangCompiler(GnuLikeCompiler): # llvm based) is retargetable, while GCC is not. # - # qcld: Qualcomm Snapdragon linker, based on LLVM + # eld: Qualcomm's opensource embedded linker + if linker == 'eld': + return ['-fuse-ld=eld'] + # qcld: Qualcomm's deprecated linker if linker == 'qcld': return ['-fuse-ld=qcld'] if linker == 'mold': @@ -173,10 +201,19 @@ class ClangCompiler(GnuLikeCompiler): # error. return ['-Werror=attributes'] + def get_prelink_args(self, prelink_name: str, obj_list: T.List[str]) -> T.Tuple[T.List[str], T.List[str]]: + if not mesonlib.version_compare(self.version, '>=14'): + raise mesonlib.MesonException('prelinking requires clang >=14') + return [prelink_name], ['-r', '-o', prelink_name] + obj_list + def get_coverage_link_args(self) -> T.List[str]: return ['--coverage'] - def get_lto_compile_args(self, *, threads: int = 0, mode: str = 'default') -> T.List[str]: + def get_embed_bitcode_args(self, bitcode: bool, lto: bool) -> T.List[str]: + return ['-fembed-bitcode'] if bitcode else [] + + def get_lto_compile_args(self, *, target: T.Optional[BuildTarget] = None, threads: int = 0, + mode: str = 'default') -> T.List[str]: args: T.List[str] = [] if mode == 'thin': # ThinLTO requires the use of gold, lld, ld64, lld-link or mold 1.1+ @@ -184,23 +221,23 @@ class ClangCompiler(GnuLikeCompiler): # https://github.com/rui314/mold/commit/46995bcfc3e3113133620bf16445c5f13cd76a18 if not mesonlib.version_compare(self.linker.version, '>=1.1'): raise mesonlib.MesonException("LLVM's ThinLTO requires mold 1.1+") - elif not isinstance(self.linker, (AppleDynamicLinker, ClangClDynamicLinker, LLVMDynamicLinker, GnuGoldDynamicLinker)): - raise mesonlib.MesonException(f"LLVM's ThinLTO only works with gold, lld, lld-link, ld64 or mold, not {self.linker.id}") + elif not isinstance(self.linker, (AppleDynamicLinker, ClangClDynamicLinker, LLVMDynamicLinker, GnuBFDDynamicLinker, GnuGoldDynamicLinker)): + raise mesonlib.MesonException(f"LLVM's ThinLTO only works with bfd, gold, lld, lld-link, ld64, or mold, not {self.linker.id}") args.append(f'-flto={mode}') else: assert mode == 'default', 'someone forgot to wire something up' - args.extend(super().get_lto_compile_args(threads=threads)) + args.extend(super().get_lto_compile_args(target=target, threads=threads)) return args def linker_to_compiler_args(self, args: T.List[str]) -> T.List[str]: - if isinstance(self.linker, (ClangClDynamicLinker, MSVCDynamicLinker)): + if isinstance(self.linker, VisualStudioLikeLinkerMixin): return [flag if flag.startswith('-Wl,') or flag.startswith('-fuse-ld=') else f'-Wl,{flag}' for flag in args] else: return args - def get_lto_link_args(self, *, threads: int = 0, mode: str = 'default', - thinlto_cache_dir: T.Optional[str] = None) -> T.List[str]: - args = self.get_lto_compile_args(threads=threads, mode=mode) + def get_lto_link_args(self, *, target: T.Optional[BuildTarget] = None, threads: int = 0, + mode: str = 'default', thinlto_cache_dir: T.Optional[str] = None) -> T.List[str]: + args = self.get_lto_compile_args(target=target, threads=threads, mode=mode) if mode == 'thin' and thinlto_cache_dir is not None: # We check for ThinLTO linker support above in get_lto_compile_args, and all of them support # get_thinlto_cache_args as well diff --git a/mesonbuild/compilers/mixins/clike.py b/mesonbuild/compilers/mixins/clike.py index e45c485..ef18912 100644 --- a/mesonbuild/compilers/mixins/clike.py +++ b/mesonbuild/compilers/mixins/clike.py @@ -128,7 +128,7 @@ class CLikeCompiler(Compiler): warn_args: T.Dict[str, T.List[str]] = {} # TODO: Replace this manual cache with functools.lru_cache - find_library_cache: T.Dict[T.Tuple[T.Tuple[str, ...], str, T.Tuple[str, ...], str, LibType], T.Optional[T.List[str]]] = {} + find_library_cache: T.Dict[T.Tuple[T.Tuple[str, ...], str, T.Tuple[str, ...], str, LibType, bool], T.Optional[T.List[str]]] = {} find_framework_cache: T.Dict[T.Tuple[T.Tuple[str, ...], str, T.Tuple[str, ...], bool], T.Optional[T.List[str]]] = {} internal_libs = arglist.UNIXY_COMPILER_INTERNAL_LIBS @@ -186,17 +186,16 @@ class CLikeCompiler(Compiler): return ['-isystem', path] return ['-I' + path] - def get_compiler_dirs(self, env: 'Environment', name: str) -> T.List[str]: + def get_compiler_dirs(self, name: str) -> T.List[str]: ''' Get dirs from the compiler, either `libraries:` or `programs:` ''' return [] @functools.lru_cache() - def _get_library_dirs(self, env: 'Environment', - elf_class: T.Optional[int] = None) -> 'ImmutableListProtocol[str]': + def _get_library_dirs(self, elf_class: T.Optional[int] = None) -> 'ImmutableListProtocol[str]': # TODO: replace elf_class with enum - dirs = self.get_compiler_dirs(env, 'libraries') + dirs = self.get_compiler_dirs('libraries') if elf_class is None or elf_class == 0: return dirs @@ -232,23 +231,22 @@ class CLikeCompiler(Compiler): return retval - def get_library_dirs(self, env: 'Environment', - elf_class: T.Optional[int] = None) -> T.List[str]: + def get_library_dirs(self, elf_class: T.Optional[int] = None) -> T.List[str]: """Wrap the lru_cache so that we return a new copy and don't allow mutation of the cached value. """ - return self._get_library_dirs(env, elf_class).copy() + return self._get_library_dirs(elf_class).copy() @functools.lru_cache() - def _get_program_dirs(self, env: 'Environment') -> 'ImmutableListProtocol[str]': + def _get_program_dirs(self) -> 'ImmutableListProtocol[str]': ''' Programs used by the compiler. Also where toolchain DLLs such as libstdc++-6.dll are found with MinGW. ''' - return self.get_compiler_dirs(env, 'programs') + return self.get_compiler_dirs('programs') - def get_program_dirs(self, env: 'Environment') -> T.List[str]: - return self._get_program_dirs(env).copy() + def get_program_dirs(self) -> T.List[str]: + return self._get_program_dirs().copy() def get_pic_args(self) -> T.List[str]: return ['-fPIC'] @@ -262,14 +260,13 @@ class CLikeCompiler(Compiler): def get_default_include_dirs(self) -> T.List[str]: return [] - def gen_export_dynamic_link_args(self, env: 'Environment') -> T.List[str]: - return self.linker.export_dynamic_args(env) + def gen_export_dynamic_link_args(self) -> T.List[str]: + return self.linker.export_dynamic_args() def gen_import_library_args(self, implibname: str) -> T.List[str]: return self.linker.import_library_args(implibname) - def _sanity_check_impl(self, work_dir: str, environment: 'Environment', - sname: str, code: str) -> None: + def _sanity_check_impl(self, work_dir: str, sname: str, code: str) -> None: mlog.debug('Sanity testing ' + self.get_display_language() + ' compiler:', mesonlib.join_args(self.exelist)) mlog.debug(f'Is cross compiler: {self.is_cross!s}.') @@ -278,14 +275,14 @@ class CLikeCompiler(Compiler): mode = CompileCheckMode.LINK if self.is_cross: binname += '_cross' - if not environment.has_exe_wrapper(): + if not self.environment.has_exe_wrapper(): # Linking cross built C/C++ apps is painful. You can't really # tell if you should use -nostdlib or not and for example # on OSX the compiler binary is the same but you need # a ton of compiler flags to differentiate between # arm and x86_64. So just compile. mode = CompileCheckMode.COMPILE - cargs, largs = self._get_basic_compiler_args(environment, mode) + cargs, largs = self._get_basic_compiler_args(mode) extra_flags = cargs + self.linker_to_compiler_args(largs) # Is a valid executable output for all toolchains and platforms @@ -307,21 +304,20 @@ class CLikeCompiler(Compiler): mlog.debug('-----') if pc.returncode != 0: raise mesonlib.EnvironmentException(f'Compiler {self.name_string()} cannot compile programs.') - self.run_sanity_check(environment, [binary_name], work_dir) + self.run_sanity_check([binary_name], work_dir) - def sanity_check(self, work_dir: str, environment: 'Environment') -> None: + def sanity_check(self, work_dir: str) -> None: code = 'int main(void) { int class=0; return class; }\n' - return self._sanity_check_impl(work_dir, environment, 'sanitycheckc.c', code) + return self._sanity_check_impl(work_dir, 'sanitycheckc.c', code) - def check_header(self, hname: str, prefix: str, env: 'Environment', *, + def check_header(self, hname: str, prefix: str, *, extra_args: T.Union[None, T.List[str], T.Callable[['CompileCheckMode'], T.List[str]]] = None, dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: code = f'''{prefix} #include <{hname}>''' - return self.compiles(code, env, extra_args=extra_args, - dependencies=dependencies) + return self.compiles(code, extra_args=extra_args, dependencies=dependencies) - def has_header(self, hname: str, prefix: str, env: 'Environment', *, + def has_header(self, hname: str, prefix: str, *, extra_args: T.Union[None, T.List[str], T.Callable[['CompileCheckMode'], T.List[str]]] = None, dependencies: T.Optional[T.List['Dependency']] = None, disable_cache: bool = False) -> T.Tuple[bool, bool]: @@ -333,11 +329,10 @@ class CLikeCompiler(Compiler): #else #include <{hname}> #endif''' - return self.compiles(code, env, extra_args=extra_args, + return self.compiles(code, extra_args=extra_args, dependencies=dependencies, mode=CompileCheckMode.PREPROCESS, disable_cache=disable_cache) - def has_header_symbol(self, hname: str, symbol: str, prefix: str, - env: 'Environment', *, + def has_header_symbol(self, hname: str, symbol: str, prefix: str, *, extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: t = f'''{prefix} @@ -349,10 +344,10 @@ class CLikeCompiler(Compiler): #endif return 0; }}''' - return self.compiles(t, env, extra_args=extra_args, + return self.compiles(t, extra_args=extra_args, dependencies=dependencies) - def _get_basic_compiler_args(self, env: 'Environment', mode: CompileCheckMode) -> T.Tuple[T.List[str], T.List[str]]: + def _get_basic_compiler_args(self, mode: CompileCheckMode) -> T.Tuple[T.List[str], T.List[str]]: cargs: T.List[str] = [] largs: T.List[str] = [] if mode is CompileCheckMode.LINK: @@ -361,14 +356,17 @@ class CLikeCompiler(Compiler): # linking with static libraries since MSVC won't select a CRT for # us in that case and will error out asking us to pick one. try: - crt_val = env.coredata.optstore.get_value('b_vscrt') - buildtype = env.coredata.optstore.get_value('buildtype') - cargs += self.get_crt_compile_args(crt_val, buildtype) # type: ignore[arg-type] + crt_val = self.environment.coredata.optstore.get_value_for('b_vscrt') + buildtype = self.environment.coredata.optstore.get_value_for('buildtype') + assert isinstance(crt_val, str), 'for mypy' + assert isinstance(buildtype, str), 'for mypy' + cargs += self.get_crt_compile_args(crt_val, buildtype) + largs += self.get_crt_link_args(crt_val, buildtype) except (KeyError, AttributeError): pass # Add CFLAGS/CXXFLAGS/OBJCFLAGS/OBJCXXFLAGS and CPPFLAGS from the env - sys_args = env.coredata.get_external_args(self.for_machine, self.language) + sys_args = self.environment.coredata.get_external_args(self.for_machine, self.language) if isinstance(sys_args, str): sys_args = [sys_args] # Apparently it is a thing to inject linker flags both @@ -379,20 +377,20 @@ class CLikeCompiler(Compiler): cargs += cleaned_sys_args if mode is CompileCheckMode.LINK: - ld_value = env.lookup_binary_entry(self.for_machine, self.language + '_ld') + ld_value = self.environment.lookup_binary_entry(self.for_machine, self.language + '_ld') if ld_value is not None: largs += self.use_linker_args(ld_value[0], self.version) # Add LDFLAGS from the env - sys_ld_args = env.coredata.get_external_link_args(self.for_machine, self.language) + sys_ld_args = self.environment.coredata.get_external_link_args(self.for_machine, self.language) # CFLAGS and CXXFLAGS go to both linking and compiling, but we want them # to only appear on the command line once. Remove dupes. - largs += [x for x in sys_ld_args if x not in sys_args] + largs += [x for x in sys_ld_args if x not in cleaned_sys_args] cargs += self.get_compiler_args_for_mode(mode) return cargs, largs - def build_wrapper_args(self, env: 'Environment', + def build_wrapper_args(self, extra_args: T.Union[None, arglist.CompilerArgs, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]], dependencies: T.Optional[T.List['Dependency']], mode: CompileCheckMode = CompileCheckMode.COMPILE) -> arglist.CompilerArgs: @@ -417,13 +415,13 @@ class CLikeCompiler(Compiler): cargs += d.get_compile_args() system_incdir = d.get_include_type() == 'system' for i in d.get_include_dirs(): - for idir in i.to_string_list(env.get_source_dir(), env.get_build_dir()): + for idir in i.to_string_list(self.environment.get_source_dir(), self.environment.get_build_dir()): cargs.extend(self.get_include_args(idir, system_incdir)) if mode is CompileCheckMode.LINK: # Add link flags needed to find dependencies largs += d.get_link_args() - ca, la = self._get_basic_compiler_args(env, mode) + ca, la = self._get_basic_compiler_args(mode) cargs += ca cargs += self.get_compiler_check_args(mode) @@ -443,35 +441,34 @@ class CLikeCompiler(Compiler): args = cargs + extra_args + largs return args - def _compile_int(self, expression: str, prefix: str, env: 'Environment', + def _compile_int(self, expression: str, prefix: str, extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]], dependencies: T.Optional[T.List['Dependency']]) -> bool: t = f'''{prefix} #include <stddef.h> int main(void) {{ static int a[1-2*!({expression})]; a[0]=0; return 0; }}''' - return self.compiles(t, env, extra_args=extra_args, - dependencies=dependencies)[0] + return self.compiles(t, extra_args=extra_args, dependencies=dependencies)[0] - def cross_compute_int(self, expression: str, low: T.Optional[int], high: T.Optional[int], - guess: T.Optional[int], prefix: str, env: 'Environment', - extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, - dependencies: T.Optional[T.List['Dependency']] = None) -> int: + def _cross_compute_int(self, expression: str, low: T.Optional[int], high: T.Optional[int], + guess: T.Optional[int], prefix: str, + extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> int: # Try user's guess first if isinstance(guess, int): - if self._compile_int(f'{expression} == {guess}', prefix, env, extra_args, dependencies): + if self._compile_int(f'{expression} == {guess}', prefix, extra_args, dependencies): return guess # Try to expand the expression and evaluate it on the build machines compiler - if self.language in env.coredata.compilers.build: + if self.language in self.environment.coredata.compilers.build: try: - expanded, _ = self.get_define(expression, prefix, env, extra_args, dependencies, False) + expanded, _ = self.get_define(expression, prefix, extra_args, dependencies, False) evaluate_expanded = f''' #include <stdio.h> #include <stdint.h> int main(void) {{ int expression = {expanded}; printf("%d", expression); return 0; }}''' - run = env.coredata.compilers.build[self.language].run(evaluate_expanded, env) + run = self.environment.coredata.compilers.build[self.language].run(evaluate_expanded) if run and run.compiled and run.returncode == 0: - if self._compile_int(f'{expression} == {run.stdout}', prefix, env, extra_args, dependencies): + if self._compile_int(f'{expression} == {run.stdout}', prefix, extra_args, dependencies): return int(run.stdout) except mesonlib.EnvironmentException: pass @@ -480,9 +477,9 @@ class CLikeCompiler(Compiler): maxint = 0x7fffffff minint = -0x80000000 if not isinstance(low, int) or not isinstance(high, int): - if self._compile_int(f'{expression} >= 0', prefix, env, extra_args, dependencies): + if self._compile_int(f'{expression} >= 0', prefix, extra_args, dependencies): low = cur = 0 - while self._compile_int(f'{expression} > {cur}', prefix, env, extra_args, dependencies): + while self._compile_int(f'{expression} > {cur}', prefix, extra_args, dependencies): low = cur + 1 if low > maxint: raise mesonlib.EnvironmentException('Cross-compile check overflowed') @@ -490,7 +487,7 @@ class CLikeCompiler(Compiler): high = cur else: high = cur = -1 - while self._compile_int(f'{expression} < {cur}', prefix, env, extra_args, dependencies): + while self._compile_int(f'{expression} < {cur}', prefix, extra_args, dependencies): high = cur - 1 if high < minint: raise mesonlib.EnvironmentException('Cross-compile check overflowed') @@ -501,13 +498,13 @@ class CLikeCompiler(Compiler): if high < low: raise mesonlib.EnvironmentException('high limit smaller than low limit') condition = f'{expression} <= {high} && {expression} >= {low}' - if not self._compile_int(condition, prefix, env, extra_args, dependencies): + if not self._compile_int(condition, prefix, extra_args, dependencies): raise mesonlib.EnvironmentException('Value out of given range') # Binary search while low != high: cur = low + int((high - low) / 2) - if self._compile_int(f'{expression} <= {cur}', prefix, env, extra_args, dependencies): + if self._compile_int(f'{expression} <= {cur}', prefix, extra_args, dependencies): high = cur else: low = cur + 1 @@ -515,13 +512,13 @@ class CLikeCompiler(Compiler): return low def compute_int(self, expression: str, low: T.Optional[int], high: T.Optional[int], - guess: T.Optional[int], prefix: str, env: 'Environment', *, + guess: T.Optional[int], prefix: str, *, extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]], dependencies: T.Optional[T.List['Dependency']] = None) -> int: if extra_args is None: extra_args = [] if self.is_cross: - return self.cross_compute_int(expression, low, high, guess, prefix, env, extra_args, dependencies) + return self._cross_compute_int(expression, low, high, guess, prefix, extra_args, dependencies) t = f'''{prefix} #include<stddef.h> #include<stdio.h> @@ -529,7 +526,7 @@ class CLikeCompiler(Compiler): printf("%ld\\n", (long)({expression})); return 0; }}''' - res = self.run(t, env, extra_args=extra_args, + res = self.run(t, extra_args=extra_args, dependencies=dependencies) if not res.compiled: return -1 @@ -537,9 +534,9 @@ class CLikeCompiler(Compiler): raise mesonlib.EnvironmentException('Could not run compute_int test binary.') return int(res.stdout) - def cross_sizeof(self, typename: str, prefix: str, env: 'Environment', *, - extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, - dependencies: T.Optional[T.List['Dependency']] = None) -> int: + def _cross_sizeof(self, typename: str, prefix: str, *, + extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> int: if extra_args is None: extra_args = [] t = f'''{prefix} @@ -548,19 +545,19 @@ class CLikeCompiler(Compiler): {typename} something; return 0; }}''' - if not self.compiles(t, env, extra_args=extra_args, + if not self.compiles(t, extra_args=extra_args, dependencies=dependencies)[0]: return -1 - return self.cross_compute_int(f'sizeof({typename})', None, None, None, prefix, env, extra_args, dependencies) + return self._cross_compute_int(f'sizeof({typename})', None, None, None, prefix, extra_args, dependencies) - def sizeof(self, typename: str, prefix: str, env: 'Environment', *, + def sizeof(self, typename: str, prefix: str, *, extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[int, bool]: if extra_args is None: extra_args = [] if self.is_cross: - r = self.cross_sizeof(typename, prefix, env, extra_args=extra_args, - dependencies=dependencies) + r = self._cross_sizeof(typename, prefix, extra_args=extra_args, + dependencies=dependencies) return r, False t = f'''{prefix} #include<stddef.h> @@ -569,7 +566,7 @@ class CLikeCompiler(Compiler): printf("%ld\\n", (long)(sizeof({typename}))); return 0; }}''' - res = self.cached_run(t, env, extra_args=extra_args, + res = self.cached_run(t, extra_args=extra_args, dependencies=dependencies) if not res.compiled: return -1, False @@ -577,9 +574,9 @@ class CLikeCompiler(Compiler): raise mesonlib.EnvironmentException('Could not run sizeof test binary.') return int(res.stdout), res.cached - def cross_alignment(self, typename: str, prefix: str, env: 'Environment', *, - extra_args: T.Optional[T.List[str]] = None, - dependencies: T.Optional[T.List['Dependency']] = None) -> int: + def _cross_alignment(self, typename: str, prefix: str, *, + extra_args: T.Optional[T.List[str]] = None, + dependencies: T.Optional[T.List['Dependency']] = None) -> int: if extra_args is None: extra_args = [] t = f'''{prefix} @@ -588,7 +585,7 @@ class CLikeCompiler(Compiler): {typename} something; return 0; }}''' - if not self.compiles(t, env, extra_args=extra_args, + if not self.compiles(t, extra_args=extra_args, dependencies=dependencies)[0]: return -1 t = f'''{prefix} @@ -597,16 +594,16 @@ class CLikeCompiler(Compiler): char c; {typename} target; }};''' - return self.cross_compute_int('offsetof(struct tmp, target)', None, None, None, t, env, extra_args, dependencies) + return self._cross_compute_int('offsetof(struct tmp, target)', None, None, None, t, extra_args, dependencies) - def alignment(self, typename: str, prefix: str, env: 'Environment', *, + def alignment(self, typename: str, prefix: str, *, extra_args: T.Optional[T.List[str]] = None, dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[int, bool]: if extra_args is None: extra_args = [] if self.is_cross: - r = self.cross_alignment(typename, prefix, env, extra_args=extra_args, - dependencies=dependencies) + r = self._cross_alignment(typename, prefix, extra_args=extra_args, + dependencies=dependencies) return r, False t = f'''{prefix} #include <stdio.h> @@ -619,7 +616,7 @@ class CLikeCompiler(Compiler): printf("%d", (int)offsetof(struct tmp, target)); return 0; }}''' - res = self.cached_run(t, env, extra_args=extra_args, + res = self.cached_run(t, extra_args=extra_args, dependencies=dependencies) if not res.compiled: raise mesonlib.EnvironmentException('Could not compile alignment test.') @@ -638,7 +635,7 @@ class CLikeCompiler(Compiler): return align, res.cached - def get_define(self, dname: str, prefix: str, env: 'Environment', + def get_define(self, dname: str, prefix: str, extra_args: T.Union[T.List[str], T.Callable[[CompileCheckMode], T.List[str]]], dependencies: T.Optional[T.List['Dependency']], disable_cache: bool = False) -> T.Tuple[str, bool]: @@ -651,9 +648,9 @@ class CLikeCompiler(Compiler): # define {dname} {sentinel_undef} #endif {delim_start}{dname}{delim_end}''' - args = self.build_wrapper_args(env, extra_args, dependencies, + args = self.build_wrapper_args(extra_args, dependencies, mode=CompileCheckMode.PREPROCESS).to_native() - func = functools.partial(self.cached_compile, code, env.coredata, extra_args=args, mode=CompileCheckMode.PREPROCESS) + func = functools.partial(self.cached_compile, code, extra_args=args, mode=CompileCheckMode.PREPROCESS) if disable_cache: func = functools.partial(self.compile, code, extra_args=args, mode=CompileCheckMode.PREPROCESS) with func() as p: @@ -677,7 +674,7 @@ class CLikeCompiler(Compiler): return define_value, cached def get_return_value(self, fname: str, rtype: str, prefix: str, - env: 'Environment', extra_args: T.Optional[T.List[str]], + extra_args: T.Optional[T.List[str]], dependencies: T.Optional[T.List['Dependency']]) -> T.Union[str, int]: # TODO: rtype should be an enum. # TODO: maybe we can use overload to tell mypy when this will return int vs str? @@ -695,7 +692,7 @@ class CLikeCompiler(Compiler): printf ("{fmt}", {cast} {fname}()); return 0; }}''' - res = self.run(code, env, extra_args=extra_args, dependencies=dependencies) + res = self.run(code, extra_args=extra_args, dependencies=dependencies) if not res.compiled: raise mesonlib.EnvironmentException(f'Could not get return value of {fname}()') if rtype == 'string': @@ -763,7 +760,7 @@ class CLikeCompiler(Compiler): }}''' return head, main - def has_function(self, funcname: str, prefix: str, env: 'Environment', *, + def has_function(self, funcname: str, prefix: str, *, extra_args: T.Optional[T.List[str]] = None, dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: """Determine if a function exists. @@ -781,7 +778,7 @@ class CLikeCompiler(Compiler): varname = 'has function ' + funcname varname = varname.replace(' ', '_') if self.is_cross: - val = env.properties.host.get(varname, None) + val = self.environment.properties.host.get(varname, None) if val is not None: if isinstance(val, bool): return val, False @@ -818,7 +815,7 @@ class CLikeCompiler(Compiler): head, main = self._no_prototype_templ() templ = head + stubs_fail + main - res, cached = self.links(templ.format(**fargs), env, extra_args=extra_args, + res, cached = self.links(templ.format(**fargs), extra_args=extra_args, dependencies=dependencies) if res: return True, cached @@ -860,11 +857,10 @@ class CLikeCompiler(Compiler): #endif return 0; }}''' - return self.links(t.format(**fargs), env, extra_args=extra_args, + return self.links(t.format(**fargs), extra_args=extra_args, dependencies=dependencies) - def has_members(self, typename: str, membernames: T.List[str], - prefix: str, env: 'Environment', *, + def has_members(self, typename: str, membernames: T.List[str], prefix: str, *, extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: if extra_args is None: @@ -877,20 +873,18 @@ class CLikeCompiler(Compiler): {members} (void) foo; }}''' - return self.compiles(t, env, extra_args=extra_args, - dependencies=dependencies) + return self.compiles(t, extra_args=extra_args, dependencies=dependencies) - def has_type(self, typename: str, prefix: str, env: 'Environment', + def has_type(self, typename: str, prefix: str, extra_args: T.Union[T.List[str], T.Callable[[CompileCheckMode], T.List[str]]], *, dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]: t = f'''{prefix} void bar(void) {{ (void) sizeof({typename}); }}''' - return self.compiles(t, env, extra_args=extra_args, - dependencies=dependencies) + return self.compiles(t, extra_args=extra_args, dependencies=dependencies) - def _symbols_have_underscore_prefix_searchbin(self, env: 'Environment') -> bool: + def _symbols_have_underscore_prefix_searchbin(self) -> bool: ''' Check if symbols have underscore prefix by compiling a small test binary and then searching the binary for the string, @@ -906,7 +900,7 @@ class CLikeCompiler(Compiler): ''' args = self.get_compiler_check_args(CompileCheckMode.COMPILE) n = '_symbols_have_underscore_prefix_searchbin' - with self._build_wrapper(code, env, extra_args=args, mode=CompileCheckMode.COMPILE, want_output=True) as p: + with self._build_wrapper(code, extra_args=args, mode=CompileCheckMode.COMPILE, want_output=True) as p: if p.returncode != 0: raise RuntimeError(f'BUG: Unable to compile {n!r} check: {p.stderr}') if not os.path.isfile(p.output_name): @@ -924,7 +918,7 @@ class CLikeCompiler(Compiler): return False raise RuntimeError(f'BUG: {n!r} check did not find symbol string in binary') - def _symbols_have_underscore_prefix_define(self, env: 'Environment') -> T.Optional[bool]: + def _symbols_have_underscore_prefix_define(self) -> T.Optional[bool]: ''' Check if symbols have underscore prefix by querying the __USER_LABEL_PREFIX__ define that most compilers provide @@ -941,7 +935,7 @@ class CLikeCompiler(Compiler): #endif {delim}MESON_UNDERSCORE_PREFIX ''' - with self._build_wrapper(code, env, mode=CompileCheckMode.PREPROCESS, want_output=False) as p: + with self._build_wrapper(code, mode=CompileCheckMode.PREPROCESS, want_output=False) as p: if p.returncode != 0: raise RuntimeError(f'BUG: Unable to preprocess _symbols_have_underscore_prefix_define check: {p.stdout}') symbol_prefix = p.stdout.partition(delim)[-1].rstrip() @@ -954,46 +948,58 @@ class CLikeCompiler(Compiler): else: return None - def _symbols_have_underscore_prefix_list(self, env: 'Environment') -> T.Optional[bool]: + def _symbols_have_underscore_prefix_list(self) -> T.Optional[bool]: ''' Check if symbols have underscore prefix by consulting a hardcoded list of cases where we know the results. Return if functions have underscore prefix or None if unknown. ''' - m = env.machines[self.for_machine] # Darwin always uses the underscore prefix, not matter what - if m.is_darwin(): + if self.info.is_darwin(): return True # Windows uses the underscore prefix on x86 (32bit) only - if m.is_windows() or m.is_cygwin(): - return m.cpu_family == 'x86' + if self.info.is_windows() or self.info.is_cygwin(): + return self.info.cpu_family == 'x86' return None - def symbols_have_underscore_prefix(self, env: 'Environment') -> bool: + def symbols_have_underscore_prefix(self) -> bool: ''' Check if the compiler prefixes an underscore to global C symbols ''' # First, try to query the compiler directly - result = self._symbols_have_underscore_prefix_define(env) + result = self._symbols_have_underscore_prefix_define() if result is not None: return result # Else, try to consult a hardcoded list of cases we know # absolutely have an underscore prefix - result = self._symbols_have_underscore_prefix_list(env) + result = self._symbols_have_underscore_prefix_list() if result is not None: return result # As a last resort, try search in a compiled binary, which is the # most unreliable way of checking this, see #5482 - return self._symbols_have_underscore_prefix_searchbin(env) + return self._symbols_have_underscore_prefix_searchbin() - def _get_patterns(self, env: 'Environment', prefixes: T.List[str], suffixes: T.List[str], shared: bool = False) -> T.List[str]: + def _get_patterns(self, prefixes: T.List[str], suffixes: T.List[str], shared: bool = False) -> T.List[str]: patterns: T.List[str] = [] + if self.info.is_os2(): + # On OS/2, search order for shared libs is + # 1. libfoo_dll.a + # 2. foo_dll.a + # 3. libfoo.a + # 4. foo.a + # 5. foo.dll + # For static libs, `_s' is used instead of `_dll'. + for s in suffixes: + dot = '' if s.startswith(('_dll.', '_s.')) else '.' + for p in prefixes: + patterns.append(p + '{}' + dot + s) + return patterns for p in prefixes: for s in suffixes: patterns.append(p + '{}.' + s) - if shared and env.machines[self.for_machine].is_openbsd(): + if shared and self.info.is_openbsd(): # Shared libraries on OpenBSD can be named libfoo.so.X.Y: # https://www.openbsd.org/faq/ports/specialtopics.html#SharedLibs # @@ -1005,7 +1011,7 @@ class CLikeCompiler(Compiler): patterns.append(p + '{}.so.[0-9]*.[0-9]*') return patterns - def get_library_naming(self, env: 'Environment', libtype: LibType, strict: bool = False) -> T.Tuple[str, ...]: + def get_library_naming(self, libtype: LibType, strict: bool = False) -> T.Tuple[str, ...]: ''' Get library prefixes and suffixes for the target platform ordered by priority @@ -1015,14 +1021,15 @@ class CLikeCompiler(Compiler): # people depend on it. Also, some people use prebuilt `foo.so` instead # of `libfoo.so` for unknown reasons, and may also want to create # `foo.so` by setting name_prefix to '' - if strict and not isinstance(self, VisualStudioLikeCompiler): # lib prefix is not usually used with msvc + # lib prefix is not usually used with msvc and OS/2 + if strict and not isinstance(self, VisualStudioLikeCompiler) and not self.info.is_os2(): prefixes = ['lib'] else: prefixes = ['lib', ''] # Library suffixes and prefixes - if env.machines[self.for_machine].is_darwin(): + if self.info.is_darwin(): shlibext = ['dylib', 'so'] - elif env.machines[self.for_machine].is_windows(): + elif self.info.is_windows(): # FIXME: .lib files can be import or static so we should read the # file, figure out which one it is, and reject the wrong kind. if isinstance(self, VisualStudioLikeCompiler): @@ -1031,28 +1038,31 @@ class CLikeCompiler(Compiler): shlibext = ['dll.a', 'lib', 'dll'] # Yep, static libraries can also be foo.lib stlibext += ['lib'] - elif env.machines[self.for_machine].is_cygwin(): + elif self.info.is_cygwin(): shlibext = ['dll', 'dll.a'] prefixes = ['cyg'] + prefixes elif self.id.lower() in {'c6000', 'c2000', 'ti'}: # TI C28x compilers can use both extensions for static or dynamic libs. stlibext = ['a', 'lib'] shlibext = ['dll', 'so'] + elif self.info.is_os2(): + stlibext = ['_s.lib', '_s.a', 'lib', 'a'] + shlibext = ['_dll.lib', '_dll.a', 'lib', 'a', 'dll'] else: # Linux/BSDs shlibext = ['so'] # Search priority if libtype is LibType.PREFER_SHARED: - patterns = self._get_patterns(env, prefixes, shlibext, True) - patterns.extend([x for x in self._get_patterns(env, prefixes, stlibext, False) if x not in patterns]) + patterns = self._get_patterns(prefixes, shlibext, True) + patterns.extend([x for x in self._get_patterns(prefixes, stlibext, False) if x not in patterns]) elif libtype is LibType.PREFER_STATIC: - patterns = self._get_patterns(env, prefixes, stlibext, False) - patterns.extend([x for x in self._get_patterns(env, prefixes, shlibext, True) if x not in patterns]) + patterns = self._get_patterns(prefixes, stlibext, False) + patterns.extend([x for x in self._get_patterns(prefixes, shlibext, True) if x not in patterns]) elif libtype is LibType.SHARED: - patterns = self._get_patterns(env, prefixes, shlibext, True) + patterns = self._get_patterns(prefixes, shlibext, True) else: assert libtype is LibType.STATIC - patterns = self._get_patterns(env, prefixes, stlibext, False) + patterns = self._get_patterns(prefixes, stlibext, False) return tuple(patterns) @staticmethod @@ -1087,10 +1097,8 @@ class CLikeCompiler(Compiler): @staticmethod def _get_file_from_list(env: Environment, paths: T.List[str]) -> T.Optional[Path]: ''' - We just check whether the library exists. We can't do a link check - because the library might have unresolved symbols that require other - libraries. On macOS we check if the library matches our target - architecture. + Check whether the library exists by filename. On macOS, we also + check if the library matches our target architecture. ''' for p in paths: if os.path.isfile(p): @@ -1107,13 +1115,14 @@ class CLikeCompiler(Compiler): return None @functools.lru_cache() - def output_is_64bit(self, env: 'Environment') -> bool: + def output_is_64bit(self) -> bool: ''' returns true if the output produced is 64-bit, false if 32-bit ''' - return self.sizeof('void *', '', env)[0] == 8 + return self.sizeof('void *', '')[0] == 8 - def _find_library_real(self, libname: str, env: 'Environment', extra_dirs: T.List[str], code: str, libtype: LibType, lib_prefix_warning: bool) -> T.Optional[T.List[str]]: + def _find_library_real(self, libname: str, extra_dirs: T.List[str], code: str, libtype: LibType, + lib_prefix_warning: bool, ignore_system_dirs: bool) -> T.Optional[T.List[str]]: # First try if we can just add the library as -l. # Gcc + co seem to prefer builtin lib dirs to -L dirs. # Only try to find std libs if no extra dirs specified. @@ -1125,48 +1134,59 @@ class CLikeCompiler(Compiler): largs = self.get_linker_always_args() + self.get_allow_undefined_link_args() extra_args = cargs + self.linker_to_compiler_args(largs) - if self.links(code, env, extra_args=extra_args, disable_cache=True)[0]: + if self.links(code, extra_args=extra_args, disable_cache=True)[0]: return cargs # Don't do a manual search for internal libs if libname in self.internal_libs: return None # Not found or we want to use a specific libtype? Try to find the # library file itself. - patterns = self.get_library_naming(env, libtype) + patterns = self.get_library_naming(libtype) # try to detect if we are 64-bit or 32-bit. If we can't # detect, we will just skip path validity checks done in # get_library_dirs() call try: - if self.output_is_64bit(env): + if self.output_is_64bit(): elf_class = 2 else: elf_class = 1 except (mesonlib.MesonException, KeyError): # TODO evaluate if catching KeyError is wanted here elf_class = 0 # Search in the specified dirs, and then in the system libraries - for d in itertools.chain(extra_dirs, self.get_library_dirs(env, elf_class)): + largs = self.get_linker_always_args() + self.get_allow_undefined_link_args() + lcargs = self.linker_to_compiler_args(largs) + for d in itertools.chain(extra_dirs, [] if ignore_system_dirs else self.get_library_dirs(elf_class)): for p in patterns: trials = self._get_trials_from_pattern(p, d, libname) if not trials: continue - trial = self._get_file_from_list(env, trials) - if not trial: + + trial_result = "" + for trial in trials: + if not os.path.isfile(trial): + continue + extra_args = [trial] + lcargs + if self.links(code, extra_args=extra_args, disable_cache=True)[0]: + trial_result = trial + break + + if not trial_result: continue - if libname.startswith('lib') and trial.name.startswith(libname) and lib_prefix_warning: + if libname.startswith('lib') and trial_result.startswith(libname) and lib_prefix_warning: mlog.warning(f'find_library({libname!r}) starting in "lib" only works by accident and is not portable') - return [trial.as_posix()] + return [Path(trial_result).as_posix()] return None - def _find_library_impl(self, libname: str, env: 'Environment', extra_dirs: T.List[str], - code: str, libtype: LibType, lib_prefix_warning: bool) -> T.Optional[T.List[str]]: + def _find_library_impl(self, libname: str, extra_dirs: T.List[str], code: str, libtype: LibType, + lib_prefix_warning: bool, ignore_system_dirs: bool) -> T.Optional[T.List[str]]: # These libraries are either built-in or invalid if libname in self.ignore_libs: return [] if isinstance(extra_dirs, str): extra_dirs = [extra_dirs] - key = (tuple(self.exelist), libname, tuple(extra_dirs), code, libtype) + key = (tuple(self.exelist), libname, tuple(extra_dirs), code, libtype, ignore_system_dirs) if key not in self.find_library_cache: - value = self._find_library_real(libname, env, extra_dirs, code, libtype, lib_prefix_warning) + value = self._find_library_real(libname, extra_dirs, code, libtype, lib_prefix_warning, ignore_system_dirs) self.find_library_cache[key] = value else: value = self.find_library_cache[key] @@ -1174,12 +1194,12 @@ class CLikeCompiler(Compiler): return None return value.copy() - def find_library(self, libname: str, env: 'Environment', extra_dirs: T.List[str], - libtype: LibType = LibType.PREFER_SHARED, lib_prefix_warning: bool = True) -> T.Optional[T.List[str]]: + def find_library(self, libname: str, extra_dirs: T.List[str], libtype: LibType = LibType.PREFER_SHARED, + lib_prefix_warning: bool = True, ignore_system_dirs: bool = False) -> T.Optional[T.List[str]]: code = 'int main(void) { return 0; }\n' - return self._find_library_impl(libname, env, extra_dirs, code, libtype, lib_prefix_warning) + return self._find_library_impl(libname, extra_dirs, code, libtype, lib_prefix_warning, ignore_system_dirs) - def find_framework_paths(self, env: 'Environment') -> T.List[str]: + def find_framework_paths(self) -> T.List[str]: ''' These are usually /Library/Frameworks and /System/Library/Frameworks, unless you select a particular macOS SDK with the -isysroot flag. @@ -1192,7 +1212,7 @@ class CLikeCompiler(Compiler): commands = self.get_exelist(ccache=False) + ['-v', '-E', '-'] commands += self.get_always_args() # Add CFLAGS/CXXFLAGS/OBJCFLAGS/OBJCXXFLAGS from the env - commands += env.coredata.get_external_args(self.for_machine, self.language) + commands += self.environment.coredata.get_external_args(self.for_machine, self.language) mlog.debug('Finding framework path by running: ', ' '.join(commands), '\n') os_env = os.environ.copy() os_env['LC_ALL'] = 'C' @@ -1206,7 +1226,7 @@ class CLikeCompiler(Compiler): paths.append(line[:-21].strip()) return paths - def _find_framework_real(self, name: str, env: 'Environment', extra_dirs: T.List[str], allow_system: bool) -> T.Optional[T.List[str]]: + def _find_framework_real(self, name: str, extra_dirs: T.List[str], allow_system: bool) -> T.Optional[T.List[str]]: code = 'int main(void) { return 0; }' link_args: T.List[str] = [] for d in extra_dirs: @@ -1215,11 +1235,11 @@ class CLikeCompiler(Compiler): # then we must also pass -L/usr/lib to pick up libSystem.dylib extra_args = [] if allow_system else ['-Z', '-L/usr/lib'] link_args += ['-framework', name] - if self.links(code, env, extra_args=(extra_args + link_args), disable_cache=True)[0]: + if self.links(code, extra_args=(extra_args + link_args), disable_cache=True)[0]: return link_args return None - def _find_framework_impl(self, name: str, env: 'Environment', extra_dirs: T.List[str], + def _find_framework_impl(self, name: str, extra_dirs: T.List[str], allow_system: bool) -> T.Optional[T.List[str]]: if isinstance(extra_dirs, str): extra_dirs = [extra_dirs] @@ -1227,20 +1247,20 @@ class CLikeCompiler(Compiler): if key in self.find_framework_cache: value = self.find_framework_cache[key] else: - value = self._find_framework_real(name, env, extra_dirs, allow_system) + value = self._find_framework_real(name, extra_dirs, allow_system) self.find_framework_cache[key] = value if value is None: return None return value.copy() - def find_framework(self, name: str, env: 'Environment', extra_dirs: T.List[str], + def find_framework(self, name: str, extra_dirs: T.List[str], allow_system: bool = True) -> T.Optional[T.List[str]]: ''' Finds the framework with the specified name, and returns link args for the same or returns None when the framework is not found. ''' # TODO: should probably check for macOS? - return self._find_framework_impl(name, env, extra_dirs, allow_system) + return self._find_framework_impl(name, extra_dirs, allow_system) def get_crt_compile_args(self, crt_val: str, buildtype: str) -> T.List[str]: # TODO: does this belong here or in GnuLike or maybe PosixLike? @@ -1250,21 +1270,22 @@ class CLikeCompiler(Compiler): # TODO: does this belong here or in GnuLike or maybe PosixLike? return [] - def thread_flags(self, env: 'Environment') -> T.List[str]: + def thread_flags(self) -> T.List[str]: # TODO: does this belong here or in GnuLike or maybe PosixLike? - host_m = env.machines[self.for_machine] - if host_m.is_haiku() or host_m.is_darwin(): + if self.info.is_haiku() or self.info.is_darwin(): return [] + if self.info.is_os2(): + return ['-lpthread'] return ['-pthread'] def linker_to_compiler_args(self, args: T.List[str]) -> T.List[str]: return args.copy() - def has_arguments(self, args: T.List[str], env: 'Environment', code: str, + def has_arguments(self, args: T.List[str], code: str, mode: CompileCheckMode) -> T.Tuple[bool, bool]: - return self.compiles(code, env, extra_args=args, mode=mode) + return self.compiles(code, extra_args=args, mode=mode) - def _has_multi_arguments(self, args: T.List[str], env: 'Environment', code: str) -> T.Tuple[bool, bool]: + def _has_multi_arguments(self, args: T.List[str], code: str) -> T.Tuple[bool, bool]: new_args: T.List[str] = [] for arg in args: # some compilers, e.g. GCC, don't warn for unsupported warning-disable @@ -1272,12 +1293,19 @@ class CLikeCompiler(Compiler): # check the equivalent enable flag too "-Wforgotten-towel". if arg.startswith('-Wno-'): # Make an exception for -Wno-attributes=x as -Wattributes=x is invalid - # for GCC at least. Also, the opposite of -Wno-vla-larger-than is - # -Wvla-larger-than=N + # for GCC at least. Also, the positive form of some flags require a + # value to be specified, i.e. we need to pass -Wfoo=N rather than just + # -Wfoo. if arg.startswith('-Wno-attributes='): pass - elif arg == '-Wno-vla-larger-than': - new_args.append('-Wvla-larger-than=1000') + elif arg in {'-Wno-alloc-size-larger-than', + '-Wno-alloca-larger-than', + '-Wno-frame-larger-than', + '-Wno-stack-usage', + '-Wno-vla-larger-than'}: + # Pass an arbitrary value to the enabling flag; since the test program + # is trivial, it is unlikely to provoke any of these warnings. + new_args.append('-W' + arg[5:] + '=1000') else: new_args.append('-W' + arg[5:]) if arg.startswith('-Wl,'): @@ -1289,21 +1317,18 @@ class CLikeCompiler(Compiler): 'the compiler you are using. has_link_argument or ' 'other similar method can be used instead.') new_args.append(arg) - return self.has_arguments(new_args, env, code, mode=CompileCheckMode.COMPILE) + return self.has_arguments(new_args, code, mode=CompileCheckMode.COMPILE) - def has_multi_arguments(self, args: T.List[str], env: 'Environment') -> T.Tuple[bool, bool]: - return self._has_multi_arguments(args, env, 'extern int i;\nint i;\n') + def has_multi_arguments(self, args: T.List[str]) -> T.Tuple[bool, bool]: + return self._has_multi_arguments(args, 'extern int i;\nint i;\n') - def _has_multi_link_arguments(self, args: T.List[str], env: 'Environment', code: str) -> T.Tuple[bool, bool]: - # First time we check for link flags we need to first check if we have - # --fatal-warnings, otherwise some linker checks could give some - # false positive. + def _has_multi_link_arguments(self, args: T.List[str], code: str) -> T.Tuple[bool, bool]: args = self.linker.fatal_warnings() + args args = self.linker_to_compiler_args(args) - return self.has_arguments(args, env, code, mode=CompileCheckMode.LINK) + return self.has_arguments(args, code, mode=CompileCheckMode.LINK) - def has_multi_link_arguments(self, args: T.List[str], env: 'Environment') -> T.Tuple[bool, bool]: - return self._has_multi_link_arguments(args, env, 'int main(void) { return 0; }\n') + def has_multi_link_arguments(self, args: T.List[str]) -> T.Tuple[bool, bool]: + return self._has_multi_link_arguments(args, 'int main(void) { return 0; }\n') @staticmethod def _concatenate_string_literals(s: str) -> str: @@ -1321,18 +1346,17 @@ class CLikeCompiler(Compiler): # mixins. return ['-Werror'] - def has_func_attribute(self, name: str, env: 'Environment') -> T.Tuple[bool, bool]: + def has_func_attribute(self, name: str) -> T.Tuple[bool, bool]: # Just assume that if we're not on windows that dllimport and dllexport # don't work - m = env.machines[self.for_machine] - if not (m.is_windows() or m.is_cygwin()): + if not (self.info.is_windows() or self.info.is_cygwin()): if name in {'dllimport', 'dllexport'}: return False, False - return self.compiles(self.attribute_check_func(name), env, + return self.compiles(self.attribute_check_func(name), extra_args=self.get_has_func_attribute_extra_args(name)) - def get_assert_args(self, disable: bool, env: 'Environment') -> T.List[str]: + def get_assert_args(self, disable: bool) -> T.List[str]: if disable: return ['-DNDEBUG'] return [] diff --git a/mesonbuild/compilers/mixins/compcert.py b/mesonbuild/compilers/mixins/compcert.py index 86d20a0..522b31e 100644 --- a/mesonbuild/compilers/mixins/compcert.py +++ b/mesonbuild/compilers/mixins/compcert.py @@ -11,7 +11,6 @@ import typing as T if T.TYPE_CHECKING: from ...envconfig import MachineInfo - from ...environment import Environment from ...compilers.compilers import Compiler else: # This is a bit clever, for mypy we pretend that these mixins descend from @@ -85,7 +84,7 @@ class CompCertCompiler(Compiler): patched_args.append(arg) return patched_args - def thread_flags(self, env: 'Environment') -> T.List[str]: + def thread_flags(self) -> T.List[str]: return [] def get_preprocess_only_args(self) -> T.List[str]: diff --git a/mesonbuild/compilers/mixins/elbrus.py b/mesonbuild/compilers/mixins/elbrus.py index 7037db2..e020123 100644 --- a/mesonbuild/compilers/mixins/elbrus.py +++ b/mesonbuild/compilers/mixins/elbrus.py @@ -17,7 +17,6 @@ from ...mesonlib import Popen_safe from ...options import OptionKey if T.TYPE_CHECKING: - from ...environment import Environment from ...build import BuildTarget @@ -39,7 +38,7 @@ class ElbrusCompiler(GnuLikeCompiler): # FIXME: use _build_wrapper to call this so that linker flags from the env # get applied - def get_library_dirs(self, env: 'Environment', elf_class: T.Optional[int] = None) -> T.List[str]: + def get_library_dirs(self, elf_class: T.Optional[int] = None) -> T.List[str]: os_env = os.environ.copy() os_env['LC_ALL'] = 'C' stdo = Popen_safe(self.get_exelist(ccache=False) + ['--print-search-dirs'], env=os_env)[1] @@ -50,7 +49,7 @@ class ElbrusCompiler(GnuLikeCompiler): return [os.path.realpath(p) for p in libstr.split(':') if os.path.exists(p)] return [] - def get_program_dirs(self, env: 'Environment') -> T.List[str]: + def get_program_dirs(self) -> T.List[str]: os_env = os.environ.copy() os_env['LC_ALL'] = 'C' stdo = Popen_safe(self.get_exelist(ccache=False) + ['--print-search-dirs'], env=os_env)[1] @@ -83,17 +82,17 @@ class ElbrusCompiler(GnuLikeCompiler): # Actually it's not supported for now, but probably will be supported in future return 'pch' - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: args: T.List[str] = [] key = OptionKey(f'{self.language}_std', subproject=subproject, machine=self.for_machine) if target: - std = env.coredata.get_option_for_target(target, key) + std = self.environment.coredata.get_option_for_target(target, key) else: - std = env.coredata.optstore.get_value_for(key) + std = self.environment.coredata.optstore.get_value_for(key) assert isinstance(std, str) if std != 'none': args.append('-std=' + std) return args - def openmp_flags(self, env: Environment) -> T.List[str]: + def openmp_flags(self) -> T.List[str]: return ['-fopenmp'] diff --git a/mesonbuild/compilers/mixins/emscripten.py b/mesonbuild/compilers/mixins/emscripten.py index 91b25e8..ca7779a 100644 --- a/mesonbuild/compilers/mixins/emscripten.py +++ b/mesonbuild/compilers/mixins/emscripten.py @@ -15,7 +15,6 @@ from ...mesonlib import LibType from mesonbuild.compilers.compilers import CompileCheckMode if T.TYPE_CHECKING: - from ...environment import Environment from ...compilers.compilers import Compiler from ...dependencies import Dependency else: @@ -48,9 +47,9 @@ class EmscriptenMixin(Compiler): suffix = 'o' return os.path.join(dirname, 'output.' + suffix) - def thread_link_flags(self, env: 'Environment') -> T.List[str]: + def thread_link_flags(self) -> T.List[str]: args = ['-pthread'] - count = env.coredata.optstore.get_value(OptionKey(f'{self.language}_thread_count', machine=self.for_machine)) + count = self.environment.coredata.optstore.get_value_for(OptionKey(f'{self.language}_thread_count', machine=self.for_machine)) assert isinstance(count, int) if count: args.append(f'-sPTHREAD_POOL_SIZE={count}') @@ -75,10 +74,10 @@ class EmscriptenMixin(Compiler): def get_dependency_link_args(self, dep: 'Dependency') -> T.List[str]: return wrap_js_includes(super().get_dependency_link_args(dep)) - def find_library(self, libname: str, env: 'Environment', extra_dirs: T.List[str], - libtype: LibType = LibType.PREFER_SHARED, lib_prefix_warning: bool = True) -> T.Optional[T.List[str]]: + def find_library(self, libname: str, extra_dirs: T.List[str], libtype: LibType = LibType.PREFER_SHARED, + lib_prefix_warning: bool = True, ignore_system_dirs: bool = False) -> T.Optional[T.List[str]]: if not libname.endswith('.js'): - return super().find_library(libname, env, extra_dirs, libtype, lib_prefix_warning) + return super().find_library(libname, extra_dirs, libtype, lib_prefix_warning) if os.path.isabs(libname): if os.path.exists(libname): return [libname] diff --git a/mesonbuild/compilers/mixins/gnu.py b/mesonbuild/compilers/mixins/gnu.py index 9ea591e..7e783e2 100644 --- a/mesonbuild/compilers/mixins/gnu.py +++ b/mesonbuild/compilers/mixins/gnu.py @@ -21,8 +21,8 @@ from mesonbuild.compilers.compilers import CompileCheckMode if T.TYPE_CHECKING: from ..._typing import ImmutableListProtocol + from ...build import BuildTarget from ...options import MutableKeyedOptionDictType - from ...environment import Environment from ..compilers import Compiler else: # This is a bit clever, for mypy we pretend that these mixins descend from @@ -373,8 +373,8 @@ class GnuLikeCompiler(Compiler, metaclass=abc.ABCMeta): self.can_compile_suffixes.add('sx') def get_pic_args(self) -> T.List[str]: - if self.info.is_windows() or self.info.is_cygwin() or self.info.is_darwin(): - return [] # On Window and OS X, pic is always on. + if self.info.is_windows() or self.info.is_cygwin() or self.info.is_darwin() or self.info.is_os2(): + return [] # On Window, OS X and OS/2, pic is always on. return ['-fPIC'] def get_pie_args(self) -> T.List[str]: @@ -401,7 +401,7 @@ class GnuLikeCompiler(Compiler, metaclass=abc.ABCMeta): return gnulike_default_include_dirs(tuple(self.get_exelist(ccache=False)), self.language).copy() @abc.abstractmethod - def openmp_flags(self, env: Environment) -> T.List[str]: + def openmp_flags(self) -> T.List[str]: pass def gnu_symbol_visibility_args(self, vistype: str) -> T.List[str]: @@ -409,16 +409,6 @@ class GnuLikeCompiler(Compiler, metaclass=abc.ABCMeta): vistype = 'hidden' return gnu_symbol_visibility_args[vistype] - def gen_vs_module_defs_args(self, defsfile: str) -> T.List[str]: - if not isinstance(defsfile, str): - raise RuntimeError('Module definitions file should be str') - # On Windows targets, .def files may be specified on the linker command - # line like an object file. - if self.info.is_windows() or self.info.is_cygwin(): - return [defsfile] - # For other targets, discard the .def file. - return [] - @staticmethod def get_argument_syntax() -> str: return 'gcc' @@ -437,9 +427,9 @@ class GnuLikeCompiler(Compiler, metaclass=abc.ABCMeta): return parameter_list @functools.lru_cache() - def _get_search_dirs(self, env: 'Environment') -> str: + def _get_search_dirs(self) -> str: extra_args = ['--print-search-dirs'] - with self._build_wrapper('', env, extra_args=extra_args, + with self._build_wrapper('', extra_args=extra_args, dependencies=None, mode=CompileCheckMode.COMPILE, want_output=True) as p: return p.stdout @@ -481,22 +471,23 @@ class GnuLikeCompiler(Compiler, metaclass=abc.ABCMeta): result.append(unresolved) return result - def get_compiler_dirs(self, env: 'Environment', name: str) -> T.List[str]: + def get_compiler_dirs(self, name: str) -> T.List[str]: ''' Get dirs from the compiler, either `libraries:` or `programs:` ''' - stdo = self._get_search_dirs(env) + stdo = self._get_search_dirs() for line in stdo.split('\n'): if line.startswith(name + ':'): return self._split_fetch_real_dirs(line.split('=', 1)[1]) return [] - def get_lto_compile_args(self, *, threads: int = 0, mode: str = 'default') -> T.List[str]: + def get_lto_compile_args(self, *, target: T.Optional[BuildTarget] = None, threads: int = 0, + mode: str = 'default') -> T.List[str]: # This provides a base for many compilers, GCC and Clang override this # for their specific arguments return ['-flto'] - def sanitizer_compile_args(self, value: T.List[str]) -> T.List[str]: + def sanitizer_compile_args(self, target: T.Optional[BuildTarget], value: T.List[str]) -> T.List[str]: if not value: return value args = ['-fsanitize=' + ','.join(value)] @@ -522,9 +513,9 @@ class GnuLikeCompiler(Compiler, metaclass=abc.ABCMeta): @classmethod def use_linker_args(cls, linker: str, version: str) -> T.List[str]: - if linker not in {'gold', 'bfd', 'lld'}: + if linker not in {'bfd', 'eld', 'gold', 'lld'}: raise mesonlib.MesonException( - f'Unsupported linker, only bfd, gold, and lld are supported, not {linker}.') + f'Unsupported linker, only bfd, eld, gold, and lld are supported, not {linker}.') return [f'-fuse-ld={linker}'] def get_coverage_args(self) -> T.List[str]: @@ -534,6 +525,8 @@ class GnuLikeCompiler(Compiler, metaclass=abc.ABCMeta): # We want to allow preprocessing files with any extension, such as # foo.c.in. In that case we need to tell GCC/CLANG to treat them as # assembly file. + if self.language == 'fortran': + return self.get_preprocess_only_args() lang = gnu_lang_map.get(self.language, 'assembler-with-cpp') return self.get_preprocess_only_args() + [f'-x{lang}'] @@ -545,13 +538,21 @@ class GnuCompiler(GnuLikeCompiler): """ id = 'gcc' + _COLOR_VERSION = '>=4.9.0' + _WPEDANTIC_VERSION = '>=4.8.0' + _LTO_AUTO_VERSION = '>=10.0' + _LTO_CACHE_VERSION = '>=15.1' + _USE_MOLD_VERSION = '>=12.0.1' + def __init__(self, defines: T.Optional[T.Dict[str, str]]): super().__init__() self.defines = defines or {} - self.base_options.update({OptionKey('b_colorout'), OptionKey('b_lto_threads')}) - self._has_color_support = mesonlib.version_compare(self.version, '>=4.9.0') - self._has_wpedantic_support = mesonlib.version_compare(self.version, '>=4.8.0') - self._has_lto_auto_support = mesonlib.version_compare(self.version, '>=10.0') + self.base_options.update({OptionKey('b_colorout'), OptionKey('b_lto_threads'), + OptionKey('b_thinlto_cache'), OptionKey('b_thinlto_cache_dir')}) + self._has_color_support = mesonlib.version_compare(self.version, self._COLOR_VERSION) + self._has_wpedantic_support = mesonlib.version_compare(self.version, self._WPEDANTIC_VERSION) + self._has_lto_auto_support = mesonlib.version_compare(self.version, self._LTO_AUTO_VERSION) + self._has_lto_cache_support = mesonlib.version_compare(self.version, self._LTO_CACHE_VERSION) def get_colorout_args(self, colortype: str) -> T.List[str]: if self._has_color_support: @@ -588,15 +589,15 @@ class GnuCompiler(GnuLikeCompiler): def get_pch_suffix(self) -> str: return 'gch' - def openmp_flags(self, env: Environment) -> T.List[str]: + def openmp_flags(self) -> T.List[str]: return ['-fopenmp'] - def has_arguments(self, args: T.List[str], env: 'Environment', code: str, + def has_arguments(self, args: T.List[str], code: str, mode: CompileCheckMode) -> T.Tuple[bool, bool]: # For some compiler command line arguments, the GNU compilers will # emit a warning on stderr indicating that an option is valid for a # another language, but still complete with exit_success - with self._build_wrapper(code, env, args, None, mode) as p: + with self._build_wrapper(code, args, None, mode) as p: result = p.returncode == 0 if self.language in {'cpp', 'objcpp'} and 'is valid for C/ObjC' in p.stderr: result = False @@ -612,26 +613,55 @@ class GnuCompiler(GnuLikeCompiler): def get_prelink_args(self, prelink_name: str, obj_list: T.List[str]) -> T.Tuple[T.List[str], T.List[str]]: return [prelink_name], ['-r', '-o', prelink_name] + obj_list - def get_lto_compile_args(self, *, threads: int = 0, mode: str = 'default') -> T.List[str]: + def get_lto_compile_args(self, *, target: T.Optional[BuildTarget] = None, threads: int = 0, + mode: str = 'default', thinlto_cache_dir: T.Optional[str] = None) -> T.List[str]: + args: T.List[str] = [] + if threads == 0: if self._has_lto_auto_support: - return ['-flto=auto'] - # This matches clang's behavior of using the number of cpus, but - # obeying meson's MESON_NUM_PROCESSES convention. - return [f'-flto={mesonlib.determine_worker_count()}'] + args.append('-flto=auto') + else: + # This matches gcc's behavior of using the number of cpus, but + # obeying meson's MESON_NUM_PROCESSES convention. + args.append(f'-flto={mesonlib.determine_worker_count()}') elif threads > 0: - return [f'-flto={threads}'] - return super().get_lto_compile_args(threads=threads) + args.append(f'-flto={threads}') + else: + args.extend(super().get_lto_compile_args(target=target, threads=threads)) + + if thinlto_cache_dir is not None: + # We check for ThinLTO linker support above in get_lto_compile_args, and all of them support + # get_thinlto_cache_args as well + args.extend(self.get_thinlto_cache_args(thinlto_cache_dir)) + + return args + + def get_thinlto_cache_args(self, path: str) -> T.List[str]: + # Unlike the ThinLTO support for Clang, everything is handled in GCC + # and the linker has no direct involvement other than the usual w/ LTO. + return [f'-flto-incremental={path}'] @classmethod def use_linker_args(cls, linker: str, version: str) -> T.List[str]: - if linker == 'mold' and mesonlib.version_compare(version, '>=12.0.1'): + if linker == 'mold' and mesonlib.version_compare(version, cls._USE_MOLD_VERSION): return ['-fuse-ld=mold'] return super().use_linker_args(linker, version) + def get_lto_link_args(self, *, target: T.Optional[BuildTarget] = None, threads: int = 0, + mode: str = 'default', thinlto_cache_dir: T.Optional[str] = None) -> T.List[str]: + args: T.List[str] = [] + args.extend(self.get_lto_compile_args(target=target, threads=threads, thinlto_cache_dir=thinlto_cache_dir)) + return args + def get_profile_use_args(self) -> T.List[str]: return super().get_profile_use_args() + ['-fprofile-correction'] + def get_always_args(self) -> T.List[str]: + args: T.List[str] = [] + if self.info.is_os2() and self.get_linker_id() == 'emxomfld': + args += ['-Zomf'] + return super().get_always_args() + args + class GnuCStds(Compiler): diff --git a/mesonbuild/compilers/mixins/intel.py b/mesonbuild/compilers/mixins/intel.py index 32cbdf0..5391c84 100644 --- a/mesonbuild/compilers/mixins/intel.py +++ b/mesonbuild/compilers/mixins/intel.py @@ -20,9 +20,6 @@ from .gnu import GnuLikeCompiler from .visualstudio import VisualStudioLikeCompiler from ...options import OptionKey -if T.TYPE_CHECKING: - from ...environment import Environment - # XXX: avoid circular dependencies # TODO: this belongs in a posix compiler class # NOTE: the default Intel optimization is -O2, unlike GNU which defaults to -O0. @@ -82,7 +79,7 @@ class IntelGnuLikeCompiler(GnuLikeCompiler): def get_pch_name(self, name: str) -> str: return os.path.basename(name) + '.' + self.get_pch_suffix() - def openmp_flags(self, env: Environment) -> T.List[str]: + def openmp_flags(self) -> T.List[str]: if mesonlib.version_compare(self.version, '>=15.0.0'): return ['-qopenmp'] else: @@ -158,7 +155,7 @@ class IntelVisualStudioLikeCompiler(VisualStudioLikeCompiler): version = int(v1 + v2) return self._calculate_toolset_version(version) - def openmp_flags(self, env: Environment) -> T.List[str]: + def openmp_flags(self) -> T.List[str]: return ['/Qopenmp'] def get_debug_args(self, is_debug: bool) -> T.List[str]: diff --git a/mesonbuild/compilers/mixins/islinker.py b/mesonbuild/compilers/mixins/islinker.py index 3f35619..1c03619 100644 --- a/mesonbuild/compilers/mixins/islinker.py +++ b/mesonbuild/compilers/mixins/islinker.py @@ -17,7 +17,6 @@ import typing as T from ...mesonlib import EnvironmentException, MesonException, is_windows if T.TYPE_CHECKING: - from ...environment import Environment from ...compilers.compilers import Compiler from ...build import BuildTarget from ...options import OptionStore @@ -38,11 +37,11 @@ class BasicLinkerIsCompilerMixin(Compiler): functionality itself. """ - def sanitizer_link_args(self, value: T.List[str]) -> T.List[str]: + def sanitizer_link_args(self, target: BuildTarget, value: T.List[str]) -> T.List[str]: return [] - def get_lto_link_args(self, *, threads: int = 0, mode: str = 'default', - thinlto_cache_dir: T.Optional[str] = None) -> T.List[str]: + def get_lto_link_args(self, *, target: T.Optional[BuildTarget] = None, threads: int = 0, + mode: str = 'default', thinlto_cache_dir: T.Optional[str] = None) -> T.List[str]: return [] def can_linker_accept_rsp(self) -> bool: @@ -60,10 +59,10 @@ class BasicLinkerIsCompilerMixin(Compiler): def get_linker_lib_prefix(self) -> str: return '' - def get_option_link_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_link_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: return [] - def has_multi_link_args(self, args: T.List[str], env: 'Environment') -> T.Tuple[bool, bool]: + def has_multi_link_args(self, args: T.List[str]) -> T.Tuple[bool, bool]: return False, False def get_link_debugfile_args(self, targetfile: str) -> T.List[str]: @@ -96,14 +95,14 @@ class BasicLinkerIsCompilerMixin(Compiler): def bitcode_args(self) -> T.List[str]: raise MesonException("This linker doesn't support bitcode bundles") - def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, + def get_soname_args(self, prefix: str, shlib_name: str, suffix: str, soversion: str, darwin_versions: T.Tuple[str, str]) -> T.List[str]: raise MesonException("This linker doesn't support soname args") - def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, - rpath_paths: T.Tuple[str, ...], build_rpath: str, - install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + def build_rpath_args(self, build_dir: str, from_dir: str, target: BuildTarget, + extra_paths: T.Optional[T.List[str]] = None + ) -> T.Tuple[T.List[str], T.Set[bytes]]: return ([], set()) def get_asneeded_args(self) -> T.List[str]: @@ -115,8 +114,8 @@ class BasicLinkerIsCompilerMixin(Compiler): def get_link_debugfile_name(self, targetfile: str) -> T.Optional[str]: return None - def thread_flags(self, env: 'Environment') -> T.List[str]: + def thread_flags(self) -> T.List[str]: return [] - def thread_link_flags(self, env: 'Environment') -> T.List[str]: + def thread_link_flags(self) -> T.List[str]: return [] diff --git a/mesonbuild/compilers/mixins/xc16.py b/mesonbuild/compilers/mixins/microchip.py index d95674e..b21b032 100644 --- a/mesonbuild/compilers/mixins/xc16.py +++ b/mesonbuild/compilers/mixins/microchip.py @@ -3,23 +3,26 @@ from __future__ import annotations -"""Representations specific to the Microchip XC16 C compiler family.""" +"""Representations specific to the Microchip XC C/C++ compiler family.""" import os import typing as T -from ...mesonlib import EnvironmentException +from .gnu import GnuCStds, GnuCPPStds +from ..compilers import Compiler +from ...mesonlib import EnvironmentException, version_compare if T.TYPE_CHECKING: + from ...build import BuildTarget from ...envconfig import MachineInfo - from ...environment import Environment - from ...compilers.compilers import Compiler + + CompilerBase = Compiler else: # This is a bit clever, for mypy we pretend that these mixins descend from # Compiler, so we get all of the methods and attributes defined for us, but # for runtime we make them descend from object (which all classes normally # do). This gives up DRYer type checking, with no runtime impact - Compiler = object + CompilerBase = object xc16_optimization_args: T.Dict[str, T.List[str]] = { 'plain': [], @@ -68,7 +71,7 @@ class Xc16Compiler(Compiler): def get_pch_use_args(self, pch_dir: str, header: str) -> T.List[str]: return [] - def thread_flags(self, env: 'Environment') -> T.List[str]: + def thread_flags(self) -> T.List[str]: return [] def get_coverage_args(self) -> T.List[str]: @@ -109,3 +112,85 @@ class Xc16Compiler(Compiler): parameter_list[idx] = i[:9] + os.path.normpath(os.path.join(build_dir, i[9:])) return parameter_list + + +class Xc32Compiler(CompilerBase): + + """Microchip XC32 compiler mixin. GCC based with some options disabled.""" + + id = 'xc32-gcc' + + gcc_version = '4.5.1' # Defaults to GCC version used by first XC32 release (v1.00). + + _COLOR_VERSION = '>=3.0' # XC32 version based on GCC 8.3.1+ + _WPEDANTIC_VERSION = '>=1.40' # XC32 version based on GCC 4.8.3+ + _LTO_AUTO_VERSION = '>=5.00' # XC32 version based on GCC 13.2.1+ + _LTO_CACHE_VERSION = '==-1' + _USE_MOLD_VERSION = '==-1' + + def __init__(self) -> None: + if not self.is_cross: + raise EnvironmentException('XC32 supports only cross-compilation.') + + def get_instruction_set_args(self, instruction_set: str) -> T.Optional[T.List[str]]: + return None + + def thread_flags(self) -> T.List[str]: + return [] + + def openmp_flags(self) -> T.List[str]: + return Compiler.openmp_flags(self) + + def get_pic_args(self) -> T.List[str]: + return Compiler.get_pic_args(self) + + def get_pie_args(self) -> T.List[str]: + return Compiler.get_pie_args(self) + + def get_profile_generate_args(self) -> T.List[str]: + return Compiler.get_profile_generate_args(self) + + def get_profile_use_args(self) -> T.List[str]: + return Compiler.get_profile_use_args(self) + + def sanitizer_compile_args(self, target: T.Optional[BuildTarget], value: T.List[str]) -> T.List[str]: + return [] + + @classmethod + def use_linker_args(cls, linker: str, version: str) -> T.List[str]: + return [] + + def get_coverage_args(self) -> T.List[str]: + return [] + + def get_largefile_args(self) -> T.List[str]: + return [] + + def get_prelink_args(self, prelink_name: str, obj_list: T.List[str]) -> T.Tuple[T.List[str], T.List[str]]: + return Compiler.get_prelink_args(self, prelink_name, obj_list) + + def get_prelink_append_compile_args(self) -> bool: + return False + + def supported_warn_args(self, warn_args_by_version: T.Dict[str, T.List[str]]) -> T.List[str]: + result: T.List[str] = [] + for version, warn_args in warn_args_by_version.items(): + if version_compare(self.gcc_version, '>=' + version): + result += warn_args + return result + +class Xc32CStds(GnuCStds): + + """Mixin for setting C standards based on XC32 version.""" + + _C18_VERSION = '>=3.0' + _C2X_VERSION = '>=5.00' + _C23_VERSION = '==-1' + _C2Y_VERSION = '==-1' + +class Xc32CPPStds(GnuCPPStds): + + """Mixin for setting C++ standards based on XC32 version.""" + + _CPP23_VERSION = '>=5.00' + _CPP26_VERSION = '==-1' diff --git a/mesonbuild/compilers/mixins/pgi.py b/mesonbuild/compilers/mixins/pgi.py index 50335c8..7bb78a8 100644 --- a/mesonbuild/compilers/mixins/pgi.py +++ b/mesonbuild/compilers/mixins/pgi.py @@ -13,7 +13,6 @@ from ..compilers import clike_debug_args, clike_optimization_args from ...options import OptionKey if T.TYPE_CHECKING: - from ...environment import Environment from ...compilers.compilers import Compiler else: # This is a bit clever, for mypy we pretend that these mixins descend from @@ -51,9 +50,15 @@ class PGICompiler(Compiler): return ['-fPIC'] return [] - def openmp_flags(self, env: Environment) -> T.List[str]: + def openmp_flags(self) -> T.List[str]: return ['-mp'] + def get_preprocess_only_args(self) -> T.List[str]: + return ['-E', '-P', '-o', '-'] + + def get_preprocess_to_file_args(self) -> T.List[str]: + return ['-E', '-P'] + def get_optimization_args(self, optimization_level: str) -> T.List[str]: return clike_optimization_args[optimization_level] @@ -83,6 +88,6 @@ class PGICompiler(Compiler): else: return [] - def thread_flags(self, env: 'Environment') -> T.List[str]: + def thread_flags(self) -> T.List[str]: # PGI cannot accept -pthread, it's already threaded return [] diff --git a/mesonbuild/compilers/mixins/ti.py b/mesonbuild/compilers/mixins/ti.py index 93cc31e..bdab3e4 100644 --- a/mesonbuild/compilers/mixins/ti.py +++ b/mesonbuild/compilers/mixins/ti.py @@ -12,7 +12,6 @@ from ...mesonlib import EnvironmentException if T.TYPE_CHECKING: from ...envconfig import MachineInfo - from ...environment import Environment from ...compilers.compilers import Compiler else: # This is a bit clever, for mypy we pretend that these mixins descend from @@ -41,6 +40,10 @@ class TICompiler(Compiler): id = 'ti' + if T.TYPE_CHECKING: + # Older versions of mypy can't figure this out for some reason. + is_cross: bool + def __init__(self) -> None: if not self.is_cross: raise EnvironmentException('TI compilers only support cross-compilation.') @@ -67,7 +70,7 @@ class TICompiler(Compiler): def get_pch_use_args(self, pch_dir: str, header: str) -> T.List[str]: return [] - def thread_flags(self, env: 'Environment') -> T.List[str]: + def thread_flags(self) -> T.List[str]: return [] def get_coverage_args(self) -> T.List[str]: diff --git a/mesonbuild/compilers/mixins/visualstudio.py b/mesonbuild/compilers/mixins/visualstudio.py index 275e7ab..1a43114 100644 --- a/mesonbuild/compilers/mixins/visualstudio.py +++ b/mesonbuild/compilers/mixins/visualstudio.py @@ -20,7 +20,7 @@ from ...options import OptionKey from mesonbuild.linkers.linkers import ClangClDynamicLinker if T.TYPE_CHECKING: - from ...environment import Environment + from ...build import BuildTarget from ...dependencies import Dependency from .clike import CLikeCompiler as Compiler else: @@ -66,11 +66,6 @@ msvc_optimization_args: T.Dict[str, T.List[str]] = { 's': ['/O1', '/Gw'], } -msvc_debug_args: T.Dict[bool, T.List[str]] = { - False: [], - True: ['/Zi'] -} - class VisualStudioLikeCompiler(Compiler, metaclass=abc.ABCMeta): @@ -167,7 +162,7 @@ class VisualStudioLikeCompiler(Compiler, metaclass=abc.ABCMeta): def get_no_optimization_args(self) -> T.List[str]: return ['/Od', '/Oi-'] - def sanitizer_compile_args(self, value: T.List[str]) -> T.List[str]: + def sanitizer_compile_args(self, target: T.Optional[BuildTarget], value: T.List[str]) -> T.List[str]: if not value: return value return [f'/fsanitize={",".join(value)}'] @@ -180,7 +175,10 @@ class VisualStudioLikeCompiler(Compiler, metaclass=abc.ABCMeta): return ['/Fo' + outputname] def get_debug_args(self, is_debug: bool) -> T.List[str]: - return msvc_debug_args[is_debug] + if is_debug: + return ['/Z7'] + else: + return [] def get_optimization_args(self, optimization_level: str) -> T.List[str]: args = msvc_optimization_args[optimization_level] @@ -189,30 +187,23 @@ class VisualStudioLikeCompiler(Compiler, metaclass=abc.ABCMeta): return args def linker_to_compiler_args(self, args: T.List[str]) -> T.List[str]: - return ['/link'] + args + return ['/link'] + [arg for arg in args if arg != '/link'] def get_pic_args(self) -> T.List[str]: return [] # PIC is handled by the loader on Windows - def gen_vs_module_defs_args(self, defsfile: str) -> T.List[str]: - if not isinstance(defsfile, str): - raise RuntimeError('Module definitions file should be str') - # With MSVC, DLLs only export symbols that are explicitly exported, - # so if a module defs file is specified, we use that to export symbols - return ['/DEF:' + defsfile] - def gen_pch_args(self, header: str, source: str, pchname: str) -> T.Tuple[str, T.List[str]]: objname = os.path.splitext(source)[0] + '.obj' return objname, ['/Yc' + header, '/Fp' + pchname, '/Fo' + objname] - def openmp_flags(self, env: Environment) -> T.List[str]: + def openmp_flags(self) -> T.List[str]: return ['/openmp'] - def openmp_link_flags(self, env: Environment) -> T.List[str]: + def openmp_link_flags(self) -> T.List[str]: return [] # FIXME, no idea what these should be. - def thread_flags(self, env: 'Environment') -> T.List[str]: + def thread_flags(self) -> T.List[str]: return [] @classmethod @@ -296,9 +287,9 @@ class VisualStudioLikeCompiler(Compiler, metaclass=abc.ABCMeta): # Visual Studio is special. It ignores some arguments it does not # understand and you can't tell it to error out on those. # http://stackoverflow.com/questions/15259720/how-can-i-make-the-microsoft-c-compiler-treat-unknown-flags-as-errors-rather-t - def has_arguments(self, args: T.List[str], env: 'Environment', code: str, mode: CompileCheckMode) -> T.Tuple[bool, bool]: + def has_arguments(self, args: T.List[str], code: str, mode: CompileCheckMode) -> T.Tuple[bool, bool]: warning_text = '4044' if mode == CompileCheckMode.LINK else '9002' - with self._build_wrapper(code, env, extra_args=args, mode=mode) as p: + with self._build_wrapper(code, extra_args=args, mode=mode) as p: if p.returncode != 0: return False, p.cached return not (warning_text in p.stderr or warning_text in p.stdout), p.cached @@ -359,7 +350,7 @@ class VisualStudioLikeCompiler(Compiler, metaclass=abc.ABCMeta): crt_val = self.get_crt_val(crt_val, buildtype) return self.crt_args[crt_val] - def has_func_attribute(self, name: str, env: 'Environment') -> T.Tuple[bool, bool]: + def has_func_attribute(self, name: str) -> T.Tuple[bool, bool]: # MSVC doesn't have __attribute__ like Clang and GCC do, so just return # false without compiling anything return name in {'dllimport', 'dllexport'}, False @@ -368,7 +359,7 @@ class VisualStudioLikeCompiler(Compiler, metaclass=abc.ABCMeta): def get_argument_syntax() -> str: return 'msvc' - def symbols_have_underscore_prefix(self, env: 'Environment') -> bool: + def symbols_have_underscore_prefix(self) -> bool: ''' Check if the compiler prefixes an underscore to global C symbols. @@ -378,12 +369,12 @@ class VisualStudioLikeCompiler(Compiler, metaclass=abc.ABCMeta): ''' # Try to consult a hardcoded list of cases we know # absolutely have an underscore prefix - result = self._symbols_have_underscore_prefix_list(env) + result = self._symbols_have_underscore_prefix_list() if result is not None: return result # As a last resort, try search in a compiled binary - return self._symbols_have_underscore_prefix_searchbin(env) + return self._symbols_have_underscore_prefix_searchbin() def get_pie_args(self) -> T.List[str]: return [] @@ -453,10 +444,10 @@ class ClangClCompiler(VisualStudioLikeCompiler): self.can_compile_suffixes.add('s') self.can_compile_suffixes.add('sx') - def has_arguments(self, args: T.List[str], env: 'Environment', code: str, mode: CompileCheckMode) -> T.Tuple[bool, bool]: + def has_arguments(self, args: T.List[str], code: str, mode: CompileCheckMode) -> T.Tuple[bool, bool]: if mode != CompileCheckMode.LINK: args = args + ['-Werror=unknown-argument', '-Werror=unknown-warning-option'] - return super().has_arguments(args, env, code, mode) + return super().has_arguments(args, code, mode) def get_toolset_version(self) -> T.Optional[str]: # XXX: what is the right thing to do here? @@ -494,14 +485,15 @@ class ClangClCompiler(VisualStudioLikeCompiler): else: return dep.get_compile_args() - def openmp_link_flags(self, env: Environment) -> T.List[str]: + def openmp_link_flags(self) -> T.List[str]: # see https://github.com/mesonbuild/meson/issues/5298 - libs = self.find_library('libomp', env, []) + libs = self.find_library('libomp', []) if libs is None: raise mesonlib.MesonBugException('Could not find libomp') - return super().openmp_link_flags(env) + libs + return super().openmp_link_flags() + libs - def get_lto_compile_args(self, *, threads: int = 0, mode: str = 'default') -> T.List[str]: + def get_lto_compile_args(self, *, target: T.Optional[BuildTarget] = None, threads: int = 0, + mode: str = 'default') -> T.List[str]: args: T.List[str] = [] if mode == 'thin': # LTO data generated by clang-cl is only usable by lld-link @@ -510,11 +502,11 @@ class ClangClCompiler(VisualStudioLikeCompiler): args.append(f'-flto={mode}') else: assert mode == 'default', 'someone forgot to wire something up' - args.extend(super().get_lto_compile_args(threads=threads)) + args.extend(super().get_lto_compile_args(target=target, threads=threads)) return args - def get_lto_link_args(self, *, threads: int = 0, mode: str = 'default', - thinlto_cache_dir: T.Optional[str] = None) -> T.List[str]: + def get_lto_link_args(self, *, target: T.Optional[BuildTarget] = None, threads: int = 0, + mode: str = 'default', thinlto_cache_dir: T.Optional[str] = None) -> T.List[str]: args = [] if mode == 'thin' and thinlto_cache_dir is not None: args.extend(self.linker.get_thinlto_cache_args(thinlto_cache_dir)) diff --git a/mesonbuild/compilers/objc.py b/mesonbuild/compilers/objc.py index d013417..d62113b 100644 --- a/mesonbuild/compilers/objc.py +++ b/mesonbuild/compilers/objc.py @@ -15,7 +15,6 @@ from .mixins.clike import CLikeCompiler from .mixins.gnu import GnuCompiler, GnuCStds, gnu_common_warning_args, gnu_objc_warning_args if T.TYPE_CHECKING: - from ..envconfig import MachineInfo from ..environment import Environment from ..linkers.linkers import DynamicLinker from ..mesonlib import MachineChoice @@ -28,11 +27,11 @@ class ObjCCompiler(CLikeCompiler, Compiler): language = 'objc' def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, - is_cross: bool, info: 'MachineInfo', + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - Compiler.__init__(self, ccache, exelist, version, for_machine, info, - is_cross=is_cross, full_version=full_version, + Compiler.__init__(self, ccache, exelist, version, for_machine, env, + full_version=full_version, linker=linker) CLikeCompiler.__init__(self) @@ -48,9 +47,9 @@ class ObjCCompiler(CLikeCompiler, Compiler): def get_display_language() -> str: return 'Objective-C' - def sanity_check(self, work_dir: str, environment: 'Environment') -> None: + def sanity_check(self, work_dir: str) -> None: code = '#import<stddef.h>\nint main(void) { return 0; }\n' - return self._sanity_check_impl(work_dir, environment, 'sanitycheckobjc.m', code) + return self._sanity_check_impl(work_dir, 'sanitycheckobjc.m', code) def form_compileropt_key(self, basename: str) -> OptionKey: if basename == 'std': @@ -60,12 +59,12 @@ class ObjCCompiler(CLikeCompiler, Compiler): class GnuObjCCompiler(GnuCStds, GnuCompiler, ObjCCompiler): def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, - is_cross: bool, info: 'MachineInfo', + env: Environment, defines: T.Optional[T.Dict[str, str]] = None, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - ObjCCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, - info, linker=linker, full_version=full_version) + ObjCCompiler.__init__(self, ccache, exelist, version, for_machine, + env, linker=linker, full_version=full_version) GnuCompiler.__init__(self, defines) default_warn_args = ['-Wall', '-Winvalid-pch'] self.warn_args = {'0': [], @@ -76,13 +75,13 @@ class GnuObjCCompiler(GnuCStds, GnuCompiler, ObjCCompiler): self.supported_warn_args(gnu_common_warning_args) + self.supported_warn_args(gnu_objc_warning_args))} - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: args: T.List[str] = [] key = OptionKey('c_std', subproject=subproject, machine=self.for_machine) if target: - std = env.coredata.get_option_for_target(target, key) + std = self.environment.coredata.get_option_for_target(target, key) else: - std = env.coredata.optstore.get_value_for(key) + std = self.environment.coredata.optstore.get_value_for(key) assert isinstance(std, str) if std != 'none': args.append('-std=' + std) @@ -90,12 +89,12 @@ class GnuObjCCompiler(GnuCStds, GnuCompiler, ObjCCompiler): class ClangObjCCompiler(ClangCStds, ClangCompiler, ObjCCompiler): def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, - is_cross: bool, info: 'MachineInfo', + env: Environment, defines: T.Optional[T.Dict[str, str]] = None, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - ObjCCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, - info, linker=linker, full_version=full_version) + ObjCCompiler.__init__(self, ccache, exelist, version, for_machine, + env, linker=linker, full_version=full_version) ClangCompiler.__init__(self, defines) default_warn_args = ['-Wall', '-Winvalid-pch'] self.warn_args = {'0': [], @@ -114,10 +113,10 @@ class ClangObjCCompiler(ClangCStds, ClangCompiler, ObjCCompiler): return 'c_std' return super().make_option_name(key) - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: args = [] key = OptionKey('c_std', machine=self.for_machine) - std = self.get_compileropt_value(key, env, target, subproject) + std = self.get_compileropt_value(key, target, subproject) assert isinstance(std, str) if std != 'none': args.append('-std=' + std) diff --git a/mesonbuild/compilers/objcpp.py b/mesonbuild/compilers/objcpp.py index 441428b..927ca7e 100644 --- a/mesonbuild/compilers/objcpp.py +++ b/mesonbuild/compilers/objcpp.py @@ -15,7 +15,6 @@ from .mixins.clang import ClangCompiler, ClangCPPStds from .mixins.clike import CLikeCompiler if T.TYPE_CHECKING: - from ..envconfig import MachineInfo from ..environment import Environment from ..linkers.linkers import DynamicLinker from ..mesonlib import MachineChoice @@ -28,12 +27,11 @@ class ObjCPPCompiler(CLikeCompiler, Compiler): language = 'objcpp' def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, - is_cross: bool, info: 'MachineInfo', + env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - Compiler.__init__(self, ccache, exelist, version, for_machine, info, - is_cross=is_cross, full_version=full_version, - linker=linker) + Compiler.__init__(self, ccache, exelist, version, for_machine, env, + full_version=full_version, linker=linker) CLikeCompiler.__init__(self) def form_compileropt_key(self, basename: str) -> OptionKey: @@ -50,9 +48,9 @@ class ObjCPPCompiler(CLikeCompiler, Compiler): def get_display_language() -> str: return 'Objective-C++' - def sanity_check(self, work_dir: str, environment: 'Environment') -> None: + def sanity_check(self, work_dir: str) -> None: code = '#import<stdio.h>\nclass MyClass;int main(void) { return 0; }\n' - return self._sanity_check_impl(work_dir, environment, 'sanitycheckobjcpp.mm', code) + return self._sanity_check_impl(work_dir, 'sanitycheckobjcpp.mm', code) def get_options(self) -> MutableKeyedOptionDictType: opts = super().get_options() @@ -65,12 +63,12 @@ class ObjCPPCompiler(CLikeCompiler, Compiler): class GnuObjCPPCompiler(GnuCPPStds, GnuCompiler, ObjCPPCompiler): def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, - is_cross: bool, info: 'MachineInfo', + env: Environment, defines: T.Optional[T.Dict[str, str]] = None, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - ObjCPPCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, - info, linker=linker, full_version=full_version) + ObjCPPCompiler.__init__(self, ccache, exelist, version, for_machine, + env, linker=linker, full_version=full_version) GnuCompiler.__init__(self, defines) default_warn_args = ['-Wall', '-Winvalid-pch'] self.warn_args = {'0': [], @@ -81,13 +79,13 @@ class GnuObjCPPCompiler(GnuCPPStds, GnuCompiler, ObjCPPCompiler): self.supported_warn_args(gnu_common_warning_args) + self.supported_warn_args(gnu_objc_warning_args))} - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: args: T.List[str] = [] key = OptionKey('cpp_std', subproject=subproject, machine=self.for_machine) if target: - std = env.coredata.get_option_for_target(target, key) + std = self.environment.coredata.get_option_for_target(target, key) else: - std = env.coredata.optstore.get_value_for(key) + std = self.environment.coredata.optstore.get_value_for(key) assert isinstance(std, str) if std != 'none': args.append('-std=' + std) @@ -96,12 +94,12 @@ class GnuObjCPPCompiler(GnuCPPStds, GnuCompiler, ObjCPPCompiler): class ClangObjCPPCompiler(ClangCPPStds, ClangCompiler, ObjCPPCompiler): def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, - is_cross: bool, info: 'MachineInfo', + env: Environment, defines: T.Optional[T.Dict[str, str]] = None, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): - ObjCPPCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross, - info, linker=linker, full_version=full_version) + ObjCPPCompiler.__init__(self, ccache, exelist, version, for_machine, + env, linker=linker, full_version=full_version) ClangCompiler.__init__(self, defines) default_warn_args = ['-Wall', '-Winvalid-pch'] self.warn_args = {'0': [], @@ -110,10 +108,10 @@ class ClangObjCPPCompiler(ClangCPPStds, ClangCompiler, ObjCPPCompiler): '3': default_warn_args + ['-Wextra', '-Wpedantic'], 'everything': ['-Weverything']} - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: args = [] key = OptionKey('cpp_std', machine=self.for_machine) - std = self.get_compileropt_value(key, env, target, subproject) + std = self.get_compileropt_value(key, target, subproject) assert isinstance(std, str) if std != 'none': args.append('-std=' + std) diff --git a/mesonbuild/compilers/rust.py b/mesonbuild/compilers/rust.py index cc9dc21..ab0706d 100644 --- a/mesonbuild/compilers/rust.py +++ b/mesonbuild/compilers/rust.py @@ -11,13 +11,13 @@ import re import typing as T from .. import options -from ..mesonlib import EnvironmentException, MesonException, Popen_safe_logged +from ..mesonlib import EnvironmentException, MesonException, Popen_safe_logged, version_compare +from ..linkers.linkers import VisualStudioLikeLinkerMixin from ..options import OptionKey -from .compilers import Compiler, CompileCheckMode, clike_debug_args +from .compilers import Compiler, CompileCheckMode, clike_debug_args, is_library if T.TYPE_CHECKING: from ..options import MutableKeyedOptionDictType - from ..envconfig import MachineInfo from ..environment import Environment # noqa: F401 from ..linkers.linkers import DynamicLinker from ..mesonlib import MachineChoice @@ -64,6 +64,15 @@ def get_rustup_run_and_args(exelist: T.List[str]) -> T.Optional[T.Tuple[T.List[s except StopIteration: return None +def rustc_link_args(args: T.List[str]) -> T.List[str]: + if not args: + return args + rustc_args: T.List[str] = [] + for arg in args: + rustc_args.append('-C') + rustc_args.append(f'link-arg={arg}') + return rustc_args + class RustCompiler(Compiler): # rustc doesn't invoke the compiler itself, it doesn't need a LINKER_PREFIX @@ -78,36 +87,42 @@ class RustCompiler(Compiler): 'everything': ['-W', 'warnings'], } - # Those are static libraries, but we use dylib= here as workaround to avoid - # rust --tests to use /WHOLEARCHIVE. - # https://github.com/rust-lang/rust/issues/116910 + allow_nightly: bool + + # libcore can be compiled with either static or dynamic CRT, so disable + # both of them just in case. MSVCRT_ARGS: T.Mapping[str, T.List[str]] = { 'none': [], - 'md': [], # this is the default, no need to inject anything - 'mdd': ['-l', 'dylib=msvcrtd'], - 'mt': ['-l', 'dylib=libcmt'], - 'mtd': ['-l', 'dylib=libcmtd'], + 'md': ['-Clink-arg=/nodefaultlib:libcmt', '-Clink-arg=/defaultlib:msvcrt'], + 'mdd': ['-Clink-arg=/nodefaultlib:libcmt', '-Clink-arg=/nodefaultlib:msvcrt', '-Clink-arg=/defaultlib:msvcrtd'], + 'mt': ['-Clink-arg=/defaultlib:libcmt', '-Clink-arg=/nodefaultlib:msvcrt'], + 'mtd': ['-Clink-arg=/nodefaultlib:libcmt', '-Clink-arg=/nodefaultlib:msvcrt', '-Clink-arg=/defaultlib:libcmtd'], } def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, - is_cross: bool, info: 'MachineInfo', - full_version: T.Optional[str] = None, + env: Environment, full_version: T.Optional[str] = None, linker: T.Optional['DynamicLinker'] = None): - super().__init__([], exelist, version, for_machine, info, - is_cross=is_cross, full_version=full_version, - linker=linker) + super().__init__([], exelist, version, for_machine, env, + full_version=full_version, linker=linker) self.rustup_run_and_args: T.Optional[T.Tuple[T.List[str], T.List[str]]] = get_rustup_run_and_args(exelist) - self.base_options.update({OptionKey(o) for o in ['b_colorout', 'b_ndebug']}) - if 'link' in self.linker.id: + self.base_options.update({OptionKey(o) for o in ['b_colorout', 'b_coverage', 'b_ndebug', 'b_pgo']}) + if isinstance(self.linker, VisualStudioLikeLinkerMixin): self.base_options.add(OptionKey('b_vscrt')) self.native_static_libs: T.List[str] = [] self.is_beta = '-beta' in full_version self.is_nightly = '-nightly' in full_version + self.has_check_cfg = version_compare(version, '>=1.80.0') + + def init_from_options(self) -> None: + nightly_opt = self.get_compileropt_value('nightly', None) + if nightly_opt == 'enabled' and not self.is_nightly: + raise EnvironmentException(f'Rust compiler {self.name_string()} is not a nightly compiler as required by the "nightly" option.') + self.allow_nightly = nightly_opt != 'disabled' and self.is_nightly def needs_static_linker(self) -> bool: return False - def sanity_check(self, work_dir: str, environment: Environment) -> None: + def sanity_check(self, work_dir: str) -> None: source_name = os.path.join(work_dir, 'sanity.rs') output_name = os.path.join(work_dir, 'rusttest.exe') cmdlist = self.exelist.copy() @@ -141,7 +156,7 @@ class RustCompiler(Compiler): if pc.returncode != 0: raise EnvironmentException(f'Rust compiler {self.name_string()} cannot compile programs.') self._native_static_libs(work_dir, source_name) - self.run_sanity_check(environment, [output_name], work_dir) + self.run_sanity_check([output_name], work_dir) def _native_static_libs(self, work_dir: str, source_name: str) -> None: # Get libraries needed to link with a Rust staticlib @@ -155,6 +170,9 @@ class RustCompiler(Compiler): # no match and kernel == none (i.e. baremetal) is a valid use case. # return and let native_static_libs list empty return + if self.info.system == 'emscripten': + # no match and emscripten is valid after rustc 1.84 + return raise EnvironmentException('Failed to find native-static-libs in Rust compiler output.') # Exclude some well known libraries that we don't need because they # are always part of C/C++ linkers. Rustc probably should not print @@ -182,10 +200,69 @@ class RustCompiler(Compiler): return stdo.split('\n', maxsplit=1)[0] @functools.lru_cache(maxsize=None) - def get_crt_static(self) -> bool: + def get_cfgs(self) -> T.List[str]: cmd = self.get_exelist(ccache=False) + ['--print', 'cfg'] p, stdo, stde = Popen_safe_logged(cmd) - return bool(re.search('^target_feature="crt-static"$', stdo, re.MULTILINE)) + return stdo.splitlines() + + @functools.lru_cache(maxsize=None) + def get_crt_static(self) -> bool: + return 'target_feature="crt-static"' in self.get_cfgs() + + def get_nightly(self, target: T.Optional[BuildTarget]) -> bool: + if not target: + return self.allow_nightly + key = self.form_compileropt_key('nightly') + nightly_opt = self.environment.coredata.get_option_for_target(target, key) + if nightly_opt == 'enabled' and not self.is_nightly: + raise EnvironmentException(f'Rust compiler {self.name_string()} is not a nightly compiler as required by the "nightly" option.') + return nightly_opt != 'disabled' and self.is_nightly + + def sanitizer_link_args(self, target: T.Optional[BuildTarget], value: T.List[str]) -> T.List[str]: + # Sanitizers are not supported yet for Rust code. Nightly supports that + # with -Zsanitizer=, but procedural macros cannot use them. But even if + # Rust code cannot be instrumented, we can link in the sanitizer libraries + # for the sake of C/C++ code + return rustc_link_args(super().sanitizer_link_args(target, value)) + + @functools.lru_cache(maxsize=None) + def has_verbatim(self) -> bool: + if version_compare(self.version, '< 1.67.0'): + return False + # GNU ld support '-l:PATH' + if 'ld.' in self.linker.id and self.linker.id != 'ld.wasm': + return True + # -l:+verbatim does not work (yet?) with MSVC link or Apple ld64 + # (https://github.com/rust-lang/rust/pull/138753). For ld64, it + # works together with -l:+whole_archive because -force_load (the macOS + # equivalent of --whole-archive), receives the full path to the library + # being linked. However, Meson uses "bundle", not "whole_archive". + return False + + def lib_file_to_l_arg(self, libname: str) -> T.Optional[str]: + """Undo the effects of -l on the filename, returning the + argument that can be passed to -l, or None if the + library name is not supported.""" + if not is_library(libname): + return None + libname, ext = os.path.splitext(libname) + + # On Windows, rustc's -lfoo searches either foo.lib or libfoo.a. + # Elsewhere, it searches both static and shared libraries and always with + # the "lib" prefix; for simplicity just skip .lib on non-Windows. + if self.info.is_windows(): + if ext == '.lib': + return libname + if ext != '.a': + return None + else: + if ext == '.lib': + return None + + if not libname.startswith('lib'): + return None + libname = libname[3:] + return libname def get_debug_args(self, is_debug: bool) -> T.List[str]: return clike_debug_args[is_debug] @@ -193,19 +270,13 @@ class RustCompiler(Compiler): def get_optimization_args(self, optimization_level: str) -> T.List[str]: return rust_optimization_args[optimization_level] - def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, - rpath_paths: T.Tuple[str, ...], build_rpath: str, - install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: - args, to_remove = super().build_rpath_args(env, build_dir, from_dir, rpath_paths, - build_rpath, install_rpath) - - # ... but then add rustc's sysroot to account for rustup - # installations - rustc_rpath_args = [] - for arg in args: - rustc_rpath_args.append('-C') - rustc_rpath_args.append(f'link-arg={arg}:{self.get_target_libdir()}') - return rustc_rpath_args, to_remove + def build_rpath_args(self, build_dir: str, from_dir: str, target: BuildTarget, + extra_paths: T.Optional[T.List[str]] = None + ) -> T.Tuple[T.List[str], T.Set[bytes]]: + # add rustc's sysroot to account for rustup installations + args, to_remove = super().build_rpath_args( + build_dir, from_dir, target, [self.get_target_libdir()]) + return rustc_link_args(args), to_remove def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], build_dir: str) -> T.List[str]: @@ -237,6 +308,18 @@ class RustCompiler(Compiler): 'none', choices=['none', '2015', '2018', '2021', '2024']) + key = self.form_compileropt_key('dynamic_std') + opts[key] = options.UserBooleanOption( + self.make_option_name(key), + 'Whether to link Rust build targets to a dynamic libstd', + False) + + key = self.form_compileropt_key('nightly') + opts[key] = options.UserFeatureOption( + self.make_option_name(key), + "Nightly Rust compiler (enabled=required, disabled=don't use nightly feature, auto=use nightly feature if available)", + 'auto') + return opts def get_dependency_compile_args(self, dep: 'Dependency') -> T.List[str]: @@ -245,9 +328,9 @@ class RustCompiler(Compiler): # provided by the linker flags. return [] - def get_option_std_args(self, target: BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: args = [] - std = self.get_compileropt_value('std', env, target, subproject) + std = self.get_compileropt_value('std', target, subproject) assert isinstance(std, str) if std != 'none': args.append('--edition=' + std) @@ -258,8 +341,11 @@ class RustCompiler(Compiler): return [] def get_crt_link_args(self, crt_val: str, buildtype: str) -> T.List[str]: - if self.linker.id not in {'link', 'lld-link'}: + if not isinstance(self.linker, VisualStudioLikeLinkerMixin): return [] + # Rustc always use non-debug Windows runtime. Inject the one selected + # by Meson options instead. + # https://github.com/rust-lang/rust/issues/39016 return self.MSVCRT_ARGS[self.get_crt_val(crt_val, buildtype)] def get_colorout_args(self, colortype: str) -> T.List[str]: @@ -267,13 +353,65 @@ class RustCompiler(Compiler): return [f'--color={colortype}'] raise MesonException(f'Invalid color type for rust {colortype}') + @functools.lru_cache(maxsize=None) def get_linker_always_args(self) -> T.List[str]: - args: T.List[str] = [] - # Rust is super annoying, calling -C link-arg foo does not work, it has - # to be -C link-arg=foo - for a in super().get_linker_always_args(): - args.extend(['-C', f'link-arg={a}']) - return args + return rustc_link_args(super().get_linker_always_args()) + ['-Cdefault-linker-libraries'] + + def get_embed_bitcode_args(self, bitcode: bool, lto: bool) -> T.List[str]: + if bitcode: + return ['-C', 'embed-bitcode=yes'] + elif lto: + return [] + else: + return ['-C', 'embed-bitcode=no'] + + def get_lto_compile_args(self, *, target: T.Optional[BuildTarget] = None, threads: int = 0, + mode: str = 'default') -> T.List[str]: + if target.rust_crate_type in {'dylib', 'proc-macro'}: + return [] + + # TODO: what about -Clinker-plugin-lto? + rustc_lto = 'lto=thin' if mode == 'thin' else 'lto' + return ['-C', rustc_lto] + + def get_lto_link_args(self, *, target: T.Optional[BuildTarget] = None, threads: int = 0, + mode: str = 'default', thinlto_cache_dir: T.Optional[str] = None) -> T.List[str]: + # no need to specify anything because the rustc command line + # includes the result of get_lto_compile_args() + return [] + + def get_lto_obj_cache_path(self, path: str) -> T.List[str]: + return rustc_link_args(super().get_lto_obj_cache_path(path)) + + def get_coverage_args(self) -> T.List[str]: + return ['-C', 'instrument-coverage'] + + def get_coverage_link_args(self) -> T.List[str]: + return rustc_link_args(super().get_coverage_link_args()) + + def gen_vs_module_defs_args(self, defsfile: str) -> T.List[str]: + return rustc_link_args(super().gen_vs_module_defs_args(defsfile)) + + def get_profile_generate_args(self) -> T.List[str]: + return ['-C', 'profile-generate'] + + def get_profile_use_args(self) -> T.List[str]: + return ['-C', 'profile-use'] + + @functools.lru_cache(maxsize=None) + def get_asneeded_args(self) -> T.List[str]: + return rustc_link_args(super().get_asneeded_args()) + + def bitcode_args(self) -> T.List[str]: + return ['-C', 'embed-bitcode=yes'] + + @functools.lru_cache(maxsize=None) + def headerpad_args(self) -> T.List[str]: + return rustc_link_args(super().headerpad_args()) + + @functools.lru_cache(maxsize=None) + def get_allow_undefined_link_args(self) -> T.List[str]: + return rustc_link_args(super().get_allow_undefined_link_args()) def get_werror_args(self) -> T.List[str]: # Use -D warnings, which makes every warning not explicitly allowed an @@ -293,11 +431,11 @@ class RustCompiler(Compiler): # pic is on by rustc return [] - def get_assert_args(self, disable: bool, env: 'Environment') -> T.List[str]: + def get_assert_args(self, disable: bool) -> T.List[str]: action = "no" if disable else "yes" return ['-C', f'debug-assertions={action}', '-C', 'overflow-checks=no'] - def get_rust_tool(self, name: str, env: Environment) -> T.List[str]: + def get_rust_tool(self, name: str) -> T.List[str]: if self.rustup_run_and_args: rustup_exelist, args = self.rustup_run_and_args # do not use extend so that exelist is copied @@ -307,7 +445,7 @@ class RustCompiler(Compiler): args = self.get_exe_args() from ..programs import find_external_program - for prog in find_external_program(env, self.for_machine, exelist[0], exelist[0], + for prog in find_external_program(self.environment, self.for_machine, exelist[0], exelist[0], [exelist[0]], allow_default_for_cross=False): exelist[0] = prog.path break @@ -316,22 +454,32 @@ class RustCompiler(Compiler): return exelist + args - def has_multi_arguments(self, args: T.List[str], env: Environment) -> T.Tuple[bool, bool]: - return self.compiles('fn main { std::process::exit(0) };\n', env, extra_args=args, mode=CompileCheckMode.COMPILE) + def has_multi_arguments(self, args: T.List[str]) -> T.Tuple[bool, bool]: + return self.compiles('fn main() { std::process::exit(0) }\n', extra_args=args, mode=CompileCheckMode.COMPILE) - def has_multi_link_arguments(self, args: T.List[str], env: Environment) -> T.Tuple[bool, bool]: - args = self.linker.fatal_warnings() + args - return self.compiles('fn main { std::process::exit(0) };\n', env, extra_args=args, mode=CompileCheckMode.LINK) + def has_multi_link_arguments(self, args: T.List[str]) -> T.Tuple[bool, bool]: + args = rustc_link_args(self.linker.fatal_warnings()) + args + return self.compiles('fn main() { std::process::exit(0) }\n', extra_args=args, mode=CompileCheckMode.LINK) @functools.lru_cache(maxsize=None) - def get_rustdoc(self, env: 'Environment') -> T.Optional[RustdocTestCompiler]: - exelist = self.get_rust_tool('rustdoc', env) + def get_rustdoc(self) -> T.Optional[RustdocTestCompiler]: + exelist = self.get_rust_tool('rustdoc') if not exelist: return None return RustdocTestCompiler(exelist, self.version, self.for_machine, - self.is_cross, self.info, full_version=self.full_version, - linker=self.linker) + self.environment, + full_version=self.full_version, + linker=self.linker, rustc=self) + + def enable_env_set_args(self) -> T.Optional[T.List[str]]: + '''Extra arguments to enable --env-set support in rustc. + Returns None if not supported. + ''' + if version_compare(self.version, '>= 1.76') and self.allow_nightly: + return ['-Z', 'unstable-options'] + return None + class ClippyRustCompiler(RustCompiler): @@ -351,6 +499,25 @@ class RustdocTestCompiler(RustCompiler): id = 'rustdoc --test' + def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, + env: Environment, full_version: T.Optional[str], + linker: T.Optional['DynamicLinker'], rustc: RustCompiler): + super().__init__(exelist, version, for_machine, + env, full_version, linker) + self.rustc = rustc + + @functools.lru_cache(maxsize=None) + def get_sysroot(self) -> str: + return self.rustc.get_sysroot() + + @functools.lru_cache(maxsize=None) + def get_target_libdir(self) -> str: + return self.rustc.get_target_libdir() + + @functools.lru_cache(maxsize=None) + def get_cfgs(self) -> T.List[str]: + return self.rustc.get_cfgs() + def get_debug_args(self, is_debug: bool) -> T.List[str]: return [] diff --git a/mesonbuild/compilers/swift.py b/mesonbuild/compilers/swift.py index 528d76f..d5f87a2 100644 --- a/mesonbuild/compilers/swift.py +++ b/mesonbuild/compilers/swift.py @@ -8,7 +8,7 @@ import subprocess, os.path import typing as T from .. import mlog, options -from ..mesonlib import MesonException, version_compare +from ..mesonlib import first, MesonException, version_compare from .compilers import Compiler, clike_debug_args @@ -16,7 +16,6 @@ if T.TYPE_CHECKING: from .. import build from ..options import MutableKeyedOptionDictType from ..dependencies import Dependency - from ..envconfig import MachineInfo from ..environment import Environment from ..linkers.linkers import DynamicLinker from ..mesonlib import MachineChoice @@ -38,11 +37,10 @@ class SwiftCompiler(Compiler): id = 'llvm' def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, - is_cross: bool, info: 'MachineInfo', full_version: T.Optional[str] = None, + env: Environment, full_version: T.Optional[str] = None, linker: T.Optional['DynamicLinker'] = None): - super().__init__([], exelist, version, for_machine, info, - is_cross=is_cross, full_version=full_version, - linker=linker) + super().__init__([], exelist, version, for_machine, env, + full_version=full_version, linker=linker) self.version = version if self.info.is_darwin(): try: @@ -130,15 +128,25 @@ class SwiftCompiler(Compiler): return opts - def get_option_std_args(self, target: build.BuildTarget, env: Environment, subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_std_args(self, target: build.BuildTarget, subproject: T.Optional[str] = None) -> T.List[str]: args: T.List[str] = [] - std = self.get_compileropt_value('std', env, target, subproject) + std = self.get_compileropt_value('std', target, subproject) assert isinstance(std, str) if std != 'none': args += ['-swift-version', std] + # Pass C compiler -std=... arg to swiftc + c_langs = ['objc', 'c'] + if target.uses_swift_cpp_interop(): + c_langs = ['objcpp', 'cpp', *c_langs] + + c_lang = first(c_langs, lambda x: x in target.compilers) + if c_lang is not None: + cc = target.compilers[c_lang] + args.extend(arg for c_arg in cc.get_option_std_args(target, subproject) for arg in ['-Xcc', c_arg]) + return args def get_working_directory_args(self, path: str) -> T.Optional[T.List[str]]: @@ -147,6 +155,18 @@ class SwiftCompiler(Compiler): return ['-working-directory', path] + def get_cxx_interoperability_args(self, target: T.Optional[build.BuildTarget] = None) -> T.List[str]: + if target is not None and not target.uses_swift_cpp_interop(): + return [] + + if version_compare(self.version, '<5.9'): + raise MesonException(f'Compiler {self} does not support C++ interoperability') + + return ['-cxx-interoperability-mode=default'] + + def get_library_args(self) -> T.List[str]: + return ['-parse-as-library'] + def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], build_dir: str) -> T.List[str]: for idx, i in enumerate(parameter_list): @@ -155,22 +175,22 @@ class SwiftCompiler(Compiler): return parameter_list - def sanity_check(self, work_dir: str, environment: 'Environment') -> None: + def sanity_check(self, work_dir: str) -> None: src = 'swifttest.swift' source_name = os.path.join(work_dir, src) output_name = os.path.join(work_dir, 'swifttest') extra_flags: T.List[str] = [] - extra_flags += environment.coredata.get_external_args(self.for_machine, self.language) + extra_flags += self.environment.coredata.get_external_args(self.for_machine, self.language) if self.is_cross: extra_flags += self.get_compile_only_args() else: - extra_flags += environment.coredata.get_external_link_args(self.for_machine, self.language) + extra_flags += self.environment.coredata.get_external_link_args(self.for_machine, self.language) with open(source_name, 'w', encoding='utf-8') as ofile: ofile.write('''print("Swift compilation is working.") ''') pc = subprocess.Popen(self.exelist + extra_flags + ['-emit-executable', '-o', output_name, src], cwd=work_dir) pc.wait() - self.run_sanity_check(environment, [output_name], work_dir) + self.run_sanity_check([output_name], work_dir) def get_debug_args(self, is_debug: bool) -> T.List[str]: return clike_debug_args[is_debug] diff --git a/mesonbuild/compilers/vala.py b/mesonbuild/compilers/vala.py index 28861a6..575cf40 100644 --- a/mesonbuild/compilers/vala.py +++ b/mesonbuild/compilers/vala.py @@ -14,7 +14,6 @@ from .compilers import CompileCheckMode, Compiler if T.TYPE_CHECKING: from ..arglist import CompilerArgs - from ..envconfig import MachineInfo from ..environment import Environment from ..mesonlib import MachineChoice from ..dependencies import Dependency @@ -26,8 +25,8 @@ class ValaCompiler(Compiler): id = 'valac' def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, - is_cross: bool, info: 'MachineInfo'): - super().__init__([], exelist, version, for_machine, info, is_cross=is_cross) + environment: Environment): + super().__init__([], exelist, version, for_machine, environment) self.version = version self.base_options = {OptionKey('b_colorout')} self.force_link = False @@ -39,6 +38,14 @@ class ValaCompiler(Compiler): def get_optimization_args(self, optimization_level: str) -> T.List[str]: return [] + def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]: + if version_compare(self.version, '>=0.47.2'): + return ['--depfile', outfile] + return [] + + def get_depfile_suffix(self) -> str: + return 'depfile' + def get_debug_args(self, is_debug: bool) -> T.List[str]: return ['--debug'] if is_debug else [] @@ -99,21 +106,21 @@ class ValaCompiler(Compiler): return parameter_list - def sanity_check(self, work_dir: str, environment: 'Environment') -> None: + def sanity_check(self, work_dir: str) -> None: code = 'class MesonSanityCheck : Object { }' extra_flags: T.List[str] = [] - extra_flags += environment.coredata.get_external_args(self.for_machine, self.language) + extra_flags += self.environment.coredata.get_external_args(self.for_machine, self.language) if self.is_cross: extra_flags += self.get_compile_only_args() else: - extra_flags += environment.coredata.get_external_link_args(self.for_machine, self.language) - with self.cached_compile(code, environment.coredata, extra_args=extra_flags, mode=CompileCheckMode.COMPILE) as p: + extra_flags += self.environment.coredata.get_external_link_args(self.for_machine, self.language) + with self.cached_compile(code, extra_args=extra_flags, mode=CompileCheckMode.COMPILE) as p: if p.returncode != 0: msg = f'Vala compiler {self.name_string()!r} cannot compile programs' raise EnvironmentException(msg) - def find_library(self, libname: str, env: 'Environment', extra_dirs: T.List[str], - libtype: LibType = LibType.PREFER_SHARED, lib_prefix_warning: bool = True) -> T.Optional[T.List[str]]: + def find_library(self, libname: str, extra_dirs: T.List[str], libtype: LibType = LibType.PREFER_SHARED, + lib_prefix_warning: bool = True, ignore_system_dirs: bool = False) -> T.Optional[T.List[str]]: if extra_dirs and isinstance(extra_dirs, str): extra_dirs = [extra_dirs] # Valac always looks in the default vapi dir, so only search there if @@ -121,10 +128,10 @@ class ValaCompiler(Compiler): if not extra_dirs: code = 'class MesonFindLibrary : Object { }' args: T.List[str] = [] - args += env.coredata.get_external_args(self.for_machine, self.language) + args += self.environment.coredata.get_external_args(self.for_machine, self.language) vapi_args = ['--pkg', libname] args += vapi_args - with self.cached_compile(code, env.coredata, extra_args=args, mode=CompileCheckMode.COMPILE) as p: + with self.cached_compile(code, extra_args=args, mode=CompileCheckMode.COMPILE) as p: if p.returncode == 0: return vapi_args # Not found? Try to find the vapi file itself. @@ -135,16 +142,16 @@ class ValaCompiler(Compiler): mlog.debug(f'Searched {extra_dirs!r} and {libname!r} wasn\'t found') return None - def thread_flags(self, env: 'Environment') -> T.List[str]: + def thread_flags(self) -> T.List[str]: return [] - def thread_link_flags(self, env: 'Environment') -> T.List[str]: + def thread_link_flags(self) -> T.List[str]: return [] - def get_option_link_args(self, target: 'BuildTarget', env: 'Environment', subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_link_args(self, target: 'BuildTarget', subproject: T.Optional[str] = None) -> T.List[str]: return [] - def build_wrapper_args(self, env: 'Environment', + def build_wrapper_args(self, extra_args: T.Union[None, CompilerArgs, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]], dependencies: T.Optional[T.List['Dependency']], mode: CompileCheckMode = CompileCheckMode.COMPILE) -> CompilerArgs: @@ -180,28 +187,28 @@ class ValaCompiler(Compiler): if mode is CompileCheckMode.COMPILE: # Add DFLAGS from the env - args += env.coredata.get_external_args(self.for_machine, self.language) + args += self.environment.coredata.get_external_args(self.for_machine, self.language) elif mode is CompileCheckMode.LINK: # Add LDFLAGS from the env - args += env.coredata.get_external_link_args(self.for_machine, self.language) + args += self.environment.coredata.get_external_link_args(self.for_machine, self.language) # extra_args must override all other arguments, so we add them last args += extra_args return args - def links(self, code: 'mesonlib.FileOrString', env: 'Environment', *, + def links(self, code: 'mesonlib.FileOrString', *, compiler: T.Optional['Compiler'] = None, extra_args: T.Union[None, T.List[str], CompilerArgs, T.Callable[[CompileCheckMode], T.List[str]]] = None, dependencies: T.Optional[T.List['Dependency']] = None, disable_cache: bool = False) -> T.Tuple[bool, bool]: self.force_link = True if compiler: - with compiler._build_wrapper(code, env, dependencies=dependencies, want_output=True) as r: + with compiler._build_wrapper(code, dependencies=dependencies, want_output=True) as r: objfile = mesonlib.File.from_absolute_file(r.output_name) - result = self.compiles(objfile, env, extra_args=extra_args, + result = self.compiles(objfile, extra_args=extra_args, dependencies=dependencies, mode=CompileCheckMode.LINK, disable_cache=True) self.force_link = False return result - result = self.compiles(code, env, extra_args=extra_args, + result = self.compiles(code, extra_args=extra_args, dependencies=dependencies, mode=CompileCheckMode.LINK, disable_cache=disable_cache) self.force_link = False return result diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 90157df..8d7a1c5 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -10,69 +10,41 @@ from . import mlog, options import pickle, os, uuid import sys from functools import lru_cache -from itertools import chain from collections import OrderedDict import textwrap from .mesonlib import ( MesonException, MachineChoice, PerMachine, PerMachineDefaultable, - default_prefix, - stringlistify, pickle_load ) from .options import OptionKey -from .machinefile import CmdLineFileParser - -import ast import enum -import shlex import typing as T if T.TYPE_CHECKING: - import argparse - from typing_extensions import Protocol - from . import dependencies from .compilers.compilers import Compiler, CompileResult, RunResult, CompileCheckMode from .dependencies.detect import TV_DepID - from .environment import Environment from .mesonlib import FileOrString from .cmake.traceparser import CMakeCacheEntry from .interpreterbase import SubProject from .options import ElementaryOptionValues, MutableKeyedOptionDictType from .build import BuildTarget - - class SharedCMDOptions(Protocol): - - """Representation of command line options from Meson setup, configure, - and dist. - - :param projectoptions: The raw list of command line options given - :param cmd_line_options: command line options parsed into an OptionKey: - str mapping - """ - - cmd_line_options: T.Dict[OptionKey, str] - projectoptions: T.List[str] - cross_file: T.List[str] - native_file: T.List[str] + from .cmdline import SharedCMDOptions OptionDictType = T.Dict[str, options.AnyOptionType] CompilerCheckCacheKey = T.Tuple[T.Tuple[str, ...], str, FileOrString, T.Tuple[str, ...], CompileCheckMode] # code, args RunCheckCacheKey = T.Tuple[str, T.Tuple[str, ...]] - # typeshed - StrOrBytesPath = T.Union[str, bytes, os.PathLike[str], os.PathLike[bytes]] - # Check major_versions_differ() if changing versioning scheme. # # Pip requires that RCs are named like this: '0.1.0.rc1' # But the corresponding Git tag needs to be '0.1.0rc1' -version = '1.8.99' +version = '1.10.99' # The next stable version when we are in dev. This is used to allow projects to # require meson version >=1.2.0 when using 1.1.99. FeatureNew won't warn when @@ -147,13 +119,13 @@ class DependencyCache: def __init__(self, builtins: options.OptionStore, for_machine: MachineChoice): self.__cache: T.MutableMapping[TV_DepID, DependencySubCache] = OrderedDict() self.__builtins = builtins - self.__pkg_conf_key = options.OptionKey('pkg_config_path') - self.__cmake_key = options.OptionKey('cmake_prefix_path') + self.__pkg_conf_key = options.OptionKey('pkg_config_path', machine=for_machine) + self.__cmake_key = options.OptionKey('cmake_prefix_path', machine=for_machine) def __calculate_subkey(self, type_: DependencyCacheType) -> T.Tuple[str, ...]: data: T.Dict[DependencyCacheType, T.List[str]] = { - DependencyCacheType.PKG_CONFIG: stringlistify(self.__builtins.get_value_for(self.__pkg_conf_key)), - DependencyCacheType.CMAKE: stringlistify(self.__builtins.get_value_for(self.__cmake_key)), + DependencyCacheType.PKG_CONFIG: T.cast('T.List[str]', self.__builtins.get_value_for(self.__pkg_conf_key)), + DependencyCacheType.CMAKE: T.cast('T.List[str]', self.__builtins.get_value_for(self.__cmake_key)), DependencyCacheType.OTHER: [], } assert type_ in data, 'Someone forgot to update subkey calculations for a new type' @@ -224,7 +196,7 @@ class CMakeStateCache: def items(self) -> T.Iterator[T.Tuple[str, T.Dict[str, T.List[str]]]]: return iter(self.__cache.items()) - def update(self, language: str, variables: T.Dict[str, T.List[str]]): + def update(self, language: str, variables: T.Dict[str, T.List[str]]) -> None: if language not in self.__cache: self.__cache[language] = {} self.__cache[language].update(variables) @@ -248,6 +220,7 @@ class CoreData: 'default': '8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942', 'c': '8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942', 'cpp': '8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942', + 'masm': '8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942', 'test': '3AC096D0-A1C2-E12C-1390-A8335801FDAB', 'directory': '2150E333-8FDC-42A3-9474-1A3956D46DE8', } @@ -255,7 +228,7 @@ class CoreData: self.regen_guid = str(uuid.uuid4()).upper() self.install_guid = str(uuid.uuid4()).upper() self.meson_command = meson_command - self.target_guids = {} + self.target_guids: T.Dict[str, str] = {} self.version = version self.cross_files = self.__load_config_files(cmd_options, scratch_dir, 'cross') self.compilers: PerMachine[T.Dict[str, Compiler]] = PerMachine(OrderedDict(), OrderedDict()) @@ -286,7 +259,7 @@ class CoreData: # Only to print a warning if it changes between Meson invocations. self.config_files = self.__load_config_files(cmd_options, scratch_dir, 'native') self.builtin_options_libdir_cross_fixup() - self.init_builtins() + self.optstore.init_builtins() @staticmethod def __load_config_files(cmd_options: SharedCMDOptions, scratch_dir: str, ftype: str) -> T.List[str]: @@ -353,34 +326,6 @@ class CoreData: if self.cross_files: options.BUILTIN_OPTIONS[OptionKey('libdir')].default = 'lib' - def init_builtins(self) -> None: - # Create builtin options with default values - for key, opt in options.BUILTIN_OPTIONS.items(): - self.add_builtin_option(self.optstore, key, opt) - for for_machine in iter(MachineChoice): - for key, opt in options.BUILTIN_OPTIONS_PER_MACHINE.items(): - self.add_builtin_option(self.optstore, key.evolve(machine=for_machine), opt) - - @staticmethod - def add_builtin_option(optstore: options.OptionStore, key: OptionKey, - opt: options.AnyOptionType) -> None: - # Create a copy of the object, as we're going to mutate it - opt = copy.copy(opt) - if key.subproject: - if opt.yielding: - # This option is global and not per-subproject - return - else: - new_value = options.argparse_prefixed_default( - opt, key, default_prefix()) - opt.set_value(new_value) - - modulename = key.get_module_prefix() - if modulename: - optstore.add_module_option(modulename, key, opt) - else: - optstore.add_system_option(key, opt) - def init_backend_options(self, backend_name: str) -> None: if backend_name == 'ninja': self.optstore.add_system_option('backend_max_links', options.UserIntegerOption( @@ -406,24 +351,14 @@ class CoreData: # key and target have the same subproject for consistency. # Now just do this to get things going. newkey = newkey.evolve(subproject=target.subproject) - (option_object, value) = self.optstore.get_value_object_and_value_for(newkey) + option_object, value = self.optstore.get_option_and_value_for(newkey) override = target.get_override(newkey.name) if override is not None: return option_object.validate_value(override) return value - def set_option(self, key: OptionKey, value, first_invocation: bool = False) -> bool: - dirty = False - try: - changed = self.optstore.set_option(key, value, first_invocation) - except KeyError: - raise MesonException(f'Tried to set unknown builtin option {str(key)}') - dirty |= changed - - if key.name == 'buildtype': - dirty |= self._set_others_from_buildtype(value) - - return dirty + def set_from_configure_command(self, options: SharedCMDOptions) -> bool: + return self.optstore.set_from_configure_command(options.cmd_line_options) def clear_cache(self) -> None: self.deps.host.clear() @@ -454,43 +389,18 @@ class CoreData: return [] actual_opt = self.optstore.get_value_for('optimization') actual_debug = self.optstore.get_value_for('debug') + assert isinstance(actual_opt, str) # for mypy + assert isinstance(actual_debug, bool) # for mypy if actual_opt != opt: result.append(('optimization', actual_opt, opt)) if actual_debug != debug: result.append(('debug', actual_debug, debug)) return result - def _set_others_from_buildtype(self, value: str) -> bool: - dirty = False - - if value == 'plain': - opt = 'plain' - debug = False - elif value == 'debug': - opt = '0' - debug = True - elif value == 'debugoptimized': - opt = '2' - debug = True - elif value == 'release': - opt = '3' - debug = False - elif value == 'minsize': - opt = 's' - debug = True - else: - assert value == 'custom' - return False - - dirty |= self.optstore.set_option(OptionKey('optimization'), opt) - dirty |= self.optstore.set_option(OptionKey('debug'), debug) - - return dirty - def get_external_args(self, for_machine: MachineChoice, lang: str) -> T.List[str]: # mypy cannot analyze type of OptionKey key = OptionKey(f'{lang}_args', machine=for_machine) - return T.cast('T.List[str]', self.optstore.get_value(key)) + return T.cast('T.List[str]', self.optstore.get_value_for(key)) @lru_cache(maxsize=None) def get_external_link_args(self, for_machine: MachineChoice, lang: str) -> T.List[str]: @@ -503,92 +413,32 @@ class CoreData: return False return len(self.cross_files) > 0 - def copy_build_options_from_regular_ones(self, shut_up_pylint: bool = True) -> bool: - # FIXME, needs cross compilation support. - if shut_up_pylint: - return False - dirty = False - assert not self.is_cross_build() - for k in options.BUILTIN_OPTIONS_PER_MACHINE: - o = self.optstore.get_value_object_for(k.name) - dirty |= self.optstore.set_option(k, o.value, True) - for bk, bv in self.optstore.items(): - if bk.machine is MachineChoice.BUILD: - hk = bk.as_host() - try: - hv = self.optstore.get_value_object(hk) - dirty |= bv.set_value(hv.value) - except KeyError: - continue - - return dirty - - def set_options(self, opts_to_set: T.Dict[OptionKey, T.Any], subproject: str = '', first_invocation: bool = False) -> bool: - dirty = False - if not self.is_cross_build(): - opts_to_set = {k: v for k, v in opts_to_set.items() if k.machine is not MachineChoice.BUILD} - # Set prefix first because it's needed to sanitize other options - pfk = OptionKey('prefix') - if pfk in opts_to_set: - prefix = self.optstore.sanitize_prefix(opts_to_set[pfk]) - for key in options.BUILTIN_DIR_NOPREFIX_OPTIONS: - if key not in opts_to_set: - val = options.BUILTIN_OPTIONS[key].prefixed_default(key, prefix) - dirty |= self.optstore.set_option(key, val) - - unknown_options: T.List[OptionKey] = [] - for k, v in opts_to_set.items(): - if k == pfk: - continue - elif k.evolve(subproject=None) in self.optstore: - dirty |= self.set_option(k, v, first_invocation) - elif k.machine != MachineChoice.BUILD and not self.optstore.is_compiler_option(k): - unknown_options.append(k) - if unknown_options: - if subproject: - # The subproject may have top-level options that should be used - # when it is not a subproject. Ignore those for now. With option - # refactor they will get per-subproject values. - really_unknown = [] - for uo in unknown_options: - topkey = uo.as_root() - if topkey not in self.optstore: - really_unknown.append(uo) - unknown_options = really_unknown - if unknown_options: - unknown_options_str = ', '.join(sorted(str(s) for s in unknown_options)) - sub = f'In subproject {subproject}: ' if subproject else '' - raise MesonException(f'{sub}Unknown options: "{unknown_options_str}"') - - if not self.is_cross_build(): - dirty |= self.copy_build_options_from_regular_ones() - - return dirty - def add_compiler_options(self, c_options: MutableKeyedOptionDictType, lang: str, for_machine: MachineChoice, - env: Environment, subproject: str) -> None: + subproject: str) -> None: for k, o in c_options.items(): - comp_key = OptionKey(f'{k.name}', None, for_machine) + assert k.subproject is None and k.machine is for_machine + if subproject: + k = k.evolve(subproject=subproject) if lang == 'objc' and k.name == 'c_std': # For objective C, always fall back to c_std. - self.optstore.add_compiler_option('c', comp_key, o) + self.optstore.add_compiler_option('c', k, o) elif lang == 'objcpp' and k.name == 'cpp_std': - self.optstore.add_compiler_option('cpp', comp_key, o) + self.optstore.add_compiler_option('cpp', k, o) else: - self.optstore.add_compiler_option(lang, comp_key, o) + self.optstore.add_compiler_option(lang, k, o) - def add_lang_args(self, lang: str, comp: T.Type['Compiler'], - for_machine: MachineChoice, env: 'Environment') -> None: - """Add global language arguments that are needed before compiler/linker detection.""" - from .compilers import compilers - # These options are all new at this point, because the compiler is - # responsible for adding its own options, thus calling - # `self.optstore.update()`` is perfectly safe. - for gopt_key, gopt_valobj in compilers.get_global_options(lang, comp, for_machine, env).items(): - self.optstore.add_compiler_option(lang, gopt_key, gopt_valobj) + def process_compiler_options(self, lang: str, comp: Compiler, subproject: str) -> None: + self.add_compiler_options(comp.get_options(), lang, comp.for_machine, subproject) - def process_compiler_options(self, lang: str, comp: Compiler, env: Environment, subproject: str) -> None: - self.add_compiler_options(comp.get_options(), lang, comp.for_machine, env, subproject) + for key in [OptionKey(f'{lang}_args'), OptionKey(f'{lang}_link_args')]: + if self.is_cross_build(): + key = key.evolve(machine=comp.for_machine) + # the global option is already there, but any augment is still + # sitting in pending_options has to be taken into account + assert key in self.optstore + if subproject: + skey = key.evolve(subproject=subproject) + self.optstore.add_compiler_option(lang, skey, self.optstore.get_value_object(key)) for key in comp.base_options: if subproject: @@ -598,71 +448,18 @@ class CoreData: if skey not in self.optstore: self.optstore.add_system_option(skey, copy.deepcopy(options.COMPILER_BASE_OPTIONS[key])) + comp.init_from_options() + self.emit_base_options_warnings() def emit_base_options_warnings(self) -> None: bcodekey = OptionKey('b_bitcode') - if bcodekey in self.optstore and self.optstore.get_value(bcodekey): + if bcodekey in self.optstore and self.optstore.get_value_for(bcodekey): msg = textwrap.dedent('''Base option 'b_bitcode' is enabled, which is incompatible with many linker options. Incompatible options such as \'b_asneeded\' have been disabled.' Please see https://mesonbuild.com/Builtin-options.html#Notes_about_Apple_Bitcode_support for more details.''') mlog.warning(msg, once=True, fatal=False) -def get_cmd_line_file(build_dir: str) -> str: - return os.path.join(build_dir, 'meson-private', 'cmd_line.txt') - -def read_cmd_line_file(build_dir: str, options: SharedCMDOptions) -> None: - filename = get_cmd_line_file(build_dir) - if not os.path.isfile(filename): - return - - config = CmdLineFileParser() - config.read(filename) - - # Do a copy because config is not really a dict. options.cmd_line_options - # overrides values from the file. - d = {OptionKey.from_string(k): v for k, v in config['options'].items()} - d.update(options.cmd_line_options) - options.cmd_line_options = d - - properties = config['properties'] - if not options.cross_file: - options.cross_file = ast.literal_eval(properties.get('cross_file', '[]')) - if not options.native_file: - # This will be a string in the form: "['first', 'second', ...]", use - # literal_eval to get it into the list of strings. - options.native_file = ast.literal_eval(properties.get('native_file', '[]')) - -def write_cmd_line_file(build_dir: str, options: SharedCMDOptions) -> None: - filename = get_cmd_line_file(build_dir) - config = CmdLineFileParser() - - properties: OrderedDict[str, str] = OrderedDict() - if options.cross_file: - properties['cross_file'] = options.cross_file - if options.native_file: - properties['native_file'] = options.native_file - - config['options'] = {str(k): str(v) for k, v in options.cmd_line_options.items()} - config['properties'] = properties - with open(filename, 'w', encoding='utf-8') as f: - config.write(f) - -def update_cmd_line_file(build_dir: str, options: SharedCMDOptions) -> None: - filename = get_cmd_line_file(build_dir) - config = CmdLineFileParser() - config.read(filename) - config['options'].update({str(k): str(v) for k, v in options.cmd_line_options.items()}) - with open(filename, 'w', encoding='utf-8') as f: - config.write(f) - -def format_cmd_line_options(options: SharedCMDOptions) -> str: - cmdline = ['-D{}={}'.format(str(k), v) for k, v in options.cmd_line_options.items()] - if options.cross_file: - cmdline += [f'--cross-file={f}' for f in options.cross_file] - if options.native_file: - cmdline += [f'--native-file={f}' for f in options.native_file] - return ' '.join([shlex.quote(x) for x in cmdline]) def major_versions_differ(v1: str, v2: str) -> bool: v1_major, v1_minor = v1.rsplit('.', 1) @@ -692,45 +489,6 @@ def save(obj: CoreData, build_dir: str) -> str: return filename -def register_builtin_arguments(parser: argparse.ArgumentParser) -> None: - for n, b in options.BUILTIN_OPTIONS.items(): - options.option_to_argparse(b, n, parser, '') - for n, b in options.BUILTIN_OPTIONS_PER_MACHINE.items(): - options.option_to_argparse(b, n, parser, ' (just for host machine)') - options.option_to_argparse(b, n.as_build(), parser, ' (just for build machine)') - parser.add_argument('-D', action='append', dest='projectoptions', default=[], metavar="option", - help='Set the value of an option, can be used several times to set multiple options.') - -def create_options_dict(options: T.List[str], subproject: str = '') -> T.Dict[str, str]: - result: T.OrderedDict[OptionKey, str] = OrderedDict() - for o in options: - try: - (key, value) = o.split('=', 1) - except ValueError: - raise MesonException(f'Option {o!r} must have a value separated by equals sign.') - result[key] = value - return result - -def parse_cmd_line_options(args: SharedCMDOptions) -> None: - args.cmd_line_options = create_options_dict(args.projectoptions) - - # Merge builtin options set with --option into the dict. - for key in chain( - options.BUILTIN_OPTIONS.keys(), - (k.as_build() for k in options.BUILTIN_OPTIONS_PER_MACHINE.keys()), - options.BUILTIN_OPTIONS_PER_MACHINE.keys(), - ): - name = str(key) - value = getattr(args, name, None) - if value is not None: - if key in args.cmd_line_options: - cmdline_name = options.argparse_name_to_arg(name) - raise MesonException( - f'Got argument {name} as both -D{name} and {cmdline_name}. Pick one.') - args.cmd_line_options[key.name] = value - delattr(args, name) - - FORBIDDEN_TARGET_NAMES = frozenset({ 'clean', 'clean-ctlist', diff --git a/mesonbuild/dependencies/__init__.py b/mesonbuild/dependencies/__init__.py index 95e6069..abe8335 100644 --- a/mesonbuild/dependencies/__init__.py +++ b/mesonbuild/dependencies/__init__.py @@ -78,7 +78,7 @@ class FooSystemDependency(ExternalDependency): self.is_found = False return - lib = self.clib_compiler.find_library('foo', environment, [os.path.join(root, 'lib')]) + lib = self.clib_compiler.find_library('foo', [os.path.join(root, 'lib')]) if lib is None: mlog.debug('Could not find lib.') self.is_found = False @@ -114,7 +114,7 @@ class FooSystemDependency(ExternalDependency): return get_option = environment.coredata.get_option - static_opt = kwargs.get('static', get_option(Mesonlib.OptionKey('prefer_static')) + static_opt = kwargs['static'] if kwargs.get('static') is not None else get_option(Mesonlib.OptionKey('prefer_static') static = Mesonlib.LibType.STATIC if static_opt else Mesonlib.LibType.SHARED lib = self.clib_compiler.find_library( 'foo', environment, [os.path.join(root, 'lib')], libtype=static) diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index 38bfc08..4536746 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -21,6 +21,8 @@ from ..options import OptionKey #from ..interpreterbase import FeatureDeprecated, FeatureNew if T.TYPE_CHECKING: + from typing_extensions import Literal, TypedDict, TypeAlias + from ..compilers.compilers import Compiler from ..environment import Environment from ..interpreterbase import FeatureCheckBase @@ -30,6 +32,42 @@ if T.TYPE_CHECKING: ) from ..interpreter.type_checking import PkgConfigDefineType + IncludeType: TypeAlias = Literal['system', 'non-system', 'preserve'] + + class DependencyObjectKWs(TypedDict, total=False): + + """Keyword arguments that the Dependency IR object accepts. + + This is different than the arguments as taken by the Interpreter, since + it is expected to be clean. + """ + + cmake_args: T.List[str] + cmake_module_path: T.List[str] + cmake_package_version: str + components: T.List[str] + include_type: IncludeType + language: T.Optional[str] + main: bool + method: DependencyMethods + modules: T.List[str] + native: MachineChoice + optional_modules: T.List[str] + private_headers: bool + required: bool + static: T.Optional[bool] + version: T.List[str] + + # Only in the python dependency + embed: bool + + # Only passed internally, not part of the DSL API + paths: T.List[str] + returncode_value: int + silent: bool + tools: T.List[str] + version_arg: str + _MissingCompilerBase = Compiler else: _MissingCompilerBase = object @@ -55,7 +93,7 @@ class MissingCompiler(_MissingCompilerBase): def get_output_args(self, outputname: str) -> T.List[str]: return [] - def sanity_check(self, work_dir: str, environment: 'Environment') -> None: + def sanity_check(self, work_dir: str) -> None: return None def __getattr__(self, item: str) -> T.Any: @@ -82,12 +120,6 @@ class DependencyMethods(Enum): SYSCONFIG = 'sysconfig' # Specify using a "program"-config style tool CONFIG_TOOL = 'config-tool' - # For backwards compatibility - SDLCONFIG = 'sdlconfig' - CUPSCONFIG = 'cups-config' - PCAPCONFIG = 'pcap-config' - LIBWMFCONFIG = 'libwmf-config' - QMAKE = 'qmake' # Misc DUB = 'dub' @@ -97,17 +129,7 @@ DependencyTypeName = T.NewType('DependencyTypeName', str) class Dependency(HoldableObject): - @classmethod - def _process_include_type_kw(cls, kwargs: T.Dict[str, T.Any]) -> str: - if 'include_type' not in kwargs: - return 'preserve' - if not isinstance(kwargs['include_type'], str): - raise DependencyException('The include_type kwarg must be a string type') - if kwargs['include_type'] not in ['preserve', 'system', 'non-system']: - raise DependencyException("include_type may only be one of ['preserve', 'system', 'non-system']") - return kwargs['include_type'] - - def __init__(self, type_name: DependencyTypeName, kwargs: T.Dict[str, T.Any]) -> None: + def __init__(self, type_name: DependencyTypeName, kwargs: DependencyObjectKWs) -> None: # This allows two Dependencies to be compared even after being copied. # The purpose is to allow the name to be changed, but still have a proper comparison self._id = uuid.uuid4().int @@ -123,11 +145,12 @@ class Dependency(HoldableObject): self.raw_link_args: T.Optional[T.List[str]] = None self.sources: T.List[T.Union[mesonlib.File, GeneratedTypes, 'StructuredSources']] = [] self.extra_files: T.List[mesonlib.File] = [] - self.include_type = self._process_include_type_kw(kwargs) + self.include_type = kwargs.get('include_type', 'preserve') self.ext_deps: T.List[Dependency] = [] self.d_features: T.DefaultDict[str, T.List[T.Any]] = collections.defaultdict(list) self.featurechecks: T.List['FeatureCheckBase'] = [] self.feature_since: T.Optional[T.Tuple[str, str]] = None + self.meson_variables: T.List[str] = [] def __eq__(self, other: object) -> bool: if not isinstance(other, Dependency): @@ -143,6 +166,11 @@ class Dependency(HoldableObject): def is_built(self) -> bool: return False + def is_named(self) -> bool: + if self.name is None: + return False + return self.name != f'dep{self._id}' + def summary_value(self) -> T.Union[str, mlog.AnsiDecorator, mlog.AnsiText]: if not self.found(): return mlog.red('NO') @@ -256,9 +284,9 @@ class Dependency(HoldableObject): return default_value raise DependencyException(f'No default provided for dependency {self!r}, which is not pkg-config, cmake, or config-tool based.') - def generate_system_dependency(self, include_type: str) -> 'Dependency': + def generate_system_dependency(self, include_type: IncludeType) -> 'Dependency': new_dep = copy.deepcopy(self) - new_dep.include_type = self._process_include_type_kw({'include_type': include_type}) + new_dep.include_type = include_type return new_dep def get_as_static(self, recursive: bool) -> Dependency: @@ -383,32 +411,23 @@ class InternalDependency(Dependency): new_dep.ext_deps = [dep.get_as_shared(True) for dep in self.ext_deps] return new_dep -class HasNativeKwarg: - def __init__(self, kwargs: T.Dict[str, T.Any]): - self.for_machine = self.get_for_machine_from_kwargs(kwargs) - - def get_for_machine_from_kwargs(self, kwargs: T.Dict[str, T.Any]) -> MachineChoice: - return MachineChoice.BUILD if kwargs.get('native', False) else MachineChoice.HOST - -class ExternalDependency(Dependency, HasNativeKwarg): - def __init__(self, type_name: DependencyTypeName, environment: 'Environment', kwargs: T.Dict[str, T.Any], language: T.Optional[str] = None): +class ExternalDependency(Dependency): + def __init__(self, type_name: DependencyTypeName, environment: 'Environment', kwargs: DependencyObjectKWs, language: T.Optional[str] = None): Dependency.__init__(self, type_name, kwargs) self.env = environment self.name = type_name # default self.is_found = False self.language = language - version_reqs = kwargs.get('version', None) - if isinstance(version_reqs, str): - version_reqs = [version_reqs] - self.version_reqs: T.Optional[T.List[str]] = version_reqs + self.version_reqs = kwargs.get('version', []) self.required = kwargs.get('required', True) self.silent = kwargs.get('silent', False) - self.static = kwargs.get('static', self.env.coredata.optstore.get_value_for(OptionKey('prefer_static'))) + static = kwargs.get('static') + if static is None: + static = T.cast('bool', self.env.coredata.optstore.get_value_for(OptionKey('prefer_static'))) + self.static = static self.libtype = LibType.STATIC if self.static else LibType.PREFER_SHARED - if not isinstance(self.static, bool): - raise DependencyException('Static keyword must be boolean') # Is this dependency to be run on the build platform? - HasNativeKwarg.__init__(self, kwargs) + self.for_machine = kwargs.get('native', MachineChoice.HOST) self.clib_compiler = detect_compiler(self.name, environment, self.for_machine, self.language) def get_compiler(self) -> T.Union['MissingCompiler', 'Compiler']: @@ -597,36 +616,14 @@ def strip_system_includedirs(environment: 'Environment', for_machine: MachineCho exclude = {f'-I{p}' for p in environment.get_compiler_system_include_dirs(for_machine)} return [i for i in include_args if i not in exclude] -def process_method_kw(possible: T.Iterable[DependencyMethods], kwargs: T.Dict[str, T.Any]) -> T.List[DependencyMethods]: - method: T.Union[DependencyMethods, str] = kwargs.get('method', 'auto') - if isinstance(method, DependencyMethods): - return [method] - # TODO: try/except? - if method not in [e.value for e in DependencyMethods]: - raise DependencyException(f'method {method!r} is invalid') - method = DependencyMethods(method) - - # Raise FeatureNew where appropriate - if method is DependencyMethods.CONFIG_TOOL: - # FIXME: needs to get a handle on the subproject - # FeatureNew.single_use('Configuration method "config-tool"', '0.44.0') - pass - # This sets per-tool config methods which are deprecated to to the new - # generic CONFIG_TOOL value. - if method in [DependencyMethods.SDLCONFIG, DependencyMethods.CUPSCONFIG, - DependencyMethods.PCAPCONFIG, DependencyMethods.LIBWMFCONFIG]: - # FIXME: needs to get a handle on the subproject - #FeatureDeprecated.single_use(f'Configuration method {method.value}', '0.44', 'Use "config-tool" instead.') - method = DependencyMethods.CONFIG_TOOL - if method is DependencyMethods.QMAKE: - # FIXME: needs to get a handle on the subproject - # FeatureDeprecated.single_use('Configuration method "qmake"', '0.58', 'Use "config-tool" instead.') - method = DependencyMethods.CONFIG_TOOL +def process_method_kw(possible: T.Iterable[DependencyMethods], kwargs: DependencyObjectKWs) -> T.List[DependencyMethods]: + method = kwargs.get('method', DependencyMethods.AUTO) # Set the detection method. If the method is set to auto, use any available method. # If method is set to a specific string, allow only that detection method. if method == DependencyMethods.AUTO: - methods = list(possible) + # annotated for https://github.com/python/mypy/issues/19894 + methods: T.List[DependencyMethods] = list(possible) elif method in possible: methods = [method] else: @@ -663,7 +660,7 @@ class SystemDependency(ExternalDependency): """Dependency base for System type dependencies.""" - def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any], + def __init__(self, name: str, env: 'Environment', kwargs: DependencyObjectKWs, language: T.Optional[str] = None) -> None: super().__init__(DependencyTypeName('system'), env, kwargs, language=language) self.name = name @@ -677,7 +674,7 @@ class BuiltinDependency(ExternalDependency): """Dependency base for Builtin type dependencies.""" - def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any], + def __init__(self, name: str, env: 'Environment', kwargs: DependencyObjectKWs, language: T.Optional[str] = None) -> None: super().__init__(DependencyTypeName('builtin'), env, kwargs, language=language) self.name = name diff --git a/mesonbuild/dependencies/boost.py b/mesonbuild/dependencies/boost.py index 662f985..1aeb451 100644 --- a/mesonbuild/dependencies/boost.py +++ b/mesonbuild/dependencies/boost.py @@ -21,6 +21,7 @@ from .misc import threads_factory if T.TYPE_CHECKING: from ..envconfig import Properties from ..environment import Environment + from .base import DependencyObjectKWs # On windows 3 directory layouts are supported: # * The default layout (versioned) installed: @@ -339,7 +340,7 @@ class BoostLibraryFile(): return [self.path.as_posix()] class BoostDependency(SystemDependency): - def __init__(self, environment: Environment, kwargs: T.Dict[str, T.Any]) -> None: + def __init__(self, environment: Environment, kwargs: DependencyObjectKWs) -> None: super().__init__('boost', environment, kwargs, language='cpp') buildtype = environment.coredata.optstore.get_value_for(OptionKey('buildtype')) assert isinstance(buildtype, str) @@ -347,13 +348,11 @@ class BoostDependency(SystemDependency): self.multithreading = kwargs.get('threading', 'multi') == 'multi' self.boost_root: T.Optional[Path] = None - self.explicit_static = 'static' in kwargs + self.explicit_static = kwargs.get('static') is not None # Extract and validate modules - self.modules: T.List[str] = mesonlib.extract_as_list(kwargs, 'modules') + self.modules = kwargs.get('modules', []) for i in self.modules: - if not isinstance(i, str): - raise DependencyException('Boost module argument is not a string.') if i.startswith('boost_'): raise DependencyException('Boost modules must be passed without the boost_ prefix') @@ -440,6 +439,8 @@ class BoostDependency(SystemDependency): mlog.debug(' - potential library dirs: {}'.format([x.as_posix() for x in lib_dirs])) mlog.debug(' - potential include dirs: {}'.format([x.path.as_posix() for x in inc_dirs])) + must_have_library = ['boost_python'] + # 2. Find all boost libraries libs: T.List[BoostLibraryFile] = [] for i in lib_dirs: @@ -452,6 +453,10 @@ class BoostDependency(SystemDependency): break libs = sorted(set(libs)) + any_libs_found = len(libs) > 0 + if not any_libs_found: + return False + modules = ['boost_' + x for x in self.modules] for inc in inc_dirs: mlog.debug(f' - found boost {inc.version} include dir: {inc.path}') @@ -462,7 +467,7 @@ class BoostDependency(SystemDependency): mlog.debug(f' - {j}') # 3. Select the libraries matching the requested modules - not_found: T.List[str] = [] + not_found_as_libs: T.List[str] = [] selected_modules: T.List[BoostLibraryFile] = [] for mod in modules: found = False @@ -472,7 +477,24 @@ class BoostDependency(SystemDependency): found = True break if not found: - not_found += [mod] + not_found_as_libs += [mod] + + # If a lib is not found, but an include directory exists, + # assume it is a header only module. + not_found: T.List[str] = [] + for boost_modulename in not_found_as_libs: + assert boost_modulename.startswith('boost_') + if boost_modulename in must_have_library: + not_found.append(boost_modulename) + continue + include_subdir = boost_modulename.replace('boost_', 'boost/', 1) + headerdir_found = False + for inc_dir in inc_dirs: + if (inc_dir.path / include_subdir).is_dir(): + headerdir_found = True + break + if not headerdir_found: + not_found.append(boost_modulename) # log the result mlog.debug(' - found:') @@ -535,7 +557,7 @@ class BoostDependency(SystemDependency): # given root path if use_system: - system_dirs_t = self.clib_compiler.get_library_dirs(self.env) + system_dirs_t = self.clib_compiler.get_library_dirs() system_dirs = [Path(x) for x in system_dirs_t] system_dirs = [x.resolve() for x in system_dirs if x.exists()] system_dirs = [x for x in system_dirs if mesonlib.path_is_in_root(x, root)] @@ -581,9 +603,9 @@ class BoostDependency(SystemDependency): # MSVC is very picky with the library tags vscrt = '' try: - crt_val = self.env.coredata.optstore.get_value('b_vscrt') + crt_val = self.env.coredata.optstore.get_value_for('b_vscrt') assert isinstance(crt_val, str) - buildtype = self.env.coredata.optstore.get_value('buildtype') + buildtype = self.env.coredata.optstore.get_value_for('buildtype') assert isinstance(buildtype, str) vscrt = self.clib_compiler.get_crt_compile_args(crt_val, buildtype)[0] except (KeyError, IndexError, AttributeError): diff --git a/mesonbuild/dependencies/cmake.py b/mesonbuild/dependencies/cmake.py index 4e44981..066bb29 100644 --- a/mesonbuild/dependencies/cmake.py +++ b/mesonbuild/dependencies/cmake.py @@ -4,7 +4,7 @@ from __future__ import annotations from .base import ExternalDependency, DependencyException, DependencyTypeName -from ..mesonlib import is_windows, MesonException, PerMachine, stringlistify, extract_as_list +from ..mesonlib import is_windows, MesonException, PerMachine, MachineChoice from ..cmake import CMakeExecutor, CMakeTraceParser, CMakeException, CMakeToolchain, CMakeExecScope, check_cmake_args, resolve_cmake_trace_targets, cmake_is_debug from .. import mlog import importlib.resources @@ -21,6 +21,7 @@ if T.TYPE_CHECKING: from ..environment import Environment from ..envconfig import MachineInfo from ..interpreter.type_checking import PkgConfigDefineType + from .base import DependencyObjectKWs class CMakeInfo(T.NamedTuple): module_paths: T.List[str] @@ -69,16 +70,12 @@ class CMakeDependency(ExternalDependency): # one module return module - def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any], language: T.Optional[str] = None, force_use_global_compilers: bool = False) -> None: + def __init__(self, name: str, environment: 'Environment', kwargs: DependencyObjectKWs, language: T.Optional[str] = None, force_use_global_compilers: bool = False) -> None: # Gather a list of all languages to support self.language_list: T.List[str] = [] if language is None or force_use_global_compilers: - compilers = None - if kwargs.get('native', False): - compilers = environment.coredata.compilers.build - else: - compilers = environment.coredata.compilers.host - + for_machine = kwargs.get('native', MachineChoice.HOST) + compilers = environment.coredata.compilers[for_machine] candidates = ['c', 'cpp', 'fortran', 'objc', 'objcxx'] self.language_list += [x for x in candidates if x in compilers] else: @@ -116,7 +113,7 @@ class CMakeDependency(ExternalDependency): # Setup the trace parser self.traceparser = CMakeTraceParser(self.cmakebin.version(), self._get_build_dir(), self.env) - cm_args = stringlistify(extract_as_list(kwargs, 'cmake_args')) + cm_args = kwargs.get('cmake_args', []) cm_args = check_cmake_args(cm_args) if CMakeDependency.class_cmakeinfo[self.for_machine] is None: CMakeDependency.class_cmakeinfo[self.for_machine] = self._get_cmake_info(cm_args) @@ -126,13 +123,10 @@ class CMakeDependency(ExternalDependency): self.cmakeinfo = cmakeinfo package_version = kwargs.get('cmake_package_version', '') - if not isinstance(package_version, str): - raise DependencyException('Keyword "cmake_package_version" must be a string.') - components = [(x, True) for x in stringlistify(extract_as_list(kwargs, 'components'))] - modules = [(x, True) for x in stringlistify(extract_as_list(kwargs, 'modules'))] - modules += [(x, False) for x in stringlistify(extract_as_list(kwargs, 'optional_modules'))] - cm_path = stringlistify(extract_as_list(kwargs, 'cmake_module_path')) - cm_path = [x if os.path.isabs(x) else os.path.join(environment.get_source_dir(), x) for x in cm_path] + components = [(x, True) for x in kwargs.get('components', [])] + modules = [(x, True) for x in kwargs.get('modules', [])] + modules += [(x, False) for x in kwargs.get('optional_modules', [])] + cm_path = [x if os.path.isabs(x) else os.path.join(environment.get_source_dir(), x) for x in kwargs.get('cmake_module_path', [])] if cm_path: cm_args.append('-DCMAKE_MODULE_PATH=' + ';'.join(cm_path)) if not self._preliminary_find_check(name, cm_path, self.cmakebin.get_cmake_prefix_paths(), environment.machines[self.for_machine]): @@ -655,7 +649,7 @@ class CMakeDependencyFactory: self.name = name self.modules = modules - def __call__(self, name: str, env: Environment, kwargs: T.Dict[str, T.Any], language: T.Optional[str] = None, force_use_global_compilers: bool = False) -> CMakeDependency: + def __call__(self, name: str, env: Environment, kwargs: DependencyObjectKWs, language: T.Optional[str] = None, force_use_global_compilers: bool = False) -> CMakeDependency: if self.modules: kwargs['modules'] = self.modules return CMakeDependency(self.name or name, env, kwargs, language, force_use_global_compilers) diff --git a/mesonbuild/dependencies/coarrays.py b/mesonbuild/dependencies/coarrays.py index 7701c06..a4dbdc5 100644 --- a/mesonbuild/dependencies/coarrays.py +++ b/mesonbuild/dependencies/coarrays.py @@ -16,12 +16,13 @@ if T.TYPE_CHECKING: from . factory import DependencyGenerator from ..environment import Environment from ..mesonlib import MachineChoice + from .base import DependencyObjectKWs @factory_methods({DependencyMethods.PKGCONFIG, DependencyMethods.CMAKE, DependencyMethods.SYSTEM}) def coarray_factory(env: 'Environment', for_machine: 'MachineChoice', - kwargs: T.Dict[str, T.Any], + kwargs: DependencyObjectKWs, methods: T.List[DependencyMethods]) -> T.List['DependencyGenerator']: fcid = detect_compiler('coarray', env, for_machine, 'fortran').get_id() candidates: T.List['DependencyGenerator'] = [] @@ -34,8 +35,8 @@ def coarray_factory(env: 'Environment', PkgConfigDependency, pkg, env, kwargs, language='fortran')) if DependencyMethods.CMAKE in methods: - if 'modules' not in kwargs: - kwargs['modules'] = 'OpenCoarrays::caf_mpi' + if not kwargs.get('modules'): + kwargs['modules'] = ['OpenCoarrays::caf_mpi'] candidates.append(functools.partial( CMakeDependency, 'OpenCoarrays', env, kwargs, language='fortran')) @@ -55,7 +56,7 @@ class CoarrayDependency(SystemDependency): Coarrays may be thought of as a high-level language abstraction of low-level MPI calls. """ - def __init__(self, environment: 'Environment', kwargs: T.Dict[str, T.Any]) -> None: + def __init__(self, environment: 'Environment', kwargs: DependencyObjectKWs) -> None: super().__init__('coarray', environment, kwargs, language='fortran') kwargs['required'] = False kwargs['silent'] = True diff --git a/mesonbuild/dependencies/configtool.py b/mesonbuild/dependencies/configtool.py index 476f7ad..e2721fe 100644 --- a/mesonbuild/dependencies/configtool.py +++ b/mesonbuild/dependencies/configtool.py @@ -10,11 +10,11 @@ from .. import mlog import re import typing as T -from mesonbuild import mesonlib - if T.TYPE_CHECKING: from ..environment import Environment from ..interpreter.type_checking import PkgConfigDefineType + from .base import DependencyObjectKWs + class ConfigToolDependency(ExternalDependency): @@ -37,7 +37,7 @@ class ConfigToolDependency(ExternalDependency): allow_default_for_cross = False __strip_version = re.compile(r'^[0-9][0-9.]+') - def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any], language: T.Optional[str] = None, exclude_paths: T.Optional[T.List[str]] = None): + def __init__(self, name: str, environment: 'Environment', kwargs: DependencyObjectKWs, language: T.Optional[str] = None, exclude_paths: T.Optional[T.List[str]] = None): super().__init__(DependencyTypeName('config-tool'), environment, kwargs, language=language) self.name = name # You may want to overwrite the class version in some cases @@ -47,11 +47,7 @@ class ConfigToolDependency(ExternalDependency): if 'version_arg' in kwargs: self.version_arg = kwargs['version_arg'] - req_version_raw = kwargs.get('version', None) - if req_version_raw is not None: - req_version = mesonlib.stringlistify(req_version_raw) - else: - req_version = [] + req_version = kwargs.get('version', []) tool, version = self.find_config(req_version, kwargs.get('returncode_value', 0), exclude_paths=exclude_paths) self.config = tool self.is_found = self.report_config(version, req_version) diff --git a/mesonbuild/dependencies/cuda.py b/mesonbuild/dependencies/cuda.py index 82bf5ad..d80c62d 100644 --- a/mesonbuild/dependencies/cuda.py +++ b/mesonbuild/dependencies/cuda.py @@ -11,35 +11,40 @@ from pathlib import Path from .. import mesonlib from .. import mlog -from ..environment import detect_cpu_family from .base import DependencyException, SystemDependency from .detect import packages - +from ..mesonlib import LibType if T.TYPE_CHECKING: from ..environment import Environment from ..compilers import Compiler + from ..envconfig import MachineInfo + from .base import DependencyObjectKWs TV_ResultTuple = T.Tuple[T.Optional[str], T.Optional[str], bool] class CudaDependency(SystemDependency): supported_languages = ['cpp', 'c', 'cuda'] # see also _default_language + targets_dir = 'targets' # Directory containing CUDA targets. - def __init__(self, environment: 'Environment', kwargs: T.Dict[str, T.Any]) -> None: - compilers = environment.coredata.compilers[self.get_for_machine_from_kwargs(kwargs)] + def __init__(self, environment: 'Environment', kwargs: DependencyObjectKWs) -> None: + for_machine = kwargs.get('native', mesonlib.MachineChoice.HOST) + compilers = environment.coredata.compilers[for_machine] + machine = environment.machines[for_machine] language = self._detect_language(compilers) + if language not in self.supported_languages: raise DependencyException(f'Language \'{language}\' is not supported by the CUDA Toolkit. Supported languages are {self.supported_languages}.') super().__init__('cuda', environment, kwargs, language=language) self.lib_modules: T.Dict[str, T.List[str]] = {} - self.requested_modules = self.get_requested(kwargs) + self.requested_modules = kwargs.get('modules', []) if not any(runtime in self.requested_modules for runtime in ['cudart', 'cudart_static']): # By default, we prefer to link the static CUDA runtime, since this is what nvcc also does by default: # https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html#cudart-none-shared-static-cudart req_modules = ['cudart'] - if kwargs.get('static', True): + if kwargs.get('static') is not False: req_modules = ['cudart_static'] self.requested_modules = req_modules + self.requested_modules @@ -50,16 +55,25 @@ class CudaDependency(SystemDependency): if not os.path.isabs(self.cuda_path): raise DependencyException(f'CUDA Toolkit path must be absolute, got \'{self.cuda_path}\'.') + # Cuda target directory relative to cuda path. + self.target_path = self._detect_target_path(machine) + # nvcc already knows where to find the CUDA Toolkit, but if we're compiling # a mixed C/C++/CUDA project, we still need to make the include dir searchable if self.language != 'cuda' or len(compilers) > 1: - self.incdir = os.path.join(self.cuda_path, 'include') + self.incdir = os.path.join(self.cuda_path, self.target_path, 'include') self.compile_args += [f'-I{self.incdir}'] arch_libdir = self._detect_arch_libdir() - self.libdir = os.path.join(self.cuda_path, arch_libdir) + self.libdir = os.path.join(self.cuda_path, self.target_path, arch_libdir) mlog.debug('CUDA library directory is', mlog.bold(self.libdir)) + # For legacy reasons cuda ignores the `prefer_static` option, and treats + # anything short of `static : false` as `static : true`. This is the + # opposite behavior of all other languages. + if kwargs.get('static') is None: + self.libtype = LibType.PREFER_STATIC + self.is_found = self._find_requested_libraries() @classmethod @@ -128,6 +142,32 @@ class CudaDependency(SystemDependency): mlog.warning(nvcc_warning) return (None, None, False) + def _detect_target_path(self, machine: MachineInfo) -> str: + # Non-Linux hosts: nothing to detect. + if not machine.is_linux(): + return '.' + + # Canonical target: '<arch>-<system>', e.g. 'x86_64-linux'. + canonical_target = f'{machine.cpu_family}-{machine.system}' + rel_path = os.path.join(self.targets_dir, canonical_target) + abs_path = os.path.join(self.cuda_path, rel_path) + + # AArch64 may need the SBSA fallback. + if machine.cpu_family == 'aarch64' and not os.path.exists(abs_path): + rel_path = os.path.join(self.targets_dir, f"sbsa-{machine.system}") + abs_path = os.path.join(self.cuda_path, rel_path) + mlog.debug( + f'Canonical CUDA target "{self.targets_dir}/{canonical_target}" missing; ' + f'falling back to "{rel_path}".' + ) + + mlog.debug(f'CUDA target resolved to "{rel_path}".') + + if not os.path.exists(abs_path): + mlog.error(f'CUDA target "{rel_path}" does not exist.') + + return rel_path + def _default_path_env_var(self) -> T.Optional[str]: env_vars = ['CUDA_PATH'] if self._is_windows() else ['CUDA_PATH', 'CUDA_HOME', 'CUDA_ROOT'] env_vars = [var for var in env_vars if var in os.environ] @@ -211,8 +251,8 @@ class CudaDependency(SystemDependency): return '.'.join(version.split('.')[:2]) def _detect_arch_libdir(self) -> str: - arch = detect_cpu_family(self.env.coredata.compilers.host) machine = self.env.machines[self.for_machine] + arch = machine.cpu_family msg = '{} architecture is not supported in {} version of the CUDA Toolkit.' if machine.is_windows(): libdirs = {'x86': 'Win32', 'x86_64': 'x64'} @@ -220,10 +260,7 @@ class CudaDependency(SystemDependency): raise DependencyException(msg.format(arch, 'Windows')) return os.path.join('lib', libdirs[arch]) elif machine.is_linux(): - libdirs = {'x86_64': 'lib64', 'ppc64': 'lib', 'aarch64': 'lib64', 'loongarch64': 'lib64'} - if arch not in libdirs: - raise DependencyException(msg.format(arch, 'Linux')) - return libdirs[arch] + return 'lib' elif machine.is_darwin(): libdirs = {'x86_64': 'lib64'} if arch not in libdirs: @@ -236,13 +273,14 @@ class CudaDependency(SystemDependency): all_found = True for module in self.requested_modules: - args = self.clib_compiler.find_library(module, self.env, [self.libdir]) - if module == 'cudart_static' and self.language != 'cuda': - machine = self.env.machines[self.for_machine] - if machine.is_linux(): - # extracted by running - # nvcc -v foo.o - args += ['-lrt', '-lpthread', '-ldl'] + # You should only ever link to libraries inside the cuda tree, nothing outside of it. + # For instance, there is a + # + # - libnvidia-ml.so in stubs/ of the CUDA tree + # - libnvidia-ml.so in /usr/lib/ that is provided by the nvidia drivers + # + # Users should never link to the latter, since its ABI may change. + args = self.clib_compiler.find_library(module, [self.libdir, os.path.join(self.libdir, 'stubs')], self.libtype, ignore_system_dirs=True) if args is None: self._report_dependency_error(f'Couldn\'t find requested CUDA module \'{module}\'') @@ -276,31 +314,27 @@ class CudaDependency(SystemDependency): def log_info(self) -> str: return self.cuda_path if self.cuda_path else '' - def get_requested(self, kwargs: T.Dict[str, T.Any]) -> T.List[str]: - candidates = mesonlib.extract_as_list(kwargs, 'modules') - for c in candidates: - if not isinstance(c, str): - raise DependencyException('CUDA module argument is not a string.') - return candidates - def get_link_args(self, language: T.Optional[str] = None, raw: bool = False) -> T.List[str]: + # when using nvcc to link, we should instead use the native driver options + REWRITE_MODULES = { + 'cudart': ['-cudart', 'shared'], + 'cudart_static': ['-cudart', 'static'], + 'cudadevrt': ['-cudadevrt'], + } + args: T.List[str] = [] for lib in self.requested_modules: link_args = self.lib_modules[lib] - # Turn canonical arguments like - # /opt/cuda/lib64/libcublas.so - # back into - # -lcublas - # since this is how CUDA modules were passed to nvcc since time immemorial - if language == 'cuda': - if lib in frozenset(['cudart', 'cudart_static']): - # nvcc always links these unconditionally - mlog.debug(f'Not adding \'{lib}\' to dependency, since nvcc will link it implicitly') - link_args = [] - elif link_args and link_args[0].startswith(self.libdir): - # module included with CUDA, nvcc knows how to find these itself - mlog.debug(f'CUDA module \'{lib}\' found in CUDA libdir') - link_args = ['-l' + lib] + if language == 'cuda' and lib in REWRITE_MODULES: + link_args = REWRITE_MODULES[lib] + mlog.debug(f'Rewriting module \'{lib}\' to \'{link_args}\'') + elif lib == 'cudart_static': + machine = self.env.machines[self.for_machine] + if machine.is_linux(): + # extracted by running + # nvcc -v foo.o + link_args += ['-lrt', '-lpthread', '-ldl'] + args += link_args return args diff --git a/mesonbuild/dependencies/detect.py b/mesonbuild/dependencies/detect.py index aa62c66..f00075b 100644 --- a/mesonbuild/dependencies/detect.py +++ b/mesonbuild/dependencies/detect.py @@ -4,6 +4,7 @@ from __future__ import annotations import collections, functools, importlib +import enum import typing as T from .base import ExternalDependency, DependencyException, DependencyMethods, NotFoundDependency @@ -14,8 +15,9 @@ from .. import mlog if T.TYPE_CHECKING: from ..environment import Environment from .factory import DependencyFactory, WrappedFactoryFunc, DependencyGenerator + from .base import DependencyObjectKWs - TV_DepIDEntry = T.Union[str, bool, int, T.Tuple[str, ...]] + TV_DepIDEntry = T.Union[str, bool, int, None, T.Tuple[str, ...]] TV_DepID = T.Tuple[T.Tuple[str, TV_DepIDEntry], ...] PackageTypes = T.Union[T.Type[ExternalDependency], DependencyFactory, WrappedFactoryFunc] @@ -38,12 +40,15 @@ class DependencyPackages(collections.UserDict): packages = DependencyPackages() _packages_accept_language: T.Set[str] = set() -def get_dep_identifier(name: str, kwargs: T.Dict[str, T.Any]) -> 'TV_DepID': +def get_dep_identifier(name: str, kwargs: DependencyObjectKWs) -> 'TV_DepID': identifier: 'TV_DepID' = (('name', name), ) - from ..interpreter import permitted_dependency_kwargs - assert len(permitted_dependency_kwargs) == 19, \ + from ..interpreter.type_checking import DEPENDENCY_KWS + nkwargs = T.cast('DependencyObjectKWs', {k.name: k.default for k in DEPENDENCY_KWS}) + nkwargs.update(kwargs) + + assert len(DEPENDENCY_KWS) == 20, \ 'Extra kwargs have been added to dependency(), please review if it makes sense to handle it here' - for key, value in kwargs.items(): + for key, value in nkwargs.items(): # 'version' is irrelevant for caching; the caller must check version matches # 'native' is handled above with `for_machine` # 'required' is irrelevant for caching; the caller handles it separately @@ -61,8 +66,11 @@ def get_dep_identifier(name: str, kwargs: T.Dict[str, T.Any]) -> 'TV_DepID': for i in value: assert isinstance(i, str), i value = tuple(frozenset(listify(value))) + elif isinstance(value, enum.Enum): + value = value.value + assert isinstance(value, str), 'for mypy' else: - assert isinstance(value, (str, bool, int)), value + assert value is None or isinstance(value, (str, bool, int)), value identifier = (*identifier, (key, value),) return identifier @@ -80,24 +88,17 @@ display_name_map = { 'wxwidgets': 'WxWidgets', } -def find_external_dependency(name: str, env: 'Environment', kwargs: T.Dict[str, object], candidates: T.Optional[T.List['DependencyGenerator']] = None) -> T.Union['ExternalDependency', NotFoundDependency]: +def find_external_dependency(name: str, env: 'Environment', kwargs: DependencyObjectKWs, candidates: T.Optional[T.List['DependencyGenerator']] = None) -> T.Union['ExternalDependency', NotFoundDependency]: assert name required = kwargs.get('required', True) - if not isinstance(required, bool): - raise DependencyException('Keyword "required" must be a boolean.') - if not isinstance(kwargs.get('method', ''), str): - raise DependencyException('Keyword "method" must be a string.') lname = name.lower() - if lname not in _packages_accept_language and 'language' in kwargs: + if lname not in _packages_accept_language and kwargs.get('language') is not None: raise DependencyException(f'{name} dependency does not accept "language" keyword argument') - if not isinstance(kwargs.get('version', ''), (str, list)): - raise DependencyException('Keyword "Version" must be string or list.') # display the dependency name with correct casing display_name = display_name_map.get(lname, lname) - for_machine = MachineChoice.BUILD if kwargs.get('native', False) else MachineChoice.HOST - + for_machine = kwargs.get('native', MachineChoice.HOST) type_text = PerMachine('Build-time', 'Run-time')[for_machine] + ' dependency' # build a list of dependency methods to try @@ -125,7 +126,7 @@ def find_external_dependency(name: str, env: 'Environment', kwargs: T.Dict[str, details = d.log_details() if details: details = '(' + details + ') ' - if 'language' in kwargs: + if kwargs.get('language') is not None: details += 'for ' + d.language + ' ' # if the dependency was found @@ -169,11 +170,7 @@ def find_external_dependency(name: str, env: 'Environment', kwargs: T.Dict[str, def _build_external_dependency_list(name: str, env: 'Environment', for_machine: MachineChoice, - kwargs: T.Dict[str, T.Any]) -> T.List['DependencyGenerator']: - # First check if the method is valid - if 'method' in kwargs and kwargs['method'] not in [e.value for e in DependencyMethods]: - raise DependencyException('method {!r} is invalid'.format(kwargs['method'])) - + kwargs: DependencyObjectKWs) -> T.List['DependencyGenerator']: # Is there a specific dependency detector for this dependency? lname = name.lower() if lname in packages: @@ -192,25 +189,26 @@ def _build_external_dependency_list(name: str, env: 'Environment', for_machine: candidates: T.List['DependencyGenerator'] = [] - if kwargs.get('method', 'auto') == 'auto': + method = kwargs.get('method', DependencyMethods.AUTO) + if method is DependencyMethods.AUTO: # Just use the standard detection methods. - methods = ['pkg-config', 'extraframework', 'cmake'] + methods = [DependencyMethods.PKGCONFIG, DependencyMethods.EXTRAFRAMEWORK, DependencyMethods.CMAKE] else: # If it's explicitly requested, use that detection method (only). - methods = [kwargs['method']] + methods = [method] # Exclusive to when it is explicitly requested - if 'dub' in methods: + if DependencyMethods.DUB in methods: from .dub import DubDependency candidates.append(functools.partial(DubDependency, name, env, kwargs)) # Preferred first candidate for auto. - if 'pkg-config' in methods: + if DependencyMethods.PKGCONFIG in methods: from .pkgconfig import PkgConfigDependency candidates.append(functools.partial(PkgConfigDependency, name, env, kwargs)) # On OSX only, try framework dependency detector. - if 'extraframework' in methods: + if DependencyMethods.EXTRAFRAMEWORK in methods: if env.machines[for_machine].is_darwin(): from .framework import ExtraFrameworkDependency candidates.append(functools.partial(ExtraFrameworkDependency, name, env, kwargs)) @@ -218,7 +216,7 @@ def _build_external_dependency_list(name: str, env: 'Environment', for_machine: # Only use CMake: # - if it's explicitly requested # - as a last resort, since it might not work 100% (see #6113) - if 'cmake' in methods: + if DependencyMethods.CMAKE in methods: from .cmake import CMakeDependency candidates.append(functools.partial(CMakeDependency, name, env, kwargs)) diff --git a/mesonbuild/dependencies/dev.py b/mesonbuild/dependencies/dev.py index 8f0f1ba..4d219a5 100644 --- a/mesonbuild/dependencies/dev.py +++ b/mesonbuild/dependencies/dev.py @@ -15,8 +15,8 @@ import functools from mesonbuild.interpreterbase.decorators import FeatureDeprecated from .. import mesonlib, mlog -from ..environment import get_llvm_tool_names -from ..mesonlib import version_compare, version_compare_many, search_version, stringlistify, extract_as_list +from ..tooldetect import get_llvm_tool_names +from ..mesonlib import version_compare, version_compare_many, search_version from .base import DependencyException, DependencyMethods, detect_compiler, strip_system_includedirs, strip_system_libdirs, SystemDependency, ExternalDependency, DependencyTypeName from .cmake import CMakeDependency from .configtool import ConfigToolDependency @@ -30,14 +30,8 @@ if T.TYPE_CHECKING: from ..environment import Environment from ..compilers import Compiler from ..mesonlib import MachineChoice - from typing_extensions import TypedDict from ..interpreter.type_checking import PkgConfigDefineType - - class JNISystemDependencyKW(TypedDict): - modules: T.List[str] - # FIXME: When dependency() moves to typed Kwargs, this should inherit - # from its TypedDict type. - version: T.Optional[str] + from .base import DependencyObjectKWs def get_shared_library_suffix(environment: 'Environment', for_machine: MachineChoice) -> str: @@ -53,7 +47,7 @@ def get_shared_library_suffix(environment: 'Environment', for_machine: MachineCh class GTestDependencySystem(SystemDependency): - def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]) -> None: + def __init__(self, name: str, environment: 'Environment', kwargs: DependencyObjectKWs) -> None: super().__init__(name, environment, kwargs, language='cpp') self.main = kwargs.get('main', False) @@ -65,8 +59,8 @@ class GTestDependencySystem(SystemDependency): self.detect() def detect(self) -> None: - gtest_detect = self.clib_compiler.find_library("gtest", self.env, []) - gtest_main_detect = self.clib_compiler.find_library("gtest_main", self.env, []) + gtest_detect = self.clib_compiler.find_library("gtest", []) + gtest_main_detect = self.clib_compiler.find_library("gtest_main", []) if gtest_detect and (not self.main or gtest_main_detect): self.is_found = True self.compile_args = [] @@ -110,7 +104,7 @@ class GTestDependencySystem(SystemDependency): class GTestDependencyPC(PkgConfigDependency): - def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]): + def __init__(self, name: str, environment: 'Environment', kwargs: DependencyObjectKWs): assert name == 'gtest' if kwargs.get('main'): name = 'gtest_main' @@ -118,7 +112,7 @@ class GTestDependencyPC(PkgConfigDependency): class GMockDependencySystem(SystemDependency): - def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]) -> None: + def __init__(self, name: str, environment: 'Environment', kwargs: DependencyObjectKWs) -> None: super().__init__(name, environment, kwargs, language='cpp') self.main = kwargs.get('main', False) if not self._add_sub_dependency(threads_factory(environment, self.for_machine, {})): @@ -141,8 +135,8 @@ class GMockDependencySystem(SystemDependency): # GMock may be a library or just source. # Work with both. - gmock_detect = self.clib_compiler.find_library("gmock", self.env, []) - gmock_main_detect = self.clib_compiler.find_library("gmock_main", self.env, []) + gmock_detect = self.clib_compiler.find_library("gmock", []) + gmock_main_detect = self.clib_compiler.find_library("gmock_main", []) if gmock_detect and (not self.main or gmock_main_detect): self.is_found = True self.link_args += gmock_detect @@ -178,7 +172,7 @@ class GMockDependencySystem(SystemDependency): class GMockDependencyPC(PkgConfigDependency): - def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]): + def __init__(self, name: str, environment: 'Environment', kwargs: DependencyObjectKWs): assert name == 'gmock' if kwargs.get('main'): name = 'gmock_main' @@ -193,14 +187,14 @@ class LLVMDependencyConfigTool(ConfigToolDependency): tool_name = 'llvm-config' __cpp_blacklist = {'-DNDEBUG'} - def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]): + def __init__(self, name: str, environment: 'Environment', kwargs: DependencyObjectKWs): self.tools = get_llvm_tool_names('llvm-config') # Fedora starting with Fedora 30 adds a suffix of the number # of bits in the isa that llvm targets, for example, on x86_64 # and aarch64 the name will be llvm-config-64, on x86 and arm # it will be llvm-config-32. - if environment.machines[self.get_for_machine_from_kwargs(kwargs)].is_64_bit: + if environment.machines[kwargs.get('native', mesonlib.MachineChoice.HOST)].is_64_bit: self.tools.append('llvm-config-64') else: self.tools.append('llvm-config-32') @@ -215,9 +209,9 @@ class LLVMDependencyConfigTool(ConfigToolDependency): return self.provided_modules = self.get_config_value(['--components'], 'modules') - modules = stringlistify(extract_as_list(kwargs, 'modules')) + modules = kwargs.get('modules', []) self.check_components(modules) - opt_modules = stringlistify(extract_as_list(kwargs, 'optional_modules')) + opt_modules = kwargs.get('optional_modules', []) self.check_components(opt_modules, required=False) cargs = mesonlib.OrderedSet(self.get_config_value(['--cppflags'], 'compile_args')) @@ -388,15 +382,12 @@ class LLVMDependencyConfigTool(ConfigToolDependency): return '' class LLVMDependencyCMake(CMakeDependency): - def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]) -> None: - self.llvm_modules = stringlistify(extract_as_list(kwargs, 'modules')) - self.llvm_opt_modules = stringlistify(extract_as_list(kwargs, 'optional_modules')) + def __init__(self, name: str, env: 'Environment', kwargs: DependencyObjectKWs) -> None: + self.llvm_modules = kwargs.get('modules', []) + self.llvm_opt_modules = kwargs.get('optional_modules', []) - compilers = None - if kwargs.get('native', False): - compilers = env.coredata.compilers.build - else: - compilers = env.coredata.compilers.host + for_machine = kwargs.get('native', mesonlib.MachineChoice.HOST) + compilers = env.coredata.compilers[for_machine] if not compilers or not {'c', 'cpp'}.issubset(compilers): # Initialize basic variables ExternalDependency.__init__(self, DependencyTypeName('cmake'), env, kwargs) @@ -515,7 +506,7 @@ class ValgrindDependency(PkgConfigDependency): Consumers of Valgrind usually only need the compile args and do not want to link to its (static) libraries. ''' - def __init__(self, env: 'Environment', kwargs: T.Dict[str, T.Any]): + def __init__(self, env: 'Environment', kwargs: DependencyObjectKWs): super().__init__('valgrind', env, kwargs) def get_link_args(self, language: T.Optional[str] = None, raw: bool = False) -> T.List[str]: @@ -526,7 +517,7 @@ packages['valgrind'] = ValgrindDependency class ZlibSystemDependency(SystemDependency): - def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]): + def __init__(self, name: str, environment: 'Environment', kwargs: DependencyObjectKWs): super().__init__(name, environment, kwargs) from ..compilers.c import AppleClangCCompiler from ..compilers.cpp import AppleClangCPPCompiler @@ -549,8 +540,8 @@ class ZlibSystemDependency(SystemDependency): else: libs = ['z'] for lib in libs: - l = self.clib_compiler.find_library(lib, environment, [], self.libtype) - h = self.clib_compiler.has_header('zlib.h', '', environment, dependencies=[self]) + l = self.clib_compiler.find_library(lib, [], self.libtype) + h = self.clib_compiler.has_header('zlib.h', '', dependencies=[self]) if l and h[0]: self.is_found = True self.link_args = l @@ -558,13 +549,13 @@ class ZlibSystemDependency(SystemDependency): else: return - v, _ = self.clib_compiler.get_define('ZLIB_VERSION', '#include <zlib.h>', self.env, [], [self]) + v, _ = self.clib_compiler.get_define('ZLIB_VERSION', '#include <zlib.h>', [], [self]) self.version = v.strip('"') class JNISystemDependency(SystemDependency): - def __init__(self, environment: 'Environment', kwargs: JNISystemDependencyKW): - super().__init__('jni', environment, T.cast('T.Dict[str, T.Any]', kwargs)) + def __init__(self, environment: 'Environment', kwargs: DependencyObjectKWs): + super().__init__('jni', environment, kwargs) self.feature_since = ('0.62.0', '') @@ -575,7 +566,7 @@ class JNISystemDependency(SystemDependency): self.javac = environment.coredata.compilers[self.for_machine]['java'] self.version = self.javac.version - modules: T.List[str] = mesonlib.listify(kwargs.get('modules', [])) + modules = kwargs.get('modules', []) for module in modules: if module not in {'jvm', 'awt'}: msg = f'Unknown JNI module ({module})' @@ -586,7 +577,7 @@ class JNISystemDependency(SystemDependency): self.is_found = False return - if 'version' in kwargs and not version_compare_many(self.version, kwargs['version'])[0]: + if kwargs.get('version') and not version_compare_many(self.version, kwargs['version'])[0]: mlog.error(f'Incorrect JDK version found ({self.version}), wanted {kwargs["version"]}') self.is_found = False return @@ -632,14 +623,14 @@ class JNISystemDependency(SystemDependency): java_home_lib_server = java_home_lib / 'server' if 'jvm' in modules: - jvm = self.clib_compiler.find_library('jvm', environment, extra_dirs=[str(java_home_lib_server)]) + jvm = self.clib_compiler.find_library('jvm', extra_dirs=[str(java_home_lib_server)]) if jvm is None: mlog.debug('jvm library not found.') self.is_found = False else: self.link_args.extend(jvm) if 'awt' in modules: - jawt = self.clib_compiler.find_library('jawt', environment, extra_dirs=[str(java_home_lib)]) + jawt = self.clib_compiler.find_library('jawt', extra_dirs=[str(java_home_lib)]) if jawt is None: mlog.debug('jawt library not found.') self.is_found = False @@ -691,7 +682,7 @@ packages['jni'] = JNISystemDependency class JDKSystemDependency(JNISystemDependency): - def __init__(self, environment: 'Environment', kwargs: JNISystemDependencyKW): + def __init__(self, environment: 'Environment', kwargs: DependencyObjectKWs): super().__init__(environment, kwargs) self.feature_since = ('0.59.0', '') @@ -754,10 +745,10 @@ class DiaSDKSystemDependency(SystemDependency): # Check if compiler has a built-in macro defined @staticmethod def _has_define(compiler: 'Compiler', dname: str, env: 'Environment') -> bool: - defval, _ = compiler.get_define(dname, '', env, [], []) + defval, _ = compiler.get_define(dname, '', [], []) return defval is not None - def __init__(self, environment: 'Environment', kwargs: T.Dict[str, T.Any]) -> None: + def __init__(self, environment: 'Environment', kwargs: DependencyObjectKWs) -> None: super().__init__('diasdk', environment, kwargs) self.is_found = False diff --git a/mesonbuild/dependencies/dub.py b/mesonbuild/dependencies/dub.py index ac137e3..2166a95 100644 --- a/mesonbuild/dependencies/dub.py +++ b/mesonbuild/dependencies/dub.py @@ -19,6 +19,7 @@ if T.TYPE_CHECKING: from typing_extensions import TypedDict from ..environment import Environment + from .base import DependencyObjectKWs # Definition of what `dub describe` returns (only the fields used by Meson) class DubDescription(TypedDict): @@ -74,7 +75,7 @@ class DubDependency(ExternalDependency): 'llvm': 'ldc', } - def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]): + def __init__(self, name: str, environment: 'Environment', kwargs: DependencyObjectKWs): super().__init__(DependencyTypeName('dub'), environment, kwargs, language='d') self.name = name from ..compilers.d import DCompiler, d_feature_args @@ -83,8 +84,8 @@ class DubDependency(ExternalDependency): assert isinstance(_temp_comp, DCompiler) self.compiler = _temp_comp - if 'required' in kwargs: - self.required = kwargs.get('required') + if kwargs.get('required') is not None: + self.required = kwargs['required'] if DubDependency.class_dubbin is None and not DubDependency.class_dubbin_searched: DubDependency.class_dubbin = self._check_dub() diff --git a/mesonbuild/dependencies/factory.py b/mesonbuild/dependencies/factory.py index aac09ca..0c4ca81 100644 --- a/mesonbuild/dependencies/factory.py +++ b/mesonbuild/dependencies/factory.py @@ -1,6 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright 2013-2021 The Meson development team -# Copyright © 2021-2023 Intel Corporation +# Copyright © 2021-2025 Intel Corporation from __future__ import annotations @@ -15,7 +15,7 @@ from .framework import ExtraFrameworkDependency from .pkgconfig import PkgConfigDependency if T.TYPE_CHECKING: - from .base import ExternalDependency + from .base import DependencyObjectKWs, ExternalDependency from .configtool import ConfigToolDependency from ..environment import Environment from ..mesonlib import MachineChoice @@ -25,7 +25,7 @@ if T.TYPE_CHECKING: [ 'Environment', MachineChoice, - T.Dict[str, T.Any], + DependencyObjectKWs, T.List[DependencyMethods] ], T.List[DependencyGenerator] @@ -35,7 +35,7 @@ if T.TYPE_CHECKING: [ 'Environment', MachineChoice, - T.Dict[str, T.Any] + DependencyObjectKWs, ], T.List[DependencyGenerator] ] @@ -68,7 +68,7 @@ class DependencyFactory: """ def __init__(self, name: str, methods: T.List[DependencyMethods], *, - extra_kwargs: T.Optional[T.Dict[str, T.Any]] = None, + extra_kwargs: T.Optional[DependencyObjectKWs] = None, pkgconfig_name: T.Optional[str] = None, pkgconfig_class: 'T.Type[PkgConfigDependency]' = PkgConfigDependency, cmake_name: T.Optional[str] = None, @@ -86,7 +86,7 @@ class DependencyFactory: self.methods = methods self.classes: T.Dict[ DependencyMethods, - T.Callable[['Environment', T.Dict[str, T.Any]], ExternalDependency] + T.Callable[['Environment', DependencyObjectKWs], ExternalDependency] ] = { # Just attach the correct name right now, either the generic name # or the method specific name. @@ -116,7 +116,7 @@ class DependencyFactory: return True def __call__(self, env: 'Environment', for_machine: MachineChoice, - kwargs: T.Dict[str, T.Any]) -> T.List['DependencyGenerator']: + kwargs: DependencyObjectKWs) -> T.List['DependencyGenerator']: """Return a list of Dependencies with the arguments already attached.""" methods = process_method_kw(self.methods, kwargs) nwargs = self.extra_kwargs.copy() @@ -131,14 +131,14 @@ def factory_methods(methods: T.Set[DependencyMethods]) -> T.Callable[['FactoryFu This helps to make factory functions self documenting >>> @factory_methods([DependencyMethods.PKGCONFIG, DependencyMethods.CMAKE]) - >>> def factory(env: Environment, for_machine: MachineChoice, kwargs: T.Dict[str, T.Any], methods: T.List[DependencyMethods]) -> T.List['DependencyGenerator']: + >>> def factory(env: Environment, for_machine: MachineChoice, kwargs: DependencyObjectKWs, methods: T.List[DependencyMethods]) -> T.List['DependencyGenerator']: >>> pass """ def inner(func: 'FactoryFunc') -> 'WrappedFactoryFunc': @functools.wraps(func) - def wrapped(env: 'Environment', for_machine: MachineChoice, kwargs: T.Dict[str, T.Any]) -> T.List['DependencyGenerator']: + def wrapped(env: 'Environment', for_machine: MachineChoice, kwargs: DependencyObjectKWs) -> T.List['DependencyGenerator']: return func(env, for_machine, kwargs, process_method_kw(methods, kwargs)) return wrapped diff --git a/mesonbuild/dependencies/framework.py b/mesonbuild/dependencies/framework.py index 1fbd628..a23b4a6 100644 --- a/mesonbuild/dependencies/framework.py +++ b/mesonbuild/dependencies/framework.py @@ -11,11 +11,12 @@ import typing as T if T.TYPE_CHECKING: from ..environment import Environment + from .base import DependencyObjectKWs class ExtraFrameworkDependency(ExternalDependency): system_framework_paths: T.Optional[T.List[str]] = None - def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any], language: T.Optional[str] = None) -> None: + def __init__(self, name: str, env: 'Environment', kwargs: DependencyObjectKWs, language: T.Optional[str] = None) -> None: paths = stringlistify(kwargs.get('paths', [])) super().__init__(DependencyTypeName('extraframeworks'), env, kwargs, language=language) self.name = name @@ -25,7 +26,7 @@ class ExtraFrameworkDependency(ExternalDependency): raise DependencyException('No C-like compilers are available') if self.system_framework_paths is None: try: - self.system_framework_paths = self.clib_compiler.find_framework_paths(self.env) + self.system_framework_paths = self.clib_compiler.find_framework_paths() except MesonException as e: if 'non-clang' in str(e): # Apple frameworks can only be found (and used) with the @@ -55,7 +56,7 @@ class ExtraFrameworkDependency(ExternalDependency): # Python.framework. We need to know for sure that the framework was # found in the path we expect. allow_system = p in self.system_framework_paths - args = self.clib_compiler.find_framework(framework_name, self.env, [p], allow_system) + args = self.clib_compiler.find_framework(framework_name, [p], allow_system) if args is None: continue self.link_args = args diff --git a/mesonbuild/dependencies/hdf5.py b/mesonbuild/dependencies/hdf5.py index 7595c7c..5894cfb 100644 --- a/mesonbuild/dependencies/hdf5.py +++ b/mesonbuild/dependencies/hdf5.py @@ -9,7 +9,7 @@ import os import re from pathlib import Path -from ..mesonlib import OrderedSet, join_args +from ..mesonlib import OrderedSet, join_args, MachineChoice from .base import DependencyException, DependencyMethods from .configtool import ConfigToolDependency from .detect import packages @@ -20,14 +20,14 @@ import typing as T if T.TYPE_CHECKING: from .factory import DependencyGenerator from ..environment import Environment - from ..mesonlib import MachineChoice + from .base import DependencyObjectKWs class HDF5PkgConfigDependency(PkgConfigDependency): """Handle brokenness in the HDF5 pkg-config files.""" - def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any], language: T.Optional[str] = None) -> None: + def __init__(self, name: str, environment: 'Environment', kwargs: DependencyObjectKWs, language: T.Optional[str] = None) -> None: language = language or 'c' if language not in {'c', 'cpp', 'fortran'}: raise DependencyException(f'Language {language} is not supported with HDF5.') @@ -78,7 +78,7 @@ class HDF5ConfigToolDependency(ConfigToolDependency): version_arg = '-showconfig' - def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any], language: T.Optional[str] = None) -> None: + def __init__(self, name: str, environment: 'Environment', kwargs: DependencyObjectKWs, language: T.Optional[str] = None) -> None: language = language or 'c' if language not in {'c', 'cpp', 'fortran'}: raise DependencyException(f'Language {language} is not supported with HDF5.') @@ -99,7 +99,7 @@ class HDF5ConfigToolDependency(ConfigToolDependency): raise DependencyException('How did you get here?') # We need this before we call super() - for_machine = self.get_for_machine_from_kwargs(kwargs) + for_machine = kwargs.get('native', MachineChoice.HOST) nkwargs = kwargs.copy() nkwargs['tools'] = tools @@ -144,7 +144,7 @@ class HDF5ConfigToolDependency(ConfigToolDependency): @factory_methods({DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL}) def hdf5_factory(env: 'Environment', for_machine: 'MachineChoice', - kwargs: T.Dict[str, T.Any], methods: T.List[DependencyMethods]) -> T.List['DependencyGenerator']: + kwargs: DependencyObjectKWs, methods: T.List[DependencyMethods]) -> T.List['DependencyGenerator']: language = kwargs.get('language') candidates: T.List['DependencyGenerator'] = [] diff --git a/mesonbuild/dependencies/misc.py b/mesonbuild/dependencies/misc.py index 3ab2194..b1b8b8e 100644 --- a/mesonbuild/dependencies/misc.py +++ b/mesonbuild/dependencies/misc.py @@ -21,13 +21,14 @@ from ..options import OptionKey if T.TYPE_CHECKING: from ..environment import Environment + from .base import DependencyObjectKWs from .factory import DependencyGenerator @factory_methods({DependencyMethods.PKGCONFIG, DependencyMethods.CMAKE}) def netcdf_factory(env: 'Environment', for_machine: 'mesonlib.MachineChoice', - kwargs: T.Dict[str, T.Any], + kwargs: DependencyObjectKWs, methods: T.List[DependencyMethods]) -> T.List['DependencyGenerator']: language = kwargs.get('language') if language is None: @@ -54,42 +55,42 @@ packages['netcdf'] = netcdf_factory class AtomicBuiltinDependency(BuiltinDependency): - def __init__(self, name: str, env: Environment, kwargs: T.Dict[str, T.Any]): + def __init__(self, name: str, env: Environment, kwargs: DependencyObjectKWs): super().__init__(name, env, kwargs) self.feature_since = ('1.7.0', "consider checking for `atomic_flag_clear` with and without `find_library('atomic')`") - if self.clib_compiler.has_function('atomic_flag_clear', '#include <stdatomic.h>', env)[0]: + if self.clib_compiler.has_function('atomic_flag_clear', '#include <stdatomic.h>')[0]: self.is_found = True class AtomicSystemDependency(SystemDependency): - def __init__(self, name: str, env: Environment, kwargs: T.Dict[str, T.Any]): + def __init__(self, name: str, env: Environment, kwargs: DependencyObjectKWs): super().__init__(name, env, kwargs) self.feature_since = ('1.7.0', "consider checking for `atomic_flag_clear` with and without `find_library('atomic')`") - h = self.clib_compiler.has_header('stdatomic.h', '', env) - self.link_args = self.clib_compiler.find_library('atomic', env, [], self.libtype) + h = self.clib_compiler.has_header('stdatomic.h', '') + self.link_args = self.clib_compiler.find_library('atomic', [], self.libtype) if h[0] and self.link_args: self.is_found = True class DlBuiltinDependency(BuiltinDependency): - def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]): + def __init__(self, name: str, env: 'Environment', kwargs: DependencyObjectKWs): super().__init__(name, env, kwargs) self.feature_since = ('0.62.0', "consider checking for `dlopen` with and without `find_library('dl')`") - if self.clib_compiler.has_function('dlopen', '#include <dlfcn.h>', env)[0]: + if self.clib_compiler.has_function('dlopen', '#include <dlfcn.h>')[0]: self.is_found = True class DlSystemDependency(SystemDependency): - def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]): + def __init__(self, name: str, env: 'Environment', kwargs: DependencyObjectKWs): super().__init__(name, env, kwargs) self.feature_since = ('0.62.0', "consider checking for `dlopen` with and without `find_library('dl')`") - h = self.clib_compiler.has_header('dlfcn.h', '', env) - self.link_args = self.clib_compiler.find_library('dl', env, [], self.libtype) + h = self.clib_compiler.has_header('dlfcn.h', '') + self.link_args = self.clib_compiler.find_library('dl', [], self.libtype) if h[0] and self.link_args: self.is_found = True @@ -98,6 +99,7 @@ class DlSystemDependency(SystemDependency): class OpenMPDependency(SystemDependency): # Map date of specification release (which is the macro value) to a version. VERSIONS = { + '202411': '6.0', '202111': '5.2', '202011': '5.1', '201811': '5.0', @@ -111,7 +113,7 @@ class OpenMPDependency(SystemDependency): '199810': '1.0', } - def __init__(self, environment: 'Environment', kwargs: T.Dict[str, T.Any]) -> None: + def __init__(self, environment: 'Environment', kwargs: DependencyObjectKWs) -> None: language = kwargs.get('language') super().__init__('openmp', environment, kwargs, language=language) self.is_found = False @@ -119,26 +121,26 @@ class OpenMPDependency(SystemDependency): # No macro defined for OpenMP, but OpenMP 3.1 is supported. self.version = '3.1' self.is_found = True - self.compile_args = self.link_args = self.clib_compiler.openmp_flags(environment) + self.compile_args = self.link_args = self.clib_compiler.openmp_flags() return if self.clib_compiler.get_id() == 'pgi': # through at least PGI 19.4, there is no macro defined for OpenMP, but OpenMP 3.1 is supported. self.version = '3.1' self.is_found = True - self.compile_args = self.link_args = self.clib_compiler.openmp_flags(environment) + self.compile_args = self.link_args = self.clib_compiler.openmp_flags() return # Set these now so they're available for the following compiler checks try: - self.compile_args.extend(self.clib_compiler.openmp_flags(environment)) - self.link_args.extend(self.clib_compiler.openmp_link_flags(environment)) + self.compile_args.extend(self.clib_compiler.openmp_flags()) + self.link_args.extend(self.clib_compiler.openmp_link_flags()) except mesonlib.MesonException as e: mlog.warning('OpenMP support not available because:', str(e), fatal=False) return try: openmp_date = self.clib_compiler.get_define( - '_OPENMP', '', self.env, [], [self], disable_cache=True)[0] + '_OPENMP', '', [], [self], disable_cache=True)[0] except mesonlib.EnvironmentException as e: mlog.debug('OpenMP support not available in the compiler') mlog.debug(e) @@ -155,7 +157,7 @@ class OpenMPDependency(SystemDependency): # Flang has omp_lib.h header_names = ('omp.h', 'omp_lib.h') for name in header_names: - if self.clib_compiler.has_header(name, '', self.env, dependencies=[self], disable_cache=True)[0]: + if self.clib_compiler.has_header(name, '', dependencies=[self], disable_cache=True)[0]: self.is_found = True break else: @@ -165,7 +167,7 @@ packages['openmp'] = OpenMPDependency class ThreadDependency(SystemDependency): - def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]) -> None: + def __init__(self, name: str, environment: 'Environment', kwargs: DependencyObjectKWs) -> None: super().__init__(name, environment, kwargs) self.is_found = True # Happens if you are using a language with threads @@ -174,12 +176,12 @@ class ThreadDependency(SystemDependency): self.compile_args = [] self.link_args = [] else: - self.compile_args = self.clib_compiler.thread_flags(environment) - self.link_args = self.clib_compiler.thread_link_flags(environment) + self.compile_args = self.clib_compiler.thread_flags() + self.link_args = self.clib_compiler.thread_link_flags() class BlocksDependency(SystemDependency): - def __init__(self, environment: 'Environment', kwargs: T.Dict[str, T.Any]) -> None: + def __init__(self, environment: 'Environment', kwargs: DependencyObjectKWs) -> None: super().__init__('blocks', environment, kwargs) self.name = 'blocks' self.is_found = False @@ -191,8 +193,8 @@ class BlocksDependency(SystemDependency): self.compile_args = ['-fblocks'] self.link_args = ['-lBlocksRuntime'] - if not self.clib_compiler.has_header('Block.h', '', environment, disable_cache=True) or \ - not self.clib_compiler.find_library('BlocksRuntime', environment, []): + if not self.clib_compiler.has_header('Block.h', '', disable_cache=True) or \ + not self.clib_compiler.find_library('BlocksRuntime', []): mlog.log(mlog.red('ERROR:'), 'BlocksRuntime not found.') return @@ -222,7 +224,7 @@ class PcapDependencyConfigTool(ConfigToolDependency): # version 1.10.3 will hopefully add actual support for --version skip_version = '--help' - def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]): + def __init__(self, name: str, environment: 'Environment', kwargs: DependencyObjectKWs): super().__init__(name, environment, kwargs) if not self.is_found: return @@ -240,7 +242,7 @@ class PcapDependencyConfigTool(ConfigToolDependency): return None v = self.clib_compiler.get_return_value('pcap_lib_version', 'string', - '#include <pcap.h>', self.env, [], [self]) + '#include <pcap.h>', [], [self]) v = re.sub(r'libpcap version ', '', str(v)) v = re.sub(r' -- Apple version.*$', '', v) return v @@ -251,7 +253,7 @@ class CupsDependencyConfigTool(ConfigToolDependency): tools = ['cups-config'] tool_name = 'cups-config' - def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]): + def __init__(self, name: str, environment: 'Environment', kwargs: DependencyObjectKWs): super().__init__(name, environment, kwargs) if not self.is_found: return @@ -264,7 +266,7 @@ class LibWmfDependencyConfigTool(ConfigToolDependency): tools = ['libwmf-config'] tool_name = 'libwmf-config' - def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]): + def __init__(self, name: str, environment: 'Environment', kwargs: DependencyObjectKWs): super().__init__(name, environment, kwargs) if not self.is_found: return @@ -277,7 +279,7 @@ class LibGCryptDependencyConfigTool(ConfigToolDependency): tools = ['libgcrypt-config'] tool_name = 'libgcrypt-config' - def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]): + def __init__(self, name: str, environment: 'Environment', kwargs: DependencyObjectKWs): super().__init__(name, environment, kwargs) if not self.is_found: return @@ -291,7 +293,7 @@ class GpgmeDependencyConfigTool(ConfigToolDependency): tools = ['gpgme-config'] tool_name = 'gpg-config' - def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]): + def __init__(self, name: str, environment: 'Environment', kwargs: DependencyObjectKWs): super().__init__(name, environment, kwargs) if not self.is_found: return @@ -302,7 +304,7 @@ class GpgmeDependencyConfigTool(ConfigToolDependency): class ShadercDependency(SystemDependency): - def __init__(self, environment: 'Environment', kwargs: T.Dict[str, T.Any]): + def __init__(self, environment: 'Environment', kwargs: DependencyObjectKWs): super().__init__('shaderc', environment, kwargs) static_lib = 'shaderc_combined' @@ -315,7 +317,7 @@ class ShadercDependency(SystemDependency): cc = self.get_compiler() for lib in libs: - self.link_args = cc.find_library(lib, environment, []) + self.link_args = cc.find_library(lib, []) if self.link_args is not None: self.is_found = True @@ -334,7 +336,7 @@ class CursesConfigToolDependency(ConfigToolDependency): # ncurses5.4-config is for macOS Catalina tools = ['ncursesw6-config', 'ncursesw5-config', 'ncurses6-config', 'ncurses5-config', 'ncurses5.4-config'] - def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any], language: T.Optional[str] = None): + def __init__(self, name: str, env: 'Environment', kwargs: DependencyObjectKWs, language: T.Optional[str] = None): exclude_paths = None # macOS mistakenly ships /usr/bin/ncurses5.4-config and a man page for # it, but none of the headers or libraries. Ignore /usr/bin because it @@ -358,7 +360,7 @@ class CursesSystemDependency(SystemDependency): implementations, and the differences between them can be very annoying. """ - def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]): + def __init__(self, name: str, env: 'Environment', kwargs: DependencyObjectKWs): super().__init__(name, env, kwargs) candidates = [ @@ -370,10 +372,10 @@ class CursesSystemDependency(SystemDependency): # Not sure how else to elegantly break out of both loops for lib, headers in candidates: - l = self.clib_compiler.find_library(lib, env, []) + l = self.clib_compiler.find_library(lib, []) if l: for header in headers: - h = self.clib_compiler.has_header(header, '', env) + h = self.clib_compiler.has_header(header, '') if h[0]: self.is_found = True self.link_args = l @@ -381,15 +383,15 @@ class CursesSystemDependency(SystemDependency): # implementations. The one in illumos/OpenIndiana # doesn't seem to have a version defined in the header. if lib.startswith('ncurses'): - v, _ = self.clib_compiler.get_define('NCURSES_VERSION', f'#include <{header}>', env, [], [self]) + v, _ = self.clib_compiler.get_define('NCURSES_VERSION', f'#include <{header}>', [], [self]) self.version = v.strip('"') if lib.startswith('pdcurses'): - v_major, _ = self.clib_compiler.get_define('PDC_VER_MAJOR', f'#include <{header}>', env, [], [self]) - v_minor, _ = self.clib_compiler.get_define('PDC_VER_MINOR', f'#include <{header}>', env, [], [self]) + v_major, _ = self.clib_compiler.get_define('PDC_VER_MAJOR', f'#include <{header}>', [], [self]) + v_minor, _ = self.clib_compiler.get_define('PDC_VER_MINOR', f'#include <{header}>', [], [self]) self.version = f'{v_major}.{v_minor}' # Check the version if possible, emit a warning if we can't - req = kwargs.get('version') + req = kwargs.get('version', []) if req: if self.version: self.is_found, *_ = mesonlib.version_compare_many(self.version, req) @@ -405,44 +407,44 @@ class CursesSystemDependency(SystemDependency): class IconvBuiltinDependency(BuiltinDependency): - def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]): + def __init__(self, name: str, env: 'Environment', kwargs: DependencyObjectKWs): super().__init__(name, env, kwargs) self.feature_since = ('0.60.0', "consider checking for `iconv_open` with and without `find_library('iconv')`") code = '''#include <iconv.h>\n\nint main() {\n iconv_open("","");\n}''' # [ignore encoding] this is C, not python, Mr. Lint - if self.clib_compiler.links(code, env)[0]: + if self.clib_compiler.links(code)[0]: self.is_found = True class IconvSystemDependency(SystemDependency): - def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]): + def __init__(self, name: str, env: 'Environment', kwargs: DependencyObjectKWs): super().__init__(name, env, kwargs) self.feature_since = ('0.60.0', "consider checking for `iconv_open` with and without find_library('iconv')") - h = self.clib_compiler.has_header('iconv.h', '', env) - self.link_args = self.clib_compiler.find_library('iconv', env, [], self.libtype) + h = self.clib_compiler.has_header('iconv.h', '') + self.link_args = self.clib_compiler.find_library('iconv', [], self.libtype) if h[0] and self.link_args: self.is_found = True class IntlBuiltinDependency(BuiltinDependency): - def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]): + def __init__(self, name: str, env: 'Environment', kwargs: DependencyObjectKWs): super().__init__(name, env, kwargs) self.feature_since = ('0.59.0', "consider checking for `ngettext` with and without `find_library('intl')`") code = '''#include <libintl.h>\n\nint main() {\n gettext("Hello world");\n}''' - if self.clib_compiler.links(code, env)[0]: + if self.clib_compiler.links(code)[0]: self.is_found = True class IntlSystemDependency(SystemDependency): - def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]): + def __init__(self, name: str, env: 'Environment', kwargs: DependencyObjectKWs): super().__init__(name, env, kwargs) self.feature_since = ('0.59.0', "consider checking for `ngettext` with and without `find_library('intl')`") - h = self.clib_compiler.has_header('libintl.h', '', env) - self.link_args = self.clib_compiler.find_library('intl', env, [], self.libtype) + h = self.clib_compiler.has_header('libintl.h', '') + self.link_args = self.clib_compiler.find_library('intl', [], self.libtype) if h[0] and self.link_args: self.is_found = True @@ -453,21 +455,21 @@ class IntlSystemDependency(SystemDependency): class OpensslSystemDependency(SystemDependency): - def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]): + def __init__(self, name: str, env: 'Environment', kwargs: DependencyObjectKWs): super().__init__(name, env, kwargs) - dependency_kwargs = { - 'method': 'system', + dependency_kwargs: DependencyObjectKWs = { + 'method': DependencyMethods.SYSTEM, 'static': self.static, } - if not self.clib_compiler.has_header('openssl/ssl.h', '', env)[0]: + if not self.clib_compiler.has_header('openssl/ssl.h', '')[0]: return # openssl >= 3 only - self.version = self.clib_compiler.get_define('OPENSSL_VERSION_STR', '#include <openssl/opensslv.h>', env, [], [self])[0] + self.version = self.clib_compiler.get_define('OPENSSL_VERSION_STR', '#include <openssl/opensslv.h>', [], [self])[0] # openssl < 3 only if not self.version: - version_hex = self.clib_compiler.get_define('OPENSSL_VERSION_NUMBER', '#include <openssl/opensslv.h>', env, [], [self])[0] + version_hex = self.clib_compiler.get_define('OPENSSL_VERSION_NUMBER', '#include <openssl/opensslv.h>', [], [self])[0] if not version_hex: return version_hex = version_hex.rstrip('L') @@ -481,7 +483,7 @@ class OpensslSystemDependency(SystemDependency): self.is_found = True return else: - self.link_args = self.clib_compiler.find_library(name.lstrip('lib'), env, [], self.libtype) + self.link_args = self.clib_compiler.find_library(name.lstrip('lib'), [], self.libtype) if not self.link_args: return @@ -492,11 +494,11 @@ class OpensslSystemDependency(SystemDependency): if self._add_sub_dependency(libcrypto_factory(env, self.for_machine, dependency_kwargs)): self.is_found = True elif name == 'libcrypto': - use_threads = self.clib_compiler.has_header_symbol('openssl/opensslconf.h', 'OPENSSL_THREADS', '', env, dependencies=[self])[0] + use_threads = self.clib_compiler.has_header_symbol('openssl/opensslconf.h', 'OPENSSL_THREADS', '', dependencies=[self])[0] if not use_threads or self._add_sub_dependency(threads_factory(env, self.for_machine, {})): self.is_found = True # only relevant on platforms where it is distributed with the libc, in which case it always succeeds - sublib = self.clib_compiler.find_library('dl', env, [], self.libtype) + sublib = self.clib_compiler.find_library('dl', [], self.libtype) if sublib: self.link_args.extend(sublib) @@ -506,7 +508,7 @@ class ObjFWDependency(ConfigToolDependency): tools = ['objfw-config'] tool_name = 'objfw-config' - def __init__(self, environment: 'Environment', kwargs: T.Dict[str, T.Any]): + def __init__(self, environment: 'Environment', kwargs: DependencyObjectKWs): super().__init__('objfw', environment, kwargs) self.feature_since = ('1.5.0', '') if not self.is_found: @@ -516,7 +518,7 @@ class ObjFWDependency(ConfigToolDependency): # TODO: Expose --framework-libs extra_flags = [] - for module in mesonlib.stringlistify(mesonlib.extract_as_list(kwargs, 'modules')): + for module in kwargs.get('modules', []): extra_flags.append('--package') extra_flags.append(module) @@ -528,7 +530,7 @@ class ObjFWDependency(ConfigToolDependency): @factory_methods({DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL, DependencyMethods.SYSTEM}) def curses_factory(env: 'Environment', for_machine: 'mesonlib.MachineChoice', - kwargs: T.Dict[str, T.Any], + kwargs: DependencyObjectKWs, methods: T.List[DependencyMethods]) -> T.List['DependencyGenerator']: candidates: T.List['DependencyGenerator'] = [] @@ -554,7 +556,7 @@ packages['curses'] = curses_factory @factory_methods({DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM}) def shaderc_factory(env: 'Environment', for_machine: 'mesonlib.MachineChoice', - kwargs: T.Dict[str, T.Any], + kwargs: DependencyObjectKWs, methods: T.List[DependencyMethods]) -> T.List['DependencyGenerator']: """Custom DependencyFactory for ShaderC. @@ -572,7 +574,10 @@ def shaderc_factory(env: 'Environment', shared_libs = ['shaderc'] static_libs = ['shaderc_combined', 'shaderc_static'] - if kwargs.get('static', env.coredata.optstore.get_value_for(OptionKey('prefer_static'))): + static = kwargs.get('static') + if static is None: + static = T.cast('bool', env.coredata.optstore.get_value_for(OptionKey('prefer_static'))) + if static: c = [functools.partial(PkgConfigDependency, name, env, kwargs) for name in static_libs + shared_libs] else: diff --git a/mesonbuild/dependencies/mpi.py b/mesonbuild/dependencies/mpi.py index a259972..1eae1a4 100644 --- a/mesonbuild/dependencies/mpi.py +++ b/mesonbuild/dependencies/mpi.py @@ -8,7 +8,7 @@ import typing as T import os import re -from ..environment import detect_cpu_family +from ..envconfig import detect_cpu_family from ..mesonlib import Popen_safe from .base import DependencyException, DependencyMethods, detect_compiler, SystemDependency from .configtool import ConfigToolDependency @@ -20,12 +20,13 @@ if T.TYPE_CHECKING: from .factory import DependencyGenerator from ..environment import Environment from ..mesonlib import MachineChoice + from .base import DependencyObjectKWs @factory_methods({DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL, DependencyMethods.SYSTEM}) def mpi_factory(env: 'Environment', for_machine: 'MachineChoice', - kwargs: T.Dict[str, T.Any], + kwargs: DependencyObjectKWs, methods: T.List[DependencyMethods]) -> T.List['DependencyGenerator']: language = kwargs.get('language') if language is None: @@ -104,7 +105,7 @@ packages['mpi'] = mpi_factory class MPIConfigToolDependency(ConfigToolDependency): """Wrapper around mpicc, Intel's mpiicc and friends.""" - def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any], + def __init__(self, name: str, env: 'Environment', kwargs: DependencyObjectKWs, language: T.Optional[str] = None): super().__init__(name, env, kwargs, language=language) if not self.is_found: @@ -214,7 +215,7 @@ class MSMPIDependency(SystemDependency): """The Microsoft MPI.""" - def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any], + def __init__(self, name: str, env: 'Environment', kwargs: DependencyObjectKWs, language: T.Optional[str] = None): super().__init__(name, env, kwargs, language=language) # MSMPI only supports the C API diff --git a/mesonbuild/dependencies/pkgconfig.py b/mesonbuild/dependencies/pkgconfig.py index 94e0893..b628e00 100644 --- a/mesonbuild/dependencies/pkgconfig.py +++ b/mesonbuild/dependencies/pkgconfig.py @@ -25,12 +25,14 @@ if T.TYPE_CHECKING: from ..environment import Environment from ..utils.core import EnvironOrDict from ..interpreter.type_checking import PkgConfigDefineType + from .base import DependencyObjectKWs class PkgConfigInterface: '''Base class wrapping a pkg-config implementation''' - class_impl: PerMachine[T.Union[Literal[False], T.Optional[PkgConfigInterface]]] = PerMachine(False, False) - class_cli_impl: PerMachine[T.Union[Literal[False], T.Optional[PkgConfigCLI]]] = PerMachine(False, False) + # keyed on machine and extra_paths + class_impl: PerMachine[T.Dict[T.Optional[T.Tuple[str, ...]], T.Union[Literal[False], T.Optional[PkgConfigInterface]]]] = PerMachine({}, {}) + class_cli_impl: PerMachine[T.Dict[T.Optional[T.Tuple[str, ...]], T.Union[Literal[False], T.Optional[PkgConfigCLI]]]] = PerMachine({}, {}) pkg_bin_per_machine: PerMachine[T.Optional[ExternalProgram]] = PerMachine(None, None) @staticmethod @@ -41,21 +43,25 @@ class PkgConfigInterface: PkgConfigInterface.pkg_bin_per_machine[for_machine] = pkg_bin @staticmethod - def instance(env: Environment, for_machine: MachineChoice, silent: bool) -> T.Optional[PkgConfigInterface]: + def instance(env: Environment, for_machine: MachineChoice, silent: bool, + extra_paths: T.Optional[T.List[str]] = None) -> T.Optional[PkgConfigInterface]: '''Return a pkg-config implementation singleton''' for_machine = for_machine if env.is_cross_build() else MachineChoice.HOST - impl = PkgConfigInterface.class_impl[for_machine] + extra_paths_key = tuple(extra_paths) if extra_paths is not None else None + impl = PkgConfigInterface.class_impl[for_machine].get(extra_paths_key, False) if impl is False: - impl = PkgConfigCLI(env, for_machine, silent, PkgConfigInterface.pkg_bin_per_machine[for_machine]) + impl = PkgConfigCLI(env, for_machine, silent, PkgConfigInterface.pkg_bin_per_machine[for_machine], extra_paths) if not impl.found(): impl = None if not impl and not silent: mlog.log('Found pkg-config:', mlog.red('NO')) - PkgConfigInterface.class_impl[for_machine] = impl + PkgConfigInterface.class_impl[for_machine][extra_paths_key] = impl return impl @staticmethod - def _cli(env: Environment, for_machine: MachineChoice, silent: bool = False) -> T.Optional[PkgConfigCLI]: + def _cli(env: Environment, for_machine: MachineChoice, + extra_paths: T.Optional[T.List[str]] = None, + silent: bool = False) -> T.Optional[PkgConfigCLI]: '''Return the CLI pkg-config implementation singleton Even when we use another implementation internally, external tools might still need the CLI implementation. @@ -64,17 +70,19 @@ class PkgConfigInterface: impl: T.Union[Literal[False], T.Optional[PkgConfigInterface]] # Help confused mypy impl = PkgConfigInterface.instance(env, for_machine, silent) if impl and not isinstance(impl, PkgConfigCLI): - impl = PkgConfigInterface.class_cli_impl[for_machine] + extra_paths_key = tuple(extra_paths) if extra_paths is not None else None + impl = PkgConfigInterface.class_cli_impl[for_machine].get(extra_paths_key, False) if impl is False: - impl = PkgConfigCLI(env, for_machine, silent, PkgConfigInterface.pkg_bin_per_machine[for_machine]) + impl = PkgConfigCLI(env, for_machine, silent, PkgConfigInterface.pkg_bin_per_machine[for_machine], extra_paths) if not impl.found(): impl = None - PkgConfigInterface.class_cli_impl[for_machine] = impl + PkgConfigInterface.class_cli_impl[for_machine][extra_paths_key] = impl return T.cast('T.Optional[PkgConfigCLI]', impl) # Trust me, mypy @staticmethod - def get_env(env: Environment, for_machine: MachineChoice, uninstalled: bool = False) -> EnvironmentVariables: - cli = PkgConfigInterface._cli(env, for_machine) + def get_env(env: Environment, for_machine: MachineChoice, uninstalled: bool = False, + extra_paths: T.Optional[T.List[str]] = None) -> EnvironmentVariables: + cli = PkgConfigInterface._cli(env, for_machine, extra_paths) return cli._get_env(uninstalled) if cli else EnvironmentVariables() @staticmethod @@ -123,11 +131,13 @@ class PkgConfigCLI(PkgConfigInterface): '''pkg-config CLI implementation''' def __init__(self, env: Environment, for_machine: MachineChoice, silent: bool, - pkgbin: T.Optional[ExternalProgram] = None) -> None: + pkgbin: T.Optional[ExternalProgram] = None, + extra_paths: T.Optional[T.List[str]] = None) -> None: super().__init__(env, for_machine) self._detect_pkgbin(pkgbin) if self.pkgbin and not silent: mlog.log('Found pkg-config:', mlog.green('YES'), mlog.bold(f'({self.pkgbin.get_path()})'), mlog.blue(self.pkgbin_version)) + self.extra_paths = extra_paths or [] def found(self) -> bool: return bool(self.pkgbin) @@ -258,7 +268,7 @@ class PkgConfigCLI(PkgConfigInterface): key = OptionKey('pkg_config_path', machine=self.for_machine) pathlist = self.env.coredata.optstore.get_value_for(key) assert isinstance(pathlist, list) - extra_paths: T.List[str] = pathlist[:] + extra_paths: T.List[str] = pathlist + self.extra_paths if uninstalled: bpath = self.env.get_build_dir() if bpath is not None: @@ -296,12 +306,14 @@ class PkgConfigCLI(PkgConfigInterface): class PkgConfigDependency(ExternalDependency): - def __init__(self, name: str, environment: Environment, kwargs: T.Dict[str, T.Any], - language: T.Optional[str] = None) -> None: + def __init__(self, name: str, environment: Environment, kwargs: DependencyObjectKWs, + language: T.Optional[str] = None, + extra_paths: T.Optional[T.List[str]] = None) -> None: super().__init__(DependencyTypeName('pkgconfig'), environment, kwargs, language=language) self.name = name self.is_libtool = False - pkgconfig = PkgConfigInterface.instance(self.env, self.for_machine, self.silent) + self.extra_paths = extra_paths or [] + pkgconfig = PkgConfigInterface.instance(self.env, self.for_machine, self.silent, self.extra_paths) if not pkgconfig: msg = f'Pkg-config for machine {self.for_machine} not found. Giving up.' if self.required: @@ -421,7 +433,7 @@ class PkgConfigDependency(ExternalDependency): # # Only prefix_libpaths are reordered here because there should not be # too many system_libpaths to cause library version issues. - pkg_config_path: T.List[str] = self.env.coredata.optstore.get_value(OptionKey('pkg_config_path', machine=self.for_machine)) # type: ignore[assignment] + pkg_config_path: T.List[str] = self.env.coredata.optstore.get_value_for(OptionKey('pkg_config_path', machine=self.for_machine)) # type: ignore[assignment] pkg_config_path = self._convert_mingw_paths(pkg_config_path) prefix_libpaths = OrderedSet(sort_libpaths(list(prefix_libpaths), pkg_config_path)) system_libpaths: OrderedSet[str] = OrderedSet() @@ -476,9 +488,8 @@ class PkgConfigDependency(ExternalDependency): if lib in libs_found: continue if self.clib_compiler: - args = self.clib_compiler.find_library(lib[2:], self.env, - libpaths, self.libtype, - lib_prefix_warning=False) + args = self.clib_compiler.find_library( + lib[2:], libpaths, self.libtype, lib_prefix_warning=False) # If the project only uses a non-clib language such as D, Rust, # C#, Python, etc, all we can do is limp along by adding the # arguments as-is and then adding the libpaths at the end. diff --git a/mesonbuild/dependencies/platform.py b/mesonbuild/dependencies/platform.py index 335faed..49ec980 100644 --- a/mesonbuild/dependencies/platform.py +++ b/mesonbuild/dependencies/platform.py @@ -12,13 +12,12 @@ import typing as T if T.TYPE_CHECKING: from ..environment import Environment + from .base import DependencyObjectKWs class AppleFrameworks(ExternalDependency): - def __init__(self, env: 'Environment', kwargs: T.Dict[str, T.Any]) -> None: + def __init__(self, env: 'Environment', kwargs: DependencyObjectKWs) -> None: super().__init__(DependencyTypeName('appleframeworks'), env, kwargs) modules = kwargs.get('modules', []) - if isinstance(modules, str): - modules = [modules] if not modules: raise DependencyException("AppleFrameworks dependency requires at least one module.") self.frameworks = modules @@ -27,7 +26,7 @@ class AppleFrameworks(ExternalDependency): self.is_found = True for f in self.frameworks: try: - args = self.clib_compiler.find_framework(f, env, []) + args = self.clib_compiler.find_framework(f, []) except MesonException as e: if 'non-clang' in str(e): self.is_found = False diff --git a/mesonbuild/dependencies/python.py b/mesonbuild/dependencies/python.py index 3dab31c..aa2e22c 100644 --- a/mesonbuild/dependencies/python.py +++ b/mesonbuild/dependencies/python.py @@ -1,29 +1,30 @@ -# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: Apache-2. # Copyright 2022 The Meson development team from __future__ import annotations -import functools, json, os, textwrap +import functools, json, operator, os, textwrap from pathlib import Path import typing as T from .. import mesonlib, mlog -from .base import process_method_kw, DependencyException, DependencyMethods, DependencyTypeName, ExternalDependency, SystemDependency +from .base import process_method_kw, DependencyException, DependencyMethods, ExternalDependency, SystemDependency from .configtool import ConfigToolDependency from .detect import packages from .factory import DependencyFactory from .framework import ExtraFrameworkDependency from .pkgconfig import PkgConfigDependency -from ..environment import detect_cpu_family +from ..envconfig import detect_cpu_family from ..programs import ExternalProgram from ..options import OptionKey if T.TYPE_CHECKING: - from typing_extensions import TypedDict + from typing_extensions import Final, TypedDict from .factory import DependencyGenerator from ..environment import Environment from ..mesonlib import MachineChoice + from .base import DependencyObjectKWs class PythonIntrospectionDict(TypedDict): @@ -56,7 +57,7 @@ class Pybind11ConfigToolDependency(ConfigToolDependency): # in the meantime skip_version = '--pkgconfigdir' - def __init__(self, name: str, environment: Environment, kwargs: T.Dict[str, T.Any]): + def __init__(self, name: str, environment: Environment, kwargs: DependencyObjectKWs): super().__init__(name, environment, kwargs) if not self.is_found: return @@ -67,16 +68,101 @@ class NumPyConfigToolDependency(ConfigToolDependency): tools = ['numpy-config'] - def __init__(self, name: str, environment: Environment, kwargs: T.Dict[str, T.Any]): + def __init__(self, name: str, environment: Environment, kwargs: DependencyObjectKWs): super().__init__(name, environment, kwargs) if not self.is_found: return self.compile_args = self.get_config_value(['--cflags'], 'compile_args') +class PythonBuildConfig: + """PEP 739 build-details.json config file.""" + + IMPLEMENTED_VERSION: Final[str] = '1.0' + """Schema version currently implemented.""" + _PATH_KEYS = ( + 'base_interpreter', + 'libpython.dynamic', + 'libpython.dynamic_stableabi', + 'libpython.static', + 'c_api.headers', + 'c_api.pkgconfig_path', + ) + """Path keys — may be relative, need to be expanded.""" + + def __init__(self, path: str) -> None: + self._path = Path(path) + + try: + self._data = json.loads(self._path.read_text(encoding='utf8')) + except OSError as e: + raise DependencyException(f'Failed to read python.build_config: {e}') from e + + self._validate_data() + self._expand_paths() + + def __getitem__(self, key: str) -> T.Any: + return functools.reduce(operator.getitem, key.split('.'), self._data) + + def __contains__(self, key: str) -> bool: + try: + self[key] + except KeyError: + return False + else: + return True + + def get(self, key: str, default: T.Any = None) -> T.Any: + try: + return self[key] + except KeyError: + return default + + def _validate_data(self) -> None: + schema_version = self._data['schema_version'] + if mesonlib.version_compare(schema_version, '< 1.0'): + raise DependencyException(f'Invalid schema_version in python.build_config: {schema_version}') + if mesonlib.version_compare(schema_version, '>= 2.0'): + raise DependencyException( + f'Unsupported schema_version {schema_version!r} in python.build_config, ' + f'but we only implement support for {self.IMPLEMENTED_VERSION!r}' + ) + # Schema version that we currently understand + if mesonlib.version_compare(schema_version, f'> {self.IMPLEMENTED_VERSION}'): + mlog.log( + f'python.build_config has schema_version {schema_version!r}, ' + f'but we only implement support for {self.IMPLEMENTED_VERSION!r}, ' + 'new functionality might be missing' + ) + + def _expand_paths(self) -> None: + """Expand relative path (they're relative to base_prefix).""" + for key in self._PATH_KEYS: + if key not in self: + continue + parent, _, child = key.rpartition('.') + container = self[parent] if parent else self._data + path = Path(container[child]) + if not path.is_absolute(): + container[child] = os.fspath(self.base_prefix / path) + + @property + def config_path(self) -> Path: + return self._path + + @mesonlib.lazy_property + def base_prefix(self) -> Path: + path = Path(self._data['base_prefix']) + if path.is_absolute(): + return path + # Non-absolute paths are relative to the build config directory + return self.config_path.parent / path + + class BasicPythonExternalProgram(ExternalProgram): def __init__(self, name: str, command: T.Optional[T.List[str]] = None, - ext_prog: T.Optional[ExternalProgram] = None): + ext_prog: T.Optional[ExternalProgram] = None, + build_config_path: T.Optional[str] = None): if ext_prog is None: super().__init__(name, command=command, silent=True) else: @@ -86,6 +172,8 @@ class BasicPythonExternalProgram(ExternalProgram): self.cached_version = None self.version_arg = '--version' + self.build_config = PythonBuildConfig(build_config_path) if build_config_path else None + # We want strong key values, so we always populate this with bogus data. # Otherwise to make the type checkers happy we'd have to do .get() for # everycall, even though we know that the introspection data will be @@ -106,6 +194,15 @@ class BasicPythonExternalProgram(ExternalProgram): } self.pure: bool = True + @property + def version(self) -> str: + if self.build_config: + value = self.build_config['language']['version'] + else: + value = self.info['variables'].get('LDVERSION') or self.info['version'] + assert isinstance(value, str) + return value + def _check_version(self, version: str) -> bool: if self.name == 'python2': return mesonlib.version_compare(version, '< 3.0') @@ -116,6 +213,14 @@ class BasicPythonExternalProgram(ExternalProgram): def sanity(self) -> bool: # Sanity check, we expect to have something that at least quacks in tune + if self.build_config: + if not self.build_config['libpython']: + mlog.debug('This Python installation does not provide a libpython') + return False + if not self.build_config['c_api']: + mlog.debug('This Python installation does support the C API') + return False + import importlib.resources with importlib.resources.path('mesonbuild.scripts', 'python_info.py') as f: @@ -143,14 +248,32 @@ class BasicPythonExternalProgram(ExternalProgram): class _PythonDependencyBase(_Base): - def __init__(self, python_holder: 'BasicPythonExternalProgram', embed: bool): + def __init__(self, python_holder: 'BasicPythonExternalProgram', embed: bool, + for_machine: 'MachineChoice'): + self.for_machine = for_machine self.embed = embed - self.version: str = python_holder.info['version'] - self.platform = python_holder.info['platform'] - self.variables = python_holder.info['variables'] + self.build_config = python_holder.build_config + + if self.build_config: + self.version = self.build_config['language']['version'] + self.platform = self.build_config['platform'] + self.is_freethreaded = 't' in self.build_config['abi']['flags'] + self.link_libpython = self.build_config['libpython']['link_extensions'] + # TODO: figure out how to deal with frameworks + # see the logic at the bottom of PythonPkgConfigDependency.__init__() + if self.env.machines.host.is_darwin(): + raise DependencyException('--python.build-config is not supported on Darwin') + else: + self.version = python_holder.info['version'] + self.platform = python_holder.info['platform'] + self.is_freethreaded = python_holder.info['is_freethreaded'] + self.link_libpython = python_holder.info['link_libpython'] + # This data shouldn't be needed when build_config is set + self.is_pypy = python_holder.info['is_pypy'] + self.variables = python_holder.info['variables'] + self.paths = python_holder.info['paths'] - self.is_pypy = python_holder.info['is_pypy'] - self.is_freethreaded = python_holder.info['is_freethreaded'] + # The "-embed" version of python.pc / python-config was introduced in 3.8, # and distutils extension linking was changed to be considered a non embed # usage. Before then, this dependency always uses the embed=True handling @@ -159,7 +282,9 @@ class _PythonDependencyBase(_Base): # On macOS and some Linux distros (Debian) distutils doesn't link extensions # against libpython, even on 3.7 and below. We call into distutils and # mirror its behavior. See https://github.com/mesonbuild/meson/issues/4117 - self.link_libpython = python_holder.info['link_libpython'] or embed + if not self.link_libpython: + self.link_libpython = embed + self.info: T.Optional[T.Dict[str, str]] = None if mesonlib.version_compare(self.version, '>= 3.0'): self.major_version = 3 @@ -173,6 +298,18 @@ class _PythonDependencyBase(_Base): self.compile_args += ['-DPy_GIL_DISABLED'] def find_libpy(self, environment: 'Environment') -> None: + if self.build_config: + path = self.build_config['libpython'].get('dynamic') + if not path: + raise DependencyException('Python does not provide a dynamic libpython library') + sysroot = environment.properties[self.for_machine].get_sys_root() or '' + path = sysroot + path + if not os.path.isfile(path): + raise DependencyException('Python dynamic library does not exist or is not a file') + self.link_args = [path] + self.is_found = True + return + if self.is_pypy: if self.major_version == 3: libname = 'pypy3-c' @@ -188,7 +325,7 @@ class _PythonDependencyBase(_Base): libname += self.variables['ABIFLAGS'] libdirs = [] - largs = self.clib_compiler.find_library(libname, environment, libdirs) + largs = self.clib_compiler.find_library(libname, libdirs) if largs is not None: self.link_args = largs self.is_found = True @@ -211,7 +348,17 @@ class _PythonDependencyBase(_Base): return 'aarch64' raise DependencyException('Unknown Windows Python platform {self.platform!r}') - def get_windows_link_args(self, limited_api: bool) -> T.Optional[T.List[str]]: + def get_windows_link_args(self, limited_api: bool, environment: 'Environment') -> T.Optional[T.List[str]]: + if self.build_config: + if self.static: + key = 'static' + elif limited_api: + key = 'dynamic-stableabi' + else: + key = 'dynamic' + sysroot = environment.properties[self.for_machine].get_sys_root() or '' + return [sysroot + self.build_config['libpython'][key]] + if self.platform.startswith('win'): vernum = self.variables.get('py_version_nodot') verdot = self.variables.get('py_version_short') @@ -251,7 +398,7 @@ class _PythonDependencyBase(_Base): is_debug_build = debug or buildtype == 'debug' vscrt_debug = False if OptionKey('b_vscrt') in self.env.coredata.optstore: - vscrt = self.env.coredata.optstore.get_value('b_vscrt') + vscrt = self.env.coredata.optstore.get_value_for('b_vscrt') if vscrt in {'mdd', 'mtd', 'from_buildtype', 'static_from_buildtype'}: vscrt_debug = True if is_debug_build and vscrt_debug and not self.variables.get('Py_DEBUG'): @@ -300,28 +447,49 @@ class _PythonDependencyBase(_Base): self.is_found = False return # This can fail if the library is not found - largs = self.get_windows_link_args(limited_api) + largs = self.get_windows_link_args(limited_api, env) if largs is None: self.is_found = False return self.link_args = largs self.is_found = True + class PythonPkgConfigDependency(PkgConfigDependency, _PythonDependencyBase): - def __init__(self, name: str, environment: 'Environment', - kwargs: T.Dict[str, T.Any], installation: 'BasicPythonExternalProgram', - libpc: bool = False): - if libpc: - mlog.debug(f'Searching for {name!r} via pkgconfig lookup in LIBPC') + def __init__(self, environment: 'Environment', kwargs: DependencyObjectKWs, + installation: 'BasicPythonExternalProgram', embed: bool, + for_machine: 'MachineChoice'): + pkg_embed = '-embed' if embed and mesonlib.version_compare(installation.info['version'], '>=3.8') else '' + pkg_name = f'python-{installation.version}{pkg_embed}' + + if installation.build_config: + pkg_libdir = installation.build_config.get('c_api.pkgconfig_path') + pkg_libdir_origin = 'c_api.pkgconfig_path from the Python build config' else: - mlog.debug(f'Searching for {name!r} via fallback pkgconfig lookup in default paths') + pkg_libdir = installation.info['variables'].get('LIBPC') + pkg_libdir_origin = 'LIBPC' + if pkg_libdir is None: + # we do not fall back to system directories, since this could lead + # to using pkg-config of another Python installation, for example + # we could end up using CPython .pc file for PyPy + mlog.debug(f'Skipping pkgconfig lookup, {pkg_libdir_origin} is unset') + self.is_found = False + return + + sysroot = environment.properties[for_machine].get_sys_root() or '' + pkg_libdir = sysroot + pkg_libdir - PkgConfigDependency.__init__(self, name, environment, kwargs) - _PythonDependencyBase.__init__(self, installation, kwargs.get('embed', False)) + mlog.debug(f'Searching for {pkg_libdir!r} via pkgconfig lookup in {pkg_libdir_origin}') + pkgconfig_paths = [pkg_libdir] if pkg_libdir else [] - if libpc and not self.is_found: - mlog.debug(f'"python-{self.version}" could not be found in LIBPC, this is likely due to a relocated python installation') + PkgConfigDependency.__init__(self, pkg_name, environment, kwargs, extra_paths=pkgconfig_paths) + _PythonDependencyBase.__init__(self, installation, kwargs.get('embed', False), for_machine) + + if pkg_libdir and not self.is_found: + mlog.debug(f'{pkg_name!r} could not be found in {pkg_libdir_origin}, ' + 'this is likely due to a relocated python installation') + return # pkg-config files are usually accurate starting with python 3.8 if not self.link_libpython and mesonlib.version_compare(self.version, '< 3.8'): @@ -330,28 +498,39 @@ class PythonPkgConfigDependency(PkgConfigDependency, _PythonDependencyBase): # But not Apple, because it's a framework if self.env.machines.host.is_darwin() and 'PYTHONFRAMEWORKPREFIX' in self.variables: framework_prefix = self.variables['PYTHONFRAMEWORKPREFIX'] - # Add rpath, will be de-duplicated if necessary + # Add rpath, will be de-duplicated if necessary if framework_prefix.startswith('/Applications/Xcode.app/'): self.link_args += ['-Wl,-rpath,' + framework_prefix] - self.raw_link_args += ['-Wl,-rpath,' + framework_prefix] + if self.raw_link_args is not None: + # When None, self.link_args is used + self.raw_link_args += ['-Wl,-rpath,' + framework_prefix] + class PythonFrameworkDependency(ExtraFrameworkDependency, _PythonDependencyBase): def __init__(self, name: str, environment: 'Environment', - kwargs: T.Dict[str, T.Any], installation: 'BasicPythonExternalProgram'): + kwargs: DependencyObjectKWs, installation: 'BasicPythonExternalProgram', + for_machine: 'MachineChoice'): ExtraFrameworkDependency.__init__(self, name, environment, kwargs) - _PythonDependencyBase.__init__(self, installation, kwargs.get('embed', False)) + _PythonDependencyBase.__init__(self, installation, kwargs.get('embed', False), for_machine) class PythonSystemDependency(SystemDependency, _PythonDependencyBase): def __init__(self, name: str, environment: 'Environment', - kwargs: T.Dict[str, T.Any], installation: 'BasicPythonExternalProgram'): + kwargs: DependencyObjectKWs, installation: 'BasicPythonExternalProgram', + for_machine: 'MachineChoice'): SystemDependency.__init__(self, name, environment, kwargs) - _PythonDependencyBase.__init__(self, installation, kwargs.get('embed', False)) - - # match pkg-config behavior - if self.link_libpython: + _PythonDependencyBase.__init__(self, installation, kwargs.get('embed', False), for_machine) + + # For most platforms, match pkg-config behavior. iOS is a special case; + # check for that first, so that check takes priority over + # `link_libpython` (which *shouldn't* be set, but just in case) + if self.platform.startswith('ios-'): + # iOS doesn't use link_libpython - it links with the *framework*. + self.link_args = ['-framework', 'Python', '-F', self.variables.get('base_prefix')] + self.is_found = True + elif self.link_libpython: # link args if mesonlib.is_windows(): self.find_libpy_windows(environment, limited_api=False) @@ -361,10 +540,14 @@ class PythonSystemDependency(SystemDependency, _PythonDependencyBase): self.is_found = True # compile args - inc_paths = mesonlib.OrderedSet([ - self.variables.get('INCLUDEPY'), - self.paths.get('include'), - self.paths.get('platinclude')]) + if self.build_config: + sysroot = environment.properties[for_machine].get_sys_root() or '' + inc_paths = mesonlib.OrderedSet([sysroot + self.build_config['c_api']['headers']]) + else: + inc_paths = mesonlib.OrderedSet([ + self.variables.get('INCLUDEPY'), + self.paths.get('include'), + self.paths.get('platinclude')]) self.compile_args += ['-I' + path for path in inc_paths if path] @@ -373,7 +556,7 @@ class PythonSystemDependency(SystemDependency, _PythonDependencyBase): if mesonlib.is_windows() and self.get_windows_python_arch().endswith('64') and mesonlib.version_compare(self.version, '<3.12'): self.compile_args += ['-DMS_WIN64='] - if not self.clib_compiler.has_header('Python.h', '', environment, extra_args=self.compile_args)[0]: + if not self.clib_compiler.has_header('Python.h', '', extra_args=self.compile_args)[0]: self.is_found = False @staticmethod @@ -381,7 +564,7 @@ class PythonSystemDependency(SystemDependency, _PythonDependencyBase): return 'sysconfig' def python_factory(env: 'Environment', for_machine: 'MachineChoice', - kwargs: T.Dict[str, T.Any], + kwargs: DependencyObjectKWs, installation: T.Optional['BasicPythonExternalProgram'] = None) -> T.List['DependencyGenerator']: # We can't use the factory_methods decorator here, as we need to pass the # extra installation argument @@ -393,58 +576,23 @@ def python_factory(env: 'Environment', for_machine: 'MachineChoice', if installation is None: installation = BasicPythonExternalProgram('python3', mesonlib.python_command) installation.sanity() - pkg_version = installation.info['variables'].get('LDVERSION') or installation.info['version'] if DependencyMethods.PKGCONFIG in methods: if from_installation: - pkg_libdir = installation.info['variables'].get('LIBPC') - pkg_embed = '-embed' if embed and mesonlib.version_compare(installation.info['version'], '>=3.8') else '' - pkg_name = f'python-{pkg_version}{pkg_embed}' - - # If python-X.Y.pc exists in LIBPC, we will try to use it - def wrap_in_pythons_pc_dir(name: str, env: 'Environment', kwargs: T.Dict[str, T.Any], - installation: 'BasicPythonExternalProgram') -> 'ExternalDependency': - if not pkg_libdir: - # there is no LIBPC, so we can't search in it - empty = ExternalDependency(DependencyTypeName('pkgconfig'), env, {}) - empty.name = 'python' - return empty - - old_pkg_libdir = os.environ.pop('PKG_CONFIG_LIBDIR', None) - old_pkg_path = os.environ.pop('PKG_CONFIG_PATH', None) - os.environ['PKG_CONFIG_LIBDIR'] = pkg_libdir - try: - return PythonPkgConfigDependency(name, env, kwargs, installation, True) - finally: - def set_env(name: str, value: str) -> None: - if value is not None: - os.environ[name] = value - elif name in os.environ: - del os.environ[name] - set_env('PKG_CONFIG_LIBDIR', old_pkg_libdir) - set_env('PKG_CONFIG_PATH', old_pkg_path) - - # Otherwise this doesn't fulfill the interface requirements - wrap_in_pythons_pc_dir.log_tried = PythonPkgConfigDependency.log_tried # type: ignore[attr-defined] - - candidates.append(functools.partial(wrap_in_pythons_pc_dir, pkg_name, env, kwargs, installation)) - # We only need to check both, if a python install has a LIBPC. It might point to the wrong location, - # e.g. relocated / cross compilation, but the presence of LIBPC indicates we should definitely look for something. - if pkg_libdir is not None: - candidates.append(functools.partial(PythonPkgConfigDependency, pkg_name, env, kwargs, installation)) + candidates.append(functools.partial(PythonPkgConfigDependency, env, kwargs, installation, embed, for_machine)) else: candidates.append(functools.partial(PkgConfigDependency, 'python3', env, kwargs)) if DependencyMethods.SYSTEM in methods: - candidates.append(functools.partial(PythonSystemDependency, 'python', env, kwargs, installation)) + candidates.append(functools.partial(PythonSystemDependency, 'python', env, kwargs, installation, for_machine)) if DependencyMethods.EXTRAFRAMEWORK in methods: nkwargs = kwargs.copy() - if mesonlib.version_compare(pkg_version, '>= 3'): + if mesonlib.version_compare(installation.version, '>= 3'): # There is a python in /System/Library/Frameworks, but that's python 2.x, # Python 3 will always be in /Library nkwargs['paths'] = ['/Library/Frameworks'] - candidates.append(functools.partial(PythonFrameworkDependency, 'Python', env, nkwargs, installation)) + candidates.append(functools.partial(PythonFrameworkDependency, 'Python', env, nkwargs, installation, for_machine)) return candidates diff --git a/mesonbuild/dependencies/qt.py b/mesonbuild/dependencies/qt.py index a3a9388..c245e5c 100644 --- a/mesonbuild/dependencies/qt.py +++ b/mesonbuild/dependencies/qt.py @@ -1,6 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright 2013-2017 The Meson development team -# Copyright © 2021-2023 Intel Corporation +# Copyright © 2021-2025 Intel Corporation from __future__ import annotations @@ -9,6 +9,7 @@ from __future__ import annotations import abc import re import os +from pathlib import Path import typing as T from .base import DependencyException, DependencyMethods @@ -25,6 +26,7 @@ if T.TYPE_CHECKING: from ..envconfig import MachineInfo from ..environment import Environment from ..dependencies import MissingCompiler + from .base import DependencyObjectKWs def _qt_get_private_includes(mod_inc_dir: str, module: str, mod_version: str) -> T.List[str]: @@ -50,7 +52,7 @@ def _qt_get_private_includes(mod_inc_dir: str, module: str, mod_version: str) -> if len(dirname.split('.')) == 3: private_dir = dirname break - return [private_dir, os.path.join(private_dir, 'Qt' + module)] + return [private_dir, Path(private_dir, f'Qt{module}').as_posix()] def get_qmake_host_bins(qvars: T.Dict[str, str]) -> str: @@ -95,7 +97,7 @@ def _get_modules_lib_suffix(version: str, info: 'MachineInfo', is_debug: bool) - class QtExtraFrameworkDependency(ExtraFrameworkDependency): - def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any], qvars: T.Dict[str, str], language: T.Optional[str] = None): + def __init__(self, name: str, env: 'Environment', kwargs: DependencyObjectKWs, qvars: T.Dict[str, str], language: T.Optional[str] = None): super().__init__(name, env, kwargs, language=language) self.mod_name = name[2:] self.qt_extra_include_directory = qvars['QT_INSTALL_HEADERS'] @@ -122,7 +124,7 @@ class _QtBase: libexecdir: T.Optional[str] = None version: str - def __init__(self, name: str, kwargs: T.Dict[str, T.Any]): + def __init__(self, name: str, kwargs: DependencyObjectKWs): self.name = name self.qtname = name.capitalize() self.qtver = name[-1] @@ -131,20 +133,20 @@ class _QtBase: else: self.qtpkgname = self.qtname - self.private_headers = T.cast('bool', kwargs.get('private_headers', False)) + self.private_headers = kwargs.get('private_headers', False) - self.requested_modules = mesonlib.stringlistify(mesonlib.extract_as_list(kwargs, 'modules')) + self.requested_modules = kwargs.get('modules', []) if not self.requested_modules: raise DependencyException('No ' + self.qtname + ' modules specified.') - self.qtmain = T.cast('bool', kwargs.get('main', False)) + self.qtmain = kwargs.get('main', False) if not isinstance(self.qtmain, bool): raise DependencyException('"main" argument must be a boolean') def _link_with_qt_winmain(self, is_debug: bool, libdir: T.Union[str, T.List[str]]) -> bool: libdir = mesonlib.listify(libdir) # TODO: shouldn't be necessary base_name = self.get_qt_winmain_base_name(is_debug) - qt_winmain = self.clib_compiler.find_library(base_name, self.env, libdir) + qt_winmain = self.clib_compiler.find_library(base_name, libdir) if qt_winmain: self.link_args.append(qt_winmain[0]) return True @@ -170,7 +172,7 @@ class QtPkgConfigDependency(_QtBase, PkgConfigDependency, metaclass=abc.ABCMeta) """Specialization of the PkgConfigDependency for Qt.""" - def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]): + def __init__(self, name: str, env: 'Environment', kwargs: DependencyObjectKWs): _QtBase.__init__(self, name, kwargs) # Always use QtCore as the "main" dependency, since it has the extra @@ -249,7 +251,7 @@ class QmakeQtDependency(_QtBase, ConfigToolDependency, metaclass=abc.ABCMeta): version: str version_arg = '-v' - def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]): + def __init__(self, name: str, env: 'Environment', kwargs: DependencyObjectKWs): _QtBase.__init__(self, name, kwargs) self.tool_name = f'qmake{self.qtver}' self.tools = [f'qmake{self.qtver}', f'qmake-{self.name}', 'qmake'] @@ -259,7 +261,7 @@ class QmakeQtDependency(_QtBase, ConfigToolDependency, metaclass=abc.ABCMeta): # is requested, add "">= 5, < 6", but if the user has ">= 5.6", don't # lose that. kwargs = kwargs.copy() - _vers = mesonlib.listify(kwargs.get('version', [])) + _vers = kwargs.get('version', []) _vers.extend([f'>= {self.qtver}', f'< {int(self.qtver) + 1}']) kwargs['version'] = _vers @@ -303,7 +305,7 @@ class QmakeQtDependency(_QtBase, ConfigToolDependency, metaclass=abc.ABCMeta): modules_lib_suffix = _get_modules_lib_suffix(self.version, self.env.machines[self.for_machine], is_debug) for module in self.requested_modules: - mincdir = os.path.join(incdir, 'Qt' + module) + mincdir = Path(incdir, f'Qt{module}').as_posix() self.compile_args.append('-I' + mincdir) if module == 'QuickTest': @@ -319,7 +321,7 @@ class QmakeQtDependency(_QtBase, ConfigToolDependency, metaclass=abc.ABCMeta): for directory in priv_inc: self.compile_args.append('-I' + directory) libfiles = self.clib_compiler.find_library( - self.qtpkgname + module + modules_lib_suffix, self.env, + self.qtpkgname + module + modules_lib_suffix, mesonlib.listify(libdir)) # TODO: shouldn't be necessary if libfiles: libfile = libfiles[0] @@ -348,12 +350,12 @@ class QmakeQtDependency(_QtBase, ConfigToolDependency, metaclass=abc.ABCMeta): def get_private_includes(self, mod_inc_dir: str, module: str) -> T.List[str]: pass - def _framework_detect(self, qvars: T.Dict[str, str], modules: T.List[str], kwargs: T.Dict[str, T.Any]) -> None: + def _framework_detect(self, qvars: T.Dict[str, str], modules: T.List[str], kwargs: DependencyObjectKWs) -> None: libdir = qvars['QT_INSTALL_LIBS'] # ExtraFrameworkDependency doesn't support any methods fw_kwargs = kwargs.copy() - fw_kwargs.pop('method', None) + fw_kwargs.pop('method') fw_kwargs['paths'] = [libdir] for m in modules: @@ -442,7 +444,7 @@ class Qt5PkgConfigDependency(QtPkgConfigDependency): class Qt6PkgConfigDependency(Qt6WinMainMixin, QtPkgConfigDependency): - def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]): + def __init__(self, name: str, env: 'Environment', kwargs: DependencyObjectKWs): super().__init__(name, env, kwargs) if not self.libexecdir: mlog.debug(f'detected Qt6 {self.version} pkg-config dependency does not ' diff --git a/mesonbuild/dependencies/scalapack.py b/mesonbuild/dependencies/scalapack.py index c04d1f5..26a6e39 100644 --- a/mesonbuild/dependencies/scalapack.py +++ b/mesonbuild/dependencies/scalapack.py @@ -9,7 +9,7 @@ import os import typing as T from ..options import OptionKey -from .base import DependencyMethods +from .base import DependencyException, DependencyMethods from .cmake import CMakeDependency from .detect import packages from .pkgconfig import PkgConfigDependency @@ -19,16 +19,17 @@ if T.TYPE_CHECKING: from ..environment import Environment from ..mesonlib import MachineChoice from .factory import DependencyGenerator + from .base import DependencyObjectKWs @factory_methods({DependencyMethods.PKGCONFIG, DependencyMethods.CMAKE}) def scalapack_factory(env: 'Environment', for_machine: 'MachineChoice', - kwargs: T.Dict[str, T.Any], + kwargs: DependencyObjectKWs, methods: T.List[DependencyMethods]) -> T.List['DependencyGenerator']: candidates: T.List['DependencyGenerator'] = [] if DependencyMethods.PKGCONFIG in methods: - static_opt = kwargs.get('static', env.coredata.optstore.get_value_for(OptionKey('prefer_static'))) + static_opt = kwargs['static'] if kwargs.get('static') is not None else env.coredata.optstore.get_value_for(OptionKey('prefer_static')) mkl = 'mkl-static-lp64-iomp' if static_opt else 'mkl-dynamic-lp64-iomp' candidates.append(functools.partial( MKLPkgConfigDependency, mkl, env, kwargs)) @@ -54,7 +55,7 @@ class MKLPkgConfigDependency(PkgConfigDependency): bunch of fixups to make it work correctly. """ - def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any], + def __init__(self, name: str, env: 'Environment', kwargs: DependencyObjectKWs, language: T.Optional[str] = None): _m = os.environ.get('MKLROOT') self.__mklroot = Path(_m).resolve() if _m else None @@ -65,8 +66,7 @@ class MKLPkgConfigDependency(PkgConfigDependency): super().__init__(name, env, kwargs, language=language) # Doesn't work with gcc on windows, but does on Linux - if (not self.__mklroot or (env.machines[self.for_machine].is_windows() - and self.clib_compiler.id == 'gcc')): + if env.machines[self.for_machine].is_windows() and self.clib_compiler.id == 'gcc': self.is_found = False # This can happen either because we're using GCC, we couldn't find the @@ -96,6 +96,9 @@ class MKLPkgConfigDependency(PkgConfigDependency): self.version = v def _set_libs(self) -> None: + if self.__mklroot is None: + raise DependencyException('MKLROOT not set') + super()._set_libs() if self.env.machines[self.for_machine].is_windows(): @@ -133,6 +136,9 @@ class MKLPkgConfigDependency(PkgConfigDependency): self.link_args.insert(i + 1, '-lmkl_blacs_intelmpi_lp64') def _set_cargs(self) -> None: + if self.__mklroot is None: + raise DependencyException('MKLROOT not set') + allow_system = False if self.language == 'fortran': # gfortran doesn't appear to look in system paths for INCLUDE files, diff --git a/mesonbuild/dependencies/ui.py b/mesonbuild/dependencies/ui.py index fc44037..566ba52 100644 --- a/mesonbuild/dependencies/ui.py +++ b/mesonbuild/dependencies/ui.py @@ -13,9 +13,8 @@ import typing as T from .. import mlog from .. import mesonlib from ..mesonlib import ( - Popen_safe, extract_as_list, version_compare_many + Popen_safe, version_compare_many ) -from ..environment import detect_cpu_family from .base import DependencyException, DependencyMethods, DependencyTypeName, SystemDependency from .configtool import ConfigToolDependency @@ -24,10 +23,11 @@ from .factory import DependencyFactory if T.TYPE_CHECKING: from ..environment import Environment + from .base import DependencyObjectKWs class GLDependencySystem(SystemDependency): - def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]) -> None: + def __init__(self, name: str, environment: 'Environment', kwargs: DependencyObjectKWs) -> None: super().__init__(name, environment, kwargs) if self.env.machines[self.for_machine].is_darwin(): @@ -43,8 +43,8 @@ class GLDependencySystem(SystemDependency): # FIXME: Detect version using self.clib_compiler return else: - links = self.clib_compiler.find_library('GL', environment, []) - has_header = self.clib_compiler.has_header('GL/gl.h', '', environment)[0] + links = self.clib_compiler.find_library('GL', []) + has_header = self.clib_compiler.has_header('GL/gl.h', '')[0] if links and has_header: self.is_found = True self.link_args = links @@ -56,7 +56,7 @@ class GnuStepDependency(ConfigToolDependency): tools = ['gnustep-config'] tool_name = 'gnustep-config' - def __init__(self, environment: 'Environment', kwargs: T.Dict[str, T.Any]) -> None: + def __init__(self, environment: 'Environment', kwargs: DependencyObjectKWs) -> None: super().__init__('gnustep', environment, kwargs, language='objc') if not self.is_found: return @@ -135,7 +135,7 @@ class SDL2DependencyConfigTool(ConfigToolDependency): tools = ['sdl2-config'] tool_name = 'sdl2-config' - def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]): + def __init__(self, name: str, environment: 'Environment', kwargs: DependencyObjectKWs): super().__init__(name, environment, kwargs) if not self.is_found: return @@ -148,11 +148,11 @@ class WxDependency(ConfigToolDependency): tools = ['wx-config-3.0', 'wx-config-3.1', 'wx-config', 'wx-config-gtk3'] tool_name = 'wx-config' - def __init__(self, environment: 'Environment', kwargs: T.Dict[str, T.Any]): + def __init__(self, environment: 'Environment', kwargs: DependencyObjectKWs): super().__init__('WxWidgets', environment, kwargs, language='cpp') if not self.is_found: return - self.requested_modules = self.get_requested(kwargs) + self.requested_modules = kwargs.get('modules', []) extra_args = [] if self.static: @@ -170,29 +170,16 @@ class WxDependency(ConfigToolDependency): self.compile_args = self.get_config_value(['--cxxflags'] + extra_args + self.requested_modules, 'compile_args') self.link_args = self.get_config_value(['--libs'] + extra_args + self.requested_modules, 'link_args') - @staticmethod - def get_requested(kwargs: T.Dict[str, T.Any]) -> T.List[str]: - if 'modules' not in kwargs: - return [] - candidates = extract_as_list(kwargs, 'modules') - for c in candidates: - if not isinstance(c, str): - raise DependencyException('wxwidgets module argument is not a string') - return candidates - packages['wxwidgets'] = WxDependency class VulkanDependencySystem(SystemDependency): - def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any], language: T.Optional[str] = None) -> None: + def __init__(self, name: str, environment: 'Environment', kwargs: DependencyObjectKWs, language: T.Optional[str] = None) -> None: super().__init__(name, environment, kwargs, language=language) - try: - self.vulkan_sdk = os.environ.get('VULKAN_SDK', os.environ['VK_SDK_PATH']) - if not os.path.isabs(self.vulkan_sdk): - raise DependencyException('VULKAN_SDK must be an absolute path.') - except KeyError: - self.vulkan_sdk = None + self.vulkan_sdk = os.environ.get('VULKAN_SDK', os.environ.get('VK_SDK_PATH')) + if self.vulkan_sdk and not os.path.isabs(self.vulkan_sdk): + raise DependencyException('VULKAN_SDK must be an absolute path.') if self.vulkan_sdk: # TODO: this config might not work on some platforms, fix bugs as reported @@ -200,18 +187,42 @@ class VulkanDependencySystem(SystemDependency): lib_name = 'vulkan' lib_dir = 'lib' inc_dir = 'include' - if mesonlib.is_windows(): + if self.env.machines[self.for_machine].is_windows(): lib_name = 'vulkan-1' - lib_dir = 'Lib32' + lib_dir = '' inc_dir = 'Include' - if detect_cpu_family(self.env.coredata.compilers.host) == 'x86_64': - lib_dir = 'Lib' + build_cpu = self.env.machines.build.cpu_family + host_cpu = self.env.machines.host.cpu_family + if build_cpu == 'x86_64': + if host_cpu == build_cpu: + lib_dir = 'Lib' + elif host_cpu == 'aarch64': + lib_dir = 'Lib-ARM64' + elif host_cpu == 'x86': + lib_dir = 'Lib32' + elif build_cpu == 'aarch64': + if host_cpu == build_cpu: + lib_dir = 'Lib' + elif host_cpu == 'x86_64': + lib_dir = 'Lib-x64' + elif host_cpu == 'x86': + lib_dir = 'Lib32' + elif build_cpu == 'x86': + if host_cpu == build_cpu: + lib_dir = 'Lib32' + if host_cpu == 'aarch64': + lib_dir = 'Lib-ARM64' + elif host_cpu == 'x86_64': + lib_dir = 'Lib' + + if lib_dir == '': + raise DependencyException(f'Target architecture \'{host_cpu}\' is not supported for this Vulkan SDK.') # make sure header and lib are valid inc_path = os.path.join(self.vulkan_sdk, inc_dir) header = os.path.join(inc_path, 'vulkan', 'vulkan.h') lib_path = os.path.join(self.vulkan_sdk, lib_dir) - find_lib = self.clib_compiler.find_library(lib_name, environment, [lib_path]) + find_lib = self.clib_compiler.find_library(lib_name, [lib_path]) if not find_lib: raise DependencyException('VULKAN_SDK point to invalid directory (no lib)') @@ -227,8 +238,8 @@ class VulkanDependencySystem(SystemDependency): self.link_args.append('-l' + lib_name) else: # simply try to guess it, usually works on linux - libs = self.clib_compiler.find_library('vulkan', environment, []) - if libs is not None and self.clib_compiler.has_header('vulkan/vulkan.h', '', environment, disable_cache=True)[0]: + libs = self.clib_compiler.find_library('vulkan', []) + if libs is not None and self.clib_compiler.has_header('vulkan/vulkan.h', '', disable_cache=True)[0]: self.is_found = True for lib in libs: self.link_args.append(lib) @@ -241,8 +252,7 @@ class VulkanDependencySystem(SystemDependency): components = [str(self.clib_compiler.compute_int(f'VK_VERSION_{c}(VK_HEADER_VERSION_COMPLETE)', low=0, high=None, guess=e, prefix='#include <vulkan/vulkan.h>', - env=environment, - extra_args=None, + extra_args=self.compile_args, dependencies=None)) # list containing vulkan version components and their expected value for c, e in [('MAJOR', 1), ('MINOR', 3), ('PATCH', None)]] diff --git a/mesonbuild/envconfig.py b/mesonbuild/envconfig.py index 43fad0c..dd050b0 100644 --- a/mesonbuild/envconfig.py +++ b/mesonbuild/envconfig.py @@ -4,17 +4,22 @@ from __future__ import annotations from dataclasses import dataclass -import subprocess import typing as T from enum import Enum +import os +import platform +import sys from . import mesonlib -from .mesonlib import EnvironmentException, HoldableObject +from .mesonlib import EnvironmentException, HoldableObject, Popen_safe +from .programs import ExternalProgram from . import mlog from pathlib import Path if T.TYPE_CHECKING: from .options import ElementaryOptionValues + from .compilers import Compiler + from .compilers.mixins.visualstudio import VisualStudioLikeCompiler # These classes contains all the data pulled from configuration files (native @@ -51,6 +56,7 @@ known_cpu_families = ( 'msp430', 'parisc', 'pic24', + 'pic32', 'ppc', 'ppc64', 'riscv32', @@ -376,11 +382,17 @@ class MachineInfo(HoldableObject): """Machine is IRIX?""" return self.system.startswith('irix') + def is_os2(self) -> bool: + """ + Machine is OS/2? + """ + return self.system == 'os/2' + # Various prefixes and suffixes for import libraries, shared libraries, # static libraries, and executables. # Versioning is added to these names in the backends as-needed. def get_exe_suffix(self) -> str: - if self.is_windows() or self.is_cygwin(): + if self.is_windows() or self.is_cygwin() or self.is_os2(): return 'exe' else: return '' @@ -423,31 +435,23 @@ class BinaryTable: del self.binaries['pkgconfig'] @staticmethod - def detect_ccache() -> T.List[str]: - try: - subprocess.check_call(['ccache', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - except (OSError, subprocess.CalledProcessError): - return [] - return ['ccache'] + def detect_ccache() -> ExternalProgram: + return ExternalProgram('ccache', silent=True) @staticmethod - def detect_sccache() -> T.List[str]: - try: - subprocess.check_call(['sccache', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - except (OSError, subprocess.CalledProcessError): - return [] - return ['sccache'] + def detect_sccache() -> ExternalProgram: + return ExternalProgram('sccache', silent=True) @staticmethod - def detect_compiler_cache() -> T.List[str]: + def detect_compiler_cache() -> ExternalProgram: # Sccache is "newer" so it is assumed that people would prefer it by default. cache = BinaryTable.detect_sccache() - if cache: + if cache.found(): return cache return BinaryTable.detect_ccache() @classmethod - def parse_entry(cls, entry: T.Union[str, T.List[str]]) -> T.Tuple[T.List[str], T.List[str]]: + def parse_entry(cls, entry: T.Union[str, T.List[str]]) -> T.Tuple[T.List[str], T.Union[None, ExternalProgram]]: parts = mesonlib.stringlistify(entry) # Ensure ccache exists and remove it if it doesn't if parts[0] == 'ccache': @@ -458,7 +462,7 @@ class BinaryTable: ccache = cls.detect_sccache() else: compiler = parts - ccache = [] + ccache = None if not compiler: raise EnvironmentException(f'Compiler cache specified without compiler: {parts[0]}') # Return value has to be a list of compiler 'choices' @@ -491,3 +495,259 @@ class CMakeVariables: def get_variables(self) -> T.Dict[str, T.List[str]]: return self.variables + + +# Machine and platform detection functions +# ======================================== + +KERNEL_MAPPINGS: T.Mapping[str, str] = {'freebsd': 'freebsd', + 'openbsd': 'openbsd', + 'netbsd': 'netbsd', + 'windows': 'nt', + 'android': 'linux', + 'linux': 'linux', + 'cygwin': 'nt', + 'darwin': 'xnu', + 'ios': 'xnu', + 'tvos': 'xnu', + 'visionos': 'xnu', + 'watchos': 'xnu', + 'dragonfly': 'dragonfly', + 'haiku': 'haiku', + 'gnu': 'gnu', + } + +def detect_windows_arch(compilers: T.Dict[str, Compiler]) -> str: + """ + Detecting the 'native' architecture of Windows is not a trivial task. We + cannot trust that the architecture that Python is built for is the 'native' + one because you can run 32-bit apps on 64-bit Windows using WOW64 and + people sometimes install 32-bit Python on 64-bit Windows. + + We also can't rely on the architecture of the OS itself, since it's + perfectly normal to compile and run 32-bit applications on Windows as if + they were native applications. It's a terrible experience to require the + user to supply a cross-info file to compile 32-bit applications on 64-bit + Windows. Thankfully, the only way to compile things with Visual Studio on + Windows is by entering the 'msvc toolchain' environment, which can be + easily detected. + + In the end, the sanest method is as follows: + 1. Check environment variables that are set by Windows and WOW64 to find out + if this is x86 (possibly in WOW64), if so use that as our 'native' + architecture. + 2. If the compiler toolchain target architecture is x86, use that as our + 'native' architecture. + 3. Otherwise, use the actual Windows architecture + + """ + os_arch = mesonlib.windows_detect_native_arch() + if os_arch == 'x86': + return os_arch + # If we're on 64-bit Windows, 32-bit apps can be compiled without + # cross-compilation. So if we're doing that, just set the native arch as + # 32-bit and pretend like we're running under WOW64. Else, return the + # actual Windows architecture that we deduced above. + for compiler in compilers.values(): + compiler = T.cast('VisualStudioLikeCompiler', compiler) + if compiler.id == 'msvc' and (compiler.target in {'x86', '80x86'}): + return 'x86' + if compiler.id == 'clang-cl' and (compiler.target in {'x86', 'i686'}): + return 'x86' + if compiler.id == 'gcc' and compiler.has_builtin_define('__i386__'): + return 'x86' + return os_arch + +def any_compiler_has_define(compilers: T.Dict[str, Compiler], define: str) -> bool: + for c in compilers.values(): + try: + if c.has_builtin_define(define): + return True + except mesonlib.MesonException: + # Ignore compilers that do not support has_builtin_define. + pass + return False + +def detect_cpu_family(compilers: T.Dict[str, Compiler]) -> str: + """ + Python is inconsistent in its platform module. + It returns different values for the same cpu. + For x86 it might return 'x86', 'i686' or some such. + Do some canonicalization. + """ + if mesonlib.is_windows(): + trial = detect_windows_arch(compilers) + elif mesonlib.is_freebsd() or mesonlib.is_netbsd() or mesonlib.is_openbsd() or mesonlib.is_qnx() or mesonlib.is_aix(): + trial = platform.processor().lower() + else: + trial = platform.machine().lower() + if trial.startswith('i') and trial.endswith('86'): + trial = 'x86' + elif trial == 'bepc': + trial = 'x86' + elif trial == 'arm64': + trial = 'aarch64' + elif trial.startswith('aarch64'): + # This can be `aarch64_be` + trial = 'aarch64' + elif trial.startswith('arm') or trial.startswith('earm'): + trial = 'arm' + elif trial.startswith(('powerpc64', 'ppc64')): + trial = 'ppc64' + elif trial.startswith(('powerpc', 'ppc')) or trial in {'macppc', 'power macintosh'}: + trial = 'ppc' + elif trial in {'amd64', 'x64', 'i86pc'}: + trial = 'x86_64' + elif trial in {'sun4u', 'sun4v'}: + trial = 'sparc64' + elif trial.startswith('mips'): + if '64' not in trial: + trial = 'mips' + else: + trial = 'mips64' + elif trial in {'ip30', 'ip35'}: + trial = 'mips64' + + # On Linux (and maybe others) there can be any mixture of 32/64 bit code in + # the kernel, Python, system, 32-bit chroot on 64-bit host, etc. The only + # reliable way to know is to check the compiler defines. + if trial == 'x86_64': + if any_compiler_has_define(compilers, '__i386__'): + trial = 'x86' + elif trial == 'aarch64': + if any_compiler_has_define(compilers, '__arm__'): + trial = 'arm' + # Add more quirks here as bugs are reported. Keep in sync with detect_cpu() + # below. + elif trial == 'parisc64': + # ATM there is no 64 bit userland for PA-RISC. Thus always + # report it as 32 bit for simplicity. + trial = 'parisc' + elif trial == 'ppc': + # AIX always returns powerpc, check here for 64-bit + if any_compiler_has_define(compilers, '__64BIT__'): + trial = 'ppc64' + # MIPS64 is able to run MIPS32 code natively, so there is a chance that + # such mixture mentioned above exists. + elif trial == 'mips64': + if compilers and not any_compiler_has_define(compilers, '__mips64'): + trial = 'mips' + + if trial not in known_cpu_families: + mlog.warning(f'Unknown CPU family {trial!r}, please report this at ' + 'https://github.com/mesonbuild/meson/issues/new with the ' + 'output of `uname -a` and `cat /proc/cpuinfo`') + + return trial + +def detect_cpu(compilers: T.Dict[str, Compiler]) -> str: + if mesonlib.is_windows(): + trial = detect_windows_arch(compilers) + elif mesonlib.is_freebsd() or mesonlib.is_netbsd() or mesonlib.is_openbsd() or mesonlib.is_aix(): + trial = platform.processor().lower() + else: + trial = platform.machine().lower() + + if trial in {'amd64', 'x64', 'i86pc'}: + trial = 'x86_64' + if trial == 'x86_64': + # Same check as above for cpu_family + if any_compiler_has_define(compilers, '__i386__'): + trial = 'i686' # All 64 bit cpus have at least this level of x86 support. + elif trial.startswith('aarch64') or trial.startswith('arm64'): + # Same check as above for cpu_family + if any_compiler_has_define(compilers, '__arm__'): + trial = 'arm' + else: + # for aarch64_be + trial = 'aarch64' + elif trial.startswith('earm'): + trial = 'arm' + elif trial == 'e2k': + # Make more precise CPU detection for Elbrus platform. + trial = platform.processor().lower() + elif trial.startswith('mips'): + if '64' not in trial: + trial = 'mips' + else: + if compilers and not any_compiler_has_define(compilers, '__mips64'): + trial = 'mips' + else: + trial = 'mips64' + elif trial == 'ppc': + # AIX always returns powerpc, check here for 64-bit + if any_compiler_has_define(compilers, '__64BIT__'): + trial = 'ppc64' + + # Add more quirks here as bugs are reported. Keep in sync with + # detect_cpu_family() above. + return trial + +def detect_kernel(system: str) -> T.Optional[str]: + if system == 'sunos': + # Solaris 5.10 uname doesn't support the -o switch, and illumos started + # with version 5.11 so shortcut the logic to report 'solaris' in such + # cases where the version is 5.10 or below. + if mesonlib.version_compare(platform.uname().release, '<=5.10'): + return 'solaris' + # This needs to be /usr/bin/uname because gnu-uname could be installed and + # won't provide the necessary information + p, out, _ = Popen_safe(['/usr/bin/uname', '-o']) + if p.returncode != 0: + raise mesonlib.MesonException('Failed to run "/usr/bin/uname -o"') + out = out.lower().strip() + if out not in {'illumos', 'solaris'}: + mlog.warning(f'Got an unexpected value for kernel on a SunOS derived platform, expected either "illumos" or "solaris", but got "{out}".' + "Please open a Meson issue with the OS you're running and the value detected for your kernel.") + return None + return out + return KERNEL_MAPPINGS.get(system, None) + +def detect_subsystem(system: str) -> T.Optional[str]: + if system == 'darwin': + return 'macos' + return system + +def detect_system() -> str: + if sys.platform == 'cygwin': + return 'cygwin' + return platform.system().lower() + +def detect_msys2_arch() -> T.Optional[str]: + return os.environ.get('MSYSTEM_CARCH', None) + +def detect_machine_info(compilers: T.Optional[T.Dict[str, Compiler]] = None) -> MachineInfo: + """Detect the machine we're running on + + If compilers are not provided, we cannot know as much. None out those + fields to avoid accidentally depending on partial knowledge. The + underlying ''detect_*'' method can be called to explicitly use the + partial information. + """ + system = detect_system() + return MachineInfo( + system, + detect_cpu_family(compilers) if compilers is not None else None, + detect_cpu(compilers) if compilers is not None else None, + sys.byteorder, + detect_kernel(system), + detect_subsystem(system)) + +# TODO make this compare two `MachineInfo`s purely. How important is the +# `detect_cpu_family({})` distinction? It is the one impediment to that. +def machine_info_can_run(machine_info: MachineInfo) -> bool: + """Whether we can run binaries for this machine on the current machine. + + Can almost always run 32-bit binaries on 64-bit natively if the host + and build systems are the same. We don't pass any compilers to + detect_cpu_family() here because we always want to know the OS + architecture, not what the compiler environment tells us. + """ + if machine_info.system != detect_system(): + return False + true_build_cpu_family = detect_cpu_family({}) + assert machine_info.cpu_family is not None, 'called on incomplete machine_info' + return \ + (machine_info.cpu_family == true_build_cpu_family) or \ + ((true_build_cpu_family == 'x86_64') and (machine_info.cpu_family == 'x86')) or \ + ((true_build_cpu_family == 'mips64') and (machine_info.cpu_family == 'mips')) diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index f322cda..f1d55cc 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -5,50 +5,45 @@ from __future__ import annotations import itertools -import os, platform, re, sys, shutil +import os, re import typing as T import collections +from . import cmdline from . import coredata from . import mesonlib from . import machinefile - -CmdLineFileParser = machinefile.CmdLineFileParser +from . import options from .mesonlib import ( MesonException, MachineChoice, Popen_safe, PerMachine, - PerMachineDefaultable, PerThreeMachineDefaultable, split_args, quote_arg, - search_version, MesonBugException + PerMachineDefaultable, PerThreeMachineDefaultable, split_args, + MesonBugException ) from .options import OptionKey from . import mlog from .programs import ExternalProgram from .envconfig import ( - BinaryTable, MachineInfo, Properties, known_cpu_families, CMakeVariables, + BinaryTable, MachineInfo, Properties, CMakeVariables, + detect_machine_info, machine_info_can_run ) from . import compilers -from .compilers import ( - is_assembly, - is_header, - is_library, - is_llvm_ir, - is_object, - is_source, -) -from functools import lru_cache from mesonbuild import envconfig if T.TYPE_CHECKING: from .compilers import Compiler - from .compilers.mixins.visualstudio import VisualStudioLikeCompiler - from .options import ElementaryOptionValues + from .options import OptionDict, ElementaryOptionValues from .wrap.wrap import Resolver - from . import cargo - CompilersDict = T.Dict[str, Compiler] +NON_LANG_ENV_OPTIONS = [ + ('PKG_CONFIG_PATH', 'pkg_config_path'), + ('CMAKE_PREFIX_PATH', 'cmake_prefix_path'), + ('LDFLAGS', 'ldflags'), + ('CPPFLAGS', 'cppflags'), +] build_filename = 'meson.build' @@ -83,494 +78,12 @@ def _get_env_var(for_machine: MachineChoice, is_cross: bool, var_name: str) -> T return value -def detect_gcovr(gcovr_exe: str = 'gcovr', min_version: str = '3.3', log: bool = False) \ - -> T.Union[T.Tuple[None, None], T.Tuple[str, str]]: - try: - p, found = Popen_safe([gcovr_exe, '--version'])[0:2] - except (FileNotFoundError, PermissionError): - # Doesn't exist in PATH or isn't executable - return None, None - found = search_version(found) - if p.returncode == 0 and mesonlib.version_compare(found, '>=' + min_version): - if log: - mlog.log('Found gcovr-{} at {}'.format(found, quote_arg(shutil.which(gcovr_exe)))) - return gcovr_exe, found - return None, None - -def detect_lcov(lcov_exe: str = 'lcov', log: bool = False) \ - -> T.Union[T.Tuple[None, None], T.Tuple[str, str]]: - try: - p, found = Popen_safe([lcov_exe, '--version'])[0:2] - except (FileNotFoundError, PermissionError): - # Doesn't exist in PATH or isn't executable - return None, None - found = search_version(found) - if p.returncode == 0 and found: - if log: - mlog.log('Found lcov-{} at {}'.format(found, quote_arg(shutil.which(lcov_exe)))) - return lcov_exe, found - return None, None - -def detect_llvm_cov(suffix: T.Optional[str] = None) -> T.Optional[str]: - # If there's a known suffix or forced lack of suffix, use that - if suffix is not None: - if suffix == '': - tool = 'llvm-cov' - else: - tool = f'llvm-cov-{suffix}' - if shutil.which(tool) is not None: - return tool - else: - # Otherwise guess in the dark - tools = get_llvm_tool_names('llvm-cov') - for tool in tools: - if shutil.which(tool): - return tool - return None - -def compute_llvm_suffix(coredata: coredata.CoreData) -> T.Optional[str]: - # Check to see if the user is trying to do coverage for either a C or C++ project - compilers = coredata.compilers[MachineChoice.BUILD] - cpp_compiler_is_clang = 'cpp' in compilers and compilers['cpp'].id == 'clang' - c_compiler_is_clang = 'c' in compilers and compilers['c'].id == 'clang' - # Extract first the C++ compiler if available. If it's a Clang of some kind, compute the suffix if possible - if cpp_compiler_is_clang: - suffix = compilers['cpp'].version.split('.')[0] - return suffix - - # Then the C compiler, again checking if it's some kind of Clang and computing the suffix - if c_compiler_is_clang: - suffix = compilers['c'].version.split('.')[0] - return suffix - - # Neither compiler is a Clang, or no compilers are for C or C++ - return None - -def detect_lcov_genhtml(lcov_exe: str = 'lcov', genhtml_exe: str = 'genhtml') \ - -> T.Tuple[str, T.Optional[str], str]: - lcov_exe, lcov_version = detect_lcov(lcov_exe) - if shutil.which(genhtml_exe) is None: - genhtml_exe = None - - return lcov_exe, lcov_version, genhtml_exe - -def find_coverage_tools(coredata: coredata.CoreData) -> T.Tuple[T.Optional[str], T.Optional[str], T.Optional[str], T.Optional[str], T.Optional[str], T.Optional[str]]: - gcovr_exe, gcovr_version = detect_gcovr() - - llvm_cov_exe = detect_llvm_cov(compute_llvm_suffix(coredata)) - # Some platforms may provide versioned clang but only non-versioned llvm utils - if llvm_cov_exe is None: - llvm_cov_exe = detect_llvm_cov('') - - lcov_exe, lcov_version, genhtml_exe = detect_lcov_genhtml() - - return gcovr_exe, gcovr_version, lcov_exe, lcov_version, genhtml_exe, llvm_cov_exe - -def detect_ninja(version: str = '1.8.2', log: bool = False) -> T.Optional[T.List[str]]: - r = detect_ninja_command_and_version(version, log) - return r[0] if r else None - -def detect_ninja_command_and_version(version: str = '1.8.2', log: bool = False) -> T.Optional[T.Tuple[T.List[str], str]]: - env_ninja = os.environ.get('NINJA', None) - for n in [env_ninja] if env_ninja else ['ninja', 'ninja-build', 'samu']: - prog = ExternalProgram(n, silent=True) - if not prog.found(): - continue - try: - p, found = Popen_safe(prog.command + ['--version'])[0:2] - except (FileNotFoundError, PermissionError): - # Doesn't exist in PATH or isn't executable - continue - found = found.strip() - # Perhaps we should add a way for the caller to know the failure mode - # (not found or too old) - if p.returncode == 0 and mesonlib.version_compare(found, '>=' + version): - if log: - name = os.path.basename(n) - if name.endswith('-' + found): - name = name[0:-1 - len(found)] - if name == 'ninja-build': - name = 'ninja' - if name == 'samu': - name = 'samurai' - mlog.log('Found {}-{} at {}'.format(name, found, - ' '.join([quote_arg(x) for x in prog.command]))) - return (prog.command, found) - return None - -def get_llvm_tool_names(tool: str) -> T.List[str]: - # Ordered list of possible suffixes of LLVM executables to try. Start with - # base, then try newest back to oldest (3.5 is arbitrary), and finally the - # devel version. Please note that the development snapshot in Debian does - # not have a distinct name. Do not move it to the beginning of the list - # unless it becomes a stable release. - suffixes = [ - '', # base (no suffix) - '-20.1', '20.1', - '-20', '20', - '-19.1', '19.1', - '-19', '19', - '-18.1', '18.1', - '-18', '18', - '-17', '17', - '-16', '16', - '-15', '15', - '-14', '14', - '-13', '13', - '-12', '12', - '-11', '11', - '-10', '10', - '-9', '90', - '-8', '80', - '-7', '70', - '-6.0', '60', - '-5.0', '50', - '-4.0', '40', - '-3.9', '39', - '-3.8', '38', - '-3.7', '37', - '-3.6', '36', - '-3.5', '35', - '-20', # Debian development snapshot - '-devel', # FreeBSD development snapshot - ] - names: T.List[str] = [] - for suffix in suffixes: - names.append(tool + suffix) - return names - -def detect_scanbuild() -> T.List[str]: - """ Look for scan-build binary on build platform - - First, if a SCANBUILD env variable has been provided, give it precedence - on all platforms. - - For most platforms, scan-build is found is the PATH contains a binary - named "scan-build". However, some distribution's package manager (FreeBSD) - don't. For those, loop through a list of candidates to see if one is - available. - - Return: a single-element list of the found scan-build binary ready to be - passed to Popen() - """ - exelist: T.List[str] = [] - if 'SCANBUILD' in os.environ: - exelist = split_args(os.environ['SCANBUILD']) - - else: - tools = get_llvm_tool_names('scan-build') - for tool in tools: - which = shutil.which(tool) - if which is not None: - exelist = [which] - break - - if exelist: - tool = exelist[0] - if os.path.isfile(tool) and os.access(tool, os.X_OK): - return [tool] - return [] - -def detect_clangformat() -> T.List[str]: - """ Look for clang-format binary on build platform - - Do the same thing as detect_scanbuild to find clang-format except it - currently does not check the environment variable. - - Return: a single-element list of the found clang-format binary ready to be - passed to Popen() - """ - tools = get_llvm_tool_names('clang-format') - for tool in tools: - path = shutil.which(tool) - if path is not None: - return [path] - return [] - -def detect_clangtidy() -> T.List[str]: - """ Look for clang-tidy binary on build platform - - Return: a single-element list of the found clang-tidy binary ready to be - passed to Popen() - """ - tools = get_llvm_tool_names('clang-tidy') - for tool in tools: - path = shutil.which(tool) - if path is not None: - return [path] - return [] - -def detect_clangapply() -> T.List[str]: - """ Look for clang-apply-replacements binary on build platform - - Return: a single-element list of the found clang-apply-replacements binary - ready to be passed to Popen() - """ - tools = get_llvm_tool_names('clang-apply-replacements') - for tool in tools: - path = shutil.which(tool) - if path is not None: - return [path] - return [] - -def detect_windows_arch(compilers: CompilersDict) -> str: - """ - Detecting the 'native' architecture of Windows is not a trivial task. We - cannot trust that the architecture that Python is built for is the 'native' - one because you can run 32-bit apps on 64-bit Windows using WOW64 and - people sometimes install 32-bit Python on 64-bit Windows. - - We also can't rely on the architecture of the OS itself, since it's - perfectly normal to compile and run 32-bit applications on Windows as if - they were native applications. It's a terrible experience to require the - user to supply a cross-info file to compile 32-bit applications on 64-bit - Windows. Thankfully, the only way to compile things with Visual Studio on - Windows is by entering the 'msvc toolchain' environment, which can be - easily detected. - - In the end, the sanest method is as follows: - 1. Check environment variables that are set by Windows and WOW64 to find out - if this is x86 (possibly in WOW64), if so use that as our 'native' - architecture. - 2. If the compiler toolchain target architecture is x86, use that as our - 'native' architecture. - 3. Otherwise, use the actual Windows architecture - - """ - os_arch = mesonlib.windows_detect_native_arch() - if os_arch == 'x86': - return os_arch - # If we're on 64-bit Windows, 32-bit apps can be compiled without - # cross-compilation. So if we're doing that, just set the native arch as - # 32-bit and pretend like we're running under WOW64. Else, return the - # actual Windows architecture that we deduced above. - for compiler in compilers.values(): - compiler = T.cast('VisualStudioLikeCompiler', compiler) - if compiler.id == 'msvc' and (compiler.target in {'x86', '80x86'}): - return 'x86' - if compiler.id == 'clang-cl' and (compiler.target in {'x86', 'i686'}): - return 'x86' - if compiler.id == 'gcc' and compiler.has_builtin_define('__i386__'): - return 'x86' - return os_arch - -def any_compiler_has_define(compilers: CompilersDict, define: str) -> bool: - for c in compilers.values(): - try: - if c.has_builtin_define(define): - return True - except mesonlib.MesonException: - # Ignore compilers that do not support has_builtin_define. - pass - return False - -def detect_cpu_family(compilers: CompilersDict) -> str: - """ - Python is inconsistent in its platform module. - It returns different values for the same cpu. - For x86 it might return 'x86', 'i686' or some such. - Do some canonicalization. - """ - if mesonlib.is_windows(): - trial = detect_windows_arch(compilers) - elif mesonlib.is_freebsd() or mesonlib.is_netbsd() or mesonlib.is_openbsd() or mesonlib.is_qnx() or mesonlib.is_aix(): - trial = platform.processor().lower() - else: - trial = platform.machine().lower() - if trial.startswith('i') and trial.endswith('86'): - trial = 'x86' - elif trial == 'bepc': - trial = 'x86' - elif trial == 'arm64': - trial = 'aarch64' - elif trial.startswith('aarch64'): - # This can be `aarch64_be` - trial = 'aarch64' - elif trial.startswith('arm') or trial.startswith('earm'): - trial = 'arm' - elif trial.startswith(('powerpc64', 'ppc64')): - trial = 'ppc64' - elif trial.startswith(('powerpc', 'ppc')) or trial in {'macppc', 'power macintosh'}: - trial = 'ppc' - elif trial in {'amd64', 'x64', 'i86pc'}: - trial = 'x86_64' - elif trial in {'sun4u', 'sun4v'}: - trial = 'sparc64' - elif trial.startswith('mips'): - if '64' not in trial: - trial = 'mips' - else: - trial = 'mips64' - elif trial in {'ip30', 'ip35'}: - trial = 'mips64' - - # On Linux (and maybe others) there can be any mixture of 32/64 bit code in - # the kernel, Python, system, 32-bit chroot on 64-bit host, etc. The only - # reliable way to know is to check the compiler defines. - if trial == 'x86_64': - if any_compiler_has_define(compilers, '__i386__'): - trial = 'x86' - elif trial == 'aarch64': - if any_compiler_has_define(compilers, '__arm__'): - trial = 'arm' - # Add more quirks here as bugs are reported. Keep in sync with detect_cpu() - # below. - elif trial == 'parisc64': - # ATM there is no 64 bit userland for PA-RISC. Thus always - # report it as 32 bit for simplicity. - trial = 'parisc' - elif trial == 'ppc': - # AIX always returns powerpc, check here for 64-bit - if any_compiler_has_define(compilers, '__64BIT__'): - trial = 'ppc64' - # MIPS64 is able to run MIPS32 code natively, so there is a chance that - # such mixture mentioned above exists. - elif trial == 'mips64': - if compilers and not any_compiler_has_define(compilers, '__mips64'): - trial = 'mips' - - if trial not in known_cpu_families: - mlog.warning(f'Unknown CPU family {trial!r}, please report this at ' - 'https://github.com/mesonbuild/meson/issues/new with the ' - 'output of `uname -a` and `cat /proc/cpuinfo`') - - return trial - -def detect_cpu(compilers: CompilersDict) -> str: - if mesonlib.is_windows(): - trial = detect_windows_arch(compilers) - elif mesonlib.is_freebsd() or mesonlib.is_netbsd() or mesonlib.is_openbsd() or mesonlib.is_aix(): - trial = platform.processor().lower() - else: - trial = platform.machine().lower() - - if trial in {'amd64', 'x64', 'i86pc'}: - trial = 'x86_64' - if trial == 'x86_64': - # Same check as above for cpu_family - if any_compiler_has_define(compilers, '__i386__'): - trial = 'i686' # All 64 bit cpus have at least this level of x86 support. - elif trial.startswith('aarch64') or trial.startswith('arm64'): - # Same check as above for cpu_family - if any_compiler_has_define(compilers, '__arm__'): - trial = 'arm' - else: - # for aarch64_be - trial = 'aarch64' - elif trial.startswith('earm'): - trial = 'arm' - elif trial == 'e2k': - # Make more precise CPU detection for Elbrus platform. - trial = platform.processor().lower() - elif trial.startswith('mips'): - if '64' not in trial: - trial = 'mips' - else: - if compilers and not any_compiler_has_define(compilers, '__mips64'): - trial = 'mips' - else: - trial = 'mips64' - elif trial == 'ppc': - # AIX always returns powerpc, check here for 64-bit - if any_compiler_has_define(compilers, '__64BIT__'): - trial = 'ppc64' - - # Add more quirks here as bugs are reported. Keep in sync with - # detect_cpu_family() above. - return trial - -KERNEL_MAPPINGS: T.Mapping[str, str] = {'freebsd': 'freebsd', - 'openbsd': 'openbsd', - 'netbsd': 'netbsd', - 'windows': 'nt', - 'android': 'linux', - 'linux': 'linux', - 'cygwin': 'nt', - 'darwin': 'xnu', - 'ios': 'xnu', - 'tvos': 'xnu', - 'visionos': 'xnu', - 'watchos': 'xnu', - 'dragonfly': 'dragonfly', - 'haiku': 'haiku', - 'gnu': 'gnu', - } - -def detect_kernel(system: str) -> T.Optional[str]: - if system == 'sunos': - # Solaris 5.10 uname doesn't support the -o switch, and illumos started - # with version 5.11 so shortcut the logic to report 'solaris' in such - # cases where the version is 5.10 or below. - if mesonlib.version_compare(platform.uname().release, '<=5.10'): - return 'solaris' - # This needs to be /usr/bin/uname because gnu-uname could be installed and - # won't provide the necessary information - p, out, _ = Popen_safe(['/usr/bin/uname', '-o']) - if p.returncode != 0: - raise MesonException('Failed to run "/usr/bin/uname -o"') - out = out.lower().strip() - if out not in {'illumos', 'solaris'}: - mlog.warning(f'Got an unexpected value for kernel on a SunOS derived platform, expected either "illumos" or "solaris", but got "{out}".' - "Please open a Meson issue with the OS you're running and the value detected for your kernel.") - return None - return out - return KERNEL_MAPPINGS.get(system, None) - -def detect_subsystem(system: str) -> T.Optional[str]: - if system == 'darwin': - return 'macos' - return system - -def detect_system() -> str: - if sys.platform == 'cygwin': - return 'cygwin' - return platform.system().lower() - -def detect_msys2_arch() -> T.Optional[str]: - return os.environ.get('MSYSTEM_CARCH', None) - -def detect_machine_info(compilers: T.Optional[CompilersDict] = None) -> MachineInfo: - """Detect the machine we're running on - - If compilers are not provided, we cannot know as much. None out those - fields to avoid accidentally depending on partial knowledge. The - underlying ''detect_*'' method can be called to explicitly use the - partial information. - """ - system = detect_system() - return MachineInfo( - system, - detect_cpu_family(compilers) if compilers is not None else None, - detect_cpu(compilers) if compilers is not None else None, - sys.byteorder, - detect_kernel(system), - detect_subsystem(system)) - -# TODO make this compare two `MachineInfo`s purely. How important is the -# `detect_cpu_family({})` distinction? It is the one impediment to that. -def machine_info_can_run(machine_info: MachineInfo) -> bool: - """Whether we can run binaries for this machine on the current machine. - - Can almost always run 32-bit binaries on 64-bit natively if the host - and build systems are the same. We don't pass any compilers to - detect_cpu_family() here because we always want to know the OS - architecture, not what the compiler environment tells us. - """ - if machine_info.system != detect_system(): - return False - true_build_cpu_family = detect_cpu_family({}) - assert machine_info.cpu_family is not None, 'called on incomplete machine_info' - return \ - (machine_info.cpu_family == true_build_cpu_family) or \ - ((true_build_cpu_family == 'x86_64') and (machine_info.cpu_family == 'x86')) or \ - ((true_build_cpu_family == 'mips64') and (machine_info.cpu_family == 'mips')) - class Environment: private_dir = 'meson-private' log_dir = 'meson-logs' info_dir = 'meson-info' - def __init__(self, source_dir: str, build_dir: T.Optional[str], cmd_options: coredata.SharedCMDOptions) -> None: + def __init__(self, source_dir: str, build_dir: T.Optional[str], cmd_options: cmdline.SharedCMDOptions) -> None: self.source_dir = source_dir # Do not try to create build directories when build_dir is none. # This reduced mode is used by the --buildoptions introspector @@ -590,15 +103,15 @@ class Environment: except coredata.MesonVersionMismatchException as e: # This is routine, but tell the user the update happened mlog.log('Regenerating configuration from scratch:', str(e)) - coredata.read_cmd_line_file(self.build_dir, cmd_options) + cmdline.read_cmd_line_file(self.build_dir, cmd_options) self.create_new_coredata(cmd_options) except MesonException as e: # If we stored previous command line options, we can recover from # a broken/outdated coredata. - if os.path.isfile(coredata.get_cmd_line_file(self.build_dir)): + if os.path.isfile(cmdline.get_cmd_line_file(self.build_dir)): mlog.warning('Regenerating configuration from scratch.', fatal=False) mlog.log('Reason:', mlog.red(str(e))) - coredata.read_cmd_line_file(self.build_dir, cmd_options) + cmdline.read_cmd_line_file(self.build_dir, cmd_options) self.create_new_coredata(cmd_options) else: raise MesonException(f'{str(e)} Try regenerating using "meson setup --wipe".') @@ -639,7 +152,12 @@ class Environment: # # Note that order matters because of 'buildtype', if it is after # 'optimization' and 'debug' keys, it override them. - self.options: T.MutableMapping[OptionKey, ElementaryOptionValues] = collections.OrderedDict() + self.options: OptionDict = collections.OrderedDict() + + # Environment variables with the name converted into an OptionKey type. + # These have subtly different behavior compared to machine files, so do + # not store them in self.options. See _set_default_options_from_env. + self.env_opts: OptionDict = {} self.machinestore = machinefile.MachineFileStore(self.coredata.config_files, self.coredata.cross_files, self.source_dir) @@ -711,18 +229,17 @@ class Environment: self.default_cmake = ['cmake'] self.default_pkgconfig = ['pkg-config'] self.wrap_resolver: T.Optional['Resolver'] = None - # Store a global state of Cargo dependencies - self.cargo: T.Optional[cargo.Interpreter] = None def mfilestr2key(self, machine_file_string: str, section: T.Optional[str], section_subproject: T.Optional[str], machine: MachineChoice) -> OptionKey: key = OptionKey.from_string(machine_file_string) - assert key.machine == MachineChoice.HOST if key.subproject: suggestion = section if section == 'project options' else 'built-in options' raise MesonException(f'Do not set subproject options in [{section}] section, use [subproject:{suggestion}] instead.') if section_subproject: key = key.evolve(subproject=section_subproject) if machine == MachineChoice.BUILD: + if key.machine == MachineChoice.BUILD: + mlog.deprecation('Setting build machine options in the native file does not need the "build." prefix', once=True) return key.evolve(machine=machine) return key @@ -756,7 +273,7 @@ class Environment: for section, values in config.items(): if ':' in section: - section_subproject, section = section.split(':') + section_subproject, section = section.split(':', 1) else: section_subproject = '' if section == 'built-in options': @@ -773,16 +290,18 @@ class Environment: # Project options are always for the host machine key = self.mfilestr2key(strk, section, section_subproject, machine) self.options[key] = v + elif ':' in section: + correct_subproject, correct_section = section.split(':')[-2:] + raise MesonException( + 'Subproject options should always be set as ' + '`[subproject:section]`, even if the options are from a ' + 'nested subproject. ' + f'Replace `[{section_subproject}:{section}]` with `[{correct_subproject}:{correct_section}]`') def _set_default_options_from_env(self) -> None: opts: T.List[T.Tuple[str, str]] = ( [(v, f'{k}_args') for k, v in compilers.compilers.CFLAGS_MAPPING.items()] + - [ - ('PKG_CONFIG_PATH', 'pkg_config_path'), - ('CMAKE_PREFIX_PATH', 'cmake_prefix_path'), - ('LDFLAGS', 'ldflags'), - ('CPPFLAGS', 'cppflags'), - ] + NON_LANG_ENV_OPTIONS ) env_opts: T.DefaultDict[OptionKey, T.List[str]] = collections.defaultdict(list) @@ -817,35 +336,32 @@ class Environment: env_opts[key].extend(p_list) elif keyname == 'cppflags': for lang in compilers.compilers.LANGUAGES_USING_CPPFLAGS: - key = OptionKey(f'{lang}_env_args', machine=for_machine) + key = OptionKey(f'{lang}_args', machine=for_machine) env_opts[key].extend(p_list) else: key = OptionKey.from_string(keyname).evolve(machine=for_machine) - if evar in compilers.compilers.CFLAGS_MAPPING.values(): - # If this is an environment variable, we have to - # store it separately until the compiler is - # instantiated, as we don't know whether the - # compiler will want to use these arguments at link - # time and compile time (instead of just at compile - # time) until we're instantiating that `Compiler` - # object. This is required so that passing - # `-Dc_args=` on the command line and `$CFLAGS` - # have subtly different behavior. `$CFLAGS` will be - # added to the linker command line if the compiler - # acts as a linker driver, `-Dc_args` will not. - # - # We still use the original key as the base here, as - # we want to inherit the machine and the compiler - # language - lang = key.name.split('_', 1)[0] - key = key.evolve(f'{lang}_env_args') env_opts[key].extend(p_list) - # Only store options that are not already in self.options, - # otherwise we'd override the machine files - for k, v in env_opts.items(): - if k not in self.options: - self.options[k] = v + # If this is an environment variable, we have to + # store it separately until the compiler is + # instantiated, as we don't know whether the + # compiler will want to use these arguments at link + # time and compile time (instead of just at compile + # time) until we're instantiating that `Compiler` + # object. This is required so that passing + # `-Dc_args=` on the command line and `$CFLAGS` + # have subtly different behavior. `$CFLAGS` will be + # added to the linker command line if the compiler + # acts as a linker driver, `-Dc_args` will not. + for (_, keyname), for_machine in itertools.product(NON_LANG_ENV_OPTIONS, MachineChoice): + key = OptionKey.from_string(keyname).evolve(machine=for_machine) + # Only store options that are not already in self.options, + # otherwise we'd override the machine files + if key in env_opts and key not in self.options: + self.options[key] = env_opts[key] + del env_opts[key] + + self.env_opts.update(env_opts) def _set_default_binaries_from_env(self) -> None: """Set default binaries from the environment. @@ -884,7 +400,7 @@ class Environment: self.properties[for_machine].properties.setdefault(name, p_env) break - def create_new_coredata(self, options: coredata.SharedCMDOptions) -> None: + def create_new_coredata(self, options: cmdline.SharedCMDOptions) -> None: # WARNING: Don't use any values from coredata in __init__. It gets # re-initialized with project options by the interpreter during # build file parsing. @@ -897,6 +413,17 @@ class Environment: self.coredata = coredata.CoreData(options, self.scratch_dir, meson_command) self.first_invocation = True + def init_backend_options(self, backend_name: str) -> None: + # Only init backend options on first invocation otherwise it would + # override values previously set from command line. + if not self.first_invocation: + return + + self.coredata.init_backend_options(backend_name) + for k, v in self.options.items(): + if self.coredata.optstore.is_backend_option(k): + self.coredata.optstore.set_option(k, v) + def is_cross_build(self, when_building_for: MachineChoice = MachineChoice.HOST) -> bool: return self.coredata.is_cross_build(when_building_for) @@ -919,25 +446,6 @@ class Environment: cmd.insert(1, '-u') return cmd - def is_header(self, fname: 'mesonlib.FileOrString') -> bool: - return is_header(fname) - - def is_source(self, fname: 'mesonlib.FileOrString') -> bool: - return is_source(fname) - - def is_assembly(self, fname: 'mesonlib.FileOrString') -> bool: - return is_assembly(fname) - - def is_llvm_ir(self, fname: 'mesonlib.FileOrString') -> bool: - return is_llvm_ir(fname) - - def is_object(self, fname: 'mesonlib.FileOrString') -> bool: - return is_object(fname) - - @lru_cache(maxsize=None) - def is_library(self, fname: mesonlib.FileOrString) -> bool: - return is_library(fname) - def lookup_binary_entry(self, for_machine: MachineChoice, name: str) -> T.Optional[T.List[str]]: return self.binaries[for_machine].lookup_entry(name) @@ -1064,3 +572,44 @@ class Environment: if extra_paths: env.prepend('PATH', list(extra_paths)) return env + + def add_lang_args(self, lang: str, comp: T.Type['Compiler'], + for_machine: MachineChoice) -> None: + """Add global language arguments that are needed before compiler/linker detection.""" + description = f'Extra arguments passed to the {lang}' + argkey = OptionKey(f'{lang}_args', machine=for_machine) + largkey = OptionKey(f'{lang}_link_args', machine=for_machine) + + comp_args_from_envvar = False + comp_options = self.coredata.optstore.get_pending_value(argkey) + if comp_options is None: + comp_args_from_envvar = True + comp_options = self.env_opts.get(argkey, []) + + link_options = self.coredata.optstore.get_pending_value(largkey) + if link_options is None: + link_options = self.env_opts.get(largkey, []) + + assert isinstance(comp_options, (str, list)), 'for mypy' + assert isinstance(link_options, (str, list)), 'for mypy' + + cargs = options.UserStringArrayOption( + argkey.name, + description + ' compiler', + comp_options, split_args=True, allow_dups=True) + + largs = options.UserStringArrayOption( + largkey.name, + description + ' linker', + link_options, split_args=True, allow_dups=True) + + self.coredata.optstore.add_compiler_option(lang, argkey, cargs) + self.coredata.optstore.add_compiler_option(lang, largkey, largs) + + if comp.INVOKES_LINKER and comp_args_from_envvar: + # If the compiler acts as a linker driver, and we're using the + # environment variable flags for both the compiler and linker + # arguments, then put the compiler flags in the linker flags as well. + # This is how autotools works, and the env vars feature is for + # autotools compatibility. + largs.extend_value(comp_options) diff --git a/mesonbuild/interpreter/__init__.py b/mesonbuild/interpreter/__init__.py index e2ccce4..600798f 100644 --- a/mesonbuild/interpreter/__init__.py +++ b/mesonbuild/interpreter/__init__.py @@ -1,12 +1,11 @@ -# SPDX-license-identifier: Apache-2.0 +# SPDX-License-Identifier: Apache-2.0 # Copyright 2012-2021 The Meson development team -# Copyright © 2021-2023 Intel Corporation +# Copyright © 2021-2024 Intel Corporation """Meson interpreter.""" __all__ = [ 'Interpreter', - 'permitted_dependency_kwargs', 'CompilerHolder', @@ -30,7 +29,7 @@ __all__ = [ 'StringHolder', ] -from .interpreter import Interpreter, permitted_dependency_kwargs +from .interpreter import Interpreter from .compiler import CompilerHolder from .interpreterobjects import (ExecutableHolder, BuildTargetHolder, CustomTargetHolder, CustomTargetIndexHolder, MachineHolder, Test, diff --git a/mesonbuild/interpreter/compiler.py b/mesonbuild/interpreter/compiler.py index 8aeac8a..d5b14af 100644 --- a/mesonbuild/interpreter/compiler.py +++ b/mesonbuild/interpreter/compiler.py @@ -19,11 +19,11 @@ from ..compilers import SUFFIX_TO_LANG, RunResult from ..compilers.compilers import CompileCheckMode from ..interpreterbase import (ObjectHolder, noPosargs, noKwargs, FeatureNew, FeatureNewKwargs, disablerIfNotFound, - InterpreterException) + InterpreterException, InterpreterObject) from ..interpreterbase.decorators import ContainerTypeInfo, typed_kwargs, KwargInfo, typed_pos_args from ..options import OptionKey from .interpreterobjects import (extract_required_kwarg, extract_search_dirs) -from .type_checking import REQUIRED_KW, in_set_validator, NoneType +from .type_checking import INCLUDE_DIRECTORIES, REQUIRED_KW, in_set_validator, NoneType if T.TYPE_CHECKING: from ..interpreter import Interpreter @@ -86,7 +86,7 @@ if T.TYPE_CHECKING: # prepended to the key header_args: T.List[str] header_dependencies: T.List[dependencies.Dependency] - header_include_directories: T.List[build.IncludeDirs] + header_include_directories: T.List[T.Union[build.IncludeDirs, str]] header_no_builtin_args: bool header_prefix: str header_required: T.Union[bool, options.UserFeatureOption] @@ -94,9 +94,9 @@ if T.TYPE_CHECKING: class PreprocessKW(TypedDict): output: str compile_args: T.List[str] - include_directories: T.List[build.IncludeDirs] + include_directories: T.List[T.Union[build.IncludeDirs, str]] dependencies: T.List[dependencies.Dependency] - depends: T.List[T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex]] + depends: T.List[build.BuildTargetTypes] class _TestMode(enum.Enum): @@ -110,29 +110,28 @@ class _TestMode(enum.Enum): class TryRunResultHolder(ObjectHolder['RunResult']): def __init__(self, res: 'RunResult', interpreter: 'Interpreter'): super().__init__(res, interpreter) - self.methods.update({'returncode': self.returncode_method, - 'compiled': self.compiled_method, - 'stdout': self.stdout_method, - 'stderr': self.stderr_method, - }) @noPosargs @noKwargs + @InterpreterObject.method('returncode') def returncode_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> int: return self.held_object.returncode @noPosargs @noKwargs + @InterpreterObject.method('compiled') def compiled_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> bool: return self.held_object.compiled @noPosargs @noKwargs + @InterpreterObject.method('stdout') def stdout_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> str: return self.held_object.stdout @noPosargs @noKwargs + @InterpreterObject.method('stderr') def stderr_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> str: return self.held_object.stderr @@ -149,18 +148,12 @@ _DEPENDENCIES_KW: KwargInfo[T.List['dependencies.Dependency']] = KwargInfo( listify=True, default=[], ) -_DEPENDS_KW: KwargInfo[T.List[T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex]]] = KwargInfo( +_DEPENDS_KW: KwargInfo[T.List[build.BuildTargetTypes]] = KwargInfo( 'depends', ContainerTypeInfo(list, (build.BuildTarget, build.CustomTarget, build.CustomTargetIndex)), listify=True, default=[], ) -_INCLUDE_DIRS_KW: KwargInfo[T.List[build.IncludeDirs]] = KwargInfo( - 'include_directories', - ContainerTypeInfo(list, build.IncludeDirs), - default=[], - listify=True, -) _PREFIX_KW: KwargInfo[str] = KwargInfo( 'prefix', (str, ContainerTypeInfo(list, str)), @@ -174,10 +167,10 @@ _WERROR_KW = KwargInfo('werror', bool, default=False, since='1.3.0') # Many of the compiler methods take this kwarg signature exactly, this allows # simplifying the `typed_kwargs` calls -_COMMON_KWS: T.List[KwargInfo] = [_ARGS_KW, _DEPENDENCIES_KW, _INCLUDE_DIRS_KW, _PREFIX_KW, _NO_BUILTIN_ARGS_KW] +_COMMON_KWS: T.List[KwargInfo] = [_ARGS_KW, _DEPENDENCIES_KW, INCLUDE_DIRECTORIES, _PREFIX_KW, _NO_BUILTIN_ARGS_KW] # Common methods of compiles, links, runs, and similar -_COMPILES_KWS: T.List[KwargInfo] = [_NAME_KW, _ARGS_KW, _DEPENDENCIES_KW, _INCLUDE_DIRS_KW, _NO_BUILTIN_ARGS_KW, +_COMPILES_KWS: T.List[KwargInfo] = [_NAME_KW, _ARGS_KW, _DEPENDENCIES_KW, INCLUDE_DIRECTORIES, _NO_BUILTIN_ARGS_KW, _WERROR_KW, REQUIRED_KW.evolve(since='1.5.0', default=False)] @@ -190,40 +183,6 @@ class CompilerHolder(ObjectHolder['Compiler']): def __init__(self, compiler: 'Compiler', interpreter: 'Interpreter'): super().__init__(compiler, interpreter) self.environment = self.env - self.methods.update({'compiles': self.compiles_method, - 'links': self.links_method, - 'get_id': self.get_id_method, - 'get_linker_id': self.get_linker_id_method, - 'compute_int': self.compute_int_method, - 'sizeof': self.sizeof_method, - 'get_define': self.get_define_method, - 'has_define': self.has_define_method, - 'check_header': self.check_header_method, - 'has_header': self.has_header_method, - 'has_header_symbol': self.has_header_symbol_method, - 'run': self.run_method, - 'has_function': self.has_function_method, - 'has_member': self.has_member_method, - 'has_members': self.has_members_method, - 'has_type': self.has_type_method, - 'alignment': self.alignment_method, - 'version': self.version_method, - 'cmd_array': self.cmd_array_method, - 'find_library': self.find_library_method, - 'has_argument': self.has_argument_method, - 'has_function_attribute': self.has_func_attribute_method, - 'get_supported_function_attributes': self.get_supported_function_attributes_method, - 'has_multi_arguments': self.has_multi_arguments_method, - 'get_supported_arguments': self.get_supported_arguments_method, - 'first_supported_argument': self.first_supported_argument_method, - 'has_link_argument': self.has_link_argument_method, - 'has_multi_link_arguments': self.has_multi_link_arguments_method, - 'get_supported_link_arguments': self.get_supported_link_arguments_method, - 'first_supported_link_argument': self.first_supported_link_argument_method, - 'symbols_have_underscore_prefix': self.symbols_have_underscore_prefix_method, - 'get_argument_syntax': self.get_argument_syntax_method, - 'preprocess': self.preprocess_method, - }) @property def compiler(self) -> 'Compiler': @@ -254,25 +213,27 @@ class CompilerHolder(ObjectHolder['Compiler']): @noPosargs @noKwargs + @InterpreterObject.method('version') def version_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> str: return self.compiler.version @noPosargs @noKwargs + @InterpreterObject.method('cmd_array') def cmd_array_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> T.List[str]: return self.compiler.exelist def _determine_args(self, kwargs: BaseCompileKW, mode: CompileCheckMode = CompileCheckMode.LINK) -> T.List[str]: args: T.List[str] = [] - for i in kwargs['include_directories']: + for i in self.interpreter.extract_incdirs(kwargs, strings_since='1.10.0'): for idir in i.to_string_list(self.environment.get_source_dir(), self.environment.get_build_dir()): args.extend(self.compiler.get_include_args(idir, False)) if not kwargs['no_builtin_args']: - args += self.compiler.get_option_compile_args(None, self.interpreter.environment, self.subproject) - args += self.compiler.get_option_std_args(None, self.interpreter.environment, self.subproject) + args += self.compiler.get_option_compile_args(None, self.subproject) + args += self.compiler.get_option_std_args(None, self.subproject) if mode is CompileCheckMode.LINK: - args.extend(self.compiler.get_option_link_args(None, self.interpreter.environment, self.subproject)) + args.extend(self.compiler.get_option_link_args(None, self.subproject)) if kwargs.get('werror', False): args.extend(self.compiler.get_werror_args()) args.extend(kwargs['args']) @@ -289,10 +250,11 @@ class CompilerHolder(ObjectHolder['Compiler']): _ARGS_KW, _DEPENDENCIES_KW, ) + @InterpreterObject.method('alignment') def alignment_method(self, args: T.Tuple[str], kwargs: 'AlignmentKw') -> int: typename = args[0] deps, msg = self._determine_dependencies(kwargs['dependencies'], compile_only=self.compiler.is_cross) - result, cached = self.compiler.alignment(typename, kwargs['prefix'], self.environment, + result, cached = self.compiler.alignment(typename, kwargs['prefix'], extra_args=kwargs['args'], dependencies=deps) cached_msg = mlog.blue('(cached)') if cached else '' @@ -302,6 +264,7 @@ class CompilerHolder(ObjectHolder['Compiler']): @typed_pos_args('compiler.run', (str, mesonlib.File)) @typed_kwargs('compiler.run', *_COMPILES_KWS) + @InterpreterObject.method('run') def run_method(self, args: T.Tuple['mesonlib.FileOrString'], kwargs: 'CompileKW') -> 'RunResult': if self.compiler.language not in {'d', 'c', 'cpp', 'objc', 'objcpp', 'fortran'}: FeatureNew.single_use(f'compiler.run for {self.compiler.get_display_language()} language', @@ -321,8 +284,7 @@ class CompilerHolder(ObjectHolder['Compiler']): code.rel_to_builddir(self.environment.source_dir)) extra_args = functools.partial(self._determine_args, kwargs) deps, msg = self._determine_dependencies(kwargs['dependencies'], compile_only=False, endl=None) - result = self.compiler.run(code, self.environment, extra_args=extra_args, - dependencies=deps) + result = self.compiler.run(code, extra_args=extra_args, dependencies=deps) if required and result.returncode != 0: raise InterpreterException(f'Could not run {testname if testname else "code"}') @@ -338,26 +300,30 @@ class CompilerHolder(ObjectHolder['Compiler']): @noPosargs @noKwargs + @InterpreterObject.method('get_id') def get_id_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> str: return self.compiler.get_id() @noPosargs @noKwargs @FeatureNew('compiler.get_linker_id', '0.53.0') + @InterpreterObject.method('get_linker_id') def get_linker_id_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> str: return self.compiler.get_linker_id() @noPosargs @noKwargs + @InterpreterObject.method('symbols_have_underscore_prefix') def symbols_have_underscore_prefix_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> bool: ''' Check if the compiler prefixes _ (underscore) to global C symbols See: https://en.wikipedia.org/wiki/Name_mangling#C ''' - return self.compiler.symbols_have_underscore_prefix(self.environment) + return self.compiler.symbols_have_underscore_prefix() @typed_pos_args('compiler.has_member', str, str) @typed_kwargs('compiler.has_member', _HAS_REQUIRED_KW, *_COMMON_KWS) + @InterpreterObject.method('has_member') def has_member_method(self, args: T.Tuple[str, str], kwargs: 'HasKW') -> bool: typename, membername = args disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False) @@ -367,9 +333,7 @@ class CompilerHolder(ObjectHolder['Compiler']): extra_args = functools.partial(self._determine_args, kwargs) deps, msg = self._determine_dependencies(kwargs['dependencies']) had, cached = self.compiler.has_members(typename, [membername], kwargs['prefix'], - self.environment, - extra_args=extra_args, - dependencies=deps) + extra_args=extra_args, dependencies=deps) cached_msg = mlog.blue('(cached)') if cached else '' if required and not had: raise InterpreterException(f'{self.compiler.get_display_language()} member {membername!r} of type {typename!r} not usable') @@ -383,6 +347,7 @@ class CompilerHolder(ObjectHolder['Compiler']): @typed_pos_args('compiler.has_members', str, varargs=str, min_varargs=1) @typed_kwargs('compiler.has_members', _HAS_REQUIRED_KW, *_COMMON_KWS) + @InterpreterObject.method('has_members') def has_members_method(self, args: T.Tuple[str, T.List[str]], kwargs: 'HasKW') -> bool: typename, membernames = args members = mlog.bold(', '.join([f'"{m}"' for m in membernames])) @@ -393,9 +358,7 @@ class CompilerHolder(ObjectHolder['Compiler']): extra_args = functools.partial(self._determine_args, kwargs) deps, msg = self._determine_dependencies(kwargs['dependencies']) had, cached = self.compiler.has_members(typename, membernames, kwargs['prefix'], - self.environment, - extra_args=extra_args, - dependencies=deps) + extra_args=extra_args, dependencies=deps) cached_msg = mlog.blue('(cached)') if cached else '' if required and not had: # print members as array: ['member1', 'member2'] @@ -410,6 +373,7 @@ class CompilerHolder(ObjectHolder['Compiler']): @typed_pos_args('compiler.has_function', str) @typed_kwargs('compiler.has_function', _HAS_REQUIRED_KW, *_COMMON_KWS) + @InterpreterObject.method('has_function') def has_function_method(self, args: T.Tuple[str], kwargs: 'HasKW') -> bool: funcname = args[0] disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False) @@ -418,7 +382,7 @@ class CompilerHolder(ObjectHolder['Compiler']): return False extra_args = self._determine_args(kwargs) deps, msg = self._determine_dependencies(kwargs['dependencies'], compile_only=False) - had, cached = self.compiler.has_function(funcname, kwargs['prefix'], self.environment, + had, cached = self.compiler.has_function(funcname, kwargs['prefix'], extra_args=extra_args, dependencies=deps) cached_msg = mlog.blue('(cached)') if cached else '' @@ -433,6 +397,7 @@ class CompilerHolder(ObjectHolder['Compiler']): @typed_pos_args('compiler.has_type', str) @typed_kwargs('compiler.has_type', _HAS_REQUIRED_KW, *_COMMON_KWS) + @InterpreterObject.method('has_type') def has_type_method(self, args: T.Tuple[str], kwargs: 'HasKW') -> bool: typename = args[0] disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False) @@ -441,7 +406,7 @@ class CompilerHolder(ObjectHolder['Compiler']): return False extra_args = functools.partial(self._determine_args, kwargs) deps, msg = self._determine_dependencies(kwargs['dependencies']) - had, cached = self.compiler.has_type(typename, kwargs['prefix'], self.environment, + had, cached = self.compiler.has_type(typename, kwargs['prefix'], extra_args=extra_args, dependencies=deps) cached_msg = mlog.blue('(cached)') if cached else '' if required and not had: @@ -462,24 +427,25 @@ class CompilerHolder(ObjectHolder['Compiler']): KwargInfo('guess', (int, NoneType)), *_COMMON_KWS, ) + @InterpreterObject.method('compute_int') def compute_int_method(self, args: T.Tuple[str], kwargs: 'ComputeIntKW') -> int: expression = args[0] extra_args = functools.partial(self._determine_args, kwargs) deps, msg = self._determine_dependencies(kwargs['dependencies'], compile_only=self.compiler.is_cross) res = self.compiler.compute_int(expression, kwargs['low'], kwargs['high'], kwargs['guess'], kwargs['prefix'], - self.environment, extra_args=extra_args, - dependencies=deps) + extra_args=extra_args, dependencies=deps) mlog.log('Computing int of', mlog.bold(expression, True), msg, res) return res @typed_pos_args('compiler.sizeof', str) @typed_kwargs('compiler.sizeof', *_COMMON_KWS) + @InterpreterObject.method('sizeof') def sizeof_method(self, args: T.Tuple[str], kwargs: 'CommonKW') -> int: element = args[0] extra_args = functools.partial(self._determine_args, kwargs) deps, msg = self._determine_dependencies(kwargs['dependencies'], compile_only=self.compiler.is_cross) - esize, cached = self.compiler.sizeof(element, kwargs['prefix'], self.environment, + esize, cached = self.compiler.sizeof(element, kwargs['prefix'], extra_args=extra_args, dependencies=deps) cached_msg = mlog.blue('(cached)') if cached else '' mlog.log('Checking for size of', @@ -489,13 +455,13 @@ class CompilerHolder(ObjectHolder['Compiler']): @FeatureNew('compiler.get_define', '0.40.0') @typed_pos_args('compiler.get_define', str) @typed_kwargs('compiler.get_define', *_COMMON_KWS) + @InterpreterObject.method('get_define') def get_define_method(self, args: T.Tuple[str], kwargs: 'CommonKW') -> str: element = args[0] extra_args = functools.partial(self._determine_args, kwargs) deps, msg = self._determine_dependencies(kwargs['dependencies']) - value, cached = self.compiler.get_define(element, kwargs['prefix'], self.environment, - extra_args=extra_args, - dependencies=deps) + value, cached = self.compiler.get_define(element, kwargs['prefix'], + extra_args=extra_args, dependencies=deps) cached_msg = mlog.blue('(cached)') if cached else '' value_msg = '(undefined)' if value is None else value mlog.log('Fetching value of define', mlog.bold(element, True), msg, value_msg, cached_msg) @@ -504,13 +470,13 @@ class CompilerHolder(ObjectHolder['Compiler']): @FeatureNew('compiler.has_define', '1.3.0') @typed_pos_args('compiler.has_define', str) @typed_kwargs('compiler.has_define', *_COMMON_KWS) + @InterpreterObject.method('has_define') def has_define_method(self, args: T.Tuple[str], kwargs: 'CommonKW') -> bool: define_name = args[0] extra_args = functools.partial(self._determine_args, kwargs) deps, msg = self._determine_dependencies(kwargs['dependencies'], endl=None) - value, cached = self.compiler.get_define(define_name, kwargs['prefix'], self.environment, - extra_args=extra_args, - dependencies=deps) + value, cached = self.compiler.get_define(define_name, kwargs['prefix'], + extra_args=extra_args, dependencies=deps) cached_msg = mlog.blue('(cached)') if cached else '' h = mlog.green('YES') if value is not None else mlog.red('NO') mlog.log('Checking if define', mlog.bold(define_name, True), msg, 'exists:', h, cached_msg) @@ -519,6 +485,7 @@ class CompilerHolder(ObjectHolder['Compiler']): @typed_pos_args('compiler.compiles', (str, mesonlib.File)) @typed_kwargs('compiler.compiles', *_COMPILES_KWS) + @InterpreterObject.method('compiles') def compiles_method(self, args: T.Tuple['mesonlib.FileOrString'], kwargs: 'CompileKW') -> bool: code = args[0] testname = kwargs['name'] @@ -538,7 +505,7 @@ class CompilerHolder(ObjectHolder['Compiler']): code.absolute_path(self.environment.source_dir, self.environment.build_dir)) extra_args = functools.partial(self._determine_args, kwargs) deps, msg = self._determine_dependencies(kwargs['dependencies'], endl=None) - result, cached = self.compiler.compiles(code, self.environment, + result, cached = self.compiler.compiles(code, extra_args=extra_args, dependencies=deps) if required and not result: @@ -555,6 +522,7 @@ class CompilerHolder(ObjectHolder['Compiler']): @typed_pos_args('compiler.links', (str, mesonlib.File)) @typed_kwargs('compiler.links', *_COMPILES_KWS) + @InterpreterObject.method('links') def links_method(self, args: T.Tuple['mesonlib.FileOrString'], kwargs: 'CompileKW') -> bool: code = args[0] testname = kwargs['name'] @@ -587,7 +555,7 @@ class CompilerHolder(ObjectHolder['Compiler']): extra_args = functools.partial(self._determine_args, kwargs) deps, msg = self._determine_dependencies(kwargs['dependencies'], compile_only=False, endl=None) - result, cached = self.compiler.links(code, self.environment, + result, cached = self.compiler.links(code, compiler=compiler, extra_args=extra_args, dependencies=deps) @@ -606,6 +574,7 @@ class CompilerHolder(ObjectHolder['Compiler']): @FeatureNew('compiler.check_header', '0.47.0') @typed_pos_args('compiler.check_header', str) @typed_kwargs('compiler.check_header', *_HEADER_KWS) + @InterpreterObject.method('check_header') def check_header_method(self, args: T.Tuple[str], kwargs: 'HeaderKW') -> bool: hname = args[0] disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False) @@ -614,7 +583,7 @@ class CompilerHolder(ObjectHolder['Compiler']): return False extra_args = functools.partial(self._determine_args, kwargs) deps, msg = self._determine_dependencies(kwargs['dependencies']) - haz, cached = self.compiler.check_header(hname, kwargs['prefix'], self.environment, + haz, cached = self.compiler.check_header(hname, kwargs['prefix'], extra_args=extra_args, dependencies=deps) cached_msg = mlog.blue('(cached)') if cached else '' @@ -634,7 +603,7 @@ class CompilerHolder(ObjectHolder['Compiler']): return False extra_args = functools.partial(self._determine_args, kwargs) deps, msg = self._determine_dependencies(kwargs['dependencies']) - haz, cached = self.compiler.has_header(hname, kwargs['prefix'], self.environment, + haz, cached = self.compiler.has_header(hname, kwargs['prefix'], extra_args=extra_args, dependencies=deps) cached_msg = mlog.blue('(cached)') if cached else '' if required and not haz: @@ -648,11 +617,13 @@ class CompilerHolder(ObjectHolder['Compiler']): @typed_pos_args('compiler.has_header', str) @typed_kwargs('compiler.has_header', *_HEADER_KWS) + @InterpreterObject.method('has_header') def has_header_method(self, args: T.Tuple[str], kwargs: 'HeaderKW') -> bool: return self._has_header_impl(args[0], kwargs) @typed_pos_args('compiler.has_header_symbol', str, str) @typed_kwargs('compiler.has_header_symbol', *_HEADER_KWS) + @InterpreterObject.method('has_header_symbol') def has_header_symbol_method(self, args: T.Tuple[str, str], kwargs: 'HeaderKW') -> bool: hname, symbol = args disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False) @@ -661,7 +632,7 @@ class CompilerHolder(ObjectHolder['Compiler']): return False extra_args = functools.partial(self._determine_args, kwargs) deps, msg = self._determine_dependencies(kwargs['dependencies']) - haz, cached = self.compiler.has_header_symbol(hname, symbol, kwargs['prefix'], self.environment, + haz, cached = self.compiler.has_header_symbol(hname, symbol, kwargs['prefix'], extra_args=extra_args, dependencies=deps) if required and not haz: @@ -692,6 +663,7 @@ class CompilerHolder(ObjectHolder['Compiler']): KwargInfo('dirs', ContainerTypeInfo(list, str), listify=True, default=[]), *(k.evolve(name=f'header_{k.name}') for k in _HEADER_KWS) ) + @InterpreterObject.method('find_library') def find_library_method(self, args: T.Tuple[str], kwargs: 'FindLibraryKW') -> 'dependencies.ExternalLibrary': # TODO add dependencies support? libname = args[0] @@ -701,13 +673,15 @@ class CompilerHolder(ObjectHolder['Compiler']): mlog.log('Library', mlog.bold(libname), 'skipped: feature', mlog.bold(feature), 'disabled') return self.notfound_library(libname) + include_directories = self.interpreter.extract_incdirs(kwargs, key='header_include_directories', strings_since='1.10.0') + # This could be done with a comprehension, but that confuses the type # checker, and having it check this seems valuable has_header_kwargs: 'HeaderKW' = { 'required': required, 'args': kwargs['header_args'], 'dependencies': kwargs['header_dependencies'], - 'include_directories': kwargs['header_include_directories'], + 'include_directories': include_directories, 'prefix': kwargs['header_prefix'], 'no_builtin_args': kwargs['header_no_builtin_args'], } @@ -726,7 +700,7 @@ class CompilerHolder(ObjectHolder['Compiler']): libtype = mesonlib.LibType.PREFER_STATIC else: libtype = mesonlib.LibType.PREFER_SHARED - linkargs = self.compiler.find_library(libname, self.environment, search_dirs, libtype) + linkargs = self.compiler.find_library(libname, search_dirs, libtype) if required and not linkargs: if libtype == mesonlib.LibType.PREFER_SHARED: libtype_s = 'shared or static' @@ -759,7 +733,7 @@ class CompilerHolder(ObjectHolder['Compiler']): mlog.log(*logargs) return False test = self.compiler.has_multi_link_arguments if mode is _TestMode.LINKER else self.compiler.has_multi_arguments - result, cached = test(arguments, self.environment) + result, cached = test(arguments) if required and not result: logargs += ['not usable'] raise InterpreterException(*logargs) @@ -772,12 +746,14 @@ class CompilerHolder(ObjectHolder['Compiler']): @typed_pos_args('compiler.has_argument', str) @typed_kwargs('compiler.has_argument', _HAS_REQUIRED_KW) + @InterpreterObject.method('has_argument') def has_argument_method(self, args: T.Tuple[str], kwargs: 'HasArgumentKW') -> bool: return self._has_argument_impl([args[0]], kwargs=kwargs) @typed_pos_args('compiler.has_multi_arguments', varargs=str) @typed_kwargs('compiler.has_multi_arguments', _HAS_REQUIRED_KW) @FeatureNew('compiler.has_multi_arguments', '0.37.0') + @InterpreterObject.method('has_multi_arguments') def has_multi_arguments_method(self, args: T.Tuple[T.List[str]], kwargs: 'HasArgumentKW') -> bool: return self._has_argument_impl(args[0], kwargs=kwargs) @@ -788,6 +764,7 @@ class CompilerHolder(ObjectHolder['Compiler']): KwargInfo('checked', str, default='off', since='0.59.0', validator=in_set_validator({'warn', 'require', 'off'})), ) + @InterpreterObject.method('get_supported_arguments') def get_supported_arguments_method(self, args: T.Tuple[T.List[str]], kwargs: 'GetSupportedArgumentKw') -> T.List[str]: supported_args: T.List[str] = [] checked = kwargs['checked'] @@ -805,6 +782,7 @@ class CompilerHolder(ObjectHolder['Compiler']): @noKwargs @typed_pos_args('compiler.first_supported_argument', varargs=str) + @InterpreterObject.method('first_supported_argument') def first_supported_argument_method(self, args: T.Tuple[T.List[str]], kwargs: 'TYPE_kwargs') -> T.List[str]: for arg in args[0]: if self._has_argument_impl([arg]): @@ -816,18 +794,21 @@ class CompilerHolder(ObjectHolder['Compiler']): @FeatureNew('compiler.has_link_argument', '0.46.0') @typed_pos_args('compiler.has_link_argument', str) @typed_kwargs('compiler.has_link_argument', _HAS_REQUIRED_KW) + @InterpreterObject.method('has_link_argument') def has_link_argument_method(self, args: T.Tuple[str], kwargs: 'HasArgumentKW') -> bool: return self._has_argument_impl([args[0]], mode=_TestMode.LINKER, kwargs=kwargs) @FeatureNew('compiler.has_multi_link_argument', '0.46.0') @typed_pos_args('compiler.has_multi_link_argument', varargs=str) @typed_kwargs('compiler.has_multi_link_argument', _HAS_REQUIRED_KW) + @InterpreterObject.method('has_multi_link_arguments') def has_multi_link_arguments_method(self, args: T.Tuple[T.List[str]], kwargs: 'HasArgumentKW') -> bool: return self._has_argument_impl(args[0], mode=_TestMode.LINKER, kwargs=kwargs) @FeatureNew('compiler.get_supported_link_arguments', '0.46.0') @noKwargs @typed_pos_args('compiler.get_supported_link_arguments', varargs=str) + @InterpreterObject.method('get_supported_link_arguments') def get_supported_link_arguments_method(self, args: T.Tuple[T.List[str]], kwargs: 'TYPE_kwargs') -> T.List[str]: supported_args: T.List[str] = [] for arg in args[0]: @@ -835,9 +816,10 @@ class CompilerHolder(ObjectHolder['Compiler']): supported_args.append(arg) return supported_args - @FeatureNew('compiler.first_supported_link_argument_method', '0.46.0') + @FeatureNew('compiler.first_supported_link_argument', '0.46.0') @noKwargs @typed_pos_args('compiler.first_supported_link_argument', varargs=str) + @InterpreterObject.method('first_supported_link_argument') def first_supported_link_argument_method(self, args: T.Tuple[T.List[str]], kwargs: 'TYPE_kwargs') -> T.List[str]: for arg in args[0]: if self._has_argument_impl([arg], mode=_TestMode.LINKER): @@ -857,7 +839,7 @@ class CompilerHolder(ObjectHolder['Compiler']): logargs += ['skipped: feature', mlog.bold(feature), 'disabled'] mlog.log(*logargs) return False - had, cached = self.compiler.has_func_attribute(attr, self.environment) + had, cached = self.compiler.has_func_attribute(attr) if required and not had: logargs += ['not usable'] raise InterpreterException(*logargs) @@ -871,18 +853,21 @@ class CompilerHolder(ObjectHolder['Compiler']): @FeatureNew('compiler.has_function_attribute', '0.48.0') @typed_pos_args('compiler.has_function_attribute', str) @typed_kwargs('compiler.has_function_attribute', _HAS_REQUIRED_KW) + @InterpreterObject.method('has_function_attribute') def has_func_attribute_method(self, args: T.Tuple[str], kwargs: 'HasArgumentKW') -> bool: return self._has_function_attribute_impl(args[0], kwargs) @FeatureNew('compiler.get_supported_function_attributes', '0.48.0') @noKwargs @typed_pos_args('compiler.get_supported_function_attributes', varargs=str) + @InterpreterObject.method('get_supported_function_attributes') def get_supported_function_attributes_method(self, args: T.Tuple[T.List[str]], kwargs: 'TYPE_kwargs') -> T.List[str]: return [a for a in args[0] if self._has_function_attribute_impl(a)] - @FeatureNew('compiler.get_argument_syntax_method', '0.49.0') + @FeatureNew('compiler.get_argument_syntax', '0.49.0') @noPosargs @noKwargs + @InterpreterObject.method('get_argument_syntax') def get_argument_syntax_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> str: return self.compiler.get_argument_syntax() @@ -893,10 +878,11 @@ class CompilerHolder(ObjectHolder['Compiler']): 'compiler.preprocess', KwargInfo('output', str, default='@PLAINNAME@.i'), KwargInfo('compile_args', ContainerTypeInfo(list, str), listify=True, default=[]), - _INCLUDE_DIRS_KW, + INCLUDE_DIRECTORIES, _DEPENDENCIES_KW.evolve(since='1.1.0'), _DEPENDS_KW.evolve(since='1.4.0'), ) + @InterpreterObject.method('preprocess') def preprocess_method(self, args: T.Tuple[T.List['mesonlib.FileOrString']], kwargs: 'PreprocessKW') -> T.List[build.CustomTargetIndex]: compiler = self.compiler.get_preprocessor() _sources: T.List[mesonlib.File] = self.interpreter.source_strings_to_files(args[0]) @@ -920,7 +906,7 @@ class CompilerHolder(ObjectHolder['Compiler']): compiler, self.interpreter.backend, kwargs['compile_args'], - kwargs['include_directories'], + self.interpreter.extract_incdirs(kwargs, strings_since='1.10.0'), kwargs['dependencies'], kwargs['depends']) self.interpreter.add_target(tg.name, tg) diff --git a/mesonbuild/interpreter/dependencyfallbacks.py b/mesonbuild/interpreter/dependencyfallbacks.py index 0ebfe3b..baf5ea3 100644 --- a/mesonbuild/interpreter/dependencyfallbacks.py +++ b/mesonbuild/interpreter/dependencyfallbacks.py @@ -4,14 +4,11 @@ from __future__ import annotations -import copy - -from .interpreterobjects import extract_required_kwarg from .. import mlog from .. import dependencies from .. import build from ..wrap import WrapMode -from ..mesonlib import extract_as_list, stringlistify, version_compare_many, listify +from ..mesonlib import stringlistify, version_compare_many, MachineChoice from ..options import OptionKey from ..dependencies import Dependency, DependencyException, NotFoundDependency from ..interpreterbase import (MesonInterpreterObject, FeatureNew, @@ -19,17 +16,22 @@ from ..interpreterbase import (MesonInterpreterObject, FeatureNew, import typing as T if T.TYPE_CHECKING: + from typing_extensions import TypeAlias from .interpreter import Interpreter - from ..interpreterbase import TYPE_nkwargs, TYPE_nvar + from .kwargs import DoSubproject + from ..dependencies.base import DependencyObjectKWs + from ..options import ElementaryOptionValues, OptionDict from .interpreterobjects import SubprojectHolder + CandidateType: TypeAlias = T.Tuple[T.Callable[[DependencyObjectKWs, str, DoSubproject], T.Optional[Dependency]], str] + class DependencyFallbacksHolder(MesonInterpreterObject): def __init__(self, interpreter: 'Interpreter', names: T.List[str], allow_fallback: T.Optional[bool] = None, - default_options: T.Optional[T.Dict[str, str]] = None) -> None: + default_options: T.Optional[T.Dict[OptionKey, ElementaryOptionValues]] = None) -> None: super().__init__(subproject=interpreter.subproject) self.interpreter = interpreter self.subproject = interpreter.subproject @@ -40,7 +42,7 @@ class DependencyFallbacksHolder(MesonInterpreterObject): self.allow_fallback = allow_fallback self.subproject_name: T.Optional[str] = None self.subproject_varname: T.Optional[str] = None - self.subproject_kwargs = {'default_options': default_options or {}} + self.default_options = default_options or {} self.names: T.List[str] = [] self.forcefallback: bool = False self.nofallback: bool = False @@ -80,36 +82,33 @@ class DependencyFallbacksHolder(MesonInterpreterObject): self.subproject_name = subp_name self.subproject_varname = varname - def _do_dependency_cache(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_kwargs: TYPE_nkwargs) -> T.Optional[Dependency]: - name = func_args[0] + def _do_dependency_cache(self, kwargs: DependencyObjectKWs, name: str, func_kwargs: DoSubproject) -> T.Optional[Dependency]: cached_dep = self._get_cached_dep(name, kwargs) if cached_dep: self._verify_fallback_consistency(cached_dep) return cached_dep - def _do_dependency(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_kwargs: TYPE_nkwargs) -> T.Optional[Dependency]: + def _do_dependency(self, kwargs: DependencyObjectKWs, name: str, func_kwargs: DoSubproject) -> T.Optional[Dependency]: # Note that there is no df.dependency() method, this is called for names # given as positional arguments to dependency_fallbacks(name1, ...). # We use kwargs from the dependency() function, for things like version, # module, etc. - name = func_args[0] self._handle_featurenew_dependencies(name) dep = dependencies.find_external_dependency(name, self.environment, kwargs) if dep.found(): - for_machine = self.interpreter.machine_from_native_kwarg(kwargs) + for_machine = kwargs.get('native', MachineChoice.HOST) identifier = dependencies.get_dep_identifier(name, kwargs) self.coredata.deps[for_machine].put(identifier, dep) return dep return None - def _do_existing_subproject(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_kwargs: TYPE_nkwargs) -> T.Optional[Dependency]: - subp_name = func_args[0] + def _do_existing_subproject(self, kwargs: DependencyObjectKWs, subp_name: str, func_kwargs: DoSubproject) -> T.Optional[Dependency]: varname = self.subproject_varname if subp_name and self._get_subproject(subp_name): return self._get_subproject_dep(subp_name, varname, kwargs) return None - def _do_subproject(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_kwargs: TYPE_nkwargs) -> T.Optional[Dependency]: + def _do_subproject(self, kwargs: DependencyObjectKWs, name: str, func_kwargs: DoSubproject) -> T.Optional[Dependency]: if self.forcefallback: mlog.log('Looking for a fallback subproject for the dependency', mlog.bold(self._display_name), 'because:\nUse of fallback dependencies is forced.') @@ -124,21 +123,16 @@ class DependencyFallbacksHolder(MesonInterpreterObject): # dependency('foo', static: true) should implicitly add # default_options: ['default_library=static'] static = kwargs.get('static') - default_options = func_kwargs.get('default_options', {}) - if static is not None and 'default_library' not in default_options: + forced_options: OptionDict = {} + if static is not None: default_library = 'static' if static else 'shared' mlog.log(f'Building fallback subproject with default_library={default_library}') - default_options = copy.copy(default_options) - default_options['default_library'] = default_library - func_kwargs['default_options'] = default_options + forced_options[OptionKey('default_library')] = default_library # Configure the subproject subp_name = self.subproject_name varname = self.subproject_varname - func_kwargs.setdefault('version', []) - if 'default_options' in kwargs and isinstance(kwargs['default_options'], str): - func_kwargs['default_options'] = listify(kwargs['default_options']) - self.interpreter.do_subproject(subp_name, func_kwargs) + self.interpreter.do_subproject(subp_name, func_kwargs, forced_options=forced_options) return self._get_subproject_dep(subp_name, varname, kwargs) def _get_subproject(self, subp_name: str) -> T.Optional[SubprojectHolder]: @@ -147,7 +141,7 @@ class DependencyFallbacksHolder(MesonInterpreterObject): return sub return None - def _get_subproject_dep(self, subp_name: str, varname: str, kwargs: TYPE_nkwargs) -> T.Optional[Dependency]: + def _get_subproject_dep(self, subp_name: str, varname: str, kwargs: DependencyObjectKWs) -> T.Optional[Dependency]: # Verify the subproject is found subproject = self._get_subproject(subp_name) if not subproject: @@ -210,12 +204,12 @@ class DependencyFallbacksHolder(MesonInterpreterObject): mlog.normal_cyan(found) if found else None) return var_dep - def _get_cached_dep(self, name: str, kwargs: TYPE_nkwargs) -> T.Optional[Dependency]: + def _get_cached_dep(self, name: str, kwargs: DependencyObjectKWs) -> T.Optional[Dependency]: # Unlike other methods, this one returns not-found dependency instead # of None in the case the dependency is cached as not-found, or if cached # version does not match. In that case we don't want to continue with # other candidates. - for_machine = self.interpreter.machine_from_native_kwarg(kwargs) + for_machine = kwargs.get('native', MachineChoice.HOST) identifier = dependencies.get_dep_identifier(name, kwargs) wanted_vers = stringlistify(kwargs.get('version', [])) @@ -298,35 +292,34 @@ class DependencyFallbacksHolder(MesonInterpreterObject): return True return not (found == 'undefined' or not version_compare_many(found, wanted)[0]) - def _get_candidates(self) -> T.List[T.Tuple[T.Callable[[TYPE_nkwargs, TYPE_nvar, TYPE_nkwargs], T.Optional[Dependency]], TYPE_nvar, TYPE_nkwargs]]: - candidates = [] + def _get_candidates(self) -> T.List[CandidateType]: + candidates: T.List[CandidateType] = [] # 1. check if any of the names is cached already. for name in self.names: - candidates.append((self._do_dependency_cache, [name], {})) + candidates.append((self._do_dependency_cache, name)) # 2. check if the subproject fallback has already been configured. if self.subproject_name: - candidates.append((self._do_existing_subproject, [self.subproject_name], self.subproject_kwargs)) + candidates.append((self._do_existing_subproject, self.subproject_name)) # 3. check external dependency if we are not forced to use subproject if not self.forcefallback or not self.subproject_name: for name in self.names: - candidates.append((self._do_dependency, [name], {})) + candidates.append((self._do_dependency, name)) # 4. configure the subproject if self.subproject_name: - candidates.append((self._do_subproject, [self.subproject_name], self.subproject_kwargs)) + candidates.append((self._do_subproject, self.subproject_name)) return candidates - def lookup(self, kwargs: TYPE_nkwargs, force_fallback: bool = False) -> Dependency: - mods = extract_as_list(kwargs, 'modules') + def lookup(self, kwargs: DependencyObjectKWs, force_fallback: bool = False) -> Dependency: + mods = kwargs.get('modules', []) if mods: self._display_name += ' (modules: {})'.format(', '.join(str(i) for i in mods)) - disabled, required, feature = extract_required_kwarg(kwargs, self.subproject) - if disabled: - mlog.log('Dependency', mlog.bold(self._display_name), 'skipped: feature', mlog.bold(feature), 'disabled') - return self._notfound_dependency() + required = kwargs.get('required', True) # Check if usage of the subproject fallback is forced - wrap_mode = WrapMode.from_string(self.coredata.optstore.get_value_for(OptionKey('wrap_mode'))) + _wm = self.coredata.optstore.get_value_for(OptionKey('wrap_mode')) + assert isinstance(_wm, str), 'for mypy' + wrap_mode = WrapMode.from_string(_wm) force_fallback_for = self.coredata.optstore.get_value_for(OptionKey('force_fallback_for')) assert isinstance(force_fallback_for, list), 'for mypy' self.nofallback = wrap_mode == WrapMode.nofallback @@ -359,15 +352,21 @@ class DependencyFallbacksHolder(MesonInterpreterObject): # Try all candidates, only the last one is really required. last = len(candidates) - 1 for i, item in enumerate(candidates): - func, func_args, func_kwargs = item - func_kwargs['required'] = required and (i == last) + func, name = item kwargs['required'] = required and (i == last) - dep = func(kwargs, func_args, func_kwargs) + func_kwargs: DoSubproject = { + 'required': kwargs['required'], + 'cmake_options': [], + 'default_options': self.default_options, + 'options': None, + 'version': [], + } + dep = func(kwargs, name, func_kwargs) if dep and dep.found(): # Override this dependency to have consistent results in subsequent # dependency lookups. for name in self.names: - for_machine = self.interpreter.machine_from_native_kwarg(kwargs) + for_machine = kwargs.get('native', MachineChoice.HOST) identifier = dependencies.get_dep_identifier(name, kwargs) if identifier not in self.build.dependency_overrides[for_machine]: self.build.dependency_overrides[for_machine][identifier] = \ diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index abdc889..e3d321e 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -54,7 +54,7 @@ from .type_checking import ( CT_BUILD_BY_DEFAULT, CT_INPUT_KW, CT_INSTALL_DIR_KW, - _EXCLUSIVE_EXECUTABLE_KWS, + EXCLUSIVE_EXECUTABLE_KWS, EXECUTABLE_KWS, JAR_KWS, LIBRARY_KWS, @@ -62,6 +62,7 @@ from .type_checking import ( OUTPUT_KW, DEFAULT_OPTIONS, DEPENDENCIES_KW, + DEPENDENCY_KWS, DEPENDS_KW, DEPEND_FILES_KW, DEPFILE_KW, @@ -108,24 +109,25 @@ import typing as T import textwrap import importlib import copy +import itertools if T.TYPE_CHECKING: from typing_extensions import Literal + from .. import cargo from . import kwargs as kwtypes from ..backend.backends import Backend from ..interpreterbase.baseobjects import InterpreterObject, TYPE_var, TYPE_kwargs + from ..options import OptionDict from ..programs import OverrideProgram from .type_checking import SourcesVarargsType # Input source types passed to Targets - SourceInputs = T.Union[mesonlib.File, build.GeneratedList, build.BuildTarget, build.BothLibraries, - build.CustomTargetIndex, build.CustomTarget, build.GeneratedList, - build.ExtractedObjects, str] + SourceInputs = T.Union[mesonlib.FileOrString, build.GeneratedTypes, build.BuildTarget, + build.BothLibraries, build.ExtractedObjects] # Input source types passed to the build.Target classes - SourceOutputs = T.Union[mesonlib.File, build.GeneratedList, - build.BuildTarget, build.CustomTargetIndex, build.CustomTarget, - build.ExtractedObjects, build.GeneratedList, build.StructuredSources] + SourceOutputs = T.Union[mesonlib.File, build.GeneratedTypes, build.BuildTarget, + build.ExtractedObjects, build.StructuredSources] BuildTargetSource = T.Union[mesonlib.FileOrString, build.GeneratedTypes, build.StructuredSources] @@ -234,28 +236,7 @@ class InterpreterRuleRelaxation(Enum): ''' ALLOW_BUILD_DIR_FILE_REFERENCES = 1 - -permitted_dependency_kwargs = { - 'allow_fallback', - 'cmake_args', - 'cmake_module_path', - 'cmake_package_version', - 'components', - 'default_options', - 'fallback', - 'include_type', - 'language', - 'main', - 'method', - 'modules', - 'native', - 'not_found_message', - 'optional_modules', - 'private_headers', - 'required', - 'static', - 'version', -} + CARGO_SUBDIR = 2 implicit_check_false_warning = """You should add the boolean check kwarg to the run_command call. It currently defaults to false, @@ -270,15 +251,17 @@ class Interpreter(InterpreterBase, HoldableObject): subproject: str = '', subdir: str = '', subproject_dir: str = 'subprojects', - default_project_options: T.Optional[T.Dict[OptionKey, str]] = None, + invoker_method_default_options: T.Optional[OptionDict] = None, ast: T.Optional[mparser.CodeBlockNode] = None, relaxations: T.Optional[T.Set[InterpreterRuleRelaxation]] = None, user_defined_options: T.Optional[coredata.SharedCMDOptions] = None, + cargo: T.Optional[cargo.Interpreter] = None, ) -> None: super().__init__(_build.environment.get_source_dir(), subdir, subproject, subproject_dir, _build.environment) self.active_projectname = '' self.build = _build self.backend = backend + self.cargo = cargo self.summary: T.Dict[str, 'Summary'] = {} self.modules: T.Dict[str, NewExtensionModule] = {} self.relaxations = relaxations or set() @@ -295,13 +278,12 @@ class Interpreter(InterpreterBase, HoldableObject): self.subproject_stack: T.List[str] = [] self.configure_file_outputs: T.Dict[str, int] = {} # Passed from the outside, only used in subprojects. - if default_project_options: - self.default_project_options = default_project_options if isinstance(default_project_options, str) else default_project_options.copy() - if isinstance(default_project_options, dict): - pass + if invoker_method_default_options: + assert isinstance(invoker_method_default_options, dict) + self.invoker_method_default_options = invoker_method_default_options else: - self.default_project_options = {} - self.project_default_options: T.List[str] = [] + self.invoker_method_default_options = {} + self.project_default_options: OptionDict = {} self.build_func_dict() self.build_holder_map() self.user_defined_options = user_defined_options @@ -312,12 +294,23 @@ class Interpreter(InterpreterBase, HoldableObject): def __getnewargs_ex__(self) -> T.Tuple[T.Tuple[object], T.Dict[str, object]]: raise MesonBugException('This class is unpicklable') + def load_root_cargo_lock_file(self) -> None: + cargo_lock = os.path.join(self.source_root, self.subdir, 'Cargo.lock') + if not os.path.isfile(cargo_lock): + return + from .. import cargo + try: + self.cargo = cargo.Interpreter(self.environment, self.subdir, self.subproject_dir) + except cargo.TomlImplementationMissing as e: + # error delayed to actual usage of a Cargo subproject + mlog.warning(f'cannot load Cargo.lock: {e}', fatal=False) + def _redetect_machines(self) -> None: # Re-initialize machine descriptions. We can do a better job now because we # have the compilers needed to gain more knowledge, so wipe out old # inference and start over. machines = self.build.environment.machines.miss_defaulting() - machines.build = environment.detect_machine_info(self.coredata.compilers.build) + machines.build = envconfig.detect_machine_info(self.coredata.compilers.build) self.build.environment.machines = machines.default_missing() assert self.build.environment.machines.build.cpu is not None assert self.build.environment.machines.host.cpu is not None @@ -426,6 +419,7 @@ class Interpreter(InterpreterBase, HoldableObject): build.Generator: OBJ.GeneratorHolder, build.GeneratedList: OBJ.GeneratedListHolder, build.ExtractedObjects: OBJ.GeneratedObjectsHolder, + build.OverrideExecutable: OBJ.OverrideExecutableHolder, build.RunTarget: OBJ.RunTargetHolder, build.AliasTarget: OBJ.AliasTargetHolder, build.Headers: OBJ.HeadersHolder, @@ -521,8 +515,10 @@ class Interpreter(InterpreterBase, HoldableObject): if isinstance(val, mparser.StringNode): self.handle_meson_version(val.value, val) - def get_build_def_files(self) -> mesonlib.OrderedSet[str]: - return self.build_def_files + def get_build_def_files(self) -> T.List[str]: + if self.cargo: + self.build_def_files.update(self.cargo.get_build_def_files()) + return list(self.build_def_files) def add_build_def_file(self, f: mesonlib.FileOrString) -> None: # Use relative path for files within source directory, and absolute path @@ -575,8 +571,7 @@ class Interpreter(InterpreterBase, HoldableObject): continue if len(di) == 1: FeatureNew.single_use('stdlib without variable name', '0.56.0', self.subproject, location=self.current_node) - kwargs = {'native': for_machine is MachineChoice.BUILD, - } + kwargs: dependencies.base.DependencyObjectKWs = {'native': for_machine} name = l + '_stdlib' df = DependencyFallbacksHolder(self, [name]) df.set_fallback(di) @@ -690,7 +685,7 @@ class Interpreter(InterpreterBase, HoldableObject): def func_declare_dependency(self, node: mparser.BaseNode, args: T.List[TYPE_var], kwargs: kwtypes.FuncDeclareDependency) -> dependencies.Dependency: deps = kwargs['dependencies'] - incs = self.extract_incdirs(kwargs) + incs = self.extract_incdirs(kwargs, strings_since='0.50.0') libs = kwargs['link_with'] libs_whole = kwargs['link_whole'] objects = kwargs['objects'] @@ -868,17 +863,27 @@ class Interpreter(InterpreterBase, HoldableObject): self.subprojects[subp_name] = sub return sub - def do_subproject(self, subp_name: str, kwargs: kwtypes.DoSubproject, force_method: T.Optional[wrap.Method] = None) -> SubprojectHolder: + def do_subproject(self, subp_name: str, kwargs: kwtypes.DoSubproject, force_method: T.Optional[wrap.Method] = None, + forced_options: T.Optional[OptionDict] = None) -> SubprojectHolder: if subp_name == 'sub_static': pass disabled, required, feature = extract_required_kwarg(kwargs, self.subproject) if disabled: - assert feature, 'for mypy' mlog.log('Subproject', mlog.bold(subp_name), ':', 'skipped: feature', mlog.bold(feature), 'disabled') return self.disabled_subproject(subp_name, disabled_feature=feature) default_options = kwargs['default_options'] + # This in practice is only used for default_library. forced_options is the + # only case in which a meson.build file overrides the machine file or the + # command line. + if forced_options: + for k, v in forced_options.items(): + # FIXME: this should have no business poking at augments[], + # but set_option() does not do what we want + self.coredata.optstore.augments[k.evolve(subproject=subp_name)] = v + default_options = {**forced_options, **default_options} + if subp_name == '': raise InterpreterException('Subproject name must not be empty.') if subp_name[0] == '.': @@ -930,7 +935,8 @@ class Interpreter(InterpreterBase, HoldableObject): m += ['method', mlog.bold(method)] mlog.log(*m, '\n', nested=False) - methods_map: T.Dict[wrap.Method, T.Callable[[str, str, T.Dict[OptionKey, str, kwtypes.DoSubproject]], SubprojectHolder]] = { + methods_map: T.Dict[wrap.Method, T.Callable[[str, str, OptionDict, kwtypes.DoSubproject], + SubprojectHolder]] = { 'meson': self._do_subproject_meson, 'cmake': self._do_subproject_cmake, 'cargo': self._do_subproject_cargo, @@ -951,30 +957,34 @@ class Interpreter(InterpreterBase, HoldableObject): return self.disabled_subproject(subp_name, exception=e) raise e + def _save_ast(self, subdir: str, ast: mparser.CodeBlockNode) -> None: + # Debug print the generated meson file + from ..ast import AstIndentationGenerator, AstPrinter + printer = AstPrinter(update_ast_line_nos=True) + ast.accept(AstIndentationGenerator()) + ast.accept(printer) + printer.post_process() + meson_filename = os.path.join(self.build.environment.get_build_dir(), subdir, 'meson.build') + with open(meson_filename, "w", encoding='utf-8') as f: + f.write(printer.result) + mlog.log('Generated Meson AST:', meson_filename) + mlog.cmd_ci_include(meson_filename) + def _do_subproject_meson(self, subp_name: str, subdir: str, - default_options: T.List[str], + default_options: OptionDict, kwargs: kwtypes.DoSubproject, ast: T.Optional[mparser.CodeBlockNode] = None, build_def_files: T.Optional[T.List[str]] = None, - relaxations: T.Optional[T.Set[InterpreterRuleRelaxation]] = None) -> SubprojectHolder: + relaxations: T.Optional[T.Set[InterpreterRuleRelaxation]] = None, + cargo: T.Optional[cargo.Interpreter] = None) -> SubprojectHolder: with mlog.nested(subp_name): if ast: - # Debug print the generated meson file - from ..ast import AstIndentationGenerator, AstPrinter - printer = AstPrinter(update_ast_line_nos=True) - ast.accept(AstIndentationGenerator()) - ast.accept(printer) - printer.post_process() - meson_filename = os.path.join(self.build.environment.get_build_dir(), subdir, 'meson.build') - with open(meson_filename, "w", encoding='utf-8') as f: - f.write(printer.result) - mlog.log('Generated Meson AST:', meson_filename) - mlog.cmd_ci_include(meson_filename) - + self._save_ast(subdir, ast) new_build = self.build.copy() subi = Interpreter(new_build, self.backend, subp_name, subdir, self.subproject_dir, default_options, ast=ast, relaxations=relaxations, - user_defined_options=self.user_defined_options) + user_defined_options=self.user_defined_options, + cargo=cargo) # Those lists are shared by all interpreters. That means that # even if the subproject fails, any modification that the subproject # made to those lists will affect the parent project. @@ -1006,22 +1016,20 @@ class Interpreter(InterpreterBase, HoldableObject): if build_def_files: self.build_def_files.update(build_def_files) # We always need the subi.build_def_files, to propagate sub-sub-projects - self.build_def_files.update(subi.build_def_files) + self.build_def_files.update(subi.get_build_def_files()) self.build.merge(subi.build) self.build.subprojects[subp_name] = subi.project_version return self.subprojects[subp_name] def _do_subproject_cmake(self, subp_name: str, subdir: str, - default_options: T.List[str], + default_options: OptionDict, kwargs: kwtypes.DoSubproject) -> SubprojectHolder: from ..cmake import CMakeInterpreter with mlog.nested(subp_name): - prefix = self.coredata.optstore.get_value_for('prefix') - from ..modules.cmake import CMakeSubprojectOptions kw_opts = kwargs.get('options') or CMakeSubprojectOptions() cmake_options = kwargs.get('cmake_options', []) + kw_opts.cmake_options - cm_int = CMakeInterpreter(Path(subdir), Path(prefix), self.build.environment, self.backend) + cm_int = CMakeInterpreter(Path(subdir), self.build.environment, self.backend) cm_int.initialise(cmake_options) cm_int.analyse() @@ -1039,20 +1047,24 @@ class Interpreter(InterpreterBase, HoldableObject): return result def _do_subproject_cargo(self, subp_name: str, subdir: str, - default_options: T.List[str], + default_options: OptionDict, kwargs: kwtypes.DoSubproject) -> SubprojectHolder: from .. import cargo FeatureNew.single_use('Cargo subproject', '1.3.0', self.subproject, location=self.current_node) mlog.warning('Cargo subproject is an experimental feature and has no backwards compatibility guarantees.', once=True, location=self.current_node) - if self.environment.cargo is None: - self.environment.cargo = cargo.Interpreter(self.environment) + self.add_languages(['rust'], True, MachineChoice.HOST) with mlog.nested(subp_name): - ast = self.environment.cargo.interpret(subdir) + try: + cargo_int = self.cargo or cargo.Interpreter(self.environment, subdir, self.subproject_dir) + except cargo.TomlImplementationMissing as e: + raise MesonException(f'Failed to load Cargo.lock: {e!s}') + + ast = cargo_int.interpret(subdir) return self._do_subproject_meson( subp_name, subdir, default_options, kwargs, ast, - # FIXME: Are there other files used by cargo interpreter? - [os.path.join(subdir, 'Cargo.toml')]) + relaxations={InterpreterRuleRelaxation.CARGO_SUBDIR}, + cargo=cargo_int) @typed_pos_args('get_option', str) @noKwargs @@ -1067,35 +1079,28 @@ class Interpreter(InterpreterBase, HoldableObject): if optname_regex.search(optname.split('.', maxsplit=1)[-1]) is not None: raise InterpreterException(f'Invalid option name {optname!r}') - # Will be None only if the value comes from the default - value_object: T.Optional[options.AnyOptionType] + option_object: T.Optional[options.AnyOptionType] try: - optkey = options.OptionKey(optname, self.subproject) - value_object, value = self.coredata.optstore.get_value_object_and_value_for(optkey) + optkey = options.OptionKey.from_string(optname).evolve(subproject=self.subproject) + option_object, value = self.coredata.optstore.get_option_and_value_for(optkey) except KeyError: if self.coredata.optstore.is_base_option(optkey): # Due to backwards compatibility return the default # option for base options instead of erroring out. - # - # TODO: This will have issues if we expect to return a user FeatureOption - # Of course, there's a bit of a layering violation here in - # that we return a UserFeatureOption, but otherwise the held value - # We probably need a lower level feature thing, or an enum - # instead of strings - value = self.coredata.optstore.get_default_for_b_option(optkey) - value_object = None + option_object = options.COMPILER_BASE_OPTIONS[optkey.evolve(subproject=None, machine=MachineChoice.HOST)] + value = option_object.default else: if self.subproject: raise MesonException(f'Option {optname} does not exist for subproject {self.subproject}.') raise MesonException(f'Option {optname} does not exist.') - if isinstance(value_object, options.UserFeatureOption): - ocopy = copy.copy(value_object) + if isinstance(option_object, options.UserFeatureOption): + ocopy = copy.copy(option_object) ocopy.name = optname ocopy.value = value return ocopy elif optname == 'b_sanitize': - assert value_object is None or isinstance(value_object, options.UserStringArrayOption) + assert isinstance(option_object, options.UserStringArrayOption) # To ensure backwards compatibility this always returns a string. # We may eventually want to introduce a new "format" kwarg that # allows the user to modify this behaviour, but for now this is @@ -1131,10 +1136,10 @@ class Interpreter(InterpreterBase, HoldableObject): # Use of the '--genvslite vsxxxx' option ultimately overrides any '--backend xxx' # option the user may specify. backend_name = self.coredata.optstore.get_value_for(OptionKey('genvslite')) - self.backend = backends.get_genvslite_backend(backend_name, self.build, self) + self.backend = backends.get_genvslite_backend(backend_name, self.build) else: backend_name = self.coredata.optstore.get_value_for(OptionKey('backend')) - self.backend = backends.get_backend_from_name(backend_name, self.build, self) + self.backend = backends.get_backend_from_name(backend_name, self.build) if self.backend is None: raise InterpreterException(f'Unknown backend "{backend_name}".') @@ -1143,15 +1148,9 @@ class Interpreter(InterpreterBase, HoldableObject): mlog.log('Auto detected Visual Studio backend:', mlog.bold(self.backend.name)) if not self.environment.first_invocation: raise MesonBugException(f'Backend changed from {backend_name} to {self.backend.name}') - self.coredata.set_option(OptionKey('backend'), self.backend.name, first_invocation=True) + self.coredata.optstore.set_option(OptionKey('backend'), self.backend.name, first_invocation=True) - # Only init backend options on first invocation otherwise it would - # override values previously set from command line. - if self.environment.first_invocation: - self.coredata.init_backend_options(backend_name) - - options = {k: v for k, v in self.environment.options.items() if self.environment.coredata.optstore.is_backend_option(k)} - self.coredata.set_options(options) + self.environment.init_backend_options(backend_name) @typed_pos_args('project', str, varargs=str) @typed_kwargs( @@ -1184,20 +1183,17 @@ class Interpreter(InterpreterBase, HoldableObject): self._load_option_file() self.project_default_options = kwargs['default_options'] - if isinstance(self.project_default_options, str): - self.project_default_options = [self.project_default_options] - assert isinstance(self.project_default_options, (list, dict)) if self.environment.first_invocation or (self.subproject != '' and self.subproject not in self.coredata.initialized_subprojects): if self.subproject == '': self.coredata.optstore.initialize_from_top_level_project_call(self.project_default_options, self.user_defined_options.cmd_line_options, self.environment.options) else: - invoker_method_default_options = self.default_project_options self.coredata.optstore.initialize_from_subproject_call(self.subproject, - invoker_method_default_options, + self.invoker_method_default_options, self.project_default_options, - self.user_defined_options.cmd_line_options) + self.user_defined_options.cmd_line_options, + self.environment.options) self.coredata.initialized_subprojects.add(self.subproject) if not self.is_subproject(): @@ -1214,7 +1210,6 @@ class Interpreter(InterpreterBase, HoldableObject): self.set_backend() if not self.is_subproject(): - self.coredata.optstore.validate_cmd_line_options(self.user_defined_options.cmd_line_options) self.build.project_name = proj_name self.active_projectname = proj_name @@ -1280,6 +1275,9 @@ class Interpreter(InterpreterBase, HoldableObject): assert self.environment.wrap_resolver is not None, 'for mypy' self.environment.wrap_resolver.load_and_merge(subprojects_dir, self.subproject) + if self.cargo is None: + self.load_root_cargo_lock_file() + self.build.projects[self.subproject] = proj_name mlog.log('Project name:', mlog.bold(proj_name)) mlog.log('Project version:', mlog.bold(self.project_version)) @@ -1298,7 +1296,6 @@ class Interpreter(InterpreterBase, HoldableObject): native = kwargs['native'] if disabled: - assert feature, 'for mypy' for lang in sorted(langs, key=compilers.sort_clink): mlog.log('Compiler for language', mlog.bold(lang), 'skipped: feature', mlog.bold(feature), 'disabled') return False @@ -1469,8 +1466,6 @@ class Interpreter(InterpreterBase, HoldableObject): def add_languages(self, args: T.List[str], required: bool, for_machine: MachineChoice) -> bool: success = self.add_languages_for(args, required, for_machine) - if not self.coredata.is_cross_build(): - self.coredata.copy_build_options_from_regular_ones() self._redetect_machines() return success @@ -1529,7 +1524,7 @@ class Interpreter(InterpreterBase, HoldableObject): self.backend.allow_thin_archives[for_machine] = False else: # update new values from commandline, if it applies - self.coredata.process_compiler_options(lang, comp, self.environment, self.subproject) + self.coredata.process_compiler_options(lang, comp, self.subproject) if for_machine == MachineChoice.HOST or self.environment.is_cross_build(): logger_fun = mlog.log @@ -1590,7 +1585,7 @@ class Interpreter(InterpreterBase, HoldableObject): def program_from_overrides(self, command_names: T.List[mesonlib.FileOrString], extra_info: T.List['mlog.TV_Loggable'] - ) -> T.Optional[T.Union[ExternalProgram, OverrideProgram, build.Executable]]: + ) -> T.Optional[T.Union[ExternalProgram, OverrideProgram, build.OverrideExecutable]]: for name in command_names: if not isinstance(name, str): continue @@ -1605,7 +1600,7 @@ class Interpreter(InterpreterBase, HoldableObject): if isinstance(name, str): self.build.searched_programs.add(name) - def add_find_program_override(self, name: str, exe: T.Union[build.Executable, ExternalProgram, 'OverrideProgram']) -> None: + def add_find_program_override(self, name: str, exe: T.Union[build.OverrideExecutable, ExternalProgram, 'OverrideProgram']) -> None: if name in self.build.searched_programs: raise InterpreterException(f'Tried to override finding of executable "{name}" which has already been found.') if name in self.build.find_overrides: @@ -1624,13 +1619,13 @@ class Interpreter(InterpreterBase, HoldableObject): # the host machine. def find_program_impl(self, args: T.List[mesonlib.FileOrString], for_machine: MachineChoice = MachineChoice.HOST, - default_options: T.Optional[T.Dict[OptionKey, options.ElementaryOptionValues]] = None, + default_options: T.Optional[OptionDict] = None, required: bool = True, silent: bool = True, wanted: T.Union[str, T.List[str]] = '', search_dirs: T.Optional[T.List[str]] = None, version_arg: T.Optional[str] = '', version_func: T.Optional[ProgramVersionFunc] = None - ) -> T.Union['ExternalProgram', 'build.Executable', 'OverrideProgram']: + ) -> T.Union['ExternalProgram', 'build.OverrideExecutable', 'OverrideProgram']: args = mesonlib.listify(args) extra_info: T.List[mlog.TV_Loggable] = [] @@ -1655,7 +1650,7 @@ class Interpreter(InterpreterBase, HoldableObject): return progobj def program_lookup(self, args: T.List[mesonlib.FileOrString], for_machine: MachineChoice, - default_options: T.Optional[T.Dict[OptionKey, options.ElementaryOptionValues]], + default_options: T.Optional[OptionDict], required: bool, search_dirs: T.Optional[T.List[str]], wanted: T.Union[str, T.List[str]], @@ -1723,7 +1718,7 @@ class Interpreter(InterpreterBase, HoldableObject): return True def find_program_fallback(self, fallback: str, args: T.List[mesonlib.FileOrString], - default_options: T.Dict[OptionKey, options.ElementaryOptionValues], + default_options: OptionDict, required: bool, extra_info: T.List[mlog.TV_Loggable] ) -> T.Optional[T.Union[ExternalProgram, build.Executable, OverrideProgram]]: mlog.log('Fallback to subproject', mlog.bold(fallback), 'which provides program', @@ -1755,7 +1750,6 @@ class Interpreter(InterpreterBase, HoldableObject): ) -> T.Union['build.Executable', ExternalProgram, 'OverrideProgram']: disabled, required, feature = extract_required_kwarg(kwargs, self.subproject) if disabled: - assert feature, 'for mypy' mlog.log('Program', mlog.bold(' '.join(args[0])), 'skipped: feature', mlog.bold(feature), 'disabled') return self.notfound_program(args[0]) @@ -1766,34 +1760,32 @@ class Interpreter(InterpreterBase, HoldableObject): search_dirs=search_dirs) # When adding kwargs, please check if they make sense in dependencies.get_dep_identifier() - @FeatureNewKwargs('dependency', '0.57.0', ['cmake_package_version']) - @FeatureNewKwargs('dependency', '0.56.0', ['allow_fallback']) - @FeatureNewKwargs('dependency', '0.54.0', ['components']) - @FeatureNewKwargs('dependency', '0.52.0', ['include_type']) - @FeatureNewKwargs('dependency', '0.50.0', ['not_found_message', 'cmake_module_path', 'cmake_args']) - @FeatureNewKwargs('dependency', '0.49.0', ['disabler']) - @FeatureNewKwargs('dependency', '0.40.0', ['method']) - @disablerIfNotFound - @permittedKwargs(permitted_dependency_kwargs) @typed_pos_args('dependency', varargs=str, min_varargs=1) - @typed_kwargs('dependency', DEFAULT_OPTIONS.evolve(since='0.38.0'), allow_unknown=True) - def func_dependency(self, node: mparser.BaseNode, args: T.Tuple[T.List[str]], kwargs) -> Dependency: + @typed_kwargs('dependency', *DEPENDENCY_KWS) + @disablerIfNotFound + def func_dependency(self, node: mparser.BaseNode, args: T.Tuple[T.List[str]], kwargs: kwtypes.FuncDependency) -> Dependency: # Replace '' by empty list of names names = [n for n in args[0] if n] if len(names) > 1: FeatureNew('dependency with more than one name', '0.60.0').use(self.subproject) - allow_fallback = kwargs.get('allow_fallback') - if allow_fallback is not None and not isinstance(allow_fallback, bool): - raise InvalidArguments('"allow_fallback" argument must be boolean') - fallback = kwargs.get('fallback') default_options = kwargs.get('default_options') - df = DependencyFallbacksHolder(self, names, allow_fallback, default_options) - df.set_fallback(fallback) - not_found_message = kwargs.get('not_found_message', '') - if not isinstance(not_found_message, str): - raise InvalidArguments('The not_found_message must be a string.') + df = DependencyFallbacksHolder(self, names, kwargs['allow_fallback'], default_options) + df.set_fallback(kwargs['fallback']) + not_found_message = kwargs['not_found_message'] + + disabled, required, feature = extract_required_kwarg(kwargs, self.subproject) + if disabled: + name = names[0] + if kwargs['modules']: + name = name + '(modules: {})'.format(', '.join(kwargs['modules'])) + mlog.log('Dependency', mlog.bold(name), 'skipped: feature', mlog.bold(feature), 'disabled') + return dependencies.NotFoundDependency(names[0], self.environment) + + nkwargs = T.cast('dependencies.base.DependencyObjectKWs', kwargs.copy()) + nkwargs['required'] = required # to replace a possible UserFeatureOption with a bool + try: - d = df.lookup(kwargs) + d = df.lookup(nkwargs) except Exception: if not_found_message: self.message_impl([not_found_message]) @@ -1802,10 +1794,8 @@ class Interpreter(InterpreterBase, HoldableObject): if not d.found() and not_found_message: self.message_impl([not_found_message]) # Ensure the correct include type - if 'include_type' in kwargs: + if kwargs['include_type'] != 'preserve': wanted = kwargs['include_type'] - if not isinstance(wanted, str): - raise InvalidArguments('The `include_type` kwarg must be a string') actual = d.get_include_type() if wanted != actual: mlog.debug(f'Current include type of {args[0]} is {actual}. Converting to requested {wanted}') @@ -1823,22 +1813,26 @@ class Interpreter(InterpreterBase, HoldableObject): def func_disabler(self, node, args, kwargs): return Disabler() - def _strip_exe_specific_kwargs(self, kwargs: kwtypes.Executable) -> kwtypes._BuildTarget: - kwargs = kwargs.copy() - for exe_kwarg in _EXCLUSIVE_EXECUTABLE_KWS: - del kwargs[exe_kwarg.name] - return kwargs + def _exe_to_shlib_kwargs(self, kwargs: kwtypes.Executable) -> kwtypes.SharedLibrary: + nkwargs = T.cast('kwtypes.SharedLibrary', kwargs.copy()) + for exe_kwarg in EXCLUSIVE_EXECUTABLE_KWS: + del nkwargs[exe_kwarg.name] # type: ignore[misc] + for sh_kwarg in SHARED_LIB_KWS: + nkwargs.setdefault(sh_kwarg.name, sh_kwarg.default) # type: ignore[misc] + nkwargs['rust_abi'] = None + nkwargs['rust_crate_type'] = 'cdylib' + return nkwargs @permittedKwargs(build.known_exe_kwargs) @typed_pos_args('executable', str, varargs=SOURCES_VARARGS) @typed_kwargs('executable', *EXECUTABLE_KWS, allow_unknown=True) def func_executable(self, node: mparser.BaseNode, args: T.Tuple[str, SourcesVarargsType], - kwargs: kwtypes.Executable) -> build.Executable: + kwargs: kwtypes.Executable) -> T.Union[build.Executable, build.SharedLibrary]: for_machine = kwargs['native'] m = self.environment.machines[for_machine] if m.is_android() and kwargs.get('android_exe_type') == 'application': - holder = self.build_target(node, args, self._strip_exe_specific_kwargs(kwargs), build.SharedLibrary) + holder = self.build_target(node, args, self._exe_to_shlib_kwargs(kwargs), build.SharedLibrary) holder.shared_library_only = True return holder return self.build_target(node, args, kwargs, build.Executable) @@ -2048,6 +2042,7 @@ class Interpreter(InterpreterBase, HoldableObject): KwargInfo('feed', bool, default=False, since='0.59.0'), KwargInfo('capture', bool, default=False), KwargInfo('console', bool, default=False, since='0.48.0'), + KwargInfo('build_subdir', str, default='', since='1.10.0'), ) def func_custom_target(self, node: mparser.FunctionNode, args: T.Tuple[str], kwargs: 'kwtypes.CustomTarget') -> build.CustomTarget: @@ -2138,7 +2133,8 @@ class Interpreter(InterpreterBase, HoldableObject): install_dir=kwargs['install_dir'], install_mode=install_mode, install_tag=kwargs['install_tag'], - backend=self.backend) + backend=self.backend, + build_subdir=kwargs['build_subdir']) self.add_target(tg.name, tg) return tg @@ -2158,6 +2154,10 @@ class Interpreter(InterpreterBase, HoldableObject): raise InterpreterException(f'Tried to use non-existing executable {i.name!r}') if isinstance(all_args[0], str): all_args[0] = self.find_program_impl([all_args[0]]) + + if '@DEPFILE@' in all_args: + raise InterpreterException('run_target does not have support for @DEPFILE@') + name = args[0] tg = build.RunTarget(name, all_args, kwargs['depends'], self.subdir, self.subproject, self.environment, kwargs['env']) @@ -2205,7 +2205,7 @@ class Interpreter(InterpreterBase, HoldableObject): if '@OUTPUT@' in o: raise InvalidArguments('Tried to use @OUTPUT@ in a rule with more than one output.') - return build.Generator(args[0], **kwargs) + return build.Generator(self.environment, args[0], **kwargs) @typed_pos_args('benchmark', str, (build.Executable, build.Jar, ExternalProgram, mesonlib.File, build.CustomTarget, build.CustomTargetIndex)) @typed_kwargs('benchmark', *TEST_KWS) @@ -2456,7 +2456,12 @@ class Interpreter(InterpreterBase, HoldableObject): os.makedirs(os.path.join(self.environment.build_dir, subdir), exist_ok=True) - if not self._evaluate_subdir(self.environment.get_source_dir(), subdir): + if InterpreterRuleRelaxation.CARGO_SUBDIR in self.relaxations and \ + os.path.exists(os.path.join(self.environment.get_source_dir(), subdir, 'Cargo.toml')): + codeblock = self.cargo.interpret(subdir, self.root_subdir) + self._save_ast(subdir, codeblock) + self._evaluate_codeblock(codeblock, subdir) + elif not self._evaluate_subdir(self.environment.get_source_dir(), subdir): buildfilename = os.path.join(subdir, environment.build_filename) raise InterpreterException(f"Nonexistent build file '{buildfilename!s}'") @@ -2583,6 +2588,13 @@ class Interpreter(InterpreterBase, HoldableObject): self.build.install_dirs.append(idir) return idir + def validate_build_subdir(self, build_subdir: str, target: str): + if build_subdir and build_subdir != '.': + if os.path.exists(os.path.join(self.source_root, self.subdir, build_subdir)): + raise InvalidArguments(f'Build subdir "{build_subdir}" in "{target}" exists in source tree.') + if '..' in build_subdir: + raise InvalidArguments(f'Build subdir "{build_subdir}" in "{target}" contains ..') + @noPosargs @typed_kwargs( 'configure_file', @@ -2619,6 +2631,7 @@ class Interpreter(InterpreterBase, HoldableObject): KwargInfo('output_format', str, default='c', since='0.47.0', since_values={'json': '1.3.0'}, validator=in_set_validator({'c', 'json', 'nasm'})), KwargInfo('macro_name', (str, NoneType), default=None, since='1.3.0'), + KwargInfo('build_subdir', str, default='', since='1.10.0'), ) def func_configure_file(self, node: mparser.BaseNode, args: T.List[TYPE_var], kwargs: kwtypes.ConfigureFile): @@ -2674,8 +2687,14 @@ class Interpreter(InterpreterBase, HoldableObject): mlog.warning('Output file', mlog.bold(ofile_rpath, True), 'for configure_file() at', current_call, 'overwrites configure_file() output at', first_call) else: self.configure_file_outputs[ofile_rpath] = self.current_node.lineno - (ofile_path, ofile_fname) = os.path.split(os.path.join(self.subdir, output)) + + # Validate build_subdir + build_subdir = kwargs['build_subdir'] + self.validate_build_subdir(build_subdir, output) + + (ofile_path, ofile_fname) = os.path.split(os.path.join(self.subdir, build_subdir, output)) ofile_abs = os.path.join(self.environment.build_dir, ofile_path, ofile_fname) + os.makedirs(os.path.split(ofile_abs)[0], exist_ok=True) # Perform the appropriate action if kwargs['configuration'] is not None: @@ -2691,7 +2710,6 @@ class Interpreter(InterpreterBase, HoldableObject): if len(inputs) > 1: raise InterpreterException('At most one input file can given in configuration mode') if inputs: - os.makedirs(os.path.join(self.environment.build_dir, self.subdir), exist_ok=True) file_encoding = kwargs['encoding'] missing_variables, confdata_useless = \ mesonlib.do_conf_file(inputs_abs[0], ofile_abs, conf, @@ -2774,12 +2792,12 @@ class Interpreter(InterpreterBase, HoldableObject): install_tag=install_tag, data_type='configure')) return mesonlib.File.from_built_file(self.subdir, output) - def extract_incdirs(self, kwargs, key: str = 'include_directories') -> T.List[build.IncludeDirs]: + def extract_incdirs(self, kwargs, key: str = 'include_directories', strings_since: T.Optional[str] = None) -> T.List[build.IncludeDirs]: prospectives = extract_as_list(kwargs, key) - if key == 'include_directories': + if strings_since: for i in prospectives: if isinstance(i, str): - FeatureNew.single_use('include_directories kwarg of type string', '0.50.0', self.subproject, + FeatureNew.single_use(f'{key} kwarg of type string', strings_since, self.subproject, f'Use include_directories({i!r}) instead', location=self.current_node) break @@ -3063,9 +3081,9 @@ class Interpreter(InterpreterBase, HoldableObject): return if OptionKey('b_sanitize') not in self.coredata.optstore: return - if (self.coredata.optstore.get_value('b_lundef') and - self.coredata.optstore.get_value('b_sanitize')): - value = self.coredata.optstore.get_value('b_sanitize') + if (self.coredata.optstore.get_value_for('b_lundef') and + self.coredata.optstore.get_value_for('b_sanitize')): + value = self.coredata.optstore.get_value_for('b_sanitize') mlog.warning(textwrap.dedent(f'''\ Trying to use {value} sanitizer on Clang with b_lundef. This will probably not work. @@ -3214,11 +3232,17 @@ class Interpreter(InterpreterBase, HoldableObject): To define a target that builds in that directory you must define it in the meson.build file in that directory. ''')) + + # Make sure build_subdir doesn't exist in the source tree and + # doesn't contain .. + build_subdir = tobj.get_build_subdir() + self.validate_build_subdir(build_subdir, name) + self.validate_forbidden_targets(name) # To permit an executable and a shared library to have the # same name, such as "foo.exe" and "libfoo.a". idname = tobj.get_id() - subdir = tobj.get_subdir() + subdir = tobj.get_builddir() namedir = (name, subdir) if idname in self.build.targets: @@ -3244,9 +3268,9 @@ class Interpreter(InterpreterBase, HoldableObject): def build_both_libraries(self, node: mparser.BaseNode, args: T.Tuple[str, SourcesVarargsType], kwargs: kwtypes.Library) -> build.BothLibraries: shared_lib = self.build_target(node, args, kwargs, build.SharedLibrary) static_lib = self.build_target(node, args, kwargs, build.StaticLibrary) - preferred_library = self.coredata.optstore.get_value_for(OptionKey('default_both_libraries')) + preferred_library = self.coredata.optstore.get_value_for(OptionKey('default_both_libraries', subproject=self.subproject)) if preferred_library == 'auto': - preferred_library = self.coredata.optstore.get_value_for(OptionKey('default_library')) + preferred_library = self.coredata.optstore.get_value_for(OptionKey('default_library', subproject=self.subproject)) if preferred_library == 'both': preferred_library = 'shared' @@ -3338,6 +3362,40 @@ class Interpreter(InterpreterBase, HoldableObject): d.extend(deps) kwargs['language_args'] = new_args + @staticmethod + def _handle_rust_abi(abi: T.Optional[Literal['c', 'rust']], + crate_type: T.Optional[build.RustCrateType], + default_rust_type: build.RustCrateType, + default_c_type: build.RustCrateType, typename: str, + extra_valid_types: T.Optional[T.Set[build.RustCrateType]] = None, + ) -> build.RustCrateType: + """Handle the interactions between the rust_abi and rust_crate_type keyword arguments. + + :param abi: Is this using Rust ABI or C ABI + :param crate_type: Is there an explicit crate type set + :param default_rust_type: The default crate type to use for Rust ABI + :param default_c_type: the default crate type to use for C ABI + :param typename: The name of the type this argument is for + :param extra_valid_types: additional valid crate types, defaults to None + :raises InvalidArguments: If the crate_type argument is set, but not valid + :raises InvalidArguments: If both crate_type and abi are set + :return: The finalized crate type + """ + if abi is not None: + if crate_type is not None: + raise InvalidArguments('rust_abi and rust_crate_type are mutually exclusive') + crate_type = default_rust_type if abi == 'rust' else default_c_type + elif crate_type is not None: + if crate_type == 'lib': + crate_type = default_rust_type + valid_types = {default_rust_type, default_c_type} | (extra_valid_types or set()) + if crate_type not in valid_types: + choices = ", " .join(f'"{c}"' for c in sorted(valid_types)) + raise InvalidArguments(f'Crate type for {typename} must be one of {choices}, but got "{crate_type}"') + else: + crate_type = default_rust_type + return crate_type + @T.overload def build_target(self, node: mparser.BaseNode, args: T.Tuple[str, SourcesVarargsType], kwargs: kwtypes.Executable, targetclass: T.Type[build.Executable]) -> build.Executable: ... @@ -3362,6 +3420,13 @@ class Interpreter(InterpreterBase, HoldableObject): kwargs: T.Union[kwtypes.Executable, kwtypes.StaticLibrary, kwtypes.SharedLibrary, kwtypes.SharedModule, kwtypes.Jar], targetclass: T.Type[T.Union[build.Executable, build.StaticLibrary, build.SharedModule, build.SharedLibrary, build.Jar]] ) -> T.Union[build.Executable, build.StaticLibrary, build.SharedModule, build.SharedLibrary, build.Jar]: + if targetclass not in {build.Executable, build.SharedLibrary, build.SharedModule, build.StaticLibrary, build.Jar}: + mlog.debug('Unknown target type:', str(targetclass)) + raise RuntimeError('Unreachable code') + + # Because who owns this isn't clear + kwargs = kwargs.copy() + name, sources = args for_machine = kwargs['native'] if kwargs.get('rust_crate_type') == 'proc-macro': @@ -3382,35 +3447,48 @@ class Interpreter(InterpreterBase, HoldableObject): # backwards compatibility anyway sources = [s for s in sources if not isinstance(s, (build.BuildTarget, build.ExtractedObjects))] - - # due to lack of type checking, these are "allowed" for legacy reasons - if not isinstance(kwargs['install'], bool): - FeatureBroken.single_use('install kwarg with non-boolean value', '1.3.0', self.subproject, - 'This was never intended to work, and is essentially the same as using `install: true` regardless of value.', - node) - sources = self.source_strings_to_files(sources) objs = kwargs['objects'] kwargs['dependencies'] = extract_as_list(kwargs, 'dependencies') - kwargs['extra_files'] = self.source_strings_to_files(kwargs['extra_files']) + # TODO: When we can do strings -> Files in the typed_kwargs validator, do this there too + kwargs['extra_files'] = mesonlib.unique_list(self.source_strings_to_files(kwargs['extra_files'])) self.check_sources_exist(os.path.join(self.source_root, self.subdir), sources) - if targetclass not in {build.Executable, build.SharedLibrary, build.SharedModule, build.StaticLibrary, build.Jar}: - mlog.debug('Unknown target type:', str(targetclass)) - raise RuntimeError('Unreachable code') self.__process_language_args(kwargs) if targetclass is build.StaticLibrary: + kwargs = T.cast('kwtypes.StaticLibrary', kwargs) for lang in compilers.all_languages - {'java'}: deps, args = self.__convert_file_args(kwargs.get(f'{lang}_static_args', [])) kwargs['language_args'][lang].extend(args) kwargs['depend_files'].extend(deps) + kwargs['rust_crate_type'] = self._handle_rust_abi( + kwargs['rust_abi'], kwargs['rust_crate_type'], 'rlib', 'staticlib', targetclass.typename) + elif targetclass is build.SharedLibrary: + kwargs = T.cast('kwtypes.SharedLibrary', kwargs) for lang in compilers.all_languages - {'java'}: deps, args = self.__convert_file_args(kwargs.get(f'{lang}_shared_args', [])) kwargs['language_args'][lang].extend(args) kwargs['depend_files'].extend(deps) + kwargs['rust_crate_type'] = self._handle_rust_abi( + kwargs['rust_abi'], kwargs['rust_crate_type'], 'dylib', 'cdylib', targetclass.typename, + extra_valid_types={'proc-macro'}) + + elif targetclass is build.Executable: + kwargs = T.cast('kwtypes.Executable', kwargs) + if kwargs['rust_crate_type'] not in {None, 'bin'}: + raise InvalidArguments('Crate type for executable must be "bin"') + kwargs['rust_crate_type'] = 'bin' + if targetclass is not build.Jar: self.check_for_jar_sources(sources, targetclass) kwargs['d_import_dirs'] = self.extract_incdirs(kwargs, 'd_import_dirs') + missing: T.List[str] = [] + for each in itertools.chain(kwargs['c_pch'] or [], kwargs['cpp_pch'] or []): + if each is not None: + if not os.path.isfile(os.path.join(self.environment.source_dir, self.subdir, each)): + missing.append(os.path.join(self.subdir, each)) + if missing: + raise InvalidArguments('The following PCH files do not exist: {}'.format(', '.join(missing))) # Filter out kwargs from other target types. For example 'soversion' # passed to library() when default_library == 'static'. @@ -3447,7 +3525,7 @@ class Interpreter(InterpreterBase, HoldableObject): node=node) outputs.update(o) - kwargs['include_directories'] = self.extract_incdirs(kwargs) + kwargs['include_directories'] = self.extract_incdirs(kwargs, strings_since='0.50.0') if targetclass is build.Executable: kwargs = T.cast('kwtypes.Executable', kwargs) @@ -3470,11 +3548,12 @@ class Interpreter(InterpreterBase, HoldableObject): elif kwargs['export_dynamic']: if kwargs['implib'] is False: raise InvalidArguments('"implib" keyword" must not be false if "export_dynamic" is set and not false.') - kwargs['implib'] = True if kwargs['export_dynamic'] is None: kwargs['export_dynamic'] = False - if kwargs['implib'] is None: - kwargs['implib'] = False + if isinstance(kwargs['implib'], bool): + kwargs['implib'] = None + + kwargs['install_tag'] = [kwargs['install_tag']] target = targetclass(name, self.subdir, self.subproject, for_machine, srcs, struct, objs, self.environment, self.compilers[for_machine], kwargs) @@ -3506,11 +3585,6 @@ class Interpreter(InterpreterBase, HoldableObject): elif isinstance(s, build.StructuredSources): self.check_for_jar_sources(s.as_list(), targetclass) - # Only permit object extraction from the same subproject - def validate_extraction(self, buildtarget: mesonlib.HoldableObject) -> None: - if self.subproject != buildtarget.subproject: - raise InterpreterException('Tried to extract objects from a different subproject.') - def is_subproject(self) -> bool: return self.subproject != '' diff --git a/mesonbuild/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py index a2fadbe..4dce3f5 100644 --- a/mesonbuild/interpreter/interpreterobjects.py +++ b/mesonbuild/interpreter/interpreterobjects.py @@ -15,7 +15,7 @@ from .. import mlog from ..modules import ModuleReturnValue, ModuleObject, ModuleState, ExtensionModule, NewExtensionModule from ..backend.backends import TestProtocol from ..interpreterbase import ( - ContainerTypeInfo, KwargInfo, MesonOperator, + ContainerTypeInfo, KwargInfo, InterpreterObject, MesonOperator, MesonInterpreterObject, ObjectHolder, MutableInterpreterObject, FeatureNew, FeatureDeprecated, typed_pos_args, typed_kwargs, typed_operator, @@ -31,11 +31,12 @@ import typing as T if T.TYPE_CHECKING: from . import kwargs from ..cmake.interpreter import CMakeInterpreter + from ..dependencies.base import IncludeType from ..envconfig import MachineInfo - from ..interpreterbase import FeatureCheckBase, InterpreterObject, SubProject, TYPE_var, TYPE_kwargs, TYPE_nvar, TYPE_nkwargs + from ..interpreterbase import FeatureCheckBase, SubProject, TYPE_var, TYPE_kwargs, TYPE_nvar, TYPE_nkwargs from .interpreter import Interpreter - from typing_extensions import TypedDict + from typing_extensions import Literal, TypedDict class EnvironmentSeparatorKW(TypedDict): @@ -51,18 +52,36 @@ _ERROR_MSG_KW: KwargInfo[T.Optional[str]] = KwargInfo('error_message', (str, Non def extract_required_kwarg(kwargs: 'kwargs.ExtractRequired', subproject: 'SubProject', feature_check: T.Optional[FeatureCheckBase] = None, - default: bool = True) -> T.Tuple[bool, bool, T.Optional[str]]: + default: bool = True + ) -> T.Union[T.Tuple[Literal[True], bool, str], + T.Tuple[Literal[False], bool, None]]: + """Check common keyword arguments for required status. + + This handles booleans vs feature option. + + :param kwargs: + keyword arguments from the Interpreter, containing a `required` argument + :param subproject: The subproject this is + :param feature_check: + A custom feature check for this use of `required` with a + `UserFeatureOption`, defaults to None. + :param default: + The default value is `required` is not set in `kwargs`, defaults to + True + :raises InterpreterException: If the type of `kwargs['required']` is invalid + :return: + a tuple of `disabled, required, feature_name`. If `disabled` is `True` + `feature_name` will be a string, otherwise it is `None` + """ val = kwargs.get('required', default) - disabled = False required = False - feature: T.Optional[str] = None if isinstance(val, options.UserFeatureOption): if not feature_check: feature_check = FeatureNew('User option "feature"', '0.47.0') feature_check.use(subproject) feature = val.name if val.is_disabled(): - disabled = True + return True, required, feature elif val.is_enabled(): required = True elif isinstance(val, bool): @@ -75,7 +94,7 @@ def extract_required_kwarg(kwargs: 'kwargs.ExtractRequired', # TODO: this should be removed, and those callers should learn about FeatureOptions kwargs['required'] = required - return disabled, required, feature + return False, required, None def extract_search_dirs(kwargs: 'kwargs.ExtractSearchDirs') -> T.List[str]: search_dirs_str = mesonlib.stringlistify(kwargs.get('dirs', [])) @@ -94,19 +113,9 @@ class FeatureOptionHolder(ObjectHolder[options.UserFeatureOption]): super().__init__(option, interpreter) if option and option.is_auto(): # TODO: we need to cast here because options is not a TypedDict - auto = T.cast('options.UserFeatureOption', self.env.coredata.optstore.get_value_object_for('auto_features')) + auto = T.cast('options.UserFeatureOption', self.env.coredata.optstore.resolve_option('auto_features')) self.held_object = copy.copy(auto) self.held_object.name = option.name - self.methods.update({'enabled': self.enabled_method, - 'disabled': self.disabled_method, - 'allowed': self.allowed_method, - 'auto': self.auto_method, - 'require': self.require_method, - 'disable_auto_if': self.disable_auto_if_method, - 'enable_auto_if': self.enable_auto_if_method, - 'disable_if': self.disable_if_method, - 'enable_if': self.enable_if_method, - }) @property def value(self) -> str: @@ -124,22 +133,26 @@ class FeatureOptionHolder(ObjectHolder[options.UserFeatureOption]): @noPosargs @noKwargs + @InterpreterObject.method('enabled') def enabled_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: return self.value == 'enabled' @noPosargs @noKwargs + @InterpreterObject.method('disabled') def disabled_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: return self.value == 'disabled' @noPosargs @noKwargs @FeatureNew('feature_option.allowed()', '0.59.0') + @InterpreterObject.method('allowed') def allowed_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: return self.value != 'disabled' @noPosargs @noKwargs + @InterpreterObject.method('auto') def auto_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: return self.value == 'auto' @@ -160,6 +173,7 @@ class FeatureOptionHolder(ObjectHolder[options.UserFeatureOption]): 'feature_option.require', _ERROR_MSG_KW, ) + @InterpreterObject.method('require') def require_method(self, args: T.Tuple[bool], kwargs: 'kwargs.FeatureOptionRequire') -> options.UserFeatureOption: return self._disable_if(not args[0], kwargs['error_message']) @@ -169,6 +183,7 @@ class FeatureOptionHolder(ObjectHolder[options.UserFeatureOption]): 'feature_option.disable_if', _ERROR_MSG_KW, ) + @InterpreterObject.method('disable_if') def disable_if_method(self, args: T.Tuple[bool], kwargs: 'kwargs.FeatureOptionRequire') -> options.UserFeatureOption: return self._disable_if(args[0], kwargs['error_message']) @@ -178,6 +193,7 @@ class FeatureOptionHolder(ObjectHolder[options.UserFeatureOption]): 'feature_option.enable_if', _ERROR_MSG_KW, ) + @InterpreterObject.method('enable_if') def enable_if_method(self, args: T.Tuple[bool], kwargs: 'kwargs.FeatureOptionRequire') -> options.UserFeatureOption: if not args[0]: return copy.deepcopy(self.held_object) @@ -192,12 +208,14 @@ class FeatureOptionHolder(ObjectHolder[options.UserFeatureOption]): @FeatureNew('feature_option.disable_auto_if()', '0.59.0') @noKwargs @typed_pos_args('feature_option.disable_auto_if', bool) + @InterpreterObject.method('disable_auto_if') def disable_auto_if_method(self, args: T.Tuple[bool], kwargs: TYPE_kwargs) -> options.UserFeatureOption: return copy.deepcopy(self.held_object) if self.value != 'auto' or not args[0] else self.as_disabled() @FeatureNew('feature_option.enable_auto_if()', '1.1.0') @noKwargs @typed_pos_args('feature_option.enable_auto_if', bool) + @InterpreterObject.method('enable_auto_if') def enable_auto_if_method(self, args: T.Tuple[bool], kwargs: TYPE_kwargs) -> options.UserFeatureOption: return self.as_enabled() if self.value == 'auto' and args[0] else copy.deepcopy(self.held_object) @@ -220,10 +238,6 @@ class RunProcess(MesonInterpreterObject): raise AssertionError('BUG: RunProcess must be passed an ExternalProgram') self.capture = capture self.returncode, self.stdout, self.stderr = self.run_command(cmd, args, env, source_dir, build_dir, subdir, mesonintrospect, in_builddir, check) - self.methods.update({'returncode': self.returncode_method, - 'stdout': self.stdout_method, - 'stderr': self.stderr_method, - }) def run_command(self, cmd: ExternalProgram, @@ -271,16 +285,19 @@ class RunProcess(MesonInterpreterObject): @noPosargs @noKwargs + @InterpreterObject.method('returncode') def returncode_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> int: return self.returncode @noPosargs @noKwargs + @InterpreterObject.method('stdout') def stdout_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self.stdout @noPosargs @noKwargs + @InterpreterObject.method('stderr') def stderr_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self.stderr @@ -288,11 +305,6 @@ class EnvironmentVariablesHolder(ObjectHolder[mesonlib.EnvironmentVariables], Mu def __init__(self, obj: mesonlib.EnvironmentVariables, interpreter: 'Interpreter'): super().__init__(obj, interpreter) - self.methods.update({'set': self.set_method, - 'unset': self.unset_method, - 'append': self.append_method, - 'prepend': self.prepend_method, - }) def __repr__(self) -> str: repr_str = "<{0}: {1}>" @@ -310,6 +322,7 @@ class EnvironmentVariablesHolder(ObjectHolder[mesonlib.EnvironmentVariables], Mu @typed_pos_args('environment.set', str, varargs=str, min_varargs=1) @typed_kwargs('environment.set', ENV_SEPARATOR_KW) + @InterpreterObject.method('set') def set_method(self, args: T.Tuple[str, T.List[str]], kwargs: 'EnvironmentSeparatorKW') -> None: name, values = args self.held_object.set(name, values, kwargs['separator']) @@ -317,11 +330,13 @@ class EnvironmentVariablesHolder(ObjectHolder[mesonlib.EnvironmentVariables], Mu @FeatureNew('environment.unset', '1.4.0') @typed_pos_args('environment.unset', str) @noKwargs + @InterpreterObject.method('unset') def unset_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> None: self.held_object.unset(args[0]) @typed_pos_args('environment.append', str, varargs=str, min_varargs=1) @typed_kwargs('environment.append', ENV_SEPARATOR_KW) + @InterpreterObject.method('append') def append_method(self, args: T.Tuple[str, T.List[str]], kwargs: 'EnvironmentSeparatorKW') -> None: name, values = args self.warn_if_has_name(name) @@ -329,6 +344,7 @@ class EnvironmentVariablesHolder(ObjectHolder[mesonlib.EnvironmentVariables], Mu @typed_pos_args('environment.prepend', str, varargs=str, min_varargs=1) @typed_kwargs('environment.prepend', ENV_SEPARATOR_KW) + @InterpreterObject.method('prepend') def prepend_method(self, args: T.Tuple[str, T.List[str]], kwargs: 'EnvironmentSeparatorKW') -> None: name, values = args self.warn_if_has_name(name) @@ -342,15 +358,6 @@ class ConfigurationDataHolder(ObjectHolder[build.ConfigurationData], MutableInte def __init__(self, obj: build.ConfigurationData, interpreter: 'Interpreter'): super().__init__(obj, interpreter) - self.methods.update({'set': self.set_method, - 'set10': self.set10_method, - '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, - }) def __deepcopy__(self, memo: T.Dict) -> 'ConfigurationDataHolder': return ConfigurationDataHolder(copy.deepcopy(self.held_object), self.interpreter) @@ -364,12 +371,14 @@ class ConfigurationDataHolder(ObjectHolder[build.ConfigurationData], MutableInte @typed_pos_args('configuration_data.set', str, (str, int, bool)) @typed_kwargs('configuration_data.set', _CONF_DATA_SET_KWS) + @InterpreterObject.method('set') def set_method(self, args: T.Tuple[str, T.Union[str, int, bool]], kwargs: 'kwargs.ConfigurationDataSet') -> None: self.__check_used() self.held_object.values[args[0]] = (args[1], kwargs['description']) @typed_pos_args('configuration_data.set_quoted', str, str) @typed_kwargs('configuration_data.set_quoted', _CONF_DATA_SET_KWS) + @InterpreterObject.method('set_quoted') def set_quoted_method(self, args: T.Tuple[str, str], kwargs: 'kwargs.ConfigurationDataSet') -> None: self.__check_used() escaped_val = '\\"'.join(args[1].split('"')) @@ -377,6 +386,7 @@ class ConfigurationDataHolder(ObjectHolder[build.ConfigurationData], MutableInte @typed_pos_args('configuration_data.set10', str, (int, bool)) @typed_kwargs('configuration_data.set10', _CONF_DATA_SET_KWS) + @InterpreterObject.method('set10') def set10_method(self, args: T.Tuple[str, T.Union[int, bool]], kwargs: 'kwargs.ConfigurationDataSet') -> None: self.__check_used() # bool is a subclass of int, so we need to check for bool explicitly. @@ -394,12 +404,14 @@ class ConfigurationDataHolder(ObjectHolder[build.ConfigurationData], MutableInte @typed_pos_args('configuration_data.has', (str, int, bool)) @noKwargs + @InterpreterObject.method('has') def has_method(self, args: T.Tuple[T.Union[str, int, bool]], kwargs: TYPE_kwargs) -> bool: return args[0] in self.held_object.values @FeatureNew('configuration_data.get()', '0.38.0') @typed_pos_args('configuration_data.get', str, optargs=[(str, int, bool)]) @noKwargs + @InterpreterObject.method('get') def get_method(self, args: T.Tuple[str, T.Optional[T.Union[str, int, bool]]], kwargs: TYPE_kwargs) -> T.Union[str, int, bool]: name = args[0] @@ -412,6 +424,7 @@ class ConfigurationDataHolder(ObjectHolder[build.ConfigurationData], MutableInte @FeatureNew('configuration_data.get_unquoted()', '0.44.0') @typed_pos_args('configuration_data.get_unquoted', str, optargs=[(str, int, bool)]) @noKwargs + @InterpreterObject.method('get_unquoted') def get_unquoted_method(self, args: T.Tuple[str, T.Optional[T.Union[str, int, bool]]], kwargs: TYPE_kwargs) -> T.Union[str, int, bool]: name = args[0] @@ -431,6 +444,7 @@ class ConfigurationDataHolder(ObjectHolder[build.ConfigurationData], MutableInte @FeatureNew('configuration_data.keys()', '0.57.0') @noPosargs @noKwargs + @InterpreterObject.method('keys') def keys_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.List[str]: return sorted(self.keys()) @@ -439,6 +453,7 @@ class ConfigurationDataHolder(ObjectHolder[build.ConfigurationData], MutableInte @typed_pos_args('configuration_data.merge_from', build.ConfigurationData) @noKwargs + @InterpreterObject.method('merge_from') def merge_from_method(self, args: T.Tuple[build.ConfigurationData], kwargs: TYPE_kwargs) -> None: from_object = args[0] self.held_object.values.update(from_object.values) @@ -455,31 +470,19 @@ _PARTIAL_DEP_KWARGS = [ class DependencyHolder(ObjectHolder[Dependency]): def __init__(self, dep: Dependency, interpreter: 'Interpreter'): super().__init__(dep, interpreter) - self.methods.update({'found': self.found_method, - 'type_name': self.type_name_method, - 'version': self.version_method, - 'name': self.name_method, - 'get_pkgconfig_variable': self.pkgconfig_method, - 'get_configtool_variable': self.configtool_method, - 'get_variable': self.variable_method, - 'partial_dependency': self.partial_dependency_method, - 'include_type': self.include_type_method, - 'as_system': self.as_system_method, - 'as_link_whole': self.as_link_whole_method, - 'as_static': self.as_static_method, - 'as_shared': self.as_shared_method, - }) def found(self) -> bool: return self.found_method([], {}) @noPosargs @noKwargs + @InterpreterObject.method('type_name') def type_name_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self.held_object.type_name @noPosargs @noKwargs + @InterpreterObject.method('found') def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: if self.held_object.type_name == 'internal': return True @@ -487,11 +490,13 @@ class DependencyHolder(ObjectHolder[Dependency]): @noPosargs @noKwargs + @InterpreterObject.method('version') def version_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self.held_object.get_version() @noPosargs @noKwargs + @InterpreterObject.method('name') def name_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self.held_object.get_name() @@ -503,6 +508,7 @@ class DependencyHolder(ObjectHolder[Dependency]): KwargInfo('default', str, default=''), PKGCONFIG_DEFINE_KW.evolve(name='define_variable') ) + @InterpreterObject.method('get_pkgconfig_variable') def pkgconfig_method(self, args: T.Tuple[str], kwargs: 'kwargs.DependencyPkgConfigVar') -> str: from ..dependencies.pkgconfig import PkgConfigDependency if not isinstance(self.held_object, PkgConfigDependency): @@ -521,6 +527,7 @@ class DependencyHolder(ObjectHolder[Dependency]): 'use dependency.get_variable(configtool : ...) instead') @noKwargs @typed_pos_args('dependency.get_config_tool_variable', str) + @InterpreterObject.method('get_configtool_variable') def configtool_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> str: from ..dependencies.configtool import ConfigToolDependency if not isinstance(self.held_object, ConfigToolDependency): @@ -533,6 +540,7 @@ class DependencyHolder(ObjectHolder[Dependency]): @FeatureNew('dependency.partial_dependency', '0.46.0') @noPosargs @typed_kwargs('dependency.partial_dependency', *_PARTIAL_DEP_KWARGS) + @InterpreterObject.method('partial_dependency') def partial_dependency_method(self, args: T.List[TYPE_nvar], kwargs: 'kwargs.DependencyMethodPartialDependency') -> Dependency: pdep = self.held_object.get_partial_dependency(**kwargs) return pdep @@ -549,6 +557,7 @@ class DependencyHolder(ObjectHolder[Dependency]): KwargInfo('default_value', (str, NoneType)), PKGCONFIG_DEFINE_KW, ) + @InterpreterObject.method('get_variable') def variable_method(self, args: T.Tuple[T.Optional[str]], kwargs: 'kwargs.DependencyGetVariable') -> str: default_varname = args[0] if default_varname is not None: @@ -570,18 +579,30 @@ class DependencyHolder(ObjectHolder[Dependency]): @FeatureNew('dependency.include_type', '0.52.0') @noPosargs @noKwargs + @InterpreterObject.method('include_type') def include_type_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self.held_object.get_include_type() @FeatureNew('dependency.as_system', '0.52.0') @noKwargs @typed_pos_args('dependency.as_system', optargs=[str]) + @InterpreterObject.method('as_system') def as_system_method(self, args: T.Tuple[T.Optional[str]], kwargs: TYPE_kwargs) -> Dependency: - return self.held_object.generate_system_dependency(args[0] or 'system') + include_type: IncludeType + if args[0] is None: + include_type = 'system' + elif args[0] not in {'preserve', 'system', 'non-system'}: + raise InvalidArguments( + 'Dependency.as_system: if an argument is given it must be one ' + f'of: "preserve", "system", "non-system", not: "{args[0]}"') + else: + include_type = T.cast('IncludeType', args[0]) + return self.held_object.generate_system_dependency(include_type) @FeatureNew('dependency.as_link_whole', '0.56.0') @noKwargs @noPosargs + @InterpreterObject.method('as_link_whole') def as_link_whole_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> Dependency: if not isinstance(self.held_object, InternalDependency): raise InterpreterException('as_link_whole method is only supported on declare_dependency() objects') @@ -594,6 +615,7 @@ class DependencyHolder(ObjectHolder[Dependency]): 'dependency.as_static', KwargInfo('recursive', bool, default=False), ) + @InterpreterObject.method('as_static') def as_static_method(self, args: T.List[TYPE_var], kwargs: InternalDependencyAsKW) -> Dependency: if not isinstance(self.held_object, InternalDependency): raise InterpreterException('as_static method is only supported on declare_dependency() objects') @@ -605,6 +627,7 @@ class DependencyHolder(ObjectHolder[Dependency]): 'dependency.as_shared', KwargInfo('recursive', bool, default=False), ) + @InterpreterObject.method('as_shared') def as_shared_method(self, args: T.List[TYPE_var], kwargs: InternalDependencyAsKW) -> Dependency: if not isinstance(self.held_object, InternalDependency): raise InterpreterException('as_shared method is only supported on declare_dependency() objects') @@ -615,13 +638,10 @@ _EXTPROG = T.TypeVar('_EXTPROG', bound=ExternalProgram) class _ExternalProgramHolder(ObjectHolder[_EXTPROG]): def __init__(self, ep: _EXTPROG, interpreter: 'Interpreter') -> None: super().__init__(ep, interpreter) - self.methods.update({'found': self.found_method, - 'path': self.path_method, - 'version': self.version_method, - 'full_path': self.full_path_method}) @noPosargs @noKwargs + @InterpreterObject.method('found') def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: return self.found() @@ -629,12 +649,14 @@ class _ExternalProgramHolder(ObjectHolder[_EXTPROG]): @noKwargs @FeatureDeprecated('ExternalProgram.path', '0.55.0', 'use ExternalProgram.full_path() instead') + @InterpreterObject.method('path') def path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self._full_path() @noPosargs @noKwargs @FeatureNew('ExternalProgram.full_path', '0.55.0') + @InterpreterObject.method('full_path') def full_path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self._full_path() @@ -647,7 +669,19 @@ class _ExternalProgramHolder(ObjectHolder[_EXTPROG]): @noPosargs @noKwargs + @FeatureNew('ExternalProgram.cmd_array', '1.10.0') + @InterpreterObject.method('cmd_array') + def cmd_array_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.List[str]: + if not self.found(): + raise InterpreterException('Unable to get the path of a not-found external program') + cmd = self.held_object.get_command() + assert cmd is not None + return cmd + + @noPosargs + @noKwargs @FeatureNew('ExternalProgram.version', '0.62.0') + @InterpreterObject.method('version') def version_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: if not self.found(): raise InterpreterException('Unable to get the version of a not-found external program') @@ -665,25 +699,23 @@ class ExternalProgramHolder(_ExternalProgramHolder[ExternalProgram]): class ExternalLibraryHolder(ObjectHolder[ExternalLibrary]): def __init__(self, el: ExternalLibrary, interpreter: 'Interpreter'): super().__init__(el, interpreter) - self.methods.update({'found': self.found_method, - 'type_name': self.type_name_method, - 'partial_dependency': self.partial_dependency_method, - 'name': self.name_method, - }) @noPosargs @noKwargs + @InterpreterObject.method('type_name') def type_name_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self.held_object.type_name @noPosargs @noKwargs + @InterpreterObject.method('found') def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: return self.held_object.found() @FeatureNew('dependency.partial_dependency', '0.46.0') @noPosargs @typed_kwargs('dependency.partial_dependency', *_PARTIAL_DEP_KWARGS) + @InterpreterObject.method('partial_dependency') def partial_dependency_method(self, args: T.List[TYPE_nvar], kwargs: 'kwargs.DependencyMethodPartialDependency') -> Dependency: pdep = self.held_object.get_partial_dependency(**kwargs) return pdep @@ -691,6 +723,7 @@ class ExternalLibraryHolder(ObjectHolder[ExternalLibrary]): @FeatureNew('dependency.name', '1.5.0') @noPosargs @noKwargs + @InterpreterObject.method('name') def name_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self.held_object.name @@ -699,36 +732,34 @@ class ExternalLibraryHolder(ObjectHolder[ExternalLibrary]): class MachineHolder(ObjectHolder['MachineInfo']): def __init__(self, machine_info: 'MachineInfo', interpreter: 'Interpreter'): super().__init__(machine_info, interpreter) - self.methods.update({'system': self.system_method, - 'cpu': self.cpu_method, - 'cpu_family': self.cpu_family_method, - 'endian': self.endian_method, - 'kernel': self.kernel_method, - 'subsystem': self.subsystem_method, - }) @noPosargs @noKwargs + @InterpreterObject.method('cpu_family') def cpu_family_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self.held_object.cpu_family @noPosargs @noKwargs + @InterpreterObject.method('cpu') def cpu_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self.held_object.cpu @noPosargs @noKwargs + @InterpreterObject.method('system') def system_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self.held_object.system @noPosargs @noKwargs + @InterpreterObject.method('endian') def endian_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self.held_object.endian @noPosargs @noKwargs + @InterpreterObject.method('kernel') def kernel_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: if self.held_object.kernel is not None: return self.held_object.kernel @@ -736,6 +767,7 @@ class MachineHolder(ObjectHolder['MachineInfo']): @noPosargs @noKwargs + @InterpreterObject.method('subsystem') def subsystem_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: if self.held_object.subsystem is not None: return self.held_object.subsystem @@ -748,12 +780,11 @@ class IncludeDirsHolder(ObjectHolder[build.IncludeDirs]): class FileHolder(ObjectHolder[mesonlib.File]): def __init__(self, file: mesonlib.File, interpreter: 'Interpreter'): super().__init__(file, interpreter) - self.methods.update({'full_path': self.full_path_method, - }) @noPosargs @noKwargs @FeatureNew('file.full_path', '1.4.0') + @InterpreterObject.method('full_path') def full_path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self.held_object.absolute_path(self.env.source_dir, self.env.build_dir) @@ -836,12 +867,10 @@ class SubprojectHolder(MesonInterpreterObject): self.subdir = PurePath(subdir).as_posix() self.cm_interpreter: T.Optional[CMakeInterpreter] = None self.callstack = callstack - self.methods.update({'get_variable': self.get_variable_method, - 'found': self.found_method, - }) @noPosargs @noKwargs + @InterpreterObject.method('found') def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: return self.found() @@ -863,6 +892,7 @@ class SubprojectHolder(MesonInterpreterObject): @noKwargs @typed_pos_args('subproject.get_variable', str, optargs=[object]) @noArgsFlattening + @InterpreterObject.method('get_variable') def get_variable_method(self, args: T.Tuple[str, T.Optional[str]], kwargs: TYPE_kwargs) -> T.Union[TYPE_var, InterpreterObject]: return self.get_variable(args, kwargs) @@ -905,16 +935,6 @@ _BuildTarget = T.TypeVar('_BuildTarget', bound=T.Union[build.BuildTarget, build. class BuildTargetHolder(ObjectHolder[_BuildTarget]): def __init__(self, target: _BuildTarget, interp: 'Interpreter'): super().__init__(target, interp) - self.methods.update({'extract_objects': self.extract_objects_method, - 'extract_all_objects': self.extract_all_objects_method, - 'name': self.name_method, - 'get_id': self.get_id_method, - 'outdir': self.outdir_method, - 'full_path': self.full_path_method, - 'path': self.path_method, - 'found': self.found_method, - 'private_dir_include': self.private_dir_include_method, - }) def __repr__(self) -> str: r = '<{} {}: {}>' @@ -934,6 +954,7 @@ class BuildTargetHolder(ObjectHolder[_BuildTarget]): @noPosargs @noKwargs + @InterpreterObject.method('found') def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: if not (isinstance(self.held_object, build.Executable) and self.held_object.was_returned_by_find_program): FeatureNew.single_use('BuildTarget.found', '0.59.0', subproject=self.held_object.subproject) @@ -941,28 +962,35 @@ class BuildTargetHolder(ObjectHolder[_BuildTarget]): @noPosargs @noKwargs + @InterpreterObject.method('private_dir_include') def private_dir_include_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> build.IncludeDirs: return build.IncludeDirs('', [], False, [self.interpreter.backend.get_target_private_dir(self._target_object)]) @noPosargs @noKwargs + @InterpreterObject.method('full_path') def full_path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self.interpreter.backend.get_target_filename_abs(self._target_object) @noPosargs @noKwargs @FeatureDeprecated('BuildTarget.path', '0.55.0', 'Use BuildTarget.full_path instead') + @InterpreterObject.method('path') def path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self.interpreter.backend.get_target_filename_abs(self._target_object) @noPosargs @noKwargs + @InterpreterObject.method('outdir') def outdir_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self.interpreter.backend.get_target_dir(self._target_object) @noKwargs @typed_pos_args('extract_objects', varargs=(mesonlib.File, str, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)) + @InterpreterObject.method('extract_objects') def extract_objects_method(self, args: T.Tuple[T.List[T.Union[mesonlib.FileOrString, 'build.GeneratedTypes']]], kwargs: TYPE_nkwargs) -> build.ExtractedObjects: + if self.subproject != self.held_object.subproject: + raise InterpreterException('Tried to extract objects from a different subproject.') tobj = self._target_object unity_value = self.interpreter.coredata.get_option_for_target(tobj, "unity") is_unity = (unity_value == 'on' or (unity_value == 'subprojects' and tobj.subproject != '')) @@ -981,6 +1009,7 @@ class BuildTargetHolder(ObjectHolder[_BuildTarget]): ''') ) ) + @InterpreterObject.method('extract_all_objects') def extract_all_objects_method(self, args: T.List[TYPE_nvar], kwargs: 'kwargs.BuildTargeMethodExtractAllObjects') -> build.ExtractedObjects: return self._target_object.extract_all_objects(kwargs['recursive']) @@ -989,15 +1018,54 @@ class BuildTargetHolder(ObjectHolder[_BuildTarget]): @FeatureDeprecated('BuildTarget.get_id', '1.2.0', 'This was never formally documented and does not seem to have a real world use. ' + 'See https://github.com/mesonbuild/meson/pull/6061') + @InterpreterObject.method('get_id') def get_id_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self._target_object.get_id() @FeatureNew('name', '0.54.0') @noPosargs @noKwargs + @InterpreterObject.method('name') def name_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self._target_object.name + @FeatureNew('vala_header', '1.10.0') + @noPosargs + @noKwargs + @InterpreterObject.method('vala_header') + def vala_header_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> mesonlib.File: + if self._target_object.vala_header is None: + raise mesonlib.MesonException("Attempted to get a Vala header from a target that doesn't generate one") + + assert self.interpreter.backend is not None, 'for mypy' + return mesonlib.File.from_built_file( + self.interpreter.backend.get_target_dir(self._target_object), self._target_object.vala_header) + + @FeatureNew('vala_vapi', '1.10.0') + @noPosargs + @noKwargs + @InterpreterObject.method('vala_vapi') + def vala_vapi_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> mesonlib.File: + if self._target_object.vala_vapi is None: + raise mesonlib.MesonException("Attempted to get a Vala VAPI from a target that doesn't generate one") + + assert self.interpreter.backend is not None, 'for mypy' + return mesonlib.File.from_built_file( + self.interpreter.backend.get_target_dir(self._target_object), self._target_object.vala_vapi) + + @FeatureNew('vala_gir', '1.10.0') + @noPosargs + @noKwargs + @InterpreterObject.method('vala_gir') + def vala_gir_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> mesonlib.File: + if self._target_object.vala_gir is None: + raise mesonlib.MesonException("Attempted to get a Vala GIR from a target that doesn't generate one") + + assert self.interpreter.backend is not None, 'for mypy' + return mesonlib.File.from_built_file( + self.interpreter.backend.get_target_dir(self._target_object), self._target_object.vala_gir) + + class ExecutableHolder(BuildTargetHolder[build.Executable]): pass @@ -1010,9 +1078,6 @@ class SharedLibraryHolder(BuildTargetHolder[build.SharedLibrary]): class BothLibrariesHolder(BuildTargetHolder[build.BothLibraries]): def __init__(self, libs: build.BothLibraries, interp: 'Interpreter'): super().__init__(libs, interp) - self.methods.update({'get_shared_lib': self.get_shared_lib_method, - 'get_static_lib': self.get_static_lib_method, - }) def __repr__(self) -> str: r = '<{} {}: {}, {}: {}>' @@ -1022,6 +1087,7 @@ class BothLibrariesHolder(BuildTargetHolder[build.BothLibraries]): @noPosargs @noKwargs + @InterpreterObject.method('get_shared_lib') def get_shared_lib_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> build.SharedLibrary: lib = copy.copy(self.held_object.shared) lib.both_lib = None @@ -1029,6 +1095,7 @@ class BothLibrariesHolder(BuildTargetHolder[build.BothLibraries]): @noPosargs @noKwargs + @InterpreterObject.method('get_static_lib') def get_static_lib_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> build.StaticLibrary: lib = copy.copy(self.held_object.static) lib.both_lib = None @@ -1043,12 +1110,11 @@ class JarHolder(BuildTargetHolder[build.Jar]): class CustomTargetIndexHolder(ObjectHolder[build.CustomTargetIndex]): def __init__(self, target: build.CustomTargetIndex, interp: 'Interpreter'): super().__init__(target, interp) - self.methods.update({'full_path': self.full_path_method, - }) @FeatureNew('custom_target[i].full_path', '0.54.0') @noPosargs @noKwargs + @InterpreterObject.method('full_path') def full_path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: assert self.interpreter.backend is not None return self.interpreter.backend.get_target_filename_abs(self.held_object) @@ -1058,13 +1124,6 @@ _CT = T.TypeVar('_CT', bound=build.CustomTarget) class _CustomTargetHolder(ObjectHolder[_CT]): def __init__(self, target: _CT, interp: 'Interpreter'): super().__init__(target, interp) - self.methods.update({'full_path': self.full_path_method, - 'to_list': self.to_list_method, - }) - - self.operators.update({ - MesonOperator.INDEX: self.op_index, - }) def __repr__(self) -> str: r = '<{} {}: {}>' @@ -1073,20 +1132,20 @@ class _CustomTargetHolder(ObjectHolder[_CT]): @noPosargs @noKwargs + @InterpreterObject.method('full_path') def full_path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self.interpreter.backend.get_target_filename_abs(self.held_object) @FeatureNew('custom_target.to_list', '0.54.0') @noPosargs @noKwargs + @InterpreterObject.method('to_list') def to_list_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.List[build.CustomTargetIndex]: - result = [] - for i in self.held_object: - result.append(i) - return result + return list(self.held_object) @noKwargs @typed_operator(MesonOperator.INDEX, int) + @InterpreterObject.operator(MesonOperator.INDEX) def op_index(self, other: int) -> build.CustomTargetIndex: try: return self.held_object[other] @@ -1108,7 +1167,6 @@ class GeneratedListHolder(ObjectHolder[build.GeneratedList]): class GeneratorHolder(ObjectHolder[build.Generator]): def __init__(self, gen: build.Generator, interpreter: 'Interpreter'): super().__init__(gen, interpreter) - self.methods.update({'process': self.process_method}) @typed_pos_args('generator.process', min_varargs=1, varargs=(str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)) @typed_kwargs( @@ -1117,6 +1175,7 @@ class GeneratorHolder(ObjectHolder[build.Generator]): KwargInfo('extra_args', ContainerTypeInfo(list, str), listify=True, default=[]), ENV_KW.evolve(since='1.3.0') ) + @InterpreterObject.method('process') def process_method(self, args: T.Tuple[T.List[T.Union[str, mesonlib.File, 'build.GeneratedTypes']]], kwargs: 'kwargs.GeneratorProcess') -> build.GeneratedList: @@ -1132,7 +1191,7 @@ class GeneratorHolder(ObjectHolder[build.Generator]): 'Calling generator.process with CustomTarget or Index of CustomTarget.', '0.57.0', self.interpreter.subproject) - gl = self.held_object.process_files(args[0], self.interpreter, + gl = self.held_object.process_files(args[0], self.interpreter.subdir, preserve_path_from, extra_args=kwargs['extra_args'], env=kwargs['env']) return gl @@ -1142,3 +1201,11 @@ class StructuredSourcesHolder(ObjectHolder[build.StructuredSources]): def __init__(self, sources: build.StructuredSources, interp: 'Interpreter'): super().__init__(sources, interp) + +class OverrideExecutableHolder(BuildTargetHolder[build.OverrideExecutable]): + @noPosargs + @noKwargs + @FeatureNew('OverrideExecutable.version', '1.9.0') + @InterpreterObject.method('version') + def version_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return self.held_object.get_version(self.interpreter) diff --git a/mesonbuild/interpreter/kwargs.py b/mesonbuild/interpreter/kwargs.py index fb34bbb..1e20642 100644 --- a/mesonbuild/interpreter/kwargs.py +++ b/mesonbuild/interpreter/kwargs.py @@ -12,15 +12,15 @@ from typing_extensions import TypedDict, Literal, Protocol, NotRequired from .. import build from .. import options from ..compilers import Compiler -from ..dependencies.base import Dependency +from ..dependencies.base import Dependency, DependencyMethods, IncludeType from ..mesonlib import EnvironmentVariables, MachineChoice, File, FileMode, FileOrString from ..options import OptionKey from ..modules.cmake import CMakeSubprojectOptions from ..programs import ExternalProgram from .type_checking import PkgConfigDefineType, SourcesVarargsType -if T.TYPE_CHECKING: - TestArgs = T.Union[str, File, build.Target, ExternalProgram] +TestArgs = T.Union[str, File, build.Target, ExternalProgram] +RustAbi = Literal['rust', 'c'] class FuncAddProjectArgs(TypedDict): @@ -181,17 +181,17 @@ class CustomTarget(TypedDict): build_always: bool build_always_stale: T.Optional[bool] build_by_default: T.Optional[bool] + build_subdir: str capture: bool - command: T.List[T.Union[str, build.BuildTarget, build.CustomTarget, - build.CustomTargetIndex, ExternalProgram, File]] + command: T.List[T.Union[str, build.BuildTargetTypes, ExternalProgram, File]] console: bool depend_files: T.List[FileOrString] depends: T.List[T.Union[build.BuildTarget, build.CustomTarget]] depfile: T.Optional[str] env: EnvironmentVariables feed: bool - input: T.List[T.Union[str, build.BuildTarget, build.CustomTarget, build.CustomTargetIndex, - build.ExtractedObjects, build.GeneratedList, ExternalProgram, File]] + input: T.List[T.Union[str, build.BuildTarget, build.GeneratedTypes, + build.ExtractedObjects, ExternalProgram, File]] install: bool install_dir: T.List[T.Union[str, T.Literal[False]]] install_mode: FileMode @@ -282,11 +282,10 @@ class ConfigurationDataSet(TypedDict): class VcsTag(TypedDict): - command: T.List[T.Union[str, build.BuildTarget, build.CustomTarget, - build.CustomTargetIndex, ExternalProgram, File]] + command: T.List[T.Union[str, build.GeneratedTypes, ExternalProgram, File]] fallback: T.Optional[str] - input: T.List[T.Union[str, build.BuildTarget, build.CustomTarget, build.CustomTargetIndex, - build.ExtractedObjects, build.GeneratedList, ExternalProgram, File]] + input: T.List[T.Union[str, build.BuildTarget, build.GeneratedTypes, + build.ExtractedObjects, ExternalProgram, File]] output: T.List[str] replace_string: str install: bool @@ -311,6 +310,7 @@ class ConfigureFile(TypedDict): input: T.List[FileOrString] configuration: T.Optional[T.Union[T.Dict[str, T.Union[str, int, bool]], build.ConfigurationData]] macro_name: T.Optional[str] + build_subdir: str class Subproject(ExtractRequired): @@ -321,7 +321,7 @@ class Subproject(ExtractRequired): class DoSubproject(ExtractRequired): - default_options: T.List[str] + default_options: T.Dict[OptionKey, options.ElementaryOptionValues] version: T.List[str] cmake_options: T.List[str] options: T.Optional[CMakeSubprojectOptions] @@ -341,17 +341,21 @@ class _BaseBuildTarget(TypedDict): gnu_symbol_visibility: str install: bool install_mode: FileMode + install_tag: T.Optional[str] install_rpath: str implicit_include_directories: bool - link_depends: T.List[T.Union[str, File, build.CustomTarget, build.CustomTargetIndex, build.BuildTarget]] + link_depends: T.List[T.Union[str, File, build.GeneratedTypes]] link_language: T.Optional[str] name_prefix: T.Optional[str] name_suffix: T.Optional[str] native: MachineChoice objects: T.List[build.ObjectTypes] - override_options: T.Dict[OptionKey, options.ElementaryOptionValues] + override_options: T.Dict[str, options.ElementaryOptionValues] depend_files: NotRequired[T.List[File]] resources: T.List[str] + vala_header: T.Optional[str] + vala_vapi: T.Optional[str] + vala_gir: T.Optional[str] class _BuildTarget(_BaseBuildTarget): @@ -362,8 +366,13 @@ class _BuildTarget(_BaseBuildTarget): d_import_dirs: T.List[T.Union[str, build.IncludeDirs]] d_module_versions: T.List[T.Union[str, int]] d_unittest: bool + rust_crate_type: T.Optional[Literal['bin', 'lib', 'rlib', 'dylib', 'cdylib', 'staticlib', 'proc-macro']] rust_dependency_map: T.Dict[str, str] + swift_interoperability_mode: Literal['c', 'cpp'] + swift_module_name: str sources: SourcesVarargsType + c_pch: T.List[str] + cpp_pch: T.List[str] c_args: T.List[str] cpp_args: T.List[str] cuda_args: T.List[str] @@ -382,7 +391,7 @@ class _BuildTarget(_BaseBuildTarget): class _LibraryMixin(TypedDict): - rust_abi: T.Optional[Literal['c', 'rust']] + rust_abi: T.Optional[RustAbi] class Executable(_BuildTarget): @@ -467,7 +476,7 @@ class Jar(_BaseBuildTarget): main_class: str java_resources: T.Optional[build.StructuredSources] - sources: T.Union[str, File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList, build.ExtractedObjects, build.BuildTarget] + sources: T.Union[str, File, build.GeneratedTypes, build.ExtractedObjects, build.BuildTarget] java_args: T.List[str] @@ -486,3 +495,25 @@ class FuncDeclareDependency(TypedDict): sources: T.List[T.Union[FileOrString, build.GeneratedTypes]] variables: T.Dict[str, str] version: T.Optional[str] + + +class FuncDependency(ExtractRequired): + + allow_fallback: T.Optional[bool] + cmake_args: T.List[str] + cmake_module_path: T.List[str] + cmake_package_version: str + components: T.List[str] + default_options: T.Dict[OptionKey, options.ElementaryOptionValues] + fallback: T.Union[str, T.List[str], None] + include_type: IncludeType + language: T.Optional[str] + main: bool + method: DependencyMethods + modules: T.List[str] + native: MachineChoice + not_found_message: str + optional_modules: T.List[str] + private_headers: bool + static: T.Optional[bool] + version: T.List[str] diff --git a/mesonbuild/interpreter/mesonmain.py b/mesonbuild/interpreter/mesonmain.py index 8ede691..7c3789f 100644 --- a/mesonbuild/interpreter/mesonmain.py +++ b/mesonbuild/interpreter/mesonmain.py @@ -9,16 +9,16 @@ import typing as T from .. import mesonlib from .. import dependencies -from .. import build -from .. import mlog, coredata +from .. import build, cmdline +from .. import mlog from ..mesonlib import MachineChoice from ..options import OptionKey from ..programs import OverrideProgram, ExternalProgram from ..interpreter.type_checking import ENV_KW, ENV_METHOD_KW, ENV_SEPARATOR_KW, env_convertor_with_method -from ..interpreterbase import (MesonInterpreterObject, FeatureNew, FeatureDeprecated, +from ..interpreterbase import (MesonInterpreterObject, FeatureNew, FeatureDeprecated, FeatureBroken, typed_pos_args, noArgsFlattening, noPosargs, noKwargs, - typed_kwargs, KwargInfo, InterpreterException) + typed_kwargs, KwargInfo, InterpreterException, InterpreterObject) from .primitives import MesonVersionString from .type_checking import NATIVE_KW, NoneType @@ -26,6 +26,7 @@ if T.TYPE_CHECKING: from typing_extensions import Literal, TypedDict from ..compilers import Compiler + from ..dependencies.base import DependencyObjectKWs from ..interpreterbase import TYPE_kwargs, TYPE_var from ..mesonlib import ExecutableSerialisation from .interpreter import Interpreter @@ -55,38 +56,6 @@ class MesonMain(MesonInterpreterObject): super().__init__(subproject=interpreter.subproject) self.build = build self.interpreter = interpreter - self.methods.update({'add_devenv': self.add_devenv_method, - 'add_dist_script': self.add_dist_script_method, - 'add_install_script': self.add_install_script_method, - 'add_postconf_script': self.add_postconf_script_method, - 'backend': self.backend_method, - 'build_options': self.build_options_method, - 'build_root': self.build_root_method, - 'can_run_host_binaries': self.can_run_host_binaries_method, - 'current_source_dir': self.current_source_dir_method, - 'current_build_dir': self.current_build_dir_method, - 'get_compiler': self.get_compiler_method, - 'get_cross_property': self.get_cross_property_method, - 'get_external_property': self.get_external_property_method, - 'global_build_root': self.global_build_root_method, - 'global_source_root': self.global_source_root_method, - 'has_exe_wrapper': self.has_exe_wrapper_method, - 'has_external_property': self.has_external_property_method, - 'install_dependency_manifest': self.install_dependency_manifest_method, - 'is_cross_build': self.is_cross_build_method, - 'is_subproject': self.is_subproject_method, - 'is_unity': self.is_unity_method, - 'override_dependency': self.override_dependency_method, - 'override_find_program': self.override_find_program_method, - 'project_build_root': self.project_build_root_method, - 'project_license': self.project_license_method, - 'project_license_files': self.project_license_files_method, - 'project_name': self.project_name_method, - 'project_source_root': self.project_source_root_method, - 'project_version': self.project_version_method, - 'source_root': self.source_root_method, - 'version': self.version_method, - }) def _find_source_script( self, name: str, prog: T.Union[str, mesonlib.File, build.Executable, ExternalProgram], @@ -157,10 +126,11 @@ class MesonMain(MesonInterpreterObject): KwargInfo('install_tag', (str, NoneType), since='0.60.0'), KwargInfo('dry_run', bool, default=False, since='1.1.0'), ) + @InterpreterObject.method('add_install_script') def add_install_script_method( self, args: T.Tuple[T.Union[str, mesonlib.File, build.Executable, ExternalProgram], - T.List[T.Union[str, mesonlib.File, build.BuildTarget, build.CustomTarget, build.CustomTargetIndex, ExternalProgram]]], + T.List[T.Union[str, mesonlib.File, build.BuildTargetTypes, ExternalProgram]]], kwargs: 'AddInstallScriptKW') -> None: script_args = self._process_script_args('add_install_script', args[1]) script = self._find_source_script('add_install_script', args[0], script_args) @@ -175,6 +145,7 @@ class MesonMain(MesonInterpreterObject): varargs=(str, mesonlib.File, ExternalProgram) ) @noKwargs + @InterpreterObject.method('add_postconf_script') def add_postconf_script_method( self, args: T.Tuple[T.Union[str, mesonlib.File, ExternalProgram], @@ -191,6 +162,7 @@ class MesonMain(MesonInterpreterObject): ) @noKwargs @FeatureNew('meson.add_dist_script', '0.48.0') + @InterpreterObject.method('add_dist_script') def add_dist_script_method( self, args: T.Tuple[T.Union[str, mesonlib.File, ExternalProgram], @@ -208,6 +180,7 @@ class MesonMain(MesonInterpreterObject): @noPosargs @noKwargs + @InterpreterObject.method('current_source_dir') def current_source_dir_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> str: src = self.interpreter.environment.source_dir sub = self.interpreter.subdir @@ -217,6 +190,7 @@ class MesonMain(MesonInterpreterObject): @noPosargs @noKwargs + @InterpreterObject.method('current_build_dir') def current_build_dir_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> str: src = self.interpreter.environment.build_dir sub = self.interpreter.subdir @@ -226,24 +200,28 @@ class MesonMain(MesonInterpreterObject): @noPosargs @noKwargs + @InterpreterObject.method('backend') def backend_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> str: return self.interpreter.backend.name @noPosargs @noKwargs @FeatureDeprecated('meson.source_root', '0.56.0', 'use meson.project_source_root() or meson.global_source_root() instead.') + @InterpreterObject.method('source_root') def source_root_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> str: return self.interpreter.environment.source_dir @noPosargs @noKwargs @FeatureDeprecated('meson.build_root', '0.56.0', 'use meson.project_build_root() or meson.global_build_root() instead.') + @InterpreterObject.method('build_root') def build_root_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> str: return self.interpreter.environment.build_dir @noPosargs @noKwargs @FeatureNew('meson.project_source_root', '0.56.0') + @InterpreterObject.method('project_source_root') def project_source_root_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> str: src = self.interpreter.environment.source_dir sub = self.interpreter.root_subdir @@ -254,6 +232,7 @@ class MesonMain(MesonInterpreterObject): @noPosargs @noKwargs @FeatureNew('meson.project_build_root', '0.56.0') + @InterpreterObject.method('project_build_root') def project_build_root_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> str: src = self.interpreter.environment.build_dir sub = self.interpreter.root_subdir @@ -264,24 +243,28 @@ class MesonMain(MesonInterpreterObject): @noPosargs @noKwargs @FeatureNew('meson.global_source_root', '0.58.0') + @InterpreterObject.method('global_source_root') def global_source_root_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> str: return self.interpreter.environment.source_dir @noPosargs @noKwargs @FeatureNew('meson.global_build_root', '0.58.0') + @InterpreterObject.method('global_build_root') def global_build_root_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> str: return self.interpreter.environment.build_dir @noPosargs @noKwargs @FeatureDeprecated('meson.has_exe_wrapper', '0.55.0', 'use meson.can_run_host_binaries instead.') + @InterpreterObject.method('has_exe_wrapper') def has_exe_wrapper_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> bool: return self._can_run_host_binaries_impl() @noPosargs @noKwargs @FeatureNew('meson.can_run_host_binaries', '0.55.0') + @InterpreterObject.method('can_run_host_binaries') def can_run_host_binaries_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> bool: return self._can_run_host_binaries_impl() @@ -294,39 +277,51 @@ class MesonMain(MesonInterpreterObject): @noPosargs @noKwargs + @InterpreterObject.method('is_cross_build') def is_cross_build_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> bool: return self.build.environment.is_cross_build() @typed_pos_args('meson.get_compiler', str) @typed_kwargs('meson.get_compiler', NATIVE_KW) + @InterpreterObject.method('get_compiler') def get_compiler_method(self, args: T.Tuple[str], kwargs: 'NativeKW') -> 'Compiler': - cname = args[0] + lang = args[0] for_machine = kwargs['native'] - clist = self.interpreter.coredata.compilers[for_machine] try: - return clist[cname] + return self.interpreter.compilers[for_machine][lang] except KeyError: - raise InterpreterException(f'Tried to access compiler for language "{cname}", not specified for {for_machine.get_lower_case_name()} machine.') + try: + comp = self.interpreter.coredata.compilers[for_machine][lang] + except KeyError: + raise InterpreterException(f'Tried to access compiler for language "{lang}", not specified for {for_machine.get_lower_case_name()} machine.') + + FeatureBroken.single_use('Using `meson.get_compiler()` for languages only initialized in another subproject', '1.11.0', self.subproject, + 'This is extremely fragile, as your project likely cannot be used outside of your environment.') + return comp @noPosargs @noKwargs + @InterpreterObject.method('is_unity') def is_unity_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> bool: optval = self.interpreter.environment.coredata.optstore.get_value_for(OptionKey('unity')) return optval == 'on' or (optval == 'subprojects' and self.interpreter.is_subproject()) @noPosargs @noKwargs + @InterpreterObject.method('is_subproject') def is_subproject_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> bool: return self.interpreter.is_subproject() @typed_pos_args('meson.install_dependency_manifest', str) @noKwargs + @InterpreterObject.method('install_dependency_manifest') def install_dependency_manifest_method(self, args: T.Tuple[str], kwargs: 'TYPE_kwargs') -> None: self.build.dep_manifest_name = args[0] @FeatureNew('meson.override_find_program', '0.46.0') @typed_pos_args('meson.override_find_program', str, (mesonlib.File, ExternalProgram, build.Executable)) @noKwargs + @InterpreterObject.method('override_find_program') def override_find_program_method(self, args: T.Tuple[str, T.Union[mesonlib.File, ExternalProgram, build.Executable]], kwargs: 'TYPE_kwargs') -> None: name, exe = args if isinstance(exe, mesonlib.File): @@ -335,6 +330,8 @@ class MesonMain(MesonInterpreterObject): if not os.path.exists(abspath): raise InterpreterException(f'Tried to override {name} with a file that does not exist.') exe = OverrideProgram(name, self.interpreter.project_version, command=[abspath]) + elif isinstance(exe, build.Executable): + exe = build.OverrideExecutable(exe, self.interpreter.project_version) self.interpreter.add_find_program_override(name, exe) @typed_kwargs( @@ -344,6 +341,7 @@ class MesonMain(MesonInterpreterObject): ) @typed_pos_args('meson.override_dependency', str, dependencies.Dependency) @FeatureNew('meson.override_dependency', '0.54.0') + @InterpreterObject.method('override_dependency') def override_dependency_method(self, args: T.Tuple[str, dependencies.Dependency], kwargs: 'FuncOverrideDependency') -> None: name, dep = args if not name: @@ -390,11 +388,8 @@ class MesonMain(MesonInterpreterObject): static: T.Optional[bool], permissive: bool = False) -> None: # We need the cast here as get_dep_identifier works on such a dict, # which FuncOverrideDependency is, but mypy can't figure that out - nkwargs = T.cast('T.Dict[str, T.Any]', kwargs.copy()) - if static is None: - del nkwargs['static'] - else: - nkwargs['static'] = static + nkwargs: DependencyObjectKWs = kwargs.copy() # type: ignore[assignment] + nkwargs['static'] = static identifier = dependencies.get_dep_identifier(name, nkwargs) for_machine = kwargs['native'] override = self.build.dependency_overrides[for_machine].get(identifier) @@ -409,28 +404,33 @@ class MesonMain(MesonInterpreterObject): @noPosargs @noKwargs + @InterpreterObject.method('project_version') def project_version_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> str: return self.build.dep_manifest[self.interpreter.active_projectname].version @FeatureNew('meson.project_license()', '0.45.0') @noPosargs @noKwargs + @InterpreterObject.method('project_license') def project_license_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> T.List[str]: return self.build.dep_manifest[self.interpreter.active_projectname].license @FeatureNew('meson.project_license_files()', '1.1.0') @noPosargs @noKwargs + @InterpreterObject.method('project_license_files') def project_license_files_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.List[mesonlib.File]: return [l[1] for l in self.build.dep_manifest[self.interpreter.active_projectname].license_files] @noPosargs @noKwargs + @InterpreterObject.method('version') def version_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> MesonVersionString: return MesonVersionString(self.interpreter.coredata.version) @noPosargs @noKwargs + @InterpreterObject.method('project_name') def project_name_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> str: return self.interpreter.active_projectname @@ -447,6 +447,7 @@ class MesonMain(MesonInterpreterObject): @FeatureDeprecated('meson.get_cross_property', '0.58.0', 'Use meson.get_external_property() instead') @typed_pos_args('meson.get_cross_property', str, optargs=[object]) @noKwargs + @InterpreterObject.method('get_cross_property') def get_cross_property_method(self, args: T.Tuple[str, T.Optional[object]], kwargs: 'TYPE_kwargs') -> object: propname, fallback = args return self.__get_external_property_impl(propname, fallback, MachineChoice.HOST) @@ -455,6 +456,7 @@ class MesonMain(MesonInterpreterObject): @FeatureNew('meson.get_external_property', '0.54.0') @typed_pos_args('meson.get_external_property', str, optargs=[object]) @typed_kwargs('meson.get_external_property', NATIVE_KW) + @InterpreterObject.method('get_external_property') def get_external_property_method(self, args: T.Tuple[str, T.Optional[object]], kwargs: 'NativeKW') -> object: propname, fallback = args return self.__get_external_property_impl(propname, fallback, kwargs['native']) @@ -462,6 +464,7 @@ class MesonMain(MesonInterpreterObject): @FeatureNew('meson.has_external_property', '0.58.0') @typed_pos_args('meson.has_external_property', str) @typed_kwargs('meson.has_external_property', NATIVE_KW) + @InterpreterObject.method('has_external_property') def has_external_property_method(self, args: T.Tuple[str], kwargs: 'NativeKW') -> bool: prop_name = args[0] return prop_name in self.interpreter.environment.properties[kwargs['native']] @@ -469,6 +472,7 @@ class MesonMain(MesonInterpreterObject): @FeatureNew('add_devenv', '0.58.0') @typed_kwargs('environment', ENV_METHOD_KW, ENV_SEPARATOR_KW.evolve(since='0.62.0')) @typed_pos_args('add_devenv', (str, list, dict, mesonlib.EnvironmentVariables)) + @InterpreterObject.method('add_devenv') def add_devenv_method(self, args: T.Tuple[T.Union[str, list, dict, mesonlib.EnvironmentVariables]], kwargs: 'AddDevenvKW') -> None: env = args[0] @@ -482,8 +486,9 @@ class MesonMain(MesonInterpreterObject): @noPosargs @noKwargs @FeatureNew('meson.build_options', '1.1.0') + @InterpreterObject.method('build_options') def build_options_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> str: options = self.interpreter.user_defined_options if options is None: return '' - return coredata.format_cmd_line_options(options) + return cmdline.format_cmd_line_options(options) diff --git a/mesonbuild/interpreter/primitives/array.py b/mesonbuild/interpreter/primitives/array.py index b42ddea..86c1ce2 100644 --- a/mesonbuild/interpreter/primitives/array.py +++ b/mesonbuild/interpreter/primitives/array.py @@ -5,13 +5,16 @@ from __future__ import annotations import typing as T from ...interpreterbase import ( - ObjectHolder, + InterpreterObject, IterableObject, + KwargInfo, MesonOperator, + ObjectHolder, typed_operator, noKwargs, noPosargs, noArgsFlattening, + typed_kwargs, typed_pos_args, FeatureNew, @@ -22,31 +25,16 @@ from ...interpreterbase import ( from ...mparser import PlusAssignmentNode if T.TYPE_CHECKING: - # Object holders need the actual interpreter - from ...interpreter import Interpreter from ...interpreterbase import TYPE_kwargs class ArrayHolder(ObjectHolder[T.List[TYPE_var]], IterableObject): - def __init__(self, obj: T.List[TYPE_var], interpreter: 'Interpreter') -> None: - super().__init__(obj, interpreter) - self.methods.update({ - 'contains': self.contains_method, - 'length': self.length_method, - 'get': self.get_method, - }) - - self.trivial_operators.update({ - MesonOperator.EQUALS: (list, lambda x: self.held_object == x), - MesonOperator.NOT_EQUALS: (list, lambda x: self.held_object != x), - MesonOperator.IN: (object, lambda x: x in self.held_object), - MesonOperator.NOT_IN: (object, lambda x: x not in self.held_object), - }) - - # Use actual methods for functions that require additional checks - self.operators.update({ - MesonOperator.PLUS: self.op_plus, - MesonOperator.INDEX: self.op_index, - }) + # Operators that only require type checks + TRIVIAL_OPERATORS = { + MesonOperator.EQUALS: (list, lambda obj, x: obj.held_object == x), + MesonOperator.NOT_EQUALS: (list, lambda obj, x: obj.held_object != x), + MesonOperator.IN: (object, lambda obj, x: x in obj.held_object), + MesonOperator.NOT_IN: (object, lambda obj, x: x not in obj.held_object), + } def display_name(self) -> str: return 'array' @@ -63,6 +51,7 @@ class ArrayHolder(ObjectHolder[T.List[TYPE_var]], IterableObject): @noArgsFlattening @noKwargs @typed_pos_args('array.contains', object) + @InterpreterObject.method('contains') def contains_method(self, args: T.Tuple[object], kwargs: TYPE_kwargs) -> bool: def check_contains(el: T.List[TYPE_var]) -> bool: for element in el: @@ -77,12 +66,14 @@ class ArrayHolder(ObjectHolder[T.List[TYPE_var]], IterableObject): @noKwargs @noPosargs + @InterpreterObject.method('length') def length_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> int: return len(self.held_object) @noArgsFlattening @noKwargs @typed_pos_args('array.get', int, optargs=[object]) + @InterpreterObject.method('get') def get_method(self, args: T.Tuple[int, T.Optional[TYPE_var]], kwargs: TYPE_kwargs) -> TYPE_var: index = args[0] if index < -len(self.held_object) or index >= len(self.held_object): @@ -91,7 +82,20 @@ class ArrayHolder(ObjectHolder[T.List[TYPE_var]], IterableObject): return args[1] return self.held_object[index] + @FeatureNew('array.slice', '1.10.0') + @typed_kwargs('array.slice', KwargInfo('step', int, default=1)) + @typed_pos_args('array.slice', optargs=[int, int]) + @InterpreterObject.method('slice') + def slice_method(self, args: T.Tuple[T.Optional[int], T.Optional[int]], kwargs: T.Dict[str, int]) -> TYPE_var: + start, stop = args + if start is not None and stop is None: + raise InvalidArguments('Providing only one positional slice argument is ambiguous.') + if kwargs['step'] == 0: + raise InvalidArguments('Slice step cannot be zero.') + return self.held_object[start:stop:kwargs['step']] + @typed_operator(MesonOperator.PLUS, object) + @InterpreterObject.operator(MesonOperator.PLUS) def op_plus(self, other: TYPE_var) -> T.List[TYPE_var]: if not isinstance(other, list): if not isinstance(self.current_node, PlusAssignmentNode): @@ -101,8 +105,23 @@ class ArrayHolder(ObjectHolder[T.List[TYPE_var]], IterableObject): return self.held_object + other @typed_operator(MesonOperator.INDEX, int) + @InterpreterObject.operator(MesonOperator.INDEX) def op_index(self, other: int) -> TYPE_var: try: return self.held_object[other] except IndexError: raise InvalidArguments(f'Index {other} out of bounds of array of size {len(self.held_object)}.') + + @noPosargs + @noKwargs + @FeatureNew('array.flatten', '1.9.0') + @InterpreterObject.method('flatten') + def flatten_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> TYPE_var: + def flatten(obj: TYPE_var) -> T.Iterable[TYPE_var]: + if isinstance(obj, list): + for o in obj: + yield from flatten(o) + else: + yield obj + + return list(flatten(self.held_object)) diff --git a/mesonbuild/interpreter/primitives/boolean.py b/mesonbuild/interpreter/primitives/boolean.py index 4b49caf..01521ac 100644 --- a/mesonbuild/interpreter/primitives/boolean.py +++ b/mesonbuild/interpreter/primitives/boolean.py @@ -1,10 +1,11 @@ # Copyright 2021 The Meson development team -# SPDX-license-identifier: Apache-2.0 +# SPDX-License-Identifier: Apache-2.0 from __future__ import annotations from ...interpreterbase import ( - ObjectHolder, + InterpreterObject, MesonOperator, + ObjectHolder, typed_pos_args, noKwargs, noPosargs, @@ -15,35 +16,28 @@ from ...interpreterbase import ( import typing as T if T.TYPE_CHECKING: - # Object holders need the actual interpreter - from ...interpreter import Interpreter from ...interpreterbase import TYPE_var, TYPE_kwargs class BooleanHolder(ObjectHolder[bool]): - def __init__(self, obj: bool, interpreter: 'Interpreter') -> None: - super().__init__(obj, interpreter) - self.methods.update({ - 'to_int': self.to_int_method, - 'to_string': self.to_string_method, - }) - - self.trivial_operators.update({ - MesonOperator.BOOL: (None, lambda x: self.held_object), - MesonOperator.NOT: (None, lambda x: not self.held_object), - MesonOperator.EQUALS: (bool, lambda x: self.held_object == x), - MesonOperator.NOT_EQUALS: (bool, lambda x: self.held_object != x), - }) + TRIVIAL_OPERATORS = { + MesonOperator.BOOL: (None, lambda obj, x: obj.held_object), + MesonOperator.NOT: (None, lambda obj, x: not obj.held_object), + MesonOperator.EQUALS: (bool, lambda obj, x: obj.held_object == x), + MesonOperator.NOT_EQUALS: (bool, lambda obj, x: obj.held_object != x), + } def display_name(self) -> str: return 'bool' @noKwargs @noPosargs + @InterpreterObject.method('to_int') def to_int_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> int: return 1 if self.held_object else 0 @noKwargs @typed_pos_args('bool.to_string', optargs=[str, str]) + @InterpreterObject.method('to_string') def to_string_method(self, args: T.Tuple[T.Optional[str], T.Optional[str]], kwargs: TYPE_kwargs) -> str: true_str = args[0] or 'true' false_str = args[1] or 'false' diff --git a/mesonbuild/interpreter/primitives/dict.py b/mesonbuild/interpreter/primitives/dict.py index ab4c15f..794c150 100644 --- a/mesonbuild/interpreter/primitives/dict.py +++ b/mesonbuild/interpreter/primitives/dict.py @@ -5,9 +5,11 @@ from __future__ import annotations import typing as T from ...interpreterbase import ( - ObjectHolder, + InterpreterObject, IterableObject, MesonOperator, + ObjectHolder, + FeatureNew, typed_operator, noKwargs, noPosargs, @@ -20,34 +22,20 @@ from ...interpreterbase import ( ) if T.TYPE_CHECKING: - # Object holders need the actual interpreter - from ...interpreter import Interpreter from ...interpreterbase import TYPE_kwargs class DictHolder(ObjectHolder[T.Dict[str, TYPE_var]], IterableObject): - def __init__(self, obj: T.Dict[str, TYPE_var], interpreter: 'Interpreter') -> None: - super().__init__(obj, interpreter) - self.methods.update({ - 'has_key': self.has_key_method, - 'keys': self.keys_method, - 'get': self.get_method, - }) - - self.trivial_operators.update({ - # Arithmetic - MesonOperator.PLUS: (dict, lambda x: {**self.held_object, **x}), - - # Comparison - MesonOperator.EQUALS: (dict, lambda x: self.held_object == x), - MesonOperator.NOT_EQUALS: (dict, lambda x: self.held_object != x), - MesonOperator.IN: (str, lambda x: x in self.held_object), - MesonOperator.NOT_IN: (str, lambda x: x not in self.held_object), - }) - - # Use actual methods for functions that require additional checks - self.operators.update({ - MesonOperator.INDEX: self.op_index, - }) + # Operators that only require type checks + TRIVIAL_OPERATORS = { + # Arithmetic + MesonOperator.PLUS: (dict, lambda obj, x: {**obj.held_object, **x}), + + # Comparison + MesonOperator.EQUALS: (dict, lambda obj, x: obj.held_object == x), + MesonOperator.NOT_EQUALS: (dict, lambda obj, x: obj.held_object != x), + MesonOperator.IN: (str, lambda obj, x: x in obj.held_object), + MesonOperator.NOT_IN: (str, lambda obj, x: x not in obj.held_object), + } def display_name(self) -> str: return 'dict' @@ -61,19 +49,32 @@ class DictHolder(ObjectHolder[T.Dict[str, TYPE_var]], IterableObject): def size(self) -> int: return len(self.held_object) + def _keys_getter(self) -> T.List[str]: + return sorted(self.held_object) + @noKwargs @typed_pos_args('dict.has_key', str) + @InterpreterObject.method('has_key') def has_key_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> bool: return args[0] in self.held_object @noKwargs @noPosargs + @InterpreterObject.method('keys') def keys_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.List[str]: - return sorted(self.held_object) + return self._keys_getter() + + @noKwargs + @noPosargs + @InterpreterObject.method('values') + @FeatureNew('dict.values', '1.10.0') + def values_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.List[TYPE_var]: + return [self.held_object[k] for k in self._keys_getter()] @noArgsFlattening @noKwargs @typed_pos_args('dict.get', str, optargs=[object]) + @InterpreterObject.method('get') def get_method(self, args: T.Tuple[str, T.Optional[TYPE_var]], kwargs: TYPE_kwargs) -> TYPE_var: if args[0] in self.held_object: return self.held_object[args[0]] @@ -82,6 +83,7 @@ class DictHolder(ObjectHolder[T.Dict[str, TYPE_var]], IterableObject): raise InvalidArguments(f'Key {args[0]!r} is not in the dictionary.') @typed_operator(MesonOperator.INDEX, str) + @InterpreterObject.operator(MesonOperator.INDEX) def op_index(self, other: str) -> TYPE_var: if other not in self.held_object: raise InvalidArguments(f'Key {other} is not in the dictionary.') diff --git a/mesonbuild/interpreter/primitives/integer.py b/mesonbuild/interpreter/primitives/integer.py index cdf2355..c59ea6e 100644 --- a/mesonbuild/interpreter/primitives/integer.py +++ b/mesonbuild/interpreter/primitives/integer.py @@ -3,47 +3,33 @@ from __future__ import annotations from ...interpreterbase import ( - FeatureBroken, InvalidArguments, MesonOperator, ObjectHolder, KwargInfo, + InterpreterObject, MesonOperator, ObjectHolder, + FeatureBroken, InvalidArguments, KwargInfo, noKwargs, noPosargs, typed_operator, typed_kwargs ) import typing as T if T.TYPE_CHECKING: - # Object holders need the actual interpreter - from ...interpreter import Interpreter from ...interpreterbase import TYPE_var, TYPE_kwargs class IntegerHolder(ObjectHolder[int]): - def __init__(self, obj: int, interpreter: 'Interpreter') -> None: - super().__init__(obj, interpreter) - self.methods.update({ - 'is_even': self.is_even_method, - 'is_odd': self.is_odd_method, - 'to_string': self.to_string_method, - }) + # Operators that only require type checks + TRIVIAL_OPERATORS = { + # Arithmetic + MesonOperator.UMINUS: (None, lambda obj, x: -obj.held_object), + MesonOperator.PLUS: (int, lambda obj, x: obj.held_object + x), + MesonOperator.MINUS: (int, lambda obj, x: obj.held_object - x), + MesonOperator.TIMES: (int, lambda obj, x: obj.held_object * x), - self.trivial_operators.update({ - # Arithmetic - MesonOperator.UMINUS: (None, lambda x: -self.held_object), - MesonOperator.PLUS: (int, lambda x: self.held_object + x), - MesonOperator.MINUS: (int, lambda x: self.held_object - x), - MesonOperator.TIMES: (int, lambda x: self.held_object * x), - - # Comparison - MesonOperator.EQUALS: (int, lambda x: self.held_object == x), - MesonOperator.NOT_EQUALS: (int, lambda x: self.held_object != x), - MesonOperator.GREATER: (int, lambda x: self.held_object > x), - MesonOperator.LESS: (int, lambda x: self.held_object < x), - MesonOperator.GREATER_EQUALS: (int, lambda x: self.held_object >= x), - MesonOperator.LESS_EQUALS: (int, lambda x: self.held_object <= x), - }) - - # Use actual methods for functions that require additional checks - self.operators.update({ - MesonOperator.DIV: self.op_div, - MesonOperator.MOD: self.op_mod, - }) + # Comparison + MesonOperator.EQUALS: (int, lambda obj, x: obj.held_object == x), + MesonOperator.NOT_EQUALS: (int, lambda obj, x: obj.held_object != x), + MesonOperator.GREATER: (int, lambda obj, x: obj.held_object > x), + MesonOperator.LESS: (int, lambda obj, x: obj.held_object < x), + MesonOperator.GREATER_EQUALS: (int, lambda obj, x: obj.held_object >= x), + MesonOperator.LESS_EQUALS: (int, lambda obj, x: obj.held_object <= x), + } def display_name(self) -> str: return 'int' @@ -57,11 +43,13 @@ class IntegerHolder(ObjectHolder[int]): @noKwargs @noPosargs + @InterpreterObject.method('is_even') def is_even_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: return self.held_object % 2 == 0 @noKwargs @noPosargs + @InterpreterObject.method('is_odd') def is_odd_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: return self.held_object % 2 != 0 @@ -70,16 +58,19 @@ class IntegerHolder(ObjectHolder[int]): KwargInfo('fill', int, default=0, since='1.3.0') ) @noPosargs + @InterpreterObject.method('to_string') def to_string_method(self, args: T.List[TYPE_var], kwargs: T.Dict[str, T.Any]) -> str: return str(self.held_object).zfill(kwargs['fill']) @typed_operator(MesonOperator.DIV, int) + @InterpreterObject.operator(MesonOperator.DIV) def op_div(self, other: int) -> int: if other == 0: raise InvalidArguments('Tried to divide by 0') return self.held_object // other @typed_operator(MesonOperator.MOD, int) + @InterpreterObject.operator(MesonOperator.MOD) def op_mod(self, other: int) -> int: if other == 0: raise InvalidArguments('Tried to divide by 0') diff --git a/mesonbuild/interpreter/primitives/range.py b/mesonbuild/interpreter/primitives/range.py index 23d5617..1aceb68 100644 --- a/mesonbuild/interpreter/primitives/range.py +++ b/mesonbuild/interpreter/primitives/range.py @@ -5,8 +5,9 @@ from __future__ import annotations import typing as T from ...interpreterbase import ( - MesonInterpreterObject, + InterpreterObject, IterableObject, + MesonInterpreterObject, MesonOperator, InvalidArguments, ) @@ -18,10 +19,8 @@ class RangeHolder(MesonInterpreterObject, IterableObject): def __init__(self, start: int, stop: int, step: int, *, subproject: 'SubProject') -> None: super().__init__(subproject=subproject) self.range = range(start, stop, step) - self.operators.update({ - MesonOperator.INDEX: self.op_index, - }) + @InterpreterObject.operator(MesonOperator.INDEX) def op_index(self, other: int) -> int: try: return self.range[other] diff --git a/mesonbuild/interpreter/primitives/string.py b/mesonbuild/interpreter/primitives/string.py index a224dfa..2adc58d 100644 --- a/mesonbuild/interpreter/primitives/string.py +++ b/mesonbuild/interpreter/primitives/string.py @@ -7,10 +7,12 @@ import os import typing as T -from ...mesonlib import version_compare, version_compare_many +from ... import mlog +from ...mesonlib import version_compare_many, underscorify from ...interpreterbase import ( - ObjectHolder, + InterpreterObject, MesonOperator, + ObjectHolder, FeatureNew, typed_operator, noArgsFlattening, @@ -24,73 +26,47 @@ from ...interpreterbase import ( if T.TYPE_CHECKING: - # Object holders need the actual interpreter - from ...interpreter import Interpreter from ...interpreterbase import TYPE_var, TYPE_kwargs class StringHolder(ObjectHolder[str]): - def __init__(self, obj: str, interpreter: 'Interpreter') -> None: - super().__init__(obj, interpreter) - self.methods.update({ - 'contains': self.contains_method, - 'startswith': self.startswith_method, - 'endswith': self.endswith_method, - 'format': self.format_method, - 'join': self.join_method, - 'replace': self.replace_method, - 'split': self.split_method, - 'splitlines': self.splitlines_method, - 'strip': self.strip_method, - 'substring': self.substring_method, - 'to_int': self.to_int_method, - 'to_lower': self.to_lower_method, - 'to_upper': self.to_upper_method, - 'underscorify': self.underscorify_method, - 'version_compare': self.version_compare_method, - }) - - self.trivial_operators.update({ - # Arithmetic - MesonOperator.PLUS: (str, lambda x: self.held_object + x), - - # Comparison - MesonOperator.EQUALS: (str, lambda x: self.held_object == x), - MesonOperator.NOT_EQUALS: (str, lambda x: self.held_object != x), - MesonOperator.GREATER: (str, lambda x: self.held_object > x), - MesonOperator.LESS: (str, lambda x: self.held_object < x), - MesonOperator.GREATER_EQUALS: (str, lambda x: self.held_object >= x), - MesonOperator.LESS_EQUALS: (str, lambda x: self.held_object <= x), - }) - - # Use actual methods for functions that require additional checks - self.operators.update({ - MesonOperator.DIV: self.op_div, - MesonOperator.INDEX: self.op_index, - MesonOperator.IN: self.op_in, - MesonOperator.NOT_IN: self.op_notin, - }) + TRIVIAL_OPERATORS = { + # Arithmetic + MesonOperator.PLUS: (str, lambda obj, x: obj.held_object + x), + + # Comparison + MesonOperator.EQUALS: (str, lambda obj, x: obj.held_object == x), + MesonOperator.NOT_EQUALS: (str, lambda obj, x: obj.held_object != x), + MesonOperator.GREATER: (str, lambda obj, x: obj.held_object > x), + MesonOperator.LESS: (str, lambda obj, x: obj.held_object < x), + MesonOperator.GREATER_EQUALS: (str, lambda obj, x: obj.held_object >= x), + MesonOperator.LESS_EQUALS: (str, lambda obj, x: obj.held_object <= x), + } def display_name(self) -> str: return 'str' @noKwargs @typed_pos_args('str.contains', str) + @InterpreterObject.method('contains') def contains_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> bool: return self.held_object.find(args[0]) >= 0 @noKwargs @typed_pos_args('str.startswith', str) + @InterpreterObject.method('startswith') def startswith_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> bool: return self.held_object.startswith(args[0]) @noKwargs @typed_pos_args('str.endswith', str) + @InterpreterObject.method('endswith') def endswith_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> bool: return self.held_object.endswith(args[0]) @noArgsFlattening @noKwargs @typed_pos_args('str.format', varargs=object) + @InterpreterObject.method('format') def format_method(self, args: T.Tuple[T.List[TYPE_var]], kwargs: TYPE_kwargs) -> str: arg_strings: T.List[str] = [] for arg in args[0]: @@ -111,27 +87,35 @@ class StringHolder(ObjectHolder[str]): @noKwargs @noPosargs @FeatureNew('str.splitlines', '1.2.0') + @InterpreterObject.method('splitlines') def splitlines_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.List[str]: return self.held_object.splitlines() @noKwargs @typed_pos_args('str.join', varargs=str) + @InterpreterObject.method('join') def join_method(self, args: T.Tuple[T.List[str]], kwargs: TYPE_kwargs) -> str: return self.held_object.join(args[0]) @noKwargs @FeatureNew('str.replace', '0.58.0') @typed_pos_args('str.replace', str, str) + @InterpreterObject.method('replace') def replace_method(self, args: T.Tuple[str, str], kwargs: TYPE_kwargs) -> str: return self.held_object.replace(args[0], args[1]) @noKwargs @typed_pos_args('str.split', optargs=[str]) + @InterpreterObject.method('split') def split_method(self, args: T.Tuple[T.Optional[str]], kwargs: TYPE_kwargs) -> T.List[str]: - return self.held_object.split(args[0]) + delimiter = args[0] + if delimiter == '': + raise InvalidArguments('str.split() delimitier must not be an empty string') + return self.held_object.split(delimiter) @noKwargs @typed_pos_args('str.strip', optargs=[str]) + @InterpreterObject.method('strip') def strip_method(self, args: T.Tuple[T.Optional[str]], kwargs: TYPE_kwargs) -> str: if args[0]: FeatureNew.single_use('str.strip with a positional argument', '0.43.0', self.subproject, location=self.current_node) @@ -140,6 +124,7 @@ class StringHolder(ObjectHolder[str]): @noKwargs @FeatureNew('str.substring', '0.56.0') @typed_pos_args('str.substring', optargs=[int, int]) + @InterpreterObject.method('substring') def substring_method(self, args: T.Tuple[T.Optional[int], T.Optional[int]], kwargs: TYPE_kwargs) -> str: start = args[0] if args[0] is not None else 0 end = args[1] if args[1] is not None else len(self.held_object) @@ -147,6 +132,7 @@ class StringHolder(ObjectHolder[str]): @noKwargs @noPosargs + @InterpreterObject.method('to_int') def to_int_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> int: try: return int(self.held_object) @@ -155,20 +141,24 @@ class StringHolder(ObjectHolder[str]): @noKwargs @noPosargs + @InterpreterObject.method('to_lower') def to_lower_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self.held_object.lower() @noKwargs @noPosargs + @InterpreterObject.method('to_upper') def to_upper_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: return self.held_object.upper() @noKwargs @noPosargs + @InterpreterObject.method('underscorify') def underscorify_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: - return re.sub(r'[^a-zA-Z0-9]', '_', self.held_object) + return underscorify(self.held_object) @noKwargs + @InterpreterObject.method('version_compare') @typed_pos_args('str.version_compare', varargs=str, min_varargs=1) def version_compare_method(self, args: T.Tuple[T.List[str]], kwargs: TYPE_kwargs) -> bool: if len(args[0]) > 1: @@ -181,10 +171,12 @@ class StringHolder(ObjectHolder[str]): @FeatureNew('/ with string arguments', '0.49.0') @typed_operator(MesonOperator.DIV, str) + @InterpreterObject.operator(MesonOperator.DIV) def op_div(self, other: str) -> str: return self._op_div(self.held_object, other) @typed_operator(MesonOperator.INDEX, int) + @InterpreterObject.operator(MesonOperator.INDEX) def op_index(self, other: int) -> str: try: return self.held_object[other] @@ -193,11 +185,13 @@ class StringHolder(ObjectHolder[str]): @FeatureNew('"in" string operator', '1.0.0') @typed_operator(MesonOperator.IN, str) + @InterpreterObject.operator(MesonOperator.IN) def op_in(self, other: str) -> bool: return other in self.held_object @FeatureNew('"not in" string operator', '1.0.0') @typed_operator(MesonOperator.NOT_IN, str) + @InterpreterObject.operator(MesonOperator.NOT_IN) def op_notin(self, other: str) -> bool: return other not in self.held_object @@ -207,10 +201,26 @@ class MesonVersionString(str): class MesonVersionStringHolder(StringHolder): @noKwargs - @typed_pos_args('str.version_compare', str) - def version_compare_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> bool: - self.interpreter.tmp_meson_version = args[0] - return version_compare(self.held_object, args[0]) + @InterpreterObject.method('version_compare') + @typed_pos_args('str.version_compare', varargs=str, min_varargs=1) + def version_compare_method(self, args: T.Tuple[T.List[str]], kwargs: TYPE_kwargs) -> bool: + unsupported = [] + for constraint in args[0]: + if not constraint.strip().startswith('>'): + unsupported.append('non-upper-bounds (> or >=) constraints') + if len(args[0]) > 1: + FeatureNew.single_use('meson.version().version_compare() with multiple arguments', '1.10.0', + self.subproject, 'From 1.8.0 - 1.9.* it failed to match str.version_compare', + location=self.current_node) + unsupported.append('multiple arguments') + else: + self.interpreter.tmp_meson_version = args[0][0] + if unsupported: + mlog.debug('meson.version().version_compare() with', ' or '.join(unsupported), + 'does not support overriding minimum meson_version checks.') + + return version_compare_many(self.held_object, args[0])[0] + # These special subclasses of string exist to cover the case where a dependency # exports a string variable interchangeable with a system dependency. This @@ -221,6 +231,7 @@ class DependencyVariableString(str): pass class DependencyVariableStringHolder(StringHolder): + @InterpreterObject.operator(MesonOperator.DIV) def op_div(self, other: str) -> T.Union[str, DependencyVariableString]: ret = super().op_div(other) if '..' in other: @@ -243,6 +254,7 @@ class OptionString(str): class OptionStringHolder(StringHolder): held_object: OptionString + @InterpreterObject.operator(MesonOperator.DIV) def op_div(self, other: str) -> T.Union[str, OptionString]: ret = super().op_div(other) name = self._op_div(self.held_object.optname, other) diff --git a/mesonbuild/interpreter/type_checking.py b/mesonbuild/interpreter/type_checking.py index 78938ba..743046c 100644 --- a/mesonbuild/interpreter/type_checking.py +++ b/mesonbuild/interpreter/type_checking.py @@ -11,10 +11,10 @@ from .. import compilers from ..build import (CustomTarget, BuildTarget, CustomTargetIndex, ExtractedObjects, GeneratedList, IncludeDirs, BothLibraries, SharedLibrary, StaticLibrary, Jar, Executable, StructuredSources) -from ..options import UserFeatureOption -from ..dependencies import Dependency, InternalDependency -from ..interpreterbase.decorators import KwargInfo, ContainerTypeInfo -from ..mesonlib import (File, FileMode, MachineChoice, listify, has_path_sep, +from ..options import OptionKey, UserFeatureOption +from ..dependencies import Dependency, DependencyMethods, InternalDependency +from ..interpreterbase.decorators import KwargInfo, ContainerTypeInfo, FeatureBroken, FeatureDeprecated +from ..mesonlib import (File, FileMode, MachineChoice, has_path_sep, listify, stringlistify, EnvironmentVariables) from ..programs import ExternalProgram @@ -24,14 +24,15 @@ NoneType: T.Type[None] = type(None) if T.TYPE_CHECKING: from typing_extensions import Literal - from ..build import ObjectTypes + from ..build import ObjectTypes, GeneratedTypes, BuildTargetTypes from ..interpreterbase import TYPE_var from ..options import ElementaryOptionValues from ..mesonlib import EnvInitValueType + from ..interpreterbase.decorators import FeatureCheckBase _FullEnvInitValueType = T.Union[EnvironmentVariables, T.List[str], T.List[T.List[str]], EnvInitValueType, str, None] PkgConfigDefineType = T.Optional[T.Tuple[T.Tuple[str, str], ...]] - SourcesVarargsType = T.List[T.Union[str, File, CustomTarget, CustomTargetIndex, GeneratedList, StructuredSources, ExtractedObjects, BuildTarget]] + SourcesVarargsType = T.List[T.Union[str, File, GeneratedTypes, StructuredSources, ExtractedObjects, BuildTarget]] def in_set_validator(choices: T.Set[str]) -> T.Callable[[str], T.Optional[str]]: @@ -269,7 +270,7 @@ DEPFILE_KW: KwargInfo[T.Optional[str]] = KwargInfo( validator=lambda x: 'Depfile must be a plain filename with a subdirectory' if has_path_sep(x) else None ) -DEPENDS_KW: KwargInfo[T.List[T.Union[BuildTarget, CustomTarget, CustomTargetIndex]]] = KwargInfo( +DEPENDS_KW: KwargInfo[T.List[BuildTargetTypes]] = KwargInfo( 'depends', ContainerTypeInfo(list, (BuildTarget, CustomTarget, CustomTargetIndex)), listify=True, @@ -284,7 +285,7 @@ DEPEND_FILES_KW: KwargInfo[T.List[T.Union[str, File]]] = KwargInfo( default=[], ) -COMMAND_KW: KwargInfo[T.List[T.Union[str, BuildTarget, CustomTarget, CustomTargetIndex, ExternalProgram, File]]] = KwargInfo( +COMMAND_KW: KwargInfo[T.List[T.Union[str, BuildTargetTypes, ExternalProgram, File]]] = KwargInfo( 'command', ContainerTypeInfo(list, (str, BuildTarget, CustomTarget, CustomTargetIndex, ExternalProgram, File), allow_empty=False), required=True, @@ -293,11 +294,22 @@ COMMAND_KW: KwargInfo[T.List[T.Union[str, BuildTarget, CustomTarget, CustomTarge ) -OVERRIDE_OPTIONS_KW: KwargInfo[T.Union[str, T.Dict[str, ElementaryOptionValues], T.List[str]]] = KwargInfo( +def _override_options_convertor(raw: T.Union[str, T.List[str], T.Dict[str, ElementaryOptionValues]]) -> T.Dict[str, ElementaryOptionValues]: + if isinstance(raw, dict): + return raw + raw = stringlistify(raw) + output: T.Dict[str, ElementaryOptionValues] = {} + for each in raw: + k, v = split_equal_string(each) + output[k] = v + return output + +OVERRIDE_OPTIONS_KW: KwargInfo[T.Union[str, T.List[str], T.Dict[str, ElementaryOptionValues]]] = KwargInfo( 'override_options', (str, ContainerTypeInfo(list, str), ContainerTypeInfo(dict, (str, int, bool, list))), default={}, validator=_options_validator, + convertor=_override_options_convertor, since_values={dict: '1.2.0'}, ) @@ -338,7 +350,7 @@ OUTPUT_KW: KwargInfo[str] = KwargInfo( validator=lambda x: _output_validator([x]) ) -CT_INPUT_KW: KwargInfo[T.List[T.Union[str, File, ExternalProgram, BuildTarget, CustomTarget, CustomTargetIndex, ExtractedObjects, GeneratedList]]] = KwargInfo( +CT_INPUT_KW: KwargInfo[T.List[T.Union[str, File, ExternalProgram, BuildTarget, GeneratedTypes, ExtractedObjects]]] = KwargInfo( 'input', ContainerTypeInfo(list, (str, File, ExternalProgram, BuildTarget, CustomTarget, CustomTargetIndex, ExtractedObjects, GeneratedList)), listify=True, @@ -394,7 +406,13 @@ INCLUDE_DIRECTORIES: KwargInfo[T.List[T.Union[str, IncludeDirs]]] = KwargInfo( default=[], ) -DEFAULT_OPTIONS = OVERRIDE_OPTIONS_KW.evolve(name='default_options') +def _default_options_convertor(raw: T.Union[str, T.List[str], T.Dict[str, ElementaryOptionValues]]) -> T.Dict[OptionKey, ElementaryOptionValues]: + d = _override_options_convertor(raw) + return {OptionKey.from_string(k): v for k, v in d.items()} + +DEFAULT_OPTIONS = OVERRIDE_OPTIONS_KW.evolve( + name='default_options', + convertor=_default_options_convertor) ENV_METHOD_KW = KwargInfo('method', str, default='set', since='0.62.0', validator=in_set_validator({'set', 'prepend', 'append'})) @@ -444,7 +462,7 @@ LINK_WHOLE_KW: KwargInfo[T.List[T.Union[BothLibraries, StaticLibrary, CustomTarg validator=link_whole_validator, ) -DEPENDENCY_SOURCES_KW: KwargInfo[T.List[T.Union[str, File, CustomTarget, CustomTargetIndex, GeneratedList]]] = KwargInfo( +DEPENDENCY_SOURCES_KW: KwargInfo[T.List[T.Union[str, File, GeneratedTypes]]] = KwargInfo( 'sources', ContainerTypeInfo(list, (str, File, CustomTarget, CustomTargetIndex, GeneratedList)), listify=True, @@ -472,6 +490,12 @@ VARIABLES_KW: KwargInfo[T.Dict[str, str]] = KwargInfo( PRESERVE_PATH_KW: KwargInfo[bool] = KwargInfo('preserve_path', bool, default=False, since='0.63.0') +def suite_convertor(suite: T.List[str]) -> T.List[str]: + # Ensure we always have at least one suite. + if not suite: + return [''] + return suite + TEST_KWS_NO_ARGS: T.List[KwargInfo] = [ KwargInfo('should_fail', bool, default=False), KwargInfo('timeout', int, default=30), @@ -485,7 +509,7 @@ TEST_KWS_NO_ARGS: T.List[KwargInfo] = [ # TODO: env needs reworks of the way the environment variable holder itself works probably ENV_KW, DEPENDS_KW.evolve(since='0.46.0'), - KwargInfo('suite', ContainerTypeInfo(list, str), listify=True, default=['']), # yes, a list of empty string + KwargInfo('suite', ContainerTypeInfo(list, str), listify=True, default=[], convertor=suite_convertor), KwargInfo('verbose', bool, default=False, since='0.62.0'), ] @@ -553,14 +577,44 @@ def _objects_validator(vals: T.List[ObjectTypes]) -> T.Optional[str]: return None +def _target_install_feature_validator(val: object) -> T.Iterable[FeatureCheckBase]: + # due to lack of type checking, these are "allowed" for legacy reasons + if not isinstance(val, bool): + yield FeatureBroken('install kwarg with non-boolean value', '1.3.0', + 'This was never intended to work, and is essentially the same as using `install: true` regardless of value.') + + +def _target_install_convertor(val: object) -> bool: + return bool(val) + + +def _extra_files_validator(args: T.List[T.Union[File, str]]) -> T.Optional[str]: + generated = [a for a in args if isinstance(a, File) and a.is_built] + if generated: + return 'extra_files contains generated files: {}'.format(', '.join(f"{f.fname}" for f in generated)) + return None + + # Applies to all build_target like classes _ALL_TARGET_KWS: T.List[KwargInfo] = [ OVERRIDE_OPTIONS_KW, KwargInfo('build_by_default', bool, default=True, since='0.38.0'), - KwargInfo('extra_files', ContainerTypeInfo(list, (str, File)), default=[], listify=True), - # Accursed. We allow this for backwards compat and warn in the interpreter. - KwargInfo('install', object, default=False), + KwargInfo( + 'extra_files', + ContainerTypeInfo(list, (str, File)), + default=[], + listify=True, + validator=_extra_files_validator, + ), + KwargInfo( + 'install', + object, + default=False, + convertor=_target_install_convertor, + feature_validator=_target_install_feature_validator, + ), INSTALL_MODE_KW, + INSTALL_TAG_KW, KwargInfo('implicit_include_directories', bool, default=True, since='0.42.0'), NATIVE_KW, KwargInfo('resources', ContainerTypeInfo(list, str), default=[], listify=True), @@ -575,6 +629,7 @@ _ALL_TARGET_KWS: T.List[KwargInfo] = [ ('1.1.0', 'generated sources as positional "objects" arguments') }, ), + KwargInfo('build_subdir', str, default='', since='1.10.0') ] @@ -598,6 +653,63 @@ _NAME_PREFIX_KW: KwargInfo[T.Optional[T.Union[str, T.List]]] = KwargInfo( ) +def _pch_validator(args: T.List[str]) -> T.Optional[str]: + num_args = len(args) + if num_args == 1: + if not compilers.is_header(args[0]): + return f'PCH argument {args[0]} is not a header.' + elif num_args == 2: + if compilers.is_header(args[0]): + if not compilers.is_source(args[1]): + return 'PCH definition must contain one header and at most one source.' + elif compilers.is_source(args[0]): + if not compilers.is_header(args[1]): + return 'PCH definition must contain one header and at most one source.' + else: + return f'PCH argument {args[0]} has neither a known header or code extension.' + + if os.path.dirname(args[0]) != os.path.dirname(args[1]): + return 'PCH files must be stored in the same folder.' + elif num_args > 2: + return 'A maximum of two elements are allowed for PCH arguments' + if num_args >= 1 and not has_path_sep(args[0]): + return f'PCH header {args[0]} must not be in the same directory as source files' + if num_args == 2 and not has_path_sep(args[1]): + return f'PCH source {args[0]} must not be in the same directory as source files' + return None + + +def _pch_feature_validator(args: T.List[str]) -> T.Iterable[FeatureCheckBase]: + if len(args) > 1: + yield FeatureDeprecated('PCH source files', '0.50.0', 'Only a single header file should be used.') + + +def _pch_convertor(args: T.List[str]) -> T.Optional[T.Tuple[str, T.Optional[str]]]: + num_args = len(args) + + if num_args == 1: + return (args[0], None) + + if num_args == 2: + if compilers.is_source(args[0]): + # Flip so that we always have [header, src] + return (args[1], args[0]) + return (args[0], args[1]) + + return None + + +_PCH_ARGS: KwargInfo[T.List[str]] = KwargInfo( + 'pch', + ContainerTypeInfo(list, str), + listify=True, + default=[], + validator=_pch_validator, + feature_validator=_pch_feature_validator, + convertor=_pch_convertor, +) + + # Applies to all build_target classes except jar _BUILD_TARGET_KWS: T.List[KwargInfo] = [ *_ALL_TARGET_KWS, @@ -607,6 +719,8 @@ _BUILD_TARGET_KWS: T.List[KwargInfo] = [ _NAME_PREFIX_KW, _NAME_PREFIX_KW.evolve(name='name_suffix', validator=_name_suffix_validator), RUST_CRATE_TYPE_KW, + _PCH_ARGS.evolve(name='c_pch'), + _PCH_ARGS.evolve(name='cpp_pch'), KwargInfo('d_debug', ContainerTypeInfo(list, (str, int)), default=[], listify=True), D_MODULE_VERSIONS_KW, KwargInfo('d_unittest', bool, default=False), @@ -616,6 +730,8 @@ _BUILD_TARGET_KWS: T.List[KwargInfo] = [ default={}, since='1.2.0', ), + KwargInfo('swift_interoperability_mode', str, default='c', validator=in_set_validator({'c', 'cpp'}), since='1.9.0'), + KwargInfo('swift_module_name', str, default='', since='1.9.0'), KwargInfo('build_rpath', str, default='', since='0.42.0'), KwargInfo( 'gnu_symbol_visibility', @@ -626,6 +742,12 @@ _BUILD_TARGET_KWS: T.List[KwargInfo] = [ ), KwargInfo('install_rpath', str, default=''), KwargInfo( + 'link_args', + ContainerTypeInfo(list, str), + default=[], + listify=True, + ), + KwargInfo( 'link_depends', ContainerTypeInfo(list, (str, File, CustomTarget, CustomTargetIndex, BuildTarget)), default=[], @@ -637,6 +759,9 @@ _BUILD_TARGET_KWS: T.List[KwargInfo] = [ validator=in_set_validator(set(compilers.all_languages)), since='0.51.0', ), + KwargInfo('vala_gir', (str, NoneType)), + KwargInfo('vala_header', (str, NoneType)), + KwargInfo('vala_vapi', (str, NoneType)), ] def _validate_win_subsystem(value: T.Optional[str]) -> T.Optional[str]: @@ -690,10 +815,17 @@ _DARWIN_VERSIONS_KW: KwargInfo[T.List[T.Union[str, int]]] = KwargInfo( # Arguments exclusive to Executable. These are separated to make integrating # them into build_target easier -_EXCLUSIVE_EXECUTABLE_KWS: T.List[KwargInfo] = [ +EXCLUSIVE_EXECUTABLE_KWS: T.List[KwargInfo] = [ KwargInfo('export_dynamic', (bool, NoneType), since='0.45.0'), KwargInfo('gui_app', (bool, NoneType), deprecated='0.56.0', deprecated_message="Use 'win_subsystem' instead"), - KwargInfo('implib', (bool, str, NoneType), since='0.42.0'), + KwargInfo( + 'implib', + (bool, str, NoneType), + since='0.42.0', + deprecated_values={ + bool: ('1.10.0', 'Use "export_dynamic" keyword instead'), + }, + ), KwargInfo('pie', (bool, NoneType)), KwargInfo( 'win_subsystem', @@ -712,7 +844,7 @@ _EXCLUSIVE_EXECUTABLE_KWS: T.List[KwargInfo] = [ # The total list of arguments used by Executable EXECUTABLE_KWS = [ *_BUILD_TARGET_KWS, - *_EXCLUSIVE_EXECUTABLE_KWS, + *EXCLUSIVE_EXECUTABLE_KWS, _VS_MODULE_DEFS_KW.evolve(since='1.3.0', since_values=None), _JAVA_LANG_KW, ] @@ -737,16 +869,22 @@ STATIC_LIB_KWS = [ _JAVA_LANG_KW, ] +def _shortname_validator(shortname: T.Optional[str]) -> T.Optional[str]: + if shortname is not None and len(shortname) > 8: + return 'must have a maximum of 8 characters' + return None + # Arguments exclusive to SharedLibrary. These are separated to make integrating # them into build_target easier _EXCLUSIVE_SHARED_LIB_KWS: T.List[KwargInfo] = [ _DARWIN_VERSIONS_KW, KwargInfo('soversion', (str, int, NoneType), convertor=lambda x: str(x) if x is not None else None), KwargInfo('version', (str, NoneType), validator=_validate_shlib_version), + KwargInfo('shortname', (str, NoneType), since='1.10.0', validator=_shortname_validator), ] # The total list of arguments used by SharedLibrary -SHARED_LIB_KWS = [ +SHARED_LIB_KWS: T.List[KwargInfo] = [ *_BUILD_TARGET_KWS, *_EXCLUSIVE_SHARED_LIB_KWS, *_EXCLUSIVE_LIB_KWS, @@ -814,8 +952,9 @@ BUILD_TARGET_KWS = [ *_EXCLUSIVE_SHARED_LIB_KWS, *_EXCLUSIVE_SHARED_MOD_KWS, *_EXCLUSIVE_STATIC_LIB_KWS, - *_EXCLUSIVE_EXECUTABLE_KWS, + *EXCLUSIVE_EXECUTABLE_KWS, *_SHARED_STATIC_ARGS, + RUST_ABI_KW.evolve(since='1.10.0'), *[a.evolve(deprecated='1.3.0', deprecated_message='The use of "jar" in "build_target()" is deprecated, and this argument is only used by jar()') for a in _EXCLUSIVE_JAR_KWS], KwargInfo( @@ -848,3 +987,64 @@ PKGCONFIG_DEFINE_KW: KwargInfo = KwargInfo( default=[], convertor=_pkgconfig_define_convertor, ) + +INCLUDE_TYPE = KwargInfo( + 'include_type', + str, + default='preserve', + since='0.52.0', + validator=in_set_validator({'system', 'non-system', 'preserve'}) +) + + +_DEPRECATED_DEPENDENCY_METHODS = frozenset( + {'sdlconfig', 'cups-config', 'pcap-config', 'libwmf-config', 'qmake'}) + + +def _dependency_method_convertor(value: str) -> DependencyMethods: + if value in _DEPRECATED_DEPENDENCY_METHODS: + return DependencyMethods.CONFIG_TOOL + return DependencyMethods(value) + + +DEPENDENCY_METHOD_KW = KwargInfo( + 'method', + str, + default='auto', + since='0.40.0', + validator=in_set_validator( + {m.value for m in DependencyMethods} | _DEPRECATED_DEPENDENCY_METHODS), + convertor=_dependency_method_convertor, + deprecated_values={ + 'sdlconfig': ('0.44.0', 'use config-tool instead'), + 'cups-config': ('0.44.0', 'use config-tool instead'), + 'pcap-config': ('0.44.0', 'use config-tool instead'), + 'libwmf-config': ('0.44.0', 'use config-tool instead'), + 'qmake': ('0.58.0', 'use config-tool instead'), + }, +) + + +DEPENDENCY_KWS: T.List[KwargInfo] = [ + DEFAULT_OPTIONS.evolve(since='0.38.0'), + DEPENDENCY_METHOD_KW, + DISABLER_KW.evolve(since='0.49.0'), + INCLUDE_TYPE, + NATIVE_KW, + REQUIRED_KW, + KwargInfo('allow_fallback', (bool, NoneType), since='0.56.0'), + KwargInfo('cmake_args', ContainerTypeInfo(list, str), listify=True, default=[], since='0.50.0'), + KwargInfo('cmake_module_path', ContainerTypeInfo(list, str), listify=True, default=[], since='0.50.0'), + KwargInfo('cmake_package_version', str, default='', since='0.57.0'), + KwargInfo('components', ContainerTypeInfo(list, str), listify=True, default=[], since='0.54.0'), + KwargInfo('fallback', (ContainerTypeInfo(list, str), str, NoneType), since='0.54.0'), + KwargInfo('language', (str, NoneType), convertor=lambda x: x.lower() if x is not None else x, + validator=lambda x: 'Must be a valid language if set' if (x is not None and x not in compilers.all_languages) else None), + KwargInfo('main', bool, default=False), + KwargInfo('modules', ContainerTypeInfo(list, str), listify=True, default=[]), + KwargInfo('not_found_message', str, default='', since='0.50.0'), + KwargInfo('optional_modules', ContainerTypeInfo(list, str), listify=True, default=[]), + KwargInfo('private_headers', bool, default=False), + KwargInfo('static', (bool, NoneType)), + KwargInfo('version', ContainerTypeInfo(list, str), listify=True, default=[]), +] diff --git a/mesonbuild/interpreterbase/__init__.py b/mesonbuild/interpreterbase/__init__.py index aa38e94..88fa706 100644 --- a/mesonbuild/interpreterbase/__init__.py +++ b/mesonbuild/interpreterbase/__init__.py @@ -59,6 +59,9 @@ __all__ = [ 'TYPE_HoldableTypes', 'HoldableTypes', + + 'UnknownValue', + 'UndefinedVariable', ] from .baseobjects import ( @@ -81,6 +84,9 @@ from .baseobjects import ( SubProject, HoldableTypes, + + UnknownValue, + UndefinedVariable, ) from .decorators import ( diff --git a/mesonbuild/interpreterbase/baseobjects.py b/mesonbuild/interpreterbase/baseobjects.py index a5cccce..c756761 100644 --- a/mesonbuild/interpreterbase/baseobjects.py +++ b/mesonbuild/interpreterbase/baseobjects.py @@ -15,16 +15,11 @@ from abc import ABCMeta from contextlib import AbstractContextManager if T.TYPE_CHECKING: - from typing_extensions import Protocol, TypeAlias + from typing_extensions import TypeAlias # Object holders need the actual interpreter from ..interpreter import Interpreter - __T = T.TypeVar('__T', bound='TYPE_var', contravariant=True) - - class OperatorCall(Protocol[__T]): - def __call__(self, other: __T) -> 'TYPE_var': ... - TV_func = T.TypeVar('TV_func', bound=T.Callable[..., T.Any]) @@ -34,34 +29,85 @@ TYPE_nvar = T.Union[TYPE_var, mparser.BaseNode] TYPE_kwargs = T.Dict[str, TYPE_var] TYPE_nkwargs = T.Dict[str, TYPE_nvar] TYPE_key_resolver = T.Callable[[mparser.BaseNode], str] +TYPE_op_arg = T.TypeVar('TYPE_op_arg', bound='TYPE_var', contravariant=True) +TYPE_op_func = T.Callable[[TYPE_op_arg, TYPE_op_arg], TYPE_var] +TYPE_method_func = T.Callable[['InterpreterObject', T.List[TYPE_var], TYPE_kwargs], TYPE_var] + SubProject = T.NewType('SubProject', str) class InterpreterObject: + TRIVIAL_OPERATORS: T.Dict[ + MesonOperator, + T.Tuple[ + T.Union[T.Type, T.Tuple[T.Type, ...]], + TYPE_op_func + ] + ] = {} + + OPERATORS: T.Dict[MesonOperator, TYPE_op_func] = {} + + METHODS: T.Dict[ + str, + TYPE_method_func, + ] = {} + + def __init_subclass__(cls: T.Type[InterpreterObject], **kwargs: T.Any) -> None: + super().__init_subclass__(**kwargs) + saved_trivial_operators = cls.TRIVIAL_OPERATORS + + cls.METHODS = {} + cls.OPERATORS = {} + cls.TRIVIAL_OPERATORS = {} + + # Compute inherited operators and methods according to the Python resolution + # order. Reverse the result of mro() because update() will overwrite entries + # that are set by the superclass with those that are set by the subclass. + for superclass in reversed(cls.mro()[1:]): + if superclass is InterpreterObject: + # InterpreterObject cannot use @InterpreterObject.operator because + # __init_subclass__ does not operate on InterpreterObject itself + cls.OPERATORS.update({ + MesonOperator.EQUALS: InterpreterObject.op_equals, + MesonOperator.NOT_EQUALS: InterpreterObject.op_not_equals + }) + + elif issubclass(superclass, InterpreterObject): + cls.METHODS.update(superclass.METHODS) + cls.OPERATORS.update(superclass.OPERATORS) + cls.TRIVIAL_OPERATORS.update(superclass.TRIVIAL_OPERATORS) + + for name, method in cls.__dict__.items(): + if hasattr(method, 'meson_method'): + cls.METHODS[method.meson_method] = method + if hasattr(method, 'meson_operator'): + cls.OPERATORS[method.meson_operator] = method + cls.TRIVIAL_OPERATORS.update(saved_trivial_operators) + + @staticmethod + def method(name: str) -> T.Callable[[TV_func], TV_func]: + '''Decorator that tags a Python method as the implementation of a method + for the Meson interpreter''' + def decorator(f: TV_func) -> TV_func: + f.meson_method = name # type: ignore[attr-defined] + return f + return decorator + + @staticmethod + def operator(op: MesonOperator) -> T.Callable[[TV_func], TV_func]: + '''Decorator that tags a method as the implementation of an operator + for the Meson interpreter''' + def decorator(f: TV_func) -> TV_func: + f.meson_operator = op # type: ignore[attr-defined] + return f + return decorator + def __init__(self, *, subproject: T.Optional['SubProject'] = None) -> None: - self.methods: T.Dict[ - str, - T.Callable[[T.List[TYPE_var], TYPE_kwargs], TYPE_var] - ] = {} - self.operators: T.Dict[MesonOperator, 'OperatorCall'] = {} - self.trivial_operators: T.Dict[ - MesonOperator, - T.Tuple[ - T.Union[T.Type, T.Tuple[T.Type, ...]], - 'OperatorCall' - ] - ] = {} # Current node set during a method call. This can be used as location # when printing a warning message during a method call. self.current_node: mparser.BaseNode = None self.subproject = subproject or SubProject('') - # Some default operators supported by all objects - self.operators.update({ - MesonOperator.EQUALS: self.op_equals, - MesonOperator.NOT_EQUALS: self.op_not_equals, - }) - # The type of the object that can be printed to the user def display_name(self) -> str: return type(self).__name__ @@ -72,25 +118,26 @@ class InterpreterObject: args: T.List[TYPE_var], kwargs: TYPE_kwargs ) -> TYPE_var: - if method_name in self.methods: - method = self.methods[method_name] + if method_name in self.METHODS: + method = self.METHODS[method_name] if not getattr(method, 'no-args-flattening', False): args = flatten(args) if not getattr(method, 'no-second-level-holder-flattening', False): args, kwargs = resolve_second_level_holders(args, kwargs) - return method(args, kwargs) + return method(self, args, kwargs) raise InvalidCode(f'Unknown method "{method_name}" in object {self} of type {type(self).__name__}.') def operator_call(self, operator: MesonOperator, other: TYPE_var) -> TYPE_var: - if operator in self.trivial_operators: - op = self.trivial_operators[operator] + if operator in self.TRIVIAL_OPERATORS: + op = self.TRIVIAL_OPERATORS[operator] if op[0] is None and other is not None: raise MesonBugException(f'The unary operator `{operator.value}` of {self.display_name()} was passed the object {other} of type {type(other).__name__}') if op[0] is not None and not isinstance(other, op[0]): raise InvalidArguments(f'The `{operator.value}` operator of {self.display_name()} does not accept objects of type {type(other).__name__} ({other})') - return op[1](other) - if operator in self.operators: - return self.operators[operator](other) + return op[1](self, other) + if operator in self.OPERATORS: + return self.OPERATORS[operator](self, other) + raise InvalidCode(f'Object {self} of type {self.display_name()} does not support the `{operator.value}` operator.') # Default comparison operator support @@ -121,6 +168,16 @@ class MesonInterpreterObject(InterpreterObject): class MutableInterpreterObject: ''' Dummy class to mark the object type as mutable ''' +class UnknownValue(MesonInterpreterObject): + '''This class is only used for the rewriter/static introspection tool and + indicates that a value cannot be determined statically, either because of + limitations in our code or because the value differs from machine to + machine.''' + +class UndefinedVariable(MesonInterpreterObject): + '''This class is only used for the rewriter/static introspection tool and + represents the `value` a meson-variable has if it was never written to.''' + HoldableTypes = (HoldableObject, int, bool, str, list, dict) TYPE_HoldableTypes = T.Union[TYPE_var, HoldableObject] InterpreterObjectTypeVar = T.TypeVar('InterpreterObjectTypeVar', bound=TYPE_HoldableTypes) @@ -142,12 +199,14 @@ class ObjectHolder(InterpreterObject, T.Generic[InterpreterObjectTypeVar]): return type(self.held_object).__name__ # Override default comparison operators for the held object + @InterpreterObject.operator(MesonOperator.EQUALS) def op_equals(self, other: TYPE_var) -> bool: # See the comment from InterpreterObject why we are using `type()` here. if type(self.held_object) is not type(other): self._throw_comp_exception(other, '==') return self.held_object == other + @InterpreterObject.operator(MesonOperator.NOT_EQUALS) def op_not_equals(self, other: TYPE_var) -> bool: if type(self.held_object) is not type(other): self._throw_comp_exception(other, '!=') diff --git a/mesonbuild/interpreterbase/decorators.py b/mesonbuild/interpreterbase/decorators.py index 06cac52..486a585 100644 --- a/mesonbuild/interpreterbase/decorators.py +++ b/mesonbuild/interpreterbase/decorators.py @@ -352,6 +352,7 @@ class KwargInfo(T.Generic[_T]): added in. :param not_set_warning: A warning message that is logged if the kwarg is not set by the user. + :param feature_validator: A callable returning an iterable of FeatureNew | FeatureDeprecated objects. """ def __init__(self, name: str, types: T.Union[T.Type[_T], T.Tuple[T.Union[T.Type[_T], ContainerTypeInfo], ...], ContainerTypeInfo], @@ -363,6 +364,7 @@ class KwargInfo(T.Generic[_T]): deprecated: T.Optional[str] = None, deprecated_message: T.Optional[str] = None, deprecated_values: T.Optional[T.Dict[T.Union[_T, ContainerTypeInfo, type], T.Union[str, T.Tuple[str, str]]]] = None, + feature_validator: T.Optional[T.Callable[[_T], T.Iterable[FeatureCheckBase]]] = None, validator: T.Optional[T.Callable[[T.Any], T.Optional[str]]] = None, convertor: T.Optional[T.Callable[[_T], object]] = None, not_set_warning: T.Optional[str] = None): @@ -374,6 +376,7 @@ class KwargInfo(T.Generic[_T]): self.since = since self.since_message = since_message self.since_values = since_values + self.feature_validator = feature_validator self.deprecated = deprecated self.deprecated_message = deprecated_message self.deprecated_values = deprecated_values @@ -392,8 +395,9 @@ class KwargInfo(T.Generic[_T]): deprecated: T.Union[str, None, _NULL_T] = _NULL, deprecated_message: T.Union[str, None, _NULL_T] = _NULL, deprecated_values: T.Union[T.Dict[T.Union[_T, ContainerTypeInfo, type], T.Union[str, T.Tuple[str, str]]], None, _NULL_T] = _NULL, + feature_validator: T.Union[T.Callable[[_T], T.Iterable[FeatureCheckBase]], None, _NULL_T] = _NULL, validator: T.Union[T.Callable[[_T], T.Optional[str]], None, _NULL_T] = _NULL, - convertor: T.Union[T.Callable[[_T], TYPE_var], None, _NULL_T] = _NULL) -> 'KwargInfo': + convertor: T.Union[T.Callable[[_T], object], None, _NULL_T] = _NULL) -> 'KwargInfo': """Create a shallow copy of this KwargInfo, with modifications. This allows us to create a new copy of a KwargInfo with modifications. @@ -417,6 +421,7 @@ class KwargInfo(T.Generic[_T]): deprecated=deprecated if not isinstance(deprecated, _NULL_T) else self.deprecated, deprecated_message=deprecated_message if not isinstance(deprecated_message, _NULL_T) else self.deprecated_message, deprecated_values=deprecated_values if not isinstance(deprecated_values, _NULL_T) else self.deprecated_values, + feature_validator=feature_validator if not isinstance(feature_validator, _NULL_T) else self.feature_validator, validator=validator if not isinstance(validator, _NULL_T) else self.validator, convertor=convertor if not isinstance(convertor, _NULL_T) else self.convertor, ) @@ -493,7 +498,7 @@ def typed_kwargs(name: str, *types: KwargInfo, allow_unknown: bool = False) -> T if n in value: warning = f'value "{n}" in list' elif isinstance(value, dict): - if n in value.keys(): + if n in value: warning = f'value "{n}" in dict keys' elif n == value: warning = f'value "{n}"' @@ -532,6 +537,10 @@ def typed_kwargs(name: str, *types: KwargInfo, allow_unknown: bool = False) -> T if msg is not None: raise InvalidArguments(f'{name} keyword argument "{info.name}" {msg}') + if info.feature_validator is not None: + for each in info.feature_validator(value): + each.use(subproject, node) + if info.deprecated_values is not None: emit_feature_change(info.deprecated_values, FeatureDeprecated) diff --git a/mesonbuild/interpreterbase/interpreterbase.py b/mesonbuild/interpreterbase/interpreterbase.py index b13bbae..a601e5c 100644 --- a/mesonbuild/interpreterbase/interpreterbase.py +++ b/mesonbuild/interpreterbase/interpreterbase.py @@ -28,6 +28,7 @@ from .exceptions import ( ) from .. import mlog +from . import operator from .decorators import FeatureNew from .disabler import Disabler, is_disabled from .helpers import default_resolve_key, flatten, resolve_second_level_holders, stringifyUserArguments @@ -344,24 +345,14 @@ class InterpreterBase: if isinstance(val2, Disabler): return val2 - # New code based on InterpreterObjects - operator = { - 'in': MesonOperator.IN, - 'notin': MesonOperator.NOT_IN, - '==': MesonOperator.EQUALS, - '!=': MesonOperator.NOT_EQUALS, - '>': MesonOperator.GREATER, - '<': MesonOperator.LESS, - '>=': MesonOperator.GREATER_EQUALS, - '<=': MesonOperator.LESS_EQUALS, - }[node.ctype] + op = operator.MAPPING[node.ctype] # Check if the arguments should be reversed for simplicity (this essentially converts `in` to `contains`) - if operator in (MesonOperator.IN, MesonOperator.NOT_IN): + if op in (MesonOperator.IN, MesonOperator.NOT_IN): val1, val2 = val2, val1 val1.current_node = node - return self._holderify(val1.operator_call(operator, _unholder(val2))) + return self._holderify(val1.operator_call(op, _unholder(val2))) def evaluate_andstatement(self, cur: mparser.AndNode) -> InterpreterObject: l = self.evaluate_statement(cur.left) @@ -414,15 +405,8 @@ class InterpreterBase: if l is None or r is None: raise InvalidCodeOnVoid(cur.operation) - mapping: T.Dict[str, MesonOperator] = { - 'add': MesonOperator.PLUS, - 'sub': MesonOperator.MINUS, - 'mul': MesonOperator.TIMES, - 'div': MesonOperator.DIV, - 'mod': MesonOperator.MOD, - } l.current_node = cur - res = l.operator_call(mapping[cur.operation], _unholder(r)) + res = l.operator_call(operator.MAPPING[cur.operation], _unholder(r)) return self._holderify(res) def evaluate_ternary(self, node: mparser.TernaryNode) -> T.Optional[InterpreterObject]: @@ -555,12 +539,6 @@ class InterpreterBase: return Disabler() if not isinstance(obj, InterpreterObject): raise InvalidArguments(f'{object_display_name} is not callable.') - # TODO: InterpreterBase **really** shouldn't be in charge of checking this - if method_name == 'extract_objects': - if isinstance(obj, ObjectHolder): - self.validate_extraction(obj.held_object) - elif not isinstance(obj, Disabler): - raise InvalidArguments(f'Invalid operation "extract_objects" on {object_display_name} of type {type(obj).__name__}') obj.current_node = self.current_node = node res = obj.method_call(method_name, args, kwargs) return self._holderify(res) if res is not None else None @@ -675,9 +653,6 @@ class InterpreterBase: return self.variables[varname] raise InvalidCode(f'Unknown variable "{varname}".') - def validate_extraction(self, buildtarget: mesonlib.HoldableObject) -> None: - raise InterpreterException('validate_extraction is not implemented in this context (please file a bug)') - def _load_option_file(self) -> None: from .. import optinterpreter # prevent circular import @@ -733,6 +708,11 @@ class InterpreterBase: except mesonlib.MesonException as me: me.file = absname raise me + self._evaluate_codeblock(codeblock, subdir, visitors) + return True + + def _evaluate_codeblock(self, codeblock: mparser.CodeBlockNode, subdir: str, + visitors: T.Optional[T.Iterable[AstVisitor]] = None) -> None: try: prev_subdir = self.subdir self.subdir = subdir @@ -744,4 +724,3 @@ class InterpreterBase: pass finally: self.subdir = prev_subdir - return True diff --git a/mesonbuild/interpreterbase/operator.py b/mesonbuild/interpreterbase/operator.py index 3419c4b..d17cd37 100644 --- a/mesonbuild/interpreterbase/operator.py +++ b/mesonbuild/interpreterbase/operator.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: Apache-2.0 from enum import Enum +import typing as T class MesonOperator(Enum): # Arithmetic @@ -30,3 +31,7 @@ class MesonOperator(Enum): IN = 'in' NOT_IN = 'not in' INDEX = '[]' + +# Accessing this directly is about 9x faster than calling MesonOperator(s), +# and about 3 times faster than a staticmethod +MAPPING: T.Mapping[str, MesonOperator] = {x.value: x for x in MesonOperator} diff --git a/mesonbuild/linkers/detect.py b/mesonbuild/linkers/detect.py index ee9bb08..d9be379 100644 --- a/mesonbuild/linkers/detect.py +++ b/mesonbuild/linkers/detect.py @@ -27,6 +27,7 @@ defaults['clang_cl_static_linker'] = ['llvm-lib'] defaults['cuda_static_linker'] = ['nvlink'] defaults['gcc_static_linker'] = ['gcc-ar'] defaults['clang_static_linker'] = ['llvm-ar'] +defaults['emxomf_static_linker'] = ['emxomfar'] def __failed_to_detect_linker(compiler: T.List[str], args: T.List[str], stdout: str, stderr: str) -> 'T.NoReturn': msg = 'Unable to detect linker for compiler `{}`\nstdout: {}\nstderr: {}'.format( @@ -39,7 +40,7 @@ def guess_win_linker(env: 'Environment', compiler: T.List[str], comp_class: T.Ty use_linker_prefix: bool = True, invoked_directly: bool = True, extra_args: T.Optional[T.List[str]] = None) -> 'DynamicLinker': from . import linkers - env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) + env.add_lang_args(comp_class.language, comp_class, for_machine) if invoked_directly or comp_class.get_argument_syntax() == 'msvc': rsp_syntax = RSPFileSyntax.MSVC @@ -72,11 +73,11 @@ def guess_win_linker(env: 'Environment', compiler: T.List[str], comp_class: T.Ty if 'LLD' in o.split('\n', maxsplit=1)[0]: if 'compatible with GNU linkers' in o: return linkers.LLVMDynamicLinker( - compiler, for_machine, comp_class.LINKER_PREFIX, + compiler, env, for_machine, comp_class.LINKER_PREFIX, override, version=search_version(o)) elif not invoked_directly: return linkers.ClangClDynamicLinker( - for_machine, override, exelist=compiler, prefix=comp_class.LINKER_PREFIX, + env, for_machine, override, exelist=compiler, prefix=comp_class.LINKER_PREFIX, version=search_version(o), direct=False, machine=None, rsp_syntax=rsp_syntax) @@ -87,13 +88,13 @@ def guess_win_linker(env: 'Environment', compiler: T.List[str], comp_class: T.Ty p, o, e = Popen_safe(compiler + check_args) if 'LLD' in o.split('\n', maxsplit=1)[0]: return linkers.ClangClDynamicLinker( - for_machine, [], + env, for_machine, [], prefix=comp_class.LINKER_PREFIX if use_linker_prefix else [], exelist=compiler, version=search_version(o), direct=invoked_directly, rsp_syntax=rsp_syntax) elif 'OPTLINK' in o: # Optlink's stdout *may* begin with a \r character. - return linkers.OptlinkDynamicLinker(compiler, for_machine, version=search_version(o)) + return linkers.OptlinkDynamicLinker(compiler, env, for_machine, version=search_version(o)) elif o.startswith('Microsoft') or e.startswith('Microsoft'): out = o or e match = re.search(r'.*(X86|X64|ARM|ARM64).*', out) @@ -103,7 +104,7 @@ def guess_win_linker(env: 'Environment', compiler: T.List[str], comp_class: T.Ty target = 'x86' return linkers.MSVCDynamicLinker( - for_machine, [], machine=target, exelist=compiler, + env, for_machine, [], machine=target, exelist=compiler, prefix=comp_class.LINKER_PREFIX if use_linker_prefix else [], version=search_version(out), direct=invoked_directly, rsp_syntax=rsp_syntax) @@ -128,7 +129,8 @@ def guess_nix_linker(env: 'Environment', compiler: T.List[str], comp_class: T.Ty :extra_args: Any additional arguments required (such as a source file) """ from . import linkers - env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env) + from ..options import OptionKey + env.add_lang_args(comp_class.language, comp_class, for_machine) extra_args = extra_args or [] system = env.machines[for_machine].system @@ -146,6 +148,9 @@ def guess_nix_linker(env: 'Environment', compiler: T.List[str], comp_class: T.Ty override = comp_class.use_linker_args(value[0], comp_version) check_args += override + if env.machines[for_machine].is_os2() and env.coredata.optstore.get_value_for(OptionKey('os2_emxomf')): + check_args += ['-Zomf'] + mlog.debug('-----') p, o, e = Popen_safe_logged(compiler + check_args, msg='Detecting linker via') @@ -165,10 +170,13 @@ def guess_nix_linker(env: 'Environment', compiler: T.List[str], comp_class: T.Ty lld_cls = linkers.LLVMDynamicLinker linker = lld_cls( - compiler, for_machine, comp_class.LINKER_PREFIX, override, system=system, version=v) + compiler, env, for_machine, comp_class.LINKER_PREFIX, override, system=system, version=v) + elif o.startswith("eld"): + linker = linkers.ELDDynamicLinker( + compiler, env, for_machine, comp_class.LINKER_PREFIX, override, version=v) elif 'Snapdragon' in e and 'LLVM' in e: linker = linkers.QualcommLLVMDynamicLinker( - compiler, for_machine, comp_class.LINKER_PREFIX, override, version=v) + compiler, env, for_machine, comp_class.LINKER_PREFIX, override, version=v) elif e.startswith('lld-link: '): # The LLD MinGW frontend didn't respond to --version before version 9.0.0, # and produced an error message about failing to link (when no object @@ -186,7 +194,7 @@ def guess_nix_linker(env: 'Environment', compiler: T.List[str], comp_class: T.Ty _, o, e = Popen_safe([linker_cmd, '--version']) v = search_version(o) - linker = linkers.LLVMDynamicLinker(compiler, for_machine, comp_class.LINKER_PREFIX, override, version=v) + linker = linkers.LLVMDynamicLinker(compiler, env, for_machine, comp_class.LINKER_PREFIX, override, version=v) elif 'GNU' in o or 'GNU' in e: gnu_cls: T.Type[GnuDynamicLinker] # this is always the only thing on stdout, except for swift @@ -197,7 +205,7 @@ def guess_nix_linker(env: 'Environment', compiler: T.List[str], comp_class: T.Ty gnu_cls = linkers.MoldDynamicLinker else: gnu_cls = linkers.GnuBFDDynamicLinker - linker = gnu_cls(compiler, for_machine, comp_class.LINKER_PREFIX, override, version=v) + linker = gnu_cls(compiler, env, for_machine, comp_class.LINKER_PREFIX, override, version=v) elif 'Solaris' in e or 'Solaris' in o: for line in (o+e).split('\n'): if 'ld: Software Generation Utilities' in line: @@ -206,7 +214,7 @@ def guess_nix_linker(env: 'Environment', compiler: T.List[str], comp_class: T.Ty else: v = 'unknown version' linker = linkers.SolarisDynamicLinker( - compiler, for_machine, comp_class.LINKER_PREFIX, override, + compiler, env, for_machine, comp_class.LINKER_PREFIX, override, version=v) elif 'ld: 0706-012 The -- flag is not recognized' in e: if isinstance(comp_class.LINKER_PREFIX, str): @@ -214,17 +222,17 @@ def guess_nix_linker(env: 'Environment', compiler: T.List[str], comp_class: T.Ty else: _, _, e = Popen_safe(compiler + comp_class.LINKER_PREFIX + ['-V'] + extra_args) linker = linkers.AIXDynamicLinker( - compiler, for_machine, comp_class.LINKER_PREFIX, override, + compiler, env, for_machine, comp_class.LINKER_PREFIX, override, version=search_version(e)) elif o.startswith('zig ld'): linker = linkers.ZigCCDynamicLinker( - compiler, for_machine, comp_class.LINKER_PREFIX, override, version=v) + compiler, env, for_machine, comp_class.LINKER_PREFIX, override, version=v) # detect xtools first, bug #10805 elif 'xtools-' in o.split('\n', maxsplit=1)[0]: xtools = o.split(' ', maxsplit=1)[0] v = xtools.split('-', maxsplit=2)[1] linker = linkers.AppleDynamicLinker( - compiler, for_machine, comp_class.LINKER_PREFIX, override, + compiler, env, for_machine, comp_class.LINKER_PREFIX, override, system=system, version=v ) # detect linker on MacOS - must be after other platforms because the @@ -246,9 +254,17 @@ def guess_nix_linker(env: 'Environment', compiler: T.List[str], comp_class: T.Ty else: __failed_to_detect_linker(compiler, check_args, o, e) linker = linkers.AppleDynamicLinker( - compiler, for_machine, comp_class.LINKER_PREFIX, override, + compiler, env, for_machine, comp_class.LINKER_PREFIX, override, system=system, version=v ) + elif 'ld.exe: unrecognized option' in e: + linker = linkers.OS2AoutDynamicLinker( + compiler, env, for_machine, comp_class.LINKER_PREFIX, override, + version='none') + elif 'emxomfld: invalid option' in e: + linker = linkers.OS2OmfDynamicLinker( + compiler, env, for_machine, comp_class.LINKER_PREFIX, override, + version='none') else: __failed_to_detect_linker(compiler, check_args, o, e) return linker diff --git a/mesonbuild/linkers/linkers.py b/mesonbuild/linkers/linkers.py index 59f60e0..d40d5e2 100644 --- a/mesonbuild/linkers/linkers.py +++ b/mesonbuild/linkers/linkers.py @@ -25,8 +25,9 @@ class StaticLinker: id: str - def __init__(self, exelist: T.List[str]): + def __init__(self, exelist: T.List[str], env: Environment): self.exelist = exelist + self.environment = env def get_id(self) -> str: return self.id @@ -65,18 +66,21 @@ class StaticLinker: def get_coverage_link_args(self) -> T.List[str]: return [] - def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, - rpath_paths: T.Tuple[str, ...], build_rpath: str, - install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + def gen_vs_module_defs_args(self) -> T.List[str]: + return [] + + def build_rpath_args(self, build_dir: str, from_dir: str, target: BuildTarget, + extra_paths: T.Optional[T.List[str]] = None + ) -> T.Tuple[T.List[str], T.Set[bytes]]: return ([], set()) - def thread_link_flags(self, env: 'Environment') -> T.List[str]: + def thread_flags(self) -> T.List[str]: return [] - def openmp_flags(self, env: Environment) -> T.List[str]: + def openmp_flags(self) -> T.List[str]: return [] - def get_option_link_args(self, target: 'BuildTarget', env: 'Environment', subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_link_args(self, target: 'BuildTarget', subproject: T.Optional[str] = None) -> T.List[str]: return [] @classmethod @@ -139,11 +143,12 @@ class DynamicLinker(metaclass=abc.ABCMeta): ret += self.prefix_arg + [arg] return ret - def __init__(self, exelist: T.List[str], + def __init__(self, exelist: T.List[str], env: Environment, for_machine: mesonlib.MachineChoice, prefix_arg: T.Union[str, T.List[str]], always_args: T.List[str], *, system: str = 'unknown system', version: str = 'unknown version'): self.exelist = exelist + self.environment = env self.for_machine = for_machine self.system = system self.version = version @@ -190,10 +195,10 @@ class DynamicLinker(metaclass=abc.ABCMeta): def get_option_args(self, target: 'BuildTarget', env: 'Environment', subproject: T.Optional[str] = None) -> T.List[str]: return [] - def get_option_link_args(self, target: 'BuildTarget', env: 'Environment', subproject: T.Optional[str] = None) -> T.List[str]: + def get_option_link_args(self, target: 'BuildTarget', subproject: T.Optional[str] = None) -> T.List[str]: return [] - def has_multi_arguments(self, args: T.List[str], env: 'Environment') -> T.Tuple[bool, bool]: + def has_multi_arguments(self, args: T.List[str]) -> T.Tuple[bool, bool]: raise EnvironmentException(f'Language {self.id} does not support has_multi_link_arguments.') def get_debugfile_name(self, targetfile: str) -> T.Optional[str]: @@ -232,6 +237,9 @@ class DynamicLinker(metaclass=abc.ABCMeta): def get_thinlto_cache_args(self, path: str) -> T.List[str]: return [] + def get_lto_obj_cache_path(self, path: str) -> T.List[str]: + return [] + def sanitizer_args(self, value: T.List[str]) -> T.List[str]: return [] @@ -253,11 +261,14 @@ class DynamicLinker(metaclass=abc.ABCMeta): def get_coverage_args(self) -> T.List[str]: raise EnvironmentException(f"Linker {self.id} doesn't implement coverage data generation.") + def gen_vs_module_defs_args(self, defsfile: str) -> T.List[str]: + return [] + @abc.abstractmethod def get_search_args(self, dirname: str) -> T.List[str]: pass - def export_dynamic_args(self, env: 'Environment') -> T.List[str]: + def export_dynamic_args(self) -> T.List[str]: return [] def import_library_args(self, implibname: str) -> T.List[str]: @@ -267,7 +278,7 @@ class DynamicLinker(metaclass=abc.ABCMeta): """ return [] - def thread_flags(self, env: 'Environment') -> T.List[str]: + def thread_flags(self) -> T.List[str]: return [] def no_undefined_args(self) -> T.List[str]: @@ -297,12 +308,12 @@ class DynamicLinker(metaclass=abc.ABCMeta): def bitcode_args(self) -> T.List[str]: raise MesonException('This linker does not support bitcode bundles') - def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, - rpath_paths: T.Tuple[str, ...], build_rpath: str, - install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + def build_rpath_args(self, build_dir: str, from_dir: str, target: BuildTarget, + extra_paths: T.Optional[T.List[str]] = None + ) -> T.Tuple[T.List[str], T.Set[bytes]]: return ([], set()) - def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, + def get_soname_args(self, prefix: str, shlib_name: str, suffix: str, soversion: str, darwin_versions: T.Tuple[str, str]) -> T.List[str]: return [] @@ -361,8 +372,8 @@ class VisualStudioLinker(VisualStudioLikeLinker, StaticLinker): id = 'lib' - def __init__(self, exelist: T.List[str], machine: str): - StaticLinker.__init__(self, exelist) + def __init__(self, exelist: T.List[str], env: Environment, machine: str): + StaticLinker.__init__(self, exelist, env) VisualStudioLikeLinker.__init__(self, machine) @@ -372,16 +383,16 @@ class IntelVisualStudioLinker(VisualStudioLikeLinker, StaticLinker): id = 'xilib' - def __init__(self, exelist: T.List[str], machine: str): - StaticLinker.__init__(self, exelist) + def __init__(self, exelist: T.List[str], env: Environment, machine: str): + StaticLinker.__init__(self, exelist, env) VisualStudioLikeLinker.__init__(self, machine) class ArLinker(ArLikeLinker, StaticLinker): id = 'ar' - def __init__(self, for_machine: mesonlib.MachineChoice, exelist: T.List[str]): - super().__init__(exelist) + def __init__(self, for_machine: mesonlib.MachineChoice, exelist: T.List[str], env: Environment): + super().__init__(exelist, env) stdo = mesonlib.Popen_safe(self.exelist + ['-h'])[1] # Enable deterministic builds if they are available. stdargs = 'csr' @@ -403,8 +414,10 @@ class ArLinker(ArLikeLinker, StaticLinker): # on Mac OS X, Solaris, or illumos, so don't build them on those OSes. # OS X ld rejects with: "file built for unknown-unsupported file format" # illumos/Solaris ld rejects with: "unknown file type" + # OS/2 ld rejects with: "malformed input file (not rel or archive)" if is_thin and not env.machines[self.for_machine].is_darwin() \ - and not env.machines[self.for_machine].is_sunos(): + and not env.machines[self.for_machine].is_sunos() \ + and not env.machines[self.for_machine].is_os2(): return self.std_thin_args else: return self.std_args @@ -422,8 +435,8 @@ class ArmarLinker(ArLikeLinker, StaticLinker): class DLinker(StaticLinker): - def __init__(self, exelist: T.List[str], arch: str, *, rsp_syntax: RSPFileSyntax = RSPFileSyntax.GCC): - super().__init__(exelist) + def __init__(self, exelist: T.List[str], env: Environment, arch: str, *, rsp_syntax: RSPFileSyntax = RSPFileSyntax.GCC): + super().__init__(exelist, env) self.id = exelist[0] self.arch = arch self.__rsp_syntax = rsp_syntax @@ -449,8 +462,8 @@ class DLinker(StaticLinker): class CcrxLinker(StaticLinker): - def __init__(self, exelist: T.List[str]): - super().__init__(exelist) + def __init__(self, exelist: T.List[str], env: Environment): + super().__init__(exelist, env) self.id = 'rlink' def can_linker_accept_rsp(self) -> bool: @@ -465,8 +478,8 @@ class CcrxLinker(StaticLinker): class Xc16Linker(StaticLinker): - def __init__(self, exelist: T.List[str]): - super().__init__(exelist) + def __init__(self, exelist: T.List[str], env: Environment): + super().__init__(exelist, env) self.id = 'xc16-ar' def can_linker_accept_rsp(self) -> bool: @@ -478,10 +491,18 @@ class Xc16Linker(StaticLinker): def get_linker_always_args(self) -> T.List[str]: return ['rcs'] + +class Xc32ArLinker(ArLinker): + + """Static linker for Microchip XC32 compiler.""" + + id = 'xc32-ar' + + class CompCertLinker(StaticLinker): - def __init__(self, exelist: T.List[str]): - super().__init__(exelist) + def __init__(self, exelist: T.List[str], env: Environment): + super().__init__(exelist, env) self.id = 'ccomp' def can_linker_accept_rsp(self) -> bool: @@ -493,8 +514,8 @@ class CompCertLinker(StaticLinker): class TILinker(StaticLinker): - def __init__(self, exelist: T.List[str]): - super().__init__(exelist) + def __init__(self, exelist: T.List[str], env: Environment): + super().__init__(exelist, env) self.id = 'ti-ar' def can_linker_accept_rsp(self) -> bool: @@ -545,8 +566,8 @@ class MetrowerksStaticLinkerEmbeddedPowerPC(MetrowerksStaticLinker): class TaskingStaticLinker(StaticLinker): id = 'tasking' - def __init__(self, exelist: T.List[str]): - super().__init__(exelist) + def __init__(self, exelist: T.List[str], env: Environment): + super().__init__(exelist, env) def can_linker_accept_rsp(self) -> bool: return True @@ -560,6 +581,13 @@ class TaskingStaticLinker(StaticLinker): def get_linker_always_args(self) -> T.List[str]: return ['-r'] + +class EmxomfArLinker(ArLinker): + id = 'emxomfar' + + def get_std_link_args(self, env: 'Environment', is_thin: bool) -> T.List[str]: + return ['cr'] + def prepare_rpaths(raw_rpaths: T.Tuple[str, ...], build_dir: str, from_dir: str) -> T.List[str]: # The rpaths we write must be relative if they point to the build dir, # because otherwise they have different length depending on the build @@ -674,8 +702,17 @@ class GnuLikeDynamicLinkerMixin(DynamicLinkerBase): def get_coverage_args(self) -> T.List[str]: return ['--coverage'] - def export_dynamic_args(self, env: 'Environment') -> T.List[str]: - m = env.machines[self.for_machine] + def gen_vs_module_defs_args(self, defsfile: str) -> T.List[str]: + # On Windows targets, .def files may be specified on the linker command + # line like an object file. + m = self.environment.machines[self.for_machine] + if m.is_windows() or m.is_cygwin(): + return [defsfile] + # For other targets, discard the .def file. + return [] + + def export_dynamic_args(self) -> T.List[str]: + m = self.environment.machines[self.for_machine] if m.is_windows() or m.is_cygwin(): return self._apply_prefix('--export-all-symbols') return self._apply_prefix('-export-dynamic') @@ -683,8 +720,8 @@ class GnuLikeDynamicLinkerMixin(DynamicLinkerBase): def import_library_args(self, implibname: str) -> T.List[str]: return self._apply_prefix('--out-implib=' + implibname) - def thread_flags(self, env: 'Environment') -> T.List[str]: - if env.machines[self.for_machine].is_haiku(): + def thread_flags(self) -> T.List[str]: + if self.environment.machines[self.for_machine].is_haiku(): return [] return ['-pthread'] @@ -694,22 +731,24 @@ class GnuLikeDynamicLinkerMixin(DynamicLinkerBase): def fatal_warnings(self) -> T.List[str]: return self._apply_prefix('--fatal-warnings') - def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, - suffix: str, soversion: str, darwin_versions: T.Tuple[str, str]) -> T.List[str]: - m = env.machines[self.for_machine] + def get_soname_args(self, prefix: str, shlib_name: str, suffix: str, + soversion: str, darwin_versions: T.Tuple[str, str] + ) -> T.List[str]: + m = self.environment.machines[self.for_machine] if m.is_windows() or m.is_cygwin(): # For PE/COFF the soname argument has no effect return [] sostr = '' if soversion is None else '.' + soversion return self._apply_prefix(f'-soname,{prefix}{shlib_name}.{suffix}{sostr}') - def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, - rpath_paths: T.Tuple[str, ...], build_rpath: str, - install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: - m = env.machines[self.for_machine] + def build_rpath_args(self, build_dir: str, from_dir: str, target: BuildTarget, + extra_paths: T.Optional[T.List[str]] = None + ) -> T.Tuple[T.List[str], T.Set[bytes]]: + m = self.environment.machines[self.for_machine] if m.is_windows() or m.is_cygwin(): return ([], set()) - if not rpath_paths and not install_rpath and not build_rpath: + rpath_paths = target.determine_rpath_dirs() + if not rpath_paths and not target.install_rpath and not target.build_rpath and not extra_paths: return ([], set()) args: T.List[str] = [] origin_placeholder = '$ORIGIN' @@ -722,10 +761,12 @@ class GnuLikeDynamicLinkerMixin(DynamicLinkerBase): for p in all_paths: rpath_dirs_to_remove.add(p.encode('utf8')) # Build_rpath is used as-is (it is usually absolute). - if build_rpath != '': - all_paths.add(build_rpath) - for p in build_rpath.split(':'): + if target.build_rpath != '': + all_paths.add(target.build_rpath) + for p in target.build_rpath.split(':'): rpath_dirs_to_remove.add(p.encode('utf8')) + if extra_paths: + all_paths.update(extra_paths) # TODO: should this actually be "for (dragonfly|open)bsd"? if mesonlib.is_dragonflybsd() or mesonlib.is_openbsd(): @@ -740,7 +781,7 @@ class GnuLikeDynamicLinkerMixin(DynamicLinkerBase): # enough space in the ELF header to hold the final installation RPATH. paths = ':'.join(all_paths) paths_length = len(paths.encode('utf-8')) - install_rpath_length = len(install_rpath.encode('utf-8')) + install_rpath_length = len(target.install_rpath.encode('utf-8')) if paths_length < install_rpath_length: padding = 'X' * (install_rpath_length - paths_length) if not paths: @@ -859,10 +900,13 @@ class AppleDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): return self._apply_prefix('-bitcode_bundle') def fatal_warnings(self) -> T.List[str]: - return self._apply_prefix('-fatal_warnings') + # no one else warns for duplicate libraries, and they're harmless; + # just make ld shup up when testing for supported flags + return self._apply_prefix('-fatal_warnings') + self._apply_prefix('-no_warn_duplicate_libraries') - def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, - suffix: str, soversion: str, darwin_versions: T.Tuple[str, str]) -> T.List[str]: + def get_soname_args(self, prefix: str, shlib_name: str, suffix: str, + soversion: str, darwin_versions: T.Tuple[str, str] + ) -> T.List[str]: install_name = ['@rpath/', prefix, shlib_name] if soversion is not None: install_name.append('.' + soversion) @@ -873,10 +917,11 @@ class AppleDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): '-current_version', darwin_versions[1]]) return args - def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, - rpath_paths: T.Tuple[str, ...], build_rpath: str, - install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: - if not rpath_paths and not install_rpath and not build_rpath: + def build_rpath_args(self, build_dir: str, from_dir: str, target: BuildTarget, + extra_paths: T.Optional[T.List[str]] = None + ) -> T.Tuple[T.List[str], T.Set[bytes]]: + rpath_paths = target.determine_rpath_dirs() + if not rpath_paths and not target.install_rpath and not target.build_rpath and not extra_paths: return ([], set()) args: T.List[str] = [] rpath_dirs_to_remove: T.Set[bytes] = set() @@ -885,8 +930,10 @@ class AppleDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): origin_placeholder = '@loader_path' processed_rpaths = prepare_rpaths(rpath_paths, build_dir, from_dir) all_paths = mesonlib.OrderedSet([os.path.join(origin_placeholder, p) for p in processed_rpaths]) - if build_rpath != '': - all_paths.update(build_rpath.split(':')) + if target.build_rpath != '': + all_paths.update(target.build_rpath.split(':')) + if extra_paths: + all_paths.update(extra_paths) for rp in all_paths: rpath_dirs_to_remove.add(rp.encode('utf8')) args.extend(self._apply_prefix('-rpath,' + rp)) @@ -896,7 +943,11 @@ class AppleDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): def get_thinlto_cache_args(self, path: str) -> T.List[str]: return ["-Wl,-cache_path_lto," + path] - def export_dynamic_args(self, env: 'Environment') -> T.List[str]: + def get_lto_obj_cache_path(self, path: str) -> T.List[str]: + # https://clang.llvm.org/docs/CommandGuide/clang.html#cmdoption-flto + return ["-Wl,-object_path_lto," + path] + + def export_dynamic_args(self) -> T.List[str]: if mesonlib.version_compare(self.version, '>=224.1'): return self._apply_prefix('-export_dynamic') return [] @@ -943,11 +994,11 @@ class LLVMDynamicLinker(GnuLikeDynamicLinkerMixin, PosixDynamicLinkerMixin, Dyna id = 'ld.lld' - def __init__(self, exelist: T.List[str], + def __init__(self, exelist: T.List[str], env: Environment, for_machine: mesonlib.MachineChoice, prefix_arg: T.Union[str, T.List[str]], always_args: T.List[str], *, system: str = 'unknown system', version: str = 'unknown version'): - super().__init__(exelist, for_machine, prefix_arg, always_args, system=system, version=version) + super().__init__(exelist, env, for_machine, prefix_arg, always_args, system=system, version=version) # Some targets don't seem to support this argument (windows, wasm, ...) self.has_allow_shlib_undefined = self._supports_flag('--allow-shlib-undefined', always_args) @@ -1015,16 +1066,17 @@ class WASMDynamicLinker(GnuLikeDynamicLinkerMixin, PosixDynamicLinkerMixin, Dyna def no_undefined_args(self) -> T.List[str]: return ['-sERROR_ON_UNDEFINED_SYMBOLS=1'] - def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, - suffix: str, soversion: str, darwin_versions: T.Tuple[str, str]) -> T.List[str]: + def get_soname_args(self, prefix: str, shlib_name: str, suffix: str, + soversion: str, darwin_versions: T.Tuple[str, str] + ) -> T.List[str]: raise MesonException(f'{self.id} does not support shared libraries.') def get_asneeded_args(self) -> T.List[str]: return [] - def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, - rpath_paths: T.Tuple[str, ...], build_rpath: str, - install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + def build_rpath_args(self, build_dir: str, from_dir: str, target: BuildTarget, + extra_paths: T.Optional[T.List[str]] = None + ) -> T.Tuple[T.List[str], T.Set[bytes]]: return ([], set()) @@ -1034,9 +1086,9 @@ class CcrxDynamicLinker(DynamicLinker): id = 'rlink' - def __init__(self, for_machine: mesonlib.MachineChoice, + def __init__(self, env: Environment, for_machine: mesonlib.MachineChoice, *, version: str = 'unknown version'): - super().__init__(['rlink.exe'], for_machine, '', [], + super().__init__(['rlink.exe'], env, for_machine, '', [], version=version) def get_accepts_rsp(self) -> bool: @@ -1057,8 +1109,9 @@ class CcrxDynamicLinker(DynamicLinker): def get_allow_undefined_args(self) -> T.List[str]: return [] - def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, - suffix: str, soversion: str, darwin_versions: T.Tuple[str, str]) -> T.List[str]: + def get_soname_args(self, prefix: str, shlib_name: str, suffix: str, + soversion: str, darwin_versions: T.Tuple[str, str] + ) -> T.List[str]: return [] @@ -1068,9 +1121,9 @@ class Xc16DynamicLinker(DynamicLinker): id = 'xc16-gcc' - def __init__(self, for_machine: mesonlib.MachineChoice, + def __init__(self, env: Environment, for_machine: mesonlib.MachineChoice, *, version: str = 'unknown version'): - super().__init__(['xc16-gcc'], for_machine, '', [], + super().__init__(['xc16-gcc'], env, for_machine, '', [], version=version) def get_link_whole_for(self, args: T.List[str]) -> T.List[str]: @@ -1096,24 +1149,45 @@ class Xc16DynamicLinker(DynamicLinker): def get_allow_undefined_args(self) -> T.List[str]: return [] - def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, - suffix: str, soversion: str, darwin_versions: T.Tuple[str, str]) -> T.List[str]: + def get_soname_args(self, prefix: str, shlib_name: str, suffix: str, + soversion: str, darwin_versions: T.Tuple[str, str] + ) -> T.List[str]: return [] - def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, - rpath_paths: T.Tuple[str, ...], build_rpath: str, - install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + def build_rpath_args(self, build_dir: str, from_dir: str, target: BuildTarget, + extra_paths: T.Optional[T.List[str]] = None + ) -> T.Tuple[T.List[str], T.Set[bytes]]: return ([], set()) + +class Xc32DynamicLinker(GnuDynamicLinker): + + """Linker for Microchip XC32 compiler.""" + + id = 'ld.xc32' + + def sanitizer_args(self, value: T.List[str]) -> T.List[str]: + return [] + + def get_coverage_args(self) -> T.List[str]: + return DynamicLinker.get_coverage_args(self) + + def get_pie_args(self) -> T.List[str]: + return DynamicLinker.get_pie_args(self) + + def thread_flags(self) -> T.List[str]: + return [] + + class CompCertDynamicLinker(DynamicLinker): """Linker for CompCert C compiler.""" id = 'ccomp' - def __init__(self, for_machine: mesonlib.MachineChoice, + def __init__(self, env: Environment, for_machine: mesonlib.MachineChoice, *, version: str = 'unknown version'): - super().__init__(['ccomp'], for_machine, '', [], + super().__init__(['ccomp'], env, for_machine, '', [], version=version) def get_link_whole_for(self, args: T.List[str]) -> T.List[str]: @@ -1139,13 +1213,9 @@ class CompCertDynamicLinker(DynamicLinker): def get_allow_undefined_args(self) -> T.List[str]: return [] - def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, - suffix: str, soversion: str, darwin_versions: T.Tuple[str, str]) -> T.List[str]: - raise MesonException(f'{self.id} does not support shared libraries.') - - def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, - rpath_paths: T.Tuple[str, ...], build_rpath: str, - install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + def build_rpath_args(self, build_dir: str, from_dir: str, target: BuildTarget, + extra_paths: T.Optional[T.List[str]] = None + ) -> T.Tuple[T.List[str], T.Set[bytes]]: return ([], set()) class TIDynamicLinker(DynamicLinker): @@ -1154,9 +1224,9 @@ class TIDynamicLinker(DynamicLinker): id = 'ti' - def __init__(self, exelist: T.List[str], for_machine: mesonlib.MachineChoice, + def __init__(self, exelist: T.List[str], env: Environment, for_machine: mesonlib.MachineChoice, *, version: str = 'unknown version'): - super().__init__(exelist, for_machine, '', [], + super().__init__(exelist, env, for_machine, '', [], version=version) def get_link_whole_for(self, args: T.List[str]) -> T.List[str]: @@ -1200,9 +1270,9 @@ class ArmDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): id = 'armlink' - def __init__(self, for_machine: mesonlib.MachineChoice, + def __init__(self, env: Environment, for_machine: mesonlib.MachineChoice, *, version: str = 'unknown version'): - super().__init__(['armlink'], for_machine, '', [], + super().__init__(['armlink'], env, for_machine, '', [], version=version) def get_accepts_rsp(self) -> bool: @@ -1223,7 +1293,7 @@ class ArmClangDynamicLinker(ArmDynamicLinker): extends a few things as needed. """ - def export_dynamic_args(self, env: 'Environment') -> T.List[str]: + def export_dynamic_args(self) -> T.List[str]: return ['--export_dynamic'] def import_library_args(self, implibname: str) -> T.List[str]: @@ -1235,6 +1305,12 @@ class QualcommLLVMDynamicLinker(LLVMDynamicLinker): id = 'ld.qcld' +class ELDDynamicLinker(GnuLikeDynamicLinkerMixin, PosixDynamicLinkerMixin, DynamicLinker): + + """Qualcomm's opensource embedded linker""" + + id = 'ld.eld' + class NAGDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): @@ -1249,17 +1325,20 @@ class NAGDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): id = 'nag' - def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, - rpath_paths: T.Tuple[str, ...], build_rpath: str, - install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: - if not rpath_paths and not install_rpath and not build_rpath: + def build_rpath_args(self, build_dir: str, from_dir: str, target: BuildTarget, + extra_paths: T.Optional[T.List[str]] = None + ) -> T.Tuple[T.List[str], T.Set[bytes]]: + rpath_paths = target.determine_rpath_dirs() + if not rpath_paths and not target.install_rpath and not target.build_rpath and not extra_paths: return ([], set()) args: T.List[str] = [] origin_placeholder = '$ORIGIN' processed_rpaths = prepare_rpaths(rpath_paths, build_dir, from_dir) all_paths = mesonlib.OrderedSet([os.path.join(origin_placeholder, p) for p in processed_rpaths]) - if build_rpath != '': - all_paths.add(build_rpath) + if target.build_rpath != '': + all_paths.add(target.build_rpath) + if extra_paths: + all_paths.update(extra_paths) for rp in all_paths: args.extend(self._apply_prefix('-Wl,-Wl,,-rpath,,' + rp)) @@ -1282,8 +1361,9 @@ class PGIDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): def get_allow_undefined_args(self) -> T.List[str]: return [] - def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, - suffix: str, soversion: str, darwin_versions: T.Tuple[str, str]) -> T.List[str]: + def get_soname_args(self, prefix: str, shlib_name: str, suffix: str, + soversion: str, darwin_versions: T.Tuple[str, str] + ) -> T.List[str]: return [] def get_std_shared_lib_args(self) -> T.List[str]: @@ -1294,10 +1374,11 @@ class PGIDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): return ['-shared'] return [] - def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, - rpath_paths: T.Tuple[str, ...], build_rpath: str, - install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: - if not env.machines[self.for_machine].is_windows(): + def build_rpath_args(self, build_dir: str, from_dir: str, target: BuildTarget, + extra_paths: T.Optional[T.List[str]] = None + ) -> T.Tuple[T.List[str], T.Set[bytes]]: + if not self.environment.machines[self.for_machine].is_windows(): + rpath_paths = target.determine_rpath_dirs() return (['-R' + os.path.join(build_dir, p) for p in rpath_paths], set()) return ([], set()) @@ -1305,8 +1386,8 @@ NvidiaHPC_DynamicLinker = PGIDynamicLinker class PGIStaticLinker(StaticLinker): - def __init__(self, exelist: T.List[str]): - super().__init__(exelist) + def __init__(self, exelist: T.List[str], env: Environment): + super().__init__(exelist, env) self.id = 'ar' self.std_args = ['-r'] @@ -1339,12 +1420,12 @@ class VisualStudioLikeLinkerMixin(DynamicLinkerBase): 's': ['/INCREMENTAL:NO', '/OPT:REF'], } - def __init__(self, exelist: T.List[str], for_machine: mesonlib.MachineChoice, - prefix_arg: T.Union[str, T.List[str]], always_args: T.List[str], *, - version: str = 'unknown version', direct: bool = True, machine: str = 'x86', - rsp_syntax: RSPFileSyntax = RSPFileSyntax.MSVC): - # There's no way I can find to make mypy understand what's going on here - super().__init__(exelist, for_machine, prefix_arg, always_args, version=version) + def __init__(self, exelist: T.List[str], env: Environment, + for_machine: mesonlib.MachineChoice, prefix_arg: T.Union[str, T.List[str]], + always_args: T.List[str], *, version: str = 'unknown version', + direct: bool = True, machine: str = 'x86', rsp_syntax: + RSPFileSyntax = RSPFileSyntax.MSVC): + super().__init__(exelist, env, for_machine, prefix_arg, always_args, version=version) self.machine = machine self.direct = direct self.rsp_syntax = rsp_syntax @@ -1382,8 +1463,14 @@ class VisualStudioLikeLinkerMixin(DynamicLinkerBase): def get_allow_undefined_args(self) -> T.List[str]: return [] - def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, - suffix: str, soversion: str, darwin_versions: T.Tuple[str, str]) -> T.List[str]: + def gen_vs_module_defs_args(self, defsfile: str) -> T.List[str]: + # With MSVC, DLLs only export symbols that are explicitly exported, + # so if a module defs file is specified, we use that to export symbols + return ['/DEF:' + defsfile] + + def get_soname_args(self, prefix: str, shlib_name: str, suffix: str, + soversion: str, darwin_versions: T.Tuple[str, str] + ) -> T.List[str]: return [] def import_library_args(self, implibname: str) -> T.List[str]: @@ -1403,12 +1490,13 @@ class MSVCDynamicLinker(VisualStudioLikeLinkerMixin, DynamicLinker): id = 'link' - def __init__(self, for_machine: mesonlib.MachineChoice, always_args: T.List[str], *, + def __init__(self, env: Environment, for_machine: mesonlib.MachineChoice, + always_args: T.List[str], *, exelist: T.Optional[T.List[str]] = None, prefix: T.Union[str, T.List[str]] = '', machine: str = 'x86', version: str = 'unknown version', direct: bool = True, rsp_syntax: RSPFileSyntax = RSPFileSyntax.MSVC): - super().__init__(exelist or ['link.exe'], for_machine, + super().__init__(exelist or ['link.exe'], env, for_machine, prefix, always_args, machine=machine, version=version, direct=direct, rsp_syntax=rsp_syntax) @@ -1428,12 +1516,13 @@ class ClangClDynamicLinker(VisualStudioLikeLinkerMixin, DynamicLinker): id = 'lld-link' - def __init__(self, for_machine: mesonlib.MachineChoice, always_args: T.List[str], *, + def __init__(self, env: Environment, for_machine: mesonlib.MachineChoice, + always_args: T.List[str], *, exelist: T.Optional[T.List[str]] = None, prefix: T.Union[str, T.List[str]] = '', machine: str = 'x86', version: str = 'unknown version', direct: bool = True, rsp_syntax: RSPFileSyntax = RSPFileSyntax.MSVC): - super().__init__(exelist or ['lld-link.exe'], for_machine, + super().__init__(exelist or ['lld-link.exe'], env, for_machine, prefix, always_args, machine=machine, version=version, direct=direct, rsp_syntax=rsp_syntax) @@ -1461,12 +1550,13 @@ class XilinkDynamicLinker(VisualStudioLikeLinkerMixin, DynamicLinker): id = 'xilink' - def __init__(self, for_machine: mesonlib.MachineChoice, always_args: T.List[str], *, + def __init__(self, env: Environment, for_machine: mesonlib.MachineChoice, + always_args: T.List[str], *, exelist: T.Optional[T.List[str]] = None, prefix: T.Union[str, T.List[str]] = '', machine: str = 'x86', version: str = 'unknown version', direct: bool = True): - super().__init__(['xilink.exe'], for_machine, '', always_args, version=version) + super().__init__(['xilink.exe'], env, for_machine, '', always_args, version=version) def get_win_subsystem_args(self, value: str) -> T.List[str]: return self._apply_prefix([f'/SUBSYSTEM:{value.upper()}']) @@ -1505,26 +1595,29 @@ class SolarisDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): def fatal_warnings(self) -> T.List[str]: return ['-z', 'fatal-warnings'] - def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, - rpath_paths: T.Tuple[str, ...], build_rpath: str, - install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: - if not rpath_paths and not install_rpath and not build_rpath: + def build_rpath_args(self, build_dir: str, from_dir: str, target: BuildTarget, + extra_paths: T.Optional[T.List[str]] = None + ) -> T.Tuple[T.List[str], T.Set[bytes]]: + rpath_paths = target.determine_rpath_dirs() + if not rpath_paths and not target.install_rpath and not target.build_rpath and not extra_paths: return ([], set()) processed_rpaths = prepare_rpaths(rpath_paths, build_dir, from_dir) all_paths = mesonlib.OrderedSet([os.path.join('$ORIGIN', p) for p in processed_rpaths]) rpath_dirs_to_remove: T.Set[bytes] = set() for p in all_paths: rpath_dirs_to_remove.add(p.encode('utf8')) - if build_rpath != '': - all_paths.add(build_rpath) - for p in build_rpath.split(':'): + if target.build_rpath != '': + all_paths.add(target.build_rpath) + for p in target.build_rpath.split(':'): rpath_dirs_to_remove.add(p.encode('utf8')) + if extra_paths: + all_paths.update(extra_paths) # In order to avoid relinking for RPATH removal, the binary needs to contain just # enough space in the ELF header to hold the final installation RPATH. paths = ':'.join(all_paths) paths_length = len(paths.encode('utf-8')) - install_rpath_length = len(install_rpath.encode('utf-8')) + install_rpath_length = len(target.install_rpath.encode('utf-8')) if paths_length < install_rpath_length: padding = 'X' * (install_rpath_length - paths_length) if not paths: @@ -1533,8 +1626,9 @@ class SolarisDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): paths = paths + ':' + padding return (self._apply_prefix(f'-rpath,{paths}'), rpath_dirs_to_remove) - def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, - suffix: str, soversion: str, darwin_versions: T.Tuple[str, str]) -> T.List[str]: + def get_soname_args(self, prefix: str, shlib_name: str, suffix: str, + soversion: str, darwin_versions: T.Tuple[str, str] + ) -> T.List[str]: sostr = '' if soversion is None else '.' + soversion return self._apply_prefix(f'-soname,{prefix}{shlib_name}.{suffix}{sostr}') @@ -1575,20 +1669,20 @@ class AIXDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): # archives or not." return args - def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, - rpath_paths: T.Tuple[str, ...], build_rpath: str, - install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: + def build_rpath_args(self, build_dir: str, from_dir: str, target: BuildTarget, + extra_paths: T.Optional[T.List[str]] = None + ) -> T.Tuple[T.List[str], T.Set[bytes]]: all_paths: mesonlib.OrderedSet[str] = mesonlib.OrderedSet() # install_rpath first, followed by other paths, and the system path last - if install_rpath != '': - all_paths.add(install_rpath) - if build_rpath != '': - all_paths.add(build_rpath) - for p in rpath_paths: + if target.install_rpath != '': + all_paths.add(target.install_rpath) + if target.build_rpath != '': + all_paths.add(target.build_rpath) + for p in target.determine_rpath_dirs(): all_paths.add(os.path.join(build_dir, p)) # We should consider allowing the $LIBPATH environment variable # to override sys_path. - sys_path = env.get_compiler_system_lib_dirs(self.for_machine) + sys_path = self.environment.get_compiler_system_lib_dirs(self.for_machine) if len(sys_path) == 0: # get_compiler_system_lib_dirs doesn't support our compiler. # Use the default system library path @@ -1598,9 +1692,11 @@ class AIXDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): for p in sys_path: if os.path.isdir(p): all_paths.add(p) + if extra_paths: + all_paths.update(extra_paths) return (self._apply_prefix('-blibpath:' + ':'.join(all_paths)), set()) - def thread_flags(self, env: 'Environment') -> T.List[str]: + def thread_flags(self) -> T.List[str]: return ['-pthread'] @@ -1610,11 +1706,11 @@ class OptlinkDynamicLinker(VisualStudioLikeLinkerMixin, DynamicLinker): id = 'optlink' - def __init__(self, exelist: T.List[str], for_machine: mesonlib.MachineChoice, + def __init__(self, exelist: T.List[str], env: Environment, for_machine: mesonlib.MachineChoice, *, version: str = 'unknown version'): # Use optlink instead of link so we don't interfere with other link.exe # implementations. - super().__init__(exelist, for_machine, '', [], version=version) + super().__init__(exelist, env, for_machine, '', [], version=version) def get_allow_undefined_args(self) -> T.List[str]: return [] @@ -1674,16 +1770,17 @@ class CudaLinker(PosixDynamicLinkerMixin, DynamicLinker): def get_allow_undefined_args(self) -> T.List[str]: return [] - def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, - suffix: str, soversion: str, darwin_versions: T.Tuple[str, str]) -> T.List[str]: + def get_soname_args(self, prefix: str, shlib_name: str, suffix: str, + soversion: str, darwin_versions: T.Tuple[str, str] + ) -> T.List[str]: return [] class MetrowerksLinker(DynamicLinker): - def __init__(self, exelist: T.List[str], for_machine: mesonlib.MachineChoice, + def __init__(self, exelist: T.List[str], env: Environment, for_machine: mesonlib.MachineChoice, *, version: str = 'unknown version'): - super().__init__(exelist, for_machine, '', [], + super().__init__(exelist, env, for_machine, '', [], version=version) def fatal_warnings(self) -> T.List[str]: @@ -1707,8 +1804,9 @@ class MetrowerksLinker(DynamicLinker): def invoked_by_compiler(self) -> bool: return False - def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, - suffix: str, soversion: str, darwin_versions: T.Tuple[str, str]) -> T.List[str]: + def get_soname_args(self, prefix: str, shlib_name: str, suffix: str, + soversion: str, darwin_versions: T.Tuple[str, str] + ) -> T.List[str]: raise MesonException(f'{self.id} does not support shared libraries.') @@ -1732,9 +1830,9 @@ class TaskingLinker(DynamicLinker): 's': ['-Os'], } - def __init__(self, exelist: T.List[str], for_machine: mesonlib.MachineChoice, + def __init__(self, exelist: T.List[str], env: Environment, for_machine: mesonlib.MachineChoice, *, version: str = 'unknown version'): - super().__init__(exelist, for_machine, '', [], + super().__init__(exelist, env, for_machine, '', [], version=version) def get_accepts_rsp(self) -> bool: @@ -1771,3 +1869,32 @@ class TaskingLinker(DynamicLinker): for a in args: l.extend(self._apply_prefix('-Wl--whole-archive=' + a)) return l + + +class OS2DynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): + """ld and emxomfld""" + + def get_allow_undefined_args(self) -> T.List[str]: + return [] + + def thread_flags(self) -> T.List[str]: + return ['-lpthread'] + + def get_std_shared_lib_args(self) -> T.List[str]: + return ['-Zdll'] + + def get_soname_args(self, prefix: str, shlib_name: str, suffix: str, + soversion: str, darwin_versions: T.Tuple[str, str] + ) -> T.List[str]: + return [] + + def get_always_args(self) -> T.List[str]: + return ['-Zomf'] + + +class OS2AoutDynamicLinker(OS2DynamicLinker): + id = 'ld.os2' + + +class OS2OmfDynamicLinker(OS2DynamicLinker): + id = 'emxomfld' diff --git a/mesonbuild/machinefile.py b/mesonbuild/machinefile.py index b39a472..19a0865 100644 --- a/mesonbuild/machinefile.py +++ b/mesonbuild/machinefile.py @@ -9,26 +9,12 @@ import os from . import mparser +from .cmdline import CmdLineFileParser from .mesonlib import MesonException if T.TYPE_CHECKING: - from .coredata import StrOrBytesPath from .options import ElementaryOptionValues -class CmdLineFileParser(configparser.ConfigParser): - def __init__(self) -> None: - # We don't want ':' as key delimiter, otherwise it would break when - # storing subproject options like "subproject:option=value" - super().__init__(delimiters=['='], interpolation=None) - - def read(self, filenames: T.Union['StrOrBytesPath', T.Iterable['StrOrBytesPath']], encoding: T.Optional[str] = 'utf-8') -> T.List[str]: - return super().read(filenames, encoding) - - def optionxform(self, optionstr: str) -> str: - # Don't call str.lower() on keys - return optionstr - - class MachineFileParser(): def __init__(self, filenames: T.List[str], sourcedir: str) -> None: self.parser = CmdLineFileParser() @@ -97,12 +83,12 @@ class MachineFileParser(): elif isinstance(node, mparser.ArithmeticNode): l = self._evaluate_statement(node.left) r = self._evaluate_statement(node.right) - if node.operation == 'add': + if node.operation == '+': if isinstance(l, str) and isinstance(r, str): return l + r if isinstance(l, list) and isinstance(r, list): return l + r - elif node.operation == 'div': + elif node.operation == '/': if isinstance(l, str) and isinstance(r, str): return os.path.join(l, r) raise MesonException('Unsupported node type') diff --git a/mesonbuild/mcompile.py b/mesonbuild/mcompile.py index cfaeb79..7221958 100644 --- a/mesonbuild/mcompile.py +++ b/mesonbuild/mcompile.py @@ -18,7 +18,7 @@ from . import mlog from . import mesonlib from .options import OptionKey from .mesonlib import MesonException, RealPathAction, join_args, listify_array_value, setup_vsenv -from mesonbuild.environment import detect_ninja +from mesonbuild.tooldetect import detect_ninja from mesonbuild import build if T.TYPE_CHECKING: diff --git a/mesonbuild/mconf.py b/mesonbuild/mconf.py index 416caf1..4de7bc2 100644 --- a/mesonbuild/mconf.py +++ b/mesonbuild/mconf.py @@ -13,6 +13,7 @@ import typing as T import collections from . import build +from . import cmdline from . import coredata from . import options from . import environment @@ -28,12 +29,11 @@ if T.TYPE_CHECKING: from typing_extensions import Protocol import argparse - class CMDOptions(coredata.SharedCMDOptions, Protocol): + class CMDOptions(cmdline.SharedCMDOptions, Protocol): builddir: str clearcache: bool pager: bool - unset_opts: T.List[str] # cannot be TV_Loggable, because non-ansidecorators do direct string concat LOGLINE = T.Union[str, mlog.AnsiDecorator] @@ -41,13 +41,13 @@ if T.TYPE_CHECKING: # Note: when adding arguments, please also add them to the completion # scripts in $MESONSRC/data/shell-completions/ def add_arguments(parser: 'argparse.ArgumentParser') -> None: - coredata.register_builtin_arguments(parser) + cmdline.register_builtin_arguments(parser) parser.add_argument('builddir', nargs='?', default='.') parser.add_argument('--clearcache', action='store_true', default=False, help='Clear cached state (e.g. found dependencies)') parser.add_argument('--no-pager', action='store_false', dest='pager', help='Do not redirect output to a pager') - parser.add_argument('-U', action='append', dest='unset_opts', default=[], + parser.add_argument('-U', action=cmdline.KeyNoneAction, dest='cmd_line_options', default={}, help='Remove a subproject option.') def stringify(val: T.Any) -> str: @@ -73,6 +73,7 @@ class Conf: self.build_dir = os.path.dirname(self.build_dir) self.build = None self.max_choices_line_length = 60 + self.pending_section: T.Optional[str] = None self.name_col: T.List[LOGLINE] = [] self.value_col: T.List[LOGLINE] = [] self.choices_col: T.List[LOGLINE] = [] @@ -125,9 +126,6 @@ class Conf: def clear_cache(self) -> None: self.coredata.clear_cache() - def set_options(self, options: T.Dict[OptionKey, str]) -> bool: - return self.coredata.set_options(options) - def save(self) -> None: # Do nothing when using introspection if self.default_values_only: @@ -149,7 +147,7 @@ class Conf: Each column will have a specific width, and will be line wrapped. """ total_width = shutil.get_terminal_size(fallback=(160, 0))[0] - _col = max(total_width // 5, 20) + _col = max(total_width // 5, 24) last_column = total_width - (3 * _col) - 3 four_column = (_col, _col, _col, last_column if last_column > 1 else _col) @@ -194,7 +192,7 @@ class Conf: ) -> T.Dict[str, options.MutableKeyedOptionDictType]: result: T.Dict[str, options.MutableKeyedOptionDictType] = {} for k, o in opts.items(): - if k.subproject: + if k.subproject is not None: self.all_subprojects.add(k.subproject) result.setdefault(k.subproject, {})[k] = o return result @@ -209,12 +207,15 @@ class Conf: self.choices_col.append(choices) self.descr_col.append(descr) - def add_option(self, name: str, descr: str, value: T.Any, choices: T.Any) -> None: + def add_option(self, key: OptionKey, descr: str, value: T.Any, choices: T.Any) -> None: + self._add_section() value = stringify(value) choices = stringify(choices) - self._add_line(mlog.green(name), mlog.yellow(value), mlog.blue(choices), descr) + self._add_line(mlog.green(str(key.evolve(subproject=None))), mlog.yellow(value), + mlog.blue(choices), descr) def add_title(self, title: str) -> None: + self._add_section() newtitle = mlog.cyan(title) descr = mlog.cyan('Description') value = mlog.cyan('Default Value' if self.default_values_only else 'Current Value') @@ -223,11 +224,17 @@ class Conf: self._add_line(newtitle, value, choices, descr) self._add_line('-' * len(newtitle), '-' * len(value), '-' * len(choices), '-' * len(descr)) - def add_section(self, section: str) -> None: + def _add_section(self) -> None: + if not self.pending_section: + return self.print_margin = 0 self._add_line('', '', '', '') - self._add_line(mlog.normal_yellow(section + ':'), '', '', '') + self._add_line(mlog.normal_yellow(self.pending_section + ':'), '', '', '') self.print_margin = 2 + self.pending_section = None + + def add_section(self, section: str) -> None: + self.pending_section = section def print_options(self, title: str, opts: T.Union[options.MutableKeyedOptionDictType, options.OptionStore]) -> None: if not opts: @@ -242,7 +249,7 @@ class Conf: # printable_value = '<inherited from main project>' #if isinstance(o, options.UserFeatureOption) and o.is_auto(): # printable_value = auto.printable_value() - self.add_option(k.name, o.description, printable_value, o.printable_choices()) + self.add_option(k, o.description, printable_value, o.printable_choices()) def print_conf(self, pager: bool) -> None: if pager: @@ -291,15 +298,15 @@ class Conf: project_options = self.split_options_per_subproject({k: v for k, v in self.coredata.optstore.items() if self.coredata.optstore.is_project_option(k)}) show_build_options = self.default_values_only or self.build.environment.is_cross_build() - self.add_section('Main project options') + self.add_section('Global build options') self.print_options('Core options', host_core_options[None]) if show_build_options and build_core_options: self.print_options('', build_core_options[None]) self.print_options('Backend options', {k: v for k, v in self.coredata.optstore.items() if self.coredata.optstore.is_backend_option(k)}) self.print_options('Base options', {k: v for k, v in self.coredata.optstore.items() if self.coredata.optstore.is_base_option(k)}) - self.print_options('Compiler options', host_compiler_options.get('', {})) + self.print_options('Compiler options', host_compiler_options.get(None, {})) if show_build_options: - self.print_options('', build_compiler_options.get('', {})) + self.print_options('', build_compiler_options.get(None, {})) for mod, mod_options in module_options.items(): self.print_options(f'{mod} module options', mod_options) self.print_options('Directories', dir_options) @@ -307,8 +314,9 @@ class Conf: self.print_options('Project options', project_options.get('', {})) for subproject in sorted(self.all_subprojects): if subproject == '': - continue - self.add_section('Subproject ' + subproject) + self.add_section('Main project') + else: + self.add_section('Subproject ' + subproject) if subproject in host_core_options: self.print_options('Core options', host_core_options[subproject]) if subproject in build_core_options and show_build_options: @@ -317,7 +325,7 @@ class Conf: self.print_options('Compiler options', host_compiler_options[subproject]) if subproject in build_compiler_options and show_build_options: self.print_options('', build_compiler_options[subproject]) - if subproject in project_options: + if subproject != '' and subproject in project_options: self.print_options('Project options', project_options[subproject]) self.print_aligned() @@ -342,16 +350,12 @@ class Conf: if self.coredata.optstore.augments: mlog.log('\nCurrently set option augments:') for k, v in self.coredata.optstore.augments.items(): - mlog.log(f'{k:21}{v:10}') + mlog.log(f'{k!s:21}{v:10}') else: mlog.log('\nThere are no option augments.') def has_option_flags(options: CMDOptions) -> bool: - if options.cmd_line_options: - return True - if options.unset_opts: - return True - return False + return bool(options.cmd_line_options) def is_print_only(options: CMDOptions) -> bool: if has_option_flags(options): @@ -373,12 +377,8 @@ def run_impl(options: CMDOptions, builddir: str) -> int: save = False if has_option_flags(options): - unset_opts = getattr(options, 'unset_opts', []) - all_D = options.projectoptions[:] - for keystr, valstr in options.cmd_line_options.items(): - all_D.append(f'{keystr}={valstr}') - save |= c.coredata.optstore.set_from_configure_command(all_D, unset_opts) - coredata.update_cmd_line_file(builddir, options) + save |= c.coredata.set_from_configure_command(options) + cmdline.update_cmd_line_file(builddir, options) if options.clearcache: c.clear_cache() save = True @@ -397,6 +397,6 @@ def run_impl(options: CMDOptions, builddir: str) -> int: return 0 def run(options: CMDOptions) -> int: - coredata.parse_cmd_line_options(options) + cmdline.parse_cmd_line_options(options) builddir = os.path.abspath(os.path.realpath(options.builddir)) return run_impl(options, builddir) diff --git a/mesonbuild/mdevenv.py b/mesonbuild/mdevenv.py index 4962d96..e6c4fad 100644 --- a/mesonbuild/mdevenv.py +++ b/mesonbuild/mdevenv.py @@ -4,13 +4,15 @@ import os, subprocess import argparse import tempfile import shutil +import sys import itertools +import signal import typing as T from pathlib import Path from . import build, minstall from .mesonlib import (EnvironmentVariables, MesonException, join_args, is_windows, setup_vsenv, - get_wine_shortpath, MachineChoice, relpath) + get_wine_shortpath, MachineChoice, relpath, is_osx) from .options import OptionKey from . import mlog @@ -82,7 +84,7 @@ def bash_completion_files(b: build.Build, install_data: 'InstallData') -> T.List from .dependencies.pkgconfig import PkgConfigDependency result = [] dep = PkgConfigDependency('bash-completion', b.environment, - {'required': False, 'silent': True, 'version': '>=2.10'}) + {'required': False, 'silent': True, 'version': ['>=2.10']}) if dep.found(): prefix = b.environment.coredata.optstore.get_value_for(OptionKey('prefix')) assert isinstance(prefix, str), 'for mypy' @@ -150,6 +152,14 @@ def write_gdb_script(privatedir: Path, install_data: 'InstallData', workdir: Pat mlog.log(' - Change current workdir to', mlog.bold(str(rel_path.parent)), 'or use', mlog.bold(f'--init-command {rel_path}')) +def macos_sip_enabled() -> bool: + if not is_osx(): + return False + ret = subprocess.run(["csrutil", "status"], text=True, capture_output=True, encoding='utf-8') + if not ret.stdout: + return True + return 'enabled' in ret.stdout + def dump(devenv: T.Dict[str, str], varnames: T.Set[str], dump_format: T.Optional[str], output: T.Optional[T.TextIO] = None) -> None: for name in varnames: print(f'{name}="{devenv[name]}"', file=output) @@ -192,6 +202,8 @@ def run(options: argparse.Namespace) -> int: args = options.devcmd if not args: prompt_prefix = f'[{b.project_name}]' + if os.environ.get("MESON_DISABLE_PS1_OVERRIDE"): + prompt_prefix = None shell_env = os.environ.get("SHELL") # Prefer $SHELL in a MSYS2 bash despite it being Windows if shell_env and os.path.exists(shell_env): @@ -202,8 +214,9 @@ def run(options: argparse.Namespace) -> int: mlog.warning('Failed to determine Windows shell, fallback to cmd.exe') if shell in POWERSHELL_EXES: args = [shell, '-NoLogo', '-NoExit'] - prompt = f'function global:prompt {{ "{prompt_prefix} PS " + $PWD + "> "}}' - args += ['-Command', prompt] + if prompt_prefix: + prompt = f'function global:prompt {{ "{prompt_prefix} PS " + $PWD + "> "}}' + args += ['-Command', prompt] else: args = [os.environ.get("COMSPEC", r"C:\WINDOWS\system32\cmd.exe")] args += ['/k', f'prompt {prompt_prefix} $P$G'] @@ -213,21 +226,51 @@ def run(options: argparse.Namespace) -> int: # Let the GC remove the tmp file tmprc = tempfile.NamedTemporaryFile(mode='w') tmprc.write('[ -e ~/.bashrc ] && . ~/.bashrc\n') - if not os.environ.get("MESON_DISABLE_PS1_OVERRIDE"): + if prompt_prefix: tmprc.write(f'export PS1="{prompt_prefix} $PS1"\n') for f in bash_completion_files(b, install_data): tmprc.write(f'. "{f}"\n') tmprc.flush() args.append("--rcfile") args.append(tmprc.name) + elif args[0].endswith('fish'): + # Ignore SIGINT while using fish as the shell to make it behave + # like other shells such as bash and zsh. + # See: https://gitlab.freedesktop.org/gstreamer/gst-build/issues/18 + signal.signal(signal.SIGINT, lambda _, __: True) + if prompt_prefix: + args.append('--init-command') + prompt_cmd = f'''functions --copy fish_prompt original_fish_prompt + function fish_prompt + echo -n '[{prompt_prefix}] '(original_fish_prompt) + end''' + args.append(prompt_cmd) + elif args[0].endswith('zsh'): + # Let the GC remove the tmp file + tmpdir = tempfile.TemporaryDirectory() + with open(os.path.join(tmpdir.name, '.zshrc'), 'w') as zshrc: # pylint: disable=unspecified-encoding + zshrc.write('[ -e ~/.zshrc ] && . ~/.zshrc\n') + if prompt_prefix: + zshrc.write(f'export PROMPT="[{prompt_prefix}] $PROMPT"\n') + devenv['ZDOTDIR'] = tmpdir.name + if 'DYLD_LIBRARY_PATH' in devenv and macos_sip_enabled(): + mlog.warning('macOS System Integrity Protection is enabled: DYLD_LIBRARY_PATH cannot be set in the subshell') + mlog.warning('To fix that, use `meson devenv --dump dev.env && source dev.env`') + del devenv['DYLD_LIBRARY_PATH'] else: # Try to resolve executable using devenv's PATH abs_path = shutil.which(args[0], path=devenv.get('PATH', None)) args[0] = abs_path or args[0] try: - os.chdir(workdir) - os.execvpe(args[0], args, env=devenv) + if is_windows(): + # execvpe doesn't return exit code on Windows + # see https://github.com/python/cpython/issues/63323 + result = subprocess.run(args, env=devenv, cwd=workdir) + sys.exit(result.returncode) + else: + os.chdir(workdir) + os.execvpe(args[0], args, env=devenv) except FileNotFoundError: raise MesonException(f'Command not found: {args[0]}') except OSError as e: diff --git a/mesonbuild/mdist.py b/mesonbuild/mdist.py index 0361606..15718d2 100644 --- a/mesonbuild/mdist.py +++ b/mesonbuild/mdist.py @@ -7,7 +7,6 @@ from __future__ import annotations import abc import argparse -import gzip import os import sys import shlex @@ -21,13 +20,14 @@ import typing as T from dataclasses import dataclass from glob import glob from pathlib import Path -from mesonbuild.environment import Environment, detect_ninja +from mesonbuild.environment import Environment +from mesonbuild.tooldetect import detect_ninja from mesonbuild.mesonlib import (GIT, MesonException, RealPathAction, get_meson_command, quiet_git, windows_proof_rmtree, setup_vsenv) from .options import OptionKey from mesonbuild.msetup import add_arguments as msetup_argparse from mesonbuild.wrap import wrap -from mesonbuild import mlog, build, coredata +from mesonbuild import mlog, build, cmdline from .scripts.meson_exe import run_exe if T.TYPE_CHECKING: @@ -41,6 +41,9 @@ archive_extension = {'bztar': '.tar.bz2', 'xztar': '.tar.xz', 'zip': '.zip'} +if sys.version_info >= (3, 14): + tarfile.TarFile.extraction_filter = staticmethod(tarfile.fully_trusted_filter) + # Note: when adding arguments, please also add them to the completion # scripts in $MESONSRC/data/shell-completions/ def add_arguments(parser: argparse.ArgumentParser) -> None: @@ -294,6 +297,7 @@ class HgDist(Dist): shutil.copyfileobj(tf, bf) output_names.append(bz2name) if 'gztar' in archives: + import gzip with gzip.open(gzname, 'wb') as zf, open(tarname, 'rb') as tf: shutil.copyfileobj(tf, zf) output_names.append(gzname) @@ -353,11 +357,11 @@ def check_dist(packagename: str, _meson_command: ImmutableListProtocol[str], ext def create_cmdline_args(bld_root: str) -> T.List[str]: parser = argparse.ArgumentParser() msetup_argparse(parser) - args = T.cast('coredata.SharedCMDOptions', parser.parse_args([])) - coredata.parse_cmd_line_options(args) - coredata.read_cmd_line_file(bld_root, args) + args = T.cast('cmdline.SharedCMDOptions', parser.parse_args([])) + cmdline.parse_cmd_line_options(args) + cmdline.read_cmd_line_file(bld_root, args) args.cmd_line_options.pop(OptionKey('backend'), '') - return shlex.split(coredata.format_cmd_line_options(args)) + return shlex.split(cmdline.format_cmd_line_options(args)) def determine_archives_to_generate(options: argparse.Namespace) -> T.List[str]: result = [] diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index 9da2786..89b3a1c 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -1,4 +1,4 @@ -# SPDX-license-identifier: Apache-2.0 +# SPDX-License-Identifier: Apache-2.0 # Copyright 2012-2021 The Meson development team # Copyright © 2021-2023 Intel Corporation @@ -12,12 +12,4 @@ from .utils.core import * from .utils.vsenv import * from .utils.universal import * - -# Here we import either the posix implementations, the windows implementations, -# or a generic no-op implementation -if os.name == 'posix': - from .utils.posix import * -elif os.name == 'nt': - from .utils.win32 import * -else: - from .utils.platform import * +from .utils.platform import * diff --git a/mesonbuild/mesonmain.py b/mesonbuild/mesonmain.py index dd265c4..ca7a184 100644 --- a/mesonbuild/mesonmain.py +++ b/mesonbuild/mesonmain.py @@ -240,14 +240,14 @@ def validate_original_args(args): def has_startswith(coll, target): for entry in coll: - if entry.startswith(target): + if entry.startswith(target + '=') or entry == target: return True return False #ds = [x for x in args if x.startswith('-D')] #longs = [x for x in args if x.startswith('--')] for optionkey in itertools.chain(mesonbuild.options.BUILTIN_DIR_OPTIONS, mesonbuild.options.BUILTIN_CORE_OPTIONS): longarg = mesonbuild.options.argparse_name_to_arg(optionkey.name) - shortarg = f'-D{optionkey.name}=' + shortarg = f'-D{optionkey.name}' if has_startswith(args, longarg) and has_startswith(args, shortarg): sys.exit( f'Got argument {optionkey.name} as both {shortarg} and {longarg}. Pick one.') diff --git a/mesonbuild/mformat.py b/mesonbuild/mformat.py index 1e134f5..0281ed5 100644 --- a/mesonbuild/mformat.py +++ b/mesonbuild/mformat.py @@ -3,6 +3,7 @@ from __future__ import annotations +import difflib import re import typing as T from configparser import ConfigParser, MissingSectionHeaderError, ParsingError @@ -242,6 +243,10 @@ class MultilineArgumentDetector(FullAstVisitor): if node.is_multiline: self.is_multiline = True + nargs = len(node) + if nargs and nargs == len(node.commas): + self.is_multiline = True + if self.is_multiline: return @@ -251,6 +256,19 @@ class MultilineArgumentDetector(FullAstVisitor): super().visit_ArgumentNode(node) +class MultilineParenthesesDetector(FullAstVisitor): + + def __init__(self) -> None: + self.last_whitespaces: T.Optional[mparser.WhitespaceNode] = None + + def enter_node(self, node: mparser.BaseNode) -> None: + self.last_whitespaces = None + + def exit_node(self, node: mparser.BaseNode) -> None: + if node.whitespaces and node.whitespaces.value: + self.last_whitespaces = node.whitespaces + + class TrimWhitespaces(FullAstVisitor): def __init__(self, config: FormatterConfig): @@ -284,6 +302,8 @@ class TrimWhitespaces(FullAstVisitor): def add_space_after(self, node: mparser.BaseNode) -> None: if not node.whitespaces.value: node.whitespaces.value = ' ' + elif '#' not in node.whitespaces.value: + node.whitespaces.value = ' ' def add_nl_after(self, node: mparser.BaseNode, force: bool = False) -> None: if not node.whitespaces.value: @@ -340,6 +360,7 @@ class TrimWhitespaces(FullAstVisitor): super().visit_SymbolNode(node) if node.value in "([{" and node.whitespaces.value == '\n': node.whitespaces.value = '' + node.whitespaces.accept(self) def visit_StringNode(self, node: mparser.StringNode) -> None: self.enter_node(node) @@ -352,7 +373,7 @@ class TrimWhitespaces(FullAstVisitor): if node.is_fstring and '@' not in node.value: node.is_fstring = False - self.exit_node(node) + node.whitespaces.accept(self) def visit_UnaryOperatorNode(self, node: mparser.UnaryOperatorNode) -> None: super().visit_UnaryOperatorNode(node) @@ -536,18 +557,28 @@ class TrimWhitespaces(FullAstVisitor): def visit_ParenthesizedNode(self, node: mparser.ParenthesizedNode) -> None: self.enter_node(node) - is_multiline = node.lpar.lineno != node.rpar.lineno - if is_multiline: + if node.lpar.whitespaces and '#' in node.lpar.whitespaces.value: + node.is_multiline = True + + elif not node.is_multiline: + ml_detector = MultilineParenthesesDetector() + node.inner.accept(ml_detector) + if ml_detector.last_whitespaces and '\n' in ml_detector.last_whitespaces.value: + # We keep it multiline if last parenthesis is on a separate line + node.is_multiline = True + + if node.is_multiline: self.indent_comments += self.config.indent_by node.lpar.accept(self) node.inner.accept(self) - if is_multiline: + if node.is_multiline: node.inner.whitespaces.value = self.dedent(node.inner.whitespaces.value) self.indent_comments = self.dedent(self.indent_comments) - if node.lpar.whitespaces and '\n' in node.lpar.whitespaces.value: - self.add_nl_after(node.inner) + self.add_nl_after(node.inner) + else: + node.inner.whitespaces = None node.rpar.accept(self) self.move_whitespaces(node.rpar, node) @@ -560,6 +591,7 @@ class ArgumentFormatter(FullAstVisitor): self.level = 0 self.indent_after = False self.is_function_arguments = False + self.par_level = 0 def add_space_after(self, node: mparser.BaseNode) -> None: if not node.whitespaces.value: @@ -570,7 +602,7 @@ class ArgumentFormatter(FullAstVisitor): node.whitespaces.value = '\n' indent_by = (node.condition_level + indent) * self.config.indent_by if indent_by: - node.whitespaces.value += indent_by + node.whitespaces.value = re.sub(rf'\n({self.config.indent_by})*', '\n' + indent_by, node.whitespaces.value) def visit_ArrayNode(self, node: mparser.ArrayNode) -> None: self.enter_node(node) @@ -670,7 +702,7 @@ class ArgumentFormatter(FullAstVisitor): if self.config.group_arg_value: for arg in node.arguments[:-1]: group_args = False - if isinstance(arg, mparser.StringNode) and arg.value.startswith('--'): + if isinstance(arg, mparser.StringNode) and arg.value.startswith('--') and arg.value != '--': next_arg = node.arguments[arg_index + 1] if isinstance(next_arg, mparser.StringNode) and not next_arg.value.startswith('--'): group_args = True @@ -678,11 +710,11 @@ class ArgumentFormatter(FullAstVisitor): # keep '--arg', 'value' on same line self.add_space_after(node.commas[arg_index]) elif arg_index < len(node.commas): - self.add_nl_after(node.commas[arg_index], self.level) + self.add_nl_after(node.commas[arg_index], self.level + self.par_level) arg_index += 1 for comma in node.commas[arg_index:-1]: - self.add_nl_after(comma, self.level) + self.add_nl_after(comma, self.level + self.par_level) if node.arguments or node.kwargs: self.add_nl_after(node, self.level - 1) @@ -697,17 +729,38 @@ class ArgumentFormatter(FullAstVisitor): def visit_ParenthesizedNode(self, node: mparser.ParenthesizedNode) -> None: self.enter_node(node) - is_multiline = '\n' in node.lpar.whitespaces.value - if is_multiline: + if node.is_multiline: + self.par_level += 1 current_indent_after = self.indent_after self.indent_after = True node.lpar.accept(self) + if node.is_multiline: + self.add_nl_after(node.lpar, indent=self.level + self.par_level) node.inner.accept(self) - if is_multiline: + if node.is_multiline: + self.par_level -= 1 self.indent_after = current_indent_after node.rpar.accept(self) self.exit_node(node) + def visit_OrNode(self, node: mparser.OrNode) -> None: + self.enter_node(node) + node.left.accept(self) + if self.par_level: + self.add_nl_after(node.left, indent=self.level + self.par_level) + node.operator.accept(self) + node.right.accept(self) + self.exit_node(node) + + def visit_AndNode(self, node: mparser.AndNode) -> None: + self.enter_node(node) + node.left.accept(self) + if self.par_level: + self.add_nl_after(node.left, indent=self.level + self.par_level) + node.operator.accept(self) + node.right.accept(self) + self.exit_node(node) + class ComputeLineLengths(FullAstVisitor): @@ -807,6 +860,16 @@ class ComputeLineLengths(FullAstVisitor): self.split_if_needed(node.args) # split if closing bracket is too far self.exit_node(node) + def visit_ParenthesizedNode(self, node: mparser.ParenthesizedNode) -> None: + self.enter_node(node) + node.lpar.accept(self) + node.inner.accept(self) + node.rpar.accept(self) + if not node.is_multiline and self.length > self.config.max_line_length: + node.is_multiline = True + self.need_regenerate = True + self.exit_node(node) + class SubdirFetcher(FullAstVisitor): @@ -837,7 +900,15 @@ class Formatter: # See https://editorconfig.org/ config = EditorConfig() - for p in source_file.parents: + if source_file == Path('STDIN'): + raise MesonException('Using editorconfig with stdin requires --source-file-path argument') + + try: + source_file_path = source_file.resolve() + except FileNotFoundError: + raise MesonException(f'Unable to resolve path for "{source_file}"') + + for p in source_file_path.parents: editorconfig_file = p / '.editorconfig' if not editorconfig_file.exists(): continue @@ -926,7 +997,13 @@ def add_arguments(parser: argparse.ArgumentParser) -> None: inplace_group.add_argument( '-q', '--check-only', action='store_true', - help='exit with 1 if files would be modified by meson format' + help='silently exit with 1 if files would be modified by meson format' + ) + inplace_group.add_argument( + '-d', '--check-diff', + action='store_true', + default=False, + help='exit with 1 and show diff if files would be modified by meson format' ) inplace_group.add_argument( '-i', '--inplace', @@ -956,6 +1033,11 @@ def add_arguments(parser: argparse.ArgumentParser) -> None: help='output file (implies having exactly one input)' ) parser.add_argument( + '--source-file-path', + type=Path, + help='path to use, when reading from stdin' + ) + parser.add_argument( 'sources', nargs='*', type=Path, @@ -981,6 +1063,10 @@ def run(options: argparse.Namespace) -> int: raise MesonException('--recursive argument is not compatible with stdin input') if options.inplace and from_stdin: raise MesonException('--inplace argument is not compatible with stdin input') + if options.source_file_path and not from_stdin: + raise MesonException('--source-file-path argument is only compatible with stdin input') + if from_stdin and options.editor_config and not options.source_file_path: + raise MesonException('using --editor-config with stdin input requires --source-file-path argument') sources: T.List[Path] = options.sources.copy() or [Path(build_filename)] @@ -996,7 +1082,7 @@ def run(options: argparse.Namespace) -> int: try: if from_stdin: - src_file = Path('STDIN') # used for error messages and introspection + src_file = options.source_file_path or Path('STDIN') # used for error messages and introspection code = sys.stdin.read() else: code = src_file.read_text(encoding='utf-8') @@ -1013,9 +1099,14 @@ def run(options: argparse.Namespace) -> int: sf.write(formatted) except IOError as e: raise MesonException(f'Unable to write to {src_file}') from e - elif options.check_only: - # TODO: add verbose output showing diffs + elif options.check_only or options.check_diff: if code != formatted: + if options.check_diff: + diff = difflib.unified_diff(code.splitlines(), formatted.splitlines(), + str(src_file), str(src_file), + '(original)', '(reformatted)', + lineterm='') + print('\n'.join(diff)) return 1 elif options.output: try: diff --git a/mesonbuild/minit.py b/mesonbuild/minit.py index 192c75a..7d0d051 100644 --- a/mesonbuild/minit.py +++ b/mesonbuild/minit.py @@ -17,7 +17,7 @@ import typing as T from mesonbuild import build, mesonlib, mlog from mesonbuild.coredata import FORBIDDEN_TARGET_NAMES -from mesonbuild.environment import detect_ninja +from mesonbuild.tooldetect import detect_ninja from mesonbuild.templates.mesontemplates import create_meson_build from mesonbuild.templates.samplefactory import sample_generator from mesonbuild.options import OptionKey diff --git a/mesonbuild/minstall.py b/mesonbuild/minstall.py index f65087c..f9c8480 100644 --- a/mesonbuild/minstall.py +++ b/mesonbuild/minstall.py @@ -15,7 +15,7 @@ import sys import typing as T import re -from . import build, environment +from . import build, tooldetect from .backend.backends import InstallData from .mesonlib import (MesonException, Popen_safe, RealPathAction, is_windows, is_aix, setup_vsenv, pickle_load, is_osx) @@ -441,15 +441,13 @@ class Installer: append_to_log(self.lf, to_file) return True - def do_symlink(self, target: str, link: str, destdir: str, full_dst_dir: str, allow_missing: bool) -> bool: + def do_symlink(self, target: str, link: str, destdir: str, full_dst_dir: str) -> bool: abs_target = target if not os.path.isabs(target): abs_target = os.path.join(full_dst_dir, target) - elif not os.path.exists(abs_target) and not allow_missing: + elif not os.path.exists(abs_target): abs_target = destdir_join(destdir, abs_target) - if not os.path.exists(abs_target) and not allow_missing: - raise MesonException(f'Tried to install symlink to missing file {abs_target}') - if os.path.exists(link): + if os.path.lexists(link): if not os.path.islink(link): raise MesonException(f'Destination {link!r} already exists and is not a symlink') self.remove(link) @@ -656,7 +654,7 @@ class Installer: full_dst_dir = get_destdir_path(destdir, fullprefix, s.install_path) full_link_name = get_destdir_path(destdir, fullprefix, s.name) dm.makedirs(full_dst_dir, exist_ok=True) - if self.do_symlink(s.target, full_link_name, destdir, full_dst_dir, s.allow_missing): + if self.do_symlink(s.target, full_link_name, destdir, full_dst_dir): self.did_install_something = True def install_man(self, d: InstallData, dm: DirMaker, destdir: str, fullprefix: str) -> None: @@ -805,7 +803,7 @@ def rebuild_all(wd: str, backend: str) -> bool: print('Only ninja backend is supported to rebuild the project before installation.') return True - ninja = environment.detect_ninja() + ninja = tooldetect.detect_ninja() if not ninja: print("Can't find ninja, can't rebuild test.") return False diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py index 462ee2f..b1e0941 100644 --- a/mesonbuild/mintro.py +++ b/mesonbuild/mintro.py @@ -19,20 +19,22 @@ from pathlib import Path, PurePath import sys import typing as T -from . import build, mesonlib, options, coredata as cdata -from .ast import IntrospectionInterpreter, BUILD_TARGET_FUNCTIONS, AstConditionLevel, AstIDGenerator, AstIndentationGenerator, AstJSONPrinter +from . import build, environment, mesonlib, options, coredata as cdata +from .ast import IntrospectionInterpreter, AstConditionLevel, AstIDGenerator, AstIndentationGenerator, AstJSONPrinter from .backend import backends -from .dependencies import Dependency -from . import environment -from .interpreterbase import ObjectHolder +from .interpreterbase import UnknownValue from .options import OptionKey -from .mparser import FunctionNode, ArrayNode, ArgumentNode, StringNode if T.TYPE_CHECKING: import argparse - from .interpreter import Interpreter - from .mparser import BaseNode + from .dependencies import Dependency + +class IntrospectionEncoder(json.JSONEncoder): + def default(self, obj: T.Any) -> T.Any: + if isinstance(obj, UnknownValue): + return 'unknown' + return json.JSONEncoder.default(self, obj) def get_meson_info_file(info_dir: str) -> str: return os.path.join(info_dir, 'meson-info.json') @@ -54,12 +56,11 @@ class IntroCommand: def get_meson_introspection_types(coredata: T.Optional[cdata.CoreData] = None, builddata: T.Optional[build.Build] = None, - backend: T.Optional[backends.Backend] = None) -> 'T.Mapping[str, IntroCommand]': + backend: T.Optional[backends.Backend] = None) -> T.Mapping[str, IntroCommand]: if backend and builddata: benchmarkdata = backend.create_test_serialisation(builddata.get_benchmarks()) testdata = backend.create_test_serialisation(builddata.get_tests()) installdata = backend.create_install_data() - interpreter = backend.interpreter else: benchmarkdata = testdata = installdata = None @@ -68,7 +69,7 @@ def get_meson_introspection_types(coredata: T.Optional[cdata.CoreData] = None, ('ast', IntroCommand('Dump the AST of the meson file', no_bd=dump_ast)), ('benchmarks', IntroCommand('List all benchmarks', func=lambda: list_benchmarks(benchmarkdata))), ('buildoptions', IntroCommand('List all build options', func=lambda: list_buildoptions(coredata), no_bd=list_buildoptions_from_source)), - ('buildsystem_files', IntroCommand('List files that make up the build system', func=lambda: list_buildsystem_files(builddata, interpreter))), + ('buildsystem_files', IntroCommand('List files that make up the build system', func=lambda: list_buildsystem_files(builddata))), ('compilers', IntroCommand('List used compilers', func=lambda: list_compilers(coredata))), ('dependencies', IntroCommand('List external dependencies', func=lambda: list_deps(coredata, backend), no_bd=list_deps_from_source)), ('scan_dependencies', IntroCommand('Scan for dependencies used in the meson.build file', no_bd=list_deps_from_source)), @@ -122,14 +123,15 @@ def list_installed(installdata: backends.InstallData) -> T.Dict[str, str]: res[basename] = os.path.join(installdata.prefix, s.install_path, basename) return res -def list_install_plan(installdata: backends.InstallData) -> T.Dict[str, T.Dict[str, T.Dict[str, T.Optional[str]]]]: - plan: T.Dict[str, T.Dict[str, T.Dict[str, T.Optional[str]]]] = { +def list_install_plan(installdata: backends.InstallData) -> T.Dict[str, T.Dict[str, T.Dict[str, T.Union[str, T.List[str], None]]]]: + plan: T.Dict[str, T.Dict[str, T.Dict[str, T.Union[str, T.List[str], None]]]] = { 'targets': { os.path.join(installdata.build_dir, target.fname): { 'destination': target.out_name, 'tag': target.tag or None, 'subproject': target.subproject or None, - 'install_rpath': target.install_rpath or None + 'install_rpath': target.install_rpath or None, + 'build_rpaths': sorted(x.decode('utf8') for x in target.rpath_dirs_to_remove), } for target in installdata.targets }, @@ -169,56 +171,35 @@ def get_target_dir(coredata: cdata.CoreData, subdir: str) -> str: else: return subdir -def list_targets_from_source(intr: IntrospectionInterpreter) -> T.List[T.Dict[str, T.Union[bool, str, T.List[T.Union[str, T.Dict[str, T.Union[str, T.List[str], bool]]]]]]]: - tlist: T.List[T.Dict[str, T.Union[bool, str, T.List[T.Union[str, T.Dict[str, T.Union[str, T.List[str], bool]]]]]]] = [] - root_dir = Path(intr.source_root) - - def nodes_to_paths(node_list: T.List[BaseNode]) -> T.List[Path]: - res: T.List[Path] = [] - for n in node_list: - args: T.List[BaseNode] = [] - if isinstance(n, FunctionNode): - args = list(n.args.arguments) - if n.func_name.value in BUILD_TARGET_FUNCTIONS: - args.pop(0) - elif isinstance(n, ArrayNode): - args = n.args.arguments - elif isinstance(n, ArgumentNode): - args = n.arguments - for j in args: - if isinstance(j, StringNode): - assert isinstance(j.value, str) - res += [Path(j.value)] - elif isinstance(j, str): - res += [Path(j)] - res = [root_dir / i['subdir'] / x for x in res] - res = [x.resolve() for x in res] - return res +def list_targets_from_source(intr: IntrospectionInterpreter) -> T.List[T.Dict[str, object]]: + tlist: T.List[T.Dict[str, object]] = [] + root_dir = Path(intr.source_root).resolve() for i in intr.targets: - sources = nodes_to_paths(i['sources']) - extra_f = nodes_to_paths(i['extra_files']) - outdir = get_target_dir(intr.coredata, i['subdir']) + sources = intr.nodes_to_pretty_filelist(root_dir, i.subdir, i.source_nodes) + extra_files = intr.nodes_to_pretty_filelist(root_dir, i.subdir, [i.extra_files] if i.extra_files else []) + + outdir = get_target_dir(intr.coredata, i.subdir) tlist += [{ - 'name': i['name'], - 'id': i['id'], - 'type': i['type'], - 'defined_in': i['defined_in'], - 'filename': [os.path.join(outdir, x) for x in i['outputs']], - 'build_by_default': i['build_by_default'], + 'name': i.name, + 'id': i.id, + 'type': i.typename, + 'defined_in': i.defined_in, + 'filename': [os.path.join(outdir, x) for x in i.outputs], + 'build_by_default': i.build_by_default, 'target_sources': [{ 'language': 'unknown', - 'machine': i['machine'], + 'machine': i.machine, 'compiler': [], 'parameters': [], - 'sources': [str(x) for x in sources], + 'sources': sources, 'generated_sources': [] }], 'depends': [], - 'extra_files': [str(x) for x in extra_f], + 'extra_files': extra_files, 'subproject': None, # Subprojects are not supported - 'installed': i['installed'] + 'installed': i.installed }] return tlist @@ -357,10 +338,9 @@ def find_buildsystem_files_list(src_dir: str) -> T.List[str]: for f in build_files.intersection(files)) return filelist -def list_buildsystem_files(builddata: build.Build, interpreter: Interpreter) -> T.List[str]: +def list_buildsystem_files(builddata: build.Build) -> T.List[str]: src_dir = builddata.environment.get_source_dir() - filelist = list(interpreter.get_build_def_files()) - filelist = [PurePath(src_dir, x).as_posix() for x in filelist] + filelist = [PurePath(src_dir, x).as_posix() for x in builddata.def_files] return filelist def list_compilers(coredata: cdata.CoreData) -> T.Dict[str, T.Dict[str, T.Dict[str, str]]]: @@ -380,23 +360,22 @@ def list_compilers(coredata: cdata.CoreData) -> T.Dict[str, T.Dict[str, T.Dict[s } return compilers -def list_deps_from_source(intr: IntrospectionInterpreter) -> T.List[T.Dict[str, T.Union[str, bool]]]: - result: T.List[T.Dict[str, T.Union[str, bool]]] = [] +def list_deps_from_source(intr: IntrospectionInterpreter) -> T.List[T.Dict[str, T.Union[str, bool, T.List[str], UnknownValue]]]: + result: T.List[T.Dict[str, T.Union[str, bool, T.List[str], UnknownValue]]] = [] for i in intr.dependencies: - keys = [ - 'name', - 'required', - 'version', - 'has_fallback', - 'conditional', - ] - result += [{k: v for k, v in i.items() if k in keys}] + result += [{ + 'name': i.name, + 'required': i.required, + 'version': i.version, + 'has_fallback': i.has_fallback, + 'conditional': i.conditional, + }] return result def list_deps(coredata: cdata.CoreData, backend: backends.Backend) -> T.List[T.Dict[str, T.Union[str, T.List[str]]]]: result: T.Dict[str, T.Dict[str, T.Union[str, T.List[str]]]] = {} - def _src_to_str(src_file: T.Union[mesonlib.FileOrString, build.CustomTarget, build.StructuredSources, build.CustomTargetIndex, build.GeneratedList]) -> T.List[str]: + def _src_to_str(src_file: T.Union[mesonlib.FileOrString, build.GeneratedTypes, build.StructuredSources]) -> T.List[str]: if isinstance(src_file, str): return [src_file] if isinstance(src_file, mesonlib.File): @@ -407,7 +386,7 @@ def list_deps(coredata: cdata.CoreData, backend: backends.Backend) -> T.List[T.D return [f for s in src_file.as_list() for f in _src_to_str(s)] raise mesonlib.MesonBugException(f'Invalid file type {type(src_file)}.') - def _create_result(d: Dependency, varname: T.Optional[str] = None) -> T.Dict[str, T.Any]: + def _create_result(d: Dependency) -> T.Dict[str, T.Any]: return { 'name': d.name, 'type': d.type_name, @@ -419,22 +398,13 @@ def list_deps(coredata: cdata.CoreData, backend: backends.Backend) -> T.List[T.D 'extra_files': [f for s in d.get_extra_files() for f in _src_to_str(s)], 'dependencies': [e.name for e in d.ext_deps], 'depends': [lib.get_id() for lib in getattr(d, 'libraries', [])], - 'meson_variables': [varname] if varname else [], + 'meson_variables': d.meson_variables, } for d in coredata.deps.host.values(): if d.found(): result[d.name] = _create_result(d) - for varname, holder in backend.interpreter.variables.items(): - if isinstance(holder, ObjectHolder): - d = holder.held_object - if isinstance(d, Dependency) and d.found(): - if d.name in result: - T.cast('T.List[str]', result[d.name]['meson_variables']).append(varname) - else: - result[d.name] = _create_result(d, varname) - return list(result.values()) def get_test_list(testdata: T.List[backends.TestSerialisation]) -> T.List[T.Dict[str, T.Union[str, int, T.List[str], T.Dict[str, str]]]]: @@ -517,12 +487,12 @@ def print_results(options: argparse.Namespace, results: T.Sequence[T.Tuple[str, return 1 elif len(results) == 1 and not options.force_dict: # Make to keep the existing output format for a single option - print(json.dumps(results[0][1], indent=indent)) + print(json.dumps(results[0][1], indent=indent, cls=IntrospectionEncoder)) else: out = {} for i in results: out[i[0]] = i[1] - print(json.dumps(out, indent=indent)) + print(json.dumps(out, indent=indent, cls=IntrospectionEncoder)) return 0 def get_infodir(builddir: T.Optional[str] = None) -> str: @@ -546,10 +516,11 @@ def run(options: argparse.Namespace) -> int: datadir = os.path.join(options.builddir, datadir) indent = 4 if options.indent else None results: T.List[T.Tuple[str, T.Union[dict, T.List[T.Any]]]] = [] - sourcedir = '.' if options.builddir == 'meson.build' else options.builddir[:-11] intro_types = get_meson_introspection_types() - if 'meson.build' in [os.path.basename(options.builddir), options.builddir]: + # TODO: This if clause is undocumented. + if os.path.basename(options.builddir) == environment.build_filename: + sourcedir = '.' if options.builddir == environment.build_filename else options.builddir[:-len(environment.build_filename)] # Make sure that log entries in other parts of meson don't interfere with the JSON output with redirect_stdout(sys.stderr): backend = backends.get_backend_from_name(options.backend) diff --git a/mesonbuild/mlog.py b/mesonbuild/mlog.py index b43ac8a..e1d998a 100644 --- a/mesonbuild/mlog.py +++ b/mesonbuild/mlog.py @@ -67,6 +67,7 @@ class _Logger: log_depth: T.List[str] = field(default_factory=list) log_to_stderr: bool = False log_file: T.Optional[T.TextIO] = None + slog_file: T.Optional[T.TextIO] = None log_timestamp_start: T.Optional[float] = None log_fatal_warnings = False log_disable_stdout = False @@ -76,6 +77,7 @@ class _Logger: log_pager: T.Optional['subprocess.Popen'] = None _LOG_FNAME: T.ClassVar[str] = 'meson-log.txt' + _SLOG_FNAME: T.ClassVar[str] = 'meson-setup.txt' @contextmanager def no_logging(self) -> T.Iterator[None]: @@ -110,6 +112,12 @@ class _Logger: self.log_file = None exception_around_goer.close() return path + if self.slog_file is not None: + path = self.slog_file.name + exception_around_goer = self.slog_file + self.slog_file = None + exception_around_goer.close() + return path self.stop_pager() return None @@ -164,6 +172,7 @@ class _Logger: def initialize(self, logdir: str, fatal_warnings: bool = False) -> None: self.log_dir = logdir self.log_file = open(os.path.join(logdir, self._LOG_FNAME), 'w', encoding='utf-8') + self.slog_file = open(os.path.join(logdir, self._SLOG_FNAME), 'w', encoding='utf-8') self.log_fatal_warnings = fatal_warnings def process_markup(self, args: T.Sequence[TV_Loggable], keep: bool, display_timestamp: bool = True) -> T.List[str]: @@ -224,6 +233,9 @@ class _Logger: if self.log_file is not None: print(*arr, file=self.log_file, sep=sep, end=end) self.log_file.flush() + if self.slog_file is not None: + print(*arr, file=self.slog_file, sep=sep, end=end) + self.slog_file.flush() if self.colorize_console(): arr = process_markup(args, True, display_timestamp) if not self.log_errors_only or is_error: @@ -394,7 +406,7 @@ class _Logger: _colorize_console = os.isatty(output.fileno()) and os.environ.get('TERM', 'dumb') != 'dumb' except Exception: _colorize_console = False - output.colorize_console = _colorize_console # type: ignore[attr-defined] + output.colorize_console = _colorize_console # type: ignore return _colorize_console def setup_console(self) -> None: diff --git a/mesonbuild/modules/__init__.py b/mesonbuild/modules/__init__.py index 67d1666..3ff9368 100644 --- a/mesonbuild/modules/__init__.py +++ b/mesonbuild/modules/__init__.py @@ -41,8 +41,8 @@ class ModuleState: self.root_subdir = interpreter.root_subdir self.current_lineno = interpreter.current_node.lineno self.environment = interpreter.environment - self.project_name = interpreter.build.project_name - self.project_version = interpreter.build.dep_manifest[interpreter.active_projectname].version + self.project_name = interpreter.active_projectname + self.project_version = interpreter.project_version # The backend object is under-used right now, but we will need it: # https://github.com/mesonbuild/meson/issues/1419 self.backend = interpreter.backend @@ -75,14 +75,14 @@ class ModuleState: required: bool = True, version_func: T.Optional[ProgramVersionFunc] = None, wanted: T.Union[str, T.List[str]] = '', silent: bool = False, - for_machine: MachineChoice = MachineChoice.HOST) -> T.Union[ExternalProgram, build.Executable, OverrideProgram]: + for_machine: MachineChoice = MachineChoice.HOST) -> T.Union[ExternalProgram, build.OverrideExecutable, OverrideProgram]: if not isinstance(prog, list): prog = [prog] return self._interpreter.find_program_impl(prog, required=required, version_func=version_func, wanted=wanted, silent=silent, for_machine=for_machine) def find_tool(self, name: str, depname: str, varname: str, required: bool = True, - wanted: T.Optional[str] = None) -> T.Union['build.Executable', ExternalProgram, 'OverrideProgram']: + wanted: T.Optional[str] = None) -> T.Union[build.OverrideExecutable, ExternalProgram, 'OverrideProgram']: # Look in overrides in case it's built as subproject progobj = self._interpreter.program_from_overrides([name], []) if progobj is not None: diff --git a/mesonbuild/modules/_qt.py b/mesonbuild/modules/_qt.py index 7d52842..ae6e2d4 100644 --- a/mesonbuild/modules/_qt.py +++ b/mesonbuild/modules/_qt.py @@ -1,6 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright 2015 The Meson development team -# Copyright © 2021-2023 Intel Corporation +# Copyright © 2021-2025 Intel Corporation from __future__ import annotations @@ -14,16 +14,17 @@ from . import ModuleReturnValue, ExtensionModule from .. import build from .. import options from .. import mlog -from ..dependencies import find_external_dependency, Dependency, ExternalLibrary, InternalDependency +from ..dependencies import DependencyMethods, find_external_dependency, Dependency, ExternalLibrary, InternalDependency from ..mesonlib import MesonException, File, FileMode, version_compare, Popen_safe from ..interpreter import extract_required_kwarg -from ..interpreter.type_checking import INSTALL_DIR_KW, INSTALL_KW, NoneType +from ..interpreter.type_checking import DEPENDENCY_METHOD_KW, INSTALL_DIR_KW, INSTALL_KW, NoneType from ..interpreterbase import ContainerTypeInfo, FeatureDeprecated, KwargInfo, noPosargs, FeatureNew, typed_kwargs, typed_pos_args from ..programs import NonExistingExternalProgram if T.TYPE_CHECKING: from . import ModuleState from ..dependencies.qt import QtPkgConfigDependency, QmakeQtDependency + from ..dependencies.base import DependencyObjectKWs from ..interpreter import Interpreter from ..interpreter import kwargs from ..mesonlib import FileOrString @@ -39,27 +40,27 @@ if T.TYPE_CHECKING: """Keyword arguments for the Resource Compiler method.""" name: T.Optional[str] - sources: T.Sequence[T.Union[FileOrString, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList]] + sources: T.Sequence[T.Union[FileOrString, build.GeneratedTypes]] extra_args: T.List[str] - method: str + method: DependencyMethods class UICompilerKwArgs(TypedDict): """Keyword arguments for the Ui Compiler method.""" - sources: T.Sequence[T.Union[FileOrString, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList]] + sources: T.Sequence[T.Union[FileOrString, build.GeneratedTypes]] extra_args: T.List[str] - method: str + method: DependencyMethods preserve_paths: bool class MocCompilerKwArgs(TypedDict): """Keyword arguments for the Moc Compiler method.""" - sources: T.Sequence[T.Union[FileOrString, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList]] - headers: T.Sequence[T.Union[FileOrString, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList]] + sources: T.Sequence[T.Union[FileOrString, build.GeneratedTypes]] + headers: T.Sequence[T.Union[FileOrString, build.GeneratedTypes]] extra_args: T.List[str] - method: str + method: DependencyMethods include_directories: T.List[T.Union[str, build.IncludeDirs]] dependencies: T.List[T.Union[Dependency, ExternalLibrary]] preserve_paths: bool @@ -78,12 +79,12 @@ if T.TYPE_CHECKING: moc_output_json: bool include_directories: T.List[T.Union[str, build.IncludeDirs]] dependencies: T.List[T.Union[Dependency, ExternalLibrary]] - method: str + method: DependencyMethods preserve_paths: bool class HasToolKwArgs(kwargs.ExtractRequired): - method: str + method: DependencyMethods tools: T.List[Literal['moc', 'uic', 'rcc', 'lrelease', 'qmlcachegen', 'qmltyperegistrar']] class CompileTranslationsKwArgs(TypedDict): @@ -91,10 +92,10 @@ if T.TYPE_CHECKING: build_by_default: bool install: bool install_dir: T.Optional[str] - method: str + method: DependencyMethods qresource: T.Optional[str] rcc_extra_arguments: T.List[str] - ts_files: T.List[T.Union[str, File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList]] + ts_files: T.List[T.Union[str, File, build.GeneratedTypes]] class GenQrcKwArgs(TypedDict): @@ -126,7 +127,7 @@ if T.TYPE_CHECKING: qml_qrc: T.Union[FileOrString, build.GeneratedTypes] extra_args: T.List[str] module_prefix: str - method: str + method: DependencyMethods class GenQmlTypeRegistrarKwArgs(TypedDict): @@ -139,7 +140,7 @@ if T.TYPE_CHECKING: generate_qmltype: bool collected_json: T.Optional[T.Union[FileOrString, build.CustomTarget]] extra_args: T.List[str] - method: str + method: DependencyMethods install: bool install_dir: T.Optional[str] @@ -147,7 +148,7 @@ if T.TYPE_CHECKING: target_name: str moc_json: T.Sequence[build.GeneratedList] - method: str + method: DependencyMethods class QmlModuleKwArgs(TypedDict): @@ -173,7 +174,7 @@ if T.TYPE_CHECKING: generate_qmltype: bool cachegen: bool dependencies: T.List[T.Union[Dependency, ExternalLibrary]] - method: str + method: DependencyMethods preserve_paths: bool install_dir: str install: bool @@ -264,12 +265,12 @@ class QtBaseModule(ExtensionModule): if p.found(): self.tools[name] = p - def _detect_tools(self, state: ModuleState, method: str, required: bool = True) -> None: + def _detect_tools(self, state: ModuleState, method: DependencyMethods, required: bool = True) -> None: if self._tools_detected: return self._tools_detected = True mlog.log(f'Detecting Qt{self.qt_version} tools') - kwargs = {'required': required, 'modules': 'Core', 'method': method} + kwargs: DependencyObjectKWs = {'required': required, 'modules': ['Core'], 'method': method} # Just pick one to make mypy happy qt = T.cast('QtPkgConfigDependency', find_external_dependency(f'qt{self.qt_version}', state.environment, kwargs)) if qt.found(): @@ -325,7 +326,7 @@ class QtBaseModule(ExtensionModule): raise MesonException(f'Unable to parse resource file {abspath}') def _parse_qrc_deps(self, state: ModuleState, - rcc_file_: T.Union[FileOrString, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList]) -> T.List[File]: + rcc_file_: T.Union[FileOrString, build.GeneratedTypes]) -> T.List[File]: result: T.List[File] = [] inputs: T.Sequence['FileOrString'] = [] if isinstance(rcc_file_, (str, File)): @@ -365,15 +366,15 @@ class QtBaseModule(ExtensionModule): @noPosargs @typed_kwargs( 'qt.has_tools', + DEPENDENCY_METHOD_KW, KwargInfo('required', (bool, options.UserFeatureOption), default=False), - KwargInfo('method', str, default='auto'), KwargInfo('tools', ContainerTypeInfo(list, str), listify=True, default=['moc', 'uic', 'rcc', 'lrelease'], validator=_list_in_set_validator(_set_of_qt_tools), since='1.6.0'), ) def has_tools(self, state: ModuleState, args: T.Tuple, kwargs: HasToolKwArgs) -> bool: - method = kwargs.get('method', 'auto') + method = kwargs['method'] # We have to cast here because TypedDicts are invariant, even though # ExtractRequiredKwArgs is a subset of HasToolKwArgs, type checkers # will insist this is wrong @@ -394,6 +395,7 @@ class QtBaseModule(ExtensionModule): @noPosargs @typed_kwargs( 'qt.compile_resources', + DEPENDENCY_METHOD_KW, KwargInfo('name', (str, NoneType)), KwargInfo( 'sources', @@ -402,7 +404,6 @@ class QtBaseModule(ExtensionModule): required=True, ), KwargInfo('extra_args', ContainerTypeInfo(list, str), listify=True, default=[]), - KwargInfo('method', str, default='auto') ) def compile_resources(self, state: 'ModuleState', args: T.Tuple, kwargs: 'ResourceCompilerKwArgs') -> ModuleReturnValue: """Compile Qt resources files. @@ -486,6 +487,7 @@ class QtBaseModule(ExtensionModule): @noPosargs @typed_kwargs( 'qt.compile_ui', + DEPENDENCY_METHOD_KW, KwargInfo( 'sources', ContainerTypeInfo(list, (File, str, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList), allow_empty=False), @@ -493,7 +495,6 @@ class QtBaseModule(ExtensionModule): required=True, ), KwargInfo('extra_args', ContainerTypeInfo(list, str), listify=True, default=[]), - KwargInfo('method', str, default='auto'), KwargInfo('preserve_paths', bool, default=False, since='1.4.0'), ) def compile_ui(self, state: ModuleState, args: T.Tuple, kwargs: UICompilerKwArgs) -> ModuleReturnValue: @@ -513,18 +514,19 @@ class QtBaseModule(ExtensionModule): raise MesonException(err_msg.format('UIC', f'uic-qt{self.qt_version}', self.qt_version)) preserve_path_from = os.path.join(state.source_root, state.subdir) if kwargs['preserve_paths'] else None - # TODO: This generator isn't added to the generator list in the Interpreter gen = build.Generator( + state.environment, self.tools['uic'], kwargs['extra_args'] + ['-o', '@OUTPUT@', '@INPUT@'], ['ui_@BASENAME@.h'], name=f'Qt{self.qt_version} ui') - return gen.process_files(kwargs['sources'], state, preserve_path_from) + return gen.process_files(kwargs['sources'], state.subdir, preserve_path_from) @FeatureNew('qt.compile_moc', '0.59.0') @noPosargs @typed_kwargs( 'qt.compile_moc', + DEPENDENCY_METHOD_KW, KwargInfo( 'sources', ContainerTypeInfo(list, (File, str, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)), @@ -538,7 +540,6 @@ class QtBaseModule(ExtensionModule): default=[] ), KwargInfo('extra_args', ContainerTypeInfo(list, str), listify=True, default=[]), - KwargInfo('method', str, default='auto'), KwargInfo('include_directories', ContainerTypeInfo(list, (build.IncludeDirs, str)), listify=True, default=[]), KwargInfo('dependencies', ContainerTypeInfo(list, (Dependency, ExternalLibrary)), listify=True, default=[]), KwargInfo('preserve_paths', bool, default=False, since='1.4.0'), @@ -567,11 +568,15 @@ class QtBaseModule(ExtensionModule): inc = state.get_include_args(include_dirs=kwargs['include_directories']) compile_args: T.List[str] = [] + sources: T.List[T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex]] = [] for dep in kwargs['dependencies']: compile_args.extend(a for a in dep.get_all_compile_args() if a.startswith(('-I', '-D'))) if isinstance(dep, InternalDependency): for incl in dep.include_directories: compile_args.extend(f'-I{i}' for i in incl.to_string_list(self.interpreter.source_root, self.interpreter.environment.build_dir)) + for src in dep.sources: + if isinstance(src, (build.CustomTarget, build.BuildTarget, build.CustomTargetIndex)): + sources.append(src) output: T.List[build.GeneratedList] = [] @@ -590,25 +595,29 @@ class QtBaseModule(ExtensionModule): if do_output_json: header_gen_output.append('moc_@BASENAME@.cpp.json') moc_gen = build.Generator( + state.environment, self.tools['moc'], arguments, header_gen_output, + depends=sources, depfile='moc_@BASENAME@.cpp.d', name=f'Qt{self.qt_version} moc header') - output.append(moc_gen.process_files(kwargs['headers'], state, preserve_path_from)) + output.append(moc_gen.process_files(kwargs['headers'], state.subdir, preserve_path_from)) if kwargs['sources']: source_gen_output: T.List[str] = ['@BASENAME@.moc'] if do_output_json: source_gen_output.append('@BASENAME@.moc.json') moc_gen = build.Generator( + state.environment, self.tools['moc'], arguments, source_gen_output, depfile='@BASENAME@.moc.d', name=f'Qt{self.qt_version} moc source') - output.append(moc_gen.process_files(kwargs['sources'], state, preserve_path_from)) + output.append(moc_gen.process_files(kwargs['sources'], state.subdir, preserve_path_from)) return output # We can't use typed_pos_args here, the signature is ambiguous @typed_kwargs( 'qt.preprocess', + DEPENDENCY_METHOD_KW, KwargInfo('sources', ContainerTypeInfo(list, (File, str)), listify=True, default=[], deprecated='0.59.0'), KwargInfo('qresources', ContainerTypeInfo(list, (File, str)), listify=True, default=[]), KwargInfo('ui_files', ContainerTypeInfo(list, (File, str, build.CustomTarget)), listify=True, default=[]), @@ -617,7 +626,6 @@ class QtBaseModule(ExtensionModule): KwargInfo('moc_extra_arguments', ContainerTypeInfo(list, str), listify=True, default=[], since='0.44.0'), KwargInfo('rcc_extra_arguments', ContainerTypeInfo(list, str), listify=True, default=[], since='0.49.0'), KwargInfo('uic_extra_arguments', ContainerTypeInfo(list, str), listify=True, default=[], since='0.49.0'), - KwargInfo('method', str, default='auto'), KwargInfo('include_directories', ContainerTypeInfo(list, (build.IncludeDirs, str)), listify=True, default=[]), KwargInfo('dependencies', ContainerTypeInfo(list, (Dependency, ExternalLibrary)), listify=True, default=[]), KwargInfo('preserve_paths', bool, default=False, since='1.4.0'), @@ -674,9 +682,9 @@ class QtBaseModule(ExtensionModule): @typed_kwargs( 'qt.compile_translations', KwargInfo('build_by_default', bool, default=False), + DEPENDENCY_METHOD_KW, INSTALL_KW, INSTALL_DIR_KW, - KwargInfo('method', str, default='auto'), KwargInfo('qresource', (str, NoneType), since='0.56.0'), KwargInfo('rcc_extra_arguments', ContainerTypeInfo(list, str), listify=True, default=[], since='0.56.0'), KwargInfo('ts_files', ContainerTypeInfo(list, (str, File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)), listify=True, default=[]), @@ -893,13 +901,14 @@ class QtBaseModule(ExtensionModule): command_args.append('@INPUT@') cache_gen = build.Generator( + state.environment, self.tools['qmlcachegen'], command_args, [f'{target_name}_@BASENAME@.cpp'], name=f'Qml cache generation for {target_name}') output: T.List[T.Union[build.CustomTarget, build.GeneratedList]] = [] - output.append(cache_gen.process_files(kwargs['qml_sources'], state)) + output.append(cache_gen.process_files(kwargs['qml_sources'], state.subdir)) cachegen_inputs: T.List[str] = [] qml_sources_paths = self._source_to_files(state, kwargs['qml_sources']) @@ -1015,7 +1024,7 @@ class QtBaseModule(ExtensionModule): KwargInfo('dependencies', ContainerTypeInfo(list, (Dependency, ExternalLibrary)), listify=True, default=[]), INSTALL_DIR_KW, INSTALL_KW, - KwargInfo('method', str, default='auto'), + DEPENDENCY_METHOD_KW, KwargInfo('preserve_paths', bool, default=False), ) def qml_module(self, state: ModuleState, args: T.Tuple[str], kwargs: QmlModuleKwArgs) -> ModuleReturnValue: diff --git a/mesonbuild/modules/cmake.py b/mesonbuild/modules/cmake.py index e3154b0..ce22cef 100644 --- a/mesonbuild/modules/cmake.py +++ b/mesonbuild/modules/cmake.py @@ -13,7 +13,7 @@ from .. import build, mesonlib, mlog, dependencies from ..options import OptionKey from ..cmake import TargetOptions, cmake_defines_to_args from ..interpreter import SubprojectHolder -from ..interpreter.type_checking import REQUIRED_KW, INSTALL_DIR_KW, NoneType, in_set_validator +from ..interpreter.type_checking import REQUIRED_KW, INSTALL_DIR_KW, INCLUDE_TYPE, NoneType, in_set_validator from ..interpreterbase import ( FeatureNew, @@ -34,6 +34,7 @@ if T.TYPE_CHECKING: from . import ModuleState from ..cmake.common import SingleTargetOptions + from ..dependencies.base import IncludeType from ..environment import Environment from ..interpreter import Interpreter, kwargs from ..interpreterbase import TYPE_kwargs, TYPE_var, InterpreterObject @@ -62,6 +63,10 @@ if T.TYPE_CHECKING: target: T.Optional[str] + class DependencyKW(TypedDict): + + include_type: IncludeType + _TARGET_KW = KwargInfo('target', (str, NoneType)) @@ -129,17 +134,8 @@ class CMakeSubproject(ModuleObject): return self.subp.get_variable(args, kwargs) @typed_pos_args('cmake.subproject.dependency', str) - @typed_kwargs( - 'cmake.subproject.dependency', - KwargInfo( - 'include_type', - str, - default='preserve', - since='0.56.0', - validator=in_set_validator({'preserve', 'system', 'non-system'}) - ), - ) - def dependency(self, state: ModuleState, args: T.Tuple[str], kwargs: T.Dict[str, str]) -> dependencies.Dependency: + @typed_kwargs('cmake.subproject.dependency', INCLUDE_TYPE.evolve(since='0.56.0')) + def dependency(self, state: ModuleState, args: T.Tuple[str], kwargs: DependencyKW) -> dependencies.Dependency: info = self._args_to_info(args[0]) if info['func'] == 'executable': raise InvalidArguments(f'{args[0]} is an executable and does not support the dependency() method. Use target() instead.') @@ -154,10 +150,11 @@ class CMakeSubproject(ModuleObject): @noKwargs @typed_pos_args('cmake.subproject.include_directories', str) - def include_directories(self, state: ModuleState, args: T.Tuple[str], kwargs: TYPE_kwargs) -> build.IncludeDirs: + def include_directories(self, state: ModuleState, args: T.Tuple[str], kwargs: TYPE_kwargs) -> T.List[build.IncludeDirs]: info = self._args_to_info(args[0]) inc = self.get_variable(state, [info['inc']], kwargs) - assert isinstance(inc, build.IncludeDirs), 'for mypy' + assert isinstance(inc, list), 'for mypy' + assert isinstance(inc[0], build.IncludeDirs), 'for mypy' return inc @noKwargs @@ -242,7 +239,7 @@ class CMakeSubprojectOptions(ModuleObject): class CmakeModule(ExtensionModule): cmake_detected = False - cmake_root = None + cmake_root: str INFO = ModuleInfo('cmake', '0.50.0') @@ -264,7 +261,7 @@ class CmakeModule(ExtensionModule): if not compiler: raise mesonlib.MesonException('Requires a C or C++ compiler to compute sizeof(void *).') - return compiler.sizeof('void *', '', env)[0] + return compiler.sizeof('void *', '')[0] def detect_cmake(self, state: ModuleState) -> bool: if self.cmake_detected: @@ -438,7 +435,7 @@ class CmakeModule(ExtensionModule): 'required': kwargs_['required'], 'options': kwargs_['options'], 'cmake_options': kwargs_['cmake_options'], - 'default_options': [], + 'default_options': {}, 'version': [], } subp = self.interpreter.do_subproject(dirname, kw, force_method='cmake') diff --git a/mesonbuild/modules/codegen.py b/mesonbuild/modules/codegen.py new file mode 100644 index 0000000..f37f964 --- /dev/null +++ b/mesonbuild/modules/codegen.py @@ -0,0 +1,445 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright © 2024-2025 Intel Corporation + +from __future__ import annotations +import dataclasses +import os +import typing as T + +from . import ExtensionModule, ModuleInfo +from ..build import CustomTarget, CustomTargetIndex, GeneratedList +from ..compilers.compilers import lang_suffixes +from ..interpreter.interpreterobjects import extract_required_kwarg +from ..interpreter.type_checking import NoneType, REQUIRED_KW, DISABLER_KW, NATIVE_KW +from ..interpreterbase import ( + ContainerTypeInfo, ObjectHolder, KwargInfo, typed_pos_args, typed_kwargs, + noPosargs, noKwargs, disablerIfNotFound, InterpreterObject +) +from ..mesonlib import File, MesonException, Popen_safe, version_compare +from ..programs import ExternalProgram, NonExistingExternalProgram +from ..utils.core import HoldableObject +from .. import mlog + +if T.TYPE_CHECKING: + from typing_extensions import Literal, TypeAlias, TypedDict + + from . import ModuleState + from .._typing import ImmutableListProtocol + from ..build import Executable + from ..interpreter import Interpreter + from ..interpreter.kwargs import ExtractRequired + from ..interpreterbase import TYPE_var, TYPE_kwargs + from ..mesonlib import MachineChoice + from ..programs import OverrideProgram + + Program: TypeAlias = T.Union[Executable, ExternalProgram, OverrideProgram] + LexImpls = Literal['lex', 'flex', 'reflex', 'win_flex'] + YaccImpls = Literal['yacc', 'byacc', 'bison', 'win_bison'] + + class LexGenerateKwargs(TypedDict): + + args: T.List[str] + source: T.Optional[str] + header: T.Optional[str] + table: T.Optional[str] + plainname: bool + + class FindLexKwargs(ExtractRequired): + + lex_version: T.List[str] + flex_version: T.List[str] + reflex_version: T.List[str] + win_flex_version: T.List[str] + implementations: T.List[LexImpls] + native: MachineChoice + + class YaccGenerateKWargs(TypedDict): + + args: T.List[str] + source: T.Optional[str] + header: T.Optional[str] + locations: T.Optional[str] + plainname: bool + + class FindYaccKwargs(ExtractRequired): + + yacc_version: T.List[str] + byacc_version: T.List[str] + bison_version: T.List[str] + win_bison_version: T.List[str] + implementations: T.List[YaccImpls] + native: MachineChoice + + +def is_subset_validator(choices: T.Set[str]) -> T.Callable[[T.List[str]], T.Optional[str]]: + + def inner(check: T.List[str]) -> T.Optional[str]: + if not set(check).issubset(choices): + invalid = ', '.join(sorted(set(check).difference(choices))) + valid = ', '.join(sorted(choices)) + return f"valid members are '{valid}', not '{invalid}'" + return None + + return inner + + +@dataclasses.dataclass +class _CodeGenerator(HoldableObject): + + name: str + program: Program + arguments: ImmutableListProtocol[str] = dataclasses.field(default_factory=list) + + def command(self) -> T.List[T.Union[Program, str]]: + return (T.cast('T.List[T.Union[Program, str]]', [self.program]) + + T.cast('T.List[T.Union[Program, str]]', self.arguments)) + + def found(self) -> bool: + return self.program.found() + + +@dataclasses.dataclass +class LexGenerator(_CodeGenerator): + pass + + +class LexHolder(ObjectHolder[LexGenerator]): + + @noPosargs + @noKwargs + @InterpreterObject.method('implementation') + def implementation_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return self.held_object.name + + @noPosargs + @noKwargs + @InterpreterObject.method('found') + def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: + return self.held_object.found() + + @typed_pos_args('codegen.lex.generate', (str, File, GeneratedList, CustomTarget, CustomTargetIndex)) + @typed_kwargs( + 'codegen.lex.generate', + KwargInfo('args', ContainerTypeInfo(list, str), default=[], listify=True), + KwargInfo('source', (str, NoneType)), + KwargInfo('header', (str, NoneType)), + KwargInfo('table', (str, NoneType)), + KwargInfo('plainname', bool, default=False), + ) + @InterpreterObject.method('generate') + def generate_method(self, args: T.Tuple[T.Union[str, File, GeneratedList, CustomTarget, CustomTargetIndex]], kwargs: LexGenerateKwargs) -> CustomTarget: + if not self.held_object.found(): + raise MesonException('Attempted to call generate without finding a lex implementation') + + input = self.interpreter.source_strings_to_files([args[0]])[0] + if isinstance(input, File): + is_cpp = input.endswith(".ll") + name = os.path.splitext(input.fname)[0] + else: + gen_input = input.get_outputs() + if len(gen_input) != 1: + raise MesonException('codegen.lex.generate: generated type inputs must have exactly one output, index into them to select the correct input') + is_cpp = gen_input[0].endswith('.ll') + name = os.path.splitext(gen_input[0])[0] + name = os.path.basename(name) + + # If an explicit source was given, use that to determine whether the + # user expects this to be a C or C++ source. + if kwargs['source'] is not None: + ext = kwargs['source'].rsplit('.', 1)[1] + is_cpp = ext in lang_suffixes['cpp'] + + for_machine = self.held_object.program.for_machine + + # Flex uses FlexLexer.h for C++ code + if is_cpp and self.held_object.name in {'flex', 'win_flex'}: + try: + comp = self.interpreter.environment.coredata.compilers[for_machine]['cpp'] + except KeyError: + raise MesonException(f"Could not find a C++ compiler for {for_machine} to search for FlexLexer.h") + found, _ = comp.has_header('FlexLexer.h', '') + if not found: + raise MesonException('Could not find FlexLexer.h, which is required for Flex with C++') + + if kwargs['source'] is None: + outputs = ['@{}@.{}'.format( + 'PLAINNAME' if kwargs['plainname'] else 'BASENAME', + 'cpp' if is_cpp else 'c')] + else: + outputs = [kwargs['source']] + + command = self.held_object.command() + if kwargs['header'] is not None: + outputs.append(kwargs['header']) + command.append(f'--header-file=@OUTPUT{len(outputs) - 1}@') + if kwargs['table'] is not None: + outputs.append(kwargs['table']) + command.append(f'--tables-file=@OUTPUT{len(outputs) - 1}@') + command.extend(kwargs['args']) + # Flex, at least, seems to require that input be the last argument given + command.append('@INPUT@') + + target = CustomTarget( + f'codegen-lex-{name}-{for_machine.get_lower_case_name()}', + self.interpreter.subdir, + self.interpreter.subproject, + self.interpreter.environment, + command, + [input], + outputs, + backend=self.interpreter.backend, + description='Generating lexer {{}} with {}'.format(self.held_object.name), + ) + self.interpreter.add_target(target.name, target) + + return target + + +@dataclasses.dataclass +class YaccGenerator(_CodeGenerator): + pass + + +class YaccHolder(ObjectHolder[YaccGenerator]): + + @noPosargs + @noKwargs + @InterpreterObject.method('implementation') + def implementation_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return self.held_object.name + + @noPosargs + @noKwargs + @InterpreterObject.method('found') + def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: + return self.held_object.found() + + @typed_pos_args('codegen.yacc.generate', (str, File, GeneratedList, CustomTarget, CustomTargetIndex)) + @typed_kwargs( + 'codegen.yacc.generate', + KwargInfo('args', ContainerTypeInfo(list, str), default=[], listify=True), + KwargInfo('source', (str, NoneType)), + KwargInfo('header', (str, NoneType)), + KwargInfo('locations', (str, NoneType)), + KwargInfo('plainname', bool, default=False), + ) + @InterpreterObject.method('generate') + def generate_method(self, args: T.Tuple[T.Union[str, File, CustomTarget, CustomTargetIndex, GeneratedList]], kwargs: YaccGenerateKWargs) -> CustomTarget: + if not self.held_object.found(): + raise MesonException('Attempted to call generate without finding a yacc implementation') + + input = self.interpreter.source_strings_to_files([args[0]])[0] + if isinstance(input, File): + is_cpp = input.endswith(".yy") + name = os.path.splitext(input.fname)[0] + else: + gen_input = input.get_outputs() + if len(gen_input) != 1: + raise MesonException('codegen.lex.generate: generated type inputs must have exactly one output, index into them to select the correct input') + is_cpp = gen_input[0].endswith('.yy') + name = os.path.splitext(gen_input[0])[0] + name = os.path.basename(name) + + command = self.held_object.command() + command.extend(kwargs['args']) + + source_ext = 'cpp' if is_cpp else 'c' + header_ext = 'hpp' if is_cpp else 'h' + + base = '@PLAINNAME@' if kwargs['plainname'] else '@BASENAME@' + outputs: T.List[str] = [] + outputs.append(f'{base}.{source_ext}' if kwargs['source'] is None else kwargs['source']) + outputs.append(f'{base}.{header_ext}' if kwargs['header'] is None else kwargs['header']) + if kwargs['locations'] is not None: + outputs.append(kwargs['locations']) + + for_machine = self.held_object.program.for_machine + target = CustomTarget( + f'codegen-yacc-{name}-{for_machine.get_lower_case_name()}', + self.interpreter.subdir, + self.interpreter.subproject, + self.interpreter.environment, + command, + [input], + outputs, + backend=self.interpreter.backend, + description='Generating parser {{}} with {}'.format(self.held_object.name), + ) + self.interpreter.add_target(target.name, target) + return target + + +class CodeGenModule(ExtensionModule): + + """Module with helpers for codegen wrappers.""" + + INFO = ModuleInfo('codegen', '1.10.0', unstable=True) + + def __init__(self, interpreter: Interpreter) -> None: + super().__init__(interpreter) + self.methods.update({ + 'lex': self.lex_method, + 'yacc': self.yacc_method, + }) + + @noPosargs + @typed_kwargs( + 'codegen.lex', + KwargInfo('lex_version', ContainerTypeInfo(list, str), default=[], listify=True), + KwargInfo('flex_version', ContainerTypeInfo(list, str), default=[], listify=True), + KwargInfo('reflex_version', ContainerTypeInfo(list, str), default=[], listify=True), + KwargInfo('win_flex_version', ContainerTypeInfo(list, str), default=[], listify=True), + KwargInfo( + 'implementations', + ContainerTypeInfo(list, str), + default=[], + listify=True, + validator=is_subset_validator({'lex', 'flex', 'reflex', 'win_flex'}) + ), + REQUIRED_KW, + DISABLER_KW, + NATIVE_KW + ) + @disablerIfNotFound + def lex_method(self, state: ModuleState, args: T.Tuple, kwargs: FindLexKwargs) -> LexGenerator: + disabled, required, feature = extract_required_kwarg(kwargs, state.subproject) + if disabled: + mlog.log('generator lex skipped: feature', mlog.bold(feature), 'disabled') + return LexGenerator('lex', NonExistingExternalProgram('lex')) + + names: T.List[LexImpls] = [] + if kwargs['implementations']: + names = kwargs['implementations'] + else: + assert state.environment.machines[kwargs['native']] is not None, 'for mypy' + if state.environment.machines[kwargs['native']].system == 'windows': + names.append('win_flex') + names.extend(['flex', 'reflex', 'lex']) + + versions: T.Mapping[str, T.List[str]] = { + 'lex': kwargs['lex_version'], + 'flex': kwargs['flex_version'], + 'reflex': kwargs['reflex_version'], + 'win_flex': kwargs['win_flex_version'] + } + + for name in names: + bin = state.find_program( + name, wanted=versions[name], for_machine=kwargs['native'], required=False) + if bin.found(): + # If you're building reflex as a subproject, we consider that you + # know what you're doing. + if name == 'reflex' and isinstance(bin, ExternalProgram): + # there are potentially 3 programs called "reflex": + # 1. https://invisible-island.net/reflex/, an alternate fork + # of the original flex, this is supported + # 2. https://www.genivia.com/doc/reflex/html/, an + # alternative implementation for generating C++ scanners. + # Not supported + # 3. https://github.com/cespare/reflex, which is not a lex + # implementation at all, but a file watcher + _, out, err = Popen_safe(bin.get_command() + ['--version']) + if 'unknown flag: --version' in err: + mlog.debug('Skipping cespare/reflex, which is not a lexer and is not supported') + continue + if 'Written by Robert van Engelen' in out: + mlog.debug('Skipping RE/flex, which is not compatible with POSIX lex.') + continue + break + else: + if required: + raise MesonException.from_node( + 'Could not find a lex implementation. Tried: ', ", ".join(names), + node=state.current_node) + return LexGenerator(name, bin) + + lex_args: T.List[str] = [] + # This option allows compiling with MSVC + # https://github.com/lexxmark/winflexbison/blob/master/UNISTD_ERROR.readme + if bin.name == 'win_flex' and state.environment.machines[kwargs['native']].is_windows(): + lex_args.append('--wincompat') + lex_args.extend(['-o', '@OUTPUT0@']) + return LexGenerator(name, bin, T.cast('ImmutableListProtocol[str]', lex_args)) + + @noPosargs + @typed_kwargs( + 'codegen.yacc', + KwargInfo('yacc_version', ContainerTypeInfo(list, str), default=[], listify=True), + KwargInfo('byacc_version', ContainerTypeInfo(list, str), default=[], listify=True), + KwargInfo('bison_version', ContainerTypeInfo(list, str), default=[], listify=True), + KwargInfo('win_bison_version', ContainerTypeInfo(list, str), default=[], listify=True), + KwargInfo( + 'implementations', + ContainerTypeInfo(list, str), + default=[], + listify=True, + validator=is_subset_validator({'yacc', 'byacc', 'bison', 'win_bison'}) + ), + REQUIRED_KW, + DISABLER_KW, + NATIVE_KW, + ) + @disablerIfNotFound + def yacc_method(self, state: ModuleState, args: T.Tuple, kwargs: FindYaccKwargs) -> YaccGenerator: + disabled, required, feature = extract_required_kwarg(kwargs, state.subproject) + if disabled: + mlog.log('generator yacc skipped: feature', mlog.bold(feature), 'disabled') + return YaccGenerator('yacc', NonExistingExternalProgram('yacc')) + names: T.List[YaccImpls] + if kwargs['implementations']: + names = kwargs['implementations'] + else: + assert state.environment.machines[kwargs['native']] is not None, 'for mypy' + if state.environment.machines[kwargs['native']].system == 'windows': + names = ['win_bison', 'bison', 'yacc'] + else: + names = ['bison', 'byacc', 'yacc'] + + versions: T.Mapping[YaccImpls, T.List[str]] = { + 'yacc': kwargs['yacc_version'], + 'byacc': kwargs['byacc_version'], + 'bison': kwargs['bison_version'], + 'win_bison': kwargs['win_bison_version'], + } + + for name in names: + bin = state.find_program( + name, wanted=versions[name], for_machine=kwargs['native'], required=False) + if bin.found(): + break + else: + if required: + raise MesonException.from_node( + 'Could not find a yacc implementation. Tried: ', ", ".join(names), + node=state.current_node) + return YaccGenerator(name, bin) + + yacc_args: T.List[str] = ['@INPUT@', '-o', '@OUTPUT0@'] + + impl = T.cast('YaccImpls', bin.name) + if impl == 'yacc' and isinstance(bin, ExternalProgram): + _, out, _ = Popen_safe(bin.get_command() + ['--version']) + if 'GNU Bison' in out: + impl = 'bison' + elif out.startswith('yacc - 2'): + impl = 'byacc' + + if impl in {'bison', 'win_bison'}: + yacc_args.append('--defines=@OUTPUT1@') + if isinstance(bin, ExternalProgram) and version_compare(bin.get_version(), '>= 3.4'): + yacc_args.append('--color=always') + elif impl == 'byacc': + yacc_args.extend(['-H', '@OUTPUT1@']) + else: + mlog.warning('This yacc does not appear to be bison or byacc, the ' + 'POSIX specification does not require that header ' + 'output location be configurable, and may not work.', + fatal=False) + yacc_args.append('-H') + return YaccGenerator(name, bin, T.cast('ImmutableListProtocol[str]', yacc_args)) + + +def initialize(interpreter: Interpreter) -> CodeGenModule: + interpreter.append_holder_map(LexGenerator, LexHolder) + interpreter.append_holder_map(YaccGenerator, YaccHolder) + return CodeGenModule(interpreter) diff --git a/mesonbuild/modules/cuda.py b/mesonbuild/modules/cuda.py index eb73a57..df1af07 100644 --- a/mesonbuild/modules/cuda.py +++ b/mesonbuild/modules/cuda.py @@ -1,8 +1,9 @@ # SPDX-License-Identifier: Apache-2.0 -# Copyright 2017 The Meson development team +# Copyright 2017-2025 The Meson development team from __future__ import annotations +import dataclasses import re import typing as T @@ -31,6 +32,78 @@ if T.TYPE_CHECKING: DETECTED_KW: KwargInfo[T.Union[None, T.List[str]]] = KwargInfo('detected', (ContainerTypeInfo(list, str), NoneType), listify=True) + +@dataclasses.dataclass +class _CudaVersion: + + meson: str + windows: str + linux: str + + def compare(self, version: str, machine: str) -> T.Optional[str]: + if version_compare(version, f'>={self.meson}'): + return self.windows if machine == 'windows' else self.linux + return None + + +# Copied from: https://docs.nvidia.com/cuda/cuda-toolkit-release-notes/index.html#id7 +_DRIVER_TABLE_VERSION: T.List[_CudaVersion] = [ + _CudaVersion('13.0.2', 'unknown', '580.95.05'), + _CudaVersion('13.0.1', 'unknown', '580.82.07'), + _CudaVersion('13.0.0', 'unknown', '580.65.06'), + _CudaVersion('12.9.1', '576.57', '575.57.08'), + _CudaVersion('12.9.0', '576.02', '575.51.03'), + _CudaVersion('12.8.1', '572.61', '570.124.06'), + _CudaVersion('12.8.0', '570.65', '570.26'), + _CudaVersion('12.6.3', '561.17', '560.35.05'), + _CudaVersion('12.6.2', '560.94', '560.35.03'), + _CudaVersion('12.6.1', '560.94', '560.35.03'), + _CudaVersion('12.6.0', '560.76', '560.28.03'), + _CudaVersion('12.5.1', '555.85', '555.42.06'), + _CudaVersion('12.5.0', '555.85', '555.42.02'), + _CudaVersion('12.4.1', '551.78', '550.54.15'), + _CudaVersion('12.4.0', '551.61', '550.54.14'), + _CudaVersion('12.3.1', '546.12', '545.23.08'), + _CudaVersion('12.3.0', '545.84', '545.23.06'), + _CudaVersion('12.2.2', '537.13', '535.104.05'), + _CudaVersion('12.2.1', '536.67', '535.86.09'), + _CudaVersion('12.2.0', '536.25', '535.54.03'), + _CudaVersion('12.1.1', '531.14', '530.30.02'), + _CudaVersion('12.1.0', '531.14', '530.30.02'), + _CudaVersion('12.0.1', '528.33', '525.85.11'), + _CudaVersion('12.0.0', '527.41', '525.60.13'), + _CudaVersion('11.8.0', '522.06', '520.61.05'), + _CudaVersion('11.7.1', '516.31', '515.48.07'), + _CudaVersion('11.7.0', '516.01', '515.43.04'), + _CudaVersion('11.6.1', '511.65', '510.47.03'), # 11.6.2 is identical + _CudaVersion('11.6.0', '511.23', '510.39.01'), + _CudaVersion('11.5.1', '496.13', '495.29.05'), # 11.5.2 is identical + _CudaVersion('11.5.0', '496.04', '495.29.05'), + _CudaVersion('11.4.3', '472.50', '470.82.01'), # 11.4.4 is identical + _CudaVersion('11.4.1', '471.41', '470.57.02'), # 11.4.2 is identical + _CudaVersion('11.4.0', '471.11', '470.42.01'), + _CudaVersion('11.3.0', '465.89', '465.19.01'), # 11.3.1 is identical + _CudaVersion('11.2.2', '461.33', '460.32.03'), + _CudaVersion('11.2.1', '461.09', '460.32.03'), + _CudaVersion('11.2.0', '460.82', '460.27.03'), + _CudaVersion('11.1.1', '456.81', '455.32'), + _CudaVersion('11.1.0', '456.38', '455.23'), + _CudaVersion('11.0.3', '451.82', '450.51.06'), # 11.0.3.1 is identical + _CudaVersion('11.0.2', '451.48', '450.51.05'), + _CudaVersion('11.0.1', '451.22', '450.36.06'), + _CudaVersion('10.2.89', '441.22', '440.33'), + _CudaVersion('10.1.105', '418.96', '418.39'), + _CudaVersion('10.0.130', '411.31', '410.48'), + _CudaVersion('9.2.148', '398.26', '396.37'), + _CudaVersion('9.2.88', '397.44', '396.26'), + _CudaVersion('9.1.85', '391.29', '390.46'), + _CudaVersion('9.0.76', '385.54', '384.81'), + _CudaVersion('8.0.61', '376.51', '375.26'), + _CudaVersion('8.0.44', '369.30', '367.48'), + _CudaVersion('7.5.16', '353.66', '352.31'), + _CudaVersion('7.0.28', '347.62', '346.46'), +] + class CudaModule(NewExtensionModule): INFO = ModuleInfo('CUDA', '0.50.0', unstable=True) @@ -51,52 +124,16 @@ class CudaModule(NewExtensionModule): 'a CUDA Toolkit version string. Beware that, since CUDA 11.0, ' + 'the CUDA Toolkit\'s components (including NVCC) are versioned ' + 'independently from each other (and the CUDA Toolkit as a whole).') - if len(args) != 1 or not isinstance(args[0], str): raise argerror cuda_version = args[0] - driver_version_table = [ - {'cuda_version': '>=12.0.0', 'windows': '527.41', 'linux': '525.60.13'}, - {'cuda_version': '>=11.8.0', 'windows': '522.06', 'linux': '520.61.05'}, - {'cuda_version': '>=11.7.1', 'windows': '516.31', 'linux': '515.48.07'}, - {'cuda_version': '>=11.7.0', 'windows': '516.01', 'linux': '515.43.04'}, - {'cuda_version': '>=11.6.1', 'windows': '511.65', 'linux': '510.47.03'}, - {'cuda_version': '>=11.6.0', 'windows': '511.23', 'linux': '510.39.01'}, - {'cuda_version': '>=11.5.1', 'windows': '496.13', 'linux': '495.29.05'}, - {'cuda_version': '>=11.5.0', 'windows': '496.04', 'linux': '495.29.05'}, - {'cuda_version': '>=11.4.3', 'windows': '472.50', 'linux': '470.82.01'}, - {'cuda_version': '>=11.4.1', 'windows': '471.41', 'linux': '470.57.02'}, - {'cuda_version': '>=11.4.0', 'windows': '471.11', 'linux': '470.42.01'}, - {'cuda_version': '>=11.3.0', 'windows': '465.89', 'linux': '465.19.01'}, - {'cuda_version': '>=11.2.2', 'windows': '461.33', 'linux': '460.32.03'}, - {'cuda_version': '>=11.2.1', 'windows': '461.09', 'linux': '460.32.03'}, - {'cuda_version': '>=11.2.0', 'windows': '460.82', 'linux': '460.27.03'}, - {'cuda_version': '>=11.1.1', 'windows': '456.81', 'linux': '455.32'}, - {'cuda_version': '>=11.1.0', 'windows': '456.38', 'linux': '455.23'}, - {'cuda_version': '>=11.0.3', 'windows': '451.82', 'linux': '450.51.06'}, - {'cuda_version': '>=11.0.2', 'windows': '451.48', 'linux': '450.51.05'}, - {'cuda_version': '>=11.0.1', 'windows': '451.22', 'linux': '450.36.06'}, - {'cuda_version': '>=10.2.89', 'windows': '441.22', 'linux': '440.33'}, - {'cuda_version': '>=10.1.105', 'windows': '418.96', 'linux': '418.39'}, - {'cuda_version': '>=10.0.130', 'windows': '411.31', 'linux': '410.48'}, - {'cuda_version': '>=9.2.148', 'windows': '398.26', 'linux': '396.37'}, - {'cuda_version': '>=9.2.88', 'windows': '397.44', 'linux': '396.26'}, - {'cuda_version': '>=9.1.85', 'windows': '391.29', 'linux': '390.46'}, - {'cuda_version': '>=9.0.76', 'windows': '385.54', 'linux': '384.81'}, - {'cuda_version': '>=8.0.61', 'windows': '376.51', 'linux': '375.26'}, - {'cuda_version': '>=8.0.44', 'windows': '369.30', 'linux': '367.48'}, - {'cuda_version': '>=7.5.16', 'windows': '353.66', 'linux': '352.31'}, - {'cuda_version': '>=7.0.28', 'windows': '347.62', 'linux': '346.46'}, - ] - - driver_version = 'unknown' - for d in driver_version_table: - if version_compare(cuda_version, d['cuda_version']): - driver_version = d.get(state.environment.machines.host.system, d['linux']) - break - - return driver_version + + for d in _DRIVER_TABLE_VERSION: + driver_version = d.compare(cuda_version, state.environment.machines.host.system) + if driver_version is not None: + return driver_version + return 'unknown' @typed_pos_args('cuda.nvcc_arch_flags', (str, CudaCompiler), varargs=str) @typed_kwargs('cuda.nvcc_arch_flags', DETECTED_KW) @@ -178,42 +215,50 @@ class CudaModule(NewExtensionModule): # except that a bug with cuda_arch_list="All" is worked around by # tracking both lower and upper limits on GPU architectures. - cuda_known_gpu_architectures = ['Fermi', 'Kepler', 'Maxwell'] # noqa: E221 + cuda_known_gpu_architectures = [] # noqa: E221 cuda_common_gpu_architectures = ['3.0', '3.5', '5.0'] # noqa: E221 cuda_hi_limit_gpu_architecture = None # noqa: E221 cuda_lo_limit_gpu_architecture = '2.0' # noqa: E221 cuda_all_gpu_architectures = ['3.0', '3.2', '3.5', '5.0'] # noqa: E221 - if version_compare(cuda_version, '<7.0'): - cuda_hi_limit_gpu_architecture = '5.2' + # Fermi and Kepler support have been dropped since 12.0 + if version_compare(cuda_version, '<12.0'): + cuda_known_gpu_architectures.extend(['Fermi', 'Kepler']) - if version_compare(cuda_version, '>=7.0'): - cuda_known_gpu_architectures += ['Kepler+Tegra', 'Kepler+Tesla', 'Maxwell+Tegra'] # noqa: E221 - cuda_common_gpu_architectures += ['5.2'] # noqa: E221 + # Everything older than Turing is dropped by 13.0 + if version_compare(cuda_version, '<13.0'): + cuda_known_gpu_architectures.append('Maxwell') - if version_compare(cuda_version, '<8.0'): - cuda_common_gpu_architectures += ['5.2+PTX'] # noqa: E221 - cuda_hi_limit_gpu_architecture = '6.0' # noqa: E221 + if version_compare(cuda_version, '<7.0'): + cuda_hi_limit_gpu_architecture = '5.2' - if version_compare(cuda_version, '>=8.0'): - cuda_known_gpu_architectures += ['Pascal', 'Pascal+Tegra'] # noqa: E221 - cuda_common_gpu_architectures += ['6.0', '6.1'] # noqa: E221 - cuda_all_gpu_architectures += ['6.0', '6.1', '6.2'] # noqa: E221 + if version_compare(cuda_version, '>=7.0'): + cuda_known_gpu_architectures += ['Kepler+Tegra', 'Kepler+Tesla', 'Maxwell+Tegra'] # noqa: E221 + cuda_common_gpu_architectures += ['5.2'] # noqa: E221 - if version_compare(cuda_version, '<9.0'): - cuda_common_gpu_architectures += ['6.1+PTX'] # noqa: E221 - cuda_hi_limit_gpu_architecture = '7.0' # noqa: E221 + if version_compare(cuda_version, '<8.0'): + cuda_common_gpu_architectures += ['5.2+PTX'] # noqa: E221 + cuda_hi_limit_gpu_architecture = '6.0' # noqa: E221 - if version_compare(cuda_version, '>=9.0'): - cuda_known_gpu_architectures += ['Volta', 'Xavier'] # noqa: E221 - cuda_common_gpu_architectures += ['7.0'] # noqa: E221 - cuda_all_gpu_architectures += ['7.0', '7.2'] # noqa: E221 - # https://docs.nvidia.com/cuda/archive/9.0/cuda-toolkit-release-notes/index.html#unsupported-features - cuda_lo_limit_gpu_architecture = '3.0' # noqa: E221 + if version_compare(cuda_version, '>=8.0'): + cuda_known_gpu_architectures += ['Pascal', 'Pascal+Tegra'] # noqa: E221 + cuda_common_gpu_architectures += ['6.0', '6.1'] # noqa: E221 + cuda_all_gpu_architectures += ['6.0', '6.1', '6.2'] # noqa: E221 - if version_compare(cuda_version, '<10.0'): - cuda_common_gpu_architectures += ['7.2+PTX'] # noqa: E221 - cuda_hi_limit_gpu_architecture = '8.0' # noqa: E221 + if version_compare(cuda_version, '<9.0'): + cuda_common_gpu_architectures += ['6.1+PTX'] # noqa: E221 + cuda_hi_limit_gpu_architecture = '7.0' # noqa: E221 + + if version_compare(cuda_version, '>=9.0'): + cuda_known_gpu_architectures += ['Volta', 'Xavier'] # noqa: E221 + cuda_common_gpu_architectures += ['7.0'] # noqa: E221 + cuda_all_gpu_architectures += ['7.0', '7.2'] # noqa: E221 + # https://docs.nvidia.com/cuda/archive/9.0/cuda-toolkit-release-notes/index.html#unsupported-features + cuda_lo_limit_gpu_architecture = '3.0' # noqa: E221 + + if version_compare(cuda_version, '<10.0'): + cuda_common_gpu_architectures += ['7.2+PTX'] # noqa: E221 + cuda_hi_limit_gpu_architecture = '8.0' # noqa: E221 if version_compare(cuda_version, '>=10.0'): cuda_known_gpu_architectures += ['Turing'] # noqa: E221 @@ -266,6 +311,29 @@ class CudaModule(NewExtensionModule): if version_compare(cuda_version, '<13'): cuda_hi_limit_gpu_architecture = '10.0' # noqa: E221 + if version_compare(cuda_version, '>=12.8'): + cuda_known_gpu_architectures.append('Blackwell') + cuda_common_gpu_architectures.extend(['10.0', '12.0']) + cuda_all_gpu_architectures.extend(['10.0', '12.0']) + + if version_compare(cuda_version, '<13'): + # Yes, 12.8 and 12.9 support 10.1, but 13.0 doesn't + cuda_common_gpu_architectures.append('10.1') + cuda_all_gpu_architectures.append('10.1') + cuda_hi_limit_gpu_architecture = '12.1' + + if version_compare(cuda_version, '>=12.9'): + cuda_common_gpu_architectures.extend(['10.3', '12.1']) + cuda_all_gpu_architectures.extend(['10.3', '12.1']) + + if version_compare(cuda_version, '>=13.0'): + cuda_common_gpu_architectures.append('11.0') + cuda_all_gpu_architectures.append('11.0') + cuda_lo_limit_gpu_architecture = '7.5' + + if version_compare(cuda_version, '<14'): + cuda_hi_limit_gpu_architecture = '12.1' + if not cuda_arch_list: cuda_arch_list = 'Auto' @@ -318,6 +386,7 @@ class CudaModule(NewExtensionModule): 'Orin': (['8.7'], []), 'Lovelace': (['8.9'], ['8.9']), 'Hopper': (['9.0'], ['9.0']), + 'Blackwell': (['10.0'], ['10.0']), }.get(arch_name, (None, None)) if arch_bin is None: diff --git a/mesonbuild/modules/dlang.py b/mesonbuild/modules/dlang.py index 966b070..35ce86b 100644 --- a/mesonbuild/modules/dlang.py +++ b/mesonbuild/modules/dlang.py @@ -7,27 +7,45 @@ from __future__ import annotations import json import os +import typing as T + from . import ExtensionModule, ModuleInfo from .. import mlog +from ..build import InvalidArguments from ..dependencies import Dependency from ..dependencies.dub import DubDependency from ..interpreterbase import typed_pos_args from ..mesonlib import Popen_safe, MesonException, listify +if T.TYPE_CHECKING: + from typing_extensions import Literal, TypeAlias + + from . import ModuleState + from ..build import OverrideExecutable + from ..interpreter.interpreter import Interpreter + from ..interpreterbase.baseobjects import TYPE_kwargs + from ..programs import ExternalProgram, OverrideProgram + + _AnyProgram: TypeAlias = T.Union[OverrideExecutable, ExternalProgram, OverrideProgram] + _JSONTypes: TypeAlias = T.Union[str, int, bool, None, T.List['_JSONTypes'], T.Dict[str, '_JSONTypes']] + + class DlangModule(ExtensionModule): - class_dubbin = None + class_dubbin: T.Union[_AnyProgram, Literal[False], None] = None init_dub = False + dubbin: T.Union[_AnyProgram, Literal[False], None] + INFO = ModuleInfo('dlang', '0.48.0') - def __init__(self, interpreter): + def __init__(self, interpreter: Interpreter): super().__init__(interpreter) self.methods.update({ 'generate_dub_file': self.generate_dub_file, }) - def _init_dub(self, state): + def _init_dub(self, state: ModuleState) -> None: if DlangModule.class_dubbin is None and DubDependency.class_dubbin is not None: self.dubbin = DubDependency.class_dubbin[0] DlangModule.class_dubbin = self.dubbin @@ -45,11 +63,11 @@ class DlangModule(ExtensionModule): raise MesonException('DUB not found.') @typed_pos_args('dlang.generate_dub_file', str, str) - def generate_dub_file(self, state, args, kwargs): + def generate_dub_file(self, state: ModuleState, args: T.Tuple[str, str], kwargs: TYPE_kwargs) -> None: if not DlangModule.init_dub: self._init_dub(state) - config = { + config: T.Dict[str, _JSONTypes] = { 'name': args[0] } @@ -70,7 +88,7 @@ class DlangModule(ExtensionModule): for key, value in kwargs.items(): if key == 'dependencies': values = listify(value, flatten=False) - config[key] = {} + data: T.Dict[str, _JSONTypes] = {} for dep in values: if isinstance(dep, Dependency): name = dep.get_name() @@ -78,20 +96,33 @@ class DlangModule(ExtensionModule): if ret == 0: version = dep.get_version() if version is None: - config[key][name] = '' + data[name] = '' else: - config[key][name] = version + data[name] = version + config[key] = data else: - config[key] = value + def _do_validate(v: object) -> _JSONTypes: + if not isinstance(v, (str, int, bool, list, dict)): + raise InvalidArguments('keyword arguments must be strings, numbers, booleans, arrays, or dictionaries of such') + if isinstance(v, list): + for e in v: + _do_validate(e) + if isinstance(v, dict): + for e in v.values(): + _do_validate(e) + return T.cast('_JSONTypes', v) + + config[key] = _do_validate(value) with open(config_path, 'w', encoding='utf-8') as ofile: ofile.write(json.dumps(config, indent=4, ensure_ascii=False)) - def _call_dubbin(self, args, env=None): + def _call_dubbin(self, args: T.List[str], env: T.Optional[T.Mapping[str, str]] = None) -> T.Tuple[int, str]: + assert self.dubbin is not None and self.dubbin is not False, 'for mypy' p, out = Popen_safe(self.dubbin.get_command() + args, env=env)[0:2] return p.returncode, out.strip() - def check_dub(self, state): + def check_dub(self, state: ModuleState) -> T.Union[_AnyProgram, Literal[False]]: dubbin = state.find_program('dub', silent=True) if dubbin.found(): try: @@ -101,17 +132,14 @@ class DlangModule(ExtensionModule): ''.format(' '.join(dubbin.get_command()))) # Set to False instead of None to signify that we've already # searched for it and not found it - dubbin = False + else: + mlog.log('Found DUB:', mlog.green('YES'), ':', mlog.bold(dubbin.get_path() or ''), + '({})'.format(out.strip())) + return dubbin except (FileNotFoundError, PermissionError): - dubbin = False - else: - dubbin = False - if dubbin: - mlog.log('Found DUB:', mlog.bold(dubbin.get_path()), - '(%s)' % out.strip()) - else: - mlog.log('Found DUB:', mlog.red('NO')) - return dubbin + pass + mlog.log('Found DUB:', mlog.red('NO')) + return False -def initialize(*args, **kwargs): - return DlangModule(*args, **kwargs) +def initialize(interp: Interpreter) -> DlangModule: + return DlangModule(interp) diff --git a/mesonbuild/modules/external_project.py b/mesonbuild/modules/external_project.py index 339d000..0881df9 100644 --- a/mesonbuild/modules/external_project.py +++ b/mesonbuild/modules/external_project.py @@ -169,7 +169,7 @@ class ExternalProject(NewExtensionModule): def _quote_and_join(self, array: T.List[str]) -> str: return ' '.join([shlex.quote(i) for i in array]) - def _validate_configure_options(self, variables: T.List[T.Tuple[str, str, str]], state: 'ModuleState') -> None: + def _validate_configure_options(self, variables: T.Sequence[T.Tuple[str, T.Optional[str], str]], state: 'ModuleState') -> None: # Ensure the user at least try to pass basic info to the build system, # like the prefix, libdir, etc. for key, default, val in variables: @@ -183,7 +183,7 @@ class ExternalProject(NewExtensionModule): FeatureNew('Default configure_option', '0.57.0').use(self.subproject, state.current_node) self.configure_options.append(default) - def _format_options(self, options: T.List[str], variables: T.List[T.Tuple[str, str, str]]) -> T.List[str]: + def _format_options(self, options: T.List[str], variables: T.Sequence[T.Tuple[str, T.Optional[str], str]]) -> T.List[str]: out: T.List[str] = [] missing = set() regex = get_variable_regex('meson') @@ -201,10 +201,10 @@ class ExternalProject(NewExtensionModule): def _run(self, step: str, command: T.List[str], workdir: Path) -> None: mlog.log(f'External project {self.name}:', mlog.bold(step)) m = 'Running command ' + str(command) + ' in directory ' + str(workdir) + '\n' - log_filename = Path(mlog.get_log_dir(), f'{self.name}-{step}.log') + logfile = Path(mlog.get_log_dir(), f'{self.name}-{step}.log') output = None if not self.verbose: - output = open(log_filename, 'w', encoding='utf-8') + output = open(logfile, 'w', encoding='utf-8') output.write(m + '\n') output.flush() else: @@ -215,7 +215,10 @@ class ExternalProject(NewExtensionModule): if p.returncode != 0: m = f'{step} step returned error code {p.returncode}.' if not self.verbose: - m += '\nSee logs: ' + str(log_filename) + m += '\nSee logs: ' + str(logfile) + contents = mlog.ci_fold_file(logfile, f'CI platform detected, click here for {os.path.basename(logfile)} contents.') + if contents: + print(contents) raise MesonException(m) def _create_targets(self, extra_depends: T.List[T.Union['BuildTarget', 'CustomTarget']]) -> T.List['TYPE_var']: diff --git a/mesonbuild/modules/fs.py b/mesonbuild/modules/fs.py index 1fa368e..57a6b6d 100644 --- a/mesonbuild/modules/fs.py +++ b/mesonbuild/modules/fs.py @@ -2,7 +2,9 @@ # Copyright 2019 The Meson development team from __future__ import annotations -from pathlib import Path, PurePath, PureWindowsPath +from ntpath import sep as ntsep +from pathlib import Path +from posixpath import sep as posixsep import hashlib import os import typing as T @@ -12,7 +14,7 @@ from .. import mlog from ..build import BuildTarget, CustomTarget, CustomTargetIndex, InvalidArguments from ..interpreter.type_checking import INSTALL_KW, INSTALL_MODE_KW, INSTALL_TAG_KW, NoneType from ..interpreterbase import FeatureNew, KwargInfo, typed_kwargs, typed_pos_args, noKwargs -from ..mesonlib import File, MesonException, has_path_sep, path_is_in_root, relpath +from ..mesonlib import File, MesonException, has_path_sep, is_windows, path_is_in_root, relpath if T.TYPE_CHECKING: from . import ModuleState @@ -42,7 +44,7 @@ class FSModule(ExtensionModule): INFO = ModuleInfo('fs', '0.53.0') - def __init__(self, interpreter: 'Interpreter') -> None: + def __init__(self, interpreter: Interpreter) -> None: super().__init__(interpreter) self.methods.update({ 'as_posix': self.as_posix, @@ -62,29 +64,30 @@ class FSModule(ExtensionModule): 'replace_suffix': self.replace_suffix, 'size': self.size, 'stem': self.stem, + 'suffix': self.suffix, }) - def _absolute_dir(self, state: 'ModuleState', arg: 'FileOrString') -> Path: + def _absolute_dir(self, state: ModuleState, arg: FileOrString) -> str: """ make an absolute path from a relative path, WITHOUT resolving symlinks """ if isinstance(arg, File): - return Path(arg.absolute_path(state.source_root, state.environment.get_build_dir())) - return Path(state.source_root) / Path(state.subdir) / Path(arg).expanduser() + return arg.absolute_path(state.source_root, state.environment.get_build_dir()) + return os.path.join(state.source_root, state.subdir, os.path.expanduser(arg)) @staticmethod - def _obj_to_path(feature_new_prefix: str, obj: T.Union[FileOrString, BuildTargetTypes], state: ModuleState) -> PurePath: + def _obj_to_pathstr(feature_new_prefix: str, obj: T.Union[FileOrString, BuildTargetTypes], state: ModuleState) -> str: if isinstance(obj, str): - return PurePath(obj) + return obj if isinstance(obj, File): FeatureNew(f'{feature_new_prefix} with file', '0.59.0').use(state.subproject, location=state.current_node) - return PurePath(str(obj)) + return str(obj) FeatureNew(f'{feature_new_prefix} with build_tgt, custom_tgt, and custom_idx', '1.4.0').use(state.subproject, location=state.current_node) - return PurePath(state.backend.get_target_filename(obj)) + return state.backend.get_target_filename(obj) - def _resolve_dir(self, state: 'ModuleState', arg: 'FileOrString') -> Path: + def _resolve_dir(self, state: ModuleState, arg: FileOrString) -> str: """ resolves symlinks and makes absolute a directory relative to calling meson.build, if not already absolute @@ -92,7 +95,7 @@ class FSModule(ExtensionModule): path = self._absolute_dir(state, arg) try: # accommodate unresolvable paths e.g. symlink loops - path = path.resolve() + path = os.path.realpath(path) except Exception: # return the best we could do pass @@ -101,123 +104,139 @@ class FSModule(ExtensionModule): @noKwargs @FeatureNew('fs.expanduser', '0.54.0') @typed_pos_args('fs.expanduser', str) - def expanduser(self, state: 'ModuleState', args: T.Tuple[str], kwargs: T.Dict[str, T.Any]) -> str: - return str(Path(args[0]).expanduser()) + def expanduser(self, state: ModuleState, args: T.Tuple[str], kwargs: T.Dict[str, T.Any]) -> str: + return os.path.expanduser(args[0]) @noKwargs @FeatureNew('fs.is_absolute', '0.54.0') @typed_pos_args('fs.is_absolute', (str, File)) - def is_absolute(self, state: 'ModuleState', args: T.Tuple['FileOrString'], kwargs: T.Dict[str, T.Any]) -> bool: - if isinstance(args[0], File): + def is_absolute(self, state: ModuleState, args: T.Tuple[FileOrString], kwargs: T.Dict[str, T.Any]) -> bool: + path = args[0] + if isinstance(path, File): FeatureNew('fs.is_absolute with file', '0.59.0').use(state.subproject, location=state.current_node) - return PurePath(str(args[0])).is_absolute() + path = str(path) + if is_windows(): + # os.path.isabs was broken for Windows before Python 3.13, so we implement it ourselves + path = path[:3].replace(posixsep, ntsep) + return path.startswith(ntsep * 2) or path.startswith(':' + ntsep, 1) + return path.startswith(posixsep) @noKwargs @FeatureNew('fs.as_posix', '0.54.0') @typed_pos_args('fs.as_posix', str) - def as_posix(self, state: 'ModuleState', args: T.Tuple[str], kwargs: T.Dict[str, T.Any]) -> str: + def as_posix(self, state: ModuleState, args: T.Tuple[str], kwargs: T.Dict[str, T.Any]) -> str: r""" this function assumes you are passing a Windows path, even if on a Unix-like system and so ALL '\' are turned to '/', even if you meant to escape a character """ - return PureWindowsPath(args[0]).as_posix() + return args[0].replace(ntsep, posixsep) @noKwargs @typed_pos_args('fs.exists', str) - def exists(self, state: 'ModuleState', args: T.Tuple[str], kwargs: T.Dict[str, T.Any]) -> bool: - return self._resolve_dir(state, args[0]).exists() + def exists(self, state: ModuleState, args: T.Tuple[str], kwargs: T.Dict[str, T.Any]) -> bool: + return os.path.exists(self._resolve_dir(state, args[0])) @noKwargs @typed_pos_args('fs.is_symlink', (str, File)) - def is_symlink(self, state: 'ModuleState', args: T.Tuple['FileOrString'], kwargs: T.Dict[str, T.Any]) -> bool: + def is_symlink(self, state: ModuleState, args: T.Tuple[FileOrString], kwargs: T.Dict[str, T.Any]) -> bool: if isinstance(args[0], File): FeatureNew('fs.is_symlink with file', '0.59.0').use(state.subproject, location=state.current_node) - return self._absolute_dir(state, args[0]).is_symlink() + return os.path.islink(self._absolute_dir(state, args[0])) @noKwargs @typed_pos_args('fs.is_file', str) - def is_file(self, state: 'ModuleState', args: T.Tuple[str], kwargs: T.Dict[str, T.Any]) -> bool: - return self._resolve_dir(state, args[0]).is_file() + def is_file(self, state: ModuleState, args: T.Tuple[str], kwargs: T.Dict[str, T.Any]) -> bool: + return os.path.isfile(self._resolve_dir(state, args[0])) @noKwargs @typed_pos_args('fs.is_dir', str) - def is_dir(self, state: 'ModuleState', args: T.Tuple[str], kwargs: T.Dict[str, T.Any]) -> bool: - return self._resolve_dir(state, args[0]).is_dir() + def is_dir(self, state: ModuleState, args: T.Tuple[str], kwargs: T.Dict[str, T.Any]) -> bool: + return os.path.isdir(self._resolve_dir(state, args[0])) @noKwargs @typed_pos_args('fs.hash', (str, File), str) - def hash(self, state: 'ModuleState', args: T.Tuple['FileOrString', str], kwargs: T.Dict[str, T.Any]) -> str: + def hash(self, state: ModuleState, args: T.Tuple[FileOrString, str], kwargs: T.Dict[str, T.Any]) -> str: if isinstance(args[0], File): FeatureNew('fs.hash with file', '0.59.0').use(state.subproject, location=state.current_node) file = self._resolve_dir(state, args[0]) - if not file.is_file(): + if not os.path.isfile(file): raise MesonException(f'{file} is not a file and therefore cannot be hashed') try: h = hashlib.new(args[1]) except ValueError: raise MesonException('hash algorithm {} is not available'.format(args[1])) - mlog.debug('computing {} sum of {} size {} bytes'.format(args[1], file, file.stat().st_size)) - h.update(file.read_bytes()) + mlog.debug('computing {} sum of {} size {} bytes'.format(args[1], file, os.stat(file).st_size)) + with open(file, mode='rb', buffering=0) as f: + h.update(f.read()) return h.hexdigest() @noKwargs @typed_pos_args('fs.size', (str, File)) - def size(self, state: 'ModuleState', args: T.Tuple['FileOrString'], kwargs: T.Dict[str, T.Any]) -> int: + def size(self, state: ModuleState, args: T.Tuple[FileOrString], kwargs: T.Dict[str, T.Any]) -> int: if isinstance(args[0], File): FeatureNew('fs.size with file', '0.59.0').use(state.subproject, location=state.current_node) file = self._resolve_dir(state, args[0]) - if not file.is_file(): + if not os.path.isfile(file): raise MesonException(f'{file} is not a file and therefore cannot be sized') try: - return file.stat().st_size + return os.stat(file).st_size except ValueError: raise MesonException('{} size could not be determined'.format(args[0])) @noKwargs @typed_pos_args('fs.is_samepath', (str, File), (str, File)) - def is_samepath(self, state: 'ModuleState', args: T.Tuple['FileOrString', 'FileOrString'], kwargs: T.Dict[str, T.Any]) -> bool: + def is_samepath(self, state: ModuleState, args: T.Tuple[FileOrString, FileOrString], kwargs: T.Dict[str, T.Any]) -> bool: if isinstance(args[0], File) or isinstance(args[1], File): FeatureNew('fs.is_samepath with file', '0.59.0').use(state.subproject, location=state.current_node) file1 = self._resolve_dir(state, args[0]) file2 = self._resolve_dir(state, args[1]) - if not file1.exists(): + if not os.path.exists(file1): return False - if not file2.exists(): + if not os.path.exists(file2): return False try: - return file1.samefile(file2) + return os.path.samefile(file1, file2) except OSError: return False @noKwargs @typed_pos_args('fs.replace_suffix', (str, File, CustomTarget, CustomTargetIndex, BuildTarget), str) - def replace_suffix(self, state: 'ModuleState', args: T.Tuple[T.Union[FileOrString, BuildTargetTypes], str], kwargs: T.Dict[str, T.Any]) -> str: - path = self._obj_to_path('fs.replace_suffix', args[0], state) - return str(path.with_suffix(args[1])) + def replace_suffix(self, state: ModuleState, args: T.Tuple[T.Union[FileOrString, BuildTargetTypes], str], kwargs: T.Dict[str, T.Any]) -> str: + if args[1] and not args[1].startswith('.'): + raise ValueError(f"Invalid suffix {args[1]!r}") + path = self._obj_to_pathstr('fs.replace_suffix', args[0], state) + return os.path.splitext(path)[0] + args[1] @noKwargs @typed_pos_args('fs.parent', (str, File, CustomTarget, CustomTargetIndex, BuildTarget)) - def parent(self, state: 'ModuleState', args: T.Tuple[T.Union[FileOrString, BuildTargetTypes]], kwargs: T.Dict[str, T.Any]) -> str: - path = self._obj_to_path('fs.parent', args[0], state) - return str(path.parent) + def parent(self, state: ModuleState, args: T.Tuple[T.Union[FileOrString, BuildTargetTypes]], kwargs: T.Dict[str, T.Any]) -> str: + path = self._obj_to_pathstr('fs.parent', args[0], state) + return os.path.split(path)[0] or '.' @noKwargs @typed_pos_args('fs.name', (str, File, CustomTarget, CustomTargetIndex, BuildTarget)) - def name(self, state: 'ModuleState', args: T.Tuple[T.Union[FileOrString, BuildTargetTypes]], kwargs: T.Dict[str, T.Any]) -> str: - path = self._obj_to_path('fs.name', args[0], state) - return str(path.name) + def name(self, state: ModuleState, args: T.Tuple[T.Union[FileOrString, BuildTargetTypes]], kwargs: T.Dict[str, T.Any]) -> str: + path = self._obj_to_pathstr('fs.name', args[0], state) + return os.path.basename(path) @noKwargs @typed_pos_args('fs.stem', (str, File, CustomTarget, CustomTargetIndex, BuildTarget)) @FeatureNew('fs.stem', '0.54.0') - def stem(self, state: 'ModuleState', args: T.Tuple[T.Union[FileOrString, BuildTargetTypes]], kwargs: T.Dict[str, T.Any]) -> str: - path = self._obj_to_path('fs.stem', args[0], state) - return str(path.stem) + def stem(self, state: ModuleState, args: T.Tuple[T.Union[FileOrString, BuildTargetTypes]], kwargs: T.Dict[str, T.Any]) -> str: + path = self._obj_to_pathstr('fs.name', args[0], state) + return os.path.splitext(os.path.basename(path))[0] + + @noKwargs + @typed_pos_args('fs.suffix', (str, File, CustomTarget, CustomTargetIndex, BuildTarget)) + @FeatureNew('fs.suffix', '1.9.0') + def suffix(self, state: ModuleState, args: T.Tuple[T.Union[FileOrString, BuildTargetTypes]], kwargs: T.Dict[str, T.Any]) -> str: + path = self._obj_to_pathstr('fs.suffix', args[0], state) + return os.path.splitext(path)[1] @FeatureNew('fs.read', '0.57.0') @typed_pos_args('fs.read', (str, File)) @typed_kwargs('fs.read', KwargInfo('encoding', str, default='utf-8')) - def read(self, state: 'ModuleState', args: T.Tuple['FileOrString'], kwargs: 'ReadKwArgs') -> str: + def read(self, state: ModuleState, args: T.Tuple[FileOrString], kwargs: ReadKwArgs) -> str: """Read a file from the source tree and return its value as a decoded string. diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index 6764133..0f522c1 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -1,6 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright 2015-2016 The Meson development team -# Copyright © 2023-2024 Intel Corporation +# Copyright © 2023-2025 Intel Corporation '''This module provides helper functions for Gnome/GLib related functionality such as gobject-introspection, gresources and gtk-doc''' @@ -37,7 +37,7 @@ from ..programs import OverrideProgram from ..scripts.gettext import read_linguas if T.TYPE_CHECKING: - from typing_extensions import Literal, TypedDict + from typing_extensions import Literal, TypeAlias, TypedDict from . import ModuleState from ..build import BuildTarget @@ -137,6 +137,8 @@ if T.TYPE_CHECKING: install_header: bool install_dir: T.Optional[str] docbook: T.Optional[str] + rst: T.Optional[str] + markdown: T.Optional[str] autocleanup: Literal['all', 'none', 'objects', 'default'] class GenMarshal(TypedDict): @@ -196,7 +198,7 @@ if T.TYPE_CHECKING: vtail: T.Optional[str] depends: T.List[T.Union[BuildTarget, CustomTarget, CustomTargetIndex]] - ToolType = T.Union[Executable, ExternalProgram, OverrideProgram] + ToolType: TypeAlias = T.Union[Executable, ExternalProgram, OverrideProgram] # Differs from the CustomTarget version in that it straight defaults to True @@ -254,8 +256,8 @@ class GnomeModule(ExtensionModule): def __init__(self, interpreter: 'Interpreter') -> None: super().__init__(interpreter) self.gir_dep: T.Optional[Dependency] = None - self.giscanner: T.Optional[T.Union[ExternalProgram, Executable, OverrideProgram]] = None - self.gicompiler: T.Optional[T.Union[ExternalProgram, Executable, OverrideProgram]] = None + self.giscanner: T.Optional[ToolType] = None + self.gicompiler: T.Optional[ToolType] = None self.install_glib_compile_schemas = False self.install_gio_querymodules: T.List[str] = [] self.install_gtk_update_icon_cache = False @@ -281,7 +283,7 @@ class GnomeModule(ExtensionModule): def _get_native_glib_version(self, state: 'ModuleState') -> str: if self.native_glib_version is None: glib_dep = PkgConfigDependency('glib-2.0', state.environment, - {'native': True, 'required': False}) + {'native': MachineChoice.BUILD, 'required': False}) if glib_dep.found(): self.native_glib_version = glib_dep.get_version() else: @@ -634,7 +636,7 @@ class GnomeModule(ExtensionModule): # https://github.com/mesonbuild/meson/issues/1911 # However, g-ir-scanner does not understand -Wl,-rpath # so we need to use -L instead - for d in state.backend.determine_rpath_dirs(lib): + for d in lib.determine_rpath_dirs(): d = os.path.join(state.environment.get_build_dir(), d) link_command.append('-L' + d) if include_rpath: @@ -646,7 +648,7 @@ class GnomeModule(ExtensionModule): return link_command, new_depends def _get_dependencies_flags_raw( - self, deps: T.Sequence[T.Union['Dependency', build.BuildTarget, CustomTarget, CustomTargetIndex]], + self, deps: T.Sequence[T.Union['Dependency', build.BuildTargetTypes]], state: 'ModuleState', depends: T.Sequence[T.Union[build.BuildTarget, 'build.GeneratedTypes', 'FileOrString', build.StructuredSources]], include_rpath: bool, @@ -739,7 +741,7 @@ class GnomeModule(ExtensionModule): return cflags, internal_ldflags, external_ldflags, gi_includes, depends def _get_dependencies_flags( - self, deps: T.Sequence[T.Union['Dependency', build.BuildTarget, CustomTarget, CustomTargetIndex]], + self, deps: T.Sequence[T.Union['Dependency', build.BuildTargetTypes]], state: 'ModuleState', depends: T.Sequence[T.Union[build.BuildTarget, 'build.GeneratedTypes', 'FileOrString', build.StructuredSources]], include_rpath: bool = False, @@ -773,9 +775,7 @@ class GnomeModule(ExtensionModule): STATIC_BUILD_REQUIRED_VERSION = ">=1.58.1" if isinstance(girtarget, (build.StaticLibrary)) and \ - not mesonlib.version_compare( - self._get_gir_dep(state)[0].get_version(), - STATIC_BUILD_REQUIRED_VERSION): + not self._giscanner_version_compare(state, STATIC_BUILD_REQUIRED_VERSION): raise MesonException('Static libraries can only be introspected with GObject-Introspection ' + STATIC_BUILD_REQUIRED_VERSION) return girtarget @@ -797,10 +797,20 @@ class GnomeModule(ExtensionModule): self.gicompiler = self._find_tool(state, 'g-ir-compiler') return self.gir_dep, self.giscanner, self.gicompiler + def _giscanner_version_compare(self, state: 'ModuleState', cmp: str) -> bool: + # Support for --version was introduced in g-i 1.58, but Ubuntu + # Bionic shipped 1.56.1. As all our version checks are greater + # than 1.58, we can just return False if get_version fails. + try: + giscanner, _, _ = self._get_gir_dep(state) + return mesonlib.version_compare(giscanner.get_version(), cmp) + except MesonException: + return False + @functools.lru_cache(maxsize=None) def _gir_has_option(self, option: str) -> bool: exe = self.giscanner - if isinstance(exe, OverrideProgram): + if isinstance(exe, (Executable, OverrideProgram)): # Handle overridden g-ir-scanner assert option in {'--extra-library', '--sources-top-dirs'} return True @@ -865,7 +875,7 @@ class GnomeModule(ExtensionModule): # https://github.com/mesonbuild/meson/issues/1911 # However, g-ir-scanner does not understand -Wl,-rpath # so we need to use -L instead - for d in state.backend.determine_rpath_dirs(girtarget): + for d in girtarget.determine_rpath_dirs(): d = os.path.join(state.environment.get_build_dir(), d) ret.append('-L' + d) @@ -885,8 +895,8 @@ class GnomeModule(ExtensionModule): @staticmethod def _get_gir_targets_deps(girtargets: T.Sequence[build.BuildTarget] - ) -> T.List[T.Union[build.BuildTarget, CustomTarget, CustomTargetIndex, Dependency]]: - ret: T.List[T.Union[build.BuildTarget, CustomTarget, CustomTargetIndex, Dependency]] = [] + ) -> T.List[T.Union[build.BuildTargetTypes, Dependency]]: + ret: T.List[T.Union[build.BuildTargetTypes, Dependency]] = [] for girtarget in girtargets: ret += girtarget.get_all_link_deps() ret += girtarget.get_external_deps() @@ -912,9 +922,9 @@ class GnomeModule(ExtensionModule): if state.project_args.get(lang): cflags += state.project_args[lang] if OptionKey('b_sanitize') in compiler.base_options: - sanitize = state.environment.coredata.optstore.get_value('b_sanitize') + sanitize = state.environment.coredata.optstore.get_value_for('b_sanitize') assert isinstance(sanitize, list) - cflags += compiler.sanitizer_compile_args(sanitize) + cflags += compiler.sanitizer_compile_args(None, sanitize) # These must be first in ldflags if 'address' in sanitize: internal_ldflags += ['-lasan'] @@ -924,7 +934,7 @@ class GnomeModule(ExtensionModule): internal_ldflags += ['-lubsan'] # FIXME: Linking directly to lib*san is not recommended but g-ir-scanner # does not understand -f LDFLAGS. https://bugzilla.gnome.org/show_bug.cgi?id=783892 - # ldflags += compiler.sanitizer_link_args(sanitize) + # ldflags += compiler.sanitizer_link_args(None, state.environment, sanitize) return cflags, internal_ldflags, external_ldflags @@ -957,12 +967,12 @@ class GnomeModule(ExtensionModule): return gir_filelist_filename - @staticmethod def _make_gir_target( + self, state: 'ModuleState', girfile: str, scan_command: T.Sequence[T.Union['FileOrString', Executable, ExternalProgram, OverrideProgram]], - generated_files: T.Sequence[T.Union[str, mesonlib.File, CustomTarget, CustomTargetIndex, GeneratedList]], + generated_files: T.Sequence[T.Union[str, mesonlib.File, build.GeneratedTypes]], depends: T.Sequence[T.Union['FileOrString', build.BuildTarget, 'build.GeneratedTypes', build.StructuredSources]], env_flags: T.Sequence[str], kwargs: T.Dict[str, T.Any]) -> GirTarget: @@ -988,6 +998,9 @@ class GnomeModule(ExtensionModule): run_env.set('CFLAGS', [quote_arg(x) for x in env_flags], ' ') run_env.merge(kwargs['env']) + # response file supported? + rspable = self._giscanner_version_compare(state, '>= 1.85.0') + return GirTarget( girfile, state.subdir, @@ -1002,12 +1015,13 @@ class GnomeModule(ExtensionModule): install_dir=[install_dir], install_tag=['devel'], env=run_env, + rspable=rspable, ) @staticmethod def _make_typelib_target(state: 'ModuleState', typelib_output: str, typelib_cmd: T.Sequence[T.Union[str, Executable, ExternalProgram, CustomTarget]], - generated_files: T.Sequence[T.Union[str, mesonlib.File, CustomTarget, CustomTargetIndex, GeneratedList]], + generated_files: T.Sequence[T.Union[str, mesonlib.File, build.GeneratedTypes]], kwargs: T.Dict[str, T.Any]) -> TypelibTarget: install = kwargs['install_typelib'] if install is None: @@ -1037,7 +1051,7 @@ class GnomeModule(ExtensionModule): @staticmethod def _gather_typelib_includes_and_update_depends( state: 'ModuleState', - deps: T.Sequence[T.Union[Dependency, build.BuildTarget, CustomTarget, CustomTargetIndex]], + deps: T.Sequence[T.Union[Dependency, build.BuildTargetTypes]], depends: T.Sequence[T.Union[build.BuildTarget, 'build.GeneratedTypes', 'FileOrString', build.StructuredSources]] ) -> T.Tuple[T.List[str], T.List[T.Union[build.BuildTarget, 'build.GeneratedTypes', 'FileOrString', build.StructuredSources]]]: # Need to recursively add deps on GirTarget sources from our @@ -1168,13 +1182,13 @@ class GnomeModule(ExtensionModule): scan_cflags += list(self._get_scanner_cflags(self._get_external_args_for_langs(state, [lc[0] for lc in langs_compilers]))) scan_internal_ldflags = [] scan_external_ldflags = [] - scan_env_ldflags = [] + scan_env_ldflags = state.environment.coredata.get_external_link_args(MachineChoice.HOST, 'c') for cli_flags, env_flags in (self._get_scanner_ldflags(internal_ldflags), self._get_scanner_ldflags(dep_internal_ldflags)): scan_internal_ldflags += cli_flags - scan_env_ldflags = env_flags + scan_env_ldflags += env_flags for cli_flags, env_flags in (self._get_scanner_ldflags(external_ldflags), self._get_scanner_ldflags(dep_external_ldflags)): scan_external_ldflags += cli_flags - scan_env_ldflags = env_flags + scan_env_ldflags += env_flags girtargets_inc_dirs = self._get_gir_targets_inc_dirs(girtargets) inc_dirs = kwargs['include_directories'] @@ -1619,6 +1633,8 @@ class GnomeModule(ExtensionModule): ), KwargInfo('install_header', bool, default=False, since='0.46.0'), KwargInfo('docbook', (str, NoneType)), + KwargInfo('rst', (str, NoneType), since='1.9.0'), + KwargInfo('markdown', (str, NoneType), since='1.9.0'), KwargInfo( 'autocleanup', str, default='default', since='0.47.0', validator=in_set_validator({'all', 'none', 'objects'})), @@ -1675,6 +1691,26 @@ class GnomeModule(ExtensionModule): cmd += ['--generate-docbook', docbook] + if kwargs['rst'] is not None: + if not mesonlib.version_compare(glib_version, '>= 2.71.1'): + mlog.error(f'Glib version ({glib_version}) is too old to ' + 'support the \'rst\' kwarg, need 2.71.1 or ' + 'newer') + + rst = kwargs['rst'] + + cmd += ['--generate-rst', rst] + + if kwargs['markdown'] is not None: + if not mesonlib.version_compare(glib_version, '>= 2.75.2'): + mlog.error(f'Glib version ({glib_version}) is too old to ' + 'support the \'markdown\' kwarg, need 2.75.2 ' + 'or newer') + + markdown = kwargs['markdown'] + + cmd += ['--generate-md', markdown] + # https://git.gnome.org/browse/glib/commit/?id=ee09bb704fe9ccb24d92dd86696a0e6bb8f0dc1a if mesonlib.version_compare(glib_version, '>= 2.51.3'): cmd += ['--output-directory', '@OUTDIR@', '--generate-c-code', namebase, '@INPUT@'] @@ -1750,6 +1786,48 @@ class GnomeModule(ExtensionModule): ) targets.append(docbook_custom_target) + if kwargs['rst'] is not None: + rst = kwargs['rst'] + # The rst output is always ${rst}-${name_of_xml_file} + output = namebase + '-rst' + outputs = [] + for f in xml_files: + outputs.append('{}-{}'.format(rst, os.path.basename(str(f)))) + + rst_custom_target = CustomTarget( + output, + state.subdir, + state.subproject, + state.environment, + cmd + ['--output-directory', '@OUTDIR@', '--generate-rst', rst, '@INPUT@'], + xml_files, + outputs, + build_by_default=build_by_default, + description='Generating gdbus reStructuredText {}', + ) + targets.append(rst_custom_target) + + if kwargs['markdown'] is not None: + markdown = kwargs['markdown'] + # The markdown output is always ${markdown}-${name_of_xml_file} + output = namebase + '-markdown' + outputs = [] + for f in xml_files: + outputs.append('{}-{}'.format(markdown, os.path.basename(str(f)))) + + markdown_custom_target = CustomTarget( + output, + state.subdir, + state.subproject, + state.environment, + cmd + ['--output-directory', '@OUTDIR@', '--generate-md', markdown, '@INPUT@'], + xml_files, + outputs, + build_by_default=build_by_default, + description='Generating gdbus markdown {}', + ) + targets.append(markdown_custom_target) + return ModuleReturnValue(targets, targets) @typed_pos_args('gnome.mkenums', str) @@ -1963,13 +2041,13 @@ class GnomeModule(ExtensionModule): def _make_mkenum_impl( self, state: 'ModuleState', - sources: T.Sequence[T.Union[str, mesonlib.File, CustomTarget, CustomTargetIndex, GeneratedList]], + sources: T.Sequence[T.Union[str, mesonlib.File, build.GeneratedTypes]], output: str, cmd: T.List[str], *, install: bool = False, install_dir: T.Optional[T.Sequence[T.Union[str, bool]]] = None, - depends: T.Optional[T.Sequence[T.Union[CustomTarget, CustomTargetIndex, BuildTarget]]] = None + depends: T.Optional[T.Sequence[build.BuildTargetTypes]] = None ) -> build.CustomTarget: real_cmd: T.List[T.Union[str, 'ToolType']] = [self._find_tool(state, 'glib-mkenums')] real_cmd.extend(cmd) @@ -1991,6 +2069,7 @@ class GnomeModule(ExtensionModule): extra_depends=depends, # https://github.com/mesonbuild/meson/issues/973 absolute_paths=True, + rspable=mesonlib.is_windows() or mesonlib.is_cygwin(), description='Generating GObject enum file {}', ) @@ -2130,7 +2209,7 @@ class GnomeModule(ExtensionModule): with open(fname, 'w', encoding='utf-8') as ofile: for package in packages: ofile.write(package + '\n') - return build.Data([mesonlib.File(True, outdir, fname)], install_dir, install_dir, mesonlib.FileMode(), state.subproject) + return build.Data([mesonlib.File(True, outdir, fname)], install_dir, install_dir, mesonlib.FileMode(), state.subproject, install_tag='devel') def _get_vapi_link_with(self, target: CustomTarget) -> T.List[build.LibTypes]: link_with: T.List[build.LibTypes] = [] diff --git a/mesonbuild/modules/hotdoc.py b/mesonbuild/modules/hotdoc.py index 5099b41..a72bf73 100644 --- a/mesonbuild/modules/hotdoc.py +++ b/mesonbuild/modules/hotdoc.py @@ -14,7 +14,7 @@ from ..build import CustomTarget, CustomTargetIndex from ..dependencies import Dependency, InternalDependency from ..interpreterbase import ( InvalidArguments, noPosargs, noKwargs, typed_kwargs, FeatureDeprecated, - ContainerTypeInfo, KwargInfo, typed_pos_args + ContainerTypeInfo, KwargInfo, typed_pos_args, InterpreterObject ) from ..interpreter.interpreterobjects import _CustomTargetHolder from ..interpreter.type_checking import NoneType @@ -146,31 +146,6 @@ class HotdocTargetBuilder: self.check_extra_arg_type(arg, value) self.set_arg_value(option, value) - def get_value(self, types, argname, default=None, value_processor=None, - mandatory=False, force_list=False): - if not isinstance(types, list): - types = [types] - try: - uvalue = value = self.kwargs.pop(argname) - if value_processor: - value = value_processor(value) - - for t in types: - if isinstance(value, t): - if force_list and not isinstance(value, list): - return [value], uvalue - return value, uvalue - raise MesonException(f"{argname} field value {value} is not valid," - f" valid types are {types}") - except KeyError: - if mandatory: - raise MesonException(f"{argname} mandatory field not found") - - if default is not None: - return default, default - - return None, None - def add_extension_paths(self, paths: T.Union[T.List[str], T.Set[str]]) -> None: for path in paths: if path in self._extra_extension_paths: @@ -383,12 +358,9 @@ class HotdocTargetBuilder: class HotdocTargetHolder(_CustomTargetHolder['HotdocTarget']): - def __init__(self, target: HotdocTarget, interp: Interpreter): - super().__init__(target, interp) - self.methods.update({'config_path': self.config_path_method}) - @noPosargs @noKwargs + @InterpreterObject.method('config_path') def config_path_method(self, *args: T.Any, **kwargs: T.Any) -> str: conf = self.held_object.hotdoc_conf.absolute_path(self.interpreter.environment.source_dir, self.interpreter.environment.build_dir) diff --git a/mesonbuild/modules/i18n.py b/mesonbuild/modules/i18n.py index 87baab2..2d8d04d 100644 --- a/mesonbuild/modules/i18n.py +++ b/mesonbuild/modules/i18n.py @@ -56,16 +56,15 @@ if T.TYPE_CHECKING: class ItsJoinFile(TypedDict): input: T.List[T.Union[ - str, build.BuildTarget, build.CustomTarget, build.CustomTargetIndex, - build.ExtractedObjects, build.GeneratedList, ExternalProgram, - mesonlib.File]] + str, build.BuildTarget, build.GeneratedTypes, + build.ExtractedObjects, ExternalProgram, mesonlib.File]] output: str build_by_default: bool install: bool install_dir: T.Optional[str] install_tag: T.Optional[str] its_files: T.List[str] - mo_targets: T.List[T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex]] + mo_targets: T.List[build.BuildTargetTypes] class XgettextProgramT(TypedDict): @@ -75,7 +74,7 @@ if T.TYPE_CHECKING: install_dir: T.Optional[str] install_tag: T.Optional[str] - SourcesType = T.Union[str, mesonlib.File, build.BuildTarget, build.BothLibraries, build.CustomTarget] + SourcesType = T.Union[str, mesonlib.File, build.BuildTargetTypes, build.BothLibraries] _ARGS: KwargInfo[T.List[str]] = KwargInfo( @@ -202,6 +201,8 @@ class XgettextProgram: source_files.update(source.get_sources()) elif isinstance(source, build.BothLibraries): source_files.update(source.get('shared').get_sources()) + elif isinstance(source, (build.CustomTarget, build.CustomTargetIndex)): + source_files.update(mesonlib.File.from_built_file(source.get_subdir(), f) for f in source.get_outputs()) return source_files def _get_depends(self, sources: T.Iterable[SourcesType]) -> T.Set[build.CustomTarget]: @@ -237,7 +238,7 @@ class XgettextProgram: return mesonlib.File.from_built_file(self.interpreter.subdir, rsp_file.name) @staticmethod - def _get_source_id(sources: T.Iterable[T.Union[SourcesType, build.CustomTargetIndex]]) -> T.Iterable[str]: + def _get_source_id(sources: T.Iterable[SourcesType]) -> T.Iterable[str]: for source in sources: if isinstance(source, build.Target): yield source.get_id() @@ -307,8 +308,7 @@ class I18nModule(ExtensionModule): ddirs = self._get_data_dirs(state, kwargs['data_dirs']) datadirs = '--datadirs=' + ':'.join(ddirs) if ddirs else None - command: T.List[T.Union[str, build.BuildTarget, build.CustomTarget, - build.CustomTargetIndex, 'ExternalProgram', mesonlib.File]] = [] + command: T.List[T.Union[str, build.BuildTargetTypes, ExternalProgram, mesonlib.File]] = [] command.extend(state.environment.get_build_command()) command.extend([ '--internal', 'msgfmthelper', @@ -487,8 +487,7 @@ class I18nModule(ExtensionModule): for target in mo_targets: mo_fnames.append(path.join(target.get_subdir(), target.get_outputs()[0])) - command: T.List[T.Union[str, build.BuildTarget, build.CustomTarget, - build.CustomTargetIndex, 'ExternalProgram', mesonlib.File]] = [] + command: T.List[T.Union[str, build.BuildTargetTypes, ExternalProgram, mesonlib.File]] = [] command.extend(state.environment.get_build_command()) itstool_cmd = self.tools['itstool'].get_command() @@ -531,7 +530,7 @@ class I18nModule(ExtensionModule): return ModuleReturnValue(ct, [ct]) @FeatureNew('i18n.xgettext', '1.8.0') - @typed_pos_args('i18n.xgettext', str, varargs=(str, mesonlib.File, build.BuildTarget, build.BothLibraries, build.CustomTarget), min_varargs=1) + @typed_pos_args('i18n.xgettext', str, varargs=(str, mesonlib.File, build.BuildTarget, build.BothLibraries, build.CustomTarget, build.CustomTargetIndex), min_varargs=1) @typed_kwargs( 'i18n.xgettext', _ARGS, @@ -541,6 +540,11 @@ class I18nModule(ExtensionModule): INSTALL_TAG_KW, ) def xgettext(self, state: ModuleState, args: T.Tuple[str, T.List[SourcesType]], kwargs: XgettextProgramT) -> build.CustomTarget: + if any(isinstance(a, build.CustomTarget) for a in args[1]): + FeatureNew.single_use('i18n.xgettext with custom_target is broken until 1.10', '1.10.0', self.interpreter.subproject, location=self.interpreter.current_node) + if any(isinstance(a, build.CustomTargetIndex) for a in args[1]): + FeatureNew.single_use('i18n.xgettext with custom_target index', '1.10.0', self.interpreter.subproject, location=self.interpreter.current_node) + toolname = 'xgettext' if self.tools[toolname] is None or not self.tools[toolname].found(): self.tools[toolname] = state.find_program(toolname, required=True, for_machine=mesonlib.MachineChoice.BUILD) diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py index cc0450a..7d5bc91 100644 --- a/mesonbuild/modules/pkgconfig.py +++ b/mesonbuild/modules/pkgconfig.py @@ -38,6 +38,7 @@ if T.TYPE_CHECKING: filebase: T.Optional[str] description: T.Optional[str] url: str + license: str subdirs: T.List[str] conflicts: T.List[str] dataonly: bool @@ -149,16 +150,30 @@ class DependenciesHelper: self.add_version_reqs(obj.name, obj.version_reqs) elif isinstance(obj, str): name, version_req = self.split_version_req(obj) + if name is None: + continue processed_reqs.append(name) self.add_version_reqs(name, [version_req] if version_req is not None else None) elif isinstance(obj, dependencies.Dependency) and not obj.found(): pass elif isinstance(obj, dependencies.ExternalDependency) and obj.name == 'threads': pass + elif isinstance(obj, dependencies.InternalDependency) and all(lib.get_id() in self.metadata for lib in obj.libraries): + FeatureNew.single_use('pkgconfig.generate requirement from internal dependency', '1.9.0', + self.state.subproject, location=self.state.current_node) + # Ensure BothLibraries are resolved: + if self.pub_libs and isinstance(self.pub_libs[0], build.StaticLibrary): + obj = obj.get_as_static(recursive=True) + else: + obj = obj.get_as_shared(recursive=True) + for lib in obj.libraries: + processed_reqs.append(self.metadata[lib.get_id()].filebase) else: raise mesonlib.MesonException('requires argument not a string, ' - 'library with pkgconfig-generated file ' - f'or pkgconfig-dependency object, got {obj!r}') + 'library with pkgconfig-generated file, ' + 'pkgconfig-dependency object, or ' + 'internal-dependency object with ' + f'pkgconfig-generated file, got {obj!r}') return processed_reqs def add_cflags(self, cflags: T.List[str]) -> None: @@ -285,12 +300,22 @@ class DependenciesHelper: # foo, bar' is ok, but 'foo,bar' is not. self.version_reqs[name].update(version_reqs) - def split_version_req(self, s: str) -> T.Tuple[str, T.Optional[str]]: + def split_version_req(self, s: str) -> T.Tuple[T.Optional[str], T.Optional[str]]: + stripped_str = s.strip() + if not stripped_str: + mlog.warning('Required dependency was found to be an empty string. Did you mean to pass an empty array?') + return None, None for op in ['>=', '<=', '!=', '==', '=', '>', '<']: - pos = s.find(op) - if pos > 0: - return s[0:pos].strip(), s[pos:].strip() - return s, None + pos = stripped_str.find(op) + if pos < 0: + continue + if pos == 0: + raise mesonlib.MesonException(f'required versioned dependency "{s}" is missing the dependency\'s name.') + stripped_str, version = stripped_str[0:pos].strip(), stripped_str[pos:].strip() + if not stripped_str: + raise mesonlib.MesonException(f'required versioned dependency "{s}" is missing the dependency\'s name.') + return stripped_str, version + return stripped_str, None def format_vreq(self, vreq: str) -> str: # vreq are '>=1.0' and pkgconfig wants '>= 1.0' @@ -441,6 +466,7 @@ class PkgConfigModule(NewExtensionModule): def _generate_pkgconfig_file(self, state: ModuleState, deps: DependenciesHelper, subdirs: T.List[str], name: str, description: str, url: str, version: str, + license: str, pcfile: str, conflicts: T.List[str], variables: T.List[T.Tuple[str, str]], unescaped_variables: T.List[T.Tuple[str, str]], @@ -519,18 +545,20 @@ class PkgConfigModule(NewExtensionModule): ofile.write(f'{k}={v}\n') ofile.write('\n') ofile.write(f'Name: {name}\n') - if len(description) > 0: + if description: ofile.write(f'Description: {description}\n') - if len(url) > 0: + if url: ofile.write(f'URL: {url}\n') + if license: + ofile.write(f'License: {license}\n') ofile.write(f'Version: {version}\n') reqs_str = deps.format_reqs(deps.pub_reqs) - if len(reqs_str) > 0: + if reqs_str: ofile.write(f'Requires: {reqs_str}\n') reqs_str = deps.format_reqs(deps.priv_reqs) - if len(reqs_str) > 0: + if reqs_str: ofile.write(f'Requires.private: {reqs_str}\n') - if len(conflicts) > 0: + if conflicts: ofile.write('Conflicts: {}\n'.format(' '.join(conflicts))) def generate_libs_flags(libs: T.List[LIBS]) -> T.Iterable[str]: @@ -571,9 +599,9 @@ class PkgConfigModule(NewExtensionModule): if isinstance(l, (build.CustomTarget, build.CustomTargetIndex)) or 'cs' not in l.compilers: yield f'-l{lname}' - if len(deps.pub_libs) > 0: + if deps.pub_libs: ofile.write('Libs: {}\n'.format(' '.join(generate_libs_flags(deps.pub_libs)))) - if len(deps.priv_libs) > 0: + if deps.priv_libs: ofile.write('Libs.private: {}\n'.format(' '.join(generate_libs_flags(deps.priv_libs)))) cflags: T.List[str] = [] @@ -605,6 +633,7 @@ class PkgConfigModule(NewExtensionModule): KwargInfo('name', (str, NoneType), validator=lambda x: 'must not be an empty string' if x == '' else None), KwargInfo('subdirs', ContainerTypeInfo(list, str), default=[], listify=True), KwargInfo('url', str, default=''), + KwargInfo('license', str, default='', since='1.9.0'), KwargInfo('version', (str, NoneType)), VARIABLES_KW.evolve(name="unescaped_uninstalled_variables", since='0.59.0'), VARIABLES_KW.evolve(name="unescaped_variables", since='0.59.0'), @@ -659,6 +688,7 @@ class PkgConfigModule(NewExtensionModule): filebase = kwargs['filebase'] if kwargs['filebase'] is not None else name description = kwargs['description'] if kwargs['description'] is not None else default_description url = kwargs['url'] + license = kwargs['license'] conflicts = kwargs['conflicts'] # Prepend the main library to public libraries list. This is required @@ -713,7 +743,7 @@ class PkgConfigModule(NewExtensionModule): pkgroot_name = os.path.join('{libdir}', 'pkgconfig') relocatable = state.get_option('pkgconfig.relocatable') self._generate_pkgconfig_file(state, deps, subdirs, name, description, url, - version, pcfile, conflicts, variables, + version, license, pcfile, conflicts, variables, unescaped_variables, False, dataonly, pkgroot=pkgroot if relocatable else None) res = build.Data([mesonlib.File(True, state.environment.get_scratch_dir(), pcfile)], pkgroot, pkgroot_name, None, state.subproject, install_tag='devel') @@ -722,7 +752,7 @@ class PkgConfigModule(NewExtensionModule): pcfile = filebase + '-uninstalled.pc' self._generate_pkgconfig_file(state, deps, subdirs, name, description, url, - version, pcfile, conflicts, variables, + version, license, pcfile, conflicts, variables, unescaped_variables, uninstalled=True, dataonly=dataonly) # Associate the main library with this generated pc file. If the library # is used in any subsequent call to the generated, it will generate a diff --git a/mesonbuild/modules/python.py b/mesonbuild/modules/python.py index 2a7e685..99602c0 100644 --- a/mesonbuild/modules/python.py +++ b/mesonbuild/modules/python.py @@ -14,13 +14,13 @@ from ..build import known_shmod_kwargs, CustomTarget, CustomTargetIndex, BuildTa from ..dependencies import NotFoundDependency from ..dependencies.detect import get_dep_identifier, find_external_dependency from ..dependencies.python import BasicPythonExternalProgram, python_factory, _PythonDependencyBase -from ..interpreter import extract_required_kwarg, permitted_dependency_kwargs, primitives as P_OBJ +from ..interpreter import extract_required_kwarg, primitives as P_OBJ from ..interpreter.interpreterobjects import _ExternalProgramHolder -from ..interpreter.type_checking import NoneType, PRESERVE_PATH_KW, SHARED_MOD_KWS +from ..interpreter.type_checking import NoneType, DEPENDENCY_KWS, PRESERVE_PATH_KW, SHARED_MOD_KWS from ..interpreterbase import ( noPosargs, noKwargs, permittedKwargs, ContainerTypeInfo, InvalidArguments, typed_pos_args, typed_kwargs, KwargInfo, - FeatureNew, FeatureNewKwargs, disablerIfNotFound + FeatureNew, disablerIfNotFound, InterpreterObject ) from ..mesonlib import MachineChoice from ..options import OptionKey @@ -31,10 +31,10 @@ if T.TYPE_CHECKING: from . import ModuleState from ..build import Build, Data - from ..dependencies import Dependency + from ..dependencies.base import Dependency, DependencyObjectKWs from ..interpreter import Interpreter from ..interpreter.interpreter import BuildTargetSource - from ..interpreter.kwargs import ExtractRequired, SharedModule as SharedModuleKw + from ..interpreter.kwargs import ExtractRequired, SharedModule as SharedModuleKw, FuncDependency from ..interpreterbase.baseobjects import TYPE_var, TYPE_kwargs class PyInstallKw(TypedDict): @@ -115,34 +115,32 @@ class PythonInstallation(_ExternalProgramHolder['PythonExternalProgram']): info = python.info prefix = self.interpreter.environment.coredata.optstore.get_value_for(OptionKey('prefix')) assert isinstance(prefix, str), 'for mypy' + + if python.build_config: + self.version = python.build_config['language']['version'] + self.platform = python.build_config['platform'] + self.suffix = python.build_config['abi']['extension_suffix'] + self.limited_api_suffix = python.build_config['abi']['stable_abi_suffix'] + self.link_libpython = python.build_config['libpython']['link_extensions'] + self.is_pypy = python.build_config['implementation']['name'] == 'pypy' + else: + self.version = info['version'] + self.platform = info['platform'] + self.suffix = info['suffix'] + self.limited_api_suffix = info['limited_api_suffix'] + self.link_libpython = info['link_libpython'] + self.is_pypy = info['is_pypy'] + self.variables = info['variables'] - self.suffix = info['suffix'] - self.limited_api_suffix = info['limited_api_suffix'] self.paths = info['paths'] self.pure = python.pure self.platlib_install_path = os.path.join(prefix, python.platlib) self.purelib_install_path = os.path.join(prefix, python.purelib) - self.version = info['version'] - self.platform = info['platform'] - self.is_pypy = info['is_pypy'] - self.link_libpython = info['link_libpython'] - self.methods.update({ - 'extension_module': self.extension_module_method, - 'dependency': self.dependency_method, - 'install_sources': self.install_sources_method, - 'get_install_dir': self.get_install_dir_method, - 'language_version': self.language_version_method, - 'found': self.found_method, - 'has_path': self.has_path_method, - 'get_path': self.get_path_method, - 'has_variable': self.has_variable_method, - 'get_variable': self.get_variable_method, - 'path': self.path_method, - }) @permittedKwargs(mod_kwargs) @typed_pos_args('python.extension_module', str, varargs=(str, mesonlib.File, CustomTarget, CustomTargetIndex, GeneratedList, StructuredSources, ExtractedObjects, BuildTarget)) @typed_kwargs('python.extension_module', *_MOD_KWARGS, _DEFAULTABLE_SUBDIR_KW, _LIMITED_API_KW, allow_unknown=True) + @InterpreterObject.method('extension_module') def extension_module_method(self, args: T.Tuple[str, T.List[BuildTargetSource]], kwargs: ExtensionModuleKw) -> 'SharedModule': if 'install_dir' in kwargs: if kwargs['subdir'] is not None: @@ -208,7 +206,7 @@ class PythonInstallation(_ExternalProgramHolder['PythonExternalProgram']): new_link_args = mesonlib.extract_as_list(kwargs, 'link_args') - is_debug = self.interpreter.environment.coredata.optstore.get_value('debug') + is_debug = self.interpreter.environment.coredata.optstore.get_value_for('debug') if is_debug: new_link_args.append(python_windows_debug_link_exception) else: @@ -248,33 +246,43 @@ class PythonInstallation(_ExternalProgramHolder['PythonExternalProgram']): return '0x{:02x}{:02x}0000'.format(major, minor) - def _dependency_method_impl(self, kwargs: TYPE_kwargs) -> Dependency: - for_machine = self.interpreter.machine_from_native_kwarg(kwargs) + def _dependency_method_impl(self, kwargs: DependencyObjectKWs) -> Dependency: + for_machine = kwargs.get('native', MachineChoice.HOST) identifier = get_dep_identifier(self._full_path(), kwargs) dep = self.interpreter.coredata.deps[for_machine].get(identifier) if dep is not None: return dep + build_config = self.interpreter.environment.coredata.optstore.get_value_for(OptionKey('python.build_config')) + new_kwargs = kwargs.copy() new_kwargs['required'] = False + if build_config: + new_kwargs['build_config'] = build_config candidates = python_factory(self.interpreter.environment, for_machine, new_kwargs, self.held_object) dep = find_external_dependency('python', self.interpreter.environment, new_kwargs, candidates) self.interpreter.coredata.deps[for_machine].put(identifier, dep) return dep - @disablerIfNotFound - @permittedKwargs(permitted_dependency_kwargs | {'embed'}) - @FeatureNewKwargs('python_installation.dependency', '0.53.0', ['embed']) @noPosargs - def dependency_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> 'Dependency': + @typed_kwargs( + 'python_installation.dependency', + *DEPENDENCY_KWS, + KwargInfo('embed', bool, default=False, since='0.53.0'), + ) + @disablerIfNotFound + @InterpreterObject.method('dependency') + def dependency_method(self, args: T.List['TYPE_var'], kwargs: FuncDependency) -> 'Dependency': disabled, required, feature = extract_required_kwarg(kwargs, self.subproject) + nkwargs = T.cast('DependencyObjectKWs', kwargs.copy()) + nkwargs['required'] = required if disabled: mlog.log('Dependency', mlog.bold('python'), 'skipped: feature', mlog.bold(feature), 'disabled') return NotFoundDependency('python', self.interpreter.environment) else: - dep = self._dependency_method_impl(kwargs) + dep = self._dependency_method_impl(nkwargs) if required and not dep.found(): raise mesonlib.MesonException('Python dependency not found') return dep @@ -287,6 +295,7 @@ class PythonInstallation(_ExternalProgramHolder['PythonExternalProgram']): PRESERVE_PATH_KW, KwargInfo('install_tag', (str, NoneType), since='0.60.0') ) + @InterpreterObject.method('install_sources') def install_sources_method(self, args: T.Tuple[T.List[T.Union[str, mesonlib.File]]], kwargs: 'PyInstallKw') -> 'Data': self.held_object.run_bytecompile[self.version] = True @@ -301,6 +310,7 @@ class PythonInstallation(_ExternalProgramHolder['PythonExternalProgram']): @noPosargs @typed_kwargs('python_installation.install_dir', _PURE_KW, _SUBDIR_KW) + @InterpreterObject.method('get_install_dir') def get_install_dir_method(self, args: T.List['TYPE_var'], kwargs: 'PyInstallKw') -> str: self.held_object.run_bytecompile[self.version] = True pure = kwargs['pure'] if kwargs['pure'] is not None else self.pure @@ -318,16 +328,19 @@ class PythonInstallation(_ExternalProgramHolder['PythonExternalProgram']): @noPosargs @noKwargs + @InterpreterObject.method('language_version') def language_version_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> str: return self.version @typed_pos_args('python_installation.has_path', str) @noKwargs + @InterpreterObject.method('has_path') def has_path_method(self, args: T.Tuple[str], kwargs: 'TYPE_kwargs') -> bool: return args[0] in self.paths @typed_pos_args('python_installation.get_path', str, optargs=[object]) @noKwargs + @InterpreterObject.method('get_path') def get_path_method(self, args: T.Tuple[str, T.Optional['TYPE_var']], kwargs: 'TYPE_kwargs') -> 'TYPE_var': path_name, fallback = args try: @@ -339,11 +352,13 @@ class PythonInstallation(_ExternalProgramHolder['PythonExternalProgram']): @typed_pos_args('python_installation.has_variable', str) @noKwargs + @InterpreterObject.method('has_variable') def has_variable_method(self, args: T.Tuple[str], kwargs: 'TYPE_kwargs') -> bool: return args[0] in self.variables @typed_pos_args('python_installation.get_variable', str, optargs=[object]) @noKwargs + @InterpreterObject.method('get_variable') def get_variable_method(self, args: T.Tuple[str, T.Optional['TYPE_var']], kwargs: 'TYPE_kwargs') -> 'TYPE_var': var_name, fallback = args try: @@ -356,6 +371,7 @@ class PythonInstallation(_ExternalProgramHolder['PythonExternalProgram']): @noPosargs @noKwargs @FeatureNew('Python module path method', '0.50.0') + @InterpreterObject.method('path') def path_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> str: return super().path_method(args, kwargs) @@ -441,11 +457,13 @@ class PythonModule(ExtensionModule): return None def _find_installation_impl(self, state: 'ModuleState', display_name: str, name_or_path: str, required: bool) -> MaybePythonProg: + build_config = self.interpreter.environment.coredata.optstore.get_value_for(OptionKey('python.build_config')) + if not name_or_path: - python = PythonExternalProgram('python3', mesonlib.python_command) + python = PythonExternalProgram('python3', mesonlib.python_command, build_config_path=build_config) else: tmp_python = ExternalProgram.from_entry(display_name, name_or_path) - python = PythonExternalProgram(display_name, ext_prog=tmp_python) + python = PythonExternalProgram(display_name, ext_prog=tmp_python, build_config_path=build_config) if not python.found() and mesonlib.is_windows(): pythonpath = self._get_win_pythonpath(name_or_path) @@ -459,7 +477,7 @@ class PythonModule(ExtensionModule): # it if not python.found() and name_or_path in {'python2', 'python3'}: tmp_python = ExternalProgram.from_entry(display_name, 'python') - python = PythonExternalProgram(name_or_path, ext_prog=tmp_python) + python = PythonExternalProgram(name_or_path, ext_prog=tmp_python, build_config_path=build_config) if python.found(): if python.sanity(state): diff --git a/mesonbuild/modules/python3.py b/mesonbuild/modules/python3.py index 2e6779a..69ad1ee 100644 --- a/mesonbuild/modules/python3.py +++ b/mesonbuild/modules/python3.py @@ -50,7 +50,7 @@ class Python3Module(ExtensionModule): # On Windows the extension is pyd for some unexplainable reason. suffix = 'pyd' else: - suffix = [] + suffix = None kwargs['name_prefix'] = '' kwargs['name_suffix'] = suffix return self.interpreter.build_target(state.current_node, args, kwargs, SharedModule) diff --git a/mesonbuild/modules/rust.py b/mesonbuild/modules/rust.py index f43a0ed..a8fcc86 100644 --- a/mesonbuild/modules/rust.py +++ b/mesonbuild/modules/rust.py @@ -5,6 +5,7 @@ from __future__ import annotations import itertools import os import re +import textwrap import typing as T from mesonbuild.interpreterbase.decorators import FeatureNew @@ -20,12 +21,12 @@ from ..interpreter.type_checking import ( ) from ..interpreterbase import ContainerTypeInfo, InterpreterException, KwargInfo, typed_kwargs, typed_pos_args, noPosargs, permittedKwargs from ..interpreter.interpreterobjects import Doctest -from ..mesonlib import File, MesonException, PerMachine +from ..mesonlib import File, MachineChoice, MesonException, PerMachine from ..programs import ExternalProgram, NonExistingExternalProgram if T.TYPE_CHECKING: from . import ModuleState - from ..build import IncludeDirs, LibTypes + from ..build import BuildTargetTypes, ExecutableKeywordArguments, IncludeDirs, LibTypes from ..compilers.rust import RustCompiler from ..dependencies import Dependency, ExternalLibrary from ..interpreter import Interpreter @@ -44,7 +45,7 @@ if T.TYPE_CHECKING: dependencies: T.List[T.Union[Dependency, ExternalLibrary]] is_parallel: bool link_with: T.List[LibTypes] - link_whole: T.List[LibTypes] + link_whole: T.List[T.Union[StaticLibrary, CustomTarget, CustomTargetIndex]] rust_args: T.List[str] FuncTest = FuncRustTest[_kwargs.TestArgs] @@ -178,17 +179,16 @@ class RustModule(ExtensionModule): tkwargs['args'] = extra_args + ['--test', '--format', 'pretty'] tkwargs['protocol'] = 'rust' - new_target_kwargs = base_target.original_kwargs.copy() - # Don't mutate the shallow copied list, instead replace it with a new - # one + new_target_kwargs = T.cast('ExecutableKeywordArguments', base_target.original_kwargs.copy()) + del new_target_kwargs['rust_crate_type'] + for kw in ('pic', 'prelink', 'rust_abi', 'version', 'soversion', 'darwin_versions', 'shortname'): + if kw in new_target_kwargs: + del new_target_kwargs[kw] # type: ignore[misc] + new_target_kwargs['install'] = False new_target_kwargs['dependencies'] = new_target_kwargs.get('dependencies', []) + kwargs['dependencies'] - new_target_kwargs['link_with'] = new_target_kwargs.get('link_with', []) + kwargs['link_with'] + new_target_kwargs['link_with'] = new_target_kwargs.get('link_with', []) + T.cast('T.List[BuildTargetTypes]', kwargs['link_with']) new_target_kwargs['link_whole'] = new_target_kwargs.get('link_whole', []) + kwargs['link_whole'] - del new_target_kwargs['rust_crate_type'] - for kw in ['pic', 'prelink', 'rust_abi', 'version', 'soversion', 'darwin_versions']: - if kw in new_target_kwargs: - del new_target_kwargs[kw] lang_args = base_target.extra_args.copy() lang_args['rust'] = base_target.extra_args['rust'] + kwargs['rust_args'] + ['--test'] @@ -201,8 +201,7 @@ class RustModule(ExtensionModule): name, base_target.subdir, state.subproject, base_target.for_machine, sources, base_target.structured_sources, base_target.objects, base_target.environment, base_target.compilers, - new_target_kwargs - ) + new_target_kwargs) return new_target, tkwargs @typed_pos_args('rust.test', str, BuildTarget) @@ -242,6 +241,14 @@ class RustModule(ExtensionModule): def doctest(self, state: ModuleState, args: T.Tuple[str, T.Union[SharedLibrary, StaticLibrary]], kwargs: FuncDoctest) -> ModuleReturnValue: name, base_target = args + if not base_target.uses_rust(): + raise MesonException('doc tests are only supported for Rust targets') + if not base_target.uses_rust_abi(): + raise MesonException("doc tests are not supported for rust_abi: 'c'") + if state.environment.is_cross_build() and state.environment.need_exe_wrapper(base_target.for_machine): + mlog.notice('skipping Rust doctests due to cross compilation', once=True) + return ModuleReturnValue(None, []) + # Link the base target's crate into the tests kwargs['link_with'].append(base_target) kwargs['depends'].append(base_target) @@ -265,7 +272,7 @@ class RustModule(ExtensionModule): if self.rustdoc[base_target.for_machine] is None: rustc = T.cast('RustCompiler', base_target.compilers['rust']) - rustdoc = rustc.get_rustdoc(state.environment) + rustdoc = rustc.get_rustdoc() if rustdoc: self.rustdoc[base_target.for_machine] = ExternalProgram(rustdoc.get_exe()) else: @@ -329,6 +336,24 @@ class RustModule(ExtensionModule): # TODO: if we want this to be per-machine we'll need a native kwarg clang_args = state.environment.properties.host.get_bindgen_clang_args().copy() + # Find the first C'ish compiler to fetch the default compiler flags + # from. Append those to the bindgen flags to ensure we use a compatible + # environment. + comp = mesonlib.first( + [state.environment.coredata.compilers.host.get(l) for l in ['c', 'cpp', 'objc', 'objcpp']], + lambda x: x is not None, + ) + if comp: + clang_args.extend(comp.get_always_args()) + else: + mlog.warning(textwrap.dedent('''\ + Using `rust.bindgen` without configuring C (or a C-like) + language in Meson will skip compiler detection and can cause + ABI incompatibilities due to missing crucial compiler flags. + Consider calling `add_languages('c')` in your Meson build + files. + ''')) + for i in state.process_include_dirs(kwargs['include_directories']): # bindgen always uses clang, so it's safe to hardcode -I here clang_args.extend([f'-I{x}' for x in i.to_string_list( @@ -435,7 +460,7 @@ class RustModule(ExtensionModule): if self._bindgen_rust_target and '--rust-target' not in cmd: cmd.extend(['--rust-target', self._bindgen_rust_target]) if self._bindgen_set_std and '--rust-edition' not in cmd: - rust_std = state.environment.coredata.optstore.get_value('rust_std') + rust_std = state.environment.coredata.optstore.get_value_for('rust_std') assert isinstance(rust_std, str), 'for mypy' if rust_std != 'none': cmd.extend(['--rust-edition', rust_std]) @@ -469,8 +494,8 @@ class RustModule(ExtensionModule): @typed_pos_args('rust.proc_macro', str, varargs=SOURCES_VARARGS) @typed_kwargs('rust.proc_macro', *SHARED_LIB_KWS, allow_unknown=True) def proc_macro(self, state: ModuleState, args: T.Tuple[str, SourcesVarargsType], kwargs: _kwargs.SharedLibrary) -> SharedLibrary: - kwargs['native'] = True # type: ignore - kwargs['rust_crate_type'] = 'proc-macro' # type: ignore + kwargs['native'] = MachineChoice.BUILD + kwargs['rust_crate_type'] = 'proc-macro' kwargs['rust_args'] = kwargs['rust_args'] + ['--extern', 'proc_macro'] target = state._interpreter.build_target(state.current_node, args, kwargs, SharedLibrary) return target diff --git a/mesonbuild/modules/simd.py b/mesonbuild/modules/simd.py index bfdc0c2..2519d53 100644 --- a/mesonbuild/modules/simd.py +++ b/mesonbuild/modules/simd.py @@ -88,7 +88,7 @@ class SimdModule(ExtensionModule): mlog.log(f'Compiler supports {iset}:', mlog.red('NO')) continue - if not compiler.has_multi_arguments(compile_args, state.environment)[0]: + if not compiler.has_multi_arguments(compile_args)[0]: mlog.log(f'Compiler supports {iset}:', mlog.red('NO')) continue mlog.log(f'Compiler supports {iset}:', mlog.green('YES')) diff --git a/mesonbuild/modules/snippets.py b/mesonbuild/modules/snippets.py new file mode 100644 index 0000000..bfc8d7a --- /dev/null +++ b/mesonbuild/modules/snippets.py @@ -0,0 +1,104 @@ +# Copyright 2025 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations +import textwrap +import typing as T + +from pathlib import Path + +from . import NewExtensionModule, ModuleInfo +from ..interpreterbase import KwargInfo, typed_kwargs, typed_pos_args +from ..interpreter.type_checking import NoneType +from .. import mesonlib + +if T.TYPE_CHECKING: + from typing_extensions import TypedDict + from . import ModuleState + + class SymbolVisibilityHeaderKW(TypedDict): + namespace: T.Optional[str] + api: T.Optional[str] + compilation: T.Optional[str] + static_compilation: T.Optional[str] + static_only: bool + + +class SnippetsModule(NewExtensionModule): + + INFO = ModuleInfo('snippets', '1.10.0') + + def __init__(self) -> None: + super().__init__() + self.methods.update({ + 'symbol_visibility_header': self.symbol_visibility_header_method, + }) + + @typed_kwargs('snippets.symbol_visibility_header', + KwargInfo('namespace', (str, NoneType)), + KwargInfo('api', (str, NoneType)), + KwargInfo('compilation', (str, NoneType)), + KwargInfo('static_compilation', (str, NoneType)), + KwargInfo('static_only', (bool, NoneType))) + @typed_pos_args('snippets.symbol_visibility_header', str) + def symbol_visibility_header_method(self, state: ModuleState, args: T.Tuple[str], kwargs: 'SymbolVisibilityHeaderKW') -> mesonlib.File: + header_name = args[0] + namespace = kwargs['namespace'] or state.project_name + namespace = mesonlib.underscorify(namespace).upper() + if namespace[0].isdigit(): + namespace = f'_{namespace}' + api = kwargs['api'] or f'{namespace}_API' + compilation = kwargs['compilation'] or f'{namespace}_COMPILATION' + static_compilation = kwargs['static_compilation'] or f'{namespace}_STATIC_COMPILATION' + static_only = kwargs['static_only'] + if static_only is None: + default_library = state.get_option('default_library') + static_only = default_library == 'static' + content = textwrap.dedent('''\ + // SPDX-license-identifier: 0BSD OR CC0-1.0 OR WTFPL OR Apache-2.0 OR LGPL-2.0-or-later + #pragma once + ''') + if static_only: + content += textwrap.dedent(f''' + #ifndef {static_compilation} + # define {static_compilation} + #endif /* {static_compilation} */ + ''') + content += textwrap.dedent(f''' + #if (defined(_WIN32) || defined(__CYGWIN__)) && !defined({static_compilation}) + # define {api}_EXPORT __declspec(dllexport) + # define {api}_IMPORT __declspec(dllimport) + #elif defined(__OS2__) && !defined({static_compilation}) + # define {api}_EXPORT __declspec(dllexport) + # define {api}_IMPORT + #elif __GNUC__ >= 4 + # define {api}_EXPORT __attribute__((visibility("default"))) + # define {api}_IMPORT + #else + # define {api}_EXPORT + # define {api}_IMPORT + #endif + + #ifdef {compilation} + # define {api} {api}_EXPORT extern + #else + # define {api} {api}_IMPORT extern + #endif + ''') + header_path = Path(state.environment.get_build_dir(), state.subdir, header_name) + header_path.write_text(content, encoding='utf-8') + return mesonlib.File.from_built_file(state.subdir, header_name) + +def initialize(*args: T.Any, **kwargs: T.Any) -> SnippetsModule: + return SnippetsModule() diff --git a/mesonbuild/modules/windows.py b/mesonbuild/modules/windows.py index 29ae96b..a24c405 100644 --- a/mesonbuild/modules/windows.py +++ b/mesonbuild/modules/windows.py @@ -75,14 +75,20 @@ class WindowsModule(ExtensionModule): rescomp = ExternalProgram.from_bin_list(state.environment, for_machine, 'windres') if not rescomp or not rescomp.found(): + def search_programs(names: T.List[str]) -> T.Optional[ExternalProgram]: + for name in names: + program = ExternalProgram(name, silent=True) + if program.found(): + return program + return None + comp = self.detect_compiler(state.environment.coredata.compilers[for_machine]) if comp.id in {'msvc', 'clang-cl', 'intel-cl'} or (comp.linker and comp.linker.id in {'link', 'lld-link'}): - # Microsoft compilers uses rc irrespective of the frontend - rescomp = ExternalProgram('rc', silent=True) + rescomp = search_programs(['rc', 'llvm-rc']) else: - rescomp = ExternalProgram('windres', silent=True) + rescomp = search_programs(['windres', 'llvm-windres']) - if not rescomp.found(): + if not rescomp: raise MesonException('Could not find Windows resource compiler') for (arg, match, rc_type) in [ diff --git a/mesonbuild/mparser.py b/mesonbuild/mparser.py index f1c6071..b5792d2 100644 --- a/mesonbuild/mparser.py +++ b/mesonbuild/mparser.py @@ -119,36 +119,39 @@ class Lexer: ('id', IDENT_RE), ('number', re.compile(r'0[bB][01]+|0[oO][0-7]+|0[xX][0-9a-fA-F]+|0|[1-9]\d*')), ('eol_cont', re.compile(r'\\[ \t]*(#.*)?\n')), - ('eol', re.compile(r'\n')), ('multiline_string', re.compile(r"'''(.|\n)*?'''", re.M)), ('comment', re.compile(r'#.*')), - ('lparen', re.compile(r'\(')), - ('rparen', re.compile(r'\)')), - ('lbracket', re.compile(r'\[')), - ('rbracket', re.compile(r'\]')), - ('lcurl', re.compile(r'\{')), - ('rcurl', re.compile(r'\}')), - ('dblquote', re.compile(r'"')), ('string', re.compile(r"'([^'\\]|(\\.))*'")), - ('comma', re.compile(r',')), ('plusassign', re.compile(r'\+=')), - ('dot', re.compile(r'\.')), - ('plus', re.compile(r'\+')), - ('dash', re.compile(r'-')), - ('star', re.compile(r'\*')), - ('percent', re.compile(r'%')), - ('fslash', re.compile(r'/')), - ('colon', re.compile(r':')), ('equal', re.compile(r'==')), ('nequal', re.compile(r'!=')), - ('assign', re.compile(r'=')), ('le', re.compile(r'<=')), - ('lt', re.compile(r'<')), ('ge', re.compile(r'>=')), - ('gt', re.compile(r'>')), - ('questionmark', re.compile(r'\?')), ] + self.single_char_tokens = { + '\n': 'eol', + '(': 'lparen', + ')': 'rparen', + '[': 'lbracket', + ']': 'rbracket', + '{': 'lcurl', + '}': 'rcurl', + '"': 'dblquote', + ',': 'comma', + '.': 'dot', + '+': 'plus', + '-': 'dash', + '*': 'star', + '%': 'percent', + '/': 'fslash', + ':': 'colon', + '=': 'assign', + '<': 'lt', + '>': 'gt', + '?': 'questionmark', + } + def getline(self, line_start: int) -> str: return self.code[line_start:self.code.find('\n', line_start)] @@ -159,22 +162,25 @@ class Lexer: par_count = 0 bracket_count = 0 curl_count = 0 - col = 0 - while loc < len(self.code): - matched = False - value: str = '' - for (tid, reg) in self.token_specification: - mo = reg.match(self.code, loc) - if mo: - curline = lineno - curline_start = line_start - col = mo.start() - line_start - matched = True - span_start = loc - loc = mo.end() - span_end = loc - bytespan = (span_start, span_end) - value = mo.group() + try: + while loc < len(self.code): + value: str + span_start = loc + col = loc - line_start + curline = lineno + curline_start = line_start + for (tid, reg) in self.token_specification: + mo = reg.match(self.code, loc) + if mo: + value = mo.group() + loc = mo.end() + break + else: + # lex single characters and raise an exception for invalid tokens + value = self.code[loc] + tid = self.single_char_tokens[value] + loc += 1 + if tid == 'lparen': par_count += 1 elif tid == 'rparen': @@ -189,39 +195,41 @@ class Lexer: curl_count -= 1 elif tid == 'dblquote': raise ParseException('Double quotes are not supported. Use single quotes.', self.getline(line_start), lineno, col) - elif tid in {'string', 'fstring'}: - if value.find("\n") != -1: - msg = ("Newline character in a string detected, use ''' (three single quotes) " - "for multiline strings instead.\n" - "This will become a hard error in a future Meson release.") - mlog.warning(mlog.code_line(msg, self.getline(line_start), col), location=BaseNode(lineno, col, filename)) - value = value[2 if tid == 'fstring' else 1:-1] - elif tid in {'multiline_string', 'multiline_fstring'}: - value = value[4 if tid == 'multiline_fstring' else 3:-3] - lines = value.split('\n') - if len(lines) > 1: - lineno += len(lines) - 1 - line_start = mo.end() - len(lines[-1]) - elif tid == 'eol_cont': - lineno += 1 - line_start = loc - tid = 'whitespace' elif tid == 'eol': lineno += 1 line_start = loc if par_count > 0 or bracket_count > 0 or curl_count > 0: tid = 'whitespace' - elif tid == 'id': - if value in self.keywords: - tid = value - else: - if value in self.future_keywords: - mlog.warning(f"Identifier '{value}' will become a reserved keyword in a future release. Please rename it.", - location=BaseNode(lineno, col, filename)) - yield Token(tid, filename, curline_start, curline, col, bytespan, value) - break - if not matched: - raise ParseException('lexer', self.getline(line_start), lineno, col) + + if tid == 'id': + if value in self.keywords: + tid = value + else: + if value in self.future_keywords: + mlog.warning(f"Identifier '{value}' will become a reserved keyword in a future release. Please rename it.", + location=BaseNode(lineno, col, filename)) + elif tid in {'string', 'fstring'}: + if value.find("\n") != -1: + msg = ("Newline character in a string detected, use ''' (three single quotes) " + "for multiline strings instead.\n" + "This will become a hard error in a future Meson release.") + mlog.warning(mlog.code_line(msg, self.getline(line_start), col), location=BaseNode(lineno, col, filename)) + value = value[2 if tid == 'fstring' else 1:-1] + elif tid in {'multiline_string', 'multiline_fstring'}: + value = value[4 if tid == 'multiline_fstring' else 3:-3] + lines = value.split('\n') + if len(lines) > 1: + lineno += len(lines) - 1 + line_start = loc - len(lines[-1]) - 3 + elif tid == 'eol_cont': + lineno += 1 + line_start = loc + tid = 'whitespace' + bytespan = (span_start, loc) + yield Token(tid, filename, curline_start, curline, col, bytespan, value) + + except KeyError: + raise ParseException(f'lexer: unrecognized token {self.code[loc]!r}', self.getline(line_start), lineno, loc - line_start) @dataclass class BaseNode: @@ -369,6 +377,13 @@ class ArgumentNode(BaseNode): mlog.warning('This will be an error in Meson 2.0.') self.kwargs[name] = value + def get_kwarg_or_default(self, name: str, default: BaseNode) -> BaseNode: + for k, v in self.kwargs.items(): + assert isinstance(k, IdNode) + if k.value == name: + return v + return default + def set_kwarg_no_check(self, name: BaseNode, value: BaseNode) -> None: self.kwargs[name] = value @@ -444,10 +459,9 @@ class ComparisonNode(BinaryOperatorNode): @dataclass(unsafe_hash=True) class ArithmeticNode(BinaryOperatorNode): - # TODO: use a Literal for operation - operation: str + operation: ARITH_OPERATORS - def __init__(self, operation: str, left: BaseNode, operator: SymbolNode, right: BaseNode): + def __init__(self, operation: ARITH_OPERATORS, left: BaseNode, operator: SymbolNode, right: BaseNode): super().__init__(left, operator, right) self.operation = operation @@ -649,18 +663,22 @@ class ParenthesizedNode(BaseNode): lpar: SymbolNode = field(hash=False) inner: BaseNode rpar: SymbolNode = field(hash=False) + is_multiline: bool def __init__(self, lpar: SymbolNode, inner: BaseNode, rpar: SymbolNode): super().__init__(lpar.lineno, lpar.colno, inner.filename, end_lineno=rpar.lineno, end_colno=rpar.colno+1) self.lpar = lpar self.inner = inner self.rpar = rpar - + self.is_multiline = False if T.TYPE_CHECKING: - COMPARISONS = Literal['==', '!=', '<', '<=', '>=', '>', 'in', 'notin'] + COMPARISONS = Literal['==', '!=', '<', '<=', '>=', '>', 'in', 'not in'] + ARITH_OPERATORS = Literal['+', '-', '*', '/', '%'] -comparison_map: T.Mapping[str, COMPARISONS] = { +ALL_STRINGS = frozenset({'string', 'fstring', 'multiline_string', 'multiline_fstring'}) + +COMPARISON_MAP: T.Mapping[str, COMPARISONS] = { 'equal': '==', 'nequal': '!=', 'lt': '<', @@ -668,7 +686,18 @@ comparison_map: T.Mapping[str, COMPARISONS] = { 'gt': '>', 'ge': '>=', 'in': 'in', - 'not in': 'notin', + 'not in': 'not in', +} + +ADDSUB_MAP: T.Mapping[str, ARITH_OPERATORS] = { + 'plus': '+', + 'dash': '-', +} + +MULDIV_MAP: T.Mapping[str, ARITH_OPERATORS] = { + 'percent': '%', + 'star': '*', + 'fslash': '/', } # Recursive descent parser for Meson's definition language. @@ -676,15 +705,16 @@ comparison_map: T.Mapping[str, COMPARISONS] = { # levels so there are not enough words to describe them all. # Enter numbering: # -# 1 assignment -# 2 or -# 3 and -# 4 comparison -# 5 arithmetic -# 6 negation -# 7 funcall, method call -# 8 parentheses -# 9 plain token +# 1 assignment +# 2 or +# 3 and +# 4 comparison +# 5 addition and subtraction +# 6 multiplication, division and modulus +# 7 negation +# 8 funcall, method call +# 9 parentheses +# 10 plain token class Parser: def __init__(self, code: str, filename: str): @@ -727,7 +757,7 @@ class Parser: return True return False - def accept_any(self, tids: T.Tuple[str, ...]) -> str: + def accept_any(self, tids: T.Union[T.AbstractSet[str], T.Mapping[str, object]]) -> str: tid = self.current.tid if tid in tids: self.getsym() @@ -810,10 +840,10 @@ class Parser: def e4(self) -> BaseNode: left = self.e5() - for nodename, operator_type in comparison_map.items(): - if self.accept(nodename): - operator = self.create_node(SymbolNode, self.previous) - return self.create_node(ComparisonNode, operator_type, left, operator, self.e5()) + op = self.accept_any(COMPARISON_MAP) + if op: + operator = self.create_node(SymbolNode, self.previous) + return self.create_node(ComparisonNode, COMPARISON_MAP[op], left, operator, self.e5()) if self.accept('not'): ws = self.current_ws.copy() not_token = self.previous @@ -827,54 +857,42 @@ class Parser: not_token.bytespan = (not_token.bytespan[0], in_token.bytespan[1]) not_token.value += temp_node.whitespaces.value + in_token.value operator = self.create_node(SymbolNode, not_token) - return self.create_node(ComparisonNode, 'notin', left, operator, self.e5()) + return self.create_node(ComparisonNode, 'not in', left, operator, self.e5()) return left def e5(self) -> BaseNode: - return self.e5addsub() - - def e5addsub(self) -> BaseNode: - op_map = { - 'plus': 'add', - 'dash': 'sub', - } - left = self.e5muldiv() + left = self.e6() while True: - op = self.accept_any(tuple(op_map.keys())) + op = self.accept_any(ADDSUB_MAP) if op: operator = self.create_node(SymbolNode, self.previous) - left = self.create_node(ArithmeticNode, op_map[op], left, operator, self.e5muldiv()) + left = self.create_node(ArithmeticNode, ADDSUB_MAP[op], left, operator, self.e6()) else: break return left - def e5muldiv(self) -> BaseNode: - op_map = { - 'percent': 'mod', - 'star': 'mul', - 'fslash': 'div', - } - left = self.e6() + def e6(self) -> BaseNode: + left = self.e7() while True: - op = self.accept_any(tuple(op_map.keys())) + op = self.accept_any(MULDIV_MAP) if op: operator = self.create_node(SymbolNode, self.previous) - left = self.create_node(ArithmeticNode, op_map[op], left, operator, self.e6()) + left = self.create_node(ArithmeticNode, MULDIV_MAP[op], left, operator, self.e7()) else: break return left - def e6(self) -> BaseNode: + def e7(self) -> BaseNode: if self.accept('not'): operator = self.create_node(SymbolNode, self.previous) - return self.create_node(NotNode, self.current, operator, self.e7()) + return self.create_node(NotNode, self.current, operator, self.e8()) if self.accept('dash'): operator = self.create_node(SymbolNode, self.previous) - return self.create_node(UMinusNode, self.current, operator, self.e7()) - return self.e7() + return self.create_node(UMinusNode, self.current, operator, self.e8()) + return self.e8() - def e7(self) -> BaseNode: - left = self.e8() + def e8(self) -> BaseNode: + left = self.e9() block_start = self.current if self.accept('lparen'): lpar = self.create_node(SymbolNode, block_start) @@ -897,7 +915,7 @@ class Parser: left = self.index_call(left) return left - def e8(self) -> BaseNode: + def e9(self) -> BaseNode: block_start = self.current if self.accept('lparen'): lpar = self.create_node(SymbolNode, block_start) @@ -918,9 +936,9 @@ class Parser: rcurl = self.create_node(SymbolNode, self.previous) return self.create_node(DictNode, lcurl, key_values, rcurl) else: - return self.e9() + return self.e10() - def e9(self) -> BaseNode: + def e10(self) -> BaseNode: t = self.current if self.accept('true'): t.value = True @@ -932,7 +950,7 @@ class Parser: return self.create_node(IdNode, t) if self.accept('number'): return self.create_node(NumberNode, t) - if self.accept_any(('string', 'fstring', 'multiline_string', 'multiline_fstring')): + if self.accept_any(ALL_STRINGS): return self.create_node(StringNode, t) return EmptyNode(self.current.lineno, self.current.colno, self.current.filename) @@ -978,7 +996,7 @@ class Parser: def method_call(self, source_object: BaseNode) -> MethodNode: dot = self.create_node(SymbolNode, self.previous) - methodname = self.e9() + methodname = self.e10() if not isinstance(methodname, IdNode): if isinstance(source_object, NumberNode) and isinstance(methodname, NumberNode): raise ParseException('meson does not support float numbers', diff --git a/mesonbuild/msetup.py b/mesonbuild/msetup.py index 81dd183..df06245 100644 --- a/mesonbuild/msetup.py +++ b/mesonbuild/msetup.py @@ -9,13 +9,16 @@ import cProfile as profile from pathlib import Path import typing as T -from . import build, coredata, environment, interpreter, mesonlib, mintro, mlog +from . import build, cmdline, coredata, environment, interpreter, mesonlib, mintro, mlog +from .dependencies import Dependency from .mesonlib import MesonException -from .options import COMPILER_BASE_OPTIONS, OptionKey +from .interpreterbase import ObjectHolder +from .options import OptionKey if T.TYPE_CHECKING: from typing_extensions import Protocol - from .coredata import SharedCMDOptions + from .cmdline import SharedCMDOptions + from .interpreter import SubprojectHolder class CMDOptions(SharedCMDOptions, Protocol): @@ -27,7 +30,6 @@ if T.TYPE_CHECKING: builddir: str sourcedir: str pager: bool - unset_opts: T.List[str] git_ignore_file = '''# This file is autogenerated by Meson. If you change or delete it, it won't be recreated. * @@ -42,7 +44,7 @@ syntax: glob # Note: when adding arguments, please also add them to the completion # scripts in $MESONSRC/data/shell-completions/ def add_arguments(parser: argparse.ArgumentParser) -> None: - coredata.register_builtin_arguments(parser) + cmdline.register_builtin_arguments(parser) parser.add_argument('--native-file', default=[], action='append', @@ -80,7 +82,7 @@ class MesonApp: # configuration fails we need to be able to wipe again. restore = [] with tempfile.TemporaryDirectory() as d: - for filename in [coredata.get_cmd_line_file(self.build_dir)] + glob.glob(os.path.join(self.build_dir, environment.Environment.private_dir, '*.ini')): + for filename in [cmdline.get_cmd_line_file(self.build_dir)] + glob.glob(os.path.join(self.build_dir, environment.Environment.private_dir, '*.ini')): try: restore.append((shutil.copy(filename, d), filename)) except FileNotFoundError: @@ -88,7 +90,7 @@ class MesonApp: # a partial build or is empty. pass - coredata.read_cmd_line_file(self.build_dir, options) + cmdline.read_cmd_line_file(self.build_dir, options) try: # Don't delete the whole tree, just all of the files and @@ -180,45 +182,48 @@ class MesonApp: # See class Backend's 'generate' for comments on capture args and returned dictionary. def generate(self, capture: bool = False, vslite_ctx: T.Optional[dict] = None) -> T.Optional[dict]: env = environment.Environment(self.source_dir, self.build_dir, self.options) + if not env.first_invocation: + assert self.options.reconfigure + env.coredata.set_from_configure_command(self.options) mlog.initialize(env.get_log_dir(), self.options.fatal_warnings) if self.options.profile: mlog.set_timestamp_start(time.monotonic()) if self.options.clearcache: env.coredata.clear_cache() - with mesonlib.BuildDirLock(self.build_dir): + with mesonlib.DirectoryLock(self.build_dir, 'meson-private/meson.lock', + mesonlib.DirectoryLockAction.FAIL, + 'Some other Meson process is already using this build directory. Exiting.'): return self._generate(env, capture, vslite_ctx) - def check_unused_options(self, coredata: 'coredata.CoreData', cmd_line_options: T.Any, all_subprojects: T.Any) -> None: - pending = coredata.optstore.pending_options + def check_unused_options(self, coredata: 'coredata.CoreData', cmd_line_options: T.Dict[OptionKey, str], all_subprojects: T.Mapping[str, SubprojectHolder]) -> None: errlist: T.List[str] = [] - for opt in pending: - # It is not an error to set wrong option for unknown subprojects or - # language because we don't have control on which one will be selected. - if opt.subproject and opt.subproject not in all_subprojects: + known_subprojects = [name for name, obj in all_subprojects.items() if obj.found()] + for opt in cmd_line_options: + # Accept options that exist or could appear in subsequent reconfigurations, + # including options for subprojects that were not used + if opt in coredata.optstore or \ + opt.evolve(subproject=None) in coredata.optstore or \ + coredata.optstore.accept_as_pending_option(opt): continue - if coredata.optstore.is_compiler_option(opt): + if opt.subproject and opt.subproject not in known_subprojects: continue - if (coredata.optstore.is_base_option(opt) and - opt.evolve(subproject=None, machine=mesonlib.MachineChoice.HOST) in COMPILER_BASE_OPTIONS): + # "foo=true" may also refer to toplevel project option ":foo" + if opt.subproject is None and coredata.optstore.is_project_option(opt.as_root()): continue - keystr = str(opt) - if keystr in cmd_line_options: - errlist.append(f'"{keystr}"') + errlist.append(f'"{opt}"') if errlist: errstr = ', '.join(errlist) raise MesonException(f'Unknown options: {errstr}') - coredata.optstore.clear_pending() - def _generate(self, env: environment.Environment, capture: bool, vslite_ctx: T.Optional[dict]) -> T.Optional[dict]: # Get all user defined options, including options that have been defined # during a previous invocation or using meson configure. user_defined_options = T.cast('CMDOptions', argparse.Namespace(**vars(self.options))) - coredata.read_cmd_line_file(self.build_dir, user_defined_options) + cmdline.read_cmd_line_file(self.build_dir, user_defined_options) mlog.debug('Build started at', datetime.datetime.now().isoformat()) mlog.debug('Main binary:', sys.executable) - mlog.debug('Build Options:', coredata.format_cmd_line_options(user_defined_options)) + mlog.debug('Build Options:', cmdline.format_cmd_line_options(user_defined_options)) mlog.debug('Python system:', platform.system()) mlog.log(mlog.bold('The Meson build system')) mlog.log('Version:', coredata.version) @@ -284,9 +289,9 @@ class MesonApp: # read from a pipe and wrote into a private file. self.options.cross_file = env.coredata.cross_files self.options.native_file = env.coredata.config_files - coredata.write_cmd_line_file(self.build_dir, self.options) + cmdline.write_cmd_line_file(self.build_dir, self.options) else: - coredata.update_cmd_line_file(self.build_dir, self.options) + cmdline.update_cmd_line_file(self.build_dir, self.options) # Generate an IDE introspection file with the same syntax as the already existing API if self.options.profile: @@ -334,9 +339,16 @@ class MesonApp: return captured_compile_args def finalize_postconf_hooks(self, b: build.Build, intr: interpreter.Interpreter) -> None: + for varname, holder in intr.variables.items(): + if isinstance(holder, ObjectHolder): + d = holder.held_object + if isinstance(d, Dependency) and d.found(): + d.meson_variables.append(varname) + b.devenv.append(intr.backend.get_devenv()) for mod in intr.modules.values(): mod.postconf_hook(b) + b.def_files = intr.get_build_def_files() def run_genvslite_setup(options: CMDOptions) -> None: # With --genvslite, we essentially want to invoke multiple 'setup' iterations. I.e. - @@ -347,17 +359,18 @@ def run_genvslite_setup(options: CMDOptions) -> None: # invoke the appropriate 'meson compile ...' build commands upon the normal visual studio build/rebuild/clean actions, instead of using # the native VS/msbuild system. builddir_prefix = options.builddir - genvsliteval = options.cmd_line_options.pop('genvslite') # type: ignore [call-overload] + k_genvslite = OptionKey('genvslite') + genvsliteval = options.cmd_line_options.pop(k_genvslite) # The command line may specify a '--backend' option, which doesn't make sense in conjunction with # '--genvslite', where we always want to use a ninja back end - - k_backend = 'backend' - if k_backend in options.cmd_line_options.keys(): - if options.cmd_line_options[k_backend] != 'ninja': # type: ignore [index] + k_backend = OptionKey('backend') + if k_backend in options.cmd_line_options: + if options.cmd_line_options[k_backend] != 'ninja': raise MesonException('Explicitly specifying a backend option with \'genvslite\' is not necessary ' '(the ninja backend is always used) but specifying a non-ninja backend ' 'conflicts with a \'genvslite\' setup') else: - options.cmd_line_options[k_backend] = 'ninja' # type: ignore [index] + options.cmd_line_options[k_backend] = 'ninja' buildtypes_list = coredata.get_genvs_default_buildtype_list() vslite_ctx = {} @@ -368,7 +381,7 @@ def run_genvslite_setup(options: CMDOptions) -> None: vslite_ctx[buildtypestr] = app.generate(capture=True) #Now for generating the 'lite' solution and project files, which will use these builds we've just set up, above. options.builddir = f'{builddir_prefix}_vs' - options.cmd_line_options[OptionKey('genvslite')] = genvsliteval + options.cmd_line_options[k_genvslite] = genvsliteval app = MesonApp(options) app.generate(capture=False, vslite_ctx=vslite_ctx) @@ -377,14 +390,14 @@ def run(options: T.Union[CMDOptions, T.List[str]]) -> int: parser = argparse.ArgumentParser() add_arguments(parser) options = T.cast('CMDOptions', parser.parse_args(options)) - coredata.parse_cmd_line_options(options) + cmdline.parse_cmd_line_options(options) # Msetup doesn't actually use this option, but we pass msetup options to # mconf, and it does. We won't actually hit the path that uses it, but don't # lie options.pager = False - if 'genvslite' in options.cmd_line_options.keys(): + if OptionKey('genvslite') in options.cmd_line_options: run_genvslite_setup(options) else: app = MesonApp(options) diff --git a/mesonbuild/msubprojects.py b/mesonbuild/msubprojects.py index c74283c..f4b4405 100755 --- a/mesonbuild/msubprojects.py +++ b/mesonbuild/msubprojects.py @@ -1,9 +1,10 @@ from __future__ import annotations from dataclasses import dataclass, InitVar -import os, subprocess +import sys, os, subprocess import argparse import asyncio +import fnmatch import threading import copy import shutil @@ -60,6 +61,9 @@ if T.TYPE_CHECKING: ALL_TYPES_STRING = ', '.join(ALL_TYPES) +if sys.version_info >= (3, 14): + tarfile.TarFile.extraction_filter = staticmethod(tarfile.fully_trusted_filter) + def read_archive_files(path: Path, base_path: Path) -> T.Set[Path]: if path.suffix == '.zip': with zipfile.ZipFile(path, 'r') as zip_archive: @@ -640,9 +644,14 @@ def add_common_arguments(p: argparse.ArgumentParser) -> None: p.add_argument('--allow-insecure', default=False, action='store_true', help='Allow insecure server connections.') -def add_subprojects_argument(p: argparse.ArgumentParser) -> None: - p.add_argument('subprojects', nargs='*', - help='List of subprojects (default: all)') +def add_subprojects_argument(p: argparse.ArgumentParser, name: str = None) -> None: + helpstr = 'Patterns of subprojects to operate on (default: all)' + if name: + p.add_argument(name, dest='subprojects', metavar='pattern', nargs=1, action='append', + default=[], help=helpstr) + else: + p.add_argument('subprojects', metavar='pattern', nargs='*', default=[], + help=helpstr) def add_wrap_update_parser(subparsers: 'SubParsers') -> argparse.ArgumentParser: p = subparsers.add_parser('update', help='Update wrap files from WrapDB (Since 0.63.0)') @@ -692,7 +701,7 @@ def add_arguments(parser: argparse.ArgumentParser) -> None: p.add_argument('args', nargs=argparse.REMAINDER, help=argparse.SUPPRESS) add_common_arguments(p) - p.set_defaults(subprojects=[]) + add_subprojects_argument(p, '--filter') p.set_defaults(subprojects_func=Runner.foreach) p = subparsers.add_parser('purge', help='Remove all wrap-based subproject artifacts') @@ -724,7 +733,8 @@ def run(options: 'Arguments') -> int: return 0 r = Resolver(source_dir, subproject_dir, wrap_frontend=True, allow_insecure=options.allow_insecure, silent=True) if options.subprojects: - wraps = [wrap for name, wrap in r.wraps.items() if name in options.subprojects] + wraps = [wrap for name, wrap in r.wraps.items() + if any(fnmatch.fnmatch(name, pat) for pat in options.subprojects)] else: wraps = list(r.wraps.values()) types = [t.strip() for t in options.types.split(',')] if options.types else [] diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py index 4a907d8..95ca11d 100644 --- a/mesonbuild/mtest.py +++ b/mesonbuild/mtest.py @@ -29,7 +29,7 @@ import unicodedata import xml.etree.ElementTree as et from . import build -from . import environment +from . import tooldetect from . import mlog from .coredata import MesonVersionMismatchException, major_versions_differ from .coredata import version as coredata_version @@ -221,11 +221,14 @@ def returncode_to_status(retcode: int) -> str: return f'exit status {retcode}' signum = retcode - 128 - try: - signame = signal.Signals(signum).name - except ValueError: - signame = 'SIGinvalid' - return f'(exit status {retcode} or signal {signum} {signame})' + if signum < 32: + try: + signame = signal.Signals(signum).name + except ValueError: + signame = 'SIGinvalid' + return f'(exit status {retcode} or signal {signum} {signame})' + + return f'(exit status {retcode} or {hex(retcode)})' # TODO for Windows sh_quote: T.Callable[[str], str] = lambda x: x @@ -790,6 +793,7 @@ class JsonLogfileBuilder(TestFileLogger): 'name': result.name, 'stdout': result.stdo, 'result': result.res.value, + 'is_fail': result.res.is_bad(), 'starttime': result.starttime, 'duration': result.duration, 'returncode': result.returncode, @@ -1699,7 +1703,7 @@ class TestHarness: if self.options.no_rebuild: return - self.ninja = environment.detect_ninja() + self.ninja = tooldetect.detect_ninja() if not self.ninja: print("Can't find ninja, can't rebuild test.") # If ninja can't be found return exit code 127, indicating command @@ -1887,7 +1891,12 @@ class TestHarness: raise RuntimeError('Test harness object can only be used once.') self.is_run = True tests = self.get_tests() - rebuild_only_tests = tests if self.options.args else [] + # NOTE: If all tests are selected anyway, we pass + # an empty list to `rebuild_deps`, which then will execute + # the "meson-test-prereq" ninja target as a fallback. + # This prevents situations, where ARG_MAX may overflow + # if there are many targets. + rebuild_only_tests = tests if tests != self.tests else [] if not tests: return 0 if not self.options.no_rebuild and not rebuild_deps(self.ninja, self.options.wd, rebuild_only_tests, self.options.benchmark): @@ -1918,7 +1927,7 @@ class TestHarness: self.run_tests(runners) finally: os.chdir(startdir) - return self.total_failure_count() + return 1 if self.total_failure_count() > 0 else 0 @staticmethod def split_suite_string(suite: str) -> T.Tuple[str, str]: @@ -1936,29 +1945,22 @@ class TestHarness: for prjst in test.suite: (prj, st) = TestHarness.split_suite_string(prjst) - # the SUITE can be passed as - # suite_name - # or - # project_name:suite_name - # so we need to select only the test belonging to project_name - - # this if handle the first case (i.e., SUITE == suite_name) - - # in this way we can run tests belonging to different - # (sub)projects which share the same suite_name - if not st_match and st == prj_match: - return True - - # these two conditions are needed to handle the second option - # i.e., SUITE == project_name:suite_name - - # in this way we select the only the tests of - # project_name with suite_name - if prj_match and prj != prj_match: - continue - if st_match and st != st_match: - continue - return True + # The SUITE can be passed as + # - `name` - We select tests belonging to (sub)project OR suite + # with the given name. + # - `:suite_name` - We select tests belonging to any (sub)projects + # and in suite_name. + # - `project_name:suite_name` - We select tests belonging + # to project_name and in suite_name. + if not st_match: + if prj_match in {prj, st}: + return True + elif not prj_match: + if st == st_match: + return True + else: + if prj == prj_match and st == st_match: + return True return False def test_suitable(self, test: TestSerialisation) -> bool: @@ -2077,21 +2079,25 @@ class TestHarness: return wrap def get_pretty_suite(self, test: TestSerialisation) -> str: - if len(self.suites) > 1 and test.suite: - rv = TestHarness.split_suite_string(test.suite[0])[0] - s = "+".join(TestHarness.split_suite_string(s)[1] for s in test.suite) + assert test.suite, 'Interpreter should ensure there is always at least one suite' + prj = TestHarness.split_suite_string(test.suite[0])[0] + suites: T.List[str] = [] + for i in test.suite: + s = TestHarness.split_suite_string(i)[1] if s: - rv += ":" - return rv + s + " / " + test.name - else: - return test.name + suites.append(s) + name = f'{prj}:{test.name}' + if suites: + s = '+'.join(suites) + name = f'{s} - {name}' + return name def run_tests(self, runners: T.List[SingleTestRunner]) -> None: try: self.open_logfiles() # TODO: this is the default for python 3.8 - if sys.platform == 'win32': + if sys.platform == 'win32' and sys.version_info < (3, 8): asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) asyncio.run(self._run_tests(runners)) diff --git a/mesonbuild/optinterpreter.py b/mesonbuild/optinterpreter.py index 892d4d5..1127cc9 100644 --- a/mesonbuild/optinterpreter.py +++ b/mesonbuild/optinterpreter.py @@ -138,7 +138,7 @@ class OptionInterpreter: elif isinstance(arg, mparser.ArithmeticNode): l = self.reduce_single(arg.left) r = self.reduce_single(arg.right) - if not (arg.operation == 'add' and isinstance(l, str) and isinstance(r, str)): + if not (arg.operation == '+' and isinstance(l, str) and isinstance(r, str)): raise OptionException('Only string concatenation with the "+" operator is allowed') FeatureNew.single_use('string concatenation in meson_options.txt', '0.55.0', self.subproject) return l + r diff --git a/mesonbuild/options.py b/mesonbuild/options.py index 3b7d8b2..8e29f29 100644 --- a/mesonbuild/options.py +++ b/mesonbuild/options.py @@ -60,8 +60,8 @@ DEFAULT_YIELDING = False # Can't bind this near the class method it seems, sadly. _T = T.TypeVar('_T') -backendlist = ['ninja', 'vs', 'vs2010', 'vs2012', 'vs2013', 'vs2015', 'vs2017', 'vs2019', 'vs2022', 'xcode', 'none'] -genvslitelist = ['vs2022'] +backendlist = ['ninja', 'vs', 'vs2010', 'vs2012', 'vs2013', 'vs2015', 'vs2017', 'vs2019', 'vs2022', 'vs2026', 'xcode', 'none'] +genvslitelist = ['vs2022', 'vs2026'] buildtypelist = ['plain', 'debug', 'debugoptimized', 'release', 'minsize', 'custom'] # This is copied from coredata. There is no way to share this, because this @@ -105,6 +105,7 @@ _BUILTIN_NAMES = { 'pkg_config_path', 'cmake_prefix_path', 'vsenv', + 'os2_emxomf', } _BAD_VALUE = 'Qwert Zuiopü' @@ -310,7 +311,7 @@ class OptionKey: return self.machine is MachineChoice.BUILD if T.TYPE_CHECKING: - OptionStringLikeDict: TypeAlias = T.Dict[T.Union[OptionKey, str], str] + OptionDict: TypeAlias = T.Dict[OptionKey, ElementaryOptionValues] @dataclasses.dataclass class UserOption(T.Generic[_T], HoldableObject): @@ -321,13 +322,20 @@ class UserOption(T.Generic[_T], HoldableObject): yielding: bool = DEFAULT_YIELDING deprecated: DeprecatedType = False readonly: bool = dataclasses.field(default=False) + parent: T.Optional[UserOption] = None def __post_init__(self, value_: _T) -> None: self.value = self.validate_value(value_) # Final isn't technically allowed in a __post_init__ method self.default: Final[_T] = self.value # type: ignore[misc] - def listify(self, value: T.Any) -> T.List[T.Any]: + def listify(self, value: ElementaryOptionValues) -> T.List[str]: + if isinstance(value, list): + return value + if isinstance(value, bool): + return ['true'] if value else ['false'] + if isinstance(value, int): + return [str(value)] return [value] def printable_value(self) -> ElementaryOptionValues: @@ -340,10 +348,10 @@ class UserOption(T.Generic[_T], HoldableObject): # Check that the input is a valid value and return the # "cleaned" or "native" version. For example the Boolean # option could take the string "true" and return True. - def validate_value(self, value: T.Any) -> _T: + def validate_value(self, value: object) -> _T: raise RuntimeError('Derived option class did not override validate_value.') - def set_value(self, newvalue: T.Any) -> bool: + def set_value(self, newvalue: object) -> bool: oldvalue = self.value self.value = self.validate_value(newvalue) return self.value != oldvalue @@ -361,7 +369,7 @@ class EnumeratedUserOption(UserOption[_T]): class UserStringOption(UserOption[str]): - def validate_value(self, value: T.Any) -> str: + def validate_value(self, value: object) -> str: if not isinstance(value, str): raise MesonException(f'The value of option "{self.name}" is "{value}", which is not a string.') return value @@ -374,7 +382,7 @@ class UserBooleanOption(EnumeratedUserOption[bool]): def __bool__(self) -> bool: return self.value - def validate_value(self, value: T.Any) -> bool: + def validate_value(self, value: object) -> bool: if isinstance(value, bool): return value if not isinstance(value, str): @@ -406,7 +414,7 @@ class _UserIntegerBase(UserOption[_T]): def printable_choices(self) -> T.Optional[T.List[str]]: return [self.__choices] - def validate_value(self, value: T.Any) -> _T: + def validate_value(self, value: object) -> _T: if isinstance(value, str): value = T.cast('_T', self.toint(value)) if not isinstance(value, int): @@ -450,7 +458,7 @@ class UserUmaskOption(_UserIntegerBase[T.Union["Literal['preserve']", OctalInt]] return format(self.value, '04o') return self.value - def validate_value(self, value: T.Any) -> T.Union[Literal['preserve'], OctalInt]: + def validate_value(self, value: object) -> T.Union[Literal['preserve'], OctalInt]: if value == 'preserve': return 'preserve' return OctalInt(super().validate_value(value)) @@ -465,7 +473,7 @@ class UserUmaskOption(_UserIntegerBase[T.Union["Literal['preserve']", OctalInt]] @dataclasses.dataclass class UserComboOption(EnumeratedUserOption[str]): - def validate_value(self, value: T.Any) -> str: + def validate_value(self, value: object) -> str: if value not in self.choices: if isinstance(value, bool): _type = 'boolean' @@ -503,13 +511,13 @@ class UserArrayOption(UserOption[T.List[_T]]): @dataclasses.dataclass class UserStringArrayOption(UserArrayOption[str]): - def listify(self, value: T.Any) -> T.List[T.Any]: + def listify(self, value: object) -> T.List[str]: try: return listify_array_value(value, self.split_args) except MesonException as e: raise MesonException(f'error in option "{self.name}": {e!s}') - def validate_value(self, value: T.Union[str, T.List[str]]) -> T.List[str]: + def validate_value(self, value: object) -> T.List[str]: newvalue = self.listify(value) if not self.allow_dups and len(set(newvalue)) != len(newvalue): @@ -606,11 +614,14 @@ class UserStdOption(UserComboOption): else: self.choices += gnu_stds_map.keys() - def validate_value(self, value: T.Union[str, T.List[str]]) -> str: + def validate_value(self, value: object) -> str: try: candidates = listify_array_value(value) except MesonException as e: raise MesonException(f'error in option "{self.name}": {e!s}') + for std in candidates: + if not isinstance(std, str): + raise MesonException(f'String array element "{candidates!s}" for option "{self.name}" is not a string.') unknown = ','.join(std for std in candidates if std not in self.all_stds) if unknown: raise MesonException(f'Unknown option "{self.name}" value {unknown}. Possible values are {self.all_stds}.') @@ -705,25 +716,25 @@ BUILTIN_CORE_OPTIONS: T.Mapping[OptionKey, AnyOptionType] = { ), UserComboOption('buildtype', 'Build type to use', 'debug', choices=buildtypelist), UserBooleanOption('debug', 'Enable debug symbols and other information', True), - UserComboOption('default_library', 'Default library type', 'shared', choices=['shared', 'static', 'both'], - yielding=False), + UserComboOption('default_library', 'Default library type', 'shared', choices=['shared', 'static', 'both']), UserComboOption('default_both_libraries', 'Default library type for both_libraries', 'shared', choices=['shared', 'static', 'auto']), UserBooleanOption('errorlogs', "Whether to print the logs from failing tests", True), UserUmaskOption('install_umask', 'Default umask to apply on permissions of installed files', OctalInt(0o022)), UserComboOption('layout', 'Build directory layout', 'mirror', choices=['mirror', 'flat']), + UserComboOption('namingscheme', 'How target file names are formed', 'classic', choices=['platform', 'classic']), UserComboOption('optimization', 'Optimization level', '0', choices=['plain', '0', 'g', '1', '2', '3', 's']), UserBooleanOption('prefer_static', 'Whether to try static linking before shared linking', False), UserBooleanOption('stdsplit', 'Split stdout and stderr in test logs', True), UserBooleanOption('strip', 'Strip targets on install', False), UserComboOption('unity', 'Unity build', 'off', choices=['on', 'off', 'subprojects']), UserIntegerOption('unity_size', 'Unity block size', 4, min_value=2), - UserComboOption('warning_level', 'Compiler warning level to use', '1', choices=['0', '1', '2', '3', 'everything'], - yielding=False), - UserBooleanOption('werror', 'Treat warnings as errors', False, yielding=False), + UserComboOption('warning_level', 'Compiler warning level to use', '1', choices=['0', '1', '2', '3', 'everything']), + UserBooleanOption('werror', 'Treat warnings as errors', False), UserComboOption('wrap_mode', 'Wrap mode', 'default', choices=['default', 'nofallback', 'nodownload', 'forcefallback', 'nopromote']), UserStringArrayOption('force_fallback_for', 'Force fallback for those subprojects', []), UserBooleanOption('vsenv', 'Activate Visual Studio environment', False, readonly=True), + UserBooleanOption('os2_emxomf', 'Use OMF format on OS/2', False), # Pkgconfig module UserBooleanOption('pkgconfig.relocatable', 'Generate pkgconfig files as relocatable', False), @@ -735,6 +746,7 @@ BUILTIN_CORE_OPTIONS: T.Mapping[OptionKey, AnyOptionType] = { UserStringOption('python.platlibdir', 'Directory for site-specific, platform-specific files.', ''), UserStringOption('python.purelibdir', 'Directory for site-specific, non-platform-specific files.', ''), UserBooleanOption('python.allow_limited_api', 'Whether to allow use of the Python Limited API', True), + UserStringOption('python.build_config', 'Config file containing the build details for the target Python installation.', ''), ]) } @@ -796,21 +808,19 @@ class OptionStore: def __init__(self, is_cross: bool) -> None: self.options: T.Dict['OptionKey', 'AnyOptionType'] = {} + self.subprojects: T.Set[str] = set() self.project_options: T.Set[OptionKey] = set() self.module_options: T.Set[OptionKey] = set() from .compilers import all_languages self.all_languages = set(all_languages) - self.project_options = set() - self.augments: T.Dict[str, str] = {} + self.augments: OptionDict = {} self.is_cross = is_cross - # Pending options are options that need to be initialized later, either - # configuration dependent options like compiler options, or options for - # a different subproject - self.pending_options: T.Dict[OptionKey, ElementaryOptionValues] = {} - - def clear_pending(self) -> None: - self.pending_options = {} + # Pending options are configuration dependent options that could be + # initialized later, such as compiler options + self.pending_options: OptionDict = {} + # Subproject options from toplevel project() + self.pending_subproject_options: OptionDict = {} def ensure_and_validate_key(self, key: T.Union[OptionKey, str]) -> OptionKey: if isinstance(key, str): @@ -829,51 +839,47 @@ class OptionStore: key = key.as_host() return key - def get_value(self, key: T.Union[OptionKey, str]) -> ElementaryOptionValues: - return self.get_value_object(key).value + def get_pending_value(self, key: T.Union[OptionKey, str], default: T.Optional[ElementaryOptionValues] = None) -> ElementaryOptionValues: + key = self.ensure_and_validate_key(key) + if key in self.options: + return self.options[key].value + return self.pending_options.get(key, default) def __len__(self) -> int: return len(self.options) - def get_value_object_for(self, key: 'T.Union[OptionKey, str]') -> AnyOptionType: + def resolve_option(self, key: 'T.Union[OptionKey, str]') -> AnyOptionType: key = self.ensure_and_validate_key(key) potential = self.options.get(key, None) if self.is_project_option(key): assert key.subproject is not None - if potential is not None and potential.yielding: - parent_key = key.as_root() - try: - parent_option = self.options[parent_key] - except KeyError: - # Subproject is set to yield, but top level - # project does not have an option of the same - # name. Return the subproject option. - return potential - # If parent object has different type, do not yield. - # This should probably be an error. - if type(parent_option) is type(potential): - return parent_option - return potential if potential is None: raise KeyError(f'Tried to access nonexistant project option {key}.') - return potential else: if potential is None: parent_key = OptionKey(key.name, subproject=None, machine=key.machine) if parent_key not in self.options: raise KeyError(f'Tried to access nonexistant project parent option {parent_key}.') + # This is a global option but it can still have per-project + # augment, so return the subproject key. return self.options[parent_key] - return potential + return potential - def get_value_object_and_value_for(self, key: OptionKey) -> T.Tuple[AnyOptionType, ElementaryOptionValues]: + def get_option_and_value_for(self, key: OptionKey) -> T.Tuple[AnyOptionType, ElementaryOptionValues]: assert isinstance(key, OptionKey) - vobject = self.get_value_object_for(key) - computed_value = vobject.value - if key.subproject is not None: - keystr = str(key) - if keystr in self.augments: - computed_value = vobject.validate_value(self.augments[keystr]) - return (vobject, computed_value) + key = self.ensure_and_validate_key(key) + option_object = self.resolve_option(key) + computed_value = option_object.value + if key in self.augments: + assert key.subproject is not None + computed_value = self.augments[key] + elif option_object.yielding: + computed_value = option_object.parent.value + return (option_object, computed_value) + + def option_has_value(self, key: OptionKey, value: ElementaryOptionValues) -> bool: + option_object, current_value = self.get_option_and_value_for(key) + return option_object.validate_value(value) == current_value def get_value_for(self, name: 'T.Union[OptionKey, str]', subproject: T.Optional[str] = None) -> ElementaryOptionValues: if isinstance(name, str): @@ -881,7 +887,7 @@ class OptionStore: else: assert subproject is None key = name - vobject, resolved_value = self.get_value_object_and_value_for(key) + _, resolved_value = self.get_option_and_value_for(key) return resolved_value def add_system_option(self, key: T.Union[OptionKey, str], valobj: AnyOptionType) -> None: @@ -897,14 +903,12 @@ class OptionStore: if key in self.options: return - self.options[key] = valobj pval = self.pending_options.pop(key, None) if key.subproject: proj_key = key.evolve(subproject=None) self.add_system_option_internal(proj_key, valobj) - if pval is None: - pval = self.options[proj_key].value - + else: + self.options[key] = valobj if pval is not None: self.set_option(key, pval) @@ -919,12 +923,23 @@ class OptionStore: assert key.subproject is not None if key in self.options: raise MesonException(f'Internal error: tried to add a project option {key} that already exists.') + if valobj.yielding and key.subproject: + parent_key = key.as_root() + try: + parent_option = self.options[parent_key] + # If parent object has different type, do not yield. + # This should probably be an error. + if type(parent_option) is type(valobj): + valobj.parent = parent_option + except KeyError: + # Subproject is set to yield, but top level + # project does not have an option of the same + pass + valobj.yielding = valobj.parent is not None self.options[key] = valobj self.project_options.add(key) - pval = self.pending_options.pop(key, None) - if pval is not None: - self.set_option(key, pval) + assert key not in self.pending_options def add_module_option(self, modulename: str, key: T.Union[OptionKey, str], valobj: AnyOptionType) -> None: key = self.ensure_and_validate_key(key) @@ -935,6 +950,27 @@ class OptionStore: self.add_system_option_internal(key, valobj) self.module_options.add(key) + def add_builtin_option(self, key: OptionKey, opt: AnyOptionType) -> None: + # Create a copy of the object, as we're going to mutate it + opt = copy.copy(opt) + assert key.subproject is None + new_value = argparse_prefixed_default(opt, key, default_prefix()) + opt.set_value(new_value) + + modulename = key.get_module_prefix() + if modulename: + self.add_module_option(modulename, key, opt) + else: + self.add_system_option(key, opt) + + def init_builtins(self) -> None: + # Create builtin options with default values + for key, opt in BUILTIN_OPTIONS.items(): + self.add_builtin_option(key, opt) + for for_machine in iter(MachineChoice): + for key, opt in BUILTIN_OPTIONS_PER_MACHINE.items(): + self.add_builtin_option(key.evolve(machine=for_machine), opt) + def sanitize_prefix(self, prefix: str) -> str: prefix = os.path.expanduser(prefix) if not os.path.isabs(prefix): @@ -985,6 +1021,11 @@ class OptionStore: return value.as_posix() def set_option(self, key: OptionKey, new_value: ElementaryOptionValues, first_invocation: bool = False) -> bool: + changed = False + error_key = key + if error_key.subproject == '': + error_key = error_key.evolve(subproject=None) + if key.name == 'prefix': assert isinstance(new_value, str), 'for mypy' new_value = self.sanitize_prefix(new_value) @@ -994,85 +1035,119 @@ class OptionStore: new_value = self.sanitize_dir_option_value(prefix, key, new_value) try: - opt = self.get_value_object_for(key) + opt = self.resolve_option(key) except KeyError: - raise MesonException(f'Unknown options: "{key!s}" not found.') + raise MesonException(f'Unknown option: "{error_key}".') if opt.deprecated is True: - mlog.deprecation(f'Option {key.name!r} is deprecated') + mlog.deprecation(f'Option "{error_key}" is deprecated') elif isinstance(opt.deprecated, list): for v in opt.listify(new_value): if v in opt.deprecated: - mlog.deprecation(f'Option {key.name!r} value {v!r} is deprecated') + mlog.deprecation(f'Option "{error_key}" value {v!r} is deprecated') elif isinstance(opt.deprecated, dict): - def replace(v: T.Any) -> T.Any: + def replace(v: str) -> str: assert isinstance(opt.deprecated, dict) # No, Mypy can not tell this from two lines above newvalue = opt.deprecated.get(v) if newvalue is not None: - mlog.deprecation(f'Option {key.name!r} value {v!r} is replaced by {newvalue!r}') + mlog.deprecation(f'Option "{error_key}" value {v!r} is replaced by {newvalue!r}') return newvalue return v valarr = [replace(v) for v in opt.listify(new_value)] new_value = ','.join(valarr) elif isinstance(opt.deprecated, str): - mlog.deprecation(f'Option {key.name!r} is replaced by {opt.deprecated!r}') + mlog.deprecation(f'Option "{error_key}" is replaced by {opt.deprecated!r}') # Change both this aption and the new one pointed to. - dirty = self.set_option(key.evolve(name=opt.deprecated), new_value) - dirty |= opt.set_value(new_value) - return dirty + changed |= self.set_option(key.evolve(name=opt.deprecated), new_value, first_invocation) - old_value = opt.value - changed = opt.set_value(new_value) + new_value = opt.validate_value(new_value) + if key in self.options: + old_value = opt.value + opt.set_value(new_value) + opt.yielding = False + else: + assert key.subproject is not None + old_value = self.augments.get(key, opt.value) + self.augments[key] = new_value + changed |= old_value != new_value if opt.readonly and changed and not first_invocation: - raise MesonException(f'Tried to modify read only option {str(key)!r}') + raise MesonException(f'Tried to modify read only option "{error_key}"') if key.name == 'prefix' and first_invocation and changed: assert isinstance(old_value, str), 'for mypy' assert isinstance(new_value, str), 'for mypy' self.reset_prefixed_options(old_value, new_value) - if changed and key.name == 'buildtype': + if changed and key.name == 'buildtype' and new_value != 'custom': assert isinstance(new_value, str), 'for mypy' optimization, debug = self.DEFAULT_DEPENDENTS[new_value] dkey = key.evolve(name='debug') optkey = key.evolve(name='optimization') - self.options[dkey].set_value(debug) - self.options[optkey].set_value(optimization) + self.set_option(dkey, debug, first_invocation) + self.set_option(optkey, optimization, first_invocation) return changed - def set_option_from_string(self, keystr: T.Union[OptionKey, str], new_value: str) -> bool: - if isinstance(keystr, OptionKey): - o = keystr - else: - o = OptionKey.from_string(keystr) + def set_user_option(self, o: OptionKey, new_value: ElementaryOptionValues, first_invocation: bool = False) -> bool: + if not self.is_cross and o.is_for_build(): + return False + + # This is complicated by the fact that a string can have two meanings: + # + # default_options: 'foo=bar' + # + # can be either + # + # A) a system option in which case the subproject is None + # B) a project option, in which case the subproject is '' + # + # The key parsing function can not handle the difference between the two + # and defaults to A. if o in self.options: - return self.set_option(o, new_value) - o = o.as_root() - return self.set_option(o, new_value) + return self.set_option(o, new_value, first_invocation) + + # could also be an augment... + global_option = o.evolve(subproject=None) + if o.subproject is not None and global_option in self.options: + return self.set_option(o, new_value, first_invocation) + + if self.accept_as_pending_option(o, first_invocation=first_invocation): + old_value = self.pending_options.get(o, None) + self.pending_options[o] = new_value + return old_value is None or str(old_value) != new_value + elif o.subproject is None: + o = o.as_root() + return self.set_option(o, new_value, first_invocation) + else: + raise MesonException(f'Unknown option: "{o}".') - def set_from_configure_command(self, D_args: T.List[str], U_args: T.List[str]) -> bool: + def set_from_configure_command(self, D_args: T.Dict[OptionKey, T.Optional[str]]) -> bool: dirty = False - D_args = [] if D_args is None else D_args - (global_options, perproject_global_options, project_options) = self.classify_D_arguments(D_args) - U_args = [] if U_args is None else U_args - for key, valstr in global_options: - dirty |= self.set_option_from_string(key, valstr) - for key, valstr in project_options: - dirty |= self.set_option_from_string(key, valstr) - for keystr, valstr in perproject_global_options: - if keystr in self.augments: - if self.augments[keystr] != valstr: - self.augments[keystr] = valstr - dirty = True - else: - self.augments[keystr] = valstr - dirty = True - for delete in U_args: - if delete in self.augments: - del self.augments[delete] + for key, valstr in D_args.items(): + if valstr is not None: + dirty |= self.set_user_option(key, valstr) + continue + + if key in self.augments: + del self.augments[key] dirty = True + else: + if key not in self.options: + raise MesonException(f"Unknown option: {key}") + + # TODO: For project options, "dropping an augment" means going + # back to the superproject's value. However, it's confusing + # that -U does not simply remove the option from the stored + # cmd_line_options. This may cause "meson setup --wipe" to + # have surprising behavior. For this to work, UserOption + # should only store the default value and the option values + # should be stored with their source (project(), subproject(), + # machine file, command line). This way the effective value + # can be easily recomputed. + opt = self.get_value_object(key) + dirty |= not opt.yielding and bool(opt.parent) + opt.yielding = bool(opt.parent) return dirty def reset_prefixed_options(self, old_prefix: str, new_prefix: str) -> None: @@ -1090,22 +1165,10 @@ class OptionStore: new_value = prefix_mapping[new_prefix] valobj.set_value(new_value) - # FIXME, this should be removed.or renamed to "change_type_of_existing_object" or something like that - def set_value_object(self, key: T.Union[OptionKey, str], new_object: AnyOptionType) -> None: - key = self.ensure_and_validate_key(key) - self.options[key] = new_object - def get_value_object(self, key: T.Union[OptionKey, str]) -> AnyOptionType: key = self.ensure_and_validate_key(key) return self.options[key] - def get_default_for_b_option(self, key: OptionKey) -> ElementaryOptionValues: - assert self.is_base_option(key) - try: - return T.cast('ElementaryOptionValues', COMPILER_BASE_OPTIONS[key.evolve(subproject=None)].default) - except KeyError: - raise MesonBugException(f'Requested base option {key} which does not exist.') - def remove(self, key: OptionKey) -> None: del self.options[key] try: @@ -1129,16 +1192,6 @@ class OptionStore: def items(self) -> T.ItemsView['OptionKey', 'AnyOptionType']: return self.options.items() - # FIXME: this method must be deleted and users moved to use "add_xxx_option"s instead. - def update(self, **kwargs: AnyOptionType) -> None: - self.options.update(**kwargs) - - def setdefault(self, k: OptionKey, o: AnyOptionType) -> AnyOptionType: - return self.options.setdefault(k, o) - - def get(self, o: OptionKey, default: T.Optional[AnyOptionType] = None, **kwargs: T.Any) -> T.Optional[AnyOptionType]: - return self.options.get(o, default, **kwargs) - def is_project_option(self, key: OptionKey) -> bool: """Convenience method to check if this is a project option.""" return key in self.project_options @@ -1169,7 +1222,8 @@ class OptionStore: def is_base_option(self, key: OptionKey) -> bool: """Convenience method to check if this is a base option.""" - return key.name.startswith('b_') + # The "startswith" check is just an optimization + return key.name.startswith('b_') and key.evolve(subproject=None, machine=MachineChoice.HOST) in COMPILER_BASE_OPTIONS def is_backend_option(self, key: OptionKey) -> bool: """Convenience method to check if this is a backend option.""" @@ -1193,64 +1247,25 @@ class OptionStore: def is_module_option(self, key: OptionKey) -> bool: return key in self.module_options - def classify_D_arguments(self, D: T.List[str]) -> T.Tuple[T.List[T.Tuple[OptionKey, str]], - T.List[T.Tuple[str, str]], - T.List[T.Tuple[OptionKey, str]]]: - global_options = [] - project_options = [] - perproject_global_options = [] - for setval in D: - keystr, valstr = setval.split('=', 1) - key = OptionKey.from_string(keystr) - valuetuple = (key, valstr) - if self.is_project_option(key): - project_options.append(valuetuple) - elif key.subproject is None: - global_options.append(valuetuple) - else: - # FIXME, augments are currently stored as strings, not OptionKeys - strvaluetuple = (keystr, valstr) - perproject_global_options.append(strvaluetuple) - return (global_options, perproject_global_options, project_options) - - def optlist2optdict(self, optlist: T.List[str]) -> T.Dict[str, str]: - optdict = {} - for p in optlist: - k, v = p.split('=', 1) - optdict[k] = v - return optdict - - def prefix_split_options(self, coll: T.Union[T.List[str], OptionStringLikeDict]) -> T.Tuple[str, T.Union[T.List[str], OptionStringLikeDict]]: + def prefix_split_options(self, coll: OptionDict) -> T.Tuple[T.Optional[str], OptionDict]: prefix = None - if isinstance(coll, list): - others: T.List[str] = [] - for e in coll: - if e.startswith('prefix='): - prefix = e.split('=', 1)[1] - else: - others.append(e) - return (prefix, others) - else: - others_d: OptionStringLikeDict = {} - for k, v in coll.items(): - if isinstance(k, OptionKey) and k.name == 'prefix': - prefix = v - elif k == 'prefix': - prefix = v - else: - others_d[k] = v - return (prefix, others_d) + others_d: OptionDict = {} + for k, v in coll.items(): + if k.name == 'prefix': + if not isinstance(v, str): + raise MesonException('Incorrect type for prefix option (expected string)') + prefix = v + else: + others_d[k] = v + return (prefix, others_d) def first_handle_prefix(self, - project_default_options: T.Union[T.List[str], OptionStringLikeDict], - cmd_line_options: OptionStringLikeDict, - machine_file_options: T.Mapping[OptionKey, ElementaryOptionValues]) \ - -> T.Tuple[T.Union[T.List[str], OptionStringLikeDict], - T.Union[T.List[str], OptionStringLikeDict], - T.MutableMapping[OptionKey, ElementaryOptionValues]]: + project_default_options: OptionDict, + cmd_line_options: OptionDict, + machine_file_options: OptionDict) \ + -> T.Tuple[OptionDict, OptionDict, OptionDict]: # Copy to avoid later mutation - nopref_machine_file_options = T.cast( - 'T.MutableMapping[OptionKey, ElementaryOptionValues]', copy.copy(machine_file_options)) + nopref_machine_file_options = copy.copy(machine_file_options) prefix = None (possible_prefix, nopref_project_default_options) = self.prefix_split_options(project_default_options) @@ -1281,160 +1296,115 @@ class OptionStore: self.options[OptionKey('prefix')].set_value(prefix) def initialize_from_top_level_project_call(self, - project_default_options_in: T.Union[T.List[str], OptionStringLikeDict], - cmd_line_options_in: OptionStringLikeDict, - machine_file_options_in: T.Mapping[OptionKey, ElementaryOptionValues]) -> None: - first_invocation = True + project_default_options_in: OptionDict, + cmd_line_options_in: OptionDict, + machine_file_options_in: OptionDict) -> None: (project_default_options, cmd_line_options, machine_file_options) = self.first_handle_prefix(project_default_options_in, cmd_line_options_in, machine_file_options_in) - if isinstance(project_default_options, str): - project_default_options = [project_default_options] - if isinstance(project_default_options, list): - project_default_options = self.optlist2optdict(project_default_options) # type: ignore [assignment] - if project_default_options is None: - project_default_options = {} - assert isinstance(machine_file_options, dict) - for keystr, valstr in machine_file_options.items(): - if isinstance(keystr, str): - # FIXME, standardise on Key or string. - key = OptionKey.from_string(keystr) - else: - key = keystr - # Due to backwards compatibility we ignore all build-machine options - # when building natively. - if not self.is_cross and key.is_for_build(): - continue - if key.subproject: - augstr = str(key) - self.augments[augstr] = valstr - elif key in self.options: - self.set_option(key, valstr, first_invocation) - else: - proj_key = key.as_root() - if proj_key in self.options: - self.set_option(proj_key, valstr, first_invocation) - else: - self.pending_options[key] = valstr - assert isinstance(project_default_options, dict) - for keystr, valstr in project_default_options.items(): - # Ths is complicated by the fact that a string can have two meanings: - # - # default_options: 'foo=bar' - # - # can be either - # - # A) a system option in which case the subproject is None - # B) a project option, in which case the subproject is '' (this method is only called from top level) - # - # The key parsing function can not handle the difference between the two - # and defaults to A. - if isinstance(keystr, str): - key = OptionKey.from_string(keystr) - else: - key = keystr + for key, valstr in project_default_options.items(): # Due to backwards compatibility we ignore build-machine options # when building natively. if not self.is_cross and key.is_for_build(): continue if key.subproject: - self.pending_options[key] = valstr - elif key in self.options: - self.set_option(key, valstr, first_invocation) + # Subproject options from toplevel project() have low priority + # and will be processed when the subproject is found + self.pending_subproject_options[key] = valstr else: - # Setting a project option with default_options. - # Argubly this should be a hard error, the default + # Setting a project option with default_options + # should arguably be a hard error; the default # value of project option should be set in the option # file, not in the project call. - proj_key = key.as_root() - if self.is_project_option(proj_key): - self.set_option(proj_key, valstr) - else: - self.pending_options[key] = valstr - assert isinstance(cmd_line_options, dict) - for keystr, valstr in cmd_line_options.items(): - if isinstance(keystr, str): - key = OptionKey.from_string(keystr) - else: - key = keystr + self.set_user_option(key, valstr, True) + + # ignore subprojects for now for machine file and command line + # options; they are applied later + for key, valstr in itertools.chain(machine_file_options.items(), cmd_line_options.items()): # Due to backwards compatibility we ignore all build-machine options # when building natively. if not self.is_cross and key.is_for_build(): continue - if key.subproject: - self.pending_options[key] = valstr - elif key in self.options: - self.set_option(key, valstr, True) - else: - proj_key = key.as_root() - if proj_key in self.options: - self.set_option(proj_key, valstr, True) - else: - self.pending_options[key] = valstr + if not key.subproject: + self.set_user_option(key, valstr, True) - def validate_cmd_line_options(self, cmd_line_options: OptionStringLikeDict) -> None: - unknown_options = [] - for keystr, valstr in cmd_line_options.items(): - if isinstance(keystr, str): - key = OptionKey.from_string(keystr) - else: - key = keystr - # Fail on unknown options that we can know must exist at this point in time. - # Subproject and compiler options are resolved later. - # - # Some base options (sanitizers etc) might get added later. - # Permitting them all is not strictly correct. - if key.subproject is None and not self.is_compiler_option(key) and not self.is_base_option(key) and \ - key in self.pending_options: - unknown_options.append(f'"{key}"') - - if unknown_options: - keys = ', '.join(unknown_options) - raise MesonException(f'Unknown options: {keys}') - - def hacky_mchackface_back_to_list(self, optdict: T.Dict[str, str]) -> T.List[str]: - if isinstance(optdict, dict): - return [f'{k}={v}' for k, v in optdict.items()] - return optdict + def accept_as_pending_option(self, key: OptionKey, first_invocation: bool = False) -> bool: + # Some base options (sanitizers etc) might get added later. + # Permitting them all is not strictly correct. + if self.is_compiler_option(key): + return True + if first_invocation and self.is_backend_option(key): + return True + return self.is_base_option(key) def initialize_from_subproject_call(self, subproject: str, - spcall_default_options: T.Union[T.List[str], OptionStringLikeDict], - project_default_options: T.Union[T.List[str], OptionStringLikeDict], - cmd_line_options: T.Union[T.List[str], OptionStringLikeDict]) -> None: - is_first_invocation = True - spcall_default_options = self.hacky_mchackface_back_to_list(spcall_default_options) # type: ignore [arg-type] - project_default_options = self.hacky_mchackface_back_to_list(project_default_options) # type: ignore [arg-type] - if isinstance(spcall_default_options, str): - spcall_default_options = [spcall_default_options] - for o in itertools.chain(project_default_options, spcall_default_options): - keystr, valstr = o.split('=', 1) - key = OptionKey.from_string(keystr) - assert key.subproject is None - key = key.evolve(subproject=subproject) - # If the key points to a project option, set the value from that. - # Otherwise set an augment. - if key in self.project_options: - self.set_option(key, valstr, is_first_invocation) - else: - self.pending_options.pop(key, None) - aug_str = f'{subproject}:{keystr}' - self.augments[aug_str] = valstr - # Check for pending options - assert isinstance(cmd_line_options, dict) - for key, valstr in cmd_line_options.items(): # type: ignore [assignment] - if not isinstance(key, OptionKey): - key = OptionKey.from_string(key) + spcall_default_options: OptionDict, + project_default_options: OptionDict, + cmd_line_options: OptionDict, + machine_file_options: OptionDict) -> None: + + options: OptionDict = {} + + # project() default_options + for key, valstr in project_default_options.items(): + if key.subproject == subproject: + without_subp = key.evolve(subproject=None) + raise MesonException(f'subproject name not needed in default_options; use "{without_subp}" instead of "{key}"') + + if key.subproject is None: + key = key.evolve(subproject=subproject) + options[key] = valstr + + # then global settings from machine file and command line + # **but not if they are toplevel project options** + for key, valstr in itertools.chain(machine_file_options.items(), cmd_line_options.items()): + if key.subproject is None and not self.is_project_option(key.as_root()): + subp_key = key.evolve(subproject=subproject) + # just leave in place the value that was set for the toplevel project + options.pop(subp_key, None) + + # augments from the toplevel project() default_options + for key, valstr in self.pending_subproject_options.items(): + if key.subproject == subproject: + options[key] = valstr + + # subproject() default_options + for key, valstr in spcall_default_options.items(): + if key.subproject == subproject: + without_subp = key.evolve(subproject=None) + raise MesonException(f'subproject name not needed in default_options; use "{without_subp}" instead of "{key}"') + + if key.subproject is None: + key = key.evolve(subproject=subproject) + options[key] = valstr + + # then finally per project augments from machine file and command line + for key, valstr in itertools.chain(machine_file_options.items(), cmd_line_options.items()): + if key.subproject == subproject: + options[key] = valstr + + # merge everything that has been computed above, while giving self.augments priority + for key, valstr in options.items(): if key.subproject != subproject: + if key.subproject in self.subprojects and not self.option_has_value(key, valstr): + mlog.warning('option {key} is set in subproject {subproject} but has already been processed') + continue + + # Subproject options from project() will be processed when the subproject is found + self.pending_subproject_options[key] = valstr continue + + self.pending_subproject_options.pop(key, None) self.pending_options.pop(key, None) - if key in self.options: - self.set_option(key, valstr, is_first_invocation) - else: - self.augments[str(key)] = valstr + if key not in self.augments: + self.set_user_option(key, valstr, True) + + self.subprojects.add(subproject) def update_project_options(self, project_options: MutableKeyedOptionDictType, subproject: SubProject) -> None: for key, value in project_options.items(): + assert key.machine is MachineChoice.HOST if key not in self.options: self.add_project_option(key, value) continue @@ -1448,7 +1418,7 @@ class OptionStore: # If the choices have changed, use the new value, but attempt # to keep the old options. If they are not valid keep the new # defaults but warn. - self.set_value_object(key, value) + self.options[key] = value try: value.set_value(oldval.value) except MesonException: diff --git a/mesonbuild/programs.py b/mesonbuild/programs.py index d01440c..a1cfc06 100644 --- a/mesonbuild/programs.py +++ b/mesonbuild/programs.py @@ -322,6 +322,11 @@ class ExternalProgram(mesonlib.HoldableObject): paths = OrderedSet(path.split(os.pathsep)).difference(exclude_paths) path = os.pathsep.join(paths) command = shutil.which(name, path=path) + if not command and mesonlib.is_os2(): + for ext in ['exe', 'cmd']: + command = shutil.which(f'{name}.{ext}', path=path) + if command: + return [command] if mesonlib.is_windows(): return self._search_windows_special_cases(name, command, exclude_paths) # On UNIX-like platforms, shutil.which() is enough to find @@ -364,9 +369,9 @@ class OverrideProgram(ExternalProgram): def __init__(self, name: str, version: str, command: T.Optional[T.List[str]] = None, silent: bool = False, search_dirs: T.Optional[T.List[T.Optional[str]]] = None, exclude_paths: T.Optional[T.List[str]] = None): - self.cached_version = version super().__init__(name, command=command, silent=silent, search_dirs=search_dirs, exclude_paths=exclude_paths) + self.cached_version = version def find_external_program(env: 'Environment', for_machine: MachineChoice, name: str, display_name: str, default_names: T.List[str], diff --git a/mesonbuild/rewriter.py b/mesonbuild/rewriter.py index 919bd38..ac23bd4 100644 --- a/mesonbuild/rewriter.py +++ b/mesonbuild/rewriter.py @@ -10,23 +10,29 @@ from __future__ import annotations from .ast import IntrospectionInterpreter, BUILD_TARGET_FUNCTIONS, AstConditionLevel, AstIDGenerator, AstIndentationGenerator, AstPrinter -from mesonbuild.mesonlib import MesonException, setup_vsenv +from .ast.interpreter import IntrospectionBuildTarget, IntrospectionDependency, _symbol +from .interpreterbase import UnknownValue, TV_func +from .interpreterbase.helpers import flatten +from mesonbuild.mesonlib import MesonException, setup_vsenv, relpath from . import mlog, environment from functools import wraps -from .mparser import Token, ArrayNode, ArgumentNode, AssignmentNode, StringNode, BooleanNode, ElementaryNode, IdNode, FunctionNode, SymbolNode -import json, os, re, sys +from .mparser import Token, ArrayNode, ArgumentNode, ArithmeticNode, AssignmentNode, BaseNode, StringNode, BooleanNode, DictNode, ElementaryNode, IdNode, FunctionNode, PlusAssignmentNode +from .mintro import IntrospectionEncoder +import json, os, re, sys, codecs import typing as T +from pathlib import Path if T.TYPE_CHECKING: - from argparse import ArgumentParser, HelpFormatter - from .mparser import BaseNode + import argparse + from argparse import ArgumentParser, _FormatterClass + from .mlog import AnsiDecorator class RewriterException(MesonException): pass # Note: when adding arguments, please also add them to the completion # scripts in $MESONSRC/data/shell-completions/ -def add_arguments(parser: ArgumentParser, formatter: T.Callable[[str], HelpFormatter]) -> None: +def add_arguments(parser: ArgumentParser, formatter: _FormatterClass) -> None: parser.add_argument('-s', '--sourcedir', type=str, default='.', metavar='SRCDIR', help='Path to source directory.') parser.add_argument('-V', '--verbose', action='store_true', default=False, help='Enable verbose output') parser.add_argument('-S', '--skip-errors', dest='skip', action='store_true', default=False, help='Skip errors instead of aborting') @@ -48,26 +54,28 @@ def add_arguments(parser: ArgumentParser, formatter: T.Callable[[str], HelpForma help='Action to execute') kw_parser.add_argument('function', choices=list(rewriter_func_kwargs.keys()), help='Function type to modify') - kw_parser.add_argument('id', help='ID of the function to modify (can be anything for "project")') - kw_parser.add_argument('kwargs', nargs='*', help='Pairs of keyword and value') + kw_parser.add_argument('id', help='ID of the function to modify (must be "/" for "project")') + kw_parser.add_argument('kwargs', nargs='*', help='<keyword> <value> pairs, or list of <keyword> for "delete"') # Default options def_parser = subparsers.add_parser('default-options', aliases=['def'], help='Modify the project default options', formatter_class=formatter) def_parser.add_argument('operation', choices=rewriter_keys['default_options']['operation'][2], help='Action to execute') - def_parser.add_argument('options', nargs='*', help='Key, value pairs of configuration option') + def_parser.add_argument('options', nargs='*', help='<key> <value> pairs for "set"; list of <key> for "delete"') # JSON file/command cmd_parser = subparsers.add_parser('command', aliases=['cmd'], help='Execute a JSON array of commands', formatter_class=formatter) cmd_parser.add_argument('json', help='JSON string or file to execute') class RequiredKeys: - def __init__(self, keys): + keys: T.Dict[str, T.Any] + + def __init__(self, keys: T.Dict[str, T.Any]): self.keys = keys - def __call__(self, f): + def __call__(self, f: TV_func) -> TV_func: @wraps(f) - def wrapped(*wrapped_args, **wrapped_kwargs): + def wrapped(*wrapped_args: T.Any, **wrapped_kwargs: T.Any) -> T.Any: assert len(wrapped_args) >= 2 cmd = wrapped_args[1] for key, val in self.keys.items(): @@ -90,12 +98,11 @@ class RequiredKeys: .format(key, choices, cmd[key])) return f(*wrapped_args, **wrapped_kwargs) - return wrapped - -def _symbol(val: str) -> SymbolNode: - return SymbolNode(Token('', '', 0, 0, 0, (0, 0), val)) + return T.cast('TV_func', wrapped) class MTypeBase: + node: BaseNode + def __init__(self, node: T.Optional[BaseNode] = None): if node is None: self.node = self.new_node() @@ -107,30 +114,30 @@ class MTypeBase: self.node_type = i @classmethod - def new_node(cls, value=None): + def new_node(cls, value: T.Any = None) -> BaseNode: # Overwrite in derived class raise RewriterException('Internal error: new_node of MTypeBase was called') @classmethod - def supported_nodes(cls): + def supported_nodes(cls) -> T.List[type]: # Overwrite in derived class return [] - def can_modify(self): + def can_modify(self) -> bool: return self.node_type is not None - def get_node(self): + def get_node(self) -> BaseNode: return self.node - def add_value(self, value): + def add_value(self, value: T.Any) -> None: # Overwrite in derived class mlog.warning('Cannot add a value of type', mlog.bold(type(self).__name__), '--> skipping') - def remove_value(self, value): + def remove_value(self, value: T.Any) -> None: # Overwrite in derived class mlog.warning('Cannot remove a value of type', mlog.bold(type(self).__name__), '--> skipping') - def remove_regex(self, value): + def remove_regex(self, value: T.Any) -> None: # Overwrite in derived class mlog.warning('Cannot remove a regex in type', mlog.bold(type(self).__name__), '--> skipping') @@ -139,13 +146,13 @@ class MTypeStr(MTypeBase): super().__init__(node) @classmethod - def new_node(cls, value=None): + def new_node(cls, value: T.Optional[str] = None) -> BaseNode: if value is None: value = '' return StringNode(Token('string', '', 0, 0, 0, None, str(value))) @classmethod - def supported_nodes(cls): + def supported_nodes(cls) -> T.List[type]: return [StringNode] class MTypeBool(MTypeBase): @@ -153,11 +160,11 @@ class MTypeBool(MTypeBase): super().__init__(node) @classmethod - def new_node(cls, value=None): + def new_node(cls, value: T.Optional[str] = None) -> BaseNode: return BooleanNode(Token('', '', 0, 0, 0, None, bool(value))) @classmethod - def supported_nodes(cls): + def supported_nodes(cls) -> T.List[type]: return [BooleanNode] class MTypeID(MTypeBase): @@ -165,21 +172,23 @@ class MTypeID(MTypeBase): super().__init__(node) @classmethod - def new_node(cls, value=None): + def new_node(cls, value: T.Optional[str] = None) -> BaseNode: if value is None: value = '' return IdNode(Token('', '', 0, 0, 0, None, str(value))) @classmethod - def supported_nodes(cls): + def supported_nodes(cls) -> T.List[type]: return [IdNode] class MTypeList(MTypeBase): + node: ArrayNode + def __init__(self, node: T.Optional[BaseNode] = None): super().__init__(node) @classmethod - def new_node(cls, value=None): + def new_node(cls, value: T.Optional[T.List[T.Any]] = None) -> ArrayNode: if value is None: value = [] elif not isinstance(value, list): @@ -189,50 +198,52 @@ class MTypeList(MTypeBase): return ArrayNode(_symbol('['), args, _symbol(']')) @classmethod - def _new_element_node(cls, value): + def _new_element_node(cls, value: T.Any) -> BaseNode: # Overwrite in derived class raise RewriterException('Internal error: _new_element_node of MTypeList was called') - def _ensure_array_node(self): + def _ensure_array_node(self) -> None: if not isinstance(self.node, ArrayNode): tmp = self.node self.node = self.new_node() self.node.args.arguments = [tmp] @staticmethod - def _check_is_equal(node, value) -> bool: + def _check_is_equal(node: BaseNode, value: str) -> bool: # Overwrite in derived class return False @staticmethod - def _check_regex_matches(node, regex: str) -> bool: + def _check_regex_matches(node: BaseNode, regex: str) -> bool: # Overwrite in derived class return False - def get_node(self): + def get_node(self) -> BaseNode: if isinstance(self.node, ArrayNode): if len(self.node.args.arguments) == 1: return self.node.args.arguments[0] return self.node @classmethod - def supported_element_nodes(cls): + def supported_element_nodes(cls) -> T.List[T.Type]: # Overwrite in derived class return [] @classmethod - def supported_nodes(cls): + def supported_nodes(cls) -> T.List[T.Type]: return [ArrayNode] + cls.supported_element_nodes() - def add_value(self, value): + def add_value(self, value: T.Any) -> None: if not isinstance(value, list): value = [value] self._ensure_array_node() for i in value: + assert hasattr(self.node, 'args') # For mypy + assert isinstance(self.node.args, ArgumentNode) # For mypy self.node.args.arguments += [self._new_element_node(i)] - def _remove_helper(self, value, equal_func): - def check_remove_node(node): + def _remove_helper(self, value: T.Any, equal_func: T.Callable[[T.Any, T.Any], bool]) -> None: + def check_remove_node(node: BaseNode) -> bool: for j in value: if equal_func(i, j): return True @@ -241,16 +252,18 @@ class MTypeList(MTypeBase): if not isinstance(value, list): value = [value] self._ensure_array_node() + assert hasattr(self.node, 'args') # For mypy + assert isinstance(self.node.args, ArgumentNode) # For mypy removed_list = [] for i in self.node.args.arguments: if not check_remove_node(i): removed_list += [i] self.node.args.arguments = removed_list - def remove_value(self, value): + def remove_value(self, value: T.Any) -> None: self._remove_helper(value, self._check_is_equal) - def remove_regex(self, regex: str): + def remove_regex(self, regex: str) -> None: self._remove_helper(regex, self._check_regex_matches) class MTypeStrList(MTypeList): @@ -258,23 +271,23 @@ class MTypeStrList(MTypeList): super().__init__(node) @classmethod - def _new_element_node(cls, value): + def _new_element_node(cls, value: str) -> StringNode: return StringNode(Token('string', '', 0, 0, 0, None, str(value))) @staticmethod - def _check_is_equal(node, value) -> bool: + def _check_is_equal(node: BaseNode, value: str) -> bool: if isinstance(node, StringNode): - return node.value == value + return bool(node.value == value) return False @staticmethod - def _check_regex_matches(node, regex: str) -> bool: + def _check_regex_matches(node: BaseNode, regex: str) -> bool: if isinstance(node, StringNode): return re.match(regex, node.value) is not None return False @classmethod - def supported_element_nodes(cls): + def supported_element_nodes(cls) -> T.List[T.Type]: return [StringNode] class MTypeIDList(MTypeList): @@ -282,26 +295,26 @@ class MTypeIDList(MTypeList): super().__init__(node) @classmethod - def _new_element_node(cls, value): + def _new_element_node(cls, value: str) -> IdNode: return IdNode(Token('', '', 0, 0, 0, None, str(value))) @staticmethod - def _check_is_equal(node, value) -> bool: + def _check_is_equal(node: BaseNode, value: str) -> bool: if isinstance(node, IdNode): - return node.value == value + return bool(node.value == value) return False @staticmethod - def _check_regex_matches(node, regex: str) -> bool: + def _check_regex_matches(node: BaseNode, regex: str) -> bool: if isinstance(node, StringNode): return re.match(regex, node.value) is not None return False @classmethod - def supported_element_nodes(cls): + def supported_element_nodes(cls) -> T.List[T.Type]: return [IdNode] -rewriter_keys = { +rewriter_keys: T.Dict[str, T.Dict[str, T.Any]] = { 'default_options': { 'operation': (str, None, ['set', 'delete']), 'options': (dict, {}, None) @@ -349,19 +362,22 @@ rewriter_func_kwargs = { 'default_options': MTypeStrList, 'meson_version': MTypeStr, 'license': MTypeStrList, + 'license_files': MTypeStrList, 'subproject_dir': MTypeStr, 'version': MTypeStr } } class Rewriter: + info_dump: T.Optional[T.Dict[str, T.Dict[str, T.Any]]] + def __init__(self, sourcedir: str, generator: str = 'ninja', skip_errors: bool = False): self.sourcedir = sourcedir self.interpreter = IntrospectionInterpreter(sourcedir, '', generator, visitors = [AstIDGenerator(), AstIndentationGenerator(), AstConditionLevel()]) self.skip_errors = skip_errors - self.modified_nodes = [] - self.to_remove_nodes = [] - self.to_add_nodes = [] + self.modified_nodes: T.List[BaseNode] = [] + self.to_remove_nodes: T.List[BaseNode] = [] + self.to_add_nodes: T.List[BaseNode] = [] self.functions = { 'default_options': self.process_default_options, 'kwargs': self.process_kwargs, @@ -369,89 +385,99 @@ class Rewriter: } self.info_dump = None - def analyze_meson(self): + def analyze_meson(self) -> None: mlog.log('Analyzing meson file:', mlog.bold(os.path.join(self.sourcedir, environment.build_filename))) self.interpreter.analyze() mlog.log(' -- Project:', mlog.bold(self.interpreter.project_data['descriptive_name'])) mlog.log(' -- Version:', mlog.cyan(self.interpreter.project_data['version'])) - def add_info(self, cmd_type: str, cmd_id: str, data: dict): + def add_info(self, cmd_type: str, cmd_id: str, data: dict) -> None: if self.info_dump is None: self.info_dump = {} if cmd_type not in self.info_dump: self.info_dump[cmd_type] = {} self.info_dump[cmd_type][cmd_id] = data - def print_info(self): + def print_info(self) -> None: if self.info_dump is None: return - sys.stdout.write(json.dumps(self.info_dump, indent=2)) + sys.stdout.write(json.dumps(self.info_dump, indent=2, cls=IntrospectionEncoder)) - def on_error(self): + def on_error(self) -> T.Tuple[AnsiDecorator, AnsiDecorator]: if self.skip_errors: return mlog.cyan('-->'), mlog.yellow('skipping') return mlog.cyan('-->'), mlog.red('aborting') - def handle_error(self): + def handle_error(self) -> None: if self.skip_errors: return None raise MesonException('Rewriting the meson.build failed') - def find_target(self, target: str): - def check_list(name: str) -> T.List[BaseNode]: - result = [] - for i in self.interpreter.targets: - if name in {i['name'], i['id']}: - result += [i] - return result - - targets = check_list(target) - if targets: - if len(targets) == 1: - return targets[0] - else: - mlog.error('There are multiple targets matching', mlog.bold(target)) - for i in targets: - mlog.error(' -- Target name', mlog.bold(i['name']), 'with ID', mlog.bold(i['id'])) - mlog.error('Please try again with the unique ID of the target', *self.on_error()) - self.handle_error() - return None - - # Check the assignments - tgt = None - if target in self.interpreter.assignments: - node = self.interpreter.assignments[target] - if isinstance(node, FunctionNode): - if node.func_name.value in {'executable', 'jar', 'library', 'shared_library', 'shared_module', 'static_library', 'both_libraries'}: - tgt = self.interpreter.assign_vals[target] - - return tgt - - def find_dependency(self, dependency: str): - def check_list(name: str): - for i in self.interpreter.dependencies: - if name == i['name']: - return i + def all_assignments(self, varname: str) -> T.List[BaseNode]: + assigned_values = [] + for ass in self.interpreter.all_assignment_nodes[varname]: + if isinstance(ass, PlusAssignmentNode): + continue + assert isinstance(ass, AssignmentNode) + assigned_values.append(ass.value) + return assigned_values + + def find_target(self, target: str) -> T.Optional[IntrospectionBuildTarget]: + for i in self.interpreter.targets: + if target == i.id: + return i + + potential_tgts = [] + for i in self.interpreter.targets: + if target == i.name: + potential_tgts.append(i) + + if not potential_tgts: + potenial_tgts_1 = self.all_assignments(target) + potenial_tgts_1 = [self.interpreter.node_to_runtime_value(el) for el in potenial_tgts_1] + potential_tgts = [el for el in potenial_tgts_1 if isinstance(el, IntrospectionBuildTarget)] + + if not potential_tgts: + return None + elif len(potential_tgts) == 1: + return potential_tgts[0] + else: + mlog.error('There are multiple targets matching', mlog.bold(target)) + for i in potential_tgts: + mlog.error(' -- Target name', mlog.bold(i.name), 'with ID', mlog.bold(i.id)) + mlog.error('Please try again with the unique ID of the target', *self.on_error()) + self.handle_error() return None - dep = check_list(dependency) - if dep is not None: - return dep + def find_dependency(self, dependency: str) -> T.Optional[IntrospectionDependency]: + potential_deps = [] + for i in self.interpreter.dependencies: + if i.name == dependency: + potential_deps.append(i) - # Check the assignments - if dependency in self.interpreter.assignments: - node = self.interpreter.assignments[dependency] - if isinstance(node, FunctionNode): - if node.func_name.value == 'dependency': - name = self.interpreter.flatten_args(node.args)[0] - dep = check_list(name) + checking_varnames = len(potential_deps) == 0 - return dep + if checking_varnames: + potential_deps1 = self.all_assignments(dependency) + potential_deps = [self.interpreter.node_to_runtime_value(el) for el in potential_deps1 if isinstance(el, FunctionNode) and el.func_name.value == 'dependency'] + + if not potential_deps: + return None + elif len(potential_deps) == 1: + return potential_deps[0] + else: + mlog.error('There are multiple dependencies matching', mlog.bold(dependency)) + for i in potential_deps: + mlog.error(' -- Dependency name', i) + if checking_varnames: + mlog.error('Please try again with the name of the dependency', *self.on_error()) + self.handle_error() + return None @RequiredKeys(rewriter_keys['default_options']) - def process_default_options(self, cmd): + def process_default_options(self, cmd: T.Dict[str, T.Any]) -> None: # First, remove the old values - kwargs_cmd = { + kwargs_cmd: T.Dict[str, T.Any] = { 'function': 'project', 'id': "/", 'operation': 'remove_regex', @@ -471,10 +497,6 @@ class Rewriter: cdata = self.interpreter.coredata options = { **{str(k): v for k, v in cdata.optstore.items()}, - **{str(k): v for k, v in cdata.optstore.items()}, - **{str(k): v for k, v in cdata.optstore.items()}, - **{str(k): v for k, v in cdata.optstore.items()}, - **{str(k): v for k, v in cdata.optstore.items()}, } for key, val in sorted(cmd['options'].items()): @@ -495,7 +517,7 @@ class Rewriter: self.process_kwargs(kwargs_cmd) @RequiredKeys(rewriter_keys['kwargs']) - def process_kwargs(self, cmd): + def process_kwargs(self, cmd: T.Dict[str, T.Any]) -> None: mlog.log('Processing function type', mlog.bold(cmd['function']), 'with id', mlog.cyan("'" + cmd['id'] + "'")) if cmd['function'] not in rewriter_func_kwargs: mlog.error('Unknown function type', cmd['function'], *self.on_error()) @@ -516,26 +538,31 @@ class Rewriter: node = self.interpreter.project_node arg_node = node.args elif cmd['function'] == 'target': - tmp = self.find_target(cmd['id']) - if tmp: - node = tmp['node'] - arg_node = node.args + tmp_tgt = self.find_target(cmd['id']) + if not tmp_tgt: + mlog.error('Unable to find the target', mlog.bold(cmd['id']), *self.on_error()) + return self.handle_error() + node = tmp_tgt.node + arg_node = node.args elif cmd['function'] == 'dependency': - tmp = self.find_dependency(cmd['id']) - if tmp: - node = tmp['node'] - arg_node = node.args + tmp_dep = self.find_dependency(cmd['id']) + if not tmp_dep: + mlog.error('Unable to find the dependency', mlog.bold(cmd['id']), *self.on_error()) + return self.handle_error() + node = tmp_dep.node + arg_node = node.args if not node: - mlog.error('Unable to find the function node') + mlog.error('Unable to find the function node', *self.on_error()) + return self.handle_error() assert isinstance(node, FunctionNode) assert isinstance(arg_node, ArgumentNode) # Transform the key nodes to plain strings - arg_node.kwargs = {k.value: v for k, v in arg_node.kwargs.items()} + kwargs = {T.cast(IdNode, k).value: v for k, v in arg_node.kwargs.items()} # Print kwargs info if cmd['operation'] == 'info': - info_data = {} - for key, val in sorted(arg_node.kwargs.items()): + info_data: T.Dict[str, T.Any] = {} + for key, val in sorted(kwargs.items()): info_data[key] = None if isinstance(val, ElementaryNode): info_data[key] = val.value @@ -547,6 +574,16 @@ class Rewriter: element = i.value data_list += [element] info_data[key] = data_list + elif isinstance(val, DictNode): + data_dict = {} + for k, v in val.args.kwargs.items(): + if not isinstance(k, StringNode): + continue + value = None + if isinstance(v, ElementaryNode): + value = v.value + data_dict[k.value] = value + info_data[key] = data_dict self.add_info('kwargs', '{}#{}'.format(cmd['function'], cmd['id']), info_data) return # Nothing else to do @@ -561,21 +598,21 @@ class Rewriter: if cmd['operation'] == 'delete': # Remove the key from the kwargs - if key not in arg_node.kwargs: + if key not in kwargs: mlog.log(' -- Key', mlog.bold(key), 'is already deleted') continue mlog.log(' -- Deleting', mlog.bold(key), 'from the kwargs') - del arg_node.kwargs[key] + del kwargs[key] elif cmd['operation'] == 'set': # Replace the key from the kwargs mlog.log(' -- Setting', mlog.bold(key), 'to', mlog.yellow(str(val))) - arg_node.kwargs[key] = kwargs_def[key].new_node(val) + kwargs[key] = kwargs_def[key].new_node(val) else: # Modify the value from the kwargs - if key not in arg_node.kwargs: - arg_node.kwargs[key] = None - modifier = kwargs_def[key](arg_node.kwargs[key]) + if key not in kwargs: + kwargs[key] = None + modifier = kwargs_def[key](kwargs[key]) if not modifier.can_modify(): mlog.log(' -- Skipping', mlog.bold(key), 'because it is too complex to modify') continue @@ -593,24 +630,251 @@ class Rewriter: modifier.remove_regex(val) # Write back the result - arg_node.kwargs[key] = modifier.get_node() + kwargs[key] = modifier.get_node() num_changed += 1 # Convert the keys back to IdNode's - arg_node.kwargs = {IdNode(Token('', '', 0, 0, 0, None, k)): v for k, v in arg_node.kwargs.items()} + arg_node.kwargs = {IdNode(Token('', '', 0, 0, 0, None, k)): v for k, v in kwargs.items()} for k, v in arg_node.kwargs.items(): k.level = v.level if num_changed > 0 and node not in self.modified_nodes: self.modified_nodes += [node] - def find_assignment_node(self, node: BaseNode) -> AssignmentNode: - if node.ast_id and node.ast_id in self.interpreter.reverse_assignment: - return self.interpreter.reverse_assignment[node.ast_id] + def find_assignment_node(self, node: BaseNode) -> T.Optional[AssignmentNode]: + for k, v in self.interpreter.all_assignment_nodes.items(): + for ass in v: + if ass.value == node: + return ass return None + def affects_no_other_targets(self, candidate: BaseNode) -> bool: + affected = self.interpreter.dataflow_dag.reachable({candidate}, False) + affected_targets = [x for x in affected if isinstance(x, FunctionNode) and x.func_name.value in BUILD_TARGET_FUNCTIONS] + return len(affected_targets) == 1 + + def get_relto(self, target_node: BaseNode, node: BaseNode) -> Path: + cwd = Path(os.getcwd()) + all_paths = self.interpreter.dataflow_dag.find_all_paths(node, target_node) + # len(all_paths) == 0 would imply that data does not flow from node to + # target_node. This would imply that adding sources to node would not + # add the source to the target. + assert all_paths + if len(all_paths) > 1: + return None + return (cwd / next(x for x in all_paths[0] if isinstance(x, FunctionNode)).filename).parent + + def add_src_or_extra(self, op: str, target: IntrospectionBuildTarget, newfiles: T.List[str], to_sort_nodes: T.List[T.Union[FunctionNode, ArrayNode]]) -> None: + assert op in {'src_add', 'extra_files_add'} + + if op == 'src_add': + old: T.Set[T.Union[BaseNode, UnknownValue]] = set(target.source_nodes) + elif op == 'extra_files_add': + if target.extra_files is None: + old = set() + else: + old = {target.extra_files} + tgt_function: FunctionNode = target.node + + cwd = Path(os.getcwd()) + target_dir_abs = cwd / os.path.dirname(target.node.filename) + source_root_abs = cwd / self.interpreter.source_root + + candidates1 = self.interpreter.dataflow_dag.reachable(old, True) + # A node is a member of the set `candidates1` exactly if data from this node + # flow into one of the `dest` nodes. We assume that this implies that if we + # add `foo.c` to this node, then 'foo.c' will be added to one of these + # nodes. This assumption is not always true: + # ar = ['a.c', 'b.c'] + # srcs = ar[1] + # executable('name', srcs) + # Data flows from `ar` to `srcs`, but if we add 'foo.c': + # ar = ['a.c', 'b.c', 'foo.c'] + # srcs = ar[1] + # executable('name', srcs) + # this does not add 'foo.c' to `srcs`. This is a known bug/limitation of + # the meson rewriter that could be fixed by replacing `reachable` with a + # more advanced analysis. But this is a lot of work and I think e.g. + # `srcs = ar[1]` is rare in real-world projects, so I will just leave + # this for now. + + candidates2 = {x for x in candidates1 if isinstance(x, (FunctionNode, ArrayNode))} + + # If we have this meson.build file: + # shared = ['shared.c'] + # executable('foo', shared + ['foo.c']) + # executable('bar', shared + ['bar.c']) + # and we are tasked with adding 'new.c' to 'foo', we should do e.g this: + # shared = ['shared.c'] + # executable('foo', shared + ['foo.c', 'new.c']) + # executable('bar', shared + ['bar.c']) + # but never this: + # shared = ['shared.c', 'new.c'] + # executable('foo', shared + ['foo.c']) + # executable('bar', shared + ['bar.c']) + # We do this by removing the `['shared.c']`-node from `candidates2`. + candidates2 = {x for x in candidates2 if self.affects_no_other_targets(x)} + + def path_contains_unknowns(candidate: BaseNode) -> bool: + all_paths = self.interpreter.dataflow_dag.find_all_paths(candidate, target.node) + for path in all_paths: + for el in path: + if isinstance(el, UnknownValue): + return True + return False + + candidates2 = {x for x in candidates2 if not path_contains_unknowns(x)} + + candidates2 = {x for x in candidates2 if self.get_relto(target.node, x) is not None} + + chosen: T.Union[FunctionNode, ArrayNode] = None + new_kwarg_flag = False + if len(candidates2) > 0: + # So that files(['a', 'b']) gets modified to files(['a', 'b', 'c']) instead of files(['a', 'b'], 'c') + if len({x for x in candidates2 if isinstance(x, ArrayNode)}) > 0: + candidates2 = {x for x in candidates2 if isinstance(x, ArrayNode)} + + # We choose one more or less arbitrary candidate + chosen = min(candidates2, key=lambda x: (x.lineno, x.colno)) + elif op == 'src_add': + chosen = target.node + elif op == 'extra_files_add': + chosen = ArrayNode(_symbol('['), ArgumentNode(Token('', tgt_function.filename, 0, 0, 0, None, '[]')), _symbol(']')) + + # this is fundamentally error prone + self.interpreter.dataflow_dag.add_edge(chosen, target.node) + + extra_files_idnode = IdNode(Token('string', tgt_function.filename, 0, 0, 0, None, 'extra_files')) + if tgt_function not in self.modified_nodes: + self.modified_nodes += [tgt_function] + new_extra_files_node: BaseNode + if target.node.args.get_kwarg_or_default('extra_files', None) is None: + # Target has no extra_files kwarg, create one + new_kwarg_flag = True + new_extra_files_node = chosen + else: + new_kwarg_flag = True + old_extra_files = target.node.args.get_kwarg_or_default('extra_files', None) + target.node.args.kwargs = {k: v for k, v in target.node.args.kwargs.items() if not (isinstance(k, IdNode) and k.value == 'extra_files')} + new_extra_files_node = ArithmeticNode('+', old_extra_files, _symbol('+'), chosen) + + tgt_function.args.kwargs[extra_files_idnode] = new_extra_files_node + + newfiles_relto = self.get_relto(target.node, chosen) + old_src_list: T.List[T.Any] = flatten([self.interpreter.node_to_runtime_value(sn) for sn in old]) + + if op == 'src_add': + name = 'Source' + elif op == 'extra_files_add': + name = 'Extra file' + # Generate the new String nodes + to_append = [] + added = [] + + old_src_list = [(target_dir_abs / x).resolve() if isinstance(x, str) else x.to_abs_path(source_root_abs) for x in old_src_list if not isinstance(x, UnknownValue)] + for _newf in sorted(set(newfiles)): + newf = Path(_newf) + if os.path.isabs(newf): + newf = Path(newf) + else: + newf = source_root_abs / newf + if newf in old_src_list: + mlog.log(' -- ', name, mlog.green(str(newf)), 'is already defined for the target --> skipping') + continue + + mlog.log(' -- Adding ', name.lower(), mlog.green(str(newf)), 'at', + mlog.yellow(f'{chosen.filename}:{chosen.lineno}')) + added.append(newf) + mocktarget = self.interpreter.funcvals[target.node] + assert isinstance(mocktarget, IntrospectionBuildTarget) + # print("adding ", str(newf), 'to', mocktarget.name) todo: should we write something to stderr? + + path = relpath(newf, newfiles_relto) + path = codecs.encode(path, 'unicode_escape').decode() # Because the StringNode constructor does the inverse + token = Token('string', chosen.filename, 0, 0, 0, None, path) + to_append += [StringNode(token)] + + assert isinstance(chosen, (FunctionNode, ArrayNode)) + arg_node = chosen.args + # Append to the AST at the right place + arg_node.arguments += to_append + + # Mark the node as modified + if chosen not in to_sort_nodes: + to_sort_nodes += [chosen] + # If the extra_files array is newly created, i.e. if new_kwarg_flag is + # True, don't mark it as its parent function node already is, otherwise + # this would cause double modification. + if chosen not in self.modified_nodes and not new_kwarg_flag: + self.modified_nodes += [chosen] + + # Utility function to get a list of the sources from a node + def arg_list_from_node(self, n: BaseNode) -> T.List[BaseNode]: + args = [] + if isinstance(n, FunctionNode): + args = list(n.args.arguments) + if n.func_name.value in BUILD_TARGET_FUNCTIONS: + args.pop(0) + elif isinstance(n, ArrayNode): + args = n.args.arguments + elif isinstance(n, ArgumentNode): + args = n.arguments + return args + + def rm_src_or_extra(self, op: str, target: IntrospectionBuildTarget, to_be_removed: T.List[str], to_sort_nodes: T.List[T.Union[FunctionNode, ArrayNode]]) -> None: + assert op in {'src_rm', 'extra_files_rm'} + cwd = Path(os.getcwd()) + source_root_abs = cwd / self.interpreter.source_root + + # Helper to find the exact string node and its parent + def find_node(src: str) -> T.Tuple[T.Optional[BaseNode], T.Optional[StringNode]]: + if op == 'src_rm': + nodes = self.interpreter.dataflow_dag.reachable(set(target.source_nodes), True).union({target.node}) + elif op == 'extra_files_rm': + nodes = self.interpreter.dataflow_dag.reachable({target.extra_files}, True) + for i in nodes: + if isinstance(i, UnknownValue): + continue + relto = self.get_relto(target.node, i) + if relto is not None: + for j in self.arg_list_from_node(i): + if isinstance(j, StringNode): + if os.path.normpath(relto / j.value) == os.path.normpath(source_root_abs / src): + return i, j + return None, None + + if op == 'src_rm': + name = 'source' + elif op == 'extra_files_rm': + name = 'extra file' + + for i in to_be_removed: + # Try to find the node with the source string + root, string_node = find_node(i) + if root is None: + mlog.warning(' -- Unable to find', name, mlog.green(i), 'in the target') + continue + if not self.affects_no_other_targets(string_node): + mlog.warning(' -- Removing the', name, mlog.green(i), 'is too compilicated') + continue + + if not isinstance(root, (FunctionNode, ArrayNode)): + raise NotImplementedError # I'm lazy + + # Remove the found string node from the argument list + arg_node = root.args + mlog.log(' -- Removing', name, mlog.green(i), 'from', + mlog.yellow(f'{string_node.filename}:{string_node.lineno}')) + arg_node.arguments.remove(string_node) + + # Mark the node as modified + if root not in to_sort_nodes: + to_sort_nodes += [root] + if root not in self.modified_nodes: + self.modified_nodes += [root] + @RequiredKeys(rewriter_keys['target']) - def process_target(self, cmd): + def process_target(self, cmd: T.Dict[str, T.Any]) -> None: mlog.log('Processing target', mlog.bold(cmd['target']), 'operation', mlog.cyan(cmd['operation'])) target = self.find_target(cmd['target']) if target is None and cmd['operation'] != 'target_add': @@ -619,7 +883,7 @@ class Rewriter: # Make source paths relative to the current subdir def rel_source(src: str) -> str: - subdir = os.path.abspath(os.path.join(self.sourcedir, target['subdir'])) + subdir = os.path.abspath(os.path.join(self.sourcedir, target.subdir)) if os.path.isabs(src): return os.path.relpath(src, subdir) elif not os.path.exists(src): @@ -630,180 +894,13 @@ class Rewriter: if target is not None: cmd['sources'] = [rel_source(x) for x in cmd['sources']] - # Utility function to get a list of the sources from a node - def arg_list_from_node(n): - args = [] - if isinstance(n, FunctionNode): - args = list(n.args.arguments) - if n.func_name.value in BUILD_TARGET_FUNCTIONS: - args.pop(0) - elif isinstance(n, ArrayNode): - args = n.args.arguments - elif isinstance(n, ArgumentNode): - args = n.arguments - return args - - to_sort_nodes = [] - - if cmd['operation'] == 'src_add': - node = None - if target['sources']: - node = target['sources'][0] - else: - node = target['node'] - assert node is not None - - # Generate the current source list - src_list = [] - for i in target['sources']: - for j in arg_list_from_node(i): - if isinstance(j, StringNode): - src_list += [j.value] - - # Generate the new String nodes - to_append = [] - for i in sorted(set(cmd['sources'])): - if i in src_list: - mlog.log(' -- Source', mlog.green(i), 'is already defined for the target --> skipping') - continue - mlog.log(' -- Adding source', mlog.green(i), 'at', - mlog.yellow(f'{node.filename}:{node.lineno}')) - token = Token('string', node.filename, 0, 0, 0, None, i) - to_append += [StringNode(token)] - - # Append to the AST at the right place - arg_node = None - if isinstance(node, (FunctionNode, ArrayNode)): - arg_node = node.args - elif isinstance(node, ArgumentNode): - arg_node = node - assert arg_node is not None - arg_node.arguments += to_append - - # Mark the node as modified - if arg_node not in to_sort_nodes and not isinstance(node, FunctionNode): - to_sort_nodes += [arg_node] - if node not in self.modified_nodes: - self.modified_nodes += [node] - - elif cmd['operation'] == 'src_rm': - # Helper to find the exact string node and its parent - def find_node(src): - for i in target['sources']: - for j in arg_list_from_node(i): - if isinstance(j, StringNode): - if j.value == src: - return i, j - return None, None - - for i in cmd['sources']: - # Try to find the node with the source string - root, string_node = find_node(i) - if root is None: - mlog.warning(' -- Unable to find source', mlog.green(i), 'in the target') - continue - - # Remove the found string node from the argument list - arg_node = None - if isinstance(root, (FunctionNode, ArrayNode)): - arg_node = root.args - elif isinstance(root, ArgumentNode): - arg_node = root - assert arg_node is not None - mlog.log(' -- Removing source', mlog.green(i), 'from', - mlog.yellow(f'{string_node.filename}:{string_node.lineno}')) - arg_node.arguments.remove(string_node) - - # Mark the node as modified - if arg_node not in to_sort_nodes and not isinstance(root, FunctionNode): - to_sort_nodes += [arg_node] - if root not in self.modified_nodes: - self.modified_nodes += [root] - - elif cmd['operation'] == 'extra_files_add': - tgt_function: FunctionNode = target['node'] - mark_array = True - try: - node = target['extra_files'][0] - except IndexError: - # Specifying `extra_files` with a list that flattens to empty gives an empty - # target['extra_files'] list, account for that. - try: - extra_files_key = next(k for k in tgt_function.args.kwargs.keys() if isinstance(k, IdNode) and k.value == 'extra_files') - node = tgt_function.args.kwargs[extra_files_key] - except StopIteration: - # Target has no extra_files kwarg, create one - node = ArrayNode(_symbol('['), ArgumentNode(Token('', tgt_function.filename, 0, 0, 0, None, '[]')), _symbol(']')) - tgt_function.args.kwargs[IdNode(Token('string', tgt_function.filename, 0, 0, 0, None, 'extra_files'))] = node - mark_array = False - if tgt_function not in self.modified_nodes: - self.modified_nodes += [tgt_function] - target['extra_files'] = [node] - if isinstance(node, IdNode): - node = self.interpreter.assignments[node.value] - target['extra_files'] = [node] - if not isinstance(node, ArrayNode): - mlog.error('Target', mlog.bold(cmd['target']), 'extra_files argument must be a list', *self.on_error()) - return self.handle_error() - - # Generate the current extra files list - extra_files_list = [] - for i in target['extra_files']: - for j in arg_list_from_node(i): - if isinstance(j, StringNode): - extra_files_list += [j.value] - - # Generate the new String nodes - to_append = [] - for i in sorted(set(cmd['sources'])): - if i in extra_files_list: - mlog.log(' -- Extra file', mlog.green(i), 'is already defined for the target --> skipping') - continue - mlog.log(' -- Adding extra file', mlog.green(i), 'at', - mlog.yellow(f'{node.filename}:{node.lineno}')) - token = Token('string', node.filename, 0, 0, 0, None, i) - to_append += [StringNode(token)] - - # Append to the AST at the right place - arg_node = node.args - arg_node.arguments += to_append - - # Mark the node as modified - if arg_node not in to_sort_nodes: - to_sort_nodes += [arg_node] - # If the extra_files array is newly created, don't mark it as its parent function node already is, - # otherwise this would cause double modification. - if mark_array and node not in self.modified_nodes: - self.modified_nodes += [node] - - elif cmd['operation'] == 'extra_files_rm': - # Helper to find the exact string node and its parent - def find_node(src): - for i in target['extra_files']: - for j in arg_list_from_node(i): - if isinstance(j, StringNode): - if j.value == src: - return i, j - return None, None + to_sort_nodes: T.List[T.Union[FunctionNode, ArrayNode]] = [] - for i in cmd['sources']: - # Try to find the node with the source string - root, string_node = find_node(i) - if root is None: - mlog.warning(' -- Unable to find extra file', mlog.green(i), 'in the target') - continue - - # Remove the found string node from the argument list - arg_node = root.args - mlog.log(' -- Removing extra file', mlog.green(i), 'from', - mlog.yellow(f'{string_node.filename}:{string_node.lineno}')) - arg_node.arguments.remove(string_node) + if cmd['operation'] in {'src_add', 'extra_files_add'}: + self.add_src_or_extra(cmd['operation'], target, cmd['sources'], to_sort_nodes) - # Mark the node as modified - if arg_node not in to_sort_nodes and not isinstance(root, FunctionNode): - to_sort_nodes += [arg_node] - if root not in self.modified_nodes: - self.modified_nodes += [root] + elif cmd['operation'] in {'src_rm', 'extra_files_rm'}: + self.rm_src_or_extra(cmd['operation'], target, cmd['sources'], to_sort_nodes) elif cmd['operation'] == 'target_add': if target is not None: @@ -813,7 +910,7 @@ class Rewriter: id_base = re.sub(r'[- ]', '_', cmd['target']) target_id = id_base + '_exe' if cmd['target_type'] == 'executable' else '_lib' source_id = id_base + '_sources' - filename = os.path.join(cmd['subdir'], environment.build_filename) + filename = os.path.join(os.getcwd(), self.interpreter.source_root, cmd['subdir'], environment.build_filename) # Build src list src_arg_node = ArgumentNode(Token('string', filename, 0, 0, 0, None, '')) @@ -838,44 +935,55 @@ class Rewriter: self.to_add_nodes += [src_ass_node, tgt_ass_node] elif cmd['operation'] == 'target_rm': - to_remove = self.find_assignment_node(target['node']) + to_remove: BaseNode = self.find_assignment_node(target.node) if to_remove is None: - to_remove = target['node'] + to_remove = target.node self.to_remove_nodes += [to_remove] mlog.log(' -- Removing target', mlog.green(cmd['target']), 'at', mlog.yellow(f'{to_remove.filename}:{to_remove.lineno}')) elif cmd['operation'] == 'info': # T.List all sources in the target - src_list = [] - for i in target['sources']: - for j in arg_list_from_node(i): - if isinstance(j, StringNode): - src_list += [j.value] - extra_files_list = [] - for i in target['extra_files']: - for j in arg_list_from_node(i): - if isinstance(j, StringNode): - extra_files_list += [j.value] + + cwd = Path(os.getcwd()) + source_root_abs = cwd / self.interpreter.source_root + + src_list = self.interpreter.nodes_to_pretty_filelist(source_root_abs, target.subdir, target.source_nodes) + extra_files_list = self.interpreter.nodes_to_pretty_filelist(source_root_abs, target.subdir, [target.extra_files] if target.extra_files else []) + + src_list = ['unknown' if isinstance(x, UnknownValue) else relpath(x, source_root_abs) for x in src_list] + extra_files_list = ['unknown' if isinstance(x, UnknownValue) else relpath(x, source_root_abs) for x in extra_files_list] + test_data = { - 'name': target['name'], + 'name': target.name, 'sources': src_list, 'extra_files': extra_files_list } - self.add_info('target', target['id'], test_data) + self.add_info('target', target.id, test_data) # Sort files for i in to_sort_nodes: - convert = lambda text: int(text) if text.isdigit() else text.lower() - alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)] - path_sorter = lambda key: ([(key.count('/') <= idx, alphanum_key(x)) for idx, x in enumerate(key.split('/'))]) + def convert(text: str) -> T.Union[int, str]: + return int(text) if text.isdigit() else text.lower() + + def alphanum_key(key: str) -> T.List[T.Union[int, str]]: + return [convert(c) for c in re.split('([0-9]+)', key)] + + def path_sorter(key: str) -> T.List[T.Tuple[bool, T.List[T.Union[int, str]]]]: + return [(key.count('/') <= idx, alphanum_key(x)) for idx, x in enumerate(key.split('/'))] - unknown = [x for x in i.arguments if not isinstance(x, StringNode)] - sources = [x for x in i.arguments if isinstance(x, StringNode)] + if isinstance(i, FunctionNode) and i.func_name.value in BUILD_TARGET_FUNCTIONS: + src_args = i.args.arguments[1:] + target_name = [i.args.arguments[0]] + else: + src_args = i.args.arguments + target_name = [] + unknown: T.List[BaseNode] = [x for x in src_args if not isinstance(x, StringNode)] + sources: T.List[StringNode] = [x for x in src_args if isinstance(x, StringNode)] sources = sorted(sources, key=lambda x: path_sorter(x.value)) - i.arguments = unknown + sources + i.args.arguments = target_name + unknown + T.cast(T.List[BaseNode], sources) - def process(self, cmd): + def process(self, cmd: T.Dict[str, T.Any]) -> None: if 'type' not in cmd: raise RewriterException('Command has no key "type"') if cmd['type'] not in self.functions: @@ -883,7 +991,7 @@ class Rewriter: .format(cmd['type'], list(self.functions.keys()))) self.functions[cmd['type']](cmd) - def apply_changes(self): + def apply_changes(self) -> None: assert all(hasattr(x, 'lineno') and hasattr(x, 'colno') and hasattr(x, 'filename') for x in self.modified_nodes) assert all(hasattr(x, 'lineno') and hasattr(x, 'colno') and hasattr(x, 'filename') for x in self.to_remove_nodes) assert all(isinstance(x, (ArrayNode, FunctionNode)) for x in self.modified_nodes) @@ -891,7 +999,7 @@ class Rewriter: # Sort based on line and column in reversed order work_nodes = [{'node': x, 'action': 'modify'} for x in self.modified_nodes] work_nodes += [{'node': x, 'action': 'rm'} for x in self.to_remove_nodes] - work_nodes = sorted(work_nodes, key=lambda x: (x['node'].lineno, x['node'].colno), reverse=True) + work_nodes = sorted(work_nodes, key=lambda x: (T.cast(BaseNode, x['node']).lineno, T.cast(BaseNode, x['node']).colno), reverse=True) work_nodes += [{'node': x, 'action': 'add'} for x in self.to_add_nodes] # Generating the new replacement string @@ -900,11 +1008,11 @@ class Rewriter: new_data = '' if i['action'] == 'modify' or i['action'] == 'add': printer = AstPrinter() - i['node'].accept(printer) + T.cast(BaseNode, i['node']).accept(printer) printer.post_process() new_data = printer.result.strip() data = { - 'file': i['node'].filename, + 'file': T.cast(BaseNode, i['node']).filename, 'str': new_data, 'node': i['node'], 'action': i['action'] @@ -912,11 +1020,11 @@ class Rewriter: str_list += [data] # Load build files - files = {} + files: T.Dict[str, T.Any] = {} for i in str_list: if i['file'] in files: continue - fpath = os.path.realpath(os.path.join(self.sourcedir, i['file'])) + fpath = os.path.realpath(T.cast(str, i['file'])) fdata = '' # Create an empty file if it does not exist if not os.path.exists(fpath): @@ -933,14 +1041,14 @@ class Rewriter: line_offsets += [offset] offset += len(j) - files[i['file']] = { + files[T.cast(str, i['file'])] = { 'path': fpath, 'raw': fdata, 'offsets': line_offsets } # Replace in source code - def remove_node(i): + def remove_node(i: T.Dict[str, T.Any]) -> None: offsets = files[i['file']]['offsets'] raw = files[i['file']]['raw'] node = i['node'] @@ -968,7 +1076,7 @@ class Rewriter: if i['action'] in {'modify', 'rm'}: remove_node(i) elif i['action'] == 'add': - files[i['file']]['raw'] += i['str'] + '\n' + files[T.cast(str, i['file'])]['raw'] += T.cast(str, i['str']) + '\n' # Write the files back for key, val in files.items(): @@ -996,10 +1104,17 @@ def list_to_dict(in_list: T.List[str]) -> T.Dict[str, str]: # key value pairs. result[i] = next(it) except StopIteration: - raise TypeError('in_list parameter of list_to_dict must have an even length.') + raise RewriterException('List of key/value pairs must have an even length.') return result -def generate_target(options) -> T.List[dict]: +def list_to_dict_for_delete(args: T.List[str]) -> T.Dict[str, T.Optional[str]]: + if len(args) % 2 == 0 and all(a == '' for a in args[1::2]): + mlog.deprecation('Even-numbered arguments are all blank; ' + 'ignoring these for compatibility with Meson < 1.10') + args = args[::2] + return {a: None for a in args} + +def generate_target(options: argparse.Namespace) -> T.List[T.Dict[str, T.Any]]: return [{ 'type': 'target', 'target': options.target, @@ -1009,28 +1124,36 @@ def generate_target(options) -> T.List[dict]: 'target_type': options.tgt_type, }] -def generate_kwargs(options) -> T.List[dict]: +def generate_kwargs(options: argparse.Namespace) -> T.List[T.Dict[str, T.Any]]: + if options.operation == 'delete': + kwargs = list_to_dict_for_delete(options.kwargs) + else: + kwargs = list_to_dict(options.kwargs) return [{ 'type': 'kwargs', 'function': options.function, 'id': options.id, 'operation': options.operation, - 'kwargs': list_to_dict(options.kwargs), + 'kwargs': kwargs, }] -def generate_def_opts(options) -> T.List[dict]: +def generate_def_opts(options: argparse.Namespace) -> T.List[T.Dict[str, T.Any]]: + if options.operation == 'delete': + kwargs = list_to_dict_for_delete(options.options) + else: + kwargs = list_to_dict(options.options) return [{ 'type': 'default_options', 'operation': options.operation, - 'options': list_to_dict(options.options), + 'options': kwargs, }] -def generate_cmd(options) -> T.List[dict]: +def generate_cmd(options: argparse.Namespace) -> T.List[T.Dict[str, T.Any]]: if os.path.exists(options.json): with open(options.json, encoding='utf-8') as fp: - return json.load(fp) + return T.cast(T.List[T.Dict[str, T.Any]], json.load(fp)) else: - return json.loads(options.json) + return T.cast(T.List[T.Dict[str, T.Any]], json.loads(options.json)) # Map options.type to the actual type name cli_type_map = { @@ -1043,7 +1166,7 @@ cli_type_map = { 'cmd': generate_cmd, } -def run(options): +def run(options: argparse.Namespace) -> int: mlog.redirect(True) if not options.verbose: mlog.set_quiet() @@ -1062,12 +1185,22 @@ def run(options): if not isinstance(commands, list): raise TypeError('Command is not a list') - for i in commands: - if not isinstance(i, object): + for i, cmd in enumerate(commands): + if not isinstance(cmd, object): raise TypeError('Command is not an object') - rewriter.process(i) + rewriter.process(cmd) + rewriter.apply_changes() + + if i == len(commands) - 1: # Improves the performance, is not necessary for correctness. + break + + rewriter.modified_nodes = [] + rewriter.to_remove_nodes = [] + rewriter.to_add_nodes = [] + # The AST changed, so we need to update every information that was derived from the AST + rewriter.interpreter = IntrospectionInterpreter(rewriter.sourcedir, '', rewriter.interpreter.backend, visitors = [AstIDGenerator(), AstIndentationGenerator(), AstConditionLevel()]) + rewriter.analyze_meson() - rewriter.apply_changes() rewriter.print_info() return 0 except Exception as e: diff --git a/mesonbuild/scripts/clangformat.py b/mesonbuild/scripts/clangformat.py index a3c19e9..281cb10 100644 --- a/mesonbuild/scripts/clangformat.py +++ b/mesonbuild/scripts/clangformat.py @@ -8,7 +8,7 @@ from pathlib import Path import sys from .run_tool import run_clang_tool, run_with_buffered_output -from ..environment import detect_clangformat +from ..tooldetect import detect_clangformat from ..mesonlib import version_compare from ..programs import ExternalProgram import typing as T diff --git a/mesonbuild/scripts/clangtidy.py b/mesonbuild/scripts/clangtidy.py index 550faee..da4d454 100644 --- a/mesonbuild/scripts/clangtidy.py +++ b/mesonbuild/scripts/clangtidy.py @@ -11,8 +11,8 @@ import os import shutil import sys -from .run_tool import run_clang_tool, run_with_buffered_output -from ..environment import detect_clangtidy, detect_clangapply +from .run_tool import run_with_buffered_output, run_clang_tool_on_sources +from ..tooldetect import detect_clangtidy, detect_clangapply import typing as T async def run_clang_tidy(fname: Path, tidyexe: list, builddir: Path, fixesdir: T.Optional[Path]) -> int: @@ -56,7 +56,7 @@ def run(args: T.List[str]) -> int: fixesdir.unlink() fixesdir.mkdir(parents=True) - tidyret = run_clang_tool('clang-tidy', srcdir, builddir, run_clang_tidy, tidyexe, builddir, fixesdir) + tidyret = run_clang_tool_on_sources('clang-tidy', srcdir, builddir, run_clang_tidy, tidyexe, builddir, fixesdir) if fixesdir is not None: print('Applying fix-its...') applyret = subprocess.run(applyexe + ['-format', '-style=file', '-ignore-insert-conflict', fixesdir]).returncode diff --git a/mesonbuild/scripts/clippy.py b/mesonbuild/scripts/clippy.py index 6d282e4..0ea7a34 100644 --- a/mesonbuild/scripts/clippy.py +++ b/mesonbuild/scripts/clippy.py @@ -23,7 +23,7 @@ class ClippyDriver: compilers = build.environment.coredata.compilers[machine] if 'rust' in compilers: compiler = T.cast('RustCompiler', compilers['rust']) - self.tools[machine] = compiler.get_rust_tool('clippy-driver', build.environment) + self.tools[machine] = compiler.get_rust_tool('clippy-driver') def warn_missing_clippy(self, machine: str) -> None: if self.warned[machine]: diff --git a/mesonbuild/scripts/coverage.py b/mesonbuild/scripts/coverage.py index a4dfebf..f515363 100644 --- a/mesonbuild/scripts/coverage.py +++ b/mesonbuild/scripts/coverage.py @@ -3,7 +3,7 @@ from __future__ import annotations -from mesonbuild import environment, mesonlib +from mesonbuild import tooldetect, mesonlib import argparse, re, sys, os, subprocess, pathlib, stat, shutil import typing as T @@ -16,11 +16,11 @@ def coverage(outputs: T.List[str], source_root: str, subproject_root: str, build if gcovr_exe == '': gcovr_exe = None else: - gcovr_exe, gcovr_version = environment.detect_gcovr(gcovr_exe) + gcovr_exe, gcovr_version = tooldetect.detect_gcovr(gcovr_exe) if llvm_cov_exe == '' or shutil.which(llvm_cov_exe) is None: llvm_cov_exe = None - lcov_exe, lcov_version, genhtml_exe = environment.detect_lcov_genhtml() + lcov_exe, lcov_version, genhtml_exe = tooldetect.detect_lcov_genhtml() # load config files for tools if available in the source tree # - lcov requires manually specifying a per-project config diff --git a/mesonbuild/scripts/depfixer.py b/mesonbuild/scripts/depfixer.py index db9c97d..8adc35c 100644 --- a/mesonbuild/scripts/depfixer.py +++ b/mesonbuild/scripts/depfixer.py @@ -31,8 +31,12 @@ class DataSizes: p = '<' else: p = '>' + self.Char = p + 'c' + self.CharSize = 1 self.Half = p + 'h' self.HalfSize = 2 + self.Section = p + 'h' + self.SectionSize = 2 self.Word = p + 'I' self.WordSize = 4 self.Sword = p + 'i' @@ -71,6 +75,24 @@ class DynamicEntry(DataSizes): ofile.write(struct.pack(self.Sword, self.d_tag)) ofile.write(struct.pack(self.Word, self.val)) +class DynsymEntry(DataSizes): + def __init__(self, ifile: T.BinaryIO, ptrsize: int, is_le: bool) -> None: + super().__init__(ptrsize, is_le) + is_64 = ptrsize == 64 + self.st_name = struct.unpack(self.Word, ifile.read(self.WordSize))[0] + if is_64: + self.st_info = struct.unpack(self.Char, ifile.read(self.CharSize))[0] + self.st_other = struct.unpack(self.Char, ifile.read(self.CharSize))[0] + self.st_shndx = struct.unpack(self.Section, ifile.read(self.SectionSize))[0] + self.st_value = struct.unpack(self.Addr, ifile.read(self.AddrSize))[0] + self.st_size = struct.unpack(self.XWord, ifile.read(self.XWordSize))[0] + else: + self.st_value = struct.unpack(self.Addr, ifile.read(self.AddrSize))[0] + self.st_size = struct.unpack(self.Word, ifile.read(self.WordSize))[0] + self.st_info = struct.unpack(self.Char, ifile.read(self.CharSize))[0] + self.st_other = struct.unpack(self.Char, ifile.read(self.CharSize))[0] + self.st_shndx = struct.unpack(self.Section, ifile.read(self.SectionSize))[0] + class SectionHeader(DataSizes): def __init__(self, ifile: T.BinaryIO, ptrsize: int, is_le: bool) -> None: super().__init__(ptrsize, is_le) @@ -115,6 +137,8 @@ class Elf(DataSizes): self.verbose = verbose self.sections: T.List[SectionHeader] = [] self.dynamic: T.List[DynamicEntry] = [] + self.dynsym: T.List[DynsymEntry] = [] + self.dynsym_strings: T.List[str] = [] self.open_bf(bfile) try: (self.ptrsize, self.is_le) = self.detect_elf_type() @@ -122,6 +146,8 @@ class Elf(DataSizes): self.parse_header() self.parse_sections() self.parse_dynamic() + self.parse_dynsym() + self.parse_dynsym_strings() except (struct.error, RuntimeError): self.close_bf() raise @@ -232,6 +258,23 @@ class Elf(DataSizes): if e.d_tag == 0: break + def parse_dynsym(self) -> None: + sec = self.find_section(b'.dynsym') + if sec is None: + return + self.bf.seek(sec.sh_offset) + for i in range(sec.sh_size // sec.sh_entsize): + e = DynsymEntry(self.bf, self.ptrsize, self.is_le) + self.dynsym.append(e) + + def parse_dynsym_strings(self) -> None: + sec = self.find_section(b'.dynstr') + if sec is None: + return + for i in self.dynsym: + self.bf.seek(sec.sh_offset + i.st_name) + self.dynsym_strings.append(self.read_str().decode()) + @generate_list def get_section_names(self) -> T.Generator[str, None, None]: section_names = self.sections[self.e_shstrndx] @@ -353,12 +396,48 @@ class Elf(DataSizes): self.bf.write(new_rpath) self.bf.write(b'\0') + def clean_rpath_entry_string(self, entrynum: int) -> None: + # Get the rpath string + offset = self.get_entry_offset(entrynum) + self.bf.seek(offset) + rpath_string = self.read_str().decode() + reused_str = '' + + # Inspect the dyn strings and check if our rpath string + # ends with one of them. + # This is to handle a subtle optimization of the linker + # where one of the dyn function name offset in the dynstr + # table might be set at the an offset of the rpath string. + # Example: + # + # rpath offset = 1314 string = /usr/lib/foo + # dym function offset = 1322 string = foo + # + # In the following case, the dym function string offset is + # placed at the offset +10 of the rpath. + # To correctly clear the rpath entry AND keep normal + # functionality of this optimization (and the binary), + # parse the maximum string we can remove from the rpath entry. + # + # Since strings MUST be null terminated, we can always check + # if the rpath string ends with the dyn function string and + # calculate what we can actually remove accordingly. + for dynsym_string in self.dynsym_strings: + if rpath_string.endswith(dynsym_string): + if len(dynsym_string) > len(reused_str): + reused_str = dynsym_string + + # Seek back to start of string + self.bf.seek(offset) + self.bf.write(b'X' * (len(rpath_string) - len(reused_str))) + def remove_rpath_entry(self, entrynum: int) -> None: sec = self.find_section(b'.dynamic') if sec is None: return None for (i, entry) in enumerate(self.dynamic): if entry.d_tag == entrynum: + self.clean_rpath_entry_string(entrynum) rpentry = self.dynamic[i] rpentry.d_tag = 0 self.dynamic = self.dynamic[:i] + self.dynamic[i + 1:] + [rpentry] diff --git a/mesonbuild/scripts/env2mfile.py b/mesonbuild/scripts/env2mfile.py index 16051a8..c529577 100755 --- a/mesonbuild/scripts/env2mfile.py +++ b/mesonbuild/scripts/env2mfile.py @@ -5,6 +5,7 @@ from __future__ import annotations from dataclasses import dataclass, field import sys, os, subprocess, shutil +import pathlib import shlex import typing as T @@ -24,11 +25,13 @@ def add_arguments(parser: 'argparse.ArgumentParser') -> None: parser.add_argument('--gccsuffix', default="", help='A particular gcc version suffix if necessary.') parser.add_argument('-o', required=True, dest='outfile', - help='The output file.') + help='The output file or directory (for Android).') parser.add_argument('--cross', default=False, action='store_true', help='Generate a cross compilation file.') parser.add_argument('--native', default=False, action='store_true', help='Generate a native compilation file.') + parser.add_argument('--android', default=False, action='store_true', + help='Generate cross files for Android toolchains.') parser.add_argument('--use-for-build', default=False, action='store_true', help='Use _FOR_BUILD envvars.') parser.add_argument('--system', default=None, @@ -234,6 +237,7 @@ def dpkg_architecture_to_machine_info(output: str, options: T.Any) -> MachineInf 'g-ir-inspect', 'g-ir-scanner', 'pkg-config', + 'vapigen', ]: try: infos.binaries[tool] = locate_path("%s-%s" % (host_arch, tool)) @@ -429,11 +433,108 @@ def detect_native_env(options: T.Any) -> MachineInfo: detect_properties_from_envvars(infos, esuffix) return infos +ANDROID_CPU_TO_MESON_CPU_FAMILY: dict[str, str] = { + 'aarch64': 'aarch64', + 'armv7a': 'arm', + 'i686': 'x86', + 'x86_64': 'x86_64', + 'riscv64': 'riscv64', +} + +class AndroidDetector: + def __init__(self, options: T.Any): + import platform + self.platform = platform.system().lower() + self.options = options + + if self.platform == 'windows': + self.build_machine_id = 'windows-X86_64' + self.command_suffix = '.cmd' + self.exe_suffix = '.exe' + elif self.platform == 'darwin': + self.build_machine_id = 'darwin-x86_64' # Yes, even on aarch64 for some reason + self.command_suffix = '' + self.exe_suffix = '' + elif self.platform == 'linux': + self.build_machine_id = 'linux-x86_64' + self.command_suffix = '' + self.exe_suffix = '' + else: + sys.exit('Android lookup only supported on Linux, Windows and macOS. Patches welcome.') + self.outdir = pathlib.Path(options.outfile) + + def detect_android_sdk_root(self) -> None: + home = pathlib.Path.home() + if self.platform == 'windows': + sdk_root = home / 'AppData/Local/Android/Sdk' + elif self.platform == 'darwin': + sdk_root = home / 'Library/Android/Sdk' + elif self.platform == 'linux': + sdk_root = home / 'Android/Sdk' + else: + sys.exit('Unsupported platform.') + if not sdk_root.is_dir(): + sys.exit(f'Could not locate Android SDK root in {sdk_root}.') + ndk_root = sdk_root / 'ndk' + if not ndk_root.is_dir(): + sys.exit(f'Could not locate Android ndk in {ndk_root}') + self.ndk_root = ndk_root + + def detect_toolchains(self) -> None: + self.detect_android_sdk_root() + if not self.outdir.is_dir(): + self.outdir.mkdir() + for ndk in self.ndk_root.glob('*'): + if not ndk.is_dir(): + continue + self.process_ndk(ndk) + + def process_ndk(self, ndk: pathlib.Path) -> None: + ndk_version = ndk.parts[-1] + toolchain_root = ndk / f'toolchains/llvm/prebuilt/{self.build_machine_id}' + bindir = toolchain_root / 'bin' + if not bindir.is_dir(): + sys.exit(f'Could not detect toolchain in {toolchain_root}.') + ar_path = bindir / f'llvm-ar{self.exe_suffix}' + if not ar_path.is_file(): + sys.exit(f'Could not detect llvm-ar in {toolchain_root}.') + ar_str = str(ar_path).replace('\\', '/') + strip_path = bindir / f'llvm-strip{self.exe_suffix}' + if not strip_path.is_file(): + sys.exit(f'Could not detect llvm-strip n {toolchain_root}.') + strip_str = str(strip_path).replace('\\', '/') + for compiler in bindir.glob('*-clang++'): + parts = compiler.parts[-1].split('-') + assert len(parts) == 4 + cpu = parts[0] + assert parts[1] == 'linux' + android_version = parts[2] + cpp_compiler_str = str(compiler).replace('\\', '/') + c_compiler_str = cpp_compiler_str[:-2] + cpp_compiler_str += self.command_suffix + c_compiler_str += self.command_suffix + crossfile_name = f'android-{ndk_version}-{android_version}-{cpu}-cross.txt' + with open(pathlib.Path(self.options.outfile) / crossfile_name, 'w', encoding='utf-8') as ofile: + ofile.write('[binaries]\n') + ofile.write(f"c = '{c_compiler_str}'\n") + ofile.write(f"cpp = '{cpp_compiler_str}'\n") + ofile.write(f"ar = '{ar_str}'\n") + ofile.write(f"strip = '{strip_str}'\n") + + ofile.write('\n[host_machine]\n') + ofile.write("system = 'android'\n") + ofile.write(f"cpu_family = '{ANDROID_CPU_TO_MESON_CPU_FAMILY[cpu]}'\n") + ofile.write(f"cpu = '{cpu}'\n") + ofile.write("endian = 'little'\n") + + def run(options: T.Any) -> None: if options.cross and options.native: sys.exit('You can only specify either --cross or --native, not both.') - if not options.cross and not options.native: - sys.exit('You must specify --cross or --native.') + if (options.cross or options.native) and options.android: + sys.exit('You can not specify either --cross or --native with --android.') + if not options.cross and not options.native and not options.android: + sys.exit('You must specify --cross, --native or --android.') mlog.notice('This functionality is experimental and subject to change.') detect_cross = options.cross if detect_cross: @@ -441,7 +542,11 @@ def run(options: T.Any) -> None: sys.exit('--use-for-build only makes sense for --native, not --cross') infos = detect_cross_env(options) write_system_info = True - else: + write_machine_file(infos, options.outfile, write_system_info) + elif options.android is None: infos = detect_native_env(options) write_system_info = False - write_machine_file(infos, options.outfile, write_system_info) + write_machine_file(infos, options.outfile, write_system_info) + else: + ad = AndroidDetector(options) + ad.detect_toolchains() diff --git a/mesonbuild/scripts/externalproject.py b/mesonbuild/scripts/externalproject.py index 4013b0a..b782a85 100644 --- a/mesonbuild/scripts/externalproject.py +++ b/mesonbuild/scripts/externalproject.py @@ -10,6 +10,7 @@ from pathlib import Path import typing as T from ..mesonlib import Popen_safe, split_args, determine_worker_count +from .. import mlog class ExternalProject: def __init__(self, options: argparse.Namespace): @@ -67,10 +68,10 @@ class ExternalProject: def _run(self, step: str, command: T.List[str], env: T.Optional[T.Dict[str, str]] = None) -> int: m = 'Running command ' + str(command) + ' in directory ' + str(self.build_dir) + '\n' - log_filename = Path(self.log_dir, f'{self.name}-{step}.log') + logfile = Path(self.log_dir, f'{self.name}-{step}.log') output = None if not self.verbose: - output = open(log_filename, 'w', encoding='utf-8') + output = open(logfile, 'w', encoding='utf-8') output.write(m + '\n') output.flush() else: @@ -84,8 +85,11 @@ class ExternalProject: if p.returncode != 0: m = f'{step} step returned error code {p.returncode}.' if not self.verbose: - m += '\nSee logs: ' + str(log_filename) + m += '\nSee logs: ' + str(logfile) print(m) + contents = mlog.ci_fold_file(logfile, f'CI platform detected, click here for {os.path.basename(logfile)} contents.') + if contents: + print(contents) return p.returncode def run(args: T.List[str]) -> int: diff --git a/mesonbuild/scripts/pickle_env.py b/mesonbuild/scripts/pickle_env.py new file mode 100644 index 0000000..79e613f --- /dev/null +++ b/mesonbuild/scripts/pickle_env.py @@ -0,0 +1,8 @@ +import os +import pickle +import typing as T + +def run(args: T.List[str]) -> int: + with open(args[0], "wb") as f: + pickle.dump(dict(os.environ), f) + return 0 diff --git a/mesonbuild/scripts/run_tool.py b/mesonbuild/scripts/run_tool.py index e206ff7..4b3878c 100644 --- a/mesonbuild/scripts/run_tool.py +++ b/mesonbuild/scripts/run_tool.py @@ -19,14 +19,14 @@ import typing as T Info = T.TypeVar("Info") -async def run_with_buffered_output(cmdlist: T.List[str]) -> int: +async def run_with_buffered_output(cmdlist: T.List[str], env: T.Optional[T.Dict[str, str]] = None) -> int: """Run the command in cmdlist, buffering the output so that it is not mixed for multiple child processes. Kill the child on cancellation.""" quoted_cmdline = join_args(cmdlist) p: T.Optional[asyncio.subprocess.Process] = None try: - p = await asyncio.create_subprocess_exec(*cmdlist, + p = await asyncio.create_subprocess_exec(*cmdlist, env=env, stdin=asyncio.subprocess.DEVNULL, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT) @@ -98,7 +98,7 @@ def parse_pattern_file(fname: Path) -> T.List[str]: def all_clike_files(name: str, srcdir: Path, builddir: Path) -> T.Iterable[Path]: patterns = parse_pattern_file(srcdir / f'.{name}-include') - globs: T.Union[T.List[T.List[Path]], T.List[T.Generator[Path, None, None]]] + globs: T.Sequence[T.Union[T.List[Path], T.Iterator[Path], T.Generator[Path, None, None]]] if patterns: globs = [srcdir.glob(p) for p in patterns] else: @@ -121,16 +121,36 @@ def all_clike_files(name: str, srcdir: Path, builddir: Path) -> T.Iterable[Path] yield f def run_clang_tool(name: str, srcdir: Path, builddir: Path, fn: T.Callable[..., T.Coroutine[None, None, int]], *args: T.Any) -> int: - if sys.platform == 'win32': + if sys.platform == 'win32' and sys.version_info < (3, 8): asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) def wrapper(path: Path) -> T.Iterable[T.Coroutine[None, None, int]]: yield fn(path, *args) return asyncio.run(_run_workers(all_clike_files(name, srcdir, builddir), wrapper)) +def run_clang_tool_on_sources(name: str, srcdir: Path, builddir: Path, fn: T.Callable[..., T.Coroutine[None, None, int]], *args: T.Any) -> int: + if sys.platform == 'win32' and sys.version_info < (3, 8): + asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) + + source_files = set() + with open('meson-info/intro-targets.json', encoding='utf-8') as fp: + targets = json.load(fp) + + for target in targets: + for target_source in target.get('target_sources') or []: + for source in target_source.get('sources') or []: + source_files.add(Path(source)) + + clike_files = set(all_clike_files(name, srcdir, builddir)) + source_files = source_files.intersection(clike_files) + + def wrapper(path: Path) -> T.Iterable[T.Coroutine[None, None, int]]: + yield fn(path, *args) + return asyncio.run(_run_workers(source_files, wrapper)) + def run_tool_on_targets(fn: T.Callable[[T.Dict[str, T.Any]], T.Iterable[T.Coroutine[None, None, int]]]) -> int: - if sys.platform == 'win32': + if sys.platform == 'win32' and sys.version_info < (3, 8): asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) with open('meson-info/intro-targets.json', encoding='utf-8') as fp: diff --git a/mesonbuild/scripts/rustdoc.py b/mesonbuild/scripts/rustdoc.py index f5f74c4..c1c0d8c 100644 --- a/mesonbuild/scripts/rustdoc.py +++ b/mesonbuild/scripts/rustdoc.py @@ -15,8 +15,8 @@ from ..wrap import WrapMode, wrap if T.TYPE_CHECKING: from ..compilers.rust import RustCompiler -async def run_and_confirm_success(cmdlist: T.List[str], crate: str) -> int: - returncode = await run_with_buffered_output(cmdlist) +async def run_and_confirm_success(cmdlist: T.List[str], environ: T.Dict[str, str], crate: str) -> int: + returncode = await run_with_buffered_output(cmdlist, environ) if returncode == 0: print(mlog.green('Generated'), os.path.join('doc', crate)) return returncode @@ -31,7 +31,7 @@ class Rustdoc: compilers = build.environment.coredata.compilers[machine] if 'rust' in compilers: compiler = T.cast('RustCompiler', compilers['rust']) - self.tools[machine] = compiler.get_rust_tool('rustdoc', build.environment) + self.tools[machine] = compiler.get_rust_tool('rustdoc') def warn_missing_rustdoc(self, machine: str) -> None: if self.warned[machine]: @@ -54,18 +54,25 @@ class Rustdoc: prev = None crate_name = None is_test = False + environ = dict(os.environ) for arg in src_block['parameters']: if prev: if prev == '--crate-name': cmdlist.extend((prev, arg)) crate_name = arg + elif prev == '--env-set': + try: + key, val = arg.split('=', maxsplit=1) + environ[key] = val + except ValueError: + pass prev = None continue if arg == '--test': is_test = True break - elif arg in {'--crate-name', '--emit', '--out-dir', '-l'}: + elif arg in {'--crate-name', '--emit', '--out-dir', '-l', '--env-set'}: prev = arg elif arg != '-g' and not arg.startswith('-l'): cmdlist.append(arg) @@ -80,7 +87,7 @@ class Rustdoc: cmdlist.append('--document-private-items') cmdlist.append('-o') cmdlist.append('doc') - yield run_and_confirm_success(cmdlist, crate_name) + yield run_and_confirm_success(cmdlist, environ, crate_name) else: print(mlog.yellow('Skipping'), target['name'], '(no crate name)') diff --git a/mesonbuild/scripts/scanbuild.py b/mesonbuild/scripts/scanbuild.py index b738aee..3bf7918 100644 --- a/mesonbuild/scripts/scanbuild.py +++ b/mesonbuild/scripts/scanbuild.py @@ -6,10 +6,9 @@ from __future__ import annotations import subprocess import shutil import tempfile -from ..environment import detect_ninja, detect_scanbuild -from ..coredata import get_cmd_line_file -from ..machinefile import CmdLineFileParser -from ..mesonlib import windows_proof_rmtree +from ..cmdline import get_cmd_line_file, CmdLineFileParser +from ..tooldetect import detect_ninja, detect_scanbuild +from ..mesonlib import windows_proof_rmtree, determine_worker_count from pathlib import Path import typing as T from ast import literal_eval @@ -20,7 +19,7 @@ def scanbuild(exelist: T.List[str], srcdir: Path, blddir: Path, privdir: Path, l # so it can be debugged. scandir = tempfile.mkdtemp(dir=str(privdir)) meson_cmd = exelist + args - build_cmd = exelist + ['--exclude', str(subprojdir), '-o', str(logdir)] + detect_ninja() + ['-C', scandir] + build_cmd = exelist + ['--exclude', str(subprojdir), '-o', str(logdir)] + detect_ninja() + ['-C', scandir, f'-j{determine_worker_count()}'] rc = subprocess.call(meson_cmd + [str(srcdir), scandir]) if rc != 0: return rc diff --git a/mesonbuild/scripts/symbolextractor.py b/mesonbuild/scripts/symbolextractor.py index b0a07d9..3ceea4b 100644 --- a/mesonbuild/scripts/symbolextractor.py +++ b/mesonbuild/scripts/symbolextractor.py @@ -23,7 +23,7 @@ parser.add_argument('--cross-host', default=None, dest='cross_host', help='cross compilation host platform') parser.add_argument('args', nargs='+') -TOOL_WARNING_FILE = None +TOOL_WARNING_FILE: str RELINKING_WARNING = 'Relinking will always happen on source changes.' def dummy_syms(outfilename: str) -> None: @@ -273,7 +273,7 @@ def gen_symbols(libfilename: str, impfilename: str, outfilename: str, cross_host windows_syms(impfilename, outfilename) else: dummy_syms(outfilename) - elif mesonlib.is_linux() or mesonlib.is_hurd(): + elif mesonlib.is_linux() or mesonlib.is_hurd() or mesonlib.is_haiku(): gnu_syms(libfilename, outfilename) elif mesonlib.is_osx(): osx_syms(libfilename, outfilename) diff --git a/mesonbuild/scripts/vcstagger.py b/mesonbuild/scripts/vcstagger.py index 26c1e81..dacf231 100644 --- a/mesonbuild/scripts/vcstagger.py +++ b/mesonbuild/scripts/vcstagger.py @@ -8,8 +8,8 @@ import typing as T def config_vcs_tag(infile: str, outfile: str, fallback: str, source_dir: str, replace_string: str, regex_selector: str, cmd: T.List[str]) -> None: try: - output = subprocess.check_output(cmd, cwd=source_dir) - new_string = re.search(regex_selector, output.decode()).group(1).strip() + output = subprocess.check_output(cmd, cwd=source_dir, stderr=subprocess.DEVNULL) + new_string = re.search(regex_selector, output.decode()).group(1).rstrip('\r\n') except Exception: new_string = fallback diff --git a/mesonbuild/templates/cpptemplates.py b/mesonbuild/templates/cpptemplates.py index 1bfa2ae..79f88a5 100644 --- a/mesonbuild/templates/cpptemplates.py +++ b/mesonbuild/templates/cpptemplates.py @@ -16,7 +16,7 @@ hello_cpp_template = '''#include <iostream> int main(int argc, char **argv) {{ if (argc != 1) {{ - std::cout << argv[0] << "takes no arguments.\\n"; + std::cout << argv[0] << " takes no arguments.\\n"; return 1; }} std::cout << "This is project " << PROJECT_NAME << ".\\n"; @@ -35,9 +35,12 @@ hello_cpp_meson_template = '''project( dependencies = [{dependencies} ] +sources = [{source_files} +] + exe = executable( '{exe_name}', - '{source_name}', + sources, install : true, dependencies : dependencies, ) @@ -52,6 +55,12 @@ lib_hpp_template = '''#pragma once #else #define {utoken}_PUBLIC __declspec(dllimport) #endif +#elif defined __OS2__ + #ifdef BUILDING_{utoken} + #define {utoken}_PUBLIC __declspec(dllexport) + #else + #define {utoken}_PUBLIC + #endif #else #ifdef BUILDING_{utoken} #define {utoken}_PUBLIC __attribute__ ((visibility ("default"))) @@ -121,9 +130,12 @@ dependencies = [{dependencies} # not the executables that use the library. lib_args = ['-DBUILDING_{utoken}'] +sources = [{source_files} +] + lib = library( '{lib_name}', - '{source_file}', + sources, install : true, cpp_shared_args : lib_args, gnu_symbol_visibility : 'hidden', diff --git a/mesonbuild/templates/cstemplates.py b/mesonbuild/templates/cstemplates.py index 59c7189..e44cf6f 100644 --- a/mesonbuild/templates/cstemplates.py +++ b/mesonbuild/templates/cstemplates.py @@ -35,9 +35,12 @@ hello_cs_meson_template = '''project( dependencies = [{dependencies} ] +sources = [{source_files} +] + exe = executable( '{exe_name}', - '{source_name}', + sources, install : true, dependencies : dependencies, ) @@ -83,9 +86,12 @@ lib_cs_meson_template = '''project( dependencies = [{dependencies} ] +sources = [{source_files} +] + stlib = shared_library( '{lib_name}', - '{source_file}', + sources, dependencies : dependencies, install : true, ) diff --git a/mesonbuild/templates/ctemplates.py b/mesonbuild/templates/ctemplates.py index 559cef9..3db6477 100644 --- a/mesonbuild/templates/ctemplates.py +++ b/mesonbuild/templates/ctemplates.py @@ -18,6 +18,12 @@ lib_h_template = '''#pragma once #else #define {utoken}_PUBLIC __declspec(dllimport) #endif +#elif defined __OS2__ + #ifdef BUILDING_{utoken} + #define {utoken}_PUBLIC __declspec(dllexport) + #else + #define {utoken}_PUBLIC + #endif #else #ifdef BUILDING_{utoken} #define {utoken}_PUBLIC __attribute__ ((visibility ("default"))) @@ -71,9 +77,12 @@ lib_args = ['-DBUILDING_{utoken}'] dependencies = [{dependencies} ] +sources = [{source_files} +] + lib = library( '{lib_name}', - '{source_file}', + sources, install : true, c_shared_args : lib_args, gnu_symbol_visibility : 'hidden', @@ -133,9 +142,12 @@ hello_c_meson_template = '''project( dependencies = [{dependencies} ] +sources = [{source_files} +] + exe = executable( '{exe_name}', - '{source_name}', + sources, dependencies : dependencies, install : true, ) diff --git a/mesonbuild/templates/cudatemplates.py b/mesonbuild/templates/cudatemplates.py index 252f44a..d10fb76 100644 --- a/mesonbuild/templates/cudatemplates.py +++ b/mesonbuild/templates/cudatemplates.py @@ -36,9 +36,12 @@ hello_cuda_meson_template = '''project( dependencies = [{dependencies} ] +sources = [{source_files} +] + exe = executable( '{exe_name}', - '{source_name}', + sources, dependencies : dependencies, install : true, ) @@ -122,9 +125,12 @@ lib_args = ['-DBUILDING_{utoken}'] dependencies = [{dependencies} ] +sources = [{source_files} +] + lib = library( '{lib_name}', - '{source_file}', + sources, install : true, cpp_shared_args : lib_args, gnu_symbol_visibility : 'hidden', diff --git a/mesonbuild/templates/dlangtemplates.py b/mesonbuild/templates/dlangtemplates.py index db3bdbf..727ee20 100644 --- a/mesonbuild/templates/dlangtemplates.py +++ b/mesonbuild/templates/dlangtemplates.py @@ -35,9 +35,12 @@ hello_d_meson_template = '''project( dependencies = [{dependencies} ] +sources = [{source_files} +] + exe = executable( '{exe_name}', - '{source_name}', + sources, dependencies : dependencies, install : true, ) @@ -84,10 +87,12 @@ lib_d_meson_template = '''project( dependencies = [{dependencies} ] +sources = [{source_files} +] stlib = static_library( '{lib_name}', - '{source_file}', + sources, install : true, gnu_symbol_visibility : 'hidden', dependencies : dependencies, diff --git a/mesonbuild/templates/fortrantemplates.py b/mesonbuild/templates/fortrantemplates.py index 7aaa9d3..03f0ff9 100644 --- a/mesonbuild/templates/fortrantemplates.py +++ b/mesonbuild/templates/fortrantemplates.py @@ -56,9 +56,12 @@ lib_args = ['-DBUILDING_{utoken}'] dependencies = [{dependencies} ] +sources = [{source_files} +] + lib = library( '{lib_name}', - '{source_file}', + sources, install : true, fortran_shared_args : lib_args, gnu_symbol_visibility : 'hidden', @@ -110,9 +113,12 @@ hello_fortran_meson_template = '''project( dependencies = [{dependencies} ] +sources = [{source_files} +] + exe = executable( '{exe_name}', - '{source_name}', + sources, dependencies : dependencies, install : true, ) diff --git a/mesonbuild/templates/javatemplates.py b/mesonbuild/templates/javatemplates.py index c30c7f7..ed32194 100644 --- a/mesonbuild/templates/javatemplates.py +++ b/mesonbuild/templates/javatemplates.py @@ -35,9 +35,12 @@ hello_java_meson_template = '''project( dependencies = [{dependencies} ] +sources = [{source_files} +] + exe = jar( '{exe_name}', - '{source_name}', + sources, main_class : '{exe_name}', dependencies : dependencies, install : true, @@ -86,9 +89,12 @@ lib_java_meson_template = '''project( dependencies = [{dependencies} ] +sources = [{source_files} +] + jarlib = jar( '{class_name}', - '{source_file}', + sources, dependencies : dependencies, main_class : '{class_name}', install : true, diff --git a/mesonbuild/templates/objcpptemplates.py b/mesonbuild/templates/objcpptemplates.py index 1fdfa06..d80c374 100644 --- a/mesonbuild/templates/objcpptemplates.py +++ b/mesonbuild/templates/objcpptemplates.py @@ -18,6 +18,12 @@ lib_h_template = '''#pragma once #else #define {utoken}_PUBLIC __declspec(dllimport) #endif +#elif defined __OS2__ + #ifdef BUILDING_{utoken} + #define {utoken}_PUBLIC __declspec(dllexport) + #else + #define {utoken}_PUBLIC + #endif #else #ifdef BUILDING_{utoken} #define {utoken}_PUBLIC __attribute__ ((visibility ("default"))) @@ -67,13 +73,16 @@ lib_objcpp_meson_template = '''project( dependencies = [{dependencies} ] +sources = [{source_files} +] + # These arguments are only used to build the shared library # not the executables that use the library. lib_args = ['-DBUILDING_{utoken}'] lib = library( '{lib_name}', - '{source_file}', + sources, install : true, objcpp_shared_args : lib_args, dependencies : dependencies, @@ -133,9 +142,12 @@ hello_objcpp_meson_template = '''project( dependencies = [{dependencies} ] +sources = [{source_files} +] + exe = executable( '{exe_name}', - '{source_name}', + sources, dependencies : dependencies, install : true, ) diff --git a/mesonbuild/templates/objctemplates.py b/mesonbuild/templates/objctemplates.py index 5603bae..7dc3d4f 100644 --- a/mesonbuild/templates/objctemplates.py +++ b/mesonbuild/templates/objctemplates.py @@ -18,6 +18,12 @@ lib_h_template = '''#pragma once #else #define {utoken}_PUBLIC __declspec(dllimport) #endif +#elif defined __OS2__ + #ifdef BUILDING_{utoken} + #define {utoken}_PUBLIC __declspec(dllexport) + #else + #define {utoken}_PUBLIC + #endif #else #ifdef BUILDING_{utoken} #define {utoken}_PUBLIC __attribute__ ((visibility ("default"))) @@ -67,13 +73,16 @@ lib_objc_meson_template = '''project( dependencies = [{dependencies} ] +sources = [{source_files} +] + # These arguments are only used to build the shared library # not the executables that use the library. lib_args = ['-DBUILDING_{utoken}'] lib = library( '{lib_name}', - '{source_file}', + sources, install : true, objc_shared_args : lib_args, dependencies : dependencies, @@ -132,9 +141,12 @@ hello_objc_meson_template = '''project( dependencies = [{dependencies} ] +sources = [{source_files} +] + exe = executable( '{exe_name}', - '{source_name}', + sources, dependencies : dependencies, install : true, ) diff --git a/mesonbuild/templates/rusttemplates.py b/mesonbuild/templates/rusttemplates.py index ee1f008..b415be7 100644 --- a/mesonbuild/templates/rusttemplates.py +++ b/mesonbuild/templates/rusttemplates.py @@ -50,9 +50,12 @@ rust = import('rust') dependencies = [{dependencies} ] +sources = [{source_files} +] + lib = static_library( '{lib_name}', - '{source_file}', + sources, dependencies : dependencies, install : true, ) @@ -86,9 +89,12 @@ hello_rust_meson_template = '''project( dependencies = [{dependencies} ] +sources = [{source_files} +] + exe = executable( '{exe_name}', - '{source_name}', + sources, dependencies : dependencies, install : true, ) diff --git a/mesonbuild/templates/sampleimpl.py b/mesonbuild/templates/sampleimpl.py index d033f3c..46f9a70 100644 --- a/mesonbuild/templates/sampleimpl.py +++ b/mesonbuild/templates/sampleimpl.py @@ -3,6 +3,7 @@ # Copyright © 2023-2025 Intel Corporation from __future__ import annotations +from pathlib import Path import abc import os @@ -17,6 +18,7 @@ class SampleImpl(metaclass=abc.ABCMeta): def __init__(self, args: Arguments): self.name = args.name + self.executable_name = args.executable if args.executable else None self.version = args.version self.lowercase_token = re.sub(r'[^a-z0-9]', '_', self.name.lower()) self.uppercase_token = self.lowercase_token.upper() @@ -24,6 +26,7 @@ class SampleImpl(metaclass=abc.ABCMeta): self.meson_version = '1.0.0' self.force = args.force self.dependencies = args.deps.split(',') if args.deps else [] + self.sources: list[Path] = [] @abc.abstractmethod def create_executable(self) -> None: @@ -66,11 +69,18 @@ class SampleImpl(metaclass=abc.ABCMeta): def _format_dependencies(self) -> str: return ''.join(f"\n dependency('{d}')," for d in self.dependencies) + def _format_sources(self) -> str: + return ''.join(f"\n '{x}'," for x in self.sources) + class ClassImpl(SampleImpl): """For Class based languages, like Java and C#""" + def __init__(self, args: Arguments): + super().__init__(args) + self.sources = args.srcfiles if args.srcfiles else [Path(f'{self.capitalized_token}.{self.source_ext}')] + def create_executable(self) -> None: source_name = f'{self.capitalized_token}.{self.source_ext}' if not os.path.exists(source_name): @@ -80,29 +90,32 @@ class ClassImpl(SampleImpl): if self.force or not os.path.exists('meson.build'): with open('meson.build', 'w', encoding='utf-8') as f: f.write(self.exe_meson_template.format(project_name=self.name, - exe_name=self.name, + exe_name=self.executable_name, source_name=source_name, version=self.version, meson_version=self.meson_version, - dependencies=self._format_dependencies())) + dependencies=self._format_dependencies(), + source_files=self._format_sources())) def create_library(self) -> None: lib_name = f'{self.capitalized_token}.{self.source_ext}' test_name = f'{self.capitalized_token}_test.{self.source_ext}' - kwargs = {'utoken': self.uppercase_token, - 'ltoken': self.lowercase_token, - 'class_test': f'{self.capitalized_token}_test', - 'class_name': self.capitalized_token, - 'source_file': lib_name, - 'test_source_file': test_name, - 'test_exe_name': f'{self.lowercase_token}_test', - 'project_name': self.name, - 'lib_name': self.lowercase_token, - 'test_name': self.lowercase_token, - 'version': self.version, - 'meson_version': self.meson_version, - 'dependencies': self._format_dependencies(), - } + kwargs = { + 'utoken': self.uppercase_token, + 'ltoken': self.lowercase_token, + 'class_test': f'{self.capitalized_token}_test', + 'class_name': self.capitalized_token, + 'source_file': lib_name, + 'test_source_file': test_name, + 'test_exe_name': f'{self.lowercase_token}_test', + 'project_name': self.name, + 'lib_name': self.lowercase_token, + 'test_name': self.lowercase_token, + 'version': self.version, + 'meson_version': self.meson_version, + 'dependencies': self._format_dependencies(), + 'source_files': self._format_sources(), + } if not os.path.exists(lib_name): with open(lib_name, 'w', encoding='utf-8') as f: f.write(self.lib_template.format(**kwargs)) @@ -118,6 +131,10 @@ class FileImpl(SampleImpl): """File based languages without headers""" + def __init__(self, args: Arguments): + super().__init__(args) + self.sources = args.srcfiles if args.srcfiles else [Path(f'{self.name}.{self.source_ext}')] + def create_executable(self) -> None: source_name = f'{self.lowercase_token}.{self.source_ext}' if not os.path.exists(source_name): @@ -126,11 +143,12 @@ class FileImpl(SampleImpl): if self.force or not os.path.exists('meson.build'): with open('meson.build', 'w', encoding='utf-8') as f: f.write(self.exe_meson_template.format(project_name=self.name, - exe_name=self.name, + exe_name=self.executable_name, source_name=source_name, version=self.version, meson_version=self.meson_version, - dependencies=self._format_dependencies())) + dependencies=self._format_dependencies(), + source_files=self._format_sources())) def lib_kwargs(self) -> T.Dict[str, str]: """Get Language specific keyword arguments @@ -153,6 +171,7 @@ class FileImpl(SampleImpl): 'version': self.version, 'meson_version': self.meson_version, 'dependencies': self._format_dependencies(), + 'source_files': self._format_sources(), } def create_library(self) -> None: diff --git a/mesonbuild/templates/valatemplates.py b/mesonbuild/templates/valatemplates.py index b2aab3f..de31877 100644 --- a/mesonbuild/templates/valatemplates.py +++ b/mesonbuild/templates/valatemplates.py @@ -24,9 +24,12 @@ dependencies = [ dependency('gobject-2.0'),{dependencies} ] +sources = [{source_files} +] + exe = executable( '{exe_name}', - '{source_name}', + sources, dependencies : dependencies, install : true, ) @@ -67,11 +70,14 @@ dependencies = [ dependency('gobject-2.0'),{dependencies} ] +sources = [{source_files} +] + # These arguments are only used to build the shared library # not the executables that use the library. lib = shared_library( 'foo', - '{source_file}', + sources, dependencies : dependencies, install : true, install_dir : [true, true, true], diff --git a/mesonbuild/tooldetect.py b/mesonbuild/tooldetect.py new file mode 100644 index 0000000..198a6ee --- /dev/null +++ b/mesonbuild/tooldetect.py @@ -0,0 +1,248 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2012-2020 The Meson development team +# Copyright © 2023-2025 Intel Corporation + +from __future__ import annotations + +import os +import shutil +import typing as T + +from . import coredata +from . import mesonlib +from . import mlog +from .mesonlib import MachineChoice, Popen_safe, search_version, quote_arg, split_args +from .programs import ExternalProgram + + +def detect_gcovr(gcovr_exe: str = 'gcovr', min_version: str = '3.3', log: bool = False) \ + -> T.Union[T.Tuple[None, None], T.Tuple[str, str]]: + try: + p, found = Popen_safe([gcovr_exe, '--version'])[0:2] + except (FileNotFoundError, PermissionError): + # Doesn't exist in PATH or isn't executable + return None, None + found = search_version(found) + if p.returncode == 0 and mesonlib.version_compare(found, '>=' + min_version): + if log: + mlog.log('Found gcovr-{} at {}'.format(found, quote_arg(shutil.which(gcovr_exe)))) + return gcovr_exe, found + return None, None + +def detect_lcov(lcov_exe: str = 'lcov', log: bool = False) \ + -> T.Union[T.Tuple[None, None], T.Tuple[str, str]]: + try: + p, found = Popen_safe([lcov_exe, '--version'])[0:2] + except (FileNotFoundError, PermissionError): + # Doesn't exist in PATH or isn't executable + return None, None + found = search_version(found) + if p.returncode == 0 and found: + if log: + mlog.log('Found lcov-{} at {}'.format(found, quote_arg(shutil.which(lcov_exe)))) + return lcov_exe, found + return None, None + +def detect_llvm_cov(suffix: T.Optional[str] = None) -> T.Optional[str]: + # If there's a known suffix or forced lack of suffix, use that + if suffix is not None: + if suffix == '': + tool = 'llvm-cov' + else: + tool = f'llvm-cov-{suffix}' + if shutil.which(tool) is not None: + return tool + else: + # Otherwise guess in the dark + tools = get_llvm_tool_names('llvm-cov') + for tool in tools: + if shutil.which(tool): + return tool + return None + +def compute_llvm_suffix(coredata: coredata.CoreData) -> T.Optional[str]: + # Check to see if the user is trying to do coverage for either a C or C++ project + compilers = coredata.compilers[MachineChoice.BUILD] + cpp_compiler_is_clang = 'cpp' in compilers and compilers['cpp'].id == 'clang' + c_compiler_is_clang = 'c' in compilers and compilers['c'].id == 'clang' + # Extract first the C++ compiler if available. If it's a Clang of some kind, compute the suffix if possible + if cpp_compiler_is_clang: + suffix = compilers['cpp'].version.split('.')[0] + return suffix + + # Then the C compiler, again checking if it's some kind of Clang and computing the suffix + if c_compiler_is_clang: + suffix = compilers['c'].version.split('.')[0] + return suffix + + # Neither compiler is a Clang, or no compilers are for C or C++ + return None + +def detect_lcov_genhtml(lcov_exe: str = 'lcov', genhtml_exe: str = 'genhtml') \ + -> T.Tuple[str, T.Optional[str], str]: + lcov_exe, lcov_version = detect_lcov(lcov_exe) + if shutil.which(genhtml_exe) is None: + genhtml_exe = None + + return lcov_exe, lcov_version, genhtml_exe + +def find_coverage_tools(coredata: coredata.CoreData) -> T.Tuple[T.Optional[str], T.Optional[str], T.Optional[str], T.Optional[str], T.Optional[str], T.Optional[str]]: + gcovr_exe, gcovr_version = detect_gcovr() + + llvm_cov_exe = detect_llvm_cov(compute_llvm_suffix(coredata)) + # Some platforms may provide versioned clang but only non-versioned llvm utils + if llvm_cov_exe is None: + llvm_cov_exe = detect_llvm_cov('') + + lcov_exe, lcov_version, genhtml_exe = detect_lcov_genhtml() + + return gcovr_exe, gcovr_version, lcov_exe, lcov_version, genhtml_exe, llvm_cov_exe + +def detect_ninja(version: str = '1.8.2', log: bool = False) -> T.Optional[T.List[str]]: + r = detect_ninja_command_and_version(version, log) + return r[0] if r else None + +def detect_ninja_command_and_version(version: str = '1.8.2', log: bool = False) -> T.Optional[T.Tuple[T.List[str], str]]: + env_ninja = os.environ.get('NINJA', None) + for n in [env_ninja] if env_ninja else ['ninja', 'ninja-build', 'samu']: + prog = ExternalProgram(n, silent=True) + if not prog.found(): + continue + try: + p, found = Popen_safe(prog.command + ['--version'])[0:2] + except (FileNotFoundError, PermissionError): + # Doesn't exist in PATH or isn't executable + continue + found = found.strip() + # Perhaps we should add a way for the caller to know the failure mode + # (not found or too old) + if p.returncode == 0 and mesonlib.version_compare(found, '>=' + version): + if log: + name = os.path.basename(n) + if name.endswith('-' + found): + name = name[0:-1 - len(found)] + if name == 'ninja-build': + name = 'ninja' + if name == 'samu': + name = 'samurai' + mlog.log('Found {}-{} at {}'.format(name, found, + ' '.join([quote_arg(x) for x in prog.command]))) + return (prog.command, found) + return None + +def get_llvm_tool_names(tool: str) -> T.List[str]: + # Ordered list of possible suffixes of LLVM executables to try. Start with + # base, then try newest back to oldest (3.5 is arbitrary), and finally the + # devel version. Please note that the development snapshot in Debian does + # not have a distinct name. Do not move it to the beginning of the list + # unless it becomes a stable release. + suffixes = [ + '', # base (no suffix) + '-21.1', '21.1', + '-21', '21', + '-20.1', '20.1', + '-20', '20', + '-19.1', '19.1', + '-19', '19', + '-18.1', '18.1', + '-18', '18', + '-17', '17', + '-16', '16', + '-15', '15', + '-14', '14', + '-13', '13', + '-12', '12', + '-11', '11', + '-10', '10', + '-9', '90', + '-8', '80', + '-7', '70', + '-6.0', '60', + '-5.0', '50', + '-4.0', '40', + '-3.9', '39', + '-3.8', '38', + '-3.7', '37', + '-3.6', '36', + '-3.5', '35', + '-20', # Debian development snapshot + '-devel', # FreeBSD development snapshot + ] + names: T.List[str] = [] + for suffix in suffixes: + names.append(tool + suffix) + return names + +def detect_scanbuild() -> T.List[str]: + """ Look for scan-build binary on build platform + + First, if a SCANBUILD env variable has been provided, give it precedence + on all platforms. + + For most platforms, scan-build is found is the PATH contains a binary + named "scan-build". However, some distribution's package manager (FreeBSD) + don't. For those, loop through a list of candidates to see if one is + available. + + Return: a single-element list of the found scan-build binary ready to be + passed to Popen() + """ + exelist: T.List[str] = [] + if 'SCANBUILD' in os.environ: + exelist = split_args(os.environ['SCANBUILD']) + + else: + tools = get_llvm_tool_names('scan-build') + for tool in tools: + which = shutil.which(tool) + if which is not None: + exelist = [which] + break + + if exelist: + tool = exelist[0] + if os.path.isfile(tool) and os.access(tool, os.X_OK): + return [tool] + return [] + +def detect_clangformat() -> T.List[str]: + """ Look for clang-format binary on build platform + + Do the same thing as detect_scanbuild to find clang-format except it + currently does not check the environment variable. + + Return: a single-element list of the found clang-format binary ready to be + passed to Popen() + """ + tools = get_llvm_tool_names('clang-format') + for tool in tools: + path = shutil.which(tool) + if path is not None: + return [path] + return [] + +def detect_clangtidy() -> T.List[str]: + """ Look for clang-tidy binary on build platform + + Return: a single-element list of the found clang-tidy binary ready to be + passed to Popen() + """ + tools = get_llvm_tool_names('clang-tidy') + for tool in tools: + path = shutil.which(tool) + if path is not None: + return [path] + return [] + +def detect_clangapply() -> T.List[str]: + """ Look for clang-apply-replacements binary on build platform + + Return: a single-element list of the found clang-apply-replacements binary + ready to be passed to Popen() + """ + tools = get_llvm_tool_names('clang-apply-replacements') + for tool in tools: + path = shutil.which(tool) + if path is not None: + return [path] + return [] diff --git a/mesonbuild/utils/platform.py b/mesonbuild/utils/platform.py index 8e762b6..1a2780f 100644 --- a/mesonbuild/utils/platform.py +++ b/mesonbuild/utils/platform.py @@ -2,26 +2,113 @@ # Copyright 2012-2021 The Meson development team # Copyright © 2021-2023 Intel Corporation -from __future__ import annotations +"""Utility functions with platform specific implementations.""" -"""base classes providing no-op functionality..""" +from __future__ import annotations +import enum import os +import sys import typing as T from .. import mlog +from .core import MesonException + +__all__ = ['DirectoryLock', 'DirectoryLockAction'] + +class DirectoryLockAction(enum.Enum): + IGNORE = 0 + WAIT = 1 + FAIL = 2 -__all__ = ['BuildDirLock'] +class DirectoryLockBase: -# This needs to be inherited by the specific implementations to make type -# checking happy -class BuildDirLock: + lockfile: T.TextIO - def __init__(self, builddir: str) -> None: - self.lockfilename = os.path.join(builddir, 'meson-private/meson.lock') + def __init__(self, directory: str, lockfile: str, action: DirectoryLockAction, err: str, + optional: bool = False) -> None: + self.action = action + self.err = err + self.lockpath = os.path.join(directory, lockfile) + self.optional = optional def __enter__(self) -> None: - mlog.debug('Calling the no-op version of BuildDirLock') + mlog.debug('Calling the no-op version of DirectoryLock') def __exit__(self, *args: T.Any) -> None: pass + + +if sys.platform == 'win32': + import msvcrt + + class DirectoryLock(DirectoryLockBase): + + def __enter__(self) -> None: + try: + self.lockfile = open(self.lockpath, 'w+', encoding='utf-8') + except (FileNotFoundError, IsADirectoryError): + # For FileNotFoundError, there is nothing to lock. + # For IsADirectoryError, something is seriously wrong. + raise + except OSError: + if self.action == DirectoryLockAction.IGNORE or self.optional: + return + + try: + mode = msvcrt.LK_LOCK + if self.action != DirectoryLockAction.WAIT: + mode = msvcrt.LK_NBLCK + msvcrt.locking(self.lockfile.fileno(), mode, 1) + except BlockingIOError: + self.lockfile.close() + if self.action == DirectoryLockAction.IGNORE: + return + raise MesonException(self.err) + except PermissionError: + self.lockfile.close() + raise MesonException(self.err) + + def __exit__(self, *args: T.Any) -> None: + if self.lockfile is None or self.lockfile.closed: + return + msvcrt.locking(self.lockfile.fileno(), msvcrt.LK_UNLCK, 1) + self.lockfile.close() +else: + import fcntl + + class DirectoryLock(DirectoryLockBase): + + def __enter__(self) -> None: + try: + self.lockfile = open(self.lockpath, 'w+', encoding='utf-8') + except (FileNotFoundError, IsADirectoryError): + # For FileNotFoundError, there is nothing to lock. + # For IsADirectoryError, something is seriously wrong. + raise + except OSError: + if self.action == DirectoryLockAction.IGNORE or self.optional: + return + + try: + flags = fcntl.LOCK_EX + if self.action != DirectoryLockAction.WAIT: + flags = flags | fcntl.LOCK_NB + fcntl.flock(self.lockfile, flags) + except BlockingIOError: + self.lockfile.close() + if self.action == DirectoryLockAction.IGNORE: + return + raise MesonException(self.err) + except PermissionError: + self.lockfile.close() + raise MesonException(self.err) + except OSError as e: + self.lockfile.close() + raise MesonException(f'Failed to lock directory {self.lockpath}: {e.strerror}') + + def __exit__(self, *args: T.Any) -> None: + if self.lockfile is None or self.lockfile.closed: + return + fcntl.flock(self.lockfile, fcntl.LOCK_UN) + self.lockfile.close() diff --git a/mesonbuild/utils/posix.py b/mesonbuild/utils/posix.py deleted file mode 100644 index e8387ba..0000000 --- a/mesonbuild/utils/posix.py +++ /dev/null @@ -1,32 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# Copyright 2012-2021 The Meson development team -# Copyright © 2021-2023 Intel Corporation - -from __future__ import annotations - -"""Posix specific implementations of mesonlib functionality.""" - -import fcntl -import typing as T - -from .core import MesonException -from .platform import BuildDirLock as BuildDirLockBase - -__all__ = ['BuildDirLock'] - -class BuildDirLock(BuildDirLockBase): - - def __enter__(self) -> None: - self.lockfile = open(self.lockfilename, 'w', encoding='utf-8') - try: - fcntl.flock(self.lockfile, fcntl.LOCK_EX | fcntl.LOCK_NB) - except (BlockingIOError, PermissionError): - self.lockfile.close() - raise MesonException('Some other Meson process is already using this build directory. Exiting.') - except OSError as e: - self.lockfile.close() - raise MesonException(f'Failed to lock the build directory: {e.strerror}') - - def __exit__(self, *args: T.Any) -> None: - fcntl.flock(self.lockfile, fcntl.LOCK_UN) - self.lockfile.close() diff --git a/mesonbuild/utils/universal.py b/mesonbuild/utils/universal.py index 5b3f131..467d4f5 100644 --- a/mesonbuild/utils/universal.py +++ b/mesonbuild/utils/universal.py @@ -34,10 +34,11 @@ if T.TYPE_CHECKING: from .._typing import ImmutableListProtocol from ..build import ConfigurationData - from ..coredata import StrOrBytesPath + from ..cmdline import StrOrBytesPath from ..environment import Environment from ..compilers.compilers import Compiler from ..interpreterbase.baseobjects import SubProject + from .. import programs class _EnvPickleLoadable(Protocol): @@ -107,6 +108,7 @@ __all__ = [ 'get_compiler_for_source', 'get_filenames_templates_dict', 'get_rsp_threshold', + 'get_subproject_dir', 'get_variable_regex', 'get_wine_shortpath', 'git', @@ -124,6 +126,7 @@ __all__ = [ 'is_linux', 'is_netbsd', 'is_openbsd', + 'is_os2', 'is_osx', 'is_parent_path', 'is_qnx', @@ -150,9 +153,11 @@ __all__ = [ 'set_meson_command', 'split_args', 'stringlistify', + 'underscorify', 'substitute_values', 'substring_is_in_list', 'typeslistify', + 'unique_list', 'verbose_git', 'version_compare', 'version_compare_condition_with_min', @@ -432,7 +437,7 @@ class File(HoldableObject): absdir = srcdir if self.is_built: absdir = builddir - return os.path.join(absdir, self.relative_name()) + return os.path.normpath(os.path.join(absdir, self.relative_name())) @property def suffix(self) -> str: @@ -680,6 +685,9 @@ def is_qnx() -> bool: def is_aix() -> bool: return platform.system().lower() == 'aix' +def is_os2() -> bool: + return platform.system().lower() == 'os/2' + @lru_cache(maxsize=None) def darwin_get_object_archs(objpath: str) -> 'ImmutableListProtocol[str]': ''' @@ -756,6 +764,20 @@ class VcsData: rev_regex: str dep: str wc_dir: T.Optional[str] = None + repo_can_be_file: bool = False + + def repo_exists(self, curdir: Path) -> bool: + if not shutil.which(self.cmd): + return False + + repo = curdir / self.repo_dir + if repo.is_dir(): + return True + if repo.is_file() and self.repo_can_be_file: + return True + + return False + def detect_vcs(source_dir: T.Union[str, Path]) -> T.Optional[VcsData]: vcs_systems = [ @@ -766,6 +788,7 @@ def detect_vcs(source_dir: T.Union[str, Path]) -> T.Optional[VcsData]: get_rev = ['git', 'describe', '--dirty=+', '--always'], rev_regex = '(.*)', dep = '.git/logs/HEAD', + repo_can_be_file=True, ), VcsData( name = 'mercurial', @@ -801,9 +824,7 @@ def detect_vcs(source_dir: T.Union[str, Path]) -> T.Optional[VcsData]: parent_paths_and_self.appendleft(source_dir) for curdir in parent_paths_and_self: for vcs in vcs_systems: - repodir = vcs.repo_dir - cmd = vcs.cmd - if curdir.joinpath(repodir).is_dir() and shutil.which(cmd): + if vcs.repo_exists(curdir): vcs.wc_dir = str(curdir) return vcs return None @@ -1226,7 +1247,7 @@ def do_replacement(regex: T.Pattern[str], line: str, if variable_format == 'meson': return do_replacement_meson(regex, line, confdata) elif variable_format in {'cmake', 'cmake@'}: - return do_replacement_cmake(regex, line, variable_format == 'cmake@', confdata) + return do_replacement_cmake(line, variable_format == 'cmake@', confdata) else: raise MesonException('Invalid variable format') @@ -1251,6 +1272,9 @@ def do_replacement_meson(regex: T.Pattern[str], line: str, if isinstance(var, str): var_str = var elif isinstance(var, int): + if isinstance(var, bool): + msg = f'Variable substitution with boolean value {varname!r} is deprecated.' + mlog.deprecation(msg) var_str = str(var) else: msg = f'Tried to replace variable {varname!r} value with ' \ @@ -1261,44 +1285,92 @@ def do_replacement_meson(regex: T.Pattern[str], line: str, return var_str return re.sub(regex, variable_replace, line), missing_variables -def do_replacement_cmake(regex: T.Pattern[str], line: str, at_only: bool, +def do_replacement_cmake(line: str, at_only: bool, confdata: T.Union[T.Dict[str, T.Tuple[str, T.Optional[str]]], 'ConfigurationData']) -> T.Tuple[str, T.Set[str]]: missing_variables: T.Set[str] = set() - def variable_replace(match: T.Match[str]) -> str: - # Pairs of escape characters before '@', '\@', '${' or '\${' - if match.group(0).endswith('\\'): - num_escapes = match.end(0) - match.start(0) - return '\\' * (num_escapes // 2) - # Handle cmake escaped \${} tags - elif not at_only and match.group(0) == '\\${': - return '${' - # \@escaped\@ variables - elif match.groupdict().get('escaped') is not None: - return match.group('escaped')[1:-2]+'@' + character_regex = re.compile(r''' + [^a-zA-Z0-9_/.+\-] + ''', re.VERBOSE) + + def variable_get(varname: str) -> str: + var_str = '' + if varname in confdata: + var, _ = confdata.get(varname) + if isinstance(var, str): + var_str = var + elif isinstance(var, bool): + var_str = str(int(var)) + elif isinstance(var, int): + var_str = str(var) + else: + msg = f'Tried to replace variable {varname!r} value with ' \ + f'something other than a string or int: {var!r}' + raise MesonException(msg) else: - # Template variable to be replaced - varname = match.group('variable') - if not varname: - varname = match.group('cmake_variable') - - var_str = '' - if varname in confdata: - var, _ = confdata.get(varname) - if isinstance(var, str): - var_str = var - elif isinstance(var, bool): - var_str = str(int(var)) - elif isinstance(var, int): - var_str = str(var) - else: - msg = f'Tried to replace variable {varname!r} value with ' \ - f'something other than a string or int: {var!r}' + missing_variables.add(varname) + return var_str + + def parse_line(line: str) -> str: + index = 0 + while len(line) > index: + if line[index] == '@': + next_at = line.find("@", index+1) + if next_at > index+1: + varname = line[index+1:next_at] + match = character_regex.search(varname) + + # at substituion doesn't occur if they key isn't valid + # however it also doesn't raise an error + if not match: + value = variable_get(varname) + line = line[:index] + value + line[next_at+1:] + + elif not at_only and line[index:index+2] == '${': + bracket_count = 1 + end_bracket = index + 2 + try: + while bracket_count > 0: + if line[end_bracket:end_bracket+2] == "${": + end_bracket += 2 + bracket_count += 1 + elif line[end_bracket] == "}": + end_bracket += 1 + bracket_count -= 1 + elif line[end_bracket] in {"@", "\n"}: + # these aren't valid variable characters + # but they are inconsequential at this point + end_bracket += 1 + elif character_regex.search(line[end_bracket]): + invalid_character = line[end_bracket] + variable = line[index+2:end_bracket] + msg = f'Found invalid character {invalid_character!r}' \ + f' in variable {variable!r}' + raise MesonException(msg) + else: + end_bracket += 1 + except IndexError: + msg = f'Found incomplete variable {line[index:-1]!r}' raise MesonException(msg) - else: - missing_variables.add(varname) - return var_str - return re.sub(regex, variable_replace, line), missing_variables + + if bracket_count == 0: + varname = parse_line(line[index+2:end_bracket-1]) + match = character_regex.search(varname) + if match: + invalid_character = line[end_bracket-2] + variable = line[index+2:end_bracket-3] + msg = f'Found invalid character {invalid_character!r}' \ + f' in variable {variable!r}' + raise MesonException(msg) + + value = variable_get(varname) + line = line[:index] + value + line[end_bracket:] + + index += 1 + + return line + + return parse_line(line), missing_variables def do_define_meson(regex: T.Pattern[str], line: str, confdata: 'ConfigurationData', subproject: T.Optional[SubProject] = None) -> str: @@ -1327,12 +1399,12 @@ def do_define_meson(regex: T.Pattern[str], line: str, confdata: 'ConfigurationDa else: raise MesonException('#mesondefine argument "%s" is of unknown type.' % varname) -def do_define_cmake(regex: T.Pattern[str], line: str, confdata: 'ConfigurationData', at_only: bool, +def do_define_cmake(line: str, confdata: 'ConfigurationData', at_only: bool, subproject: T.Optional[SubProject] = None) -> str: cmake_bool_define = 'cmakedefine01' in line def get_cmake_define(line: str, confdata: 'ConfigurationData') -> str: - arr = line.split() + arr = line[1:].split() if cmake_bool_define: (v, desc) = confdata.get(arr[1]) @@ -1347,7 +1419,7 @@ def do_define_cmake(regex: T.Pattern[str], line: str, confdata: 'ConfigurationDa define_value += [token] return ' '.join(define_value) - arr = line.split() + arr = line[1:].split() if len(arr) != 2 and subproject is not None: from ..interpreterbase.decorators import FeatureNew @@ -1367,12 +1439,12 @@ def do_define_cmake(regex: T.Pattern[str], line: str, confdata: 'ConfigurationDa result = get_cmake_define(line, confdata) result = f'#define {varname} {result}'.strip() + '\n' - result, _ = do_replacement_cmake(regex, result, at_only, confdata) + result, _ = do_replacement_cmake(result, at_only, confdata) return result def get_variable_regex(variable_format: Literal['meson', 'cmake', 'cmake@'] = 'meson') -> T.Pattern[str]: # Only allow (a-z, A-Z, 0-9, _, -) as valid characters for a define - if variable_format in {'meson', 'cmake@'}: + if variable_format == 'meson': # Also allow escaping pairs of '@' with '\@' regex = re.compile(r''' (?:\\\\)+(?=\\?@) # Matches multiple backslashes followed by an @ symbol @@ -1381,17 +1453,13 @@ def get_variable_regex(variable_format: Literal['meson', 'cmake', 'cmake@'] = 'm | # OR (?P<escaped>\\@[-a-zA-Z0-9_]+\\@) # Match an escaped variable enclosed in @ symbols ''', re.VERBOSE) - else: + elif variable_format == 'cmake@': regex = re.compile(r''' - (?:\\\\)+(?=\\?(\$|@)) # Match multiple backslashes followed by a dollar sign or an @ symbol - | # OR - \\\${ # Match a backslash followed by a dollar sign and an opening curly brace - | # OR - \${(?P<cmake_variable>[-a-zA-Z0-9_]+)} # Match a variable enclosed in curly braces and capture the variable name - | # OR (?<!\\)@(?P<variable>[-a-zA-Z0-9_]+)@ # Match a variable enclosed in @ symbols and capture the variable name; no matches beginning with '\@' - | # OR - (?P<escaped>\\@[-a-zA-Z0-9_]+\\@) # Match an escaped variable enclosed in @ symbols + ''', re.VERBOSE) + elif variable_format == "cmake": + regex = re.compile(r''' + \${(?P<variable>[-a-zA-Z0-9_]*)} # Match a variable enclosed in curly braces and capture the variable name ''', re.VERBOSE) return regex @@ -1439,9 +1507,7 @@ def do_conf_str_cmake(src: str, data: T.List[str], confdata: 'ConfigurationData' if at_only: variable_format = 'cmake@' - regex = get_variable_regex(variable_format) - - search_token = '#cmakedefine' + search_token = 'cmakedefine' result: T.List[str] = [] missing_variables: T.Set[str] = set() @@ -1449,13 +1515,15 @@ def do_conf_str_cmake(src: str, data: T.List[str], confdata: 'ConfigurationData' # during substitution so we can warn the user to use the `copy:` kwarg. confdata_useless = not confdata.keys() for line in data: - if line.lstrip().startswith(search_token): + stripped_line = line.lstrip() + if len(stripped_line) >= 2 and stripped_line[0] == '#' and stripped_line[1:].lstrip().startswith(search_token): confdata_useless = False - line = do_define_cmake(regex, line, confdata, at_only, subproject) + + line = do_define_cmake(line, confdata, at_only, subproject) else: if '#mesondefine' in line: raise MesonException(f'Format error in {src}: saw "{line.strip()}" when format set to "{variable_format}"') - line, missing = do_replacement_cmake(regex, line, at_only, confdata) + line, missing = do_replacement_cmake(line, at_only, confdata) missing_variables.update(missing) if missing: confdata_useless = False @@ -1578,7 +1646,7 @@ def listify(item: T.Any, flatten: bool = True) -> T.List[T.Any]: result.append(i) return result -def listify_array_value(value: T.Union[str, T.List[str]], shlex_split_args: bool = False) -> T.List[str]: +def listify_array_value(value: object, shlex_split_args: bool = False) -> T.List[str]: if isinstance(value, str): if value.startswith('['): try: @@ -1629,6 +1697,8 @@ def typeslistify(item: 'T.Union[_T, T.Sequence[_T]]', def stringlistify(item: T.Union[T.Any, T.Sequence[T.Any]]) -> T.List[str]: return typeslistify(item, str) +def underscorify(item: str) -> str: + return re.sub(r'[^a-zA-Z0-9]', '_', item) def expand_arguments(args: T.Iterable[str]) -> T.Optional[T.List[str]]: expended_args: T.List[str] = [] @@ -1738,7 +1808,7 @@ def Popen_safe_logged(args: T.List[str], msg: str = 'Called', **kwargs: T.Any) - return p, o, e -def iter_regexin_iter(regexiter: T.Iterable[str], initer: T.Iterable[str]) -> T.Optional[str]: +def iter_regexin_iter(regexiter: T.Iterable[str], initer: T.Iterable[str | programs.ExternalProgram]) -> T.Optional[str]: ''' Takes each regular expression in @regexiter and tries to search for it in every item in @initer. If there is a match, returns that match. @@ -1754,7 +1824,7 @@ def iter_regexin_iter(regexiter: T.Iterable[str], initer: T.Iterable[str]) -> T. return None -def _substitute_values_check_errors(command: T.List[str], values: T.Dict[str, T.Union[str, T.List[str]]]) -> None: +def _substitute_values_check_errors(command: T.List[str | programs.ExternalProgram], values: T.Dict[str, T.Union[str, T.List[str]]]) -> None: # Error checking inregex: T.List[str] = ['@INPUT([0-9]+)?@', '@PLAINNAME@', '@BASENAME@'] outregex: T.List[str] = ['@OUTPUT([0-9]+)?@', '@OUTDIR@'] @@ -1794,7 +1864,7 @@ def _substitute_values_check_errors(command: T.List[str], values: T.Dict[str, T. raise MesonException(m.format(match2.group(), len(values['@OUTPUT@']))) -def substitute_values(command: T.List[str], values: T.Dict[str, T.Union[str, T.List[str]]]) -> T.List[str]: +def substitute_values(command: T.List[str | programs.ExternalProgram], values: T.Dict[str, T.Union[str, T.List[str]]]) -> T.List[str | programs.ExternalProgram]: ''' Substitute the template strings in the @values dict into the list of strings @command and return a new list. For a full list of the templates, @@ -1821,7 +1891,7 @@ def substitute_values(command: T.List[str], values: T.Dict[str, T.Union[str, T.L _substitute_values_check_errors(command, values) # Substitution - outcmd: T.List[str] = [] + outcmd: T.List[str | programs.ExternalProgram] = [] rx_keys = [re.escape(key) for key in values if key not in ('@INPUT@', '@OUTPUT@')] value_rx = re.compile('|'.join(rx_keys)) if rx_keys else None for vv in command: @@ -2003,6 +2073,8 @@ def detect_subprojects(spdir_name: str, current_dir: str = '', continue append_this = True if os.path.isdir(trial): + spdir_name = get_subproject_dir(trial) or 'subprojects' + detect_subprojects(spdir_name, trial, result) elif trial.endswith('.wrap') and os.path.isfile(trial): basename = os.path.splitext(basename)[0] @@ -2023,6 +2095,10 @@ def substring_is_in_list(substr: str, strlist: T.List[str]) -> bool: return False +def unique_list(x: T.Iterable[_T]) -> T.List[_T]: + return list(dict.fromkeys(x)) + + class OrderedSet(T.MutableSet[_T]): """A set that preserves the order in which items are added, by first insertion. @@ -2167,6 +2243,7 @@ _BUILTIN_NAMES = { 'pkg_config_path', 'cmake_prefix_path', 'vsenv', + 'os2_emxomf', } @@ -2436,3 +2513,23 @@ class lazy_property(T.Generic[_T]): value = self.__func(instance) setattr(instance, self.__name, value) return value + + +def get_subproject_dir(directory: str = '.') -> T.Optional[str]: + """Get the name of the subproject directory for a specific project. + + If the subproject does not have a meson.build file, it is called in an + invalid directory, it returns None + + :param directory: Where to search, defaults to current working directory + :return: the name of the subproject directory or None. + """ + from ..ast import IntrospectionInterpreter + from ..interpreterbase.exceptions import InvalidArguments + intr = IntrospectionInterpreter(directory, '', 'none') + try: + intr.load_root_meson_file() + except InvalidArguments: # Root meson file cannot be found + return None + + return intr.extract_subproject_dir() or 'subprojects' diff --git a/mesonbuild/utils/vsenv.py b/mesonbuild/utils/vsenv.py index 5a02379..3cc9b4b 100644 --- a/mesonbuild/utils/vsenv.py +++ b/mesonbuild/utils/vsenv.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2012-2025 The Meson development team + from __future__ import annotations import os @@ -6,11 +9,12 @@ import json import pathlib import shutil import tempfile -import locale +import pickle from .. import mlog from .core import MesonException -from .universal import is_windows, windows_detect_native_arch +from .universal import (is_windows, windows_detect_native_arch, windows_proof_rm, + get_meson_command, join_args) __all__ = [ @@ -18,14 +22,6 @@ __all__ = [ ] -bat_template = '''@ECHO OFF - -call "{}" - -ECHO {} -SET -''' - # If on Windows and VS is installed but not set up in the environment, # set it to be runnable. In this way Meson can be directly invoked # from any shell, VS Code etc. @@ -88,32 +84,24 @@ def _setup_vsenv(force: bool) -> bool: raise MesonException(f'Could not find {bat_path}') mlog.log('Activating VS', bat_info[0]['catalog']['productDisplayVersion']) - bat_separator = '---SPLIT---' - bat_contents = bat_template.format(bat_path, bat_separator) - bat_file = tempfile.NamedTemporaryFile('w', suffix='.bat', encoding='utf-8', delete=False) - bat_file.write(bat_contents) - bat_file.flush() - bat_file.close() - bat_output = subprocess.check_output(bat_file.name, universal_newlines=True, - encoding=locale.getpreferredencoding(False)) - os.unlink(bat_file.name) - bat_lines = bat_output.split('\n') - bat_separator_seen = False - for bat_line in bat_lines: - if bat_line == bat_separator: - bat_separator_seen = True - continue - if not bat_separator_seen: - continue - if not bat_line: - continue - try: - k, v = bat_line.split('=', 1) - except ValueError: - # there is no "=", ignore junk data - pass - else: + # Write a bat file that first activates VS environment, and then calls + # a Meson script that pickles the environment into a temp file. + with tempfile.NamedTemporaryFile(delete=False) as env_file: + pass + vcvars_cmd = ['call', str(bat_path)] + pickle_cmd = get_meson_command() + ['--internal', 'pickle_env', env_file.name] + with tempfile.NamedTemporaryFile('w', suffix='.bat', encoding='utf-8', delete=False) as bat_file: + bat_file.write(join_args(vcvars_cmd) + '\n') + bat_file.write(join_args(pickle_cmd)) + try: + subprocess.check_call([bat_file.name], stdout=subprocess.DEVNULL) + with open(env_file.name, 'rb') as f: + vsenv = pickle.load(f) + for k, v in vsenv.items(): os.environ[k] = v + finally: + windows_proof_rm(env_file.name) + windows_proof_rm(bat_file.name) return True def setup_vsenv(force: bool = False) -> bool: diff --git a/mesonbuild/utils/win32.py b/mesonbuild/utils/win32.py deleted file mode 100644 index 4fcb8ed..0000000 --- a/mesonbuild/utils/win32.py +++ /dev/null @@ -1,29 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# Copyright 2012-2021 The Meson development team -# Copyright © 2021-2023 Intel Corporation - -from __future__ import annotations - -"""Windows specific implementations of mesonlib functionality.""" - -import msvcrt -import typing as T - -from .core import MesonException -from .platform import BuildDirLock as BuildDirLockBase - -__all__ = ['BuildDirLock'] - -class BuildDirLock(BuildDirLockBase): - - def __enter__(self) -> None: - self.lockfile = open(self.lockfilename, 'w', encoding='utf-8') - try: - msvcrt.locking(self.lockfile.fileno(), msvcrt.LK_NBLCK, 1) - except (BlockingIOError, PermissionError): - self.lockfile.close() - raise MesonException('Some other Meson process is already using this build directory. Exiting.') - - def __exit__(self, *args: T.Any) -> None: - msvcrt.locking(self.lockfile.fileno(), msvcrt.LK_UNLCK, 1) - self.lockfile.close() diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py index 9af1f39..67ca96b 100644 --- a/mesonbuild/wrap/wrap.py +++ b/mesonbuild/wrap/wrap.py @@ -21,6 +21,7 @@ import time import typing as T import textwrap import json +import gzip from base64 import b64encode from netrc import netrc @@ -29,7 +30,10 @@ from functools import lru_cache from . import WrapMode from .. import coredata -from ..mesonlib import quiet_git, GIT, ProgressBar, MesonException, windows_proof_rmtree, Popen_safe +from ..mesonlib import ( + DirectoryLock, DirectoryLockAction, quiet_git, GIT, ProgressBar, MesonException, + windows_proof_rmtree, Popen_safe +) from ..interpreterbase import FeatureNew from ..interpreterbase import SubProject from .. import mesonlib @@ -53,7 +57,42 @@ WHITELIST_SUBDOMAIN = 'wrapdb.mesonbuild.com' ALL_TYPES = ['file', 'git', 'hg', 'svn', 'redirect'] -PATCH = shutil.which('patch') +if sys.version_info >= (3, 14): + import tarfile + tarfile.TarFile.extraction_filter = staticmethod(tarfile.fully_trusted_filter) + +if mesonlib.is_windows(): + from ..programs import ExternalProgram + from ..mesonlib import version_compare + _exclude_paths: T.List[str] = [] + while True: + _patch = ExternalProgram('patch', silent=True, exclude_paths=_exclude_paths) + if not _patch.found(): + break + if version_compare(_patch.get_version(), '>=2.6.1'): + break + _exclude_paths.append(os.path.dirname(_patch.get_path())) + PATCH = _patch.get_path() if _patch.found() else None +else: + PATCH = shutil.which('patch') + + +truststore_message = ''' + + If you believe the connection should be secure, but python cannot see the + correct SSL certificates, install https://truststore.readthedocs.io/ and + try again.''' + +@lru_cache(maxsize=None) +def ssl_truststore() -> T.Optional[ssl.SSLContext]: + """ Provide a default context=None for urlopen, but use truststore if installed. """ + try: + import truststore + except ImportError: + # use default + return None + else: + return truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT) def whitelist_wrapdb(urlstr: str) -> urllib.parse.ParseResult: """ raises WrapException if not whitelisted subdomain """ @@ -66,21 +105,30 @@ def whitelist_wrapdb(urlstr: str) -> urllib.parse.ParseResult: raise WrapException(f'WrapDB did not have expected SSL https url, instead got {urlstr}') return url -def open_wrapdburl(urlstring: str, allow_insecure: bool = False, have_opt: bool = False) -> 'http.client.HTTPResponse': +def open_wrapdburl(urlstring: str, allow_insecure: bool = False, have_opt: bool = False, allow_compression: bool = False) -> http.client.HTTPResponse: if have_opt: insecure_msg = '\n\n To allow connecting anyway, pass `--allow-insecure`.' else: insecure_msg = '' + def do_urlopen(url: urllib.parse.ParseResult) -> http.client.HTTPResponse: + headers = {} + if allow_compression: + headers['Accept-Encoding'] = 'gzip' + req = urllib.request.Request(urllib.parse.urlunparse(url), headers=headers) + return T.cast('http.client.HTTPResponse', urllib.request.urlopen(req, timeout=REQ_TIMEOUT, context=ssl_truststore())) + url = whitelist_wrapdb(urlstring) if has_ssl: try: - return T.cast('http.client.HTTPResponse', urllib.request.urlopen(urllib.parse.urlunparse(url), timeout=REQ_TIMEOUT)) + return do_urlopen(url) except OSError as excp: msg = f'WrapDB connection failed to {urlstring} with error {excp}.' if isinstance(excp, urllib.error.URLError) and isinstance(excp.reason, ssl.SSLCertVerificationError): if allow_insecure: mlog.warning(f'{msg}\n\n Proceeding without authentication.') + elif ssl_truststore() is None: + raise WrapException(f'{msg}{insecure_msg}{truststore_message}') else: raise WrapException(f'{msg}{insecure_msg}') else: @@ -92,15 +140,24 @@ def open_wrapdburl(urlstring: str, allow_insecure: bool = False, have_opt: bool mlog.warning(f'SSL module not available in {sys.executable}: WrapDB traffic not authenticated.', once=True) # If we got this far, allow_insecure was manually passed - nossl_url = url._replace(scheme='http') try: - return T.cast('http.client.HTTPResponse', urllib.request.urlopen(urllib.parse.urlunparse(nossl_url), timeout=REQ_TIMEOUT)) + return do_urlopen(url._replace(scheme='http')) except OSError as excp: raise WrapException(f'WrapDB connection failed to {urlstring} with error {excp}') +def read_and_decompress(resp: http.client.HTTPResponse) -> bytes: + data = resp.read() + encoding = resp.headers['Content-Encoding'] + if encoding == 'gzip': + return gzip.decompress(data) + elif encoding: + raise WrapException(f'Unexpected Content-Encoding for {resp.url}: {encoding}') + else: + return data + def get_releases_data(allow_insecure: bool) -> bytes: - url = open_wrapdburl('https://wrapdb.mesonbuild.com/v2/releases.json', allow_insecure, True) - return url.read() + url = open_wrapdburl('https://wrapdb.mesonbuild.com/v2/releases.json', allow_insecure, True, True) + return read_and_decompress(url) @lru_cache(maxsize=None) def get_releases(allow_insecure: bool) -> T.Dict[str, T.Any]: @@ -109,9 +166,9 @@ def get_releases(allow_insecure: bool) -> T.Dict[str, T.Any]: def update_wrap_file(wrapfile: str, name: str, new_version: str, new_revision: str, allow_insecure: bool) -> None: url = open_wrapdburl(f'https://wrapdb.mesonbuild.com/v2/{name}_{new_version}-{new_revision}/{name}.wrap', - allow_insecure, True) + allow_insecure, True, True) with open(wrapfile, 'wb') as f: - f.write(url.read()) + f.write(read_and_decompress(url)) def parse_patch_url(patch_url: str) -> T.Tuple[str, str]: u = urllib.parse.urlparse(patch_url) @@ -130,6 +187,7 @@ def parse_patch_url(patch_url: str) -> T.Tuple[str, str]: else: raise WrapException(f'Invalid wrapdb URL {patch_url}') + class WrapException(MesonException): pass @@ -213,6 +271,15 @@ class PackageDefinition: wrap.original_filename = filename wrap.parse_provide_section(config) + patch_url = values.get('patch_url') + if patch_url and patch_url.startswith('https://wrapdb.mesonbuild.com/v1'): + if name == 'sqlite': + mlog.deprecation('sqlite wrap has been renamed to sqlite3, update using `meson wrap install sqlite3`') + elif name == 'libjpeg': + mlog.deprecation('libjpeg wrap has been renamed to libjpeg-turbo, update using `meson wrap install libjpeg-turbo`') + else: + mlog.deprecation(f'WrapDB v1 is deprecated, updated using `meson wrap update {name}`') + with open(filename, 'r', encoding='utf-8') as file: wrap.wrapfile_hash = hashlib.sha256(file.read().encode('utf-8')).hexdigest() @@ -275,6 +342,9 @@ class PackageDefinition: with open(self.get_hashfile(subproject_directory), 'w', encoding='utf-8') as file: file.write(self.wrapfile_hash + '\n') + def add_provided_dep(self, name: str) -> None: + self.provided_deps[name] = None + def get_directory(subdir_root: str, packagename: str) -> str: fname = os.path.join(subdir_root, packagename + '.wrap') if os.path.isfile(fname): @@ -311,6 +381,7 @@ class Resolver: self.wrapdb: T.Dict[str, T.Any] = {} self.wrapdb_provided_deps: T.Dict[str, str] = {} self.wrapdb_provided_programs: T.Dict[str, str] = {} + self.loaded_dirs: T.Set[str] = set() self.load_wraps() self.load_netrc() self.load_wrapdb() @@ -324,12 +395,6 @@ class Resolver: mlog.warning(f'failed to process netrc file: {e}.', fatal=False) def load_wraps(self) -> None: - # Load Cargo.lock at the root of source tree - source_dir = os.path.dirname(self.subdir_root) - if os.path.exists(os.path.join(source_dir, 'Cargo.lock')): - from .. import cargo - for wrap in cargo.load_wraps(source_dir, self.subdir_root): - self.wraps[wrap.name] = wrap # Load subprojects/*.wrap if os.path.isdir(self.subdir_root): root, dirs, files = next(os.walk(self.subdir_root)) @@ -352,6 +417,7 @@ class Resolver: # Add provided deps and programs into our lookup tables for wrap in self.wraps.values(): self.add_wrap(wrap) + self.loaded_dirs.add(self.subdir) def add_wrap(self, wrap: PackageDefinition) -> None: for k in wrap.provided_deps.keys(): @@ -384,28 +450,37 @@ class Resolver: self.check_can_download() latest_version = info['versions'][0] version, revision = latest_version.rsplit('-', 1) - url = urllib.request.urlopen(f'https://wrapdb.mesonbuild.com/v2/{subp_name}_{version}-{revision}/{subp_name}.wrap') + url = open_wrapdburl(f'https://wrapdb.mesonbuild.com/v2/{subp_name}_{version}-{revision}/{subp_name}.wrap', allow_compression=True) fname = Path(self.subdir_root, f'{subp_name}.wrap') with fname.open('wb') as f: - f.write(url.read()) + f.write(read_and_decompress(url)) mlog.log(f'Installed {subp_name} version {version} revision {revision}') wrap = PackageDefinition.from_wrap_file(str(fname)) self.wraps[wrap.name] = wrap self.add_wrap(wrap) return wrap - def _merge_wraps(self, other_resolver: 'Resolver') -> None: - for k, v in other_resolver.wraps.items(): - self.wraps.setdefault(k, v) - for k, v in other_resolver.provided_deps.items(): - self.provided_deps.setdefault(k, v) - for k, v in other_resolver.provided_programs.items(): - self.provided_programs.setdefault(k, v) + def merge_wraps(self, wraps: T.Dict[str, PackageDefinition]) -> None: + for k, v in wraps.items(): + prev_wrap = self.wraps.get(v.directory) + if prev_wrap and prev_wrap.type is None and v.type is not None: + # This happens when a subproject has been previously downloaded + # using a wrap from another subproject and the wrap-redirect got + # deleted. In that case, the main project created a bare wrap + # for the download directory, but now we have a proper wrap. + # It also happens for wraps coming from Cargo.lock files, which + # don't create wrap-redirect. + del self.wraps[v.directory] + del self.provided_deps[v.directory.lower()] + if k not in self.wraps: + self.wraps[k] = v + self.add_wrap(v) def load_and_merge(self, subdir: str, subproject: SubProject) -> None: - if self.wrap_mode != WrapMode.nopromote: + if self.wrap_mode != WrapMode.nopromote and subdir not in self.loaded_dirs: other_resolver = Resolver(self.source_dir, subdir, subproject, self.wrap_mode, self.wrap_frontend, self.allow_insecure, self.silent) - self._merge_wraps(other_resolver) + self.merge_wraps(other_resolver.wraps) + self.loaded_dirs.add(subdir) def find_dep_provider(self, packagename: str) -> T.Tuple[T.Optional[str], T.Optional[str]]: # Python's ini parser converts all key values to lowercase. @@ -432,7 +507,7 @@ class Resolver: return wrap_name return None - def resolve(self, packagename: str, force_method: T.Optional[Method] = None) -> T.Tuple[str, Method]: + def _resolve(self, packagename: str, force_method: T.Optional[Method] = None) -> T.Tuple[str, Method]: wrap = self.wraps.get(packagename) if wrap is None: wrap = self.get_from_wrapdb(packagename) @@ -530,6 +605,15 @@ class Resolver: self.wrap.update_hash_cache(self.dirname) return rel_path, method + def resolve(self, packagename: str, force_method: T.Optional[Method] = None) -> T.Tuple[str, Method]: + try: + with DirectoryLock(self.subdir_root, '.wraplock', + DirectoryLockAction.WAIT, + 'Failed to lock subprojects directory', optional=True): + return self._resolve(packagename, force_method) + except FileNotFoundError: + raise WrapNotFoundException('Attempted to resolve subproject without subprojects directory present.') + def check_can_download(self) -> None: # Don't download subproject data based on wrap file if requested. # Git submodules are ok (see above)! @@ -691,6 +775,23 @@ class Resolver: resp = open_wrapdburl(urlstring, allow_insecure=self.allow_insecure, have_opt=self.wrap_frontend) elif WHITELIST_SUBDOMAIN in urlstring: raise WrapException(f'{urlstring} may be a WrapDB-impersonating URL') + elif url.scheme == 'sftp': + sftp = shutil.which('sftp') + if sftp is None: + raise WrapException('Scheme sftp is not available. Install sftp to enable it.') + with tempfile.TemporaryDirectory() as workdir, \ + tempfile.NamedTemporaryFile(mode='wb', dir=self.cachedir, delete=False) as tmpfile: + args = [] + # Older versions of the sftp client cannot handle URLs, hence the splitting of url below + if url.port: + args += ['-P', f'{url.port}'] + user = f'{url.username}@' if url.username else '' + command = [sftp, '-o', 'KbdInteractiveAuthentication=no', *args, f'{user}{url.hostname}:{url.path[1:]}'] + subprocess.run(command, cwd=workdir, check=True) + downloaded = os.path.join(workdir, os.path.basename(url.path)) + tmpfile.close() + shutil.move(downloaded, tmpfile.name) + return self.hash_file(tmpfile.name), tmpfile.name else: headers = { 'User-Agent': f'mesonbuild/{coredata.version}', @@ -712,10 +813,13 @@ class Resolver: try: req = urllib.request.Request(urlstring, headers=headers) - resp = urllib.request.urlopen(req, timeout=REQ_TIMEOUT) + resp = urllib.request.urlopen(req, timeout=REQ_TIMEOUT, context=ssl_truststore()) except OSError as e: mlog.log(str(e)) - raise WrapException(f'could not get {urlstring} is the internet available?') + if isinstance(e, urllib.error.URLError) and isinstance(e.reason, ssl.SSLCertVerificationError) and ssl_truststore() is None: + raise WrapException(f'could not get {urlstring}; is the internet available?{truststore_message}') + else: + raise WrapException(f'could not get {urlstring}; is the internet available?') with contextlib.closing(resp) as resp, tmpfile as tmpfile: try: dlsize = int(resp.info()['Content-Length']) @@ -746,14 +850,17 @@ class Resolver: hashvalue = h.hexdigest() return hashvalue, tmpfile.name + def hash_file(self, path: str) -> str: + h = hashlib.sha256() + with open(path, 'rb') as f: + h.update(f.read()) + return h.hexdigest() + def check_hash(self, what: str, path: str, hash_required: bool = True) -> None: if what + '_hash' not in self.wrap.values and not hash_required: return expected = self.wrap.get(what + '_hash').lower() - h = hashlib.sha256() - with open(path, 'rb') as f: - h.update(f.read()) - dhash = h.hexdigest() + dhash = self.hash_file(path) if dhash != expected: raise WrapException(f'Incorrect hash for {what}:\n {expected} expected\n {dhash} actual.') diff --git a/mesonbuild/wrap/wraptool.py b/mesonbuild/wrap/wraptool.py index 5486a26..8517511 100644 --- a/mesonbuild/wrap/wraptool.py +++ b/mesonbuild/wrap/wraptool.py @@ -9,10 +9,8 @@ import shutil import typing as T from glob import glob -from .wrap import (open_wrapdburl, WrapException, get_releases, get_releases_data, - parse_patch_url) -from pathlib import Path - +from .wrap import (open_wrapdburl, read_and_decompress, WrapException, get_releases, + get_releases_data, parse_patch_url) from .. import mesonlib, msubprojects if T.TYPE_CHECKING: @@ -91,17 +89,18 @@ def get_latest_version(name: str, allow_insecure: bool) -> T.Tuple[str, str]: def install(options: 'argparse.Namespace') -> None: name = options.name - if not os.path.isdir('subprojects'): + subproject_dir_name = mesonlib.get_subproject_dir() + if subproject_dir_name is None or not os.path.isdir(subproject_dir_name): raise SystemExit('Subprojects dir not found. Run this script in your source root directory.') - if os.path.isdir(os.path.join('subprojects', name)): + if os.path.isdir(os.path.join(subproject_dir_name, name)): raise SystemExit('Subproject directory for this project already exists.') - wrapfile = os.path.join('subprojects', name + '.wrap') + wrapfile = os.path.join(subproject_dir_name, name + '.wrap') if os.path.exists(wrapfile): raise SystemExit('Wrap file already exists.') (version, revision) = get_latest_version(name, options.allow_insecure) - url = open_wrapdburl(f'https://wrapdb.mesonbuild.com/v2/{name}_{version}-{revision}/{name}.wrap', options.allow_insecure, True) + url = open_wrapdburl(f'https://wrapdb.mesonbuild.com/v2/{name}_{version}-{revision}/{name}.wrap', options.allow_insecure, True, True) with open(wrapfile, 'wb') as f: - f.write(url.read()) + f.write(read_and_decompress(url)) print(f'Installed {name} version {version} revision {revision}') def get_current_version(wrapfile: str) -> T.Tuple[str, str, str, str, T.Optional[str]]: @@ -143,11 +142,20 @@ def do_promotion(from_path: str, spdir_name: str) -> None: outputdir = os.path.join(spdir_name, sproj_name) if os.path.exists(outputdir): raise SystemExit(f'Output dir {outputdir} already exists. Will not overwrite.') - shutil.copytree(from_path, outputdir, ignore=shutil.ignore_patterns('subprojects')) + + subpdir = mesonlib.get_subproject_dir() + if subpdir is not None: + ignore = shutil.ignore_patterns(subpdir) + else: + ignore = None + + shutil.copytree(from_path, outputdir, ignore=ignore) def promote(options: 'argparse.Namespace') -> None: argument = options.project_path - spdir_name = 'subprojects' + spdir_name = mesonlib.get_subproject_dir() + if spdir_name is None: + raise SystemExit('Subproject dir not found. Run this script in your source root directory.') sprojs = mesonlib.detect_subprojects(spdir_name) # check if the argument is a full path to a subproject directory or wrap file @@ -170,7 +178,9 @@ def promote(options: 'argparse.Namespace') -> None: def status(options: 'argparse.Namespace') -> None: print('Subproject status') - for w in glob('subprojects/*.wrap'): + subdir = mesonlib.get_subproject_dir() + assert subdir is not None, "This should only happen in a non-native subproject" + for w in glob(f'{subdir}/*.wrap'): name = os.path.basename(w)[:-5] try: (latest_branch, latest_revision) = get_latest_version(name, options.allow_insecure) @@ -189,8 +199,12 @@ def status(options: 'argparse.Namespace') -> None: def update_db(options: 'argparse.Namespace') -> None: data = get_releases_data(options.allow_insecure) - Path('subprojects').mkdir(exist_ok=True) - with Path('subprojects/wrapdb.json').open('wb') as f: + subproject_dir_name = mesonlib.get_subproject_dir() + if subproject_dir_name is None: + raise SystemExit('Subproject dir not found. Run this script in your source root directory.') + + os.makedirs(subproject_dir_name, exist_ok=True) + with open(os.path.join(subproject_dir_name, 'wrapdb.json'), 'wb') as f: f.write(data) def run(options: 'argparse.Namespace') -> int: diff --git a/packaging/builddist.py b/packaging/builddist.py new file mode 100755 index 0000000..5cf3b02 --- /dev/null +++ b/packaging/builddist.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 + +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2025 The Meson development team + +# This script must be run from the source root. + +import pathlib, shutil, subprocess + +gendir = pathlib.Path('distgendir') +distdir = pathlib.Path('dist') +gitdir = pathlib.Path('.git') + +if distdir.is_dir(): + shutil.rmtree(distdir) +distdir.mkdir() + +if gendir.is_dir(): + shutil.rmtree(gendir) +gendir.mkdir() + +shutil.copytree(gitdir, gendir / '.git') + +subprocess.check_call(['git', 'reset', '--hard'], + cwd=gendir) +subprocess.check_call(['python3', 'setup.py', 'sdist', 'bdist_wheel'], + cwd=gendir) +for f in (gendir / 'dist').glob('*'): + shutil.copy(f, distdir) + +shutil.rmtree(gendir) + diff --git a/packaging/builddist.sh b/packaging/builddist.sh deleted file mode 100755 index edcf3ec..0000000 --- a/packaging/builddist.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/zsh - -# This script must be run from the source root. - -set -e - -GENDIR=distgendir - -rm -rf dist -rm -rf $GENDIR -mkdir dist -mkdir $GENDIR -cp -r .git $GENDIR -cd $GENDIR -git reset --hard -python3 setup.py sdist bdist_wheel -cp dist/* ../dist -cd .. -rm -rf $GENDIR diff --git a/packaging/mpackage.py b/packaging/mpackage.py index a075e06..e457e55 100755 --- a/packaging/mpackage.py +++ b/packaging/mpackage.py @@ -25,16 +25,19 @@ with tarfile.open(infile , 'r') as tf: fname = os.path.split(infile)[1] tmp = fname.replace('-', '_') -if '0rc' in fname: - version = tmp[6:-7] - base_version = tmp[6:-10] + +assert fname.endswith('.tar.gz') +version_part = fname.split('-', 1)[1][:-7] + +if 'rc' in version_part: + base_version, rcnum = version_part.split('rc') + version = base_version + 'rc' + rcnum extension = tmp[-7:] - rcnum = tmp[-8:-7] dchversion = base_version + '~rc' + rcnum - origname = tmp[:11] + '~rc' + rcnum + '.orig' + extension + origname = tmp.split('rc', 1)[0] + '~rc' + rcnum + '.orig' + extension else: - origname = tmp[:11] + '.orig.' + tmp[-6:] - version = tmp[6:-7] + origname = tmp[:-7] + '.orig.' + tmp[-6:] + version = version_part dchversion = version version_lines = pathlib.Path(relfile).read_text().split('\n')[:-1] prev_ver = version_lines[-1] diff --git a/run_meson_command_tests.py b/run_meson_command_tests.py index f9faca9..c2a621b 100755 --- a/run_meson_command_tests.py +++ b/run_meson_command_tests.py @@ -46,6 +46,11 @@ def get_pybindir(): return sysconfig.get_path('scripts', scheme=scheme, vars={'base': ''}).strip('\\/') return sysconfig.get_path('scripts', vars={'base': ''}).strip('\\/') +def has_python_module(module: str) -> bool: + result = subprocess.run(python_command + ['-c', f'import {module}']) + return result.returncode == 0 + + class CommandTests(unittest.TestCase): ''' Test that running meson in various ways works as expected by checking the @@ -79,9 +84,13 @@ class CommandTests(unittest.TestCase): # If this call hangs CI will just abort. It is very hard to distinguish # between CI issue and test bug in that case. Set timeout and fail loud # instead. - p = subprocess.run(command, stdout=subprocess.PIPE, - env=env, text=True, - cwd=workdir, timeout=60 * 5) + p = subprocess.run(command, + stdout=subprocess.PIPE, + env=env, + encoding='utf-8', + text=True, + cwd=workdir, + timeout=60 * 5) print(p.stdout) if p.returncode != 0: raise subprocess.CalledProcessError(p.returncode, command) @@ -141,11 +150,17 @@ class CommandTests(unittest.TestCase): # distutils complains that prefix isn't contained in PYTHONPATH os.environ['PYTHONPATH'] = os.path.join(str(pylibdir), '') os.environ['PATH'] = str(bindir) + os.pathsep + os.environ['PATH'] - self._run(python_command + ['setup.py', 'install', '--prefix', str(prefix)]) - # Fix importlib-metadata by appending all dirs in pylibdir - PYTHONPATHS = [pylibdir] + [x for x in pylibdir.iterdir()] - PYTHONPATHS = [os.path.join(str(x), '') for x in PYTHONPATHS] - os.environ['PYTHONPATH'] = os.pathsep.join(PYTHONPATHS) + if has_python_module('gpep517'): + self._run(python_command + ['-m', 'gpep517', 'install-from-source', '--destdir', '/', '--prefix', str(prefix)]) + elif has_python_module('pip'): + self._run(python_command + ['-m', 'pip', 'install', '--prefix', str(prefix), '.']) + else: + # Legacy deprecated setuptools command used as fallback + self._run(python_command + ['setup.py', 'install', '--prefix', str(prefix)]) + # Fix importlib-metadata by appending all dirs in pylibdir + PYTHONPATHS = [pylibdir] + [x for x in pylibdir.iterdir() if x.name.endswith('.egg')] + PYTHONPATHS = [os.path.join(str(x), '') for x in PYTHONPATHS] + os.environ['PYTHONPATH'] = os.pathsep.join(PYTHONPATHS) # Check that all the files were installed correctly self.assertTrue(bindir.is_dir()) self.assertTrue(pylibdir.is_dir()) diff --git a/run_mypy.py b/run_mypy.py index d7d3aaa..5823e09 100755 --- a/run_mypy.py +++ b/run_mypy.py @@ -13,7 +13,7 @@ from mesonbuild.mesonlib import version_compare modules = [ # fully typed submodules - # 'mesonbuild/ast/', + 'mesonbuild/ast/', 'mesonbuild/cargo/', 'mesonbuild/cmake/', 'mesonbuild/compilers/', @@ -23,31 +23,26 @@ modules = [ 'mesonbuild/linkers/', 'mesonbuild/scripts/', 'mesonbuild/templates/', + 'mesonbuild/utils/', 'mesonbuild/wrap/', # specific files - 'mesonbuild/ast/introspection.py', - 'mesonbuild/ast/printer.py', - 'mesonbuild/ast/postprocess.py', - 'mesonbuild/ast/visitor.py', 'mesonbuild/arglist.py', 'mesonbuild/backend/backends.py', 'mesonbuild/backend/nonebackend.py', - # 'mesonbuild/coredata.py', + 'mesonbuild/cmdline.py', + 'mesonbuild/coredata.py', 'mesonbuild/depfile.py', 'mesonbuild/envconfig.py', 'mesonbuild/environment.py', 'mesonbuild/interpreter/compiler.py', + 'mesonbuild/interpreter/dependencyfallbacks.py', 'mesonbuild/interpreter/mesonmain.py', 'mesonbuild/interpreter/interpreterobjects.py', 'mesonbuild/interpreter/type_checking.py', 'mesonbuild/machinefile.py', 'mesonbuild/mcompile.py', 'mesonbuild/mdevenv.py', - 'mesonbuild/utils/core.py', - 'mesonbuild/utils/platform.py', - 'mesonbuild/utils/universal.py', - 'mesonbuild/utils/vsenv.py', 'mesonbuild/mconf.py', 'mesonbuild/mdist.py', 'mesonbuild/mformat.py', @@ -58,7 +53,9 @@ modules = [ 'mesonbuild/msubprojects.py', 'mesonbuild/modules/__init__.py', 'mesonbuild/modules/cmake.py', + 'mesonbuild/modules/codegen.py', 'mesonbuild/modules/cuda.py', + 'mesonbuild/modules/dlang.py', 'mesonbuild/modules/external_project.py', 'mesonbuild/modules/fs.py', 'mesonbuild/modules/gnome.py', @@ -74,6 +71,7 @@ modules = [ 'mesonbuild/modules/qt6.py', 'mesonbuild/modules/rust.py', 'mesonbuild/modules/simd.py', + 'mesonbuild/modules/snippets.py', 'mesonbuild/modules/sourceset.py', 'mesonbuild/modules/wayland.py', 'mesonbuild/modules/windows.py', @@ -83,6 +81,8 @@ modules = [ 'mesonbuild/optinterpreter.py', 'mesonbuild/options.py', 'mesonbuild/programs.py', + 'mesonbuild/rewriter.py', + 'mesonbuild/tooldetect.py', ] additional = [ 'run_mypy.py', @@ -94,11 +94,6 @@ additional = [ 'unittests/helpers.py', ] -if os.name == 'posix': - modules.append('mesonbuild/utils/posix.py') -elif os.name == 'nt': - modules.append('mesonbuild/utils/win32.py') - def check_mypy() -> None: try: import mypy diff --git a/run_project_tests.py b/run_project_tests.py index fa7c8a6..62fe73a 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -79,7 +79,7 @@ ALL_TESTS = ['cmake', 'common', 'native', 'warning-meson', 'failing-meson', 'fai 'keyval', 'platform-osx', 'platform-windows', 'platform-linux', 'platform-android', 'java', 'C#', 'vala', 'cython', 'rust', 'd', 'objective c', 'objective c++', 'fortran', 'swift', 'cuda', 'python3', 'python', 'fpga', 'frameworks', 'nasm', 'wasm', 'wayland', - 'format', + 'format', 'snippets', ] @@ -355,15 +355,15 @@ def setup_commands(optbackend: str) -> None: def platform_fix_name(fname: str, canonical_compiler: str, env: environment.Environment) -> str: if '?lib' in fname: if env.machines.host.is_windows() and canonical_compiler == 'msvc': - fname = re.sub(r'lib/\?lib(.*)\.', r'bin/\1.', fname) + fname = re.sub(r'lib/\?lib(.*)$', r'bin/\1', fname) fname = re.sub(r'/\?lib/', r'/bin/', fname) elif env.machines.host.is_windows(): - fname = re.sub(r'lib/\?lib(.*)\.', r'bin/lib\1.', fname) + fname = re.sub(r'lib/\?lib(.*)$', r'bin/lib\1', fname) fname = re.sub(r'\?lib(.*)\.dll$', r'lib\1.dll', fname) fname = re.sub(r'/\?lib/', r'/bin/', fname) elif env.machines.host.is_cygwin(): fname = re.sub(r'lib/\?lib(.*)\.so$', r'bin/cyg\1.dll', fname) - fname = re.sub(r'lib/\?lib(.*)\.', r'bin/cyg\1.', fname) + fname = re.sub(r'lib/\?lib(.*)$', r'bin/cyg\1', fname) fname = re.sub(r'\?lib(.*)\.dll$', r'cyg\1.dll', fname) fname = re.sub(r'/\?lib/', r'/bin/', fname) else: @@ -555,8 +555,8 @@ def clear_internal_caches() -> None: from mesonbuild.mesonlib import PerMachine mesonbuild.interpreterbase.FeatureNew.feature_registry = {} CMakeDependency.class_cmakeinfo = PerMachine(None, None) - PkgConfigInterface.class_impl = PerMachine(False, False) - PkgConfigInterface.class_cli_impl = PerMachine(False, False) + PkgConfigInterface.class_impl = PerMachine({}, {}) + PkgConfigInterface.class_cli_impl = PerMachine({}, {}) PkgConfigInterface.pkg_bin_per_machine = PerMachine(None, None) @@ -997,9 +997,9 @@ def have_working_compiler(lang: str, use_tmp: bool) -> bool: return False if not compiler: return False - env.coredata.process_compiler_options(lang, compiler, env, '') + env.coredata.process_compiler_options(lang, compiler, '') try: - compiler.sanity_check(env.get_scratch_dir(), env) + compiler.sanity_check(env.get_scratch_dir()) except mesonlib.MesonException: return False return True @@ -1145,6 +1145,7 @@ def detect_tests_to_run(only: T.Dict[str, T.List[str]], use_tmp: bool) -> T.List TestCategory('wasm', 'wasm', shutil.which('emcc') is None or backend is not Backend.ninja), TestCategory('wayland', 'wayland', should_skip_wayland()), TestCategory('format', 'format'), + TestCategory('snippets', 'snippets'), ] categories = [t.category for t in all_tests] @@ -1577,11 +1578,11 @@ def detect_tools(report: bool = True) -> None: print('{0:<{2}}: {1}'.format(tool.tool, get_version(tool), max_width)) print() -symlink_test_dir1 = None -symlink_test_dir2 = None -symlink_file1 = None -symlink_file2 = None -symlink_file3 = None +symlink_test_dir1: T.Optional[Path] = None +symlink_test_dir2: T.Optional[Path] = None +symlink_file1: T.Optional[Path] = None +symlink_file2: T.Optional[Path] = None +symlink_file3: T.Optional[Path] = None def scan_test_data_symlinks() -> None: global symlink_test_dir1, symlink_test_dir2, symlink_file1, symlink_file2, symlink_file3 diff --git a/run_shell_checks.py b/run_shell_checks.py new file mode 100755 index 0000000..f929d80 --- /dev/null +++ b/run_shell_checks.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 + +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2025 The Meson development team + +import pathlib +import sys + +# DO NOT ADD FILES IN THIS LIST! +# They are here because they got added +# in the past before this was properly checked. +# Instead you should consider removing things +# from this list by rewriting them to Python. +# +# The CI scripts probably need to remain shell +# scripts due to the way the CI systems work. + +permitted_files = ( + 'ci/ciimage/common.sh', + 'ci/intel-scripts/cache_exclude_windows.sh', + 'ci/ciimage/opensuse/install.sh', + 'ci/ciimage/ubuntu-rolling/install.sh', + 'ci/ciimage/ubuntu-rolling/test.sh', + 'ci/ciimage/cuda-cross/install.sh', + 'ci/ciimage/cuda/install.sh', + 'ci/ciimage/bionic/install.sh', + 'ci/ciimage/fedora/install.sh', + 'ci/ciimage/arch/install.sh', + 'ci/ciimage/gentoo/install.sh', + 'manual tests/4 standalone binaries/myapp.sh', + 'manual tests/4 standalone binaries/osx_bundler.sh', + 'manual tests/4 standalone binaries/linux_bundler.sh', + 'manual tests/4 standalone binaries/build_osx_package.sh', + 'manual tests/4 standalone binaries/build_linux_package.sh', + 'test cases/failing test/3 ambiguous/test_runner.sh', + 'test cases/common/190 install_mode/runscript.sh', + 'test cases/common/48 file grabber/grabber.sh', + 'test cases/common/12 data/runscript.sh', + 'test cases/common/33 run program/scripts/hello.sh', + ) + + +def check_bad_files(filename_glob): + num_errors = 0 + for f in pathlib.Path('.').glob(f'**/{filename_glob}'): + if str(f) not in permitted_files: + print('Forbidden file type:', f) + num_errors += 1 + return num_errors + +def check_deletions(): + num_errors = 0 + for f in permitted_files: + p = pathlib.Path(f) + if not p.is_file(): + print('Exception list has a file that does not exist:', f) + num_errors += 1 + return num_errors + +def check_shell_usage(): + total_errors = 0 + total_errors += check_bad_files('Makefile') + total_errors += check_bad_files('*.sh') + total_errors += check_bad_files('*.awk') + total_errors += check_deletions() + return total_errors + +if __name__ == '__main__': + sys.exit(check_shell_usage()) + diff --git a/run_tests.py b/run_tests.py index 4e22028..0548671 100755 --- a/run_tests.py +++ b/run_tests.py @@ -32,7 +32,9 @@ from mesonbuild import mesonlib from mesonbuild import mesonmain from mesonbuild import mtest from mesonbuild import mlog -from mesonbuild.environment import Environment, detect_ninja, detect_machine_info +from mesonbuild.environment import Environment +from mesonbuild.envconfig import detect_machine_info +from mesonbuild.tooldetect import detect_ninja from mesonbuild.coredata import version as meson_version from mesonbuild.options import backendlist from mesonbuild.mesonlib import setup_vsenv @@ -135,10 +137,6 @@ class FakeBuild: def __init__(self, env): self.environment = env -class FakeCompilerOptions: - def __init__(self): - self.value = [] - def get_fake_options(prefix: str = '') -> SharedCMDOptions: opts = T.cast('SharedCMDOptions', argparse.Namespace()) opts.native_file = [] @@ -153,7 +151,6 @@ def get_fake_env(sdir: str = '', bdir: T.Optional[str] = None, prefix: str = '', if opts is None: opts = get_fake_options(prefix) env = Environment(sdir, bdir, opts) - env.coredata.optstore.set_value_object(OptionKey('c_args'), FakeCompilerOptions()) env.machines.host.cpu_family = 'x86_64' # Used on macOS inside find_library # Invalidate cache when using a different Environment object. clear_meson_configure_class_caches() @@ -294,7 +291,7 @@ def run_mtest_inprocess(commandlist: T.List[str]) -> T.Tuple[int, str]: def clear_meson_configure_class_caches() -> None: CCompiler.find_library_cache.clear() CCompiler.find_framework_cache.clear() - PkgConfigInterface.class_impl.assign(False, False) + PkgConfigInterface.class_impl.assign({}, {}) mesonlib.project_meson_versions.clear() def run_configure_inprocess(commandlist: T.List[str], env: T.Optional[T.Dict[str, str]] = None, catch_exception: bool = False) -> T.Tuple[int, str, str]: diff --git a/run_unittests.py b/run_unittests.py index 84edb34..6a84f5c 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -11,43 +11,12 @@ import time import subprocess import os import unittest +import importlib +import typing as T -import mesonbuild.mlog -import mesonbuild.depfile -import mesonbuild.dependencies.base -import mesonbuild.dependencies.factory -import mesonbuild.compilers -import mesonbuild.envconfig -import mesonbuild.environment import mesonbuild.coredata -import mesonbuild.modules.gnome from mesonbuild.mesonlib import python_command, setup_vsenv -import mesonbuild.modules.pkgconfig -from unittests.allplatformstests import AllPlatformTests -from unittests.cargotests import CargoVersionTest, CargoCfgTest, CargoLockTest -from unittests.darwintests import DarwinTests -from unittests.failuretests import FailureTests -from unittests.linuxcrosstests import LinuxCrossArmTests, LinuxCrossMingwTests -from unittests.machinefiletests import NativeFileTests, CrossFileTests -from unittests.rewritetests import RewriterTests -from unittests.taptests import TAPParserTests -from unittests.datatests import DataTests -from unittests.internaltests import InternalTests -from unittests.linuxliketests import LinuxlikeTests -from unittests.pythontests import PythonTests -from unittests.subprojectscommandtests import SubprojectsCommandTests -from unittests.windowstests import WindowsTests -from unittests.platformagnostictests import PlatformAgnosticTests - -def unset_envs(): - # For unit tests we must fully control all command lines - # so that there are no unexpected changes coming from the - # environment, for example when doing a package build. - varnames = ['CPPFLAGS', 'LDFLAGS'] + list(mesonbuild.compilers.compilers.CFLAGS_MAPPING.values()) - for v in varnames: - if v in os.environ: - del os.environ[v] def convert_args(argv): # If we got passed a list of tests, pass it on @@ -100,15 +69,33 @@ def setup_backend(): os.environ['MESON_UNIT_TEST_BACKEND'] = be sys.argv = filtered +def import_test_cases(suite: unittest.TestSuite) -> T.Set[str]: + ''' + Imports all test classes into the current module and returns their names + ''' + classes = set() + for test in suite: + if isinstance(test, unittest.TestSuite): + classes.update(import_test_cases(test)) + elif isinstance(test, unittest.TestCase): + mod = importlib.import_module(test.__module__) + class_name = test.__class__.__name__ + test_class = getattr(mod, class_name) + classes.add(class_name) + setattr(sys.modules[__name__], class_name, test_class) + return classes + +def discover_test_cases() -> T.Set[str]: + current_dir = os.path.dirname(os.path.realpath(__file__)) + loader = unittest.TestLoader() + suite = loader.discover(os.path.join(current_dir, 'unittests'), '*tests.py', current_dir) + if loader.errors: + raise SystemExit(loader.errors) + return import_test_cases(suite) + def main(): - unset_envs() + cases = sorted(discover_test_cases()) setup_backend() - cases = ['InternalTests', 'DataTests', 'AllPlatformTests', 'FailureTests', - 'PythonTests', 'NativeFileTests', 'RewriterTests', 'CrossFileTests', - 'TAPParserTests', 'SubprojectsCommandTests', 'PlatformAgnosticTests', - - 'LinuxlikeTests', 'LinuxCrossArmTests', 'LinuxCrossMingwTests', - 'WindowsTests', 'DarwinTests'] try: import pytest # noqa: F401 diff --git a/test cases/cmake/13 system includes/main2.cpp b/test cases/cmake/13 system includes/main2.cpp new file mode 100644 index 0000000..a94a116 --- /dev/null +++ b/test cases/cmake/13 system includes/main2.cpp @@ -0,0 +1,5 @@ +#include <triggerWarn.hpp> + +int main(void) { + return 0; +} diff --git a/test cases/cmake/13 system includes/meson.build b/test cases/cmake/13 system includes/meson.build index 1265d46..fe71580 100644 --- a/test cases/cmake/13 system includes/meson.build +++ b/test cases/cmake/13 system includes/meson.build @@ -13,6 +13,10 @@ endif cm = import('cmake') sub_pro = cm.subproject('cmMod') sub_dep = sub_pro.dependency('cmModLib') +sub_inc = sub_pro.include_directories('cmModLib') exe1 = executable('main1', ['main.cpp'], dependencies: [sub_dep]) test('test1', exe1) + +exe2 = executable('main2', ['main2.cpp'], include_directories: sub_inc) +test('test2', exe1) diff --git a/test cases/cmake/2 advanced/meson.build b/test cases/cmake/2 advanced/meson.build index 39da0c6..43b5f8b 100644 --- a/test cases/cmake/2 advanced/meson.build +++ b/test cases/cmake/2 advanced/meson.build @@ -27,3 +27,21 @@ test('test3', sub_pro.target('testEXE')) # Test that we can add a new target with the same name as the CMake subproject exe4 = executable('testEXE', ['main.cpp'], dependencies: [sub_sta]) test('test4', exe4) + +# Test if libraries are named correctly +assert(sub_pro.target_type('cmModLib') == 'shared_library', 'Type should be shared_library') +expected_soversion_str = '1' +expected_version_str = '1.0.1' +lib_file_name = import('fs').name(sub_pro.target('cmModLib').full_path()) +if host_machine.system() == 'linux' + # Linux shared libraries end with their version: libcmModLib.so.1.0.1 + lib_version_ok = lib_file_name.endswith(expected_version_str) +elif host_machine.system() == 'darwin' + # MacOS shared libraries are suffixed with their soversion: libcmModLib.1.dylib + lib_version_ok = lib_file_name.split('.')[1] == expected_soversion_str +else + # Don't try to assert anything about the library version, either unknown host + # system or host system doesn't support versioning of shared libraries + lib_version_ok = true +endif +assert(lib_version_ok, f'Shared library version @lib_file_name@ not picked up correctly') diff --git a/test cases/common/104 has arg/meson.build b/test cases/common/104 has arg/meson.build index 466bed9..500b8a9 100644 --- a/test cases/common/104 has arg/meson.build +++ b/test cases/common/104 has arg/meson.build @@ -56,9 +56,22 @@ if cpp.get_id() == 'gcc' and cpp.version().version_compare('>=12.1.0') # Handle special -Wno-attributes=foo where -Wattributes=foo is invalid # i.e. our usual -Wno-foo -Wfoo hack doesn't work for -Wattributes=foo. assert(cpp.has_argument('-Wno-attributes=meson::i_do_not_exist')) - # Likewise, the positive counterpart to -Wno-vla-larger-than is - # -Wvla-larger-than=N - assert(cpp.has_argument('-Wno-vla-larger-than')) +endif + +if cpp.get_id() == 'gcc' + # Handle negative flags whose positive counterparts require a value to be + # specified. + if cpp.version().version_compare('>=4.4.0') + assert(cpp.has_argument('-Wno-frame-larger-than')) + endif + if cpp.version().version_compare('>=4.7.0') + assert(cpp.has_argument('-Wno-stack-usage')) + endif + if cpp.version().version_compare('>=7.1.0') + assert(cpp.has_argument('-Wno-alloc-size-larger-than')) + assert(cpp.has_argument('-Wno-alloca-larger-than')) + assert(cpp.has_argument('-Wno-vla-larger-than')) + endif endif if cc.get_id() == 'clang' and cc.version().version_compare('<=4.0.0') diff --git a/test cases/common/109 custom target capture/meson.build b/test cases/common/109 custom target capture/meson.build index b762201..9fd7e22 100644 --- a/test cases/common/109 custom target capture/meson.build +++ b/test cases/common/109 custom target capture/meson.build @@ -22,3 +22,13 @@ if not os.path.exists(sys.argv[1]): ''' test('capture-wrote', python3, args : ['-c', ct_output_exists, mytarget]) + +mytarget = custom_target('bindat', + output : 'data.dat', + input : 'data_source.txt', + build_subdir : 'subdir2', + capture : true, + command : [python3, comp, '@INPUT@'], + install : true, + install_dir : 'subdir2' +) diff --git a/test cases/common/109 custom target capture/test.json b/test cases/common/109 custom target capture/test.json index ba66b02..663a8f3 100644 --- a/test cases/common/109 custom target capture/test.json +++ b/test cases/common/109 custom target capture/test.json @@ -1,5 +1,6 @@ { "installed": [ - {"type": "file", "file": "usr/subdir/data.dat"} + {"type": "file", "file": "usr/subdir/data.dat"}, + {"type": "file", "file": "usr/subdir2/data.dat"} ] } diff --git a/test cases/common/117 shared module/meson.build b/test cases/common/117 shared module/meson.build index 94d17a7..494ce42 100644 --- a/test cases/common/117 shared module/meson.build +++ b/test cases/common/117 shared module/meson.build @@ -34,6 +34,14 @@ test('import test', e, args : m) m2 = build_target('mymodule2', 'module.c', target_type: 'shared_module') test('import test 2', e, args : m2) +# Same as above, but built and installed in a sub directory +m2_subdir = build_target('mymodule2', 'module.c', + target_type: 'shared_module', + build_subdir: 'subdir', + install: true, + install_dir: join_paths(get_option('libdir'), 'modules/subdir')) +test('import test 2 subdir', e, args : m2_subdir) + # Shared module that does not export any symbols shared_module('nosyms', 'nosyms.c', override_options: ['werror=false'], diff --git a/test cases/common/117 shared module/test.json b/test cases/common/117 shared module/test.json index 33bfeff..b24149c 100644 --- a/test cases/common/117 shared module/test.json +++ b/test cases/common/117 shared module/test.json @@ -2,6 +2,9 @@ "installed": [ {"type": "expr", "file": "usr/lib/modules/libnosyms?so"}, {"type": "implibempty", "file": "usr/lib/modules/libnosyms"}, - {"type": "pdb", "file": "usr/lib/modules/nosyms"} + {"type": "pdb", "file": "usr/lib/modules/nosyms"}, + {"type": "expr", "file": "usr/lib/modules/subdir/libmymodule2?so"}, + {"type": "implib", "file": "usr/lib/modules/subdir/libmymodule2"}, + {"type": "pdb", "file": "usr/lib/modules/subdir/mymodule2"} ] } diff --git a/test cases/common/14 configure file/CMakeLists.txt b/test cases/common/14 configure file/CMakeLists.txt new file mode 100644 index 0000000..6a894b0 --- /dev/null +++ b/test cases/common/14 configure file/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.12) + +project("configure file test" LANGUAGES C) + +set("var1" "foo") +set("var2" "bar") +configure_file("config7.h.in" "config7.h") + +set("var" "foo") +configure_file("config10.h.in" "config10.h") diff --git a/test cases/common/14 configure file/config7.h.in b/test cases/common/14 configure file/config7.h.in index edd0bb3..5180c2f 100644 --- a/test cases/common/14 configure file/config7.h.in +++ b/test cases/common/14 configure file/config7.h.in @@ -1,16 +1,11 @@ -/* No escape */ +/* cmake substitions cannot be escaped */ #define MESSAGE1 "${var1}" - -/* Single escape means no replace */ #define MESSAGE2 "\${var1}" - -/* Replace pairs of escapes before '@' or '\@' with escape characters - * (note we have to double number of pairs due to C string escaping) - */ #define MESSAGE3 "\\\\${var1}" - -/* Pairs of escapes and then single escape to avoid replace */ #define MESSAGE4 "\\\\\${var1}" +#define MESSAGE5 "@var1@" +#define MESSAGE6 "\\@var1@" +#define MESSAGE7 "\\\\@var1@" -/* Check escape character outside variables */ -#define MESSAGE5 "\\ ${ \${ \\\\${ \\\\\${" +/* backslash is an invalid variable character */ +#define MESSAGE8 "@var1\@" diff --git a/test cases/common/14 configure file/meson.build b/test cases/common/14 configure file/meson.build index 3a4ff4d..80a5d82 100644 --- a/test cases/common/14 configure file/meson.build +++ b/test cases/common/14 configure file/meson.build @@ -30,6 +30,13 @@ configure_file(input : files('config.h.in'), output : 'config2.h', configuration : conf) +# Test if build_subdir works +configure_file(input : files('config.h.in'), + output : 'config2.h', + build_subdir : 'config-subdir', + install_dir : 'share/appdir/config-subdir', + configuration : conf) + # Now generate a header file with an external script. genprog = import('python3').find_python() scriptfile = '@0@/generator.py'.format(meson.current_source_dir()) diff --git a/test cases/common/14 configure file/prog7.c b/test cases/common/14 configure file/prog7.c index 802bc46..900522c 100644 --- a/test cases/common/14 configure file/prog7.c +++ b/test cases/common/14 configure file/prog7.c @@ -3,8 +3,12 @@ int main(void) { return strcmp(MESSAGE1, "foo") - || strcmp(MESSAGE2, "${var1}") - || strcmp(MESSAGE3, "\\foo") - || strcmp(MESSAGE4, "\\${var1}") - || strcmp(MESSAGE5, "\\ ${ ${ \\${ \\${"); + || strcmp(MESSAGE2, "\foo") + || strcmp(MESSAGE3, "\\\\foo") + || strcmp(MESSAGE4, "\\\\\foo") + || strcmp(MESSAGE5, "foo") + || strcmp(MESSAGE6, "\\foo") + || strcmp(MESSAGE7, "\\\\foo") + || strcmp(MESSAGE8, "@var1\@") + || 0; } diff --git a/test cases/common/14 configure file/test.json b/test cases/common/14 configure file/test.json index 5a6ccd5..51e6770 100644 --- a/test cases/common/14 configure file/test.json +++ b/test cases/common/14 configure file/test.json @@ -4,6 +4,7 @@ {"type": "file", "file": "usr/share/appdir/config2b.h"}, {"type": "file", "file": "usr/share/appdireh/config2-1.h"}, {"type": "file", "file": "usr/share/appdirok/config2-2.h"}, - {"type": "file", "file": "usr/share/configure file test/invalid-utf8-1.bin"} + {"type": "file", "file": "usr/share/configure file test/invalid-utf8-1.bin"}, + {"type": "file", "file": "usr/share/appdir/config-subdir/config2.h"} ] } diff --git a/test cases/common/182 find override/broken.py b/test cases/common/182 find override/broken.py new file mode 100755 index 0000000..e7c2af0 --- /dev/null +++ b/test cases/common/182 find override/broken.py @@ -0,0 +1,3 @@ +#! /usr/bin/env python3 + +raise SystemExit(1) diff --git a/test cases/common/182 find override/meson.build b/test cases/common/182 find override/meson.build index edb1687..7f68991 100644 --- a/test cases/common/182 find override/meson.build +++ b/test cases/common/182 find override/meson.build @@ -1,4 +1,4 @@ -project('find program override', 'c') +project('find program override', 'c', version: '1.0.0') gencodegen = find_program('gencodegen', required : false) six_prog = find_program('six_meson_exe', required : false) @@ -29,3 +29,11 @@ assert(six_prog.full_path() == six_prog.path()) prog = find_program('prog-version.py', 'prog-version', version: '>= 2.0') assert(prog.found()) assert(prog.version() == '2.0') + +# Meson should not invoke the script to get its version, it uses current project +# version. +script = files('broken.py') +meson.override_find_program('broken', script) +prog = find_program('broken', version: '>= 1.0') +assert(prog.found()) +assert(prog.version() == '1.0.0') diff --git a/test cases/common/188 dict/meson.build b/test cases/common/188 dict/meson.build index 2f4c4c9..020ae3c 100644 --- a/test cases/common/188 dict/meson.build +++ b/test cases/common/188 dict/meson.build @@ -16,12 +16,24 @@ endforeach assert(i == 3, 'There should be three elements in that dictionary') +# Test keys() works as expected (note that it sorts the keys) +assert(dict.keys() == ['baz', 'foo', 'foo bar'], 'Keys returned unexpected list') + +# Test values() works as expected (note that it sorts first, to match keys()) +assert(dict.values() == ['foo', 'bar', 'baz']) + empty_dict = {} foreach key, value : empty_dict assert(false, 'This dict should be empty') endforeach +# Test keys() returns an empty list +assert(empty_dict.keys() == [], 'Keys for an empty dict should be an empty list') + +# Test values() returns an empty list +assert(empty_dict.values() == [], 'Values for an empty dict should be an empty list') + d1 = empty_dict + {'a' : 'b'} assert(d1 == {'a' : 'b'}, 'dict addition is not working') diff --git a/test cases/common/197 function attributes/meson.build b/test cases/common/197 function attributes/meson.build index db7e3af..a2dc5b7 100644 --- a/test cases/common/197 function attributes/meson.build +++ b/test cases/common/197 function attributes/meson.build @@ -117,7 +117,16 @@ if ['gcc', 'intel'].contains(c.get_id()) endif endif +if c.get_id() == 'clang' + if c.version().version_compare('>= 18.0.0') + attributes += 'counted_by' + endif +endif + if c.get_id() == 'gcc' + if c.version().version_compare('>= 15.1') + attributes += 'counted_by' + endif if c.version().version_compare('>= 14.1') attributes += 'null_terminated_string_arg' endif @@ -128,19 +137,27 @@ if get_option('mode') == 'single' x = c.has_function_attribute(a) expected_result = expected.get(a, expected_default) assert(x == expected_result, '@0@: @1@'.format(c.get_id(), a)) - x = cpp.has_function_attribute(a) - assert(x == expected_result, '@0@: @1@'.format(cpp.get_id(), a)) + # counted_by is not currently supported by GCC or clang on C++ + if a != 'counted_by' + x = cpp.has_function_attribute(a) + assert(x == expected_result, '@0@: @1@'.format(cpp.get_id(), a)) + endif endforeach else - multi_expected = [] + multi_expected_c = [] + multi_expected_cpp = [] foreach a : attributes if expected.get(a, expected_default) - multi_expected += a + multi_expected_c += a + # counted_by is not currently supported by GCC or clang on C++ + if a != 'counted_by' + multi_expected_cpp += a + endif endif endforeach multi_check = c.get_supported_function_attributes(attributes) - assert(multi_check == multi_expected, 'get_supported_function_arguments works (C)') + assert(multi_check == multi_expected_c, 'get_supported_function_arguments works (C)') multi_check = cpp.get_supported_function_attributes(attributes) - assert(multi_check == multi_expected, 'get_supported_function_arguments works (C++)') + assert(multi_check == multi_expected_cpp, 'get_supported_function_arguments works (C++)') endif diff --git a/test cases/common/22 object extraction/meson.build b/test cases/common/22 object extraction/meson.build index 37ac66d..cc6de6f 100644 --- a/test cases/common/22 object extraction/meson.build +++ b/test cases/common/22 object extraction/meson.build @@ -1,50 +1,57 @@ project('object extraction', 'c') if meson.is_unity() - message('Skipping extraction test because this is a Unity build.') -else - lib1 = library('somelib', 'src/lib.c') - lib2 = library('somelib2', 'lib.c', 'header.h', 'lib2.c') - - obj1 = lib1.extract_objects('src/lib.c') - obj2 = lib2.extract_objects(['lib.c']) - obj3 = lib2.extract_objects(files('lib.c')) - obj4 = lib2.extract_objects(['lib.c', 'lib.c']) - obj5 = lib2.extract_objects(['lib.c', 'header.h']) - obj6 = lib2.extract_all_objects(recursive: true) - - e1 = executable('main1', 'main.c', objects : obj1) - e2 = executable('main2', 'main.c', objects : obj2) - e3 = executable('main3', 'main.c', objects : obj3) - e4 = executable('main4', 'main.c', objects : obj4) - e5 = executable('main5', 'main.c', objects : obj5) - e6 = executable('main6', 'main.c', objects : obj6) - - ct_src = custom_target('lib3.c', output: 'lib3.c', capture: true, - command: [find_program('create-source.py'), 'lib.c']) - lib3 = library('somelib3', ct_src) - e7 = executable('main7', 'main.c', objects: lib3.extract_objects(ct_src[0])) - e8 = executable('main8', 'main.c', objects: lib3.extract_objects(ct_src)) + error('MESON_SKIP_TEST, Skipping extraction test because this is a Unity build.') +endif - gen = generator(find_program('create-source.py'), arguments: ['@INPUT@'], - output: '@BASENAME@4.c', capture: true) - gen_src = gen.process('lib.c') - lib4 = library('somelib4', gen_src) - e9 = executable('main9', 'main.c', objects: lib4.extract_objects(gen_src)) +lib1 = library('somelib', 'src/lib.c') +lib2 = library('somelib2', 'lib.c', 'header.h', 'lib2.c') +obj1 = lib1.extract_objects('src/lib.c') +obj2 = lib2.extract_objects(['lib.c']) +obj3 = lib2.extract_objects(files('lib.c')) +obj4 = lib2.extract_objects(['lib.c', 'lib.c']) +obj5 = lib2.extract_objects(['lib.c', 'header.h']) +obj6 = lib2.extract_all_objects(recursive: true) +e1 = executable('main1', 'main.c', objects : obj1) +e2 = executable('main2', 'main.c', objects : obj2) +e3 = executable('main3', 'main.c', objects : obj3) +e4 = executable('main4', 'main.c', objects : obj4) +e5 = executable('main5', 'main.c', objects : obj5) +e6 = executable('main6', 'main.c', objects : obj6) +ct_src = custom_target('lib3.c', output: 'lib3.c', capture: true, + command: [find_program('create-source.py'), 'lib.c']) +lib3 = library('somelib3', ct_src) +e7 = executable('main7', 'main.c', objects: lib3.extract_objects(ct_src[0])) +e8 = executable('main8', 'main.c', objects: lib3.extract_objects(ct_src)) +gen = generator(find_program('create-source.py'), arguments: ['@INPUT@'], + output: '@BASENAME@4.c', capture: true) +gen_src = gen.process('lib.c') +lib4 = library('somelib4', gen_src) +e9 = executable('main9', 'main.c', objects: lib4.extract_objects(gen_src)) +custom_target('custom_target with object inputs', output: 'objs', + input: [obj1, obj2, obj3, obj5, obj6], + build_by_default: true, + command: [find_program('check-obj.py'), meson.backend(), '@INPUT@'], + capture: true) +test('extraction test 1', e1) +test('extraction test 2', e2) +test('extraction test 3', e3) +test('extraction test 4', e4) +test('extraction test 5', e5) +test('extraction test 6', e6) +test('extraction test 7', e7) +test('extraction test 8', e8) +test('extraction test 9', e9) - custom_target('custom_target with object inputs', output: 'objs', - input: [obj1, obj2, obj3, obj5, obj6], - build_by_default: true, - command: [find_program('check-obj.py'), meson.backend(), '@INPUT@'], - capture: true) +lib = subproject('sub').get_variable('lib') +testcase expect_error('Tried to extract objects from a different subproject.') + lib.extract_objects() +endtestcase - test('extraction test 1', e1) - test('extraction test 2', e2) - test('extraction test 3', e3) - test('extraction test 4', e4) - test('extraction test 5', e5) - test('extraction test 6', e6) - test('extraction test 7', e7) - test('extraction test 8', e8) - test('extraction test 9', e9) +cc = meson.get_compiler('c') +lib = cc.find_library('z', required: false) +if lib.found() + testcase expect_error('Unknown method .*', how: 're') + lib.extract_objects() + endtestcase endif diff --git a/test cases/common/22 object extraction/subprojects/sub/meson.build b/test cases/common/22 object extraction/subprojects/sub/meson.build new file mode 100644 index 0000000..e7fbb28 --- /dev/null +++ b/test cases/common/22 object extraction/subprojects/sub/meson.build @@ -0,0 +1,4 @@ +project('sub', 'c') + +lib = static_library('a', 'source.c') +lib.extract_objects() diff --git a/test cases/common/22 object extraction/subprojects/sub/source.c b/test cases/common/22 object extraction/subprojects/sub/source.c new file mode 100644 index 0000000..0584ef4 --- /dev/null +++ b/test cases/common/22 object extraction/subprojects/sub/source.c @@ -0,0 +1,5 @@ +int func(void); + +int func(void) { + return 1; +} diff --git a/test cases/common/220 fs module/meson.build b/test cases/common/220 fs module/meson.build index e5397ee..383b263 100644 --- a/test cases/common/220 fs module/meson.build +++ b/test cases/common/220 fs module/meson.build @@ -52,6 +52,7 @@ unixabs = '/foo' if is_windows assert(fs.is_absolute(winabs), 'is_absolute windows not detected') assert(not fs.is_absolute(unixabs), 'is_absolute unix false positive') + assert(fs.is_absolute('//foo'), 'is_absolute failed on incomplete UNC path') else assert(fs.is_absolute(unixabs), 'is_absolute unix not detected') assert(not fs.is_absolute(winabs), 'is_absolute windows false positive') @@ -84,7 +85,7 @@ assert(new == 'foo', 'replace_suffix did not only delete last suffix') # `/` on windows is interpreted like `.drive` which in general may not be `c:/` # the files need not exist for fs.replace_suffix() -original = is_windows ? 'j:/foo/bar.txt' : '/foo/bar.txt' +original = is_windows ? 'j:\\foo\\bar.txt' : '/foo/bar.txt' new_check = is_windows ? 'j:\\foo\\bar.ini' : '/foo/bar.ini' new = fs.replace_suffix(original, '.ini') @@ -139,11 +140,7 @@ endif # parts of path assert(fs.parent('foo/bar') == 'foo', 'failed to get dirname') -if not is_windows -assert(fs.parent(f[1]) == 'subdir/..', 'failed to get dirname') -else -assert(fs.parent(f[1]) == 'subdir\..', 'failed to get dirname') -endif +assert(fs.parent(f[1]) == 'subdir/..', 'failed to get dirname for file') assert(fs.parent(btgt) == '.', 'failed to get dirname for build target') assert(fs.parent(ctgt) == '.', 'failed to get dirname for custom target') assert(fs.parent(ctgt[0]) == '.', 'failed to get dirname for custom target index') @@ -153,8 +150,10 @@ assert(fs.name(f[1]) == 'meson.build', 'failed to get basename') assert(fs.name('foo/bar/baz.dll.a') == 'baz.dll.a', 'failed to get basename with compound suffix') if host_machine.system() in ['cygwin', 'windows'] assert(fs.name(btgt) == 'btgt.exe', 'failed to get basename of build target') + assert(fs.suffix(btgt) == '.exe', 'failed to get build target suffix') else assert(fs.name(btgt) == 'btgt', 'failed to get basename of build target') + assert(fs.suffix(btgt) == '', 'failed to get build target suffix') endif assert(fs.name(ctgt) == 'ctgt.txt', 'failed to get basename of custom target') assert(fs.name(ctgt[0]) == 'ctgt.txt', 'failed to get basename of custom target index') @@ -163,6 +162,12 @@ assert(fs.stem('foo/bar/baz.dll.a') == 'baz.dll', 'failed to get stem with compo assert(fs.stem(btgt) == 'btgt', 'failed to get stem of build target') assert(fs.stem(ctgt) == 'ctgt', 'failed to get stem of custom target') assert(fs.stem(ctgt[0]) == 'ctgt', 'failed to get stem of custom target index') +assert(fs.suffix('foo/bar/baz') == '', 'failed to get missing suffix') +assert(fs.suffix('foo/bar/baz.') == '.', 'failed to get empty suffix') +assert(fs.suffix('foo/bar/baz.dll') == '.dll', 'failed to get plain suffix') +assert(fs.suffix('foo/bar/baz.dll.a') == '.a', 'failed to get final suffix') +assert(fs.suffix(ctgt) == '.txt', 'failed to get suffix of custom target') +assert(fs.suffix(ctgt[0]) == '.txt', 'failed to get suffix of custom target index') # relative_to if build_machine.system() == 'windows' diff --git a/test cases/common/247 deprecated option/test.json b/test cases/common/247 deprecated option/test.json index a644b04..4b9a475 100644 --- a/test cases/common/247 deprecated option/test.json +++ b/test cases/common/247 deprecated option/test.json @@ -26,77 +26,77 @@ }, "stdout": [ { - "line": ".*DEPRECATION: Option 'o1' is deprecated", + "line": ".*DEPRECATION: Option \"o1\" is deprecated", "match": "re", "count": 1 }, { - "line": ".*DEPRECATION: Option 'o2' value 'a' is deprecated", + "line": ".*DEPRECATION: Option \"o2\" value 'a' is deprecated", "match": "re", "count": 1 }, { - "line": ".*DEPRECATION: Option 'o3' value 'a' is replaced by 'c'", + "line": ".*DEPRECATION: Option \"o3\" value 'a' is replaced by 'c'", "match": "re", "count": 1 }, { - "line": ".*DEPRECATION: Option 'o4' value 'true' is replaced by 'enabled'", + "line": ".*DEPRECATION: Option \"o4\" value 'true' is replaced by 'enabled'", "match": "re", "count": 1 }, { - "line": ".*DEPRECATION: Option 'o5' value 'auto' is replaced by 'false'", + "line": ".*DEPRECATION: Option \"o5\" value 'auto' is replaced by 'false'", "match": "re", "count": 1 }, { - "line": ".*DEPRECATION: Option 'p1' is deprecated", + "line": ".*DEPRECATION: Option \"p1\" is deprecated", "match": "re", "count": 1 }, { - "line": ".*DEPRECATION: Option 'p2' value 'a' is deprecated", + "line": ".*DEPRECATION: Option \"p2\" value 'a' is deprecated", "match": "re", "count": 1 }, { - "line": ".*DEPRECATION: Option 'p3' value 'a' is replaced by 'c'", + "line": ".*DEPRECATION: Option \"p3\" value 'a' is replaced by 'c'", "match": "re", "count": 1 }, { - "line": ".*DEPRECATION: Option 'p4' value 'true' is replaced by 'enabled'", + "line": ".*DEPRECATION: Option \"p4\" value 'true' is replaced by 'enabled'", "match": "re", "count": 1 }, { - "line": ".*DEPRECATION: Option 'p5' value 'auto' is replaced by 'false'", + "line": ".*DEPRECATION: Option \"p5\" value 'auto' is replaced by 'false'", "match": "re", "count": 1 }, { - "line": ".*DEPRECATION: Option 'c1' is deprecated", + "line": ".*DEPRECATION: Option \"c1\" is deprecated", "match": "re", "count": 1 }, { - "line": ".*DEPRECATION: Option 'c2' value 'a' is deprecated", + "line": ".*DEPRECATION: Option \"c2\" value 'a' is deprecated", "match": "re", "count": 1 }, { - "line": ".*DEPRECATION: Option 'c3' value 'a' is replaced by 'c'", + "line": ".*DEPRECATION: Option \"c3\" value 'a' is replaced by 'c'", "match": "re", "count": 1 }, { - "line": ".*DEPRECATION: Option 'c4' value 'true' is replaced by 'enabled'", + "line": ".*DEPRECATION: Option \"c4\" value 'true' is replaced by 'enabled'", "match": "re", "count": 1 }, { - "line": ".*DEPRECATION: Option 'c5' value 'auto' is replaced by 'false'", + "line": ".*DEPRECATION: Option \"c5\" value 'auto' is replaced by 'false'", "match": "re", "count": 1 } diff --git a/test cases/common/26 find program/meson.build b/test cases/common/26 find program/meson.build index a20f6b4..72d311c 100644 --- a/test cases/common/26 find program/meson.build +++ b/test cases/common/26 find program/meson.build @@ -1,4 +1,4 @@ -project('find program') +project('find program', meson_version: '>= 1.10.0',) if build_machine.system() == 'windows' # Things Windows does not provide: @@ -40,3 +40,21 @@ assert(not prog.found(), 'Program should not be found') prog = find_program('test_subdir.py', dirs : ['/nonexistent', meson.current_source_dir() / 'scripts']) assert(prog.found(), 'Program should be found') + +prog = find_program('print-version.py') +if build_machine.system() != 'cygwin' + assert(prog.cmd_array() != [prog.full_path()]) + assert(prog.cmd_array().length() == 2) +endif + +ret = run_command(prog.cmd_array(),'--version', check: true) +assert(ret.returncode() == 0) +assert(ret.stdout().strip() == '1.0') + +if build_machine.system() == 'windows' + prog = find_program('cmd.exe') + assert(prog.cmd_array() == [prog.full_path()]) +else + prog = find_program('ld') + assert(prog.cmd_array() == [prog.full_path()]) +endif
\ No newline at end of file diff --git a/test cases/common/280 pkgconfig-gen/meson.build b/test cases/common/280 pkgconfig-gen/meson.build index 3f15888..42ce128 100644 --- a/test cases/common/280 pkgconfig-gen/meson.build +++ b/test cases/common/280 pkgconfig-gen/meson.build @@ -1,4 +1,4 @@ -project('pkgconfig-get', 'c') +project('pkgconfig-get', 'c', meson_version: '>=1.9.0') pkgg = import('pkgconfig') @@ -16,4 +16,5 @@ pkgg.generate( filebase : 'simple', description : 'A simple demo library.', libraries: [lib_dep], -)
\ No newline at end of file + license: 'Apache-2.0', +) diff --git a/test cases/common/281 subproj options/meson.build b/test cases/common/281 subproj options/meson.build index 55fb109..d450004 100644 --- a/test cases/common/281 subproj options/meson.build +++ b/test cases/common/281 subproj options/meson.build @@ -1,4 +1,3 @@ -project('pkg_opt_test') +project('pkg_opt_test', default_options: ['werror=false', 'sub:from_toplevel=true', 'sub:werror=true']) -subproject('sub') -subproject('sub2') +subproject('sub', default_options: ['sub2:from_subp=true']) diff --git a/test cases/common/281 subproj options/subprojects/sub/meson.build b/test cases/common/281 subproj options/subprojects/sub/meson.build index 82cd386..6cc4906 100644 --- a/test cases/common/281 subproj options/subprojects/sub/meson.build +++ b/test cases/common/281 subproj options/subprojects/sub/meson.build @@ -1,8 +1,12 @@ project('subproject', 'c') assert(get_option('bar') == true) +assert(get_option('werror') == true) +assert(get_option('from_toplevel') == true) # b_lto is only initialized if used, see test "common/40 options" cc = meson.get_compiler('c') if cc.get_id() in ['gcc', 'clang', 'clang-cl'] assert(get_option('b_lto') == true) endif + +subproject('sub2') diff --git a/test cases/common/281 subproj options/subprojects/sub/meson_options.txt b/test cases/common/281 subproj options/subprojects/sub/meson_options.txt index 129a7d4..7f94d02 100644 --- a/test cases/common/281 subproj options/subprojects/sub/meson_options.txt +++ b/test cases/common/281 subproj options/subprojects/sub/meson_options.txt @@ -1 +1,2 @@ option('bar', type: 'boolean', value: false) +option('from_toplevel', type: 'boolean', value: false) diff --git a/test cases/common/281 subproj options/subprojects/sub2/meson.build b/test cases/common/281 subproj options/subprojects/sub2/meson.build index 3b0ed92..65f3e5a 100644 --- a/test cases/common/281 subproj options/subprojects/sub2/meson.build +++ b/test cases/common/281 subproj options/subprojects/sub2/meson.build @@ -1,5 +1,7 @@ project('subproject', 'c') +assert(get_option('from_subp') == true) + # b_lto is only initialized if used, see test "common/40 options" cc = meson.get_compiler('c') if cc.get_id() in ['gcc', 'clang', 'clang-cl'] diff --git a/test cases/common/281 subproj options/subprojects/sub2/meson_options.txt b/test cases/common/281 subproj options/subprojects/sub2/meson_options.txt new file mode 100644 index 0000000..d645182 --- /dev/null +++ b/test cases/common/281 subproj options/subprojects/sub2/meson_options.txt @@ -0,0 +1 @@ +option('from_subp', type: 'boolean', value: false) diff --git a/test cases/common/282 test args and depends in path/libs/a/lib_a.c b/test cases/common/282 test args and depends in path/libs/a/lib_a.c new file mode 100644 index 0000000..7191a69 --- /dev/null +++ b/test cases/common/282 test args and depends in path/libs/a/lib_a.c @@ -0,0 +1,5 @@ +char +func_a (void) +{ + return 'a'; +} diff --git a/test cases/common/282 test args and depends in path/libs/a/lib_a.def b/test cases/common/282 test args and depends in path/libs/a/lib_a.def new file mode 100644 index 0000000..4af3bdb --- /dev/null +++ b/test cases/common/282 test args and depends in path/libs/a/lib_a.def @@ -0,0 +1,3 @@ +LIBRARY LIBA +EXPORTS + func_a diff --git a/test cases/common/282 test args and depends in path/libs/a/meson.build b/test cases/common/282 test args and depends in path/libs/a/meson.build new file mode 100644 index 0000000..0b4b6a4 --- /dev/null +++ b/test cases/common/282 test args and depends in path/libs/a/meson.build @@ -0,0 +1,5 @@ +lib_a = shared_library('a', + ['lib_a.c'], + name_prefix: 'lib', + gnu_symbol_visibility: 'default', + vs_module_defs: 'lib_a.def') diff --git a/test cases/common/282 test args and depends in path/libs/b/lib_b.c b/test cases/common/282 test args and depends in path/libs/b/lib_b.c new file mode 100644 index 0000000..17e5730 --- /dev/null +++ b/test cases/common/282 test args and depends in path/libs/b/lib_b.c @@ -0,0 +1,5 @@ +char +func_b (void) +{ + return 'b'; +} diff --git a/test cases/common/282 test args and depends in path/libs/b/lib_b.def b/test cases/common/282 test args and depends in path/libs/b/lib_b.def new file mode 100644 index 0000000..403a731 --- /dev/null +++ b/test cases/common/282 test args and depends in path/libs/b/lib_b.def @@ -0,0 +1,3 @@ +LIBRARY LIBB +EXPORTS + func_b diff --git a/test cases/common/282 test args and depends in path/libs/b/meson.build b/test cases/common/282 test args and depends in path/libs/b/meson.build new file mode 100644 index 0000000..766125d --- /dev/null +++ b/test cases/common/282 test args and depends in path/libs/b/meson.build @@ -0,0 +1,5 @@ +lib_b = shared_library('b', + ['lib_b.c'], + name_prefix: 'lib', + gnu_symbol_visibility: 'default', + vs_module_defs: 'lib_b.def') diff --git a/test cases/common/282 test args and depends in path/libs/meson.build b/test cases/common/282 test args and depends in path/libs/meson.build new file mode 100644 index 0000000..b00ea8a --- /dev/null +++ b/test cases/common/282 test args and depends in path/libs/meson.build @@ -0,0 +1,2 @@ +subdir('a') +subdir('b') diff --git a/test cases/common/282 test args and depends in path/meson.build b/test cases/common/282 test args and depends in path/meson.build new file mode 100644 index 0000000..d9dd9ad --- /dev/null +++ b/test cases/common/282 test args and depends in path/meson.build @@ -0,0 +1,19 @@ +project('test-args-and-depends-in-path', 'c') + +subdir('libs') + +dl_dep = dependency('dl', required: false) + +fs = import('fs') + +test_exe = executable('test-exe', + c_args: [ + '-DLIBA=' + fs.name(lib_a.full_path()), + '-DLIBB=' + fs.name(lib_b.full_path()), + ], + sources: ['test.c'], + dependencies: [dl_dep]) + +test ('test', test_exe, + args: [lib_a], + depends: [lib_b]) diff --git a/test cases/common/282 test args and depends in path/test.c b/test cases/common/282 test args and depends in path/test.c new file mode 100644 index 0000000..82452ba --- /dev/null +++ b/test cases/common/282 test args and depends in path/test.c @@ -0,0 +1,67 @@ +#include <stdlib.h> +#include <stddef.h> +#include <stdio.h> +#include <assert.h> + +#ifndef _WIN32 +#include <dlfcn.h> +#else +#include <windows.h> +#endif + +typedef struct { + const char *library_name; + const char *func_name; + char expected_result; +} test_t; + +static void +load (test_t *test) +{ +#ifndef _WIN32 + void *h = dlopen (test->library_name, RTLD_NOW | RTLD_LOCAL); + if (h == NULL) { + fprintf (stderr, "dlopen (%s) failed: %s\n", + test->library_name, dlerror ()); + exit (EXIT_FAILURE); + } + + typedef char (*func_t)(void); + func_t func = (func_t) dlsym (h, test->func_name); + assert (func != NULL); + + assert (func () == test->expected_result); + dlclose (h); +#else /* _WIN32 */ + HMODULE h = LoadLibraryA (test->library_name); + if (h == NULL) { + fprintf (stderr, "LoadLibrary (%s) failed with error code %u\n", + test->library_name, (unsigned int) GetLastError ()); + exit (EXIT_FAILURE); + } + + typedef char (*func_t)(void); + func_t func = (func_t) GetProcAddress (h, test->func_name); + assert (func != NULL); + + assert (func () == test->expected_result); + FreeLibrary (h); +#endif +} + +#define STRINGIFY_HELPER(x) #x +#define STRINGIFY(x) STRINGIFY_HELPER(x) + +int +main (void) +{ + test_t tests[] = { + {STRINGIFY (LIBA), "func_a", 'a'}, + {STRINGIFY (LIBB), "func_b", 'b'}, + }; + + for (size_t i = 0; i < sizeof (tests) / sizeof (tests[0]); i++) + load (&tests[i]); + + return 0; +} diff --git a/test cases/common/283 wrap override/meson.build b/test cases/common/283 wrap override/meson.build new file mode 100644 index 0000000..76c84d6 --- /dev/null +++ b/test cases/common/283 wrap override/meson.build @@ -0,0 +1,8 @@ +project('wrap override') + + +subproject('sub') + +# sub has subsub.wrap that provides subsub-1.0 dependency. Even if sub itself +# does not use it, that dependency should now be available. +dependency('subsub-1.0') diff --git a/test cases/common/283 wrap override/subprojects/sub/meson.build b/test cases/common/283 wrap override/subprojects/sub/meson.build new file mode 100644 index 0000000..abefb30 --- /dev/null +++ b/test cases/common/283 wrap override/subprojects/sub/meson.build @@ -0,0 +1,7 @@ +project('sub') + +# Simulate an optional feature that requires subsub.wrap, but that feature is +# not enabled. +if false + dependency('subsub-1.0') +endif diff --git a/test cases/common/283 wrap override/subprojects/sub/subprojects/subsub.wrap b/test cases/common/283 wrap override/subprojects/sub/subprojects/subsub.wrap new file mode 100644 index 0000000..85a1a7c --- /dev/null +++ b/test cases/common/283 wrap override/subprojects/sub/subprojects/subsub.wrap @@ -0,0 +1,5 @@ +[wrap-file] + + +[provide] +dependency_names = subsub-1.0 diff --git a/test cases/common/283 wrap override/subprojects/subsub/meson.build b/test cases/common/283 wrap override/subprojects/subsub/meson.build new file mode 100644 index 0000000..668dcb3 --- /dev/null +++ b/test cases/common/283 wrap override/subprojects/subsub/meson.build @@ -0,0 +1,5 @@ +project('sub') + +# This simulates a subproject we previously downloaded using +# subproject/sub/subproject/subsub.wrap +meson.override_dependency('subsub-1.0', declare_dependency()) diff --git a/test cases/common/284 pkgconfig subproject/meson.build b/test cases/common/284 pkgconfig subproject/meson.build new file mode 100644 index 0000000..3b1335b --- /dev/null +++ b/test cases/common/284 pkgconfig subproject/meson.build @@ -0,0 +1,13 @@ +project('simple', 'c', meson_version: '>=1.9.0') +pkgg = import('pkgconfig') + +simple2_dep = dependency('simple2') + +simple_lib = library('simple', + 'simple.c', + dependencies: [simple2_dep] +) + +pkgg.generate(simple_lib, +requires: simple2_dep, +) diff --git a/test cases/common/284 pkgconfig subproject/simple.c b/test cases/common/284 pkgconfig subproject/simple.c new file mode 100644 index 0000000..da1d909 --- /dev/null +++ b/test cases/common/284 pkgconfig subproject/simple.c @@ -0,0 +1,6 @@ +#include"simple.h" +#include <simple2.h> + +int simple_function(void) { + return simple_simple_function(); +} diff --git a/test cases/common/284 pkgconfig subproject/simple.h b/test cases/common/284 pkgconfig subproject/simple.h new file mode 100644 index 0000000..6896bfd --- /dev/null +++ b/test cases/common/284 pkgconfig subproject/simple.h @@ -0,0 +1,6 @@ +#ifndef SIMPLE_H_ +#define SIMPLE_H_ + +int simple_function(void); + +#endif diff --git a/test cases/common/284 pkgconfig subproject/subprojects/simple2/exports.def b/test cases/common/284 pkgconfig subproject/subprojects/simple2/exports.def new file mode 100644 index 0000000..42c911b --- /dev/null +++ b/test cases/common/284 pkgconfig subproject/subprojects/simple2/exports.def @@ -0,0 +1,2 @@ +EXPORTS + simple_simple_function @1 diff --git a/test cases/common/284 pkgconfig subproject/subprojects/simple2/meson.build b/test cases/common/284 pkgconfig subproject/subprojects/simple2/meson.build new file mode 100644 index 0000000..199fea6 --- /dev/null +++ b/test cases/common/284 pkgconfig subproject/subprojects/simple2/meson.build @@ -0,0 +1,9 @@ +project('simple2', 'c', meson_version: '>=1.9.0') +pkgg = import('pkgconfig') + +lib2 = library('simple2', 'simple2.c', vs_module_defs: 'exports.def') +lib_dep = declare_dependency(link_with: lib2, include_directories: include_directories('.')) + +pkgg.generate(lib2) + +meson.override_dependency('simple2', lib_dep) diff --git a/test cases/common/284 pkgconfig subproject/subprojects/simple2/simple2.c b/test cases/common/284 pkgconfig subproject/subprojects/simple2/simple2.c new file mode 100644 index 0000000..215b2ae --- /dev/null +++ b/test cases/common/284 pkgconfig subproject/subprojects/simple2/simple2.c @@ -0,0 +1,5 @@ +#include"simple2.h" + +int simple_simple_function(void) { + return 42; +} diff --git a/test cases/common/284 pkgconfig subproject/subprojects/simple2/simple2.h b/test cases/common/284 pkgconfig subproject/subprojects/simple2/simple2.h new file mode 100644 index 0000000..472e135 --- /dev/null +++ b/test cases/common/284 pkgconfig subproject/subprojects/simple2/simple2.h @@ -0,0 +1,6 @@ +#ifndef SIMPLE2_H_ +#define SIMPLE2_H_ + +int simple_simple_function(void); + +#endif diff --git a/test cases/common/284 pkgconfig subproject/test.json b/test cases/common/284 pkgconfig subproject/test.json new file mode 100644 index 0000000..db6b52f --- /dev/null +++ b/test cases/common/284 pkgconfig subproject/test.json @@ -0,0 +1,15 @@ +{ + "installed": [ + { "type": "file", "file": "usr/lib/pkgconfig/simple.pc"}, + { "type": "file", "file": "usr/lib/pkgconfig/simple2.pc"} + ], + "matrix": { + "options": { + "default_library": [ + { "val": "shared" }, + { "val": "static" }, + { "val": "both" } + ] + } + } +} diff --git a/test cases/failing/76 override exe config/foo.c b/test cases/common/285 atomic/a.c index 03b2213..03b2213 100644 --- a/test cases/failing/76 override exe config/foo.c +++ b/test cases/common/285 atomic/a.c diff --git a/test cases/common/285 atomic/meson.build b/test cases/common/285 atomic/meson.build new file mode 100644 index 0000000..323b9cc --- /dev/null +++ b/test cases/common/285 atomic/meson.build @@ -0,0 +1,23 @@ +project('meson system dependency', 'c', meson_version: '>=1.7.0') + +cc = meson.get_compiler('c') + +# We could check if dependency('atomic') actually finds something when +# we 'know' it exists (MESON_SKIP_TEST) but that's likely to be brittle, +# so don't bother (for now, at least). +atomic = dependency('atomic', required : false) + +# If the dependency provider says it found something, make sure it can +# be linked against (https://github.com/mesonbuild/meson/issues/14946). +dependencies = [ + atomic +] + +exe = executable( + 'a', + 'a.c', + dependencies : dependencies, + install : false, +) + +test('basic', exe) diff --git a/test cases/common/286 importstd/meson.build b/test cases/common/286 importstd/meson.build new file mode 100644 index 0000000..06e0504 --- /dev/null +++ b/test cases/common/286 importstd/meson.build @@ -0,0 +1,26 @@ +project('importstd', 'cpp', default_options: {'cpp_importstd': 'true', + 'cpp_std': 'c++latest,c++23,c++20'}) + +cpp = meson.get_compiler('cpp') + +cpp_id = cpp.get_id() +cpp_version = cpp.version() + +if cpp_id == 'gcc' and cpp_version.version_compare('>=15.3') + # Versions between 15.0 and 15.2 are too unreliable to test. + # The same version number of GCC works in some distros + # but fails in others. + istd_supported = true +elif cpp_id == 'msvc' and cpp_version.version_compare('>=19.44.35219') + istd_supported = get_option('backend') == 'ninja' +else + istd_supported = false +endif + +if istd_supported + useistd = executable('useistd', 'useistd.cpp') + test('useistd', useistd) + +else + message('Compiler not yet supported, import std test not run.') +endif diff --git a/test cases/common/286 importstd/useistd.cpp b/test cases/common/286 importstd/useistd.cpp new file mode 100644 index 0000000..70f62ec --- /dev/null +++ b/test cases/common/286 importstd/useistd.cpp @@ -0,0 +1,6 @@ +import std; + +int main(int, char**) { + std::print("Import STD is working.\n"); + return 0; +} diff --git a/test cases/common/32 has header/meson.build b/test cases/common/32 has header/meson.build index e6f6efb..7358be7 100644 --- a/test cases/common/32 has header/meson.build +++ b/test cases/common/32 has header/meson.build @@ -1,6 +1,7 @@ project('has header', 'c', 'cpp') host_system = host_machine.system() +cpp = meson.get_compiler('cpp') non_existent_header = 'ouagadougou.h' @@ -9,16 +10,20 @@ configure_file(input : non_existent_header, output : non_existent_header, configuration : configuration_data()) +is_libcxx = cpp.has_header_symbol('ciso646', '_LIBCPP_VERSION') + # Test that the fallback to __has_include also works on all compilers -if host_system != 'darwin' +# +# darwin: can't redefine builtin macros so the above doesn't work +# libcxx: Undefining __has_include() breaks LLVM libcxx platform.h +if host_system != 'darwin' and not is_libcxx fallbacks = ['', '\n#undef __has_include'] else - # On Darwin's clang you can't redefine builtin macros so the above doesn't work fallbacks = [''] endif foreach fallback : fallbacks - foreach comp : [meson.get_compiler('c'), meson.get_compiler('cpp')] + foreach comp : [meson.get_compiler('c'), cpp] assert(comp.has_header('stdio.h', prefix : fallback), 'Stdio missing.') # stdio.h doesn't actually need stdlib.h, but just test that setting the diff --git a/test cases/common/35 string operations/meson.build b/test cases/common/35 string operations/meson.build index ab77b49..8cc5b0c 100644 --- a/test cases/common/35 string operations/meson.build +++ b/test cases/common/35 string operations/meson.build @@ -67,6 +67,12 @@ assert(''.join(['a', 'b', 'c']) == 'abc', 'empty join() broken') assert(' '.join(['a']) == 'a', 'single join broken') assert(' '.join(['a'], ['b', ['c']], 'd') == 'a b c d', 'varargs join broken') +# meson.version() has it's own cmp method +assert(meson.version().version_compare('>1.1.0'), 'meson version compare 1 arg broken') +assert(meson.version().version_compare('>1.1.0', '<10.0.0'), 'meson version compare 2 args broken') +assert(not meson.version().version_compare('<1.1.0', '<1.0.0'), 'meson version compare 2 args broken') +assert(meson.version().version_compare('!= 1.9.1', '> 1.8.0'), 'meson version compare neq broken') + version_number = '1.2.8' assert(version_number.version_compare('>=1.2.8'), 'Version_compare gt broken') diff --git a/test cases/common/40 options/meson.build b/test cases/common/40 options/meson.build index 3849d54..f41265a 100644 --- a/test cases/common/40 options/meson.build +++ b/test cases/common/40 options/meson.build @@ -40,8 +40,7 @@ if get_option('integer_opt') != 3 endif negint = get_option('neg_int_opt') - -if negint != -3 and negint != -10 +if negint not in [-2, -3, -10] error('Incorrect value @0@ in negative integer option.'.format(negint)) endif diff --git a/test cases/common/42 subproject/meson.build b/test cases/common/42 subproject/meson.build index bc24adb..be2d90f 100644 --- a/test cases/common/42 subproject/meson.build +++ b/test cases/common/42 subproject/meson.build @@ -8,6 +8,7 @@ project('subproj user', 'c', assert(meson.project_name() == 'subproj user', 'Incorrect project name') sub = subproject('sublib', version : '1.0.0') +assert(sub.found()) if meson.project_version() != '2.3.4' error('Incorrect master project version string:' + meson.project_version()) @@ -29,3 +30,6 @@ unknown_var = sub.get_variable('does-not-exist', []) if unknown_var != [] error ('unexpected fallback value for subproject.get_variable()') endif + +unusedsub = subproject('subunused', required: get_option('disabled_feature')) +assert(not unusedsub.found()) diff --git a/test cases/common/42 subproject/meson.options b/test cases/common/42 subproject/meson.options new file mode 100644 index 0000000..ae9a39f --- /dev/null +++ b/test cases/common/42 subproject/meson.options @@ -0,0 +1 @@ +option('disabled_feature', type: 'feature', value: 'disabled') diff --git a/test cases/common/42 subproject/subprojects/subunused/meson.build b/test cases/common/42 subproject/subprojects/subunused/meson.build new file mode 100644 index 0000000..a42fed3 --- /dev/null +++ b/test cases/common/42 subproject/subprojects/subunused/meson.build @@ -0,0 +1 @@ +syntax error because the subproject is unused diff --git a/test cases/common/42 subproject/subprojects/subunused/meson.options b/test cases/common/42 subproject/subprojects/subunused/meson.options new file mode 100644 index 0000000..a42fed3 --- /dev/null +++ b/test cases/common/42 subproject/subprojects/subunused/meson.options @@ -0,0 +1 @@ +syntax error because the subproject is unused diff --git a/test cases/common/42 subproject/test.json b/test cases/common/42 subproject/test.json index e469052..7400ff4 100644 --- a/test cases/common/42 subproject/test.json +++ b/test cases/common/42 subproject/test.json @@ -7,5 +7,12 @@ {"type": "file", "file": "usr/share/sublib/COPYING"}, {"type": "file", "file": "usr/share/sublib/subprojects/sublib/sublicense1.txt"}, {"type": "file", "file": "usr/share/sublib/subprojects/sublib/sublicense2.txt"} - ] + ], + "matrix": { + "options": { + "subunused:opt1": [ + { "val": "42" } + ] + } + } } diff --git a/test cases/common/43 subproject options/meson.build b/test cases/common/43 subproject options/meson.build index a905272..d274fbf 100644 --- a/test cases/common/43 subproject options/meson.build +++ b/test cases/common/43 subproject options/meson.build @@ -1,4 +1,4 @@ -project('suboptions') +project('suboptions', default_options: {'c_args': ['-O']}) subproject('subproject') diff --git a/test cases/common/43 subproject options/subprojects/subproject/meson.build b/test cases/common/43 subproject options/subprojects/subproject/meson.build index d00a024..24a9dc0 100644 --- a/test cases/common/43 subproject options/subprojects/subproject/meson.build +++ b/test cases/common/43 subproject options/subprojects/subproject/meson.build @@ -1,4 +1,11 @@ -project('subproject') +project('subproject', 'c', + default_options: {'c_std': 'c11', 'c_args': ['-O2']}) + +assert(get_option('c_std') == 'c11') +# could be -O2 as above, or for some cross compilation tests in +# CI it could come from the machine file. but it's certainly +# not the value in the top-level project. +assert(get_option('c_args') != ['-O']) if get_option('opt') error('option set when it should be unset.') diff --git a/test cases/common/44 pkgconfig-gen/meson.build b/test cases/common/44 pkgconfig-gen/meson.build index fd6371e..c312873 100644 --- a/test cases/common/44 pkgconfig-gen/meson.build +++ b/test cases/common/44 pkgconfig-gen/meson.build @@ -193,3 +193,11 @@ simple7 = library('simple7', include_directories: 'inc1') dep = declare_dependency(include_directories: 'inc2') install_headers('inc1/inc1.h', 'inc2/inc2.h') pkgg.generate(simple7, libraries: dep) + +# Regression test: empty string passed to requires.private should not +# fail the build +pkgg.generate( + name : 'emptytest', + description : 'Check that requires.private can be an empty string', + requires_private: '', +) diff --git a/test cases/common/44 pkgconfig-gen/test.json b/test cases/common/44 pkgconfig-gen/test.json index 01786d4..36c7e95 100644 --- a/test cases/common/44 pkgconfig-gen/test.json +++ b/test cases/common/44 pkgconfig-gen/test.json @@ -16,6 +16,7 @@ {"type": "file", "file": "usr/lib/pkgconfig/simple5.pc"}, {"type": "file", "file": "usr/lib/pkgconfig/simple6.pc"}, {"type": "file", "file": "usr/lib/pkgconfig/simple7.pc"}, + {"type": "file", "file": "usr/lib/pkgconfig/emptytest.pc"}, {"type": "file", "file": "usr/lib/pkgconfig/ct.pc"}, {"type": "file", "file": "usr/lib/pkgconfig/ct0.pc"}, {"type": "file", "file": "usr/share/pkgconfig/libhello_nolib.pc"} diff --git a/test cases/common/56 array methods/meson.build b/test cases/common/56 array methods/meson.build index e9e4969..3cc1067 100644 --- a/test cases/common/56 array methods/meson.build +++ b/test cases/common/56 array methods/meson.build @@ -1,4 +1,4 @@ -project('array methods') +project('array methods', meson_version : '>= 1.9') empty = [] one = ['abc'] @@ -68,3 +68,20 @@ endif if not combined.contains('ghi') error('Combined claims not to contain ghi.') endif + +# test array slicing +assert(['a', 'b', 'c'].slice() == ['a', 'b', 'c']) +assert(['a', 'b', 'c'].slice(step : 2) == ['a', 'c']) +assert(['a', 'b', 'c'].slice(step : -1) == ['c', 'b', 'a']) +assert(['a', 'b', 'c'].slice(step : -2) == ['c', 'a']) +assert(['a', 'b', 'c'].slice(1, 2) == ['b']) +assert(['a', 'b', 'c'].slice(2, -2) == []) +assert(['a', 'b', 'c'].slice(-9876543, 2) == ['a', 'b']) +assert(['a', 'b', 'c', 'd', 'e'].slice(1, 12, step : 2) == ['b', 'd']) + +# test array flattening +x = ['a', ['b'], [[[[[[['c'], 'd']]], 'e']]]] +assert(x.length() == 3) +assert(x.flatten().length() == 5) +assert(x.flatten() == ['a', 'b', 'c', 'd', 'e']) +assert(['a', ['b', 'c']].flatten() == ['a', 'b', 'c']) diff --git a/test cases/common/98 subproject subdir/meson.build b/test cases/common/98 subproject subdir/meson.build index d2bafed..5d92772 100644 --- a/test cases/common/98 subproject subdir/meson.build +++ b/test cases/common/98 subproject subdir/meson.build @@ -83,7 +83,7 @@ d = dependency('subsubsub') assert(d.found(), 'Should be able to fallback to sub-sub-subproject') # Verify that `static: true` implies 'default_library=static'. -d = dependency('sub_static', static: true) +d = dependency('sub_static', static: true, default_options: ['bar=true']) assert(d.found()) # Verify that when not specifying static kwarg we can still get fallback dep. d = dependency('sub_static') diff --git a/test cases/common/98 subproject subdir/subprojects/sub_static/meson.build b/test cases/common/98 subproject subdir/subprojects/sub_static/meson.build index 6c00623..8de7cb4 100644 --- a/test cases/common/98 subproject subdir/subprojects/sub_static/meson.build +++ b/test cases/common/98 subproject subdir/subprojects/sub_static/meson.build @@ -1,6 +1,7 @@ project('sub_static') assert(get_option('default_library') == 'static') +assert(get_option('bar') == true) meson.override_dependency('sub_static', declare_dependency()) meson.override_dependency('sub_static2', declare_dependency(), static: true) meson.override_dependency('sub_static3', declare_dependency(variables: {'static': 'true'}), static: true) diff --git a/test cases/common/98 subproject subdir/subprojects/sub_static/meson_options.txt b/test cases/common/98 subproject subdir/subprojects/sub_static/meson_options.txt new file mode 100644 index 0000000..129a7d4 --- /dev/null +++ b/test cases/common/98 subproject subdir/subprojects/sub_static/meson_options.txt @@ -0,0 +1 @@ +option('bar', type: 'boolean', value: false) diff --git a/test cases/csharp/4 external dep/meson.build b/test cases/csharp/4 external dep/meson.build index 019d618..e1c3a7c 100644 --- a/test cases/csharp/4 external dep/meson.build +++ b/test cases/csharp/4 external dep/meson.build @@ -1,9 +1,9 @@ project('C# external library', 'cs') -glib_sharp_2 = dependency('glib-sharp-2.0', required : false) +glib_sharp_3 = dependency('glib-sharp-3.0', required : false) -if not glib_sharp_2.found() +if not glib_sharp_3.found() error('MESON_SKIP_TEST glib# not found.') endif -e = executable('prog', 'prog.cs', dependencies: glib_sharp_2, install : true) +e = executable('prog', 'prog.cs', dependencies: glib_sharp_3, install : true) test('libtest', e, args: [join_paths(meson.current_source_dir(), 'hello.txt')]) diff --git a/test cases/cuda/10 cuda dependency/modules/meson.build b/test cases/cuda/10 cuda dependency/modules/meson.build index 0da43f2..b934c6b 100644 --- a/test cases/cuda/10 cuda dependency/modules/meson.build +++ b/test cases/cuda/10 cuda dependency/modules/meson.build @@ -1,2 +1,2 @@ -exe = executable('prog', 'prog.cc', dependencies: dependency('cuda', modules: ['cublas'])) +exe = executable('prog', 'prog.cc', dependencies: dependency('cuda', modules: ['cublas', 'nvidia-ml'])) test('cudatest', exe) diff --git a/test cases/cuda/11 cuda dependency (nvcc)/modules/meson.build b/test cases/cuda/11 cuda dependency (nvcc)/modules/meson.build index c0fed83..e36d877 100644 --- a/test cases/cuda/11 cuda dependency (nvcc)/modules/meson.build +++ b/test cases/cuda/11 cuda dependency (nvcc)/modules/meson.build @@ -1,2 +1,2 @@ -exe = executable('prog', 'prog.cu', dependencies: dependency('cuda', modules: ['cublas'])) +exe = executable('prog', 'prog.cu', dependencies: dependency('cuda', modules: ['cublas', 'nvidia-ml'])) test('cudatest', exe) diff --git a/test cases/cuda/12 cuda dependency (mixed)/meson.build b/test cases/cuda/12 cuda dependency (mixed)/meson.build index 44a49db..4519b28 100644 --- a/test cases/cuda/12 cuda dependency (mixed)/meson.build +++ b/test cases/cuda/12 cuda dependency (mixed)/meson.build @@ -1,4 +1,4 @@ project('cuda dependency', 'cpp', 'cuda') -exe = executable('prog', 'prog.cpp', 'kernel.cu', dependencies: dependency('cuda', modules: ['cublas']), link_language: 'cpp') +exe = executable('prog', 'prog.cpp', 'kernel.cu', dependencies: dependency('cuda', modules: ['cublas', 'nvidia-ml']), link_language: 'cpp') test('cudatest', exe) diff --git a/test cases/cython/2 generated sources/meson.build b/test cases/cython/2 generated sources/meson.build index d438d80..3d2837d 100644 --- a/test cases/cython/2 generated sources/meson.build +++ b/test cases/cython/2 generated sources/meson.build @@ -79,13 +79,20 @@ stuff_pxi = fs.copyfile( 'stuff.pxi' ) +stuff_pxi_2 = configure_file( + input: 'stuff.pxi.in', + output: 'stuff.pxi', + configuration: configuration_data(), + install: false +) + # Need to copy the cython source to the build directory # since meson can only generate the .pxi there includestuff_pyx = fs.copyfile( 'includestuff.pyx' ) -stuff_pxi_dep = declare_dependency(sources: stuff_pxi) +stuff_pxi_dep = declare_dependency(sources: [stuff_pxi, stuff_pxi_2]) includestuff_ext = py3.extension_module( 'includestuff', diff --git a/test cases/failing/100 compiler no lang/test.json b/test cases/failing/100 compiler no lang/test.json deleted file mode 100644 index 935d79d..0000000 --- a/test cases/failing/100 compiler no lang/test.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "stdout": [ - { - "line": "test cases/failing/100 compiler no lang/meson.build:2:6: ERROR: Tried to access compiler for language \"c\", not specified for host machine." - } - ] -} diff --git a/test cases/failing/101 no fallback/meson.build b/test cases/failing/100 no fallback/meson.build index 91d6b93..91d6b93 100644 --- a/test cases/failing/101 no fallback/meson.build +++ b/test cases/failing/100 no fallback/meson.build diff --git a/test cases/failing/101 no fallback/subprojects/foob/meson.build b/test cases/failing/100 no fallback/subprojects/foob/meson.build index b2c4814..b2c4814 100644 --- a/test cases/failing/101 no fallback/subprojects/foob/meson.build +++ b/test cases/failing/100 no fallback/subprojects/foob/meson.build diff --git a/test cases/failing/101 no fallback/test.json b/test cases/failing/100 no fallback/test.json index 5fbffe3..5fbffe3 100644 --- a/test cases/failing/101 no fallback/test.json +++ b/test cases/failing/100 no fallback/test.json diff --git a/test cases/failing/102 feature require/meson.build b/test cases/failing/101 feature require/meson.build index 8c47e37..8c47e37 100644 --- a/test cases/failing/102 feature require/meson.build +++ b/test cases/failing/101 feature require/meson.build diff --git a/test cases/failing/102 feature require/meson_options.txt b/test cases/failing/101 feature require/meson_options.txt index 5910a87..5910a87 100644 --- a/test cases/failing/102 feature require/meson_options.txt +++ b/test cases/failing/101 feature require/meson_options.txt diff --git a/test cases/failing/102 feature require/test.json b/test cases/failing/101 feature require/test.json index 2da5494..2da5494 100644 --- a/test cases/failing/102 feature require/test.json +++ b/test cases/failing/101 feature require/test.json diff --git a/test cases/failing/103 feature require.bis/meson.build b/test cases/failing/102 feature require.bis/meson.build index 4ea65e5..4ea65e5 100644 --- a/test cases/failing/103 feature require.bis/meson.build +++ b/test cases/failing/102 feature require.bis/meson.build diff --git a/test cases/failing/103 feature require.bis/meson_options.txt b/test cases/failing/102 feature require.bis/meson_options.txt index 5910a87..5910a87 100644 --- a/test cases/failing/103 feature require.bis/meson_options.txt +++ b/test cases/failing/102 feature require.bis/meson_options.txt diff --git a/test cases/failing/103 feature require.bis/test.json b/test cases/failing/102 feature require.bis/test.json index ed488af..ed488af 100644 --- a/test cases/failing/103 feature require.bis/test.json +++ b/test cases/failing/102 feature require.bis/test.json diff --git a/test cases/failing/104 no build get_external_property/meson.build b/test cases/failing/103 no build get_external_property/meson.build index 8a4215c..8a4215c 100644 --- a/test cases/failing/104 no build get_external_property/meson.build +++ b/test cases/failing/103 no build get_external_property/meson.build diff --git a/test cases/failing/104 no build get_external_property/test.json b/test cases/failing/103 no build get_external_property/test.json index 5792213..74ecb75 100644 --- a/test cases/failing/104 no build get_external_property/test.json +++ b/test cases/failing/103 no build get_external_property/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/104 no build get_external_property/meson.build:3:14: ERROR: Unknown property for build machine: nonexisting" + "line": "test cases/failing/103 no build get_external_property/meson.build:3:14: ERROR: Unknown property for build machine: nonexisting" } ] } diff --git a/test cases/failing/105 enter subdir twice/meson.build b/test cases/failing/104 enter subdir twice/meson.build index d9bf9f5..d9bf9f5 100644 --- a/test cases/failing/105 enter subdir twice/meson.build +++ b/test cases/failing/104 enter subdir twice/meson.build diff --git a/test cases/failing/105 enter subdir twice/sub/meson.build b/test cases/failing/104 enter subdir twice/sub/meson.build index d036a3f..d036a3f 100644 --- a/test cases/failing/105 enter subdir twice/sub/meson.build +++ b/test cases/failing/104 enter subdir twice/sub/meson.build diff --git a/test cases/failing/105 enter subdir twice/test.json b/test cases/failing/104 enter subdir twice/test.json index 80e8c10..9d63889 100644 --- a/test cases/failing/105 enter subdir twice/test.json +++ b/test cases/failing/104 enter subdir twice/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/105 enter subdir twice/meson.build:3:0: ERROR: Tried to enter directory \"sub\", which has already been visited." + "line": "test cases/failing/104 enter subdir twice/meson.build:3:0: ERROR: Tried to enter directory \"sub\", which has already been visited." } ] } diff --git a/test cases/failing/106 invalid fstring/109 invalid fstring/meson.build b/test cases/failing/105 invalid fstring/109 invalid fstring/meson.build index dd22f56..dd22f56 100644 --- a/test cases/failing/106 invalid fstring/109 invalid fstring/meson.build +++ b/test cases/failing/105 invalid fstring/109 invalid fstring/meson.build diff --git a/test cases/failing/106 invalid fstring/109 invalid fstring/test.json b/test cases/failing/105 invalid fstring/109 invalid fstring/test.json index 71d8f59..71d8f59 100644 --- a/test cases/failing/106 invalid fstring/109 invalid fstring/test.json +++ b/test cases/failing/105 invalid fstring/109 invalid fstring/test.json diff --git a/test cases/failing/106 invalid fstring/meson.build b/test cases/failing/105 invalid fstring/meson.build index 7eb3ccf..7eb3ccf 100644 --- a/test cases/failing/106 invalid fstring/meson.build +++ b/test cases/failing/105 invalid fstring/meson.build diff --git a/test cases/failing/106 invalid fstring/test.json b/test cases/failing/105 invalid fstring/test.json index 8d53428..ae2a531 100644 --- a/test cases/failing/106 invalid fstring/test.json +++ b/test cases/failing/105 invalid fstring/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/106 invalid fstring/meson.build:3:4: ERROR: Identifier \"foo\" does not name a variable." + "line": "test cases/failing/105 invalid fstring/meson.build:3:4: ERROR: Identifier \"foo\" does not name a variable." } ] } diff --git a/test cases/failing/107 compiler argument checking/meson.build b/test cases/failing/106 compiler argument checking/meson.build index bb1f447..bb1f447 100644 --- a/test cases/failing/107 compiler argument checking/meson.build +++ b/test cases/failing/106 compiler argument checking/meson.build diff --git a/test cases/failing/107 compiler argument checking/test.json b/test cases/failing/106 compiler argument checking/test.json index a1576aa..262db69 100644 --- a/test cases/failing/107 compiler argument checking/test.json +++ b/test cases/failing/106 compiler argument checking/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/107 compiler argument checking/meson.build:4:25: ERROR: Compiler for C does not support \"-meson-goober-arg-for-testing\"" + "line": "test cases/failing/106 compiler argument checking/meson.build:4:25: ERROR: Compiler for C does not support \"-meson-goober-arg-for-testing\"" } ] } diff --git a/test cases/failing/108 empty fallback/meson.build b/test cases/failing/107 empty fallback/meson.build index f4eb5fe..f4eb5fe 100644 --- a/test cases/failing/108 empty fallback/meson.build +++ b/test cases/failing/107 empty fallback/meson.build diff --git a/test cases/failing/108 empty fallback/subprojects/foo/meson.build b/test cases/failing/107 empty fallback/subprojects/foo/meson.build index c9e134b..c9e134b 100644 --- a/test cases/failing/108 empty fallback/subprojects/foo/meson.build +++ b/test cases/failing/107 empty fallback/subprojects/foo/meson.build diff --git a/test cases/failing/108 empty fallback/test.json b/test cases/failing/107 empty fallback/test.json index c631882..3d69a58 100644 --- a/test cases/failing/108 empty fallback/test.json +++ b/test cases/failing/107 empty fallback/test.json @@ -2,7 +2,7 @@ "stdout": [ { "match": "re", - "line": "test cases/failing/108 empty fallback/meson.build:6:0: ERROR: Dependency \"foo\" not found.*" + "line": "test cases/failing/107 empty fallback/meson.build:6:0: ERROR: Dependency \"foo\" not found.*" } ] } diff --git a/test cases/failing/109 cmake executable dependency/meson.build b/test cases/failing/108 cmake executable dependency/meson.build index 0fc0f9b..0fc0f9b 100644 --- a/test cases/failing/109 cmake executable dependency/meson.build +++ b/test cases/failing/108 cmake executable dependency/meson.build diff --git a/test cases/failing/109 cmake executable dependency/subprojects/cmlib/CMakeLists.txt b/test cases/failing/108 cmake executable dependency/subprojects/cmlib/CMakeLists.txt index e927eae..e927eae 100644 --- a/test cases/failing/109 cmake executable dependency/subprojects/cmlib/CMakeLists.txt +++ b/test cases/failing/108 cmake executable dependency/subprojects/cmlib/CMakeLists.txt diff --git a/test cases/failing/109 cmake executable dependency/subprojects/cmlib/main.c b/test cases/failing/108 cmake executable dependency/subprojects/cmlib/main.c index 9b6bdc2..9b6bdc2 100644 --- a/test cases/failing/109 cmake executable dependency/subprojects/cmlib/main.c +++ b/test cases/failing/108 cmake executable dependency/subprojects/cmlib/main.c diff --git a/test cases/failing/109 cmake executable dependency/test.json b/test cases/failing/108 cmake executable dependency/test.json index 92a6ee1..5a88b0d 100644 --- a/test cases/failing/109 cmake executable dependency/test.json +++ b/test cases/failing/108 cmake executable dependency/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/109 cmake executable dependency/meson.build:5:14: ERROR: main is an executable and does not support the dependency() method. Use target() instead." + "line": "test cases/failing/108 cmake executable dependency/meson.build:5:14: ERROR: main is an executable and does not support the dependency() method. Use target() instead." } ], "tools": { diff --git a/test cases/failing/110 allow_fallback with fallback/meson.build b/test cases/failing/109 allow_fallback with fallback/meson.build index 2874e42..2874e42 100644 --- a/test cases/failing/110 allow_fallback with fallback/meson.build +++ b/test cases/failing/109 allow_fallback with fallback/meson.build diff --git a/test cases/failing/110 allow_fallback with fallback/test.json b/test cases/failing/109 allow_fallback with fallback/test.json index b558ecc..76917b1 100644 --- a/test cases/failing/110 allow_fallback with fallback/test.json +++ b/test cases/failing/109 allow_fallback with fallback/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/110 allow_fallback with fallback/meson.build:3:0: ERROR: \"fallback\" and \"allow_fallback\" arguments are mutually exclusive" + "line": "test cases/failing/109 allow_fallback with fallback/meson.build:3:0: ERROR: \"fallback\" and \"allow_fallback\" arguments are mutually exclusive" } ] } diff --git a/test cases/failing/111 nonsensical bindgen/meson.build b/test cases/failing/110 nonsensical bindgen/meson.build index 6995f67..40e9a2d 100644 --- a/test cases/failing/111 nonsensical bindgen/meson.build +++ b/test cases/failing/110 nonsensical bindgen/meson.build @@ -1,4 +1,4 @@ -# SPDX-license-identifer: Apache-2.0 +# SPDX-License-Identifier: Apache-2.0 # Copyright © 2021 Intel Corporation project('rustmod bindgen', 'c') diff --git a/test cases/failing/111 nonsensical bindgen/src/header.h b/test cases/failing/110 nonsensical bindgen/src/header.h index 750621f..5299f13 100644 --- a/test cases/failing/111 nonsensical bindgen/src/header.h +++ b/test cases/failing/110 nonsensical bindgen/src/header.h @@ -1,4 +1,4 @@ -// SPDX-license-identifer: Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 // Copyright © 2021 Intel Corporation #pragma once diff --git a/test cases/failing/111 nonsensical bindgen/src/source.c b/test cases/failing/110 nonsensical bindgen/src/source.c index d652d28..d968ba4 100644 --- a/test cases/failing/111 nonsensical bindgen/src/source.c +++ b/test cases/failing/110 nonsensical bindgen/src/source.c @@ -1,4 +1,4 @@ -// SPDX-license-identifer: Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 // Copyright © 2021 Intel Corporation #include "header.h" diff --git a/test cases/failing/111 nonsensical bindgen/test.json b/test cases/failing/110 nonsensical bindgen/test.json index f2dc92c..43cfa3a 100644 --- a/test cases/failing/111 nonsensical bindgen/test.json +++ b/test cases/failing/110 nonsensical bindgen/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/111 nonsensical bindgen/meson.build:17:24: ERROR: bindgen source file must be a C header, not an object or build target" + "line": "test cases/failing/110 nonsensical bindgen/meson.build:17:24: ERROR: bindgen source file must be a C header, not an object or build target" } ] } diff --git a/test cases/failing/112 run_target in test/meson.build b/test cases/failing/111 run_target in test/meson.build index 117009e..117009e 100644 --- a/test cases/failing/112 run_target in test/meson.build +++ b/test cases/failing/111 run_target in test/meson.build diff --git a/test cases/failing/112 run_target in test/test.json b/test cases/failing/111 run_target in test/test.json index 5158978..c0ec0e6 100644 --- a/test cases/failing/112 run_target in test/test.json +++ b/test cases/failing/111 run_target in test/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/112 run_target in test/meson.build:7:0: ERROR: test keyword argument 'args' was of type array[RunTarget] but should have been array[str | File | BuildTarget | CustomTarget | CustomTargetIndex | ExternalProgram]" + "line": "test cases/failing/111 run_target in test/meson.build:7:0: ERROR: test keyword argument 'args' was of type array[RunTarget] but should have been array[str | File | BuildTarget | CustomTarget | CustomTargetIndex | ExternalProgram]" } ] } diff --git a/test cases/failing/112 run_target in test/trivial.c b/test cases/failing/111 run_target in test/trivial.c index 96612d4..96612d4 100644 --- a/test cases/failing/112 run_target in test/trivial.c +++ b/test cases/failing/111 run_target in test/trivial.c diff --git a/test cases/failing/113 run_target in add_install_script/meson.build b/test cases/failing/112 run_target in add_install_script/meson.build index d3634bf..d3634bf 100644 --- a/test cases/failing/113 run_target in add_install_script/meson.build +++ b/test cases/failing/112 run_target in add_install_script/meson.build diff --git a/test cases/failing/113 run_target in add_install_script/test.json b/test cases/failing/112 run_target in add_install_script/test.json index c9ed33e..7753c31 100644 --- a/test cases/failing/113 run_target in add_install_script/test.json +++ b/test cases/failing/112 run_target in add_install_script/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/113 run_target in add_install_script/meson.build:7:6: ERROR: meson.add_install_script argument 2 was of type \"RunTarget\" but should have been one of: \"str\", \"File\", \"BuildTarget\", \"CustomTarget\", \"CustomTargetIndex\", \"ExternalProgram\"" + "line": "test cases/failing/112 run_target in add_install_script/meson.build:7:6: ERROR: meson.add_install_script argument 2 was of type \"RunTarget\" but should have been one of: \"str\", \"File\", \"BuildTarget\", \"CustomTarget\", \"CustomTargetIndex\", \"ExternalProgram\"" } ] } diff --git a/test cases/failing/113 run_target in add_install_script/trivial.c b/test cases/failing/112 run_target in add_install_script/trivial.c index 1b14571..1b14571 100644 --- a/test cases/failing/113 run_target in add_install_script/trivial.c +++ b/test cases/failing/112 run_target in add_install_script/trivial.c diff --git a/test cases/failing/114 pathsep in install_symlink/meson.build b/test cases/failing/113 pathsep in install_symlink/meson.build index cce82c2..cce82c2 100644 --- a/test cases/failing/114 pathsep in install_symlink/meson.build +++ b/test cases/failing/113 pathsep in install_symlink/meson.build diff --git a/test cases/failing/114 pathsep in install_symlink/test.json b/test cases/failing/113 pathsep in install_symlink/test.json index bca4e0e..bbf25e2 100644 --- a/test cases/failing/114 pathsep in install_symlink/test.json +++ b/test cases/failing/113 pathsep in install_symlink/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/114 pathsep in install_symlink/meson.build:3:0: ERROR: Link name is \"foo/bar\", but link names cannot contain path separators. The dir part should be in install_dir." + "line": "test cases/failing/113 pathsep in install_symlink/meson.build:3:0: ERROR: Link name is \"foo/bar\", but link names cannot contain path separators. The dir part should be in install_dir." } ] } diff --git a/test cases/failing/115 subproject version conflict/meson.build b/test cases/failing/114 subproject version conflict/meson.build index ffbcc13..ffbcc13 100644 --- a/test cases/failing/115 subproject version conflict/meson.build +++ b/test cases/failing/114 subproject version conflict/meson.build diff --git a/test cases/failing/115 subproject version conflict/subprojects/A/meson.build b/test cases/failing/114 subproject version conflict/subprojects/A/meson.build index 7da4df0..7da4df0 100644 --- a/test cases/failing/115 subproject version conflict/subprojects/A/meson.build +++ b/test cases/failing/114 subproject version conflict/subprojects/A/meson.build diff --git a/test cases/failing/115 subproject version conflict/subprojects/B/meson.build b/test cases/failing/114 subproject version conflict/subprojects/B/meson.build index 0ead934..0ead934 100644 --- a/test cases/failing/115 subproject version conflict/subprojects/B/meson.build +++ b/test cases/failing/114 subproject version conflict/subprojects/B/meson.build diff --git a/test cases/failing/115 subproject version conflict/test.json b/test cases/failing/114 subproject version conflict/test.json index 23c7eb5..6548972 100644 --- a/test cases/failing/115 subproject version conflict/test.json +++ b/test cases/failing/114 subproject version conflict/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/115 subproject version conflict/meson.build:4:8: ERROR: Subproject B version is 100 but ['1'] required." + "line": "test cases/failing/114 subproject version conflict/meson.build:4:8: ERROR: Subproject B version is 100 but ['1'] required." } ] } diff --git a/test cases/failing/116 structured source empty string/main.rs b/test cases/failing/115 structured source empty string/main.rs index e69de29..e69de29 100644 --- a/test cases/failing/116 structured source empty string/main.rs +++ b/test cases/failing/115 structured source empty string/main.rs diff --git a/test cases/failing/116 structured source empty string/meson.build b/test cases/failing/115 structured source empty string/meson.build index 0715991..0715991 100644 --- a/test cases/failing/116 structured source empty string/meson.build +++ b/test cases/failing/115 structured source empty string/meson.build diff --git a/test cases/failing/116 structured source empty string/test.json b/test cases/failing/115 structured source empty string/test.json index 00efbe3..48c1c83 100644 --- a/test cases/failing/116 structured source empty string/test.json +++ b/test cases/failing/115 structured source empty string/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/116 structured source empty string/meson.build:9:2: ERROR: structured_sources: keys to dictionary argument may not be an empty string." + "line": "test cases/failing/115 structured source empty string/meson.build:9:2: ERROR: structured_sources: keys to dictionary argument may not be an empty string." } ] } diff --git a/test cases/failing/117 structured_sources conflicts/main.rs b/test cases/failing/116 structured_sources conflicts/main.rs index e69de29..e69de29 100644 --- a/test cases/failing/117 structured_sources conflicts/main.rs +++ b/test cases/failing/116 structured_sources conflicts/main.rs diff --git a/test cases/failing/117 structured_sources conflicts/meson.build b/test cases/failing/116 structured_sources conflicts/meson.build index 561ad86..561ad86 100644 --- a/test cases/failing/117 structured_sources conflicts/meson.build +++ b/test cases/failing/116 structured_sources conflicts/meson.build diff --git a/test cases/failing/117 structured_sources conflicts/test.json b/test cases/failing/116 structured_sources conflicts/test.json index a7201b5..7b53923 100644 --- a/test cases/failing/117 structured_sources conflicts/test.json +++ b/test cases/failing/116 structured_sources conflicts/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/117 structured_sources conflicts/meson.build:7:0: ERROR: Conflicting sources in structured sources: main.rs" + "line": "test cases/failing/116 structured_sources conflicts/meson.build:7:0: ERROR: Conflicting sources in structured sources: main.rs" } ] } diff --git a/test cases/failing/118 missing compiler/meson.build b/test cases/failing/117 missing compiler/meson.build index 19bdd0c..19bdd0c 100644 --- a/test cases/failing/118 missing compiler/meson.build +++ b/test cases/failing/117 missing compiler/meson.build diff --git a/test cases/failing/118 missing compiler/subprojects/sub/main.c b/test cases/failing/117 missing compiler/subprojects/sub/main.c index 44e82e2..44e82e2 100644 --- a/test cases/failing/118 missing compiler/subprojects/sub/main.c +++ b/test cases/failing/117 missing compiler/subprojects/sub/main.c diff --git a/test cases/failing/118 missing compiler/subprojects/sub/meson.build b/test cases/failing/117 missing compiler/subprojects/sub/meson.build index b60850c..b60850c 100644 --- a/test cases/failing/118 missing compiler/subprojects/sub/meson.build +++ b/test cases/failing/117 missing compiler/subprojects/sub/meson.build diff --git a/test cases/failing/118 missing compiler/test.json b/test cases/failing/117 missing compiler/test.json index a2494e4..525e927 100644 --- a/test cases/failing/118 missing compiler/test.json +++ b/test cases/failing/117 missing compiler/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/118 missing compiler/subprojects/sub/meson.build:4:0: ERROR: No host machine compiler for 'subprojects/sub/main.c'" + "line": "test cases/failing/117 missing compiler/subprojects/sub/meson.build:4:0: ERROR: No host machine compiler for 'subprojects/sub/main.c'" } ] } diff --git a/test cases/failing/119 cmake subproject error/meson.build b/test cases/failing/118 cmake subproject error/meson.build index 9304af7..9304af7 100644 --- a/test cases/failing/119 cmake subproject error/meson.build +++ b/test cases/failing/118 cmake subproject error/meson.build diff --git a/test cases/failing/119 cmake subproject error/subprojects/cmlib/CMakeLists.txt b/test cases/failing/118 cmake subproject error/subprojects/cmlib/CMakeLists.txt index a845525..a845525 100644 --- a/test cases/failing/119 cmake subproject error/subprojects/cmlib/CMakeLists.txt +++ b/test cases/failing/118 cmake subproject error/subprojects/cmlib/CMakeLists.txt diff --git a/test cases/failing/119 cmake subproject error/test.json b/test cases/failing/118 cmake subproject error/test.json index d8271a2..4e47f89 100644 --- a/test cases/failing/119 cmake subproject error/test.json +++ b/test cases/failing/118 cmake subproject error/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/119 cmake subproject error/meson.build:4:14: ERROR: Failed to configure the CMake subproject: Fancy error message" + "line": "test cases/failing/118 cmake subproject error/meson.build:4:14: ERROR: Failed to configure the CMake subproject: Fancy error message" } ], "tools": { diff --git a/test cases/failing/120 pkgconfig not relocatable outside prefix/meson.build b/test cases/failing/119 pkgconfig not relocatable outside prefix/meson.build index 7ebdfc3..7ebdfc3 100644 --- a/test cases/failing/120 pkgconfig not relocatable outside prefix/meson.build +++ b/test cases/failing/119 pkgconfig not relocatable outside prefix/meson.build diff --git a/test cases/failing/120 pkgconfig not relocatable outside prefix/test.json b/test cases/failing/119 pkgconfig not relocatable outside prefix/test.json index 32c6e09..1bb2494 100644 --- a/test cases/failing/120 pkgconfig not relocatable outside prefix/test.json +++ b/test cases/failing/119 pkgconfig not relocatable outside prefix/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/120 pkgconfig not relocatable outside prefix/meson\\.build:17:5: ERROR: Pkgconfig prefix cannot be outside of the prefix when pkgconfig\\.relocatable=true. Pkgconfig prefix is (C:)?/opt/lib/pkgconfig.", + "line": "test cases/failing/119 pkgconfig not relocatable outside prefix/meson\\.build:17:5: ERROR: Pkgconfig prefix cannot be outside of the prefix when pkgconfig\\.relocatable=true. Pkgconfig prefix is (C:)?/opt/lib/pkgconfig.", "match": "re" } ] diff --git a/test cases/failing/121 subproject sandbox violation/meson.build b/test cases/failing/120 subproject sandbox violation/meson.build index d41994c..d41994c 100644 --- a/test cases/failing/121 subproject sandbox violation/meson.build +++ b/test cases/failing/120 subproject sandbox violation/meson.build diff --git a/test cases/failing/121 subproject sandbox violation/meson_options.txt b/test cases/failing/120 subproject sandbox violation/meson_options.txt index e7b782d..e7b782d 100644 --- a/test cases/failing/121 subproject sandbox violation/meson_options.txt +++ b/test cases/failing/120 subproject sandbox violation/meson_options.txt diff --git a/test cases/failing/121 subproject sandbox violation/subprojects/subproj1/file.txt b/test cases/failing/120 subproject sandbox violation/subprojects/subproj1/file.txt index e69de29..e69de29 100644 --- a/test cases/failing/121 subproject sandbox violation/subprojects/subproj1/file.txt +++ b/test cases/failing/120 subproject sandbox violation/subprojects/subproj1/file.txt diff --git a/test cases/failing/121 subproject sandbox violation/subprojects/subproj1/meson.build b/test cases/failing/120 subproject sandbox violation/subprojects/subproj1/meson.build index bd33bf3..bd33bf3 100644 --- a/test cases/failing/121 subproject sandbox violation/subprojects/subproj1/meson.build +++ b/test cases/failing/120 subproject sandbox violation/subprojects/subproj1/meson.build diff --git a/test cases/failing/121 subproject sandbox violation/subprojects/subproj1/nested/meson.build b/test cases/failing/120 subproject sandbox violation/subprojects/subproj1/nested/meson.build index 038c139..038c139 100644 --- a/test cases/failing/121 subproject sandbox violation/subprojects/subproj1/nested/meson.build +++ b/test cases/failing/120 subproject sandbox violation/subprojects/subproj1/nested/meson.build diff --git a/test cases/failing/121 subproject sandbox violation/subprojects/subproj2/file.txt b/test cases/failing/120 subproject sandbox violation/subprojects/subproj2/file.txt index e69de29..e69de29 100644 --- a/test cases/failing/121 subproject sandbox violation/subprojects/subproj2/file.txt +++ b/test cases/failing/120 subproject sandbox violation/subprojects/subproj2/file.txt diff --git a/test cases/failing/121 subproject sandbox violation/subprojects/subproj2/meson.build b/test cases/failing/120 subproject sandbox violation/subprojects/subproj2/meson.build index a6032aa..a6032aa 100644 --- a/test cases/failing/121 subproject sandbox violation/subprojects/subproj2/meson.build +++ b/test cases/failing/120 subproject sandbox violation/subprojects/subproj2/meson.build diff --git a/test cases/failing/121 subproject sandbox violation/subprojects/subproj2/nested/meson.build b/test cases/failing/120 subproject sandbox violation/subprojects/subproj2/nested/meson.build index e69de29..e69de29 100644 --- a/test cases/failing/121 subproject sandbox violation/subprojects/subproj2/nested/meson.build +++ b/test cases/failing/120 subproject sandbox violation/subprojects/subproj2/nested/meson.build diff --git a/test cases/failing/121 subproject sandbox violation/subprojects/subproj3/file.txt b/test cases/failing/120 subproject sandbox violation/subprojects/subproj3/file.txt index e69de29..e69de29 100644 --- a/test cases/failing/121 subproject sandbox violation/subprojects/subproj3/file.txt +++ b/test cases/failing/120 subproject sandbox violation/subprojects/subproj3/file.txt diff --git a/test cases/failing/121 subproject sandbox violation/subprojects/subproj3/meson.build b/test cases/failing/120 subproject sandbox violation/subprojects/subproj3/meson.build index c4fa64c..c4fa64c 100644 --- a/test cases/failing/121 subproject sandbox violation/subprojects/subproj3/meson.build +++ b/test cases/failing/120 subproject sandbox violation/subprojects/subproj3/meson.build diff --git a/test cases/failing/121 subproject sandbox violation/test.json b/test cases/failing/120 subproject sandbox violation/test.json index d8c28d7..d506076 100644 --- a/test cases/failing/121 subproject sandbox violation/test.json +++ b/test cases/failing/120 subproject sandbox violation/test.json @@ -10,7 +10,7 @@ }, "stdout": [ { - "line": "test cases/failing/121 subproject sandbox violation/meson.build:24:0: ERROR: Sandbox violation: Tried to grab file file.txt from a nested subproject." + "line": "test cases/failing/120 subproject sandbox violation/meson.build:24:0: ERROR: Sandbox violation: Tried to grab file file.txt from a nested subproject." } ] } diff --git a/test cases/failing/122 override and add_project_dependency/inc/lib.h b/test cases/failing/121 override and add_project_dependency/inc/lib.h index 4cfc47e..4cfc47e 100644 --- a/test cases/failing/122 override and add_project_dependency/inc/lib.h +++ b/test cases/failing/121 override and add_project_dependency/inc/lib.h diff --git a/test cases/failing/122 override and add_project_dependency/lib.c b/test cases/failing/121 override and add_project_dependency/lib.c index b8b22a3..b8b22a3 100644 --- a/test cases/failing/122 override and add_project_dependency/lib.c +++ b/test cases/failing/121 override and add_project_dependency/lib.c diff --git a/test cases/failing/122 override and add_project_dependency/meson.build b/test cases/failing/121 override and add_project_dependency/meson.build index c878b01..c878b01 100644 --- a/test cases/failing/122 override and add_project_dependency/meson.build +++ b/test cases/failing/121 override and add_project_dependency/meson.build diff --git a/test cases/failing/122 override and add_project_dependency/subprojects/a/meson.build b/test cases/failing/121 override and add_project_dependency/subprojects/a/meson.build index 4f6f070..4f6f070 100644 --- a/test cases/failing/122 override and add_project_dependency/subprojects/a/meson.build +++ b/test cases/failing/121 override and add_project_dependency/subprojects/a/meson.build diff --git a/test cases/failing/122 override and add_project_dependency/subprojects/a/prog.c b/test cases/failing/121 override and add_project_dependency/subprojects/a/prog.c index ce60f81..ce60f81 100644 --- a/test cases/failing/122 override and add_project_dependency/subprojects/a/prog.c +++ b/test cases/failing/121 override and add_project_dependency/subprojects/a/prog.c diff --git a/test cases/failing/122 override and add_project_dependency/test.json b/test cases/failing/121 override and add_project_dependency/test.json index 08eb0c6..e762359 100644 --- a/test cases/failing/122 override and add_project_dependency/test.json +++ b/test cases/failing/121 override and add_project_dependency/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/122 override and add_project_dependency/subprojects/a/meson.build:6:0: ERROR: Dependencies must be external dependencies" + "line": "test cases/failing/121 override and add_project_dependency/subprojects/a/meson.build:6:0: ERROR: Dependencies must be external dependencies" } ] } diff --git a/test cases/failing/123 targets before add_project_dependency/inc/lib.h b/test cases/failing/122 targets before add_project_dependency/inc/lib.h index 4cfc47e..4cfc47e 100644 --- a/test cases/failing/123 targets before add_project_dependency/inc/lib.h +++ b/test cases/failing/122 targets before add_project_dependency/inc/lib.h diff --git a/test cases/failing/123 targets before add_project_dependency/lib.c b/test cases/failing/122 targets before add_project_dependency/lib.c index b8b22a3..b8b22a3 100644 --- a/test cases/failing/123 targets before add_project_dependency/lib.c +++ b/test cases/failing/122 targets before add_project_dependency/lib.c diff --git a/test cases/failing/123 targets before add_project_dependency/meson.build b/test cases/failing/122 targets before add_project_dependency/meson.build index 38fe102..38fe102 100644 --- a/test cases/failing/123 targets before add_project_dependency/meson.build +++ b/test cases/failing/122 targets before add_project_dependency/meson.build diff --git a/test cases/failing/123 targets before add_project_dependency/test.json b/test cases/failing/122 targets before add_project_dependency/test.json index 4a21eec..674467a 100644 --- a/test cases/failing/123 targets before add_project_dependency/test.json +++ b/test cases/failing/122 targets before add_project_dependency/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/123 targets before add_project_dependency/meson.build:5:0: ERROR: Tried to use 'add_project_dependencies' after a build target has been declared." + "line": "test cases/failing/122 targets before add_project_dependency/meson.build:5:0: ERROR: Tried to use 'add_project_dependencies' after a build target has been declared." } ] } diff --git a/test cases/failing/124 extract from unity/meson.build b/test cases/failing/123 extract from unity/meson.build index 9e3e65f..9e3e65f 100644 --- a/test cases/failing/124 extract from unity/meson.build +++ b/test cases/failing/123 extract from unity/meson.build diff --git a/test cases/failing/124 extract from unity/src1.c b/test cases/failing/123 extract from unity/src1.c index da971bb..da971bb 100644 --- a/test cases/failing/124 extract from unity/src1.c +++ b/test cases/failing/123 extract from unity/src1.c diff --git a/test cases/failing/124 extract from unity/src2.c b/test cases/failing/123 extract from unity/src2.c index a461669..a461669 100644 --- a/test cases/failing/124 extract from unity/src2.c +++ b/test cases/failing/123 extract from unity/src2.c diff --git a/test cases/failing/124 extract from unity/test.json b/test cases/failing/123 extract from unity/test.json index ef76d2f..ce9f105 100644 --- a/test cases/failing/124 extract from unity/test.json +++ b/test cases/failing/123 extract from unity/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/124 extract from unity/meson.build:4:37: ERROR: Single object files cannot be extracted in Unity builds. You can only extract all the object files for each compiler at once." + "line": "test cases/failing/123 extract from unity/meson.build:4:37: ERROR: Single object files cannot be extracted in Unity builds. You can only extract all the object files for each compiler at once." } ] } diff --git a/test cases/failing/125 subproject object as a dependency/main.c b/test cases/failing/124 subproject object as a dependency/main.c index 78f2de1..78f2de1 100644 --- a/test cases/failing/125 subproject object as a dependency/main.c +++ b/test cases/failing/124 subproject object as a dependency/main.c diff --git a/test cases/failing/125 subproject object as a dependency/meson.build b/test cases/failing/124 subproject object as a dependency/meson.build index 0114b9a..0114b9a 100644 --- a/test cases/failing/125 subproject object as a dependency/meson.build +++ b/test cases/failing/124 subproject object as a dependency/meson.build diff --git a/test cases/failing/125 subproject object as a dependency/subprojects/sub/meson.build b/test cases/failing/124 subproject object as a dependency/subprojects/sub/meson.build index 0adfd6a..0adfd6a 100644 --- a/test cases/failing/125 subproject object as a dependency/subprojects/sub/meson.build +++ b/test cases/failing/124 subproject object as a dependency/subprojects/sub/meson.build diff --git a/test cases/failing/125 subproject object as a dependency/test.json b/test cases/failing/124 subproject object as a dependency/test.json index 0cb2571..a0eea22 100644 --- a/test cases/failing/125 subproject object as a dependency/test.json +++ b/test cases/failing/124 subproject object as a dependency/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/125 subproject object as a dependency/meson.build:3:0: ERROR: Tried to use subproject object as a dependency." + "line": "test cases/failing/124 subproject object as a dependency/meson.build:3:0: ERROR: Tried to use subproject object as a dependency." } ] } diff --git a/test cases/failing/126 generator host binary/exe.c b/test cases/failing/125 generator host binary/exe.c index 78f2de1..78f2de1 100644 --- a/test cases/failing/126 generator host binary/exe.c +++ b/test cases/failing/125 generator host binary/exe.c diff --git a/test cases/failing/126 generator host binary/lib.in b/test cases/failing/125 generator host binary/lib.in index d0b6ab7..d0b6ab7 100644 --- a/test cases/failing/126 generator host binary/lib.in +++ b/test cases/failing/125 generator host binary/lib.in diff --git a/test cases/failing/126 generator host binary/meson.build b/test cases/failing/125 generator host binary/meson.build index e769338..e769338 100644 --- a/test cases/failing/126 generator host binary/meson.build +++ b/test cases/failing/125 generator host binary/meson.build diff --git a/test cases/failing/126 generator host binary/test.json b/test cases/failing/125 generator host binary/test.json index c633622..c633622 100644 --- a/test cases/failing/126 generator host binary/test.json +++ b/test cases/failing/125 generator host binary/test.json diff --git a/test cases/failing/127 invalid ast/meson.build b/test cases/failing/126 invalid ast/meson.build index 06011c2..06011c2 100644 --- a/test cases/failing/127 invalid ast/meson.build +++ b/test cases/failing/126 invalid ast/meson.build diff --git a/test cases/failing/127 invalid ast/test.json b/test cases/failing/126 invalid ast/test.json index db7bfe8..7dc0684 100644 --- a/test cases/failing/127 invalid ast/test.json +++ b/test cases/failing/126 invalid ast/test.json @@ -2,7 +2,7 @@ "stdout": [ { "match": "re", - "line": "test cases/failing/127 invalid ast/meson.build:1:44: ERROR: Meson version is [0-9.]+(\\.rc[0-9]+)? but project requires 0.1.0" + "line": "test cases/failing/126 invalid ast/meson.build:1:44: ERROR: Meson version is [0-9.]+(\\.rc[0-9]+)? but project requires 0.1.0" } ] } diff --git a/test cases/failing/128 invalid project function/meson.build b/test cases/failing/127 invalid project function/meson.build index 0032c9c..0032c9c 100644 --- a/test cases/failing/128 invalid project function/meson.build +++ b/test cases/failing/127 invalid project function/meson.build diff --git a/test cases/failing/128 invalid project function/test.json b/test cases/failing/127 invalid project function/test.json index 17c1ac6..bf9cc16 100644 --- a/test cases/failing/128 invalid project function/test.json +++ b/test cases/failing/127 invalid project function/test.json @@ -2,7 +2,7 @@ "stdout": [ { "match": "re", - "line": "test cases/failing/128 invalid project function/meson.build:1:67: ERROR: Meson version is [0-9.]+(\\.rc[0-9]+)? but project requires 0.1.0" + "line": "test cases/failing/127 invalid project function/meson.build:1:67: ERROR: Meson version is [0-9.]+(\\.rc[0-9]+)? but project requires 0.1.0" } ] } diff --git a/test cases/failing/129 utf8 with bom/meson.build b/test cases/failing/128 utf8 with bom/meson.build index 492a0c6..492a0c6 100644 --- a/test cases/failing/129 utf8 with bom/meson.build +++ b/test cases/failing/128 utf8 with bom/meson.build diff --git a/test cases/failing/129 utf8 with bom/test.json b/test cases/failing/128 utf8 with bom/test.json index ec2df13..8b12ba2 100644 --- a/test cases/failing/129 utf8 with bom/test.json +++ b/test cases/failing/128 utf8 with bom/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/129 utf8 with bom/meson.build:0:0: ERROR: Builder file must be encoded in UTF-8 (with no BOM)" + "line": "test cases/failing/128 utf8 with bom/meson.build:0:0: ERROR: Builder file must be encoded in UTF-8 (with no BOM)" } ] } diff --git a/test cases/failing/130 utf8 with bom subdir/meson.build b/test cases/failing/129 utf8 with bom subdir/meson.build index 8d3bbd7..8d3bbd7 100644 --- a/test cases/failing/130 utf8 with bom subdir/meson.build +++ b/test cases/failing/129 utf8 with bom subdir/meson.build diff --git a/test cases/failing/130 utf8 with bom subdir/subdir/meson.build b/test cases/failing/129 utf8 with bom subdir/subdir/meson.build index dbf2b9c..dbf2b9c 100644 --- a/test cases/failing/130 utf8 with bom subdir/subdir/meson.build +++ b/test cases/failing/129 utf8 with bom subdir/subdir/meson.build diff --git a/test cases/failing/130 utf8 with bom subdir/test.json b/test cases/failing/129 utf8 with bom subdir/test.json index bbaea1b..66a97d7 100644 --- a/test cases/failing/130 utf8 with bom subdir/test.json +++ b/test cases/failing/129 utf8 with bom subdir/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/130 utf8 with bom subdir/subdir/meson.build:0:0: ERROR: Builder file must be encoded in UTF-8 (with no BOM)" + "line": "test cases/failing/129 utf8 with bom subdir/subdir/meson.build:0:0: ERROR: Builder file must be encoded in UTF-8 (with no BOM)" } ] } diff --git a/test cases/failing/131 utf8 with bom options/meson.build b/test cases/failing/130 utf8 with bom options/meson.build index 50413e0..50413e0 100644 --- a/test cases/failing/131 utf8 with bom options/meson.build +++ b/test cases/failing/130 utf8 with bom options/meson.build diff --git a/test cases/failing/131 utf8 with bom options/meson.options b/test cases/failing/130 utf8 with bom options/meson.options index 250c032..250c032 100644 --- a/test cases/failing/131 utf8 with bom options/meson.options +++ b/test cases/failing/130 utf8 with bom options/meson.options diff --git a/test cases/failing/131 utf8 with bom options/test.json b/test cases/failing/130 utf8 with bom options/test.json index e6391c5..f11a78f 100644 --- a/test cases/failing/131 utf8 with bom options/test.json +++ b/test cases/failing/130 utf8 with bom options/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/131 utf8 with bom options/meson.options:0:0: ERROR: Builder file must be encoded in UTF-8 (with no BOM)" + "line": "test cases/failing/130 utf8 with bom options/meson.options:0:0: ERROR: Builder file must be encoded in UTF-8 (with no BOM)" } ] } diff --git a/test cases/failing/132 module use inside project decl/meson.build b/test cases/failing/131 module use inside project decl/meson.build index 8f82a5d..8f82a5d 100644 --- a/test cases/failing/132 module use inside project decl/meson.build +++ b/test cases/failing/131 module use inside project decl/meson.build diff --git a/test cases/failing/132 module use inside project decl/test.json b/test cases/failing/131 module use inside project decl/test.json index 33e377b..4fef501 100644 --- a/test cases/failing/132 module use inside project decl/test.json +++ b/test cases/failing/131 module use inside project decl/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/132 module use inside project decl/meson.build:4:21: ERROR: Module methods (python.find_installation) cannot be invoked during project declaration." + "line": "test cases/failing/131 module use inside project decl/meson.build:4:21: ERROR: Module methods (python.find_installation) cannot be invoked during project declaration." } ] } diff --git a/test cases/failing/133 dub missing dependency/dub.json b/test cases/failing/132 dub missing dependency/dub.json index 1ad9ddc..1ad9ddc 100644 --- a/test cases/failing/133 dub missing dependency/dub.json +++ b/test cases/failing/132 dub missing dependency/dub.json diff --git a/test cases/failing/133 dub missing dependency/dub.selections.json b/test cases/failing/132 dub missing dependency/dub.selections.json index 322586b..322586b 100644 --- a/test cases/failing/133 dub missing dependency/dub.selections.json +++ b/test cases/failing/132 dub missing dependency/dub.selections.json diff --git a/test cases/failing/133 dub missing dependency/meson.build b/test cases/failing/132 dub missing dependency/meson.build index fcccb3b..fcccb3b 100644 --- a/test cases/failing/133 dub missing dependency/meson.build +++ b/test cases/failing/132 dub missing dependency/meson.build diff --git a/test cases/failing/133 dub missing dependency/source/app.d b/test cases/failing/132 dub missing dependency/source/app.d index d66321b..d66321b 100644 --- a/test cases/failing/133 dub missing dependency/source/app.d +++ b/test cases/failing/132 dub missing dependency/source/app.d diff --git a/test cases/failing/133 dub missing dependency/test.json b/test cases/failing/132 dub missing dependency/test.json index e58dbc7..e58dbc7 100644 --- a/test cases/failing/133 dub missing dependency/test.json +++ b/test cases/failing/132 dub missing dependency/test.json diff --git a/test cases/failing/134 java sources in non jar target/Test.java b/test cases/failing/133 java sources in non jar target/Test.java index e69de29..e69de29 100644 --- a/test cases/failing/134 java sources in non jar target/Test.java +++ b/test cases/failing/133 java sources in non jar target/Test.java diff --git a/test cases/failing/134 java sources in non jar target/meson.build b/test cases/failing/133 java sources in non jar target/meson.build index 0aa802a..0aa802a 100644 --- a/test cases/failing/134 java sources in non jar target/meson.build +++ b/test cases/failing/133 java sources in non jar target/meson.build diff --git a/test cases/failing/134 java sources in non jar target/test.json b/test cases/failing/133 java sources in non jar target/test.json index 271ac36..877ea16 100644 --- a/test cases/failing/134 java sources in non jar target/test.json +++ b/test cases/failing/133 java sources in non jar target/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/134 java sources in non jar target/meson.build:3:0: ERROR: Build target of type \"executable\" cannot build java source: \"Test.java\". Use \"jar\" instead." + "line": "test cases/failing/133 java sources in non jar target/meson.build:3:0: ERROR: Build target of type \"executable\" cannot build java source: \"Test.java\". Use \"jar\" instead." } ] } diff --git a/test cases/failing/134 rust link_language/f.rs b/test cases/failing/134 rust link_language/f.rs new file mode 100644 index 0000000..da0f5d9 --- /dev/null +++ b/test cases/failing/134 rust link_language/f.rs @@ -0,0 +1 @@ +pub fn main() {} diff --git a/test cases/failing/134 rust link_language/meson.build b/test cases/failing/134 rust link_language/meson.build new file mode 100644 index 0000000..7fd223d --- /dev/null +++ b/test cases/failing/134 rust link_language/meson.build @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright © 2021 Intel Corporation + +project('rust wrong link language', 'c') + +if not add_languages('rust', required: false) + error('MESON_SKIP_TEST test requires rust compiler') +endif + +executable('f', 'f.rs', link_language: 'c') diff --git a/test cases/failing/134 rust link_language/test.json b/test cases/failing/134 rust link_language/test.json new file mode 100644 index 0000000..e144149 --- /dev/null +++ b/test cases/failing/134 rust link_language/test.json @@ -0,0 +1,8 @@ +{ + "stdout": [ + { + "line": "test cases/failing/134 rust link_language/meson.build:10:0: ERROR: cannot build Rust sources with a different link_language" + } + ] +} + diff --git a/test cases/failing/135 invalid build_subdir/existing-dir/config.h.in b/test cases/failing/135 invalid build_subdir/existing-dir/config.h.in new file mode 100644 index 0000000..14a1558 --- /dev/null +++ b/test cases/failing/135 invalid build_subdir/existing-dir/config.h.in @@ -0,0 +1,5 @@ +#define MESSAGE "@var@" +#define OTHER "@other@" "@second@" "@empty@" + +#mesondefine BE_TRUE +#mesondefine SHOULD_BE_UNDEF diff --git a/test cases/failing/135 invalid build_subdir/meson.build b/test cases/failing/135 invalid build_subdir/meson.build new file mode 100644 index 0000000..b54ec9a --- /dev/null +++ b/test cases/failing/135 invalid build_subdir/meson.build @@ -0,0 +1,24 @@ +project('invalid build_subdir test', 'c', meson_version : '>= 1.9.1') + +# This setup intentially tries to use a build_subdir +# with a name matching one in the source directory. +# produce a Ninja targets with the same name. It only works on +# unix, because on Windows the target has a '.exe' suffix. +# +# This test might fail to work on different backends or when +# output location is redirected. + +conf = configuration_data() + +conf.set('var', 'mystring') +conf.set('other', 'string 2') +conf.set('second', ' bonus') +conf.set('BE_TRUE', true) + +configure_file(input : files('existing-dir/config.h.in'), + output : 'config.h', + build_subdir : 'existing-dir', + install_dir : 'share/appdir/existing-dir', + configuration : conf) + +run_target('build_dir', command: ['echo', 'clash 1']) diff --git a/test cases/failing/135 invalid build_subdir/test.json b/test cases/failing/135 invalid build_subdir/test.json new file mode 100644 index 0000000..aecc4e9 --- /dev/null +++ b/test cases/failing/135 invalid build_subdir/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/135 invalid build_subdir/meson.build:18:0: ERROR: Build subdir \"existing-dir\" in \"config.h\" exists in source tree." + } + ] +} diff --git a/test cases/failing/16 extract from subproject/main.c b/test cases/failing/16 extract from subproject/main.c deleted file mode 100644 index 6c8ecae..0000000 --- a/test cases/failing/16 extract from subproject/main.c +++ /dev/null @@ -1,5 +0,0 @@ -int sub_lib_method(void); - -int main(void) { - return 1337 - sub_lib_method(); -} diff --git a/test cases/failing/16 extract from subproject/meson.build b/test cases/failing/16 extract from subproject/meson.build deleted file mode 100644 index 286aaa1..0000000 --- a/test cases/failing/16 extract from subproject/meson.build +++ /dev/null @@ -1,9 +0,0 @@ -project('extract subproject object', 'c') - -sub = subproject('sub_project') -lib = sub.get_variable('lib') - -exe = executable('exe', 'main.c', - objects : lib.extract_objects('sub_lib.c')) - -test('extraction test', exe) diff --git a/test cases/failing/16 extract from subproject/subprojects/sub_project/meson.build b/test cases/failing/16 extract from subproject/subprojects/sub_project/meson.build deleted file mode 100644 index 0810df5..0000000 --- a/test cases/failing/16 extract from subproject/subprojects/sub_project/meson.build +++ /dev/null @@ -1,3 +0,0 @@ -project('extract subproject object -- subproject', 'c') - -lib = library('sub_lib', 'sub_lib.c') diff --git a/test cases/failing/16 extract from subproject/subprojects/sub_project/sub_lib.c b/test cases/failing/16 extract from subproject/subprojects/sub_project/sub_lib.c deleted file mode 100644 index be3c9aa..0000000 --- a/test cases/failing/16 extract from subproject/subprojects/sub_project/sub_lib.c +++ /dev/null @@ -1,3 +0,0 @@ -int sub_lib_method() { - return 1337; -} diff --git a/test cases/failing/16 extract from subproject/test.json b/test cases/failing/16 extract from subproject/test.json deleted file mode 100644 index 1616033..0000000 --- a/test cases/failing/16 extract from subproject/test.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "stdout": [ - { - "line": "test cases/failing/16 extract from subproject/meson.build:7:32: ERROR: Tried to extract objects from a different subproject." - } - ] -} diff --git a/test cases/failing/17 same target/file.c b/test cases/failing/16 same target/file.c index 7412372..7412372 100644 --- a/test cases/failing/17 same target/file.c +++ b/test cases/failing/16 same target/file.c diff --git a/test cases/failing/17 same target/meson.build b/test cases/failing/16 same target/meson.build index ee586d0..ee586d0 100644 --- a/test cases/failing/17 same target/meson.build +++ b/test cases/failing/16 same target/meson.build diff --git a/test cases/failing/17 same target/test.json b/test cases/failing/16 same target/test.json index 0005ba4..ec154b1 100644 --- a/test cases/failing/17 same target/test.json +++ b/test cases/failing/16 same target/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/17 same target/meson.build:4:0: ERROR: Tried to create target \"foo\", but a target of that name already exists." + "line": "test cases/failing/16 same target/meson.build:4:0: ERROR: Tried to create target \"foo\", but a target of that name already exists." } ] } diff --git a/test cases/failing/18 wrong plusassign/meson.build b/test cases/failing/17 wrong plusassign/meson.build index af7727a..af7727a 100644 --- a/test cases/failing/18 wrong plusassign/meson.build +++ b/test cases/failing/17 wrong plusassign/meson.build diff --git a/test cases/failing/18 wrong plusassign/test.json b/test cases/failing/17 wrong plusassign/test.json index c698f85..a613d7b 100644 --- a/test cases/failing/18 wrong plusassign/test.json +++ b/test cases/failing/17 wrong plusassign/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/18 wrong plusassign/meson.build:3:0: ERROR: Plusassignment target must be an id." + "line": "test cases/failing/17 wrong plusassign/meson.build:3:0: ERROR: Plusassignment target must be an id." } ] } diff --git a/test cases/failing/19 target clash/clash.c b/test cases/failing/18 target clash/clash.c index 2daa06c..2daa06c 100644 --- a/test cases/failing/19 target clash/clash.c +++ b/test cases/failing/18 target clash/clash.c diff --git a/test cases/failing/19 target clash/meson.build b/test cases/failing/18 target clash/meson.build index 4fd0934..4fd0934 100644 --- a/test cases/failing/19 target clash/meson.build +++ b/test cases/failing/18 target clash/meson.build diff --git a/test cases/failing/19 target clash/test.json b/test cases/failing/18 target clash/test.json index d22b894..d22b894 100644 --- a/test cases/failing/19 target clash/test.json +++ b/test cases/failing/18 target clash/test.json diff --git a/test cases/failing/20 version/meson.build b/test cases/failing/19 version/meson.build index d25a395..d25a395 100644 --- a/test cases/failing/20 version/meson.build +++ b/test cases/failing/19 version/meson.build diff --git a/test cases/failing/20 version/test.json b/test cases/failing/19 version/test.json index 565fbf2..f78e34c 100644 --- a/test cases/failing/20 version/test.json +++ b/test cases/failing/19 version/test.json @@ -2,7 +2,7 @@ "stdout": [ { "match": "re", - "line": "test cases/failing/20 version/meson\\.build:1:44: ERROR: Meson version is .* but project requires >100\\.0\\.0" + "line": "test cases/failing/19 version/meson\\.build:1:44: ERROR: Meson version is .* but project requires >100\\.0\\.0" } ] } diff --git a/test cases/failing/21 subver/meson.build b/test cases/failing/20 subver/meson.build index ea0f914..ea0f914 100644 --- a/test cases/failing/21 subver/meson.build +++ b/test cases/failing/20 subver/meson.build diff --git a/test cases/failing/21 subver/subprojects/foo/meson.build b/test cases/failing/20 subver/subprojects/foo/meson.build index 615ee92..615ee92 100644 --- a/test cases/failing/21 subver/subprojects/foo/meson.build +++ b/test cases/failing/20 subver/subprojects/foo/meson.build diff --git a/test cases/failing/21 subver/test.json b/test cases/failing/20 subver/test.json index 694b777..86f4e5b 100644 --- a/test cases/failing/21 subver/test.json +++ b/test cases/failing/20 subver/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/21 subver/meson.build:3:4: ERROR: Subproject foo version is 1.0.0 but ['>1.0.0'] required." + "line": "test cases/failing/20 subver/meson.build:3:4: ERROR: Subproject foo version is 1.0.0 but ['>1.0.0'] required." } ] } diff --git a/test cases/failing/22 assert/meson.build b/test cases/failing/21 assert/meson.build index 45cff07..45cff07 100644 --- a/test cases/failing/22 assert/meson.build +++ b/test cases/failing/21 assert/meson.build diff --git a/test cases/failing/22 assert/test.json b/test cases/failing/21 assert/test.json index edae999..8f80c2d 100644 --- a/test cases/failing/22 assert/test.json +++ b/test cases/failing/21 assert/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/22 assert/meson.build:3:0: ERROR: Assert failed: I am fail." + "line": "test cases/failing/21 assert/meson.build:3:0: ERROR: Assert failed: I am fail." } ] } diff --git a/test cases/failing/23 rel testdir/meson.build b/test cases/failing/22 rel testdir/meson.build index c10558b..c10558b 100644 --- a/test cases/failing/23 rel testdir/meson.build +++ b/test cases/failing/22 rel testdir/meson.build diff --git a/test cases/failing/23 rel testdir/simple.c b/test cases/failing/22 rel testdir/simple.c index 11b7fad..11b7fad 100644 --- a/test cases/failing/23 rel testdir/simple.c +++ b/test cases/failing/22 rel testdir/simple.c diff --git a/test cases/failing/23 rel testdir/test.json b/test cases/failing/22 rel testdir/test.json index bc80bc6..c16acf9 100644 --- a/test cases/failing/23 rel testdir/test.json +++ b/test cases/failing/22 rel testdir/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/23 rel testdir/meson.build:4:0: ERROR: test keyword argument \"workdir\" must be an absolute path" + "line": "test cases/failing/22 rel testdir/meson.build:4:0: ERROR: test keyword argument \"workdir\" must be an absolute path" } ] } diff --git a/test cases/failing/24 int conversion/meson.build b/test cases/failing/23 int conversion/meson.build index 8db72f3..8db72f3 100644 --- a/test cases/failing/24 int conversion/meson.build +++ b/test cases/failing/23 int conversion/meson.build diff --git a/test cases/failing/24 int conversion/test.json b/test cases/failing/23 int conversion/test.json index e749928..df3fdd5 100644 --- a/test cases/failing/24 int conversion/test.json +++ b/test cases/failing/23 int conversion/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/24 int conversion/meson.build:3:13: ERROR: String 'notanumber' cannot be converted to int" + "line": "test cases/failing/23 int conversion/meson.build:3:13: ERROR: String 'notanumber' cannot be converted to int" } ] } diff --git a/test cases/failing/25 badlang/meson.build b/test cases/failing/24 badlang/meson.build index a31059b..a31059b 100644 --- a/test cases/failing/25 badlang/meson.build +++ b/test cases/failing/24 badlang/meson.build diff --git a/test cases/failing/25 badlang/test.json b/test cases/failing/24 badlang/test.json index 0b23fd7..bf387c2 100644 --- a/test cases/failing/25 badlang/test.json +++ b/test cases/failing/24 badlang/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/25 badlang/meson.build:3:0: ERROR: Tried to use unknown language \"nonexisting\"." + "line": "test cases/failing/24 badlang/meson.build:3:0: ERROR: Tried to use unknown language \"nonexisting\"." } ] } diff --git a/test cases/failing/26 output subdir/foo.in b/test cases/failing/25 output subdir/foo.in index 3d1bf19..3d1bf19 100644 --- a/test cases/failing/26 output subdir/foo.in +++ b/test cases/failing/25 output subdir/foo.in diff --git a/test cases/failing/26 output subdir/meson.build b/test cases/failing/25 output subdir/meson.build index c4bd806..c4bd806 100644 --- a/test cases/failing/26 output subdir/meson.build +++ b/test cases/failing/25 output subdir/meson.build diff --git a/test cases/failing/26 output subdir/subdir/dummy.txt b/test cases/failing/25 output subdir/subdir/dummy.txt index 7e907f1..7e907f1 100644 --- a/test cases/failing/26 output subdir/subdir/dummy.txt +++ b/test cases/failing/25 output subdir/subdir/dummy.txt diff --git a/test cases/failing/26 output subdir/test.json b/test cases/failing/25 output subdir/test.json index df09726..63e0e94 100644 --- a/test cases/failing/26 output subdir/test.json +++ b/test cases/failing/25 output subdir/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/26 output subdir/meson.build:3:0: ERROR: configure_file keyword argument \"output\" Output 'subdir/foo' must not contain a path segment." + "line": "test cases/failing/25 output subdir/meson.build:3:0: ERROR: configure_file keyword argument \"output\" Output 'subdir/foo' must not contain a path segment." } ] } diff --git a/test cases/failing/27 noprog use/meson.build b/test cases/failing/26 noprog use/meson.build index af31ece..af31ece 100644 --- a/test cases/failing/27 noprog use/meson.build +++ b/test cases/failing/26 noprog use/meson.build diff --git a/test cases/failing/27 noprog use/test.json b/test cases/failing/26 noprog use/test.json index b84562e..9c6cec4 100644 --- a/test cases/failing/27 noprog use/test.json +++ b/test cases/failing/26 noprog use/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/27 noprog use/meson.build:5:0: ERROR: Tried to use not-found external program in \"command\"" + "line": "test cases/failing/26 noprog use/meson.build:5:0: ERROR: Tried to use not-found external program in \"command\"" } ] } diff --git a/test cases/failing/28 no crossprop/meson.build b/test cases/failing/27 no crossprop/meson.build index 9152077..9152077 100644 --- a/test cases/failing/28 no crossprop/meson.build +++ b/test cases/failing/27 no crossprop/meson.build diff --git a/test cases/failing/28 no crossprop/test.json b/test cases/failing/27 no crossprop/test.json index 78be0b7..1182c4f 100644 --- a/test cases/failing/28 no crossprop/test.json +++ b/test cases/failing/27 no crossprop/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/28 no crossprop/meson.build:3:14: ERROR: Unknown property for host machine: nonexisting" + "line": "test cases/failing/27 no crossprop/meson.build:3:14: ERROR: Unknown property for host machine: nonexisting" } ] } diff --git a/test cases/failing/29 nested ternary/meson.build b/test cases/failing/28 nested ternary/meson.build index 76364ce..76364ce 100644 --- a/test cases/failing/29 nested ternary/meson.build +++ b/test cases/failing/28 nested ternary/meson.build diff --git a/test cases/failing/29 nested ternary/test.json b/test cases/failing/28 nested ternary/test.json index ba05013..5efeca0 100644 --- a/test cases/failing/29 nested ternary/test.json +++ b/test cases/failing/28 nested ternary/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/29 nested ternary/meson.build:3:12: ERROR: Nested ternary operators are not allowed." + "line": "test cases/failing/28 nested ternary/meson.build:3:12: ERROR: Nested ternary operators are not allowed." } ] } diff --git a/test cases/failing/30 invalid man extension/foo.a1 b/test cases/failing/29 invalid man extension/foo.a1 index e69de29..e69de29 100644 --- a/test cases/failing/30 invalid man extension/foo.a1 +++ b/test cases/failing/29 invalid man extension/foo.a1 diff --git a/test cases/failing/30 invalid man extension/meson.build b/test cases/failing/29 invalid man extension/meson.build index b3694d5..b3694d5 100644 --- a/test cases/failing/30 invalid man extension/meson.build +++ b/test cases/failing/29 invalid man extension/meson.build diff --git a/test cases/failing/30 invalid man extension/test.json b/test cases/failing/29 invalid man extension/test.json index 6ab6843..2ff03f1 100644 --- a/test cases/failing/30 invalid man extension/test.json +++ b/test cases/failing/29 invalid man extension/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/30 invalid man extension/meson.build:2:5: ERROR: Man file must have a file extension of a number between 1 and 9" + "line": "test cases/failing/29 invalid man extension/meson.build:2:5: ERROR: Man file must have a file extension of a number between 1 and 9" } ] } diff --git a/test cases/failing/31 no man extension/foo b/test cases/failing/30 no man extension/foo index e69de29..e69de29 100644 --- a/test cases/failing/31 no man extension/foo +++ b/test cases/failing/30 no man extension/foo diff --git a/test cases/failing/31 no man extension/meson.build b/test cases/failing/30 no man extension/meson.build index 33fdd3e..33fdd3e 100644 --- a/test cases/failing/31 no man extension/meson.build +++ b/test cases/failing/30 no man extension/meson.build diff --git a/test cases/failing/31 no man extension/test.json b/test cases/failing/30 no man extension/test.json index 3082f48..8239f01 100644 --- a/test cases/failing/31 no man extension/test.json +++ b/test cases/failing/30 no man extension/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/31 no man extension/meson.build:2:5: ERROR: Man file must have a file extension of a number between 1 and 9" + "line": "test cases/failing/30 no man extension/meson.build:2:5: ERROR: Man file must have a file extension of a number between 1 and 9" } ] } diff --git a/test cases/failing/32 exe static shared/meson.build b/test cases/failing/31 exe static shared/meson.build index 2ae5125..2ae5125 100644 --- a/test cases/failing/32 exe static shared/meson.build +++ b/test cases/failing/31 exe static shared/meson.build diff --git a/test cases/failing/32 exe static shared/prog.c b/test cases/failing/31 exe static shared/prog.c index 26603b6..26603b6 100644 --- a/test cases/failing/32 exe static shared/prog.c +++ b/test cases/failing/31 exe static shared/prog.c diff --git a/test cases/failing/32 exe static shared/shlib2.c b/test cases/failing/31 exe static shared/shlib2.c index 5b68843..5b68843 100644 --- a/test cases/failing/32 exe static shared/shlib2.c +++ b/test cases/failing/31 exe static shared/shlib2.c diff --git a/test cases/failing/32 exe static shared/stat.c b/test cases/failing/31 exe static shared/stat.c index 56ec66c..56ec66c 100644 --- a/test cases/failing/32 exe static shared/stat.c +++ b/test cases/failing/31 exe static shared/stat.c diff --git a/test cases/failing/32 exe static shared/test.json b/test cases/failing/31 exe static shared/test.json index 0b859e3..cb9efa6 100644 --- a/test cases/failing/32 exe static shared/test.json +++ b/test cases/failing/31 exe static shared/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/32 exe static shared/meson.build:9:9: ERROR: Can't link non-PIC static library 'stat' into shared library 'shr2'. Use the 'pic' option to static_library to build with PIC." + "line": "test cases/failing/31 exe static shared/meson.build:9:9: ERROR: Can't link non-PIC static library 'stat' into shared library 'shr2'. Use the 'pic' option to static_library to build with PIC." } ] } diff --git a/test cases/failing/33 non-root subproject/meson.build b/test cases/failing/32 non-root subproject/meson.build index 65b1d23..65b1d23 100644 --- a/test cases/failing/33 non-root subproject/meson.build +++ b/test cases/failing/32 non-root subproject/meson.build diff --git a/test cases/failing/33 non-root subproject/some/meson.build b/test cases/failing/32 non-root subproject/some/meson.build index d82f451..d82f451 100644 --- a/test cases/failing/33 non-root subproject/some/meson.build +++ b/test cases/failing/32 non-root subproject/some/meson.build diff --git a/test cases/failing/32 non-root subproject/test.json b/test cases/failing/32 non-root subproject/test.json new file mode 100644 index 0000000..6f6d4ee --- /dev/null +++ b/test cases/failing/32 non-root subproject/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/32 non-root subproject/some/meson.build:1:0: ERROR: Attempted to resolve subproject without subprojects directory present." + } + ] +} diff --git a/test cases/failing/34 dependency not-required then required/meson.build b/test cases/failing/33 dependency not-required then required/meson.build index 54f5a58..54f5a58 100644 --- a/test cases/failing/34 dependency not-required then required/meson.build +++ b/test cases/failing/33 dependency not-required then required/meson.build diff --git a/test cases/failing/34 dependency not-required then required/test.json b/test cases/failing/33 dependency not-required then required/test.json index 7dd8519..7dd8519 100644 --- a/test cases/failing/34 dependency not-required then required/test.json +++ b/test cases/failing/33 dependency not-required then required/test.json diff --git a/test cases/failing/33 non-root subproject/test.json b/test cases/failing/33 non-root subproject/test.json deleted file mode 100644 index 52baf6a..0000000 --- a/test cases/failing/33 non-root subproject/test.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "stdout": [ - { - "line": "test cases/failing/33 non-root subproject/some/meson.build:1:0: ERROR: Neither a subproject directory nor a someproj.wrap file was found." - } - ] -} diff --git a/test cases/failing/35 project argument after target/exe.c b/test cases/failing/34 project argument after target/exe.c index 11b7fad..11b7fad 100644 --- a/test cases/failing/35 project argument after target/exe.c +++ b/test cases/failing/34 project argument after target/exe.c diff --git a/test cases/failing/35 project argument after target/meson.build b/test cases/failing/34 project argument after target/meson.build index 5402c67..5402c67 100644 --- a/test cases/failing/35 project argument after target/meson.build +++ b/test cases/failing/34 project argument after target/meson.build diff --git a/test cases/failing/35 project argument after target/test.json b/test cases/failing/34 project argument after target/test.json index f5efd9b..7a41b7d 100644 --- a/test cases/failing/35 project argument after target/test.json +++ b/test cases/failing/34 project argument after target/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/35 project argument after target/meson.build:7:0: ERROR: Tried to use 'add_project_arguments' after a build target has been declared." + "line": "test cases/failing/34 project argument after target/meson.build:7:0: ERROR: Tried to use 'add_project_arguments' after a build target has been declared." } ] } diff --git a/test cases/failing/36 pkgconfig dependency impossible conditions/meson.build b/test cases/failing/35 pkgconfig dependency impossible conditions/meson.build index 874b581..874b581 100644 --- a/test cases/failing/36 pkgconfig dependency impossible conditions/meson.build +++ b/test cases/failing/35 pkgconfig dependency impossible conditions/meson.build diff --git a/test cases/failing/36 pkgconfig dependency impossible conditions/test.json b/test cases/failing/35 pkgconfig dependency impossible conditions/test.json index 6deddbe..74b9731 100644 --- a/test cases/failing/36 pkgconfig dependency impossible conditions/test.json +++ b/test cases/failing/35 pkgconfig dependency impossible conditions/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/36 pkgconfig dependency impossible conditions/meson.build:7:0: ERROR: Dependency 'zlib' is required but not found." + "line": "test cases/failing/35 pkgconfig dependency impossible conditions/meson.build:7:0: ERROR: Dependency 'zlib' is required but not found." } ] } diff --git a/test cases/failing/37 has function external dependency/meson.build b/test cases/failing/36 has function external dependency/meson.build index 45a3bc2..45a3bc2 100644 --- a/test cases/failing/37 has function external dependency/meson.build +++ b/test cases/failing/36 has function external dependency/meson.build diff --git a/test cases/failing/37 has function external dependency/mylib.c b/test cases/failing/36 has function external dependency/mylib.c index d9fbd34..d9fbd34 100644 --- a/test cases/failing/37 has function external dependency/mylib.c +++ b/test cases/failing/36 has function external dependency/mylib.c diff --git a/test cases/failing/37 has function external dependency/test.json b/test cases/failing/36 has function external dependency/test.json index 81d6f91..705cf45 100644 --- a/test cases/failing/37 has function external dependency/test.json +++ b/test cases/failing/36 has function external dependency/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/37 has function external dependency/meson.build:8:3: ERROR: Dependencies must be external dependencies" + "line": "test cases/failing/36 has function external dependency/meson.build:8:3: ERROR: Dependencies must be external dependencies" } ] } diff --git a/test cases/failing/38 prefix absolute/meson.build b/test cases/failing/37 prefix absolute/meson.build index 6d0114b..6d0114b 100644 --- a/test cases/failing/38 prefix absolute/meson.build +++ b/test cases/failing/37 prefix absolute/meson.build diff --git a/test cases/failing/38 prefix absolute/test.json b/test cases/failing/37 prefix absolute/test.json index 859906c..c1f8271 100644 --- a/test cases/failing/38 prefix absolute/test.json +++ b/test cases/failing/37 prefix absolute/test.json @@ -5,7 +5,7 @@ "stdout": [ { "comment": "literal 'some/path/notabs' appears in output, irrespective of os.path.sep, as that's the prefix", - "line": "test cases/failing/38 prefix absolute/meson.build:1:0: ERROR: prefix value 'some/path/notabs' must be an absolute path" + "line": "test cases/failing/37 prefix absolute/meson.build:1:0: ERROR: prefix value 'some/path/notabs' must be an absolute path" } ] } diff --git a/test cases/failing/39 kwarg assign/dummy.c b/test cases/failing/38 kwarg assign/dummy.c index 16fcdd9..16fcdd9 100644 --- a/test cases/failing/39 kwarg assign/dummy.c +++ b/test cases/failing/38 kwarg assign/dummy.c diff --git a/test cases/failing/39 kwarg assign/meson.build b/test cases/failing/38 kwarg assign/meson.build index 7d42521..7d42521 100644 --- a/test cases/failing/39 kwarg assign/meson.build +++ b/test cases/failing/38 kwarg assign/meson.build diff --git a/test cases/failing/39 kwarg assign/prog.c b/test cases/failing/38 kwarg assign/prog.c index 11b7fad..11b7fad 100644 --- a/test cases/failing/39 kwarg assign/prog.c +++ b/test cases/failing/38 kwarg assign/prog.c diff --git a/test cases/failing/39 kwarg assign/test.json b/test cases/failing/38 kwarg assign/test.json index e12f2dc..daf2550 100644 --- a/test cases/failing/39 kwarg assign/test.json +++ b/test cases/failing/38 kwarg assign/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/39 kwarg assign/meson.build:3:30: ERROR: Tried to assign values inside an argument list." + "line": "test cases/failing/38 kwarg assign/meson.build:3:30: ERROR: Tried to assign values inside an argument list." } ] } diff --git a/test cases/failing/40 custom target plainname many inputs/1.txt b/test cases/failing/39 custom target plainname many inputs/1.txt index d00491f..d00491f 100644 --- a/test cases/failing/40 custom target plainname many inputs/1.txt +++ b/test cases/failing/39 custom target plainname many inputs/1.txt diff --git a/test cases/failing/40 custom target plainname many inputs/2.txt b/test cases/failing/39 custom target plainname many inputs/2.txt index 0cfbf08..0cfbf08 100644 --- a/test cases/failing/40 custom target plainname many inputs/2.txt +++ b/test cases/failing/39 custom target plainname many inputs/2.txt diff --git a/test cases/failing/40 custom target plainname many inputs/catfiles.py b/test cases/failing/39 custom target plainname many inputs/catfiles.py index 1c53e24..1c53e24 100644 --- a/test cases/failing/40 custom target plainname many inputs/catfiles.py +++ b/test cases/failing/39 custom target plainname many inputs/catfiles.py diff --git a/test cases/failing/40 custom target plainname many inputs/meson.build b/test cases/failing/39 custom target plainname many inputs/meson.build index 8513bf8..8513bf8 100644 --- a/test cases/failing/40 custom target plainname many inputs/meson.build +++ b/test cases/failing/39 custom target plainname many inputs/meson.build diff --git a/test cases/failing/40 custom target plainname many inputs/test.json b/test cases/failing/39 custom target plainname many inputs/test.json index f5c3abf..e3f4c1b 100644 --- a/test cases/failing/40 custom target plainname many inputs/test.json +++ b/test cases/failing/39 custom target plainname many inputs/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/40 custom target plainname many inputs/meson.build:5:0: ERROR: custom_target: output cannot contain \"@PLAINNAME@\" or \"@BASENAME@\" when there is more than one input (we can't know which to use)" + "line": "test cases/failing/39 custom target plainname many inputs/meson.build:5:0: ERROR: custom_target: output cannot contain \"@PLAINNAME@\" or \"@BASENAME@\" when there is more than one input (we can't know which to use)" } ] } diff --git a/test cases/failing/41 custom target outputs not matching install_dirs/generator.py b/test cases/failing/40 custom target outputs not matching install_dirs/generator.py index 4ac6179..4ac6179 100755 --- a/test cases/failing/41 custom target outputs not matching install_dirs/generator.py +++ b/test cases/failing/40 custom target outputs not matching install_dirs/generator.py diff --git a/test cases/failing/41 custom target outputs not matching install_dirs/meson.build b/test cases/failing/40 custom target outputs not matching install_dirs/meson.build index ed99dba..ed99dba 100644 --- a/test cases/failing/41 custom target outputs not matching install_dirs/meson.build +++ b/test cases/failing/40 custom target outputs not matching install_dirs/meson.build diff --git a/test cases/failing/41 custom target outputs not matching install_dirs/test.json b/test cases/failing/40 custom target outputs not matching install_dirs/test.json index f9e2ba7..f9e2ba7 100644 --- a/test cases/failing/41 custom target outputs not matching install_dirs/test.json +++ b/test cases/failing/40 custom target outputs not matching install_dirs/test.json diff --git a/test cases/failing/42 project name colon/meson.build b/test cases/failing/41 project name colon/meson.build index 53e947e..53e947e 100644 --- a/test cases/failing/42 project name colon/meson.build +++ b/test cases/failing/41 project name colon/meson.build diff --git a/test cases/failing/42 project name colon/test.json b/test cases/failing/41 project name colon/test.json index c3b880e..8c8e284 100644 --- a/test cases/failing/42 project name colon/test.json +++ b/test cases/failing/41 project name colon/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/42 project name colon/meson.build:1:0: ERROR: Project name 'name with :' must not contain ':'" + "line": "test cases/failing/41 project name colon/meson.build:1:0: ERROR: Project name 'name with :' must not contain ':'" } ] } diff --git a/test cases/failing/43 abs subdir/bob/meson.build b/test cases/failing/42 abs subdir/bob/meson.build index 7bbf4b2..7bbf4b2 100644 --- a/test cases/failing/43 abs subdir/bob/meson.build +++ b/test cases/failing/42 abs subdir/bob/meson.build diff --git a/test cases/failing/43 abs subdir/meson.build b/test cases/failing/42 abs subdir/meson.build index a8534d0..a8534d0 100644 --- a/test cases/failing/43 abs subdir/meson.build +++ b/test cases/failing/42 abs subdir/meson.build diff --git a/test cases/failing/43 abs subdir/test.json b/test cases/failing/42 abs subdir/test.json index 201ed44..5b99c24 100644 --- a/test cases/failing/43 abs subdir/test.json +++ b/test cases/failing/42 abs subdir/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/43 abs subdir/meson.build:5:0: ERROR: Subdir argument must be a relative path." + "line": "test cases/failing/42 abs subdir/meson.build:5:0: ERROR: Subdir argument must be a relative path." } ] } diff --git a/test cases/failing/44 abspath to srcdir/meson.build b/test cases/failing/43 abspath to srcdir/meson.build index 78c6124..78c6124 100644 --- a/test cases/failing/44 abspath to srcdir/meson.build +++ b/test cases/failing/43 abspath to srcdir/meson.build diff --git a/test cases/failing/44 abspath to srcdir/test.json b/test cases/failing/43 abspath to srcdir/test.json index c64ecfb..af9588d 100644 --- a/test cases/failing/44 abspath to srcdir/test.json +++ b/test cases/failing/43 abspath to srcdir/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/44 abspath to srcdir/meson.build:3:0: ERROR: Tried to form an absolute path to a dir in the source tree." + "line": "test cases/failing/43 abspath to srcdir/meson.build:3:0: ERROR: Tried to form an absolute path to a dir in the source tree." } ] } diff --git a/test cases/failing/45 pkgconfig variables reserved/meson.build b/test cases/failing/44 pkgconfig variables reserved/meson.build index 82ae995..82ae995 100644 --- a/test cases/failing/45 pkgconfig variables reserved/meson.build +++ b/test cases/failing/44 pkgconfig variables reserved/meson.build diff --git a/test cases/failing/45 pkgconfig variables reserved/simple.c b/test cases/failing/44 pkgconfig variables reserved/simple.c index e8a6d83..e8a6d83 100644 --- a/test cases/failing/45 pkgconfig variables reserved/simple.c +++ b/test cases/failing/44 pkgconfig variables reserved/simple.c diff --git a/test cases/failing/45 pkgconfig variables reserved/simple.h b/test cases/failing/44 pkgconfig variables reserved/simple.h index bb52e6d..bb52e6d 100644 --- a/test cases/failing/45 pkgconfig variables reserved/simple.h +++ b/test cases/failing/44 pkgconfig variables reserved/simple.h diff --git a/test cases/failing/45 pkgconfig variables reserved/test.json b/test cases/failing/44 pkgconfig variables reserved/test.json index 853eb82..66ded59 100644 --- a/test cases/failing/45 pkgconfig variables reserved/test.json +++ b/test cases/failing/44 pkgconfig variables reserved/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/45 pkgconfig variables reserved/meson.build:8:5: ERROR: Variable \"prefix\" is reserved" + "line": "test cases/failing/44 pkgconfig variables reserved/meson.build:8:5: ERROR: Variable \"prefix\" is reserved" } ] } diff --git a/test cases/failing/46 pkgconfig variables zero length/meson.build b/test cases/failing/45 pkgconfig variables zero length/meson.build index 65d3344..65d3344 100644 --- a/test cases/failing/46 pkgconfig variables zero length/meson.build +++ b/test cases/failing/45 pkgconfig variables zero length/meson.build diff --git a/test cases/failing/46 pkgconfig variables zero length/simple.c b/test cases/failing/45 pkgconfig variables zero length/simple.c index e8a6d83..e8a6d83 100644 --- a/test cases/failing/46 pkgconfig variables zero length/simple.c +++ b/test cases/failing/45 pkgconfig variables zero length/simple.c diff --git a/test cases/failing/46 pkgconfig variables zero length/simple.h b/test cases/failing/45 pkgconfig variables zero length/simple.h index bb52e6d..bb52e6d 100644 --- a/test cases/failing/46 pkgconfig variables zero length/simple.h +++ b/test cases/failing/45 pkgconfig variables zero length/simple.h diff --git a/test cases/failing/46 pkgconfig variables zero length/test.json b/test cases/failing/45 pkgconfig variables zero length/test.json index b174065..899b9ed 100644 --- a/test cases/failing/46 pkgconfig variables zero length/test.json +++ b/test cases/failing/45 pkgconfig variables zero length/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/46 pkgconfig variables zero length/meson.build:8:5: ERROR: pkgconfig.generate keyword argument \"variables\" empty variable name" + "line": "test cases/failing/45 pkgconfig variables zero length/meson.build:8:5: ERROR: pkgconfig.generate keyword argument \"variables\" empty variable name" } ] } diff --git a/test cases/failing/47 pkgconfig variables not key value/meson.build b/test cases/failing/46 pkgconfig variables not key value/meson.build index 02fa737..02fa737 100644 --- a/test cases/failing/47 pkgconfig variables not key value/meson.build +++ b/test cases/failing/46 pkgconfig variables not key value/meson.build diff --git a/test cases/failing/47 pkgconfig variables not key value/simple.c b/test cases/failing/46 pkgconfig variables not key value/simple.c index e8a6d83..e8a6d83 100644 --- a/test cases/failing/47 pkgconfig variables not key value/simple.c +++ b/test cases/failing/46 pkgconfig variables not key value/simple.c diff --git a/test cases/failing/47 pkgconfig variables not key value/simple.h b/test cases/failing/46 pkgconfig variables not key value/simple.h index bb52e6d..bb52e6d 100644 --- a/test cases/failing/47 pkgconfig variables not key value/simple.h +++ b/test cases/failing/46 pkgconfig variables not key value/simple.h diff --git a/test cases/failing/47 pkgconfig variables not key value/test.json b/test cases/failing/46 pkgconfig variables not key value/test.json index 11cd374..5729d1e 100644 --- a/test cases/failing/47 pkgconfig variables not key value/test.json +++ b/test cases/failing/46 pkgconfig variables not key value/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/47 pkgconfig variables not key value/meson.build:8:5: ERROR: pkgconfig.generate keyword argument \"variables\" variable 'this_should_be_key_value' must have a value separated by equals sign." + "line": "test cases/failing/46 pkgconfig variables not key value/meson.build:8:5: ERROR: pkgconfig.generate keyword argument \"variables\" variable 'this_should_be_key_value' must have a value separated by equals sign." } ] } diff --git a/test cases/failing/48 executable comparison/meson.build b/test cases/failing/47 executable comparison/meson.build index 041bcf3..041bcf3 100644 --- a/test cases/failing/48 executable comparison/meson.build +++ b/test cases/failing/47 executable comparison/meson.build diff --git a/test cases/failing/48 executable comparison/prog.c b/test cases/failing/47 executable comparison/prog.c index 0314ff1..0314ff1 100644 --- a/test cases/failing/48 executable comparison/prog.c +++ b/test cases/failing/47 executable comparison/prog.c diff --git a/test cases/failing/48 executable comparison/test.json b/test cases/failing/47 executable comparison/test.json index db6dac3..f365477 100644 --- a/test cases/failing/48 executable comparison/test.json +++ b/test cases/failing/47 executable comparison/test.json @@ -2,7 +2,7 @@ "stdout": [ { "match": "re", - "line": "test cases/failing/48 executable comparison/meson.build:6:14: ERROR: Object <ExecutableHolder prog1@exe: prog1(.exe)?> of type Executable does not support the `<` operator." + "line": "test cases/failing/47 executable comparison/meson.build:6:14: ERROR: Object <ExecutableHolder prog1@exe: prog1(.exe)?> of type Executable does not support the `<` operator." } ] } diff --git a/test cases/failing/49 inconsistent comparison/meson.build b/test cases/failing/48 inconsistent comparison/meson.build index 237a157..237a157 100644 --- a/test cases/failing/49 inconsistent comparison/meson.build +++ b/test cases/failing/48 inconsistent comparison/meson.build diff --git a/test cases/failing/49 inconsistent comparison/test.json b/test cases/failing/48 inconsistent comparison/test.json index 4440a86..42157a5 100644 --- a/test cases/failing/49 inconsistent comparison/test.json +++ b/test cases/failing/48 inconsistent comparison/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/49 inconsistent comparison/meson.build:5:12: ERROR: Object <[ArrayHolder] holds [list]: []> of type array does not support the `<` operator." + "line": "test cases/failing/48 inconsistent comparison/meson.build:5:12: ERROR: Object <[ArrayHolder] holds [list]: []> of type array does not support the `<` operator." } ] } diff --git a/test cases/failing/50 slashname/meson.build b/test cases/failing/49 slashname/meson.build index 29fe1fc..29fe1fc 100644 --- a/test cases/failing/50 slashname/meson.build +++ b/test cases/failing/49 slashname/meson.build diff --git a/test cases/failing/50 slashname/sub/meson.build b/test cases/failing/49 slashname/sub/meson.build index 3baacf6..3baacf6 100644 --- a/test cases/failing/50 slashname/sub/meson.build +++ b/test cases/failing/49 slashname/sub/meson.build diff --git a/test cases/failing/50 slashname/sub/prog.c b/test cases/failing/49 slashname/sub/prog.c index 722de0a..722de0a 100644 --- a/test cases/failing/50 slashname/sub/prog.c +++ b/test cases/failing/49 slashname/sub/prog.c diff --git a/test cases/failing/50 slashname/test.json b/test cases/failing/49 slashname/test.json index 44b566c..e07d163 100644 --- a/test cases/failing/50 slashname/test.json +++ b/test cases/failing/49 slashname/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/50 slashname/meson.build:9:0: ERROR: Target \"sub/prog\" has a path segment pointing to directory \"sub\". This is an error." + "line": "test cases/failing/49 slashname/meson.build:9:0: ERROR: Target \"sub/prog\" has a path segment pointing to directory \"sub\". This is an error." } ] } diff --git a/test cases/failing/51 reserved meson prefix/meson-foo/meson.build b/test cases/failing/50 reserved meson prefix/meson-foo/meson.build index e69de29..e69de29 100644 --- a/test cases/failing/51 reserved meson prefix/meson-foo/meson.build +++ b/test cases/failing/50 reserved meson prefix/meson-foo/meson.build diff --git a/test cases/failing/51 reserved meson prefix/meson.build b/test cases/failing/50 reserved meson prefix/meson.build index 1339035..1339035 100644 --- a/test cases/failing/51 reserved meson prefix/meson.build +++ b/test cases/failing/50 reserved meson prefix/meson.build diff --git a/test cases/failing/51 reserved meson prefix/test.json b/test cases/failing/50 reserved meson prefix/test.json index 9eb1fa4..512a445 100644 --- a/test cases/failing/51 reserved meson prefix/test.json +++ b/test cases/failing/50 reserved meson prefix/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/51 reserved meson prefix/meson.build:3:0: ERROR: The \"meson-\" prefix is reserved and cannot be used for top-level subdir()." + "line": "test cases/failing/50 reserved meson prefix/meson.build:3:0: ERROR: The \"meson-\" prefix is reserved and cannot be used for top-level subdir()." } ] } diff --git a/test cases/failing/52 or on new line/meson.build b/test cases/failing/51 or on new line/meson.build index b0bd08e..b0bd08e 100644 --- a/test cases/failing/52 or on new line/meson.build +++ b/test cases/failing/51 or on new line/meson.build diff --git a/test cases/failing/52 or on new line/meson_options.txt b/test cases/failing/51 or on new line/meson_options.txt index 3302cf4..3302cf4 100644 --- a/test cases/failing/52 or on new line/meson_options.txt +++ b/test cases/failing/51 or on new line/meson_options.txt diff --git a/test cases/failing/52 or on new line/test.json b/test cases/failing/51 or on new line/test.json index 49a7255..c05d1a8 100644 --- a/test cases/failing/52 or on new line/test.json +++ b/test cases/failing/51 or on new line/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/52 or on new line/meson.build:4:8: ERROR: Invalid or clause." + "line": "test cases/failing/51 or on new line/meson.build:4:8: ERROR: Invalid or clause." } ] } diff --git a/test cases/failing/53 link with executable/meson.build b/test cases/failing/52 link with executable/meson.build index 186b3e5..186b3e5 100644 --- a/test cases/failing/53 link with executable/meson.build +++ b/test cases/failing/52 link with executable/meson.build diff --git a/test cases/failing/53 link with executable/module.c b/test cases/failing/52 link with executable/module.c index dc0124a..dc0124a 100644 --- a/test cases/failing/53 link with executable/module.c +++ b/test cases/failing/52 link with executable/module.c diff --git a/test cases/failing/53 link with executable/prog.c b/test cases/failing/52 link with executable/prog.c index f3836d7..f3836d7 100644 --- a/test cases/failing/53 link with executable/prog.c +++ b/test cases/failing/52 link with executable/prog.c diff --git a/test cases/failing/53 link with executable/test.json b/test cases/failing/52 link with executable/test.json index 8c25e07..ba9c345 100644 --- a/test cases/failing/53 link with executable/test.json +++ b/test cases/failing/52 link with executable/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/53 link with executable/meson.build:4:4: ERROR: Link target 'prog' is not linkable." + "line": "test cases/failing/52 link with executable/meson.build:4:4: ERROR: Link target 'prog' is not linkable." } ] } diff --git a/test cases/failing/54 assign custom target index/meson.build b/test cases/failing/53 assign custom target index/meson.build index 7f2a820..7f2a820 100644 --- a/test cases/failing/54 assign custom target index/meson.build +++ b/test cases/failing/53 assign custom target index/meson.build diff --git a/test cases/failing/54 assign custom target index/test.json b/test cases/failing/53 assign custom target index/test.json index 15287d6..e9990a2 100644 --- a/test cases/failing/54 assign custom target index/test.json +++ b/test cases/failing/53 assign custom target index/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/54 assign custom target index/meson.build:24:0: ERROR: Assignment target must be an id." + "line": "test cases/failing/53 assign custom target index/meson.build:24:0: ERROR: Assignment target must be an id." } ] } diff --git a/test cases/failing/55 getoption prefix/meson.build b/test cases/failing/54 getoption prefix/meson.build index 8f85cff..8f85cff 100644 --- a/test cases/failing/55 getoption prefix/meson.build +++ b/test cases/failing/54 getoption prefix/meson.build diff --git a/test cases/failing/55 getoption prefix/subprojects/abc/meson.build b/test cases/failing/54 getoption prefix/subprojects/abc/meson.build index aa9c3df..aa9c3df 100644 --- a/test cases/failing/55 getoption prefix/subprojects/abc/meson.build +++ b/test cases/failing/54 getoption prefix/subprojects/abc/meson.build diff --git a/test cases/failing/55 getoption prefix/subprojects/abc/meson_options.txt b/test cases/failing/54 getoption prefix/subprojects/abc/meson_options.txt index 89e624e..89e624e 100644 --- a/test cases/failing/55 getoption prefix/subprojects/abc/meson_options.txt +++ b/test cases/failing/54 getoption prefix/subprojects/abc/meson_options.txt diff --git a/test cases/failing/55 getoption prefix/test.json b/test cases/failing/54 getoption prefix/test.json index 9f3a936..d2ba2f6 100644 --- a/test cases/failing/55 getoption prefix/test.json +++ b/test cases/failing/54 getoption prefix/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/55 getoption prefix/meson.build:5:0: ERROR: Having a colon in option name is forbidden, projects are not allowed to directly access options of other subprojects." + "line": "test cases/failing/54 getoption prefix/meson.build:5:0: ERROR: Having a colon in option name is forbidden, projects are not allowed to directly access options of other subprojects." } ] } diff --git a/test cases/failing/56 bad option argument/meson.build b/test cases/failing/55 bad option argument/meson.build index 5219cfb..5219cfb 100644 --- a/test cases/failing/56 bad option argument/meson.build +++ b/test cases/failing/55 bad option argument/meson.build diff --git a/test cases/failing/56 bad option argument/meson_options.txt b/test cases/failing/55 bad option argument/meson_options.txt index 0e0372b..0e0372b 100644 --- a/test cases/failing/56 bad option argument/meson_options.txt +++ b/test cases/failing/55 bad option argument/meson_options.txt diff --git a/test cases/failing/56 bad option argument/test.json b/test cases/failing/55 bad option argument/test.json index c7957cd..0bfc4b9 100644 --- a/test cases/failing/56 bad option argument/test.json +++ b/test cases/failing/55 bad option argument/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/56 bad option argument/meson_options.txt:1:0: ERROR: string option got unknown keyword arguments \"value_\"" + "line": "test cases/failing/55 bad option argument/meson_options.txt:1:0: ERROR: string option got unknown keyword arguments \"value_\"" } ] } diff --git a/test cases/failing/57 subproj filegrab/meson.build b/test cases/failing/56 subproj filegrab/meson.build index b5c484c..b5c484c 100644 --- a/test cases/failing/57 subproj filegrab/meson.build +++ b/test cases/failing/56 subproj filegrab/meson.build diff --git a/test cases/failing/57 subproj filegrab/prog.c b/test cases/failing/56 subproj filegrab/prog.c index 0314ff1..0314ff1 100644 --- a/test cases/failing/57 subproj filegrab/prog.c +++ b/test cases/failing/56 subproj filegrab/prog.c diff --git a/test cases/failing/57 subproj filegrab/subprojects/a/meson.build b/test cases/failing/56 subproj filegrab/subprojects/a/meson.build index 80b9888..80b9888 100644 --- a/test cases/failing/57 subproj filegrab/subprojects/a/meson.build +++ b/test cases/failing/56 subproj filegrab/subprojects/a/meson.build diff --git a/test cases/failing/57 subproj filegrab/test.json b/test cases/failing/56 subproj filegrab/test.json index 8b0b27f..cdd1029 100644 --- a/test cases/failing/57 subproj filegrab/test.json +++ b/test cases/failing/56 subproj filegrab/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/57 subproj filegrab/subprojects/a/meson.build:3:0: ERROR: Sandbox violation: Tried to grab file prog.c outside current (sub)project." + "line": "test cases/failing/56 subproj filegrab/subprojects/a/meson.build:3:0: ERROR: Sandbox violation: Tried to grab file prog.c outside current (sub)project." } ] } diff --git a/test cases/failing/58 grab subproj/meson.build b/test cases/failing/57 grab subproj/meson.build index 30fc690..30fc690 100644 --- a/test cases/failing/58 grab subproj/meson.build +++ b/test cases/failing/57 grab subproj/meson.build diff --git a/test cases/failing/58 grab subproj/subprojects/foo/meson.build b/test cases/failing/57 grab subproj/subprojects/foo/meson.build index b346f6d..b346f6d 100644 --- a/test cases/failing/58 grab subproj/subprojects/foo/meson.build +++ b/test cases/failing/57 grab subproj/subprojects/foo/meson.build diff --git a/test cases/failing/58 grab subproj/subprojects/foo/sub.c b/test cases/failing/57 grab subproj/subprojects/foo/sub.c index a94b1f5..a94b1f5 100644 --- a/test cases/failing/58 grab subproj/subprojects/foo/sub.c +++ b/test cases/failing/57 grab subproj/subprojects/foo/sub.c diff --git a/test cases/failing/58 grab subproj/test.json b/test cases/failing/57 grab subproj/test.json index bdb0fdf..1915efd 100644 --- a/test cases/failing/58 grab subproj/test.json +++ b/test cases/failing/57 grab subproj/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/58 grab subproj/meson.build:7:0: ERROR: Sandbox violation: Tried to grab file sub.c from a nested subproject." + "line": "test cases/failing/57 grab subproj/meson.build:7:0: ERROR: Sandbox violation: Tried to grab file sub.c from a nested subproject." } ] } diff --git a/test cases/failing/59 grab sibling/meson.build b/test cases/failing/58 grab sibling/meson.build index 5ddc295..5ddc295 100644 --- a/test cases/failing/59 grab sibling/meson.build +++ b/test cases/failing/58 grab sibling/meson.build diff --git a/test cases/failing/59 grab sibling/subprojects/a/meson.build b/test cases/failing/58 grab sibling/subprojects/a/meson.build index 6dd9f61..6dd9f61 100644 --- a/test cases/failing/59 grab sibling/subprojects/a/meson.build +++ b/test cases/failing/58 grab sibling/subprojects/a/meson.build diff --git a/test cases/failing/59 grab sibling/subprojects/b/meson.build b/test cases/failing/58 grab sibling/subprojects/b/meson.build index 57f261a..57f261a 100644 --- a/test cases/failing/59 grab sibling/subprojects/b/meson.build +++ b/test cases/failing/58 grab sibling/subprojects/b/meson.build diff --git a/test cases/failing/59 grab sibling/subprojects/b/sneaky.c b/test cases/failing/58 grab sibling/subprojects/b/sneaky.c index 46718c6..46718c6 100644 --- a/test cases/failing/59 grab sibling/subprojects/b/sneaky.c +++ b/test cases/failing/58 grab sibling/subprojects/b/sneaky.c diff --git a/test cases/failing/59 grab sibling/test.json b/test cases/failing/58 grab sibling/test.json index ec17e7e..cbf8b2d 100644 --- a/test cases/failing/59 grab sibling/test.json +++ b/test cases/failing/58 grab sibling/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/59 grab sibling/subprojects/a/meson.build:3:0: ERROR: Sandbox violation: Tried to grab file sneaky.c outside current (sub)project." + "line": "test cases/failing/58 grab sibling/subprojects/a/meson.build:3:0: ERROR: Sandbox violation: Tried to grab file sneaky.c outside current (sub)project." } ] } diff --git a/test cases/failing/60 string as link target/meson.build b/test cases/failing/59 string as link target/meson.build index cb83fff..cb83fff 100644 --- a/test cases/failing/60 string as link target/meson.build +++ b/test cases/failing/59 string as link target/meson.build diff --git a/test cases/failing/60 string as link target/prog.c b/test cases/failing/59 string as link target/prog.c index 0314ff1..0314ff1 100644 --- a/test cases/failing/60 string as link target/prog.c +++ b/test cases/failing/59 string as link target/prog.c diff --git a/test cases/failing/60 string as link target/test.json b/test cases/failing/59 string as link target/test.json index a97b124..ddc6399 100644 --- a/test cases/failing/60 string as link target/test.json +++ b/test cases/failing/59 string as link target/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/60 string as link target/meson.build:2:0: ERROR: '' is not a target." + "line": "test cases/failing/59 string as link target/meson.build:2:0: ERROR: '' is not a target." } ] } diff --git a/test cases/failing/61 dependency not-found and required/meson.build b/test cases/failing/60 dependency not-found and required/meson.build index 1ce5747..1ce5747 100644 --- a/test cases/failing/61 dependency not-found and required/meson.build +++ b/test cases/failing/60 dependency not-found and required/meson.build diff --git a/test cases/failing/61 dependency not-found and required/test.json b/test cases/failing/60 dependency not-found and required/test.json index f7a6c9f..c104a6a 100644 --- a/test cases/failing/61 dependency not-found and required/test.json +++ b/test cases/failing/60 dependency not-found and required/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/61 dependency not-found and required/meson.build:2:6: ERROR: Dependency is required but has no candidates." + "line": "test cases/failing/60 dependency not-found and required/meson.build:2:6: ERROR: Dependency is required but has no candidates." } ] } diff --git a/test cases/failing/62 subproj different versions/main.c b/test cases/failing/61 subproj different versions/main.c index 8793c62..8793c62 100644 --- a/test cases/failing/62 subproj different versions/main.c +++ b/test cases/failing/61 subproj different versions/main.c diff --git a/test cases/failing/62 subproj different versions/meson.build b/test cases/failing/61 subproj different versions/meson.build index e964e42..e964e42 100644 --- a/test cases/failing/62 subproj different versions/meson.build +++ b/test cases/failing/61 subproj different versions/meson.build diff --git a/test cases/failing/62 subproj different versions/subprojects/a/a.c b/test cases/failing/61 subproj different versions/subprojects/a/a.c index cd41a65..cd41a65 100644 --- a/test cases/failing/62 subproj different versions/subprojects/a/a.c +++ b/test cases/failing/61 subproj different versions/subprojects/a/a.c diff --git a/test cases/failing/62 subproj different versions/subprojects/a/a.h b/test cases/failing/61 subproj different versions/subprojects/a/a.h index 8f1d49e..8f1d49e 100644 --- a/test cases/failing/62 subproj different versions/subprojects/a/a.h +++ b/test cases/failing/61 subproj different versions/subprojects/a/a.h diff --git a/test cases/failing/62 subproj different versions/subprojects/a/meson.build b/test cases/failing/61 subproj different versions/subprojects/a/meson.build index e84182a..e84182a 100644 --- a/test cases/failing/62 subproj different versions/subprojects/a/meson.build +++ b/test cases/failing/61 subproj different versions/subprojects/a/meson.build diff --git a/test cases/failing/62 subproj different versions/subprojects/b/b.c b/test cases/failing/61 subproj different versions/subprojects/b/b.c index f85f8c3..f85f8c3 100644 --- a/test cases/failing/62 subproj different versions/subprojects/b/b.c +++ b/test cases/failing/61 subproj different versions/subprojects/b/b.c diff --git a/test cases/failing/62 subproj different versions/subprojects/b/b.h b/test cases/failing/61 subproj different versions/subprojects/b/b.h index eced786..eced786 100644 --- a/test cases/failing/62 subproj different versions/subprojects/b/b.h +++ b/test cases/failing/61 subproj different versions/subprojects/b/b.h diff --git a/test cases/failing/62 subproj different versions/subprojects/b/meson.build b/test cases/failing/61 subproj different versions/subprojects/b/meson.build index 0398340..0398340 100644 --- a/test cases/failing/62 subproj different versions/subprojects/b/meson.build +++ b/test cases/failing/61 subproj different versions/subprojects/b/meson.build diff --git a/test cases/failing/62 subproj different versions/subprojects/c/c.h b/test cases/failing/61 subproj different versions/subprojects/c/c.h index 2b15f60..2b15f60 100644 --- a/test cases/failing/62 subproj different versions/subprojects/c/c.h +++ b/test cases/failing/61 subproj different versions/subprojects/c/c.h diff --git a/test cases/failing/62 subproj different versions/subprojects/c/meson.build b/test cases/failing/61 subproj different versions/subprojects/c/meson.build index 7184933..7184933 100644 --- a/test cases/failing/62 subproj different versions/subprojects/c/meson.build +++ b/test cases/failing/61 subproj different versions/subprojects/c/meson.build diff --git a/test cases/failing/62 subproj different versions/test.json b/test cases/failing/61 subproj different versions/test.json index 705ff51..b395baa 100644 --- a/test cases/failing/62 subproj different versions/test.json +++ b/test cases/failing/61 subproj different versions/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/62 subproj different versions/subprojects/b/meson.build:3:8: ERROR: Dependency 'c' is required but not found." + "line": "test cases/failing/61 subproj different versions/subprojects/b/meson.build:3:8: ERROR: Dependency 'c' is required but not found." } ] } diff --git a/test cases/failing/63 wrong boost module/meson.build b/test cases/failing/62 wrong boost module/meson.build index 937e587..937e587 100644 --- a/test cases/failing/63 wrong boost module/meson.build +++ b/test cases/failing/62 wrong boost module/meson.build diff --git a/test cases/failing/63 wrong boost module/test.json b/test cases/failing/62 wrong boost module/test.json index ec3c1b0..75ef82b 100644 --- a/test cases/failing/63 wrong boost module/test.json +++ b/test cases/failing/62 wrong boost module/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/63 wrong boost module/meson.build:9:10: ERROR: Dependency \"boost\" not found, tried system" + "line": "test cases/failing/62 wrong boost module/meson.build:9:10: ERROR: Dependency \"boost\" not found, tried system" } ] } diff --git a/test cases/failing/64 install_data rename bad size/file1.txt b/test cases/failing/63 install_data rename bad size/file1.txt index e69de29..e69de29 100644 --- a/test cases/failing/64 install_data rename bad size/file1.txt +++ b/test cases/failing/63 install_data rename bad size/file1.txt diff --git a/test cases/failing/64 install_data rename bad size/file2.txt b/test cases/failing/63 install_data rename bad size/file2.txt index e69de29..e69de29 100644 --- a/test cases/failing/64 install_data rename bad size/file2.txt +++ b/test cases/failing/63 install_data rename bad size/file2.txt diff --git a/test cases/failing/64 install_data rename bad size/meson.build b/test cases/failing/63 install_data rename bad size/meson.build index 849bb9a..849bb9a 100644 --- a/test cases/failing/64 install_data rename bad size/meson.build +++ b/test cases/failing/63 install_data rename bad size/meson.build diff --git a/test cases/failing/64 install_data rename bad size/test.json b/test cases/failing/63 install_data rename bad size/test.json index 5ed4748..28988af 100644 --- a/test cases/failing/64 install_data rename bad size/test.json +++ b/test cases/failing/63 install_data rename bad size/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/64 install_data rename bad size/meson.build:3:0: ERROR: \"rename\" and \"sources\" argument lists must be the same length if \"rename\" is given. Rename has 1 elements and sources has 2." + "line": "test cases/failing/63 install_data rename bad size/meson.build:3:0: ERROR: \"rename\" and \"sources\" argument lists must be the same length if \"rename\" is given. Rename has 1 elements and sources has 2." } ] } diff --git a/test cases/failing/65 skip only subdir/meson.build b/test cases/failing/64 skip only subdir/meson.build index 4832bd4..4832bd4 100644 --- a/test cases/failing/65 skip only subdir/meson.build +++ b/test cases/failing/64 skip only subdir/meson.build diff --git a/test cases/failing/65 skip only subdir/subdir/meson.build b/test cases/failing/64 skip only subdir/subdir/meson.build index 1ba447b..1ba447b 100644 --- a/test cases/failing/65 skip only subdir/subdir/meson.build +++ b/test cases/failing/64 skip only subdir/subdir/meson.build diff --git a/test cases/failing/65 skip only subdir/test.json b/test cases/failing/64 skip only subdir/test.json index 2747566..54fa369 100644 --- a/test cases/failing/65 skip only subdir/test.json +++ b/test cases/failing/64 skip only subdir/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/65 skip only subdir/meson.build:8:0: ERROR: File main.cpp does not exist." + "line": "test cases/failing/64 skip only subdir/meson.build:8:0: ERROR: File main.cpp does not exist." } ] } diff --git a/test cases/failing/66 dual override/meson.build b/test cases/failing/65 dual override/meson.build index 999b8bc..999b8bc 100644 --- a/test cases/failing/66 dual override/meson.build +++ b/test cases/failing/65 dual override/meson.build diff --git a/test cases/failing/66 dual override/overrides.py b/test cases/failing/65 dual override/overrides.py index 49e9b7a..49e9b7a 100644 --- a/test cases/failing/66 dual override/overrides.py +++ b/test cases/failing/65 dual override/overrides.py diff --git a/test cases/failing/66 dual override/test.json b/test cases/failing/65 dual override/test.json index b8c2651..6daa014 100644 --- a/test cases/failing/66 dual override/test.json +++ b/test cases/failing/65 dual override/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/66 dual override/meson.build:5:6: ERROR: Tried to override executable \"override\" which has already been overridden." + "line": "test cases/failing/65 dual override/meson.build:5:6: ERROR: Tried to override executable \"override\" which has already been overridden." } ] } diff --git a/test cases/failing/67 override used/meson.build b/test cases/failing/66 override used/meson.build index 582fc1b..582fc1b 100644 --- a/test cases/failing/67 override used/meson.build +++ b/test cases/failing/66 override used/meson.build diff --git a/test cases/failing/67 override used/other.py b/test cases/failing/66 override used/other.py index f62ba96..f62ba96 100755 --- a/test cases/failing/67 override used/other.py +++ b/test cases/failing/66 override used/other.py diff --git a/test cases/failing/67 override used/something.py b/test cases/failing/66 override used/something.py index 64c9454..64c9454 100755 --- a/test cases/failing/67 override used/something.py +++ b/test cases/failing/66 override used/something.py diff --git a/test cases/failing/67 override used/test.json b/test cases/failing/66 override used/test.json index 90d8b4b..c73e4a4 100644 --- a/test cases/failing/67 override used/test.json +++ b/test cases/failing/66 override used/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/67 override used/meson.build:5:6: ERROR: Tried to override finding of executable \"something.py\" which has already been found." + "line": "test cases/failing/66 override used/meson.build:5:6: ERROR: Tried to override finding of executable \"something.py\" which has already been found." } ] } diff --git a/test cases/failing/68 run_command unclean exit/meson.build b/test cases/failing/67 run_command unclean exit/meson.build index f6bf895..f6bf895 100644 --- a/test cases/failing/68 run_command unclean exit/meson.build +++ b/test cases/failing/67 run_command unclean exit/meson.build diff --git a/test cases/failing/68 run_command unclean exit/returncode.py b/test cases/failing/67 run_command unclean exit/returncode.py index 84dbc5d..84dbc5d 100755 --- a/test cases/failing/68 run_command unclean exit/returncode.py +++ b/test cases/failing/67 run_command unclean exit/returncode.py diff --git a/test cases/failing/68 run_command unclean exit/test.json b/test cases/failing/67 run_command unclean exit/test.json index cef67e5..9578849 100644 --- a/test cases/failing/68 run_command unclean exit/test.json +++ b/test cases/failing/67 run_command unclean exit/test.json @@ -2,7 +2,7 @@ "stdout": [ { "match": "re", - "line": "test cases/failing/68 run_command unclean exit/meson\\.build:4:0: ERROR: Command `.*['\"].*[\\\\/]test cases[\\\\/]failing[\\\\/]68 run_command unclean exit[\\\\/]\\.[\\\\/]returncode\\.py['\"] 1` failed with status 1\\." + "line": "test cases/failing/67 run_command unclean exit/meson\\.build:4:0: ERROR: Command `.*['\"].*[\\\\/]test cases[\\\\/]failing[\\\\/]67 run_command unclean exit[\\\\/]\\.[\\\\/]returncode\\.py['\"] 1` failed with status 1\\." } ] } diff --git a/test cases/failing/69 int literal leading zero/meson.build b/test cases/failing/68 int literal leading zero/meson.build index 87c776f..87c776f 100644 --- a/test cases/failing/69 int literal leading zero/meson.build +++ b/test cases/failing/68 int literal leading zero/meson.build diff --git a/test cases/failing/69 int literal leading zero/test.json b/test cases/failing/68 int literal leading zero/test.json index 200b569..c98217a 100644 --- a/test cases/failing/69 int literal leading zero/test.json +++ b/test cases/failing/68 int literal leading zero/test.json @@ -2,7 +2,7 @@ "stdout": [ { "comment": "this error message is not very informative", - "line": "test cases/failing/69 int literal leading zero/meson.build:5:13: ERROR: Expecting eof got number." + "line": "test cases/failing/68 int literal leading zero/meson.build:5:13: ERROR: Expecting eof got number." } ] } diff --git a/test cases/failing/70 configuration immutable/input b/test cases/failing/69 configuration immutable/input index e69de29..e69de29 100644 --- a/test cases/failing/70 configuration immutable/input +++ b/test cases/failing/69 configuration immutable/input diff --git a/test cases/failing/70 configuration immutable/meson.build b/test cases/failing/69 configuration immutable/meson.build index b6cac41..b6cac41 100644 --- a/test cases/failing/70 configuration immutable/meson.build +++ b/test cases/failing/69 configuration immutable/meson.build diff --git a/test cases/failing/70 configuration immutable/test.json b/test cases/failing/69 configuration immutable/test.json index fc735fa..94f0428 100644 --- a/test cases/failing/70 configuration immutable/test.json +++ b/test cases/failing/69 configuration immutable/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/70 configuration immutable/meson.build:12:16: ERROR: Can not set values on configuration object that has been used." + "line": "test cases/failing/69 configuration immutable/meson.build:12:16: ERROR: Can not set values on configuration object that has been used." } ] } diff --git a/test cases/failing/71 link with shared module on osx/meson.build b/test cases/failing/70 link with shared module on osx/meson.build index bf18b36..bf18b36 100644 --- a/test cases/failing/71 link with shared module on osx/meson.build +++ b/test cases/failing/70 link with shared module on osx/meson.build diff --git a/test cases/failing/71 link with shared module on osx/module.c b/test cases/failing/70 link with shared module on osx/module.c index 81b0d5a..81b0d5a 100644 --- a/test cases/failing/71 link with shared module on osx/module.c +++ b/test cases/failing/70 link with shared module on osx/module.c diff --git a/test cases/failing/71 link with shared module on osx/prog.c b/test cases/failing/70 link with shared module on osx/prog.c index 8164d8d..8164d8d 100644 --- a/test cases/failing/71 link with shared module on osx/prog.c +++ b/test cases/failing/70 link with shared module on osx/prog.c diff --git a/test cases/failing/71 link with shared module on osx/test.json b/test cases/failing/70 link with shared module on osx/test.json index 206d429..430edba 100644 --- a/test cases/failing/71 link with shared module on osx/test.json +++ b/test cases/failing/70 link with shared module on osx/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/71 link with shared module on osx/meson.build:8:4: ERROR: target prog links against shared module mymodule. This is not permitted on OSX" + "line": "test cases/failing/70 link with shared module on osx/meson.build:8:4: ERROR: target prog links against shared module mymodule. This is not permitted on OSX" } ] } diff --git a/test cases/failing/72 non-ascii in ascii encoded configure file/config9.h.in b/test cases/failing/71 non-ascii in ascii encoded configure file/config9.h.in index 323bec6..323bec6 100644 --- a/test cases/failing/72 non-ascii in ascii encoded configure file/config9.h.in +++ b/test cases/failing/71 non-ascii in ascii encoded configure file/config9.h.in diff --git a/test cases/failing/72 non-ascii in ascii encoded configure file/meson.build b/test cases/failing/71 non-ascii in ascii encoded configure file/meson.build index eadb627..eadb627 100644 --- a/test cases/failing/72 non-ascii in ascii encoded configure file/meson.build +++ b/test cases/failing/71 non-ascii in ascii encoded configure file/meson.build diff --git a/test cases/failing/72 non-ascii in ascii encoded configure file/test.json b/test cases/failing/71 non-ascii in ascii encoded configure file/test.json index 1dc0b91..8f993c9 100644 --- a/test cases/failing/72 non-ascii in ascii encoded configure file/test.json +++ b/test cases/failing/71 non-ascii in ascii encoded configure file/test.json @@ -2,7 +2,7 @@ "stdout": [ { "match": "re", - "line": "test cases/failing/72 non-ascii in ascii encoded configure file/meson\\.build:5:0: ERROR: Could not write output file .*[\\\\/]config9\\.h: 'ascii' codec can't encode character '\\\\u0434' in position 17: ordinal not in range\\(128\\)" + "line": "test cases/failing/71 non-ascii in ascii encoded configure file/meson\\.build:5:0: ERROR: Could not write output file .*[\\\\/]config9\\.h: 'ascii' codec can't encode character '\\\\u0434' in position 17: ordinal not in range\\(128\\)" } ] } diff --git a/test cases/failing/73 subproj dependency not-found and required/meson.build b/test cases/failing/72 subproj dependency not-found and required/meson.build index c5a2961..c5a2961 100644 --- a/test cases/failing/73 subproj dependency not-found and required/meson.build +++ b/test cases/failing/72 subproj dependency not-found and required/meson.build diff --git a/test cases/failing/72 subproj dependency not-found and required/test.json b/test cases/failing/72 subproj dependency not-found and required/test.json new file mode 100644 index 0000000..015893b --- /dev/null +++ b/test cases/failing/72 subproj dependency not-found and required/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/72 subproj dependency not-found and required/meson.build:2:10: ERROR: Attempted to resolve subproject without subprojects directory present." + } + ] +} diff --git a/test cases/failing/73 subproj dependency not-found and required/test.json b/test cases/failing/73 subproj dependency not-found and required/test.json deleted file mode 100644 index 11ab031..0000000 --- a/test cases/failing/73 subproj dependency not-found and required/test.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "stdout": [ - { - "line": "test cases/failing/73 subproj dependency not-found and required/meson.build:2:10: ERROR: Neither a subproject directory nor a missing.wrap file was found." - } - ] -} diff --git a/test cases/failing/74 unfound run/meson.build b/test cases/failing/73 unfound run/meson.build index 3f37e9a..3f37e9a 100644 --- a/test cases/failing/74 unfound run/meson.build +++ b/test cases/failing/73 unfound run/meson.build diff --git a/test cases/failing/74 unfound run/test.json b/test cases/failing/73 unfound run/test.json index 8d31785..00e708c 100644 --- a/test cases/failing/74 unfound run/test.json +++ b/test cases/failing/73 unfound run/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/74 unfound run/meson.build:4:0: ERROR: Tried to use non-existing executable 'nonexisting_prog'" + "line": "test cases/failing/73 unfound run/meson.build:4:0: ERROR: Tried to use non-existing executable 'nonexisting_prog'" } ] } diff --git a/test cases/failing/75 framework dependency with version/meson.build b/test cases/failing/74 framework dependency with version/meson.build index ee315eb..ee315eb 100644 --- a/test cases/failing/75 framework dependency with version/meson.build +++ b/test cases/failing/74 framework dependency with version/meson.build diff --git a/test cases/failing/75 framework dependency with version/test.json b/test cases/failing/74 framework dependency with version/test.json index 8a01356..5673758 100644 --- a/test cases/failing/75 framework dependency with version/test.json +++ b/test cases/failing/74 framework dependency with version/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/75 framework dependency with version/meson.build:8:6: ERROR: Dependency lookup for appleframeworks with method 'framework' failed: Unknown version, but need ['>0']." + "line": "test cases/failing/74 framework dependency with version/meson.build:8:6: ERROR: Dependency lookup for appleframeworks with method 'framework' failed: Unknown version, but need ['>0']." } ] } diff --git a/test cases/failing/75 override exe config/foo.c b/test cases/failing/75 override exe config/foo.c new file mode 100644 index 0000000..03b2213 --- /dev/null +++ b/test cases/failing/75 override exe config/foo.c @@ -0,0 +1,3 @@ +int main(void) { + return 0; +} diff --git a/test cases/failing/76 override exe config/meson.build b/test cases/failing/75 override exe config/meson.build index a5d0924..a5d0924 100644 --- a/test cases/failing/76 override exe config/meson.build +++ b/test cases/failing/75 override exe config/meson.build diff --git a/test cases/failing/76 override exe config/test.json b/test cases/failing/75 override exe config/test.json index 23b9055..2260dcc 100644 --- a/test cases/failing/76 override exe config/test.json +++ b/test cases/failing/75 override exe config/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/76 override exe config/meson.build:6:0: ERROR: Program 'bar' was overridden with the compiled executable 'foo' and therefore cannot be used during configuration" + "line": "test cases/failing/75 override exe config/meson.build:6:0: ERROR: Program 'bar' was overridden with the compiled executable 'foo' and therefore cannot be used during configuration" } ] } diff --git a/test cases/failing/77 gl dependency with version/meson.build b/test cases/failing/76 gl dependency with version/meson.build index 0127093..0127093 100644 --- a/test cases/failing/77 gl dependency with version/meson.build +++ b/test cases/failing/76 gl dependency with version/meson.build diff --git a/test cases/failing/77 gl dependency with version/test.json b/test cases/failing/76 gl dependency with version/test.json index 83665cb..3fd6f45 100644 --- a/test cases/failing/77 gl dependency with version/test.json +++ b/test cases/failing/76 gl dependency with version/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/77 gl dependency with version/meson.build:9:6: ERROR: Dependency lookup for gl with method 'system' failed: Unknown version, but need ['>0']." + "line": "test cases/failing/76 gl dependency with version/meson.build:9:6: ERROR: Dependency lookup for gl with method 'system' failed: Unknown version, but need ['>0']." } ] } diff --git a/test cases/failing/78 threads dependency with version/meson.build b/test cases/failing/77 threads dependency with version/meson.build index 6023fae..6023fae 100644 --- a/test cases/failing/78 threads dependency with version/meson.build +++ b/test cases/failing/77 threads dependency with version/meson.build diff --git a/test cases/failing/78 threads dependency with version/test.json b/test cases/failing/77 threads dependency with version/test.json index ec02c2b..270f7ad 100644 --- a/test cases/failing/78 threads dependency with version/test.json +++ b/test cases/failing/77 threads dependency with version/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/78 threads dependency with version/meson.build:3:6: ERROR: Dependency lookup for threads with method 'system' failed: Unknown version, but need ['>0']." + "line": "test cases/failing/77 threads dependency with version/meson.build:3:6: ERROR: Dependency lookup for threads with method 'system' failed: Unknown version, but need ['>0']." } ] } diff --git a/test cases/failing/79 gtest dependency with version/meson.build b/test cases/failing/78 gtest dependency with version/meson.build index efbffe1..efbffe1 100644 --- a/test cases/failing/79 gtest dependency with version/meson.build +++ b/test cases/failing/78 gtest dependency with version/meson.build diff --git a/test cases/failing/79 gtest dependency with version/test.json b/test cases/failing/78 gtest dependency with version/test.json index a32a3c2..48789c6 100644 --- a/test cases/failing/79 gtest dependency with version/test.json +++ b/test cases/failing/78 gtest dependency with version/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/79 gtest dependency with version/meson.build:8:6: ERROR: Dependency 'gtest' is required but not found." + "line": "test cases/failing/78 gtest dependency with version/meson.build:8:6: ERROR: Dependency 'gtest' is required but not found." } ] } diff --git a/test cases/failing/80 dub library/meson.build b/test cases/failing/79 dub library/meson.build index 306d5b3..306d5b3 100644 --- a/test cases/failing/80 dub library/meson.build +++ b/test cases/failing/79 dub library/meson.build diff --git a/test cases/failing/80 dub library/test.json b/test cases/failing/79 dub library/test.json index d3c7d42..9f59604 100644 --- a/test cases/failing/80 dub library/test.json +++ b/test cases/failing/79 dub library/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/80 dub library/meson.build:11:0: ERROR: Dependency \"dubtestproject\" not found" + "line": "test cases/failing/79 dub library/meson.build:11:0: ERROR: Dependency \"dubtestproject\" not found" } ] } diff --git a/test cases/failing/81 dub executable/meson.build b/test cases/failing/80 dub executable/meson.build index 9a134ea..9a134ea 100644 --- a/test cases/failing/81 dub executable/meson.build +++ b/test cases/failing/80 dub executable/meson.build diff --git a/test cases/failing/81 dub executable/test.json b/test cases/failing/80 dub executable/test.json index 8aefa17..edb74f6 100644 --- a/test cases/failing/81 dub executable/test.json +++ b/test cases/failing/80 dub executable/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/81 dub executable/meson.build:11:0: ERROR: Dependency \"dubtestproject:test1\" not found" + "line": "test cases/failing/80 dub executable/meson.build:11:0: ERROR: Dependency \"dubtestproject:test1\" not found" } ] } diff --git a/test cases/failing/82 dub compiler/meson.build b/test cases/failing/81 dub compiler/meson.build index 36f1849..36f1849 100644 --- a/test cases/failing/82 dub compiler/meson.build +++ b/test cases/failing/81 dub compiler/meson.build diff --git a/test cases/failing/82 dub compiler/test.json b/test cases/failing/81 dub compiler/test.json index 89d1882..d82984d 100644 --- a/test cases/failing/82 dub compiler/test.json +++ b/test cases/failing/81 dub compiler/test.json @@ -13,7 +13,7 @@ }, "stdout": [ { - "line": "test cases/failing/82 dub compiler/meson.build:17:0: ERROR: Dependency \"dubtestproject:test2\" not found" + "line": "test cases/failing/81 dub compiler/meson.build:17:0: ERROR: Dependency \"dubtestproject:test2\" not found" } ] } diff --git a/test cases/failing/83 subproj not-found dep/meson.build b/test cases/failing/82 subproj not-found dep/meson.build index 2b17df1..2b17df1 100644 --- a/test cases/failing/83 subproj not-found dep/meson.build +++ b/test cases/failing/82 subproj not-found dep/meson.build diff --git a/test cases/failing/83 subproj not-found dep/subprojects/somesubproj/meson.build b/test cases/failing/82 subproj not-found dep/subprojects/somesubproj/meson.build index 5f451f4..5f451f4 100644 --- a/test cases/failing/83 subproj not-found dep/subprojects/somesubproj/meson.build +++ b/test cases/failing/82 subproj not-found dep/subprojects/somesubproj/meson.build diff --git a/test cases/failing/83 subproj not-found dep/test.json b/test cases/failing/82 subproj not-found dep/test.json index 5da4fb9..e551ac7 100644 --- a/test cases/failing/83 subproj not-found dep/test.json +++ b/test cases/failing/82 subproj not-found dep/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/83 subproj not-found dep/meson.build:2:10: ERROR: Dependency '(anonymous)' is required but not found." + "line": "test cases/failing/82 subproj not-found dep/meson.build:2:10: ERROR: Dependency '(anonymous)' is required but not found." } ] } diff --git a/test cases/failing/84 invalid configure file/input b/test cases/failing/83 invalid configure file/input index e69de29..e69de29 100644 --- a/test cases/failing/84 invalid configure file/input +++ b/test cases/failing/83 invalid configure file/input diff --git a/test cases/failing/84 invalid configure file/meson.build b/test cases/failing/83 invalid configure file/meson.build index 08eca2b..08eca2b 100644 --- a/test cases/failing/84 invalid configure file/meson.build +++ b/test cases/failing/83 invalid configure file/meson.build diff --git a/test cases/failing/84 invalid configure file/test.json b/test cases/failing/83 invalid configure file/test.json index 2b7a745..7982d17 100644 --- a/test cases/failing/84 invalid configure file/test.json +++ b/test cases/failing/83 invalid configure file/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/84 invalid configure file/meson.build:3:0: ERROR: \"install_dir\" must be specified when \"install\" in a configure_file is true" + "line": "test cases/failing/83 invalid configure file/meson.build:3:0: ERROR: \"install_dir\" must be specified when \"install\" in a configure_file is true" } ] } diff --git a/test cases/failing/85 kwarg dupe/meson.build b/test cases/failing/84 kwarg dupe/meson.build index 06821a2..06821a2 100644 --- a/test cases/failing/85 kwarg dupe/meson.build +++ b/test cases/failing/84 kwarg dupe/meson.build diff --git a/test cases/failing/85 kwarg dupe/prog.c b/test cases/failing/84 kwarg dupe/prog.c index 5f3fbe6..5f3fbe6 100644 --- a/test cases/failing/85 kwarg dupe/prog.c +++ b/test cases/failing/84 kwarg dupe/prog.c diff --git a/test cases/failing/85 kwarg dupe/test.json b/test cases/failing/84 kwarg dupe/test.json index 99719ad..c568d6b 100644 --- a/test cases/failing/85 kwarg dupe/test.json +++ b/test cases/failing/84 kwarg dupe/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/85 kwarg dupe/meson.build:6:2: ERROR: Entry \"install\" defined both as a keyword argument and in a \"kwarg\" entry." + "line": "test cases/failing/84 kwarg dupe/meson.build:6:2: ERROR: Entry \"install\" defined both as a keyword argument and in a \"kwarg\" entry." } ] } diff --git a/test cases/failing/86 missing pch file/meson.build b/test cases/failing/85 missing pch file/meson.build index a67b798..a67b798 100644 --- a/test cases/failing/86 missing pch file/meson.build +++ b/test cases/failing/85 missing pch file/meson.build diff --git a/test cases/failing/86 missing pch file/prog.c b/test cases/failing/85 missing pch file/prog.c index 11b7fad..11b7fad 100644 --- a/test cases/failing/86 missing pch file/prog.c +++ b/test cases/failing/85 missing pch file/prog.c diff --git a/test cases/failing/85 missing pch file/test.json b/test cases/failing/85 missing pch file/test.json new file mode 100644 index 0000000..05fb7ac --- /dev/null +++ b/test cases/failing/85 missing pch file/test.json @@ -0,0 +1,8 @@ +{ + "stdout": [ + { + "comment": "literal 'pch/prog.h' from meson.build appears in output, irrespective of os.path.sep", + "line": "test cases/failing/85 missing pch file/meson.build:2:6: ERROR: The following PCH files do not exist: pch/prog.h, pch/prog_pch.c" + } + ] +} diff --git a/test cases/failing/86 missing pch file/test.json b/test cases/failing/86 missing pch file/test.json deleted file mode 100644 index d6a50c2..0000000 --- a/test cases/failing/86 missing pch file/test.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "stdout": [ - { - "comment": "literal 'pch/prog.h' from meson.build appears in output, irrespective of os.path.sep", - "line": "test cases/failing/86 missing pch file/meson.build:2:6: ERROR: File pch/prog.h does not exist." - } - ] -} diff --git a/test cases/failing/87 pch source different folder/include/pch.h b/test cases/failing/86 pch source different folder/include/pch.h index e69de29..e69de29 100644 --- a/test cases/failing/87 pch source different folder/include/pch.h +++ b/test cases/failing/86 pch source different folder/include/pch.h diff --git a/test cases/failing/87 pch source different folder/meson.build b/test cases/failing/86 pch source different folder/meson.build index d320717..d320717 100644 --- a/test cases/failing/87 pch source different folder/meson.build +++ b/test cases/failing/86 pch source different folder/meson.build diff --git a/test cases/failing/87 pch source different folder/prog.c b/test cases/failing/86 pch source different folder/prog.c index 3fb1295..3fb1295 100644 --- a/test cases/failing/87 pch source different folder/prog.c +++ b/test cases/failing/86 pch source different folder/prog.c diff --git a/test cases/failing/87 pch source different folder/src/pch.c b/test cases/failing/86 pch source different folder/src/pch.c index e69de29..e69de29 100644 --- a/test cases/failing/87 pch source different folder/src/pch.c +++ b/test cases/failing/86 pch source different folder/src/pch.c diff --git a/test cases/failing/86 pch source different folder/test.json b/test cases/failing/86 pch source different folder/test.json new file mode 100644 index 0000000..05b6b78 --- /dev/null +++ b/test cases/failing/86 pch source different folder/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/86 pch source different folder/meson.build:4:6: ERROR: executable keyword argument \"c_pch\" PCH files must be stored in the same folder." + } + ] +} diff --git a/test cases/failing/87 pch source different folder/test.json b/test cases/failing/87 pch source different folder/test.json deleted file mode 100644 index 0a9d39d..0000000 --- a/test cases/failing/87 pch source different folder/test.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "stdout": [ - { - "line": "test cases/failing/87 pch source different folder/meson.build:4:6: ERROR: PCH files must be stored in the same folder." - } - ] -} diff --git a/test cases/failing/88 unknown config tool/meson.build b/test cases/failing/87 unknown config tool/meson.build index 536976e..536976e 100644 --- a/test cases/failing/88 unknown config tool/meson.build +++ b/test cases/failing/87 unknown config tool/meson.build diff --git a/test cases/failing/88 unknown config tool/test.json b/test cases/failing/87 unknown config tool/test.json index 5a53b26..025a5a4 100644 --- a/test cases/failing/88 unknown config tool/test.json +++ b/test cases/failing/87 unknown config tool/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/88 unknown config tool/meson.build:2:0: ERROR: Dependency \"no-such-config-tool\" not found" + "line": "test cases/failing/87 unknown config tool/meson.build:2:0: ERROR: Dependency \"no-such-config-tool\" not found" } ] } diff --git a/test cases/failing/89 custom target install data/Info.plist.cpp b/test cases/failing/88 custom target install data/Info.plist.cpp index 9ca2fcb..9ca2fcb 100644 --- a/test cases/failing/89 custom target install data/Info.plist.cpp +++ b/test cases/failing/88 custom target install data/Info.plist.cpp diff --git a/test cases/failing/89 custom target install data/meson.build b/test cases/failing/88 custom target install data/meson.build index 00d348c..00d348c 100644 --- a/test cases/failing/89 custom target install data/meson.build +++ b/test cases/failing/88 custom target install data/meson.build diff --git a/test cases/failing/89 custom target install data/preproc.py b/test cases/failing/88 custom target install data/preproc.py index e6eba4c..e6eba4c 100644 --- a/test cases/failing/89 custom target install data/preproc.py +++ b/test cases/failing/88 custom target install data/preproc.py diff --git a/test cases/failing/89 custom target install data/test.json b/test cases/failing/88 custom target install data/test.json index 3364a6a..fe34eba 100644 --- a/test cases/failing/89 custom target install data/test.json +++ b/test cases/failing/88 custom target install data/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/89 custom target install data/meson.build:11:0: ERROR: install_data argument 1 was of type \"CustomTarget\" but should have been one of: \"str\", \"File\"" + "line": "test cases/failing/88 custom target install data/meson.build:11:0: ERROR: install_data argument 1 was of type \"CustomTarget\" but should have been one of: \"str\", \"File\"" } ] } diff --git a/test cases/failing/90 add dict non string key/meson.build b/test cases/failing/89 add dict non string key/meson.build index c81a3f7..c81a3f7 100644 --- a/test cases/failing/90 add dict non string key/meson.build +++ b/test cases/failing/89 add dict non string key/meson.build diff --git a/test cases/failing/90 add dict non string key/test.json b/test cases/failing/89 add dict non string key/test.json index 822e09f..ba841f4 100644 --- a/test cases/failing/90 add dict non string key/test.json +++ b/test cases/failing/89 add dict non string key/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/90 add dict non string key/meson.build:9:9: ERROR: Key must be a string" + "line": "test cases/failing/89 add dict non string key/meson.build:9:9: ERROR: Key must be a string" } ] } diff --git a/test cases/failing/91 add dict duplicate keys/meson.build b/test cases/failing/90 add dict duplicate keys/meson.build index 7a9b523..7a9b523 100644 --- a/test cases/failing/91 add dict duplicate keys/meson.build +++ b/test cases/failing/90 add dict duplicate keys/meson.build diff --git a/test cases/failing/91 add dict duplicate keys/test.json b/test cases/failing/90 add dict duplicate keys/test.json index b6941e7..a816913 100644 --- a/test cases/failing/91 add dict duplicate keys/test.json +++ b/test cases/failing/90 add dict duplicate keys/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/91 add dict duplicate keys/meson.build:9:27: ERROR: Duplicate dictionary key: myKey" + "line": "test cases/failing/90 add dict duplicate keys/meson.build:9:27: ERROR: Duplicate dictionary key: myKey" } ] } diff --git a/test cases/failing/92 no host get_external_property/meson.build b/test cases/failing/91 no host get_external_property/meson.build index c956754..c956754 100644 --- a/test cases/failing/92 no host get_external_property/meson.build +++ b/test cases/failing/91 no host get_external_property/meson.build diff --git a/test cases/failing/92 no host get_external_property/test.json b/test cases/failing/91 no host get_external_property/test.json index 0f7a803..64f4681 100644 --- a/test cases/failing/92 no host get_external_property/test.json +++ b/test cases/failing/91 no host get_external_property/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/92 no host get_external_property/meson.build:3:14: ERROR: Unknown property for host machine: nonexisting" + "line": "test cases/failing/91 no host get_external_property/meson.build:3:14: ERROR: Unknown property for host machine: nonexisting" } ] } diff --git a/test cases/failing/93 no native compiler/main.c b/test cases/failing/92 no native compiler/main.c index 9b6bdc2..9b6bdc2 100644 --- a/test cases/failing/93 no native compiler/main.c +++ b/test cases/failing/92 no native compiler/main.c diff --git a/test cases/failing/93 no native compiler/meson.build b/test cases/failing/92 no native compiler/meson.build index f0126ac..f0126ac 100644 --- a/test cases/failing/93 no native compiler/meson.build +++ b/test cases/failing/92 no native compiler/meson.build diff --git a/test cases/failing/93 no native compiler/test.json b/test cases/failing/92 no native compiler/test.json index adf99f1..38a8359 100644 --- a/test cases/failing/93 no native compiler/test.json +++ b/test cases/failing/92 no native compiler/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/93 no native compiler/meson.build:12:0: ERROR: No host machine compiler for \"main.c\"" + "line": "test cases/failing/92 no native compiler/meson.build:12:0: ERROR: No host machine compiler for \"main.c\"" } ] } diff --git a/test cases/failing/94 subdir parse error/meson.build b/test cases/failing/93 subdir parse error/meson.build index a744396..a744396 100644 --- a/test cases/failing/94 subdir parse error/meson.build +++ b/test cases/failing/93 subdir parse error/meson.build diff --git a/test cases/failing/94 subdir parse error/subdir/meson.build b/test cases/failing/93 subdir parse error/subdir/meson.build index 3ac5ef9..3ac5ef9 100644 --- a/test cases/failing/94 subdir parse error/subdir/meson.build +++ b/test cases/failing/93 subdir parse error/subdir/meson.build diff --git a/test cases/failing/94 subdir parse error/test.json b/test cases/failing/93 subdir parse error/test.json index 0bf82c1..fa565da 100644 --- a/test cases/failing/94 subdir parse error/test.json +++ b/test cases/failing/93 subdir parse error/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/94 subdir parse error/subdir/meson.build:1:0: ERROR: Plusassignment target must be an id." + "line": "test cases/failing/93 subdir parse error/subdir/meson.build:1:0: ERROR: Plusassignment target must be an id." } ] } diff --git a/test cases/failing/95 invalid option file/meson.build b/test cases/failing/94 invalid option file/meson.build index b0347c3..b0347c3 100644 --- a/test cases/failing/95 invalid option file/meson.build +++ b/test cases/failing/94 invalid option file/meson.build diff --git a/test cases/failing/95 invalid option file/meson_options.txt b/test cases/failing/94 invalid option file/meson_options.txt index eef843b..eef843b 100644 --- a/test cases/failing/95 invalid option file/meson_options.txt +++ b/test cases/failing/94 invalid option file/meson_options.txt diff --git a/test cases/failing/94 invalid option file/test.json b/test cases/failing/94 invalid option file/test.json new file mode 100644 index 0000000..6e04708 --- /dev/null +++ b/test cases/failing/94 invalid option file/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/94 invalid option file/meson_options.txt:1:0: ERROR: lexer: unrecognized token \"'\"" + } + ] +} diff --git a/test cases/failing/95 invalid option file/test.json b/test cases/failing/95 invalid option file/test.json deleted file mode 100644 index 073ac67..0000000 --- a/test cases/failing/95 invalid option file/test.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "stdout": [ - { - "line": "test cases/failing/95 invalid option file/meson_options.txt:1:0: ERROR: lexer" - } - ] -} diff --git a/test cases/failing/96 no lang/main.c b/test cases/failing/95 no lang/main.c index 9b6bdc2..9b6bdc2 100644 --- a/test cases/failing/96 no lang/main.c +++ b/test cases/failing/95 no lang/main.c diff --git a/test cases/failing/96 no lang/meson.build b/test cases/failing/95 no lang/meson.build index 85c5db8..85c5db8 100644 --- a/test cases/failing/96 no lang/meson.build +++ b/test cases/failing/95 no lang/meson.build diff --git a/test cases/failing/96 no lang/test.json b/test cases/failing/95 no lang/test.json index ab4ca5c..1e5ea2d 100644 --- a/test cases/failing/96 no lang/test.json +++ b/test cases/failing/95 no lang/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/96 no lang/meson.build:2:0: ERROR: No host machine compiler for 'main.c'" + "line": "test cases/failing/95 no lang/meson.build:2:0: ERROR: No host machine compiler for 'main.c'" } ] } diff --git a/test cases/failing/97 no glib-compile-resources/meson.build b/test cases/failing/96 no glib-compile-resources/meson.build index aae0569..aae0569 100644 --- a/test cases/failing/97 no glib-compile-resources/meson.build +++ b/test cases/failing/96 no glib-compile-resources/meson.build diff --git a/test cases/failing/97 no glib-compile-resources/test.json b/test cases/failing/96 no glib-compile-resources/test.json index f3d92dd..682b2c2 100644 --- a/test cases/failing/97 no glib-compile-resources/test.json +++ b/test cases/failing/96 no glib-compile-resources/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/97 no glib-compile-resources/meson.build:8:12: ERROR: Program 'glib-compile-resources' not found or not executable" + "line": "test cases/failing/96 no glib-compile-resources/meson.build:8:12: ERROR: Program 'glib-compile-resources' not found or not executable" } ] } diff --git a/test cases/failing/97 no glib-compile-resources/trivial.gresource.xml b/test cases/failing/96 no glib-compile-resources/trivial.gresource.xml index 1447b98..1447b98 100644 --- a/test cases/failing/97 no glib-compile-resources/trivial.gresource.xml +++ b/test cases/failing/96 no glib-compile-resources/trivial.gresource.xml diff --git a/test cases/failing/98 number in combo/meson.build b/test cases/failing/97 number in combo/meson.build index 1a647df..1a647df 100644 --- a/test cases/failing/98 number in combo/meson.build +++ b/test cases/failing/97 number in combo/meson.build diff --git a/test cases/failing/98 number in combo/nativefile.ini b/test cases/failing/97 number in combo/nativefile.ini index 55f10fc..55f10fc 100644 --- a/test cases/failing/98 number in combo/nativefile.ini +++ b/test cases/failing/97 number in combo/nativefile.ini diff --git a/test cases/failing/98 number in combo/test.json b/test cases/failing/97 number in combo/test.json index c1c9484..195f592 100644 --- a/test cases/failing/98 number in combo/test.json +++ b/test cases/failing/97 number in combo/test.json @@ -1,5 +1,5 @@ { "stdout": [ - { "line": "test cases/failing/98 number in combo/meson.build:1:0: ERROR: Value \"1\" (of type \"number\") for option \"optimization\" is not one of the choices. Possible choices are (as string): \"plain\", \"0\", \"g\", \"1\", \"2\", \"3\", \"s\"." } + { "line": "test cases/failing/97 number in combo/meson.build:1:0: ERROR: Value \"1\" (of type \"number\") for option \"optimization\" is not one of the choices. Possible choices are (as string): \"plain\", \"0\", \"g\", \"1\", \"2\", \"3\", \"s\"." } ] } diff --git a/test cases/failing/99 bool in combo/meson.build b/test cases/failing/98 bool in combo/meson.build index c5efd67..c5efd67 100644 --- a/test cases/failing/99 bool in combo/meson.build +++ b/test cases/failing/98 bool in combo/meson.build diff --git a/test cases/failing/99 bool in combo/meson_options.txt b/test cases/failing/98 bool in combo/meson_options.txt index 0c8f5de..0c8f5de 100644 --- a/test cases/failing/99 bool in combo/meson_options.txt +++ b/test cases/failing/98 bool in combo/meson_options.txt diff --git a/test cases/failing/99 bool in combo/nativefile.ini b/test cases/failing/98 bool in combo/nativefile.ini index b423957..b423957 100644 --- a/test cases/failing/99 bool in combo/nativefile.ini +++ b/test cases/failing/98 bool in combo/nativefile.ini diff --git a/test cases/failing/99 bool in combo/test.json b/test cases/failing/98 bool in combo/test.json index b3effc7..f3df2bd 100644 --- a/test cases/failing/99 bool in combo/test.json +++ b/test cases/failing/98 bool in combo/test.json @@ -1,5 +1,5 @@ { "stdout": [ - { "line": "test cases/failing/99 bool in combo/meson.build:1:0: ERROR: Value \"True\" (of type \"boolean\") for option \"opt\" is not one of the choices. Possible choices are (as string): \"true\", \"false\"." } + { "line": "test cases/failing/98 bool in combo/meson.build:1:0: ERROR: Value \"True\" (of type \"boolean\") for option \"opt\" is not one of the choices. Possible choices are (as string): \"true\", \"false\"." } ] } diff --git a/test cases/failing/100 compiler no lang/meson.build b/test cases/failing/99 compiler no lang/meson.build index 366bbdd..366bbdd 100644 --- a/test cases/failing/100 compiler no lang/meson.build +++ b/test cases/failing/99 compiler no lang/meson.build diff --git a/test cases/failing/99 compiler no lang/test.json b/test cases/failing/99 compiler no lang/test.json new file mode 100644 index 0000000..9b531dd --- /dev/null +++ b/test cases/failing/99 compiler no lang/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "test cases/failing/99 compiler no lang/meson.build:2:6: ERROR: Tried to access compiler for language \"c\", not specified for host machine." + } + ] +} diff --git a/test cases/format/1 default/indentation.meson b/test cases/format/1 default/indentation.meson index fe78847..14e1beb 100644 --- a/test cases/format/1 default/indentation.meson +++ b/test cases/format/1 default/indentation.meson @@ -92,3 +92,31 @@ if meson.project_version().version_compare('>1.2') # comment endif endif + +# Test for trailing comma: +m = [1, 2, 3] +n = [ + 1, + 2, + 3, +] + +# Overindent regressions (issue #14935) +if (host_cpu_family == 'x86' and host_system in [ + 'cygwin', + 'windows', + 'os2', + 'interix', +]) + message('hi') +endif + +# Underindent in files() (issue #14998) +sources = files( + # bar + # more bar + 'bar.c', + # foo + # more foo + 'foo.c', +) diff --git a/test cases/format/5 transform/default.expected.meson b/test cases/format/5 transform/default.expected.meson index 4a9851a..91b3bc2 100644 --- a/test cases/format/5 transform/default.expected.meson +++ b/test cases/format/5 transform/default.expected.meson @@ -8,7 +8,7 @@ f = files(options_ini, 'expected.meson', 'source.meson') # This array should fit on one line a1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21] -# This array is too long and should be splitted +# This array is too long and should be split a2 = [ 2, 3, @@ -81,4 +81,30 @@ arguments = [ '--x', ] +# issue #15032 +if true + if true + if ( + true + and true + and false + ) + message('Hello, world!') + endif + endif +endif + +# issue #15019 +if get_option('client') + sources += files( + 'pypaste/client/__init__.py', + 'pypaste/client/__main__.py', + 'pypaste/client/plugins/__init__.py', + 'pypaste/client/plugins/pgz/__init__.py', + 'pypaste/client/plugins/zen/__init__.py', + ) + + dependencies += ['requests'] +endif + # no final endline diff --git a/test cases/format/5 transform/muon.expected.meson b/test cases/format/5 transform/muon.expected.meson index 3b61270..e7d4610 100644 --- a/test cases/format/5 transform/muon.expected.meson +++ b/test cases/format/5 transform/muon.expected.meson @@ -8,7 +8,7 @@ f = files('expected.meson', 'source.meson', options_ini) # This array should fit on one line a1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21] -# This array is too long and should be splitted +# This array is too long and should be split a2 = [ 2, 3, @@ -81,4 +81,30 @@ arguments = [ '--x', ] +# issue #15032 +if true + if true + if ( + true + and true + and false + ) + message('Hello, world!') + endif + endif +endif + +# issue #15019 +if get_option('client') + sources += files( + 'pypaste/client/__init__.py', + 'pypaste/client/__main__.py', + 'pypaste/client/plugins/__init__.py', + 'pypaste/client/plugins/zen/__init__.py', + 'pypaste/client/plugins/pgz/__init__.py', + ) + + dependencies += ['requests'] +endif + # no final endline diff --git a/test cases/format/5 transform/options.expected.meson b/test cases/format/5 transform/options.expected.meson index 84917c1..756c226 100644 --- a/test cases/format/5 transform/options.expected.meson +++ b/test cases/format/5 transform/options.expected.meson @@ -8,25 +8,14 @@ f = files(options_ini, 'expected.meson', 'source.meson') # This array should fit on one line a1 = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 ] -# This array is too long and should be splitted +# This array is too long and should be split a2 = [ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22 ] # space array a3 = [ 1, 2, 3 ] # multi line expression -is_foo = ( - true - and false - and true - and false - and true - and false - and true - and false - and true - and false -) +is_foo = (true and false and true and false and true and false and true and false and true and false) # no single comma function fct = files( @@ -60,4 +49,30 @@ arguments = [ '--x', ] +# issue #15032 +if true + if true + if ( + true + and true + and false + ) + message('Hello, world!') + endif + endif +endif + +# issue #15019 +if get_option('client') + sources += files( + 'pypaste/client/__init__.py', + 'pypaste/client/__main__.py', + 'pypaste/client/plugins/__init__.py', + 'pypaste/client/plugins/pgz/__init__.py', + 'pypaste/client/plugins/zen/__init__.py', + ) + + dependencies += [ 'requests' ] +endif + # no final endline
\ No newline at end of file diff --git a/test cases/format/5 transform/source.meson b/test cases/format/5 transform/source.meson index a3b326b..a400412 100644 --- a/test cases/format/5 transform/source.meson +++ b/test cases/format/5 transform/source.meson @@ -12,25 +12,14 @@ f = files( # This array should fit on one line a1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21] -# This array is too long and should be splitted +# This array is too long and should be split a2 = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22] # space array a3 = [ 1, 2, 3 ] # multi line expression -is_foo = ( - true - and false - and true - and false - and true - and false - and true - and false - and true - and false -) +is_foo = (true and false and true and false and true and false and true and false and true and false) # no single comma function fct = files( @@ -49,4 +38,29 @@ f'This is not a fstring' arguments = ['a', '--opt_a', 'opt_a_value', 'b', 'c', '--opt_d', '--opt_e', 'opt_e_value', '--opt_f', '--opt_g', 'opt_g_value', 'other_value', 'again', '--x'] +# issue #15032 +if true + if true + if (true and + true and + false + ) + message('Hello, world!') + endif + endif +endif + +# issue #15019 +if get_option('client') + sources += files( + 'pypaste/client/__init__.py', + 'pypaste/client/__main__.py', + 'pypaste/client/plugins/__init__.py', + 'pypaste/client/plugins/zen/__init__.py', + 'pypaste/client/plugins/pgz/__init__.py' + ) + + dependencies += ['requests'] +endif + # no final endline
\ No newline at end of file diff --git a/test cases/fortran/23 preprocess/main.f90 b/test cases/fortran/23 preprocess/main.f90 index 7cbc11c..8251741 100644 --- a/test cases/fortran/23 preprocess/main.f90 +++ b/test cases/fortran/23 preprocess/main.f90 @@ -1,4 +1,14 @@ #define MYDEF program MYDEF foo - write (*,*) 'Hello, world!' + character(20) :: str +#ifdef CORRECT + str = 'Hello, ' // 'world!' +#else + str = 'Preprocessing error!' +#endif + if (str /= 'Hello, world!') then + print *, 'Preprocessing failed.' + error stop 1 + end if + stop 0 end MYDEF foo diff --git a/test cases/fortran/23 preprocess/meson.build b/test cases/fortran/23 preprocess/meson.build index b776940..88077d3 100644 --- a/test cases/fortran/23 preprocess/meson.build +++ b/test cases/fortran/23 preprocess/meson.build @@ -1,7 +1,12 @@ -project('preprocess', 'fortran') +project('preprocess', 'fortran', meson_version: '>1.3.2') fc = meson.get_compiler('fortran') -pp_files = fc.preprocess('main.f90', output: '@PLAINNAME@') +pp_files = fc.preprocess( + 'main.f90', + compile_args: ['-DCORRECT=true'], + output: '@PLAINNAME@') -library('foo', pp_files) +t = executable('foo', pp_files) + +test('check_result', t) diff --git a/test cases/frameworks/15 llvm/test.json b/test cases/frameworks/15 llvm/test.json index fa883b1..b9cdc20 100644 --- a/test cases/frameworks/15 llvm/test.json +++ b/test cases/frameworks/15 llvm/test.json @@ -2,9 +2,9 @@ "matrix": { "options": { "method": [ - { "val": "config-tool", "expect_skip_on_jobname": ["msys2-gcc"] }, - { "val": "cmake", "expect_skip_on_jobname": ["msys2-gcc"] }, - { "val": "combination", "expect_skip_on_jobname": ["msys2-gcc"] } + { "val": "config-tool" }, + { "val": "cmake" }, + { "val": "combination" } ], "link-static": [ { "val": true, "expect_skip_on_jobname": ["arch", "opensuse", "linux-gentoo-gcc"] }, diff --git a/test cases/frameworks/17 mpi/test.json b/test cases/frameworks/17 mpi/test.json index 3a46657..cbd1686 100644 --- a/test cases/frameworks/17 mpi/test.json +++ b/test cases/frameworks/17 mpi/test.json @@ -2,10 +2,8 @@ "matrix": { "options": { "method": [ - { "val": "auto", - "expect_skip_on_jobname": ["ubuntu"] }, - { "val": "pkg-config", - "expect_skip_on_jobname": ["ubuntu"] }, + { "val": "auto" }, + { "val": "pkg-config" }, { "val": "config-tool", "expect_skip_on_jobname": ["fedora"] }, { diff --git a/test cases/frameworks/18 vulkan/meson.build b/test cases/frameworks/18 vulkan/meson.build index 5cfe89f..ab9f291 100644 --- a/test cases/frameworks/18 vulkan/meson.build +++ b/test cases/frameworks/18 vulkan/meson.build @@ -1,6 +1,8 @@ project('vulkan test', 'c') -vulkan_dep = dependency('vulkan', required : false) +method = get_option('method') + +vulkan_dep = dependency('vulkan', required : false, method : method) if not vulkan_dep.found() error('MESON_SKIP_TEST: vulkan not found.') endif diff --git a/test cases/frameworks/18 vulkan/meson.options b/test cases/frameworks/18 vulkan/meson.options new file mode 100644 index 0000000..962fbe2 --- /dev/null +++ b/test cases/frameworks/18 vulkan/meson.options @@ -0,0 +1,6 @@ +option( + 'method', + type : 'combo', + choices : ['auto', 'pkg-config', 'system'], + value : 'auto', +) diff --git a/test cases/frameworks/18 vulkan/test.json b/test cases/frameworks/18 vulkan/test.json index 66afb97..820f075 100644 --- a/test cases/frameworks/18 vulkan/test.json +++ b/test cases/frameworks/18 vulkan/test.json @@ -1,3 +1,14 @@ { + "env": { + "VULKAN_SDK": "/usr" + }, + "matrix": { + "options": { + "method": [ + { "val": "pkg-config" }, + { "val": "system" } + ] + } + }, "expect_skip_on_jobname": ["azure", "cygwin", "macos", "msys2"] } diff --git a/test cases/frameworks/25 hdf5/meson.build b/test cases/frameworks/25 hdf5/meson.build index 38e0012..095c63f 100644 --- a/test cases/frameworks/25 hdf5/meson.build +++ b/test cases/frameworks/25 hdf5/meson.build @@ -28,6 +28,7 @@ test_fortran = add_languages('fortran', required: false) if test_fortran cpp = meson.get_compiler('cpp') fc = meson.get_compiler('fortran') + fs = import('fs') if host_machine.system() == 'darwin' and cpp.get_id() == 'clang' and fc.get_id() == 'gcc' # Search paths don't work correctly here and -lgfortran doesn't work @@ -35,6 +36,10 @@ if test_fortran elif host_machine.system() == 'windows' and cpp.get_id() != 'gcc' and fc.get_id() == 'gcc' # mixing gfortran with non-gcc doesn't work on windows test_fortran = false + elif fs.is_dir('/ci') and '-I' not in run_command('h5fc', '-show').stdout() + # h5fc does not include needed -I flags when HDF5 is built using CMake + # https://github.com/HDFGroup/hdf5/issues/5660 + test_fortran = false endif # --- Fortran tests diff --git a/test cases/frameworks/25 hdf5/test.json b/test cases/frameworks/25 hdf5/test.json index 2448f57..5243d54 100644 --- a/test cases/frameworks/25 hdf5/test.json +++ b/test cases/frameworks/25 hdf5/test.json @@ -2,7 +2,7 @@ "matrix": { "options": { "method": [ - { "val": "pkg-config", "expect_skip_on_jobname": ["linux-gentoo-gcc"] }, + { "val": "pkg-config" }, { "val": "config-tool" } ] } diff --git a/test cases/frameworks/38 gettext extractor/meson.build b/test cases/frameworks/38 gettext extractor/meson.build index 9a54df5..a31c87d 100644 --- a/test cases/frameworks/38 gettext extractor/meson.build +++ b/test cases/frameworks/38 gettext extractor/meson.build @@ -9,6 +9,10 @@ if not find_program('xgettext', required: false).found() error('MESON_SKIP_TEST xgettext command not found') endif +if host_machine.system() == 'darwin' + error('MESON_SKIP_TEST test is unstable on macOS for unknown reasons') +endif + i18n = import('i18n') xgettext_args = ['-ktr', '--add-comments=TRANSLATOR:', '--from-code=UTF-8'] diff --git a/test cases/unit/126 test slice/test.py b/test cases/frameworks/38 gettext extractor/src/lib3/foo.c index e69de29..e69de29 100644 --- a/test cases/unit/126 test slice/test.py +++ b/test cases/frameworks/38 gettext extractor/src/lib3/foo.c diff --git a/test cases/frameworks/38 gettext extractor/src/lib3/meson.build b/test cases/frameworks/38 gettext extractor/src/lib3/meson.build new file mode 100644 index 0000000..34afeeb --- /dev/null +++ b/test cases/frameworks/38 gettext extractor/src/lib3/meson.build @@ -0,0 +1,4 @@ +fs = import('fs') + +foo = fs.copyfile('foo.c') +i18n.xgettext('test', foo[0]) diff --git a/test cases/frameworks/38 gettext extractor/src/meson.build b/test cases/frameworks/38 gettext extractor/src/meson.build index 27fc813..b26646c 100644 --- a/test cases/frameworks/38 gettext extractor/src/meson.build +++ b/test cases/frameworks/38 gettext extractor/src/meson.build @@ -1,5 +1,6 @@ subdir('lib1') subdir('lib2') +subdir('lib3') main = executable('say', 'main.c', link_with: [lib2], include_directories: lib2_includes) diff --git a/test cases/frameworks/38 gettext extractor/test.json b/test cases/frameworks/38 gettext extractor/test.json index c5952ff..032698e 100644 --- a/test cases/frameworks/38 gettext extractor/test.json +++ b/test cases/frameworks/38 gettext extractor/test.json @@ -2,5 +2,5 @@ "installed": [ { "type": "file", "file": "usr/intl/main.pot" } ], - "expect_skip_on_jobname": ["azure", "cygwin"] + "expect_skip_on_jobname": ["azure", "cygwin", "macos"] } diff --git a/test cases/frameworks/7 gnome/gdbus/meson.build b/test cases/frameworks/7 gnome/gdbus/meson.build index fdb3896..22896e0 100644 --- a/test cases/frameworks/7 gnome/gdbus/meson.build +++ b/test cases/frameworks/7 gnome/gdbus/meson.build @@ -52,6 +52,23 @@ assert(gdbus_src.length() == 3, 'expected 3 targets') assert(gdbus_src[0].full_path().endswith('.c'), 'expected 1 c source file') assert(gdbus_src[1].full_path().endswith('.h'), 'expected 1 c header file') +if not pretend_glib_old and glib.version().version_compare('>=2.75.2') + gdbus_src_docs = gnome.gdbus_codegen('generated-gdbus-docs', + sources : files('data/com.example.Sample.xml'), + interface_prefix : 'com.example.', + namespace : 'Sample', + docbook : 'generated-gdbus-docs-doc', + rst : 'generated-gdbus-docs-rst', + markdown : 'generated-gdbus-docs-md', + ) + assert(gdbus_src_docs.length() == 5, 'expected 5 targets') + assert(gdbus_src_docs[0].full_path().endswith('.c'), 'expected 1 c source file') + assert(gdbus_src_docs[1].full_path().endswith('.h'), 'expected 1 c header file') + assert('generated-gdbus-docs-doc' in gdbus_src_docs[2].full_path(), 'expected 1 docbook file') + assert('generated-gdbus-docs-rst' in gdbus_src_docs[3].full_path(), 'expected 1 reStructuredText file') + assert('generated-gdbus-docs-md' in gdbus_src_docs[4].full_path(), 'expected 1 markdown file') +endif + if not pretend_glib_old and glib.version().version_compare('>=2.51.3') includes = [] else diff --git a/test cases/frameworks/7 gnome/meson.build b/test cases/frameworks/7 gnome/meson.build index f75ca93..37934b7 100644 --- a/test cases/frameworks/7 gnome/meson.build +++ b/test cases/frameworks/7 gnome/meson.build @@ -1,4 +1,4 @@ -project('gobject-introspection', 'c', meson_version: '>= 1.2.0') +project('gobject-introspection', 'c', meson_version: '>= 1.9.0') copyfile = find_program('copyfile.py') copyfile_gen = generator(copyfile, diff --git a/test cases/frameworks/8 flex/meson.build b/test cases/frameworks/8 flex/meson.build index 55b96dd..0b11070 100644 --- a/test cases/frameworks/8 flex/meson.build +++ b/test cases/frameworks/8 flex/meson.build @@ -1,11 +1,16 @@ -project('flex and bison', 'c') +# SPDX-License-Identifier: Apache-2.0 +# Copyright © 2024-2025 Intel Corporation + +project('flex and bison', 'c', meson_version : '>= 1.10.0') # The point of this test is that one generator # may output headers that are necessary to build # the sources of a different generator. -flex = find_program('flex', required: false) -bison = find_program('bison', required: false) +# TODO: handle win_flex/win_bison + +flex = find_program('reflex', 'flex', 'lex', required: false) +bison = find_program('bison', 'byacc', 'yacc', required: false) if not flex.found() error('MESON_SKIP_TEST flex not found.') @@ -15,22 +20,20 @@ if not bison.found() error('MESON_SKIP_TEST bison not found.') endif -lgen = generator(flex, -output : '@PLAINNAME@.yy.c', -arguments : ['-o', '@OUTPUT@', '@INPUT@']) - -lfiles = lgen.process('lexer.l') - -pgen = generator(bison, -output : ['@BASENAME@.tab.c', '@BASENAME@.tab.h'], -arguments : ['@INPUT@', '--defines=@OUTPUT1@', '--output=@OUTPUT0@']) +codegen = import('unstable-codegen') +lex = codegen.lex(implementations : ['flex', 'reflex', 'lex']) +message('lex implementation:', lex.implementation()) +lfiles = lex.generate('lexer.l') -pfiles = pgen.process('parser.y') +yacc = codegen.yacc(implementations : ['byacc', 'bison', 'yacc']) +message('yacc implementation:', yacc.implementation()) +pfiles = yacc.generate('parser.y', header : '@BASENAME@.tab.h') -e = executable('pgen', 'prog.c', - lfiles, - pfiles, - override_options: 'unity=off') +e = executable( + 'pgen', + 'prog.c', lfiles, pfiles, + override_options : ['unity=off'], +) test('parsertest', e, args: [meson.current_source_dir() / 'testfile']) diff --git a/test cases/native/9 override with exe/subprojects/sub/meson.build b/test cases/native/9 override with exe/subprojects/sub/meson.build index f0343b2..74deaea 100644 --- a/test cases/native/9 override with exe/subprojects/sub/meson.build +++ b/test cases/native/9 override with exe/subprojects/sub/meson.build @@ -1,3 +1,11 @@ -project('sub', 'c', version : '1.0') +project('sub', 'c', version : '1.0', meson_version: '>= 1.9.0') foobar = executable('foobar', 'foobar.c', native : true) meson.override_find_program('foobar', foobar) + +found_foobar = find_program('foobar') +if found_foobar.version() != meson.project_version() + error('Overriden Executable had incorrect version: got @0@, expected @1@'.format(found_foobar.version(), meson.project_version())) +endif + +test('foobar executable', foobar, args : [ meson.current_build_dir() / 'test-output.c' ]) +test('overriden foobar executable', found_foobar, args : [ meson.current_build_dir() / 'test-output.c' ]) diff --git a/test cases/python/11 script path/gen b/test cases/python/11 script path/gen new file mode 100755 index 0000000..3d31694 --- /dev/null +++ b/test cases/python/11 script path/gen @@ -0,0 +1,7 @@ +#!/usr/bin/env python3 + + +if __name__ == '__main__': + with open('x.c', 'w', encoding='utf-8') as f: + f.write('int main() { return 0; }\n') + exit(0) diff --git a/test cases/python/11 script path/meson.build b/test cases/python/11 script path/meson.build new file mode 100644 index 0000000..c913ca4 --- /dev/null +++ b/test cases/python/11 script path/meson.build @@ -0,0 +1,19 @@ +project('11 script path', 'c') + +if meson.backend() != 'ninja' + error('MESON_SKIP_TEST: Ninja backend required') +endif + +run = find_program('run.py') + +gen = find_program('gen') + +src = custom_target( + 'src', + command: [run, gen], + output: 'x.c', +) + +exe = executable('e', + src, +) diff --git a/test cases/python/11 script path/run.py b/test cases/python/11 script path/run.py new file mode 100755 index 0000000..a8e6011 --- /dev/null +++ b/test cases/python/11 script path/run.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python3 + +import sys +import subprocess + +if __name__ == '__main__': + subprocess.check_call(sys.argv[1:]) diff --git a/test cases/python/9 extmodule limited api/meson.build b/test cases/python/9 extmodule limited api/meson.build index bdf1b7b..9585124 100644 --- a/test cases/python/9 extmodule limited api/meson.build +++ b/test cases/python/9 extmodule limited api/meson.build @@ -4,6 +4,10 @@ project('Python limited api', 'c', py_mod = import('python') py = py_mod.find_installation() +if py.get_variable('Py_GIL_DISABLED', 0) == 1 and py.language_version().version_compare('<3.15') + error('MESON_SKIP_TEST: Freethreading Python does not support limited API') +endif + ext_mod_limited = py.extension_module('limited', 'limited.c', limited_api: '3.7', diff --git a/test cases/rewrite/1 basic/addSrc.json b/test cases/rewrite/1 basic/addSrc.json index b8bc439..52603f6 100644 --- a/test cases/rewrite/1 basic/addSrc.json +++ b/test cases/rewrite/1 basic/addSrc.json @@ -43,6 +43,24 @@ }, { "type": "target", + "target": "trivialprog10", + "operation": "src_add", + "sources": ["fileA.cpp", "fileB.cpp", "a1.cpp"] + }, + { + "type": "target", + "target": "trivialprog11", + "operation": "src_add", + "sources": ["fileA.cpp", "a1.cpp"] + }, + { + "type": "target", + "target": "trivialprog12", + "operation": "src_add", + "sources": ["fileA.cpp", "fileB.cpp", "a1.cpp"] + }, + { + "type": "target", "target": "trivialprog0", "operation": "info" }, @@ -90,5 +108,25 @@ "type": "target", "target": "trivialprog9", "operation": "info" + }, + { + "type": "target", + "target": "trivialprog10", + "operation": "info" + }, + { + "type": "target", + "target": "trivialprog11", + "operation": "info" + }, + { + "type": "target", + "target": "trivialprog12", + "operation": "info" + }, + { + "type": "target", + "target": "rightName", + "operation": "info" } ] diff --git a/test cases/rewrite/1 basic/addTgt.json b/test cases/rewrite/1 basic/addTgt.json index 2f4e7e2..02d600a 100644 --- a/test cases/rewrite/1 basic/addTgt.json +++ b/test cases/rewrite/1 basic/addTgt.json @@ -1,7 +1,7 @@ [ { "type": "target", - "target": "trivialprog10", + "target": "trivialprog13", "operation": "target_add", "sources": ["new1.cpp", "new2.cpp"], "target_type": "shared_library" diff --git a/test cases/rewrite/1 basic/expected_dag.txt b/test cases/rewrite/1 basic/expected_dag.txt new file mode 100644 index 0000000..c5025b4 --- /dev/null +++ b/test cases/rewrite/1 basic/expected_dag.txt @@ -0,0 +1,129 @@ +Data flowing to FunctionNode(1:0): + StringNode(1:8) + StringNode(1:23) +Data flowing to ArrayNode(3:7): + StringNode(3:8) + StringNode(3:20) +Data flowing to FunctionNode(4:7): + ArrayNode(4:13) +Data flowing to ArrayNode(4:13): + StringNode(4:14) + StringNode(4:27) +Data flowing to IdNode(5:7): + ArrayNode(3:7) +Data flowing to ArrayNode(6:7): + IdNode(6:8) +Data flowing to IdNode(6:8): + IdNode(5:7) +Data flowing to ArithmeticNode(7:7): + ArrayNode(7:7) + ArrayNode(8:8) +Data flowing to ArrayNode(7:7): + StringNode(7:8) + StringNode(7:20) +Data flowing to ArrayNode(8:8): + StringNode(8:9) +Data flowing to ArrayNode(9:7): + StringNode(9:8) + StringNode(9:20) +Data flowing to FunctionNode(10:7): + IdNode(10:13) +Data flowing to IdNode(10:13): + ArrayNode(9:7) +Data flowing to ArrayNode(11:7): + StringNode(11:8) + StringNode(11:20) +Data flowing to IdNode(12:7): + ArrayNode(11:7) +Data flowing to ArrayNode(13:7): + StringNode(13:8) + StringNode(13:21) +Data flowing to FunctionNode(15:13): + StringNode(14:7) + StringNode(15:26) +Data flowing to FunctionNode(20:7): + StringNode(20:18) + ArithmeticNode(20:34) +Data flowing to ArithmeticNode(20:34): + IdNode(20:34) + IdNode(20:41) +Data flowing to IdNode(20:34): + ArrayNode(3:7) +Data flowing to IdNode(20:41): + FunctionNode(4:7) +Data flowing to FunctionNode(21:7): + StringNode(21:18) + IdNode(21:34) +Data flowing to IdNode(21:34): + ArrayNode(3:7) +Data flowing to FunctionNode(22:7): + StringNode(22:18) + ArrayNode(22:34) +Data flowing to ArrayNode(22:34): + IdNode(22:35) +Data flowing to IdNode(22:35): + FunctionNode(4:7) +Data flowing to FunctionNode(23:7): + StringNode(23:18) + ArrayNode(23:34) +Data flowing to ArrayNode(23:34): + StringNode(23:35) + StringNode(23:47) +Data flowing to FunctionNode(24:7): + StringNode(24:18) + ArrayNode(24:34) +Data flowing to ArrayNode(24:34): + StringNode(24:35) + ArrayNode(24:47) +Data flowing to ArrayNode(24:47): + StringNode(24:48) +Data flowing to FunctionNode(25:7): + StringNode(25:18) + ArrayNode(25:34) +Data flowing to ArrayNode(25:34): + IdNode(25:35) + StringNode(25:41) +Data flowing to IdNode(25:35): + FunctionNode(4:7) +Data flowing to FunctionNode(26:7): + StringNode(26:18) + StringNode(26:34) + StringNode(26:46) +Data flowing to FunctionNode(27:7): + StringNode(27:18) + StringNode(27:34) + FunctionNode(27:47) + StringNode(27:69) +Data flowing to FunctionNode(27:47): + ArrayNode(3:7) + StringNode(27:60) +Data flowing to FunctionNode(28:7): + StringNode(28:18) + IdNode(28:34) +Data flowing to IdNode(28:34): + IdNode(5:7) +Data flowing to FunctionNode(29:0): + StringNode(29:11) + IdNode(29:27) +Data flowing to IdNode(29:27): + ArrayNode(6:7) +Data flowing to FunctionNode(30:0): + StringNode(30:11) + IdNode(30:28) +Data flowing to IdNode(30:28): + ArithmeticNode(7:7) +Data flowing to FunctionNode(31:0): + StringNode(31:11) + IdNode(31:28) +Data flowing to IdNode(31:28): + FunctionNode(10:7) +Data flowing to FunctionNode(32:0): + StringNode(32:11) + IdNode(32:28) +Data flowing to IdNode(32:28): + IdNode(12:7) +Data flowing to FunctionNode(33:0): + IdNode(33:11) + StringNode(33:23) +Data flowing to IdNode(33:11): + FunctionNode(15:13) diff --git a/test cases/rewrite/1 basic/info.json b/test cases/rewrite/1 basic/info.json index 0f1a3bd..9977f5a 100644 --- a/test cases/rewrite/1 basic/info.json +++ b/test cases/rewrite/1 basic/info.json @@ -53,5 +53,25 @@ "type": "target", "target": "trivialprog10", "operation": "info" + }, + { + "type": "target", + "target": "trivialprog11", + "operation": "info" + }, + { + "type": "target", + "target": "trivialprog12", + "operation": "info" + }, + { + "type": "target", + "target": "trivialprog13", + "operation": "info" + }, + { + "type": "target", + "target": "rightName", + "operation": "info" } ] diff --git a/test cases/rewrite/1 basic/meson.build b/test cases/rewrite/1 basic/meson.build index 0f87c45..5fe9527 100644 --- a/test cases/rewrite/1 basic/meson.build +++ b/test cases/rewrite/1 basic/meson.build @@ -4,6 +4,16 @@ src1 = ['main.cpp', 'fileA.cpp'] src2 = files(['fileB.cpp', 'fileC.cpp']) src3 = src1 src4 = [src3] +src5 = ['main.cpp', 'fileA.cpp'] +src5 += ['fileB.cpp'] +src6 = ['main.cpp', 'fileA.cpp'] +src6 = files(src6) +src7 = ['main.cpp', 'fileA.cpp'] +src8 = src7 +src7 = ['fileB.cpp', 'fileC.cpp'] +name = 'rightName' +trickyName = get_variable('name') +name = 'wrongName' # Magic comment @@ -14,6 +24,10 @@ exe3 = executable('trivialprog3', ['main.cpp', 'fileA.cpp']) exe4 = executable('trivialprog4', ['main.cpp', ['fileA.cpp']]) exe5 = executable('trivialprog5', [src2, 'main.cpp']) exe6 = executable('trivialprog6', 'main.cpp', 'fileA.cpp') -exe7 = executable('trivialprog7', 'fileB.cpp', src1, 'fileC.cpp') +exe7 = executable('trivialprog7', 'fileB.cpp', get_variable('src1'), 'fileC.cpp') exe8 = executable('trivialprog8', src3) executable('trivialprog9', src4) +executable('trivialprog10', src5) +executable('trivialprog11', src6) +executable('trivialprog12', src8) +executable(trickyName, 'main.cpp') diff --git a/test cases/rewrite/1 basic/rmSrc.json b/test cases/rewrite/1 basic/rmSrc.json index 2e7447c..de56bbe 100644 --- a/test cases/rewrite/1 basic/rmSrc.json +++ b/test cases/rewrite/1 basic/rmSrc.json @@ -1,12 +1,6 @@ [ { "type": "target", - "target": "trivialprog1", - "operation": "src_rm", - "sources": ["fileA.cpp"] - }, - { - "type": "target", "target": "trivialprog3", "operation": "src_rm", "sources": ["fileA.cpp"] @@ -21,7 +15,7 @@ "type": "target", "target": "trivialprog5", "operation": "src_rm", - "sources": ["fileB.cpp"] + "sources": ["main.cpp"] }, { "type": "target", @@ -37,6 +31,18 @@ }, { "type": "target", + "target": "trivialprog10", + "operation": "src_rm", + "sources": ["fileA.cpp", "fileB.cpp"] + }, + { + "type": "target", + "target": "trivialprog11", + "operation": "src_rm", + "sources": ["fileA.cpp"] + }, + { + "type": "target", "target": "trivialprog0", "operation": "info" }, @@ -84,5 +90,25 @@ "type": "target", "target": "trivialprog9", "operation": "info" + }, + { + "type": "target", + "target": "trivialprog10", + "operation": "info" + }, + { + "type": "target", + "target": "trivialprog11", + "operation": "info" + }, + { + "type": "target", + "target": "trivialprog12", + "operation": "info" + }, + { + "type": "target", + "target": "rightName", + "operation": "info" } ] diff --git a/test cases/rewrite/1 basic/rmTgt.json b/test cases/rewrite/1 basic/rmTgt.json index dbaf025..bc6dc30 100644 --- a/test cases/rewrite/1 basic/rmTgt.json +++ b/test cases/rewrite/1 basic/rmTgt.json @@ -13,5 +13,10 @@ "type": "target", "target": "trivialprog9", "operation": "target_rm" + }, + { + "type": "target", + "target": "rightName", + "operation": "target_rm" } ] diff --git a/test cases/rewrite/10 duplicate globals/info.json b/test cases/rewrite/10 duplicate globals/info.json new file mode 100644 index 0000000..3d15f04 --- /dev/null +++ b/test cases/rewrite/10 duplicate globals/info.json @@ -0,0 +1,8 @@ +[ + { + "type": "kwargs", + "function": "project", + "id": "/", + "operation": "info" + } +] diff --git a/test cases/rewrite/10 duplicate globals/meson.build b/test cases/rewrite/10 duplicate globals/meson.build new file mode 100644 index 0000000..a9ebf96 --- /dev/null +++ b/test cases/rewrite/10 duplicate globals/meson.build @@ -0,0 +1,5 @@ +project('a', 'c', license: 'MIT') +set_variable( + 'z', + '-Wl,--version-script,@0@/src/lib.sym'.format(meson.current_source_dir()) +) diff --git a/test cases/rewrite/3 kwargs/add.json b/test cases/rewrite/3 kwargs/add.json index 5b3ce0b..94354d1 100644 --- a/test cases/rewrite/3 kwargs/add.json +++ b/test cases/rewrite/3 kwargs/add.json @@ -5,7 +5,8 @@ "id": "/", "operation": "set", "kwargs": { - "license": "GPL" + "license": "GPL", + "license_files": "GPL.txt" } }, { diff --git a/test cases/rewrite/3 kwargs/set.json b/test cases/rewrite/3 kwargs/set.json index 6ca2ee4..923ce40 100644 --- a/test cases/rewrite/3 kwargs/set.json +++ b/test cases/rewrite/3 kwargs/set.json @@ -7,7 +7,8 @@ "kwargs": { "version": "0.0.2", "meson_version": "0.50.0", - "license": ["GPL", "MIT"] + "license": ["GPL", "MIT"], + "license_files": ["GPL.txt", "MIT.txt"] } }, { diff --git a/test cases/rewrite/8 kwargs dict/info.json b/test cases/rewrite/8 kwargs dict/info.json new file mode 100644 index 0000000..11a9e1a --- /dev/null +++ b/test cases/rewrite/8 kwargs dict/info.json @@ -0,0 +1,14 @@ +[ + { + "type": "kwargs", + "function": "project", + "id": "/", + "operation": "info" + }, + { + "type": "kwargs", + "function": "dependency", + "id": "dep1", + "operation": "info" + } +] diff --git a/test cases/rewrite/8 kwargs dict/meson.build b/test cases/rewrite/8 kwargs dict/meson.build new file mode 100644 index 0000000..1c5f25b --- /dev/null +++ b/test cases/rewrite/8 kwargs dict/meson.build @@ -0,0 +1,10 @@ +project( + 'rewritetest', 'cpp', + version: '0.0.1', + default_options: { + 'c_std': 'c11', + 'cpp_std': 'c++17', + }, +) + +dep1 = dependency('zlib', required: false, default_options: {'foo': 'bar'}) diff --git a/test cases/rewrite/9 tricky dataflow/addSrc.json b/test cases/rewrite/9 tricky dataflow/addSrc.json new file mode 100644 index 0000000..17e4292 --- /dev/null +++ b/test cases/rewrite/9 tricky dataflow/addSrc.json @@ -0,0 +1,77 @@ +[ + { + "type": "target", + "target": "tgt1", + "operation": "src_add", + "sources": [ + "new.c" + ] + }, + { + "type": "target", + "target": "tgt2", + "operation": "src_add", + "sources": [ + "new.c" + ] + }, + { + "type": "target", + "target": "tgt3", + "operation": "src_add", + "sources": [ + "new.c" + ] + }, + { + "type": "target", + "target": "tgt5", + "operation": "src_add", + "sources": [ + "new.c" + ] + }, + { + "type": "target", + "target": "tgt6", + "operation": "src_add", + "sources": [ + "new.c" + ] + }, + { + "type": "target", + "target": "tgt1", + "operation": "info" + }, + { + "type": "target", + "target": "tgt2", + "operation": "info" + }, + { + "type": "target", + "target": "tgt3", + "operation": "info" + }, + { + "type": "target", + "target": "tgt4", + "operation": "info" + }, + { + "type": "target", + "target": "tgt5", + "operation": "info" + }, + { + "type": "target", + "target": "tgt6", + "operation": "info" + }, + { + "type": "target", + "target": "tgt7", + "operation": "info" + } +] diff --git a/test cases/rewrite/9 tricky dataflow/info.json b/test cases/rewrite/9 tricky dataflow/info.json new file mode 100644 index 0000000..8d4ac55 --- /dev/null +++ b/test cases/rewrite/9 tricky dataflow/info.json @@ -0,0 +1,37 @@ +[ + { + "type": "target", + "target": "tgt1", + "operation": "info" + }, + { + "type": "target", + "target": "tgt2", + "operation": "info" + }, + { + "type": "target", + "target": "tgt3", + "operation": "info" + }, + { + "type": "target", + "target": "tgt4", + "operation": "info" + }, + { + "type": "target", + "target": "tgt5", + "operation": "info" + }, + { + "type": "target", + "target": "tgt6", + "operation": "info" + }, + { + "type": "target", + "target": "tgt7", + "operation": "info" + } +] diff --git a/test cases/rewrite/9 tricky dataflow/meson.build b/test cases/rewrite/9 tricky dataflow/meson.build new file mode 100644 index 0000000..0431739 --- /dev/null +++ b/test cases/rewrite/9 tricky dataflow/meson.build @@ -0,0 +1,41 @@ +project('rewrite tricky dataflow', 'c') + +# Adding a file to `begin` will add this file to the sources of `tgt1`, but +# not to any other target. But a buggy rewriter might think that adding a file +# to `begin` will also add this file to `end` and will refuse to add a file to +# `begin`. +begin = ['foo.c'] +tgt1 = library('tgt1', begin) +distraction = executable('distraction', link_with: tgt1) + + +tgt2_srcs = ['foo.c'] +if host_machine.system() == 'windows' # Some condition that cannot be known statically + tgt2_srcs += ['bar.c'] +endif +executable('tgt2', tgt2_srcs) + + +tgt34_srcs = ['foo.c'] +executable('tgt3', tgt34_srcs) +if host_machine.system() == 'windows' + tgt34_srcs += ['bar.c'] +endif +executable('tgt4', tgt34_srcs) + + +dont_add_here_5 = ['foo.c'] +ct = custom_target('ct', output: 'out.c', input: dont_add_here_5, command: ['placeholder', '@INPUT@', '@OUTPUT@']) +executable('tgt5', ct) + + +dont_add_here_6 = ['foo.c'] +gen = generator(find_program('cp'), output: '@BASENAME@_copy.c', arguments: ['@INPUT@', '@OUTPUT@']) +generated = gen.process(dont_add_here_6) +executable('tgt6', generated) + +if false + # Should produce a warning, but should not crash + var = not_defined_1 + executable('tgt7', not_defined_2, var) +endif diff --git a/test cases/rust/1 basic/meson.build b/test cases/rust/1 basic/meson.build index f422beb..c8cef92 100644 --- a/test cases/rust/1 basic/meson.build +++ b/test cases/rust/1 basic/meson.build @@ -1,11 +1,16 @@ project('rustprog', 'rust', default_options : ['b_ndebug=true']) e = executable('rust-program', 'prog.rs', - rust_args : ['-C', 'lto'], # Just a test install : true ) test('rusttest', e) +e = executable('rust-dynamic', 'prog.rs', + override_options: {'rust_dynamic_std': true}, + install : true +) +test('rusttest-dynamic', e) + subdir('subdir') # this should fail due to debug_assert diff --git a/test cases/rust/1 basic/test.json b/test cases/rust/1 basic/test.json index 95e6ced..3cbdefa 100644 --- a/test cases/rust/1 basic/test.json +++ b/test cases/rust/1 basic/test.json @@ -3,6 +3,8 @@ {"type": "exe", "file": "usr/bin/rust-program"}, {"type": "pdb", "file": "usr/bin/rust-program"}, {"type": "exe", "file": "usr/bin/rust-program2"}, - {"type": "pdb", "file": "usr/bin/rust-program2"} + {"type": "pdb", "file": "usr/bin/rust-program2"}, + {"type": "exe", "file": "usr/bin/rust-dynamic"}, + {"type": "pdb", "file": "usr/bin/rust-dynamic"} ] } diff --git a/test cases/rust/12 bindgen/cpp/meson.build b/test cases/rust/12 bindgen/cpp/meson.build index 8e7103f..6494ebd 100644 --- a/test cases/rust/12 bindgen/cpp/meson.build +++ b/test cases/rust/12 bindgen/cpp/meson.build @@ -1,4 +1,4 @@ -# SPDX-license-identifer: Apache-2.0 +# SPDX-License-Identifier: Apache-2.0 # Copyright © 2021-2023 Intel Corporation fs = import('fs') diff --git a/test cases/rust/12 bindgen/dependencies/internal_dep.h b/test cases/rust/12 bindgen/dependencies/internal_dep.h index b0629de..f44e278 100644 --- a/test cases/rust/12 bindgen/dependencies/internal_dep.h +++ b/test cases/rust/12 bindgen/dependencies/internal_dep.h @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright © 2022 Intel Corporation -#include "gen.h" +#include "gen/gen.h" int64_t add64(const int64_t, const int64_t); diff --git a/test cases/rust/12 bindgen/dependencies/internal_main.rs b/test cases/rust/12 bindgen/dependencies/internal_main.rs index 4890b43..8a04601 100644 --- a/test cases/rust/12 bindgen/dependencies/internal_main.rs +++ b/test cases/rust/12 bindgen/dependencies/internal_main.rs @@ -1,4 +1,4 @@ -// SPDX-license-identifer: Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 // Copyright © 2021 Intel Corporation #![allow(non_upper_case_globals)] diff --git a/test cases/rust/12 bindgen/src/gen_header.py b/test cases/rust/12 bindgen/gen/gen_header.py index 07b699b..71bf7fa 100644 --- a/test cases/rust/12 bindgen/src/gen_header.py +++ b/test cases/rust/12 bindgen/gen/gen_header.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# SPDX-license-Identifier: Apache-2.0 +# SPDX-License-Identifier: Apache-2.0 # Copyright © 2021-2023 Intel Corporation import argparse diff --git a/test cases/rust/12 bindgen/gen/meson.build b/test cases/rust/12 bindgen/gen/meson.build new file mode 100644 index 0000000..ff5fd84 --- /dev/null +++ b/test cases/rust/12 bindgen/gen/meson.build @@ -0,0 +1,13 @@ +gen_h = custom_target( + 'gen.h', + command : [find_program('gen_header.py'), '@INPUT@', '@OUTPUT@'], + output : 'gen.h', + input : meson.project_source_root() / 'src/header.h' +) + +gen2_h = custom_target( + 'other.h', + command : [find_program('gen_header.py'), '@INPUT@', '@OUTPUT@'], + output : 'other.h', + input : meson.project_source_root() / 'include/other.h' +) diff --git a/test cases/rust/12 bindgen/include/other.h b/test cases/rust/12 bindgen/include/other.h index 3f715f1..df70e00 100644 --- a/test cases/rust/12 bindgen/include/other.h +++ b/test cases/rust/12 bindgen/include/other.h @@ -1,4 +1,4 @@ -// SPDX-license-identifer: Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 // Copyright © 2021 Intel Corporation # pragma once diff --git a/test cases/rust/12 bindgen/meson.build b/test cases/rust/12 bindgen/meson.build index 57e44a0..d3dfba9 100644 --- a/test cases/rust/12 bindgen/meson.build +++ b/test cases/rust/12 bindgen/meson.build @@ -1,4 +1,4 @@ -# SPDX-license-identifer: Apache-2.0 +# SPDX-License-Identifier: Apache-2.0 # Copyright © 2021-2024 Intel Corporation project( @@ -66,20 +66,7 @@ rust_bin = executable( test('main', rust_bin) # Test a generated header -gen_h = custom_target( - 'gen.h', - command : [find_program('src/gen_header.py'), '@INPUT@', '@OUTPUT@'], - output : 'gen.h', - input : 'src/header.h' -) - -gen2_h = custom_target( - 'other.h', - command : [find_program('src/gen_header.py'), '@INPUT@', '@OUTPUT@'], - output : 'other.h', - input : 'include/other.h' -) - +subdir('gen') gen_rs = rust.bindgen( input : [gen_h, gen2_h], output : 'gen.rs', diff --git a/test cases/rust/12 bindgen/src/cpp.rs b/test cases/rust/12 bindgen/src/cpp.rs index 51a594c..4164b38 100644 --- a/test cases/rust/12 bindgen/src/cpp.rs +++ b/test cases/rust/12 bindgen/src/cpp.rs @@ -1,4 +1,4 @@ -// SPDX-license-identifer: Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 // Copyright © 2023 Intel Corporation #![allow(non_upper_case_globals)] diff --git a/test cases/rust/12 bindgen/src/global.rs b/test cases/rust/12 bindgen/src/global.rs index 4b70b1e..b3cc545 100644 --- a/test cases/rust/12 bindgen/src/global.rs +++ b/test cases/rust/12 bindgen/src/global.rs @@ -1,4 +1,4 @@ -// SPDX-license-identifer: Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 // Copyright © 2023 Intel Corporation #![allow(non_upper_case_globals)] diff --git a/test cases/rust/12 bindgen/src/header.h b/test cases/rust/12 bindgen/src/header.h index 2c03f33..4fa4e8f 100644 --- a/test cases/rust/12 bindgen/src/header.h +++ b/test cases/rust/12 bindgen/src/header.h @@ -1,4 +1,4 @@ -// SPDX-license-identifer: Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 // Copyright © 2021 Intel Corporation #pragma once diff --git a/test cases/rust/12 bindgen/src/header3.h b/test cases/rust/12 bindgen/src/header3.h index 958a79f..ec2949b 100644 --- a/test cases/rust/12 bindgen/src/header3.h +++ b/test cases/rust/12 bindgen/src/header3.h @@ -1,4 +1,4 @@ -// SPDX-license-identifer: Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 // Copyright © 2023 Red Hat, Inc #pragma once diff --git a/test cases/rust/12 bindgen/src/main.rs b/test cases/rust/12 bindgen/src/main.rs index 3f85e96..ae3850c 100644 --- a/test cases/rust/12 bindgen/src/main.rs +++ b/test cases/rust/12 bindgen/src/main.rs @@ -1,4 +1,4 @@ -// SPDX-license-identifer: Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 // Copyright © 2021 Intel Corporation #![allow(non_upper_case_globals)] diff --git a/test cases/rust/12 bindgen/src/main2.rs b/test cases/rust/12 bindgen/src/main2.rs index a3c28d7..0f81ef0 100644 --- a/test cases/rust/12 bindgen/src/main2.rs +++ b/test cases/rust/12 bindgen/src/main2.rs @@ -1,5 +1,5 @@ -// SPDX-license-identifer: Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 // Copyright © 2021 Intel Corporation #![allow(non_upper_case_globals)] diff --git a/test cases/rust/12 bindgen/src/main3.rs b/test cases/rust/12 bindgen/src/main3.rs index fa9d30b..8bb1ba5 100644 --- a/test cases/rust/12 bindgen/src/main3.rs +++ b/test cases/rust/12 bindgen/src/main3.rs @@ -1,4 +1,4 @@ -// SPDX-license-identifer: Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 // Copyright © 2023 Red Hat, Inc #![allow(non_upper_case_globals)] diff --git a/test cases/rust/12 bindgen/src/source.c b/test cases/rust/12 bindgen/src/source.c index d652d28..d968ba4 100644 --- a/test cases/rust/12 bindgen/src/source.c +++ b/test cases/rust/12 bindgen/src/source.c @@ -1,4 +1,4 @@ -// SPDX-license-identifer: Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 // Copyright © 2021 Intel Corporation #include "header.h" diff --git a/test cases/rust/13 external c dependencies/foo.h b/test cases/rust/13 external c dependencies/foo.h new file mode 100644 index 0000000..8912552 --- /dev/null +++ b/test cases/rust/13 external c dependencies/foo.h @@ -0,0 +1,3 @@ +#pragma once + +int foo; diff --git a/test cases/rust/13 external c dependencies/meson.build b/test cases/rust/13 external c dependencies/meson.build index c91674c..ff1136b 100644 --- a/test cases/rust/13 external c dependencies/meson.build +++ b/test cases/rust/13 external c dependencies/meson.build @@ -1,9 +1,5 @@ project('rust linking to c using dependency', 'c', 'rust') -if host_machine.system() == 'darwin' - error('MESON_SKIP_TEST: does not work right on macos, please fix!') -endif - dep_zlib = dependency('zlib', static : get_option('static'), method : get_option('method'), required : false) if not dep_zlib.found() error('MESON_SKIP_TEST: Could not find a @0@ zlib'.format(get_option('static') ? 'static' : 'shared')) @@ -21,3 +17,10 @@ e = executable( ) test('cdepstest', e) + +e2 = executable( + 'prog2', 'prog.rs', + dependencies : declare_dependency(link_with: l, sources: files('foo.h')), +) + +test('cdepstest', e2) diff --git a/test cases/rust/14 external libm/meson.build b/test cases/rust/14 external libm/meson.build index e42eefe..e8c0167 100644 --- a/test cases/rust/14 external libm/meson.build +++ b/test cases/rust/14 external libm/meson.build @@ -1,9 +1,5 @@ project('rust linking to libm', 'c', 'rust') -if host_machine.system() == 'darwin' - error('MESON_SKIP_TEST: does not work right on macos, please fix!') -endif - cc = meson.get_compiler('c') dep_m = cc.find_library('m', required : false, static : get_option('static')) if not dep_m.found() diff --git a/test cases/rust/19 structured sources/empty.file b/test cases/rust/19 structured sources/empty.file new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test cases/rust/19 structured sources/empty.file diff --git a/test cases/rust/19 structured sources/meson.build b/test cases/rust/19 structured sources/meson.build index d5b3909..f1925835 100644 --- a/test cases/rust/19 structured sources/meson.build +++ b/test cases/rust/19 structured sources/meson.build @@ -41,6 +41,7 @@ test('no-copy', find_program('no_copy_test.py'), args : meson.current_build_dir( subdir('src2') executable('copy-no-gen', srcs2) +executable('copy-no-gen-with-non-rs', srcs2_empty) m_src = configure_file( input : 'main-gen-copy.rs', @@ -56,3 +57,4 @@ m_src2 = configure_file( executable('gen-no-copy', structured_sources([m_src, m_src2])) +executable('gen-no-copy-with-non-rust', structured_sources(['empty.file', m_src, m_src2])) diff --git a/test cases/rust/19 structured sources/src2/meson.build b/test cases/rust/19 structured sources/src2/meson.build index b4844d2..16ede0d 100644 --- a/test cases/rust/19 structured sources/src2/meson.build +++ b/test cases/rust/19 structured sources/src2/meson.build @@ -2,3 +2,7 @@ srcs2 = structured_sources( ['main-unique.rs'], {'foo': 'foo/mod.rs'}, ) +srcs2_empty = structured_sources( + ['../empty.file', 'main-unique.rs'], + {'foo': 'foo/mod.rs'}, +) diff --git a/test cases/rust/2 sharedlib/meson.build b/test cases/rust/2 sharedlib/meson.build index 295fa04..2380cce 100644 --- a/test cases/rust/2 sharedlib/meson.build +++ b/test cases/rust/2 sharedlib/meson.build @@ -1,9 +1,5 @@ project('rust shared library', 'rust', 'c') -if host_machine.system() == 'darwin' - error('MESON_SKIP_TEST: does not work right on macos, please fix!') -endif - s = static_library('static', 'value.c') l = shared_library('stuff', 'stuff.rs', link_whole : s, install : true) e = executable('prog', 'prog.rs', link_with : l, install : true) diff --git a/test cases/rust/2 sharedlib/test.json b/test cases/rust/2 sharedlib/test.json index 585fdeb..11c7f9d 100644 --- a/test cases/rust/2 sharedlib/test.json +++ b/test cases/rust/2 sharedlib/test.json @@ -2,7 +2,7 @@ "installed": [ {"type": "exe", "file": "usr/bin/prog"}, {"type": "pdb", "file": "usr/bin/prog"}, - {"type": "file", "platform": "gcc", "file": "usr/lib/libstuff.so"}, + {"type": "shared_lib", "platform": "gcc", "file": "usr/lib/stuff"}, {"type": "file", "platform": "msvc", "file": "usr/bin/stuff.dll"}, {"type": "pdb", "file": "usr/bin/stuff"}, {"type": "file", "platform": "msvc", "file": "usr/lib/stuff.dll.lib"} diff --git a/test cases/rust/20 rust and cpp/meson.build b/test cases/rust/20 rust and cpp/meson.build index c301012..2a545f9 100644 --- a/test cases/rust/20 rust and cpp/meson.build +++ b/test cases/rust/20 rust and cpp/meson.build @@ -8,7 +8,7 @@ project( meson_version : '>= 1.2.0', ) -cpplib = static_library('cpp', 'lib.cpp') +cpplib = static_library('cpp-lib', 'lib.cpp') exe = executable('main', 'main.rs', link_with : cpplib) test('main', exe) diff --git a/test cases/rust/22 cargo subproject/meson.build b/test cases/rust/22 cargo subproject/meson.build index 1b60014..3a79bc8 100644 --- a/test cases/rust/22 cargo subproject/meson.build +++ b/test cases/rust/22 cargo subproject/meson.build @@ -1,6 +1,6 @@ project('cargo subproject', 'c') -foo_dep = dependency('foo-0-rs') +foo_dep = dependency('foo-0') exe = executable('app', 'main.c', dependencies: foo_dep, ) diff --git a/test cases/rust/22 cargo subproject/subprojects/extra-dep-1-rs/Cargo.toml b/test cases/rust/22 cargo subproject/subprojects/extra-dep-1-rs/Cargo.toml deleted file mode 100644 index 4b6fa57..0000000 --- a/test cases/rust/22 cargo subproject/subprojects/extra-dep-1-rs/Cargo.toml +++ /dev/null @@ -1,3 +0,0 @@ -[package] -name = "extra-deps" -version = "1.0" diff --git a/test cases/rust/22 cargo subproject/subprojects/foo-0-rs.wrap b/test cases/rust/22 cargo subproject/subprojects/foo-0-rs.wrap index 99686e9..c399701 100644 --- a/test cases/rust/22 cargo subproject/subprojects/foo-0-rs.wrap +++ b/test cases/rust/22 cargo subproject/subprojects/foo-0-rs.wrap @@ -1,2 +1,5 @@ [wrap-file] method = cargo + +[provide] +dependency_names = foo-0 diff --git a/test cases/rust/22 cargo subproject/subprojects/foo-0-rs/Cargo.toml b/test cases/rust/22 cargo subproject/subprojects/foo-0-rs/Cargo.toml index 8c5351a..9dc3460 100644 --- a/test cases/rust/22 cargo subproject/subprojects/foo-0-rs/Cargo.toml +++ b/test cases/rust/22 cargo subproject/subprojects/foo-0-rs/Cargo.toml @@ -4,7 +4,7 @@ version = "0.0.1" edition = "2021" [lib] -crate-type = ["cdylib"] +crate-type = ["lib", "staticlib", "cdylib"] path = "lib.rs" # This dependency does not exist, verify optional works. @@ -12,11 +12,6 @@ path = "lib.rs" optional = true version = "1.0" -# This dependency is optional but required for f3 which is on by default. -[dependencies.extra-dep] -optional = true -version = "1.0" - [dependencies] mybar = { version = "0.1", package = "bar", default-features = false } @@ -27,9 +22,12 @@ features = ["f1"] [dependencies.libname] version = "1" +[target."cfg(unix)".dependencies.unixdep] +version = "0.1" + [features] default = ["f1"] f1 = ["f2", "f3"] f2 = ["f1"] -f3 = ["mybar/f1", "dep:extra-dep", "notfound?/invalid"] +f3 = ["mybar/f1", "notfound?/invalid"] f4 = ["dep:notfound"] diff --git a/test cases/rust/22 cargo subproject/subprojects/foo-0-rs/lib.rs b/test cases/rust/22 cargo subproject/subprojects/foo-0-rs/lib.rs index 4497dc4..c579815 100644 --- a/test cases/rust/22 cargo subproject/subprojects/foo-0-rs/lib.rs +++ b/test cases/rust/22 cargo subproject/subprojects/foo-0-rs/lib.rs @@ -8,6 +8,11 @@ extern "C" { #[cfg(feature = "foo")] #[no_mangle] pub extern "C" fn rust_func() -> i32 { + #[cfg(unix)] + { + extern crate unixdep; + assert!(unixdep::only_on_unix() == 0); + } assert!(common::common_func() == 0); assert!(libothername::stuff() == 42); let v: i32; diff --git a/test cases/rust/22 cargo subproject/subprojects/foo-0-rs/meson/meson.build b/test cases/rust/22 cargo subproject/subprojects/foo-0-rs/meson/meson.build index 67c7b82..84bd066 100644 --- a/test cases/rust/22 cargo subproject/subprojects/foo-0-rs/meson/meson.build +++ b/test cases/rust/22 cargo subproject/subprojects/foo-0-rs/meson/meson.build @@ -1 +1,4 @@ extra_args += ['--cfg', 'feature="foo"'] +if 'f3' in features + extra_deps += dependency('extra-dep-1-rs', fallback: 'extra-dep-1-rs') +endif diff --git a/test cases/rust/22 cargo subproject/subprojects/unixdep-0.1-rs.wrap b/test cases/rust/22 cargo subproject/subprojects/unixdep-0.1-rs.wrap new file mode 100644 index 0000000..99686e9 --- /dev/null +++ b/test cases/rust/22 cargo subproject/subprojects/unixdep-0.1-rs.wrap @@ -0,0 +1,2 @@ +[wrap-file] +method = cargo diff --git a/test cases/rust/22 cargo subproject/subprojects/unixdep-0.1-rs/Cargo.toml b/test cases/rust/22 cargo subproject/subprojects/unixdep-0.1-rs/Cargo.toml new file mode 100644 index 0000000..d72fb39 --- /dev/null +++ b/test cases/rust/22 cargo subproject/subprojects/unixdep-0.1-rs/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "unixdep" +version = "0.1" +edition = "2021" + +[lib] +path = "lib.rs" diff --git a/test cases/rust/22 cargo subproject/subprojects/unixdep-0.1-rs/lib.rs b/test cases/rust/22 cargo subproject/subprojects/unixdep-0.1-rs/lib.rs new file mode 100644 index 0000000..a736e8a --- /dev/null +++ b/test cases/rust/22 cargo subproject/subprojects/unixdep-0.1-rs/lib.rs @@ -0,0 +1,8 @@ +pub fn only_on_unix() -> i32 { + 0 +} + +#[cfg(not(unix))] +pub fn broken() -> i32 { + plop +} diff --git a/test cases/rust/27 objects/lib1-dylib.rs b/test cases/rust/27 objects/lib1-dylib.rs index 1dbf614..b5080e8 100644 --- a/test cases/rust/27 objects/lib1-dylib.rs +++ b/test cases/rust/27 objects/lib1-dylib.rs @@ -13,3 +13,13 @@ pub extern "C" fn c_func() { unsafe { from_lib1(); } } + +/// ``` +/// #[cfg(not(nodep))] use lib12::rust_func; +/// #[cfg(nodep)] use lib12_nodep::rust_func; +/// rust_func(); +/// ``` +pub fn rust_func() +{ + unsafe { from_lib1(); } +} diff --git a/test cases/rust/27 objects/meson.build b/test cases/rust/27 objects/meson.build index 78373e4..ad9578a 100644 --- a/test cases/rust/27 objects/meson.build +++ b/test cases/rust/27 objects/meson.build @@ -1,4 +1,5 @@ -project('staticlib group', 'c', 'rust', meson_version: '>=1.8.0') +project('staticlib group', 'c', 'rust', meson_version: '>=1.8.0', + default_options: ['rust_std=2021']) lib1 = static_library('lib1', 'lib1.c') dep1 = declare_dependency(objects: lib1.extract_all_objects(recursive: false)) @@ -26,3 +27,14 @@ lib12 = shared_library('dylib2objs_as_dep', 'lib1-dylib.rs', dependencies: dep2, rust_abi: 'c') executable('dylib_as_dep', 'main.rs', link_with: lib12) + +lib12_rlib = static_library('lib12', 'lib1-dylib.rs', + dependencies: dep2) + +rust = import('rust') +rust.doctest('rlib with dep', lib12_rlib) + +lib12_rlib_nodep = static_library('lib12_nodep', 'lib1-dylib.rs') +rust.doctest('rlib with dep in tests', lib12_rlib_nodep, + rust_args: ['--cfg', 'nodep'], + dependencies: dep2) diff --git a/test cases/rust/28 mixed/hello.rs b/test cases/rust/28 mixed/hello.rs new file mode 100644 index 0000000..549fa94 --- /dev/null +++ b/test cases/rust/28 mixed/hello.rs @@ -0,0 +1,6 @@ +#![no_main] + +#[no_mangle] +pub extern "C" fn hello_rust() { + println!("hello world"); +} diff --git a/test cases/rust/28 mixed/main.cc b/test cases/rust/28 mixed/main.cc new file mode 100644 index 0000000..10daae4 --- /dev/null +++ b/test cases/rust/28 mixed/main.cc @@ -0,0 +1,5 @@ +#include <iostream> + +extern "C" void hello_rust(void); + +int main() { std::cout << "This is C++!\n"; hello_rust(); } diff --git a/test cases/rust/28 mixed/meson.build b/test cases/rust/28 mixed/meson.build new file mode 100644 index 0000000..fac3d46 --- /dev/null +++ b/test cases/rust/28 mixed/meson.build @@ -0,0 +1,12 @@ +# use C++ to make it harder +project('mixed', ['cpp', 'rust']) + +e1 = executable('mixed', 'hello.rs', 'main.cc') +e2 = executable('mixed-structured', structured_sources('hello.rs'), 'main.cc') + +hello2 = import('fs').copyfile('hello.rs', 'hello2.rs') +e3 = executable('mixed-structured-gen', structured_sources(hello2), 'main.cc') + +test('mixed', e1) +test('mixed-structured', e2) +test('mixed-structured-gen', e3) diff --git a/test cases/rust/29 self-contained/lib.rs b/test cases/rust/29 self-contained/lib.rs new file mode 100644 index 0000000..f8a8bf2 --- /dev/null +++ b/test cases/rust/29 self-contained/lib.rs @@ -0,0 +1,4 @@ +#[unsafe(export_name = "hello")] +pub extern "C" fn hello() { + println!("Hello, world!"); +} diff --git a/test cases/rust/29 self-contained/main.c b/test cases/rust/29 self-contained/main.c new file mode 100644 index 0000000..11bedf5 --- /dev/null +++ b/test cases/rust/29 self-contained/main.c @@ -0,0 +1,6 @@ +extern void hello(void); + +int main(void) +{ + hello(); +} diff --git a/test cases/rust/29 self-contained/meson.build b/test cases/rust/29 self-contained/meson.build new file mode 100644 index 0000000..25de68f --- /dev/null +++ b/test cases/rust/29 self-contained/meson.build @@ -0,0 +1,17 @@ +project('self-contained', meson_version : '>= 1.3.0') + +if not add_languages('c', native : false, required : false) + error('MESON_SKIP_TEST clang not installed') +endif + +if not add_languages('rust', native : false, required : false) + error('MESON_SKIP_TEST Rust x86_64-unknown-linux-musl target not installed') +endif + +if meson.get_compiler('c').find_library('libunwind', required : false).found() + error('MESON_SKIP_TEST libunwind is installed globally') +endif + +lib = static_library('rust', 'lib.rs', rust_abi : 'c') + +executable('exe', 'main.c', link_with : lib) diff --git a/test cases/rust/29 self-contained/test.json b/test cases/rust/29 self-contained/test.json new file mode 100644 index 0000000..fccefd6 --- /dev/null +++ b/test cases/rust/29 self-contained/test.json @@ -0,0 +1,6 @@ +{ + "env": { + "CC": "clang -target x86_64-unknown-linux-musl", + "RUSTC": "rustc --target x86_64-unknown-linux-musl" + } +} diff --git a/test cases/rust/3 staticlib/meson.build b/test cases/rust/3 staticlib/meson.build index cf8e103..c577fab 100644 --- a/test cases/rust/3 staticlib/meson.build +++ b/test cases/rust/3 staticlib/meson.build @@ -5,3 +5,6 @@ v = static_library('value', 'value.c') l = static_library('stuff', 'stuff.rs', link_whole : [o, v], install : true) e = executable('prog', 'prog.rs', link_with : l, install : true) test('linktest', e) + +l = static_library('stuff2', 'stuff2.rs', link_with : [o, v]) +e = executable('prog2', 'prog2.rs', link_with : l) diff --git a/test cases/rust/3 staticlib/prog2.rs b/test cases/rust/3 staticlib/prog2.rs new file mode 100644 index 0000000..9c25c77 --- /dev/null +++ b/test cases/rust/3 staticlib/prog2.rs @@ -0,0 +1,5 @@ +extern crate stuff2; + +fn main() { + println!("printing: {}", stuff2::explore()); +} diff --git a/test cases/rust/3 staticlib/stuff2.rs b/test cases/rust/3 staticlib/stuff2.rs new file mode 100644 index 0000000..5e0167a --- /dev/null +++ b/test cases/rust/3 staticlib/stuff2.rs @@ -0,0 +1,14 @@ +#![crate_name = "stuff2"] + +extern crate other; + +extern "C" { + fn c_explore_value() -> i32; +} + +pub fn explore( +) -> String { + unsafe { + other::explore(c_explore_value()) + } +} diff --git a/test cases/rust/30 rlib link subdir/cdep/f.c b/test cases/rust/30 rlib link subdir/cdep/f.c new file mode 100644 index 0000000..d64ff8c --- /dev/null +++ b/test cases/rust/30 rlib link subdir/cdep/f.c @@ -0,0 +1 @@ +int f() { return 42; } diff --git a/test cases/rust/30 rlib link subdir/cdep/meson.build b/test cases/rust/30 rlib link subdir/cdep/meson.build new file mode 100644 index 0000000..b7fd7e2 --- /dev/null +++ b/test cases/rust/30 rlib link subdir/cdep/meson.build @@ -0,0 +1 @@ +cdep = static_library('f', 'f.c') diff --git a/test cases/rust/30 rlib link subdir/f-sys.rs b/test cases/rust/30 rlib link subdir/f-sys.rs new file mode 100644 index 0000000..8897120 --- /dev/null +++ b/test cases/rust/30 rlib link subdir/f-sys.rs @@ -0,0 +1,3 @@ +extern "C" { + pub fn f() -> ::std::os::raw::c_int; +} diff --git a/test cases/rust/30 rlib link subdir/main.rs b/test cases/rust/30 rlib link subdir/main.rs new file mode 100644 index 0000000..8ae3c32 --- /dev/null +++ b/test cases/rust/30 rlib link subdir/main.rs @@ -0,0 +1 @@ +fn main() { assert_eq!(unsafe { ::f_sys::f() }, 42); } diff --git a/test cases/rust/30 rlib link subdir/meson.build b/test cases/rust/30 rlib link subdir/meson.build new file mode 100644 index 0000000..89acff2 --- /dev/null +++ b/test cases/rust/30 rlib link subdir/meson.build @@ -0,0 +1,21 @@ +project( + 'rlib-link-subdir', + ['c', 'rust'], + default_options: ['rust_std=2021'] +) + +subdir('cdep') +f_sys = static_library( + 'f_sys', + 'f-sys.rs', + link_with: cdep, + rust_abi: 'rust', +) + +exe = executable( + 'main', + 'main.rs', + link_with: f_sys, +) + +test('basic', exe) diff --git a/test cases/rust/31 both machines/lib.rs b/test cases/rust/31 both machines/lib.rs new file mode 100644 index 0000000..4325933 --- /dev/null +++ b/test cases/rust/31 both machines/lib.rs @@ -0,0 +1,3 @@ +pub fn answer() -> u32 { + 42 +} diff --git a/test cases/rust/31 both machines/meson.build b/test cases/rust/31 both machines/meson.build new file mode 100644 index 0000000..511518a --- /dev/null +++ b/test cases/rust/31 both machines/meson.build @@ -0,0 +1,36 @@ +project( + 'testproj', + 'rust', + version : '0.1', + meson_version : '>= 1.9.0', + default_options : ['rust_std=2021'], +) + +lib_host = static_library( + 'lib', + 'lib.rs', + rust_abi: 'rust' +) + +lib_build = static_library( + 'lib+build', + 'lib.rs', + rust_abi: 'rust', + native: true, +) + +exe_host = executable( + 'test-host', + 'test.rs', + link_with: lib_host, +) + +exe_build = executable( + 'test-build', + 'test.rs', + link_with: lib_build, + native: true, +) + +test('host', exe_host) +test('build', exe_build) diff --git a/test cases/rust/31 both machines/test.rs b/test cases/rust/31 both machines/test.rs new file mode 100644 index 0000000..c9679d4 --- /dev/null +++ b/test cases/rust/31 both machines/test.rs @@ -0,0 +1,5 @@ +use lib::answer; + +fn main() { + println!("The answer is {}.\n", answer()); +} diff --git a/test cases/rust/32 cargo workspace/meson.build b/test cases/rust/32 cargo workspace/meson.build new file mode 100644 index 0000000..4d69fdf --- /dev/null +++ b/test cases/rust/32 cargo workspace/meson.build @@ -0,0 +1,13 @@ +project('cargo workspace', 'c', 'rust') + +foo_rs = dependency('foo-1-rs') +e = executable('test-foo-1-rs', 'test_foo_1.rs', + dependencies: [foo_rs], +) +test('test-foo-1-rs', e) + +foo_cdylib = dependency('foo-1') +e = executable('test-foo-1-cdylib', 'test_foo_1.c', + dependencies: [foo_cdylib], +) +test('test-foo-1-rs', e) diff --git a/test cases/rust/32 cargo workspace/subprojects/foo.wrap b/test cases/rust/32 cargo workspace/subprojects/foo.wrap new file mode 100644 index 0000000..3d21ca7 --- /dev/null +++ b/test cases/rust/32 cargo workspace/subprojects/foo.wrap @@ -0,0 +1,5 @@ +[wrap-file] +method=cargo + +[provide] +dependency_names=foo-1-rs diff --git a/test cases/rust/32 cargo workspace/subprojects/foo/Cargo.toml b/test cases/rust/32 cargo workspace/subprojects/foo/Cargo.toml new file mode 100644 index 0000000..52f28d8 --- /dev/null +++ b/test cases/rust/32 cargo workspace/subprojects/foo/Cargo.toml @@ -0,0 +1,15 @@ +[workspace] +resolver = "2" +members = [ + "src/foo", + "src/member1" +] +default-members = ["src/foo"] + +[workspace.package] +edition = "2021" +version = "1.0.0" + +[workspace.dependencies] +member1 = { path="./src/member1" } +member2 = { path="subprojects/member2-1.0", features = ["f1"] } diff --git a/test cases/rust/32 cargo workspace/subprojects/foo/src/foo/Cargo.toml b/test cases/rust/32 cargo workspace/subprojects/foo/src/foo/Cargo.toml new file mode 100644 index 0000000..52b39e9 --- /dev/null +++ b/test cases/rust/32 cargo workspace/subprojects/foo/src/foo/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "foo" +edition.workspace = true +version.workspace = true + +[lib] +crate-type = ["lib", "cdylib"] + +[dependencies] +m1 = { path="../member1", package="member1" } diff --git a/test cases/rust/32 cargo workspace/subprojects/foo/src/foo/src/lib.rs b/test cases/rust/32 cargo workspace/subprojects/foo/src/foo/src/lib.rs new file mode 100644 index 0000000..65803da --- /dev/null +++ b/test cases/rust/32 cargo workspace/subprojects/foo/src/foo/src/lib.rs @@ -0,0 +1,6 @@ +extern crate m1; + +#[no_mangle] +pub extern "C" fn foo() -> i32 { + m1::member1() + 1 +} diff --git a/test cases/rust/32 cargo workspace/subprojects/foo/src/lib.rs b/test cases/rust/32 cargo workspace/subprojects/foo/src/lib.rs new file mode 100644 index 0000000..c377381 --- /dev/null +++ b/test cases/rust/32 cargo workspace/subprojects/foo/src/lib.rs @@ -0,0 +1,3 @@ +pub fn foo() -> i32 { + member1::member1() + 1 +} diff --git a/test cases/rust/32 cargo workspace/subprojects/foo/src/member1/Cargo.toml b/test cases/rust/32 cargo workspace/subprojects/foo/src/member1/Cargo.toml new file mode 100644 index 0000000..2c52137 --- /dev/null +++ b/test cases/rust/32 cargo workspace/subprojects/foo/src/member1/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "member1" +edition.workspace = true +version.workspace = true + +[dependencies] +member2 = { workspace = true, features=["f2"] } diff --git a/test cases/rust/32 cargo workspace/subprojects/foo/src/member1/src/lib.rs b/test cases/rust/32 cargo workspace/subprojects/foo/src/member1/src/lib.rs new file mode 100644 index 0000000..d4778ce --- /dev/null +++ b/test cases/rust/32 cargo workspace/subprojects/foo/src/member1/src/lib.rs @@ -0,0 +1,5 @@ +extern crate member2; + +pub fn member1() -> i32 { + member2::member2() + 1 +} diff --git a/test cases/rust/32 cargo workspace/subprojects/foo/subprojects/member2-1-rs.wrap b/test cases/rust/32 cargo workspace/subprojects/foo/subprojects/member2-1-rs.wrap new file mode 100644 index 0000000..57ae2fa --- /dev/null +++ b/test cases/rust/32 cargo workspace/subprojects/foo/subprojects/member2-1-rs.wrap @@ -0,0 +1,6 @@ +[wrap-file] +directory=member2-1.0 +method=cargo + +[provide] +dependency_names=member2-1-rs diff --git a/test cases/rust/32 cargo workspace/subprojects/foo/subprojects/member2-1.0/Cargo.toml b/test cases/rust/32 cargo workspace/subprojects/foo/subprojects/member2-1.0/Cargo.toml new file mode 100644 index 0000000..059120a --- /dev/null +++ b/test cases/rust/32 cargo workspace/subprojects/foo/subprojects/member2-1.0/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "member2" +edition.workspace = true +version.workspace = true + +[features] +default = [] +f1 = [] +f2 = [] diff --git a/test cases/rust/32 cargo workspace/subprojects/foo/subprojects/member2-1.0/src/lib.rs b/test cases/rust/32 cargo workspace/subprojects/foo/subprojects/member2-1.0/src/lib.rs new file mode 100644 index 0000000..75703a9 --- /dev/null +++ b/test cases/rust/32 cargo workspace/subprojects/foo/subprojects/member2-1.0/src/lib.rs @@ -0,0 +1,5 @@ +#[cfg(feature = "f1")] +#[cfg(feature = "f2")] +pub fn member2() -> i32 { + 1 +} diff --git a/test cases/rust/32 cargo workspace/subprojects/member2.wrap b/test cases/rust/32 cargo workspace/subprojects/member2.wrap new file mode 100644 index 0000000..3d43da6 --- /dev/null +++ b/test cases/rust/32 cargo workspace/subprojects/member2.wrap @@ -0,0 +1,2 @@ +[wrap-redirect] +filename = foo/subprojects/member2-1-rs.wrap diff --git a/test cases/rust/32 cargo workspace/test_foo_1.c b/test cases/rust/32 cargo workspace/test_foo_1.c new file mode 100644 index 0000000..d5f12a6 --- /dev/null +++ b/test cases/rust/32 cargo workspace/test_foo_1.c @@ -0,0 +1,5 @@ +extern int foo(void); + +int main(void) { + return foo() == 3 ? 0 : 1; +} diff --git a/test cases/rust/32 cargo workspace/test_foo_1.rs b/test cases/rust/32 cargo workspace/test_foo_1.rs new file mode 100644 index 0000000..7d1ebb7 --- /dev/null +++ b/test cases/rust/32 cargo workspace/test_foo_1.rs @@ -0,0 +1,5 @@ +extern crate foo; + +pub fn main() { + assert!(foo::foo() == 3); +} diff --git a/test cases/rust/33 cargo multiple versions/meson.build b/test cases/rust/33 cargo multiple versions/meson.build new file mode 100644 index 0000000..dd11508 --- /dev/null +++ b/test cases/rust/33 cargo multiple versions/meson.build @@ -0,0 +1,3 @@ +project('cargo multiple crate versions') + +subproject('main') diff --git a/test cases/rust/33 cargo multiple versions/subprojects/foo-1-rs.wrap b/test cases/rust/33 cargo multiple versions/subprojects/foo-1-rs.wrap new file mode 100644 index 0000000..5133599 --- /dev/null +++ b/test cases/rust/33 cargo multiple versions/subprojects/foo-1-rs.wrap @@ -0,0 +1,2 @@ +[wrap-file] +method=cargo diff --git a/test cases/rust/33 cargo multiple versions/subprojects/foo-1-rs/Cargo.toml b/test cases/rust/33 cargo multiple versions/subprojects/foo-1-rs/Cargo.toml new file mode 100644 index 0000000..41de669 --- /dev/null +++ b/test cases/rust/33 cargo multiple versions/subprojects/foo-1-rs/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "foo" +version = "1.0" + +[lib] +path = "lib.rs" diff --git a/test cases/rust/33 cargo multiple versions/subprojects/foo-1-rs/lib.rs b/test cases/rust/33 cargo multiple versions/subprojects/foo-1-rs/lib.rs new file mode 100644 index 0000000..f3662d5 --- /dev/null +++ b/test cases/rust/33 cargo multiple versions/subprojects/foo-1-rs/lib.rs @@ -0,0 +1,3 @@ +pub fn foo1() -> i32 { + 1 +} diff --git a/test cases/rust/33 cargo multiple versions/subprojects/foo-2-rs.wrap b/test cases/rust/33 cargo multiple versions/subprojects/foo-2-rs.wrap new file mode 100644 index 0000000..5133599 --- /dev/null +++ b/test cases/rust/33 cargo multiple versions/subprojects/foo-2-rs.wrap @@ -0,0 +1,2 @@ +[wrap-file] +method=cargo diff --git a/test cases/rust/33 cargo multiple versions/subprojects/foo-2-rs/Cargo.toml b/test cases/rust/33 cargo multiple versions/subprojects/foo-2-rs/Cargo.toml new file mode 100644 index 0000000..ce55217 --- /dev/null +++ b/test cases/rust/33 cargo multiple versions/subprojects/foo-2-rs/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "foo" +version = "2.0" + +[lib] +path = "lib.rs" diff --git a/test cases/rust/33 cargo multiple versions/subprojects/foo-2-rs/lib.rs b/test cases/rust/33 cargo multiple versions/subprojects/foo-2-rs/lib.rs new file mode 100644 index 0000000..afbf386 --- /dev/null +++ b/test cases/rust/33 cargo multiple versions/subprojects/foo-2-rs/lib.rs @@ -0,0 +1,3 @@ +pub fn foo2() -> i32 { + 1 +} diff --git a/test cases/rust/33 cargo multiple versions/subprojects/main.wrap b/test cases/rust/33 cargo multiple versions/subprojects/main.wrap new file mode 100644 index 0000000..5133599 --- /dev/null +++ b/test cases/rust/33 cargo multiple versions/subprojects/main.wrap @@ -0,0 +1,2 @@ +[wrap-file] +method=cargo diff --git a/test cases/rust/33 cargo multiple versions/subprojects/main/Cargo.toml b/test cases/rust/33 cargo multiple versions/subprojects/main/Cargo.toml new file mode 100644 index 0000000..0401a59 --- /dev/null +++ b/test cases/rust/33 cargo multiple versions/subprojects/main/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "main" + +[dependencies] +foo1 = { package="foo", version="1" } +foo2 = { package="foo", version="2" } + +[lib] +path = "lib.rs" diff --git a/test cases/rust/33 cargo multiple versions/subprojects/main/lib.rs b/test cases/rust/33 cargo multiple versions/subprojects/main/lib.rs new file mode 100644 index 0000000..5029a27 --- /dev/null +++ b/test cases/rust/33 cargo multiple versions/subprojects/main/lib.rs @@ -0,0 +1,6 @@ +extern crate foo1; +extern crate foo2; + +pub fn func() -> i32 { + foo1::foo1() + foo2::foo2() +} diff --git a/test cases/rust/4 polyglot/meson.build b/test cases/rust/4 polyglot/meson.build index b2fd8f9..29af73f 100644 --- a/test cases/rust/4 polyglot/meson.build +++ b/test cases/rust/4 polyglot/meson.build @@ -1,9 +1,5 @@ project('rust and c polyglot executable', 'c', 'rust') -if host_machine.system() == 'darwin' - error('MESON_SKIP_TEST: does not work right on macos, please fix!') -endif - cc = meson.get_compiler('c') # Test all combinations of crate and target types. @@ -22,7 +18,7 @@ foreach crate_type : ['lib', 'rlib', 'dylib', 'cdylib', 'staticlib', 'proc-macro # Note: in the both_libraries() case it is possible that the static part # is still being built because the shared part raised an error but we # don't rollback correctly. - testcase expect_error('(Crate type .* invalid for .*)|(.*must be one of.*not invalid)', how: 're') + testcase expect_error('(Crate type .* must be .*)|(.*must be one of.*not invalid)', how: 're') build_target(name, src, target_type: target_type, rust_crate_type: crate_type, diff --git a/test cases/rust/4 polyglot/stuff.rs b/test cases/rust/4 polyglot/stuff.rs index 30c3a36..e5d9386 100644 --- a/test cases/rust/4 polyglot/stuff.rs +++ b/test cases/rust/4 polyglot/stuff.rs @@ -1,4 +1,4 @@ #[no_mangle] -pub extern fn f() { +pub extern "C" fn f() { println!("Hello from Rust!"); } diff --git a/test cases/rust/4 polyglot/test.json b/test cases/rust/4 polyglot/test.json index d963ad8..443bf6c 100644 --- a/test cases/rust/4 polyglot/test.json +++ b/test cases/rust/4 polyglot/test.json @@ -3,7 +3,7 @@ {"type": "exe", "file": "usr/bin/prog-stuff_clib_both_libraries"}, {"type": "pdb", "file": "usr/bin/prog-stuff_clib_both_libraries"}, {"type": "pdb", "file": "usr/bin/stuff_clib_both_libraries.pdb"}, - {"type": "file", "platform": "gcc", "file": "usr/lib/libstuff_clib_both_libraries.so"}, + {"type": "shared_lib", "platform": "gcc", "file": "usr/lib/stuff_clib_both_libraries"}, {"type": "file", "platform": "msvc", "file": "usr/bin/stuff_clib_both_libraries.dll"}, {"type": "file", "platform": "msvc", "file": "usr/lib/stuff_clib_both_libraries.dll.lib"}, {"type": "file", "file": "usr/lib/libstuff_clib_both_libraries.a"}, @@ -11,7 +11,7 @@ {"type": "exe", "file": "usr/bin/prog-stuff_clib_shared_library"}, {"type": "pdb", "file": "usr/bin/prog-stuff_clib_shared_library"}, {"type": "pdb", "file": "usr/bin/stuff_clib_shared_library.pdb"}, - {"type": "file", "platform": "gcc", "file": "usr/lib/libstuff_clib_shared_library.so"}, + {"type": "shared_lib", "platform": "gcc", "file": "usr/lib/stuff_clib_shared_library"}, {"type": "file", "platform": "msvc", "file": "usr/bin/stuff_clib_shared_library.dll"}, {"type": "file", "platform": "msvc", "file": "usr/lib/stuff_clib_shared_library.dll.lib"}, @@ -22,7 +22,7 @@ {"type": "exe", "file": "usr/bin/prog-stuff_cdylib_shared_library"}, {"type": "pdb", "file": "usr/bin/prog-stuff_cdylib_shared_library"}, {"type": "pdb", "file": "usr/bin/stuff_cdylib_shared_library.pdb"}, - {"type": "file", "platform": "gcc", "file": "usr/lib/libstuff_cdylib_shared_library.so"}, + {"type": "shared_lib", "platform": "gcc", "file": "usr/lib/stuff_cdylib_shared_library"}, {"type": "file", "platform": "msvc", "file": "usr/bin/stuff_cdylib_shared_library.dll"}, {"type": "file", "platform": "msvc", "file": "usr/lib/stuff_cdylib_shared_library.dll.lib"}, @@ -31,7 +31,7 @@ {"type": "file", "file": "usr/lib/libstuff_staticlib_static_library.a"}, {"type": "pdb", "file": "usr/bin/stuff__both_libraries.pdb"}, - {"type": "file", "platform": "gcc", "file": "usr/lib/libstuff__both_libraries.so"}, + {"type": "shared_lib", "platform": "gcc", "file": "usr/lib/stuff__both_libraries"}, {"type": "file", "platform": "gcc", "file": "usr/lib/libstuff__both_libraries.rlib"}, {"type": "file", "platform": "msvc", "file": "usr/lib/libstuff__both_libraries.rlib"}, {"type": "file", "platform": "msvc", "file": "usr/bin/stuff__both_libraries.dll"}, @@ -41,13 +41,13 @@ {"type": "file", "platform": "msvc", "file": "usr/lib/libstuff__static_library.rlib"}, {"type": "pdb", "file": "usr/bin/stuff_proc_macro_shared_library.pdb"}, - {"type": "file", "platform": "gcc", "file": "usr/lib/libstuff_proc_macro_shared_library.so"}, + {"type": "shared_lib", "platform": "gcc", "file": "usr/lib/stuff_proc_macro_shared_library"}, {"type": "file", "platform": "msvc", "file": "usr/bin/stuff_proc_macro_shared_library.dll"}, {"type": "file", "platform": "msvc", "file": "usr/lib/stuff_proc_macro_shared_library.dll.lib"}, {"type": "pdb", "file": "usr/bin/stuff_lib_both_libraries.pdb"}, {"type": "file", "platform": "gcc", "file": "usr/lib/libstuff_lib_both_libraries.rlib"}, - {"type": "file", "platform": "gcc", "file": "usr/lib/libstuff_lib_both_libraries.so"}, + {"type": "shared_lib", "platform": "gcc", "file": "usr/lib/stuff_lib_both_libraries"}, {"type": "file", "platform": "msvc", "file": "usr/bin/stuff_lib_both_libraries.dll"}, {"type": "file", "platform": "msvc", "file": "usr/lib/libstuff_lib_both_libraries.rlib"}, {"type": "file", "platform": "msvc", "file": "usr/lib/stuff_lib_both_libraries.dll.lib"}, @@ -59,33 +59,32 @@ {"type": "file", "platform": "msvc", "file": "usr/lib/libstuff_lib_static_library.rlib"}, {"type": "pdb", "file": "usr/bin/stuff__shared_library.pdb"}, - {"type": "file", "platform": "gcc", "file": "usr/lib/libstuff__shared_library.so"}, + {"type": "shared_lib", "platform": "gcc", "file": "usr/lib/stuff__shared_library"}, {"type": "file", "platform": "msvc", "file": "usr/bin/stuff__shared_library.dll"}, {"type": "file", "platform": "msvc", "file": "usr/lib/stuff__shared_library.dll.lib"}, {"type": "pdb", "file": "usr/bin/stuff_lib_shared_library.pdb"}, - {"type": "file", "platform": "gcc", "file": "usr/lib/libstuff_lib_shared_library.so"}, + {"type": "shared_lib", "platform": "gcc", "file": "usr/lib/stuff_lib_shared_library"}, {"type": "file", "platform": "msvc", "file": "usr/bin/stuff_lib_shared_library.dll"}, {"type": "file", "platform": "msvc", "file": "usr/lib/stuff_lib_shared_library.dll.lib"}, {"type": "pdb", "file": "usr/bin/stuff_dylib_shared_library.pdb"}, - {"type": "file", "platform": "gcc", "file": "usr/lib/libstuff_dylib_shared_library.so"}, + {"type": "shared_lib", "platform": "gcc", "file": "usr/lib/stuff_dylib_shared_library"}, {"type": "file", "platform": "msvc", "file": "usr/lib/stuff_dylib_shared_library.dll.lib"}, {"type": "file", "platform": "msvc", "file": "usr/bin/stuff_dylib_shared_library.dll"}, {"type": "pdb", "file": "usr/bin/stuff_cdylib_both_libraries.pdb"}, - {"type": "file", "platform": "gcc", "file": "usr/lib/libstuff_cdylib_both_libraries.so"}, - {"type": "file", "platform": "gcc", "file": "usr/lib/libstuff_cdylib_both_libraries.so"}, + {"type": "shared_lib", "platform": "gcc", "file": "usr/lib/stuff_cdylib_both_libraries"}, {"type": "file", "platform": "msvc", "file": "usr/bin/stuff_cdylib_both_libraries.dll"}, {"type": "file", "platform": "msvc", "file": "usr/lib/stuff_cdylib_both_libraries.dll.lib"}, {"type": "pdb", "file": "usr/bin/stuff_proc_macro_both_libraries.pdb"}, - {"type": "file", "platform": "gcc", "file": "usr/lib/libstuff_proc_macro_both_libraries.so"}, + {"type": "shared_lib", "platform": "gcc", "file": "usr/lib/stuff_proc_macro_both_libraries"}, {"type": "file", "platform": "msvc", "file": "usr/bin/stuff_proc_macro_both_libraries.dll"}, {"type": "file", "platform": "msvc", "file": "usr/lib/stuff_proc_macro_both_libraries.dll.lib"}, {"type": "pdb", "file": "usr/bin/stuff_dylib_both_libraries.pdb"}, - {"type": "file", "platform": "gcc", "file": "usr/lib/libstuff_dylib_both_libraries.so"}, + {"type": "shared_lib", "platform": "gcc", "file": "usr/lib/stuff_dylib_both_libraries"}, {"type": "file", "platform": "msvc", "file": "usr/bin/stuff_dylib_both_libraries.dll"}, {"type": "file", "platform": "msvc", "file": "usr/lib/stuff_dylib_both_libraries.dll.lib"} ] diff --git a/test cases/rust/9 unit tests/meson.build b/test cases/rust/9 unit tests/meson.build index 0fa2fa8..81045f2 100644 --- a/test cases/rust/9 unit tests/meson.build +++ b/test cases/rust/9 unit tests/meson.build @@ -40,6 +40,12 @@ if rustdoc.found() protocol : 'rust', suite : ['doctests'], ) + + doclib = shared_library('rust_doc_lib', ['doctest1.rs'], build_by_default : false) + rust.doctest('rust shared doctests', doclib, + protocol : 'rust', + suite : ['doctests'], + ) endif exe = executable('rust_exe', ['test2.rs', 'test.rs'], build_by_default : false) diff --git a/test cases/snippets/1 symbol visibility header/main-static-only.c b/test cases/snippets/1 symbol visibility header/main-static-only.c new file mode 100644 index 0000000..26f5b06 --- /dev/null +++ b/test cases/snippets/1 symbol visibility header/main-static-only.c @@ -0,0 +1,3 @@ +#include <mylib/lib-static-only.h> + +int main(void) { return do_stuff(); } diff --git a/test cases/snippets/1 symbol visibility header/main.c b/test cases/snippets/1 symbol visibility header/main.c new file mode 100644 index 0000000..1d32f62 --- /dev/null +++ b/test cases/snippets/1 symbol visibility header/main.c @@ -0,0 +1,3 @@ +#include <mylib/lib.h> + +int main(void) { return do_stuff(); } diff --git a/test cases/snippets/1 symbol visibility header/meson.build b/test cases/snippets/1 symbol visibility header/meson.build new file mode 100644 index 0000000..9a6c27a --- /dev/null +++ b/test cases/snippets/1 symbol visibility header/meson.build @@ -0,0 +1,13 @@ +project('symbol visibility header', 'c') + +sta_dep = dependency('mylib-sta', fallback: 'sub') +exe = executable('exe-sta', 'main.c', dependencies: sta_dep) +test('test-sta', exe) + +sha_dep = dependency('mylib-sha', fallback: 'sub') +exe = executable('exe-sha', 'main.c', dependencies: sha_dep) +test('test-sha', exe) + +static_only_dep = dependency('static-only', fallback: 'sub') +exe = executable('exe-static-only', 'main-static-only.c', dependencies: static_only_dep) +test('test-static-only', exe) diff --git a/test cases/snippets/1 symbol visibility header/subprojects/sub/meson.build b/test cases/snippets/1 symbol visibility header/subprojects/sub/meson.build new file mode 100644 index 0000000..83b7970 --- /dev/null +++ b/test cases/snippets/1 symbol visibility header/subprojects/sub/meson.build @@ -0,0 +1,5 @@ +project('my lib', 'c') + +pkg = import('pkgconfig') + +subdir('mylib') diff --git a/test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/lib-static-only.c b/test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/lib-static-only.c new file mode 100644 index 0000000..b7662cd --- /dev/null +++ b/test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/lib-static-only.c @@ -0,0 +1,3 @@ +#include "lib-static-only.h" + +int do_stuff(void) { return 0; } diff --git a/test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/lib-static-only.h b/test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/lib-static-only.h new file mode 100644 index 0000000..ebe4524 --- /dev/null +++ b/test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/lib-static-only.h @@ -0,0 +1,3 @@ +#include <mylib/apiconfig-static-only.h> + +MY_LIB_API int do_stuff(void); diff --git a/test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/lib.c b/test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/lib.c new file mode 100644 index 0000000..1171720 --- /dev/null +++ b/test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/lib.c @@ -0,0 +1,3 @@ +#include "lib.h" + +int do_stuff(void) { return 0; } diff --git a/test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/lib.h b/test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/lib.h new file mode 100644 index 0000000..bea53af --- /dev/null +++ b/test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/lib.h @@ -0,0 +1,3 @@ +#include <mylib/apiconfig.h> + +MY_LIB_API int do_stuff(void); diff --git a/test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/meson.build b/test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/meson.build new file mode 100644 index 0000000..1e7b45e --- /dev/null +++ b/test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/meson.build @@ -0,0 +1,39 @@ +snippets = import('snippets') + +lib_incdir = include_directories('..') +lib_args = ['-DMY_LIB_COMPILATION'] +lib_static_args = ['-DMY_LIB_STATIC_COMPILATION'] + +h = snippets.symbol_visibility_header('apiconfig.h') +install_headers(h, 'lib.h', subdir: 'mylib') +mylib = both_libraries('mylib', 'lib.c', + include_directories: lib_incdir, + gnu_symbol_visibility: 'hidden', + c_args: lib_args, + c_static_args: lib_static_args, + install: true) +mylib_sta_dep = declare_dependency(link_with: mylib.get_static_lib(), + include_directories: lib_incdir, + compile_args: lib_static_args) +mylib_sha_dep = declare_dependency(link_with: mylib.get_shared_lib(), + include_directories: lib_incdir) +meson.override_dependency('mylib-sta', mylib_sta_dep) +meson.override_dependency('mylib-sha', mylib_sha_dep) +pkg.generate(mylib, + extra_cflags: lib_static_args, +) + +# When using static_only, we don't need lib_static_args because +# MY_LIB_STATIC_COMPILATION gets defined in the generated header. +h = snippets.symbol_visibility_header('apiconfig-static-only.h', + static_only: true) +install_headers(h, 'lib-static-only.h', subdir: 'mylib') +libstaticonly = static_library('static-only', 'lib-static-only.c', + include_directories: lib_incdir, + gnu_symbol_visibility: 'hidden', + c_args: lib_args, + install: true) +static_only_dep = declare_dependency(link_with: libstaticonly, + include_directories: lib_incdir) +meson.override_dependency('static-only', static_only_dep) +pkg.generate(libstaticonly) diff --git a/test cases/snippets/1 symbol visibility header/test.json b/test cases/snippets/1 symbol visibility header/test.json new file mode 100644 index 0000000..0d0be4e --- /dev/null +++ b/test cases/snippets/1 symbol visibility header/test.json @@ -0,0 +1,15 @@ +{ + "installed": [ + {"type": "file", "file": "usr/include/mylib/apiconfig-static-only.h"}, + {"type": "file", "file": "usr/include/mylib/apiconfig.h"}, + {"type": "file", "file": "usr/include/mylib/lib-static-only.h"}, + {"type": "file", "file": "usr/include/mylib/lib.h"}, + {"type": "file", "file": "usr/lib/libmylib.a"}, + {"type": "expr", "file": "usr/lib/?libmylib?so"}, + {"type": "implib", "file": "usr/lib/libmylib"}, + {"type": "pdb", "file": "usr/bin/mylib"}, + {"type": "file", "file": "usr/lib/libstatic-only.a"}, + {"type": "file", "file": "usr/lib/pkgconfig/mylib.pc"}, + {"type": "file", "file": "usr/lib/pkgconfig/static-only.pc"} + ] +} diff --git a/test cases/swift/11 mixed cpp/main.swift b/test cases/swift/11 mixed cpp/main.swift new file mode 100644 index 0000000..c055dcd --- /dev/null +++ b/test cases/swift/11 mixed cpp/main.swift @@ -0,0 +1,6 @@ +testCallFromSwift() +testCallWithParam("Calling C++ function from Swift with param is working") + +var test = Test() +var testtwo = Test(1) +test.testCallFromClass() diff --git a/test cases/swift/11 mixed cpp/meson.build b/test cases/swift/11 mixed cpp/meson.build new file mode 100644 index 0000000..6027341 --- /dev/null +++ b/test cases/swift/11 mixed cpp/meson.build @@ -0,0 +1,12 @@ +project('mixed cpp', 'cpp', 'swift') + +swiftc = meson.get_compiler('swift') + +# Testing C++ and Swift interoperability requires Swift 5.9 +if not swiftc.version().version_compare('>= 5.9') + error('MESON_SKIP_TEST Test requires Swift 5.9') +endif + +lib = static_library('mylib', 'mylib.cpp') +exe = executable('prog', 'main.swift', 'mylib.h', link_with: lib, swift_interoperability_mode: 'cpp') +test('cpp interface', exe) diff --git a/test cases/swift/11 mixed cpp/mylib.cpp b/test cases/swift/11 mixed cpp/mylib.cpp new file mode 100644 index 0000000..0c61681 --- /dev/null +++ b/test cases/swift/11 mixed cpp/mylib.cpp @@ -0,0 +1,22 @@ +#include "mylib.h" +#include <iostream> + +Test::Test() { + std::cout << "Test initialized" << std::endl; +} + +Test::Test(int param) { + std::cout << "Test initialized with param " << param << std::endl; +} + +void Test::testCallFromClass() { + std::cout << "Calling C++ class function from Swift is working" << std::endl; +} + +void testCallFromSwift() { + std::cout << "Calling this C++ function from Swift is working" << std::endl; +} + +void testCallWithParam(const std::string ¶m) { + std::cout << param << std::endl; +} diff --git a/test cases/swift/11 mixed cpp/mylib.h b/test cases/swift/11 mixed cpp/mylib.h new file mode 100644 index 0000000..c465be4 --- /dev/null +++ b/test cases/swift/11 mixed cpp/mylib.h @@ -0,0 +1,13 @@ +#pragma once +#include <string> + +class Test { +public: + Test(); + Test(int param); + + void testCallFromClass(); +}; + +void testCallFromSwift(); +void testCallWithParam(const std::string ¶m); diff --git a/test cases/swift/12 c std passthrough/header.h b/test cases/swift/12 c std passthrough/header.h new file mode 100644 index 0000000..287cdf4 --- /dev/null +++ b/test cases/swift/12 c std passthrough/header.h @@ -0,0 +1,10 @@ +#pragma once + +// let's just assume the default isn't c18. +#if __STDC_VERSION__ == 201710L +typedef struct Datatype { + int x; +} Datatype; +#else +#error C standard version not set! +#endif diff --git a/test cases/swift/12 c std passthrough/main.swift b/test cases/swift/12 c std passthrough/main.swift new file mode 100644 index 0000000..f6358db --- /dev/null +++ b/test cases/swift/12 c std passthrough/main.swift @@ -0,0 +1,3 @@ +let d = Datatype(x: 1) + +precondition(d.x == 1) diff --git a/test cases/swift/12 c std passthrough/meson.build b/test cases/swift/12 c std passthrough/meson.build new file mode 100644 index 0000000..202768f --- /dev/null +++ b/test cases/swift/12 c std passthrough/meson.build @@ -0,0 +1,3 @@ +project('c std passthrough', 'swift', 'c', default_options: {'c_std': 'c18'}) + +executable('program', 'main.swift', 'header.h') diff --git a/test cases/swift/13 mixed objcpp/main.swift b/test cases/swift/13 mixed objcpp/main.swift new file mode 100644 index 0000000..cd6dd2b --- /dev/null +++ b/test cases/swift/13 mixed objcpp/main.swift @@ -0,0 +1,2 @@ +var test: ObjCPPTest = ObjCPPTest() +test.testCallToObjCPP() diff --git a/test cases/swift/13 mixed objcpp/meson.build b/test cases/swift/13 mixed objcpp/meson.build new file mode 100644 index 0000000..a76162a --- /dev/null +++ b/test cases/swift/13 mixed objcpp/meson.build @@ -0,0 +1,12 @@ +project('mixed objcpp', 'objcpp', 'swift') + +swiftc = meson.get_compiler('swift') + +# Testing Objective-C++ and Swift interoperability requires Swift 5.9 +if not swiftc.version().version_compare('>= 5.9') + error('MESON_SKIP_TEST Test requires Swift 5.9') +endif + +lib = static_library('mylib', 'mylib.mm') +exe = executable('prog', 'main.swift', 'mylib.h', link_with: lib, swift_interoperability_mode: 'cpp') +test('objcpp interface', exe) diff --git a/test cases/swift/13 mixed objcpp/mylib.h b/test cases/swift/13 mixed objcpp/mylib.h new file mode 100644 index 0000000..1e7b23d --- /dev/null +++ b/test cases/swift/13 mixed objcpp/mylib.h @@ -0,0 +1,17 @@ +#pragma once +#import <Foundation/Foundation.h> + +class Test { +public: + Test(); + + void testCallFromClass(); +}; + +@interface ObjCPPTest: NSObject { + @private Test *test; +} +- (id)init; +- (void)dealloc; +- (void)testCallToObjCPP; +@end diff --git a/test cases/swift/13 mixed objcpp/mylib.mm b/test cases/swift/13 mixed objcpp/mylib.mm new file mode 100644 index 0000000..f7e9ab3 --- /dev/null +++ b/test cases/swift/13 mixed objcpp/mylib.mm @@ -0,0 +1,29 @@ +#include "mylib.h" +#include <iostream> + +Test::Test() { + std::cout << "Test initialized" << std::endl; +} + +void Test::testCallFromClass() { + std::cout << "Calling Objective-C++ class function from Swift is working" << std::endl; +} + +@implementation ObjCPPTest +- (id)init { + self = [super init]; + if (self) { + test = new Test(); + } + return self; +} + +- (void)dealloc { + delete test; + [super dealloc]; +} + +- (void)testCallToObjCPP { + test->testCallFromClass(); +} +@end diff --git a/test cases/swift/14 single-file library/main.swift b/test cases/swift/14 single-file library/main.swift new file mode 100644 index 0000000..ccc8fb9 --- /dev/null +++ b/test cases/swift/14 single-file library/main.swift @@ -0,0 +1,3 @@ +import SingleFile + +callMe() diff --git a/test cases/swift/14 single-file library/meson.build b/test cases/swift/14 single-file library/meson.build new file mode 100644 index 0000000..8eda1d5 --- /dev/null +++ b/test cases/swift/14 single-file library/meson.build @@ -0,0 +1,4 @@ +project('single-file library', 'swift') + +lib = static_library('SingleFile', 'singlefile.swift') +executable('program', 'main.swift', link_with: [lib]) diff --git a/test cases/swift/14 single-file library/singlefile.swift b/test cases/swift/14 single-file library/singlefile.swift new file mode 100644 index 0000000..617952f --- /dev/null +++ b/test cases/swift/14 single-file library/singlefile.swift @@ -0,0 +1 @@ +public func callMe() {} diff --git a/test cases/swift/15 main in single-file library/main.swift b/test cases/swift/15 main in single-file library/main.swift new file mode 100644 index 0000000..0d95abb --- /dev/null +++ b/test cases/swift/15 main in single-file library/main.swift @@ -0,0 +1,3 @@ +import CProgram + +precondition(callMe() == 4) diff --git a/test cases/swift/15 main in single-file library/meson.build b/test cases/swift/15 main in single-file library/meson.build new file mode 100644 index 0000000..2e1202e --- /dev/null +++ b/test cases/swift/15 main in single-file library/meson.build @@ -0,0 +1,4 @@ +project('main in single-file library', 'swift', 'c') + +lib = static_library('Library', 'main.swift', include_directories: ['.']) +executable('program', 'program.c', link_with: [lib]) diff --git a/test cases/swift/15 main in single-file library/module.modulemap b/test cases/swift/15 main in single-file library/module.modulemap new file mode 100644 index 0000000..3c1817a --- /dev/null +++ b/test cases/swift/15 main in single-file library/module.modulemap @@ -0,0 +1,3 @@ +module CProgram [extern_c] { + header "program.h" +} diff --git a/test cases/swift/15 main in single-file library/program.c b/test cases/swift/15 main in single-file library/program.c new file mode 100644 index 0000000..8959dae --- /dev/null +++ b/test cases/swift/15 main in single-file library/program.c @@ -0,0 +1,5 @@ +#include "program.h" + +int callMe() { + return 4; +} diff --git a/test cases/swift/15 main in single-file library/program.h b/test cases/swift/15 main in single-file library/program.h new file mode 100644 index 0000000..5058be3 --- /dev/null +++ b/test cases/swift/15 main in single-file library/program.h @@ -0,0 +1 @@ +int callMe(void); diff --git a/test cases/swift/16 main in multi-file library/main.swift b/test cases/swift/16 main in multi-file library/main.swift new file mode 100644 index 0000000..3682e8d --- /dev/null +++ b/test cases/swift/16 main in multi-file library/main.swift @@ -0,0 +1,4 @@ +import CProgram + +precondition(callMe() == 4) +precondition(callMe2() == 6) diff --git a/test cases/swift/16 main in multi-file library/meson.build b/test cases/swift/16 main in multi-file library/meson.build new file mode 100644 index 0000000..4d287f3 --- /dev/null +++ b/test cases/swift/16 main in multi-file library/meson.build @@ -0,0 +1,4 @@ +project('main in multi-file library', 'swift', 'c') + +lib = static_library('Library', 'main.swift', 'more.swift', include_directories: ['.']) +executable('program', 'program.c', link_with: [lib]) diff --git a/test cases/swift/16 main in multi-file library/module.modulemap b/test cases/swift/16 main in multi-file library/module.modulemap new file mode 100644 index 0000000..3c1817a --- /dev/null +++ b/test cases/swift/16 main in multi-file library/module.modulemap @@ -0,0 +1,3 @@ +module CProgram [extern_c] { + header "program.h" +} diff --git a/test cases/swift/16 main in multi-file library/more.swift b/test cases/swift/16 main in multi-file library/more.swift new file mode 100644 index 0000000..716500f --- /dev/null +++ b/test cases/swift/16 main in multi-file library/more.swift @@ -0,0 +1,3 @@ +func callMe2() -> Int { + 6 +} diff --git a/test cases/swift/16 main in multi-file library/program.c b/test cases/swift/16 main in multi-file library/program.c new file mode 100644 index 0000000..8959dae --- /dev/null +++ b/test cases/swift/16 main in multi-file library/program.c @@ -0,0 +1,5 @@ +#include "program.h" + +int callMe() { + return 4; +} diff --git a/test cases/swift/16 main in multi-file library/program.h b/test cases/swift/16 main in multi-file library/program.h new file mode 100644 index 0000000..5058be3 --- /dev/null +++ b/test cases/swift/16 main in multi-file library/program.h @@ -0,0 +1 @@ +int callMe(void); diff --git a/test cases/swift/8 extra args/lib.swift b/test cases/swift/8 extra args/lib.swift new file mode 100644 index 0000000..f8167ad --- /dev/null +++ b/test cases/swift/8 extra args/lib.swift @@ -0,0 +1,3 @@ +public func callMe() { + print("test") +} diff --git a/test cases/swift/8 extra args/main.swift b/test cases/swift/8 extra args/main.swift deleted file mode 100644 index 1ff8e07..0000000 --- a/test cases/swift/8 extra args/main.swift +++ /dev/null @@ -1 +0,0 @@ -print("test") diff --git a/test cases/swift/8 extra args/meson.build b/test cases/swift/8 extra args/meson.build index ead2ff5..d243e36 100644 --- a/test cases/swift/8 extra args/meson.build +++ b/test cases/swift/8 extra args/meson.build @@ -2,8 +2,8 @@ project('extra args', 'swift') trace_fname = 'trace.json' -lib = static_library('main', - 'main.swift', +lib = static_library('lib', + 'lib.swift', swift_args: [ '-emit-loaded-module-trace', '-emit-loaded-module-trace-path', '../' + trace_fname diff --git a/test cases/unit/116 empty project/expected_mods.json b/test cases/unit/116 empty project/expected_mods.json index fa5e0ec..e479bcb 100644 --- a/test cases/unit/116 empty project/expected_mods.json +++ b/test cases/unit/116 empty project/expected_mods.json @@ -181,6 +181,7 @@ "mesonbuild.backend.backends", "mesonbuild.backend.ninjabackend", "mesonbuild.build", + "mesonbuild.cmdline", "mesonbuild.compilers", "mesonbuild.compilers.compilers", "mesonbuild.compilers.detect", @@ -230,15 +231,15 @@ "mesonbuild.programs", "mesonbuild.scripts", "mesonbuild.scripts.meson_exe", + "mesonbuild.tooldetect", "mesonbuild.utils", "mesonbuild.utils.core", "mesonbuild.utils.platform", - "mesonbuild.utils.posix", "mesonbuild.utils.universal", "mesonbuild.utils.vsenv", "mesonbuild.wrap", "mesonbuild.wrap.wrap" ], - "count": 69 + "count": 71 } } diff --git a/test cases/unit/120 rewrite/meson.build b/test cases/unit/120 rewrite/meson.build index 7d0330b..654b09d 100644 --- a/test cases/unit/120 rewrite/meson.build +++ b/test cases/unit/120 rewrite/meson.build @@ -62,6 +62,7 @@ cppcoro = declare_dependency( ) +cpp_compiler = meson.get_compiler('cpp') if get_option('unicode') #if comment #if comment 2 mfc=cpp_compiler.find_library(get_option('debug')?'mfc140ud':'mfc140u') @@ -80,6 +81,10 @@ assert(not (3 in [1, 2]), '''3 shouldn't be in [1, 2]''') assert('b' in ['a', 'b'], ''''b' should be in ['a', 'b']''') assert('c' not in ['a', 'b'], ''''c' shouldn't be in ['a', 'b']''') +exe1 = 'exe1' +exe2 = 'exe2' +exe3 = 'exe3' + assert(exe1 in [exe1, exe2], ''''exe1 should be in [exe1, exe2]''') assert(exe3 not in [exe1, exe2], ''''exe3 shouldn't be in [exe1, exe2]''') @@ -185,5 +190,11 @@ if a \ debug('help!') endif +if false + # Should produce a warning, but should not crash + foo = not_defined + message(not_defined) +endif + # End of file comment with no linebreak
\ No newline at end of file diff --git a/test cases/unit/126 python extension/foo.c b/test cases/unit/126 python extension/foo.c new file mode 100644 index 0000000..e1a0dfb --- /dev/null +++ b/test cases/unit/126 python extension/foo.c @@ -0,0 +1,31 @@ +#define PY_SSIZE_T_CLEAN +#include <Python.h> + + +static PyObject * +bar_impl(PyObject *self, PyObject *args) +{ + return Py_None; +} + + +static PyMethodDef foo_methods[] = { + {"bar", bar_impl, METH_NOARGS, NULL}, + {NULL, NULL, 0, NULL} /* sentinel */ +}; + + +static struct PyModuleDef foo_module = { + PyModuleDef_HEAD_INIT, + "foo", /* m_name */ + NULL, /* m_doc */ + -1, /* m_size */ + foo_methods, /* m_methods */ +}; + + +PyMODINIT_FUNC +PyInit_foo(void) +{ + return PyModule_Create(&foo_module); +} diff --git a/test cases/unit/126 python extension/meson.build b/test cases/unit/126 python extension/meson.build new file mode 100644 index 0000000..8cebcea --- /dev/null +++ b/test cases/unit/126 python extension/meson.build @@ -0,0 +1,20 @@ +project('python extension', 'c', meson_version : '>=1.3.0') + +py = import('python').find_installation('') + +py.extension_module( + 'foo', 'foo.c', + install: true, +) + +limited_api_supported = true +if py.language_version().version_compare('>=3.13') and py.language_version().version_compare('<3.15') + limited_api_supported = py.get_variable('Py_GIL_DISABLED') != 1 +endif +if limited_api_supported + py.extension_module( + 'foo_stable', 'foo.c', + install: true, + limited_api: '3.2', + ) +endif diff --git a/test cases/unit/125 pkgsubproj/meson.build b/test cases/unit/127 pkgsubproj/meson.build index b4cf89f..b4cf89f 100644 --- a/test cases/unit/125 pkgsubproj/meson.build +++ b/test cases/unit/127 pkgsubproj/meson.build diff --git a/test cases/unit/125 pkgsubproj/subprojects/sub/meson.build b/test cases/unit/127 pkgsubproj/subprojects/sub/meson.build index 99622b6..99622b6 100644 --- a/test cases/unit/125 pkgsubproj/subprojects/sub/meson.build +++ b/test cases/unit/127 pkgsubproj/subprojects/sub/meson.build diff --git a/test cases/unit/126 test slice/meson.build b/test cases/unit/128 test slice/meson.build index a41c2f6..a41c2f6 100644 --- a/test cases/unit/126 test slice/meson.build +++ b/test cases/unit/128 test slice/meson.build diff --git a/test cases/unit/128 test slice/test.py b/test cases/unit/128 test slice/test.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test cases/unit/128 test slice/test.py diff --git a/test cases/unit/127 sanitizers/meson.build b/test cases/unit/129 sanitizers/meson.build index b42fb35..b42fb35 100644 --- a/test cases/unit/127 sanitizers/meson.build +++ b/test cases/unit/129 sanitizers/meson.build diff --git a/test cases/unit/128 long opt vs D/meson.build b/test cases/unit/130 long opt vs D/meson.build index e05d88d..e05d88d 100644 --- a/test cases/unit/128 long opt vs D/meson.build +++ b/test cases/unit/130 long opt vs D/meson.build diff --git a/test cases/unit/128 long opt vs D/meson_options.txt b/test cases/unit/130 long opt vs D/meson_options.txt index 255bf15..255bf15 100644 --- a/test cases/unit/128 long opt vs D/meson_options.txt +++ b/test cases/unit/130 long opt vs D/meson_options.txt diff --git a/test cases/unit/131 vala internal glib/lib.vala b/test cases/unit/131 vala internal glib/lib.vala new file mode 100644 index 0000000..e62e632 --- /dev/null +++ b/test cases/unit/131 vala internal glib/lib.vala @@ -0,0 +1,3 @@ +public int func() { + return 42; +} diff --git a/test cases/unit/131 vala internal glib/meson.build b/test cases/unit/131 vala internal glib/meson.build new file mode 100644 index 0000000..9479082 --- /dev/null +++ b/test cases/unit/131 vala internal glib/meson.build @@ -0,0 +1,21 @@ +project('vala internal glib') + +if not add_languages('vala', required: false) + error('MESON_SKIP_TEST valac not installed') +endif + +glib_ver = get_option('glib-version') +if glib_ver == 'unset' + error('Required to set -Dglib-version') +endif + +glib_dep = declare_dependency(version: glib_ver) +meson.override_dependency('glib-2.0', glib_dep) + +named_glib_dep = dependency('glib-2.0') + +assert(named_glib_dep.type_name() == 'internal') +assert(glib_dep == named_glib_dep) + +tgt = static_library('vala-tgt', 'lib.vala', + dependencies: named_glib_dep) diff --git a/test cases/unit/131 vala internal glib/meson.options b/test cases/unit/131 vala internal glib/meson.options new file mode 100644 index 0000000..f8a1ece --- /dev/null +++ b/test cases/unit/131 vala internal glib/meson.options @@ -0,0 +1 @@ +option('glib-version', type: 'string', value: 'unset') diff --git a/test cases/unit/132 custom target index test/meson.build b/test cases/unit/132 custom target index test/meson.build new file mode 100644 index 0000000..adbbb4d --- /dev/null +++ b/test cases/unit/132 custom target index test/meson.build @@ -0,0 +1,16 @@ +# testcase by blue42u +project('test') +python3 = find_program('python3') + +gen_code = ''' +import sys, pathlib +pathlib.Path(sys.argv[1]).write_text("foo") +pathlib.Path(sys.argv[2]).write_text("bar")''' +foobar_txt = custom_target(command: [python3, '-c', gen_code, '@OUTPUT@'], output: ['foo.txt', 'bar.txt']) + +test_code = ''' +import sys, pathlib +sys.exit(0 if pathlib.Path(sys.argv[1]).read_text() == sys.argv[2] else 4)''' + +test('foo.txt', python3, args: ['-c', test_code, foobar_txt[0], 'foo']) +test('bar.txt', python3, args: ['-c', test_code, foobar_txt[1], 'bar']) diff --git a/test cases/unit/56 introspection/meson.build b/test cases/unit/56 introspection/meson.build index 568d5cc..c7856fe 100644 --- a/test cases/unit/56 introspection/meson.build +++ b/test cases/unit/56 introspection/meson.build @@ -58,6 +58,21 @@ test('test case 1', t1) test('test case 2', t2, depends: t3) benchmark('benchmark 1', t3, args: [cus1, cus2, cus3]) +### BEGIN: Stuff to test the handling of `UnknownValue` +var_1 = 1 +var_2 = 2 +unknown_var = 'var_1' +if host_machine.system() == 'windows' + unknown_var = 'var_2' +endif +# The IntrospectionInterpreter can't know the value of unknown_var and use the `UnknownValue` class. + +message(get_variable(unknown_var)) + +dependency(unknown_var, version: [unknown_var], required: false) +dependency(unknown_var, version: unknown_var, required: false) +### END: Stuff to test the handling of `UnknownValue` + ### Stuff to test the AST JSON printer foreach x : ['a', 'b', 'c'] if x == 'a' diff --git a/test cases/unit/58 introspect buildoptions/meson.build b/test cases/unit/58 introspect buildoptions/meson.build index 36e03e0..a6a8b86 100644 --- a/test cases/unit/58 introspect buildoptions/meson.build +++ b/test cases/unit/58 introspect buildoptions/meson.build @@ -14,5 +14,13 @@ if r.returncode() != 0 error('FAILED') endif +name_prefix = 'lib' +if get_option('buildtype') == 'release' + # ensure that these variables become an UnkownValue + name_prefix = [] +endif + +static_library('hello', 'hello.c', name_prefix: name_prefix) + add_languages(r.stdout().strip(), required: true) add_languages('afgggergearvearghergervergreaergaergasv', required: false) diff --git a/test cases/unit/69 cross/crossfile.in b/test cases/unit/69 cross/crossfile.in index 678e8d3..beab9bc 100644 --- a/test cases/unit/69 cross/crossfile.in +++ b/test cases/unit/69 cross/crossfile.in @@ -3,3 +3,6 @@ system = '@system@' cpu_family = '@cpu_family@' cpu = '@cpu@' endian = '@endian@' + +[built-in options] +c_args = ['-funroll-loops'] diff --git a/test cases/unit/69 cross/meson.build b/test cases/unit/69 cross/meson.build index acf4f0f..645d453 100644 --- a/test cases/unit/69 cross/meson.build +++ b/test cases/unit/69 cross/meson.build @@ -1,16 +1,25 @@ project('crosstest') +add_languages('c', native: true) if get_option('generate') conf_data = configuration_data() conf_data.set('system', build_machine.system()) conf_data.set('cpu', build_machine.cpu()) conf_data.set('cpu_family', build_machine.cpu_family()) conf_data.set('endian', build_machine.endian()) + conf_data.set('c_args', '-pedantic') configure_file(input: 'crossfile.in', output: 'crossfile', configuration: conf_data) - message('Written cross file') + configure_file(input: 'nativefile.in', + output: 'nativefile', + configuration: conf_data) + message('Written native and cross file') + + add_languages('c', native: false) + assert(get_option('build.c_args') == get_option('c_args')) else assert(meson.is_cross_build(), 'not setup as cross build') + assert(get_option('build.c_args') == ['-pedantic']) endif diff --git a/test cases/unit/69 cross/nativefile.in b/test cases/unit/69 cross/nativefile.in new file mode 100644 index 0000000..9a63999 --- /dev/null +++ b/test cases/unit/69 cross/nativefile.in @@ -0,0 +1,2 @@ +[built-in options] +build.c_args = ['@c_args@'] diff --git a/test cases/unit/92 new subproject in configured project/meson.build b/test cases/unit/92 new subproject in configured project/meson.build index b82aa41..c200e57 100644 --- a/test cases/unit/92 new subproject in configured project/meson.build +++ b/test cases/unit/92 new subproject in configured project/meson.build @@ -1,4 +1,4 @@ -# SPDX-license-identifier: Apache-2.0 +# SPDX-License-Identifier: Apache-2.0 # Copyright © 2021 Intel Corporation project('existing project with new subproject', 'c') diff --git a/test cases/unit/92 new subproject in configured project/meson_options.txt b/test cases/unit/92 new subproject in configured project/meson_options.txt index 12d8395..a928da2 100644 --- a/test cases/unit/92 new subproject in configured project/meson_options.txt +++ b/test cases/unit/92 new subproject in configured project/meson_options.txt @@ -1,3 +1,3 @@ -# SPDX-license-identifier: Apache-2.0 +# SPDX-License-Identifier: Apache-2.0 # Copyright © 2021 Intel Corporation option('use-sub', type : 'boolean', value : false) diff --git a/test cases/unit/92 new subproject in configured project/subprojects/sub/foo.c b/test cases/unit/92 new subproject in configured project/subprojects/sub/foo.c index 9713d9f..1a14f7e 100644 --- a/test cases/unit/92 new subproject in configured project/subprojects/sub/foo.c +++ b/test cases/unit/92 new subproject in configured project/subprojects/sub/foo.c @@ -1,4 +1,4 @@ -/* SPDX-license-identifier: Apache-2.0 */ +/* SPDX-License-Identifier: Apache-2.0 */ /* Copyright © 2021 Intel Corporation */ int func(void) { diff --git a/test cases/unit/92 new subproject in configured project/subprojects/sub/meson.build b/test cases/unit/92 new subproject in configured project/subprojects/sub/meson.build index a833b0c..5f9ac7e 100644 --- a/test cases/unit/92 new subproject in configured project/subprojects/sub/meson.build +++ b/test cases/unit/92 new subproject in configured project/subprojects/sub/meson.build @@ -1,4 +1,4 @@ -# SPDX-license-identifier: Apache-2.0 +# SPDX-License-Identifier: Apache-2.0 # Copyright © 2021 Intel Corporation project('new subproject', 'c') diff --git a/test cases/vala/31 generated ui file subdirectory/meson.build b/test cases/vala/31 generated ui file subdirectory/meson.build new file mode 100644 index 0000000..4210581 --- /dev/null +++ b/test cases/vala/31 generated ui file subdirectory/meson.build @@ -0,0 +1,22 @@ +project('demo', 'c', 'vala') + +gnome = import('gnome', required: false) + +if not gnome.found() + error('MESON_SKIP_TEST: gnome module not supported') +endif + +deps = [ + dependency('glib-2.0', version : '>=2.50'), + dependency('gobject-2.0'), + dependency('gtk+-3.0'), +] + +subdir('subdir') + +executable( + 'demo', + 'test.vala', + resources, + dependencies: deps, +) diff --git a/test cases/vala/31 generated ui file subdirectory/subdir/TestBox.ui.in b/test cases/vala/31 generated ui file subdirectory/subdir/TestBox.ui.in new file mode 100644 index 0000000..bf5c831 --- /dev/null +++ b/test cases/vala/31 generated ui file subdirectory/subdir/TestBox.ui.in @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <requires lib="gtk" version="3.0"/> + <template class="TestBox" parent="GtkBox"> + </template> +</interface> diff --git a/test cases/vala/31 generated ui file subdirectory/subdir/meson.build b/test cases/vala/31 generated ui file subdirectory/subdir/meson.build new file mode 100644 index 0000000..dbe9344 --- /dev/null +++ b/test cases/vala/31 generated ui file subdirectory/subdir/meson.build @@ -0,0 +1,13 @@ +ui_tgt = custom_target( + input: 'TestBox.ui.in', + output: 'TestBox.ui', + command: [find_program('cat')], + feed: true, + capture: true, +) + +resources = gnome.compile_resources('test-resources', + 'test.gresource.xml', + c_name: 'test_res', + dependencies: ui_tgt, +) diff --git a/test cases/vala/31 generated ui file subdirectory/subdir/test.gresource.xml b/test cases/vala/31 generated ui file subdirectory/subdir/test.gresource.xml new file mode 100644 index 0000000..382b951 --- /dev/null +++ b/test cases/vala/31 generated ui file subdirectory/subdir/test.gresource.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/com/mesonbuild/test"> + <file>TestBox.ui</file> + </gresource> +</gresources> diff --git a/test cases/vala/31 generated ui file subdirectory/test.vala b/test cases/vala/31 generated ui file subdirectory/test.vala new file mode 100644 index 0000000..36f565b --- /dev/null +++ b/test cases/vala/31 generated ui file subdirectory/test.vala @@ -0,0 +1,7 @@ +[GtkTemplate (ui = "/com/mesonbuild/test/TestBox.ui")] +class TestBox: Gtk.Box { +} + +int main() { + return 0; +} diff --git a/test cases/vala/32 valaless vapigen/clib.c b/test cases/vala/32 valaless vapigen/clib.c new file mode 100644 index 0000000..a55ecd4 --- /dev/null +++ b/test cases/vala/32 valaless vapigen/clib.c @@ -0,0 +1,5 @@ +#include "clib.h" + +int clib_fun(void) { + return 42; +} diff --git a/test cases/vala/32 valaless vapigen/clib.h b/test cases/vala/32 valaless vapigen/clib.h new file mode 100644 index 0000000..4d855c9 --- /dev/null +++ b/test cases/vala/32 valaless vapigen/clib.h @@ -0,0 +1,3 @@ +#pragma once + +int clib_fun(void); diff --git a/test cases/vala/32 valaless vapigen/meson.build b/test cases/vala/32 valaless vapigen/meson.build new file mode 100644 index 0000000..22a99e5 --- /dev/null +++ b/test cases/vala/32 valaless vapigen/meson.build @@ -0,0 +1,34 @@ +project('valaless-vapigen', 'c') + +if host_machine.system() == 'cygwin' + error('MESON_SKIP_TEST Does not work with the Vala currently packaged in cygwin') +endif + +gnome = import('gnome') + +clib_src = [ + 'clib.c', + 'clib.h' +] + +clib_lib = shared_library('clib', clib_src) + +clib_gir = gnome.generate_gir(clib_lib, + sources: clib_src, + namespace: 'Clib', + nsversion: '0', + header: 'clib.h', + symbol_prefix: 'clib' +) + +clib_vapi = gnome.generate_vapi('clib', sources: clib_gir[0]) + +clib_dep = declare_dependency( + include_directories: include_directories('.'), + link_with: clib_lib, + sources: clib_gir, + dependencies: clib_vapi +) + + +test('clib-test', executable('clib-test', 'test_clib.c', dependencies: clib_dep)) diff --git a/test cases/vala/32 valaless vapigen/test_clib.c b/test cases/vala/32 valaless vapigen/test_clib.c new file mode 100644 index 0000000..6fd426c --- /dev/null +++ b/test cases/vala/32 valaless vapigen/test_clib.c @@ -0,0 +1,9 @@ +#include <stdlib.h> +#include <clib.h> + +int main(void) { + if (clib_fun () == 42) + return EXIT_SUCCESS; + else + return EXIT_FAILURE; +} diff --git a/test cases/vala/9 gir/meson.build b/test cases/vala/9 gir/meson.build index 3320527..c70938b 100644 --- a/test cases/vala/9 gir/meson.build +++ b/test cases/vala/9 gir/meson.build @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright © 2025 Intel Corporation + project('foo', 'c', 'vala') glib = dependency('glib-2.0') @@ -12,6 +15,6 @@ foo = shared_library('foo', 'foo.vala', custom_target('foo-typelib', command: [g_ir_compiler, '--output', '@OUTPUT@', '@INPUT@'], - input: meson.current_build_dir() + '/Foo-1.0.gir', + input: foo.vala_gir(), output: 'Foo-1.0.typelib', - depends: foo) + build_by_default : true) diff --git a/test cases/warning/9 meson.options/meson.build b/test cases/warning/8 meson.options/meson.build index 59c3872..59c3872 100644 --- a/test cases/warning/9 meson.options/meson.build +++ b/test cases/warning/8 meson.options/meson.build diff --git a/test cases/warning/9 meson.options/meson.options b/test cases/warning/8 meson.options/meson.options index b84ee83..b84ee83 100644 --- a/test cases/warning/9 meson.options/meson.options +++ b/test cases/warning/8 meson.options/meson.options diff --git a/test cases/warning/9 meson.options/subprojects/no-warn/meson.build b/test cases/warning/8 meson.options/subprojects/no-warn/meson.build index f86fbf7..f86fbf7 100644 --- a/test cases/warning/9 meson.options/subprojects/no-warn/meson.build +++ b/test cases/warning/8 meson.options/subprojects/no-warn/meson.build diff --git a/test cases/warning/9 meson.options/subprojects/no-warn/meson.options b/test cases/warning/8 meson.options/subprojects/no-warn/meson.options index b84ee83..b84ee83 100644 --- a/test cases/warning/9 meson.options/subprojects/no-warn/meson.options +++ b/test cases/warning/8 meson.options/subprojects/no-warn/meson.options diff --git a/test cases/warning/9 meson.options/subprojects/no-warn/meson_options.txt b/test cases/warning/8 meson.options/subprojects/no-warn/meson_options.txt index b84ee83..b84ee83 100644 --- a/test cases/warning/9 meson.options/subprojects/no-warn/meson_options.txt +++ b/test cases/warning/8 meson.options/subprojects/no-warn/meson_options.txt diff --git a/test cases/warning/9 meson.options/test.json b/test cases/warning/8 meson.options/test.json index f711924..f711924 100644 --- a/test cases/warning/9 meson.options/test.json +++ b/test cases/warning/8 meson.options/test.json diff --git a/test cases/warning/8 target with no sources/meson.build b/test cases/warning/9 target with no sources/meson.build index 2a8ee11..2a8ee11 100644 --- a/test cases/warning/8 target with no sources/meson.build +++ b/test cases/warning/9 target with no sources/meson.build diff --git a/test cases/warning/8 target with no sources/test.json b/test cases/warning/9 target with no sources/test.json index 30e5b05..30e5b05 100644 --- a/test cases/warning/8 target with no sources/test.json +++ b/test cases/warning/9 target with no sources/test.json diff --git a/test cases/windows/21 masm/meson.build b/test cases/windows/21 masm/meson.build index b6b8fbb..5335a0d 100644 --- a/test cases/windows/21 masm/meson.build +++ b/test cases/windows/21 masm/meson.build @@ -1,9 +1,5 @@ project('test-masm', 'c') -if get_option('backend').startswith('vs') - error('MESON_SKIP_TEST: masm is not supported by vs backend') -endif - cc = meson.get_compiler('c') # MASM must be found when using MSVC, otherwise it is optional diff --git a/test cases/windows/25 embed manifest/DPIAware.manifest b/test cases/windows/25 embed manifest/DPIAware.manifest new file mode 100644 index 0000000..f2708ec --- /dev/null +++ b/test cases/windows/25 embed manifest/DPIAware.manifest @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3"> + <asmv3:application> + <asmv3:windowsSettings> + <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware> + <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> + </asmv3:windowsSettings> + </asmv3:application> +</assembly> diff --git a/test cases/windows/25 embed manifest/meson.build b/test cases/windows/25 embed manifest/meson.build new file mode 100644 index 0000000..0f4c9b4 --- /dev/null +++ b/test cases/windows/25 embed manifest/meson.build @@ -0,0 +1,11 @@ +project('can-manifests-be-embedded', 'c') + +cc = meson.get_compiler('c') + +if cc.get_linker_id() not in ['link', 'lld-link', 'xilink'] # cc.get_linker_argument_syntax() != 'link' + error('MESON_SKIP_TEST: test is only relevant for the Microsoft linker') +endif + +# Ensure that the manifest can be embedded +executable('prog', 'prog.c', + link_args: ['/MANIFEST:EMBED', '/MANIFESTINPUT:' + meson.project_source_root() / 'DPIAware.manifest']) diff --git a/test cases/windows/25 embed manifest/prog.c b/test cases/windows/25 embed manifest/prog.c new file mode 100644 index 0000000..b1d9c2c --- /dev/null +++ b/test cases/windows/25 embed manifest/prog.c @@ -0,0 +1,3 @@ +int main(int argc, char *argv[]) { + return 0; +} diff --git a/tools/dircondenser.py b/tools/dircondenser.py index b8679a4..a07ede1 100755 --- a/tools/dircondenser.py +++ b/tools/dircondenser.py @@ -71,6 +71,7 @@ def condense(dirname: str) -> None: replace_source('run_unittests.py', replacements) replace_source('run_project_tests.py', replacements) replace_source('run_format_tests.py', replacements) + replace_source('run_shell_checks.py', replacements) for f in glob('unittests/*.py'): replace_source(f, replacements) diff --git a/tools/run_with_cov.py b/tools/run_with_cov.py deleted file mode 100755 index 0d3fba6..0000000 --- a/tools/run_with_cov.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-License-Identifier: Apache-2.0 -# Copyright 2021 The Meson development team - -import subprocess -import coverage -import os -import sys -from pathlib import Path - -root_path = Path(__file__).parent.parent.absolute() - -# Python magic so we can import mesonlib -sys.path.append(root_path.as_posix()) -from mesonbuild import mesonlib - -def generate_coveragerc() -> Path: - i_file = (root_path / 'data' / '.coveragerc.in') - o_file = (root_path / '.coveragerc') - raw = i_file.read_text(encoding='utf-8') - raw = raw.replace('@ROOT@', root_path.as_posix()) - o_file.write_text(raw, encoding='utf-8') - return o_file - -def main() -> int: - # Remove old run data - out_dir = root_path / '.coverage' - mesonlib.windows_proof_rmtree(out_dir.as_posix()) - out_dir.mkdir(parents=True, exist_ok=True) - - # Setup coverage - python_path = (root_path / 'ci').as_posix() - os.environ['PYTHONPATH'] = os.pathsep.join([python_path, os.environ.get('PYTHONPATH', '')]) - os.environ['COVERAGE_PROCESS_START'] = generate_coveragerc().as_posix() - coverage.process_startup() - - # Run the actual command - cmd = mesonlib.python_command + sys.argv[1:] - return subprocess.run(cmd, env=os.environ.copy()).returncode - -if __name__ == '__main__': - raise SystemExit(main()) diff --git a/unittests/__init__.py b/unittests/__init__.py index e69de29..fb8fb8e 100644 --- a/unittests/__init__.py +++ b/unittests/__init__.py @@ -0,0 +1,20 @@ +import os + +import mesonbuild.compilers +from mesonbuild.mesonlib import setup_vsenv + +def unset_envs(): + # For unit tests we must fully control all command lines + # so that there are no unexpected changes coming from the + # environment, for example when doing a package build. + varnames = ['CPPFLAGS', 'LDFLAGS'] + list(mesonbuild.compilers.compilers.CFLAGS_MAPPING.values()) + for v in varnames: + if v in os.environ: + del os.environ[v] + +def set_envs(): + os.environ.setdefault('MESON_UNIT_TEST_BACKEND', 'ninja') + +setup_vsenv() +unset_envs() +set_envs() diff --git a/unittests/allplatformstests.py b/unittests/allplatformstests.py index 0618b20..1304658 100644 --- a/unittests/allplatformstests.py +++ b/unittests/allplatformstests.py @@ -2,6 +2,7 @@ # Copyright 2016-2021 The Meson development team # Copyright © 2023-2025 Intel Corporation +import itertools import subprocess import re import json @@ -13,6 +14,7 @@ import platform import pickle import zipfile, tarfile import sys +import sysconfig from unittest import mock, SkipTest, skipIf, skipUnless, expectedFailure from contextlib import contextmanager from glob import glob @@ -29,7 +31,7 @@ import mesonbuild.coredata import mesonbuild.machinefile import mesonbuild.modules.gnome from mesonbuild.mesonlib import ( - BuildDirLock, MachineChoice, is_windows, is_osx, is_cygwin, is_dragonflybsd, + DirectoryLock, DirectoryLockAction, MachineChoice, is_windows, is_osx, is_cygwin, is_dragonflybsd, is_sunos, windows_proof_rmtree, python_command, version_compare, split_args, quote_arg, relpath, is_linux, git, search_version, do_conf_file, do_conf_str, default_prefix, MesonException, EnvironmentException, @@ -45,7 +47,7 @@ from mesonbuild.compilers.c import VisualStudioCCompiler, ClangClCCompiler from mesonbuild.compilers.cpp import VisualStudioCPPCompiler, ClangClCPPCompiler from mesonbuild.compilers import ( detect_static_linker, detect_c_compiler, compiler_from_language, - detect_compiler_for + detect_compiler_for, lang_suffixes ) from mesonbuild.linkers import linkers @@ -222,6 +224,47 @@ class AllPlatformTests(BasePlatformTests): confdata.values = {'VAR': (['value'], 'description')} self.assertRaises(MesonException, conf_str, ['#mesondefine VAR'], confdata, 'meson') + def test_cmake_configuration(self): + if self.backend is not Backend.ninja: + raise SkipTest('ninja backend needed to configure with cmake') + + cmake = ExternalProgram('cmake') + if not cmake.found(): + raise SkipTest('cmake not available') + + cmake_version = cmake.get_version() + if not version_compare(cmake_version, '>=3.13.5'): + raise SkipTest('cmake is too old') + + with tempfile.TemporaryDirectory() as tmpdir: + srcdir = os.path.join(tmpdir, 'src') + + shutil.copytree(os.path.join(self.src_root, 'test cases', 'common', '14 configure file'), srcdir) + self.init(srcdir) + + cmake_builddir = os.path.join(srcdir, "cmake_builddir") + self.assertNotEqual(self.builddir, cmake_builddir) + self._run([cmake.path, '-G', 'Ninja', '-S', srcdir, '-B', cmake_builddir]) + + header_list = [ + 'config7.h', + 'config10.h', + ] + + for header in header_list: + meson_header = "" + cmake_header = "" + + with open(os.path.join(self.builddir, header), encoding='utf-8') as f: + meson_header = f.read() + + cmake_header_path = os.path.join(cmake_builddir, header) + with open(os.path.join(cmake_builddir, header), encoding='utf-8') as f: + cmake_header = f.read() + + self.assertTrue(cmake_header, f'cmake generated header {header} is empty') + self.assertEqual(cmake_header, meson_header) + def test_absolute_prefix_libdir(self): ''' Tests that setting absolute paths for --prefix and --libdir work. Can't @@ -853,7 +896,13 @@ class AllPlatformTests(BasePlatformTests): self._run(command) self.assertEqual(0, failure_count, 'Expected %d tests to fail.' % failure_count) except subprocess.CalledProcessError as e: - self.assertEqual(e.returncode, failure_count) + actual_fails = 0 + with open(os.path.join(self.logdir, 'testlog.json'), encoding='utf-8') as f: + for line in f: + res = json.loads(line) + if res['is_fail']: + actual_fails += 1 + self.assertEqual(actual_fails, failure_count) def test_suite_selection(self): testdir = os.path.join(self.unit_test_dir, '4 suite selection') @@ -869,11 +918,11 @@ class AllPlatformTests(BasePlatformTests): self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'mainprj']) self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc']) - self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail']) + self.assertFailedTestCount(2, self.mtest_command + ['--suite', 'subprjfail']) self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjmix']) self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'mainprj']) self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'subprjsucc']) - self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjfail']) + self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjfail']) self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjmix']) self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'mainprj:fail']) @@ -896,9 +945,9 @@ class AllPlatformTests(BasePlatformTests): self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjmix:fail']) self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'subprjmix:success']) - self.assertFailedTestCount(2, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix:fail']) - self.assertFailedTestCount(3, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj']) - self.assertFailedTestCount(2, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj', '--no-suite', 'subprjmix:fail']) + self.assertFailedTestCount(3, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix:fail']) + self.assertFailedTestCount(4, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj']) + self.assertFailedTestCount(3, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj', '--no-suite', 'subprjmix:fail']) self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj', '--no-suite', 'subprjmix:fail', 'mainprj-failing_test']) self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjfail:fail', '--no-suite', 'subprjmix:fail']) @@ -912,7 +961,7 @@ class AllPlatformTests(BasePlatformTests): self.utime(os.path.join(testdir, 'meson.build')) o = self._run(self.mtest_command + ['--list']) self.assertIn('Regenerating build files', o) - self.assertIn('test_features / xfail', o) + self.assertIn('test_features:xfail', o) o = self._run(self.mtest_command + ['--list']) self.assertNotIn('Regenerating build files', o) # no real targets should have been built @@ -1012,7 +1061,7 @@ class AllPlatformTests(BasePlatformTests): def test_internal_include_order(self): - if mesonbuild.environment.detect_msys2_arch() and ('MESON_RSP_THRESHOLD' in os.environ): + if mesonbuild.envconfig.detect_msys2_arch() and ('MESON_RSP_THRESHOLD' in os.environ): raise SkipTest('Test does not yet support gcc rsp files on msys2') testdir = os.path.join(self.common_test_dir, '130 include order') @@ -1099,110 +1148,144 @@ class AllPlatformTests(BasePlatformTests): for lang, evar in langs: # Detect with evar and do sanity checks on that if evar in os.environ: - ecc = compiler_from_language(env, lang, MachineChoice.HOST) - self.assertTrue(ecc.version) - elinker = detect_static_linker(env, ecc) - # Pop it so we don't use it for the next detection - evalue = os.environ.pop(evar) - # Very rough/strict heuristics. Would never work for actual - # compiler detection, but should be ok for the tests. - ebase = os.path.basename(evalue) - if ebase.startswith('g') or ebase.endswith(('-gcc', '-g++')): - self.assertIsInstance(ecc, gnu) - self.assertIsInstance(elinker, ar) - elif 'clang-cl' in ebase: - self.assertIsInstance(ecc, clangcl) - self.assertIsInstance(elinker, lib) - elif 'clang' in ebase: - self.assertIsInstance(ecc, clang) - self.assertIsInstance(elinker, ar) - elif ebase.startswith('ic'): - self.assertIsInstance(ecc, intel) - self.assertIsInstance(elinker, ar) - elif ebase.startswith('cl'): - self.assertIsInstance(ecc, msvc) - self.assertIsInstance(elinker, lib) - else: - raise AssertionError(f'Unknown compiler {evalue!r}') - # Check that we actually used the evalue correctly as the compiler - self.assertEqual(ecc.get_exelist(), split_args(evalue)) + with self.subTest(lang=lang, evar=evar): + try: + ecc = compiler_from_language(env, lang, MachineChoice.HOST) + except EnvironmentException: + # always raise in ci, we expect to have a valid ObjC and ObjC++ compiler of some kind + if is_ci(): + self.fail(f'Could not find a compiler for {lang}') + if sys.version_info < (3, 11): + continue + self.skipTest(f'No valid compiler for {lang}.') + finally: + # Pop it so we don't use it for the next detection + evalue = os.environ.pop(evar) + assert ecc is not None, "Something went really wrong" + self.assertTrue(ecc.version) + elinker = detect_static_linker(env, ecc) + # Very rough/strict heuristics. Would never work for actual + # compiler detection, but should be ok for the tests. + ebase = os.path.basename(evalue) + if ebase.startswith('g') or ebase.endswith(('-gcc', '-g++')): + self.assertIsInstance(ecc, gnu) + self.assertIsInstance(elinker, ar) + elif 'clang-cl' in ebase: + self.assertIsInstance(ecc, clangcl) + self.assertIsInstance(elinker, lib) + elif 'clang' in ebase: + self.assertIsInstance(ecc, clang) + self.assertIsInstance(elinker, ar) + elif ebase.startswith('ic'): + self.assertIsInstance(ecc, intel) + self.assertIsInstance(elinker, ar) + elif ebase.startswith('cl'): + self.assertIsInstance(ecc, msvc) + self.assertIsInstance(elinker, lib) + else: + self.fail(f'Unknown compiler {evalue!r}') + # Check that we actually used the evalue correctly as the compiler + self.assertEqual(ecc.get_exelist(), split_args(evalue)) + # Do auto-detection of compiler based on platform, PATH, etc. - cc = compiler_from_language(env, lang, MachineChoice.HOST) - self.assertTrue(cc.version) - linker = detect_static_linker(env, cc) - # Check compiler type - if isinstance(cc, gnu): - self.assertIsInstance(linker, ar) - if is_osx(): - self.assertIsInstance(cc.linker, linkers.AppleDynamicLinker) - elif is_sunos(): - self.assertIsInstance(cc.linker, (linkers.SolarisDynamicLinker, linkers.GnuLikeDynamicLinkerMixin)) - else: - self.assertIsInstance(cc.linker, linkers.GnuLikeDynamicLinkerMixin) - if isinstance(cc, clangcl): - self.assertIsInstance(linker, lib) - self.assertIsInstance(cc.linker, linkers.ClangClDynamicLinker) - if isinstance(cc, clang): - self.assertIsInstance(linker, ar) - if is_osx(): - self.assertIsInstance(cc.linker, linkers.AppleDynamicLinker) - elif is_windows(): - # This is clang, not clang-cl. This can be either an - # ld-like linker of link.exe-like linker (usually the - # former for msys2, the latter otherwise) - self.assertIsInstance(cc.linker, (linkers.MSVCDynamicLinker, linkers.GnuLikeDynamicLinkerMixin)) - elif is_sunos(): - self.assertIsInstance(cc.linker, (linkers.SolarisDynamicLinker, linkers.GnuLikeDynamicLinkerMixin)) - else: - self.assertIsInstance(cc.linker, linkers.GnuLikeDynamicLinkerMixin) - if isinstance(cc, intel): - self.assertIsInstance(linker, ar) - if is_osx(): - self.assertIsInstance(cc.linker, linkers.AppleDynamicLinker) - elif is_windows(): - self.assertIsInstance(cc.linker, linkers.XilinkDynamicLinker) - else: - self.assertIsInstance(cc.linker, linkers.GnuDynamicLinker) - if isinstance(cc, msvc): - self.assertTrue(is_windows()) - self.assertIsInstance(linker, lib) - self.assertEqual(cc.id, 'msvc') - self.assertTrue(hasattr(cc, 'is_64')) - self.assertIsInstance(cc.linker, linkers.MSVCDynamicLinker) - # If we're on Windows CI, we know what the compiler will be - if 'arch' in os.environ: - if os.environ['arch'] == 'x64': - self.assertTrue(cc.is_64) + with self.subTest(lang=lang): + try: + cc = compiler_from_language(env, lang, MachineChoice.HOST) + except EnvironmentException: + # always raise in ci, we expect to have a valid ObjC and ObjC++ compiler of some kind + if is_ci(): + self.fail(f'Could not find a compiler for {lang}') + if sys.version_info < (3, 11): + continue + self.skipTest(f'No valid compiler for {lang}.') + assert cc is not None, "Something went really wrong" + self.assertTrue(cc.version) + linker = detect_static_linker(env, cc) + # Check compiler type + if isinstance(cc, gnu): + self.assertIsInstance(linker, ar) + if is_osx(): + self.assertIsInstance(cc.linker, linkers.AppleDynamicLinker) + elif is_sunos(): + self.assertIsInstance(cc.linker, (linkers.SolarisDynamicLinker, linkers.GnuLikeDynamicLinkerMixin)) + else: + self.assertIsInstance(cc.linker, linkers.GnuLikeDynamicLinkerMixin) + if isinstance(cc, clangcl): + self.assertIsInstance(linker, lib) + self.assertIsInstance(cc.linker, linkers.ClangClDynamicLinker) + if isinstance(cc, clang): + self.assertIsInstance(linker, ar) + if is_osx(): + self.assertIsInstance(cc.linker, linkers.AppleDynamicLinker) + elif is_windows(): + # This is clang, not clang-cl. This can be either an + # ld-like linker of link.exe-like linker (usually the + # former for msys2, the latter otherwise) + self.assertIsInstance(cc.linker, (linkers.MSVCDynamicLinker, linkers.GnuLikeDynamicLinkerMixin)) + elif is_sunos(): + self.assertIsInstance(cc.linker, (linkers.SolarisDynamicLinker, linkers.GnuLikeDynamicLinkerMixin)) else: - self.assertFalse(cc.is_64) + self.assertIsInstance(cc.linker, linkers.GnuLikeDynamicLinkerMixin) + if isinstance(cc, intel): + self.assertIsInstance(linker, ar) + if is_osx(): + self.assertIsInstance(cc.linker, linkers.AppleDynamicLinker) + elif is_windows(): + self.assertIsInstance(cc.linker, linkers.XilinkDynamicLinker) + else: + self.assertIsInstance(cc.linker, linkers.GnuDynamicLinker) + if isinstance(cc, msvc): + self.assertTrue(is_windows()) + self.assertIsInstance(linker, lib) + self.assertEqual(cc.id, 'msvc') + self.assertTrue(hasattr(cc, 'is_64')) + self.assertIsInstance(cc.linker, linkers.MSVCDynamicLinker) + # If we're on Windows CI, we know what the compiler will be + if 'arch' in os.environ: + if os.environ['arch'] == 'x64': + self.assertTrue(cc.is_64) + else: + self.assertFalse(cc.is_64) + # Set evar ourselves to a wrapper script that just calls the same # exelist + some argument. This is meant to test that setting # something like `ccache gcc -pipe` or `distcc ccache gcc` works. - wrapper = os.path.join(testdir, 'compiler wrapper.py') - wrappercc = python_command + [wrapper] + cc.get_exelist() + ['-DSOME_ARG'] - os.environ[evar] = ' '.join(quote_arg(w) for w in wrappercc) - - # Check static linker too - wrapperlinker = python_command + [wrapper] + linker.get_exelist() + linker.get_always_args() - os.environ['AR'] = ' '.join(quote_arg(w) for w in wrapperlinker) - - # Need a new env to re-run environment loading - env = get_fake_env(testdir, self.builddir, self.prefix) - - wcc = compiler_from_language(env, lang, MachineChoice.HOST) - wlinker = detect_static_linker(env, wcc) - # Pop it so we don't use it for the next detection - os.environ.pop('AR') - # Must be the same type since it's a wrapper around the same exelist - self.assertIs(type(cc), type(wcc)) - self.assertIs(type(linker), type(wlinker)) - # Ensure that the exelist is correct - self.assertEqual(wcc.get_exelist(), wrappercc) - self.assertEqual(wlinker.get_exelist(), wrapperlinker) - # Ensure that the version detection worked correctly - self.assertEqual(cc.version, wcc.version) - if hasattr(cc, 'is_64'): - self.assertEqual(cc.is_64, wcc.is_64) + with self.subTest('wrapper script', lang=lang): + wrapper = os.path.join(testdir, 'compiler wrapper.py') + wrappercc = python_command + [wrapper] + cc.get_exelist() + ['-DSOME_ARG'] + os.environ[evar] = ' '.join(quote_arg(w) for w in wrappercc) + + # Check static linker too + wrapperlinker = python_command + [wrapper] + linker.get_exelist() + linker.get_always_args() + os.environ['AR'] = ' '.join(quote_arg(w) for w in wrapperlinker) + + # Need a new env to re-run environment loading + env = get_fake_env(testdir, self.builddir, self.prefix) + + try: + wcc = compiler_from_language(env, lang, MachineChoice.HOST) + except EnvironmentException: + # always raise in ci, we expect to have a valid ObjC and ObjC++ compiler of some kind + if is_ci(): + self.fail(f'Could not find a compiler for {lang}') + if sys.version_info < (3, 11): + continue + self.skipTest(f'No valid compiler for {lang}.') + wlinker = detect_static_linker(env, wcc) + del os.environ['AR'] + + # Must be the same type since it's a wrapper around the same exelist + self.assertIs(type(cc), type(wcc)) + self.assertIs(type(linker), type(wlinker)) + + # Ensure that the exelist is correct + self.assertEqual(wcc.get_exelist(), wrappercc) + self.assertEqual(wlinker.get_exelist(), wrapperlinker) + + # Ensure that the version detection worked correctly + self.assertEqual(cc.version, wcc.version) + if hasattr(cc, 'is_64'): + self.assertEqual(cc.is_64, wcc.is_64) def test_always_prefer_c_compiler_for_asm(self): testdir = os.path.join(self.common_test_dir, '133 c cpp and asm') @@ -1367,7 +1450,7 @@ class AllPlatformTests(BasePlatformTests): Test that conflicts between -D for builtin options and the corresponding long option are detected without false positives or negatives. ''' - testdir = os.path.join(self.unit_test_dir, '128 long opt vs D') + testdir = os.path.join(self.unit_test_dir, '130 long opt vs D') for opt in ['-Dsysconfdir=/etc', '-Dsysconfdir2=/etc']: exception_raised = False @@ -2001,8 +2084,8 @@ class AllPlatformTests(BasePlatformTests): against what was detected in the binary. ''' env, cc = get_convincing_fake_env_and_cc(self.builddir, self.prefix) - expected_uscore = cc._symbols_have_underscore_prefix_searchbin(env) - list_uscore = cc._symbols_have_underscore_prefix_list(env) + expected_uscore = cc._symbols_have_underscore_prefix_searchbin() + list_uscore = cc._symbols_have_underscore_prefix_list() if list_uscore is not None: self.assertEqual(list_uscore, expected_uscore) else: @@ -2014,8 +2097,8 @@ class AllPlatformTests(BasePlatformTests): against what was detected in the binary. ''' env, cc = get_convincing_fake_env_and_cc(self.builddir, self.prefix) - expected_uscore = cc._symbols_have_underscore_prefix_searchbin(env) - define_uscore = cc._symbols_have_underscore_prefix_define(env) + expected_uscore = cc._symbols_have_underscore_prefix_searchbin() + define_uscore = cc._symbols_have_underscore_prefix_define() if define_uscore is not None: self.assertEqual(define_uscore, expected_uscore) else: @@ -2390,10 +2473,10 @@ class AllPlatformTests(BasePlatformTests): # lexer/parser/interpreter we have tests for. for (t, f) in [ ('10 out of bounds', 'meson.build'), - ('18 wrong plusassign', 'meson.build'), - ('56 bad option argument', 'meson_options.txt'), - ('94 subdir parse error', os.path.join('subdir', 'meson.build')), - ('95 invalid option file', 'meson_options.txt'), + ('17 wrong plusassign', 'meson.build'), + ('55 bad option argument', 'meson_options.txt'), + ('93 subdir parse error', os.path.join('subdir', 'meson.build')), + ('94 invalid option file', 'meson_options.txt'), ]: tdir = os.path.join(self.src_root, 'test cases', 'failing', t) @@ -2421,17 +2504,17 @@ class AllPlatformTests(BasePlatformTests): self.assertIn('ERROR: compiler.has_header_symbol got unknown keyword arguments "prefixxx"', cm.exception.output) def test_templates(self): - ninja = mesonbuild.environment.detect_ninja() + ninja = mesonbuild.tooldetect.detect_ninja() if ninja is None: raise SkipTest('This test currently requires ninja. Fix this once "meson build" works.') langs = ['c'] env = get_fake_env() - for l in ['cpp', 'cs', 'd', 'java', 'cuda', 'fortran', 'objc', 'objcpp', 'rust', 'vala']: + for l in ['cpp', 'cs', 'cuda', 'd', 'fortran', 'java', 'objc', 'objcpp', 'rust', 'vala']: try: comp = detect_compiler_for(env, l, MachineChoice.HOST, True, '') with tempfile.TemporaryDirectory() as d: - comp.sanity_check(d, env) + comp.sanity_check(d) langs.append(l) except EnvironmentException: pass @@ -2441,34 +2524,81 @@ class AllPlatformTests(BasePlatformTests): if is_osx(): langs = [l for l in langs if l != 'd'] - for lang in langs: - for target_type in ('executable', 'library'): - with self.subTest(f'Language: {lang}; type: {target_type}'): - if is_windows() and lang == 'fortran' and target_type == 'library': - # non-Gfortran Windows Fortran compilers do not do shared libraries in a Fortran standard way - # see "test cases/fortran/6 dynamic" - fc = detect_compiler_for(env, 'fortran', MachineChoice.HOST, True, '') - if fc.get_id() in {'intel-cl', 'pgi'}: - continue - # test empty directory - with tempfile.TemporaryDirectory() as tmpdir: - self._run(self.meson_command + ['init', '--language', lang, '--type', target_type], - workdir=tmpdir) - self._run(self.setup_command + ['--backend=ninja', 'builddir'], - workdir=tmpdir) - self._run(ninja, - workdir=os.path.join(tmpdir, 'builddir')) - # test directory with existing code file - if lang in {'c', 'cpp', 'd'}: - with tempfile.TemporaryDirectory() as tmpdir: - with open(os.path.join(tmpdir, 'foo.' + lang), 'w', encoding='utf-8') as f: - f.write('int main(void) {}') - self._run(self.meson_command + ['init', '-b'], workdir=tmpdir) - elif lang in {'java'}: - with tempfile.TemporaryDirectory() as tmpdir: - with open(os.path.join(tmpdir, 'Foo.' + lang), 'w', encoding='utf-8') as f: - f.write('public class Foo { public static void main() {} }') - self._run(self.meson_command + ['init', '-b'], workdir=tmpdir) + def _template_test_fresh(lang, target_type): + if is_windows() and lang == 'fortran' and target_type == 'library': + # non-Gfortran Windows Fortran compilers do not do shared libraries in a Fortran standard way + # see "test cases/fortran/6 dynamic" + fc = detect_compiler_for(env, 'fortran', MachineChoice.HOST, True, '') + if fc.get_id() in {'intel-cl', 'pgi'}: + return + + # test empty directory + with tempfile.TemporaryDirectory() as tmpdir: + self._run(self.meson_command + ['init', '--language', lang, '--type', target_type], + workdir=tmpdir) + self._run(self.setup_command + ['--backend=ninja', 'builddir'], workdir=tmpdir) + self._run(ninja, workdir=os.path.join(tmpdir, 'builddir')) + + # custom executable name + if target_type == 'executable': + with tempfile.TemporaryDirectory() as tmpdir: + self._run(self.meson_command + ['init', '--language', lang, '--type', target_type, + '--executable', 'foobar'], + workdir=tmpdir) + self._run(self.setup_command + ['--backend=ninja', 'builddir'], workdir=tmpdir) + self._run(ninja, workdir=os.path.join(tmpdir, 'builddir')) + + if lang not in {'cs', 'java'}: + exe = os.path.join(tmpdir, 'builddir', 'foobar' + exe_suffix) + self.assertTrue(os.path.exists(exe)) + + def _template_test_dirty(lang, target_type): + if is_windows() and lang == 'fortran' and target_type == 'library': + # non-Gfortran Windows Fortran compilers do not do shared libraries in a Fortran standard way + # see "test cases/fortran/6 dynamic" + fc = detect_compiler_for(env, 'fortran', MachineChoice.HOST, True, '') + if fc.get_id() in {'intel-cl', 'pgi'}: + return + + # test empty directory + with tempfile.TemporaryDirectory() as tmpdir: + self._run(self.meson_command + ['init', '--language', lang, '--type', target_type], + workdir=tmpdir) + self._run(self.setup_command + ['--backend=ninja', 'builddir'], workdir=tmpdir) + self._run(ninja, workdir=os.path.join(tmpdir, 'builddir')) + + # Check for whether we're doing source collection by repeating + # with a bogus file we should pick up (and then fail to compile). + with tempfile.TemporaryDirectory() as tmpdir: + suffix = lang_suffixes[lang][0] + # Assume that this is a good enough string to error out + # in all languages. + with open(os.path.join(tmpdir, 'bar.' + suffix), 'w', encoding='utf-8') as f: + f.write('error bar') + self._run(self.meson_command + ['init', '--language', lang, '--type', target_type], + workdir=tmpdir) + self._run(self.setup_command + ['--backend=ninja', 'builddir'], + workdir=tmpdir) + with self.assertRaises(subprocess.CalledProcessError): + self._run(ninja, + workdir=os.path.join(tmpdir, 'builddir')) + + # test directory with existing code file + if lang in {'c', 'cpp', 'd'}: + with tempfile.TemporaryDirectory() as tmpdir: + with open(os.path.join(tmpdir, 'foo.' + lang), 'w', encoding='utf-8') as f: + f.write('int main(void) {}') + self._run(self.meson_command + ['init', '-b'], workdir=tmpdir) + + elif lang in {'java'}: + with tempfile.TemporaryDirectory() as tmpdir: + with open(os.path.join(tmpdir, 'Foo.' + lang), 'w', encoding='utf-8') as f: + f.write('public class Foo { public static void main() {} }') + self._run(self.meson_command + ['init', '-b'], workdir=tmpdir) + + for lang, target_type, fresh in itertools.product(langs, ('executable', 'library'), (True, False)): + with self.subTest(f'Language: {lang}; type: {target_type}; fresh: {fresh}'): + _template_test_fresh(lang, target_type) if fresh else _template_test_dirty(lang, target_type) def test_compiler_run_command(self): ''' @@ -2499,10 +2629,9 @@ class AllPlatformTests(BasePlatformTests): def test_flock(self): exception_raised = False with tempfile.TemporaryDirectory() as tdir: - os.mkdir(os.path.join(tdir, 'meson-private')) - with BuildDirLock(tdir): + with DirectoryLock(tdir, 'lock', DirectoryLockAction.FAIL, 'failed to lock directory'): try: - with BuildDirLock(tdir): + with DirectoryLock(tdir, 'lock', DirectoryLockAction.FAIL, 'expected failure'): pass except MesonException: exception_raised = True @@ -2677,35 +2806,35 @@ class AllPlatformTests(BasePlatformTests): out = self.init(testdir, extra_args=['--profile-self', '--fatal-meson-warnings']) self.assertNotIn('[default: true]', out) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore.get_value('default_library'), 'static') - self.assertEqual(obj.optstore.get_value('warning_level'), '1') - self.assertEqual(obj.optstore.get_value(OptionKey('set_sub_opt', '')), True) - self.assertEqual(obj.optstore.get_value(OptionKey('subp_opt', 'subp')), 'default3') + self.assertEqual(obj.optstore.get_value_for('default_library'), 'static') + self.assertEqual(obj.optstore.get_value_for('warning_level'), '1') + self.assertEqual(obj.optstore.get_value_for(OptionKey('set_sub_opt', '')), True) + self.assertEqual(obj.optstore.get_value_for(OptionKey('subp_opt', 'subp')), 'default3') self.wipe() # warning_level is special, it's --warnlevel instead of --warning-level # for historical reasons self.init(testdir, extra_args=['--warnlevel=2', '--fatal-meson-warnings']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore.get_value('warning_level'), '2') + self.assertEqual(obj.optstore.get_value_for('warning_level'), '2') self.setconf('--warnlevel=3') obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore.get_value('warning_level'), '3') + self.assertEqual(obj.optstore.get_value_for('warning_level'), '3') self.setconf('--warnlevel=everything') obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore.get_value('warning_level'), 'everything') + self.assertEqual(obj.optstore.get_value_for('warning_level'), 'everything') self.wipe() # But when using -D syntax, it should be 'warning_level' self.init(testdir, extra_args=['-Dwarning_level=2', '--fatal-meson-warnings']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore.get_value('warning_level'), '2') + self.assertEqual(obj.optstore.get_value_for('warning_level'), '2') self.setconf('-Dwarning_level=3') obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore.get_value('warning_level'), '3') + self.assertEqual(obj.optstore.get_value_for('warning_level'), '3') self.setconf('-Dwarning_level=everything') obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore.get_value('warning_level'), 'everything') + self.assertEqual(obj.optstore.get_value_for('warning_level'), 'everything') self.wipe() # Mixing --option and -Doption is forbidden @@ -2729,15 +2858,15 @@ class AllPlatformTests(BasePlatformTests): # --default-library should override default value from project() self.init(testdir, extra_args=['--default-library=both', '--fatal-meson-warnings']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore.get_value('default_library'), 'both') + self.assertEqual(obj.optstore.get_value_for('default_library'), 'both') self.setconf('--default-library=shared') obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore.get_value('default_library'), 'shared') + self.assertEqual(obj.optstore.get_value_for('default_library'), 'shared') if self.backend is Backend.ninja: # reconfigure target works only with ninja backend self.build('reconfigure') obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore.get_value('default_library'), 'shared') + self.assertEqual(obj.optstore.get_value_for('default_library'), 'shared') self.wipe() # Should fail on unknown options @@ -2774,22 +2903,22 @@ class AllPlatformTests(BasePlatformTests): # Test we can set subproject option self.init(testdir, extra_args=['-Dsubp:subp_opt=foo', '--fatal-meson-warnings']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore.get_value(OptionKey('subp_opt', 'subp')), 'foo') + self.assertEqual(obj.optstore.get_value_for(OptionKey('subp_opt', 'subp')), 'foo') self.wipe() # c_args value should be parsed with split_args self.init(testdir, extra_args=['-Dc_args=-Dfoo -Dbar "-Dthird=one two"', '--fatal-meson-warnings']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore.get_value(OptionKey('c_args')), ['-Dfoo', '-Dbar', '-Dthird=one two']) + self.assertEqual(obj.optstore.get_value_for(OptionKey('c_args')), ['-Dfoo', '-Dbar', '-Dthird=one two']) self.setconf('-Dc_args="foo bar" one two') obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore.get_value(OptionKey('c_args')), ['foo bar', 'one', 'two']) + self.assertEqual(obj.optstore.get_value_for(OptionKey('c_args')), ['foo bar', 'one', 'two']) self.wipe() self.init(testdir, extra_args=['-Dset_percent_opt=myoption%', '--fatal-meson-warnings']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore.get_value(OptionKey('set_percent_opt', '')), 'myoption%') + self.assertEqual(obj.optstore.get_value_for(OptionKey('set_percent_opt', '')), 'myoption%') self.wipe() # Setting a 2nd time the same option should override the first value @@ -2800,19 +2929,19 @@ class AllPlatformTests(BasePlatformTests): '-Dc_args=-Dfoo', '-Dc_args=-Dbar', '-Db_lundef=false', '--fatal-meson-warnings']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore.get_value('bindir'), 'bar') - self.assertEqual(obj.optstore.get_value('buildtype'), 'release') - self.assertEqual(obj.optstore.get_value('b_sanitize'), ['thread']) - self.assertEqual(obj.optstore.get_value(OptionKey('c_args')), ['-Dbar']) + self.assertEqual(obj.optstore.get_value_for('bindir'), 'bar') + self.assertEqual(obj.optstore.get_value_for('buildtype'), 'release') + self.assertEqual(obj.optstore.get_value_for('b_sanitize'), ['thread']) + self.assertEqual(obj.optstore.get_value_for(OptionKey('c_args')), ['-Dbar']) self.setconf(['--bindir=bar', '--bindir=foo', '-Dbuildtype=release', '-Dbuildtype=plain', '-Db_sanitize=thread', '-Db_sanitize=address', '-Dc_args=-Dbar', '-Dc_args=-Dfoo']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore.get_value('bindir'), 'foo') - self.assertEqual(obj.optstore.get_value('buildtype'), 'plain') - self.assertEqual(obj.optstore.get_value('b_sanitize'), ['address']) - self.assertEqual(obj.optstore.get_value(OptionKey('c_args')), ['-Dfoo']) + self.assertEqual(obj.optstore.get_value_for('bindir'), 'foo') + self.assertEqual(obj.optstore.get_value_for('buildtype'), 'plain') + self.assertEqual(obj.optstore.get_value_for('b_sanitize'), ['address']) + self.assertEqual(obj.optstore.get_value_for(OptionKey('c_args')), ['-Dfoo']) self.wipe() except KeyError: # Ignore KeyError, it happens on CI for compilers that does not @@ -2826,25 +2955,25 @@ class AllPlatformTests(BasePlatformTests): # Verify default values when passing no args self.init(testdir) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore.get_value('warning_level'), '0') + self.assertEqual(obj.optstore.get_value_for('warning_level'), '0') self.wipe() # verify we can override w/ --warnlevel self.init(testdir, extra_args=['--warnlevel=1']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore.get_value('warning_level'), '1') + self.assertEqual(obj.optstore.get_value_for('warning_level'), '1') self.setconf('--warnlevel=0') obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore.get_value('warning_level'), '0') + self.assertEqual(obj.optstore.get_value_for('warning_level'), '0') self.wipe() # verify we can override w/ -Dwarning_level self.init(testdir, extra_args=['-Dwarning_level=1']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore.get_value('warning_level'), '1') + self.assertEqual(obj.optstore.get_value_for('warning_level'), '1') self.setconf('-Dwarning_level=0') obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore.get_value('warning_level'), '0') + self.assertEqual(obj.optstore.get_value_for('warning_level'), '0') self.wipe() def test_feature_check_usage_subprojects(self): @@ -2981,6 +3110,121 @@ class AllPlatformTests(BasePlatformTests): self.wipe() self.init(testdir, extra_args=['-Dstart_native=true'], override_envvars=env) + @skipIf(is_osx(), 'Not implemented for Darwin yet') + @skipIf(is_windows(), 'POSIX only') + def test_python_build_config_extensions(self): + testdir = os.path.join(self.unit_test_dir, + '126 python extension') + + VERSION_INFO_KEYS = ('major', 'minor', 'micro', 'releaselevel', 'serial') + EXTENSION_SUFFIX = '.extension-suffix.so' + STABLE_ABI_SUFFIX = '.stable-abi-suffix.so' + # macOS framework builds put libpython in PYTHONFRAMEWORKPREFIX. + LIBDIR = (sysconfig.get_config_var('PYTHONFRAMEWORKPREFIX') or + sysconfig.get_config_var('LIBDIR')) + + python_build_config = { + 'schema_version': '1.0', + 'base_interpreter': sys.executable, + 'base_prefix': '/usr', + 'platform': sysconfig.get_platform(), + 'language': { + 'version': sysconfig.get_python_version(), + 'version_info': {key: getattr(sys.version_info, key) for key in VERSION_INFO_KEYS} + }, + 'implementation': { + attr: ( + getattr(sys.implementation, attr) + if attr != 'version' else + {key: getattr(sys.implementation.version, key) for key in VERSION_INFO_KEYS} + ) + for attr in dir(sys.implementation) + if not attr.startswith('__') + }, + 'abi': { + 'flags': list(sys.abiflags), + 'extension_suffix': EXTENSION_SUFFIX, + 'stable_abi_suffix': STABLE_ABI_SUFFIX, + }, + 'suffixes': { + 'source': ['.py'], + 'bytecode': ['.pyc'], + 'optimized_bytecode': ['.pyc'], + 'debug_bytecode': ['.pyc'], + 'extensions': [EXTENSION_SUFFIX, STABLE_ABI_SUFFIX, '.so'], + }, + 'libpython': { + 'dynamic': os.path.join(LIBDIR, sysconfig.get_config_var('LDLIBRARY')), + 'static': os.path.join(LIBDIR, sysconfig.get_config_var('LIBRARY')), + # set it to False on PyPy, since dylib is optional, but also + # the value is currently wrong: + # https://github.com/pypy/pypy/issues/5249 + 'link_extensions': '__pypy__' not in sys.builtin_module_names, + }, + 'c_api': { + 'headers': sysconfig.get_config_var('INCLUDEPY'), + } + } + + py3library = sysconfig.get_config_var('PY3LIBRARY') + if py3library is not None: + python_build_config['libpython']['dynamic_stableabi'] = os.path.join(LIBDIR, py3library) + + build_stable_abi = sysconfig.get_config_var('Py_GIL_DISABLED') != 1 or sys.version_info >= (3, 15) + intro_installed_file = os.path.join(self.builddir, 'meson-info', 'intro-installed.json') + expected_files = [ + os.path.join(self.builddir, 'foo' + EXTENSION_SUFFIX), + ] + if build_stable_abi: + expected_files += [ + os.path.join(self.builddir, 'foo_stable' + STABLE_ABI_SUFFIX), + ] + if is_cygwin(): + expected_files += [ + os.path.join(self.builddir, 'foo' + EXTENSION_SUFFIX.replace('.so', '.dll.a')), + ] + if build_stable_abi: + expected_files += [ + os.path.join(self.builddir, 'foo_stable' + STABLE_ABI_SUFFIX.replace('.so', '.dll.a')), + ] + + for with_pkgconfig in (False, True): + with self.subTest(with_pkgconfig=with_pkgconfig): + if with_pkgconfig: + libpc = sysconfig.get_config_var('LIBPC') + if libpc is None: + continue + python_build_config['c_api']['pkgconfig_path'] = libpc + # Old Ubuntu versions have incorrect LIBDIR, skip testing non-pkgconfig variant there. + elif not os.path.exists(python_build_config['libpython']['dynamic']): + continue + + with tempfile.NamedTemporaryFile(mode='w', delete=False, encoding='utf-8') as python_build_config_file: + json.dump(python_build_config, fp=python_build_config_file) + with tempfile.NamedTemporaryFile(mode='w', delete=False, encoding='utf-8') as cross_file: + cross_file.write( + textwrap.dedent(f''' + [binaries] + pkg-config = 'pkg-config' + + [built-in options] + python.build_config = '{python_build_config_file.name}' + '''.strip()) + ) + cross_file.flush() + + for extra_args in ( + ['--python.build-config', python_build_config_file.name], + ['--cross-file', cross_file.name], + ): + with self.subTest(extra_args=extra_args): + self.init(testdir, extra_args=extra_args) + self.build() + with open(intro_installed_file) as f: + intro_installed = json.load(f) + self.assertEqual(sorted(expected_files), sorted(intro_installed)) + self.wipe() + def __reconfigure(self): # Set an older version to force a reconfigure from scratch filename = os.path.join(self.privatedir, 'coredata.dat') @@ -3242,10 +3486,15 @@ class AllPlatformTests(BasePlatformTests): def test_identity_cross(self): testdir = os.path.join(self.unit_test_dir, '69 cross') # Do a build to generate a cross file where the host is this target - self.init(testdir, extra_args=['-Dgenerate=true']) + # build.c_args is ignored here. + self.init(testdir, extra_args=['-Dgenerate=true', '-Dc_args=-funroll-loops', + '-Dbuild.c_args=-pedantic']) + self.meson_native_files = [os.path.join(self.builddir, "nativefile")] + self.assertTrue(os.path.exists(self.meson_native_files[0])) self.meson_cross_files = [os.path.join(self.builddir, "crossfile")] self.assertTrue(os.path.exists(self.meson_cross_files[0])) - # Now verify that this is detected as cross + # Now verify that this is detected as cross and build options are + # processed correctly self.new_builddir() self.init(testdir) @@ -3264,6 +3513,11 @@ class AllPlatformTests(BasePlatformTests): testdir = os.path.join(self.unit_test_dir, '58 introspect buildoptions') self._run(self.mconf_command + [testdir]) + @skip_if_not_language('rust') + def test_meson_configure_srcdir(self): + testdir = os.path.join(self.rust_test_dir, '20 rust and cpp') + self._run(self.mconf_command + [testdir]) + def test_introspect_buildoptions_cross_only(self): testdir = os.path.join(self.unit_test_dir, '82 cross only introspect') testfile = os.path.join(testdir, 'meson.build') @@ -3612,6 +3866,8 @@ class AllPlatformTests(BasePlatformTests): # Account for differences in output res_wb = [i for i in res_wb if i['type'] != 'custom'] for i in res_wb: + if i['id'] == 'test1@exe': + i['build_by_default'] = 'unknown' i['filename'] = [os.path.relpath(x, self.builddir) for x in i['filename']] for k in ('install_filename', 'dependencies', 'win_subsystem'): if k in i: @@ -3730,7 +3986,7 @@ class AllPlatformTests(BasePlatformTests): }, { 'name': 'bugDep1', - 'required': True, + 'required': 'unknown', 'version': [], 'has_fallback': False, 'conditional': False @@ -3748,7 +4004,21 @@ class AllPlatformTests(BasePlatformTests): 'version': ['>=1.0.0', '<=99.9.9'], 'has_fallback': True, 'conditional': True - } + }, + { + 'conditional': False, + 'has_fallback': False, + 'name': 'unknown', + 'required': False, + 'version': 'unknown' + }, + { + 'conditional': False, + 'has_fallback': False, + 'name': 'unknown', + 'required': False, + 'version': 'unknown' + }, ] self.maxDiff = None self.assertListEqual(res_nb, expected) @@ -3859,7 +4129,7 @@ class AllPlatformTests(BasePlatformTests): return basename def get_shared_lib_name(basename: str) -> str: - if mesonbuild.environment.detect_msys2_arch(): + if mesonbuild.envconfig.detect_msys2_arch(): return f'lib{basename}.dll' elif is_windows(): return f'{basename}.dll' @@ -4072,16 +4342,16 @@ class AllPlatformTests(BasePlatformTests): self.assertTrue((covdir / f).is_file(), msg=f'{f} is not a file') def test_coverage(self): - if mesonbuild.environment.detect_msys2_arch(): + if mesonbuild.envconfig.detect_msys2_arch(): raise SkipTest('Skipped due to problems with coverage on MSYS2') - gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr() + gcovr_exe, gcovr_new_rootdir = mesonbuild.tooldetect.detect_gcovr() if not gcovr_exe: raise SkipTest('gcovr not found, or too old') testdir = os.path.join(self.common_test_dir, '1 trivial') env = get_fake_env(testdir, self.builddir, self.prefix) cc = detect_c_compiler(env, MachineChoice.HOST) if cc.get_id() == 'clang': - if not mesonbuild.environment.detect_llvm_cov(): + if not mesonbuild.tooldetect.detect_llvm_cov(): raise SkipTest('llvm-cov not found') if cc.get_id() == 'msvc': raise SkipTest('Test only applies to non-MSVC compilers') @@ -4092,16 +4362,16 @@ class AllPlatformTests(BasePlatformTests): self._check_coverage_files() def test_coverage_complex(self): - if mesonbuild.environment.detect_msys2_arch(): + if mesonbuild.envconfig.detect_msys2_arch(): raise SkipTest('Skipped due to problems with coverage on MSYS2') - gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr() + gcovr_exe, gcovr_new_rootdir = mesonbuild.tooldetect.detect_gcovr() if not gcovr_exe: raise SkipTest('gcovr not found, or too old') testdir = os.path.join(self.common_test_dir, '105 generatorcustom') env = get_fake_env(testdir, self.builddir, self.prefix) cc = detect_c_compiler(env, MachineChoice.HOST) if cc.get_id() == 'clang': - if not mesonbuild.environment.detect_llvm_cov(): + if not mesonbuild.tooldetect.detect_llvm_cov(): raise SkipTest('llvm-cov not found') if cc.get_id() == 'msvc': raise SkipTest('Test only applies to non-MSVC compilers') @@ -4112,16 +4382,16 @@ class AllPlatformTests(BasePlatformTests): self._check_coverage_files() def test_coverage_html(self): - if mesonbuild.environment.detect_msys2_arch(): + if mesonbuild.envconfig.detect_msys2_arch(): raise SkipTest('Skipped due to problems with coverage on MSYS2') - gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr() + gcovr_exe, gcovr_new_rootdir = mesonbuild.tooldetect.detect_gcovr() if not gcovr_exe: raise SkipTest('gcovr not found, or too old') testdir = os.path.join(self.common_test_dir, '1 trivial') env = get_fake_env(testdir, self.builddir, self.prefix) cc = detect_c_compiler(env, MachineChoice.HOST) if cc.get_id() == 'clang': - if not mesonbuild.environment.detect_llvm_cov(): + if not mesonbuild.tooldetect.detect_llvm_cov(): raise SkipTest('llvm-cov not found') if cc.get_id() == 'msvc': raise SkipTest('Test only applies to non-MSVC compilers') @@ -4132,16 +4402,16 @@ class AllPlatformTests(BasePlatformTests): self._check_coverage_files(['html']) def test_coverage_text(self): - if mesonbuild.environment.detect_msys2_arch(): + if mesonbuild.envconfig.detect_msys2_arch(): raise SkipTest('Skipped due to problems with coverage on MSYS2') - gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr() + gcovr_exe, gcovr_new_rootdir = mesonbuild.tooldetect.detect_gcovr() if not gcovr_exe: raise SkipTest('gcovr not found, or too old') testdir = os.path.join(self.common_test_dir, '1 trivial') env = get_fake_env(testdir, self.builddir, self.prefix) cc = detect_c_compiler(env, MachineChoice.HOST) if cc.get_id() == 'clang': - if not mesonbuild.environment.detect_llvm_cov(): + if not mesonbuild.tooldetect.detect_llvm_cov(): raise SkipTest('llvm-cov not found') if cc.get_id() == 'msvc': raise SkipTest('Test only applies to non-MSVC compilers') @@ -4152,16 +4422,16 @@ class AllPlatformTests(BasePlatformTests): self._check_coverage_files(['text']) def test_coverage_xml(self): - if mesonbuild.environment.detect_msys2_arch(): + if mesonbuild.envconfig.detect_msys2_arch(): raise SkipTest('Skipped due to problems with coverage on MSYS2') - gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr() + gcovr_exe, gcovr_new_rootdir = mesonbuild.tooldetect.detect_gcovr() if not gcovr_exe: raise SkipTest('gcovr not found, or too old') testdir = os.path.join(self.common_test_dir, '1 trivial') env = get_fake_env(testdir, self.builddir, self.prefix) cc = detect_c_compiler(env, MachineChoice.HOST) if cc.get_id() == 'clang': - if not mesonbuild.environment.detect_llvm_cov(): + if not mesonbuild.tooldetect.detect_llvm_cov(): raise SkipTest('llvm-cov not found') if cc.get_id() == 'msvc': raise SkipTest('Test only applies to non-MSVC compilers') @@ -4172,16 +4442,16 @@ class AllPlatformTests(BasePlatformTests): self._check_coverage_files(['xml']) def test_coverage_escaping(self): - if mesonbuild.environment.detect_msys2_arch(): + if mesonbuild.envconfig.detect_msys2_arch(): raise SkipTest('Skipped due to problems with coverage on MSYS2') - gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr() + gcovr_exe, gcovr_new_rootdir = mesonbuild.tooldetect.detect_gcovr() if not gcovr_exe: raise SkipTest('gcovr not found, or too old') testdir = os.path.join(self.common_test_dir, '243 escape++') env = get_fake_env(testdir, self.builddir, self.prefix) cc = detect_c_compiler(env, MachineChoice.HOST) if cc.get_id() == 'clang': - if not mesonbuild.environment.detect_llvm_cov(): + if not mesonbuild.tooldetect.detect_llvm_cov(): raise SkipTest('llvm-cov not found') if cc.get_id() == 'msvc': raise SkipTest('Test only applies to non-MSVC compilers') @@ -4255,6 +4525,14 @@ class AllPlatformTests(BasePlatformTests): self.build() self.run_tests() + def test_custom_target_index_as_test_prereq(self): + if self.backend is not Backend.ninja: + raise SkipTest('ninja backend needed for "meson test" to build test dependencies') + + testdir = os.path.join(self.unit_test_dir, '132 custom target index test') + self.init(testdir) + self.run_tests() + @skipUnless(is_linux() and (re.search('^i.86$|^x86$|^x64$|^x86_64$|^amd64$', platform.processor()) is not None), 'Requires ASM compiler for x86 or x86_64 platform currently only available on Linux CI runners') def test_nostdlib(self): @@ -4453,6 +4731,10 @@ class AllPlatformTests(BasePlatformTests): self.assertIn(f'TEST_C="{expected}"', o) self.assertIn('export TEST_C', o) + cmd = self.meson_command + ['devenv', '-C', self.builddir] + python_command + ['-c', 'import sys; sys.exit(42)'] + result = subprocess.run(cmd, encoding='utf-8') + self.assertEqual(result.returncode, 42) + def test_clang_format_check(self): if self.backend is not Backend.ninja: raise SkipTest(f'Skipping clang-format tests with {self.backend.name} backend') @@ -4726,120 +5008,140 @@ class AllPlatformTests(BasePlatformTests): expected = { 'targets': { f'{self.builddir}/out1-notag.txt': { + 'build_rpaths': [], 'destination': '{datadir}/out1-notag.txt', 'install_rpath': None, 'tag': None, 'subproject': None, }, f'{self.builddir}/out2-notag.txt': { + 'build_rpaths': [], 'destination': '{datadir}/out2-notag.txt', 'install_rpath': None, 'tag': None, 'subproject': None, }, f'{self.builddir}/libstatic.a': { + 'build_rpaths': [], 'destination': '{libdir_static}/libstatic.a', 'install_rpath': None, 'tag': 'devel', 'subproject': None, }, f'{self.builddir}/' + exe_name('app'): { + 'build_rpaths': [], 'destination': '{bindir}/' + exe_name('app'), 'install_rpath': None, 'tag': 'runtime', 'subproject': None, }, f'{self.builddir}/' + exe_name('app-otherdir'): { + 'build_rpaths': [], 'destination': '{prefix}/otherbin/' + exe_name('app-otherdir'), 'install_rpath': None, 'tag': 'runtime', 'subproject': None, }, f'{self.builddir}/subdir/' + exe_name('app2'): { + 'build_rpaths': [], 'destination': '{bindir}/' + exe_name('app2'), 'install_rpath': None, 'tag': 'runtime', 'subproject': None, }, f'{self.builddir}/' + shared_lib_name('shared'): { + 'build_rpaths': [], 'destination': '{libdir_shared}/' + shared_lib_name('shared'), 'install_rpath': None, 'tag': 'runtime', 'subproject': None, }, f'{self.builddir}/' + shared_lib_name('both'): { + 'build_rpaths': [], 'destination': '{libdir_shared}/' + shared_lib_name('both'), 'install_rpath': None, 'tag': 'runtime', 'subproject': None, }, f'{self.builddir}/' + static_lib_name('both'): { + 'build_rpaths': [], 'destination': '{libdir_static}/' + static_lib_name('both'), 'install_rpath': None, 'tag': 'devel', 'subproject': None, }, f'{self.builddir}/' + shared_lib_name('bothcustom'): { + 'build_rpaths': [], 'destination': '{libdir_shared}/' + shared_lib_name('bothcustom'), 'install_rpath': None, 'tag': 'custom', 'subproject': None, }, f'{self.builddir}/' + static_lib_name('bothcustom'): { + 'build_rpaths': [], 'destination': '{libdir_static}/' + static_lib_name('bothcustom'), 'install_rpath': None, 'tag': 'custom', 'subproject': None, }, f'{self.builddir}/subdir/' + shared_lib_name('both2'): { + 'build_rpaths': [], 'destination': '{libdir_shared}/' + shared_lib_name('both2'), 'install_rpath': None, 'tag': 'runtime', 'subproject': None, }, f'{self.builddir}/subdir/' + static_lib_name('both2'): { + 'build_rpaths': [], 'destination': '{libdir_static}/' + static_lib_name('both2'), 'install_rpath': None, 'tag': 'devel', 'subproject': None, }, f'{self.builddir}/out1-custom.txt': { + 'build_rpaths': [], 'destination': '{datadir}/out1-custom.txt', 'install_rpath': None, 'tag': 'custom', 'subproject': None, }, f'{self.builddir}/out2-custom.txt': { + 'build_rpaths': [], 'destination': '{datadir}/out2-custom.txt', 'install_rpath': None, 'tag': 'custom', 'subproject': None, }, f'{self.builddir}/out3-custom.txt': { + 'build_rpaths': [], 'destination': '{datadir}/out3-custom.txt', 'install_rpath': None, 'tag': 'custom', 'subproject': None, }, f'{self.builddir}/subdir/out1.txt': { + 'build_rpaths': [], 'destination': '{datadir}/out1.txt', 'install_rpath': None, 'tag': None, 'subproject': None, }, f'{self.builddir}/subdir/out2.txt': { + 'build_rpaths': [], 'destination': '{datadir}/out2.txt', 'install_rpath': None, 'tag': None, 'subproject': None, }, f'{self.builddir}/out-devel.h': { + 'build_rpaths': [], 'destination': '{includedir}/out-devel.h', 'install_rpath': None, 'tag': 'devel', 'subproject': None, }, f'{self.builddir}/out3-notag.txt': { + 'build_rpaths': [], 'destination': '{datadir}/out3-notag.txt', 'install_rpath': None, 'tag': None, @@ -5185,7 +5487,7 @@ class AllPlatformTests(BasePlatformTests): self.__test_multi_stds(test_objc=True) def test_slice(self): - testdir = os.path.join(self.unit_test_dir, '126 test slice') + testdir = os.path.join(self.unit_test_dir, '128 test slice') self.init(testdir) self.build() @@ -5197,7 +5499,9 @@ class AllPlatformTests(BasePlatformTests): '10/10': [10], }.items(): output = self._run(self.mtest_command + ['--slice=' + arg]) - tests = sorted([ int(x) for x in re.findall(r'\n[ 0-9]+/[0-9]+ test-([0-9]*)', output) ]) + tests = sorted([ + int(x) for x in re.findall(r'^[ 0-9]+/[0-9]+ test_slice:test-([0-9]*)', output, flags=re.MULTILINE) + ]) self.assertEqual(tests, expectation) for arg, expectation in {'': 'error: argument --slice: value does not conform to format \'SLICE/NUM_SLICES\'', @@ -5217,7 +5521,7 @@ class AllPlatformTests(BasePlatformTests): env = get_fake_env() cc = detect_c_compiler(env, MachineChoice.HOST) has_rsp = cc.linker.id in { - 'ld.bfd', 'ld.gold', 'ld.lld', 'ld.mold', 'ld.qcld', 'ld.wasm', + 'ld.bfd', 'ld.eld', 'ld.gold', 'ld.lld', 'ld.mold', 'ld.qcld', 'ld.wasm', 'link', 'lld-link', 'mwldarm', 'mwldeppc', 'optlink', 'xilink', } self.assertEqual(cc.linker.get_accepts_rsp(), has_rsp) diff --git a/unittests/cargotests.py b/unittests/cargotests.py index d1ac838..643cece 100644 --- a/unittests/cargotests.py +++ b/unittests/cargotests.py @@ -8,8 +8,11 @@ import tempfile import textwrap import typing as T -from mesonbuild.cargo import builder, cfg, load_wraps +from mesonbuild.cargo import cfg from mesonbuild.cargo.cfg import TokenType +from mesonbuild.cargo.interpreter import load_cargo_lock +from mesonbuild.cargo.manifest import Dependency, Lint, Manifest, Package, Workspace +from mesonbuild.cargo.toml import load_toml from mesonbuild.cargo.version import convert @@ -101,6 +104,12 @@ class CargoCfgTest(unittest.TestCase): (TokenType.IDENTIFIER, 'unix'), (TokenType.RPAREN, None), ]), + ('cfg(windows)', [ + (TokenType.CFG, None), + (TokenType.LPAREN, None), + (TokenType.IDENTIFIER, 'windows'), + (TokenType.RPAREN, None), + ]), ] for data, expected in cases: with self.subTest(): @@ -130,69 +139,51 @@ class CargoCfgTest(unittest.TestCase): cfg.Equal(cfg.Identifier("target_arch"), cfg.String("x86")), cfg.Equal(cfg.Identifier("target_os"), cfg.String("linux")), ]))), + ('cfg(all(any(target_os = "android", target_os = "linux"), any(custom_cfg)))', + cfg.All([ + cfg.Any([ + cfg.Equal(cfg.Identifier("target_os"), cfg.String("android")), + cfg.Equal(cfg.Identifier("target_os"), cfg.String("linux")), + ]), + cfg.Any([ + cfg.Identifier("custom_cfg"), + ]), + ])), ] for data, expected in cases: with self.subTest(): self.assertEqual(cfg.parse(iter(cfg.lexer(data))), expected) - def test_ir_to_meson(self) -> None: - build = builder.Builder('') - HOST_MACHINE = build.identifier('host_machine') - + def test_eval_ir(self) -> None: + d = { + 'target_os': 'unix', + 'unix': '', + } cases = [ - ('target_os = "windows"', - build.equal(build.method('system', HOST_MACHINE), - build.string('windows'))), - ('target_arch = "x86"', - build.equal(build.method('cpu_family', HOST_MACHINE), - build.string('x86'))), - ('target_family = "unix"', - build.equal(build.method('system', HOST_MACHINE), - build.string('unix'))), - ('not(target_arch = "x86")', - build.not_(build.equal( - build.method('cpu_family', HOST_MACHINE), - build.string('x86')))), - ('any(target_arch = "x86", target_arch = "x86_64")', - build.or_( - build.equal(build.method('cpu_family', HOST_MACHINE), - build.string('x86')), - build.equal(build.method('cpu_family', HOST_MACHINE), - build.string('x86_64')))), - ('any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")', - build.or_( - build.equal(build.method('cpu_family', HOST_MACHINE), - build.string('x86')), - build.or_( - build.equal(build.method('cpu_family', HOST_MACHINE), - build.string('x86_64')), - build.equal(build.method('cpu_family', HOST_MACHINE), - build.string('aarch64'))))), - ('all(target_arch = "x86", target_arch = "x86_64")', - build.and_( - build.equal(build.method('cpu_family', HOST_MACHINE), - build.string('x86')), - build.equal(build.method('cpu_family', HOST_MACHINE), - build.string('x86_64')))), - ('all(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")', - build.and_( - build.equal(build.method('cpu_family', HOST_MACHINE), - build.string('x86')), - build.and_( - build.equal(build.method('cpu_family', HOST_MACHINE), - build.string('x86_64')), - build.equal(build.method('cpu_family', HOST_MACHINE), - build.string('aarch64'))))), + ('target_os = "windows"', False), + ('target_os = "unix"', True), + ('doesnotexist = "unix"', False), + ('not(target_os = "windows")', True), + ('any(target_os = "windows", target_arch = "x86_64")', False), + ('any(target_os = "windows", target_os = "unix")', True), + ('all(target_os = "windows", target_os = "unix")', False), + ('all(not(target_os = "windows"), target_os = "unix")', True), + ('any(unix, windows)', True), + ('all()', True), + ('any()', False), + ('cfg(unix)', True), + ('cfg(windows)', False), ] for data, expected in cases: with self.subTest(): - value = cfg.ir_to_meson(cfg.parse(iter(cfg.lexer(data))), build) + value = cfg.eval_cfg(data, d) self.assertEqual(value, expected) class CargoLockTest(unittest.TestCase): - def test_cargo_lock(self) -> None: + def test_wraps(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: - with open(os.path.join(tmpdir, 'Cargo.lock'), 'w', encoding='utf-8') as f: + filename = os.path.join(tmpdir, 'Cargo.lock') + with open(filename, 'w', encoding='utf-8') as f: f.write(textwrap.dedent('''\ version = 3 [[package]] @@ -204,18 +195,379 @@ class CargoLockTest(unittest.TestCase): name = "bar" version = "0.1" source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#23c5599424cc75ec66618891c915d9f490f6e4c2" + [[package]] + name = "member" + version = "0.1" + source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#23c5599424cc75ec66618891c915d9f490f6e4c2" ''')) - wraps = load_wraps(tmpdir, 'subprojects') + cargolock = load_cargo_lock(filename, 'subprojects') + wraps = cargolock.wraps self.assertEqual(len(wraps), 2) - self.assertEqual(wraps[0].name, 'foo-0.1-rs') - self.assertEqual(wraps[0].directory, 'foo-0.1') - self.assertEqual(wraps[0].type, 'file') - self.assertEqual(wraps[0].get('method'), 'cargo') - self.assertEqual(wraps[0].get('source_url'), 'https://crates.io/api/v1/crates/foo/0.1/download') - self.assertEqual(wraps[0].get('source_hash'), '8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb') - self.assertEqual(wraps[1].name, 'bar-0.1-rs') - self.assertEqual(wraps[1].directory, 'bar') - self.assertEqual(wraps[1].type, 'git') - self.assertEqual(wraps[1].get('method'), 'cargo') - self.assertEqual(wraps[1].get('url'), 'https://github.com/gtk-rs/gtk-rs-core') - self.assertEqual(wraps[1].get('revision'), '23c5599424cc75ec66618891c915d9f490f6e4c2') + self.assertEqual(wraps['foo-0.1-rs'].name, 'foo-0.1-rs') + self.assertEqual(wraps['foo-0.1-rs'].directory, 'foo-0.1') + self.assertEqual(wraps['foo-0.1-rs'].type, 'file') + self.assertEqual(wraps['foo-0.1-rs'].get('method'), 'cargo') + self.assertEqual(wraps['foo-0.1-rs'].get('source_url'), 'https://crates.io/api/v1/crates/foo/0.1/download') + self.assertEqual(wraps['foo-0.1-rs'].get('source_hash'), '8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb') + self.assertEqual(wraps['gtk-rs-core-0.19'].name, 'gtk-rs-core-0.19') + self.assertEqual(wraps['gtk-rs-core-0.19'].directory, 'gtk-rs-core-0.19') + self.assertEqual(wraps['gtk-rs-core-0.19'].type, 'git') + self.assertEqual(wraps['gtk-rs-core-0.19'].get('method'), 'cargo') + self.assertEqual(wraps['gtk-rs-core-0.19'].get('url'), 'https://github.com/gtk-rs/gtk-rs-core') + self.assertEqual(wraps['gtk-rs-core-0.19'].get('revision'), '23c5599424cc75ec66618891c915d9f490f6e4c2') + self.assertEqual(list(wraps['gtk-rs-core-0.19'].provided_deps), ['gtk-rs-core-0.19', 'bar-0.1-rs', 'member-0.1-rs']) + +class CargoTomlTest(unittest.TestCase): + CARGO_TOML_1 = textwrap.dedent('''\ + [package] + name = "mandelbrot" + version = "0.1.0" + authors = ["Sebastian Dröge <sebastian@centricular.com>"] + edition = "2018" + license = "GPL-3.0" + + [package.metadata.docs.rs] + all-features = true + rustc-args = [ + "--cfg", + "docsrs", + ] + rustdoc-args = [ + "--cfg", + "docsrs", + "--generate-link-to-definition", + ] + + [dependencies] + gtk = { package = "gtk4", version = "0.9" } + num-complex = "0.4" + rayon = "1.0" + once_cell = "1" + async-channel = "2.0" + zerocopy = { version = "0.7", features = ["derive"] } + + [lints.rust] + unknown_lints = "allow" + unexpected_cfgs = { level = "deny", check-cfg = [ 'cfg(MESON)' ] } + + [lints.clippy] + pedantic = {level = "warn", priority = -1} + + [dev-dependencies.gir-format-check] + version = "^0.1" + ''') + + CARGO_TOML_2 = textwrap.dedent('''\ + [package] + name = "pango" + edition = "2021" + rust-version = "1.70" + version = "0.20.4" + authors = ["The gtk-rs Project Developers"] + + [package.metadata.system-deps.pango] + name = "pango" + version = "1.40" + + [package.metadata.system-deps.pango.v1_42] + version = "1.42" + + [lib] + name = "pango" + + [[test]] + name = "check_gir" + path = "tests/check_gir.rs" + + [features] + v1_42 = ["pango-sys/v1_42"] + v1_44 = [ + "v1_42", + "pango-sys/v1_44", + ] + ''') + + CARGO_TOML_3 = textwrap.dedent('''\ + [package] + name = "bits" + edition = "2021" + rust-version = "1.70" + version = "0.1.0" + + [lib] + proc-macro = true + crate-type = ["lib"] # ignored + ''') + + CARGO_TOML_WS = textwrap.dedent('''\ + [workspace] + resolver = "2" + members = ["tutorial"] + + [workspace.package] + version = "0.14.0-alpha.1" + repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs" + edition = "2021" + rust-version = "1.83" + + [workspace.dependencies] + glib = { path = "glib" } + gtk = { package = "gtk4", version = "0.9" } + once_cell = "1.0" + syn = { version = "2", features = ["parse"] } + + [workspace.lints.rust] + warnings = "deny" + ''') + + def test_cargo_toml_ws_lints(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + fname = os.path.join(tmpdir, 'Cargo.toml') + with open(fname, 'w', encoding='utf-8') as f: + f.write(self.CARGO_TOML_WS) + workspace_toml = load_toml(fname) + + workspace = Workspace.from_raw(workspace_toml, tmpdir) + pkg = Manifest.from_raw({'package': {'name': 'foo'}, + 'lints': {'workspace': True}}, 'Cargo.toml', workspace) + lints = pkg.lints + self.assertEqual(lints[0].name, 'warnings') + self.assertEqual(lints[0].level, 'deny') + self.assertEqual(lints[0].priority, 0) + + pkg = Manifest.from_raw({'package': {'name': 'bar'}}, 'Cargo.toml', workspace) + self.assertEqual(pkg.lints, []) + + def test_cargo_toml_ws_package(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + fname = os.path.join(tmpdir, 'Cargo.toml') + with open(fname, 'w', encoding='utf-8') as f: + f.write(self.CARGO_TOML_WS) + workspace_toml = load_toml(fname) + + workspace = Workspace.from_raw(workspace_toml, tmpdir) + pkg = Package.from_raw({'name': 'foo', 'version': {'workspace': True}}, workspace) + self.assertEqual(pkg.name, 'foo') + self.assertEqual(pkg.version, '0.14.0-alpha.1') + self.assertEqual(pkg.edition, '2015') + self.assertEqual(pkg.repository, None) + + def test_cargo_toml_ws_dependency(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + fname = os.path.join(tmpdir, 'Cargo.toml') + with open(fname, 'w', encoding='utf-8') as f: + f.write(self.CARGO_TOML_WS) + workspace_toml = load_toml(fname) + + workspace = Workspace.from_raw(workspace_toml, tmpdir) + dep = Dependency.from_raw('glib', {'workspace': True}, 'member', workspace) + self.assertEqual(dep.package, 'glib') + self.assertEqual(dep.version, '') + self.assertEqual(dep.meson_version, []) + self.assertEqual(dep.path, os.path.join('..', 'glib')) + self.assertEqual(dep.features, []) + + dep = Dependency.from_raw('gtk', {'workspace': True}, 'member', workspace) + self.assertEqual(dep.package, 'gtk4') + self.assertEqual(dep.version, '0.9') + self.assertEqual(dep.meson_version, ['>= 0.9', '< 0.10']) + self.assertEqual(dep.api, '0.9') + self.assertEqual(dep.features, []) + + dep = Dependency.from_raw('once_cell', {'workspace': True, 'optional': True}, 'member', workspace) + self.assertEqual(dep.package, 'once_cell') + self.assertEqual(dep.version, '1.0') + self.assertEqual(dep.meson_version, ['>= 1.0', '< 2']) + self.assertEqual(dep.api, '1') + self.assertEqual(dep.features, []) + self.assertTrue(dep.optional) + + dep = Dependency.from_raw('syn', {'workspace': True, 'features': ['full']}, 'member', workspace) + self.assertEqual(dep.package, 'syn') + self.assertEqual(dep.version, '2') + self.assertEqual(dep.meson_version, ['>= 2', '< 3']) + self.assertEqual(dep.api, '2') + self.assertEqual(sorted(set(dep.features)), ['full', 'parse']) + + def test_cargo_toml_package(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + fname = os.path.join(tmpdir, 'Cargo.toml') + with open(fname, 'w', encoding='utf-8') as f: + f.write(self.CARGO_TOML_1) + manifest_toml = load_toml(fname) + manifest = Manifest.from_raw(manifest_toml, 'Cargo.toml') + + self.assertEqual(manifest.package.name, 'mandelbrot') + self.assertEqual(manifest.package.version, '0.1.0') + self.assertEqual(manifest.package.authors[0], 'Sebastian Dröge <sebastian@centricular.com>') + self.assertEqual(manifest.package.edition, '2018') + self.assertEqual(manifest.package.license, 'GPL-3.0') + + print(manifest.package.metadata) + self.assertEqual(len(manifest.package.metadata), 1) + + def test_cargo_toml_lints(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + fname = os.path.join(tmpdir, 'Cargo.toml') + with open(fname, 'w', encoding='utf-8') as f: + f.write(self.CARGO_TOML_1) + manifest_toml = load_toml(fname) + manifest = Manifest.from_raw(manifest_toml, 'Cargo.toml') + + self.assertEqual(len(manifest.lints), 3) + self.assertEqual(manifest.lints[0].name, 'clippy::pedantic') + self.assertEqual(manifest.lints[0].level, 'warn') + self.assertEqual(manifest.lints[0].priority, -1) + self.assertEqual(manifest.lints[0].check_cfg, None) + + self.assertEqual(manifest.lints[1].name, 'unknown_lints') + self.assertEqual(manifest.lints[1].level, 'allow') + self.assertEqual(manifest.lints[1].priority, 0) + self.assertEqual(manifest.lints[1].check_cfg, None) + + self.assertEqual(manifest.lints[2].name, 'unexpected_cfgs') + self.assertEqual(manifest.lints[2].level, 'deny') + self.assertEqual(manifest.lints[2].priority, 0) + self.assertEqual(manifest.lints[2].check_cfg, ['cfg(test)', 'cfg(MESON)']) + + def test_cargo_toml_lints_to_args(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + fname = os.path.join(tmpdir, 'Cargo.toml') + with open(fname, 'w', encoding='utf-8') as f: + f.write(self.CARGO_TOML_1) + manifest_toml = load_toml(fname) + manifest = Manifest.from_raw(manifest_toml, 'Cargo.toml') + + self.assertEqual(manifest.lints[0].to_arguments(False), ['-W', 'clippy::pedantic']) + self.assertEqual(manifest.lints[0].to_arguments(True), ['-W', 'clippy::pedantic']) + self.assertEqual(manifest.lints[1].to_arguments(False), ['-A', 'unknown_lints']) + self.assertEqual(manifest.lints[1].to_arguments(True), ['-A', 'unknown_lints']) + self.assertEqual(manifest.lints[2].to_arguments(False), ['-D', 'unexpected_cfgs']) + self.assertEqual(manifest.lints[2].to_arguments(True), + ['-D', 'unexpected_cfgs', '--check-cfg', 'cfg(test)', + '--check-cfg', 'cfg(MESON)']) + + def test_cargo_toml_dependencies(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + fname = os.path.join(tmpdir, 'Cargo.toml') + with open(fname, 'w', encoding='utf-8') as f: + f.write(self.CARGO_TOML_1) + manifest_toml = load_toml(fname) + manifest = Manifest.from_raw(manifest_toml, 'Cargo.toml') + + self.assertEqual(len(manifest.dependencies), 6) + self.assertEqual(manifest.dependencies['gtk'].package, 'gtk4') + self.assertEqual(manifest.dependencies['gtk'].version, '0.9') + self.assertEqual(manifest.dependencies['gtk'].meson_version, ['>= 0.9', '< 0.10']) + self.assertEqual(manifest.dependencies['gtk'].api, '0.9') + self.assertEqual(manifest.dependencies['num-complex'].package, 'num-complex') + self.assertEqual(manifest.dependencies['num-complex'].version, '0.4') + self.assertEqual(manifest.dependencies['num-complex'].meson_version, ['>= 0.4', '< 0.5']) + self.assertEqual(manifest.dependencies['rayon'].package, 'rayon') + self.assertEqual(manifest.dependencies['rayon'].version, '1.0') + self.assertEqual(manifest.dependencies['rayon'].meson_version, ['>= 1.0', '< 2']) + self.assertEqual(manifest.dependencies['rayon'].api, '1') + self.assertEqual(manifest.dependencies['once_cell'].package, 'once_cell') + self.assertEqual(manifest.dependencies['once_cell'].version, '1') + self.assertEqual(manifest.dependencies['once_cell'].meson_version, ['>= 1', '< 2']) + self.assertEqual(manifest.dependencies['once_cell'].api, '1') + self.assertEqual(manifest.dependencies['async-channel'].package, 'async-channel') + self.assertEqual(manifest.dependencies['async-channel'].version, '2.0') + self.assertEqual(manifest.dependencies['async-channel'].meson_version, ['>= 2.0', '< 3']) + self.assertEqual(manifest.dependencies['async-channel'].api, '2') + self.assertEqual(manifest.dependencies['zerocopy'].package, 'zerocopy') + self.assertEqual(manifest.dependencies['zerocopy'].version, '0.7') + self.assertEqual(manifest.dependencies['zerocopy'].meson_version, ['>= 0.7', '< 0.8']) + self.assertEqual(manifest.dependencies['zerocopy'].features, ['derive']) + self.assertEqual(manifest.dependencies['zerocopy'].api, '0.7') + + self.assertEqual(len(manifest.dev_dependencies), 1) + self.assertEqual(manifest.dev_dependencies['gir-format-check'].package, 'gir-format-check') + self.assertEqual(manifest.dev_dependencies['gir-format-check'].version, '^0.1') + self.assertEqual(manifest.dev_dependencies['gir-format-check'].meson_version, ['>= 0.1', '< 0.2']) + self.assertEqual(manifest.dev_dependencies['gir-format-check'].api, '0.1') + + def test_cargo_toml_proc_macro(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + fname = os.path.join(tmpdir, 'Cargo.toml') + with open(fname, 'w', encoding='utf-8') as f: + f.write(self.CARGO_TOML_3) + manifest_toml = load_toml(fname) + manifest = Manifest.from_raw(manifest_toml, 'Cargo.toml') + + self.assertEqual(manifest.lib.name, 'bits') + self.assertEqual(manifest.lib.crate_type, ['proc-macro']) + self.assertEqual(manifest.lib.path, 'src/lib.rs') + + del manifest_toml['lib']['crate-type'] + manifest = Manifest.from_raw(manifest_toml, 'Cargo.toml') + self.assertEqual(manifest.lib.name, 'bits') + self.assertEqual(manifest.lib.crate_type, ['proc-macro']) + self.assertEqual(manifest.lib.path, 'src/lib.rs') + + def test_cargo_toml_targets(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + fname = os.path.join(tmpdir, 'Cargo.toml') + with open(fname, 'w', encoding='utf-8') as f: + f.write(self.CARGO_TOML_2) + manifest_toml = load_toml(fname) + manifest = Manifest.from_raw(manifest_toml, 'Cargo.toml') + + self.assertEqual(manifest.lib.name, 'pango') + self.assertEqual(manifest.lib.crate_type, ['lib']) + self.assertEqual(manifest.lib.path, 'src/lib.rs') + self.assertEqual(manifest.lib.test, True) + self.assertEqual(manifest.lib.doctest, True) + self.assertEqual(manifest.lib.bench, True) + self.assertEqual(manifest.lib.doc, True) + self.assertEqual(manifest.lib.harness, True) + self.assertEqual(manifest.lib.edition, '2021') + self.assertEqual(manifest.lib.required_features, []) + self.assertEqual(manifest.lib.plugin, False) + + self.assertEqual(len(manifest.test), 1) + self.assertEqual(manifest.test[0].name, 'check_gir') + self.assertEqual(manifest.test[0].crate_type, ['bin']) + self.assertEqual(manifest.test[0].path, 'tests/check_gir.rs') + self.assertEqual(manifest.lib.path, 'src/lib.rs') + self.assertEqual(manifest.test[0].test, True) + self.assertEqual(manifest.test[0].doctest, True) + self.assertEqual(manifest.test[0].bench, False) + self.assertEqual(manifest.test[0].doc, False) + self.assertEqual(manifest.test[0].harness, True) + self.assertEqual(manifest.test[0].edition, '2021') + self.assertEqual(manifest.test[0].required_features, []) + self.assertEqual(manifest.test[0].plugin, False) + + def test_cargo_toml_system_deps(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + fname = os.path.join(tmpdir, 'Cargo.toml') + with open(fname, 'w', encoding='utf-8') as f: + f.write(self.CARGO_TOML_2) + manifest_toml = load_toml(fname) + manifest = Manifest.from_raw(manifest_toml, 'Cargo.toml') + + self.assertIn('system-deps', manifest.package.metadata) + + self.assertEqual(len(manifest.system_dependencies), 1) + self.assertEqual(manifest.system_dependencies['pango'].name, 'pango') + self.assertEqual(manifest.system_dependencies['pango'].version, '1.40') + self.assertEqual(manifest.system_dependencies['pango'].meson_version, ['>=1.40']) + self.assertEqual(manifest.system_dependencies['pango'].optional, False) + self.assertEqual(manifest.system_dependencies['pango'].feature, None) + + self.assertEqual(len(manifest.system_dependencies['pango'].feature_overrides), 1) + self.assertEqual(manifest.system_dependencies['pango'].feature_overrides['v1_42'], {'version': '1.42'}) + + def test_cargo_toml_features(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + fname = os.path.join(tmpdir, 'Cargo.toml') + with open(fname, 'w', encoding='utf-8') as f: + f.write(self.CARGO_TOML_2) + manifest_toml = load_toml(fname) + manifest = Manifest.from_raw(manifest_toml, 'Cargo.toml') + + self.assertEqual(len(manifest.features), 3) + self.assertEqual(manifest.features['v1_42'], ['pango-sys/v1_42']) + self.assertEqual(manifest.features['v1_44'], ['v1_42', 'pango-sys/v1_44']) + self.assertEqual(manifest.features['default'], []) diff --git a/unittests/datatests.py b/unittests/datatests.py index bd83b81..e529e4e 100644 --- a/unittests/datatests.py +++ b/unittests/datatests.py @@ -167,10 +167,10 @@ class DataTests(unittest.TestCase): debug = False else: raise RuntimeError(f'Invalid debug value {debug!r} in row:\n{m.group()}') - env.coredata.set_option(OptionKey('buildtype'), buildtype) - self.assertEqual(env.coredata.optstore.get_value('buildtype'), buildtype) - self.assertEqual(env.coredata.optstore.get_value('optimization'), opt) - self.assertEqual(env.coredata.optstore.get_value('debug'), debug) + env.coredata.optstore.set_option(OptionKey('buildtype'), buildtype) + self.assertEqual(env.coredata.optstore.get_value_for('buildtype'), buildtype) + self.assertEqual(env.coredata.optstore.get_value_for('optimization'), opt) + self.assertEqual(env.coredata.optstore.get_value_for('debug'), debug) def test_cpu_families_documented(self): with open("docs/markdown/Reference-tables.md", encoding='utf-8') as f: @@ -183,7 +183,7 @@ class DataTests(unittest.TestCase): arches = [m.group(1) for m in re.finditer(r"^\| (\w+) +\|", content, re.MULTILINE)] # Drop the header arches = set(arches[1:]) - self.assertEqual(arches, set(mesonbuild.environment.known_cpu_families)) + self.assertEqual(arches, set(mesonbuild.envconfig.known_cpu_families)) def test_markdown_files_in_sitemap(self): ''' diff --git a/unittests/failuretests.py b/unittests/failuretests.py index 18d0c5e..3743f29 100644 --- a/unittests/failuretests.py +++ b/unittests/failuretests.py @@ -41,10 +41,10 @@ def no_pkgconfig(): return [None] return old_search(self, name, search_dirs, exclude_paths) - def new_which(cmd, *kwargs): + def new_which(cmd, **kwargs): if cmd == 'pkg-config': return None - return old_which(cmd, *kwargs) + return old_which(cmd, **kwargs) shutil.which = new_which ExternalProgram._search = new_search @@ -147,11 +147,11 @@ class FailureTests(BasePlatformTests): def test_dependency(self): if subprocess.call(['pkg-config', '--exists', 'zlib']) != 0: raise unittest.SkipTest('zlib not found with pkg-config') - a = (("dependency('zlib', method : 'fail')", "'fail' is invalid"), - ("dependency('zlib', static : '1')", "[Ss]tatic.*boolean"), - ("dependency('zlib', version : 1)", "Item must be a list or one of <class 'str'>"), - ("dependency('zlib', required : 1)", "[Rr]equired.*boolean"), - ("dependency('zlib', method : 1)", "[Mm]ethod.*string"), + a = (("dependency('zlib', method : 'fail')", 'dependency keyword argument "method" must be one of auto, builtin, cmake, config-tool, cups-config, dub, extraframework, libwmf-config, pcap-config, pkg-config, qmake, sdlconfig, sysconfig, system, not fail'), + ("dependency('zlib', static : '1')", "dependency keyword argument 'static' was of type str but should have been one of: bool, NoneType"), + ("dependency('zlib', version : 1)", r"dependency keyword argument 'version' was of type array\[int\] but should have been array\[str\]"), + ("dependency('zlib', required : 1)", "dependency keyword argument 'required' was of type int but should have been one of: bool, UserFeatureOption"), + ("dependency('zlib', method : 1)", "dependency keyword argument 'method' was of type int but should have been str"), ("dependency('zlibfail')", self.dnf),) for contents, match in a: self.assertMesonRaises(contents, match) @@ -206,7 +206,7 @@ class FailureTests(BasePlatformTests): if not shutil.which('wx-config-3.0') and not shutil.which('wx-config') and not shutil.which('wx-config-gtk3'): raise unittest.SkipTest('Neither wx-config, wx-config-3.0 nor wx-config-gtk3 found') self.assertMesonRaises("dependency('wxwidgets', modules : 1)", - "module argument is not a string") + r"dependency keyword argument 'modules' was of type array\[int\] but should have been array\[str\]") def test_llvm_dependency(self): self.assertMesonRaises("dependency('llvm', modules : 'fail')", @@ -215,7 +215,7 @@ class FailureTests(BasePlatformTests): def test_boost_notfound_dependency(self): # Can be run even if Boost is found or not self.assertMesonRaises("dependency('boost', modules : 1)", - "module.*not a string") + r"dependency keyword argument 'modules' was of type array\[int\] but should have been array\[str\]") self.assertMesonRaises("dependency('boost', modules : 'fail')", f"(fail.*not found|{self.dnf})") diff --git a/unittests/internaltests.py b/unittests/internaltests.py index d7994ee..74b36a8 100644 --- a/unittests/internaltests.py +++ b/unittests/internaltests.py @@ -44,10 +44,7 @@ from mesonbuild.programs import ExternalProgram import mesonbuild.modules.pkgconfig from mesonbuild import utils - -from run_tests import ( - FakeCompilerOptions, get_fake_env, get_fake_options -) +from run_tests import get_fake_env, get_fake_options from .helpers import * @@ -112,7 +109,7 @@ class InternalTests(unittest.TestCase): stat.S_IRGRP | stat.S_IXGRP) def test_compiler_args_class_none_flush(self): - cc = ClangCCompiler([], [], 'fake', MachineChoice.HOST, False, mock.Mock()) + cc = ClangCCompiler([], [], 'fake', MachineChoice.HOST, get_fake_env()) a = cc.compiler_args(['-I.']) #first we are checking if the tree construction deduplicates the correct -I argument a += ['-I..'] @@ -129,14 +126,14 @@ class InternalTests(unittest.TestCase): self.assertEqual(a, ['-I.', '-I./tests2/', '-I./tests/', '-I..']) def test_compiler_args_class_d(self): - d = DmdDCompiler([], 'fake', MachineChoice.HOST, 'info', 'arch') + d = DmdDCompiler([], 'fake', MachineChoice.HOST, get_fake_env(), 'arch') # check include order is kept when deduplicating a = d.compiler_args(['-Ifirst', '-Isecond', '-Ithird']) a += ['-Ifirst'] self.assertEqual(a, ['-Ifirst', '-Isecond', '-Ithird']) def test_compiler_args_class_clike(self): - cc = ClangCCompiler([], [], 'fake', MachineChoice.HOST, False, mock.Mock()) + cc = ClangCCompiler([], [], 'fake', MachineChoice.HOST, get_fake_env()) # Test that empty initialization works a = cc.compiler_args() self.assertEqual(a, []) @@ -217,9 +214,10 @@ class InternalTests(unittest.TestCase): def test_compiler_args_class_visualstudio(self): - linker = linkers.MSVCDynamicLinker(MachineChoice.HOST, []) + env = get_fake_env() + linker = linkers.MSVCDynamicLinker(env, MachineChoice.HOST, []) # Version just needs to be > 19.0.0 - cc = VisualStudioCPPCompiler([], [], '20.00', MachineChoice.HOST, False, mock.Mock(), 'x64', linker=linker) + cc = VisualStudioCPPCompiler([], [], '20.00', MachineChoice.HOST, env, 'x64', linker=linker) a = cc.compiler_args(cc.get_always_args()) self.assertEqual(a.to_native(copy=True), ['/nologo', '/showIncludes', '/utf-8', '/Zc:__cplusplus']) @@ -239,8 +237,9 @@ class InternalTests(unittest.TestCase): def test_compiler_args_class_gnuld(self): ## Test --start/end-group - linker = linkers.GnuBFDDynamicLinker([], MachineChoice.HOST, '-Wl,', []) - gcc = GnuCCompiler([], [], 'fake', False, MachineChoice.HOST, mock.Mock(), linker=linker) + env = get_fake_env() + linker = linkers.GnuBFDDynamicLinker([], env, MachineChoice.HOST, '-Wl,', []) + gcc = GnuCCompiler([], [], 'fake', MachineChoice.HOST, env, linker=linker) ## Ensure that the fake compiler is never called by overriding the relevant function gcc.get_default_include_dirs = lambda: ['/usr/include', '/usr/share/include', '/usr/local/include'] ## Test that 'direct' append and extend works @@ -267,8 +266,9 @@ class InternalTests(unittest.TestCase): def test_compiler_args_remove_system(self): ## Test --start/end-group - linker = linkers.GnuBFDDynamicLinker([], MachineChoice.HOST, '-Wl,', []) - gcc = GnuCCompiler([], [], 'fake', False, MachineChoice.HOST, mock.Mock(), linker=linker) + env = get_fake_env() + linker = linkers.GnuBFDDynamicLinker([], env, MachineChoice.HOST, '-Wl,', []) + gcc = GnuCCompiler([], [], 'fake', MachineChoice.HOST, env, linker=linker) ## Ensure that the fake compiler is never called by overriding the relevant function gcc.get_default_include_dirs = lambda: ['/usr/include', '/usr/share/include', '/usr/local/include'] ## Test that 'direct' append and extend works @@ -534,18 +534,18 @@ class InternalTests(unittest.TestCase): kwargs = {'sources': [1, [2, [3]]]} self.assertEqual([1, 2, 3], extract(kwargs, 'sources')) - def _test_all_naming(self, cc, env, patterns, platform): + def _test_all_naming(self, cc, patterns, platform): shr = patterns[platform]['shared'] stc = patterns[platform]['static'] shrstc = shr + tuple(x for x in stc if x not in shr) stcshr = stc + tuple(x for x in shr if x not in stc) - p = cc.get_library_naming(env, LibType.SHARED) + p = cc.get_library_naming(LibType.SHARED) self.assertEqual(p, shr) - p = cc.get_library_naming(env, LibType.STATIC) + p = cc.get_library_naming(LibType.STATIC) self.assertEqual(p, stc) - p = cc.get_library_naming(env, LibType.PREFER_STATIC) + p = cc.get_library_naming(LibType.PREFER_STATIC) self.assertEqual(p, stcshr) - p = cc.get_library_naming(env, LibType.PREFER_SHARED) + p = cc.get_library_naming(LibType.PREFER_SHARED) self.assertEqual(p, shrstc) # Test find library by mocking up openbsd if platform != 'openbsd': @@ -554,10 +554,14 @@ class InternalTests(unittest.TestCase): for i in ['libfoo.so.6.0', 'libfoo.so.5.0', 'libfoo.so.54.0', 'libfoo.so.66a.0b', 'libfoo.so.70.0.so.1', 'libbar.so.7.10', 'libbar.so.7.9', 'libbar.so.7.9.3']: libpath = Path(tmpdir) / i - libpath.write_text('', encoding='utf-8') - found = cc._find_library_real('foo', env, [tmpdir], '', LibType.PREFER_SHARED, lib_prefix_warning=True) + src = libpath.with_suffix('.c') + with src.open('w', encoding='utf-8') as f: + f.write('int meson_foobar (void) { return 0; }') + subprocess.check_call(['gcc', str(src), '-o', str(libpath), '-shared']) + + found = cc._find_library_real('foo', [tmpdir], 'int main(void) { return 0; }', LibType.PREFER_SHARED, lib_prefix_warning=True, ignore_system_dirs=False) self.assertEqual(os.path.basename(found[0]), 'libfoo.so.54.0') - found = cc._find_library_real('bar', env, [tmpdir], '', LibType.PREFER_SHARED, lib_prefix_warning=True) + found = cc._find_library_real('bar', [tmpdir], 'int main(void) { return 0; }', LibType.PREFER_SHARED, lib_prefix_warning=True, ignore_system_dirs=False) self.assertEqual(os.path.basename(found[0]), 'libbar.so.7.10') def test_find_library_patterns(self): @@ -584,26 +588,26 @@ class InternalTests(unittest.TestCase): env = get_fake_env() cc = detect_c_compiler(env, MachineChoice.HOST) if is_osx(): - self._test_all_naming(cc, env, patterns, 'darwin') + self._test_all_naming(cc, patterns, 'darwin') elif is_cygwin(): - self._test_all_naming(cc, env, patterns, 'cygwin') + self._test_all_naming(cc, patterns, 'cygwin') elif is_windows(): if cc.get_argument_syntax() == 'msvc': - self._test_all_naming(cc, env, patterns, 'windows-msvc') + self._test_all_naming(cc, patterns, 'windows-msvc') else: - self._test_all_naming(cc, env, patterns, 'windows-mingw') + self._test_all_naming(cc, patterns, 'windows-mingw') elif is_openbsd(): - self._test_all_naming(cc, env, patterns, 'openbsd') + self._test_all_naming(cc, patterns, 'openbsd') else: - self._test_all_naming(cc, env, patterns, 'linux') + self._test_all_naming(cc, patterns, 'linux') env.machines.host.system = 'openbsd' - self._test_all_naming(cc, env, patterns, 'openbsd') + self._test_all_naming(cc, patterns, 'openbsd') env.machines.host.system = 'darwin' - self._test_all_naming(cc, env, patterns, 'darwin') + self._test_all_naming(cc, patterns, 'darwin') env.machines.host.system = 'cygwin' - self._test_all_naming(cc, env, patterns, 'cygwin') + self._test_all_naming(cc, patterns, 'cygwin') env.machines.host.system = 'windows' - self._test_all_naming(cc, env, patterns, 'windows-mingw') + self._test_all_naming(cc, patterns, 'windows-mingw') @skipIfNoPkgconfig def test_pkgconfig_parse_libs(self): @@ -613,23 +617,27 @@ class InternalTests(unittest.TestCase): https://github.com/mesonbuild/meson/issues/3951 ''' def create_static_lib(name): - if not is_osx(): - name.open('w', encoding='utf-8').close() - return src = name.with_suffix('.c') out = name.with_suffix('.o') with src.open('w', encoding='utf-8') as f: f.write('int meson_foobar (void) { return 0; }') # use of x86_64 is hardcoded in run_tests.py:get_fake_env() - subprocess.check_call(['clang', '-c', str(src), '-o', str(out), '-arch', 'x86_64']) + if is_osx(): + subprocess.check_call(['clang', '-c', str(src), '-o', str(out), '-arch', 'x86_64']) + else: + subprocess.check_call(['gcc', '-c', str(src), '-o', str(out)]) subprocess.check_call(['ar', 'csr', str(name), str(out)]) + # The test relies on some open-coded toolchain invocations for + # library creation in create_static_lib. + if is_windows() or is_cygwin(): + return + with tempfile.TemporaryDirectory() as tmpdir: pkgbin = ExternalProgram('pkg-config', command=['pkg-config'], silent=True) env = get_fake_env() compiler = detect_c_compiler(env, MachineChoice.HOST) env.coredata.compilers.host = {'c': compiler} - env.coredata.optstore.set_value_object(OptionKey('c_link_args'), FakeCompilerOptions()) p1 = Path(tmpdir) / '1' p2 = Path(tmpdir) / '2' p1.mkdir() @@ -1598,9 +1606,9 @@ class InternalTests(unittest.TestCase): """Mock all of the ways we could get the trial at once.""" mocked = mock.Mock(return_value=value) - with mock.patch('mesonbuild.environment.detect_windows_arch', mocked), \ - mock.patch('mesonbuild.environment.platform.processor', mocked), \ - mock.patch('mesonbuild.environment.platform.machine', mocked): + with mock.patch('mesonbuild.envconfig.detect_windows_arch', mocked), \ + mock.patch('mesonbuild.envconfig.platform.processor', mocked), \ + mock.patch('mesonbuild.envconfig.platform.machine', mocked): yield cases = [ @@ -1631,28 +1639,28 @@ class InternalTests(unittest.TestCase): ('aarch64_be', 'aarch64'), ] - cc = ClangCCompiler([], [], 'fake', MachineChoice.HOST, False, mock.Mock()) + cc = ClangCCompiler([], [], 'fake', MachineChoice.HOST, get_fake_env()) - with mock.patch('mesonbuild.environment.any_compiler_has_define', mock.Mock(return_value=False)): + with mock.patch('mesonbuild.envconfig.any_compiler_has_define', mock.Mock(return_value=False)): for test, expected in cases: with self.subTest(test, has_define=False), mock_trial(test): - actual = mesonbuild.environment.detect_cpu_family({'c': cc}) + actual = mesonbuild.envconfig.detect_cpu_family({'c': cc}) self.assertEqual(actual, expected) - with mock.patch('mesonbuild.environment.any_compiler_has_define', mock.Mock(return_value=True)): + with mock.patch('mesonbuild.envconfig.any_compiler_has_define', mock.Mock(return_value=True)): for test, expected in [('x86_64', 'x86'), ('aarch64', 'arm'), ('ppc', 'ppc64'), ('mips64', 'mips64')]: with self.subTest(test, has_define=True), mock_trial(test): - actual = mesonbuild.environment.detect_cpu_family({'c': cc}) + actual = mesonbuild.envconfig.detect_cpu_family({'c': cc}) self.assertEqual(actual, expected) # machine_info_can_run calls detect_cpu_family with no compilers at all with mock.patch( - 'mesonbuild.environment.any_compiler_has_define', + 'mesonbuild.envconfig.any_compiler_has_define', mock.Mock(side_effect=AssertionError('Should not be called')), ): for test, expected in [('mips64', 'mips64')]: with self.subTest(test, has_compiler=False), mock_trial(test): - actual = mesonbuild.environment.detect_cpu_family({}) + actual = mesonbuild.envconfig.detect_cpu_family({}) self.assertEqual(actual, expected) def test_detect_cpu(self) -> None: @@ -1662,9 +1670,9 @@ class InternalTests(unittest.TestCase): """Mock all of the ways we could get the trial at once.""" mocked = mock.Mock(return_value=value) - with mock.patch('mesonbuild.environment.detect_windows_arch', mocked), \ - mock.patch('mesonbuild.environment.platform.processor', mocked), \ - mock.patch('mesonbuild.environment.platform.machine', mocked): + with mock.patch('mesonbuild.envconfig.detect_windows_arch', mocked), \ + mock.patch('mesonbuild.envconfig.platform.processor', mocked), \ + mock.patch('mesonbuild.envconfig.platform.machine', mocked): yield cases = [ @@ -1680,27 +1688,27 @@ class InternalTests(unittest.TestCase): ('aarch64_be', 'aarch64'), ] - cc = ClangCCompiler([], [], 'fake', MachineChoice.HOST, False, mock.Mock()) + cc = ClangCCompiler([], [], 'fake', MachineChoice.HOST, get_fake_env()) - with mock.patch('mesonbuild.environment.any_compiler_has_define', mock.Mock(return_value=False)): + with mock.patch('mesonbuild.envconfig.any_compiler_has_define', mock.Mock(return_value=False)): for test, expected in cases: with self.subTest(test, has_define=False), mock_trial(test): - actual = mesonbuild.environment.detect_cpu({'c': cc}) + actual = mesonbuild.envconfig.detect_cpu({'c': cc}) self.assertEqual(actual, expected) - with mock.patch('mesonbuild.environment.any_compiler_has_define', mock.Mock(return_value=True)): + with mock.patch('mesonbuild.envconfig.any_compiler_has_define', mock.Mock(return_value=True)): for test, expected in [('x86_64', 'i686'), ('aarch64', 'arm'), ('ppc', 'ppc64'), ('mips64', 'mips64')]: with self.subTest(test, has_define=True), mock_trial(test): - actual = mesonbuild.environment.detect_cpu({'c': cc}) + actual = mesonbuild.envconfig.detect_cpu({'c': cc}) self.assertEqual(actual, expected) with mock.patch( - 'mesonbuild.environment.any_compiler_has_define', + 'mesonbuild.envconfig.any_compiler_has_define', mock.Mock(side_effect=AssertionError('Should not be called')), ): for test, expected in [('mips64', 'mips64')]: with self.subTest(test, has_compiler=False), mock_trial(test): - actual = mesonbuild.environment.detect_cpu({}) + actual = mesonbuild.envconfig.detect_cpu({}) self.assertEqual(actual, expected) @mock.patch('mesonbuild.interpreter.Interpreter.load_root_meson_file', mock.Mock(return_value=None)) @@ -1786,6 +1794,7 @@ class InternalTests(unittest.TestCase): 'g-ir-generate': [f'/usr/bin/{gnu_tuple}-g-ir-generate'], 'g-ir-inspect': [f'/usr/bin/{gnu_tuple}-g-ir-inspect'], 'g-ir-scanner': [f'/usr/bin/{gnu_tuple}-g-ir-scanner'], + 'vapigen': [f'/usr/bin/{gnu_tuple}-vapigen'], } for title, dpkg_arch, gccsuffix, env, expected in [ diff --git a/unittests/linuxliketests.py b/unittests/linuxliketests.py index 6b896d7..4f775d6 100644 --- a/unittests/linuxliketests.py +++ b/unittests/linuxliketests.py @@ -446,6 +446,24 @@ class LinuxlikeTests(BasePlatformTests): libdir = self.installdir + os.path.join(self.prefix, self.libdir) self._test_soname_impl(libdir, True) + @skip_if_not_base_option('b_sanitize') + def test_c_link_args_and_env(self): + ''' + Test that the CFLAGS / CXXFLAGS environment variables are + included on the linker command line when c_link_args is + set but c_args is not. + ''' + if is_cygwin(): + raise SkipTest('asan not available on Cygwin') + if is_openbsd(): + raise SkipTest('-fsanitize=address is not supported on OpenBSD') + + testdir = os.path.join(self.common_test_dir, '1 trivial') + env = {'CFLAGS': '-fsanitize=address'} + self.init(testdir, extra_args=['-Dc_link_args="-L/usr/lib"'], + override_envvars=env) + self.build() + def test_compiler_check_flags_order(self): ''' Test that compiler check flags override all other flags. This can't be @@ -986,6 +1004,22 @@ class LinuxlikeTests(BasePlatformTests): self.assertEqual(got_rpath, yonder_libdir, rpath_format) @skip_if_not_base_option('b_sanitize') + def test_env_cflags_ldflags(self): + if is_cygwin(): + raise SkipTest('asan not available on Cygwin') + if is_openbsd(): + raise SkipTest('-fsanitize=address is not supported on OpenBSD') + + testdir = os.path.join(self.common_test_dir, '1 trivial') + env = {'CFLAGS': '-fsanitize=address', 'LDFLAGS': '-I.'} + self.init(testdir, override_envvars=env) + self.build() + compdb = self.get_compdb() + for i in compdb: + self.assertIn("-fsanitize=address", i["command"]) + self.wipe() + + @skip_if_not_base_option('b_sanitize') def test_pch_with_address_sanitizer(self): if is_cygwin(): raise SkipTest('asan not available on Cygwin') @@ -1110,7 +1144,7 @@ class LinuxlikeTests(BasePlatformTests): self.assertPathExists(os.path.join(pkg_dir, 'librelativepath.pc')) env = get_fake_env(testdir, self.builddir, self.prefix) - env.coredata.set_options({OptionKey('pkg_config_path'): pkg_dir}, subproject='') + env.coredata.optstore.set_option(OptionKey('pkg_config_path'), pkg_dir) kwargs = {'required': True, 'silent': True} relative_path_dep = PkgConfigDependency('librelativepath', env, kwargs) self.assertTrue(relative_path_dep.found()) @@ -1126,13 +1160,13 @@ class LinuxlikeTests(BasePlatformTests): pkg_dir = os.path.join(testdir, 'pkgconfig') env = get_fake_env(testdir, self.builddir, self.prefix) - env.coredata.set_options({OptionKey('pkg_config_path'): pkg_dir}, subproject='') + env.coredata.optstore.set_option(OptionKey('pkg_config_path'), pkg_dir) # Regression test: This used to modify the value of `pkg_config_path` # option, adding the meson-uninstalled directory to it. PkgConfigInterface.setup_env({}, env, MachineChoice.HOST, uninstalled=True) - pkg_config_path = env.coredata.optstore.get_value('pkg_config_path') + pkg_config_path = env.coredata.optstore.get_value_for('pkg_config_path') self.assertEqual(pkg_config_path, [pkg_dir]) def test_pkgconfig_uninstalled_env_added(self): @@ -1161,8 +1195,7 @@ class LinuxlikeTests(BasePlatformTests): env = get_fake_env(testdir, self.builddir, self.prefix) - env.coredata.set_options({OptionKey('pkg_config_path'): external_pkg_config_path_dir}, - subproject='') + env.coredata.optstore.set_option(OptionKey('pkg_config_path'), external_pkg_config_path_dir) newEnv = PkgConfigInterface.setup_env({}, env, MachineChoice.HOST, uninstalled=True) @@ -1227,8 +1260,9 @@ class LinuxlikeTests(BasePlatformTests): myenv['PKG_CONFIG_PATH'] = _prepend_pkg_config_path(self.privatedir) stdo = subprocess.check_output([PKG_CONFIG, '--libs-only-l', 'libsomething'], env=myenv) deps = [b'-lgobject-2.0', b'-lgio-2.0', b'-lglib-2.0', b'-lsomething'] - if is_windows() or is_cygwin() or is_osx() or is_openbsd(): + if is_windows() or is_osx() or is_openbsd(): # On Windows, libintl is a separate library + # It used to be on Cygwin as well, but no longer is. deps.append(b'-lintl') self.assertEqual(set(deps), set(stdo.split())) @@ -1489,7 +1523,7 @@ class LinuxlikeTests(BasePlatformTests): env = get_fake_env() cc = detect_c_compiler(env, MachineChoice.HOST) linker = cc.linker - if not linker.export_dynamic_args(env): + if not linker.export_dynamic_args(): raise SkipTest('Not applicable for linkers without --export-dynamic') self.init(testdir) build_ninja = os.path.join(self.builddir, 'build.ninja') @@ -1725,16 +1759,13 @@ class LinuxlikeTests(BasePlatformTests): self.assertNotIn('-lfoo', content) def test_prelinking(self): - # Prelinking currently only works on recently new GNU toolchains. - # Skip everything else. When support for other toolchains is added, - # remove limitations as necessary. - if 'clang' in os.environ.get('CC', 'dummy') and not is_osx(): - raise SkipTest('Prelinking not supported with Clang.') testdir = os.path.join(self.unit_test_dir, '86 prelinking') env = get_fake_env(testdir, self.builddir, self.prefix) cc = detect_c_compiler(env, MachineChoice.HOST) if cc.id == "gcc" and not version_compare(cc.version, '>=9'): raise SkipTest('Prelinking not supported with gcc 8 or older.') + if cc.id == 'clang' and not version_compare(cc.version, '>=14'): + raise SkipTest('Prelinking not supported with Clang 13 or older.') self.init(testdir) self.build() outlib = os.path.join(self.builddir, 'libprelinked.a') @@ -1859,7 +1890,7 @@ class LinuxlikeTests(BasePlatformTests): self.assertIn('build t13-e1: c_LINKER t13-e1.p/main.c.o | libt12-s1.a libt13-s3.a\n', content) def test_top_options_in_sp(self): - testdir = os.path.join(self.unit_test_dir, '125 pkgsubproj') + testdir = os.path.join(self.unit_test_dir, '127 pkgsubproj') self.init(testdir) def test_unreadable_dir_in_declare_dep(self): @@ -1937,8 +1968,25 @@ class LinuxlikeTests(BasePlatformTests): self.check_has_flag(compdb, sub1src, '-O2') self.check_has_flag(compdb, sub2src, '-O2') + @skip_if_not_language('rust') + @skip_if_not_base_option('b_sanitize') + def test_rust_sanitizers(self): + args = ['-Drust_nightly=disabled', '-Db_lundef=false'] + testdir = os.path.join(self.rust_test_dir, '28 mixed') + tests = ['address'] + + env = get_fake_env(testdir, self.builddir, self.prefix) + cpp = detect_cpp_compiler(env, MachineChoice.HOST) + if cpp.find_library('ubsan', []): + tests += ['address,undefined'] + + for value in tests: + self.init(testdir, extra_args=args + ['-Db_sanitize=' + value]) + self.build() + self.wipe() + def test_sanitizers(self): - testdir = os.path.join(self.unit_test_dir, '127 sanitizers') + testdir = os.path.join(self.unit_test_dir, '129 sanitizers') with self.subTest('no b_sanitize value'): try: diff --git a/unittests/machinefiletests.py b/unittests/machinefiletests.py index b2839e6..7f583de 100644 --- a/unittests/machinefiletests.py +++ b/unittests/machinefiletests.py @@ -204,7 +204,7 @@ class NativeFileTests(BasePlatformTests): def test_config_tool_dep(self): # Do the skip at this level to avoid screwing up the cache - if mesonbuild.environment.detect_msys2_arch(): + if mesonbuild.envconfig.detect_msys2_arch(): raise SkipTest('Skipped due to problems with LLVM on MSYS2') if not shutil.which('llvm-config'): raise SkipTest('No llvm-installed, cannot test') @@ -550,8 +550,8 @@ class NativeFileTests(BasePlatformTests): # into augments. self.assertEqual(found, 2, 'Did not find all two sections') - def test_builtin_options_subprojects_overrides_buildfiles(self): - # If the buildfile says subproject(... default_library: shared), ensure that's overwritten + def test_builtin_options_machinefile_overrides_subproject(self): + # The buildfile says subproject(... default_library: static), the machinefile overrides it testcase = os.path.join(self.common_test_dir, '223 persubproject options') config = self.helper_create_native_file({'sub2:built-in options': {'default_library': 'shared'}}) @@ -563,8 +563,8 @@ class NativeFileTests(BasePlatformTests): check = cm.exception.stdout self.assertIn(check, 'Parent should override default_library') - def test_builtin_options_subprojects_dont_inherits_parent_override(self): - # If the buildfile says subproject(... default_library: shared), ensure that's overwritten + def test_builtin_options_machinefile_global_loses_over_subproject(self): + # The buildfile says subproject(... default_library: static), ensure that it overrides the machinefile testcase = os.path.join(self.common_test_dir, '223 persubproject options') config = self.helper_create_native_file({'built-in options': {'default_library': 'both'}}) self.init(testcase, extra_args=['--native-file', config]) diff --git a/unittests/optiontests.py b/unittests/optiontests.py index 5ed601f..a3a2f54 100644 --- a/unittests/optiontests.py +++ b/unittests/optiontests.py @@ -35,13 +35,29 @@ class OptionTests(unittest.TestCase): optstore.initialize_from_top_level_project_call({OptionKey('someoption'): new_value}, {}, {}) self.assertEqual(optstore.get_value_for(k), new_value) + def test_machine_vs_project(self): + optstore = OptionStore(False) + name = 'backend' + default_value = 'ninja' + proj_value = 'xcode' + mfile_value = 'vs2010' + k = OptionKey(name) + prefix = UserStringOption('prefix', 'This is needed by OptionStore', '/usr') + optstore.add_system_option('prefix', prefix) + vo = UserStringOption(k.name, 'You know what this is', default_value) + optstore.add_system_option(k.name, vo) + self.assertEqual(optstore.get_value_for(k), default_value) + optstore.initialize_from_top_level_project_call({OptionKey(name): proj_value}, {}, + {OptionKey(name): mfile_value}) + self.assertEqual(optstore.get_value_for(k), mfile_value) + def test_subproject_system_option(self): """Test that subproject system options get their default value from the global option (e.g. "sub:b_lto" can be initialized from "b_lto").""" optstore = OptionStore(False) - name = 'someoption' - default_value = 'somevalue' - new_value = 'new_value' + name = 'b_lto' + default_value = 'false' + new_value = 'true' k = OptionKey(name) subk = k.evolve(subproject='sub') optstore.initialize_from_top_level_project_call({}, {}, {OptionKey(name): new_value}) @@ -105,6 +121,14 @@ class OptionTests(unittest.TestCase): self.assertEqual(optstore.get_value_for(name, 'sub'), sub_value) self.assertEqual(num_options(optstore), 2) + def test_toplevel_project_yielding(self): + optstore = OptionStore(False) + name = 'someoption' + top_value = 'top' + vo = UserStringOption(name, 'A top level option', top_value, True) + optstore.add_project_option(OptionKey(name, ''), vo) + self.assertEqual(optstore.get_value_for(name, ''), top_value) + def test_project_yielding(self): optstore = OptionStore(False) name = 'someoption' @@ -136,6 +160,30 @@ class OptionTests(unittest.TestCase): self.assertEqual(optstore.get_value_for(sub_name, 'sub'), sub_value) self.assertEqual(num_options(optstore), 2) + def test_project_yielding_initialize(self): + optstore = OptionStore(False) + name = 'someoption' + top_value = 'top' + sub_value = 'sub' + subp = 'subp' + cmd_line = { OptionKey(name): top_value, OptionKey(name, subp): sub_value } + + vo = UserStringOption(name, 'A top level option', 'default1') + optstore.add_project_option(OptionKey(name, ''), vo) + optstore.initialize_from_top_level_project_call({}, cmd_line, {}) + self.assertEqual(optstore.get_value_for(name, ''), top_value) + self.assertEqual(num_options(optstore), 1) + + vo2 = UserStringOption(name, 'A subproject option', 'default2', True) + optstore.add_project_option(OptionKey(name, 'subp'), vo2) + self.assertEqual(optstore.get_value_for(name, ''), top_value) + self.assertEqual(optstore.get_value_for(name, subp), top_value) + self.assertEqual(num_options(optstore), 2) + + optstore.initialize_from_subproject_call(subp, {}, {}, cmd_line, {}) + self.assertEqual(optstore.get_value_for(name, ''), top_value) + self.assertEqual(optstore.get_value_for(name, subp), sub_value) + def test_augments(self): optstore = OptionStore(False) name = 'cpp_std' @@ -155,25 +203,25 @@ class OptionTests(unittest.TestCase): # First augment a subproject with self.subTest('set subproject override'): - optstore.set_from_configure_command([f'{sub_name}:{name}={aug_value}'], []) + optstore.set_from_configure_command({OptionKey.from_string(f'{sub_name}:{name}'): aug_value}) self.assertEqual(optstore.get_value_for(name), top_value) self.assertEqual(optstore.get_value_for(name, sub_name), aug_value) self.assertEqual(optstore.get_value_for(name, sub2_name), top_value) with self.subTest('unset subproject override'): - optstore.set_from_configure_command([], [f'{sub_name}:{name}']) + optstore.set_from_configure_command({OptionKey.from_string(f'{sub_name}:{name}'): None}) self.assertEqual(optstore.get_value_for(name), top_value) self.assertEqual(optstore.get_value_for(name, sub_name), top_value) self.assertEqual(optstore.get_value_for(name, sub2_name), top_value) # And now augment the top level option - optstore.set_from_configure_command([f':{name}={aug_value}'], []) + optstore.set_from_configure_command({OptionKey.from_string(f':{name}'): aug_value}) self.assertEqual(optstore.get_value_for(name, None), top_value) self.assertEqual(optstore.get_value_for(name, ''), aug_value) self.assertEqual(optstore.get_value_for(name, sub_name), top_value) self.assertEqual(optstore.get_value_for(name, sub2_name), top_value) - optstore.set_from_configure_command([], [f':{name}']) + optstore.set_from_configure_command({OptionKey.from_string(f':{name}'): None}) self.assertEqual(optstore.get_value_for(name), top_value) self.assertEqual(optstore.get_value_for(name, sub_name), top_value) self.assertEqual(optstore.get_value_for(name, sub2_name), top_value) @@ -193,12 +241,257 @@ class OptionTests(unittest.TestCase): choices=['c++98', 'c++11', 'c++14', 'c++17', 'c++20', 'c++23'], ) optstore.add_system_option(name, co) - optstore.set_from_configure_command([f'{sub_name}:{name}={aug_value}'], []) - optstore.set_from_configure_command([f'{sub_name}:{name}={set_value}'], []) + optstore.set_from_configure_command({OptionKey.from_string(f'{sub_name}:{name}'): aug_value}) + optstore.set_from_configure_command({OptionKey.from_string(f'{sub_name}:{name}'): set_value}) self.assertEqual(optstore.get_value_for(name), top_value) self.assertEqual(optstore.get_value_for(name, sub_name), set_value) - def test_b_default(self): + def test_build_to_host(self): + key = OptionKey('cpp_std') + def_value = 'c++98' + opt_value = 'c++17' + optstore = OptionStore(False) + co = UserComboOption(key.name, + 'C++ language standard to use', + def_value, + choices=['c++98', 'c++11', 'c++14', 'c++17', 'c++20', 'c++23'], + ) + optstore.add_compiler_option('cpp', key, co) + + cmd_line = {key: opt_value} + optstore.initialize_from_top_level_project_call({}, cmd_line, {}) + self.assertEqual(optstore.get_option_and_value_for(key.as_build())[1], opt_value) + self.assertEqual(optstore.get_value_for(key.as_build()), opt_value) + + def test_build_to_host_subproject(self): + key = OptionKey('cpp_std') + def_value = 'c++98' + opt_value = 'c++17' + subp = 'subp' + optstore = OptionStore(False) + co = UserComboOption(key.name, + 'C++ language standard to use', + def_value, + choices=['c++98', 'c++11', 'c++14', 'c++17', 'c++20', 'c++23'], + ) + optstore.add_compiler_option('cpp', key, co) + + spcall = {key: opt_value} + optstore.initialize_from_top_level_project_call({}, {}, {}) + optstore.initialize_from_subproject_call(subp, spcall, {}, {}, {}) + self.assertEqual(optstore.get_option_and_value_for(key.evolve(subproject=subp, + machine=MachineChoice.BUILD))[1], opt_value) + self.assertEqual(optstore.get_value_for(key.evolve(subproject=subp, + machine=MachineChoice.BUILD)), opt_value) + + def test_build_to_host_cross(self): + key = OptionKey('cpp_std') + def_value = 'c++98' + opt_value = 'c++17' + optstore = OptionStore(True) + for k in [key, key.as_build()]: + co = UserComboOption(key.name, + 'C++ language standard to use', + def_value, + choices=['c++98', 'c++11', 'c++14', 'c++17', 'c++20', 'c++23'], + ) + optstore.add_compiler_option('cpp', k, co) + + cmd_line = {key: opt_value} + optstore.initialize_from_top_level_project_call({}, cmd_line, {}) + print(optstore.options) + + self.assertEqual(optstore.get_option_and_value_for(key)[1], opt_value) + self.assertEqual(optstore.get_option_and_value_for(key.as_build())[1], def_value) + self.assertEqual(optstore.get_value_for(key), opt_value) + self.assertEqual(optstore.get_value_for(key.as_build()), def_value) + + def test_b_nonexistent(self): + optstore = OptionStore(False) + self.assertTrue(optstore.accept_as_pending_option(OptionKey('b_ndebug'))) + self.assertFalse(optstore.accept_as_pending_option(OptionKey('b_whatever'))) + + def test_backend_option_pending(self): optstore = OptionStore(False) - value = optstore.get_default_for_b_option(OptionKey('b_vscrt')) - self.assertEqual(value, 'from_buildtype') + # backend options are known after the first invocation + self.assertTrue(optstore.accept_as_pending_option(OptionKey('backend_whatever'), True)) + self.assertFalse(optstore.accept_as_pending_option(OptionKey('backend_whatever'), False)) + + def test_reconfigure_b_nonexistent(self): + optstore = OptionStore(False) + optstore.set_from_configure_command({OptionKey('b_ndebug'): True}) + + def test_unconfigure_nonexistent(self): + optstore = OptionStore(False) + with self.assertRaises(MesonException): + optstore.set_from_configure_command({OptionKey('nonexistent'): None}) + + def test_subproject_proj_opt_with_same_name(self): + name = 'tests' + subp = 'subp' + + optstore = OptionStore(False) + prefix = UserStringOption('prefix', 'This is needed by OptionStore', '/usr') + optstore.add_system_option('prefix', prefix) + o = UserBooleanOption(name, 'Tests', False) + optstore.add_project_option(OptionKey(name, subproject=''), o) + o = UserBooleanOption(name, 'Tests', True) + optstore.add_project_option(OptionKey(name, subproject=subp), o) + + cmd_line = {OptionKey(name): True} + spcall = {OptionKey(name): False} + + optstore.initialize_from_top_level_project_call({}, cmd_line, {}) + optstore.initialize_from_subproject_call(subp, spcall, {}, cmd_line, {}) + self.assertEqual(optstore.get_value_for(name, ''), True) + self.assertEqual(optstore.get_value_for(name, subp), False) + + def test_subproject_cmdline_override_global(self): + name = 'optimization' + subp = 'subp' + new_value = '0' + + optstore = OptionStore(False) + prefix = UserStringOption('prefix', 'This is needed by OptionStore', '/usr') + optstore.add_system_option('prefix', prefix) + o = UserComboOption(name, 'Optimization level', '0', choices=['plain', '0', 'g', '1', '2', '3', 's']) + optstore.add_system_option(name, o) + + toplevel_proj_default = {OptionKey(name): 's'} + subp_proj_default = {OptionKey(name): '3'} + cmd_line = {OptionKey(name): new_value} + + optstore.initialize_from_top_level_project_call(toplevel_proj_default, cmd_line, {}) + optstore.initialize_from_subproject_call(subp, {}, subp_proj_default, cmd_line, {}) + self.assertEqual(optstore.get_value_for(name, subp), new_value) + self.assertEqual(optstore.get_value_for(name), new_value) + + def test_subproject_parent_override_subp(self): + name = 'optimization' + subp = 'subp' + default_value = 's' + subp_value = '0' + + optstore = OptionStore(False) + prefix = UserStringOption('prefix', 'This is needed by OptionStore', '/usr') + optstore.add_system_option('prefix', prefix) + o = UserComboOption(name, 'Optimization level', '0', choices=['plain', '0', 'g', '1', '2', '3', 's']) + optstore.add_system_option(name, o) + + toplevel_proj_default = {OptionKey(name, subproject=subp): subp_value, OptionKey(name): default_value} + subp_proj_default = {OptionKey(name): '3'} + + optstore.initialize_from_top_level_project_call(toplevel_proj_default, {}, {}) + optstore.initialize_from_subproject_call(subp, {}, subp_proj_default, {}, {}) + self.assertEqual(optstore.get_value_for(name, subp), subp_value) + self.assertEqual(optstore.get_value_for(name), default_value) + + def test_subproject_cmdline_override_global_and_augment(self): + name = 'optimization' + subp = 'subp' + global_value = 's' + new_value = '0' + + optstore = OptionStore(False) + prefix = UserStringOption('prefix', 'This is needed by OptionStore', '/usr') + optstore.add_system_option('prefix', prefix) + o = UserComboOption(name, 'Optimization level', '0', choices=['plain', '0', 'g', '1', '2', '3', 's']) + optstore.add_system_option(name, o) + + toplevel_proj_default = {OptionKey(name): '1'} + subp_proj_default = {OptionKey(name): '3'} + cmd_line = {OptionKey(name): global_value, OptionKey(name, subproject=subp): new_value} + + optstore.initialize_from_top_level_project_call(toplevel_proj_default, cmd_line, {}) + optstore.initialize_from_subproject_call(subp, {}, subp_proj_default, cmd_line, {}) + self.assertEqual(optstore.get_value_for(name, subp), new_value) + self.assertEqual(optstore.get_value_for(name), global_value) + + def test_subproject_cmdline_override_toplevel(self): + name = 'default_library' + subp = 'subp' + toplevel_value = 'both' + subp_value = 'static' + + optstore = OptionStore(False) + prefix = UserStringOption('prefix', 'This is needed by OptionStore', '/usr') + optstore.add_system_option('prefix', prefix) + o = UserComboOption(name, 'Kind of library', 'both', choices=['shared', 'static', 'both']) + optstore.add_system_option(name, o) + + toplevel_proj_default = {OptionKey(name): 'shared'} + subp_proj_default = {OptionKey(name): subp_value} + cmd_line = {OptionKey(name, subproject=''): toplevel_value} + + optstore.initialize_from_top_level_project_call(toplevel_proj_default, cmd_line, {}) + optstore.initialize_from_subproject_call(subp, {}, subp_proj_default, cmd_line, {}) + self.assertEqual(optstore.get_value_for(name, subp), subp_value) + self.assertEqual(optstore.get_value_for(name, ''), toplevel_value) + + def test_subproject_buildtype(self): + subp = 'subp' + main1 = {OptionKey('buildtype'): 'release'} + main2 = {OptionKey('optimization'): '3', OptionKey('debug'): 'false'} + sub1 = {OptionKey('buildtype'): 'debug'} + sub2 = {OptionKey('optimization'): '0', OptionKey('debug'): 'true'} + + for mainopt, subopt in ((main1, sub1), + (main2, sub2), + ({**main1, **main2}, {**sub1, **sub2})): + optstore = OptionStore(False) + prefix = UserStringOption('prefix', 'This is needed by OptionStore', '/usr') + optstore.add_system_option('prefix', prefix) + o = UserComboOption('buildtype', 'Build type to use', 'debug', choices=['plain', 'debug', 'debugoptimized', 'release', 'minsize', 'custom']) + optstore.add_system_option(o.name, o) + o = UserComboOption('optimization', 'Optimization level', '0', choices=['plain', '0', 'g', '1', '2', '3', 's']) + optstore.add_system_option(o.name, o) + o = UserBooleanOption('debug', 'Enable debug symbols and other information', True) + optstore.add_system_option(o.name, o) + + optstore.initialize_from_top_level_project_call(mainopt, {}, {}) + optstore.initialize_from_subproject_call(subp, {}, subopt, {}, {}) + self.assertEqual(optstore.get_value_for('buildtype', subp), 'debug') + self.assertEqual(optstore.get_value_for('optimization', subp), '0') + self.assertEqual(optstore.get_value_for('debug', subp), True) + + def test_deprecated_nonstring_value(self): + # TODO: add a lot more deprecated option tests + optstore = OptionStore(False) + name = 'deprecated' + do = UserStringOption(name, 'An option with some deprecation', '0', + deprecated={'true': '1'}) + optstore.add_system_option(name, do) + optstore.set_option(OptionKey(name), True) + value = optstore.get_value_for(name) + self.assertEqual(value, '1') + + def test_pending_augment_validation(self): + name = 'b_lto' + subproject = 'mysubproject' + + optstore = OptionStore(False) + prefix = UserStringOption('prefix', 'This is needed by OptionStore', '/usr') + optstore.add_system_option('prefix', prefix) + + optstore.initialize_from_top_level_project_call({}, {}, {}) + optstore.initialize_from_subproject_call(subproject, {}, {OptionKey(name): 'true'}, {}, {}) + + bo = UserBooleanOption(name, 'LTO', False) + key = OptionKey(name, subproject=subproject) + optstore.add_system_option(key, bo) + stored_value = optstore.get_value_for(key) + self.assertIsInstance(stored_value, bool) + self.assertTrue(stored_value) + + def test_yielding_boolean_option_with_falsy_parent(self): + """Test that yielding is correctly initialized when parent option value is False.""" + optstore = OptionStore(False) + name = 'someoption' + subproject_name = 'sub' + parent_option = UserBooleanOption(name, 'A parent boolean option', False, yielding=True) + optstore.add_project_option(OptionKey(name, ''), parent_option) + + child_option = UserBooleanOption(name, 'A child boolean option', True, yielding=True) + child_key = OptionKey(name, subproject_name) + optstore.add_project_option(child_key, child_option) + self.assertTrue(optstore.options[child_key].yielding) diff --git a/unittests/platformagnostictests.py b/unittests/platformagnostictests.py index d6c0078..7660ee4 100644 --- a/unittests/platformagnostictests.py +++ b/unittests/platformagnostictests.py @@ -95,6 +95,23 @@ class PlatformAgnosticTests(BasePlatformTests): testdir = os.path.join(self.unit_test_dir, '102 python without pkgconfig') self.init(testdir, override_envvars={'PKG_CONFIG': 'notfound'}) + def test_vala_target_with_internal_glib(self): + testdir = os.path.join(self.unit_test_dir, '131 vala internal glib') + for run in [{ 'version': '2.84.4', 'expected': '2.84'}, { 'version': '2.85.2', 'expected': '2.84' }]: + self.new_builddir() + self.init(testdir, extra_args=[f'-Dglib-version={run["version"]}']) + try: + with open(os.path.join(self.builddir, 'meson-info', 'intro-targets.json'), 'r', encoding='utf-8') as tgt_intro: + intro = json.load(tgt_intro) + target = list(filter(lambda tgt: tgt['name'] == 'vala-tgt', intro)) + self.assertLength(target, 1) + sources = target[0]['target_sources'] + vala_sources = filter(lambda src: src.get('language') == 'vala', sources) + for src in vala_sources: + self.assertIn(('--target-glib', run['expected']), zip(src['parameters'], src['parameters'][1:])) + except FileNotFoundError: + self.skipTest('Current backend does not produce introspection data') + def test_debug_function_outputs_to_meson_log(self): testdir = os.path.join(self.unit_test_dir, '104 debug function') log_msg = 'This is an example debug output, should only end up in debug log' @@ -175,7 +192,7 @@ class PlatformAgnosticTests(BasePlatformTests): with self.subTest('Changing the backend'): with self.assertRaises(subprocess.CalledProcessError) as cm: self.setconf('-Dbackend=none') - self.assertIn("ERROR: Tried to modify read only option 'backend'", cm.exception.stdout) + self.assertIn('ERROR: Tried to modify read only option "backend"', cm.exception.stdout) # Check that the new value was not written in the store. with self.subTest('option is stored correctly'): @@ -203,10 +220,10 @@ class PlatformAgnosticTests(BasePlatformTests): # Reconfigure of not empty builddir should work self.new_builddir() Path(self.builddir, 'dummy').touch() - self.init(testdir, extra_args=['--reconfigure']) + self.init(testdir, extra_args=['--reconfigure', '--buildtype=custom']) # Setup a valid builddir should update options but not reconfigure - self.assertEqual(self.getconf('buildtype'), 'debug') + self.assertEqual(self.getconf('buildtype'), 'custom') o = self.init(testdir, extra_args=['-Dbuildtype=release']) self.assertIn('Directory already configured', o) self.assertNotIn('The Meson build system', o) @@ -279,10 +296,10 @@ class PlatformAgnosticTests(BasePlatformTests): data = json.load(f)['meson'] with open(os.path.join(testdir, 'expected_mods.json'), encoding='utf-8') as f: - expected = json.load(f)['meson']['modules'] + expected = json.load(f)['meson'] - self.assertEqual(data['modules'], expected) - self.assertEqual(data['count'], 70) + self.assertEqual(data['modules'], expected['modules']) + self.assertEqual(data['count'], expected['count']) def test_meson_package_cache_dir(self): # Copy testdir into temporary directory to not pollute meson source tree. @@ -421,12 +438,12 @@ class PlatformAgnosticTests(BasePlatformTests): with self.subTest('unknown user option'): out = self.init(testdir, extra_args=['-Dnot_an_option=1'], allow_fail=True) - self.assertIn('ERROR: Unknown options: "not_an_option"', out) + self.assertIn('ERROR: Unknown option: "not_an_option"', out) with self.subTest('unknown builtin option'): self.new_builddir() out = self.init(testdir, extra_args=['-Db_not_an_option=1'], allow_fail=True) - self.assertIn('ERROR: Unknown options: "b_not_an_option"', out) + self.assertIn('ERROR: Unknown option: "b_not_an_option"', out) def test_configure_new_option(self) -> None: @@ -451,7 +468,17 @@ class PlatformAgnosticTests(BasePlatformTests): f.write(line) with self.assertRaises(subprocess.CalledProcessError) as e: self.setconf('-Dneg_int_opt=0') - self.assertIn('Unknown options: ":neg_int_opt"', e.exception.stdout) + self.assertIn('Unknown option: "neg_int_opt"', e.exception.stdout) + + def test_reconfigure_option(self) -> None: + testdir = self.copy_srcdir(os.path.join(self.common_test_dir, '40 options')) + self.init(testdir) + self.assertEqual(self.getconf('neg_int_opt'), -3) + with self.assertRaises(subprocess.CalledProcessError) as e: + self.init(testdir, extra_args=['--reconfigure', '-Dneg_int_opt=0']) + self.assertEqual(self.getconf('neg_int_opt'), -3) + self.init(testdir, extra_args=['--reconfigure', '-Dneg_int_opt=-2']) + self.assertEqual(self.getconf('neg_int_opt'), -2) def test_configure_option_changed_constraints(self) -> None: """Changing the constraints of an option without reconfiguring should work.""" @@ -491,7 +518,7 @@ class PlatformAgnosticTests(BasePlatformTests): os.unlink(os.path.join(testdir, 'meson_options.txt')) with self.assertRaises(subprocess.CalledProcessError) as e: self.setconf('-Dneg_int_opt=0') - self.assertIn('Unknown options: ":neg_int_opt"', e.exception.stdout) + self.assertIn('Unknown option: "neg_int_opt"', e.exception.stdout) def test_configure_options_file_added(self) -> None: """A new project option file should be detected.""" @@ -533,3 +560,8 @@ class PlatformAgnosticTests(BasePlatformTests): self.clean() self._run(self.mtest_command + ['runner-with-exedep']) + + def test_setup_mixed_long_short_options(self) -> None: + """Mixing unity and unity_size as long and short options should work.""" + testdir = self.copy_srcdir(os.path.join(self.common_test_dir, '1 trivial')) + self.init(testdir, extra_args=['-Dunity=on', '--unity-size=123']) diff --git a/unittests/rewritetests.py b/unittests/rewritetests.py index 57a6782..73e6c7f 100644 --- a/unittests/rewritetests.py +++ b/unittests/rewritetests.py @@ -46,6 +46,18 @@ class RewriterTests(BasePlatformTests): args = [args] return self.rewrite_raw(directory, ['command'] + args) + # The rewriter sorts the sources alphabetically, but this is very unstable + # and buggy, so we do not test it. + def assertEqualIgnoreOrder(self, a, b): + def deepsort(x): + if isinstance(x, list): + return sorted(deepsort(el) for el in x) + elif isinstance(x, dict): + return {k: deepsort(v) for k,v in x.items()} + else: + return x + self.assertDictEqual(deepsort(a), deepsort(b)) + def test_target_source_list(self): self.prime('1 basic') out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) @@ -61,32 +73,40 @@ class RewriterTests(BasePlatformTests): 'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['fileB.cpp', 'fileC.cpp', 'main.cpp', 'fileA.cpp'], 'extra_files': []}, 'trivialprog8@exe': {'name': 'trivialprog8', 'sources': ['main.cpp', 'fileA.cpp'], 'extra_files': []}, 'trivialprog9@exe': {'name': 'trivialprog9', 'sources': ['main.cpp', 'fileA.cpp'], 'extra_files': []}, + 'trivialprog10@exe': {'name': 'trivialprog10', 'sources': ['main.cpp', 'fileA.cpp', 'fileB.cpp'], 'extra_files': []}, + 'trivialprog11@exe': {'name': 'trivialprog11', 'sources': ['main.cpp', 'fileA.cpp'], 'extra_files': []}, + 'trivialprog12@exe': {'name': 'trivialprog12', 'sources': ['main.cpp', 'fileA.cpp'], 'extra_files': []}, + 'rightName@exe': {'name': 'rightName', 'sources': ['main.cpp'], 'extra_files': []}, } } - self.assertDictEqual(out, expected) + self.assertEqualIgnoreOrder(out, expected) def test_target_add_sources(self): self.prime('1 basic') out = self.rewrite(self.builddir, os.path.join(self.builddir, 'addSrc.json')) expected = { 'target': { - 'trivialprog0@exe': {'name': 'trivialprog0', 'sources': ['a1.cpp', 'a2.cpp', 'a6.cpp', 'fileA.cpp', 'main.cpp', 'a7.cpp', 'fileB.cpp', 'fileC.cpp'], 'extra_files': []}, - 'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['a1.cpp', 'a2.cpp', 'a6.cpp', 'fileA.cpp', 'main.cpp'], 'extra_files': []}, - 'trivialprog2@exe': {'name': 'trivialprog2', 'sources': ['a7.cpp', 'fileB.cpp', 'fileC.cpp'], 'extra_files': []}, + 'trivialprog0@exe': {'name': 'trivialprog0', 'sources': ['main.cpp', 'fileA.cpp', 'fileB.cpp', 'fileC.cpp'], 'extra_files': []}, + 'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['main.cpp', 'fileA.cpp', 'a1.cpp', 'a2.cpp'], 'extra_files': []}, + 'trivialprog2@exe': {'name': 'trivialprog2', 'sources': ['fileB.cpp', 'fileC.cpp', 'a7.cpp'], 'extra_files': []}, 'trivialprog3@exe': {'name': 'trivialprog3', 'sources': ['a5.cpp', 'fileA.cpp', 'main.cpp'], 'extra_files': []}, - 'trivialprog4@exe': {'name': 'trivialprog4', 'sources': ['a5.cpp', 'main.cpp', 'fileA.cpp'], 'extra_files': []}, - 'trivialprog5@exe': {'name': 'trivialprog5', 'sources': ['a3.cpp', 'main.cpp', 'a7.cpp', 'fileB.cpp', 'fileC.cpp'], 'extra_files': []}, + 'trivialprog4@exe': {'name': 'trivialprog4', 'sources': ['main.cpp', 'a5.cpp', 'fileA.cpp'], 'extra_files': []}, + 'trivialprog5@exe': {'name': 'trivialprog5', 'sources': ['fileB.cpp', 'fileC.cpp', 'a3.cpp', 'main.cpp'], 'extra_files': []}, 'trivialprog6@exe': {'name': 'trivialprog6', 'sources': ['main.cpp', 'fileA.cpp', 'a4.cpp'], 'extra_files': []}, - 'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['fileB.cpp', 'fileC.cpp', 'a1.cpp', 'a2.cpp', 'a6.cpp', 'fileA.cpp', 'main.cpp'], 'extra_files': []}, - 'trivialprog8@exe': {'name': 'trivialprog8', 'sources': ['a1.cpp', 'a2.cpp', 'a6.cpp', 'fileA.cpp', 'main.cpp'], 'extra_files': []}, - 'trivialprog9@exe': {'name': 'trivialprog9', 'sources': ['a1.cpp', 'a2.cpp', 'a6.cpp', 'fileA.cpp', 'main.cpp'], 'extra_files': []}, + 'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['fileB.cpp', 'fileC.cpp', 'fileA.cpp', 'main.cpp'], 'extra_files': []}, + 'trivialprog8@exe': {'name': 'trivialprog8', 'sources': ['main.cpp', 'fileA.cpp'], 'extra_files': []}, + 'trivialprog9@exe': {'name': 'trivialprog9', 'sources': ['main.cpp', 'fileA.cpp', 'a1.cpp', 'a6.cpp' ], 'extra_files': []}, + 'trivialprog10@exe': {'name': 'trivialprog10', 'sources': ['main.cpp', 'fileA.cpp', 'fileB.cpp', 'a1.cpp'], 'extra_files': []}, + 'trivialprog11@exe': {'name': 'trivialprog11', 'sources': ['a1.cpp', 'fileA.cpp', 'main.cpp'], 'extra_files': []}, + 'trivialprog12@exe': {'name': 'trivialprog12', 'sources': ['a1.cpp', 'fileA.cpp', 'fileB.cpp', 'main.cpp'], 'extra_files': []}, + 'rightName@exe': {'name': 'rightName', 'sources': ['main.cpp'], 'extra_files': []}, } } - self.assertDictEqual(out, expected) + self.assertEqualIgnoreOrder(out, expected) # Check the written file out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - self.assertDictEqual(out, expected) + self.assertEqualIgnoreOrder(out, expected) def test_target_add_sources_abs(self): self.prime('1 basic') @@ -95,7 +115,7 @@ class RewriterTests(BasePlatformTests): inf = json.dumps([{"type": "target", "target": "trivialprog1", "operation": "info"}]) self.rewrite(self.builddir, add) out = self.rewrite(self.builddir, inf) - expected = {'target': {'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['a1.cpp', 'a2.cpp', 'a6.cpp', 'fileA.cpp', 'main.cpp'], 'extra_files': []}}} + expected = {'target': {'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['main.cpp', 'fileA.cpp', 'a1.cpp', 'a2.cpp', 'a6.cpp'], 'extra_files': []}}} self.assertDictEqual(out, expected) def test_target_remove_sources(self): @@ -103,28 +123,32 @@ class RewriterTests(BasePlatformTests): out = self.rewrite(self.builddir, os.path.join(self.builddir, 'rmSrc.json')) expected = { 'target': { - 'trivialprog0@exe': {'name': 'trivialprog0', 'sources': ['main.cpp', 'fileC.cpp'], 'extra_files': []}, - 'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['main.cpp'], 'extra_files': []}, - 'trivialprog2@exe': {'name': 'trivialprog2', 'sources': ['fileC.cpp'], 'extra_files': []}, + 'trivialprog0@exe': {'name': 'trivialprog0', 'sources': ['main.cpp', 'fileA.cpp', 'fileB.cpp', 'fileC.cpp'], 'extra_files': []}, + 'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['main.cpp', 'fileA.cpp'], 'extra_files': []}, + 'trivialprog2@exe': {'name': 'trivialprog2', 'sources': ['fileB.cpp', 'fileC.cpp'], 'extra_files': []}, 'trivialprog3@exe': {'name': 'trivialprog3', 'sources': ['main.cpp'], 'extra_files': []}, 'trivialprog4@exe': {'name': 'trivialprog4', 'sources': ['main.cpp'], 'extra_files': []}, - 'trivialprog5@exe': {'name': 'trivialprog5', 'sources': ['main.cpp', 'fileC.cpp'], 'extra_files': []}, + 'trivialprog5@exe': {'name': 'trivialprog5', 'sources': ['fileB.cpp', 'fileC.cpp'], 'extra_files': []}, 'trivialprog6@exe': {'name': 'trivialprog6', 'sources': ['main.cpp'], 'extra_files': []}, - 'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['fileC.cpp', 'main.cpp'], 'extra_files': []}, - 'trivialprog8@exe': {'name': 'trivialprog8', 'sources': ['main.cpp'], 'extra_files': []}, - 'trivialprog9@exe': {'name': 'trivialprog9', 'sources': ['main.cpp'], 'extra_files': []}, + 'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['fileC.cpp', 'main.cpp', 'fileA.cpp'], 'extra_files': []}, + 'trivialprog8@exe': {'name': 'trivialprog8', 'sources': ['main.cpp', 'fileA.cpp'], 'extra_files': []}, + 'trivialprog9@exe': {'name': 'trivialprog9', 'sources': ['main.cpp', 'fileA.cpp'], 'extra_files': []}, + 'trivialprog10@exe': {'name': 'trivialprog10', 'sources': ['main.cpp'], 'extra_files': []}, + 'trivialprog11@exe': {'name': 'trivialprog11', 'sources': ['main.cpp'], 'extra_files': []}, + 'trivialprog12@exe': {'name': 'trivialprog12', 'sources': ['main.cpp', 'fileA.cpp'], 'extra_files': []}, + 'rightName@exe': {'name': 'rightName', 'sources': ['main.cpp'], 'extra_files': []}, } } - self.assertDictEqual(out, expected) + self.assertEqualIgnoreOrder(out, expected) # Check the written file out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - self.assertDictEqual(out, expected) + self.assertEqualIgnoreOrder(out, expected) def test_target_subdir(self): self.prime('2 subdirs') out = self.rewrite(self.builddir, os.path.join(self.builddir, 'addSrc.json')) - expected = {'name': 'something', 'sources': ['first.c', 'second.c', 'third.c'], 'extra_files': []} + expected = {'name': 'something', 'sources': ['third.c', f'sub2{os.path.sep}first.c', f'sub2{os.path.sep}second.c'], 'extra_files': []} self.assertDictEqual(list(out['target'].values())[0], expected) # Check the written file @@ -145,9 +169,12 @@ class RewriterTests(BasePlatformTests): 'trivialprog6@exe': {'name': 'trivialprog6', 'sources': ['main.cpp', 'fileA.cpp'], 'extra_files': []}, 'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['fileB.cpp', 'fileC.cpp', 'main.cpp', 'fileA.cpp'], 'extra_files': []}, 'trivialprog8@exe': {'name': 'trivialprog8', 'sources': ['main.cpp', 'fileA.cpp'], 'extra_files': []}, + 'trivialprog10@exe': {'name': 'trivialprog10', 'sources': ['main.cpp', 'fileA.cpp', 'fileB.cpp'], 'extra_files': []}, + 'trivialprog11@exe': {'name': 'trivialprog11', 'sources': ['main.cpp', 'fileA.cpp'], 'extra_files': []}, + 'trivialprog12@exe': {'name': 'trivialprog12', 'sources': ['main.cpp', 'fileA.cpp'], 'extra_files': []}, } } - self.assertDictEqual(out, expected) + self.assertEqualIgnoreOrder(out, expected) def test_target_add(self): self.prime('1 basic') @@ -166,10 +193,14 @@ class RewriterTests(BasePlatformTests): 'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['fileB.cpp', 'fileC.cpp', 'main.cpp', 'fileA.cpp'], 'extra_files': []}, 'trivialprog8@exe': {'name': 'trivialprog8', 'sources': ['main.cpp', 'fileA.cpp'], 'extra_files': []}, 'trivialprog9@exe': {'name': 'trivialprog9', 'sources': ['main.cpp', 'fileA.cpp'], 'extra_files': []}, - 'trivialprog10@sha': {'name': 'trivialprog10', 'sources': ['new1.cpp', 'new2.cpp'], 'extra_files': []}, + 'trivialprog10@exe': {'name': 'trivialprog10', 'sources': ['main.cpp', 'fileA.cpp', 'fileB.cpp'], 'extra_files': []}, + 'trivialprog11@exe': {'name': 'trivialprog11', 'sources': ['main.cpp', 'fileA.cpp'], 'extra_files': []}, + 'trivialprog12@exe': {'name': 'trivialprog12', 'sources': ['main.cpp', 'fileA.cpp'], 'extra_files': []}, + 'trivialprog13@sha': {'name': 'trivialprog13', 'sources': ['new1.cpp', 'new2.cpp'], 'extra_files': []}, + 'rightName@exe': {'name': 'rightName', 'sources': ['main.cpp'], 'extra_files': []}, } } - self.assertDictEqual(out, expected) + self.assertEqualIgnoreOrder(out, expected) def test_target_remove_subdir(self): self.prime('2 subdirs') @@ -181,7 +212,7 @@ class RewriterTests(BasePlatformTests): self.prime('2 subdirs') self.rewrite(self.builddir, os.path.join(self.builddir, 'addTgt.json')) out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - expected = {'name': 'something', 'sources': ['first.c', 'second.c'], 'extra_files': []} + expected = {'name': 'something', 'sources': [f'sub2{os.path.sep}first.c', f'sub2{os.path.sep}second.c'], 'extra_files': []} self.assertDictEqual(out['target']['94b671c@@something@exe'], expected) def test_target_source_sorting(self): @@ -228,16 +259,23 @@ class RewriterTests(BasePlatformTests): } } } + for k1, v1 in expected.items(): + for k2, v2 in v1.items(): + for k3, v3 in v2.items(): + if isinstance(v3, list): + for i in range(len(v3)): + v3[i] = v3[i].replace('/', os.path.sep) self.assertDictEqual(out, expected) def test_target_same_name_skip(self): self.prime('4 same name targets') out = self.rewrite(self.builddir, os.path.join(self.builddir, 'addSrc.json')) out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - expected = {'name': 'myExe', 'sources': ['main.cpp'], 'extra_files': []} + expected1 = {'name': 'myExe', 'sources': ['main.cpp'], 'extra_files': []} + expected2 = {'name': 'myExe', 'sources': [f'sub1{os.path.sep}main.cpp'], 'extra_files': []} self.assertEqual(len(out['target']), 2) - for val in out['target'].values(): - self.assertDictEqual(expected, val) + self.assertDictEqual(expected1, out['target']['myExe@exe']) + self.assertDictEqual(expected2, out['target']['9a11041@@myExe@exe']) def test_kwargs_info(self): self.prime('3 kwargs') @@ -251,13 +289,30 @@ class RewriterTests(BasePlatformTests): } self.assertDictEqual(out, expected) + def test_kwargs_info_dict(self): + self.prime('8 kwargs dict') + out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) + expected = { + 'kwargs': { + 'project#/': { + 'default_options': {'c_std': 'c11', 'cpp_std': 'c++17'}, + 'version': '0.0.1' + }, + 'dependency#dep1': { + 'default_options': {'foo': 'bar'}, + 'required': False + } + } + } + self.assertDictEqual(out, expected) + def test_kwargs_set(self): self.prime('3 kwargs') self.rewrite(self.builddir, os.path.join(self.builddir, 'set.json')) out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) expected = { 'kwargs': { - 'project#/': {'version': '0.0.2', 'meson_version': '0.50.0', 'license': ['GPL', 'MIT']}, + 'project#/': {'version': '0.0.2', 'meson_version': '0.50.0', 'license': ['GPL', 'MIT'], 'license_files': ['GPL.txt', 'MIT.txt']}, 'target#tgt1': {'build_by_default': False, 'build_rpath': '/usr/local', 'dependencies': 'dep1'}, 'dependency#dep1': {'required': True, 'method': 'cmake'} } @@ -270,7 +325,7 @@ class RewriterTests(BasePlatformTests): out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) expected = { 'kwargs': { - 'project#/': {'version': '0.0.1', 'license': ['GPL', 'MIT', 'BSD', 'Boost']}, + 'project#/': {'version': '0.0.1', 'license': ['GPL', 'MIT', 'BSD', 'Boost'], 'license_files': 'GPL.txt'}, 'target#tgt1': {'build_by_default': True}, 'dependency#dep1': {'required': False} } @@ -347,48 +402,78 @@ class RewriterTests(BasePlatformTests): out = self.rewrite(self.builddir, os.path.join(self.builddir, 'addExtraFiles.json')) expected = { 'target': { - 'trivialprog0@exe': {'name': 'trivialprog0', 'sources': ['main.cpp'], 'extra_files': ['a1.hpp', 'a2.hpp', 'a6.hpp', 'fileA.hpp', 'main.hpp', 'a7.hpp', 'fileB.hpp', 'fileC.hpp']}, - 'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['main.cpp'], 'extra_files': ['a1.hpp', 'a2.hpp', 'a6.hpp', 'fileA.hpp', 'main.hpp']}, + 'trivialprog0@exe': {'name': 'trivialprog0', 'sources': ['main.cpp'], 'extra_files': ['fileA.hpp', 'main.hpp', 'fileB.hpp', 'fileC.hpp']}, + 'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['main.cpp'], 'extra_files': ['a1.hpp', 'a2.hpp', 'fileA.hpp', 'main.hpp']}, 'trivialprog2@exe': {'name': 'trivialprog2', 'sources': ['main.cpp'], 'extra_files': ['a7.hpp', 'fileB.hpp', 'fileC.hpp']}, 'trivialprog3@exe': {'name': 'trivialprog3', 'sources': ['main.cpp'], 'extra_files': ['a5.hpp', 'fileA.hpp', 'main.hpp']}, 'trivialprog4@exe': {'name': 'trivialprog4', 'sources': ['main.cpp'], 'extra_files': ['a5.hpp', 'main.hpp', 'fileA.hpp']}, - 'trivialprog5@exe': {'name': 'trivialprog5', 'sources': ['main.cpp'], 'extra_files': ['a3.hpp', 'main.hpp', 'a7.hpp', 'fileB.hpp', 'fileC.hpp']}, - 'trivialprog6@exe': {'name': 'trivialprog6', 'sources': ['main.cpp'], 'extra_files': ['a1.hpp', 'a2.hpp', 'a6.hpp', 'fileA.hpp', 'main.hpp']}, - 'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['main.cpp'], 'extra_files': ['a1.hpp', 'a2.hpp', 'a6.hpp', 'fileA.hpp', 'main.hpp']}, + 'trivialprog5@exe': {'name': 'trivialprog5', 'sources': ['main.cpp'], 'extra_files': ['a3.hpp', 'main.hpp', 'fileB.hpp', 'fileC.hpp']}, + 'trivialprog6@exe': {'name': 'trivialprog6', 'sources': ['main.cpp'], 'extra_files': ['fileA.hpp', 'main.hpp']}, + 'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['main.cpp'], 'extra_files': ['a1.hpp', 'a6.hpp', 'fileA.hpp', 'main.hpp']}, 'trivialprog8@exe': {'name': 'trivialprog8', 'sources': ['main.cpp'], 'extra_files': ['a2.hpp', 'a7.hpp']}, 'trivialprog9@exe': {'name': 'trivialprog9', 'sources': ['main.cpp'], 'extra_files': ['a8.hpp', 'a9.hpp']}, 'trivialprog10@exe': {'name': 'trivialprog10', 'sources': ['main.cpp'], 'extra_files': ['a1.hpp', 'a4.hpp']}, } } - self.assertDictEqual(out, expected) + self.assertEqualIgnoreOrder(out, expected) # Check the written file out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - self.assertDictEqual(out, expected) + self.assertEqualIgnoreOrder(out, expected) def test_target_remove_extra_files(self): self.prime('6 extra_files') out = self.rewrite(self.builddir, os.path.join(self.builddir, 'rmExtraFiles.json')) expected = { 'target': { - 'trivialprog0@exe': {'name': 'trivialprog0', 'sources': ['main.cpp'], 'extra_files': ['main.hpp', 'fileC.hpp']}, - 'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['main.cpp'], 'extra_files': ['main.hpp']}, - 'trivialprog2@exe': {'name': 'trivialprog2', 'sources': ['main.cpp'], 'extra_files': ['fileC.hpp']}, + 'trivialprog0@exe': {'name': 'trivialprog0', 'sources': ['main.cpp'], 'extra_files': ['main.hpp', 'fileA.hpp', 'fileB.hpp', 'fileC.hpp']}, + 'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['main.cpp'], 'extra_files': ['main.hpp', 'fileA.hpp']}, + 'trivialprog2@exe': {'name': 'trivialprog2', 'sources': ['main.cpp'], 'extra_files': ['fileB.hpp', 'fileC.hpp']}, 'trivialprog3@exe': {'name': 'trivialprog3', 'sources': ['main.cpp'], 'extra_files': ['main.hpp']}, 'trivialprog4@exe': {'name': 'trivialprog4', 'sources': ['main.cpp'], 'extra_files': ['main.hpp']}, - 'trivialprog5@exe': {'name': 'trivialprog5', 'sources': ['main.cpp'], 'extra_files': ['main.hpp', 'fileC.hpp']}, - 'trivialprog6@exe': {'name': 'trivialprog6', 'sources': ['main.cpp'], 'extra_files': ['main.hpp']}, - 'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['main.cpp'], 'extra_files': ['main.hpp']}, + 'trivialprog5@exe': {'name': 'trivialprog5', 'sources': ['main.cpp'], 'extra_files': ['fileB.hpp', 'fileC.hpp', 'main.hpp']}, + 'trivialprog6@exe': {'name': 'trivialprog6', 'sources': ['main.cpp'], 'extra_files': ['main.hpp', 'fileA.hpp']}, + 'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['main.cpp'], 'extra_files': ['main.hpp', 'fileA.hpp']}, 'trivialprog8@exe': {'name': 'trivialprog8', 'sources': ['main.cpp'], 'extra_files': []}, 'trivialprog9@exe': {'name': 'trivialprog9', 'sources': ['main.cpp'], 'extra_files': []}, 'trivialprog10@exe': {'name': 'trivialprog10', 'sources': ['main.cpp'], 'extra_files': []}, } } - self.assertDictEqual(out, expected) + self.assertEqualIgnoreOrder(out, expected) # Check the written file out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - self.assertDictEqual(out, expected) + self.assertEqualIgnoreOrder(out, expected) + + def test_duplicate_globals(self): + self.prime('10 duplicate globals') + out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) + expected = { + 'kwargs': { + 'project#/': {'license': 'MIT'} + } + } + self.assertEqualIgnoreOrder(out, expected) + + def test_tricky_dataflow(self): + self.prime('9 tricky dataflow') + out = self.rewrite(self.builddir, os.path.join(self.builddir, 'addSrc.json')) + expected = { + 'target': { + 'tgt1@sha': {'name': 'tgt1', 'sources': ['foo.c', 'new.c'], 'extra_files': []}, + 'tgt2@exe': {'name': 'tgt2', 'sources': ['new.c', 'unknown'], 'extra_files': []}, + 'tgt3@exe': {'name': 'tgt3', 'sources': ['foo.c', 'new.c'], 'extra_files': []}, + 'tgt4@exe': {'name': 'tgt4', 'sources': ['unknown'], 'extra_files': []}, + 'tgt5@exe': {'name': 'tgt5', 'sources': ['unknown', 'new.c'], 'extra_files': []}, + 'tgt6@exe': {'name': 'tgt6', 'sources': ['unknown', 'new.c'], 'extra_files': []}, + 'tgt7@exe': {'name': 'tgt7', 'sources': ['unknown', 'unknown'], 'extra_files': []}, + } + } + self.assertEqualIgnoreOrder(out, expected) + + # Check the written file + out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) + self.assertEqualIgnoreOrder(out, expected) def test_raw_printer_is_idempotent(self): test_path = Path(self.unit_test_dir, '120 rewrite') @@ -421,3 +506,24 @@ class RewriterTests(BasePlatformTests): } } self.assertDictEqual(out, expected) + + # Asserts that AstInterpreter.dataflow_dag is what it should be + def test_dataflow_dag(self): + test_path = Path(self.rewrite_test_dir, '1 basic') + interpreter = IntrospectionInterpreter(test_path, '', 'ninja') + interpreter.analyze() + + def sortkey(node): + return (node.lineno, node.colno, node.end_lineno, node.end_colno) + + def node_to_str(node): + return f"{node.__class__.__name__}({node.lineno}:{node.colno})" + + dag_as_str = "" + for target in sorted(interpreter.dataflow_dag.tgt_to_srcs.keys(), key=sortkey): + dag_as_str += f"Data flowing to {node_to_str(target)}:\n" + for source in sorted(interpreter.dataflow_dag.tgt_to_srcs[target], key=sortkey): + dag_as_str += f" {node_to_str(source)}\n" + + expected = Path(test_path / "expected_dag.txt").read_text(encoding='utf-8').strip() + self.assertEqual(dag_as_str.strip(), expected) diff --git a/unittests/windowstests.py b/unittests/windowstests.py index 7fa4ab2..bf1225d 100644 --- a/unittests/windowstests.py +++ b/unittests/windowstests.py @@ -398,7 +398,16 @@ class WindowsTests(BasePlatformTests): if OptionKey('b_vscrt') not in cc.base_options: raise SkipTest('Compiler does not support setting the VS CRT') + MSVCRT_MAP = { + '/MD': '-fms-runtime-lib=dll', + '/MDd': '-fms-runtime-lib=dll_dbg', + '/MT': '-fms-runtime-lib=static', + '/MTd': '-fms-runtime-lib=static_dbg', + } + def sanitycheck_vscrt(vscrt): + if cc.get_argument_syntax() != 'msvc': + vscrt = MSVCRT_MAP[vscrt] checks = self.get_meson_log_sanitychecks() self.assertGreater(len(checks), 0) for check in checks: @@ -466,6 +475,12 @@ class WindowsTests(BasePlatformTests): # Studio is picked, as a regression test for # https://github.com/mesonbuild/meson/issues/9774 env['PATH'] = get_path_without_cmd('ninja', env['PATH']) + # Add a multiline variable to test that it is handled correctly + # with a line that contains only '=' and a line that would result + # in an invalid variable name. + # see: https://github.com/mesonbuild/meson/pull/13682 + env['MULTILINE_VAR_WITH_EQUALS'] = 'Foo\r\n=====\r\n' + env['MULTILINE_VAR_WITH_INVALID_NAME'] = 'Foo\n%=Bar\n' testdir = os.path.join(self.common_test_dir, '1 trivial') out = self.init(testdir, extra_args=['--vsenv'], override_envvars=env) self.assertIn('Activating VS', out) |
