patch:
default:
informational: true
+comment: false
+github_checks:
+ annotations: false
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
+ # bypass cache: https://github.com/github/codeql-action/issues/1445
+ tools: latest
config-file: .github/codeql/codeql-config.yml
languages: python
# we have none
MESON_CI_JOBNAME: cygwin-${{ matrix.NAME }}
steps:
- # cache should be saved on failure, but the action doesn't support that
- # https://github.com/actions/cache/issues/92
- - uses: actions/cache@v1
+ - uses: actions/cache/restore@v3
+ id: restore-cache
with:
# should use 'pip3 cache dir' to discover this path
path: C:\cygwin\home\runneradmin\.cache\pip
vala
zlib-devel
- - name: work around gcovr bug https://github.com/gcovr/gcovr/pull/576
- run: |
- export PATH=/usr/bin:/usr/local/bin:$(cygpath ${SYSTEMROOT})/system32
- python3 -m pip --disable-pip-version-check install 'jinja2<3.1.0'
- shell: C:\cygwin\bin\bash.exe --noprofile --norc -o igncr -eo pipefail '{0}'
-
- name: Run pip
run: |
export PATH=/usr/bin:/usr/local/bin:$(cygpath ${SYSTEMROOT})/system32
- python3 -m pip --disable-pip-version-check install gcovr jsonschema pefile pytest pytest-subtests pytest-xdist coverage codecov
+ # jsonschema is max capped because the new version depends on rust dependencies which are... hard to get on cygwin
+ python3 -m pip --disable-pip-version-check install gcovr 'jsonschema<4.18' pefile pytest pytest-subtests pytest-xdist coverage
shell: C:\cygwin\bin\bash.exe --noprofile --norc -o igncr -eo pipefail '{0}'
+ - uses: actions/cache/save@v3
+ with:
+ # should use 'pip3 cache dir' to discover this path
+ path: C:\cygwin\home\runneradmin\.cache\pip
+ key: cygwin-pip-${{ github.run_number }}
+
- name: Run tests
run: |
export PATH=/usr/bin:/usr/local/bin:$(cygpath ${SYSTEMROOT})/system32
# 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
- run: ./ci/upload_cov.sh "${{ matrix.NAME }}"
+ uses: codecov/codecov-action@v3
+ with:
+ files: .coverage/coverage.xml
+ name: "${{ matrix.NAME }}"
+ fail_ci_if_error: false
+ verbose: true
with:
python-version: '3.x'
- run: python -m pip install pylint
- - run: pylint mesonbuild
+ - run: pylint --output-format colorized mesonbuild
flake8:
runs-on: ubuntu-latest
with:
python-version: '3.x'
- run: python -m pip install flake8
- - run: flake8 mesonbuild/
+ - run: flake8 --color always mesonbuild/
mypy:
runs-on: ubuntu-latest
with:
python-version: '3.x'
- run: python -m pip install mypy types-PyYAML
- - run: python run_mypy.py
+ - run: python run_mypy.py --allver
+ env:
+ PYTHONUNBUFFERED: 1
+ TERM: xterm-color
+ MYPY_FORCE_COLOR: 1
python-version: '3.x'
- run: |
python -m pip install --upgrade pip
- python -m pip install pytest pytest-xdist pytest-subtests jsonschema coverage codecov
+ python -m pip install pytest pytest-xdist pytest-subtests jsonschema coverage
- run: brew install pkg-config ninja llvm qt@5
- env:
CPPFLAGS: "-I/usr/local/include"
export PATH="$HOME/tools:/usr/local/opt/qt@5/bin:$PATH:$(brew --prefix llvm)/bin"
export PKG_CONFIG_PATH="/usr/local/opt/qt@5/lib/pkgconfig:$PKG_CONFIG_PATH"
./tools/run_with_cov.py ./run_unittests.py
+
+ - name: Aggregate coverage reports
+ run: ./ci/combine_cov.sh
+
- name: Upload coverage report
- run: ./ci/upload_cov.sh "appleclang [unit tests]"
+ uses: codecov/codecov-action@v3
+ with:
+ files: .coverage/coverage.xml
+ name: "appleclang [unit tests]"
+ fail_ci_if_error: false
+ verbose: true
project-tests-appleclang:
- run: |
python3 -m pip install --upgrade setuptools
python3 -m pip install --upgrade pip
- python3 -m pip install cython coverage codecov
+ python3 -m pip install cython coverage
- env:
CPPFLAGS: "-I/usr/local/include"
LDFLAGS: "-L/usr/local/lib"
export PKG_CONFIG_PATH="/usr/local/opt/qt@5/lib/pkgconfig:$PKG_CONFIG_PATH"
export XML_CATALOG_FILES="/usr/local/etc/xml/catalog"
./tools/run_with_cov.py ./run_project_tests.py --backend=ninja
+
+ - name: Aggregate coverage reports
+ run: ./ci/combine_cov.sh
+
- name: Upload coverage report
- run: ./ci/upload_cov.sh "appleclang [project tests; unity=${{ matrix.unity }}]"
+ uses: codecov/codecov-action@v3
+ with:
+ files: .coverage/coverage.xml
+ name: "appleclang [project tests; unity=${{ matrix.unity }}]"
+ fail_ci_if_error: false
+ verbose: true
Qt4macos:
runs-on: macos-latest
TOOLCHAIN: clang
env:
MESON_CI_JOBNAME: msys2-${{ matrix.NAME }}
- # XXX: For some reason enabling jit debugging "fixes" random python crashes
- # see https://github.com/msys2/MINGW-packages/issues/11864
- MSYS: "winjitdebug"
defaults:
run:
mingw-w64-${{ matrix.MSYS2_ARCH }}-python-lxml
mingw-w64-${{ matrix.MSYS2_ARCH }}-python-setuptools
mingw-w64-${{ matrix.MSYS2_ARCH }}-python-pip
+ mingw-w64-${{ matrix.MSYS2_ARCH }}-python-jsonschema
mingw-w64-${{ matrix.MSYS2_ARCH }}-${{ matrix.TOOLCHAIN }}
- name: Install dependencies
run: |
- python3 -m pip --disable-pip-version-check install gcovr jsonschema pefile pytest pytest-subtests pytest-xdist coverage codecov
+ python3 -m pip --disable-pip-version-check install gcovr pefile pytest pytest-subtests pytest-xdist coverage
- name: Install pypy3 on x86_64
run: |
name: ${{ matrix.NAME }}
path: meson-test-run.*
+ - name: Aggregate coverage reports
+ run: ./ci/combine_cov.sh
+
- name: Upload coverage report
- run: ./ci/upload_cov.sh "${{ matrix.NAME }}"
+ uses: codecov/codecov-action@v3
+ with:
+ files: .coverage/coverage.xml
+ name: "${{ matrix.NAME }}"
+ fail_ci_if_error: false
+ verbose: true
name: Cross-only compilation environment
concurrency:
- group: nonative-${{ github.head_ref || github.ref }}
+ group: nonnative-${{ github.head_ref || github.ref }}
cancel-in-progress: true
on:
paths:
- "mesonbuild/**"
- "test cases/**"
- - ".github/workflows/nonative.yml"
+ - ".github/workflows/nonnative.yml"
- "run*tests.py"
pull_request:
paths:
- "mesonbuild/**"
- "test cases/**"
- - ".github/workflows/nonative.yml"
+ - ".github/workflows/nonnative.yml"
- "run*tests.py"
permissions:
- run: |
apt-get -y purge clang gcc gdc
apt-get -y autoremove
- python3 -m pip install coverage codecov
+ python3 -m pip install coverage
- uses: actions/checkout@v3
- 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'
+
+ - name: Aggregate coverage reports
+ run: ./ci/combine_cov.sh
+
- name: Upload coverage report
- run: ./ci/upload_cov.sh "Ubuntu nonnative"
+ uses: codecov/codecov-action@v3
+ with:
+ files: .coverage/coverage.xml
+ name: "Ubuntu nonnative"
+ fail_ci_if_error: false
+ verbose: true
# They are defined in the `env` section in each image.json. CI_ARGS should be set
# via the `args` array ub the image.json
run: bash -c '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
- run: ./ci/upload_cov.sh "OS Comp [${{ matrix.cfg.name }}]"
+ uses: codecov/codecov-action@v3
+ with:
+ files: .coverage/coverage.xml
+ name: "OS Comp [${{ matrix.cfg.name }}]"
+ fail_ci_if_error: false
+ verbose: true
+
+ pypy:
+ name: 'Arch / PyPy'
+ runs-on: ubuntu-latest
+ container: mesonbuild/arch:latest
+ env:
+ MESON_CI_JOBNAME_UPDATE: linux-arch-gcc-pypy
+
+ steps:
+ - uses: actions/checkout@v3
+ - name: Run tests
+ run: |
+ source /ci/env_vars.sh
+ export MESON_CI_JOBNAME=$MESON_CI_JOBNAME_UPDATE
+ pypy3 run_tests.py
ubuntu-rolling:
name: 'Ubuntu Rolling'
./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
- run: ./ci/upload_cov.sh "Ubuntu [${{ matrix.cfg.CC }} ${{ matrix.cfg.RUN_TESTS_ARGS }} ${{ matrix.cfg.MESON_ARGS }}]"
+ 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
- "test cases/linuxlike/**"
- "test cases/objc/**"
- "test cases/objcpp/**"
- - "test caes/windows/**"
+ - "test cases/windows/**"
pull_request:
paths:
- "test cases/linuxlike/**"
- "test cases/objc/**"
- "test cases/objcpp/**"
- - "test caes/windows/**"
+ - "test cases/windows/**"
permissions:
contents: read
run: |
sudo apt update -yq
sudo apt install -yq --no-install-recommends g++ gfortran ninja-build gobjc gobjc++
- python -m pip install coverage codecov
+ 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++"
env:
MESON_CI_JOBNAME: linux-ubuntu-gcc-werror
+
+ - name: Aggregate coverage reports
+ run: ./ci/combine_cov.sh
+
- name: Upload coverage report
- run: ./ci/upload_cov.sh "UnusedMissingReturn"
+ uses: codecov/codecov-action@v3
+ with:
+ files: .coverage/coverage.xml
+ name: "UnusedMissingReturn"
+ fail_ci_if_error: false
+ verbose: true
windows:
runs-on: windows-latest
with:
python-version: '3.x'
- - run: pip install ninja pefile coverage codecov
+ - run: pip install ninja pefile coverage
- run: python ./tools/run_with_cov.py run_project_tests.py --only platform-windows
env:
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
- run: ./ci/upload_cov.sh "UnusedMissingReturn Windows"
+ uses: codecov/codecov-action@v3
+ with:
+ files: .coverage/coverage.xml
+ name: "UnusedMissingReturn Windows"
+ fail_ci_if_error: false
+ verbose: true
branches:
- master
paths:
+ - .github/workflows/website.yml
- docs/**
pull_request:
paths:
+ - .github/workflows/website.yml
- docs/**
workflow_dispatch:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
+
+ - uses: actions/cache/restore@v3
+ id: restore-cache
+ with:
+ # should use 'pip3 cache dir' to discover this path
+ path: ~/.cache/pip
+ key: website-pip-${{ github.run_number }}
+ restore-keys: website-pip-
+
- name: Install package
run: |
sudo apt-get -y install python3-pip ninja-build libjson-glib-dev
pip install hotdoc chevron strictyaml
+
+ - uses: actions/cache/save@v3
+ with:
+ # should use 'pip3 cache dir' to discover this path
+ path: ~/.cache/pip
+ key: website-pip-${{ github.run_number }}
+
- name: Setup SSH Keys and known_hosts
env:
SSH_AUTH_SOCK: /tmp/ssh_agent.sock
run: |
ssh-agent -a $SSH_AUTH_SOCK > /dev/null
ssh-add - <<< "${{ secrets.WEBSITE_PRIV_KEY }}"
- if: env.HAS_SSH_KEY == 'true'
+ if: github.ref == 'refs/heads/master' && env.HAS_SSH_KEY == 'true'
- name: Build website
run: |
git config --global user.name "github-actions"
run: |
cd docs
ninja -C _build upload
- if: env.HAS_SSH_KEY == 'true'
+ if: github.ref == 'refs/heads/master' && env.HAS_SSH_KEY == 'true'
- name: Release the current JSON docs
uses: svenstaro/upload-release-action@v2
with:
file: docs/_build/meson-reference.3
tag: ${{ github.ref }}
if: ${{ github.event_name == 'release' }}
-
show_error_context = False
show_column_numbers = True
ignore_missing_imports = True
+implicit_reexport = False
follow_imports = silent
warn_redundant_casts = True
- [Python](https://python.org) (version 3.7 or newer)
- [Ninja](https://ninja-build.org) (version 1.8.2 or newer)
+Latest Meson version supporting previous Python versions:
+- Python 3.6: **0.61.5**
+- Python 3.5: **0.56.2**
+- Python 3.4: **0.45.1**
+
#### Installing from source
Meson is available on [PyPi](https://pypi.python.org/pypi/meson), so
include:
- 'master'
# Release branches
- - '0.*'
+ - '1.*'
paths:
include:
- 'mesonbuild'
- bash: ci/intel-scripts/cache_exclude_windows.sh
displayName: exclude unused files from cache
condition: and(ne(variables.CACHE_RESTORED, 'true'), eq(variables.ifort, 'true'))
+ - script: choco install -y nasm
+ displayName: install NASM
- task: UsePythonVersion@0
inputs:
versionSpec: '3.7'
# Inspired by https://github.com/greyltc/docker-archlinux-aur/blob/master/add-aur.sh
pkgs=(
- python python-pip
+ 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
- mono boost qt5-base gtkmm3 gtest gmock protobuf wxgtk2 gobject-introspection
+ mono boost qt5-base gtkmm3 gtest gmock protobuf gobject-introspection
itstool gtk3 java-environment=8 gtk-doc llvm clang sdl2 graphviz
doxygen vulkan-validation-layers openssh mercurial gtk-sharp-2 qt5-tools
- libwmf valgrind cmake netcdf-fortran openmpi nasm gnustep-base gettext
+ libwmf cmake netcdf-fortran openmpi nasm gnustep-base gettext
python-lxml hotdoc rust-bindgen qt6-base qt6-tools wayland wayland-protocols
# cuda
)
-aur_pkgs=(scalapack)
+aur_pkgs=(scalapack wxwidgets-gtk2)
cleanup_pkgs=(go)
AUR_USER=docker
pacman -Syu $PACMAN_OPTS "${pkgs[@]}"
install_python_packages
+pypy3 -m ensurepip
+pypy3 -m pip install "${base_python_pkgs[@]}"
+
# Setup the user
useradd -m $AUR_USER
echo "${AUR_USER}:" | chpasswd -e
python3 -m pip install -U "${base_python_pkgs[@]}" "${python_pkgs[@]}"
-pushd /opt
-# Download and install PyPy3.8, and link it to /usr/bin/pypy3
-# At some point it would be more robust to download and parse
-# https://downloads.python.org/pypy/versions.json
-wget https://downloads.python.org/pypy/pypy3.8-v7.3.9-linux64.tar.bz2
-pypy_sha256="08be25ec82fc5d23b78563eda144923517daba481a90af0ace7a047c9c9a3c34"
-if [ $pypy_sha256 != $(sha256sum pypy3.8-v7.3.9-linux64.tar.bz2 | cut -f1 -d" ") ]; then
- echo bad sha256 for PyPy
- exit -1
-fi
-tar -xf pypy3.8-v7.3.9-linux64.tar.bz2
-pypy3.8-v7.3.9-linux64/bin/pypy3 -m ensurepip
-popd
-ln -s /opt/pypy3.8-v7.3.9-linux64/bin/pypy3 /usr/bin/pypy3
-
-
# Install the ninja 0.10
wget https://github.com/ninja-build/ninja/releases/download/v1.10.0/ninja-linux.zip
unzip ninja-linux.zip -d /ci
# Also add /ci to PATH
out_data += 'export PATH="/ci:$PATH"\n'
+ out_data += '''
+ if [ -f "$HOME/.cargo/env" ]; then
+ source "$HOME/.cargo/env"
+ fi
+ '''
+
out_file.write_text(out_data, encoding='utf-8')
# make it executable
shutil.copytree(
self.meson_root,
self.temp_dir / 'meson',
+ symlinks=True,
ignore=shutil.ignore_patterns(
'.git',
'*_cache',
'__pycache__',
# 'work area',
self.temp_dir.name,
- )
+ ),
)
def do_test(self, tty: bool = False) -> None:
else:
test_cmd = [
self.docker, 'run', '--rm', '-t', 'meson_test_image',
- '/bin/bash', '-c', 'source /ci/env_vars.sh; cd meson; ./run_tests.py $CI_ARGS'
+ '/bin/bash', '-xc', 'source /ci/env_vars.sh; cd meson; ./run_tests.py $CI_ARGS'
]
if subprocess.run(test_cmd).returncode != 0 and not tty:
pytest-xdist
pytest-subtests
coverage
- codecov
jsonschema
)
}
install_minimal_python_packages() {
+ rm -f /usr/lib*/python3.*/EXTERNALLY-MANAGED
python3 -m pip install "${base_python_pkgs[@]}" $*
}
install_python_packages() {
+ rm -f /usr/lib*/python3.*/EXTERNALLY-MANAGED
python3 -m pip install "${base_python_pkgs[@]}" "${python_pkgs[@]}" $*
}
source /ci/common.sh
pkgs=(
- python python-pip
+ python python-pip python-setuptools
ninja gcc gcc-objc git cmake
cuda zlib pkgconf
)
python python-pip python3-devel
ninja-build make git autoconf automake patch
elfutils gcc gcc-c++ gcc-fortran gcc-objc gcc-objc++ vala rust bison flex ldc libasan libasan-static
- mono-core boost-devel gtkmm30 gtest-devel gmock-devel protobuf-devel wxGTK3-devel gobject-introspection
+ mono-core boost-devel gtkmm30 gtest-devel gmock-devel protobuf-devel wxGTK-devel gobject-introspection
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
ninja make git autoconf automake patch libjpeg-devel
elfutils gcc gcc-c++ gcc-fortran gcc-objc gcc-obj-c++ vala rust bison flex curl lcov
mono-core gtkmm3-devel gtest gmock protobuf-devel wxGTK3-3_2-devel gobject-introspection-devel
- itstool gtk3-devel java-15-openjdk-devel gtk-doc llvm-devel clang-devel libSDL2-devel graphviz-devel zlib-devel zlib-devel-static
+ itstool gtk3-devel java-17-openjdk-devel gtk-doc llvm-devel clang-devel libSDL2-devel graphviz-devel zlib-devel zlib-devel-static
#hdf5-devel netcdf-devel libscalapack2-openmpi3-devel libscalapack2-gnu-openmpi3-hpc-devel openmpi3-devel
doxygen vulkan-devel vulkan-validationlayers openssh mercurial gtk-sharp3-complete gtk-sharp2-complete libpcap-devel libgpgme-devel
libqt5-qtbase-devel libqt5-qttools-devel libqt5-linguist libqt5-qtbase-private-headers-devel
dub build dubtestproject:test1 --compiler=ldc2
dub build dubtestproject:test2 --compiler=ldc2
+# Remove debian version of Rust and install latest with rustup.
+# This is needed to get the cross toolchain as well.
+apt-get -y remove rustc || true
+wget -O - https://sh.rustup.rs | sh -s -- -y --profile minimal --component clippy
+source "$HOME/.cargo/env"
+rustup target add x86_64-pc-windows-gnu
+rustup target add arm-unknown-linux-gnueabihf
+
# cleanup
apt-get -y clean
apt-get -y autoclean
echo "Printing report"
coverage report
-
-echo "Uploading to codecov..."
-codecov -f .coverage/coverage.xml -n "$1"
exit 0
}
-# remove Chocolately, MinGW, Strawberry Perl from path, so we don't find gcc/gfortran and try to use it
+# remove Chocolatey, MinGW, Strawberry Perl from path, so we don't find gcc/gfortran and try to use it
# remove PostgreSQL from path so we don't pickup a broken zlib from it
$env:Path = ($env:Path.Split(';') | Where-Object { $_ -notmatch 'mingw|Strawberry|Chocolatey|PostgreSQL' }) -join ';'
Expand-Archive $env:AGENT_WORKFOLDER\pypy38.zip -DestinationPath $env:AGENT_WORKFOLDER\pypy38
$ENV:Path = $ENV:Path + ";$ENV:AGENT_WORKFOLDER\pypy38\pypy3.8-v7.3.9-win64;$ENV:AGENT_WORKFOLDER\pypy38\pypy3.8-v7.3.9-win64\Scripts"
pypy3 -m ensurepip
+
+ DownloadFile -Source https://www.python.org/ftp/python/2.7.18/python-2.7.18.amd64.msi -Destination $env:AGENT_WORKFOLDER\python27.msi
+ Start-Process msiexec.exe -Wait -ArgumentList "/I $env:AGENT_WORKFOLDER\python27.msi /quiet"
}
echo ""
-echo "Ninja / MSBuld version:"
+echo "Ninja / MSBuild version:"
if ($env:backend -eq 'ninja') {
ninja --version
} else {
echo ""
python -m pip --disable-pip-version-check install --upgrade pefile pytest-xdist pytest-subtests jsonschema coverage
+# Needed for running the Cython tests
+python -m pip --disable-pip-version-check install cython
+
echo ""
echo "=== Start running tests ==="
# Starting from VS2019 Powershell(?) will fail the test run
[binaries]
# we could set exe_wrapper = qemu-arm-static but to test the case
# when cross compiled binaries can't be run we don't do that
-c = '/opt/arm/developmentstudio-2019.0/sw/ARMCompiler6.12/bin/armclang'
+c = ['/opt/arm/developmentstudio-2019.0/sw/ARMCompiler6.12/bin/armclang', '--target=aarch64-arm-none-eabi']
#c = '/opt/arm/developmentstudio-2019.0/sw/ARMCompiler5.06u6/bin/armcc'
#cpp = '/usr/bin/arm-linux-gnueabihf-g++'
ar = '/opt/arm/developmentstudio-2019.0/sw/ARMCompiler6.12/bin/armar'
#strip = '/usr/arm-linux-gnueabihf/bin/strip'
-#pkgconfig = '/usr/bin/arm-linux-gnueabihf-pkg-config'
-
-[built-in options]
-c_args = ['--target=aarch64-arm-none-eabi']
+#pkg-config = '/usr/bin/arm-linux-gnueabihf-pkg-config'
[host_machine]
system = 'baremetal'
# to the environment(PATH) variable, so that Meson can find
# the armclang, armlink and armar while building.
[binaries]
-c = 'armclang'
-cpp = 'armclang'
+c = ['armclang', '--target=arm-arm-none-eabi']
+cpp = ['armclang', '--target=arm-arm-none-eabi']
ar = 'armar'
strip = 'armar'
[built-in options]
# The '--target', '-mcpu' options with the appropriate values should be mentioned
# to cross compile c/c++ code with armclang.
-c_args = ['--target=arm-arm-none-eabi', '-mcpu=cortex-m0plus']
-cpp_args = ['--target=arm-arm-none-eabi', '-mcpu=cortex-m0plus']
+c_args = ['-mcpu=cortex-m0plus']
+cpp_args = ['-mcpu=cortex-m0plus']
[host_machine]
system = 'bare metal' # Update with your system name - bare metal/OS.
[binaries]
-c = 'ccomp'
+c = ['ccomp', '-target', 'armv7a-eabi']
ar = 'ccomp'
strip = 'strip'
[built-in options]
-c_args = ['-target', 'armv7a-eabi', '-fall']
+c_args = ['-fall']
[host_machine]
system = 'bare metal' # Update with your system name - bare metal/OS.
# This is a cross compilation file from OSX Yosemite to iPhone
# Apple keeps changing the location and names of files so
-# these might not work for you. Use the googels and xcrun.
+# these might not work for you. Use the googles and xcrun.
[binaries]
-c = 'clang'
-cpp = 'clang++'
-objc = 'clang'
-objcpp = 'clang++'
+c = ['clang', '-arch', 'arm64', '-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk']
+cpp = ['clang++', '-arch', 'arm64', '-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk']
+objc = ['clang', '-arch', 'arm64', '-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk']
+objcpp = ['clang++', '-arch', 'arm64', '-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk']
ar = 'ar'
strip = 'strip'
[built-in options]
-c_args = ['-arch', 'arm64', '-miphoneos-version-min=11.0', '-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk']
-cpp_args = ['-arch', 'arm64', '-miphoneos-version-min=11.0', '-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk']
-c_link_args = ['-arch', 'arm64', '-miphoneos-version-min=11.0', '-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk']
-cpp_link_args = ['-arch', 'arm64', '-miphoneos-version-min=11.0', '-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk']
-objc_args = ['-arch', 'arm64', '-miphoneos-version-min=11.0', '-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk']
-objcpp_args = ['-arch', 'arm64', '-miphoneos-version-min=11.0', '-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk']
+c_args = ['-miphoneos-version-min=11.0']
+cpp_args = ['-miphoneos-version-min=11.0']
+c_link_args = ['-miphoneos-version-min=11.0']
+cpp_link_args = ['-miphoneos-version-min=11.0']
+objc_args = ['-miphoneos-version-min=11.0']
+objcpp_args = ['-miphoneos-version-min=11.0']
[properties]
root = '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer'
[host_machine]
system = 'darwin'
+subsystem = 'ios'
+kernel = 'xnu'
cpu_family = 'aarch64'
cpu = 'aarch64'
endian = 'little'
objc = '/usr/bin/i686-w64-mingw32-gcc'
ar = '/usr/bin/i686-w64-mingw32-ar'
strip = '/usr/bin/i686-w64-mingw32-strip'
-pkgconfig = '/usr/bin/i686-w64-mingw32-pkg-config'
+pkg-config = '/usr/bin/i686-w64-mingw32-pkg-config'
windres = '/usr/bin/i686-w64-mingw32-windres'
exe_wrapper = 'wine'
ld = '/usr/bin/i686-w64-mingw32-ld'
objc = '/usr/bin/x86_64-w64-mingw32-gcc'
ar = '/usr/bin/x86_64-w64-mingw32-ar'
strip = '/usr/bin/x86_64-w64-mingw32-strip'
-pkgconfig = '/usr/bin/x86_64-w64-mingw32-pkg-config'
+pkg-config = '/usr/bin/x86_64-w64-mingw32-pkg-config'
windres = '/usr/bin/x86_64-w64-mingw32-windres'
-exe_wrapper = 'wine64'
+exe_wrapper = 'wine'
cmake = '/usr/bin/cmake'
[properties]
--- /dev/null
+# This file assumes that the path to your Metrowerks Embedded ARM
+# toolchain is added to the environment(PATH) variable, so that
+# Meson can find the binaries while building.
+
+[binaries]
+c = 'mwccarm'
+c_ld = 'mwldarm'
+cpp = 'mwccarm'
+cpp_ld = 'mwldarm'
+ar = 'mwldarm'
+as = 'mwasmarm'
+
+[built-in options]
+c_args = ['-lang', 'c99', '-D_NITRO', '-nosyspath']
+c_link_args = '@DIRNAME@' / 'metrowerks.lcf'
+cpp_args = ['-lang', 'c++', '-D_NITRO', '-nosyspath']
+cpp_link_args = '@DIRNAME@' / 'metrowerks.lcf'
+
+[host_machine]
+system = 'bare metal'
+cpu = 'arm'
+cpu_family = 'arm'
+endian = 'little'
--- /dev/null
+# This file assumes that the path to your Metrowerks toolchain
+# of choice is added to the environment(PATH) variable, so that
+# Meson can find the binaries while building.
+
+[binaries]
+c = 'mwcceppc'
+c_ld = 'mwldeppc'
+cpp = 'mwcceppc'
+cpp_ld = 'mwldeppc'
+ar = 'mwldeppc'
+as = 'mwasmeppc'
+
+[built-in options]
+c_args = ['-lang', 'c99', '-nosyspath']
+c_link_args = '@DIRNAME@' / 'metrowerks.lcf'
+cpp_args = ['-lang', 'c++', '-nosyspath']
+cpp_link_args = '@DIRNAME@' / 'metrowerks.lcf'
+
+[host_machine]
+system = 'bare metal'
+cpu = 'ppc'
+cpu_family = 'ppc'
+endian = 'little'
--- /dev/null
+# General-purpose linker script for Metrowerks toolchains.
+# This script will link a blank application. Its only purpose
+# is to allow the toolchains to run Meson tests. To link an
+# actual application, you need to write your own fine-tuned lcf.
+
+MEMORY {
+ TEST (RWX) : ORIGIN=0, LENGTH=0
+}
+
+SECTIONS {
+ .TEST:{
+ * (.text)
+ * (.data)
+ * (.rodata)
+ * (.bss)
+ __startup=.;
+ } > TEST
+}
\ No newline at end of file
objc = ['false']
objcpp = ['false']
ar = ['false']
-pkgconfig = ['false']
+pkg-config = ['false']
cmake = ['false']
# This is a cross compilation file from OSX Yosemite to Apple tvOS
# Apple keeps changing the location and names of files so
-# these might not work for you. Use the googels and xcrun.
+# these might not work for you. Use the googles and xcrun.
[binaries]
-c = 'clang'
-cpp = 'clang++'
+c = ['clang', '-arch', 'arm64', '-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk']
+cpp = ['clang++', '-arch', 'arm64', '-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk']
ar = 'ar'
strip = 'strip'
[built-in options]
-c_args = ['-arch', 'arm64', '-mtvos-version-min=12.0', '-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk']
-cpp_args = ['-arch', 'arm64', '-mtvos-version-min=12.0', '-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk']
-c_link_args = ['-arch', 'arm64', '-mtvos-version-min=12.0', '-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk']
-cpp_link_args = ['-arch', 'arm64', '-mtvos-version-min=12.0', '-isysroot', '/Applications/Xcode.app/Contents/Developer/Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk']
+c_args = ['-mtvos-version-min=12.0']
+cpp_args = ['-mtvos-version-min=12.0']
+c_link_args = ['-mtvos-version-min=12.0']
+cpp_link_args = ['-mtvos-version-min=12.0']
[properties]
root = '/Applications/Xcode.app/Contents/Developer/Platforms/AppleTVOS.platform/Developer'
[host_machine]
system = 'darwin'
+subsystem = 'tvos'
+kernel = 'xnu'
cpu_family = 'arm'
cpu = 'arm64'
endian = 'little'
[binaries]
# we could set exe_wrapper = qemu-arm-static but to test the case
# when cross compiled binaries can't be run we don't do that
-c = '/usr/bin/arm-linux-gnueabihf-gcc'
-cpp = '/usr/bin/arm-linux-gnueabihf-g++'
+c = ['/usr/bin/arm-linux-gnueabihf-gcc']
+cpp = ['/usr/bin/arm-linux-gnueabihf-g++']
rust = ['rustc', '--target', 'arm-unknown-linux-gnueabihf', '-C', 'linker=/usr/bin/arm-linux-gnueabihf-gcc-7']
ar = '/usr/arm-linux-gnueabihf/bin/ar'
strip = '/usr/arm-linux-gnueabihf/bin/strip'
-pkgconfig = '/usr/bin/arm-linux-gnueabihf-pkg-config'
+pkg-config = '/usr/bin/arm-linux-gnueabihf-pkg-config'
ld = '/usr/bin/arm-linux/gnueabihf-ld'
[built-in options]
[binaries]
-c = '/home/jpakkane/src/emsdk/upstream/emscripten/emcc'
-cpp = '/home/jpakkane/src/emsdk/upstream/emscripten/em++'
-ar = '/home/jpakkane/src/emsdk/upstream/emscripten/emar'
+c = 'emcc'
+cpp = 'em++'
+ar = 'emar'
[built-in options]
c_args = []
-c_link_args = ['-s','EXPORT_ALL=1']
+c_link_args = ['-sEXPORT_ALL=1']
cpp_args = []
-cpp_link_args = ['-s', 'EXPORT_ALL=1']
+cpp_link_args = ['-sEXPORT_ALL=1']
[host_machine]
cur=$2
case $option in
prefix |\
- libdir |\
- libexecdir |\
bindir |\
- sbindir |\
- includedir |\
datadir |\
- mandir |\
+ includedir |\
infodir |\
+ libdir |\
+ licensedir |\
+ libexecdir |\
localedir |\
- sysconfdir |\
localstatedir |\
- sharedstatedir)
+ mandir |\
+ sbindir |\
+ sharedstatedir |\
+ sysconfdir |\
+ python.platlibdir |\
+ python.purelibdir |\
+ pkg-config-path |\
+ build.pkg-config-path |\
+ cmake-prefix-path |\
+ build.cmake-prefix-path)
_filedir -d
;;
+
cross-file)
_filedir
COMPREPLY+=($(_filedir_in "$XDG_DATA_DIRS"/meson/cross))
COMPREPLY+=($(_filedir_in "$XDG_DATA_HOME"/meson/cross))
COMPREPLY+=($(_filedir_in ~/.local/share/meson/cross))
;;
+
+ native-file)
+ _filedir
+ COMPREPLY+=($(_filedir_in "$XDG_DATA_DIRS"/meson/native))
+ COMPREPLY+=($(_filedir_in /usr/local/share/meson/native))
+ COMPREPLY+=($(_filedir_in /usr/share/meson/native))
+ COMPREPLY+=($(_filedir_in "$XDG_DATA_HOME"/meson/native))
+ COMPREPLY+=($(_filedir_in ~/.local/share/meson/native))
+ ;;
+
*)
return 1;;
esac
return 0
}
-_meson-setup() {
+_meson_compgen_options() {
+ local -r cur=$1
+ if [[ ${cur:0:2} == -- ]]; then
+ COMPREPLY+=($(compgen -P '--' -W '${longopts[*]}' -- "${cur:2}"))
+ elif [[ ${cur:0:1} == - ]]; then
+ if [[ ${#cur} == 1 ]]; then
+ # Only add longopts if cur not "-something"
+ COMPREPLY+=($(compgen -P '--' -W '${longopts[*]}' -- ""))
+ fi
+
+ COMPREPLY+=($(compgen -P '-' -W '${shortopts[*]}' -- "${cur:1}"))
+ else
+ return 1
+ fi
+
+ return 0
+}
+
+_meson_common_setup_configure_longopts=(
+ help
+ prefix
+ bindir
+ datadir
+ includedir
+ infodir
+ libdir
+ licensedir
+ libexecdir
+ localedir
+ localstatedir
+ mandir
+ sbindir
+ sharedstatedir
+ sysconfdir
+ auto-features
+ backend
+ genvslite
+ buildtype
+ debug
+ default-library
+ errorlogs
+ install-umask
+ layout
+ optimization
+ prefer-static
+ stdsplit
+ strip
+ unity
+ unity-size
+ warnlevel
+ werror
+ wrap-mode
+ force-fallback-for
+ vsenv
+ pkgconfig.relocatable
+ python.bytecompile
+ python.install-env
+ python.platlibdir
+ python.purelibdir
+ python.allow-limited-api
+ pkg-config-path
+ build.pkg-config-path
+ cmake-prefix-path
+ build.cmake-prefix-path
+ clearcache
+)
+
+_meson-setup() {
shortopts=(
h
D
)
longopts=(
- help
- prefix
- libdir
- libexecdir
- bindir
- sbindir
- includedir
- datadir
- mandir
- infodir
- localedir
- sysconfdir
- localstatedir
- sharedstatedir
- backend
- buildtype
- strip
- unity
- werror
- layout
- default-library
- warnlevel
- stdsplit
- errorlogs
+ ${_meson_common_setup_configure_longopts[@]}
+ native-file
cross-file
version
- wrap-mode
+ fatal-meson-warnings
+ reconfigure
+ wipe
)
local cur prev
- if _get_comp_words_by_ref cur prev &>/dev/null &&
- [ "${prev:0:2}" = '--' ] && _meson_complete_option "${prev:2}" "$cur"; then
- return
- elif _get_comp_words_by_ref cur prev &>/dev/null &&
- [ "${prev:0:1}" = '-' ] && [ "${prev:1:2}" != '-' ] && _meson_complete_option "${prev:1}"; then
- return
- elif _get_comp_words_by_ref -n '=' cur prev &>/dev/null; then
- if [ $prev == -D ]; then
+ if _get_comp_words_by_ref cur prev &>/dev/null; then
+ if [[ ${prev:0:2} == -- ]] && _meson_complete_option "${prev:2}" "$cur"; then
+ return
+ elif [[ ${prev:0:1} == - ]] && [[ ${prev:1:2} != - ]] && _meson_complete_option "${prev:1}"; then
+ return
+ fi
+ fi
+
+ if _get_comp_words_by_ref -n '=' cur prev &>/dev/null; then
+ if [[ $prev == -D ]]; then
_meson_complete_option "$cur"
return
fi
cur="${COMP_WORDS[COMP_CWORD]}"
fi
- if [[ "$cur" == "--"* ]]; then
- COMPREPLY+=($(compgen -P '--' -W '${longopts[*]}' -- "${cur:2}"))
- elif [[ "$cur" == "-"* ]]; then
- COMPREPLY+=($(compgen -P '--' -W '${longopts[*]}' -- "${cur:2}"))
- COMPREPLY+=($(compgen -P '-' -W '${shortopts[*]}' -- "${cur:1}"))
- else
+ if ! _meson_compgen_options "$cur"; then
_filedir -d
- if [ -z "$cur" ]; then
- COMPREPLY+=($(compgen -P '--' -W '${longopts[*]}'))
- COMPREPLY+=($(compgen -P '-' -W '${shortopts[*]}'))
+ if [[ -z $cur ]]; then
+ COMPREPLY+=($(compgen -P '--' -W '${longopts[*]}'))
+ COMPREPLY+=($(compgen -P '-' -W '${shortopts[*]}'))
fi
- if [ $COMP_CWORD -eq 1 ]; then
+ if [[ $COMP_CWORD == 1 ]]; then
COMPREPLY+=($(compgen -W "${meson_subcommands[*]}" -- "$cur"))
fi
fi
}
_meson-configure() {
-
shortopts=(
h
D
)
longopts=(
- help
- clearcache
+ ${_meson_common_setup_configure_longopts[@]}
+ no-pager
)
local cur prev
if _get_comp_words_by_ref -n '=' cur prev &>/dev/null; then
- if [ $prev == -D ]; then
+ if [[ $prev == -D ]]; then
_meson_complete_option "$cur"
return
fi
cur="${COMP_WORDS[COMP_CWORD]}"
fi
- if [[ "$cur" == "--"* ]]; then
- COMPREPLY+=($(compgen -P '--' -W '${longopts[*]}' -- "${cur:2}"))
- elif [[ "$cur" == "-"* ]]; then
- COMPREPLY+=($(compgen -P '--' -W '${longopts[*]}' -- "${cur:2}"))
- COMPREPLY+=($(compgen -P '-' -W '${shortopts[*]}' -- "${cur:1}"))
- else
+ if ! _meson_compgen_options "$cur"; then
for dir in "${COMP_WORDS[@]}"; do
- if [ -d "$dir" ]; then
+ if [[ -d "$dir" ]]; then
break
fi
dir=.
done
- if [ ! -d "$dir/meson-private" ]; then
+ if [[ ! -d "$dir/meson-private" ]]; then
_filedir -d
fi
- if [ -z "$cur" ]; then
- COMPREPLY+=($(compgen -P '--' -W '${longopts[*]}'))
- COMPREPLY+=($(compgen -P '-' -W '${shortopts[*]}'))
+ if [[ -z $cur ]]; then
+ COMPREPLY+=($(compgen -P '--' -W '${longopts[*]}'))
+ COMPREPLY+=($(compgen -P '-' -W '${shortopts[*]}'))
fi
fi
}
_meson-introspect() {
shortopts=(
h
+ a
+ i
+ f
)
longopts=(
- targets
- installed
- buildsystem-files
- buildoptions
- tests
+ ast
benchmarks
+ buildoptions
+ buildsystem-files
dependencies
+ scan-dependencies
+ installed
+ install-plan
projectinfo
+ targets
+ tests
+ backend
+ all
+ indent
+ force-object-output
)
local cur prev
cur="${COMP_WORDS[COMP_CWORD]}"
fi
- if [[ "$cur" == "--"* ]]; then
- COMPREPLY+=($(compgen -P '--' -W '${longopts[*]}' -- "${cur:2}"))
- elif [[ "$cur" == "-"* ]]; then
- COMPREPLY+=($(compgen -P '--' -W '${longopts[*]}' -- "${cur:2}"))
- COMPREPLY+=($(compgen -P '-' -W '${shortopts[*]}' -- "${cur:1}"))
- else
+ if ! _meson_compgen_options "$cur"; then
for dir in "${COMP_WORDS[@]}"; do
if [ -d "$dir" ]; then
break
C
n
e
- e
d
l
b
longopts=(
help
- name
- executable
- deps
- language
- builddir
- force
- type
- version
+ name
+ executable
+ deps
+ language
+ build
+ builddir
+ force
+ type
+ version
)
- if [[ "$cur" == "--"* ]]; then
- COMPREPLY+=($(compgen -P '--' -W '${longopts[*]}' -- "${cur:2}"))
- elif [[ "$cur" == "-"* && ${#cur} -gt 1 ]]; then
- COMPREPLY+=($(compgen -P '-' -W '${shortopts[*]}' -- "${cur:1}"))
- else
- if [ -z "$cur" ]; then
+ if ! _meson_compgen_options "$cur"; then
+ if [[ -z $cur ]]; then
COMPREPLY+=($(compgen -P '--' -W '${longopts[*]}'))
COMPREPLY+=($(compgen -P '-' -W '${shortopts[*]}'))
fi
fi
-
}
_meson-test() {
shortopts=(
+ h
q
v
t
)
longopts=(
- quiet
- verbose
- timeout-multiplier
+ help
+ maxfail
repeat
no-rebuild
gdb
+ gdb-path
list
- wrapper --wrap
- no-suite
+ wrapper
suite
+ no-suite
no-stdsplit
print-errorlogs
benchmark
logbase
num-processes
+ verbose
+ quiet
+ timeout-multiplier
setup
test-args
)
local cur prev
if _get_comp_words_by_ref -n ':' cur prev &>/dev/null; then
case $prev in
- --repeat)
+ --maxfail | --repeat)
# number, can't be completed
return
;;
+
--wrapper)
_command_offset $COMP_CWORD
return
;;
- -C)
+
+ --gdb-path | -C)
_filedir -d
return
;;
+
--suite | --no-suite)
for i in "${!COMP_WORDS[@]}"; do
opt="${COMP_WORDS[i]}"
esac
dir=.
done
- suites=($(python3 -c 'import sys, json;
+
+ suites=$(meson introspect "$dir" --tests | python3 -c 'import sys, json;
for test in json.load(sys.stdin):
for suite in test["suite"]:
print(suite)
- ' <<< "$(meson introspect "$dir" --tests)"))
-# TODO
- COMPREPLY+=($(compgen -W "${suites[*]}" -- "$cur"))
+' 2> /dev/null)
+# TODO - what?
+
+ if [[ $? == 0 ]]; then
+ COMPREPLY+=($(compgen -W "${suites[*]}" -- "$cur"))
+ fi
return
;;
+
--logbase)
# free string, can't be completed
return
;;
+
--num-processes)
# number, can't be completed
return
;;
+
-t | --timeout-multiplier)
# number, can't be completed
return
;;
+
--setup)
# TODO
return
;;
+
--test-args)
return
;;
cur="${COMP_WORDS[COMP_CWORD]}"
fi
- if [[ "$cur" == "--"* ]]; then
- COMPREPLY+=($(compgen -P '--' -W '${longopts[*]}' -- "${cur:2}"))
- elif [[ "$cur" == "-"* && ${#cur} -gt 1 ]]; then
- COMPREPLY+=($(compgen -P '-' -W '${shortopts[*]}' -- "${cur:1}"))
- else
+ if ! _meson_compgen_options "$cur"; then
for dir in "${COMP_WORDS[@]}"; do
if [ -d "$dir" ]; then
break
fi
dir=.
done
+
if [ ! -d "$dir/meson-private" ]; then
_filedir -d
fi
esac
dir=.
done
- tests=($(python3 -c 'import sys, json;
+
+ tests=$(meson introspect "$dir" --tests | python3 -c 'import sys, json;
for test in json.load(sys.stdin):
print(test["name"])
-' <<< "$(meson introspect "$dir" --tests)"))
- COMPREPLY+=($(compgen -W "${tests[*]}" -- "$cur"))
+' 2> /dev/null)
+
+ if [[ $? == 0 ]]; then
+ COMPREPLY+=($(compgen -W "${tests[*]}" -- "$cur"))
+ fi
if [ -z "$cur" ]; then
COMPREPLY+=($(compgen -P '--' -W '${longopts[*]}' -- "${cur:2}"))
}
_meson-help() {
- : # Nothing to do
+ longopts=(
+ setup
+ configure
+ dist
+ install
+ introspect
+ init
+ test
+ wrap
+ subprojects
+ rewrite
+ compile
+ devenv
+ env2mfile
+ )
+
+ local cur prev
+
+ if _get_comp_words_by_ref cur prev &>/dev/null; then
+ COMPREPLY+=($(compgen -W '${longopts[*]}' -- "${cur}"))
+ fi
}
_meson-rewrite() {
}
_meson-compile() {
- : TODO
+ shortopts=(
+ h
+ C
+ j
+ l
+ v
+ )
+
+ longopts=(
+ help
+ clean
+ jobs
+ load-average
+ verbose
+ ninja-args
+ vs-args
+ xcode-args
+ )
+
+ local cur prev
+ if _get_comp_words_by_ref cur prev &>/dev/null; then
+ if [[ ${prev:0:2} == -- ]] && _meson_complete_option "${prev:2}" "$cur"; then
+ return
+ elif [[ ${prev:0:1} == - ]] && [[ ${prev:1:2} != - ]] && _meson_complete_option "${prev:1}"; then
+ return
+ fi
+ else
+ cur="${COMP_WORDS[COMP_CWORD]}"
+ fi
+
+ if ! _meson_compgen_options "$cur"; then
+ _filedir -d
+
+ if [[ -z $cur ]]; then
+ COMPREPLY+=($(compgen -P '--' -W '${longopts[*]}'))
+ COMPREPLY+=($(compgen -P '-' -W '${shortopts[*]}'))
+ fi
+
+ if [[ $COMP_CWORD == 1 ]]; then
+ COMPREPLY+=($(compgen -W "${meson_subcommands[*]}" -- "$cur"))
+ fi
+ fi
}
_meson-devenv() {
'configure:configure a project'
'dist:generate release archive'
'init:create a new project'
-'install:install one more more targets'
+'install:install one or more targets'
'introspect:query project properties'
'setup:set up a build directory'
'test:run tests'
'--repeat[number of times to run the tests]:number of times to repeat: '
'--no-rebuild[do not rebuild before running tests]'
'--gdb[run tests under gdb]'
- '--gdb-path=[program to run for gdb (can be wrapper or compaitble program)]:program:_path_commands'
+ '--gdb-path=[program to run for gdb (can be wrapper or compatible program)]:program:_path_commands'
'--list[list available tests]'
'(--wrapper --wrap)'{'--wrapper=','--wrap='}'[wrapper to run tests with]:wrapper program:_path_commands'
"$__meson_cd"
"$__meson_cd"
'--clean[Clean the build directory]'
'(-j --jobs)'{'-j','--jobs'}'=[the number fo work jobs to run (if supported)]:_guard "[0-9]#" "number of jobs"'
- '(-l --load-averate)'{'-l','--load-average'}'=[the system load average to try to maintain (if supported)]:_guard "[0-9]#" "load average"'
+ '(-l --load-average)'{'-l','--load-average'}'=[the system load average to try to maintain (if supported)]:_guard "[0-9]#" "load average"'
'(-v --verbose)'{'-v','--verbose'}'[Show more output]'
'--ninja-args=[Arguments to pass to ninja (only when using ninja)]'
'--vs-args=[Arguments to pass to vs (only when using msbuild)]'
au BufNewFile,BufRead meson.build set filetype=meson
+au BufNewFile,BufRead meson.options set filetype=meson
au BufNewFile,BufRead meson_options.txt set filetype=meson
au BufNewFile,BufRead *.wrap set filetype=dosini
" License: VIM License
" Maintainer: Nirbheek Chauhan <nirbheek.chauhan@gmail.com>
" Liam Beguin <liambeguin@gmail.com>
-" Last Change: 2021 Aug 16
+" Last Change: 2023 Aug 27
" Credits: Zvezdan Petkovic <zpetkovic@acm.org>
" Neil Schemenauer <nas@meson.ca>
" Dmitry Vasiliev
" Built-in functions
syn keyword mesonBuiltin
+ \ build_machine
+ \ host_machine
+ \ meson
+ \ option
+ \ target_machine
\ add_global_arguments
\ add_global_link_arguments
\ add_languages
\ assert
\ benchmark
\ both_libraries
- \ build_machine
\ build_target
\ configuration_data
\ configure_file
\ custom_target
+ \ debug
\ declare_dependency
\ dependency
\ disabler
\ error
\ executable
\ files
- \ find_library
\ find_program
\ generator
\ get_option
\ get_variable
- \ gettext
- \ host_machine
\ import
\ include_directories
\ install_data
+ \ install_emptydir
\ install_headers
\ install_man
\ install_subdir
\ install_symlink
- \ install_emptydir
\ is_disabler
\ is_variable
\ jar
\ join_paths
\ library
- \ meson
\ message
- \ option
\ project
+ \ range
\ run_command
\ run_target
\ set_variable
\ subdir_done
\ subproject
\ summary
- \ target_machine
\ test
\ unset_variable
\ vcs_tag
\ warning
- \ range
- \ debug
if exists("meson_space_error_highlight")
" trailing whitespace
hi def link mesonNumber Number
hi def link mesonBuiltin Function
hi def link mesonBoolean Boolean
-if exists("meson_space_error_higlight")
+if exists("meson_space_error_highlight")
hi def link mesonSpaceError Error
endif
"exe",
"shared_lib",
"python_lib",
+ "python_limited_lib",
+ "python_bytecode",
"pdb",
"implib",
"py_implib",
+ "py_limited_implib",
"implibempty",
- "expr"
+ "expr",
+ "link"
]
},
"platform": {
Instructions on how to install hotdoc are [here](https://hotdoc.github.io/installing.html).
+Our custom hotdoc extensions require:
+- [chevron](https://pypi.org/project/chevron)
+- [strictyaml](https://pypi.org/project/strictyaml)
+
## Building the documentation
From the Meson repository root dir:
`subprojects/packagefiles/<project-name>`.
```
-${EDITOR} meson.build meson_options.txt
+${EDITOR} meson.build meson.options
```
In order to apply the locally added build files to the upstream
latest packagefiles. The second command configures meson and selects a
set of subprojects to enable.
-The Github project contains automatic CI on pushing to run the project
+The GitHub project contains automatic CI on pushing to run the project
and check the metadata for obvious mistakes. This can be checked from
your fork before submitting a PR.
Most non-trivial builds require user-settable options. As an example a
program may have two different data backends that are selectable at
build time. Meson provides for this by having a option definition
-file. Its name is `meson_options.txt` and it is placed at the root of
-your source tree.
+file. Its name is `meson.options` and it is placed at the root of
+your source tree. For versions of meson before 1.1, this file was called
+`meson_options.txt`.
Here is a simple option file.
prefix = get_option('prefix')
```
-It should be noted that you can not set option values in your Meson
+It should be noted that you cannot set option values in your Meson
scripts. They have to be set externally with the `meson configure`
command line tool. Running `meson configure` without arguments in a
build dir shows you all options you can set.
the build configuration time), static libraries, and shared libraries.
They are created with the commands `executable`, `library`,
`static_library` and `shared_library`, respectively. All objects created
-in this way are **immutable**. That is, you can not change any aspect of
+in this way are **immutable**. That is, you cannot change any aspect of
them after they have been constructed. This ensures that all information
pertaining to a given build target is specified in one well defined
place.
| includedir | include | Header file directory |
| infodir | share/info | Info page directory |
| libdir | see below | Library directory |
+| licensedir | see below | Licenses directory (since 1.1.0)|
| libexecdir | libexec | Library executable directory |
| localedir | share/locale | Locale data directory |
| localstatedir | var | Localstate data directory |
file](Cross-compilation.md#defining-the-environment), particularly the
paths section may be necessary.
+`licensedir` is empty by default. If set, it defines the default location
+to install a dependency manifest and project licenses. For more details,
+see [[meson.install_dependency_manifest]].
+
### Core options
Options that are labeled "per machine" in the table are set per
| Option | Default value | Description | Is per machine | Is per subproject |
| -------------------------------------- | ------------- | ----------- | -------------- | ----------------- |
-| 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} | ninja | Backend to use | no | no |
-| buildtype {plain, debug,<br>debugoptimized, release, minsize, custom} | debug | Build type to use | no | no |
+| 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-builtype 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_library {shared, static, both} | shared | Default library type | no | yes |
+| default_library {shared, static, both} | shared | Default library type | no | yes |
| 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 |
| unity_size {>=2} | 4 | Unity file block size | no | no |
| warning_level {0, 1, 2, 3, everything} | 1 | Set the warning level. From 0 = none to everything = highest | no | yes |
| werror | false | Treat warnings as errors | no | yes |
-| wrap_mode {default, nofallback,<br>nodownload, forcefallback, nopromote} | default | Wrap mode to use | no | no |
+| 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 |
+
+#### Details for `backend`
+
+Several build file formats are supported as command runners to build the
+configured project. Meson prefers ninja by default, but platform-specific
+backends are also available for better IDE integration with native tooling:
+Visual Studio for Windows, and xcode for macOS. It is also possible to
+configure with no backend at all, which is an error if you have targets to
+build, but for projects that need configuration + testing + installation allows
+for a lighter automated build pipeline.
+
+#### Details for `genvslite`
+
+Setup multiple buildtype-suffixed, ninja-backend build directories (e.g.
+[builddir]_[debug/release/etc.]) and generate [builddir]_vs containing a Visual
+Studio solution with multiple configurations that invoke a meson compile of the
+setup build directories, as appropriate for the current configuration (builtype).
+
+This has the effect of a simple setup macro of multiple 'meson setup ...'
+invocations with a set of different buildtype values. E.g.
+`meson setup ... --genvslite vs2022 somebuilddir` does the following -
+```
+meson setup ... --backend ninja --buildtype debug somebuilddir_debug
+meson setup ... --backend ninja --buildtype debugoptimized somebuilddir_debugoptimized
+meson setup ... --backend ninja --buildtype release somebuilddir_release
+```
+and additionally creates another 'somebuilddir_vs' directory that contains
+a generated multi-configuration visual studio solution and project(s) that are
+set to build/compile with the somebuilddir_[...] that's appropriate for the
+solution's selected buildtype configuration.
+
+#### Details for `buildtype`
<a name="build-type-options"></a> For setting optimization levels and
toggling debug, you can either set the `buildtype` option, or you can
All other combinations of `debug` and `optimization` set `buildtype` to `'custom'`.
+#### Details for `warning_level`
+
+Exact flags per warning level is compiler specific, but there is an approximative
+table for most common compilers.
+
+| Warning level | GCC/Clang | MSVC |
+| ------------- | --- | ---- |
+| 0 | | |
+| 1 | -Wall | /W2 |
+| 2 | -Wall -Wextra | /W3 |
+| 3 | -Wall -Wextra -Wpedantic | /W4 |
+| everything | -Weverything | /Wall |
+
+Clang's `-Weverything` is emulated on GCC by passing all known warning flags.
+
+#### Details for `vsenv`
+
+The `--vsenv` argument is supported since `0.60.0`, `-Dvsenv=true` syntax is supported
+since `1.1.0`.
+
+Since `0.59.0`, meson automatically activates a Visual Studio environment on Windows
+for all its subcommands, but only if no other compilers (e.g. `gcc` or `clang`)
+are found, and silently continues if Visual Studio activation fails.
+
+Setting the `vsenv` option to `true` forces Visual Studio activation even when other
+compilers are found. It also make Meson abort with an error message when activation
+fails.
+
+`vsenv` is `true` by default when using the `vs` backend.
+
## Base options
These are set in the same way as universal options, either by
| c_thread_count | 4 | integer value ≥ 0 | Number of threads to use with emcc when using threads |
| cpp_args | | free-form comma-separated list | C++ compile arguments to use |
| cpp_link_args | | free-form comma-separated list | C++ link arguments to use |
-| cpp_std | none | none, c++98, c++03, c++11, c++14, c++17, c++20 <br/>c++2a, c++1z, gnu++03, gnu++11, gnu++14, gnu++17, gnu++1z, <br/> gnu++2a, gnu++20, vc++14, vc++17, vc++latest | C++ language standard to use |
+| cpp_std | none | none, c++98, c++03, c++11, c++14, c++17, c++20 <br/>c++2a, c++1z, gnu++03, gnu++11, gnu++14, gnu++17, gnu++1z, <br/> gnu++2a, gnu++20, vc++14, vc++17, vc++20, vc++latest | C++ language standard to use |
| cpp_debugstl | false | true, false | C++ STL debug mode |
| cpp_eh | default | none, default, a, s, sc | C++ exception handling type |
| cpp_rtti | true | true, false | Whether to enable RTTI (runtime type identification) |
project requires C++11, but a subproject requires C++14. The `cpp_std` value
from the subproject's `default_options` is now respected.
+Since *1.3.0* `c_std` and `cpp_std` options now accept a list of values.
+Projects that prefer GNU C, but can fallback to ISO C, can now set, for
+example, `default_options: 'c_std=gnu11,c11'`, and it will use `gnu11` when
+available, but fallback to c11 otherwise. It is an error only if none of the
+values are supported by the current compiler.
+Likewise, a project that can take benefit of `c++17` but can still build with
+`c++11` can set `default_options: 'cpp_std=c++17,c++11'`.
+This allows us to deprecate `gnuXX` values from the MSVC compiler. That means
+that `default_options: 'c_std=gnu11'` will now print a warning with MSVC
+but fallback to `c11`. No warning is printed if at least one
+of the values is valid, i.e. `default_options: 'c_std=gnu11,c11'`.
+In the future that deprecation warning will become an hard error because
+`c_std=gnu11` should mean GNU is required, for projects that cannot be
+built with MSVC for example.
+
## Specifying options per machine
Since *0.51.0*, some options are specified per machine rather than
### Python module
-| Option | Default value | Possible values | Description |
-| ------ | ------------- | ----------------- | ----------- |
-| install_env | prefix | {auto,prefix,system,venv} | Which python environment to install to (Since 0.62.0) |
-| 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) |
+| Option | Default value | Possible values | Description |
+| ------ | ------------- | ----------------- | ----------- |
+| bytecompile | 0 | integer from -1 to 2 | What bytecode optimization level to use (Since 1.2.0) |
+| install_env | prefix | {auto,prefix,system,venv} | Which python environment to install to (Since 0.62.0) |
+| 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) |
*Since 0.60.0* The `python.platlibdir` and `python.purelibdir` options are used
by the python module methods `python.install_sources()` and
never `prefix`). This option is mutually exclusive with the `platlibdir`/`purelibdir`.
For backwards compatibility purposes, the default `install_env` is `prefix`.
+
+*Since 1.2.0* The `python.bytecompile` option can be used to enable compiling
+python bytecode. Bytecode has 3 optimization levels:
+
+- 0, bytecode without optimizations
+- 1, bytecode with some optimizations
+- 2, bytecode with some more optimizations
+
+To this, Meson adds level `-1`, which is to not attempt to compile bytecode at
+all.
+
+*Since 1.3.0* The `python.allow_limited_api` option affects whether the
+`limited_api` keyword argument of the `extension_module` method is respected.
+If set to `false`, the effect of the `limited_api` argument is disabled.
# Fetch the dependency object
cm_lib = sub_proj.dependency('cm_lib')
-executable(exe1, ['sources'], dependencies: [cm_lib])
+executable('exe1', ['sources'], dependencies: [cm_lib])
```
The `subproject` method is almost identical to the normal Meson
*(since 0.55.0)*
-`TARGET` has the following syntax `[PATH/]NAME[:TYPE]`, where:
+`TARGET` has the following syntax `[PATH/]NAME.SUFFIX[:TYPE]`, where:
- `NAME`: name of the target from `meson.build` (e.g. `foo` from `executable('foo', ...)`).
+- `SUFFIX`: name of the suffix of the target from `meson.build` (e.g. `exe` from `executable('foo', suffix: 'exe', ...)`).
- `PATH`: path to the target relative to the root `meson.build` file. Note: relative path for a target specified in the root `meson.build` is `./`.
-- `TYPE`: type of the target. Can be one of the following: 'executable', 'static_library', 'shared_library', 'shared_module', 'custom', 'run', 'jar'.
+- `TYPE`: type of the target. Can be one of the following: 'executable', 'static_library', 'shared_library', 'shared_module', 'custom', 'alias', 'run', 'jar'.
-`PATH` and/or `TYPE` can be omitted if the resulting `TARGET` can be
+`PATH`, `SUFFIX`, and `TYPE` can all be omitted if the resulting `TARGET` can be
used to uniquely identify the target in `meson.build`.
+Note that `SUFFIX` did not exist prior to 1.3.0.
+
#### Backend specific arguments
*(since 0.55.0)*
was no COMMAND supplied). However, supplying the command is necessary to avoid
clashes with future added commands, so "setup" should be used explicitly.
+*Since 1.1.0* `--reconfigure` is allowed even if the build directory does not
+already exist, that argument is ignored in that case.
+
+*Since 1.3.0* If the build directory already exists, options are updated with
+their new value given on the command line (`-Dopt=value`). Unless `--reconfigure`
+is also specified, this won't reconfigure immediately. This has the same behaviour
+as `meson configure <builddir> -Dopt=value`.
+
+*Since 1.3.0* It is possible to clear the cache and reconfigure in a single command
+with `meson setup --clearcache --reconfigure <builddir>`.
+
{{ setup_arguments.inc }}
See [Meson introduction
See [the unit test documentation](Unit-tests.md) for more info.
+Since *1.2.0* you can use wildcards in *args* for test names.
+For example, "bas*" will match all test with names beginning with "bas".
+
+Since *1.2.0* it is an error to provide a test name or wildcard that
+does not match any test.
+
#### Examples:
Run tests for the project:
- `QEMU_LD_PREFIX` *Since 1.0.0* is set to the `sys_root` value from cross file
when cross compiling and that property is defined.
-Since *Since 0.62.0* if bash-completion scripts are being installed and the
+*Since 0.62.0* if bash-completion scripts are being installed and the
shell is bash, they will be automatically sourced.
-Since *Since 0.62.0* when GDB helper scripts (*-gdb.py, *-gdb.gdb, and *-gdb.csm)
+*Since 0.62.0* when GDB helper scripts (*-gdb.py, *-gdb.gdb, and *-gdb.csm)
are installed with a library name that matches one being built, Meson adds the
needed auto-load commands into `<builddir>/.gdbinit` file. When running gdb from
top build directory, that file is loaded by gdb automatically. In the case of
python scripts that needs to load other python modules, `PYTHONPATH` may need
to be modified using `meson.add_devenv()`.
-Since *Since 0.63.0* when cross compiling for Windows `WINEPATH` is used instead
+*Since 0.63.0* when cross compiling for Windows `WINEPATH` is used instead
of `PATH` which allows running Windows executables using wine. Note that since
`WINEPATH` size is currently limited to 1024 characters, paths relative to the
root of build directory are used. That means current workdir must be the root of
build directory when running wine.
+*Since 1.1.0* `meson devenv --dump [<filename>]` command takes an optional
+filename argument to write the environment into a file instead of printing to
+stdout.
+
+*Since 1.1.0* `--dump-format` argument has been added to select which shell
+format should be used. There are currently 3 formats supported:
+- `sh`: Lines are in the format `VAR=/prepend:$VAR:/append`.
+- `export`: Same as `sh` but with extra `export VAR` lines.
+- `vscode`: Same as `sh` but without `$VAR` substitution because they do not
+ seems to be properly supported by vscode.
+
{{ devenv_arguments.inc }}
Not all compilers and platforms are alike. Therefore Meson provides
the tools to detect properties of the system during configure time. To
get most of this information, you first need to extract the *[compiler
-object](Reference-manual_returned_compiler.html)* from the main
+object](Reference-manual_returned_compiler.md)* from the main
*meson* variable.
```meson
```
*Note*: some compilers silently swallow command line arguments they do
-not understand. Thus this test can not be made 100% reliable.
+not understand. Thus this test cannot be made 100% reliable.
Often you want to change the settings of your build after it has been
generated. For example you might want to change from a debug build
into a release build, set custom compiler flags, change the build
-options provided in your `meson_options.txt` file and so on.
+options provided in your `meson.options` file and so on.
The main tool for this is the `meson configure` command.
## Submitting patches
All changes must be submitted as [pull requests to
-Github](https://github.com/mesonbuild/meson/pulls). This causes them
+GitHub](https://github.com/mesonbuild/meson/pulls). This causes them
to be run through the CI system. All submissions must pass a full CI
test run before they are even considered for submission.
These goals are slightly contradictory so the correct thing to do
often requires some judgement on part of the person doing the
-merge. Github provides three different merge options, The rules of
+merge. GitHub provides three different merge options, The rules of
thumb for choosing between them goes like this:
- single commit pull requests should always be rebased
```ini
[host_machine]
system = 'windows'
+subsystem = 'windows'
+kernel = 'nt'
cpu_family = 'x86'
cpu = 'i686'
endian = 'little'
purposes. The corresponding target definition would look the same but
have `target_machine` in the header. These values are available in
your Meson scripts. There are three predefined variables called,
-surprisingly, [[@build_machine]], [[@host_machine]] and [[@target_machine]].
-Determining the operating system of your host machine is simply a
-matter of calling `host_machine.system()`.
+surprisingly, [[@build_machine]], [[@host_machine]] and
+[[@target_machine]]. Determining the operating system of your host
+machine is simply a matter of calling `host_machine.system()`.
+Starting from version 1.2.0 you can get more fine grained information
+using the `.subsystem()` and `.kernel()` methods. The return values of
+these functions are documented in [the reference table
+page](Reference-tables.md).
There are two different values for the CPU. The first one is
`cpu_family`. It is a general type of the CPU. This should have a
## Custom data
You can store arbitrary data in `properties` and access them from your
-Meson files. As an example if you cross file has this:
+Meson files. As an example if your cross file has this:
```ini
[properties]
whether the actual dependency was found or not. Meson will ignore
non-found dependencies.
-Meson also allows to get variables that are defined in the
+Meson also allows one to get variables that are defined in a
`pkg-config` file. This can be done by using the
[[dep.get_pkgconfig_variable]] function.
*Note* new in 0.51.0
*new in 0.54.0, the `internal` keyword*
-When you need to get an arbitrary variables from a dependency that can
-be found multiple ways and you don't want to constrain the type you
+When you need to get an arbitrary variable from a dependency that can
+be found multiple ways and you don't want to constrain the type, you
can use the generic `get_variable` method. This currently supports
cmake, pkg-config, and config-tool based variables.
If your boost headers or libraries are in non-standard locations you
can set the `BOOST_ROOT`, or the `BOOST_INCLUDEDIR` and
`BOOST_LIBRARYDIR` environment variables. *(added in 0.56.0)* You can
-also set these parameters as `boost_root`, `boost_include`, and
+also set these parameters as `boost_root`, `boost_includedir`, and
`boost_librarydir` in your native or cross machine file. Note that
machine file variables are preferred to environment variables, and
that specifying any of these disables system-wide search for boost.
`method` may be `auto`, `config-tool` or `pkg-config`.
+## Pybind11
+
+*(added 1.1.0)*
+
+`method` may be `auto`, `pkg-config`, `config-tool`, or `cmake`.
+
## Python3
Python3 is handled specially by Meson:
## SDL2
SDL2 can be located using `pkg-confg`, the `sdl2-config` config tool,
-or as an OSX framework.
+as an OSX framework, or `cmake`.
-`method` may be `auto`, `config-tool`, `extraframework` or
-`pkg-config`.
+`method` may be `auto`, `config-tool`, `extraframework`,
+`pkg-config` or `cmake`.
## Shaderc
executable('myapp', 'myapp.cpp', pch : 'pch/myapp.hh')
```
-The main reason other build systems can not provide pch support this
+The main reason other build systems cannot provide pch support this
easily is because they don't enforce certain best practices. Due to
the way include paths work, it is impossible to provide pch support
that always works with both in-source and out-of-source
lib = shared_library('mylib', 'mylib.c',
dependencies : dep)
-# And ín a third directory
+# And in a third directory
exe = executable('mytest', 'mytest.c',
link_with : lib)
the executable/interpreter specified in it both on Windows and on
Unixes.
-Note that you can not pass your command line as a single string. That
+Note that you cannot pass your command line as a single string. That
is, calling `run_command('do_something foo bar')` will not work. You
must either split up the string into separate arguments or pass the
split command as an array. It should also be noted that Meson will not
```
Meson does not support this syntax and the reason for this is simple.
-This can not be made both reliable and fast. By reliable we mean that
+This cannot be made both reliable and fast. By reliable we mean that
if the user adds a new source file to the subdirectory, Meson should
detect that and make it part of the build automatically.
## Why is there not a Make backend?
Because Make is slow. This is not an implementation issue, Make simply
-can not be made fast. For further info we recommend you read [this
+cannot be made fast. For further info we recommend you read [this
post](http://neugierig.org/software/chromium/notes/2011/02/ninja.html)
by Evan Martin, the author of Ninja. Makefiles also have a syntax that
is very unpleasant to write which makes them a big maintenance burden.
Meson needs to know several details about each compiler in order to
compile code with it. These include things such as which compiler
flags to use for each option and how to detect the compiler from its
-output. This information can not be input via a configuration file,
+output. This information cannot be input via a configuration file,
instead it requires changes to Meson's source code that need to be
submitted to Meson master repository. In theory you can run your own
forked version with custom patches, but that's not good use of your
not care what the extension is](https://docs.microsoft.com/en-us/cpp/build/reference/link-input-files?view=vs-2019),
so specifying `libfoo.a` instead of `foo.lib` does not change the workflow,
and is an improvement since it's less ambiguous.
+1. Projects built with the MinGW compiler are fully compatible with
+ MSVC as long as they use the same CRT (e.g. UCRT with MSYS2).
+ These projects also name their static libraries `libfoo.a`.
If, for some reason, you really need your project to output static
libraries of the form `foo.lib` when building with MSVC, you can set
If you do not wish to use Ccache for some reason, just specify your
compiler with environment variables `CC` and/or `CXX` when first
-running Meson (remember that once specified the compiler can not be
+running Meson (remember that once specified the compiler cannot be
changed). Meson will then use the specified compiler without Ccache.
Coverage
useful with them (this excludes `exists`, `is_dir`, `is_file`, `is_absolute`
since a `files()` object is always the absolute path to an existing file).
+## Usage
+
+The module may be imported as follows:
+
+``` meson
+fs = [[#import]]('fs')
+```
+
## File lookup rules
Non-absolute paths are looked up relative to the directory where the
Environment variables are not available as is the rule throughout Meson.
That is, $HOME, %USERPROFILE%, $MKLROOT, etc. have no meaning to the Meson
filesystem module. If needed, pass such variables into Meson via command
-line options in `meson_options.txt`, native-file or cross-file.
+line options in `meson.options`, native-file or cross-file.
Where possible, symlinks and parent directory notation are resolved to an
absolute path.
x = 'foo.txt'
y = 'sub/../foo.txt'
z = 'bar.txt' # a symlink pointing to foo.txt
-j = 'notafile.txt' # non-existent file
+j = 'notafile.txt' # nonexistent file
fs.is_samepath(x, y) # true
fs.is_samepath(x, z) # true
p = 'foo/bar'
q = 'foo/bar/baz/..'
r = 'buz' # a symlink pointing to foo/bar
-s = 'notapath' # non-existent directory
+s = 'notapath' # nonexistent directory
fs.is_samepath(p, q) # true
fs.is_samepath(p, r) # true
project. If the file specified by `path` is a `files()` object it
cannot refer to a built file.
+### relative_to
+
+*Since 1.3.0*
+
+Return a relative filepath. In the event a relative path could not be found, the
+absolute path of `to` is returned. Relative path arguments will be assumed to be
+relative to `meson.current_source_dir()`.
+
+Has the following positional arguments:
+ - to `str | file | custom_tgt | custom_idx | build_tgt`: end path
+ - from `str | file | custom_tgt | custom_idx | build_tgt`: start path
+
+returns:
+ - a string
### copyfile
target:
```meson
-prog_python = import('python').find_installation('python3')
+prog_python = [[#find_program]]('python3')
foo_c = custom_target(
'foo.c',
the output argument to `custom_target`
```meson
-prog_python = import('python').find_installation('python3')
+prog_python = [[#find_program]]('python3')
foo_ch = custom_target(
'foo.[ch]',
arguments : ['--out_dir=@BUILD_DIR@', '@INPUT@'])
```
-In this case you can not use the plain `@OUTPUT@` variable, as it
+In this case you cannot use the plain `@OUTPUT@` variable, as it
would be ambiguous. This program only needs to know the output
directory, it will generate the file names by itself.
resource and the second is the XML file containing the resource
definitions. If the name is `foobar`, Meson will generate a header
file called `foobar.h`, which you can then include in your sources.
+The resources specified are automatically added as dependencies of the
+generated target.
* `c_name`: passed to the resource compiler as an argument after
`--c-name`
* `dependencies`: deps to use during introspection scanning
* `extra_args`: command line arguments to pass to gir compiler
+* `env`: (*Added 1.2.0*) environment variables to set, such as
+ `{'NAME1': 'value1', 'NAME2': 'value2'}` or `['NAME1=value1', 'NAME2=value2']`,
+ or an [[@env]] object which allows more sophisticated environment juggling.
* `export_packages`: extra packages the gir file exports
* `sources`: the list of sources to be scanned for gir data
* `nsversion`: namespace version
target's list of sources to codify the dependency. This is true for
all generated sources, not just `mkenums_simple`.
+The generated source file includes all headers passed to the sources keyword
+argument, using paths relative to current build or source directory. That means
+that targets that compile the generated source file must have the current
+directory in its `include_directories`. *Since 1.3.0* `sources` outside of
+current directory do not require adding those directories into
+`include_directories` anymore.
+
* `body_prefix`: additional prefix at the top of the body file,
e.g. for extra includes
* `decorator`: optional decorator for the function declarations,
`intro-dependencies.json`. Here, the name, version, compiler and
linker arguments for a dependency are listed.
-### Scanning for dependecie with `--scan-dependencies`
+### Scanning for dependencies with `--scan-dependencies`
It is also possible to get most dependencies used without a build
directory. This can be done by running `meson introspect
```json
[
"/Path/to/the/targets/meson.build",
- "/Path/to/the/targets/meson_options.txt",
+ "/Path/to/the/targets/meson.options",
"/Path/to/the/targets/subdir/meson.build"
]
```
- [Gnome Builder](https://wiki.gnome.org/Apps/Builder)
- [KDevelop](https://www.kdevelop.org)
-- [Eclipse CDT](https://www.eclipse.org/cdt/) (experimental)
-- [Meson Cmake Wrapper](https://github.com/prozum/meson-cmake-wrapper) (for cmake IDEs) (currently unmaintained !!)
-- [Meson-UI](https://github.com/michaelbadcrumble/meson-ui) (Meson build GUI)
+- [Eclipse CDT](https://www.eclipse.org/cdt/)
- [Meson Syntax Highlighter](https://plugins.jetbrains.com/plugin/13269-meson-syntax-highlighter) plugin for JetBrains IDEs.
-- [asabil.meson](https://open-vsx.org/extension/asabil/meson) extension for VS Code/Codium
+- [vscode-meson](https://github.com/mesonbuild/vscode-meson) extension for VS Code/Codium
- [Qt Creator](https://doc.qt.io/qtcreator/creator-project-meson.html)
+- [Meson-UI](https://github.com/dreamer-coding-555/meson-ui) (build GUI for Meson)
+- [mmeson](https://github.com/stephanlachnit/mmeson) (ccmake clone for Meson)
The argument is the name of the script file relative to the current
subdirectory.
+## Installing as the superuser
+
+When building as a non-root user, but installing to root-owned locations via
+e.g. `sudo ninja install`, ninja will attempt to rebuild any out of date
+targets as root. This results in various bad behaviors due to build outputs and
+ninja internal files being owned by root.
+
+Running `meson install` is preferred for several reasons. It can rebuild out of
+date targets and then re-invoke itself as root. *(since 1.1.0)* Additionally,
+running `sudo meson install` will drop permissions and rebuild out of date
+targets as the original user, not as root.
+
+*(since 1.1.0)* Re-invoking as root will try to guess the user's preferred method for
+re-running commands as root. The order of precedence is: sudo, doas, pkexec
+(polkit). An elevation tool can be forced by setting `$MESON_ROOT_CMD`.
+
## DESTDIR support
Sometimes you need to install to a different directory than the
c = arch + '-gcc'
cpp = arch + '-g++'
strip = arch + '-strip'
-pkgconfig = arch + '-pkg-config'
+pkg-config = arch + '-pkg-config'
...
```
a = 'Hello'
```
+*Since 1.3.0* Some tokens are replaced in the machine file before parsing it:
+- `@GLOBAL_SOURCE_ROOT@`: the absolute path to the project's source tree
+- `@DIRNAME@`: the absolute path to the machine file's parent directory.
+
+It can be used, for example, to have paths relative to the source directory, or
+relative to toolchain's installation directory.
+```ini
+[binaries]
+c = '@DIRNAME@/toolchain/gcc'
+exe_wrapper = '@GLOBAL_SOURCE_ROOT@' / 'build-aux' / 'my-exe-wrapper.sh'
+```
+
### Binaries
The binaries section contains a list of binaries. These can be used
cpp_ld = 'gold'
ar = '/usr/i586-mingw32msvc/bin/ar'
strip = '/usr/i586-mingw32msvc/bin/strip'
-pkgconfig = '/usr/bin/i586-mingw32msvc-pkg-config'
+pkg-config = '/usr/bin/i586-mingw32msvc-pkg-config'
```
An incomplete list of internally used programs that can be overridden
- libwmf-config
- llvm-config
- pcap-config
-- pkgconfig
+- pkg-config
- sdl2-config
- wx-config (or wx-3.0-config or wx-config-gtk)
subprojects. This setting has no effect if the `exe_wrapper` was not specified.
The default value is `true`. (*new in 0.56.0*)
- `java_home` is an absolute path pointing to the root of a Java installation.
+- `bindgen_clang_arguments` an array of extra arguments to pass to clang when
+ calling bindgen
### CMake variables
only adds a few common files and runs the `install.sh` script which
should contain all distribution specific setup steps. The `common.sh`
can be sourced via `source /ci/common.sh` to access some shared
-functionalety.
+functionality.
To generate the image run `build.py -t build <image>`. A generated
image can be tested with `build.py -t test <image>`.
This page lists the Meson project's stance on mixing build systems.
The tl/dr version is that while we do provide some functionality for
this use case, it only works for simple cases. Anything more complex
-can not be made reliable and trying to do that would burden Meson
+cannot be made reliable and trying to do that would burden Meson
developers with an effectively infinite maintenance burden. Thus these
use cases are not guaranteed to work, and even if a project using them
works today there are no guarantees that it will work in any future
endif
```
-`meson_options.txt`:
+`meson.options`:
```meson
option('enable-dep11', type : 'boolean', value : true, description : 'enable DEP-11')
- One of `python2` or `python3`: in either case, the module will try
some alternative names: `py -2` or `py -3` on Windows, and `python`
everywhere. In the latter case, it will check whether the version
- provided by the sysconfig module matches the required major version
+ provided by the sysconfig module matches the required major version.
+
+ *Since 1.2.0*, searching for minor version (e.g. `python3.11`) also
+ works on Windows.
Keyword arguments are the following:
`/usr/lib/site-packages`. When subdir is passed to this method,
it will be appended to that location. This keyword argument is
mutually exclusive with `install_dir`
+- `limited_api`: *since 1.3.0* A string containing the Python version
+ of the [Py_LIMITED_API](https://docs.python.org/3/c-api/stable.html) that
+ the extension targets. For example, '3.7' to target Python 3.7's version of
+ the limited API. This behavior can be disabled by setting the value of
+ `python.allow_limited_api`. See [Python module options](Builtin-options.md#python-module).
Additionally, the following diverge from [[shared_module]]'s default behavior:
- `ui_files`: (string | File | CustomTarget)[]: Passed the `uic` compiler
- `moc_sources`: (string | File | CustomTarget)[]: Passed the `moc` compiler.
These are converted into .moc files meant to be `#include`ed
- - `moc_headers`: (string | File | CustomTarget)[]: Passied the `moc` compiler.
+ - `moc_headers`: (string | File | CustomTarget)[]: Passed the `moc` compiler.
These will be converted into .cpp files
- `include_directories` (IncludeDirectories | string)[], the directories to add
to header search path for `moc`
| lcc | Elbrus C/C++/Fortran Compiler | |
| llvm | LLVM-based compiler (Swift, D) | |
| mono | Xamarin C# compiler | |
+| mwccarm | Metrowerks C/C++ compiler for Embedded ARM | |
+| mwcceppc | Metrowerks C/C++ compiler for Embedded PowerPC | |
| msvc | Microsoft Visual Studio | msvc |
| nagfor | The NAG Fortran compiler | |
| nvidia_hpc| NVidia HPC SDK compilers | |
+| nvcc | NVidia CUDA compiler | |
| open64 | The Open64 Fortran Compiler | |
| pathscale | The Pathscale Fortran compiler | |
| pgi | Portland PGI C/C++/Fortran compilers | |
| yasm | The YASM compiler (Since 0.64.0) | |
| ml | Microsoft Macro Assembler for x86 and x86_64 (Since 0.64.0) | msvc |
| armasm | Microsoft Macro Assembler for ARM and AARCH64 (Since 0.64.0) | |
+| mwasmarm | Metrowerks Assembler for Embedded ARM | |
+| mwasmeppc | Metrowerks Assembler for Embedded PowerPC | |
## Linker ids
| ld.solaris | Solaris and illumos |
| ld.wasm | emscripten's wasm-ld linker |
| ld64 | Apple ld64 |
+| ld64.lld | The LLVM linker, with the ld64 interface |
| link | MSVC linker |
| lld-link | The LLVM linker, with the MSVC interface |
| xilink | Used with Intel-cl only, MSVC like |
| pgi | Portland/Nvidia PGI |
| nvlink | Nvidia Linker used with cuda |
| ccomp | CompCert used as the linker driver |
+| mwldarm | The Metrowerks Linker with the ARM interface, used with mwccarm only |
+| mwldeppc | The Metrowerks Linker with the PowerPC interface, used with mwcceppc only |
For languages that don't have separate dynamic linkers such as C# and Java, the
`get_linker_id` will return the compiler name.
| sh4 | SuperH SH-4 |
| sparc | 32 bit SPARC |
| sparc64 | SPARC v9 processor |
+| sw_64 | 64 bit sunway processor |
| wasm32 | 32 bit Webassembly |
| wasm64 | 64 bit Webassembly |
| x86 | 32 bit x86 processor |
| x86_64 | 64 bit x86 processor |
+
Any cpu family not listed in the above list is not guaranteed to
remain stable in future releases.
| cygwin | The Cygwin environment for Windows |
| darwin | Either OSX or iOS |
| dragonfly | DragonFly BSD |
-| emscripten | Emscripten's Javascript environment |
+| emscripten | Emscripten's JavaScript environment |
| freebsd | FreeBSD and its derivatives |
| gnu | GNU Hurd |
| haiku | |
Any string not listed above is not guaranteed to remain stable in
future releases.
+## Kernel names (since 1.2.0)
+
+Native names as returned by the `.kernel()` method.
+
+| Value | Comment |
+| ----- | ------- |
+| linux | |
+| freebsd | |
+| openbsd | |
+| netbsd | |
+| nt | |
+| xnu | Kernel of various Apple OSes |
+| illumos | Kernel derived from OpenSolaris by community efforts |
+| solaris | Kernel derived from OpenSolaris by Oracle |
+| dragonfly | |
+| haiku| |
+| none | For e.g. bare metal embedded |
+
+
+## Subsystem names (since 1.2.0)
+
+A more specific description of the system in question. Most values are
+meant to be used in cross files only, as those platforms can not run
+Meson natively.
+
+| Value | Comment |
+| ----- | ------- |
+| macos | Apple macOS (formerly OSX) |
+| ios | Apple iOS |
+| ios-simulator | |
+| tvos | Apple tvOS |
+| tvos-simulator | |
+| watchos | Apple watchOS |
+| watchos-simulator | |
+
## Language arguments parameter names
-These are the parameter names for passing language specific arguments to your build target.
+These are the parameter names for passing language specific arguments
+to your build target.
| Language | compiler name | linker name |
| ------------- | ------------- | ----------------- |
| C | c_args | c_link_args |
| C++ | cpp_args | cpp_link_args |
| C# | cs_args | cs_link_args |
+| CUDA | cuda_args | cuda_link_args |
| D | d_args | d_link_args |
| Fortran | fortran_args | fortran_link_args |
| Java | java_args | java_link_args |
| ----- | ------- |
| CFLAGS | Flags for the C compiler |
| CXXFLAGS | Flags for the C++ compiler |
+| CUFLAGS | Flags for the CUDA compiler |
| OBJCFLAGS | Flags for the Objective C compiler |
| FFLAGS | Flags for the Fortran compiler |
| DFLAGS | Flags for the D compiler |
| sentinel⁵ |
| unused |
| used |
+| vector_size⁶ |
| visibility* |
| visibility:default† |
| visibility:hidden† |
⁵ *New in 0.63.0*
+⁶ *New in 1.1.0*
+
### MSVC __declspec
These values are supported using the MSVC style `__declspec` annotation,
| C# | CSC | CSC | The linker is the compiler |
| nasm | NASM | | Uses the C linker |
-*The old environment variales are still supported, but are deprecated
+*The old environment variables are still supported, but are deprecated
and will be removed in a future version of Meson.*
+*changed in 1.3.0* Paths with spaces were split unconditionally to extract
+components such as the [path to Ccache](Feature-autodetection.md#ccache),
+intrinsic compiler flags like `-m32` or `--target`, etc. This broke passing
+a hardcoded compiler path to CMake subprojects. To work around this, paths
+must be wrapped with double quotes:
+
+```bash
+export CC='"C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.34.31933/bin/Hostx64/x64/cl.exe"'
+```
+
+You can also set the values through [machine files](Machine-files.md#binaries).
+
+*New in 1.3.0* Paths that point to an existing executable no longer need
+wrapping:
+
+```bash
+export CC='C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.34.31933/bin/Hostx64/x64/cl.exe'
+```
+
+
## Environment variables per machine
Since *0.54.0*, Following Autotool and other legacy build systems,
## Link whole contents of static libraries
The default behavior of static libraries is to discard all symbols
-that are not not directly referenced. This may lead to exported
+that are not directly referenced. This may lead to exported
symbols being lost. Most compilers support "whole archive" linking
that includes all symbols and code of a given static library. This is
exposed with the `link_whole` keyword.
The value `if-release` can be given for the `b_ndebug` project option.
This will make the `NDEBUG` pre-compiler macro to be defined for
-release type builds as if the `b_ndebug` project option had had the
+release type builds as if the `b_ndebug` project option had the
value `true` defined for it.
## `install_data()` defaults to `{datadir}/{projectname}`
final = shared_library(
'final',
['final.c'],
- dependencyes : dep,
+ dependencies : dep,
)
```
Cross-compilation is now supported for ARM targets using ARM compiler
version 6 - ARMCLANG. The required ARMCLANG compiler options for
building a shareable library are not included in the current Meson
-implementation for ARMCLANG support, so it can not build shareable
+implementation for ARMCLANG support, so it cannot build shareable
libraries. This current Meson implementation for ARMCLANG support can
not build assembly files with arm syntax (we need to use armasm
instead of ARMCLANG for the `.s` files with this syntax) and only
adds nothing by default but instead makes it the user's responsibility
to add everything by hand. This works but is a bit tedious.
-In this release we have added new new options to manually toggle e.g.
+In this release we have added new options to manually toggle e.g.
optimization levels and debug info so those can be changed
independently of other options. For example by default the debug
buildtype has no optimization enabled at all. If you wish to use GCC's
serializing all targets in this pool.
The primary use-case for this is to be able to run external commands
-that take a long time to exeute. Without setting this, the user does
+that take a long time to execute. Without setting this, the user does
not receive any feedback about what the program is doing.
## `dependency(version:)` now applies to all dependency types
first positional argument instead of in the `libraries` keyword
argument. The previous behaviour is maintained but prints a
deprecation warning and support for this will be removed in a future
-Meson release. If you can not create the needed pkg-config file
+Meson release. If you cannot create the needed pkg-config file
without this warning, please file an issue with as much details as
possible about the situation.
introspection are now lists of strings with identical length.
The `--target-files` option is now deprecated, since the same information
-can be acquired from the `--tragets` introspection API.
+can be acquired from the `--targets` introspection API.
## Meson file rewriter
directory. This dump will be (re)generated each time meson updates the
configuration of the build directory.
-Additionlly the format of `meson introspect target` was changed:
+Additionally the format of `meson introspect target` was changed:
- New: the `sources` key. It stores the source files of a target and their compiler parameters.
- New: the `defined_in` key. It stores the Meson file where a target is defined
integrating custom code generator steps, but note that there are many
limitations:
- - Meson can not know about link dependencies of the custom target. If
+ - Meson cannot know about link dependencies of the custom target. If
the target requires further link libraries, you need to add them manually
- The user is responsible for ensuring that the code produced by
# Fetch the dependency object
cm_lib = sub_proj.dependency('cm_lib')
-executable(exe1, ['sources'], dependencies: [cm_lib])
+executable('exe1', ['sources'], dependencies: [cm_lib])
```
It should be noted that not all projects are guaranteed to work. The
## Compiler and dynamic linker representation split
-0.52.0 includes a massive refactor of the representaitons of compilers to
+0.52.0 includes a massive refactor of the representations of compilers to
tease apart the representations of compilers and dynamic linkers (ld). This
fixes a number of compiler/linker combinations. In particular this fixes
use GCC and vanilla clang on macOS.
```
- `link_with:` of a static library with an uninstalled static library. In the
example below, lib2 now implicitly promote `link_with:` to `link_whole:` because
-the installed lib2 would oterhwise be unusable.
+the installed lib2 would otherwise be unusable.
```meson
lib1 = static_library(sources, install : false)
lib2 = static_library(sources, link_with : lib1, install : true)
undermines the purpose of distro-integration that is the only reason
environment variables are supported at all in Meson. The new behavior
is not quite the same, but doesn't conflict: Meson doesn't always
-repond to an environment when Autoconf would, but when it does it
+respond to an environment when Autoconf would, but when it does it
interprets it as Autotools would.
## Added 'pkg_config_libdir' property
overriding certain build details extracted from the CMake subproject.
The new CMake configuration options object is very similar to the
-[[@cfg_data]] object object returned
+[[@cfg_data]] object returned
by [[configuration_data]]. It
is generated by the `subproject_options` function
## String concatenation in meson_options.txt
It is now possible to use string concatenation (with the `+`
-opperator) in the `meson_options.txt` file. This allows splitting long
+operator) in the `meson_options.txt` file. This allows splitting long
option descriptions.
```meson
## New `build target` methods
The [[@build_tgt]] object now supports
-the following two functions, to ensure feature compatebility with
+the following two functions, to ensure feature compatibility with
[[@external_program]] objects:
- `found()`: Always returns `true`. This function is meant
use-cases where an executable is used instead of an external program.
- `path()`: **(deprecated)** does the exact same as `full_path()`.
- **NOTE:** This function is solely kept for compatebility
+ **NOTE:** This function is solely kept for compatibility
with `external program` objects. It will be
removed once the, also deprecated, corresponding `path()` function in the
`external program` object is removed.
## The qt modules now accept generated outputs as inputs for qt.compile_*
-This means you can uset `custom_target`, custom_target indices
+This means you can use `custom_target`, custom_target indices
(`custom_target[0]`, for example), or the output of `generator.process` as
inputs to the various `qt.compile_*` methods.
`meson setup --vsenv` command line argument can now be used to force Visual Studio
activation even when other compilers are found. It also make Meson abort with an
-error message when activation fails. This is especially useful for Github Action
+error message when activation fails. This is especially useful for GitHub Actions
because their Windows images have gcc in their PATH by default.
`--vsenv` is set by default when using `vs` backend.
## Print modified environment variables with `meson devenv --dump`
-With `--dump` option, all envorinment variables that have been modified are
+With `--dump` option, all environment variables that have been modified are
printed instead of starting an interactive shell. It can be used by shell
-scripts that wish to setup their environment themself.
+scripts that wish to setup their environment themselves.
## New `method` and `separator` kwargs on `environment()` and `meson.add_devenv()`
## JAR Resources
The ability to add resources to a JAR has been added. Use the `java_resources`
-keyword argument. It takes a `sturctured_src` object.
+keyword argument. It takes a `structured_src` object.
```meson
jar(
The `bindgen` method of the `rust` module now accepts a dependencies argument.
Any include paths in these dependencies will be passed to the underlying call to
-`clang`, and the call to `bindgen` will correctly depend on any generatd sources.
+`clang`, and the call to `bindgen` will correctly depend on any generated sources.
## String arguments to the rust.bindgen include_directories argument
--- /dev/null
+---
+title: Release 1.1.0
+short-description: Release notes for 1.1.0
+...
+
+# New features
+
+Meson 1.1.0 was released on 10 April 2023
+## `clang-cl` now accepts `cpp_std=c++20`
+
+Requires `clang-cl` 13 or later.
+
+## coercing values in the option() function is deprecated
+
+Currently code such as:
+```meson
+option('foo', type : 'boolean', value : 'false')
+```
+works, because Meson coerces `'false'` to `false`.
+
+This should be avoided, and will now result in a deprecation warning.
+
+## New `declare_dependency(objects: )` argument
+
+A new argument to `declare_dependency` makes it possible to add objects
+directly to executables that use an internal dependency, without going
+for example through `link_whole`.
+
+## Dump devenv into file and select format
+
+`meson devenv --dump [<filename>]` command now takes an optional filename argument
+to write the environment into a file instead of printing to stdout.
+
+A new `--dump-format` argument has been added to select which shell format
+should be used. There are currently 3 formats supported:
+- `sh`: Lines are in the format `VAR=/prepend:$VAR:/append`.
+- `export`: Same as `sh` but with extra `export VAR` lines.
+- `vscode`: Same as `sh` but without `$VAR` substitution because they do not
+ seems to be properly supported by vscode.
+
+## Feature objects now have an enable_auto_if method
+
+This performs the opposite task of the disable_auto_if method, enabling the
+feature if the condition is true.
+
+## Add a FeatureOption.enable_if and .disable_if
+
+These are useful when features need to be constrained to pass to [[dependency]],
+as the behavior of an `auto` and `disabled` or `enabled` feature is markedly
+different. consider the following case:
+
+```meson
+opt = get_option('feature').disable_auto_if(not foo)
+if opt.enabled() and not foo
+ error('Cannot enable feat when foo is not also enabled')
+endif
+dep = dependency('foo', required : opt)
+```
+
+This could be simplified to
+```meson
+opt = get_option('feature').disable_if(not foo, error_message : 'Cannot enable feature when foo is not also enabled')
+dep = dependency('foo', required : opt)
+```
+
+For a real life example, here is some code in mesa:
+```meson
+_llvm = get_option('llvm')
+dep_llvm = null_dep
+with_llvm = false
+if _llvm.allowed()
+ dep_llvm = dependency(
+ 'llvm',
+ version : _llvm_version,
+ modules : llvm_modules,
+ optional_modules : llvm_optional_modules,
+ required : (
+ with_amd_vk or with_gallium_radeonsi or with_gallium_opencl or with_clc
+ or _llvm.enabled()
+ ),
+ static : not _shared_llvm,
+ fallback : ['llvm', 'dep_llvm'],
+ include_type : 'system',
+ )
+ with_llvm = dep_llvm.found()
+endif
+if with_llvm
+ ...
+elif with_amd_vk and with_aco_tests
+ error('ACO tests require LLVM, but LLVM is disabled.')
+elif with_gallium_radeonsi or with_swrast_vk
+ error('The following drivers require LLVM: RadeonSI, SWR, Lavapipe. One of these is enabled, but LLVM is disabled.')
+elif with_gallium_opencl
+ error('The OpenCL "Clover" state tracker requires LLVM, but LLVM is disabled.')
+elif with_clc
+ error('The CLC compiler requires LLVM, but LLVM is disabled.')
+else
+ draw_with_llvm = false
+endif
+```
+
+simplified to:
+```meson
+_llvm = get_option('llvm') \
+ .enable_if(with_amd_vk and with_aco_tests, error_message : 'ACO tests requires LLVM') \
+ .enable_if(with_gallium_radeonsi, error_message : 'RadeonSI requires LLVM') \
+ .enable_if(with_swrast_vk, error_message : 'Vulkan SWRAST requires LLVM') \
+ .enable_if(with_gallium_opencl, error_message : 'The OpenCL Clover state trackers requires LLVM') \
+ .enable_if(with_clc, error_message : 'CLC library requires LLVM')
+
+dep_llvm = dependency(
+ 'llvm',
+ version : _llvm_version,
+ modules : llvm_modules,
+ optional_modules : llvm_optional_modules,
+ required : _llvm,
+ static : not _shared_llvm,
+ fallback : ['llvm', 'dep_llvm'],
+ include_type : 'system',
+)
+with_llvm = dep_llvm.found()
+```
+
+## Generated objects can be passed in the `objects:` keyword argument
+
+In previous versions of Meson, generated objects could only be
+passed as sources of a build target. This was confusing, therefore
+generated objects can now be passed in the `objects:` keyword
+argument as well.
+
+## The project function now supports setting the project license files
+
+This goes together with the license name. The license files can be
+automatically installed via [[meson.install_dependency_manifest]],
+or queried via [[meson.project_license_files]].
+
+## A new core directory option "licensedir" is available
+
+This will install a dependency manifest to the specified directory, if none
+is is explicitly set.
+
+## `sudo meson install` now drops privileges when rebuilding targets
+
+It is common to install projects using sudo, which should not affect build
+outputs but simply install the results. Unfortunately, since the ninja backend
+updates a state file when run, it's not safe to run ninja as root at all.
+
+It has always been possible to carefully build with:
+
+```
+ninja && sudo meson install --no-rebuild
+```
+
+Meson now tries to be extra safe as a general solution. `sudo meson install`
+will attempt to rebuild, but has learned to run `ninja` as the original
+(pre-sudo or pre-doas) user, ensuring that build outputs are generated/compiled
+as non-root.
+
+## `meson install` now supports user-preferred root elevation tools
+
+Previously, when installing a project, if any files could not be installed due
+to insufficient permissions the install process was automatically re-run using
+polkit. Now it prompts to ask whether that is desirable, and checks for
+CLI-based tools such as sudo or opendoas or `$MESON_ROOT_CMD`, first.
+
+Meson will no longer attempt privilege elevation at all, when not running
+interactively.
+
+## Support for reading options from meson.options
+
+Support has been added for reading options from `meson.options` instead of
+`meson_options.txt`. These are equivalent, but not using the `.txt` extension
+for a build file has a few advantages, chief among them many tools and text
+editors expect a file with the `.txt` extension to be plain text files, not
+build scripts.
+
+## Redirect introspection outputs to stderr
+
+`meson introspect` used to disable logging to `stdout` to not interfere with generated json.
+It now redirect outputs to `stderr` to allow printing warnings to the console
+while keeping `stdout` clean for json outputs.
+
+## New "none" backend
+
+The `--backend=none` option has been added, to configure a project that has no
+build rules, only install rules. This avoids depending on ninja.
+
+## compiler.preprocess()
+
+Dependencies keyword argument can now be passed to `compiler.preprocess()` to
+add include directories or compiler arguments.
+
+Generated sources such as custom targets are now allowed too.
+
+## New pybind11 custom dependency
+
+`dependency('pybind11')` works with pkg-config and cmake without any special
+support, but did not handle the `pybind11-config` script.
+
+This is useful because the config-tool will work out of the box when pybind11
+is installed, but the pkg-config and cmake files are shoved into python's
+site-packages, which makes it impossible to use in an out of the box manner.
+
+
+## Allow --reconfigure and --wipe of empty builddir
+
+`meson setup --reconfigure builddir` and `meson setup --wipe builddir` are now
+accepting `builddir/` to be empty or containing a previously failed setup attempt.
+Note that in that case previously passed command line options must be repeated
+as only a successful build saves configured options.
+
+This is useful for example with scripts that always repeat all options,
+`meson setup builddir --wipe -Dfoo=bar` will always work regardless whether
+it is a first invocation or not.
+
+## Allow custom install scripts to run with `--dry-run` option
+
+An new `dry_run` keyword is added to `meson.add_install_script()`
+to allow a custom install script to run when meson is invoked
+with `meson install --dry-run`.
+
+In dry run mode, the `MESON_INSTALL_DRY_RUN` environment variable
+is set.
+
--- /dev/null
+---
+title: Release 1.2.0
+short-description: Release notes for 1.2.0
+...
+
+# New features
+
+Meson 1.2.0 was released on 17 July 2023
+## Added Metrowerks C/C++ toolchains
+
+Added support for the Metrowerks Embedded ARM and Metrowerks Embedded PowerPC toolchains (https://www.nxp.com/docs/en/reference-manual/CWMCUKINCMPREF.pdf).
+
+The implementation is somewhat experimental. It has been tested on a few projects and works fairly well, but may have issues.
+
+## Added str.splitlines method
+
+[[str.splitlines]] can now be used to split a string into an array of lines.
+
+## `generator.process(generator.process(...))`
+
+Added support for code like this:
+```meson
+gen1 = generator(...)
+gen2 = generator(...)
+gen2.process(gen1.process('input.txt'))
+```
+
+## Extra files keyword in `declare_dependency`
+
+`declare_dependency` have a new `extra_files` keyword,
+to add extra files to a target. It is used mostly for IDE integration.
+
+## Added a new '--genvslite' option for use with 'meson setup ...'
+
+To facilitate a more usual visual studio work-flow of supporting and switching between
+multiple build configurations (buildtypes) within the same solution, among other
+[reasons](https://github.com/mesonbuild/meson/pull/11049), use of this new option
+has the effect of setting up multiple ninja back-end-configured build directories,
+named with their respective buildtype suffix. E.g. 'somebuilddir_debug',
+'somebuilddir_release', etc. as well as a '_vs'-suffixed directory that contains the
+generated multi-buildtype solution. Building/cleaning/rebuilding in the solution
+now launches the meson build (compile) of the corresponding buildtype-suffixed build
+directory, instead of using Visual Studio's native engine.
+
+## `gnome.generate_gir()` now supports `env` kwarg
+
+`gnome.generate_gir()` now accepts the `env` kwarg which lets you set environment variables.
+
+## More data in introspection files
+
+- Used compilers are listed in `intro-compilers.json`
+- Informations about `host`, `build` and `target` machines
+ are lister in `intro-machines.json`
+- `intro-dependencies.json` now includes internal dependencies,
+ and relations between dependencies.
+- `intro-targets.json` now includes dependencies, `vs_module_defs`,
+ `win_subsystem`, and linker parameters.
+
+## Machine objects get `kernel` and `subsystem` properties
+
+Meson has traditionally provided a `system` property to detect the
+system being run on. However this is not enough to reliably
+differentiate between e.g. an iOS platform from a watchOS one. Two new
+properties, namely `kernel` and `subsystem` have been added so these
+setups can be reliably detected.
+
+These new properties are not necessary in cross files for now, but if
+they are not defined and a build file tries to access them, Meson will
+exit with a hard error. It is expected that at some point in the
+future defining the new properties will become mandatory.
+
+## default_options and override_options may now be dictionaries
+
+Instead of passing them as `default_options : ['key=value']`, they can now be
+passed as `default_options : {'key': 'value'}`, and the same for
+`override_options`.
+
+## New override of `find_program('meson')`
+
+In some cases, it has been useful for build scripts to access the Meson command
+used to invoke the build script. This has led to various ad-hoc solutions that
+can be very brittle and project-specific.
+
+```meson
+meson_prog = find_program('meson')
+```
+
+This call will supply the build script with an external program pointing at the
+invoked Meson.
+
+Because Meson also uses `find_program` for program lookups internally, this
+override will also be handled in cases similar to the following:
+
+```meson
+custom_target(
+ # ...
+ command: [
+ 'meson',
+ ],
+ # ...
+)
+
+run_command(
+ 'meson',
+ # ...
+)
+
+run_target(
+ 'tgt',
+ command: [
+ 'meson',
+ # ...
+ ]
+)
+```
+
+## Find more specific python version on Windows
+
+You can now use `python3.x`, where `x` is the minor version,
+to find a more specific version of python on Windows, when
+using the python module. On other platforms, it was already
+working as `python3.x` is the executable name.
+
+## Python module can now compile bytecode
+
+A new builtin option is available: `-Dpython.bytecompile=2`. It can be used to
+compile bytecode for all pure python files installed via the python module.
+
+## rust.bindgen allows passing extra arguments to rustc
+
+This may be necessary to pass extra `cfg`s or to change warning levels.
+
+## Support for defining crate names of Rust dependencies in Rust targets
+
+Rust supports defining a different crate name for a dependency than what the
+actual crate name during compilation of that dependency was.
+
+This allows using multiple versions of the same crate at once, or simply using
+a shorter name of the crate for convenience.
+
+```meson
+a_dep = dependency('some-very-long-name')
+
+my_executable = executable('my-executable', 'src/main.rs',
+ rust_dependency_map : {
+ 'some_very_long_name' : 'a',
+ },
+ dependencies : [a_dep],
+)
+```
+
+## A machine file may be used to pass extra arguments to clang in a bindgen call
+
+Because of the way that bindgen proxies arguments to clang the only choice to
+add extra arguments currently is to wrap bindgen in a script, since the
+arguments must come after a `--`. This is inelegant, and not very portable. Now
+a `bindgen_clang_arguments` field may be placed in the machine file for the host
+machine, and these arguments will be added to every bindgen call for clang. This
+is intended to be useful for things like injecting `--target` arguments.
+
+## Add a `link_with` keyword to `rust.test()`
+
+This can already be be worked around by creating `declare_dependency()` objects
+to pass to the `dependencies` keyword, but this cuts out the middle man.
+
+## Rust now supports the b_ndebug option
+
+Which controls the `debug_assertions` cfg, which in turn controls
+`debug_assert!()` macro. This macro is roughly equivalent to C's `assert()`, as
+it can be toggled with command line options, unlike Rust's `assert!()`, which
+cannot be turned off, and is not designed to be.
+
+## Wildcards in list of tests to run
+
+The `meson test` command now accepts wildcards in the list of test names.
+For example `meson test basic*` will run all tests whose name begins
+with "basic".
+
+meson will report an error if the given test name does not match any
+existing test. meson will log a warning if two redundant test names
+are given (for example if you give both "proj:basic" and "proj:").
+
+## New for the generation of Visual Studio vcxproj projects
+
+When vcxproj is generated, another file vcxproj.filters is generated in parallel.
+It enables to set a hierarchy of the files inside the solution following their place on filesystem.
+
--- /dev/null
+---
+title: Release 1.3.0
+short-description: Release notes for 1.3.0
+...
+
+# New features
+
+Meson 1.3.0 was released on 19 November 2023
+## Clarify of implicitly-included headers in C-like compiler checks
+
+Compiler check methods `compiler.compute_int()`, `compiler.alignment()`
+and `compiler.sizeof()` now have their implicitly-included headers
+corrected and documented.
+
+`<stdio.h>` was included unintentionally when cross-compiling, which
+is less than ideal because there is no guarantee that a standard library
+is available for the target platform. Only `<stddef.h>` is included instead.
+
+For projects that depend on the old behavior, the compiler check methods
+have an optional argument `prefix`, which can be used to specify additional
+`#include` directives.
+
+## Treat warnings as error in compiler checks
+
+Compiler check methods `compiler.compiles()`, `compiler.links()` and `compiler.run()`
+now have a new `werror: true` keyword argument to treat compiler warnings as error.
+This can be used to check if code compiles without warnings.
+
+## Compilers now have a `has_define` method
+
+This method returns true if the given preprocessor symbol is
+defined, else false is returned. This is useful is cases where
+an empty define has to be distinguished from a non-set one, which
+is not possible using `get_define`.
+
+Additionally it makes intent clearer for code that only needs
+to check if a specific define is set at all and does not care
+about its value.
+
+## [[configure_file]] now has a `macro_name` parameter.
+
+This new paramater, `macro_name` allows C macro-style include guards to be added
+to [[configure_file]]'s output when a template file is not given. This change
+simplifies the creation of configure files that define macros with dynamic names
+and want the C-style include guards.
+
+## `c_std` and `cpp_std` options now accepts a list of values
+
+Projects that prefer GNU C, but can fallback to ISO C, can now set, for
+example, `default_options: 'c_std=gnu11,c11'`, and it will use `gnu11` when
+available, but fallback to `c11` otherwise. It is an error only if none of the
+values are supported by the current compiler.
+
+Likewise, a project that can take benefit of `c++17` but can still build with
+`c++11` can set `default_options: 'cpp_std=c++17,c++11'`.
+
+This allows us to deprecate `gnuXX` values from the MSVC compiler. That means
+that `default_options: 'c_std=gnu11'` will now print a warning with MSVC
+but fallback to `c11`. No warning is printed if at least one
+of the values is valid, i.e. `default_options: 'c_std=gnu11,c11'`.
+
+In the future that deprecation warning will become an hard error because
+`c_std=gnu11` should mean GNU is required, for projects that cannot be
+built with MSVC for example.
+
+## More meaningful description of many generative tasks
+
+When a module uses a `CustomTarget` to process files, it now has the possibility
+to customize the message displayed by ninja.
+
+Many modules were updated to take advantage of this new feature.
+
+## Deprecate 'jar' as a build_target type
+
+The point of `build_target()` is that what is produced can be conditionally
+changed. However, `jar()` has a significant number of non-overlapping arguments
+from other build_targets, including the kinds of sources it can include. Because
+of this crafting a `build_target` that can be used as a Jar and as something
+else is incredibly hard to do. As such, it has been deprecated, and using
+`jar()` directly is recommended.
+
+## generator.process() gains 'env' keyword argument
+
+Like the kwarg of the same name in `custom_target()`, `env` allows
+you to set the environment in which the generator will process inputs.
+
+## Target names for executables now take into account suffixes.
+
+In previous versions of meson, a `meson.build` file like this:
+
+```
+exectuable('foo', 'main.c')
+exectuable('foo', 'main.c', name_suffix: 'bar')
+```
+
+would result in a configure error because meson internally used
+the same id for both executables. This build file is now allowed
+since meson takes into account the `bar` suffix when generating the
+second executable. This allows for executables with the same basename
+but different suffixes to be built in the same subdirectory.
+
+## Executable gains vs_module_defs keyword
+
+This allows using a .def file to control which functions an [[executable]] will
+expose to a [[shared_module]].
+
+## find_program() now supports the 'default_options' argument
+
+In a similar fashion as dependency(), find_program() now also allows you to set default
+options for the subproject that gets built in case of a fallback.
+
+## `fs.relative_to()`
+
+The `fs` module now has a `relative_to` method. The method will return the
+relative path from argument one to argument two, if one exists. Otherwise, the
+absolute path to argument one is returned.
+
+```meson
+assert(fs.relative_to('c:\\prefix\\lib', 'c:\\prefix\\bin') == '..\\lib')
+assert(fs.relative_to('c:\\proj1\\foo', 'd:\\proj1\\bar') == 'c:\\proj1\\foo')
+assert(fs.relative_to('prefix\\lib\\foo', 'prefix') == 'lib\\foo')
+
+assert(fs.relative_to('/prefix/lib', '/prefix/bin') == '../lib')
+assert(fs.relative_to('prefix/lib/foo', 'prefix') == 'lib/foo')
+```
+
+In addition to strings, it can handle files, custom targets, custom target
+indices, and build targets.
+
+## Added follow_symlinks arg to install_data, install_header, and install_subdir
+
+The [[install_data]], [[install_headers]], [[install_subdir]] functions now
+have an optional argument `follow_symlinks` that, if set to `true`, makes it so
+symbolic links in the source are followed, rather than copied into the
+destination tree, to match the old behavior. The default, which is currently
+to follow links, is subject to change in the future.
+
+## Added 'fill' kwarg to int.to_string()
+
+int.to_string() now accepts a `fill` argument. This allows you to pad the
+string representation of the integer with leading zeroes:
+
+```meson
+n = 4
+message(n.to_string())
+message(n.to_string(fill: 3))
+
+n = -4
+message(n.to_string(fill: 3))
+```
+
+OUTPUT:
+```meson
+4
+004
+-04
+```
+
+## Added 'json' output_format to configure_file()
+
+When no input file is specified, [[configure_file]] can now
+generate a `json` file from given [[@cfg_data]].
+Field descriptions are not preserved in the json file.
+
+## `@GLOBAL_SOURCE_ROOT@` and `@DIRNAME@` in machine files
+
+Some tokens are now replaced in the machine file before parsing it:
+- `@GLOBAL_SOURCE_ROOT@`: the absolute path to the project's source tree
+- `@DIRNAME@`: the absolute path to the machine file's parent directory.
+
+It can be used, for example, to have paths relative to the source directory, or
+relative to toolchain's installation directory.
+```ini
+[binaries]
+c = '@DIRNAME@/toolchain/gcc'
+exe_wrapper = '@GLOBAL_SOURCE_ROOT@' / 'build-aux' / 'my-exe-wrapper.sh'
+```
+
+## clang-tidy-fix target
+
+If `clang-tidy` is installed and the project's source root contains a
+`.clang-tidy` (or `_clang-tidy`) file, Meson will automatically define
+a `clang-tidy-fix` target that runs `run-clang-tidy` tool with `-fix`
+option to apply the changes found by clang-tidy to the source code.
+
+If you have defined your own `clang-tidy-fix` target, Meson will not
+generate its own target.
+
+## Meson compile command now accepts suffixes for TARGET
+
+The syntax for specifying a target for meson compile is now
+`[PATH_TO_TARGET/]TARGET_NAME.TARGET_SUFFIX[:TARGET_TYPE]` where
+`TARGET_SUFFIX` is the suffix argument given in the build target
+within meson.build. It is optional and `TARGET_NAME` remains
+sufficient if it uniquely resolves to one single target.
+
+## New environment variable `MESON_PACKAGE_CACHE_DIR`
+
+If the `MESON_PACKAGE_CACHE_DIR` environment variable is set, it is used instead of the
+project's `subprojects/packagecache`. This allows sharing the cache across multiple
+projects. In addition it can contain an already extracted source tree as long as it
+has the same directory name as the `directory` field in the wrap file. In that
+case, the directory will be copied into `subprojects/` before applying patches.
+
+## Update options with `meson setup <builddir> -Dopt=value`
+
+If the build directory already exists, options are updated with their new value
+given on the command line (`-Dopt=value`). Unless `--reconfigure` is also specified,
+this won't reconfigure immediately. This has the same behaviour as
+`meson configure <builddir> -Dopt=value`.
+
+Previous Meson versions were simply a no-op.
+
+## Clear persistent cache with `meson setup --clearcache`
+
+Just like `meson configure --clearcache`, it is now possible to clear the cache
+and reconfigure in a single command with `meson setup --clearcache --reconfigure <builddir>`.
+
+## pkg-config dependencies can now get a variable with multiple replacements
+
+When using [[dep.get_variable]] and defining a `pkgconfig_define`, it is
+sometimes useful to remap multiple dependency variables. For example, if the
+upstream project changed the variable name that is interpolated and it is
+desirable to support both versions.
+
+It is now possible to pass multiple pairs of variable/value.
+
+The same applies to the compatibility [[dep.get_pkgconfig_variable]] method.
+
+## Machine files: `pkgconfig` field deprecated and replaced by `pkg-config`
+
+Meson used to allow both `pkgconfig` and `pkg-config` entries in machine files,
+the former was used for `dependency()` lookup and the latter was used as return
+value for `find_program('pkg-config')`.
+
+This inconsistency is now fixed by deprecating `pkgconfig` in favor of
+`pkg-config` which matches the name of the binary. For backward compatibility
+it is still allowed to define both with the same value, in that case no
+deprecation warning is printed.
+
+## Support targeting Python's limited C API
+
+The Python module's `extension_module` function has gained the ability
+to build extensions which target Python's limited C API via a new keyword
+argument: `limited_api`.
+
+## All compiler `has_*` methods support the `required` keyword
+
+Now instead of
+
+```meson
+assert(cc.has_function('some_function'))
+assert(cc.has_type('some_type'))
+assert(cc.has_member('struct some_type', 'x'))
+assert(cc.has_members('struct some_type', ['x', 'y']))
+```
+
+we can use
+
+```meson
+cc.has_function('some_function', required: true)
+cc.has_type('some_type', required: true)
+cc.has_member('struct some_type', 'x', required: true)
+cc.has_members('struct some_type', ['x', 'y'], required: true)
+```
+
+## Deprecated `rust_crate_type` and replaced by `rust_abi`
+
+The new `rust_abi` keyword argument is accepted by [[shared_library]],
+[[static_library]], [[library]] and [[shared_module]] functions. It can be either
+`'rust'` (the default) or `'c'` strings.
+
+`rust_crate_type` is now deprecated because Meson already knows if it's a shared
+or static library, user only need to specify the ABI (Rust or C).
+
+`proc_macro` crates are now handled by the [`rust.proc_macro()`](Rust-module.md#proc_macro)
+method.
+
+## Tests now abort on errors by default under sanitizers
+
+Sanitizers like AddressSanitizer and UndefinedBehaviorSanitizer do not abort
+by default on detected violations. Meson now exports `ASAN_OPTIONS` and `UBSAN_OPTIONS`
+when unset in the environment to provide sensible abort-by-default behavior.
+
+## `<lang>_(shared|static)_args` for both_library, library, and build_target
+
+We now allow passing arguments like `c_static_args` and `c_shared_args`. This
+allows a [[both_libraries]] to have arguments specific to either the shared or
+static library, as well as common arguments to both.
+
+There is a drawback to this, since Meson now cannot re-use object files between
+the static and shared targets. This could lead to much higher compilation time
+when using a [[both_libraries]] if there are many sources.
+
+## `-j` shorthand for `--num-processes`
+
+`-j` now means the same thing as `--num-processes`. It was inconsistently
+supported only in some subcommands. Now you may use it everywhere
+
+## Unified message(), str.format() and f-string formatting
+
+They now all support the same set of values: strings, integers, bools, options,
+dictionaries and lists thereof.
+
+- Feature options (i.e. enabled, disabled, auto) were not previously supported
+ by any of those functions.
+- Lists and dictionaries were not previously supported by f-string.
+- str.format() allowed any type and often resulted in printing the internal
+ representation which is now deprecated.
+
+## Subprojects excluded from scan-build reports
+
+The `scan-build` target, created when using the `ninja` backend with `scan-build`
+present, now excludes bugs found in subprojects from its final report.
+
+## vs_module_defs keyword now supports indexes of custom_target
+
+This means you can do something like:
+```meson
+defs = custom_target('generate_module_defs', ...)
+shared_library('lib1', vs_module_defs : defs[0])
+shared_library('lib2', vs_module_defs : defs[2])
+```
+
+## Automatic fallback to `cmake` and `cargo` subproject
+
+CMake subprojects have been supported for a while using the `cmake.subproject()`
+module method. However until now it was not possible to use a CMake subproject
+as fallback in a `dependency()` call.
+
+A wrap file can now specify the method used to build it by setting the `method`
+key in the wrap file's first section. The method defaults to `meson`.
+
+Supported methods:
+- `meson` requires `meson.build` file.
+- `cmake` requires `CMakeLists.txt` file. [See details](Wrap-dependency-system-manual.md#cmake-wraps).
+- `cargo` requires `Cargo.toml` file. [See details](Wrap-dependency-system-manual.md#cargo-wraps).
+
will be made, and all bugs effecting the RC will be assigned to this
milestone. Patches fixing bugs in the milestone will be picked to the
stable branch, and normal development will continue on the master
-branch. Every week after after this a new release candidate will be
+branch. Every week after this a new release candidate will be
made until all bugs are resolved in that milestone. When all of the
bugs are fixed the 0.X.0 release will be made.
The rewriter itself is considered stable, however the user interface
and the "script mode" API might change in the future. These changes
-may also break backwards comaptibility to older releases.
+may also break backwards compatibility to older releases.
We are also open to suggestions for API improvements.
## Functions
-### test(name: string, target: library | executable, dependencies: []Dependency)
+### test()
+
+```meson
+rustmod.test(name, target, ...)
+```
This function creates a new rust unittest target from an existing rust
based target, which may be a library or executable. It does this by
adding the `--test` argument to the compilation, then creates a new
test target which calls that executable, using the rust test protocol.
-This accepts all of the keyword arguments as the
-[[test]] function except `protocol`, it will set
-that automatically.
+This function takes two positional arguments, the first is the name of the
+test and the second is the library or executable that is the rust based target.
+It also takes the following keyword arguments:
-Additional, test only dependencies may be passed via the dependencies
-argument.
+- `dependencies`: a list of test-only Dependencies
+- `link_with`: a list of additional build Targets to link with (*since 1.2.0*)
+- `rust_args`: a list of extra arguments passed to the Rust compiler (*since 1.2.0*)
-### bindgen(*, input: string | BuildTarget | [](string | BuildTarget), output: string, include_directories: [](include_directories | string), c_args: []string, args: []string, dependencies: []Dependency)
+This function also accepts all of the keyword arguments accepted by the
+[[test]] function except `protocol`, it will set that automatically.
+
+### bindgen()
This function wraps bindgen to simplify creating rust bindings around C
-libraries. This has two advantages over hand-rolling ones own with a
+libraries. This has two advantages over invoking bindgen with a
`generator` or `custom_target`:
- It handles `include_directories`, so one doesn't have to manually convert them to `-I...`
- It automatically sets up a depfile, making the results more reliable
+- It automatically handles assertions, synchronizing Rust and C/C++ to have the same behavior
It takes the following keyword arguments
-- input — A list of Files, Strings, or CustomTargets. The first element is
+- `input`: a list of Files, Strings, or CustomTargets. The first element is
the header bindgen will parse, additional elements are dependencies.
-- output — the name of the output rust file
-- include_directories — A list of `include_directories` or `string` objects,
+- `output`: the name of the output rust file
+- `include_directories`: A list of `include_directories` or `string` objects,
these are passed to clang as `-I` arguments *(string since 1.0.0)*
-- c_args — A list of string arguments to pass to clang untouched
-- args — A list of string arguments to pass to `bindgen` untouched.
-- dependencies — A list of `Dependency` objects to pass to the underlying clang call (*since 1.0.0*)
+- `c_args`: a list of string arguments to pass to clang untouched
+- `args`: a list of string arguments to pass to `bindgen` untouched.
+- `dependencies`: a list of `Dependency` objects to pass to the underlying clang call (*since 1.0.0*)
```meson
rust = import('unstable-rust')
)
```
-If the header depeneds on generated headers, those headers must be passed to
+If the header depends on generated headers, those headers must be passed to
`bindgen` as well to ensure proper dependency ordering, static headers do not
need to be passed, as a proper depfile is generated:
output : 'out.rs',
)
```
+
+*Since 1.1.0* Meson will synchronize assertions for Rust and C/C++ when the
+`b_ndebug` option is set (via `-DNDEBUG` for C/C++, and `-C
+debug-assertions=on` for Rust), and will pass `-DNDEBUG` as an extra argument
+to clang. This allows for reliable wrapping of `-DNDEBUG` controlled behavior
+with `#[cfg(debug_asserions)]` and or `cfg!()`. Before 1.1.0, assertions for Rust
+were never turned on by Meson.
+
+*Since 1.2.0* Additional arguments to pass to clang may be specified in a
+*machine file in the properties section:
+
+```ini
+[properties]
+bindgen_clang_arguments = ['--target', 'x86_64-linux-gnu']
+```
+
+### proc_macro()
+
+```meson
+rustmod.proc_macro(name, sources, ...)
+```
+
+*Since 1.3.0*
+
+This function creates a Rust `proc-macro` crate, similar to:
+```meson
+[[shared_library]](name, sources,
+ rust_crate_type: 'proc-macro',
+ native: true)
+```
+
+`proc-macro` targets can be passed to `link_with` keyword argument of other Rust
+targets.
+
+Only a subset of [[shared_library]] keyword arguments are allowed:
+- rust_args
+- rust_dependency_map
+- sources
+- dependencies
+- extra_files
+- link_args
+- link_depends
+- link_with
+- override_options
write files into the source directory). [See the upstream
docs](https://rust-analyzer.github.io/manual.html#non-cargo-based-projects) for
more information on how to configure that.
+
+## Linking with standard libraries
+
+Meson will link the Rust standard libraries (e.g. libstd) statically, unless the
+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.
`include`. The Meson build definition would look like the following.
```meson
-project('binary dep', 'c')
+project('bob', 'c')
+
+# Do some sanity checking so that meson can fail early instead of at final link time
+if not (host_machine.system() == 'windows' and host_machine.cpu_family() == 'x86_64')
+ error('This wrap of libbob is a binary wrap for x64_64 Windows, and will not work on your system')
+endif
cc = meson.get_compiler('c')
-bin_dep = declare_dependency(
+bob_dep = declare_dependency(
dependencies : cc.find_library('bob', dirs : meson.current_source_dir()),
include_directories : include_directories('include'))
+
+meson.override_dependency('bob', bob_dep)
```
Now you can use this subproject as if it was a Meson project:
```meson
project('using dep', 'c')
-bob_dep = subproject('bob').get_variable('bin_dep')
+bob_dep = dependency('bob')
executable('prog', 'prog.c', dependencies : bob_dep)
```
responsible for verifying that your libraries are compatible, Meson
will not check things for you.
+## Using a wrap file
+
+To make this all work automatically, a project will need a
+[wrap file](Wrap-dependency-system-manual.md#wrap-format), as well as the
+meson.build definition from above. For this example our dependency is called
+`bob`.
+
+The wrap ini (subprojects/bob.wrap):
+```ini
+[wrap-file]
+directory = libbob-1.0
+source_url = https://libbob.example.com/libbob-1.0.zip
+source_filename = libbob-1.0.zip
+source_hash = 5ebeea0dfb75d090ea0e7ff84799b2a7a1550db3fe61eb5f6f61c2e971e57663
+patch_directory = libbob
+
+[provide]
+dependency_names = bob
+```
+
+Then create `subprojects/packagefiles/libbob/`, and place the `meson.build` from
+above in that directory. With these in place a call to `dependency('bob')` will
+first try standard discovery methods for your system (such as pkg-config, cmake,
+and any built-in meson find methods), and then fall back to using the binary
+wrap if it cannot find the dependency on the system. Meson provides the
+`--force-fallback-for=bob` command line option to force the use of the fallback.
+
## Note for Linux libraries
A precompiled linux shared library (.so) requires a soname field to be properly installed. If the soname field is missing, binaries referencing the library will require a hard link to the location of the library at install time (`/path/to/your/project/subprojects/precompiledlibrary/lib.so` instead of `$INSTALL_PREFIX/lib/lib.so`) after installation.
files because if any of them could potentially cause a rebuild.
Since CMake has two different backends, Make and Ninja, we ran the
-tests on both of them. All tests were run on a 2011 era Macbook Pro
+tests on both of them. All tests were run on a 2011 era MacBook Pro
running Ubuntu 13/04. The tests were run multiple times and we always
took the fastest time.
dependencies += [zlib]
endif
# many more "if"s here...
-executable('exe', sources: sources, dependencies: dependencies())
+executable('exe', sources: sources, dependencies: dependencies)
```
Sourcesets can be used with a single invocation of the `apply` method,
* **--wrap-mode=nodownload**
Meson will not use the network to download any subprojects or
- fetch any wrap information. Only pre-existing sources will be used.
+ fetch any wrap information. Only preexisting sources will be used.
This is useful (mostly for distros) when you want to only use the
sources provided by a software release, and want to manually handle
or provide missing dependencies.
single quote do it like this:
```meson
-single quote = 'contains a \' character'
+single_quote = 'contains a \' character'
```
The full list of escape sequences is:
#### .strip()
```meson
-# Similar to the Python str.strip(). Removes leading/ending spaces and newlines
+# Similar to the Python str.strip(). Removes leading/ending spaces and newlines.
define = ' -Dsomedefine '
stripped_define = define.strip()
# 'stripped_define' now has the value '-Dsomedefine'
# You may also pass a string to strip, which specifies the set of characters to
-# be removed.
+# be removed instead of the default whitespace.
string = 'xyxHelloxyx'.strip('xy')
# 'string' now has the value 'Hello'
```
my_dict = {'foo': 42, 'foo': 43}
```
+Accessing elements of a dictionary works similarly to array indexing:
+
+```meson
+my_dict = {'foo': 42, 'bar': 'baz'}
+forty_two = my_dict['foo']
+# This will fail
+my_dict['does_not_exist']
+```
+
Dictionaries are immutable and do not have a guaranteed order.
Dictionaries are available since 0.47.0.
additive_operator: "+" | "-"
argument_list: positional_arguments ["," keyword_arguments] | keyword_arguments
array_literal: "[" [expression_list] "]"
-assignment_statement: expression asssignment_operator expression
-assignment_operator: "=" | "*=" | "/=" | "%=" | "+=" | "-="
+assignment_statement: expression assignment_operator expression
+assignment_operator: "=" | "+="
binary_literal: "0b" BINARY_NUMBER
BINARY_NUMBER: /[01]+/
boolean_literal: "true" | "false"
STRING_SIMPLE_VALUE: \.*?(?<!\\)(\\\\)*?'\
subscript_expression: postfix_expression "[" expression "]"
unary_expression: postfix_expression | (unary_operator unary_expression)
-unary_operator: "not" | "+" | "-"
+unary_operator: "not" | "-"
```
sudo apt install libgtk-3-dev
```
+In addition, it is recommended to have the glib library with version 2.74 or higher.
+
It is possible to build the GUI application on other platforms, such
as Windows and macOS, but you need to install the needed dependencies.
GtkApplication *app;
int status;
- app = gtk_application_new(NULL, G_APPLICATION_FLAGS_NONE);
+ app = gtk_application_new(NULL, G_APPLICATION_DEFAULT_FLAGS);
g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
status = g_application_run(G_APPLICATION(app), argc, argv);
g_object_unref(app);
configurations using glibc, including with non-GCC compilers. This feature
can be disabled as discussed in [[test]].
+### ASAN_OPTIONS and UBSAN_OPTIONS
+
+By default, the environment variables `ASAN_OPTIONS` and `UBSAN_OPTIONS` are
+set to enable aborting on detected violations and to give a backtrace. This
+feature can be disabled as discussed in [[test]].
+
## Coverage
If you enable coverage measurements by giving Meson the command line
are both provided by [Gcovr](http://gcovr.com) (version 3.3 or higher)
`coverage-sonarqube` which is provided by [Gcovr](http://gcovr.com) (version 4.2 or higher)
and `coverage-html`, which requires
-[Lcov](https://ltp.sourceforge.io/coverage/lcov.php) and
+[lcov](https://github.com/linux-test-project/lcov) and
[GenHTML](https://linux.die.net/man/1/genhtml) or
[Gcovr](http://gcovr.com). As a convenience, a high-level `coverage`
target is also generated which will produce all 3 coverage report
## Parallelism
To reduce test times, Meson will by default run multiple unit tests in
-parallel. It is common to have some tests which can not be run in
+parallel. It is common to have some tests which cannot be run in
parallel because they require unique hold on some resource such as a
file or a D-Bus name. You have to specify these tests with a keyword
argument.
## Skipped tests and hard errors
-Sometimes a test can only determine at runtime that it can not be run.
+Sometimes a test can only determine at runtime that it cannot be run.
For the default `exitcode` testing protocol, the GNU standard approach
in this case is to exit the program with error code 77. Meson will
$ meson test A D
```
+You can run tests from specific (sub)project:
+
+```console
+$ meson test (sub)project_name:
+```
+
+or a specific test in a specific project:
+
+```console
+$ meson test (sub)project_name:test_name
+```
+
+Since version *1.2.0*, you can use wildcards in project
+and test names. For instance, to run all tests beginning with
+"foo" and all tests from projects beginning with "bar":
+
+```console
+$ meson test "foo*" "bar*:"
+```
+
+
Tests belonging to a suite `suite` can be run as follows
```console
or variable of the same name, there will be a clash.
Meson has built-in support for unity builds. To enable them, just pass
-`--unity on` on the command line or enable unity builds with the GUI.
+`--unity on` on the command line or enable unity builds in you project
+default_options.
No code changes are necessary apart from the potential clash issue
discussed above. Meson will automatically generate all the necessary
inclusion files for you.
+
+You may modify unity settings for a given build target using
+the `override_options` parameter. This may be useful when a build
+target is not compatible with unity build.
+
+The `unity` option can also have the value `subprojects`, to build
+subprojects with unity, while building the main project incrementally.
+
+The `unity_size` option allows to specify the number of source files
+included per unity file. The default is 4. Having more source files
+per unity file will speed up full builds, but slow down incremental
+builds. To get only one unity file per build target, you can use
+a very big number for `unity_size`.
- [Arduino sample project](https://github.com/jpakkane/mesonarduino)
- [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
- [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
- [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.
+ - [GIMP](https://gitlab.gnome.org/GNOME/gimp), an image manipulation program (experimental replacing autoconf)
- [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
- [Marker](https://github.com/fabiocolacio/Marker), a GTK-3 markdown editor
- [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
+ - [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
- [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)
- - [rmw](https://remove-to-waste.info), safe-remove utility for the command line
+ - [refivar](https://github.com/nvinson/refivar), A reimplementation of efivar in Rust.
- [Rizin](https://rizin.re), Free and Open Source Reverse Engineering Framework
- - [QEMU](https://qemu.org), a processor emulator and virtualizer
+ - [rmw](https://remove-to-waste.info), 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
- [Terminology](https://github.com/billiob/terminology), a terminal emulator based on the Enlightenment Foundation Libraries
- [Tilix](https://github.com/gnunn1/tilix), a tiling terminal emulator for Linux using GTK+ 3
- [Tizonia](https://github.com/tizonia/tizonia-openmax-il), a command-line cloud music player for Linux with support for Spotify, Google Play Music, YouTube, SoundCloud, TuneIn, Plex servers and Chromecast devices
+ - [Trilobite XUnit Test](https://github.com/trilobite-stdlib/trilo-xtest-c), a framework for testing C code
+ - [Trilobite XType](https://github.com/trilobite-stdlib/trilo-xtype-c), a data type library for C code
+ - [Trilobite XCore](https://github.com/trilobite-stdlib/trilo-xcore-c), a core essential library for C code
+ - [Trilobite XData](https://github.com/trilobite-stdlib/trilo-xdata-c), a data structures and algorithms for C code
- [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://github.com/wayland-project/wayland) and [Weston](https://github.com/wayland-project/weston), a next generation display server
- - [wlroots](https://github.com/swaywm/wlroots), a modular Wayland compositor library
+ - [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
```
Using `[GtkTemplate]` also requires the GTK+ user interface definition
-files to be built in to the binary as GResources. For completeness,
+files to be built into the binary as GResources. For completeness,
the next example shows this:
```meson
community maintained repository of VAPIs that are not distributed.
Developers use the repository to share early work on new bindings and
improvements to existing bindings. So the VAPIs can frequently change.
-It is recommended VAPIs from this repository are copied in to your
+It is recommended VAPIs from this repository are copied into your
project's source files.
This also works well for starting to write new bindings before they
# Videos
+ - [Lessons Learned From Porting LibreOffice's Build System To
+ Meson](https://www.youtube.com/watch?v=asB_hx_jJSw), CppNorth 2022
+
+ - [The Meson build system
+ overview](https://www.youtube.com/watch?v=rewhp5o0xZg), PyCon US
+ 2022
+
- [Behind (and under) the scenes of the Meson build
system](https://www.youtube.com/watch?v=iLN6wL7ExHU), Linux.conf.au
2020
|build | `meson compile -C $(Configuration)` |
|clean | `meson compile -C $(Configuration) --clean` |
|rebuild| `meson compile -C $(Configuration) --clean && meson compile -C $(Configuration)` |
-|Output | `$(Configuration)\name_of_your_executable.exe|
+|Output | `$(Configuration)\name_of_your_executable.exe` |
Then click `Finish`.
Returns a list of `CustomTarget` objects that you put in the list of sources for
the target you want to have the resources in.
-*Since 0.61.0* CustomTargetIndexs and CustomTargets with more than out output
+*Since 0.61.0* CustomTargetIndexes and CustomTargets with more than out output
*may be used as positional arguments.
This method has the following keyword arguments:
`subprojects/packagefiles`.
- `diff_files` - *Since 0.63.0* Comma-separated list of local diff files (see
[Diff files](#diff-files) below).
+- `method` - *Since 1.3.0* The build system used by this subproject. Defaults to `meson`.
+ Supported methods:
+ - `meson` requires `meson.build` file.
+ - `cmake` requires `CMakeLists.txt` file. [See details](#cmake-wraps).
+ - `cargo` requires `Cargo.toml` file. [See details](#cargo-wraps).
### Specific to wrap-file
- `source_url` - download url to retrieve the wrap-file source archive
of downloading the file, even if `--wrap-mode` option is set to
`nodownload`. The file's hash will be checked.
+Since *1.3.0* if the `MESON_PACKAGE_CACHE_DIR` environment variable is set, it is used instead of
+the project's `subprojects/packagecache`. This allows sharing the cache across multiple
+projects. In addition it can contain an already extracted source tree as long as it
+has the same directory name as the `directory` field in the wrap file. In that
+case, the directory will be copied into `subprojects/` before applying patches.
+
### Specific to VCS-based wraps
- `url` - name of the wrap-git repository to clone. Required.
- `revision` - name of the revision to checkout. Must be either: a
fallback to use the subproject, assuming it uses
`meson.override_find_program('myprog')`.
+### CMake wraps
+
+Since the CMake module does not know the public name of the provided
+dependencies, a CMake `.wrap` file cannot use the `dependency_names = foo`
+syntax. Instead, the `dep_name = <target_name>_dep` syntax should be used, where
+`<target_name>` is the name of a CMake library with all non alphanumeric
+characters replaced by underscores `_`.
+
+For example, a CMake project that contains `add_library(foo-bar ...)` in its
+`CMakeList.txt` and that applications would usually find using the dependency
+name `foo-bar-1.0` (e.g. via pkg-config) would have a wrap file like this:
+
+```ini
+[wrap-file]
+...
+method = cmake
+[provide]
+foo-bar-1.0 = foo_bar_dep
+```
+### Cargo wraps
+
+Cargo subprojects automatically override the `<package_name>-rs` dependency name.
+`package_name` is defined in `[package] name = ...` section of the `Cargo.toml`
+and `-rs` suffix is added. That means the `.wrap` file should have
+`dependency_names = foo-rs` in their `[provide]` section when `Cargo.toml` has
+package name `foo`.
+
+Cargo subprojects require a toml parser. Python >= 3.11 have one built-in, older
+Python versions require either the external `tomli` module or `toml2json` program.
+
+For example, a Cargo project with the package name `foo-bar` would have a wrap
+file like that:
+```ini
+[wrap-file]
+...
+method = cargo
+[provide]
+dependency_names = foo-bar-rs
+```
+
## Using wrapped projects
Wraps provide a convenient way of obtaining a project into your
`[[<func name>]]`.
Methods (for all kinds of objects, including modules) can be linked to like
this: `[[<object name>.<method name>]]`.
-To link to objects themself, the `[[@<object name>]]` syntax can be used.
+To link to objects themselves, the `[[@<object name>]]` syntax can be used.
These tags do **not** need to be put in inline code! A hotdoc extension handles
the formatting here. If tags need to be placed (for instance, to include reference
`module.yaml`. All objects returned by the module are then located next to this
file.
-The name of the YAML files themself are ignored (with the exception of
+The name of the YAML files themselves are ignored (with the exception of
`module.yaml`) and carry no specific meaning. However, it is recommended to name
the YAML files after the `name` entry of the object.
# YAML schema
-The YAML files themself are structured as follows:
+The YAML files themselves are structured as follows:
## Functions
- `qresources` (string | File)[]: Passed to the RCC compiler
- `ui_files`: (string | File | CustomTarget)[]: Passed the `uic` compiler
- `moc_sources`: (string | File | CustomTarget)[]: Passed the `moc` compiler. These are converted into .moc files meant to be `#include`ed
- - `moc_headers`: (string | File | CustomTarget)[]: Passied the `moc` compiler. These will be converted into .cpp files
+ - `moc_headers`: (string | File | CustomTarget)[]: Passed the `moc` compiler. These will be converted into .cpp files
- `include_directories` (IncludeDirectories | string)[], the directories to add to header search path for `moc`
- `moc_extra_arguments` string[]: any additional arguments to `moc`. Since v0.44.0.
- `uic_extra_arguments` string[]: any additional arguments to `uic`. Since v0.49.0.
qt5_dep = dependency('qt5', modules: ['Core', 'Gui'])
inc = include_directories('includes')
moc_files = qt5.compile_moc(headers : 'myclass.h',
- extra_arguments: ['-DMAKES_MY_MOC_HEADER_COMPILE'],
+ extra_args: ['-DMAKES_MY_MOC_HEADER_COMPILE'],
include_directories: inc,
dependencies: qt5_dep)
translations = qt5.compile_translations(ts_files : 'myTranslation_fr.ts', build_by_default : true)
## Use address sanitizer
-Clang comes with a selection of analysis tools such as the [address
+Clang and gcc come with a selection of analysis tools such as the [address
sanitizer](https://clang.llvm.org/docs/AddressSanitizer.html). Meson
has native support for these with the `b_sanitize` option.
* `preset`: (*Added 0.37.0*) name of a preset list of arguments,
current option is `'glib'`, see
[source](https://github.com/mesonbuild/meson/blob/master/mesonbuild/modules/i18n.py)
- for for their value
+ for their value
* `install`: (*Added 0.43.0*) if false, do not install the built translations.
* `install_dir`: (*Added 0.50.0*) override default install location, default is `localedir`
project('Meson documentation', version: '1.0')
-cur_bdir = meson.current_build_dir()
+yaml_modname = get_option('unsafe_yaml') ? 'yaml' : 'strictyaml'
+py = import('python').find_installation('python3', modules: [yaml_modname], required: false)
+if not py.found()
+ error(f'Cannot build documentation without yaml support')
+endif
-# Only the script knows which files are being generated
-docs_gen = custom_target(
- 'gen_docs',
- input: files('markdown/index.md'),
- output: 'gen_docs.stamp',
- command: [
- files('../tools/regenerate_docs.py'),
- '--output-dir', cur_bdir,
- '--dummy-output-file', '@OUTPUT@',
- ],
- build_by_default: true,
- install: false)
+cur_bdir = meson.current_build_dir()
sitemap = files('sitemap.txt')
+yaml_loader = get_option('unsafe_yaml') ? 'fastyaml' : 'yaml'
genrefman = find_program('./genrefman.py')
refman_binary = custom_target(
'gen_refman_bin',
depfile: 'reman_dep.d',
command: [
genrefman,
- '-l', 'yaml',
+ '-l', yaml_loader,
'-g', 'pickle',
'-o', '@OUTPUT@',
'--depfile', '@DEPFILE@',
]
)
-refman_md = custom_target(
- 'gen_refman_md',
- input: refman_binary,
- output: ['configured_sitemap.txt', 'refman_links.json'],
- command: [
- genrefman,
- '-l', 'pickle',
- '-g', 'md',
- '-s', sitemap,
- '-i', '@INPUT@',
- '-o', '@OUTPUT0@',
- '--link-defs', '@OUTPUT1@',
- '--force-color',
- '--no-modules',
- ],
-)
-sitemap = refman_md[0]
-
refman_json = custom_target(
'gen_refman_json',
build_by_default: true,
'--force-color',
],
)
+test('validate_docs', find_program('./jsonvalidator.py'), args: [refman_json])
refman_man = custom_target(
'gen_refman_man',
'--force-color',
'--no-modules',
],
+ install: true,
+ install_dir: get_option('mandir') / 'man3',
)
+# Everything past here is HTML resources.
+if not get_option('html')
+ subdir_done()
+endif
+
+# Only the script knows which files are being generated
+docs_gen = custom_target(
+ 'gen_docs',
+ input: files('markdown/index.md'),
+ output: 'gen_docs.stamp',
+ command: [
+ files('../tools/regenerate_docs.py'),
+ '--output-dir', cur_bdir,
+ '--dummy-output-file', '@OUTPUT@',
+ ],
+ build_by_default: true,
+ install: false,
+)
+
+refman_md = custom_target(
+ 'gen_refman_md',
+ input: refman_binary,
+ output: ['configured_sitemap.txt', 'refman_links.json'],
+ command: [
+ genrefman,
+ '-l', 'pickle',
+ '-g', 'md',
+ '-s', sitemap,
+ '-i', '@INPUT@',
+ '-o', '@OUTPUT0@',
+ '--link-defs', '@OUTPUT1@',
+ '--force-color',
+ '--no-modules',
+ ],
+)
+sitemap = refman_md[0]
+
genrelnotes = custom_target(
output: ['sitemap-genrelnotes.txt'],
build_always_stale: true,
)
sitemap = genrelnotes[0]
-test('validate_docs', find_program('./jsonvalidator.py'), args: [refman_json])
-
hotdoc_prog = find_program('hotdoc', version: '>=0.13.7')
+py = import('python').find_installation('python3', modules: ['chevron'], required: false)
+if not py.found()
+ error('Building the HTML docs requires the chevron module to render generated markdown pages')
+endif
hotdoc = import('hotdoc')
documentation = hotdoc.generate_doc(meson.project_name(),
keep_markup_in_code_blocks: true,
extra_extension: meson.current_source_dir() / 'extensions' / 'refman_links.py',
refman_data_file: refman_md[1],
+ fatal_warnings: true,
)
run_target('upload',
--- /dev/null
+option('unsafe_yaml', type: 'boolean', value: false,
+ description: 'disable safety checks and use a faster, but less correct YAML loader')
+option('html', type: 'boolean', value: true,
+ description: 'build the hotdoc-based HTML documentation')
# SPDX-License-Identifer: Apache-2.0
# Copyright 2021 The Meson development team
+from __future__ import annotations
from pathlib import Path
import json
def _link_to_object(self, obj: T.Union[Function, Object], in_code_block: bool = False) -> str:
'''
- Generate a palaceholder tag for the the function/method/object documentation.
+ Generate a palaceholder tag for the function/method/object documentation.
This tag is then replaced in the custom hotdoc plugin.
'''
prefix = '#' if in_code_block else ''
# I know, this regex is ugly but it works.
return len(re.sub(r'\[\[(#|@)*([^\[])', r'\2', s))
+ def arg_anchor(arg: ArgBase) -> str:
+ return f'{func.name}_{arg.name.replace("<", "_").replace(">", "_")}'
+
def render_signature() -> str:
# Skip a lot of computations if the function does not take any arguments
if not any([func.posargs, func.optargs, func.kwargs, func.varargs]):
max_name_len = max([len(x.name) for x in all_args])
# Generate some common strings
- def prepare(arg: ArgBase) -> T.Tuple[str, str, str, str]:
+ def prepare(arg: ArgBase, link: bool = True) -> T.Tuple[str, str, str, str]:
type_str = render_type(arg.type, True)
type_len = len_stripped(type_str)
type_space = ' ' * (max_type_len - type_len)
name_space = ' ' * (max_name_len - len(arg.name))
name_str = f'<b>{arg.name.replace("<", "<").replace(">", ">")}</b>'
+ if link:
+ name_str = f'<a href="#{arg_anchor(arg)}">{name_str}</a>'
+
return type_str, type_space, name_str, name_space
for i in func.posargs:
signature += f' {type_str}{type_space} [{name_str}],{name_space} # {self.brief(i)}\n'
if func.varargs:
- type_str, type_space, name_str, name_space = prepare(func.varargs)
+ type_str, type_space, name_str, name_space = prepare(func.varargs, link=False)
signature += f' {type_str}{type_space} {name_str}...,{name_space} # {self.brief(func.varargs)}\n'
# Abort if there are no kwargs
def gen_arg_data(arg: T.Union[PosArg, Kwarg, VarArgs], *, optional: bool = False) -> T.Dict[str, PlaceholderTypes]:
data: T.Dict[str, PlaceholderTypes] = {
+ 'row-id': arg_anchor(arg),
'name': arg.name,
'type': render_type(arg.type),
'description': arg.description,
def _write_object(self, obj: Object) -> None:
data = {
'name': obj.name,
+ 'title': obj.long_name if obj.obj_type == ObjectType.RETURNED else obj.name,
'description': obj.description,
'notes': obj.notes,
'warnings': obj.warnings,
import typing as T
def my_nested() -> T.ContextManager[None]:
- prefix = '|' * len(mlog.log_depth)
+ prefix = '|' * mlog.get_log_depth()
return mlog.nested(prefix)
class GeneratorPrint(GeneratorBase):
--- /dev/null
+# SPDX-License-Identifer: Apache-2.0
+# Copyright 2023 The Meson development team
+from __future__ import annotations
+
+from pathlib import Path
+
+from .generatorbase import GeneratorBase
+from .model import ReferenceManual
+
+
+class GeneratorVim(GeneratorBase):
+ def __init__(self, manual: ReferenceManual, out_dir: Path) -> None:
+ super().__init__(manual)
+ self.out_dir = out_dir
+
+ def generate(self) -> None:
+ template_dir = Path(__file__).resolve().parent / 'templates'
+ outname = 'meson.vim'
+ template_name = f'{outname}.mustache'
+ template_file = template_dir / template_name
+
+ builtin_funcs = [f.name for f in self.sorted_and_filtered(self.functions)]
+ data = {
+ 'builtin_funcs': '\n \\ '.join(builtin_funcs)
+ }
+
+ # Import here, so that other generators don't also depend on it
+ import chevron
+ result = chevron.render(
+ template=template_file.read_text(encoding='utf-8'),
+ data=data,
+ warn=True,
+ )
+
+ self.out_dir.mkdir(parents=True, exist_ok=True)
+ out_file = self.out_dir / outname
+ out_file.write_text(result, encoding='utf-8')
VERSION_MAJOR = 1 # Changes here indicate breaking format changes (changes to existing keys)
VERSION_MINOR = 1 # Changes here indicate non-breaking changes (only new keys are added to the existing structure)
-class BaseObject(T.TypedDict):
- '''
- Base object for most dicts in the JSON doc.
+if T.TYPE_CHECKING:
+ from typing_extensions import TypedDict
- All objects inheriting from BaseObject will support
- the keys specified here:
- '''
- name: str
- description: str
- since: T.Optional[str]
- deprecated: T.Optional[str]
- notes: T.List[str]
- warnings: T.List[str]
+ class BaseObject(TypedDict):
+ '''
+ Base object for most dicts in the JSON doc.
-class Type(T.TypedDict):
- obj: str # References an object from `root.objects`
- holds: T.Sequence[object] # Mypy does not support recursive dicts, but this should be T.List[Type]...
+ All objects inheriting from BaseObject will support
+ the keys specified here:
+ '''
+ name: str
+ description: str
+ since: T.Optional[str]
+ deprecated: T.Optional[str]
+ notes: T.List[str]
+ warnings: T.List[str]
-class Argument(BaseObject):
- '''
- Object that represents any type of a single function or method argument.
- '''
- type: T.List[Type] # A non-empty list of types that are supported.
- type_str: str # Formatted version of `type`. Is guaranteed to not contain any whitespaces.
- required: bool
- default: T.Optional[str]
- min_varargs: T.Optional[int] # Only relevant for varargs, must be `null` for all other types of arguments
- max_varargs: T.Optional[int] # Only relevant for varargs, must be `null` for all other types of arguments
+ class Type(TypedDict):
+ obj: str # References an object from `root.objects`
+ holds: T.Sequence[object] # Mypy does not support recursive dicts, but this should be T.List[Type]...
-class Function(BaseObject):
- '''
- Represents a function or method.
- '''
- returns: T.List[Type] # A non-empty list of types that are supported.
- returns_str: str # Formatted version of `returns`. Is guaranteed to not contain any whitespaces.
- example: T.Optional[str]
- posargs: T.Dict[str, Argument]
- optargs: T.Dict[str, Argument]
- kwargs: T.Dict[str, Argument]
- varargs: T.Optional[Argument]
- arg_flattening: bool
+ class Argument(BaseObject):
+ '''
+ Object that represents any type of a single function or method argument.
+ '''
+ type: T.List[Type] # A non-empty list of types that are supported.
+ type_str: str # Formatted version of `type`. Is guaranteed to not contain any whitespaces.
+ required: bool
+ default: T.Optional[str]
+ min_varargs: T.Optional[int] # Only relevant for varargs, must be `null` for all other types of arguments
+ max_varargs: T.Optional[int] # Only relevant for varargs, must be `null` for all other types of arguments
-class Object(BaseObject):
- '''
- Represents all types of Meson objects. The specific object type is stored in the `object_type` field.
- '''
- example: T.Optional[str]
- object_type: str # Defines the object type: Must be one of: ELEMENTARY, BUILTIN, MODULE, RETURNED
- methods: T.Dict[str, Function]
- is_container: bool
- extends: T.Optional[str]
- returned_by: T.List[str]
- extended_by: T.List[str]
- defined_by_module: T.Optional[str]
+ class Function(BaseObject):
+ '''
+ Represents a function or method.
+ '''
+ returns: T.List[Type] # A non-empty list of types that are supported.
+ returns_str: str # Formatted version of `returns`. Is guaranteed to not contain any whitespaces.
+ example: T.Optional[str]
+ posargs: T.Dict[str, Argument]
+ optargs: T.Dict[str, Argument]
+ kwargs: T.Dict[str, Argument]
+ varargs: T.Optional[Argument]
+ arg_flattening: bool
-class ObjectsByType(T.TypedDict):
- '''
- References to other objects are stored here for ease of navigation / filtering
- '''
- elementary: T.List[str]
- builtins: T.List[str]
- returned: T.List[str]
- modules: T.Dict[str, T.List[str]]
+ class Object(BaseObject):
+ '''
+ Represents all types of Meson objects. The specific object type is stored in the `object_type` field.
+ '''
+ example: T.Optional[str]
+ object_type: str # Defines the object type: Must be one of: ELEMENTARY, BUILTIN, MODULE, RETURNED
+ methods: T.Dict[str, Function]
+ is_container: bool
+ extends: T.Optional[str]
+ returned_by: T.List[str]
+ extended_by: T.List[str]
+ defined_by_module: T.Optional[str]
+ class ObjectsByType(TypedDict):
+ '''
+ References to other objects are stored here for ease of navigation / filtering
+ '''
+ elementary: T.List[str]
+ builtins: T.List[str]
+ returned: T.List[str]
+ modules: T.Dict[str, T.List[str]]
-class Root(T.TypedDict):
- '''
- The root object of the JSON reference manual
- '''
- version_major: int # See the description above for
- version_minor: int # VERSION_MAJOR and VERSION_MINOR
- meson_version: str
- functions: T.Dict[str, Function] # A mapping of <name> to a `Function` object for *all* Meson functions
- objects: T.Dict[str, Object] # A mapping of <name> to a `Object` object for *all* Meson objects (including modules, elementary, etc.)
- objects_by_type: ObjectsByType
+
+ class Root(TypedDict):
+ '''
+ The root object of the JSON reference manual
+ '''
+ version_major: int # See the description above for
+ version_minor: int # VERSION_MAJOR and VERSION_MINOR
+ meson_version: str
+ functions: T.Dict[str, Function] # A mapping of <name> to a `Function` object for *all* Meson functions
+ objects: T.Dict[str, Object] # A mapping of <name> to a `Object` object for *all* Meson objects (including modules, elementary, etc.)
+ objects_by_type: ObjectsByType
for obj in func.returns.resolved:
obj.data_type.returned_by += [func]
- # Handle kwargs inehritance
+ # Handle kwargs inheritance
for base_name in func.kwargs_inherit:
base_name = base_name.strip()
assert base_name in self.func_map, f'Unknown base function `{base_name}` for {func.name}'
missing = {k: v for k, v in base.kwargs.items() if k in base_keys - curr_keys}
func.kwargs.update(missing)
- # Handloe other args inheritance
+ # Handle other args inheritance
_T = T.TypeVar('_T', bound=T.Union[ArgBase, T.List[PosArg]])
def resolve_inherit(name: str, curr: _T, resolver: T.Callable[[Function], _T]) -> _T:
if name and not curr:
from .generatorpickle import GeneratorPickle
from .generatormd import GeneratorMD
from .generatorman import GeneratorMan
+from .generatorvim import GeneratorVim
meson_root = Path(__file__).absolute().parents[2]
def main() -> int:
parser = argparse.ArgumentParser(description='Meson reference manual generator')
parser.add_argument('-l', '--loader', type=str, default='yaml', choices=['yaml', 'fastyaml', 'pickle'], help='Information loader backend')
- parser.add_argument('-g', '--generator', type=str, choices=['print', 'pickle', 'md', 'json', 'man'], required=True, help='Generator backend')
+ parser.add_argument('-g', '--generator', type=str, choices=['print', 'pickle', 'md', 'json', 'man', 'vim'], required=True, help='Generator backend')
parser.add_argument('-s', '--sitemap', type=Path, default=meson_root / 'docs' / 'sitemap.txt', help='Path to the input sitemap.txt')
parser.add_argument('-o', '--out', type=Path, required=True, help='Output directory for generated files')
parser.add_argument('-i', '--input', type=Path, default=meson_root / 'docs' / 'yaml', help='Input path for the selected loader')
'md': lambda: GeneratorMD(refMan, args.out, args.sitemap, args.link_defs, not args.no_modules),
'json': lambda: GeneratorJSON(refMan, args.out, not args.no_modules),
'man': lambda: GeneratorMan(refMan, args.out, not args.no_modules),
+ 'vim': lambda: GeneratorVim(refMan, args.out),
}
generator = generators[args.generator]()
</thead>
<tbody class="nomargin">
{{#args}}
- <tr>
+ <tr id="{{row-id}}">
<td style="white-space: nowrap; text-align: center; padding: 6px;"><code class="language-meson">{{name}}</code></td>
<td style="white-space: revert; text-align: center; padding: 6px; word-wrap: break-word;">{{&type}}</td>
<!-- This suboptimal formatting is required to ensure hotdoc correctly generates the HTML -->
--- /dev/null
+" Vim syntax file
+" Language: Meson
+" License: VIM License
+" Maintainer: Nirbheek Chauhan <nirbheek.chauhan@gmail.com>
+" Liam Beguin <liambeguin@gmail.com>
+" Last Change: 2023 Aug 27
+" Credits: Zvezdan Petkovic <zpetkovic@acm.org>
+" Neil Schemenauer <nas@meson.ca>
+" Dmitry Vasiliev
+"
+" This version is copied and edited from python.vim
+" It's very basic, and doesn't do many things I'd like it to
+" For instance, it should show errors for syntax that is valid in
+" Python but not in Meson.
+"
+" Optional highlighting can be controlled using these variables.
+"
+" let meson_space_error_highlight = 1
+"
+
+if exists("b:current_syntax")
+ finish
+endif
+
+" We need nocompatible mode in order to continue lines with backslashes.
+" Original setting will be restored.
+let s:cpo_save = &cpo
+set cpo&vim
+
+" http://mesonbuild.com/Syntax.html
+syn keyword mesonConditional elif else if endif
+syn keyword mesonRepeat foreach endforeach
+syn keyword mesonOperator and not or in
+syn keyword mesonStatement continue break
+
+syn match mesonComment "#.*$" contains=mesonTodo,@Spell
+syn keyword mesonTodo FIXME NOTE NOTES TODO XXX contained
+
+" Strings can either be single quoted or triple counted across multiple lines,
+" but always with a '
+syn region mesonString
+ \ start="\z('\)" end="\z1" skip="\\\\\|\\\z1"
+ \ contains=mesonEscape,@Spell
+syn region mesonString
+ \ start="\z('''\)" end="\z1" keepend
+ \ contains=mesonEscape,mesonSpaceError,@Spell
+
+syn match mesonEscape "\\[abfnrtv'\\]" contained
+syn match mesonEscape "\\\o\{1,3}" contained
+syn match mesonEscape "\\x\x\{2}" contained
+syn match mesonEscape "\%(\\u\x\{4}\|\\U\x\{8}\)" contained
+" Meson allows case-insensitive Unicode IDs: http://www.unicode.org/charts/
+syn match mesonEscape "\\N{\a\+\%(\s\a\+\)*}" contained
+syn match mesonEscape "\\$"
+
+" Meson only supports integer numbers
+" http://mesonbuild.com/Syntax.html#numbers
+syn match mesonNumber "\<\d\+\>"
+syn match mesonNumber "\<0x\x\+\>"
+syn match mesonNumber "\<0o\o\+\>"
+
+" booleans
+syn keyword mesonBoolean false true
+
+" Built-in functions
+syn keyword mesonBuiltin
+ \ build_machine
+ \ host_machine
+ \ meson
+ \ option
+ \ target_machine
+ \ {{builtin_funcs}}
+
+if exists("meson_space_error_highlight")
+ " trailing whitespace
+ syn match mesonSpaceError display excludenl "\s\+$"
+ " mixed tabs and spaces
+ syn match mesonSpaceError display " \+\t"
+ syn match mesonSpaceError display "\t\+ "
+endif
+
+" The default highlight links. Can be overridden later.
+hi def link mesonStatement Statement
+hi def link mesonConditional Conditional
+hi def link mesonRepeat Repeat
+hi def link mesonOperator Operator
+hi def link mesonComment Comment
+hi def link mesonTodo Todo
+hi def link mesonString String
+hi def link mesonEscape Special
+hi def link mesonNumber Number
+hi def link mesonBuiltin Function
+hi def link mesonBoolean Boolean
+if exists("meson_space_error_highlight")
+ hi def link mesonSpaceError Error
+endif
+
+let b:current_syntax = "meson"
+
+let &cpo = s:cpo_save
+unlet s:cpo_save
+
+" vim:set sw=2 sts=2 ts=8 noet:
---
short-description: "{{obj_type_name}} object: {{long_name}}"
-title: {{name}}{{#extends}} (extends {{.}}){{/extends}}
+title: {{title}}
render-subpages: false
...
# {{long_name}} (`{{name}}`{{#extends}} extends [[@{{.}}]]{{/extends}})
Wrap-best-practices-and-tips.md
Shipping-prebuilt-binaries-as-wraps.md
Release-notes.md
+ Release-notes-for-1.3.0.md
+ Release-notes-for-1.2.0.md
+ Release-notes-for-1.1.0.md
Release-notes-for-1.0.0.md
Release-notes-for-0.64.0.md
Release-notes-for-0.63.0.md
distribution source has been generated but before it is
archived. Note that this runs the script file that is in the
_staging_ directory, not the one in the source directory. If the
- script file can not be found in the staging directory, it is a hard
+ script file cannot be found in the staging directory, it is a hard
error. The `MESON_DIST_ROOT` environment variables is set when dist scripts is
run.
*(since 0.54.0)* If `meson install` is called with the `--quiet` option, the
environment variable `MESON_INSTALL_QUIET` will be set.
+ *(since 1.1.0)* If `meson install` is called with the `--dry-run` option, the
+ environment variable `MESON_INSTALL_DRY_RUN` will be set.
+
Meson uses the `DESTDIR` environment variable as set by the
inherited environment to determine the (temporary) installation
location for files. Your install script must be aware of this while
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
+ default: false
+ description: |
+ If `true` the script will be run even if `--dry-run` option is provided to
+ the `meson install` command. The script can use the `MESON_INSTALL_DRY_RUN`
+ variable to determine if it is in dry run mode or not.
- name: add_postconf_script
returns: void
- `vs2022`
- `xcode`
+ - name: build_options
+ returns: str
+ since: 1.1.0
+ description: |
+ Returns a string with the configuration line used to set the current project up.
+ notes:
+ - |
+ **Do not try to parse this string!**
+
+ You should use [[cfg_data.set_quoted]] to safely escape any embedded
+ quotes prior to storing it into e.g. a C header macro.
+
+ The contents returned by this function are the same as the
+ "Build Options:" line reported in `<builddir>/meson-logs/meson-log.txt`.
+
- name: build_root
returns: str
deprecated: 0.56.0
returns: str
since: 0.58.0
description: |
- Returns a string with the absolute path to the source root directory
+ Returns a string with the absolute path to the source root directory.
This function will return the source root of the
main project if called from a subproject, which is usually not what you want.
It is usually preferable to use [[meson.current_source_dir]] or [[meson.project_source_root]].
returns: void
description: |
Installs a manifest file
- containing a list of all subprojects, their versions and license
- files to the file name given as the argument.
+ containing a list of all subprojects, their versions and license names
+ to the file name given as the argument.
+
+ If license files are defined as well, they will be copied next to the
+ manifest and referenced in it.
+
+ If this function is not used, the builtin option `licensedir` can
+ be used to install the manifest to a given directory with the name
+ `depmf.json`.
posargs:
output_name:
since: 0.45.0
description: Returns the array of licenses specified in [[project]] function call.
+ - name: project_license_files
+ returns: list[file]
+ since: 1.1.0
+ description: Returns the array of license files specified in the [[project]] function call.
+
- name: project_name
returns: str
description: Returns the project name specified in the [[project]] function call.
You can also iterate over dictionaries with the [`foreach`
statement](Syntax.md#foreach-statements).
- *(since 0.48.0)* Dictionaries can be added (e.g. `d1 = d2 + d3` and `d1 += d2`).
+ *(since 0.48.0)*: Dictionaries can be added (e.g. `d1 = d2 + d3` and `d1 += d2`).
Values from the second dictionary overrides values from the first.
+ *(since 0.62.0)*: Dictionary order is guaranteed to be insertion order.
methods:
- name: to_string
returns: str
description: Returns the value of the number as a string.
+
+ optargs:
+ fill:
+ type: int
+ description: |
+ Left fill the string with zeros until it reaches the length
+ specified by this argument. A leading negative sign counts towards
+ the length, and is handled by inserting the padding after the `-`
+ character rather than before. The original string is returned if the
+ value provided is less than or equal to the former's length.
+
+
See [the Meson syntax entry](Syntax.md#string-formatting) for more
information.
+
+ *Since 1.3.0* values other than strings, integers, bools, options,
+ dictionaries and lists thereof are deprecated. They were previously printing
+ the internal representation of the raw Python object.
example: |
```meson
template = 'string: @0@, number: @1@, bool: @2@'
# str.replace(old, new)
- name: replace
- description: Search all occurrences of `old` and and replace it with `new`
+ description: Search all occurrences of `old` and replace it with `new`
returns: str
since: 0.58.0
example: |
# str.strip()
- name: strip
- description: Removes leading/ending spaces and newlines from the string.
+ description: |
+ Removes leading/ending characters from the string.
+
+ By default the characters to remove are spaces and newlines.
returns: str
example: |
```meson
strip_chars:
type: str
since: 0.43.0
- description: All characters in this string will be stripped.
+ description: Instead of whitespace, strip all the characters in this string.
# str.to_lower()
- name: to_lower
type: str
description: Specifies the character / substring where to split the string.
+- name: splitlines
+ returns: list[str]
+ since: 1.2.0
+ description: |
+ Splits the string into an array of lines.
+ Unlike .split('\n'), the empty string produced an empty array,
+ and if the string ends in a newline, splitlines() doesn't split
+ on that last newline.
+ '\n', '\r' and '\r\n' are all considered newlines.
+
+ example: |
+ ```meson
+ output = 'hello\nworld\n'.splitlines()
+ # Output value is ['hello', 'world']
+ output = ''.splitlines()
+ # Output value is []
+ fs = import('fs')
+ paths = fs.read('my_paths.list').splitlines()
+ # paths is now the paths listed in 'my_paths.list', or an empty list
+ # if 'my_paths.list' is empty
+ ```
+
# str.join()
- name: join
returns: str
compiler flags to use for the given language;
eg: `cpp_args` for C++
+ vala_args:
+ type: list[str | file]
+ description: |
+ Compiler flags for Vala. Unlike other languages this may contain Files
+
sources:
type: str | file | custom_tgt | custom_idx | generated_list | structured_src
description: Additional source files. Same as the source varargs.
default: false
description: |
When set to true flags this target as a GUI application
- on platforms where this makes a differerence, **deprecated** since
+ on platforms where this makes a difference, **deprecated** since
0.56.0, use `win_subsystem` instead.
link_args:
type: list[lib | custom_tgt | custom_idx]
since: 0.40.0
description: |
- Links all contents of the given static libraries
- whether they are used by not, equivalent to the `-Wl,--whole-archive` argument flag of GCC.
+ Links all contents of the given static libraries whether they are used or
+ not, equivalent to the `-Wl,--whole-archive` argument flag of GCC, or the
+ '/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.
type: list[extracted_obj | file | str]
description: |
List of object files that should be linked in this target.
- These can include third party products you don't have source to,
- or object files produced by other build targets.
+
+ **Since 1.1.0** this can include generated files in addition to
+ object files that you don't have source to or that object files
+ produced by other build targets. In earlier release, generated
+ object files had to be placed in `sources`.
name_prefix:
type: str | list[void]
Set this to `[]`, or omit the keyword argument for the default behaviour.
override_options:
- type: list[str]
+ type: list[str] | dict[str | bool | int | list[str]]
since: 0.40.0
description: |
takes an array of strings in the same format as `project`'s `default_options`
overriding the values of these options
for this target only.
+ *(since 1.2.0)*: A dictionary may now be passed.
gnu_symbol_visibility:
type: str
rust_crate_type:
type: str
since: 0.42.0
+ deprecated: 1.3.0
description: |
Set the specific type of rust crate to compile (when compiling rust).
If it is a [[shared_library]] it defaults to "lib", and may be "lib",
"dylib", "cdylib", or "proc-macro". If "lib" then Rustc will pick a
default, "cdylib" means a C ABI library, "dylib" means a Rust ABI, and
- "proc-macro" is a special rust proceedural macro crate.
+ "proc-macro" is a special rust procedural macro crate.
"proc-macro" is new in 0.62.0.
+
+ *Since 1.3.0* this is deprecated and replaced by "rust_abi" keyword argument.
+ `proc_macro` crates are now handled by the [`rust.proc_macro()`](Rust-module.md#proc_macro)
+ method.
+
+ rust_dependency_map:
+ 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.
+
+ This allows renaming similar to the dependency renaming feature of cargo
+ or `extern crate foo as bar` inside rust code.
kwargs:
required:
- type: bool
+ type: bool | feature
default: true
description: |
If set to `true`, Meson will halt if any of the languages
name: add_project_link_arguments
returns: void
description: |
- Adds global arguments to the linker command line.
+ Adds project specific arguments to the linker command line.
- Like [[add_global_arguments]] but the arguments are passed to the linker.
+ Like [[add_project_arguments]] but the arguments are passed to the linker.
notes:
- You must pass always arguments individually `arg1, arg2, ...`
name: Linker argument
description: The linker arguments to add
-kwargs_inherit: add_global_arguments
+kwargs_inherit: add_project_arguments
- `static_library` (see [[static_library]])
- `both_libraries` (see [[both_libraries]])
- `library` (see [[library]])
- - `jar` (see [[jar]])
+ - `jar` (see [[jar]])*
This declaration:
The returned object also has methods that are documented in [[@build_tgt]].
+ *"jar" is deprecated because it is fundementally a different thing than the
+ other build_target types.
+
posargs_inherit: _build_target_base
varargs_inherit: _build_target_base
kwargs_inherit:
kwargs:
target_type:
type: str
- description: The actual target to build
+ description: The actual target type to build
this function will copy the file provided in `input:` to a file in the
build directory with the name `output:` in the current directory.
+warnings:
+ - the `install_mode` kwarg ignored integer values between 0.62 -- 1.1.0.
+
kwargs:
capture:
type: bool
The format of the output to generate when no input
was specified. It defaults to `c`, in which case preprocessor directives
will be prefixed with `#`, you can also use `nasm`, in which case the
- prefix will be `%`.
+ prefix will be `%`. *(since 1.3.0)* `json` format can also be used.
encoding:
type: str
Set the file encoding for the input and output file.
The supported encodings are those of python3, see
[standard-encodings](https://docs.python.org/3/library/codecs.html#standard-encodings).
+
+ macro_name:
+ type: str
+ since: 1.3.0
+ description: |
+ When specified, macro guards will be used instead of '#pragma once'. The
+ macro guard name will be the specified name.
is not portable, notably to Windows. Instead, consider using a
`native: true` [[executable]], or a python script.
+warnings:
+ - the `install_mode` kwarg ignored integer values between 0.60.0 -- 1.1.0.
+
optargs:
name:
type: str
(or generated header files
that should be built before sources including them are built)
+ extra_files:
+ type: list[str | file]
+ since: 1.2.0
+ description: |
+ extra files to add to targets.
+ mostly used for IDE integration.
+
version:
type: str
description: |
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]
+ since: 1.1.0
+ description: |
+ a list of object files, to be linked directly into the targets that use the
+ dependency.
kwargs:
default_options:
- type: list[str]
+ type: list[str] | dict[str | bool | int | list[str]]
since: 0.38.0
description: |
An array of default option values
- that override those set in the subproject's `meson_options.txt`
+ that override those set in the subproject's `meson.options`
(like `default_options` in [[project]], they only have
effect when Meson is run for the first time, and command line
arguments override any default options in build files)
+ *(since 1.2.0)*: A dictionary may now be passed.
allow_fallback:
type: bool
The returned object also has methods that are documented in [[@exe]].
+ *Since 1.3.0* executable names can be the same across multiple targets as
+ long as they each have a different `name_suffix`.
+
warnings:
- The `link_language` kwarg was broken until 0.55.0
type: bool
since: 0.49.0
description: Build a position-independent executable.
+
+ vs_module_defs:
+ type: str | file | custom_tgt | custom_idx
+ since: 1.3.0
+ description: |
+ Specify a Microsoft module definition file for controlling symbol exports,
+ etc., on platforms where that is possible (e.g. Windows).
+
+ This can be used to expose which functions a shared_module loaded by an
+ executable will be allowed to use.
setcap = find_program(['setcap', '/usr/sbin/setcap', '/sbin/setcap'], required : false)
```
+ *Since 1.2.0* `find_program('meson')` is automatically overridden to the Meson
+ command used to execute the build script.
+
The returned [[@external_program]] object also has documented methods.
posargs:
type: list[str]
since: 0.53.0
description: extra list of absolute paths where to look for program names.
+
+ default_options:
+ type: list[str] | dict[str | bool | int | list[str]]
+ since: 1.3.0
+ description: |
+ An array of default option values
+ that override those set in the subproject's `meson.options`
+ (like `default_options` in [[project]], they only have
+ effect when Meson is run for the first time, and command line
+ arguments override any default options in build files)
type: file | str
description: Files to install.
+warnings:
+ - the `install_mode` kwarg ignored integer values between 0.59.0 -- 1.1.0.
+ - an omitted `install_dir` kwarg did not work correctly inside of a subproject until 1.3.0.
+ - an omitted `install_dir` kwarg did not work correctly when combined with the `preserve_path` kwarg untill 1.3.0.
+
kwargs:
install_dir:
type: str
sources:
type: list[file | str]
description: Additional files to install.
+
+ follow_symlinks:
+ type: bool
+ since: 1.3.0
+ default: true
+ description: |
+ If true, dereferences links and copies their target instead. The default
+ value will become false in the future.
argument. If the directory exists and is not empty, the contents are left in
place.
+warnings:
+ - the `install_mode` kwarg ignored integer values before 1.1.0.
+
varargs:
name: dirpath
type: str
type: file | str
description: Header files to install.
+warnings:
+ - the `install_mode` kwarg ignored integer values between 0.59.0 -- 1.1.0.
+
kwargs:
install_dir:
type: str
since: 0.63.0
default: false
description: |
- Disable stripping child-direcories from header files when installing.
+ Disable stripping child-directories from header files when installing.
This is equivalent to GNU Automake's `nobase` option.
+
+ follow_symlinks:
+ type: bool
+ since: 1.3.0
+ default: true
+ description: |
+ If true, dereferences links and copies their target instead. The default
+ value will become false in the future.
type: file | str
description: Man pages to install.
+warnings:
+ - the `install_mode` kwarg ignored integer values between 0.59.0 -- 1.1.0.
+
kwargs:
install_mode:
type: list[str | int]
new_directory/
```
+warnings:
+ - the `install_mode` kwarg ignored integer values between 0.59.0 -- 1.1.0.
+
posargs:
subdir_name:
type: str
description: |
Install directory contents.
If `strip_directory=true` only the last component of the source path is used.
+
+ follow_symlinks:
+ type: bool
+ since: 1.3.0
+ default: true
+ description: |
+ If true, dereferences links and copies their target instead. The default
+ value will become false in the future.
The keyword arguments for this are the same as for
[[build_target]]
+warnings:
+ - using <lang>_shared_args and/or <lang>_static_args may lead to much higher
+ compilation times with both_library, as object files cannot be shared between
+ the static and shared targets. It is guaranteed to not duplicate the build if
+ these arguments are empty arrays
+
posargs_inherit: _build_target_base
varargs_inherit: _build_target_base
kwargs_inherit:
- shared_library
- static_library
+
+kwargs:
+ rust_abi:
+ type: str
+ since: 1.3.0
+ description: |
+ Set the specific ABI to compile (when compiling rust).
+ - 'rust' (default): Create a "rlib" or "dylib" crate depending on the library
+ type being build.
+ - 'c': Create a "cdylib" or "staticlib" crate depending on the library
+ type being build.
+
+ <lang>_static_args:
+ type: list[str]
+ since: 1.3.0
+ description:
+ Arguments that are only passed to a static library
+
+ vala_static_args:
+ type: list[str | file]
+ since: 1.3.0
+ description:
+ Arguments that are only passed to a static library
+
+ Like `vala_args`, [[files]] is allowed in addition to string
+
+ <lang>_shared_args:
+ type: list[str]
+ since: 1.3.0
+ description:
+ Arguments that are only passed to a shared library
+
+ vala_shared_args:
+ type: list[str | file]
+ since: 1.3.0
+ description:
+ Arguments that are only passed to a shared library
+
+ Like `vala_args`, [[files]] is allowed in addition to string
*(since 0.56.0)* The build machine compilers for the specified
languages are not required.
- Supported values for languages are `c`, `cpp` (for `C++`), `cuda`, `d`,
- `objc`, `objcpp`, `fortran`, `java`, `cs` (for `C#`), `vala` and `rust`.
+ Supported values for languages are `c`, `cpp` (for `C++`), `cuda`,
+ `cython`, `d`, `objc`, `objcpp`, `fortran`, `java`, `cs` (for `C#`),
+ `vala` and `rust`.
posargs:
project_name:
kwargs:
default_options:
- type: list[str]
+ type: list[str] | dict[str | bool | int | list[str]]
description: |
- Accecpts strings in the form `key=value`
+ 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
the master project, settings in subprojects are ignored. Project
specific options are used normally even in subprojects.
+ 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.
+
+ *(since 1.2.0)*: A dictionary may now be passed.
+
version:
type: str | file
description: |
For backwards compatibility reasons you can also pass an array of
licenses here. This is not recommended, as it is ambiguous: `license :
- ['Apache-2.0', 'GPL-2.0-only']` instead use an SPDX espression: `license
+ ['Apache-2.0', 'GPL-2.0-only']` instead use an SPDX expression: `license
: 'Apache-2.0 OR GPL-2.0-only'`, which makes it clear that the license
mean OR, not AND.
for verifying that you abide by all licensing terms. You can access the
value in your Meson build files with `meson.project_license()`.
+ license_files:
+ type: str | list[str]
+ since: 1.1.0
+ description: |
+ Takes a string or array of strings with the paths to the license file(s)
+ the code is under.
+
+ This enhances the value of the `license` kwarg by allowing to specify both
+ the short license name and the full license text. Usually this would be
+ something like `license_files: ['COPYING']`.
+
+ Note that the files are informal and are only installed with the dependency
+ manifest. Meson does not do any license validation, you are responsible
+ for verifying that you abide by all licensing terms. You can access the
+ value in your Meson build files with [[meson.project_license_files]].
+
subproject_dir:
type: str
default: "'subprojects'"
description: |
Specify a Microsoft module definition file for controlling symbol exports,
etc., on platforms where that is possible (e.g. Windows).
+
+ *(Since 1.3.0)* [[@custom_idx]] are supported
+
+ rust_abi:
+ type: str
+ since: 1.3.0
+ description: |
+ Set the specific ABI to compile (when compiling rust).
+ - 'rust' (default): Create a "dylib" crate.
+ - 'c': Create a "cdylib" crate.
description: |
Specify a Microsoft module definition file for controlling symbol exports,
etc., on platforms where that is possible (e.g. Windows).
+
+ *(Since 1.3.0)* [[@custom_idx]] are supported
+
+ rust_abi:
+ type: str
+ since: 1.3.0
+ description: |
+ Set the specific ABI to compile (when compiling rust).
+ - 'rust' (default): Create a "dylib" crate.
+ - 'c': Create a "cdylib" crate.
If `true` the object files in the target will be prelinked,
meaning that it will contain only one prelinked
object file rather than the individual object files.
+
+ rust_abi:
+ type: str
+ since: 1.3.0
+ description: |
+ Set the specific ABI to compile (when compiling rust).
+ - 'rust' (default): Create a "rlib" crate.
+ - 'c': Create a "staticlib" crate.
`${MESON_SOURCE_ROOT}/subprojects/foo`.
- `default_options` *(since 0.37.0)*: an array of default option values
- that override those set in the subproject's `meson_options.txt`
+ that override those set in the subproject's `meson.options`
(like `default_options` in `project`, they only have effect when
Meson is run for the first time, and command line arguments override
- any default options in build files). *(since 0.54.0)*: `default_library`
- built-in option can also be overridden.
+ any default options in build files).
+ *(since 0.54.0)*: `default_library` built-in option can also be overridden.
+ *(since 1.2.0)*: A dictionary may be passed instead of array.
- `version`: works just like the same as in `dependency`.
It specifies what version the subproject should be, as an example `>=1.0.1`
- `required` *(since 0.48.0)*: By default, `required` is `true` and
kwargs:
default_options:
- type: list[str]
+ type: list[str] | dict[str | bool | int | list[str]]
since: 0.37.0
description: |
An array of default option values
- that override those set in the subproject's `meson_options.txt`
+ that override those set in the subproject's `meson.options`
(like `default_options` in [[project]], they only have effect when
Meson is run for the first time, and command line arguments override
- any default options in build files). *(since 0.54.0)*: `default_library`
- built-in option can also be overridden.
+ any default options in build files).
+ *(since 0.54.0)*: `default_library` built-in option can also be overridden.
+ *(since 1.2.0)*: A dictionary may now be passed.
version:
type: str
test(..., env: nomalloc, ...)
```
+ By default, the environment variables `ASAN_OPTIONS` and `UBSAN_OPTIONS` are
+ set to enable aborting on detected violations and to give a backtrace. To suppress
+ this, `ASAN_OPTIONS` and `UBSAN_OPTIONS` can be set in the environment.
+
In addition to running individual executables as test cases, `test()`
can also be used to invoke an external test harness. In this case,
it is best to use `verbose: true` *(since 0.62.0)* and, if supported
source files. This is typically used to take single object files and link
them to unit tests or to compile some source files with custom flags. To
use the object file(s) in another build target, use the
- `objects:` keyword argument to a [[build_target]] or include them in the command
- line of a [[custom_target]].
+ `objects:` keyword argument to a [[build_target]] or [[declare_dependency]],
+ or include them in the command line of a [[custom_target]].
varargs:
name: source
type: str | file
deprecated: 0.59.0
description: |
Does the exact same as [[build_tgt.full_path]]. **NOTE**: This
- function is solely kept for compatebility with [[@external_program]] objects.
+ function is solely kept for compatibility with [[@external_program]] objects.
It will be removed once the, also deprecated, corresponding `path()`
function in the [[@external_program]] object is removed.
- compiler._dependencies
- compiler._no_builtin_args
- compiler._name
+ - compiler._werror
- name: _header
returns: void
When set to a [`feature`](Build-options.md#features) option, the feature
will control if it is searched and whether to fail if not found.
+- name: _required
+ returns: void
+ description: You have found a bug if you can see this!
+ kwargs:
+ required:
+ type: bool | feature
+ default: false
+ since: 1.3.0
+ description:
+ When set to `true`, Meson will halt if the check fails.
+
+ When set to a [`feature`](Build-options.md#features) option, the feature
+ will control if it is searched and whether to fail if not found.
+
+- name: _werror
+ returns: void
+ description: You have found a bug if you can see this!
+ kwargs:
+ werror:
+ type: bool
+ default: false
+ description: When set to `true`, compiler warnings are treated as error.
+ since: 1.3.0
# Star of the actual functions
- name: version
- name: alignment
returns: int
- description: Returns the alignment of the specified type.
+ description: |
+ Returns the alignment of the specified type. For C-like languages,
+ For C-like languages, the header `stddef.h` and `stdio.h` are included
+ implicitly for native compilation, only `stddef.h` is included when
+ cross-compiling.
posargs:
typename:
- name: has_member
returns: bool
description: Returns true if the type has the specified member.
- kwargs_inherit: compiler._common
+ kwargs_inherit:
+ - compiler._common
+ - compiler._required
posargs:
typename:
type: str
- name: has_members
returns: bool
description: Returns `true` if the type has *all* the specified members.
- kwargs_inherit: compiler._common
+ kwargs_inherit:
+ - compiler._common
+ - compiler._required
posargs:
typename:
type: str
Returns true if the given function is provided
by the standard library or a library passed in with the `args` keyword.
- kwargs_inherit: compiler._common
+ kwargs_inherit:
+ - compiler._common
+ - compiler._required
posargs:
funcname:
type: str
- name: has_type
returns: bool
description: Returns `true` if the specified token is a type.
- kwargs_inherit: compiler._common
+ kwargs_inherit:
+ - compiler._common
+ - compiler._required
posargs:
typename:
type: str
(defaults to -1024), `high` (defaults to 1024) and `guess` to
specify max and min values for the search and the value to try
first.
+ For C-like languages, the header `stddef.h` and `stdio.h` are included
+ implicitly for native compilation, only `stddef.h` is included when
+ cross-compiling.
posargs:
expr:
- name: sizeof
returns: int
- description: returns the size of the given type (e.g. `'int'`) or -1 if the type is unknown.
+ description: |
+ returns the size of the given type (e.g. `'int'`) or -1 if the type is unknown.
+ For C-like languages, the header `stddef.h` and `stdio.h` are included
+ implicitly for native compilation, only `stddef.h` is included when
+ cross-compiling.
kwargs_inherit: compiler._common
posargs:
typename:
type: str
description: The define to check.
+- name: has_define
+ returns: bool
+ since: 1.3.0
+ description: |
+ Returns true if the given preprocessor symbol is *defined*.
+ kwargs_inherit: compiler._common
+ posargs:
+ definename:
+ type: str
+ description: The define to check.
+
- name: compiles
returns: bool
description: Returns true if the code compiles.
argument:
type: str
description: The argument to check.
+ kwargs_inherit:
+ - compiler._required
- name: has_multi_arguments
since: 0.37.0
name: arg
type: str
description: The arguments to check.
+ kwargs_inherit:
+ - compiler._required
- name: get_supported_arguments
returns: list[str]
default: "'off'"
description: |
Supported values:
- - `'off'`: Quietely ignore unsupported arguments
+ - `'off'`: Quietly ignore unsupported arguments
- `'warn'`: Print a warning for unsupported arguments
- `'require'`: Abort if at least one argument is not supported
argument:
type: str
description: The argument to check.
+ kwargs_inherit:
+ - compiler._required
- name: has_multi_link_arguments
since: 0.46.0
name: arg
type: str
description: The link arguments to check.
+ kwargs_inherit:
+ - compiler._required
- name: get_supported_link_arguments
returns: list[str]
# default: "'off'"
# description: |
# Supported values:
- # - `'off'`: Quietely ignore unsupported arguments
+ # - `'off'`: Quietly ignore unsupported arguments
# - `'warn'`: Print a warning for unsupported arguments
# - `'require'`: Abort if at least one argument is not supported
Given a list 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
returns: bool
since: 0.48.0
name:
type: str
description: The attribute name to check.
+ kwargs_inherit:
+ - compiler._required
- name: get_supported_function_attributes
returns: list[str]
type: list[str]
description: |
Extra flags to pass to the preprocessor
+ dependencies:
+ type: dep | list[dep]
+ description: Additionally dependencies required.
+ since: 1.1.0
variable by passing a list to this kwarg
that can affect the retrieved variable: `['prefix', '/'])`.
+ *(Since 1.3.0)* Multiple variables can be specified in pairs.
+
default:
type: str
since: 0.45.0
from the object then `default_value` is returned, if it is not set
then an error is raised.
+ warnings:
+ - Before 1.3.0, specifying multiple pkgconfig_define pairs would silently
+ malform the results. Only the first variable would be redefined, but
+ its value would contain both the second variable name, as well as its
+ value.
+
optargs:
varname:
type: str
long_name: Environment
description: |
This object is returned by [[environment]] and stores
- detailed information about how environment variables should be set
- during tests. It should be passed as the `env` keyword argument to
- tests and other functions.
+ detailed information about how environment variables should be set.
+ It should be passed as the `env` keyword argument to tests and other functions.
*Since 0.58.0* [[env.append]] and [[env.prepend]] can be called multiple times
on the same `varname`. Earlier Meson versions would warn and only the last
**NOTE:** You should not usually need to use this method. Passing the
object itself should work in most contexts where a program can appear,
and allows Meson to setup inter-target dependencies correctly (for
- example in cases where a program might be overridden by a [[build_tgt]]).
+ example in cases where a program might be overridden by a [[@build_tgt]]).
Only use this if you specifically need a string, such as when embedding
a program path into a header file.
**NOTE:** You should not usually need to use this method. Passing the
object itself should work in most contexts where a program can appear,
and allows Meson to setup inter-target dependencies correctly (for
- example in cases where a program might be overridden by a [[build_tgt]]).
+ example in cases where a program might be overridden by a [[@build_tgt]]).
Only use this if you specifically need a string, such as when embedding
a program path into a header file.
description: |
Returns the feature, with `'auto'` converted to `'disabled'` if value is true.
- | Feature / Condition | `value = true` | `value = false` |
- | ------------------- | -------------- | --------------- |
- | Enabled | Enabled | Enabled |
- | Disabled | Disabled | Disabled |
- | Auto | Disabled | Auto |
+ | Feature | `value = true` | `value = false` |
+ | -------- | -------------- | --------------- |
+ | Auto | Disabled | Auto |
+ | Enabled | Enabled | Enabled |
+ | Disabled | Disabled | Disabled |
+
+ example: |
+ `disable_auto_if` is useful to give precedence to mutually exclusive dependencies
+ (that provide the same API) if either or both are available:
+
+ ```
+ # '-Dfoo=auto -Dbar=enabled' will not pick foo even if installed.
+ use_bar = get_option('bar')
+ use_foo = get_option('foo').disable_auto_if(use_bar.enabled())
+ dep_foo = dependency('foo', required: use_foo)
+ if not dep_foo.found()
+ dep_foo = dependency('bar', required: use_bar)
+ endif
+ ```
+
+ posargs:
+ value:
+ type: bool
+ description: See the table above
+
+- name: enable_auto_if
+ since: 1.1.0
+ returns: feature
+ description: |
+ Returns the feature, with `'auto'` converted to `'enabled'` if value is true.
+
+ | Feature | `value = true` | `value = false` |
+ | -------- | -------------- | --------------- |
+ | Auto | Enabled | Auto |
+ | Enabled | Enabled | Enabled |
+ | Disabled | Disabled | Disabled |
posargs:
value:
`'enabled'` and the value is false; a disabled feature if the object
is `'auto'` or `'disabled'` and the value is false.
+ | Feature | `value = true` | `value = false` |
+ | -------- | -------------- | --------------- |
+ | Auto | Auto | Disabled |
+ | Enabled | Enabled | Error |
+ | Disabled | Disabled | Disabled |
+
example: |
`require` is useful to restrict the applicability of `'auto'` features,
for example based on other features or on properties of the host machine:
error_message:
type: str
default: "''"
- description: The error Message to print if the check fails
+ description: The error message to print if the check fails
+
+- name: enable_if
+ returns: feature
+ since: 1.1.0
+ description: |
+ Returns the object itself if the value is false; an error if the object is
+ `'disabled'` and the value is true; an enabled feature if the object
+ is `'auto'` or `'enabled'` and the value is true.
+
+ | Feature | `value = true` | `value = false` |
+ | -------- | -------------- | --------------- |
+ | Auto | Enabled | Auto |
+ | Enabled | Enabled | Enabled |
+ | Disabled | Error | Disabled |
+
+ example: |
+ `enable_if` is useful to restrict the applicability of `'auto'` features,
+ particularly when passing them to [[dependency]]:
+
+ ```
+ use_llvm = get_option('llvm').enable_if(with_clang).enable_if(with_llvm_libs)
+ dep_llvm = dependency('llvm', required: use_llvm)
+ ```
+
+ posargs:
+ value:
+ type: bool
+ description: The value to check
+
+ kwargs:
+ error_message:
+ type: str
+ default: "''"
+ description: The error message to print if the check fails
+
+- name: disable_if
+ returns: feature
+ since: 1.1.0
+ description: |
+ Returns the object itself if the value is false; an error if the object is
+ `'enabled'` and the value is true; a disabled feature if the object
+ is `'auto'` or `'disabled'` and the value is true.
+
+ | Feature | `value = true` | `value = false` |
+ | -------- | -------------- | --------------- |
+ | Auto | Disabled | Auto |
+ | Enabled | Error | Enabled |
+ | Disabled | Disabled | Disabled |
+
+ This is equivalent to `feature_opt.require(not condition)`, but may make
+ code easier to reason about, especially when mixed with `enable_if`
+
+ example: |
+ `disable_if` is useful to restrict the applicability of `'auto'` features,
+ particularly when passing them to [[dependency]]:
+
+ ```
+ use_os_feature = get_option('foo') \
+ .disable_if(host_machine.system() == 'darwin', error_message : 'os feature not supported on MacOS')
+ dep_os_feature = dependency('os_feature', required: use_os_feature)
+ ```
+
+ posargs:
+ value:
+ type: bool
+ description: The value to check
+
+ kwargs:
+ error_message:
+ type: str
+ default: "''"
+ description: The error message to print if the check fails
`subdir/one.input` is processed it generates a file `{target private
directory}/subdir/one.out` as opposed to `{target private
directory}/one.out`.
+
+ env:
+ type: env | list[str] | dict[str]
+ since: 1.3.0
+ description: |
+ environment variables to set, such as
+ `{'NAME1': 'value1', 'NAME2': 'value2'}` or `['NAME1=value1', 'NAME2=value2']`,
+ or an [[@env]] object which allows more
+ sophisticated environment juggling.
Base type for all modules.
Modules provide their own specific implementation methods, but all modules
- proivide the following methods:
+ provide the following methods:
methods:
- name: found
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="210mm"
height="297mm"
viewBox="0 0 210 297"
version="1.1"
id="svg1769"
- inkscape:version="0.92.3 (2405546, 2018-03-11)"
- sodipodi:docname="meson_logo.svg">
+ inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
+ sodipodi:docname="meson_logo.svg"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs1763">
<marker
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.4"
- inkscape:cx="369.10197"
- inkscape:cy="819.78077"
+ inkscape:cx="368.92857"
+ inkscape:cy="305.71429"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
- inkscape:window-width="1226"
- inkscape:window-height="910"
- inkscape:window-x="536"
- inkscape:window-y="82"
- inkscape:window-maximized="0" />
+ inkscape:window-width="2504"
+ inkscape:window-height="1650"
+ inkscape:window-x="546"
+ inkscape:window-y="110"
+ inkscape:window-maximized="0"
+ inkscape:showpageshadow="2"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1" />
<metadata
id="metadata1766">
<rdf:RDF>
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title></dc:title>
+ <dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26416996;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 96.975039,102.57831 c -3.04835,1.72126 -4.94049,4.94629 -4.95784,8.44909 3.18944,1.73892 6.93427,1.67939 9.836611,0.0181 2.6e-4,-0.008 7.9e-4,-0.0165 0.001,-0.0248 -0.004,-3.484 -1.863431,-6.70217 -4.879301,-8.44238 z m 1.64176,3.72948 c 0.45198,0.30715 0.38492,1.04655 0.31212,1.55288 -0.0926,0.55776 -0.62713,1.47487 -1.32188,2.07326 0.0899,0.12885 0.14181,0.28753 0.14108,0.45682 -0.43052,0.21761 -0.85742,-0.0278 -1.37408,-0.47026 -0.51665,-0.44244 -1.07391,-1.32905 -1.25884,-2.24017 -0.16403,0.0174 -0.33188,-0.0192 -0.47594,-0.11007 0.0376,-0.51089 0.57985,-0.8146 1.0604,-0.99374 0.48056,-0.17913 1.71868,-0.2395 2.59158,0.0708 0.0681,-0.1429 0.18071,-0.26288 0.32556,-0.339 z"
id="path817-6-2-9-7-9-93-9-8-1-2-4-6" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.52777px;line-height:1.25;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
+ x="37.077953"
+ y="153.3485"
+ id="text1"><tspan
+ sodipodi:role="line"
+ id="tspan1"
+ style="stroke-width:0.264583"
+ x="37.077953"
+ y="153.3485">The official color of the logo is PANTONE 2105 C.</tspan><tspan
+ sodipodi:role="line"
+ style="stroke-width:0.264583"
+ x="37.077953"
+ y="157.75821"
+ id="tspan2">The sRGB substitute is (57, 32, 124).</tspan></text>
</g>
</svg>
-.TH MESON "1" "December 2022" "meson 1.0.0" "User Commands"
+.TH MESON "1" "November 2023" "meson 1.3.0" "User Commands"
.SH NAME
meson - a high productivity build system
.SH DESCRIPTION
print the source files of the given target
.TP
\fB\-\-buildsystem\-files\fR
-print all files that make up the build system (meson.build, meson_options.txt etc)
+print all files that make up the build system (meson.build, meson.options, meson_options.txt etc)
.TP
\fB\-\-tests\fR
print all unit tests
project('downloader')
-# this test will timeout, showing that a subdomain isn't caught as masquarading url
+# this test will timeout, showing that a subdomain isn't caught as masquerading url
subproject('zlib')
-project('git outcheckker', 'c')
+project('git outchecker', 'c')
sp = subproject('samplesubproject')
This directory shows how you can build redistributable binaries. On
-OSX this menans building an app bundle and a .dmg installer. On Linux
+OSX this means building an app bundle and a .dmg installer. On Linux
it means building an archive that bundles its dependencies. On Windows
it means building an .exe installer.
-project('Mercurial outcheckker', 'c')
+project('Mercurial outchecker', 'c')
sp = subproject('samplesubproject')
python bugs are fixed and it is OK to tell our users to "just upgrade
python".
'''
+from __future__ import annotations
import pathlib
import os
# 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
from functools import lru_cache
import collections
import typing as T
if T.TYPE_CHECKING:
- from .linkers import StaticLinker
+ from .linkers.linkers import StaticLinker
from .compilers import Compiler
# execinfo is a compiler lib on BSD
-UNIXY_COMPILER_INTERNAL_LIBS = ['m', 'c', 'pthread', 'dl', 'rt', 'execinfo'] # type: T.List[str]
+UNIXY_COMPILER_INTERNAL_LIBS = ['m', 'c', 'pthread', 'dl', 'rt', 'execinfo']
class Dedup(enum.Enum):
'''
# Arg prefixes that override by prepending instead of appending
- prepend_prefixes = () # type: T.Tuple[str, ...]
+ prepend_prefixes: T.Tuple[str, ...] = ()
# Arg prefixes and args that must be de-duped by returning 2
- dedup2_prefixes = () # type: T.Tuple[str, ...]
- dedup2_suffixes = () # type: T.Tuple[str, ...]
- dedup2_args = () # type: T.Tuple[str, ...]
+ dedup2_prefixes: T.Tuple[str, ...] = ()
+ dedup2_suffixes: T.Tuple[str, ...] = ()
+ dedup2_args: T.Tuple[str, ...] = ()
# Arg prefixes and args that must be de-duped by returning 1
#
# NOTE: not thorough. A list of potential corner cases can be found in
# https://github.com/mesonbuild/meson/pull/4593#pullrequestreview-182016038
- dedup1_prefixes = () # type: T.Tuple[str, ...]
- dedup1_suffixes = ('.lib', '.dll', '.so', '.dylib', '.a') # type: T.Tuple[str, ...]
+ dedup1_prefixes: T.Tuple[str, ...] = ()
+ dedup1_suffixes = ('.lib', '.dll', '.so', '.dylib', '.a')
# Match a .so of the form path/to/libfoo.so.0.1.0
# Only UNIX shared libraries require this. Others have a fixed extension.
dedup1_regex = re.compile(r'([\/\\]|\A)lib.*\.so(\.[0-9]+)?(\.[0-9]+)?(\.[0-9]+)?$')
- dedup1_args = () # type: T.Tuple[str, ...]
+ dedup1_args: T.Tuple[str, ...] = ()
# In generate_link() we add external libs without de-dup, but we must
# *always* de-dup these because they're special arguments to the linker
# TODO: these should probably move too
- always_dedup_args = tuple('-l' + lib for lib in UNIXY_COMPILER_INTERNAL_LIBS) # type : T.Tuple[str, ...]
+ always_dedup_args = tuple('-l' + lib for lib in UNIXY_COMPILER_INTERNAL_LIBS)
def __init__(self, compiler: T.Union['Compiler', 'StaticLinker'],
iterable: T.Optional[T.Iterable[str]] = None):
self.compiler = compiler
- self._container = list(iterable) if iterable is not None else [] # type: T.List[str]
- self.pre = collections.deque() # type: T.Deque[str]
- self.post = collections.deque() # type: T.Deque[str]
+ self._container: T.List[str] = list(iterable) if iterable is not None else []
+ self.pre: T.Deque[str] = collections.deque()
+ self.post: T.Deque[str] = collections.deque()
# Flush the saved pre and post list into the _container list
#
# This correctly deduplicates the entries after _can_dedup definition
# Note: This function is designed to work without delete operations, as deletions are worsening the performance a lot.
def flush_pre_post(self) -> None:
- new = [] # type: T.List[str]
- pre_flush_set = set() # type: T.Set[str]
- post_flush = collections.deque() # type: T.Deque[str]
- post_flush_set = set() # type: T.Set[str]
+ new: T.List[str] = []
+ pre_flush_set: T.Set[str] = set()
+ post_flush: T.Deque[str] = collections.deque()
+ post_flush_set: T.Set[str] = set()
#The two lists are here walked from the front to the back, in order to not need removals for deduplication
for a in self.pre:
"""Returns whether the argument can be safely de-duped.
In addition to these, we handle library arguments specially.
- With GNU ld, we surround library arguments with -Wl,--start/end-gr -> Dedupoup
+ With GNU ld, we surround library arguments with -Wl,--start/end-group
to recursively search for symbols in the libraries. This is not needed
with other linkers.
"""
# A standalone argument must never be deduplicated because it is
- # defined by what comes _after_ it. Thus dedupping this:
+ # defined by what comes _after_ it. Thus deduping this:
# -D FOO -D BAR
# would yield either
# -D FOO BAR
Add two CompilerArgs while taking into account overriding of arguments
and while preserving the order of arguments as much as possible
'''
- tmp_pre = collections.deque() # type: T.Deque[str]
+ tmp_pre: T.Deque[str] = collections.deque()
if not isinstance(args, collections.abc.Iterable):
raise TypeError(f'can only concatenate Iterable[str] (not "{args}") to CompilerArgs')
for arg in args:
InvalidArguments,
BreakRequest,
ContinueRequest,
+ Disabler,
default_resolve_key,
)
NotNode,
PlusAssignmentNode,
TernaryNode,
+ TestCaseClauseNode,
)
if T.TYPE_CHECKING:
def __init__(self, source_root: str, subdir: str, subproject: str, visitors: T.Optional[T.List[AstVisitor]] = None):
super().__init__(source_root, subdir, subproject)
self.visitors = visitors if visitors is not None else []
- self.processed_buildfiles = set() # type: T.Set[str]
- self.assignments = {} # type: T.Dict[str, BaseNode]
- self.assign_vals = {} # type: T.Dict[str, T.Any]
- self.reverse_assignment = {} # type: T.Dict[str, BaseNode]
+ self.processed_buildfiles: T.Set[str] = set()
+ self.assignments: T.Dict[str, BaseNode] = {}
+ self.assign_vals: T.Dict[str, T.Any] = {}
+ self.reverse_assignment: T.Dict[str, BaseNode] = {}
self.funcs.update({'project': self.func_do_nothing,
'test': self.func_do_nothing,
'benchmark': self.func_do_nothing,
'is_disabler': self.func_do_nothing,
'is_variable': self.func_do_nothing,
'disabler': self.func_do_nothing,
- 'gettext': self.func_do_nothing,
'jar': self.func_do_nothing,
'warning': self.func_do_nothing,
'shared_module': self.func_do_nothing,
'option': self.func_do_nothing,
'both_libraries': self.func_do_nothing,
'add_test_setup': self.func_do_nothing,
- 'find_library': self.func_do_nothing,
'subdir_done': self.func_do_nothing,
'alias_target': self.func_do_nothing,
'summary': self.func_do_nothing,
def evaluate_dictstatement(self, node: mparser.DictNode) -> TYPE_nkwargs:
def resolve_key(node: mparser.BaseNode) -> str:
- if isinstance(node, mparser.StringNode):
+ if isinstance(node, mparser.BaseStringNode):
return node.value
return '__AST_UNKNOWN__'
arguments, kwargs = self.reduce_arguments(node.args, key_resolver=resolve_key)
def evaluate_plusassign(self, node: PlusAssignmentNode) -> None:
assert isinstance(node, PlusAssignmentNode)
# Cheat by doing a reassignment
- self.assignments[node.var_name] = node.value # Save a reference to the value node
+ 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] = self.evaluate_statement(node.value)
+ self.assign_vals[node.var_name.value] = self.evaluate_statement(node.value)
def evaluate_indexing(self, node: IndexNode) -> int:
return 0
duplicate_key_error: T.Optional[str] = None,
) -> T.Tuple[T.List[TYPE_nvar], TYPE_nkwargs]:
if isinstance(args, ArgumentNode):
- kwargs = {} # type: T.Dict[str, TYPE_nvar]
+ kwargs: T.Dict[str, TYPE_nvar] = {}
for key, val in args.kwargs.items():
kwargs[key_resolver(key)] = val
if args.incorrect_order():
for i in node.ifs:
self.evaluate_codeblock(i.block)
if not isinstance(node.elseblock, EmptyNode):
- self.evaluate_codeblock(node.elseblock)
+ 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] = node.value # Save a reference to the value node
+ 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] = self.evaluate_statement(node.value) # Evaluate the value just in case
+ 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:
return None # Loop detected
id_loop_detect += [node.ast_id]
- # Try to evealuate the value of the node
+ # Try to evaluate the value of the node
if isinstance(node, IdNode):
result = quick_resolve(node)
elif isinstance(node, ArithmeticNode):
if node.operation != 'add':
return None # Only handle string and array concats
- l = quick_resolve(node.left)
- r = quick_resolve(node.right)
+ 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
else:
elif isinstance(node, MethodNode):
src = quick_resolve(node.source_object)
margs = self.flatten_args(node.args.arguments, include_unknown_args, id_loop_detect)
- mkwargs = {} # type: T.Dict[str, TYPE_nvar]
+ mkwargs: T.Dict[str, TYPE_nvar] = {}
+ method_name = node.name.value
try:
if isinstance(src, str):
- result = StringHolder(src, T.cast('Interpreter', self)).method_call(node.name, margs, mkwargs)
+ 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(node.name, margs, mkwargs)
+ 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(node.name, margs, mkwargs)
+ 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(node.name, margs, mkwargs)
+ 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(node.name, margs, mkwargs)
+ result = DictHolder(src, T.cast('Interpreter', self)).method_call(method_name, margs, mkwargs)
except mesonlib.MesonException:
return None
if isinstance(result, BaseNode):
result = self.resolve_node(result, include_unknown_args, id_loop_detect)
elif isinstance(result, list):
- new_res = [] # type: T.List[TYPE_nvar]
+ new_res: T.List[TYPE_nvar] = []
for i in result:
if isinstance(i, BaseNode):
resolved = self.resolve_node(i, include_unknown_args, id_loop_detect)
else:
args = [args_raw]
- flattend_args = [] # type: T.List[TYPE_nvar]
+ flattened_args: T.List[TYPE_nvar] = []
# Resolve the contents of args
for i in args:
if resolved is not None:
if not isinstance(resolved, list):
resolved = [resolved]
- flattend_args += resolved
+ flattened_args += resolved
elif isinstance(i, (str, bool, int, float)) or include_unknown_args:
- flattend_args += [i]
- return flattend_args
+ flattened_args += [i]
+ return flattened_args
def flatten_kwargs(self, kwargs: T.Dict[str, TYPE_nvar], include_unknown_args: bool = False) -> T.Dict[str, TYPE_nvar]:
- flattend_kwargs = {}
+ flattened_kwargs = {}
for key, val in kwargs.items():
if isinstance(val, BaseNode):
resolved = self.resolve_node(val, include_unknown_args)
if resolved is not None:
- flattend_kwargs[key] = resolved
+ flattened_kwargs[key] = resolved
elif isinstance(val, (str, bool, int, float)) or include_unknown_args:
- flattend_kwargs[key] = val
- return flattend_kwargs
+ flattened_kwargs[key] = val
+ return flattened_kwargs
+
+ def evaluate_testcase(self, node: TestCaseClauseNode) -> Disabler | None:
+ return Disabler(subproject=self.subproject)
from ..compilers import detect_compiler_for
from ..interpreterbase import InvalidArguments
from ..mesonlib import MachineChoice, OptionKey
-from ..mparser import BaseNode, ArithmeticNode, ArrayNode, ElementaryNode, IdNode, FunctionNode, StringNode
+from ..mparser import BaseNode, ArithmeticNode, ArrayNode, ElementaryNode, IdNode, FunctionNode, BaseStringNode
from .interpreter import AstInterpreter
if T.TYPE_CHECKING:
# mimic an argparse namespace
def __init__(self, cross_file: str):
super().__init__()
- self.cross_file = cross_file # type: str
- self.native_file = None # type: str
- self.cmd_line_options = {} # type: T.Dict[str, str]
+ self.cross_file = cross_file
+ self.native_file: str = None
+ self.cmd_line_options: T.Dict[str, str] = {}
def __eq__(self, other: object) -> bool:
return NotImplemented
self.environment = env
self.subproject_dir = subproject_dir
self.coredata = self.environment.get_coredata()
- self.option_file = os.path.join(self.source_root, self.subdir, 'meson_options.txt')
self.backend = backend
self.default_options = {OptionKey('backend'): self.backend}
- self.project_data = {} # type: T.Dict[str, T.Any]
- self.targets = [] # type: T.List[T.Dict[str, T.Any]]
- self.dependencies = [] # type: T.List[T.Dict[str, T.Any]]
- self.project_node = None # type: BaseNode
+ 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.funcs.update({
'add_languages': self.func_add_languages,
proj_vers = 'undefined'
self.project_data = {'descriptive_name': proj_name, 'version': proj_vers}
- if os.path.exists(self.option_file):
+ optfile = os.path.join(self.source_root, self.subdir, 'meson.options')
+ if not os.path.exists(optfile):
+ optfile = os.path.join(self.source_root, self.subdir, 'meson_options.txt')
+ if os.path.exists(optfile):
oi = optinterpreter.OptionInterpreter(self.subproject)
- oi.process(self.option_file)
+ oi.process(optfile)
self.coredata.update_project_options(oi.options)
def_opts = self.flatten_args(kwargs.get('default_options', []))
if not self.is_subproject() and 'subproject_dir' in kwargs:
spdirname = kwargs['subproject_dir']
- if isinstance(spdirname, StringNode):
+ if isinstance(spdirname, BaseStringNode):
assert isinstance(spdirname.value, str)
self.subproject_dir = spdirname.value
if not self.is_subproject():
options = {k: v for k, v in self.environment.options.items() if k.is_backend()}
self.coredata.set_options(options)
- self._add_languages(proj_langs, MachineChoice.HOST)
- self._add_languages(proj_langs, MachineChoice.BUILD)
+ self._add_languages(proj_langs, True, MachineChoice.HOST)
+ self._add_languages(proj_langs, True, MachineChoice.BUILD)
def do_subproject(self, dirname: str) -> None:
subproject_dir_abs = os.path.join(self.environment.get_source_dir(), self.subproject_dir)
def func_add_languages(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[str, TYPE_nvar]) -> None:
kwargs = self.flatten_kwargs(kwargs)
+ required = kwargs.get('required', True)
+ if isinstance(required, cdata.UserFeatureOption):
+ required = required.is_enabled()
if 'native' in kwargs:
native = kwargs.get('native', False)
- self._add_languages(args, MachineChoice.BUILD if native else MachineChoice.HOST)
+ self._add_languages(args, required, MachineChoice.BUILD if native else MachineChoice.HOST)
else:
for for_machine in [MachineChoice.BUILD, MachineChoice.HOST]:
- self._add_languages(args, for_machine)
+ self._add_languages(args, required, for_machine)
- def _add_languages(self, raw_langs: T.List[TYPE_nvar], for_machine: MachineChoice) -> None:
- langs = [] # type: T.List[str]
+ def _add_languages(self, raw_langs: T.List[TYPE_nvar], required: bool, for_machine: MachineChoice) -> None:
+ langs: T.List[str] = []
for l in self.flatten_args(raw_langs):
if isinstance(l, str):
langs.append(l)
- elif isinstance(l, StringNode):
+ elif isinstance(l, BaseStringNode):
langs.append(l.value)
for lang in sorted(langs, key=compilers.sort_clink):
lang = lang.lower()
if lang not in self.coredata.compilers[for_machine]:
- comp = detect_compiler_for(self.environment, lang, for_machine)
+ try:
+ comp = detect_compiler_for(self.environment, lang, for_machine, True)
+ except mesonlib.MesonException:
+ # do we even care about introspecting this language?
+ if required:
+ raise
+ else:
+ continue
if self.subproject:
options = {}
for k in comp.get_options():
kwargs = self.flatten_kwargs(kwargs_raw, True)
def traverse_nodes(inqueue: T.List[BaseNode]) -> T.List[BaseNode]:
- res = [] # type: T.List[BaseNode]
+ res: T.List[BaseNode] = []
while inqueue:
curr = inqueue.pop(0)
arg_node = 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 in BUILD_TARGET_FUNCTIONS:
+ if isinstance(curr, FunctionNode) and curr.func_name.value in BUILD_TARGET_FUNCTIONS:
arg_nodes.pop(0)
- elemetary_nodes = [x for x in arg_nodes if isinstance(x, (str, StringNode))]
+ elementary_nodes = [x for x in arg_nodes if isinstance(x, (str, BaseStringNode))]
inqueue += [x for x in arg_nodes if isinstance(x, (FunctionNode, ArrayNode, IdNode, ArithmeticNode))]
- if elemetary_nodes:
+ if elementary_nodes:
res += [curr]
return res
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)}
for_machine = MachineChoice.HOST
- objects = [] # type: T.List[T.Any]
- empty_sources = [] # type: T.List[T.Any]
+ objects: T.List[T.Any] = []
+ empty_sources: T.List[T.Any] = []
# Passing the unresolved sources list causes errors
+ kwargs_reduced['_allow_no_sources'] = True
target = targetclass(name, self.subdir, self.subproject, for_machine, empty_sources, [], objects,
self.environment, self.coredata.compilers[for_machine], kwargs_reduced)
- target.process_compilers()
- target.process_compilers_late([])
+ target.process_compilers_late()
new_target = {
'name': target.get_basename(),
self.sanity_check_ast()
self.parse_project()
self.run()
+
+ def extract_subproject_dir(self) -> T.Optional[str]:
+ '''Fast path to extract subproject_dir kwarg.
+ This is faster than self.parse_project() which also initialize options
+ and also calls parse_project() on every subproject.
+ '''
+ if not self.ast.lines:
+ return
+ project = self.ast.lines[0]
+ # first line is always project()
+ if not isinstance(project, FunctionNode):
+ return
+ for kw, val in project.args.kwargs.items():
+ assert isinstance(kw, IdNode), 'for mypy'
+ if kw.value == 'subproject_dir':
+ # mypy does not understand "and isinstance"
+ if isinstance(val, BaseStringNode):
+ return val.value
+ return None
# or an interpreter-based tool
from __future__ import annotations
-from . import AstVisitor
+from .visitor import AstVisitor
import typing as T
if T.TYPE_CHECKING:
class AstIDGenerator(AstVisitor):
def __init__(self) -> None:
- self.counter = {} # type: T.Dict[str, int]
+ self.counter: T.Dict[str, int] = {}
def visit_default_func(self, node: mparser.BaseNode) -> None:
name = type(node).__name__
# This class contains the basic functionality needed to run any interpreter
# or an interpreter-based tool
+from __future__ import annotations
from .. import mparser
-from . import AstVisitor
+from .visitor import AstVisitor
+
+from itertools import zip_longest
import re
import typing as T
def visit_FormatStringNode(self, node: mparser.FormatStringNode) -> None:
assert isinstance(node.value, str)
- self.append("f'" + node.value + "'", node)
+ self.append("f'" + self.escape(node.value) + "'", node)
+ node.lineno = self.curr_line or node.lineno
+
+ def visit_MultilineStringNode(self, node: mparser.StringNode) -> None:
+ assert isinstance(node.value, str)
+ self.append("'''" + node.value + "'''", node)
+ node.lineno = self.curr_line or node.lineno
+
+ def visit_FormatMultilineStringNode(self, node: mparser.FormatStringNode) -> None:
+ assert isinstance(node.value, str)
+ self.append("f'''" + node.value + "'''", node)
node.lineno = self.curr_line or node.lineno
def visit_ContinueNode(self, node: mparser.ContinueNode) -> None:
def visit_MethodNode(self, node: mparser.MethodNode) -> None:
node.lineno = self.curr_line or node.lineno
node.source_object.accept(self)
- self.append('.' + node.name + '(', node)
+ self.append('.' + node.name.value + '(', node)
node.args.accept(self)
self.append(')', node)
def visit_FunctionNode(self, node: mparser.FunctionNode) -> None:
node.lineno = self.curr_line or node.lineno
- self.append(node.func_name + '(', node)
+ self.append(node.func_name.value + '(', node)
node.args.accept(self)
self.append(')', node)
def visit_AssignmentNode(self, node: mparser.AssignmentNode) -> None:
node.lineno = self.curr_line or node.lineno
- self.append(node.var_name + ' = ', node)
+ self.append(node.var_name.value + ' = ', node)
node.value.accept(self)
def visit_PlusAssignmentNode(self, node: mparser.PlusAssignmentNode) -> None:
node.lineno = self.curr_line or node.lineno
- self.append(node.var_name + ' += ', node)
+ self.append(node.var_name.value + ' += ', node)
node.value.accept(self)
def visit_ForeachClauseNode(self, node: mparser.ForeachClauseNode) -> None:
node.lineno = self.curr_line or node.lineno
self.append_padded('foreach', node)
- self.append_padded(', '.join(node.varnames), node)
+ self.append_padded(', '.join(varname.value for varname in node.varnames), node)
self.append_padded(':', node)
node.items.accept(self)
self.newline()
else:
self.result = re.sub(r', $', '', self.result)
+class RawPrinter(AstVisitor):
+
+ def __init__(self):
+ self.result = ''
+
+ def visit_default_func(self, node: mparser.BaseNode):
+ self.result += node.value
+ if node.whitespaces:
+ node.whitespaces.accept(self)
+
+ def visit_unary_operator(self, node: mparser.UnaryOperatorNode):
+ node.operator.accept(self)
+ node.value.accept(self)
+ if node.whitespaces:
+ node.whitespaces.accept(self)
+
+ def visit_binary_operator(self, node: mparser.BinaryOperatorNode):
+ node.left.accept(self)
+ node.operator.accept(self)
+ node.right.accept(self)
+ if node.whitespaces:
+ node.whitespaces.accept(self)
+
+ def visit_BooleanNode(self, node: mparser.BooleanNode) -> None:
+ self.result += 'true' if node.value else 'false'
+ if node.whitespaces:
+ node.whitespaces.accept(self)
+
+ def visit_NumberNode(self, node: mparser.NumberNode) -> None:
+ self.result += node.raw_value
+ if node.whitespaces:
+ node.whitespaces.accept(self)
+
+ def visit_StringNode(self, node: mparser.StringNode) -> None:
+ self.result += f"'{node.raw_value}'"
+ if node.whitespaces:
+ node.whitespaces.accept(self)
+
+ def visit_MultilineStringNode(self, node: mparser.MultilineStringNode) -> None:
+ self.result += f"'''{node.value}'''"
+ if node.whitespaces:
+ node.whitespaces.accept(self)
+
+ def visit_FormatStringNode(self, node: mparser.FormatStringNode) -> None:
+ self.result += 'f'
+ self.visit_StringNode(node)
+
+ def visit_MultilineFormatStringNode(self, node: mparser.MultilineFormatStringNode) -> None:
+ self.result += 'f'
+ self.visit_MultilineStringNode(node)
+
+ def visit_ContinueNode(self, node: mparser.ContinueNode) -> None:
+ self.result += 'continue'
+ if node.whitespaces:
+ node.whitespaces.accept(self)
+
+ def visit_BreakNode(self, node: mparser.BreakNode) -> None:
+ self.result += 'break'
+ if node.whitespaces:
+ node.whitespaces.accept(self)
+
+ def visit_ArrayNode(self, node: mparser.ArrayNode) -> None:
+ node.lbracket.accept(self)
+ node.args.accept(self)
+ node.rbracket.accept(self)
+ if node.whitespaces:
+ node.whitespaces.accept(self)
+
+ def visit_DictNode(self, node: mparser.DictNode) -> None:
+ node.lcurl.accept(self)
+ node.args.accept(self)
+ node.rcurl.accept(self)
+ if node.whitespaces:
+ node.whitespaces.accept(self)
+
+ def visit_ParenthesizedNode(self, node: mparser.ParenthesizedNode) -> None:
+ node.lpar.accept(self)
+ node.inner.accept(self)
+ node.rpar.accept(self)
+ if node.whitespaces:
+ node.whitespaces.accept(self)
+
+ def visit_OrNode(self, node: mparser.OrNode) -> None:
+ self.visit_binary_operator(node)
+
+ def visit_AndNode(self, node: mparser.AndNode) -> None:
+ self.visit_binary_operator(node)
+
+ def visit_ComparisonNode(self, node: mparser.ComparisonNode) -> None:
+ self.visit_binary_operator(node)
+
+ def visit_ArithmeticNode(self, node: mparser.ArithmeticNode) -> None:
+ self.visit_binary_operator(node)
+
+ def visit_NotNode(self, node: mparser.NotNode) -> None:
+ self.visit_unary_operator(node)
+
+ def visit_CodeBlockNode(self, node: mparser.CodeBlockNode) -> None:
+ if node.pre_whitespaces:
+ node.pre_whitespaces.accept(self)
+ for i in node.lines:
+ i.accept(self)
+ if node.whitespaces:
+ node.whitespaces.accept(self)
+
+ def visit_IndexNode(self, node: mparser.IndexNode) -> None:
+ node.iobject.accept(self)
+ node.lbracket.accept(self)
+ node.index.accept(self)
+ node.rbracket.accept(self)
+ if node.whitespaces:
+ node.whitespaces.accept(self)
+
+ def visit_MethodNode(self, node: mparser.MethodNode) -> None:
+ node.source_object.accept(self)
+ node.dot.accept(self)
+ node.name.accept(self)
+ node.lpar.accept(self)
+ node.args.accept(self)
+ node.rpar.accept(self)
+ if node.whitespaces:
+ node.whitespaces.accept(self)
+
+ def visit_FunctionNode(self, node: mparser.FunctionNode) -> None:
+ node.func_name.accept(self)
+ node.lpar.accept(self)
+ node.args.accept(self)
+ node.rpar.accept(self)
+ if node.whitespaces:
+ node.whitespaces.accept(self)
+
+ def visit_AssignmentNode(self, node: mparser.AssignmentNode) -> None:
+ node.var_name.accept(self)
+ node.operator.accept(self)
+ node.value.accept(self)
+ if node.whitespaces:
+ node.whitespaces.accept(self)
+
+ def visit_PlusAssignmentNode(self, node: mparser.PlusAssignmentNode) -> None:
+ node.var_name.accept(self)
+ node.operator.accept(self)
+ node.value.accept(self)
+ if node.whitespaces:
+ node.whitespaces.accept(self)
+
+ def visit_ForeachClauseNode(self, node: mparser.ForeachClauseNode) -> None:
+ node.foreach_.accept(self)
+ for varname, comma in zip_longest(node.varnames, node.commas):
+ varname.accept(self)
+ if comma is not None:
+ comma.accept(self)
+ node.column.accept(self)
+ node.items.accept(self)
+ node.block.accept(self)
+ node.endforeach.accept(self)
+ if node.whitespaces:
+ node.whitespaces.accept(self)
+
+ def visit_IfClauseNode(self, node: mparser.IfClauseNode) -> None:
+ for i in node.ifs:
+ i.accept(self)
+ if not isinstance(node.elseblock, mparser.EmptyNode):
+ node.elseblock.accept(self)
+ node.endif.accept(self)
+ if node.whitespaces:
+ node.whitespaces.accept(self)
+
+ def visit_UMinusNode(self, node: mparser.UMinusNode) -> None:
+ self.visit_unary_operator(node)
+
+ def visit_IfNode(self, node: mparser.IfNode) -> None:
+ node.if_.accept(self)
+ node.condition.accept(self)
+ node.block.accept(self)
+ if node.whitespaces:
+ node.whitespaces.accept(self)
+
+ def visit_ElseNode(self, node: mparser.ElseNode) -> None:
+ node.else_.accept(self)
+ node.block.accept(self)
+ if node.whitespaces:
+ node.whitespaces.accept(self)
+
+ def visit_TernaryNode(self, node: mparser.TernaryNode) -> None:
+ node.condition.accept(self)
+ node.questionmark.accept(self)
+ node.trueblock.accept(self)
+ node.column.accept(self)
+ node.falseblock.accept(self)
+ if node.whitespaces:
+ node.whitespaces.accept(self)
+
+ def visit_ArgumentNode(self, node: mparser.ArgumentNode) -> None:
+ commas_iter = iter(node.commas)
+
+ for arg in node.arguments:
+ arg.accept(self)
+ try:
+ comma = next(commas_iter)
+ comma.accept(self)
+ except StopIteration:
+ pass
+
+ assert len(node.columns) == len(node.kwargs)
+ for (key, val), column in zip(node.kwargs.items(), node.columns):
+ key.accept(self)
+ column.accept(self)
+ val.accept(self)
+ try:
+ comma = next(commas_iter)
+ comma.accept(self)
+ except StopIteration:
+ pass
+
+ if node.whitespaces:
+ node.whitespaces.accept(self)
+
class AstJSONPrinter(AstVisitor):
def __init__(self) -> None:
- self.result = {} # type: T.Dict[str, T.Any]
+ self.result: T.Dict[str, T.Any] = {}
self.current = self.result
def _accept(self, key: str, node: mparser.BaseNode) -> None:
old = self.current
- data = {} # type: T.Dict[str, T.Any]
+ data: T.Dict[str, T.Any] = {}
self.current = data
node.accept(self)
self.current = old
def _accept_list(self, key: str, nodes: T.Sequence[mparser.BaseNode]) -> None:
old = self.current
- datalist = [] # type: T.List[T.Dict[str, T.Any]]
+ datalist: T.List[T.Dict[str, T.Any]] = []
for i in nodes:
self.current = {}
i.accept(self)
def visit_MethodNode(self, node: mparser.MethodNode) -> None:
self._accept('object', node.source_object)
self._accept('args', node.args)
- self.current['name'] = node.name
+ self.current['name'] = node.name.value
self.setbase(node)
def visit_FunctionNode(self, node: mparser.FunctionNode) -> None:
self._accept('args', node.args)
- self.current['name'] = node.func_name
+ self.current['name'] = node.func_name.value
self.setbase(node)
def visit_AssignmentNode(self, node: mparser.AssignmentNode) -> None:
self._accept('value', node.value)
- self.current['var_name'] = node.var_name
+ self.current['var_name'] = node.var_name.value
self.setbase(node)
def visit_PlusAssignmentNode(self, node: mparser.PlusAssignmentNode) -> None:
self._accept('value', node.value)
- self.current['var_name'] = node.var_name
+ self.current['var_name'] = node.var_name.value
self.setbase(node)
def visit_ForeachClauseNode(self, node: mparser.ForeachClauseNode) -> None:
self._accept('items', node.items)
self._accept('block', node.block)
- self.current['varnames'] = node.varnames
+ self.current['varnames'] = [varname.value for varname in node.varnames]
self.setbase(node)
def visit_IfClauseNode(self, node: mparser.IfClauseNode) -> None:
def visit_ArgumentNode(self, node: mparser.ArgumentNode) -> None:
self._accept_list('positional', node.arguments)
- kwargs_list = [] # type: T.List[T.Dict[str, T.Dict[str, T.Any]]]
+ kwargs_list: T.List[T.Dict[str, T.Dict[str, T.Any]]] = []
for key, val in node.kwargs.items():
- key_res = {} # type: T.Dict[str, T.Any]
- val_res = {} # type: T.Dict[str, T.Any]
+ key_res: T.Dict[str, T.Any] = {}
+ val_res: T.Dict[str, T.Any] = {}
self._raw_accept(key, key_res)
self._raw_accept(val, val_res)
kwargs_list += [{'key': key_res, 'val': val_res}]
def visit_FormatStringNode(self, node: mparser.FormatStringNode) -> None:
self.visit_default_func(node)
+ def visit_MultilineStringNode(self, node: mparser.StringNode) -> None:
+ self.visit_default_func(node)
+
+ def visit_FormatMultilineStringNode(self, node: mparser.FormatStringNode) -> None:
+ self.visit_default_func(node)
+
def visit_ContinueNode(self, node: mparser.ContinueNode) -> None:
self.visit_default_func(node)
def visit_BreakNode(self, node: mparser.BreakNode) -> None:
self.visit_default_func(node)
+ def visit_SymbolNode(self, node: mparser.SymbolNode) -> None:
+ self.visit_default_func(node)
+
+ def visit_WhitespaceNode(self, node: mparser.WhitespaceNode) -> None:
+ self.visit_default_func(node)
+
def visit_ArrayNode(self, node: mparser.ArrayNode) -> None:
self.visit_default_func(node)
node.args.accept(self)
def visit_MethodNode(self, node: mparser.MethodNode) -> None:
self.visit_default_func(node)
node.source_object.accept(self)
+ node.name.accept(self)
node.args.accept(self)
def visit_FunctionNode(self, node: mparser.FunctionNode) -> None:
self.visit_default_func(node)
+ node.func_name.accept(self)
node.args.accept(self)
def visit_AssignmentNode(self, node: mparser.AssignmentNode) -> None:
self.visit_default_func(node)
+ node.var_name.accept(self)
node.value.accept(self)
def visit_PlusAssignmentNode(self, node: mparser.PlusAssignmentNode) -> None:
self.visit_default_func(node)
+ node.var_name.accept(self)
node.value.accept(self)
def visit_ForeachClauseNode(self, node: mparser.ForeachClauseNode) -> None:
self.visit_default_func(node)
+ for varname in node.varnames:
+ varname.accept(self)
node.items.accept(self)
node.block.accept(self)
node.condition.accept(self)
node.block.accept(self)
+ def visit_ElseNode(self, node: mparser.IfNode) -> None:
+ self.visit_default_func(node)
+ node.block.accept(self)
+
def visit_TernaryNode(self, node: mparser.TernaryNode) -> None:
self.visit_default_func(node)
node.condition.accept(self)
for key, val in node.kwargs.items():
key.accept(self)
val.accept(self)
+
+ def visit_ParenthesizedNode(self, node: mparser.ParenthesizedNode) -> None:
+ self.visit_default_func(node)
+ node.inner.accept(self)
from ..compilers import Compiler
from ..environment import Environment
from ..interpreter import Interpreter, Test
- from ..linkers import StaticLinker
+ from ..linkers.linkers import StaticLinker
from ..mesonlib import FileMode, FileOrString
from typing_extensions import TypedDict
subproject: str
tag: T.Optional[str] = None
data_type: T.Optional[str] = None
+ follow_symlinks: T.Optional[bool] = None
@dataclass(eq=False)
class InstallSymlinkData:
class SubdirInstallData(InstallDataBase):
def __init__(self, path: str, install_path: str, install_path_name: str,
install_mode: 'FileMode', exclude: T.Tuple[T.Set[str], T.Set[str]],
- subproject: str, tag: T.Optional[str] = None, data_type: T.Optional[str] = None):
- super().__init__(path, install_path, install_path_name, install_mode, subproject, tag, data_type)
+ subproject: str, tag: T.Optional[str] = None, data_type: T.Optional[str] = None,
+ follow_symlinks: T.Optional[bool] = None):
+ super().__init__(path, install_path, install_path_name, install_mode, subproject, tag, data_type, follow_symlinks)
self.exclude = exclude
needs_exe_wrapper: bool
is_parallel: bool
cmd_args: T.List[str]
- env: build.EnvironmentVariables
+ env: mesonlib.EnvironmentVariables
should_fail: bool
timeout: T.Optional[int]
workdir: T.Optional[str]
elif backend == 'xcode':
from . import xcodebackend
return xcodebackend.XCodeBackend(build, interpreter)
+ elif backend == 'none':
+ from . import nonebackend
+ return nonebackend.NoneBackend(build, interpreter)
+ return None
+
+
+def get_genvslite_backend(genvsname: str, build: T.Optional[build.Build] = None, interpreter: T.Optional['Interpreter'] = None) -> T.Optional['Backend']:
+ if genvsname == 'vs2022':
+ from . import vs2022backend
+ return vs2022backend.Vs2022Backend(build, interpreter, gen_lite = True)
return None
# This class contains the basic functionality that is needed by all backends.
class Backend:
environment: T.Optional['Environment']
+ name = '<UNKNOWN>'
def __init__(self, build: T.Optional[build.Build], interpreter: T.Optional['Interpreter']):
# Make it possible to construct a dummy backend
self.interpreter = interpreter
self.environment = build.environment
self.processed_targets: T.Set[str] = set()
- self.name = '<UNKNOWN>'
self.build_dir = self.environment.get_build_dir()
self.source_dir = self.environment.get_source_dir()
self.build_to_src = mesonlib.relpath(self.environment.get_source_dir(),
self.src_to_build = mesonlib.relpath(self.environment.get_build_dir(),
self.environment.get_source_dir())
- def generate(self) -> None:
+ # If requested via 'capture = True', returns captured compile args per
+ # target (e.g. captured_args[target]) that can be used later, for example,
+ # to populate things like intellisense fields in generated visual studio
+ # projects (as is the case when using '--genvslite').
+ #
+ # 'vslite_ctx' is only provided when
+ # we expect this backend setup/generation to make use of previously captured
+ # compile args (as is the case when using '--genvslite').
+ def generate(self, capture: bool = False, vslite_ctx: dict = None) -> T.Optional[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:
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! '
- 'Using the first one.')
+ f'Using the first one. Consider using `{t.name}[0]`.')
filename = t.get_outputs()[0]
elif isinstance(t, build.CustomTargetIndex):
filename = t.get_outputs()[0]
def get_target_filename_abs(self, target: T.Union[build.Target, build.CustomTargetIndex]) -> 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]:
+ assert isinstance(target, build.BuildTarget), target
+ if target.get_debug_filename():
+ debug_filename = target.get_debug_filename()
+ return os.path.join(self.get_target_dir(target), debug_filename)
+ else:
+ return None
+
+ def get_target_debug_filename_abs(self, target: build.BuildTarget) -> T.Optional[str]:
+ assert isinstance(target, build.BuildTarget), target
+ if not target.get_debug_filename():
+ return None
+ return os.path.join(self.environment.get_build_dir(), self.get_target_debug_filename(target))
+
def get_source_dir_include_args(self, target: build.BuildTarget, compiler: 'Compiler', *, absolute_path: bool = False) -> T.List[str]:
curdir = target.get_subdir()
if absolute_path:
# On all other platforms, we link to the library directly.
if isinstance(target, build.SharedLibrary):
link_lib = target.get_import_filename() or target.get_filename()
+ # In AIX, if we archive .so, the blibpath must link to archived shared library otherwise to the .so file.
+ if mesonlib.is_aix() and target.aix_so_archive:
+ link_lib = re.sub('[.][a]([.]?([0-9]+))*([.]?([a-z]+))*', '.a', link_lib.replace('.so', '.a'))
return os.path.join(self.get_target_dir(target), link_lib)
elif isinstance(target, build.StaticLibrary):
return os.path.join(self.get_target_dir(target), target.get_filename())
self, cmd: T.Sequence[T.Union[programs.ExternalProgram, build.BuildTarget, build.CustomTarget, File, str]],
workdir: T.Optional[str] = None,
extra_bdeps: T.Optional[T.List[build.BuildTarget]] = None,
- capture: T.Optional[bool] = None,
- feed: T.Optional[bool] = None,
- env: T.Optional[build.EnvironmentVariables] = None,
+ capture: T.Optional[str] = None,
+ feed: T.Optional[str] = None,
+ env: T.Optional[mesonlib.EnvironmentVariables] = None,
tag: T.Optional[str] = None,
- verbose: bool = False) -> 'ExecutableSerialisation':
+ verbose: bool = False,
+ installdir_map: T.Optional[T.Dict[str, str]] = None) -> 'ExecutableSerialisation':
# XXX: cmd_args either need to be lowered to strings, or need to be checked for non-string arguments, right?
exe, *raw_cmd_args = cmd
workdir = workdir or self.environment.get_build_dir()
return ExecutableSerialisation(exe_cmd + cmd_args, env,
exe_wrapper, workdir,
- extra_paths, capture, feed, tag, verbose)
+ extra_paths, capture, feed, tag, verbose, installdir_map)
def as_meson_exe_cmdline(self, exe: T.Union[str, mesonlib.File, build.BuildTarget, build.CustomTarget, programs.ExternalProgram],
cmd_args: T.Sequence[T.Union[str, mesonlib.File, build.BuildTarget, build.CustomTarget, programs.ExternalProgram]],
workdir: T.Optional[str] = None,
extra_bdeps: T.Optional[T.List[build.BuildTarget]] = None,
- capture: T.Optional[bool] = None,
- feed: T.Optional[bool] = None,
+ capture: T.Optional[str] = None,
+ feed: T.Optional[str] = None,
force_serialize: bool = False,
- env: T.Optional[build.EnvironmentVariables] = None,
+ env: T.Optional[mesonlib.EnvironmentVariables] = None,
verbose: bool = False) -> T.Tuple[T.Sequence[T.Union[str, File, build.Target, programs.ExternalProgram]], str]:
'''
Serialize an executable for running with a generator or a custom target
return es.cmd_args, ''
args: T.List[str] = []
if capture:
- args += ['--capture', str(capture)]
+ args += ['--capture', capture]
if feed:
- args += ['--feed', str(feed)]
+ args += ['--feed', feed]
return (
self.environment.get_build_command() + ['--internal', 'exe'] + args + ['--'] + es.cmd_args,
if isinstance(exe, (programs.ExternalProgram,
build.BuildTarget, build.CustomTarget)):
- basename = exe.name
+ basename = os.path.basename(exe.name)
elif isinstance(exe, mesonlib.File):
basename = os.path.basename(exe.fname)
else:
@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 not isinstance(dep, (dependencies.ExternalLibrary, dependencies.PkgConfigDependency)):
+ if dep.type_name not in {'library', 'pkgconfig'}:
continue
for libpath in dep.link_args:
# For all link args that are absolute paths to a library file, add RPATH args
if libdir in self.get_external_rpath_dirs(target):
continue
# Windows doesn't support rpaths, but we use this function to
- # emulate rpaths by setting PATH, so also accept DLLs here
- if os.path.splitext(libpath)[1] not in ['.dll', '.lib', '.so', '.dylib']:
+ # emulate rpaths by setting PATH
+ # .dll is there for mingw gcc
+ if os.path.splitext(libpath)[1] not in {'.dll', '.lib', '.so', '.dylib'}:
continue
- if libdir.startswith(self.environment.get_source_dir()):
- rel_to_src = libdir[len(self.environment.get_source_dir()) + 1:]
+
+ try:
+ commonpath = os.path.commonpath((libdir, srcdir))
+ except ValueError: # when paths are on different drives on Windows
+ commonpath = ''
+
+ if commonpath == srcdir:
+ 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:
def _determine_ext_objs(self, extobj: 'build.ExtractedObjects', proj_dir_to_build_root: str) -> T.List[str]:
result: T.List[str] = []
+ targetdir = self.get_target_private_dir(extobj.target)
+
# Merge sources and generated sources
raw_sources = list(extobj.srclist)
for gensrc in extobj.genlist:
elif self.environment.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(proj_dir_to_build_root, targetdir, objname))
+
# extobj could contain only objects and no sources
if not sources:
return result
- targetdir = self.get_target_private_dir(extobj.target)
-
# With unity builds, sources don't map directly to objects,
# we only support extracting all the objects in this mode,
# so just return all object files.
if comp.language in LANGS_CANT_UNITY:
sources += srcs
continue
- for i in range(len(srcs) // unity_size + 1):
+ for i in range((len(srcs) + unity_size - 1) // unity_size):
_src = self.get_unity_source_file(extobj.target,
comp.get_default_suffix(), i)
sources.append(_src)
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:
+ # Same name as in create_msvc_pch_implementation() below.
+ return f'meson_pch-{lang}.obj'
+ return os.path.splitext(pch[1])[0] + '.obj'
+
def create_msvc_pch_implementation(self, target: build.BuildTarget, lang: str, pch_header: str) -> str:
# We have to include the language in the file name, otherwise
# pch.c and pch.cpp will both end up as pch.obj in VS backends.
mesonlib.replace_if_different(pch_file, pch_file_tmp)
return pch_rel_to_build
+ def target_uses_pch(self, target: build.BuildTarget) -> bool:
+ try:
+ return T.cast('bool', target.get_option(OptionKey('b_pch')))
+ except KeyError:
+ return False
+
@staticmethod
def escape_extra_args(args: T.List[str]) -> T.List[str]:
# all backslashes in defines are doubly-escaped
continue
if compiler.language == 'vala':
- if isinstance(dep, dependencies.PkgConfigDependency):
+ 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(('>=', '==')):
paths.update(cc.get_library_dirs(self.environment))
return list(paths)
+ @staticmethod
+ @lru_cache(maxsize=None)
+ def search_dll_path(link_arg: str) -> T.Optional[str]:
+ if link_arg.startswith(('-l', '-L')):
+ link_arg = link_arg[2:]
+
+ p = Path(link_arg)
+ if not p.is_absolute():
+ return None
+
+ try:
+ p = p.resolve(strict=True)
+ except FileNotFoundError:
+ return None
+
+ for f in p.parent.glob('*.dll'):
+ # path contains dlls
+ return str(p.parent)
+
+ if p.is_file():
+ p = p.parent
+ # Heuristic: replace *last* occurence of '/lib'
+ binpath = Path('/bin'.join(p.as_posix().rsplit('/lib', maxsplit=1)))
+ for _ in binpath.glob('*.dll'):
+ return str(binpath)
+
+ return None
+
+ @classmethod
+ @lru_cache(maxsize=None)
+ def extract_dll_paths(cls, target: build.BuildTarget) -> T.Set[str]:
+ """Find paths to all DLLs needed for a given target, since
+ we link against import libs, and we don't know the actual
+ path of the DLLs.
+
+ 1. If there are DLLs in the same directory than the .lib dir, use it
+ 2. If there is a sibbling directory named 'bin' with DLLs in it, use it
+ """
+ results = set()
+ for dep in target.external_deps:
+
+ if dep.type_name == 'pkgconfig':
+ # If by chance pkg-config knows the bin dir...
+ bindir = dep.get_variable(pkgconfig='bindir', default_value='')
+ if bindir:
+ results.add(bindir)
+ continue
+
+ results.update(filter(None, map(cls.search_dll_path, dep.link_args))) # pylint: disable=bad-builtin
+
+ for i in chain(target.link_targets, target.link_whole_targets):
+ if isinstance(i, build.BuildTarget):
+ results.update(cls.extract_dll_paths(i))
+
+ return results
+
def determine_windows_extra_paths(
self, target: T.Union[build.BuildTarget, build.CustomTarget, programs.ExternalProgram, mesonlib.File, str],
extra_bdeps: T.Sequence[T.Union[build.BuildTarget, build.CustomTarget]]) -> T.List[str]:
if isinstance(target, build.BuildTarget):
prospectives.update(target.get_transitive_link_deps())
# External deps
- for deppath in self.rpaths_for_non_system_absolute_shared_libraries(target, exclude_system=False):
- result.add(os.path.normpath(os.path.join(self.environment.get_build_dir(), deppath)))
+ result.update(self.extract_dll_paths(target))
+
for bdep in extra_bdeps:
prospectives.add(bdep)
if isinstance(bdep, build.BuildTarget):
if isinstance(exe, build.CustomTarget):
extra_bdeps = list(exe.get_transitive_build_target_deps())
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 = []
depends.add(a)
elif isinstance(a, build.CustomTargetIndex):
depends.add(a.target)
- if isinstance(a, build.BuildTarget):
- extra_paths += self.determine_windows_extra_paths(a, [])
if isinstance(a, mesonlib.File):
a = os.path.join(self.environment.get_build_dir(), a.rel_to_builddir(self.build_to_src))
return outputs
def generate_depmf_install(self, d: InstallData) -> None:
- if self.build.dep_manifest_name is None:
- return
+ depmf_path = self.build.dep_manifest_name
+ if depmf_path is None:
+ option_dir = self.environment.coredata.get_option(OptionKey('licensedir'))
+ assert isinstance(option_dir, str), 'for mypy'
+ if option_dir:
+ depmf_path = os.path.join(option_dir, 'depmf.json')
+ else:
+ return
ifilename = os.path.join(self.environment.get_build_dir(), 'depmf.json')
- ofilename = os.path.join(self.environment.get_prefix(), self.build.dep_manifest_name)
- out_name = os.path.join('{prefix}', self.build.dep_manifest_name)
+ ofilename = os.path.join(self.environment.get_prefix(), depmf_path)
+ odirname = os.path.join(self.environment.get_prefix(), os.path.dirname(depmf_path))
+ out_name = os.path.join('{prefix}', depmf_path)
+ out_dir = os.path.join('{prefix}', os.path.dirname(depmf_path))
mfobj = {'type': 'dependency manifest', 'version': '1.0',
'projects': {k: v.to_json() for k, v in self.build.dep_manifest.items()}}
with open(ifilename, 'w', encoding='utf-8') as f:
# Copy file from, to, and with mode unchanged
d.data.append(InstallDataBase(ifilename, ofilename, out_name, None, '',
tag='devel', data_type='depmf'))
+ for m in self.build.dep_manifest.values():
+ for ifilename, name in m.license_files:
+ ofilename = os.path.join(odirname, name.relative_name())
+ out_name = os.path.join(out_dir, name.relative_name())
+ d.data.append(InstallDataBase(ifilename, ofilename, out_name, None,
+ m.subproject, tag='devel', data_type='depmf'))
def get_regen_filelist(self) -> T.List[str]:
'''List of all files whose alteration means that the build
srcs += fname
return srcs
- def get_custom_target_depend_files(self, target: build.CustomTarget, absolute_paths: bool = False) -> T.List[str]:
+ def get_target_depend_files(self, target: T.Union[build.CustomTarget, build.BuildTarget], absolute_paths: bool = False) -> T.List[str]:
deps: T.List[str] = []
for i in target.depend_files:
if isinstance(i, mesonlib.File):
cmd = [i.replace('\\', '/') for i in cmd]
return inputs, outputs, cmd
- def get_run_target_env(self, target: build.RunTarget) -> build.EnvironmentVariables:
- env = target.env if target.env else build.EnvironmentVariables()
+ def get_run_target_env(self, target: build.RunTarget) -> mesonlib.EnvironmentVariables:
+ env = target.env if target.env else mesonlib.EnvironmentVariables()
if target.default_env:
introspect_cmd = join_args(self.environment.get_build_command() + ['introspect'])
env.set('MESON_SOURCE_ROOT', [self.environment.get_source_dir()])
mlog.log(f'Running postconf script {name!r}')
run_exe(s, env)
- @lru_cache(maxsize=1)
def create_install_data(self) -> InstallData:
strip_bin = self.environment.lookup_binary_entry(MachineChoice.HOST, 'strip')
if strip_bin is None:
outdir_name = os.path.join('{includedir}', subdir)
for f in h.get_sources():
- if not isinstance(f, File):
- raise MesonException(f'Invalid header type {f!r} can\'t be installed')
abspath = f.absolute_path(srcdir, builddir)
- i = InstallDataBase(abspath, outdir, outdir_name, h.get_custom_install_mode(), h.subproject, tag='devel')
+ i = InstallDataBase(abspath, outdir, outdir_name, h.get_custom_install_mode(), h.subproject, tag='devel', follow_symlinks=h.follow_symlinks)
d.headers.append(i)
def generate_man_install(self, d: InstallData) -> None:
assert isinstance(de, build.Data)
subdir = de.install_dir
subdir_name = de.install_dir_name
- if not subdir:
- subdir = os.path.join(self.environment.get_datadir(), self.interpreter.build.project_name)
- subdir_name = os.path.join('{datadir}', self.interpreter.build.project_name)
for src_file, dst_name in zip(de.sources, de.rename):
assert isinstance(src_file, mesonlib.File)
dst_abs = os.path.join(subdir, dst_name)
dstdir_name = os.path.join(subdir_name, dst_name)
tag = de.install_tag or self.guess_install_tag(dst_abs)
i = InstallDataBase(src_file.absolute_path(srcdir, builddir), dst_abs, dstdir_name,
- de.install_mode, de.subproject, tag=tag, data_type=de.data_type)
+ de.install_mode, de.subproject, tag=tag, data_type=de.data_type,
+ follow_symlinks=de.follow_symlinks)
d.data.append(i)
def generate_symlink_install(self, d: InstallData) -> None:
dst_dir = os.path.join(dst_dir, os.path.basename(src_dir))
dst_name = os.path.join(dst_name, os.path.basename(src_dir))
tag = sd.install_tag or self.guess_install_tag(os.path.join(sd.install_dir, 'dummy'))
- i = SubdirInstallData(src_dir, dst_dir, dst_name, sd.install_mode, sd.exclude, sd.subproject, tag)
+ i = SubdirInstallData(src_dir, dst_dir, dst_name, sd.install_mode, sd.exclude, sd.subproject, tag,
+ follow_symlinks=sd.follow_symlinks)
d.install_subdirs.append(i)
def get_introspection_data(self, target_id: str, target: build.Target) -> T.List['TargetIntrospectionData']:
return []
- def get_devenv(self) -> build.EnvironmentVariables:
- env = build.EnvironmentVariables()
+ def get_devenv(self) -> mesonlib.EnvironmentVariables:
+ env = mesonlib.EnvironmentVariables()
extra_paths = set()
library_paths = set()
build_machine = self.environment.machines[MachineChoice.BUILD]
from . import backends
from .. import modules
-from ..modules import gnome
from .. import environment, mesonlib
from .. import build
from .. import mlog
from .. import compilers
from ..arglist import CompilerArgs
from ..compilers import Compiler
-from ..linkers import ArLinker, RSPFileSyntax
+from ..linkers import ArLikeLinker, RSPFileSyntax
from ..mesonlib import (
- File, LibType, MachineChoice, MesonException, OrderedSet, PerMachine,
+ File, LibType, MachineChoice, MesonBugException, MesonException, OrderedSet, PerMachine,
ProgressBar, quote_arg
)
from ..mesonlib import get_compiler_for_source, has_path_sep, OptionKey
from typing_extensions import Literal
from .._typing import ImmutableListProtocol
- from ..build import ExtractedObjects
+ from ..build import ExtractedObjects, LibTypes
from ..interpreter import Interpreter
- from ..linkers import DynamicLinker, StaticLinker
+ from ..linkers.linkers import DynamicLinker, StaticLinker
from ..compilers.cs import CsCompiler
from ..compilers.fortran import FortranCompiler
+ CommandArgOrStr = T.List[T.Union['NinjaCommandArg', str]]
RUST_EDITIONS = Literal['2015', '2018', '2021']
FORTRAN_SUBMOD_PAT = r"^\s*\bsubmodule\b\s*\((\w+:?\w+)\)\s*(\w+)"
FORTRAN_USE_PAT = r"^\s*use,?\s*(?:non_intrinsic)?\s*(?:::)?\s*(\w+)"
-def cmd_quote(s):
+def cmd_quote(arg: str) -> str:
# see: https://docs.microsoft.com/en-us/windows/desktop/api/shellapi/nf-shellapi-commandlinetoargvw#remarks
# backslash escape any existing double quotes
# any existing backslashes preceding a quote are doubled
- s = re.sub(r'(\\*)"', lambda m: '\\' * (len(m.group(1)) * 2 + 1) + '"', s)
+ arg = re.sub(r'(\\*)"', lambda m: '\\' * (len(m.group(1)) * 2 + 1) + '"', arg)
# any terminal backslashes likewise need doubling
- s = re.sub(r'(\\*)$', lambda m: '\\' * (len(m.group(1)) * 2), s)
+ arg = re.sub(r'(\\*)$', lambda m: '\\' * (len(m.group(1)) * 2), arg)
# and double quote
- s = f'"{s}"'
+ arg = f'"{arg}"'
- return s
+ return arg
-def gcc_rsp_quote(s):
+def gcc_rsp_quote(s: str) -> str:
# see: the function buildargv() in libiberty
#
# this differs from sh-quoting in that a backslash *always* escapes the
rmfile_prefix = ['rm', '-f', '{}', '&&']
-def get_rsp_threshold():
+def get_rsp_threshold() -> int:
'''Return a conservative estimate of the commandline size in bytes
above which a response file should be used. May be overridden for
debugging by setting environment variable MESON_RSP_THRESHOLD.'''
# single argument; see MAX_ARG_STRLEN
limit = 131072
# Be conservative
- limit = limit / 2
+ limit = limit // 2
return int(os.environ.get('MESON_RSP_THRESHOLD', limit))
# a conservative estimate of the command-line length limit
NINJA_QUOTE_BUILD_PAT = re.compile(r"[$ :\n]")
NINJA_QUOTE_VAR_PAT = re.compile(r"[$ \n]")
-def ninja_quote(text: str, is_build_line=False) -> str:
+def ninja_quote(text: str, is_build_line: bool = False) -> str:
if is_build_line:
quote_re = NINJA_QUOTE_BUILD_PAT
else:
none = 3
class NinjaCommandArg:
- def __init__(self, s, quoting = Quoting.both):
+ def __init__(self, s: str, quoting: Quoting = Quoting.both) -> None:
self.s = s
self.quoting = quoting
- def __str__(self):
+ def __str__(self) -> str:
return self.s
@staticmethod
- def list(l, q):
+ def list(l: T.List[str], q: Quoting) -> T.List[NinjaCommandArg]:
return [NinjaCommandArg(i, q) for i in l]
+@dataclass
class NinjaComment:
- def __init__(self, comment):
- self.comment = comment
+ comment: str
- def write(self, outfile):
+ def write(self, outfile: T.TextIO) -> None:
for l in self.comment.split('\n'):
outfile.write('# ')
outfile.write(l)
outfile.write('\n')
class NinjaRule:
- def __init__(self, rule, command, args, description,
- rspable = False, deps = None, depfile = None, extra = None,
+ def __init__(self, rule: str, command: CommandArgOrStr, args: CommandArgOrStr,
+ description: str, rspable: bool = False, deps: T.Optional[str] = None,
+ depfile: T.Optional[str] = None, extra: T.Optional[str] = None,
rspfile_quote_style: RSPFileSyntax = RSPFileSyntax.GCC):
- def strToCommandArg(c):
+ def strToCommandArg(c: T.Union[NinjaCommandArg, str]) -> NinjaCommandArg:
if isinstance(c, NinjaCommandArg):
return c
return NinjaCommandArg(c)
self.name = rule
- self.command = [strToCommandArg(c) for c in command] # includes args which never go into a rspfile
- self.args = [strToCommandArg(a) for a in args] # args which will go into a rspfile, if used
+ self.command: T.List[NinjaCommandArg] = [strToCommandArg(c) for c in command] # includes args which never go into a rspfile
+ self.args: T.List[NinjaCommandArg] = [strToCommandArg(a) for a in args] # args which will go into a rspfile, if used
self.description = description
self.deps = deps # depstyle 'gcc' or 'msvc'
self.depfile = depfile
# fallthrough
return ninja_quote(qf(str(x)))
- def write(self, outfile):
+ def write(self, outfile: T.TextIO) -> None:
+ rspfile_args = self.args
if self.rspfile_quote_style is RSPFileSyntax.MSVC:
rspfile_quote_func = cmd_quote
+ rspfile_args = [NinjaCommandArg('$in_newline', arg.quoting) if arg.s == '$in' else arg for arg in rspfile_args]
else:
rspfile_quote_func = gcc_rsp_quote
if rsp == '_RSP':
outfile.write(' command = {} @$out.rsp\n'.format(' '.join([self._quoter(x) for x in self.command])))
outfile.write(' rspfile = $out.rsp\n')
- outfile.write(' rspfile_content = {}\n'.format(' '.join([self._quoter(x, rspfile_quote_func) for x in self.args])))
+ outfile.write(' rspfile_content = {}\n'.format(' '.join([self._quoter(x, rspfile_quote_func) for x in rspfile_args])))
else:
outfile.write(' command = {}\n'.format(' '.join([self._quoter(x) for x in self.command + self.args])))
if self.deps:
return estimate
class NinjaBuildElement:
- def __init__(self, all_outputs, outfilenames, rulename, infilenames, implicit_outs=None):
+ def __init__(self, all_outputs: T.Set[str], outfilenames, rulename, infilenames, implicit_outs=None):
self.implicit_outfilenames = implicit_outs or []
if isinstance(outfilenames, str):
self.outfilenames = [outfilenames]
self.all_outputs = all_outputs
self.output_errors = ''
- def add_dep(self, dep):
+ def add_dep(self, dep: T.Union[str, T.List[str]]) -> None:
if isinstance(dep, list):
self.deps.update(dep)
else:
else:
self.orderdeps.add(dep)
- def add_item(self, name, elems):
+ def add_item(self, name: str, elems: T.Union[str, T.List[str, CompilerArgs]]) -> None:
# Always convert from GCC-style argument naming to the naming used by the
# current compiler. Also filter system include paths, deduplicate, etc.
if isinstance(elems, CompilerArgs):
if len(self.deps) > 0:
line += ' | ' + ' '.join([ninja_quote(x, True) for x in sorted(self.deps)])
if len(self.orderdeps) > 0:
- line += ' || ' + ' '.join([ninja_quote(x, True) for x in sorted(self.orderdeps)])
+ orderdeps = [str(x) for x in self.orderdeps]
+ line += ' || ' + ' '.join([ninja_quote(x, True) for x in sorted(orderdeps)])
line += '\n'
# This is the only way I could find to make this work on all
# platforms including Windows command shell. Slash is a dir separator
for n in self.outfilenames:
if n in self.all_outputs:
self.output_errors = f'Multiple producers for Ninja target "{n}". Please rename your targets.'
- self.all_outputs[n] = True
+ self.all_outputs.add(n)
@dataclass
class RustDep:
self.name = 'ninja'
self.ninja_filename = 'build.ninja'
self.fortran_deps = {}
- self.all_outputs = {}
+ self.all_outputs: T.Set[str] = set()
self.introspection_data = {}
self.created_llvm_ir_rule = PerMachine(False, False)
self.rust_crates: T.Dict[str, RustCrate] = {}
+ self.implicit_meson_outs = []
- def create_phony_target(self, all_outputs, dummy_outfile, rulename, phony_infilename, implicit_outs=None):
+ def create_phony_target(self, dummy_outfile: str, rulename: str, phony_infilename: str) -> NinjaBuildElement:
'''
We need to use aliases for targets that might be used as directory
names to workaround a Ninja bug that breaks `ninja -t clean`.
raise AssertionError(f'Invalid usage of create_phony_target with {dummy_outfile!r}')
to_name = f'meson-internal__{dummy_outfile}'
- elem = NinjaBuildElement(all_outputs, dummy_outfile, 'phony', to_name)
+ elem = NinjaBuildElement(self.all_outputs, dummy_outfile, 'phony', to_name)
self.add_build(elem)
- return NinjaBuildElement(all_outputs, to_name, rulename, phony_infilename, implicit_outs)
+ return NinjaBuildElement(self.all_outputs, to_name, rulename, phony_infilename)
def detect_vs_dep_prefix(self, tempfilename):
'''VS writes its dependency in a locale dependent format.
for compiler in self.environment.coredata.compilers.host.values():
# Have to detect the dependency format
- # IFort on windows is MSVC like, but doesn't have /showincludes
- if compiler.language == 'fortran':
+ # IFort / masm on windows is MSVC like, but doesn't have /showincludes
+ if compiler.language in {'fortran', 'masm'}:
continue
if compiler.id == 'pgi' and mesonlib.is_windows():
# for the purpose of this function, PGI doesn't act enough like MSVC
else:
# None of our compilers are MSVC, we're done.
return open(tempfilename, 'a', encoding='utf-8')
+ filebase = 'incdetect.' + compilers.lang_suffixes[compiler.language][0]
filename = os.path.join(self.environment.get_scratch_dir(),
- 'incdetect.c')
+ filebase)
with open(filename, 'w', encoding='utf-8') as f:
f.write(dedent('''\
#include<stdio.h>
# Python strings leads to failure. We _must_ do this detection
# in raw byte mode and write the result in raw bytes.
pc = subprocess.Popen(compiler.get_exelist() +
- ['/showIncludes', '/c', 'incdetect.c'],
+ ['/showIncludes', '/c', filebase],
cwd=self.environment.get_scratch_dir(),
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(stdout, stderr) = pc.communicate()
# We want to match 'Note: including file: ' in the line
# 'Note: including file: d:\MyDir\include\stdio.h', however
# different locales have different messages with a different
- # number of colons. Match up to the the drive name 'd:\'.
+ # number of colons. Match up to the drive name 'd:\'.
# When used in cross compilation, the path separator is a
# forward slash rather than a backslash so handle both; i.e.
# the path is /MyDir/include/stdio.h.
raise MesonException(f'Could not determine vs dep dependency prefix string. output: {stderr} {stdout}')
- def generate(self):
+ def generate(self, capture: bool = False, vslite_ctx: dict = None) -> T.Optional[dict]:
+ if vslite_ctx:
+ # 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.build.need_vsenv:
+ if self.environment.coredata.get_option(OptionKey('vsenv')):
builddir = Path(self.environment.get_build_dir())
try:
# For prettier printing, reduce to a relative path. If
self.build_elements = []
self.generate_phony()
self.add_build_comment(NinjaComment('Build rules for targets'))
+
+ # Optionally capture compile args per target, for later use (i.e. VisStudio project's NMake intellisense include dirs, defines, and compile options).
+ if capture:
+ captured_compile_args_per_target = {}
+ for target in self.build.get_targets().values():
+ if isinstance(target, build.BuildTarget):
+ captured_compile_args_per_target[target.get_id()] = self.generate_common_compile_args_per_src_type(target)
+
for t in ProgressBar(self.build.get_targets().values(), desc='Generating targets'):
self.generate_target(t)
+ mlog.log_timestamp("Targets generated")
self.add_build_comment(NinjaComment('Test rules'))
self.generate_tests()
+ mlog.log_timestamp("Tests generated")
self.add_build_comment(NinjaComment('Install rules'))
self.generate_install()
+ mlog.log_timestamp("Install generated")
self.generate_dist()
+ mlog.log_timestamp("Dist generated")
key = OptionKey('b_coverage')
if (key in self.environment.coredata.options and
self.environment.coredata.options[key].value):
- gcovr_exe, gcovr_version, lcov_exe, genhtml_exe, _ = environment.find_coverage_tools()
+ gcovr_exe, gcovr_version, lcov_exe, lcov_version, genhtml_exe, _ = environment.find_coverage_tools()
if gcovr_exe or (lcov_exe and genhtml_exe):
self.add_build_comment(NinjaComment('Coverage rules'))
self.generate_coverage_rules(gcovr_exe, gcovr_version)
+ mlog.log_timestamp("Coverage rules generated")
else:
# FIXME: since we explicitly opted in, should this be an error?
# The docs just say these targets will be created "if possible".
mlog.warning('Need gcovr or lcov/genhtml to generate any coverage reports')
self.add_build_comment(NinjaComment('Suffix'))
self.generate_utils()
+ mlog.log_timestamp("Utils generated")
self.generate_ending()
self.write_rules(outfile)
os.replace(tempfilename, outfilename)
mlog.cmd_ci_include(outfilename) # For CI debugging
# Refresh Ninja's caches. https://github.com/ninja-build/ninja/pull/1685
- if mesonlib.version_compare(self.ninja_version, '>=1.10.0') and os.path.exists('.ninja_deps'):
- subprocess.call(self.ninja_command + ['-t', 'restat'])
- subprocess.call(self.ninja_command + ['-t', 'cleandead'])
+ if mesonlib.version_compare(self.ninja_version, '>=1.10.0') and os.path.exists(os.path.join(self.environment.build_dir, '.ninja_log')):
+ subprocess.call(self.ninja_command + ['-t', 'restat'], cwd=self.environment.build_dir)
+ subprocess.call(self.ninja_command + ['-t', 'cleandead'], cwd=self.environment.build_dir)
self.generate_compdb()
self.generate_rust_project_json()
+ if capture:
+ return captured_compile_args_per_target
+
def generate_rust_project_json(self) -> None:
"""Generate a rust-analyzer compatible rust-project.json file."""
if not self.rust_crates:
return False
return True
- def create_target_source_introspection(self, target: build.Target, comp: compilers.Compiler, parameters, sources, generated_sources):
+ def create_target_source_introspection(self, target: build.Target, comp: compilers.Compiler, parameters, sources, generated_sources,
+ unity_sources: T.Optional[T.List[mesonlib.FileOrString]] = None):
'''
Adds the source file introspection information for a language of a target
- Internal introspection storage formart:
+ Internal introspection storage format:
self.introspection_data = {
'<target ID>': {
<id tuple>: {
'parameters': parameters,
'sources': [],
'generated_sources': [],
+ 'unity_sources': [],
}
tgt[id_hash] = src_block
- # Make source files absolute
- sources = [x.absolute_path(self.source_dir, self.build_dir) if isinstance(x, File) else os.path.normpath(os.path.join(self.build_dir, x))
- for x in sources]
- generated_sources = [x.absolute_path(self.source_dir, self.build_dir) if isinstance(x, File) else os.path.normpath(os.path.join(self.build_dir, x))
- for x in generated_sources]
- # Add the source files
- src_block['sources'] += sources
- src_block['generated_sources'] += generated_sources
+
+ def compute_path(file: mesonlib.FileOrString) -> str:
+ """ Make source files absolute """
+ if isinstance(file, File):
+ return file.absolute_path(self.source_dir, self.build_dir)
+ return os.path.normpath(os.path.join(self.build_dir, file))
+
+ src_block['sources'].extend(compute_path(x) for x in sources)
+ src_block['generated_sources'].extend(compute_path(x) for x in generated_sources)
+ if unity_sources:
+ src_block['unity_sources'].extend(compute_path(x) for x in unity_sources)
+
+ def create_target_linker_introspection(self, target: build.Target, linker: T.Union[Compiler, StaticLinker], parameters):
+ tid = target.get_id()
+ tgt = self.introspection_data[tid]
+ lnk_hash = tuple(parameters)
+ lnk_block = tgt.get(lnk_hash, None)
+ if lnk_block is None:
+ if isinstance(parameters, CompilerArgs):
+ parameters = parameters.to_native(copy=True)
+
+ if isinstance(linker, Compiler):
+ linkers = linker.get_linker_exelist()
+ else:
+ linkers = linker.get_exelist()
+
+ lnk_block = {
+ 'linker': linkers,
+ 'parameters': parameters,
+ }
+ tgt[lnk_hash] = lnk_block
def generate_target(self, target):
try:
self.generate_swift_target(target)
return
- # Pre-existing target C/C++ sources to be built; dict of full path to
+ # CompileTarget compiles all its sources and does not do a final link.
+ # This is, for example, a preprocessor.
+ is_compile_target = isinstance(target, build.CompileTarget)
+
+ # Preexisting target C/C++ sources to be built; dict of full path to
# source relative to build root and the original File object.
target_sources: T.MutableMapping[str, File]
generated_sources: T.MutableMapping[str, File]
# List of sources that have been transpiled from a DSL (like Vala) into
- # a language that is haneled below, such as C or C++
+ # a language that is handled below, such as C or C++
transpiled_sources: T.List[str]
if 'vala' in target.compilers:
mlog.log(mlog.red('FIXME'), msg)
# Get a list of all generated headers that will be needed while building
- # this target's sources (generated sources and pre-existing sources).
+ # this target's sources (generated sources and preexisting sources).
# This will be set as dependencies of all the target's sources. At the
# same time, also deal with generated sources that need to be compiled.
generated_source_files = []
obj_list.append(rel_src)
elif self.environment.is_library(rel_src) or modules.is_module_library(rel_src):
pass
+ elif is_compile_target:
+ generated_source_files.append(raw_src)
else:
# Assume anything not specifically a source file is a header. This is because
# people generate files with weird suffixes (.inc, .fh) that they then include
if s.split('.')[-1] in compilers.lang_suffixes['d']:
d_generated_deps.append(o)
- use_pch = self.environment.coredata.options.get(OptionKey('b_pch'))
+ use_pch = self.target_uses_pch(target)
if use_pch and target.has_pch():
pch_objects = self.generate_pch(target, header_deps=header_deps)
else:
obj_targets = [t for t in od if t.uses_fortran()]
obj_list.extend(o)
- fortran_order_deps = [self.get_target_filename(t) for t in obj_targets]
+ fortran_order_deps = [File(True, *os.path.split(self.get_target_filename(t))) for t in obj_targets]
fortran_inc_args: T.List[str] = []
if target.uses_fortran():
fortran_inc_args = mesonlib.listify([target.compilers['fortran'].get_include_args(
o, s = self.generate_single_compile(target, src, 'vala', [], header_deps)
obj_list.append(o)
- # Generate compile targets for all the pre-existing sources for this target
+ # Generate compile targets for all the preexisting sources for this target
for src in target_sources.values():
- if not self.environment.is_header(src):
+ 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)
if is_unity:
for src in self.generate_unity_files(target, unity_src):
o, s = self.generate_single_compile(target, src, True, unity_deps + header_deps + d_generated_deps,
- fortran_order_deps, fortran_inc_args)
+ fortran_order_deps, fortran_inc_args, unity_src)
obj_list.append(o)
compiled_sources.append(s)
source2object[s] = o
- if isinstance(target, build.CompileTarget):
+ if is_compile_target:
# Skip the link stage for this special type of target
return
linker, stdlib_args = self.determine_linker_and_stdlib_args(target)
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, generated_source_files, fortran_order_deps)
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.
+ if isinstance(target, build.SharedLibrary) and self.environment.machines[target.for_machine].is_aix():
+ if target.aix_so_archive:
+ elem = NinjaBuildElement(self.all_outputs, linker.get_archive_name(outname), 'AIX_LINKER', [outname])
+ self.add_build(elem)
def should_use_dyndeps_for_target(self, target: 'build.BuildTarget') -> bool:
if mesonlib.version_compare(self.ninja_version, '<1.10.0'):
return True
if 'cpp' not in target.compilers:
return False
- if '-fmodules-ts' in target.extra_args.get('cpp', []):
+ if '-fmodules-ts' in target.extra_args['cpp']:
return True
# Currently only the preview version of Visual Studio is supported.
cpp = target.compilers['cpp']
if cpp.get_id() != 'msvc':
return False
- cppversion = self.environment.coredata.options[OptionKey('std', machine=target.for_machine, lang='cpp')].value
+ cppversion = target.get_option(OptionKey('std', machine=target.for_machine, lang='cpp'))
if cppversion not in ('latest', 'c++latest', 'vc++latest'):
return False
if not mesonlib.current_vs_supports_modules():
return False
return True
- def generate_dependency_scan_target(self, target, compiled_sources, source2object, generated_source_files: T.List[mesonlib.File],
- object_deps: T.List[str]) -> None:
+ def generate_dependency_scan_target(self, target: build.BuildTarget, compiled_sources, source2object, generated_source_files: T.List[mesonlib.File],
+ object_deps: T.List['mesonlib.FileOrString']) -> None:
if not self.should_use_dyndeps_for_target(target):
return
depscan_file = self.get_dep_scan_file_for(target)
rule_name = 'depscan'
scan_sources = self.select_sources_to_scan(compiled_sources)
- # Dump the sources as a json list. This avoids potential probllems where
- # the number of sources passed to depscan exceedes the limit imposed by
+ # Dump the sources as a json list. This avoids potential problems where
+ # the number of sources passed to depscan exceeds the limit imposed by
# the OS.
with open(json_abs, 'w', encoding='utf-8') as f:
json.dump(scan_sources, f)
deps.append(os.path.join(self.get_target_dir(i), output))
return deps
- def generate_custom_target(self, target):
+ def generate_custom_target(self, target: build.CustomTarget):
self.custom_target_generator_inputs(target)
(srcs, ofilenames, cmd) = self.eval_custom_target_command(target)
deps = self.unwrap_dep_list(target)
- deps += self.get_custom_target_depend_files(target)
+ deps += self.get_target_depend_files(target)
if target.build_always_stale:
deps.append('PHONY')
if target.depfile is None:
elem.add_item('pool', 'console')
full_name = Path(target.subdir, target.name).as_posix()
elem.add_item('COMMAND', cmd)
- elem.add_item('description', f'Generating {full_name} with a custom command{cmd_type}')
+ elem.add_item('description', target.description.format(full_name) + cmd_type)
self.add_build(elem)
self.processed_targets.add(target.get_id())
env=target_env,
verbose=True)
cmd_type = f' (wrapped by meson {reason})' if reason else ''
- elem = self.create_phony_target(self.all_outputs, target_name, 'CUSTOM_COMMAND', [])
+ elem = self.create_phony_target(target_name, 'CUSTOM_COMMAND', [])
elem.add_item('COMMAND', meson_exe_cmd)
elem.add_item('description', f'Running external command {target.name}{cmd_type}')
elem.add_item('pool', 'console')
deps = self.unwrap_dep_list(target)
- deps += self.get_custom_target_depend_files(target)
+ deps += self.get_target_depend_files(target)
elem.add_dep(deps)
self.add_build(elem)
self.processed_targets.add(target.get_id())
(['--use_llvm_cov'] if use_llvm_cov else []))
def generate_coverage_rules(self, gcovr_exe: T.Optional[str], gcovr_version: T.Optional[str]):
- e = self.create_phony_target(self.all_outputs, 'coverage', 'CUSTOM_COMMAND', 'PHONY')
+ e = self.create_phony_target('coverage', 'CUSTOM_COMMAND', 'PHONY')
self.generate_coverage_command(e, [])
e.add_item('description', 'Generates coverage reports')
self.add_build(e)
self.generate_coverage_legacy_rules(gcovr_exe, gcovr_version)
def generate_coverage_legacy_rules(self, gcovr_exe: T.Optional[str], gcovr_version: T.Optional[str]):
- e = self.create_phony_target(self.all_outputs, 'coverage-html', 'CUSTOM_COMMAND', 'PHONY')
+ e = self.create_phony_target('coverage-html', 'CUSTOM_COMMAND', 'PHONY')
self.generate_coverage_command(e, ['--html'])
e.add_item('description', 'Generates HTML coverage report')
self.add_build(e)
if gcovr_exe:
- e = self.create_phony_target(self.all_outputs, 'coverage-xml', 'CUSTOM_COMMAND', 'PHONY')
+ e = self.create_phony_target('coverage-xml', 'CUSTOM_COMMAND', 'PHONY')
self.generate_coverage_command(e, ['--xml'])
e.add_item('description', 'Generates XML coverage report')
self.add_build(e)
- e = self.create_phony_target(self.all_outputs, 'coverage-text', 'CUSTOM_COMMAND', 'PHONY')
+ e = self.create_phony_target('coverage-text', 'CUSTOM_COMMAND', 'PHONY')
self.generate_coverage_command(e, ['--text'])
e.add_item('description', 'Generates text coverage report')
self.add_build(e)
if mesonlib.version_compare(gcovr_version, '>=4.2'):
- e = self.create_phony_target(self.all_outputs, 'coverage-sonarqube', 'CUSTOM_COMMAND', 'PHONY')
+ e = self.create_phony_target('coverage-sonarqube', 'CUSTOM_COMMAND', 'PHONY')
self.generate_coverage_command(e, ['--sonarqube'])
e.add_item('description', 'Generates Sonarqube XML coverage report')
self.add_build(e)
def generate_install(self):
self.create_install_data_files()
- elem = self.create_phony_target(self.all_outputs, 'install', 'CUSTOM_COMMAND', 'PHONY')
+ elem = self.create_phony_target('install', 'CUSTOM_COMMAND', 'PHONY')
elem.add_dep('all')
elem.add_item('DESC', 'Installing files.')
elem.add_item('COMMAND', self.environment.get_build_command() + ['install', '--no-rebuild'])
cmd += ['--no-stdsplit']
if self.environment.coredata.get_option(OptionKey('errorlogs')):
cmd += ['--print-errorlogs']
- elem = self.create_phony_target(self.all_outputs, 'test', 'CUSTOM_COMMAND', ['all', 'PHONY'])
+ elem = self.create_phony_target('test', 'CUSTOM_COMMAND', ['all', 'PHONY'])
elem.add_item('COMMAND', cmd)
elem.add_item('DESC', 'Running all tests.')
elem.add_item('pool', 'console')
cmd = self.environment.get_build_command(True) + [
'test', '--benchmark', '--logbase',
'benchmarklog', '--num-processes=1', '--no-rebuild']
- elem = self.create_phony_target(self.all_outputs, 'benchmark', 'CUSTOM_COMMAND', ['all', 'PHONY'])
+ elem = self.create_phony_target('benchmark', 'CUSTOM_COMMAND', ['all', 'PHONY'])
elem.add_item('COMMAND', cmd)
elem.add_item('DESC', 'Running benchmark suite.')
elem.add_item('pool', 'console')
['--internal',
'regenerate',
self.environment.get_source_dir(),
- self.environment.get_build_dir()]
+ # Ninja always runs from the build_dir. This includes cases where the user moved the
+ # build directory and invalidated most references. Make sure it still regenerates.
+ '.']
self.add_rule(NinjaRule('REGENERATE_BUILD',
c, [],
'Regenerating build files.',
extra='generator = 1'))
- def add_rule_comment(self, comment):
+ def add_rule_comment(self, comment: NinjaComment) -> None:
self.rules.append(comment)
- def add_build_comment(self, comment):
+ def add_build_comment(self, comment: NinjaComment) -> None:
self.build_elements.append(comment)
- def add_rule(self, rule):
+ def add_rule(self, rule: NinjaRule) -> None:
if rule.name in self.ruledict:
raise MesonException(f'Tried to add rule {rule.name} twice.')
self.rules.append(rule)
self.ruledict[rule.name] = rule
- def add_build(self, build):
+ def add_build(self, build: NinjaBuildElement) -> None:
build.check_outputs()
self.build_elements.append(build)
if build.rulename in self.ruledict:
build.rule = self.ruledict[build.rulename]
else:
- mlog.warning(f"build statement for {build.outfilenames} references non-existent rule {build.rulename}")
+ mlog.warning(f"build statement for {build.outfilenames} references nonexistent rule {build.rulename}")
- def write_rules(self, outfile):
+ def write_rules(self, outfile: T.TextIO) -> None:
for b in self.build_elements:
if isinstance(b, NinjaBuildElement):
b.count_rule_references()
for r in self.rules:
r.write(outfile)
- def write_builds(self, outfile):
+ def write_builds(self, outfile: T.TextIO) -> None:
for b in ProgressBar(self.build_elements, desc='Writing build.ninja'):
b.write(outfile)
+ mlog.log_timestamp("build.ninja generated")
- def generate_phony(self):
+ def generate_phony(self) -> None:
self.add_build_comment(NinjaComment('Phony build target, always out of date'))
elem = NinjaBuildElement(self.all_outputs, 'PHONY', 'phony', '')
self.add_build(elem)
compiler = target.compilers['cs']
rel_srcs = [os.path.normpath(s.rel_to_builddir(self.build_to_src)) for s in src_list]
deps = []
- commands = compiler.compiler_args(target.extra_args.get('cs', []))
+ commands = compiler.compiler_args(target.extra_args['cs'])
commands += compiler.get_buildtype_args(buildtype)
commands += compiler.get_optimization_args(target.get_option(OptionKey('optimization')))
commands += compiler.get_debug_args(target.get_option(OptionKey('debug')))
T.Tuple[T.MutableMapping[str, File], T.MutableMapping]]:
"""
Splits the target's sources into .vala, .gs, .vapi, and other sources.
- Handles both pre-existing and generated sources.
+ Handles both preexisting and generated sources.
Returns a tuple (vala, vapi, others) each of which is a dictionary with
the keys being the path to the file (relative to the build directory)
vapi: T.MutableMapping[str, File] = OrderedDict()
others: T.MutableMapping[str, File] = OrderedDict()
othersgen: T.MutableMapping[str, File] = OrderedDict()
- # Split pre-existing sources
+ # Split preexisting sources
for s in t.get_sources():
# BuildTarget sources are always mesonlib.File files which are
# either in the source root, or generated with configure_file and
valac_outputs.append(vala_c_file)
args = self.generate_basic_compiler_args(target, valac)
- args += valac.get_colorout_args(self.environment.coredata.options.get(OptionKey('b_colorout')).value)
+ args += valac.get_colorout_args(target.get_option(OptionKey('b_colorout')))
# Tell Valac to output everything in our private directory. Sadly this
# means it will also preserve the directory components of Vala sources
# found inside the build tree (generated sources).
# Without this, it will write it inside c_out_dir
args += ['--vapi', os.path.join('..', target.vala_vapi)]
valac_outputs.append(vapiname)
- target.outputs += [target.vala_header, target.vala_vapi]
- target.install_tag += ['devel', 'devel']
# 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()
girname = os.path.join(self.get_target_dir(target), target.vala_gir)
args += ['--gir', os.path.join('..', target.vala_gir)]
valac_outputs.append(girname)
- target.outputs.append(target.vala_gir)
- target.install_tag.append('devel')
# 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')
# Detect gresources and add --gresources arguments for each
for gensrc in other_src[1].values():
- if isinstance(gensrc, gnome.GResourceTarget):
+ if isinstance(gensrc, modules.GResourceTarget):
gres_xml, = self.get_custom_target_sources(gensrc)
args += ['--gresources=' + gres_xml]
- extra_args = []
-
- for a in target.extra_args.get('vala', []):
- if isinstance(a, File):
- relname = a.rel_to_builddir(self.build_to_src)
- extra_dep_files.append(relname)
- extra_args.append(relname)
- else:
- extra_args.append(a)
dependency_vapis = self.determine_dep_vapis(target)
extra_dep_files += dependency_vapis
- args += extra_args
+ extra_dep_files.extend(self.get_target_depend_files(target))
+ args += target.get_extra_args('vala')
element = NinjaBuildElement(self.all_outputs, valac_outputs,
self.compiler_to_rule_name(valac),
all_files + dependency_vapis)
ext = target.get_option(OptionKey('language', machine=target.for_machine, lang='cython'))
+ pyx_sources = [] # Keep track of sources we're adding to build
+
for src in target.get_sources():
if src.endswith('.pyx'):
output = os.path.join(self.get_target_private_dir(target), f'{src}.{ext}')
- args = args.copy()
- args += cython.get_output_args(output)
element = NinjaBuildElement(
self.all_outputs, [output],
self.compiler_to_rule_name(cython),
self.add_build(element)
# TODO: introspection?
cython_sources.append(output)
+ pyx_sources.append(element)
else:
static_sources[src.rel_to_builddir(self.build_to_src)] = src
+ header_deps = [] # Keep track of generated headers for those sources
for gen in target.get_generated_sources():
for ssrc in gen.get_outputs():
if isinstance(gen, GeneratedList):
else:
ssrc = os.path.join(gen.get_subdir(), ssrc)
if ssrc.endswith('.pyx'):
- args = args.copy()
output = os.path.join(self.get_target_private_dir(target), f'{ssrc}.{ext}')
- args += cython.get_output_args(output)
element = NinjaBuildElement(
self.all_outputs, [output],
self.compiler_to_rule_name(cython),
[ssrc])
element.add_item('ARGS', args)
self.add_build(element)
+ pyx_sources.append(element)
# TODO: introspection?
cython_sources.append(output)
else:
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 \
+ not modules.is_module_library(ssrc):
+ header_deps.append(ssrc)
+ for source in pyx_sources:
+ source.add_orderdep(header_deps)
return static_sources, generated_sources, cython_sources
return orderdeps, first_file
def _add_rust_project_entry(self, name: str, main_rust_file: str, args: CompilerArgs,
- from_subproject: bool, is_proc_macro: bool,
- output: str, deps: T.List[RustDep]) -> None:
+ from_subproject: bool, proc_macro_dylib_path: T.Optional[str],
+ deps: T.List[RustDep]) -> None:
raw_edition: T.Optional[str] = mesonlib.first(reversed(args), lambda x: x.startswith('--edition'))
edition: RUST_EDITIONS = '2015' if not raw_edition else raw_edition.split('=')[-1]
arg_itr: T.Iterator[str] = iter(args)
for arg in arg_itr:
if arg == '--cfg':
- cfg.append(next(arg))
+ cfg.append(next(arg_itr))
elif arg.startswith('--cfg'):
cfg.append(arg[len('--cfg'):])
deps,
cfg,
is_workspace_member=not from_subproject,
- is_proc_macro=is_proc_macro,
- proc_macro_dylib_path=output if is_proc_macro else None,
+ is_proc_macro=proc_macro_dylib_path is not None,
+ proc_macro_dylib_path=proc_macro_dylib_path,
)
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('-', '_')
+
def generate_rust_target(self, target: build.BuildTarget) -> None:
rustc = target.compilers['rust']
# Rust compiler takes only the main file as input and
self.generate_generator_list_rules(target)
# dependencies need to cause a relink, they're not just for ordering
- deps = [
- os.path.join(t.subdir, t.get_filename())
- for t in itertools.chain(target.link_targets, target.link_whole_targets)
- ]
+ deps: T.List[str] = []
# Dependencies for rust-project.json
project_deps: T.List[RustDep] = []
if main_rust_file is None:
raise RuntimeError('A Rust target has no Rust sources. This is weird. Also a bug. Please report')
target_name = os.path.join(target.subdir, target.get_filename())
- if isinstance(target, build.Executable):
- cratetype = 'bin'
- elif hasattr(target, 'rust_crate_type'):
- cratetype = target.rust_crate_type
- elif isinstance(target, build.SharedLibrary):
- cratetype = 'dylib'
- elif isinstance(target, build.StaticLibrary):
- cratetype = 'rlib'
- else:
- raise InvalidArguments('Unknown target type for rustc.')
+ cratetype = target.rust_crate_type
args.extend(['--crate-type', cratetype])
# If we're dynamically linking, add those arguments
args.extend(rustc.get_linker_always_args())
args += self.generate_basic_compiler_args(target, rustc, False)
- # Rustc replaces - with _. spaces are not allowed, so we replace them with underscores
- args += ['--crate-name', target.name.replace('-', '_').replace(' ', '_')]
+ # Rustc replaces - with _. spaces or dots are not allowed, so we replace them with underscores
+ args += ['--crate-name', target.name.replace('-', '_').replace(' ', '_').replace('.', '_')]
depfile = os.path.join(target.subdir, target.name + '.d')
- args += ['--emit', f'dep-info={depfile}', '--emit', 'link']
+ args += ['--emit', f'dep-info={depfile}', '--emit', f'link={target_name}']
+ args += ['--out-dir', self.get_target_private_dir(target)]
+ args += ['-C', 'metadata=' + target.get_id()]
args += target.get_extra_args('rust')
- output = rustc.get_output_args(os.path.join(target.subdir, target.get_filename()))
- args += output
+
+ # 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 = target.get_option(OptionKey('buildtype'))
+ crt = target.get_option(OptionKey('b_vscrt'))
+ args += rustc.get_crt_link_args(crt, buildtype)
+ except KeyError:
+ pass
+
+ if mesonlib.version_compare(rustc.version, '>= 1.67.0'):
+ verbatim = '+verbatim'
+ else:
+ verbatim = ''
+
+ def _link_library(libname: str, static: bool, bundle: bool = False):
+ type_ = 'static' if static else 'dylib'
+ modifiers = []
+ if not bundle and static:
+ modifiers.append('-bundle')
+ if verbatim:
+ modifiers.append(verbatim)
+ if modifiers:
+ type_ += ':' + ','.join(modifiers)
+ args.append(f'-l{type_}={libname}')
+
linkdirs = mesonlib.OrderedSet()
external_deps = target.external_deps.copy()
- for d in itertools.chain(target.link_targets, target.link_whole_targets):
+ target_deps = target.get_dependencies()
+ for d in target_deps:
linkdirs.add(d.subdir)
- if d.uses_rust():
+ deps.append(self.get_dependency_filename(d))
+ if d.uses_rust_abi():
+ if d not in itertools.chain(target.link_targets, target.link_whole_targets):
+ # Indirect Rust ABI dependency, we only need its path in linkdirs.
+ continue
# specify `extern CRATE_NAME=OUTPUT_FILE` for each Rust
# dependency, so that collisions with libraries in rustc's
# sysroot don't cause ambiguity
- args += ['--extern', '{}={}'.format(d.name, os.path.join(d.subdir, d.filename))]
- project_deps.append(RustDep(d.name, self.rust_crates[d.name].order))
- elif d.typename == 'static library':
- # Rustc doesn't follow Meson's convention that static libraries
- # are called .a, and import libraries are .lib, so we have to
- # manually handle that.
- if rustc.linker.id in {'link', 'lld-link'}:
- args += ['-C', f'link-arg={self.get_target_filename_for_linking(d)}']
- else:
- args += ['-l', f'static={d.name}']
+ d_name = self._get_rust_dependency_name(target, d)
+ args += ['--extern', '{}={}'.format(d_name, os.path.join(d.subdir, d.filename))]
+ project_deps.append(RustDep(d_name, self.rust_crates[d.name].order))
+ continue
+
+ # Link a C ABI library
+
+ if isinstance(d, build.StaticLibrary):
external_deps.extend(d.external_deps)
+
+ # Pass native libraries directly to the linker with "-C link-arg"
+ # because rustc's "-l:+verbatim=" is not portable and we cannot rely
+ # on linker to find the right library without using verbatim filename.
+ # For example "-lfoo" won't find "foo.so" in the case name_prefix set
+ # to "", or would always pick the shared library when both "libfoo.so"
+ # and "libfoo.a" are available.
+ # See https://doc.rust-lang.org/rustc/command-line-arguments.html#linking-modifiers-verbatim.
+ #
+ # However, rustc static linker (rlib and staticlib) requires using
+ # "-l" argument and does not rely on platform specific dynamic linker.
+ lib = self.get_target_filename_for_linking(d)
+ link_whole = d in target.link_whole_targets
+ if isinstance(target, build.StaticLibrary):
+ static = isinstance(d, build.StaticLibrary)
+ libname = os.path.basename(lib) if verbatim else d.name
+ _link_library(libname, 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]
else:
- # Rust uses -l for non rust dependencies, but we still need to
- # add dylib=foo
- args += ['-l', f'dylib={d.name}']
+ args.append(f'-Clink-arg={lib}')
+
for e in external_deps:
for a in e.get_link_args():
- if a.endswith(('.dll', '.so', '.dylib')):
- dir_, lib = os.path.split(a)
- linkdirs.add(dir_)
- lib, ext = os.path.splitext(lib)
- if lib.startswith('lib'):
- lib = lib[3:]
- args.extend(['-l', f'dylib={lib}'])
+ if a in rustc.native_static_libs:
+ # Exclude link args that rustc already add by default
+ pass
elif a.startswith('-L'):
args.append(a)
- elif a.startswith('-l'):
- _type = 'static' if e.static else 'dylib'
- args.extend(['-l', f'{_type}={a[2:]}'])
+ elif a.endswith(('.dll', '.so', '.dylib', '.a', '.lib')) and isinstance(target, build.StaticLibrary):
+ dir_, lib = 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}')
+
for d in linkdirs:
- if d == '':
- d = '.'
- args += ['-L', d]
- has_shared_deps = any(isinstance(dep, build.SharedLibrary) for dep in target.get_dependencies())
- if isinstance(target, build.SharedLibrary) or has_shared_deps:
+ d = d or '.'
+ args.append(f'-L{d}')
+
+ # Because of the way rustc links, this must come after any potential
+ # library need to link with their stdlibs (C++ and Fortran, for example)
+ args.extend(f'-Clink-arg={a}' for a in target.get_used_stdlib_args('rust'))
+
+ has_shared_deps = any(isinstance(dep, build.SharedLibrary) for dep in target_deps)
+ has_rust_shared_deps = any(dep.uses_rust()
+ and dep.rust_crate_type == 'dylib'
+ for dep in target_deps)
+
+ if cratetype in {'dylib', 'proc-macro'} or has_rust_shared_deps:
# add prefer-dynamic if any of the Rust libraries we link
- # against are dynamic, otherwise we'll end up with
- # multiple implementations of crates
+ # against are dynamic or this is a dynamic library itself,
+ # otherwise we'll end up with multiple implementations of libstd.
args += ['-C', 'prefer-dynamic']
+ if isinstance(target, build.SharedLibrary) or has_shared_deps:
# build the usual rpath arguments as well...
# Set runtime-paths so we can run executables without needing to set
for rpath_arg in rpath_args:
args += ['-C', 'link-arg=' + rpath_arg + ':' + os.path.join(rustc.get_sysroot(), 'lib')]
- self._add_rust_project_entry(target.name, main_rust_file, args, bool(target.subproject),
- #XXX: There is a fix for this pending
- getattr(target, 'rust_crate_type', '') == 'procmacro',
- output, project_deps)
+ proc_macro_dylib_path = None
+ if getattr(target, 'rust_crate_type', '') == 'proc-macro':
+ proc_macro_dylib_path = os.path.abspath(os.path.join(target.subdir, target.get_filename()))
+
+ self._add_rust_project_entry(target.name,
+ os.path.abspath(os.path.join(self.environment.build_dir, main_rust_file)),
+ args,
+ bool(target.subproject),
+ proc_macro_dylib_path,
+ project_deps)
compiler_name = self.compiler_to_rule_name(rustc)
element = NinjaBuildElement(self.all_outputs, target_name, compiler_name, main_rust_file)
rsp_file_syntax() is only guaranteed to be implemented if
can_linker_accept_rsp() returns True.
"""
- options = dict(rspable=tool.can_linker_accept_rsp())
+ options = {'rspable': tool.can_linker_accept_rsp()}
if options['rspable']:
options['rspfile_quote_style'] = tool.rsp_file_syntax()
return options
if static_linker is None:
continue
rule = 'STATIC_LINKER{}'.format(self.get_rule_suffix(for_machine))
- cmdlist = []
+ cmdlist: T.List[T.Union[str, NinjaCommandArg]] = []
args = ['$in']
# FIXME: Must normalize file names with pathlib.Path before writing
# them out to fix this properly on Windows. See:
# https://github.com/mesonbuild/meson/issues/1517
# https://github.com/mesonbuild/meson/issues/1526
- if isinstance(static_linker, ArLinker) and not mesonlib.is_windows():
+ if isinstance(static_linker, ArLikeLinker) and not mesonlib.is_windows():
# `ar` has no options to overwrite archives. It always appends,
# which is never what we want. Delete an existing library first if
# it exists. https://github.com/mesonbuild/meson/issues/1355
cmdlist += static_linker.get_exelist()
cmdlist += ['$LINK_ARGS']
cmdlist += NinjaCommandArg.list(static_linker.get_output_args('$out'), Quoting.none)
+ # The default ar on MacOS (at least through version 12), does not
+ # add extern'd variables to the symbol table by default, and
+ # requires that apple's ranlib be called with a special flag
+ # instead after linking
+ if static_linker.id == 'applear':
+ # This is a bit of a hack, but we assume that that we won't need
+ # an rspfile on MacOS, otherwise the arguments are passed to
+ # ranlib, not to ar
+ cmdlist.extend(args)
+ args = []
+ # Ensure that we use the user-specified ranlib if any, and
+ # fallback to just picking up some ranlib otherwise
+ ranlib = self.environment.lookup_binary_entry(for_machine, 'ranlib')
+ if ranlib is None:
+ ranlib = ['ranlib']
+ cmdlist.extend(['&&'] + ranlib + ['-c', '$out'])
description = 'Linking static target $out'
if num_pools > 0:
pool = 'pool = link_pool'
options = self._rsp_options(compiler)
self.add_rule(NinjaRule(rule, command, args, description, **options, extra=pool))
+ if self.environment.machines[for_machine].is_aix():
+ rule = 'AIX_LINKER{}'.format(self.get_rule_suffix(for_machine))
+ description = 'Archiving AIX shared library'
+ cmdlist = compiler.get_command_to_archive_shlib()
+ args = []
+ options = {}
+ self.add_rule(NinjaRule(rule, cmdlist, args, description, **options, extra=None))
args = self.environment.get_build_command() + \
['--internal',
def generate_cython_compile_rules(self, compiler: 'Compiler') -> None:
rule = self.compiler_to_rule_name(compiler)
- command = compiler.get_exelist() + ['$ARGS', '$in']
description = 'Compiling Cython source $in'
- self.add_rule(NinjaRule(rule, command, [], description, extra='restat = 1'))
+ command = compiler.get_exelist()
+
+ depargs = compiler.get_dependency_gen_args('$out', '$DEPFILE')
+ depfile = '$out.dep' if depargs else None
+
+ args = depargs + ['$ARGS', '$in']
+ args += NinjaCommandArg.list(compiler.get_output_args('$out'), Quoting.none)
+ self.add_rule(NinjaRule(rule, command + args, [],
+ description,
+ depfile=depfile,
+ extra='restat = 1'))
def generate_rust_compile_rules(self, compiler):
rule = self.compiler_to_rule_name(compiler)
self.generate_cython_compile_rules(compiler)
return
crstr = self.get_rule_suffix(compiler.for_machine)
+ options = self._rsp_options(compiler)
if langname == 'fortran':
self.generate_fortran_dep_hack(crstr)
+ # gfortran does not update the modification time of *.mod files, therefore restat is needed.
+ # See also: https://github.com/ninja-build/ninja/pull/2275
+ options['extra'] = 'restat = 1'
rule = self.compiler_to_rule_name(compiler)
depargs = NinjaCommandArg.list(compiler.get_dependency_gen_args('$out', '$DEPFILE'), Quoting.none)
command = compiler.get_exelist()
else:
deps = 'gcc'
depfile = '$DEPFILE'
- options = self._rsp_options(compiler)
self.add_rule(NinjaRule(rule, command, args, description, **options,
deps=deps, depfile=depfile))
output = []
else:
output = NinjaCommandArg.list(compiler.get_output_args('$out'), Quoting.none)
- command = compiler.get_exelist() + ['$ARGS'] + depargs + output + compiler.get_compile_only_args() + ['$in']
+
+ if 'mwcc' in compiler.id:
+ output[0].s = '-precompile'
+ command = compiler.get_exelist() + ['$ARGS'] + depargs + output + ['$in'] # '-c' must be removed
+ else:
+ command = compiler.get_exelist() + ['$ARGS'] + depargs + output + compiler.get_compile_only_args() + ['$in']
description = 'Precompiling header $in'
if compiler.get_argument_syntax() == 'msvc':
deps = 'msvc'
return args
def generate_genlist_for_target(self, genlist: build.GeneratedList, target: build.BuildTarget) -> None:
+ for x in genlist.depends:
+ if isinstance(x, build.GeneratedList):
+ self.generate_genlist_for_target(x, target)
generator = genlist.get_generator()
subdir = genlist.subdir
exe = generator.get_exe()
- exe_arr = self.build_target_to_cmd_array(exe)
infilelist = genlist.get_inputs()
outfilelist = genlist.get_outputs()
- extra_dependencies = self.get_custom_target_depend_files(genlist)
+ extra_dependencies = self.get_target_depend_files(genlist)
for i, curfile in enumerate(infilelist):
if len(generator.outputs) == 1:
sole_output = os.path.join(self.get_target_private_dir(target), outfilelist[i])
else:
sole_output = f'{curfile}'
- infilename = curfile.rel_to_builddir(self.build_to_src)
+ infilename = curfile.rel_to_builddir(self.build_to_src, self.get_target_private_dir(target))
base_args = generator.get_arglist(infilename)
outfiles = genlist.get_outputs_for(curfile)
outfiles = [os.path.join(self.get_target_private_dir(target), of) for of in outfiles]
if len(generator.outputs) > 1:
outfilelist = outfilelist[len(generator.outputs):]
args = self.replace_paths(target, args, override_subdir=subdir)
- cmdlist = exe_arr + self.replace_extra_args(args, genlist)
- cmdlist, reason = self.as_meson_exe_cmdline(cmdlist[0], cmdlist[1:],
- capture=outfiles[0] if generator.capture else None)
+ cmdlist, reason = self.as_meson_exe_cmdline(exe,
+ self.replace_extra_args(args, genlist),
+ capture=outfiles[0] if generator.capture else None,
+ env=genlist.env)
abs_pdir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target))
os.makedirs(abs_pdir, exist_ok=True)
what = f'{sole_output!r}'
else:
# since there are multiple outputs, we log the source that caused the rebuild
- what = f'from {sole_output!r}.'
+ what = f'from {sole_output!r}'
if reason:
reason = f' (wrapped by meson {reason})'
- elem.add_item('DESC', f'Generating {what}{reason}.')
+ elem.add_item('DESC', f'Generating {what}{reason}')
if isinstance(exe, build.BuildTarget):
elem.add_dep(self.get_target_filename(exe))
# inside it. This can be either the final PDB (for, say,
# foo.exe) or an object pdb (for foo.obj). If the former, then
# each compilation step locks the pdb file for writing, which
- # is a bottleneck and object files from one target can not be
+ # is a bottleneck and object files from one target cannot be
# used in a different target. The latter seems to be the
# sensible one (and what Unix does) but there is a catch. If
# you try to use precompiled headers MSVC will error out
# has pdb file called foo.pdb. So will a static library
# foo.lib, which clobbers both foo.pdb _and_ the dll file's
# export library called foo.lib (by default, currently we name
- # them libfoo.a to avoidt this issue). You can give the files
+ # them libfoo.a to avoid this issue). You can give the files
# unique names such as foo_exe.pdb but VC also generates a
# bunch of other files which take their names from the target
# basename (i.e. "foo") and stomp on each other.
# this is actually doable, please send patches.
if target.has_pch():
- tfilename = self.get_target_filename_abs(target)
+ tfilename = self.get_target_debug_filename_abs(target)
+ if not tfilename:
+ tfilename = self.get_target_filename_abs(target)
return compiler.get_compile_debugfile_args(tfilename, pch=True)
else:
return compiler.get_compile_debugfile_args(objfile, pch=False)
- def get_link_debugfile_name(self, linker, target, outname):
- return linker.get_link_debugfile_name(outname)
+ def get_link_debugfile_name(self, linker, target) -> T.Optional[str]:
+ return linker.get_link_debugfile_name(self.get_target_debug_filename(target))
- def get_link_debugfile_args(self, linker, target, outname):
- return linker.get_link_debugfile_args(outname)
+ def get_link_debugfile_args(self, linker, target):
+ return linker.get_link_debugfile_args(self.get_target_debug_filename(target))
def generate_llvm_ir_compile(self, target, src):
base_proxy = target.get_options()
is_generated: bool = False) -> 'ImmutableListProtocol[str]':
# The code generated by valac is usually crap and has tons of unused
# variables and such, so disable warnings for Vala C sources.
- no_warn_args = (is_generated == 'vala')
+ no_warn_args = is_generated == 'vala'
# Add compiler args and include paths from several sources; defaults,
# build options, external dependencies, etc.
commands = self.generate_basic_compiler_args(target, compiler, no_warn_args)
commands += compiler.get_include_args(self.get_target_private_dir(target), False)
return commands
+ # Returns a dictionary, mapping from each compiler src type (e.g. 'c', 'cpp', etc.) to a list of compiler arg strings
+ # used for that respective src type.
+ # Currently used for the purpose of populating VisualStudio intellisense fields but possibly useful in other scenarios.
+ def generate_common_compile_args_per_src_type(self, target: build.BuildTarget) -> dict[str, list[str]]:
+ src_type_to_args = {}
+
+ use_pch = self.target_uses_pch(target)
+
+ for src_type_str in target.compilers.keys():
+ compiler = target.compilers[src_type_str]
+ commands = self._generate_single_compile_base_args(target, compiler)
+
+ # Include PCH header as first thing as it must be the first one or it will be
+ # ignored by gcc https://gcc.gnu.org/bugzilla/show_bug.cgi?id=100462
+ if use_pch and 'mw' not in compiler.id:
+ commands += self.get_pch_include_args(compiler, target)
+
+ commands += self._generate_single_compile_target_args(target, compiler, is_generated=False)
+
+ # Metrowerks compilers require PCH include args to come after intraprocedural analysis args
+ if use_pch and 'mw' in compiler.id:
+ commands += self.get_pch_include_args(compiler, target)
+
+ commands = commands.compiler.compiler_args(commands)
+
+ src_type_to_args[src_type_str] = commands.to_native()
+ return src_type_to_args
+
def generate_single_compile(self, target: build.BuildTarget, src,
is_generated=False, header_deps=None,
- order_deps: T.Optional[T.List[str]] = None,
- extra_args: T.Optional[T.List[str]] = None) -> None:
+ order_deps: T.Optional[T.List['mesonlib.FileOrString']] = None,
+ extra_args: T.Optional[T.List[str]] = None,
+ unity_sources: T.Optional[T.List[mesonlib.FileOrString]] = None) -> None:
"""
Compiles C/C++, ObjC/ObjC++, Fortran, and D sources
"""
# Include PCH header as first thing as it must be the first one or it will be
# ignored by gcc https://gcc.gnu.org/bugzilla/show_bug.cgi?id=100462
- if self.environment.coredata.options.get(OptionKey('b_pch')) and is_generated != 'pch':
+ use_pch = self.target_uses_pch(target) and is_generated != 'pch'
+ if use_pch and 'mw' not in compiler.id:
commands += self.get_pch_include_args(compiler, target)
commands += self._generate_single_compile_target_args(target, compiler, is_generated)
+
+ # Metrowerks compilers require PCH include args to come after intraprocedural analysis args
+ if use_pch and 'mw' in compiler.id:
+ commands += self.get_pch_include_args(compiler, target)
+
commands = commands.compiler.compiler_args(commands)
# Create introspection information
if is_generated is False:
- self.create_target_source_introspection(target, compiler, commands, [src], [])
+ self.create_target_source_introspection(target, compiler, commands, [src], [], unity_sources)
else:
- self.create_target_source_introspection(target, compiler, commands, [], [src])
+ self.create_target_source_introspection(target, compiler, commands, [], [src], unity_sources)
build_dir = self.environment.get_build_dir()
if isinstance(src, File):
commands += self.get_compile_debugfile_args(compiler, target, rel_obj)
# PCH handling
- if self.environment.coredata.options.get(OptionKey('b_pch')):
+ if self.target_uses_pch(target):
pchlist = target.get_pch(compiler.language)
else:
pchlist = []
assert isinstance(rel_src, str)
return (rel_obj, rel_src.replace('\\', '/'))
- def add_dependency_scanner_entries_to_element(self, target, compiler, element, src):
+ def add_dependency_scanner_entries_to_element(self, target: build.BuildTarget, compiler, element, src):
if not self.should_use_dyndeps_for_target(target):
return
+ if isinstance(target, build.CompileTarget):
+ return
extension = os.path.splitext(src.fname)[1][1:]
if extension != 'C':
extension = extension.lower()
element.add_item('dyndep', dep_scan_file)
element.add_orderdep(dep_scan_file)
- def get_dep_scan_file_for(self, target):
+ def get_dep_scan_file_for(self, target: build.BuildTarget) -> str:
return os.path.join(self.get_target_private_dir(target), 'depscan.dd')
def add_header_deps(self, target, ninja_element, header_deps):
d = os.path.join(self.get_target_private_dir(target), d)
ninja_element.add_dep(d)
- def has_dir_part(self, fname):
+ def has_dir_part(self, fname: mesonlib.FileOrString) -> bool:
# FIXME FIXME: The usage of this is a terrible and unreliable hack
if isinstance(fname, File):
return fname.subdir != ''
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):
+ 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):
header_deps = header_deps if header_deps is not None else []
pch_objects = []
elif compiler.id == 'intel':
# Intel generates on target generation
continue
+ elif 'mwcc' in compiler.id:
+ src = os.path.join(self.build_to_src, target.get_source_subdir(), pch[0])
+ (commands, dep, dst, objs) = self.generate_mwcc_pch_command(target, compiler, pch[0])
+ extradep = None
else:
src = os.path.join(self.build_to_src, target.get_source_subdir(), pch[0])
(commands, dep, dst, objs) = self.generate_gcc_pch_command(target, compiler, pch[0])
extradep = None
pch_objects += objs
rulename = self.compiler_to_pch_rule_name(compiler)
- elem = NinjaBuildElement(self.all_outputs, dst, rulename, src)
+ elem = NinjaBuildElement(self.all_outputs, objs + [dst], rulename, src)
if extradep is not None:
elem.add_dep(extradep)
self.add_header_deps(target, elem, header_deps)
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'):
+ 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):
- options = self.environment.coredata.options
- commands += linker.get_std_shared_module_link_args(options)
+ commands += linker.get_std_shared_module_link_args(target.get_options())
else:
commands += linker.get_std_shared_lib_link_args()
# All shared libraries are PIC
def get_target_type_link_args_post_dependencies(self, target, linker):
commands = []
if isinstance(target, build.Executable):
- # If gui_app is significant on this platform, add the appropriate linker arguments.
+ # If win_subsystem is significant on this platform, add the appropriate linker arguments.
# Unfortunately this can't be done in get_target_type_link_args, because some misguided
# libraries (such as SDL2) add -mwindows to their link flags.
m = self.environment.machines[target.for_machine]
if m.is_windows() or m.is_cygwin():
- if target.gui_app is not None:
- commands += linker.get_gui_app_args(target.gui_app)
- else:
- commands += linker.get_win_subsystem_args(target.win_subsystem)
+ commands += linker.get_win_subsystem_args(target.win_subsystem)
return commands
def get_link_whole_args(self, linker, target):
def guess_external_link_dependencies(self, linker, target, commands, internal):
# Ideally the linker would generate dependency information that could be used.
# But that has 2 problems:
- # * currently ld can not create dependency information in a way that ninja can use:
+ # * currently ld cannot create dependency information in a way that ninja can use:
# https://sourceware.org/bugzilla/show_bug.cgi?id=22843
# * Meson optimizes libraries from the same build using the symbol extractor.
# Just letting ninja use ld generated dependencies would undo this optimization.
commands += linker.get_buildtype_linker_args(target.get_option(OptionKey('buildtype')))
# Add /DEBUG and the pdb filename when using MSVC
if target.get_option(OptionKey('debug')):
- commands += self.get_link_debugfile_args(linker, target, outname)
- debugfile = self.get_link_debugfile_name(linker, target, outname)
+ commands += self.get_link_debugfile_args(linker, target)
+ debugfile = self.get_link_debugfile_name(linker, target)
if debugfile is not None:
implicit_outs += [debugfile]
# Add link args specific to this BuildTarget type, such as soname args,
#
# 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(self.environment.coredata.options)
+ commands += linker.get_option_link_args(target.get_options())
dep_targets = []
dep_targets.extend(self.guess_external_link_dependencies(linker, target, commands, internal))
elem = NinjaBuildElement(self.all_outputs, outname, linker_rule, obj_list, implicit_outs=implicit_outs)
elem.add_dep(dep_targets + custom_target_libraries)
elem.add_item('LINK_ARGS', commands)
+ self.create_target_linker_introspection(target, linker, commands)
return elem
def get_dependency_filename(self, t):
def generate_shlib_aliases(self, target, outdir):
for alias, to, tag in target.get_aliases():
- aliasfile = os.path.join(self.environment.get_build_dir(), outdir, alias)
+ aliasfile = os.path.join(outdir, alias)
+ abs_aliasfile = os.path.join(self.environment.get_build_dir(), outdir, alias)
try:
- os.remove(aliasfile)
+ os.remove(abs_aliasfile)
except Exception:
pass
try:
- os.symlink(to, aliasfile)
+ os.symlink(to, abs_aliasfile)
except NotImplementedError:
mlog.debug("Library versioning disabled because symlinks are not supported.")
except OSError:
mlog.debug("Library versioning disabled because we do not have symlink creation privileges.")
+ else:
+ self.implicit_meson_outs.append(aliasfile)
def generate_custom_target_clean(self, trees: T.List[str]) -> str:
- e = self.create_phony_target(self.all_outputs, 'clean-ctlist', 'CUSTOM_COMMAND', 'PHONY')
+ e = self.create_phony_target('clean-ctlist', 'CUSTOM_COMMAND', 'PHONY')
d = CleanTrees(self.environment.get_build_dir(), trees)
d_file = os.path.join(self.environment.get_scratch_dir(), 'cleantrees.dat')
e.add_item('COMMAND', self.environment.get_build_command() + ['--internal', 'cleantrees', d_file])
pickle.dump(d, ofile)
return 'clean-ctlist'
- def generate_gcov_clean(self):
- gcno_elem = self.create_phony_target(self.all_outputs, 'clean-gcno', 'CUSTOM_COMMAND', 'PHONY')
+ def generate_gcov_clean(self) -> None:
+ gcno_elem = self.create_phony_target('clean-gcno', 'CUSTOM_COMMAND', 'PHONY')
gcno_elem.add_item('COMMAND', mesonlib.get_meson_command() + ['--internal', 'delwithsuffix', '.', 'gcno'])
gcno_elem.add_item('description', 'Deleting gcno files')
self.add_build(gcno_elem)
- gcda_elem = self.create_phony_target(self.all_outputs, 'clean-gcda', 'CUSTOM_COMMAND', 'PHONY')
+ gcda_elem = self.create_phony_target('clean-gcda', 'CUSTOM_COMMAND', 'PHONY')
gcda_elem.add_item('COMMAND', mesonlib.get_meson_command() + ['--internal', 'delwithsuffix', '.', 'gcda'])
gcda_elem.add_item('description', 'Deleting gcda files')
self.add_build(gcda_elem)
# affect behavior in any other way.
return sorted(cmds)
- def generate_dist(self):
- elem = self.create_phony_target(self.all_outputs, 'dist', 'CUSTOM_COMMAND', 'PHONY')
+ def generate_dist(self) -> None:
+ elem = self.create_phony_target('dist', 'CUSTOM_COMMAND', 'PHONY')
elem.add_item('DESC', 'Creating source packages')
elem.add_item('COMMAND', self.environment.get_build_command() + ['dist'])
elem.add_item('pool', 'console')
self.add_build(elem)
- def generate_scanbuild(self):
+ def generate_scanbuild(self) -> None:
if not environment.detect_scanbuild():
return
if 'scan-build' in self.all_outputs:
return
cmd = self.environment.get_build_command() + \
- ['--internal', 'scanbuild', self.environment.source_dir, self.environment.build_dir] + \
+ ['--internal', 'scanbuild', self.environment.source_dir, self.environment.build_dir, self.build.get_subproject_dir()] + \
self.environment.get_build_command() + self.get_user_option_args()
- elem = self.create_phony_target(self.all_outputs, 'scan-build', 'CUSTOM_COMMAND', 'PHONY')
+ elem = self.create_phony_target('scan-build', 'CUSTOM_COMMAND', 'PHONY')
elem.add_item('COMMAND', cmd)
elem.add_item('pool', 'console')
self.add_build(elem)
- def generate_clangtool(self, name, extra_arg=None):
+ def generate_clangtool(self, name: str, extra_arg: T.Optional[str] = None) -> None:
target_name = 'clang-' + name
extra_args = []
if extra_arg:
cmd = self.environment.get_build_command() + \
['--internal', 'clang' + name, self.environment.source_dir, self.environment.build_dir] + \
extra_args
- elem = self.create_phony_target(self.all_outputs, target_name, 'CUSTOM_COMMAND', 'PHONY')
+ elem = self.create_phony_target(target_name, 'CUSTOM_COMMAND', 'PHONY')
elem.add_item('COMMAND', cmd)
elem.add_item('pool', 'console')
self.add_build(elem)
- def generate_clangformat(self):
+ def generate_clangformat(self) -> None:
if not environment.detect_clangformat():
return
self.generate_clangtool('format')
self.generate_clangtool('format', 'check')
- def generate_clangtidy(self):
+ def generate_clangtidy(self) -> None:
import shutil
if not shutil.which('clang-tidy'):
return
self.generate_clangtool('tidy')
+ self.generate_clangtool('tidy', 'fix')
- def generate_tags(self, tool, target_name):
+ def generate_tags(self, tool: str, target_name: str) -> None:
import shutil
if not shutil.which(tool):
return
return
cmd = self.environment.get_build_command() + \
['--internal', 'tags', tool, self.environment.source_dir]
- elem = self.create_phony_target(self.all_outputs, target_name, 'CUSTOM_COMMAND', 'PHONY')
+ elem = self.create_phony_target(target_name, 'CUSTOM_COMMAND', 'PHONY')
elem.add_item('COMMAND', cmd)
elem.add_item('pool', 'console')
self.add_build(elem)
# For things like scan-build and other helper tools we might have.
- def generate_utils(self):
+ def generate_utils(self) -> None:
self.generate_scanbuild()
self.generate_clangformat()
self.generate_clangtidy()
self.generate_tags('ctags', 'ctags')
self.generate_tags('cscope', 'cscope')
cmd = self.environment.get_build_command() + ['--internal', 'uninstall']
- elem = self.create_phony_target(self.all_outputs, 'uninstall', 'CUSTOM_COMMAND', 'PHONY')
+ elem = self.create_phony_target('uninstall', 'CUSTOM_COMMAND', 'PHONY')
elem.add_item('COMMAND', cmd)
elem.add_item('pool', 'console')
self.add_build(elem)
- def generate_ending(self):
+ def generate_ending(self) -> None:
for targ, deps in [
('all', self.get_build_by_default_targets()),
('meson-test-prereq', self.get_testlike_targets()),
for t in deps.values():
# 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.
+ if isinstance(t, build.SharedLibrary) and t.aix_so_archive:
+ if self.environment.machines[t.for_machine].is_aix():
+ linker, stdlib_args = self.determine_linker_and_stdlib_args(t)
+ t.get_outputs()[0] = linker.get_archive_name(t.get_outputs()[0])
targetlist.append(os.path.join(self.get_target_dir(t), t.get_outputs()[0]))
elem = NinjaBuildElement(self.all_outputs, targ, 'phony', targetlist)
self.add_build(elem)
- elem = self.create_phony_target(self.all_outputs, 'clean', 'CUSTOM_COMMAND', 'PHONY')
+ elem = self.create_phony_target('clean', 'CUSTOM_COMMAND', 'PHONY')
elem.add_item('COMMAND', self.ninja_command + ['-t', 'clean'])
elem.add_item('description', 'Cleaning')
elem.add_item('pool', 'console')
self.add_build(elem)
+ # If these files used to be explicitly created, they need to appear on the build graph somehow,
+ # otherwise cleandead deletes them. See https://github.com/ninja-build/ninja/issues/2299
+ if self.implicit_meson_outs:
+ elem = NinjaBuildElement(self.all_outputs, 'meson-implicit-outs', 'phony', self.implicit_meson_outs)
+ self.add_build(elem)
+
elem = NinjaBuildElement(self.all_outputs, 'reconfigure', 'REGENERATE_BUILD', 'PHONY')
elem.add_item('pool', 'console')
self.add_build(elem)
self.add_build(elem)
def get_introspection_data(self, target_id: str, target: build.Target) -> T.List[T.Dict[str, T.Union[bool, str, T.List[T.Union[str, T.Dict[str, T.Union[str, T.List[str], bool]]]]]]]:
- if target_id not in self.introspection_data or len(self.introspection_data[target_id]) == 0:
+ data = self.introspection_data.get(target_id)
+ if not data:
return super().get_introspection_data(target_id, target)
- result = []
- for i in self.introspection_data[target_id].values():
- result += [i]
- return result
+ return list(data.values())
def _scan_fortran_file_deps(src: Path, srcdir: Path, dirname: Path, tdeps, compiler) -> T.List[str]:
# a common occurrence, which would lead to lots of
# distracting noise.
continue
- srcfile = srcdir / tdeps[usename].fname # type: Path
+ srcfile = srcdir / tdeps[usename].fname
if not srcfile.is_file():
if srcfile.name != src.name: # generated source file
pass
ancestor_child = '_'.join(parents)
if ancestor_child not in tdeps:
raise MesonException("submodule {} relies on ancestor module {} that was not found.".format(submodmatch.group(2).lower(), ancestor_child.split('_', maxsplit=1)[0]))
- submodsrcfile = srcdir / tdeps[ancestor_child].fname # type: Path
+ submodsrcfile = srcdir / tdeps[ancestor_child].fname
if not submodsrcfile.is_file():
if submodsrcfile.name != src.name: # generated source file
pass
--- /dev/null
+# Copyright 2022 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 typing as T
+
+from .backends import Backend
+from .. import mlog
+from ..mesonlib import MesonBugException
+
+
+class NoneBackend(Backend):
+
+ name = 'none'
+
+ def generate(self, capture: bool = False, vslite_ctx: dict = None) -> T.Optional[dict]:
+ # Check for (currently) unexpected capture arg use cases -
+ if capture:
+ raise MesonBugException('We do not expect the none backend to generate with \'capture = True\'')
+ if vslite_ctx:
+ raise MesonBugException('We do not expect the none backend to be given a valid \'vslite_ctx\'')
+
+ if self.build.get_targets():
+ raise MesonBugException('None backend cannot generate target rules, but should have failed earlier.')
+ mlog.log('Generating simple install-only backend')
+ self.serialize_tests()
+ self.create_install_data_files()
import xml.etree.ElementTree as ET
import uuid
import typing as T
-from pathlib import Path, PurePath
+from pathlib import Path, PurePath, PureWindowsPath
import re
+from collections import Counter
from . import backends
from .. import build
-from .. import dependencies
from .. import mlog
from .. import compilers
+from .. import mesonlib
from ..mesonlib import (
- File, MesonException, replace_if_different, OptionKey, version_compare, MachineChoice
+ File, MesonBugException, MesonException, replace_if_different, OptionKey, version_compare, MachineChoice
)
from ..environment import Environment, build_filename
+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:
vs_version = os.getenv('VisualStudioVersion', None)
'Please specify the exact backend to use.'.format(vs_version, vs_install_dir))
-def split_o_flags_args(args):
+def split_o_flags_args(args: T.List[str]) -> T.List[str]:
"""
Splits any /O args and returns them. Does not take care of flags overriding
previous ones. Skips non-O flag arguments.
o_flags += ['/O' + f for f in flags]
return o_flags
-
-def generate_guid_from_path(path, path_type):
+def generate_guid_from_path(path, path_type) -> str:
return str(uuid.uuid5(uuid.NAMESPACE_URL, 'meson-vs-' + path_type + ':' + str(path))).upper()
def detect_microsoft_gdk(platform: str) -> bool:
return re.match(r'Gaming\.(Desktop|Xbox.XboxOne|Xbox.Scarlett)\.x64', platform, re.IGNORECASE)
+def filtered_src_langs_generator(sources: T.List[str]):
+ for src in sources:
+ ext = src.split('.')[-1]
+ if compilers.compilers.is_source_suffix(ext):
+ yield compilers.compilers.SUFFIX_TO_LANG[ext]
+
+# Returns the source language (i.e. a key from 'lang_suffixes') of the most frequent source language in the given
+# list of sources.
+# We choose the most frequent language as 'primary' because it means the most sources in a target/project can
+# simply refer to the project's shared intellisense define and include fields, rather than have to fill out their
+# own duplicate full set of defines/includes/opts intellisense fields. All of which helps keep the vcxproj file
+# size down.
+def get_primary_source_lang(target_sources: T.List[File], custom_sources: T.List[str]) -> T.Optional[str]:
+ lang_counts = Counter([compilers.compilers.SUFFIX_TO_LANG[src.suffix] for src in target_sources if compilers.compilers.is_source_suffix(src.suffix)])
+ lang_counts += Counter(filtered_src_langs_generator(custom_sources))
+ most_common_lang_list = lang_counts.most_common(1)
+ # It may be possible that we have a target with no actual src files of interest (e.g. a generator target),
+ # leaving us with an empty list, which we should handle -
+ return most_common_lang_list[0][0] if most_common_lang_list else None
+
+# Returns a dictionary (by [src type][build type]) that contains a tuple of -
+# (pre-processor defines, include paths, additional compiler options)
+# fields to use to fill in the respective intellisense fields of sources that can't simply
+# reference and re-use the shared 'primary' language intellisense fields of the vcxproj.
+def get_non_primary_lang_intellisense_fields(vslite_ctx: dict,
+ target_id: str,
+ primary_src_lang: str) -> T.Dict[str, T.Dict[str, T.Tuple[str, str, str]]]:
+ defs_paths_opts_per_lang_and_buildtype = {}
+ for buildtype in coredata.get_genvs_default_buildtype_list():
+ captured_build_args = vslite_ctx[buildtype][target_id] # Results in a 'Src types to compile args' dict
+ non_primary_build_args_per_src_lang = [(lang, build_args) for lang, build_args in captured_build_args.items() if lang != primary_src_lang] # Only need to individually populate intellisense fields for sources of non-primary types.
+ for src_lang, args_list in non_primary_build_args_per_src_lang:
+ if src_lang not in defs_paths_opts_per_lang_and_buildtype:
+ defs_paths_opts_per_lang_and_buildtype[src_lang] = {}
+ defs_paths_opts_per_lang_and_buildtype[src_lang][buildtype] = Vs2010Backend._extract_nmake_fields(args_list)
+ return defs_paths_opts_per_lang_and_buildtype
+
class Vs2010Backend(backends.Backend):
- def __init__(self, build: T.Optional[build.Build], interpreter: T.Optional[Interpreter]):
+
+ name = 'vs2010'
+
+ def __init__(self, build: T.Optional[build.Build], interpreter: T.Optional[Interpreter], gen_lite: bool = False):
super().__init__(build, interpreter)
- self.name = 'vs2010'
self.project_file_version = '10.0.30319.1'
self.sln_file_version = '11.00'
self.sln_version_comment = '2010'
self.windows_target_platform_version = None
self.subdirs = {}
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 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:
+ if isinstance(genlist, build.GeneratedList):
+ for x in genlist.depends:
+ self.generate_genlist_for_target(x, target, parent_node, [], [], [])
+ target_private_dir = self.relpath(self.get_target_private_dir(target), self.get_target_dir(target))
+ down = self.target_to_build_root(target)
+ if isinstance(genlist, (build.CustomTarget, build.CustomTargetIndex)):
+ for i in genlist.get_outputs():
+ # Path to the generated source from the current vcxproj dir via the build root
+ ipath = os.path.join(down, self.get_target_dir(genlist), i)
+ custom_target_output_files.append(ipath)
+ idir = self.relpath(self.get_target_dir(genlist), self.get_target_dir(target))
+ if idir not in custom_target_include_dirs:
+ custom_target_include_dirs.append(idir)
+ else:
+ generator = genlist.get_generator()
+ exe = generator.get_exe()
+ infilelist = genlist.get_inputs()
+ outfilelist = genlist.get_outputs()
+ source_dir = os.path.join(down, self.build_to_src, genlist.subdir)
+ idgroup = ET.SubElement(parent_node, 'ItemGroup')
+ samelen = len(infilelist) == len(outfilelist)
+ for i, curfile in enumerate(infilelist):
+ if samelen:
+ sole_output = os.path.join(target_private_dir, outfilelist[i])
+ else:
+ sole_output = ''
+ infilename = os.path.join(down, curfile.rel_to_builddir(self.build_to_src, target_private_dir))
+ deps = self.get_target_depend_files(genlist, True)
+ base_args = generator.get_arglist(infilename)
+ outfiles_rel = genlist.get_outputs_for(curfile)
+ outfiles = [os.path.join(target_private_dir, of) for of in outfiles_rel]
+ generator_output_files += outfiles
+ args = [x.replace("@INPUT@", infilename).replace('@OUTPUT@', sole_output)
+ for x in base_args]
+ args = self.replace_outputs(args, target_private_dir, outfiles_rel)
+ args = [x.replace("@SOURCE_DIR@", self.environment.get_source_dir())
+ .replace("@BUILD_DIR@", target_private_dir)
+ for x in args]
+ args = [x.replace("@CURRENT_SOURCE_DIR@", source_dir) for x in args]
+ args = [x.replace("@SOURCE_ROOT@", self.environment.get_source_dir())
+ .replace("@BUILD_ROOT@", self.environment.get_build_dir())
+ for x in args]
+ args = [x.replace('\\', '/') for x in args]
+ # Always use a wrapper because MSBuild eats random characters when
+ # there are many arguments.
+ tdir_abs = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target))
+ cmd, _ = self.as_meson_exe_cmdline(
+ exe,
+ self.replace_extra_args(args, genlist),
+ workdir=tdir_abs,
+ capture=outfiles[0] if generator.capture else None,
+ force_serialize=True,
+ env=genlist.env
+ )
+ deps = cmd[-1:] + deps
+ abs_pdir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target))
+ os.makedirs(abs_pdir, exist_ok=True)
+ cbs = ET.SubElement(idgroup, 'CustomBuild', Include=infilename)
+ ET.SubElement(cbs, 'Command').text = ' '.join(self.quote_arguments(cmd))
+ ET.SubElement(cbs, 'Outputs').text = ';'.join(outfiles)
+ ET.SubElement(cbs, 'AdditionalInputs').text = ';'.join(deps)
+
def generate_custom_generator_commands(self, target, parent_node):
generator_output_files = []
custom_target_include_dirs = []
custom_target_output_files = []
- target_private_dir = self.relpath(self.get_target_private_dir(target), self.get_target_dir(target))
- down = self.target_to_build_root(target)
for genlist in target.get_generated_sources():
- if isinstance(genlist, (build.CustomTarget, build.CustomTargetIndex)):
- for i in genlist.get_outputs():
- # Path to the generated source from the current vcxproj dir via the build root
- ipath = os.path.join(down, self.get_target_dir(genlist), i)
- custom_target_output_files.append(ipath)
- idir = self.relpath(self.get_target_dir(genlist), self.get_target_dir(target))
- if idir not in custom_target_include_dirs:
- custom_target_include_dirs.append(idir)
- else:
- generator = genlist.get_generator()
- exe = generator.get_exe()
- infilelist = genlist.get_inputs()
- outfilelist = genlist.get_outputs()
- source_dir = os.path.join(down, self.build_to_src, genlist.subdir)
- exe_arr = self.build_target_to_cmd_array(exe)
- idgroup = ET.SubElement(parent_node, 'ItemGroup')
- for i, curfile in enumerate(infilelist):
- if len(infilelist) == len(outfilelist):
- sole_output = os.path.join(target_private_dir, outfilelist[i])
- else:
- sole_output = ''
- infilename = os.path.join(down, curfile.rel_to_builddir(self.build_to_src))
- deps = self.get_custom_target_depend_files(genlist, True)
- base_args = generator.get_arglist(infilename)
- outfiles_rel = genlist.get_outputs_for(curfile)
- outfiles = [os.path.join(target_private_dir, of) for of in outfiles_rel]
- generator_output_files += outfiles
- args = [x.replace("@INPUT@", infilename).replace('@OUTPUT@', sole_output)
- for x in base_args]
- args = self.replace_outputs(args, target_private_dir, outfiles_rel)
- args = [x.replace("@SOURCE_DIR@", self.environment.get_source_dir())
- .replace("@BUILD_DIR@", target_private_dir)
- for x in args]
- args = [x.replace("@CURRENT_SOURCE_DIR@", source_dir) for x in args]
- args = [x.replace("@SOURCE_ROOT@", self.environment.get_source_dir())
- .replace("@BUILD_ROOT@", self.environment.get_build_dir())
- for x in args]
- args = [x.replace('\\', '/') for x in args]
- cmd = exe_arr + self.replace_extra_args(args, genlist)
- # Always use a wrapper because MSBuild eats random characters when
- # there are many arguments.
- tdir_abs = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target))
- cmd, _ = self.as_meson_exe_cmdline(
- cmd[0],
- cmd[1:],
- workdir=tdir_abs,
- capture=outfiles[0] if generator.capture else None,
- force_serialize=True
- )
- deps = cmd[-1:] + deps
- abs_pdir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target))
- os.makedirs(abs_pdir, exist_ok=True)
- cbs = ET.SubElement(idgroup, 'CustomBuild', Include=infilename)
- ET.SubElement(cbs, 'Command').text = ' '.join(self.quote_arguments(cmd))
- ET.SubElement(cbs, 'Outputs').text = ';'.join(outfiles)
- ET.SubElement(cbs, 'AdditionalInputs').text = ';'.join(deps)
+ self.generate_genlist_for_target(genlist, target, parent_node, generator_output_files, custom_target_include_dirs, custom_target_output_files)
return generator_output_files, custom_target_output_files, custom_target_include_dirs
- def generate(self):
+ def generate(self,
+ capture: bool = False,
+ vslite_ctx: dict = None) -> T.Optional[dict]:
+ # Check for (currently) unexpected capture arg use cases -
+ if capture:
+ raise MesonBugException('We do not expect any vs backend to generate with \'capture = True\'')
target_machine = self.interpreter.builtin['target_machine'].cpu_family_method(None, None)
if target_machine in {'64', 'x86_64'}:
# amd64 or x86_64
except MesonException:
self.sanitize = 'none'
sln_filename = os.path.join(self.environment.get_build_dir(), self.build.project_name + '.sln')
- projlist = self.generate_projects()
- self.gen_testproj('RUN_TESTS', os.path.join(self.environment.get_build_dir(), 'RUN_TESTS.vcxproj'))
- self.gen_installproj('RUN_INSTALL', os.path.join(self.environment.get_build_dir(), 'RUN_INSTALL.vcxproj'))
- self.gen_regenproj('REGEN', os.path.join(self.environment.get_build_dir(), 'REGEN.vcxproj'))
+ projlist = self.generate_projects(vslite_ctx)
+ self.gen_testproj()
+ self.gen_installproj()
+ self.gen_regenproj()
self.generate_solution(sln_filename, projlist)
self.generate_regen_info()
Vs2010Backend.touch_regen_timestamp(self.environment.get_build_dir())
ret.update(all_deps)
return ret
- def generate_solution_dirs(self, ofile, parents):
+ def generate_solution_dirs(self, ofile: str, parents: T.Sequence[Path]) -> None:
prj_templ = 'Project("{%s}") = "%s", "%s", "{%s}"\n'
iterpaths = reversed(parents)
# Skip first path
ofile.write(prj_line)
ofile.write('EndProject\n')
- def generate_solution(self, sln_filename, projlist):
+ 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())
sln_filename_tmp = sln_filename + '~'
ofile.write('# Visual Studio %s\n' % self.sln_version_comment)
prj_templ = 'Project("{%s}") = "%s", "%s", "{%s}"\n'
for prj in projlist:
- coredata = self.environment.coredata
- if coredata.get_option(OptionKey('layout')) == 'mirror':
+ if self.environment.coredata.get_option(OptionKey('layout')) == 'mirror':
self.generate_solution_dirs(ofile, prj[1].parents)
target = self.build.targets[prj[0]]
lang = 'default'
self.environment.coredata.test_guid)
ofile.write(test_line)
ofile.write('EndProject\n')
+ if self.gen_lite: # REGEN is replaced by the lighter-weight RECONFIGURE utility, for now. See comment in 'gen_regenproj'
+ regen_proj_name = 'RECONFIGURE'
+ regen_proj_fname = 'RECONFIGURE.vcxproj'
+ else:
+ regen_proj_name = 'REGEN'
+ regen_proj_fname = 'REGEN.vcxproj'
regen_line = prj_templ % (self.environment.coredata.lang_guids['default'],
- 'REGEN', 'REGEN.vcxproj',
+ regen_proj_name, regen_proj_fname,
self.environment.coredata.regen_guid)
ofile.write(regen_line)
ofile.write('EndProject\n')
ofile.write('Global\n')
ofile.write('\tGlobalSection(SolutionConfigurationPlatforms) = '
'preSolution\n')
- ofile.write('\t\t%s|%s = %s|%s\n' %
- (self.buildtype, self.platform, self.buildtype,
- self.platform))
+ multi_config_buildtype_list = coredata.get_genvs_default_buildtype_list() if self.gen_lite else [self.buildtype]
+ for buildtype in multi_config_buildtype_list:
+ ofile.write('\t\t%s|%s = %s|%s\n' %
+ (buildtype, self.platform, buildtype,
+ self.platform))
ofile.write('\tEndGlobalSection\n')
ofile.write('\tGlobalSection(ProjectConfigurationPlatforms) = '
'postSolution\n')
- ofile.write('\t\t{%s}.%s|%s.ActiveCfg = %s|%s\n' %
- (self.environment.coredata.regen_guid, self.buildtype,
- self.platform, self.buildtype, self.platform))
- ofile.write('\t\t{%s}.%s|%s.Build.0 = %s|%s\n' %
- (self.environment.coredata.regen_guid, self.buildtype,
- self.platform, self.buildtype, self.platform))
+ # REGEN project (multi-)configurations
+ for buildtype in multi_config_buildtype_list:
+ ofile.write('\t\t{%s}.%s|%s.ActiveCfg = %s|%s\n' %
+ (self.environment.coredata.regen_guid, buildtype,
+ self.platform, buildtype, self.platform))
+ if not self.gen_lite: # With a 'genvslite'-generated solution, the regen (i.e. reconfigure) utility is only intended to run when the user explicitly builds this proj.
+ ofile.write('\t\t{%s}.%s|%s.Build.0 = %s|%s\n' %
+ (self.environment.coredata.regen_guid, buildtype,
+ self.platform, buildtype, self.platform))
# Create the solution configuration
for p in projlist:
if p[3] is MachineChoice.BUILD:
else:
config_platform = self.platform
# Add to the list of projects in this solution
+ for buildtype in multi_config_buildtype_list:
+ ofile.write('\t\t{%s}.%s|%s.ActiveCfg = %s|%s\n' %
+ (p[2], buildtype, self.platform,
+ buildtype, config_platform))
+ # If we're building the solution with Visual Studio's build system, enable building of buildable
+ # projects. However, if we're building with meson (via --genvslite), then, since each project's
+ # 'build' action just ends up doing the same 'meson compile ...' we don't want the 'solution build'
+ # repeatedly going off and doing the same 'meson compile ...' multiple times over, so we just
+ # leave it up to the user to select or build just one project.
+ # FIXME: Would be slightly nicer if we could enable building of just one top level target/project,
+ # but not sure how to identify that.
+ if not self.gen_lite and \
+ p[0] in default_projlist and \
+ not isinstance(self.build.targets[p[0]], build.RunTarget):
+ ofile.write('\t\t{%s}.%s|%s.Build.0 = %s|%s\n' %
+ (p[2], buildtype, self.platform,
+ buildtype, config_platform))
+ # RUN_TESTS and RUN_INSTALL project (multi-)configurations
+ for buildtype in multi_config_buildtype_list:
ofile.write('\t\t{%s}.%s|%s.ActiveCfg = %s|%s\n' %
- (p[2], self.buildtype, self.platform,
- self.buildtype, config_platform))
- if p[0] in default_projlist and \
- not isinstance(self.build.targets[p[0]], build.RunTarget):
- # Add to the list of projects to be built
- ofile.write('\t\t{%s}.%s|%s.Build.0 = %s|%s\n' %
- (p[2], self.buildtype, self.platform,
- self.buildtype, config_platform))
- ofile.write('\t\t{%s}.%s|%s.ActiveCfg = %s|%s\n' %
- (self.environment.coredata.test_guid, self.buildtype,
- self.platform, self.buildtype, self.platform))
- ofile.write('\t\t{%s}.%s|%s.ActiveCfg = %s|%s\n' %
- (self.environment.coredata.install_guid, self.buildtype,
- self.platform, self.buildtype, self.platform))
+ (self.environment.coredata.test_guid, buildtype,
+ self.platform, buildtype, self.platform))
+ ofile.write('\t\t{%s}.%s|%s.ActiveCfg = %s|%s\n' %
+ (self.environment.coredata.install_guid, buildtype,
+ self.platform, buildtype, self.platform))
ofile.write('\tEndGlobalSection\n')
ofile.write('\tGlobalSection(SolutionProperties) = preSolution\n')
ofile.write('\t\tHideSolutionNode = FALSE\n')
ofile.write('EndGlobal\n')
replace_if_different(sln_filename, sln_filename_tmp)
- def generate_projects(self):
+ def generate_projects(self, vslite_ctx: dict = None) -> T.List[Project]:
startup_project = self.environment.coredata.options[OptionKey('backend_startup_project')].value
- projlist = []
+ projlist: T.List[Project] = []
startup_idx = 0
for (i, (name, target)) in enumerate(self.build.targets.items()):
if startup_project and startup_project == target.get_basename():
relname = target_dir / fname
projfile_path = outdir / fname
proj_uuid = self.environment.coredata.target_guids[name]
- self.gen_vcxproj(target, str(projfile_path), proj_uuid)
- projlist.append((name, relname, proj_uuid, target.for_machine))
+ generated = self.gen_vcxproj(target, str(projfile_path), proj_uuid, vslite_ctx)
+ if generated:
+ projlist.append((name, relname, proj_uuid, target.for_machine))
# Put the startup project first in the project list
if startup_idx:
def quote_arguments(self, arr):
return ['"%s"' % i for i in arr]
- def add_project_reference(self, root, include, projid, link_outputs=False):
+ def add_project_reference(self, root: ET.Element, include: str, projid: str, link_outputs: bool = False) -> None:
ig = ET.SubElement(root, 'ItemGroup')
pref = ET.SubElement(ig, 'ProjectReference', Include=include)
ET.SubElement(pref, 'Project').text = '{%s}' % projid
# objects and .lib files manually.
ET.SubElement(pref, 'LinkLibraryDependencies').text = 'false'
- def add_target_deps(self, root, target):
+ def add_target_deps(self, root: ET.Element, target):
target_dict = {target.get_id(): target}
for dep in self.get_target_deps(target_dict).values():
if dep.get_id() in self.handled_target_deps[target.get_id()]:
guid,
conftype='Utility',
target_ext=None,
- target_platform=None):
+ target_platform=None) -> T.Tuple[ET.Element, ET.Element]:
root = ET.Element('Project', {'DefaultTargets': "Build",
'ToolsVersion': '4.0',
'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'})
confitems = ET.SubElement(root, 'ItemGroup', {'Label': 'ProjectConfigurations'})
if not target_platform:
target_platform = self.platform
- prjconf = ET.SubElement(confitems, 'ProjectConfiguration',
- {'Include': self.buildtype + '|' + target_platform})
- p = ET.SubElement(prjconf, 'Configuration')
- p.text = self.buildtype
- pl = ET.SubElement(prjconf, 'Platform')
- pl.text = target_platform
+
+ multi_config_buildtype_list = coredata.get_genvs_default_buildtype_list() if self.gen_lite else [self.buildtype]
+ for buildtype in multi_config_buildtype_list:
+ prjconf = ET.SubElement(confitems, 'ProjectConfiguration',
+ {'Include': buildtype + '|' + target_platform})
+ ET.SubElement(prjconf, 'Configuration').text = buildtype
+ ET.SubElement(prjconf, 'Platform').text = target_platform
# Globals
globalgroup = ET.SubElement(root, 'PropertyGroup', Label='Globals')
guidelem.text = '{%s}' % guid
kw = ET.SubElement(globalgroup, 'Keyword')
kw.text = self.platform + 'Proj'
- # XXX Wasn't here before for anything but gen_vcxproj , but seems fine?
- ns = ET.SubElement(globalgroup, 'RootNamespace')
- ns.text = target_name
-
- p = ET.SubElement(globalgroup, 'Platform')
- p.text = target_platform
- pname = ET.SubElement(globalgroup, 'ProjectName')
- pname.text = target_name
- if self.windows_target_platform_version:
- ET.SubElement(globalgroup, 'WindowsTargetPlatformVersion').text = self.windows_target_platform_version
- ET.SubElement(globalgroup, 'UseMultiToolTask').text = 'true'
ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.Default.props')
- # Start configuration
+ # Configuration
type_config = ET.SubElement(root, 'PropertyGroup', Label='Configuration')
ET.SubElement(type_config, 'ConfigurationType').text = conftype
- ET.SubElement(type_config, 'CharacterSet').text = 'MultiByte'
- # Fixme: wasn't here before for gen_vcxproj()
- ET.SubElement(type_config, 'UseOfMfc').text = 'false'
if self.platform_toolset:
ET.SubElement(type_config, 'PlatformToolset').text = self.platform_toolset
- # End configuration section (but it can be added to further via type_config)
+ # This must come AFTER the '<PropertyGroup Label="Configuration">' element; importing before the 'PlatformToolset' elt
+ # gets set leads to msbuild failures reporting -
+ # "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')
- # Project information
- direlem = ET.SubElement(root, 'PropertyGroup')
- fver = ET.SubElement(direlem, '_ProjectFileVersion')
- fver.text = self.project_file_version
- outdir = ET.SubElement(direlem, 'OutDir')
- outdir.text = '.\\'
- intdir = ET.SubElement(direlem, 'IntDir')
- intdir.text = temp_dir + '\\'
-
- tname = ET.SubElement(direlem, 'TargetName')
- tname.text = target_name
-
- if target_ext:
- ET.SubElement(direlem, 'TargetExt').text = target_ext
+ if not self.gen_lite: # Plenty of elements aren't necessary for 'makefile'-style project that just redirects to meson builds
+ # XXX Wasn't here before for anything but gen_vcxproj , but seems fine?
+ ns = ET.SubElement(globalgroup, 'RootNamespace')
+ ns.text = target_name
+
+ p = ET.SubElement(globalgroup, 'Platform')
+ p.text = target_platform
+ pname = ET.SubElement(globalgroup, 'ProjectName')
+ pname.text = target_name
+ if self.windows_target_platform_version:
+ ET.SubElement(globalgroup, 'WindowsTargetPlatformVersion').text = self.windows_target_platform_version
+ ET.SubElement(globalgroup, 'UseMultiToolTask').text = 'true'
+
+ ET.SubElement(type_config, 'CharacterSet').text = 'MultiByte'
+ # Fixme: wasn't here before for gen_vcxproj()
+ ET.SubElement(type_config, 'UseOfMfc').text = 'false'
+
+ # Project information
+ direlem = ET.SubElement(root, 'PropertyGroup')
+ fver = ET.SubElement(direlem, '_ProjectFileVersion')
+ fver.text = self.project_file_version
+ outdir = ET.SubElement(direlem, 'OutDir')
+ outdir.text = '.\\'
+ intdir = ET.SubElement(direlem, 'IntDir')
+ intdir.text = temp_dir + '\\'
+
+ tname = ET.SubElement(direlem, 'TargetName')
+ tname.text = target_name
+
+ if target_ext:
+ ET.SubElement(direlem, 'TargetExt').text = target_ext
return (root, type_config)
- def gen_run_target_vcxproj(self, target, ofname, guid):
+ def gen_run_target_vcxproj(self, target: build.RunTarget, ofname: str, guid: str) -> None:
(root, type_config) = self.create_basic_project(target.name,
temp_dir=target.get_id(),
guid=guid)
- depend_files = self.get_custom_target_depend_files(target)
+ depend_files = self.get_target_depend_files(target)
if not target.command:
# This is an alias target and thus doesn't run any command. It's
self.add_target_deps(root, target)
self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname)
- def gen_custom_target_vcxproj(self, target, ofname, guid):
+ def gen_custom_target_vcxproj(self, target: build.CustomTarget, ofname: str, guid: str) -> None:
if target.for_machine is MachineChoice.BUILD:
platform = self.build_platform
else:
# from the target dir, not the build root.
target.absolute_paths = True
(srcs, ofilenames, cmd) = self.eval_custom_target_command(target, True)
- depend_files = self.get_custom_target_depend_files(target, True)
+ depend_files = self.get_target_depend_files(target, True)
# Always use a wrapper because MSBuild eats random characters when
# there are many arguments.
tdir_abs = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target))
self.add_target_deps(root, target)
self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname)
- def gen_compile_target_vcxproj(self, target, ofname, guid):
+ def gen_compile_target_vcxproj(self, target: build.CompileTarget, ofname: str, guid: str) -> None:
if target.for_machine is MachineChoice.BUILD:
platform = self.build_platform
else:
args.append(self.escape_additional_option(arg))
ET.SubElement(parent_node, "AdditionalOptions").text = ' '.join(args)
+ # Set up each project's source file ('CLCompile') element with appropriate preprocessor, include dir, and compile option values for correct intellisense.
+ def add_project_nmake_defs_incs_and_opts(self, parent_node, src: str, defs_paths_opts_per_lang_and_buildtype: dict, platform: str):
+ # For compactness, sources whose type matches the primary src type (i.e. most frequent in the set of source types used in the target/project,
+ # according to the 'captured_build_args' map), can simply reference the preprocessor definitions, include dirs, and compile option NMake fields of
+ # the project itself.
+ # However, if a src is of a non-primary type, it could have totally different defs/dirs/options so we're going to have to fill in the full, verbose
+ # set of values for these fields, which needs to be fully expanded per build type / configuration.
+ #
+ # FIXME: Suppose a project contains .cpp and .c src files with different compile defs/dirs/options, while also having .h files, some of which
+ # are included by .cpp sources and others included by .c sources: How do we know whether the .h source should be using the .cpp or .c src
+ # defs/dirs/options? Might it also be possible for a .h header to be shared between .cpp and .c sources? If so, I don't see how we can
+ # correctly configure these intellisense fields.
+ # For now, all sources/headers that fail to find their extension's language in the '...nmake_defs_paths_opts...' map will just adopt the project
+ # defs/dirs/opts that are set for the nominal 'primary' src type.
+ ext = src.split('.')[-1]
+ lang = compilers.compilers.SUFFIX_TO_LANG.get(ext, None)
+ if lang in defs_paths_opts_per_lang_and_buildtype.keys():
+ # This is a non-primary src type for which can't simply reference the project's nmake fields;
+ # we must laboriously fill in the fields for all buildtypes.
+ for buildtype in coredata.get_genvs_default_buildtype_list():
+ (defs, paths, opts) = defs_paths_opts_per_lang_and_buildtype[lang][buildtype]
+ condition = f'\'$(Configuration)|$(Platform)\'==\'{buildtype}|{platform}\''
+ ET.SubElement(parent_node, 'PreprocessorDefinitions', Condition=condition).text = defs
+ ET.SubElement(parent_node, 'AdditionalIncludeDirectories', Condition=condition).text = paths
+ ET.SubElement(parent_node, 'AdditionalOptions', Condition=condition).text = opts
+ else: # Can't find bespoke nmake defs/dirs/opts fields for this extention, so just reference the project's fields
+ ET.SubElement(parent_node, 'PreprocessorDefinitions').text = '$(NMakePreprocessorDefinitions)'
+ ET.SubElement(parent_node, 'AdditionalIncludeDirectories').text = '$(NMakeIncludeSearchPath)'
+ ET.SubElement(parent_node, 'AdditionalOptions').text = '$(AdditionalOptions)'
+
def add_preprocessor_defines(self, lang, parent_node, file_defines):
defines = []
for define in file_defines[lang]:
return
@staticmethod
- def escape_preprocessor_define(define):
+ def escape_preprocessor_define(define: str) -> str:
# See: https://msdn.microsoft.com/en-us/library/bb383819.aspx
table = str.maketrans({'%': '%25', '$': '%24', '@': '%40',
"'": '%27', ';': '%3B', '?': '%3F', '*': '%2A',
return define.translate(table)
@staticmethod
- def escape_additional_option(option):
+ def escape_additional_option(option: str) -> str:
# See: https://msdn.microsoft.com/en-us/library/bb383819.aspx
table = str.maketrans({'%': '%25', '$': '%24', '@': '%40',
"'": '%27', ';': '%3B', '?': '%3F', '*': '%2A', ' ': '%20'})
return c
raise MesonException('Could not find a C or C++ compiler. MSVC can only build C/C++ projects.')
- def _prettyprint_vcxproj_xml(self, tree, ofname):
+ def _prettyprint_vcxproj_xml(self, tree: ET.ElementTree, ofname: str) -> None:
ofname_tmp = ofname + '~'
tree.write(ofname_tmp, encoding='utf-8', xml_declaration=True)
- # ElementTree can not do prettyprinting so do it manually
+ # ElementTree cannot do pretty-printing, so do it manually
doc = xml.dom.minidom.parse(ofname_tmp)
with open(ofname_tmp, 'w', encoding='utf-8') as of:
of.write(doc.toprettyxml())
replace_if_different(ofname, ofname_tmp)
- def gen_vcxproj(self, target, ofname, guid):
- mlog.debug(f'Generating vcxproj {target.name}.')
- subsystem = 'Windows'
- self.handled_target_deps[target.get_id()] = []
- if isinstance(target, build.Executable):
- conftype = 'Application'
- if target.gui_app is not None:
- if not target.gui_app:
- subsystem = 'Console'
- else:
- # If someone knows how to set the version properly,
- # please send a patch.
- subsystem = target.win_subsystem.split(',')[0]
- elif isinstance(target, build.StaticLibrary):
- conftype = 'StaticLibrary'
- elif isinstance(target, build.SharedLibrary):
- conftype = 'DynamicLibrary'
- elif isinstance(target, build.CustomTarget):
- return self.gen_custom_target_vcxproj(target, ofname, guid)
- elif isinstance(target, build.RunTarget):
- return self.gen_run_target_vcxproj(target, ofname, guid)
- elif isinstance(target, build.CompileTarget):
- return self.gen_compile_target_vcxproj(target, ofname, guid)
- else:
- raise MesonException(f'Unknown target type for {target.get_basename()}')
- # Prefix to use to access the build root from the vcxproj dir
- down = self.target_to_build_root(target)
- # Prefix to use to access the source tree's root from the vcxproj dir
- proj_to_src_root = os.path.join(down, self.build_to_src)
- # Prefix to use to access the source tree's subdir from the vcxproj dir
- proj_to_src_dir = os.path.join(proj_to_src_root, self.get_target_dir(target))
- (sources, headers, objects, languages) = self.split_sources(target.sources)
- if target.is_unity:
- sources = self.generate_unity_files(target, sources)
- compiler = self._get_cl_compiler(target)
- build_args = compiler.get_buildtype_args(self.buildtype)
- build_args += compiler.get_optimization_args(self.optimization)
- build_args += compiler.get_debug_args(self.debug)
- build_args += compiler.sanitizer_compile_args(self.sanitize)
- buildtype_link_args = compiler.get_buildtype_linker_args(self.buildtype)
- vscrt_type = self.environment.coredata.options[OptionKey('b_vscrt')]
- target_name = target.name
- if target.for_machine is MachineChoice.BUILD:
- platform = self.build_platform
- else:
- platform = self.platform
-
- tfilename = os.path.splitext(target.get_filename())
-
- (root, type_config) = self.create_basic_project(tfilename[0],
- temp_dir=target.get_id(),
- guid=guid,
- conftype=conftype,
- target_ext=tfilename[1],
- target_platform=platform)
-
- # FIXME: Should these just be set in create_basic_project(), even if
- # irrelevant for current target?
-
- # FIXME: Meson's LTO support needs to be integrated here
- ET.SubElement(type_config, 'WholeProgramOptimization').text = 'false'
- # Let VS auto-set the RTC level
- ET.SubElement(type_config, 'BasicRuntimeChecks').text = 'Default'
- # Incremental linking increases code size
- if '/INCREMENTAL:NO' in buildtype_link_args:
- ET.SubElement(type_config, 'LinkIncremental').text = 'false'
-
- # Build information
- compiles = ET.SubElement(root, 'ItemDefinitionGroup')
- clconf = ET.SubElement(compiles, 'ClCompile')
- # CRT type; debug or release
- if vscrt_type.value == 'from_buildtype':
- if self.buildtype == 'debug':
- ET.SubElement(type_config, 'UseDebugLibraries').text = 'true'
- ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDebugDLL'
- else:
- ET.SubElement(type_config, 'UseDebugLibraries').text = 'false'
- ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDLL'
- elif vscrt_type.value == 'static_from_buildtype':
- if self.buildtype == 'debug':
- ET.SubElement(type_config, 'UseDebugLibraries').text = 'true'
- ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDebug'
- else:
- ET.SubElement(type_config, 'UseDebugLibraries').text = 'false'
- ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreaded'
- elif vscrt_type.value == 'mdd':
- ET.SubElement(type_config, 'UseDebugLibraries').text = 'true'
- ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDebugDLL'
- elif vscrt_type.value == 'mt':
- # FIXME, wrong
- ET.SubElement(type_config, 'UseDebugLibraries').text = 'false'
- ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreaded'
- elif vscrt_type.value == 'mtd':
- # FIXME, wrong
- ET.SubElement(type_config, 'UseDebugLibraries').text = 'true'
- ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDebug'
- else:
- ET.SubElement(type_config, 'UseDebugLibraries').text = 'false'
- ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDLL'
- # Sanitizers
- if '/fsanitize=address' in build_args:
- ET.SubElement(type_config, 'EnableASAN').text = 'true'
- # Debug format
- if '/ZI' in build_args:
- ET.SubElement(clconf, 'DebugInformationFormat').text = 'EditAndContinue'
- elif '/Zi' in build_args:
- ET.SubElement(clconf, 'DebugInformationFormat').text = 'ProgramDatabase'
- elif '/Z7' in build_args:
- ET.SubElement(clconf, 'DebugInformationFormat').text = 'OldStyle'
- else:
- ET.SubElement(clconf, 'DebugInformationFormat').text = 'None'
- # Runtime checks
- if '/RTC1' in build_args:
- ET.SubElement(clconf, 'BasicRuntimeChecks').text = 'EnableFastChecks'
- elif '/RTCu' in build_args:
- ET.SubElement(clconf, 'BasicRuntimeChecks').text = 'UninitializedLocalUsageCheck'
- elif '/RTCs' in build_args:
- ET.SubElement(clconf, 'BasicRuntimeChecks').text = 'StackFrameRuntimeCheck'
- # Exception handling has to be set in the xml in addition to the "AdditionalOptions" because otherwise
- # cl will give warning D9025: overriding '/Ehs' with cpp_eh value
- if 'cpp' in target.compilers:
- eh = self.environment.coredata.options[OptionKey('eh', machine=target.for_machine, lang='cpp')]
- if eh.value == 'a':
- ET.SubElement(clconf, 'ExceptionHandling').text = 'Async'
- elif eh.value == 's':
- ET.SubElement(clconf, 'ExceptionHandling').text = 'SyncCThrow'
- elif eh.value == 'none':
- ET.SubElement(clconf, 'ExceptionHandling').text = 'false'
- else: # 'sc' or 'default'
- ET.SubElement(clconf, 'ExceptionHandling').text = 'Sync'
- generated_files, custom_target_output_files, generated_files_include_dirs = self.generate_custom_generator_commands(
- target, root)
- (gen_src, gen_hdrs, gen_objs, gen_langs) = self.split_sources(generated_files)
- (custom_src, custom_hdrs, custom_objs, custom_langs) = self.split_sources(custom_target_output_files)
- gen_src += custom_src
- gen_hdrs += custom_hdrs
- gen_langs += custom_langs
-
+ # Returns: (target_args,file_args), (target_defines,file_defines), (target_inc_dirs,file_inc_dirs)
+ def get_args_defines_and_inc_dirs(self, target, compiler, generated_files_include_dirs, proj_to_src_root, proj_to_src_dir, build_args):
# Arguments, include dirs, defines for all files in the current target
target_args = []
target_defines = []
#
# file_args is also later split out into defines and include_dirs in
# case someone passed those in there
- file_args = {l: c.compiler_args() for l, c in target.compilers.items()}
+ file_args: T.Dict[str, CompilerArgs] = {l: c.compiler_args() for l, c in target.compilers.items()}
file_defines = {l: [] for l in target.compilers}
file_inc_dirs = {l: [] for l in target.compilers}
# The order in which these compile args are added must match
# Compile args added from the env or cross file: CFLAGS/CXXFLAGS, etc. We want these
# to override all the defaults, but not the per-target compile args.
for l in file_args.keys():
- opts = self.environment.coredata.options[OptionKey('args', machine=target.for_machine, lang=l)]
- file_args[l] += opts.value
+ file_args[l] += target.get_option(OptionKey('args', machine=target.for_machine, lang=l))
for args in file_args.values():
# This is where Visual Studio will insert target_args, target_defines,
# etc, which are added later from external deps (see below).
for i in reversed(d.get_incdirs()):
curdir = os.path.join(d.get_curdir(), i)
try:
- args.append('-I' + self.relpath(curdir, target.subdir)) # build dir
+ # Add source subdir first so that the build subdir overrides it
args.append('-I' + os.path.join(proj_to_src_root, curdir)) # src dir
+ args.append('-I' + self.relpath(curdir, target.subdir)) # build dir
except ValueError:
# Include is on different drive
args.append('-I' + os.path.normpath(curdir))
for d in reversed(target.get_external_deps()):
# Cflags required by external deps might have UNIX-specific flags,
# so filter them out if needed
- if isinstance(d, dependencies.OpenMPDependency):
- ET.SubElement(clconf, 'OpenMPSupport').text = 'true'
- else:
+ if d.name != 'openmp':
d_compile_args = compiler.unix_args_to_native(d.get_compile_args())
for arg in d_compile_args:
if arg.startswith(('-D', '/D')):
else:
target_args.append(arg)
- languages += gen_langs
if '/Gw' in build_args:
target_args.append('/Gw')
+
+ return (target_args, file_args), (target_defines, file_defines), (target_inc_dirs, file_inc_dirs)
+
+ @staticmethod
+ def get_build_args(compiler, buildtype: str, optimization_level: str, debug: bool, sanitize: str) -> T.List[str]:
+ build_args = compiler.get_buildtype_args(buildtype)
+ build_args += compiler.get_optimization_args(optimization_level)
+ build_args += compiler.get_debug_args(debug)
+ build_args += compiler.sanitizer_compile_args(sanitize)
+
+ return build_args
+
+ # Used in populating a simple nmake-style project's intellisense fields.
+ # Given a list of compile args, for example -
+ # [ '-I..\\some\\dir\\include', '-I../../some/other/dir', '/MDd', '/W2', '/std:c++17', '/Od', '/Zi', '-DSOME_DEF=1', '-DANOTHER_DEF=someval', ...]
+ # returns a tuple of pre-processor defs (for this example) -
+ # 'SOME_DEF=1;ANOTHER_DEF=someval;'
+ # and include paths, e.g. -
+ # '..\\some\\dir\\include;../../some/other/dir;'
+ # and finally any remaining compiler options, e.g. -
+ # '/MDd;/W2;/std:c++17;/Od/Zi'
+ @staticmethod
+ def _extract_nmake_fields(captured_build_args: list[str]) -> T.Tuple[str, str, str]:
+ include_dir_options = [
+ '-I',
+ '/I',
+ '-isystem', # regular gcc / clang option to denote system header include search paths
+ '/clang:-isystem', # clang-cl (msvc 'cl'-style clang wrapper) option to pass '-isystem' option to clang driver
+ '/imsvc', # clang-cl option to 'Add directory to system include search path'
+ '/external:I', # msvc cl option to add 'external' include search paths
+ ]
+
+ defs = ''
+ paths = ''
+ additional_opts = ''
+ for arg in captured_build_args:
+ if arg.startswith(('-D', '/D')):
+ defs += arg[2:] + ';'
+ else:
+ opt_match = next((opt for opt in include_dir_options if arg.startswith(opt)), None)
+ if opt_match:
+ paths += arg[len(opt_match):] + ';'
+ elif arg.startswith(('-', '/')):
+ additional_opts += arg + ';'
+ return (defs, paths, additional_opts)
+
+ @staticmethod
+ def get_nmake_base_meson_command_and_exe_search_paths() -> T.Tuple[str, str]:
+ meson_cmd_list = mesonlib.get_meson_command()
+ assert (len(meson_cmd_list) == 1) or (len(meson_cmd_list) == 2)
+ # We expect get_meson_command() to either be of the form -
+ # 1: ['path/to/meson.exe']
+ # or -
+ # 2: ['path/to/python.exe', 'and/path/to/meson.py']
+ # so we'd like to ensure our makefile-style project invokes the same meson executable or python src as this instance.
+ exe_search_paths = os.path.dirname(meson_cmd_list[0])
+ nmake_base_meson_command = os.path.basename(meson_cmd_list[0])
+ if len(meson_cmd_list) != 1:
+ # We expect to be dealing with case '2', shown above.
+ # With Windows, it's also possible that we get a path to the second element of meson_cmd_list that contains spaces
+ # (e.g. 'and/path to/meson.py'). So, because this will end up directly in the makefile/NMake command lines, we'd
+ # better always enclose it in quotes. Only strictly necessary for paths with spaces but no harm for paths without -
+ nmake_base_meson_command += ' \"' + meson_cmd_list[1] + '\"'
+ exe_search_paths += ';' + os.path.dirname(meson_cmd_list[1])
+
+ # Additionally, in some cases, we appear to have to add 'C:\Windows\system32;C:\Windows' to the 'Path' environment (via the
+ # ExecutablePath element), without which, the 'meson compile ...' (NMakeBuildCommandLine) command can fail (failure to find
+ # stdio.h and similar), so something is quietly switching some critical build behaviour based on the presence of these in
+ # the 'Path'.
+ # Not sure if this ultimately comes down to some 'find and guess' hidden behaviours within meson or within MSVC tools, but
+ # I guess some projects may implicitly rely on this behaviour.
+ # Things would be cleaner, more robust, repeatable, and portable if meson (and msvc tools) replaced all this kind of
+ # find/guess behaviour with the requirement that things just be explicitly specified by the user.
+ # An example of this can be seen with -
+ # 1: Download https://github.com/facebook/zstd source
+ # 2: cd to the 'zstd-dev\build\meson' dir
+ # 3: meson setup -Dbin_programs=true -Dbin_contrib=true --genvslite vs2022 builddir_vslite
+ # 4: Open the generated 'builddir_vslite_vs\zstd.sln' and build through a project, which should explicitly add the above to
+ # the project's 'Executable Directories' paths and build successfully.
+ # 5: Remove 'C:\Windows\system32;C:\Windows;' from the same project's 'Executable Directories' paths and rebuild.
+ # This should now fail.
+ # It feels uncomfortable to do this but what better alternative is there (and might this introduce new problems)? -
+ exe_search_paths += ';C:\\Windows\\system32;C:\\Windows'
+ # A meson project that explicitly specifies compiler/linker tools and sdk/include paths is not going to have any problems
+ # with this addition.
+
+ return (nmake_base_meson_command, exe_search_paths)
+
+ def add_gen_lite_makefile_vcxproj_elements(self,
+ root: ET.Element,
+ platform: str,
+ target_ext: str,
+ vslite_ctx: dict,
+ target,
+ proj_to_build_root: str,
+ primary_src_lang: T.Optional[str]) -> None:
+ ET.SubElement(root, 'ImportGroup', Label='ExtensionSettings')
+ ET.SubElement(root, 'ImportGroup', Label='Shared')
+ prop_sheets_grp = ET.SubElement(root, 'ImportGroup', Label='PropertySheets')
+ ET.SubElement(prop_sheets_grp, 'Import', {'Project': r'$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props',
+ 'Condition': r"exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')",
+ 'Label': 'LocalAppDataPlatform'
+ })
+ ET.SubElement(root, 'PropertyGroup', Label='UserMacros')
+
+ (nmake_base_meson_command, exe_search_paths) = Vs2010Backend.get_nmake_base_meson_command_and_exe_search_paths()
+
+ # Relative path from this .vcxproj to the directory containing the set of '..._[debug/debugoptimized/release]' setup meson build dirs.
+ proj_to_multiconfigured_builds_parent_dir = os.path.join(proj_to_build_root, '..')
+
+ # Conditional property groups per configuration (buildtype). E.g. -
+ # <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='release|x64'">
+ multi_config_buildtype_list = coredata.get_genvs_default_buildtype_list()
+ for buildtype in multi_config_buildtype_list:
+ per_config_prop_group = ET.SubElement(root, 'PropertyGroup', Condition=f'\'$(Configuration)|$(Platform)\'==\'{buildtype}|{platform}\'')
+ (_, build_dir_tail) = os.path.split(self.src_to_build)
+ meson_build_dir_for_buildtype = build_dir_tail[:-2] + buildtype # Get the buildtype suffixed 'builddir_[debug/release/etc]' from 'builddir_vs', for example.
+ proj_to_build_dir_for_buildtype = str(os.path.join(proj_to_multiconfigured_builds_parent_dir, meson_build_dir_for_buildtype))
+ ET.SubElement(per_config_prop_group, 'OutDir').text = f'{proj_to_build_dir_for_buildtype}\\'
+ ET.SubElement(per_config_prop_group, 'IntDir').text = f'{proj_to_build_dir_for_buildtype}\\'
+ ET.SubElement(per_config_prop_group, 'NMakeBuildCommandLine').text = f'{nmake_base_meson_command} compile -C "{proj_to_build_dir_for_buildtype}"'
+ ET.SubElement(per_config_prop_group, 'NMakeOutput').text = f'$(OutDir){target.name}{target_ext}'
+ captured_build_args = vslite_ctx[buildtype][target.get_id()]
+ # 'captured_build_args' is a dictionary, mapping from each src file type to a list of compile args to use for that type.
+ # Usually, there's just one but we could have multiple src types. However, since there's only one field for the makefile
+ # project's NMake... preprocessor/include intellisense fields, we'll just use the first src type we have to fill in
+ # these fields. Then, any src files in this VS project that aren't of this first src type will then need to override
+ # its intellisense fields instead of simply referencing the values in the project.
+ ET.SubElement(per_config_prop_group, 'NMakeReBuildCommandLine').text = f'{nmake_base_meson_command} compile -C "{proj_to_build_dir_for_buildtype}" --clean && {nmake_base_meson_command} compile -C "{proj_to_build_dir_for_buildtype}"'
+ ET.SubElement(per_config_prop_group, 'NMakeCleanCommandLine').text = f'{nmake_base_meson_command} compile -C "{proj_to_build_dir_for_buildtype}" --clean'
+ # Need to set the 'ExecutablePath' element for the above NMake... commands to be able to invoke the meson command.
+ ET.SubElement(per_config_prop_group, 'ExecutablePath').text = exe_search_paths
+ # We may not have any src files and so won't have a primary src language. In which case, we've nothing to fill in for this target's intellisense fields -
+ if primary_src_lang:
+ primary_src_type_build_args = captured_build_args[primary_src_lang]
+ preproc_defs, inc_paths, other_compile_opts = Vs2010Backend._extract_nmake_fields(primary_src_type_build_args)
+ ET.SubElement(per_config_prop_group, 'NMakePreprocessorDefinitions').text = preproc_defs
+ ET.SubElement(per_config_prop_group, 'NMakeIncludeSearchPath').text = inc_paths
+ ET.SubElement(per_config_prop_group, 'AdditionalOptions').text = other_compile_opts
+
+ # Unless we explicitly specify the following empty path elements, the project is assigned a load of nasty defaults that fill these
+ # with values like -
+ # $(VC_IncludePath);$(WindowsSDK_IncludePath);
+ # which are all based on the current install environment (a recipe for non-reproducibility problems), not the paths that will be used by
+ # the actual meson compile jobs. Although these elements look like they're only for MSBuild operations, they're not needed with our simple,
+ # lite/makefile-style projects so let's just remove them in case they do get used/confused by intellisense.
+ ET.SubElement(per_config_prop_group, 'IncludePath')
+ ET.SubElement(per_config_prop_group, 'ExternalIncludePath')
+ ET.SubElement(per_config_prop_group, 'ReferencePath')
+ ET.SubElement(per_config_prop_group, 'LibraryPath')
+ ET.SubElement(per_config_prop_group, 'LibraryWPath')
+ ET.SubElement(per_config_prop_group, 'SourcePath')
+ ET.SubElement(per_config_prop_group, 'ExcludePath')
+
+ def add_non_makefile_vcxproj_elements(
+ self,
+ root: ET.Element,
+ type_config: ET.Element,
+ target,
+ platform: str,
+ subsystem,
+ build_args,
+ target_args,
+ target_defines,
+ target_inc_dirs,
+ file_args
+ ) -> None:
+ compiler = self._get_cl_compiler(target)
+ buildtype_link_args = compiler.get_buildtype_linker_args(self.buildtype)
+
+ # Prefix to use to access the build root from the vcxproj dir
+ down = self.target_to_build_root(target)
+
+ # FIXME: Should the following just be set in create_basic_project(), even if
+ # irrelevant for current target?
+
+ # FIXME: Meson's LTO support needs to be integrated here
+ ET.SubElement(type_config, 'WholeProgramOptimization').text = 'false'
+ # Let VS auto-set the RTC level
+ ET.SubElement(type_config, 'BasicRuntimeChecks').text = 'Default'
+ # Incremental linking increases code size
+ if '/INCREMENTAL:NO' in buildtype_link_args:
+ ET.SubElement(type_config, 'LinkIncremental').text = 'false'
+
+ # Build information
+ compiles = ET.SubElement(root, 'ItemDefinitionGroup')
+ clconf = ET.SubElement(compiles, 'ClCompile')
+ if True in ((dep.name == 'openmp') for dep in target.get_external_deps()):
+ ET.SubElement(clconf, 'OpenMPSupport').text = 'true'
+ # CRT type; debug or release
+ vscrt_type = target.get_option(OptionKey('b_vscrt'))
+ vscrt_val = compiler.get_crt_val(vscrt_type, self.buildtype)
+ if vscrt_val == 'mdd':
+ ET.SubElement(type_config, 'UseDebugLibraries').text = 'true'
+ ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDebugDLL'
+ elif vscrt_val == 'mt':
+ # FIXME, wrong
+ ET.SubElement(type_config, 'UseDebugLibraries').text = 'false'
+ ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreaded'
+ elif vscrt_val == 'mtd':
+ # FIXME, wrong
+ ET.SubElement(type_config, 'UseDebugLibraries').text = 'true'
+ ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDebug'
+ else:
+ ET.SubElement(type_config, 'UseDebugLibraries').text = 'false'
+ ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDLL'
+ # Sanitizers
+ if '/fsanitize=address' in build_args:
+ ET.SubElement(type_config, 'EnableASAN').text = 'true'
+ # Debug format
+ if '/ZI' in build_args:
+ ET.SubElement(clconf, 'DebugInformationFormat').text = 'EditAndContinue'
+ elif '/Zi' in build_args:
+ ET.SubElement(clconf, 'DebugInformationFormat').text = 'ProgramDatabase'
+ elif '/Z7' in build_args:
+ ET.SubElement(clconf, 'DebugInformationFormat').text = 'OldStyle'
+ else:
+ ET.SubElement(clconf, 'DebugInformationFormat').text = 'None'
+ # Runtime checks
+ if '/RTC1' in build_args:
+ ET.SubElement(clconf, 'BasicRuntimeChecks').text = 'EnableFastChecks'
+ elif '/RTCu' in build_args:
+ ET.SubElement(clconf, 'BasicRuntimeChecks').text = 'UninitializedLocalUsageCheck'
+ elif '/RTCs' in build_args:
+ ET.SubElement(clconf, 'BasicRuntimeChecks').text = 'StackFrameRuntimeCheck'
+ # Exception handling has to be set in the xml in addition to the "AdditionalOptions" because otherwise
+ # cl will give warning D9025: overriding '/Ehs' with cpp_eh value
+ if 'cpp' in target.compilers:
+ eh = target.get_option(OptionKey('eh', machine=target.for_machine, lang='cpp'))
+ if eh == 'a':
+ ET.SubElement(clconf, 'ExceptionHandling').text = 'Async'
+ elif eh == 's':
+ ET.SubElement(clconf, 'ExceptionHandling').text = 'SyncCThrow'
+ elif eh == 'none':
+ ET.SubElement(clconf, 'ExceptionHandling').text = 'false'
+ else: # 'sc' or 'default'
+ ET.SubElement(clconf, 'ExceptionHandling').text = 'Sync'
+
if len(target_args) > 0:
target_args.append('%(AdditionalOptions)')
ET.SubElement(clconf, "AdditionalOptions").text = ' '.join(target_args)
ET.SubElement(clconf, 'PreprocessorDefinitions').text = ';'.join(target_defines)
ET.SubElement(clconf, 'FunctionLevelLinking').text = 'true'
# Warning level
- warning_level = target.get_option(OptionKey('warning_level'))
+ warning_level = T.cast('str', target.get_option(OptionKey('warning_level')))
ET.SubElement(clconf, 'WarningLevel').text = 'Level' + str(1 + int(warning_level))
if target.get_option(OptionKey('werror')):
ET.SubElement(clconf, 'TreatWarningAsError').text = 'true'
ET.SubElement(clconf, 'FavorSizeOrSpeed').text = 'Speed'
# Note: SuppressStartupBanner is /NOLOGO and is 'true' by default
self.generate_lang_standard_info(file_args, clconf)
- pch_sources = {}
- if self.environment.coredata.options.get(OptionKey('b_pch')):
- for lang in ['c', 'cpp']:
- pch = target.get_pch(lang)
- if not pch:
- continue
- if compiler.id == 'msvc':
- if len(pch) == 1:
- # Auto generate PCH.
- src = os.path.join(down, 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]
- 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]
resourcecompile = ET.SubElement(compiles, 'ResourceCompile')
ET.SubElement(resourcecompile, 'PreprocessorDefinitions')
ET.SubElement(link, 'GenerateDebugInformation').text = 'false'
if not isinstance(target, build.StaticLibrary):
if isinstance(target, build.SharedModule):
- options = self.environment.coredata.options
- extra_link_args += compiler.get_std_shared_module_link_args(options)
+ extra_link_args += compiler.get_std_shared_module_link_args(target.get_options())
# Add link args added using add_project_link_arguments()
extra_link_args += self.build.get_project_link_args(compiler, target.subproject, target.for_machine)
# Add link args added using add_global_link_arguments()
for dep in target.get_external_deps():
# Extend without reordering or de-dup to preserve `-L -l` sets
# https://github.com/mesonbuild/meson/issues/1718
- if isinstance(dep, dependencies.OpenMPDependency):
- ET.SubElement(clconf, 'OpenMPSuppport').text = 'true'
+ if dep.name == 'openmp':
+ ET.SubElement(clconf, 'OpenMPSupport').text = 'true'
else:
extra_link_args.extend_direct(dep.get_link_args())
for d in target.get_dependencies():
if isinstance(d, build.StaticLibrary):
for dep in d.get_external_deps():
- if isinstance(dep, dependencies.OpenMPDependency):
- ET.SubElement(clconf, 'OpenMPSuppport').text = 'true'
+ if dep.name == 'openmp':
+ ET.SubElement(clconf, 'OpenMPSupport').text = 'true'
else:
extra_link_args.extend_direct(dep.get_link_args())
# Add link args for c_* or cpp_* build options. Currently this only
# 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(self.environment.coredata.options)
+ extra_link_args += compiler.get_option_link_args(target.get_options())
(additional_libpaths, additional_links, extra_link_args) = self.split_link_args(extra_link_args.to_native())
# Add more libraries to be linked if needed
additional_links.append(linkname)
for lib in self.get_custom_target_provided_libraries(target):
additional_links.append(self.relpath(lib, self.get_target_dir(target)))
- additional_objects = []
- for o in self.flatten_object_list(target, down)[0]:
- assert isinstance(o, str)
- additional_objects.append(o)
- for o in custom_objs:
- additional_objects.append(o)
if len(extra_link_args) > 0:
extra_link_args.append('%(AdditionalOptions)')
# DLLs built with MSVC always have an import library except when
# they're data-only DLLs, but we don't support those yet.
ET.SubElement(link, 'ImportLibrary').text = target.get_import_filename()
- if isinstance(target, build.SharedLibrary):
+ if isinstance(target, (build.SharedLibrary, build.Executable)):
# Add module definitions file, if provided
if target.vs_module_defs:
relpath = os.path.join(down, target.vs_module_defs.rel_to_builddir(self.build_to_src))
ET.SubElement(link, 'ModuleDefinitionFile').text = relpath
if self.debug:
pdb = ET.SubElement(link, 'ProgramDataBaseFileName')
- pdb.text = f'$(OutDir){target_name}.pdb'
+ pdb.text = f'$(OutDir){target.name}.pdb'
targetmachine = ET.SubElement(link, 'TargetMachine')
if target.for_machine is MachineChoice.BUILD:
targetplatform = platform.lower()
# /nologo
ET.SubElement(link, 'SuppressStartupBanner').text = 'true'
# /release
- if not self.environment.coredata.get_option(OptionKey('debug')):
+ if not target.get_option(OptionKey('debug')):
ET.SubElement(link, 'SetChecksum').text = 'true'
+ # Visual studio doesn't simply allow the src files of a project to be added with the 'Condition=...' attribute,
+ # to allow us to point to the different debug/debugoptimized/release sets of generated src files for each of
+ # the solution's configurations. Similarly, 'ItemGroup' also doesn't support 'Condition'. So, without knowing
+ # a better (simple) alternative, for now, we'll repoint these generated sources (which will be incorrectly
+ # pointing to non-existent files under our '[builddir]_vs' directory) to the appropriate location under one of
+ # our buildtype build directores (e.g. '[builddir]_debug').
+ # This will at least allow the user to open the files of generated sources listed in the solution explorer,
+ # 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:
+ (_, 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.
+ proj_to_build_root = self.target_to_build_root(target)
+ proj_to_multiconfigured_builds_parent_dir = os.path.join(proj_to_build_root, '..')
+ proj_to_build_dir_for_buildtype = str(os.path.join(proj_to_multiconfigured_builds_parent_dir, meson_build_dir_for_buildtype))
+ relocate_to_concrete_builddir_target = os.path.normpath(os.path.join(proj_to_build_dir_for_buildtype, self.get_target_dir(target)))
+ for idx, file_path in enumerate(gen_files):
+ gen_files[idx] = os.path.normpath(os.path.join(relocate_to_concrete_builddir_target, file_path))
+
+ # Returns bool indicating whether the .vcxproj has been generated.
+ # Under some circumstances, it's unnecessary to create some .vcxprojs, so, when generating the .sln,
+ # we need to respect that not all targets will have generated a project.
+ def gen_vcxproj(self, target: build.BuildTarget, ofname: str, guid: str, vslite_ctx: dict = None) -> bool:
+ mlog.debug(f'Generating vcxproj {target.name}.')
+ subsystem = 'Windows'
+ self.handled_target_deps[target.get_id()] = []
+
+ if self.gen_lite:
+ if not isinstance(target, build.BuildTarget):
+ # Since we're going to delegate all building to the one true meson build command, we don't need
+ # to generate .vcxprojs for targets that don't add any source files or just perform custom build
+ # commands. These are targets of types CustomTarget or RunTarget. So let's just skip generating
+ # these otherwise insubstantial non-BuildTarget targets.
+ return False
+ conftype = 'Makefile'
+ elif isinstance(target, build.Executable):
+ conftype = 'Application'
+ # If someone knows how to set the version properly,
+ # please send a patch.
+ subsystem = target.win_subsystem.split(',')[0]
+ elif isinstance(target, build.StaticLibrary):
+ conftype = 'StaticLibrary'
+ elif isinstance(target, build.SharedLibrary):
+ conftype = 'DynamicLibrary'
+ elif isinstance(target, build.CustomTarget):
+ self.gen_custom_target_vcxproj(target, ofname, guid)
+ return True
+ elif isinstance(target, build.RunTarget):
+ self.gen_run_target_vcxproj(target, ofname, guid)
+ return True
+ elif isinstance(target, build.CompileTarget):
+ self.gen_compile_target_vcxproj(target, ofname, guid)
+ return True
+ else:
+ raise MesonException(f'Unknown target type for {target.get_basename()}')
+
+ (sources, headers, objects, _languages) = self.split_sources(target.sources)
+ if target.is_unity:
+ sources = self.generate_unity_files(target, sources)
+ if target.for_machine is MachineChoice.BUILD:
+ platform = self.build_platform
+ else:
+ platform = self.platform
+
+ tfilename = os.path.splitext(target.get_filename())
+
+ (root, type_config) = self.create_basic_project(tfilename[0],
+ temp_dir=target.get_id(),
+ guid=guid,
+ conftype=conftype,
+ target_ext=tfilename[1],
+ target_platform=platform)
+
+ generated_files, custom_target_output_files, generated_files_include_dirs = self.generate_custom_generator_commands(
+ target, root)
+ (gen_src, gen_hdrs, gen_objs, _gen_langs) = self.split_sources(generated_files)
+ (custom_src, custom_hdrs, custom_objs, _custom_langs) = self.split_sources(custom_target_output_files)
+ gen_src += custom_src
+ gen_hdrs += custom_hdrs
+
+ compiler = self._get_cl_compiler(target)
+ build_args = Vs2010Backend.get_build_args(compiler, self.buildtype, 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
+ proj_to_build_root = self.target_to_build_root(target)
+ # Prefix to use to access the source tree's root from the vcxproj dir
+ proj_to_src_root = os.path.join(proj_to_build_root, self.build_to_src)
+ # Prefix to use to access the source tree's subdir from the vcxproj dir
+ proj_to_src_dir = os.path.join(proj_to_src_root, self.get_target_dir(target))
+
+ (target_args, file_args), (target_defines, file_defines), (target_inc_dirs, file_inc_dirs) = self.get_args_defines_and_inc_dirs(
+ target, compiler, generated_files_include_dirs, proj_to_src_root, proj_to_src_dir, build_args)
+
+ if self.gen_lite:
+ assert vslite_ctx is not None
+ primary_src_lang = get_primary_source_lang(target.sources, custom_src)
+ self.add_gen_lite_makefile_vcxproj_elements(root, platform, tfilename[1], vslite_ctx, target, proj_to_build_root, primary_src_lang)
+ else:
+ self.add_non_makefile_vcxproj_elements(root, type_config, target, platform, subsystem, build_args, target_args, target_defines, target_inc_dirs, file_args)
+
meson_file_group = ET.SubElement(root, 'ItemGroup')
ET.SubElement(meson_file_group, 'None', Include=os.path.join(proj_to_src_dir, build_filename))
else:
return False
+ pch_sources = {}
+ if self.target_uses_pch(target):
+ for lang in ['c', 'cpp']:
+ pch = target.get_pch(lang)
+ if not pch:
+ continue
+ if compiler.id == 'msvc':
+ if len(pch) == 1:
+ # 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]
+ 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]
+
previous_includes = []
if len(headers) + len(gen_hdrs) + len(target.extra_files) + len(pch_sources) > 0:
+ if self.gen_lite and gen_hdrs:
+ # Although we're constructing our .vcxproj under our '..._vs' directory, we want to reference generated files
+ # in our concrete build directories (e.g. '..._debug'), where generated files will exist after building.
+ self.relocate_generated_file_paths_to_concrete_build_dir(gen_hdrs, target)
+
inc_hdrs = ET.SubElement(root, 'ItemGroup')
for h in headers:
- relpath = os.path.join(down, h.rel_to_builddir(self.build_to_src))
+ relpath = os.path.join(proj_to_build_root, h.rel_to_builddir(self.build_to_src))
if path_normalize_add(relpath, previous_includes):
ET.SubElement(inc_hdrs, 'CLInclude', Include=relpath)
for h in gen_hdrs:
if path_normalize_add(h, previous_includes):
ET.SubElement(inc_hdrs, 'CLInclude', Include=h)
for h in target.extra_files:
- relpath = os.path.join(down, h.rel_to_builddir(self.build_to_src))
+ relpath = os.path.join(proj_to_build_root, h.rel_to_builddir(self.build_to_src))
if path_normalize_add(relpath, previous_includes):
ET.SubElement(inc_hdrs, 'CLInclude', Include=relpath)
for headers in pch_sources.values():
previous_sources = []
if len(sources) + len(gen_src) + len(pch_sources) > 0:
+ if self.gen_lite:
+ # Get data to fill in intellisense fields for sources that can't reference the project-wide values
+ defs_paths_opts_per_lang_and_buildtype = get_non_primary_lang_intellisense_fields(
+ vslite_ctx,
+ target.get_id(),
+ primary_src_lang)
+ if gen_src:
+ # Although we're constructing our .vcxproj under our '..._vs' directory, we want to reference generated files
+ # in our concrete build directories (e.g. '..._debug'), where generated files will exist after building.
+ self.relocate_generated_file_paths_to_concrete_build_dir(gen_src, target)
+
inc_src = ET.SubElement(root, 'ItemGroup')
for s in sources:
- relpath = os.path.join(down, s.rel_to_builddir(self.build_to_src))
+ 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)
- 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)
- ET.SubElement(inc_cl, 'ObjectFileName').text = "$(IntDir)" + \
- self.object_filename_from_source(target, s)
+ 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)
+ 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)
+ ET.SubElement(inc_cl, 'ObjectFileName').text = "$(IntDir)" + \
+ self.object_filename_from_source(target, 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)
- 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)
- s = File.from_built_file(target.get_subdir(), s)
- ET.SubElement(inc_cl, 'ObjectFileName').text = "$(IntDir)" + \
- self.object_filename_from_source(target, 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)
+ 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)
+ s = File.from_built_file(target.get_subdir(), s)
+ ET.SubElement(inc_cl, 'ObjectFileName').text = "$(IntDir)" + \
+ self.object_filename_from_source(target, s)
for lang, headers in pch_sources.items():
impl = headers[1]
if impl and path_normalize_add(impl, previous_sources):
inc_cl = ET.SubElement(inc_src, 'CLCompile', Include=impl)
self.create_pch(pch_sources, lang, inc_cl)
- self.add_additional_options(lang, inc_cl, file_args)
- self.add_preprocessor_defines(lang, inc_cl, file_defines)
- pch_header_dir = pch_sources[lang][3]
- if pch_header_dir:
- inc_dirs = copy.deepcopy(file_inc_dirs)
- inc_dirs[lang] = [pch_header_dir] + inc_dirs[lang]
+ if self.gen_lite:
+ self.add_project_nmake_defs_incs_and_opts(inc_cl, impl, defs_paths_opts_per_lang_and_buildtype, platform)
else:
- inc_dirs = file_inc_dirs
- self.add_include_dirs(lang, inc_cl, inc_dirs)
- # XXX: Do we need to set the object file name name here too?
+ self.add_additional_options(lang, inc_cl, file_args)
+ self.add_preprocessor_defines(lang, inc_cl, file_defines)
+ pch_header_dir = pch_sources[lang][3]
+ if pch_header_dir:
+ inc_dirs = copy.deepcopy(file_inc_dirs)
+ inc_dirs[lang] = [pch_header_dir] + inc_dirs[lang]
+ else:
+ inc_dirs = file_inc_dirs
+ self.add_include_dirs(lang, inc_cl, inc_dirs)
+ # XXX: Do we need to set the object file name here too?
+
+ additional_objects = []
+ for o in self.flatten_object_list(target, proj_to_build_root)[0]:
+ assert isinstance(o, str)
+ additional_objects.append(o)
+ for o in custom_objs:
+ additional_objects.append(o)
previous_objects = []
if self.has_objects(objects, additional_objects, gen_objs):
inc_objs = ET.SubElement(root, 'ItemGroup')
for s in objects:
- relpath = os.path.join(down, s.rel_to_builddir(self.build_to_src))
+ relpath = os.path.join(proj_to_build_root, s.rel_to_builddir(self.build_to_src))
if path_normalize_add(relpath, previous_objects):
ET.SubElement(inc_objs, 'Object', Include=relpath)
for s in additional_objects:
ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.targets')
self.add_regen_dependency(root)
- self.add_target_deps(root, target)
+ if not self.gen_lite:
+ # Injecting further target dependencies into this vcxproj implies and forces a Visual Studio BUILD dependency,
+ # which we don't want when using 'genvslite'. A gen_lite build as little involvement with the visual studio's
+ # build system as possible.
+ self.add_target_deps(root, target)
self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname)
+ if self.environment.coredata.get_option(OptionKey('layout')) == 'mirror':
+ self.gen_vcxproj_filters(target, ofname)
+ return True
+
+ def gen_vcxproj_filters(self, target, ofname):
+ # Generate pitchfork of filters based on directory structure.
+ root = ET.Element('Project', {'ToolsVersion': '4.0',
+ 'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'})
+ filter_folders = ET.SubElement(root, 'ItemGroup')
+ filter_items = ET.SubElement(root, 'ItemGroup')
+ mlog.debug(f'Generating vcxproj filters {target.name}.')
+
+ def relative_to_defined_in(file):
+ # Get the relative path to file's directory from the location of the meson.build that defines this target.
+ return os.path.dirname(self.relpath(PureWindowsPath(file.subdir, file.fname), self.get_target_dir(target)))
+
+ found_folders_to_filter = {}
+ all_files = target.sources + target.extra_files
+
+ # Build a dictionary of all used relative paths (i.e. from the meson.build defining this target)
+ # for all sources.
+ for i in all_files:
+ if not os.path.isabs(i.fname):
+ dirname = relative_to_defined_in(i)
+ if dirname:
+ found_folders_to_filter[dirname] = ''
+
+ # Now walk up each of those relative paths checking for empty intermediate dirs to generate the filter.
+ for folder in found_folders_to_filter:
+ dirname = folder
+ filter = ''
+
+ while dirname:
+ basename = os.path.basename(dirname)
+
+ if filter == '':
+ filter = basename
+ else:
+ # Use '/' to squash empty dirs. To actually get a '\', use '%255c'.
+ filter = basename + ('\\' if dirname in found_folders_to_filter else '/') + filter
+
+ dirname = os.path.dirname(dirname)
+
+ # Don't add an empty filter, breaks all other (?) filters.
+ if filter != '':
+ found_folders_to_filter[folder] = filter
+ filter_element = ET.SubElement(filter_folders, 'Filter', {'Include': filter})
+ uuid_element = ET.SubElement(filter_element, 'UniqueIdentifier')
+ uuid_element.text = '{' + str(uuid.uuid4()).upper() + '}'
+
+ sources, headers, objects, _ = self.split_sources(all_files)
+ down = self.target_to_build_root(target)
+
+ def add_element(type_name, elements):
+ for i in elements:
+ if not os.path.isabs(i.fname):
+ dirname = relative_to_defined_in(i)
+
+ if dirname and dirname in found_folders_to_filter:
+ relpath = os.path.join(down, i.rel_to_builddir(self.build_to_src))
+ target_element = ET.SubElement(filter_items, type_name, {'Include': relpath})
+ filter_element = ET.SubElement(target_element, 'Filter')
+ filter_element.text = found_folders_to_filter[dirname]
+
+ add_element('ClCompile', sources)
+ add_element('ClInclude', headers)
+ add_element('Object', objects)
+
+ self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname + '.filters')
+
+ def gen_regenproj(self):
+ # To fully adapt the REGEN work for a 'genvslite' solution, to check timestamps, settings, and regenerate the
+ # '[builddir]_vs' solution/vcxprojs, as well as regenerating the accompanying buildtype-suffixed ninja build
+ # directories (from which we need to first collect correct, updated preprocessor defs and compiler options in
+ # order to fill in the regenerated solution's intellisense settings) would require some non-trivial intrusion
+ # into the 'meson --internal regencheck ./meson-private' execution path (and perhaps also the '--internal
+ # regenerate' and even 'meson setup --reconfigure' code). So, for now, we'll instead give the user a simpler
+ # 'reconfigure' utility project that just runs 'meson setup --reconfigure [builddir]_[buildtype] [srcdir]' on
+ # each of the ninja build dirs.
+ #
+ # FIXME: That will keep the building and compiling correctly configured but obviously won't update the
+ # solution and vcxprojs, which may allow solution src files and intellisense options to go out-of-date; the
+ # user would still have to manually 'meson setup --genvslite [vsxxxx] [builddir] [srcdir]' to fully regenerate
+ # a complete and correct solution.
+ if self.gen_lite:
+ project_name = 'RECONFIGURE'
+ ofname = os.path.join(self.environment.get_build_dir(), 'RECONFIGURE.vcxproj')
+ conftype = 'Makefile'
+ # I find the REGEN project doesn't work; it fails to invoke the appropriate -
+ # python meson.py --internal regencheck builddir\meson-private
+ # command, despite the fact that manually running such a command in a shell runs just fine.
+ # Running/building the regen project produces the error -
+ # ...Microsoft.CppBuild.targets(460,5): error MSB8020: The build tools for ClangCL (Platform Toolset = 'ClangCL') cannot be found. To build using the ClangCL build tools, please install ...
+ # Not sure why but a simple makefile-style project that executes the full '...regencheck...' command actually works (and seems a little simpler).
+ # Although I've limited this change to only happen under '--genvslite', perhaps ...
+ # FIXME : Should all utility projects use the simpler and less problematic makefile-style project?
+ else:
+ project_name = 'REGEN'
+ ofname = os.path.join(self.environment.get_build_dir(), 'REGEN.vcxproj')
+ conftype = 'Utility'
- def gen_regenproj(self, project_name, ofname):
guid = self.environment.coredata.regen_guid
(root, type_config) = self.create_basic_project(project_name,
temp_dir='regen-temp',
- guid=guid)
-
- action = ET.SubElement(root, 'ItemDefinitionGroup')
- midl = ET.SubElement(action, 'Midl')
- ET.SubElement(midl, "AdditionalIncludeDirectories").text = '%(AdditionalIncludeDirectories)'
- ET.SubElement(midl, "OutputDirectory").text = '$(IntDir)'
- ET.SubElement(midl, 'HeaderFileName').text = '%(Filename).h'
- ET.SubElement(midl, 'TypeLibraryName').text = '%(Filename).tlb'
- ET.SubElement(midl, 'InterfaceIdentifierFilename').text = '%(Filename)_i.c'
- ET.SubElement(midl, 'ProxyFileName').text = '%(Filename)_p.c'
- regen_command = self.environment.get_build_command() + ['--internal', 'regencheck']
- cmd_templ = '''call %s > NUL
+ guid=guid,
+ conftype=conftype
+ )
+
+ if self.gen_lite:
+ (nmake_base_meson_command, exe_search_paths) = Vs2010Backend.get_nmake_base_meson_command_and_exe_search_paths()
+ all_configs_prop_group = ET.SubElement(root, 'PropertyGroup')
+
+ # Multi-line command to reconfigure all buildtype-suffixed build dirs
+ multi_config_buildtype_list = coredata.get_genvs_default_buildtype_list()
+ (_, build_dir_tail) = os.path.split(self.src_to_build)
+ proj_to_multiconfigured_builds_parent_dir = '..' # We know this RECONFIGURE.vcxproj will always be in the '[buildir]_vs' dir.
+ proj_to_src_dir = self.build_to_src
+ reconfigure_all_cmd = ''
+ for buildtype in multi_config_buildtype_list:
+ meson_build_dir_for_buildtype = build_dir_tail[:-2] + buildtype # Get the buildtype suffixed 'builddir_[debug/release/etc]' from 'builddir_vs', for example.
+ proj_to_build_dir_for_buildtype = str(os.path.join(proj_to_multiconfigured_builds_parent_dir, meson_build_dir_for_buildtype))
+ reconfigure_all_cmd += f'{nmake_base_meson_command} setup --reconfigure "{proj_to_build_dir_for_buildtype}" "{proj_to_src_dir}"\n'
+ ET.SubElement(all_configs_prop_group, 'NMakeBuildCommandLine').text = reconfigure_all_cmd
+ ET.SubElement(all_configs_prop_group, 'NMakeReBuildCommandLine').text = reconfigure_all_cmd
+ ET.SubElement(all_configs_prop_group, 'NMakeCleanCommandLine').text = ''
+
+ #Need to set the 'ExecutablePath' element for the above NMake... commands to be able to execute
+ ET.SubElement(all_configs_prop_group, 'ExecutablePath').text = exe_search_paths
+ else:
+ action = ET.SubElement(root, 'ItemDefinitionGroup')
+ midl = ET.SubElement(action, 'Midl')
+ ET.SubElement(midl, "AdditionalIncludeDirectories").text = '%(AdditionalIncludeDirectories)'
+ ET.SubElement(midl, "OutputDirectory").text = '$(IntDir)'
+ ET.SubElement(midl, 'HeaderFileName').text = '%(Filename).h'
+ ET.SubElement(midl, 'TypeLibraryName').text = '%(Filename).tlb'
+ ET.SubElement(midl, 'InterfaceIdentifierFilename').text = '%(Filename)_i.c'
+ ET.SubElement(midl, 'ProxyFileName').text = '%(Filename)_p.c'
+ regen_command = self.environment.get_build_command() + ['--internal', 'regencheck']
+ cmd_templ = '''call %s > NUL
"%s" "%s"'''
- regen_command = cmd_templ % \
- (self.get_vcvars_command(), '" "'.join(regen_command), self.environment.get_scratch_dir())
- self.add_custom_build(root, 'regen', regen_command, deps=self.get_regen_filelist(),
- outputs=[Vs2010Backend.get_regen_stampfile(self.environment.get_build_dir())],
- msg='Checking whether solution needs to be regenerated.')
+ regen_command = cmd_templ % \
+ (self.get_vcvars_command(), '" "'.join(regen_command), self.environment.get_scratch_dir())
+ self.add_custom_build(root, 'regen', regen_command, deps=self.get_regen_filelist(),
+ outputs=[Vs2010Backend.get_regen_stampfile(self.environment.get_build_dir())],
+ msg='Checking whether solution needs to be regenerated.')
+
ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.targets')
ET.SubElement(root, 'ImportGroup', Label='ExtensionTargets')
self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname)
- def gen_testproj(self, target_name, ofname):
+ def gen_testproj(self):
+ project_name = 'RUN_TESTS'
+ ofname = os.path.join(self.environment.get_build_dir(), f'{project_name}.vcxproj')
guid = self.environment.coredata.test_guid
- (root, type_config) = self.create_basic_project(target_name,
- temp_dir='test-temp',
- guid=guid)
+ if self.gen_lite:
+ (root, type_config) = self.create_basic_project(project_name,
+ temp_dir='install-temp',
+ guid=guid,
+ conftype='Makefile'
+ )
+ (nmake_base_meson_command, exe_search_paths) = Vs2010Backend.get_nmake_base_meson_command_and_exe_search_paths()
+ multi_config_buildtype_list = coredata.get_genvs_default_buildtype_list()
+ (_, build_dir_tail) = os.path.split(self.src_to_build)
+ proj_to_multiconfigured_builds_parent_dir = '..' # We know this .vcxproj will always be in the '[buildir]_vs' dir.
+ # Add appropriate 'test' commands for the 'build' action of this project, for all buildtypes
+ for buildtype in multi_config_buildtype_list:
+ meson_build_dir_for_buildtype = build_dir_tail[:-2] + buildtype # Get the buildtype suffixed 'builddir_[debug/release/etc]' from 'builddir_vs', for example.
+ proj_to_build_dir_for_buildtype = str(os.path.join(proj_to_multiconfigured_builds_parent_dir, meson_build_dir_for_buildtype))
+ test_cmd = f'{nmake_base_meson_command} test -C "{proj_to_build_dir_for_buildtype}" --no-rebuild'
+ if not self.environment.coredata.get_option(OptionKey('stdsplit')):
+ test_cmd += ' --no-stdsplit'
+ if self.environment.coredata.get_option(OptionKey('errorlogs')):
+ test_cmd += ' --print-errorlogs'
+ condition = f'\'$(Configuration)|$(Platform)\'==\'{buildtype}|{self.platform}\''
+ prop_group = ET.SubElement(root, 'PropertyGroup', Condition=condition)
+ ET.SubElement(prop_group, 'NMakeBuildCommandLine').text = test_cmd
+ #Need to set the 'ExecutablePath' element for the NMake... commands to be able to execute
+ ET.SubElement(prop_group, 'ExecutablePath').text = exe_search_paths
+ else:
+ (root, type_config) = self.create_basic_project(project_name,
+ temp_dir='test-temp',
+ guid=guid)
+
+ action = ET.SubElement(root, 'ItemDefinitionGroup')
+ midl = ET.SubElement(action, 'Midl')
+ ET.SubElement(midl, "AdditionalIncludeDirectories").text = '%(AdditionalIncludeDirectories)'
+ ET.SubElement(midl, "OutputDirectory").text = '$(IntDir)'
+ ET.SubElement(midl, 'HeaderFileName').text = '%(Filename).h'
+ ET.SubElement(midl, 'TypeLibraryName').text = '%(Filename).tlb'
+ ET.SubElement(midl, 'InterfaceIdentifierFilename').text = '%(Filename)_i.c'
+ ET.SubElement(midl, 'ProxyFileName').text = '%(Filename)_p.c'
+ # FIXME: No benchmarks?
+ test_command = self.environment.get_build_command() + ['test', '--no-rebuild']
+ if not self.environment.coredata.get_option(OptionKey('stdsplit')):
+ test_command += ['--no-stdsplit']
+ if self.environment.coredata.get_option(OptionKey('errorlogs')):
+ test_command += ['--print-errorlogs']
+ self.serialize_tests()
+ self.add_custom_build(root, 'run_tests', '"%s"' % ('" "'.join(test_command)))
- action = ET.SubElement(root, 'ItemDefinitionGroup')
- midl = ET.SubElement(action, 'Midl')
- ET.SubElement(midl, "AdditionalIncludeDirectories").text = '%(AdditionalIncludeDirectories)'
- ET.SubElement(midl, "OutputDirectory").text = '$(IntDir)'
- ET.SubElement(midl, 'HeaderFileName').text = '%(Filename).h'
- ET.SubElement(midl, 'TypeLibraryName').text = '%(Filename).tlb'
- ET.SubElement(midl, 'InterfaceIdentifierFilename').text = '%(Filename)_i.c'
- ET.SubElement(midl, 'ProxyFileName').text = '%(Filename)_p.c'
- # FIXME: No benchmarks?
- test_command = self.environment.get_build_command() + ['test', '--no-rebuild']
- if not self.environment.coredata.get_option(OptionKey('stdsplit')):
- test_command += ['--no-stdsplit']
- if self.environment.coredata.get_option(OptionKey('errorlogs')):
- test_command += ['--print-errorlogs']
- self.serialize_tests()
- self.add_custom_build(root, 'run_tests', '"%s"' % ('" "'.join(test_command)))
ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.targets')
self.add_regen_dependency(root)
self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname)
- def gen_installproj(self, target_name, ofname):
- self.create_install_data_files()
-
+ def gen_installproj(self):
+ project_name = 'RUN_INSTALL'
+ ofname = os.path.join(self.environment.get_build_dir(), f'{project_name}.vcxproj')
guid = self.environment.coredata.install_guid
- (root, type_config) = self.create_basic_project(target_name,
- temp_dir='install-temp',
- guid=guid)
+ if self.gen_lite:
+ (root, type_config) = self.create_basic_project(project_name,
+ temp_dir='install-temp',
+ guid=guid,
+ conftype='Makefile'
+ )
+ (nmake_base_meson_command, exe_search_paths) = Vs2010Backend.get_nmake_base_meson_command_and_exe_search_paths()
+ multi_config_buildtype_list = coredata.get_genvs_default_buildtype_list()
+ (_, build_dir_tail) = os.path.split(self.src_to_build)
+ proj_to_multiconfigured_builds_parent_dir = '..' # We know this .vcxproj will always be in the '[buildir]_vs' dir.
+ # Add appropriate 'install' commands for the 'build' action of this project, for all buildtypes
+ for buildtype in multi_config_buildtype_list:
+ meson_build_dir_for_buildtype = build_dir_tail[:-2] + buildtype # Get the buildtype suffixed 'builddir_[debug/release/etc]' from 'builddir_vs', for example.
+ proj_to_build_dir_for_buildtype = str(os.path.join(proj_to_multiconfigured_builds_parent_dir, meson_build_dir_for_buildtype))
+ install_cmd = f'{nmake_base_meson_command} install -C "{proj_to_build_dir_for_buildtype}" --no-rebuild'
+ condition = f'\'$(Configuration)|$(Platform)\'==\'{buildtype}|{self.platform}\''
+ prop_group = ET.SubElement(root, 'PropertyGroup', Condition=condition)
+ ET.SubElement(prop_group, 'NMakeBuildCommandLine').text = install_cmd
+ #Need to set the 'ExecutablePath' element for the NMake... commands to be able to execute
+ ET.SubElement(prop_group, 'ExecutablePath').text = exe_search_paths
+ else:
+ self.create_install_data_files()
+
+ (root, type_config) = self.create_basic_project(project_name,
+ temp_dir='install-temp',
+ guid=guid)
+
+ action = ET.SubElement(root, 'ItemDefinitionGroup')
+ midl = ET.SubElement(action, 'Midl')
+ ET.SubElement(midl, "AdditionalIncludeDirectories").text = '%(AdditionalIncludeDirectories)'
+ ET.SubElement(midl, "OutputDirectory").text = '$(IntDir)'
+ ET.SubElement(midl, 'HeaderFileName').text = '%(Filename).h'
+ ET.SubElement(midl, 'TypeLibraryName').text = '%(Filename).tlb'
+ ET.SubElement(midl, 'InterfaceIdentifierFilename').text = '%(Filename)_i.c'
+ ET.SubElement(midl, 'ProxyFileName').text = '%(Filename)_p.c'
+ install_command = self.environment.get_build_command() + ['install', '--no-rebuild']
+ self.add_custom_build(root, 'run_install', '"%s"' % ('" "'.join(install_command)))
- action = ET.SubElement(root, 'ItemDefinitionGroup')
- midl = ET.SubElement(action, 'Midl')
- ET.SubElement(midl, "AdditionalIncludeDirectories").text = '%(AdditionalIncludeDirectories)'
- ET.SubElement(midl, "OutputDirectory").text = '$(IntDir)'
- ET.SubElement(midl, 'HeaderFileName').text = '%(Filename).h'
- ET.SubElement(midl, 'TypeLibraryName').text = '%(Filename).tlb'
- ET.SubElement(midl, 'InterfaceIdentifierFilename').text = '%(Filename)_i.c'
- ET.SubElement(midl, 'ProxyFileName').text = '%(Filename)_p.c'
- install_command = self.environment.get_build_command() + ['install', '--no-rebuild']
- self.add_custom_build(root, 'run_install', '"%s"' % ('" "'.join(install_command)))
ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.targets')
self.add_regen_dependency(root)
self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname)
- def add_custom_build(self, node, rulename, command, deps=None, outputs=None, msg=None, verify_files=True):
+ def add_custom_build(self, node: ET.Element, rulename: str, command: str, deps: T.Optional[T.List[str]] = None,
+ outputs: T.Optional[T.List[str]] = None, msg: T.Optional[str] = None, verify_files: bool = True) -> None:
igroup = ET.SubElement(node, 'ItemGroup')
rulefile = os.path.join(self.environment.get_scratch_dir(), rulename + '.rule')
if not os.path.exists(rulefile):
ET.SubElement(custombuild, 'AdditionalInputs').text = ';'.join(deps)
@staticmethod
- def nonexistent_file(prefix):
+ def nonexistent_file(prefix: str) -> str:
i = 0
file = prefix
while os.path.exists(file):
file = '%s%d' % (prefix, i)
return file
- def generate_debug_information(self, link):
+ def generate_debug_information(self, link: ET.Element) -> None:
# valid values for vs2015 is 'false', 'true', 'DebugFastLink'
ET.SubElement(link, 'GenerateDebugInformation').text = 'true'
- def add_regen_dependency(self, root):
- regen_vcxproj = os.path.join(self.environment.get_build_dir(), 'REGEN.vcxproj')
- self.add_project_reference(root, regen_vcxproj, self.environment.coredata.regen_guid)
+ def add_regen_dependency(self, root: ET.Element) -> None:
+ # For now, with 'genvslite' solutions, REGEN is replaced by the lighter-weight RECONFIGURE utility that is
+ # no longer a forced build dependency. See comment in 'gen_regenproj'
+ if not self.gen_lite:
+ regen_vcxproj = os.path.join(self.environment.get_build_dir(), 'REGEN.vcxproj')
+ self.add_project_reference(root, regen_vcxproj, self.environment.coredata.regen_guid)
- def generate_lang_standard_info(self, file_args, clconf):
+ def generate_lang_standard_info(self, file_args: T.Dict[str, CompilerArgs], clconf: ET.Element) -> None:
pass
from ..interpreter import Interpreter
class Vs2012Backend(Vs2010Backend):
+
+ name = 'vs2012'
+
def __init__(self, build: T.Optional[Build], interpreter: T.Optional[Interpreter]):
super().__init__(build, interpreter)
- self.name = 'vs2012'
self.vs_version = '2012'
self.sln_file_version = '12.00'
self.sln_version_comment = '2012'
from ..interpreter import Interpreter
class Vs2013Backend(Vs2010Backend):
+
+ name = 'vs2013'
+
def __init__(self, build: T.Optional[Build], interpreter: T.Optional[Interpreter]):
super().__init__(build, interpreter)
- self.name = 'vs2013'
self.vs_version = '2013'
self.sln_file_version = '12.00'
self.sln_version_comment = '2013'
from ..interpreter import Interpreter
class Vs2015Backend(Vs2010Backend):
+
+ name = 'vs2015'
+
def __init__(self, build: T.Optional[Build], interpreter: T.Optional[Interpreter]):
super().__init__(build, interpreter)
- self.name = 'vs2015'
self.vs_version = '2015'
self.sln_file_version = '12.00'
self.sln_version_comment = '14'
class Vs2017Backend(Vs2010Backend):
+
+ name = 'vs2017'
+
def __init__(self, build: T.Optional[Build], interpreter: T.Optional[Interpreter]):
super().__init__(build, interpreter)
- self.name = 'vs2017'
self.vs_version = '2017'
self.sln_file_version = '12.00'
self.sln_version_comment = '15'
class Vs2019Backend(Vs2010Backend):
+
+ name = 'vs2019'
+
def __init__(self, build: T.Optional[Build], interpreter: T.Optional[Interpreter]):
super().__init__(build, interpreter)
- self.name = 'vs2019'
self.sln_file_version = '12.00'
self.sln_version_comment = 'Version 16'
if self.environment is not None:
class Vs2022Backend(Vs2010Backend):
- def __init__(self, build: T.Optional[Build], interpreter: T.Optional[Interpreter]):
- super().__init__(build, interpreter)
- self.name = 'vs2022'
+
+ 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)
self.sln_file_version = '12.00'
self.sln_version_comment = 'Version 17'
if self.environment is not None:
from . import backends
from .. import build
-from .. import dependencies
from .. import mesonlib
from .. import mlog
-from ..mesonlib import MesonException, OptionKey
+from ..mesonlib import MesonBugException, MesonException, OptionKey
if T.TYPE_CHECKING:
from ..interpreter import Interpreter
class FileTreeEntry:
- def __init__(self):
+ def __init__(self) -> None:
self.subdirs = {}
self.targets = []
-class PbxItem:
- def __init__(self, value, comment = ''):
- self.value = value
- self.comment = comment
-
class PbxArray:
- def __init__(self):
+ def __init__(self) -> None:
self.items = []
- def add_item(self, item, comment=''):
+ def add_item(self, item: T.Union[PbxArrayItem, str], comment: str = '') -> None:
if isinstance(item, PbxArrayItem):
self.items.append(item)
else:
self.items.append(PbxArrayItem(item, comment))
- def write(self, ofile, indent_level):
+ def write(self, ofile: T.TextIO, indent_level: int) -> None:
ofile.write('(\n')
indent_level += 1
for i in self.items:
ofile.write(indent_level*INDENT + ');\n')
class PbxArrayItem:
- def __init__(self, value, comment = ''):
+ def __init__(self, value: str, comment: str = ''):
self.value = value
if comment:
if '/*' in comment:
self.comment = comment
class PbxComment:
- def __init__(self, text):
+ def __init__(self, text: str):
assert isinstance(text, str)
assert '/*' not in text
self.text = f'/* {text} */'
- def write(self, ofile, indent_level):
+ def write(self, ofile: T.TextIO, indent_level: int) -> None:
ofile.write(f'\n{self.text}\n')
class PbxDictItem:
- def __init__(self, key, value, comment = ''):
+ def __init__(self, key: str, value: T.Union[PbxArray, PbxDict, str, int], comment: str = ''):
self.key = key
self.value = value
if comment:
self.comment = comment
class PbxDict:
- def __init__(self):
+ def __init__(self) -> None:
# This class is a bit weird, because we want to write PBX dicts in
# defined order _and_ we want to write intermediate comments also in order.
self.keys = set()
self.items = []
- def add_item(self, key, value, comment=''):
+ def add_item(self, key: str, value: T.Union[PbxArray, PbxDict, str, int], comment: str = '') -> None:
assert key not in self.keys
item = PbxDictItem(key, value, comment)
self.keys.add(key)
def has_item(self, key):
return key in self.keys
- def add_comment(self, comment):
- if isinstance(comment, str):
- self.items.append(PbxComment(str))
- else:
- assert isinstance(comment, PbxComment)
- self.items.append(comment)
+ def add_comment(self, comment: PbxComment) -> None:
+ assert isinstance(comment, PbxComment)
+ self.items.append(comment)
- def write(self, ofile, indent_level):
+ def write(self, ofile: T.TextIO, indent_level: int) -> None:
ofile.write('{\n')
indent_level += 1
for i in self.items:
ofile.write(';\n')
class XCodeBackend(backends.Backend):
+
+ name = 'xcode'
+
def __init__(self, build: T.Optional[build.Build], interpreter: T.Optional[Interpreter]):
super().__init__(build, interpreter)
- self.name = 'xcode'
self.project_uid = self.environment.coredata.lang_guids['default'].replace('-', '')[:24]
- self.buildtype = self.environment.coredata.get_option(OptionKey('buildtype'))
+ self.buildtype = T.cast('str', self.environment.coredata.get_option(OptionKey('buildtype')))
self.project_conflist = self.gen_id()
self.maingroup_id = self.gen_id()
self.all_id = self.gen_id()
top_level_dict.write(ofile, 0)
os.replace(tmpname, ofilename)
- def gen_id(self):
+ def gen_id(self) -> str:
return str(uuid.uuid4()).upper().replace('-', '')[:24]
def get_target_dir(self, target):
- dirname = os.path.join(target.get_subdir(), self.environment.coredata.get_option(OptionKey('buildtype')))
+ dirname = os.path.join(target.get_subdir(), T.cast('str', self.environment.coredata.get_option(OptionKey('buildtype'))))
#os.makedirs(os.path.join(self.environment.get_build_dir(), dirname), exist_ok=True)
return dirname
obj_path = f'{project}.build/{buildtype}/{tname}.build/Objects-normal/{arch}/{stem}.o'
return obj_path
- def generate(self):
+ def generate(self, capture: bool = False, vslite_ctx: dict = None) -> T.Optional[dict]:
+ # Check for (currently) unexpected capture arg use cases -
+ if capture:
+ raise MesonBugException('We do not expect the xcode backend to generate with \'capture = True\'')
+ if vslite_ctx:
+ raise MesonBugException('We do not expect the xcode backend to be given a valid \'vslite_ctx\'')
self.serialize_tests()
# Cache the result as the method rebuilds the array every time it is called.
self.build_targets = self.build.get_build_targets()
xcodetype = 'sourcecode.unknown'
return xcodetype
- def generate_filemap(self):
+ def generate_filemap(self) -> None:
self.filemap = {} # Key is source file relative to src root.
self.target_filemap = {}
for name, t in self.build_targets.items():
self.filemap[o] = self.gen_id()
self.target_filemap[name] = self.gen_id()
- def generate_buildstylemap(self):
+ def generate_buildstylemap(self) -> None:
self.buildstylemap = {self.buildtype: self.gen_id()}
- def generate_build_phase_map(self):
+ def generate_build_phase_map(self) -> None:
for tname, t in self.build_targets.items():
# generate id for our own target-name
t.buildphasemap = {}
t.buildphasemap['Resources'] = self.gen_id()
t.buildphasemap['Sources'] = self.gen_id()
- def generate_build_configuration_map(self):
+ def generate_build_configuration_map(self) -> None:
self.buildconfmap = {}
for t in self.build_targets:
bconfs = {self.buildtype: self.gen_id()}
bconfs = {self.buildtype: self.gen_id()}
self.buildconfmap[t] = bconfs
- def generate_project_configurations_map(self):
+ def generate_project_configurations_map(self) -> None:
self.project_configurations = {self.buildtype: self.gen_id()}
- def generate_buildall_configurations_map(self):
+ def generate_buildall_configurations_map(self) -> None:
self.buildall_configurations = {self.buildtype: self.gen_id()}
- def generate_test_configurations_map(self):
+ def generate_test_configurations_map(self) -> None:
self.test_configurations = {self.buildtype: self.gen_id()}
- def generate_build_configurationlist_map(self):
+ def generate_build_configurationlist_map(self) -> None:
self.buildconflistmap = {}
for t in self.build_targets:
self.buildconflistmap[t] = self.gen_id()
for t in self.custom_targets:
self.buildconflistmap[t] = self.gen_id()
- def generate_native_target_map(self):
+ def generate_native_target_map(self) -> None:
self.native_targets = {}
for t in self.build_targets:
self.native_targets[t] = self.gen_id()
- def generate_custom_target_map(self):
+ def generate_custom_target_map(self) -> None:
self.shell_targets = {}
self.custom_target_output_buildfile = {}
self.custom_target_output_fileref = {}
self.custom_target_output_buildfile[o] = self.gen_id()
self.custom_target_output_fileref[o] = self.gen_id()
- def generate_generator_target_map(self):
+ def generate_generator_target_map(self) -> None:
# Generator objects do not have natural unique ids
# so use a counter.
self.generator_fileref_ids = {}
self.generator_buildfile_ids[k] = buildfile_ids
self.generator_fileref_ids[k] = fileref_ids
- def generate_native_frameworks_map(self):
+ def generate_native_frameworks_map(self) -> None:
self.native_frameworks = {}
self.native_frameworks_fileref = {}
for t in self.build_targets.values():
for dep in t.get_external_deps():
- if isinstance(dep, dependencies.AppleFrameworks):
+ if dep.name == 'appleframeworks':
for f in dep.frameworks:
self.native_frameworks[f] = self.gen_id()
self.native_frameworks_fileref[f] = self.gen_id()
- def generate_target_dependency_map(self):
+ def generate_target_dependency_map(self) -> None:
self.target_dependency_map = {}
for tname, t in self.build_targets.items():
for target in t.link_targets:
assert k not in self.target_dependency_map
self.target_dependency_map[k] = self.gen_id()
- def generate_pbxdep_map(self):
+ def generate_pbxdep_map(self) -> None:
self.pbx_dep_map = {}
self.pbx_custom_dep_map = {}
for t in self.build_targets:
for t in self.custom_targets:
self.pbx_custom_dep_map[t] = self.gen_id()
- def generate_containerproxy_map(self):
+ def generate_containerproxy_map(self) -> None:
self.containerproxy_map = {}
for t in self.build_targets:
self.containerproxy_map[t] = self.gen_id()
- def generate_target_file_maps(self):
+ def generate_target_file_maps(self) -> None:
self.generate_target_file_maps_impl(self.build_targets)
self.generate_target_file_maps_impl(self.custom_targets)
else:
raise RuntimeError('Unknown input type ' + str(o))
- def generate_build_file_maps(self):
+ def generate_build_file_maps(self) -> None:
for buildfile in self.interpreter.get_build_def_files():
assert isinstance(buildfile, str)
self.buildfile_ids[buildfile] = self.gen_id()
self.fileref_ids[buildfile] = self.gen_id()
- def generate_source_phase_map(self):
+ def generate_source_phase_map(self) -> None:
self.source_phase = {}
for t in self.build_targets:
self.source_phase[t] = self.gen_id()
def generate_pbx_build_file(self, objects_dict):
for tname, t in self.build_targets.items():
for dep in t.get_external_deps():
- if isinstance(dep, dependencies.AppleFrameworks):
+ if dep.name == 'appleframeworks':
for f in dep.frameworks:
fw_dict = PbxDict()
fwkey = self.native_frameworks[f]
def generate_pbx_file_reference(self, objects_dict):
for tname, t in self.build_targets.items():
for dep in t.get_external_deps():
- if isinstance(dep, dependencies.AppleFrameworks):
+ if dep.name == 'appleframeworks':
for f in dep.frameworks:
fw_dict = PbxDict()
framework_fileref = self.native_frameworks_fileref[f]
file_list = PbxArray()
bt_dict.add_item('files', file_list)
for dep in t.get_external_deps():
- if isinstance(dep, dependencies.AppleFrameworks):
+ if dep.name == 'appleframeworks':
for f in dep.frameworks:
file_list.add_item(self.native_frameworks[f], f'{f}.framework in Frameworks')
bt_dict.add_item('runOnlyForDeploymentPostprocessing', 0)
for t in self.build_targets.values():
for dep in t.get_external_deps():
- if isinstance(dep, dependencies.AppleFrameworks):
+ if dep.name == 'appleframeworks':
for f in dep.frameworks:
frameworks_children.add_item(self.native_frameworks_fileref[f], f)
group_id = self.write_group_target_entry(objects_dict, target)
children_array.add_item(group_id)
potentials = [os.path.join(current_subdir, 'meson.build'),
+ os.path.join(current_subdir, 'meson.options'),
os.path.join(current_subdir, 'meson_options.txt')]
for bf in potentials:
i = self.fileref_ids.get(bf, None)
generator_id += 1
def generate_single_generator_phase(self, tname, t, genlist, generator_id, objects_dict):
+ # 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()
exe = generator.get_exe()
exe_arr = self.build_target_to_cmd_array(exe)
else:
raise RuntimeError(o)
if isinstance(target, build.SharedModule):
- options = self.environment.coredata.options
- ldargs += linker.get_std_shared_module_link_args(options)
+ ldargs += linker.get_std_shared_module_link_args(target.get_options())
elif isinstance(target, build.SharedLibrary):
ldargs += linker.get_std_shared_lib_link_args()
ldstr = ' '.join(ldargs)
# add the root build dir to the search path. So add an absolute path instead.
# This may break reproducible builds, in which case patches are welcome.
lang_cargs += self.get_custom_target_dir_include_args(target, compiler, absolute_path=True)
- # Xcode can not handle separate compilation flags for C and ObjectiveC. They are both
+ # Xcode cannot handle separate compilation flags for C and ObjectiveC. They are both
# put in OTHER_CFLAGS. Same with C++ and ObjectiveC++.
if lang == 'objc':
lang = 'c'
quoted_args.append(a)
settings_dict.add_item(f'OTHER_{langname}FLAGS', '"' + ' '.join(quoted_args) + '"')
- def generate_xc_configurationList(self, objects_dict):
+ def generate_xc_configurationList(self, objects_dict: PbxDict) -> None:
# FIXME: sort items
conf_dict = PbxDict()
objects_dict.add_item(self.project_conflist, conf_dict, f'Build configuration list for PBXProject "{self.build.project_name}"')
t_dict.add_item('defaultConfigurationIsVisible', 0)
t_dict.add_item('defaultConfigurationName', self.buildtype)
- def generate_prefix(self, pbxdict):
+ def generate_prefix(self, pbxdict: PbxDict) -> PbxDict:
pbxdict.add_item('archiveVersion', '1')
pbxdict.add_item('classes', PbxDict())
pbxdict.add_item('objectVersion', '46')
return objects_dict
- def generate_suffix(self, pbxdict):
+ def generate_suffix(self, pbxdict: PbxDict) -> None:
pbxdict.add_item('rootObject', self.project_uid, 'Project object')
from __future__ import annotations
from collections import defaultdict, OrderedDict
-from dataclasses import dataclass, field
+from dataclasses import dataclass, field, InitVar
from functools import lru_cache
-import copy
+import abc
import hashlib
import itertools, pathlib
import os
import textwrap
import typing as T
-
-from . import environment
+from . import coredata
from . import dependencies
from . import mlog
from . import programs
File, MesonException, MachineChoice, PerMachine, OrderedSet, listify,
extract_as_list, typeslistify, stringlistify, classify_unity_sources,
get_filenames_templates_dict, substitute_values, has_path_sep,
- OptionKey, PerMachineDefaultable, OptionOverrideProxy,
+ OptionKey, PerMachineDefaultable,
MesonBugException, EnvironmentVariables, pickle_load,
)
from .compilers import (
- is_object, clink_langs, sort_clink, all_languages,
+ is_header, is_object, is_source, clink_langs, sort_clink, all_languages,
is_known_suffix, detect_static_linker
)
from .interpreterbase import FeatureNew, FeatureDeprecated
if T.TYPE_CHECKING:
- from typing_extensions import Literal
+ from typing_extensions import Literal, TypedDict
+
+ from . import environment
from ._typing import ImmutableListProtocol
- from .backend.backends import Backend, ExecutableSerialisation
+ from .backend.backends import Backend
from .compilers import Compiler
- from .interpreter.interpreter import Test, SourceOutputs, Interpreter
+ from .interpreter.interpreter import SourceOutputs, Interpreter
+ from .interpreter.interpreterobjects import Test
from .interpreterbase import SubProject
- from .linkers import StaticLinker
- from .mesonlib import FileMode, FileOrString
+ from .linkers.linkers import StaticLinker
+ from .mesonlib import ExecutableSerialisation, FileMode, FileOrString
from .modules import ModuleState
from .mparser import BaseNode
from .wrap import WrapMode
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']
+
+ class DFeatures(TypedDict):
+
+ unittest: bool
+ debug: T.List[T.Union[str, int]]
+ import_dirs: T.List[IncludeDirs]
+ versions: T.List[T.Union[str, int]]
pch_kwargs = {'c_pch', 'cpp_pch'}
}
vala_kwargs = {'vala_header', 'vala_gir', 'vala_vapi'}
-rust_kwargs = {'rust_crate_type'}
+rust_kwargs = {'rust_crate_type', 'rust_dependency_map'}
cs_kwargs = {'resources', 'cs_args'}
buildtarget_kwargs = {
rust_kwargs |
cs_kwargs)
-known_exe_kwargs = known_build_target_kwargs | {'implib', 'export_dynamic', 'pie'}
-known_shlib_kwargs = known_build_target_kwargs | {'version', 'soversion', 'vs_module_defs', 'darwin_versions'}
-known_shmod_kwargs = known_build_target_kwargs | {'vs_module_defs'}
-known_stlib_kwargs = known_build_target_kwargs | {'pic', 'prelink'}
+known_exe_kwargs = known_build_target_kwargs | {'implib', 'export_dynamic', 'pie', 'vs_module_defs'}
+known_shlib_kwargs = known_build_target_kwargs | {'version', 'soversion', 'vs_module_defs', 'darwin_versions', 'rust_abi'}
+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'}
def _process_install_tag(install_tag: T.Optional[T.List[T.Optional[str]]],
custom_install_dir: T.Optional[str]
custom_install_mode: 'FileMode'
subproject: str
+ follow_symlinks: T.Optional[bool] = None
# TODO: we really don't need any of these methods, but they're preserved to
# keep APIs relying on them working.
subproject: str
from_source_dir: bool = True
install_tag: T.Optional[str] = None
+ follow_symlinks: T.Optional[bool] = None
@dataclass(eq=False)
class DepManifest:
version: str
license: T.List[str]
+ license_files: T.List[T.Tuple[str, File]]
+ subproject: str
def to_json(self) -> T.Dict[str, T.Union[str, T.List[str]]]:
return {
'version': self.version,
'license': self.license,
+ 'license_files': [l[1].relative_name() for l in self.license_files],
}
"""
def __init__(self, environment: environment.Environment):
+ self.version = coredata.version
self.project_name = 'name of master project'
self.project_version = None
self.environment = environment
self.projects = {}
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({}, {})
self.global_link_args: PerMachine[T.Dict[str, T.List[str]]] = PerMachine({}, {})
self.projects_args: PerMachine[T.Dict[str, T.Dict[str, T.List[str]]]] = PerMachine({}, {})
environment.is_cross_build(), {}, {})
self.devenv: T.List[EnvironmentVariables] = []
self.modules: T.List[str] = []
- self.need_vsenv = False
def get_build_targets(self):
build_targets = OrderedDict()
def get_extra_build_dirs(self) -> T.List[str]:
return self.extra_build_dirs
- def to_string_list(self, sourcedir: str, builddir: T.Optional[str] = None) -> T.List[str]:
+ def to_string_list(self, sourcedir: str, builddir: str) -> T.List[str]:
"""Convert IncludeDirs object to a list of strings.
:param sourcedir: The absolute source directory
strlist: T.List[str] = []
for idir in self.incdirs:
strlist.append(os.path.join(sourcedir, self.curdir, idir))
- if builddir:
- strlist.append(os.path.join(builddir, self.curdir, idir))
+ strlist.append(os.path.join(builddir, self.curdir, idir))
return strlist
@dataclass(eq=False)
genlist: T.List['GeneratedTypes'] = field(default_factory=list)
objlist: T.List[T.Union[str, 'File', 'ExtractedObjects']] = field(default_factory=list)
recursive: bool = True
+ pch: bool = False
def __post_init__(self) -> None:
if self.target.is_unity:
sources.append(s)
# Filter out headers and all non-source files
- return [s for s in sources if environment.is_source(s)]
+ return [s for s in sources if is_source(s)]
def classify_all_sources(self, sources: T.List[FileOrString], generated_sources: T.Sequence['GeneratedTypes']) -> T.Dict['Compiler', T.List['FileOrString']]:
sources_ = self.get_sources(sources, generated_sources)
for comp, srcs in extracted_cmpsrcs.items():
if set(srcs) != set(cmpsrcs[comp]):
- raise MesonException('Single object files can not be extracted '
+ raise MesonException('Single object files cannot be extracted '
'in Unity builds. You can only extract all '
'the object files for each compiler at once.')
@dataclass(eq=False)
-class Target(HoldableObject):
-
- # TODO: should Target be an abc.ABCMeta?
+class Target(HoldableObject, metaclass=abc.ABCMeta):
name: str
subdir: str
build_by_default: bool
for_machine: MachineChoice
environment: environment.Environment
+ install: bool = False
+ build_always_stale: bool = False
+ extra_files: T.List[File] = field(default_factory=list)
+ override_options: InitVar[T.Optional[T.Dict[OptionKey, str]]] = None
- def __post_init__(self) -> None:
+ @abc.abstractproperty
+ def typename(self) -> str:
+ pass
+
+ @abc.abstractmethod
+ def type_suffix(self) -> str:
+ pass
+
+ def __post_init__(self, overrides: T.Optional[T.Dict[OptionKey, str]]) -> None:
+ if overrides:
+ ovr = {k.evolve(machine=self.for_machine) if k.lang else k: v
+ for k, v in overrides.items()}
+ else:
+ ovr = {}
+ self.options = coredata.OptionsView(self.environment.coredata.options, self.subproject, ovr)
+ # XXX: this should happen in the interpreter
if has_path_sep(self.name):
# Fix failing test 53 when this becomes an error.
mlog.warning(textwrap.dedent(f'''\
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.install = False
- self.build_always_stale = False
- self.options = OptionOverrideProxy({}, self.environment.coredata.options, self.subproject)
- self.extra_files = [] # type: T.List[File]
- if not hasattr(self, 'typename'):
- raise RuntimeError(f'Target type is not set for target class "{type(self).__name__}". This is a bug')
+ a hard error in the future.'''))
# dataclass comparators?
def __lt__(self, other: object) -> bool:
return self.typename
@staticmethod
- def _get_id_hash(target_id):
+ def _get_id_hash(target_id: str) -> str:
# We don't really need cryptographic security here.
# Small-digest hash function with unlikely collision is good enough.
h = hashlib.sha256()
return my_id
def get_id(self) -> str:
+ name = self.name
+ if getattr(self, 'name_suffix_set', False):
+ name += '.' + self.suffix
return self.construct_id_from_path(
- self.subdir, self.name, self.type_suffix())
+ self.subdir, name, self.type_suffix())
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.')
- elif kwargs.get('install', False):
+
+ 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
else:
self.options.overrides[k] = v
- def get_options(self) -> OptionOverrideProxy:
+ def get_options(self) -> coredata.OptionsView:
return self.options
def get_option(self, key: 'OptionKey') -> T.Union[str, int, bool, 'WrapMode']:
install_dir: T.List[T.Union[str, Literal[False]]]
- def __init__(self, name: str, subdir: str, subproject: SubProject, for_machine: MachineChoice,
- sources: T.List['SourceOutputs'], structured_sources: T.Optional[StructuredSources],
- objects, environment: environment.Environment, compilers: T.Dict[str, 'Compiler'], kwargs):
- super().__init__(name, subdir, subproject, True, for_machine, environment)
+ # This set contains all the languages a linker can link natively
+ # without extra flags. For instance, nvcc (cuda) can link C++
+ # without injecting -lc++/-lstdc++, see
+ # https://github.com/mesonbuild/meson/issues/10570
+ _MASK_LANGS: T.FrozenSet[T.Tuple[str, str]] = frozenset([
+ # (language, linker)
+ ('cpp', 'cuda'),
+ ])
+
+ def __init__(
+ self,
+ name: str,
+ subdir: str,
+ subproject: SubProject,
+ for_machine: MachineChoice,
+ sources: T.List['SourceOutputs'],
+ structured_sources: T.Optional[StructuredSources],
+ objects: T.List[ObjectTypes],
+ 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))
self.all_compilers = compilers
- self.compilers = OrderedDict() # type: OrderedDict[str, Compiler]
- self.objects: T.List[T.Union[str, 'File', 'ExtractedObjects']] = []
+ self.compilers: OrderedDict[str, Compiler] = OrderedDict()
+ self.objects: T.List[ObjectTypes] = []
self.structured_sources = structured_sources
self.external_deps: T.List[dependencies.Dependency] = []
self.include_dirs: T.List['IncludeDirs'] = []
self.link_language = kwargs.get('link_language')
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.added_deps = set()
self.name_prefix_set = False
self.name_suffix_set = False
self.filename = 'no_name'
+ # The debugging information file this target will generate
+ self.debug_filename = None
# 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.need_install = False
self.pch: T.Dict[str, T.List[str]] = {}
- self.extra_args: T.Dict[str, T.List['FileOrString']] = {}
+ self.extra_args: T.DefaultDict[str, T.List[str]] = kwargs.get('language_args', defaultdict(list))
self.sources: T.List[File] = []
self.generated: T.List['GeneratedTypes'] = []
- self.d_features = defaultdict(list)
+ self.extra_files: T.List[File] = []
+ self.d_features: DFeatures = {
+ 'debug': kwargs.get('d_debug', []),
+ 'import_dirs': kwargs.get('d_import_dirs', []),
+ 'versions': kwargs.get('d_module_versions', []),
+ 'unittest': kwargs.get('d_unittest', False),
+ }
self.pic = False
self.pie = False
# Track build_rpath entries so we can remove them at install time
self.rpath_dirs_to_remove: T.Set[bytes] = set()
self.process_sourcelist(sources)
# Objects can be:
- # 1. Pre-existing objects provided by the user with the `objects:` kwarg
+ # 1. Preexisting objects provided by the user with the `objects:` kwarg
# 2. Compiled objects created by and extracted from another target
self.process_objectlist(objects)
self.process_kwargs(kwargs)
- self.check_unknown_kwargs(kwargs)
- if not any([self.sources, self.generated, self.objects, self.link_whole_targets, self.structured_sources]):
+ self.missing_languages = self.process_compilers()
+
+ # self.link_targets and self.link_whole_targets contains libraries from
+ # dependencies (see add_deps()). They have not been processed yet because
+ # we have to call process_compilers() first and we need to process libraries
+ # from link_with and link_whole first.
+ # See https://github.com/mesonbuild/meson/pull/11957#issuecomment-1629243208.
+ link_targets = extract_as_list(kwargs, 'link_with') + self.link_targets
+ link_whole_targets = extract_as_list(kwargs, 'link_whole') + self.link_whole_targets
+ self.link_targets.clear()
+ self.link_whole_targets.clear()
+ self.link(link_targets)
+ self.link_whole(link_whole_targets)
+
+ if not any([self.sources, 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')
+ self.check_unknown_kwargs(kwargs)
self.validate_install()
self.check_module_linking()
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():
+ # 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.outputs.append(self.vala_gir)
+ self.install_tag.append('devel')
def __repr__(self):
repr_str = "<{0} {1}: {2}>"
return unity_opt == 'on' or (unity_opt == 'subprojects' and self.subproject != '')
def validate_install(self):
- if self.for_machine is MachineChoice.BUILD and self.need_install:
+ if self.for_machine is MachineChoice.BUILD and self.install:
if self.environment.is_cross_build():
raise InvalidArguments('Tried to install a target for the build machine in a cross build.')
else:
def check_unknown_kwargs_int(self, kwargs, known_kwargs):
unknowns = []
for k in kwargs:
+ if k == 'language_args':
+ continue
if k not in known_kwargs:
unknowns.append(k)
if len(unknowns) > 0:
def process_objectlist(self, objects):
assert isinstance(objects, list)
+ deprecated_non_objects = []
for s in objects:
if isinstance(s, (str, File, ExtractedObjects)):
self.objects.append(s)
- elif isinstance(s, (GeneratedList, CustomTarget)):
- msg = 'Generated files are not allowed in the \'objects\' kwarg ' + \
- f'for target {self.name!r}.\nIt is meant only for ' + \
- 'pre-built object files that are shipped with the\nsource ' + \
- 'tree. Try adding it in the list of sources.'
- raise InvalidArguments(msg)
+ if not isinstance(s, ExtractedObjects) and not is_object(s):
+ deprecated_non_objects.append(s)
+ elif isinstance(s, (CustomTarget, CustomTargetIndex, GeneratedList)):
+ non_objects = [o for o in s.get_outputs() if not is_object(o)]
+ if non_objects:
+ raise InvalidArguments(f'Generated file {non_objects[0]} in the \'objects\' kwarg is not an object.')
+ self.generated.append(s)
else:
raise InvalidArguments(f'Bad object of type {type(s).__name__!r} in target {self.name!r}.')
+ if deprecated_non_objects:
+ FeatureDeprecated.single_use(f'Source file {deprecated_non_objects[0]} in the \'objects\' kwarg is not an object.',
+ '1.3.0', self.subproject)
def process_sourcelist(self, sources: T.List['SourceOutputs']) -> None:
"""Split sources into generated and static sources.
Sources can be:
- 1. Pre-existing source files in the source tree (static)
- 2. Pre-existing sources generated by configure_file in the build tree.
+ 1. Preexisting source files in the source tree (static)
+ 2. Preexisting sources generated by configure_file in the build tree.
(static as they are only regenerated if meson itself is regenerated)
3. Sources files generated by another target or a Generator (generated)
"""
removed = True
return removed
- def process_compilers_late(self, extra_languages: T.List[str]):
+ def process_compilers_late(self):
"""Processes additional compilers after kwargs have been evaluated.
This can add extra compilers that might be required by keyword
arguments, such as link_with or dependencies. It will also try to guess
which compiler to use if one hasn't been selected already.
"""
- for lang in extra_languages:
+ for lang in self.missing_languages:
self.compilers[lang] = self.all_compilers[lang]
# did user override clink_langs for this target?
missing_languages: T.List[str] = []
if not any([self.sources, self.generated, self.objects, self.structured_sources]):
return missing_languages
- # Pre-existing sources
+ # Preexisting sources
sources: T.List['FileOrString'] = list(self.sources)
generated = self.generated.copy()
'Link_depends arguments must be strings, Files, '
'or a Custom Target, or lists thereof.')
- def get_original_kwargs(self):
- return self.kwargs
-
- def copy_kwargs(self, kwargs):
- self.kwargs = copy.copy(kwargs)
- for k, v in self.kwargs.items():
- if isinstance(v, list):
- self.kwargs[k] = listify(v, flatten=True)
- for t in ['dependencies', 'link_with', 'include_directories', 'sources']:
- if t in self.kwargs:
- self.kwargs[t] = listify(self.kwargs[t], flatten=True)
-
def extract_objects(self, srclist: T.List[T.Union['FileOrString', 'GeneratedTypes']]) -> ExtractedObjects:
sources_set = set(self.sources)
generated_set = set(self.generated)
def extract_all_objects(self, recursive: bool = True) -> ExtractedObjects:
return ExtractedObjects(self, self.sources, self.generated, self.objects,
- recursive)
+ recursive, pch=True)
def get_all_link_deps(self) -> ImmutableListProtocol[BuildTargetTypes]:
return self.get_transitive_link_deps()
def process_kwargs(self, kwargs):
self.process_kwargs_base(kwargs)
- self.copy_kwargs(kwargs)
- kwargs.get('modules', [])
- self.need_install = kwargs.get('install', self.need_install)
- llist = extract_as_list(kwargs, 'link_with')
- for linktarget in llist:
- if isinstance(linktarget, dependencies.ExternalLibrary):
- raise MesonException(textwrap.dedent('''\
- An external library was used in link_with keyword argument, which
- is reserved for libraries built as part of this project. External
- libraries must be passed using the dependencies keyword argument
- instead, because they are conceptually "external dependencies",
- just like those detected with the dependency() function.
- '''))
- self.link(linktarget)
- lwhole = extract_as_list(kwargs, 'link_whole')
- for linktarget in lwhole:
- self.link_whole(linktarget)
-
- for lang in all_languages:
- lang_args = extract_as_list(kwargs, f'{lang}_args')
- self.add_compiler_args(lang, lang_args)
+ 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 not isinstance(self, Executable) or 'export_dynamic' in kwargs:
+ 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)
- dfeatures = defaultdict(list)
- dfeature_unittest = kwargs.get('d_unittest', False)
- if dfeature_unittest:
- dfeatures['unittest'] = dfeature_unittest
- dfeature_versions = kwargs.get('d_module_versions', [])
- if dfeature_versions:
- dfeatures['versions'] = dfeature_versions
- dfeature_debug = kwargs.get('d_debug', [])
- if dfeature_debug:
- dfeatures['debug'] = dfeature_debug
- if 'd_import_dirs' in kwargs:
- dfeature_import_dirs = extract_as_list(kwargs, 'd_import_dirs')
- for d in dfeature_import_dirs:
- if not isinstance(d, IncludeDirs):
- raise InvalidArguments('Arguments to d_import_dirs must be include_directories.')
- dfeatures['import_dirs'] = dfeature_import_dirs
- if dfeatures:
- self.d_features = dfeatures
-
self.link_args = extract_as_list(kwargs, 'link_args')
for i in self.link_args:
if not isinstance(i, str):
(str, bool))
self.install_mode = kwargs.get('install_mode', None)
self.install_tag = stringlistify(kwargs.get('install_tag', [None]))
- main_class = kwargs.get('main_class', '')
- if not isinstance(main_class, str):
- raise InvalidArguments('Main class must be a string')
- self.main_class = main_class
- if isinstance(self, Executable):
- # This kwarg is deprecated. The value of "none" means that the kwarg
- # was not specified and win_subsystem should be used instead.
- self.gui_app = None
- if 'gui_app' in kwargs:
- if 'win_subsystem' in kwargs:
- raise InvalidArguments('Can specify only gui_app or win_subsystem for a target, not both.')
- self.gui_app = kwargs['gui_app']
- if not isinstance(self.gui_app, bool):
- raise InvalidArguments('Argument gui_app must be boolean.')
- self.win_subsystem = self.validate_win_subsystem(kwargs.get('win_subsystem', 'console'))
- elif 'gui_app' in kwargs:
- raise InvalidArguments('Argument gui_app can only be used on executables.')
- elif 'win_subsystem' in kwargs:
- raise InvalidArguments('Argument win_subsystem can only be used on executables.')
+ 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 = extra_files
+ self.extra_files.append(i)
self.install_rpath: str = kwargs.get('install_rpath', '')
if not isinstance(self.install_rpath, str):
raise InvalidArguments('Install_rpath is not a string.')
if not os.path.isfile(trial):
raise InvalidArguments(f'Tried to add non-existing resource {r}.')
self.resources = resources
- if 'name_prefix' in kwargs:
+ 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 must be a string.')
self.prefix = name_prefix
self.name_prefix_set = True
- if 'name_suffix' in kwargs:
+ if kwargs.get('name_suffix') is not None:
name_suffix = kwargs['name_suffix']
if isinstance(name_suffix, list):
if name_suffix:
if self.gnu_symbol_visibility not in permitted:
raise InvalidArguments('GNU symbol visibility arg {} not one of: {}'.format(self.gnu_symbol_visibility, ', '.join(permitted)))
- def validate_win_subsystem(self, value: str) -> str:
- value = value.lower()
- if re.fullmatch(r'(boot_application|console|efi_application|efi_boot_service_driver|efi_rom|efi_runtime_driver|native|posix|windows)(,\d+(\.\d+)?)?', value) is None:
- raise InvalidArguments(f'Invalid value for win_subsystem: {value}.')
- return value
+ 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, arg: str, option: str):
+ def _extract_pic_pie(self, kwargs: T.Dict[str, T.Any], arg: str, option: str) -> bool:
# 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:
return True
k = OptionKey(option)
- if arg in kwargs:
- val = kwargs[arg]
+ if kwargs.get(arg) is not None:
+ val = T.cast('bool', kwargs[arg])
elif k in self.environment.coredata.options:
val = self.environment.coredata.options[k].value
else:
def get_filename(self) -> str:
return self.filename
+ def get_debug_filename(self) -> T.Optional[str]:
+ """
+ The name of debuginfo file that will be created by the compiler
+
+ Returns None if the build won't create any debuginfo file
+ """
+ return self.debug_filename
+
def get_outputs(self) -> T.List[str]:
return self.outputs
- def get_extra_args(self, language):
- return self.extra_args.get(language, [])
+ def get_extra_args(self, language: str) -> T.List[str]:
+ return self.extra_args[language]
- def get_dependencies(self, exclude=None):
- transitive_deps = []
- if exclude is None:
- exclude = []
+ @lru_cache(maxsize=None)
+ def get_dependencies(self) -> OrderedSet[BuildTargetTypes]:
+ # Get all targets needed for linking. This includes all link_with and
+ # link_whole targets, and also all dependencies of static libraries
+ # recursively. The algorithm here is closely related to what we do in
+ # get_internal_static_libraries(): Installed static libraries include
+ # objects from all their dependencies already.
+ result: OrderedSet[BuildTargetTypes] = OrderedSet()
for t in itertools.chain(self.link_targets, self.link_whole_targets):
- if t in transitive_deps or t in exclude:
+ if t not in result:
+ result.add(t)
+ if isinstance(t, StaticLibrary):
+ t.get_dependencies_recurse(result)
+ return result
+
+ def get_dependencies_recurse(self, result: OrderedSet[BuildTargetTypes], include_internals: bool = True) -> None:
+ # self is always a static library because we don't need to pull dependencies
+ # of shared libraries. If self is installed (not internal) it already
+ # include objects extracted from all its internal dependencies so we can
+ # skip them.
+ include_internals = include_internals and self.is_internal()
+ for t in self.link_targets:
+ if t in result:
continue
- transitive_deps.append(t)
+ if t.rust_crate_type == 'proc-macro':
+ continue
+ if include_internals or not t.is_internal():
+ result.add(t)
if isinstance(t, StaticLibrary):
- transitive_deps += t.get_dependencies(transitive_deps + exclude)
- return transitive_deps
+ t.get_dependencies_recurse(result, include_internals)
+ for t in self.link_whole_targets:
+ t.get_dependencies_recurse(result, include_internals)
def get_source_subdir(self):
return self.subdir
return self.generated
def should_install(self) -> bool:
- return self.need_install
+ return self.install
def has_pch(self) -> bool:
return bool(self.pch)
if isinstance(dep, dependencies.InternalDependency):
# Those parts that are internal.
self.process_sourcelist(dep.sources)
+ self.extra_files.extend(f for f in dep.extra_files if f not in self.extra_files)
self.add_include_dirs(dep.include_directories, dep.get_include_type())
- for l in dep.libraries:
- self.link(l)
- for l in dep.whole_libraries:
- self.link_whole(l)
+ self.objects.extend(dep.objects)
+ self.link_targets.extend(dep.libraries)
+ self.link_whole_targets.extend(dep.whole_libraries)
if dep.get_compile_args() or dep.get_link_args():
# Those parts that are external.
extpart = dependencies.InternalDependency('undefined',
[],
dep.get_compile_args(),
dep.get_link_args(),
- [], [], [], [], {}, [], [])
+ [], [], [], [], [], {}, [], [], [])
self.external_deps.append(extpart)
# Deps of deps.
self.add_deps(dep.ext_deps)
self.process_sourcelist(dep.get_sources())
self.add_deps(dep.ext_deps)
elif isinstance(dep, BuildTarget):
- raise InvalidArguments('''Tried to use a build target as a dependency.
-You probably should put it in link_with instead.''')
+ raise InvalidArguments(f'Tried to use a build target {dep.name} as a dependency of target {self.name}.\n'
+ 'You probably should put it in link_with instead.')
else:
# This is a bit of a hack. We do not want Build to know anything
# about the interpreter so we can't import it and use isinstance.
def is_internal(self) -> bool:
return False
- def link(self, target):
- for t in listify(target):
- if isinstance(self, StaticLibrary) and self.need_install:
- if isinstance(t, (CustomTarget, CustomTargetIndex)):
- if not t.should_install():
- mlog.warning(f'Try to link an installed static library target {self.name} with a'
- 'custom target that is not installed, this might cause problems'
- 'when you try to use this static library')
- elif t.is_internal() and not t.uses_rust():
- # When we're a static library and we link_with to an
- # internal/convenience library, promote to link_whole.
- #
- # There are cases we cannot do this, however. In Rust, for
- # example, this can't be done with Rust ABI libraries, though
- # it could be done with C ABI libraries, though there are
- # several meson issues that need to be fixed:
- # https://github.com/mesonbuild/meson/issues/10722
- # https://github.com/mesonbuild/meson/issues/10723
- # https://github.com/mesonbuild/meson/issues/10724
- return self.link_whole(t)
+ def link(self, targets: T.List[BuildTargetTypes]) -> None:
+ for t in targets:
if not isinstance(t, (Target, CustomTargetIndex)):
+ if isinstance(t, dependencies.ExternalLibrary):
+ raise MesonException(textwrap.dedent('''\
+ An external library was used in link_with keyword argument, which
+ is reserved for libraries built as part of this project. External
+ libraries must be passed using the dependencies keyword argument
+ instead, because they are conceptually "external dependencies",
+ just like those detected with the dependency() function.
+ '''))
raise InvalidArguments(f'{t!r} is not a target.')
if not t.is_linkable_target():
raise InvalidArguments(f"Link target '{t!s}' is not linkable.")
+ if isinstance(self, StaticLibrary) and self.install and t.is_internal():
+ # When we're a static library and we link_with to an
+ # internal/convenience library, promote to link_whole.
+ self.link_whole([t], promoted=True)
+ continue
if isinstance(self, SharedLibrary) and isinstance(t, StaticLibrary) and not t.pic:
msg = f"Can't link non-PIC static library {t.name!r} into shared library {self.name!r}. "
msg += "Use the 'pic' option to static_library to build with PIC."
raise InvalidArguments(msg)
- if self.for_machine is not t.for_machine:
- msg = f'Tried to mix libraries for machines {self.for_machine} and {t.for_machine} in target {self.name!r}'
- if self.environment.is_cross_build():
- raise InvalidArguments(msg + ' This is not possible in a cross build.')
- else:
- mlog.warning(msg + ' This will fail in cross build.')
+ self.check_can_link_together(t)
self.link_targets.append(t)
- def link_whole(self, target):
- for t in listify(target):
+ def link_whole(self, targets: T.List[BuildTargetTypes], promoted: bool = False) -> None:
+ for t in targets:
if isinstance(t, (CustomTarget, CustomTargetIndex)):
if not t.is_linkable_target():
raise InvalidArguments(f'Custom target {t!r} is not linkable.')
if t.links_dynamically():
raise InvalidArguments('Can only link_whole custom targets that are static archives.')
- if isinstance(self, StaticLibrary):
- # FIXME: We could extract the .a archive to get object files
- raise InvalidArguments('Cannot link_whole a custom target into a static library')
elif not isinstance(t, StaticLibrary):
raise InvalidArguments(f'{t!r} is not a static library.')
elif isinstance(self, SharedLibrary) and not t.pic:
msg = f"Can't link non-PIC static library {t.name!r} into shared library {self.name!r}. "
msg += "Use the 'pic' option to static_library to build with PIC."
raise InvalidArguments(msg)
- if self.for_machine is not t.for_machine:
- msg = f'Tried to mix libraries for machines {self.for_machine} and {t.for_machine} in target {self.name!r}'
- if self.environment.is_cross_build():
- raise InvalidArguments(msg + ' This is not possible in a cross build.')
- else:
- mlog.warning(msg + ' This will fail in cross build.')
+ self.check_can_link_together(t)
if isinstance(self, StaticLibrary):
# When we're a static library and we link_whole: to another static
# library, we need to add that target's objects to ourselves.
- self.objects += t.extract_all_objects_recurse()
+ self._bundle_static_library(t, promoted)
+ # If we install this static library we also need to include objects
+ # from all uninstalled static libraries it depends on.
+ if self.install:
+ for lib in t.get_internal_static_libraries():
+ self._bundle_static_library(lib, True)
self.link_whole_targets.append(t)
- def extract_all_objects_recurse(self) -> T.List[T.Union[str, 'ExtractedObjects']]:
- objs = [self.extract_all_objects()]
+ @lru_cache(maxsize=None)
+ def get_internal_static_libraries(self) -> OrderedSet[BuildTargetTypes]:
+ result: OrderedSet[BuildTargetTypes] = OrderedSet()
+ self.get_internal_static_libraries_recurse(result)
+ return result
+
+ def get_internal_static_libraries_recurse(self, result: OrderedSet[BuildTargetTypes]) -> None:
for t in self.link_targets:
+ if t.is_internal() and t not in result:
+ result.add(t)
+ t.get_internal_static_libraries_recurse(result)
+ for t in self.link_whole_targets:
if t.is_internal():
- objs += t.extract_all_objects_recurse()
- return objs
+ t.get_internal_static_libraries_recurse(result)
+
+ def _bundle_static_library(self, t: T.Union[BuildTargetTypes], promoted: bool = False) -> None:
+ if self.uses_rust():
+ # Rustc can bundle static libraries, no need to extract objects.
+ self.link_whole_targets.append(t)
+ elif isinstance(t, (CustomTarget, CustomTargetIndex)) or t.uses_rust():
+ # To extract objects from a custom target we would have to extract
+ # the archive, WIP implementation can be found in
+ # https://github.com/mesonbuild/meson/pull/9218.
+ # For Rust C ABI we could in theory have access to objects, but there
+ # are several meson issues that need to be fixed:
+ # https://github.com/mesonbuild/meson/issues/10722
+ # https://github.com/mesonbuild/meson/issues/10723
+ # https://github.com/mesonbuild/meson/issues/10724
+ m = (f'Cannot link_whole a custom or Rust target {t.name!r} into a static library {self.name!r}. '
+ 'Instead, pass individual object files with the "objects:" keyword argument if possible.')
+ if promoted:
+ m += (f' Meson had to promote link to link_whole because {self.name!r} is installed but not {t.name!r},'
+ f' and thus has to include objects from {t.name!r} to be usable.')
+ raise InvalidArguments(m)
+ else:
+ self.objects.append(t.extract_all_objects())
+
+ def check_can_link_together(self, t: BuildTargetTypes) -> None:
+ links_with_rust_abi = isinstance(t, BuildTarget) and t.uses_rust_abi()
+ if not self.uses_rust() and links_with_rust_abi:
+ raise InvalidArguments(f'Try to link Rust ABI library {t.name!r} with a non-Rust target {self.name!r}')
+ if self.for_machine is not t.for_machine and (not links_with_rust_abi or t.rust_crate_type != 'proc-macro'):
+ msg = f'Tried to mix libraries for machines {self.for_machine} and {t.for_machine} in target {self.name!r}'
+ if self.environment.is_cross_build():
+ raise InvalidArguments(msg + ' This is not possible in a cross build.')
+ 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 environment.is_header(pchlist[0]):
+ if not is_header(pchlist[0]):
raise InvalidArguments(f'PCH argument {pchlist[0]} is not a header.')
elif len(pchlist) == 2:
- if environment.is_header(pchlist[0]):
- if not environment.is_source(pchlist[1]):
+ 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 environment.is_source(pchlist[0]):
- if not environment.is_header(pchlist[1]):
+ 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:
ids = [IncludeDirs(x.get_curdir(), x.get_incdirs(), is_system, x.get_extra_build_dirs()) for x in ids]
self.include_dirs += ids
- def add_compiler_args(self, language: str, args: T.List['FileOrString']) -> None:
- args = listify(args)
- for a in args:
- if not isinstance(a, (str, File)):
- raise InvalidArguments('A non-string passed to compiler args.')
- if language in self.extra_args:
- self.extra_args[language] += args
- else:
- self.extra_args[language] = args
-
def get_aliases(self) -> T.List[T.Tuple[str, str, str]]:
return []
See: https://github.com/mesonbuild/meson/issues/1653
'''
- langs = [] # type: T.List[str]
+ langs: T.List[str] = []
# Check if any of the external libraries were written in this language
for dep in self.external_deps:
# Languages used by dependencies
dep_langs = self.get_langs_used_by_deps()
- # This set contains all the languages a linker can link natively
- # without extra flags. For instance, nvcc (cuda) can link C++
- # without injecting -lc++/-lstdc++, see
- # https://github.com/mesonbuild/meson/issues/10570
- MASK_LANGS = frozenset([
- # (language, linker)
- ('cpp', 'cuda'),
- ])
# Pick a compiler based on the language priority-order
for l in clink_langs:
if l in self.compilers or l in dep_langs:
f'Could not get a dynamic linker for build target {self.name!r}. '
f'Requires a linker for language "{l}", but that is not '
'a project language.')
- stdlib_args: T.List[str] = []
- for dl in itertools.chain(self.compilers, dep_langs):
- if dl != linker.language and (dl, linker.language) not in MASK_LANGS:
- stdlib_args += all_compilers[dl].language_stdlib_only_link_flags(self.environment)
+ stdlib_args: T.List[str] = self.get_used_stdlib_args(linker.language)
# Type of var 'linker' is Compiler.
# Pretty hard to fix because the return value is passed everywhere
return linker, stdlib_args
raise AssertionError(f'Could not get a dynamic linker for build target {self.name!r}')
+ def get_used_stdlib_args(self, link_language: str) -> T.List[str]:
+ all_compilers = self.environment.coredata.compilers[self.for_machine]
+ all_langs = set(self.compilers).union(self.get_langs_used_by_deps())
+ stdlib_args: T.List[str] = []
+ for dl in all_langs:
+ if dl != link_language and (dl, link_language) not in self._MASK_LANGS:
+ # 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))
+ return stdlib_args
+
def uses_rust(self) -> bool:
return 'rust' in self.compilers
+ def uses_rust_abi(self) -> bool:
+ return self.uses_rust() and self.rust_crate_type in {'dylib', 'rlib', 'proc-macro'}
+
def uses_fortran(self) -> bool:
return 'fortran' in self.compilers
'\n '
f'If shared_module() was used for {link_target.name} because it has references to undefined symbols,'
'\n '
- 'use shared_libary() with `override_options: [\'b_lundef=false\']` instead.')
+ '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:
+ 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)
+ else:
+ self.vs_module_defs = File.from_source_file(self.environment.source_dir, self.subdir, path)
+ elif isinstance(path, File):
+ # When passing a generated file.
+ self.vs_module_defs = path
+ elif isinstance(path, (CustomTarget, CustomTargetIndex)):
+ # 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)
+
+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'.
+ """
+
+ def __init__(self, fname: str):
+ self.fname = fname
+
+ def __str__(self) -> str:
+ return self.fname
+
+class FileMaybeInTargetPrivateDir:
+ """Union between 'File' and 'FileInTargetPrivateDir'"""
+
+ def __init__(self, inner: T.Union[File, FileInTargetPrivateDir]):
+ self.inner = inner
+
+ @property
+ def fname(self) -> str:
+ return self.inner.fname
+
+ def rel_to_builddir(self, build_to_src: str, target_private_dir: str) -> str:
+ if isinstance(self.inner, FileInTargetPrivateDir):
+ return os.path.join(target_private_dir, self.inner.fname)
+ return self.inner.rel_to_builddir(build_to_src)
+
+ def absolute_path(self, srcdir: str, builddir: str) -> str:
+ if isinstance(self.inner, FileInTargetPrivateDir):
+ raise RuntimeError('Unreachable code')
+ return self.inner.absolute_path(srcdir, builddir)
+
+ def __str__(self) -> str:
+ return self.fname
+
class Generator(HoldableObject):
def __init__(self, exe: T.Union['Executable', programs.ExternalProgram],
arguments: T.List[str],
def process_files(self, files: T.Iterable[T.Union[str, File, 'CustomTarget', 'CustomTargetIndex', 'GeneratedList']],
state: T.Union['Interpreter', 'ModuleState'],
preserve_path_from: T.Optional[str] = None,
- extra_args: T.Optional[T.List[str]] = None) -> 'GeneratedList':
- output = GeneratedList(self, state.subdir, preserve_path_from, extra_args=extra_args if extra_args is not None else [])
+ extra_args: T.Optional[T.List[str]] = None,
+ env: T.Optional[EnvironmentVariables] = None) -> 'GeneratedList':
+ output = GeneratedList(
+ self,
+ state.subdir,
+ preserve_path_from,
+ extra_args=extra_args if extra_args is not None else [],
+ env=env if env is not None else EnvironmentVariables())
for e in files:
if isinstance(e, CustomTarget):
output.depends.add(e)
if isinstance(e, CustomTargetIndex):
output.depends.add(e.target)
-
- if isinstance(e, (CustomTarget, CustomTargetIndex, GeneratedList)):
+ if isinstance(e, (CustomTarget, CustomTargetIndex)):
output.depends.add(e)
fs = [File.from_built_file(state.subdir, f) for f in e.get_outputs()]
+ elif isinstance(e, GeneratedList):
+ if preserve_path_from:
+ raise InvalidArguments("generator.process: 'preserve_path_from' is not allowed if one input is a 'generated_list'.")
+ 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)]
else:
abs_f = f.absolute_path(state.environment.source_dir, state.environment.build_dir)
if not self.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)
return output
subdir: str
preserve_path_from: T.Optional[str]
extra_args: T.List[str]
+ env: T.Optional[EnvironmentVariables]
def __post_init__(self) -> None:
self.name = self.generator.exe
self.depends: T.Set[GeneratedTypes] = set()
- self.infilelist: T.List['File'] = []
+ self.infilelist: T.List[FileMaybeInTargetPrivateDir] = []
self.outfilelist: T.List[str] = []
- self.outmap: T.Dict[File, T.List[str]] = {}
+ self.outmap: T.Dict[FileMaybeInTargetPrivateDir, T.List[str]] = {}
self.extra_depends = [] # XXX: Doesn't seem to be used?
self.depend_files: T.List[File] = []
if self.extra_args is None:
self.extra_args: T.List[str] = []
+ if self.env is None:
+ self.env: EnvironmentVariables = EnvironmentVariables()
+
if isinstance(self.generator.exe, programs.ExternalProgram):
if not self.generator.exe.found():
raise InvalidArguments('Tried to use not-found external program as generator')
# know the absolute path of
self.depend_files.append(File.from_absolute_file(path))
- def add_preserved_path_segment(self, infile: File, outfiles: T.List[str], state: T.Union['Interpreter', 'ModuleState']) -> T.List[str]:
+ def add_preserved_path_segment(self, infile: FileMaybeInTargetPrivateDir, outfiles: T.List[str], state: T.Union['Interpreter', 'ModuleState']) -> T.List[str]:
result: T.List[str] = []
in_abs = infile.absolute_path(state.environment.source_dir, state.environment.build_dir)
assert os.path.isabs(self.preserve_path_from)
result.append(os.path.join(path_segment, of))
return result
- def add_file(self, newfile: File, state: T.Union['Interpreter', 'ModuleState']) -> None:
+ def add_file(self, newfile: FileMaybeInTargetPrivateDir, state: T.Union['Interpreter', 'ModuleState']) -> None:
self.infilelist.append(newfile)
outfiles = self.generator.get_base_outnames(newfile.fname)
if self.preserve_path_from:
self.outfilelist += outfiles
self.outmap[newfile] = outfiles
- def get_inputs(self) -> T.List['File']:
+ def get_inputs(self) -> T.List[FileMaybeInTargetPrivateDir]:
return self.infilelist
def get_outputs(self) -> T.List[str]:
return self.outfilelist
- def get_outputs_for(self, filename: 'File') -> T.List[str]:
+ def get_outputs_for(self, filename: FileMaybeInTargetPrivateDir) -> T.List[str]:
return self.outmap[filename]
def get_generator(self) -> 'Generator':
typename = 'executable'
- 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'],
- kwargs):
+ def __init__(
+ self,
+ name: str,
+ subdir: str,
+ subproject: SubProject,
+ for_machine: MachineChoice,
+ sources: T.List['SourceOutputs'],
+ structured_sources: T.Optional[StructuredSources],
+ objects: T.List[ObjectTypes],
+ environment: environment.Environment,
+ compilers: T.Dict[str, 'Compiler'],
+ kwargs):
key = OptionKey('b_pie')
if 'pie' not in kwargs and key in environment.coredata.options:
kwargs['pie'] = environment.coredata.options[key].value
super().__init__(name, subdir, subproject, for_machine, sources, structured_sources, objects,
environment, compilers, kwargs)
+ self.win_subsystem = kwargs.get('win_subsystem') or 'console'
# Check for export_dynamic
self.export_dynamic = kwargs.get('export_dynamic', False)
if not isinstance(self.export_dynamic, bool):
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')
- if self.implib:
- self.export_dynamic = True
- if self.export_dynamic and self.implib is False:
- raise InvalidArguments('"implib" keyword argument must not be false for if "export_dynamic" is true')
# Only linkwithable if using export_dynamic
self.is_linkwithable = self.export_dynamic
# Remember that this exe was returned by `find_program()` through an override
self.was_returned_by_find_program = False
+ self.vs_module_defs: T.Optional[File] = None
+ self.process_vs_module_defs_kw(kwargs)
+
def post_init(self) -> None:
super().post_init()
machine = self.environment.machines[self.for_machine]
elif ('c' in self.compilers and self.compilers['c'].get_id() in {'ti', 'c2000'} or
'cpp' in self.compilers and self.compilers['cpp'].get_id() in {'ti', 'c2000'}):
self.suffix = 'out'
+ elif ('c' in self.compilers and self.compilers['c'].get_id() in {'mwccarm', 'mwcceppc'} or
+ 'cpp' in self.compilers and self.compilers['cpp'].get_id() in {'mwccarm', 'mwcceppc'}):
+ self.suffix = 'nef'
else:
self.suffix = machine.get_exe_suffix()
self.filename = self.name
if self.suffix:
self.filename += '.' + self.suffix
- self.outputs = [self.filename]
+ self.outputs[0] = self.filename
# The import library this target will generate
self.import_filename = None
- # The import library that Visual Studio would generate (and accept)
- self.vs_import_filename = None
- # The import library that GCC would generate (and prefer)
- self.gcc_import_filename = None
# The debugging information file this target will generate
self.debug_filename = None
if isinstance(self.implib, str):
implib_basename = self.implib
if machine.is_windows() or machine.is_cygwin():
- self.vs_import_filename = f'{implib_basename}.lib'
- self.gcc_import_filename = f'lib{implib_basename}.a'
if self.get_using_msvc():
- self.import_filename = self.vs_import_filename
+ self.import_filename = f'{implib_basename}.lib'
else:
- self.import_filename = self.gcc_import_filename
+ self.import_filename = f'lib{implib_basename}.a'
+
+ create_debug_file = (
+ machine.is_windows()
+ and ('cs' in self.compilers or self.uses_rust() or self.get_using_msvc())
+ # .pdb file is created only when debug symbols are enabled
+ and self.environment.coredata.get_option(OptionKey("debug"))
+ )
+ if create_debug_file:
+ # If the target is has a standard exe extension (i.e. 'foo.exe'),
+ # then the pdb name simply becomes 'foo.pdb'. If the extension is
+ # something exotic, then include that in the name for uniqueness
+ # reasons (e.g. 'foo_com.pdb').
+ name = self.name
+ if getattr(self, 'suffix', 'exe') != 'exe':
+ name += '_' + self.suffix
+ self.debug_filename = name + '.pdb'
+
+ def process_kwargs(self, kwargs):
+ super().process_kwargs(kwargs)
- if machine.is_windows() and ('cs' in self.compilers or
- self.uses_rust() or
- self.get_using_msvc()):
- self.debug_filename = self.name + '.pdb'
+ 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.Tuple[str, str]:
return self.environment.get_bindir(), '{bindir}'
"""
return self.import_filename
- def get_import_filenameslist(self):
- if self.import_filename:
- return [self.vs_import_filename, self.gcc_import_filename]
- return []
-
def get_debug_filename(self) -> T.Optional[str]:
"""
The name of debuginfo file that will be created by the compiler
"""
return self.outputs
+ def get_path(self) -> str:
+ """Provides compatibility with ExternalProgram."""
+ return os.path.join(self.subdir, self.filename)
+
+ def found(self) -> bool:
+ """Provides compatibility with ExternalProgram."""
+ return True
+
+
class StaticLibrary(BuildTarget):
known_kwargs = known_stlib_kwargs
typename = 'static library'
- 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'],
- kwargs):
- self.prelink = kwargs.get('prelink', False)
- if not isinstance(self.prelink, bool):
- raise InvalidArguments('Prelink keyword argument must be a boolean.')
+ def __init__(
+ self,
+ name: str,
+ subdir: str,
+ subproject: SubProject,
+ for_machine: MachineChoice,
+ sources: T.List['SourceOutputs'],
+ structured_sources: T.Optional[StructuredSources],
+ objects: T.List[ObjectTypes],
+ environment: environment.Environment,
+ compilers: T.Dict[str, 'Compiler'],
+ kwargs):
+ self.prelink = T.cast('bool', kwargs.get('prelink', False))
super().__init__(name, subdir, subproject, for_machine, sources, structured_sources, objects,
environment, compilers, kwargs)
super().post_init()
if 'cs' in self.compilers:
raise InvalidArguments('Static libraries not supported for C#.')
- if 'rust' in self.compilers:
- # If no crate type is specified, or it's the generic lib type, use rlib
- if not hasattr(self, 'rust_crate_type') or self.rust_crate_type == 'lib':
- mlog.debug('Defaulting Rust static library target crate type to rlib')
- self.rust_crate_type = 'rlib'
- # Don't let configuration proceed with a non-static crate type
- elif self.rust_crate_type not in ['rlib', 'staticlib']:
- raise InvalidArguments(f'Crate type "{self.rust_crate_type}" invalid for static libraries; must be "rlib" or "staticlib"')
+ if self.uses_rust():
+ # See https://github.com/rust-lang/rust/issues/110460
+ if self.rust_crate_type == 'rlib' and any(c in self.name for c in ['-', ' ', '.']):
+ raise InvalidArguments(f'Rust crate {self.name} type {self.rust_crate_type} does not allow spaces, '
+ 'periods or dashes in the library name due to a limitation of rustc. '
+ 'Replace them with underscores, for example')
+ if self.rust_crate_type == 'staticlib':
+ # FIXME: In the case of no-std we should not add those libraries,
+ # but we have no way to know currently.
+ rustc = self.compilers['rust']
+ d = dependencies.InternalDependency('undefined', [], [],
+ rustc.native_static_libs,
+ [], [], [], [], [], {}, [], [], [])
+ self.external_deps.append(d)
# 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
# libfoo.a. However, we cannot use foo.lib because that's the same as
# the import library. Using libfoo.a is ok because people using MSVC
# always pass the library filename while linking anyway.
+ #
+ # 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'
if not hasattr(self, 'suffix'):
- if 'rust' in self.compilers:
- if not hasattr(self, 'rust_crate_type') or self.rust_crate_type == 'rlib':
+ if self.uses_rust():
+ if self.rust_crate_type == 'rlib':
# default Rust static library suffix
self.suffix = 'rlib'
elif self.rust_crate_type == 'staticlib':
else:
self.suffix = 'a'
self.filename = self.prefix + self.name + '.' + self.suffix
- self.outputs = [self.filename]
+ self.outputs[0] = self.filename
def get_link_deps_mapping(self, prefix: str) -> T.Mapping[str, str]:
return {}
def process_kwargs(self, kwargs):
super().process_kwargs(kwargs)
- if 'rust_crate_type' in kwargs:
- rust_crate_type = kwargs['rust_crate_type']
- if isinstance(rust_crate_type, str):
+
+ 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'Invalid rust_crate_type "{rust_crate_type}": must be a string.')
+ 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):
return True
def is_internal(self) -> bool:
- return not self.need_install
+ return not self.install
class SharedLibrary(BuildTarget):
known_kwargs = known_shlib_kwargs
typename = 'shared library'
- 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'],
- kwargs):
- self.soversion = None
- self.ltversion = None
+ # Used by AIX to decide whether to archive shared library or not.
+ aix_so_archive = True
+
+ def __init__(
+ self,
+ name: str,
+ subdir: str,
+ subproject: SubProject,
+ for_machine: MachineChoice,
+ sources: T.List['SourceOutputs'],
+ structured_sources: T.Optional[StructuredSources],
+ objects: T.List[ObjectTypes],
+ environment: environment.Environment,
+ compilers: T.Dict[str, 'Compiler'],
+ kwargs):
+ self.soversion: T.Optional[str] = None
+ self.ltversion: T.Optional[str] = None
# Max length 2, first element is compatibility_version, second is current_version
- self.darwin_versions = []
+ self.darwin_versions: T.Optional[T.Tuple[str, str]] = None
self.vs_module_defs = None
# The import library this target will generate
self.import_filename = None
- # The import library that Visual Studio would generate (and accept)
- self.vs_import_filename = None
- # The import library that GCC would generate (and prefer)
- self.gcc_import_filename = None
# The debugging information file this target will generate
self.debug_filename = None
# Use by the pkgconfig module
def post_init(self) -> None:
super().post_init()
- if 'rust' in self.compilers:
- # If no crate type is specified, or it's the generic lib type, use dylib
- if not hasattr(self, 'rust_crate_type') or self.rust_crate_type == 'lib':
- mlog.debug('Defaulting Rust dynamic library target crate type to "dylib"')
- self.rust_crate_type = 'dylib'
- # Don't let configuration proceed with a non-dynamic crate type
- elif self.rust_crate_type not in ['dylib', 'cdylib', 'proc-macro']:
- raise InvalidArguments(f'Crate type "{self.rust_crate_type}" invalid for dynamic libraries; must be "dylib", "cdylib", or "proc-macro"')
+ if self.uses_rust():
+ # See https://github.com/rust-lang/rust/issues/110460
+ if self.rust_crate_type != 'cdylib' and any(c in self.name for c in ['-', ' ', '.']):
+ raise InvalidArguments(f'Rust crate {self.name} type {self.rust_crate_type} does not allow spaces, '
+ 'periods or dashes in the library name due to a limitation of rustc. '
+ 'Replace them with underscores, for example')
+
if not hasattr(self, 'prefix'):
self.prefix = None
if not hasattr(self, 'suffix'):
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, 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). The toolchain will output an import library in one of
- two formats: GCC or Visual Studio.
-
- When we're building with Visual Studio, the import library that will be
- generated by the toolchain is self.vs_import_filename, and with
- MinGW/GCC, it's self.gcc_import_filename. self.import_filename will
- always contain the import library name this target will generate.
+ 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 = ''
create_debug_file = False
self.filename_tpl = self.basic_filename_tpl
+ import_filename_tpl = None
# NOTE: manual prefix/suffix override is currently only tested for C/C++
# C# and Mono
if 'cs' in self.compilers:
# For all other targets/platforms import_filename stays None
elif self.environment.machines[self.for_machine].is_windows():
suffix = 'dll'
- self.vs_import_filename = '{}{}.lib'.format(self.prefix if self.prefix is not None else '', self.name)
- self.gcc_import_filename = '{}{}.dll.a'.format(self.prefix if self.prefix is not None else 'lib', self.name)
if self.uses_rust():
# Shared library is of the form foo.dll
prefix = ''
# Import library is called foo.dll.lib
- self.import_filename = f'{self.name}.dll.lib'
- # Debug files(.pdb) is only created with debug buildtype
+ 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.get_option(OptionKey("debug"))
elif self.get_using_msvc():
# Shared library is of the form foo.dll
prefix = ''
# Import library is called foo.lib
- self.import_filename = self.vs_import_filename
- # Debug files(.pdb) is only created with debug buildtype
+ import_filename_tpl = '{0.prefix}{0.name}.lib'
+ # .pdb file is only created when debug symbols are enabled
create_debug_file = self.environment.coredata.get_option(OptionKey("debug"))
# Assume GCC-compatible naming
else:
# Shared library is of the form libfoo.dll
prefix = 'lib'
# Import library is called libfoo.dll.a
- self.import_filename = self.gcc_import_filename
+ import_filename_tpl = '{0.prefix}{0.name}.dll.a'
# Shared library has the soversion if it is defined
if self.soversion:
self.filename_tpl = '{0.prefix}{0.name}-{0.soversion}.{0.suffix}'
self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}'
elif self.environment.machines[self.for_machine].is_cygwin():
suffix = 'dll'
- self.gcc_import_filename = '{}{}.dll.a'.format(self.prefix if self.prefix is not None else 'lib', self.name)
# 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
- self.import_filename = self.gcc_import_filename
+ import_prefix = self.prefix if self.prefix is not None else 'lib'
+ import_filename_tpl = import_prefix + '{0.name}.dll.a'
if self.soversion:
self.filename_tpl = '{0.prefix}{0.name}-{0.soversion}.{0.suffix}'
else:
if self.suffix is None:
self.suffix = suffix
self.filename = self.filename_tpl.format(self)
+ 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
# only replace the first entry
self.outputs[0] = self.filename
if create_debug_file:
self.debug_filename = os.path.splitext(self.filename)[0] + '.pdb'
- @staticmethod
- def _validate_darwin_versions(darwin_versions):
- try:
- if isinstance(darwin_versions, int):
- darwin_versions = str(darwin_versions)
- if isinstance(darwin_versions, str):
- darwin_versions = 2 * [darwin_versions]
- if not isinstance(darwin_versions, list):
- raise InvalidArguments('Shared library darwin_versions: must be a string, integer,'
- f'or a list, not {darwin_versions!r}')
- if len(darwin_versions) > 2:
- raise InvalidArguments('Shared library darwin_versions: list must contain 2 or fewer elements')
- if len(darwin_versions) == 1:
- darwin_versions = 2 * darwin_versions
- for i, v in enumerate(darwin_versions[:]):
- if isinstance(v, int):
- v = str(v)
- if not isinstance(v, str):
- raise InvalidArguments('Shared library darwin_versions: list elements '
- f'must be strings or integers, not {v!r}')
- if not re.fullmatch(r'[0-9]+(\.[0-9]+){0,2}', v):
- raise InvalidArguments('Shared library darwin_versions: must be X.Y.Z where '
- 'X, Y, Z are numbers, and Y and Z are optional')
- parts = v.split('.')
- if len(parts) in {1, 2, 3} and int(parts[0]) > 65535:
- raise InvalidArguments('Shared library darwin_versions: must be X.Y.Z '
- 'where X is [0, 65535] and Y, Z are optional')
- if len(parts) in {2, 3} and int(parts[1]) > 255:
- raise InvalidArguments('Shared library darwin_versions: must be X.Y.Z '
- 'where Y is [0, 255] and Y, Z are optional')
- if len(parts) == 3 and int(parts[2]) > 255:
- raise InvalidArguments('Shared library darwin_versions: must be X.Y.Z '
- 'where Z is [0, 255] and Y, Z are optional')
- darwin_versions[i] = v
- except ValueError:
- raise InvalidArguments('Shared library darwin_versions: value is invalid')
- return darwin_versions
-
def process_kwargs(self, kwargs):
super().process_kwargs(kwargs)
if not self.environment.machines[self.for_machine].is_android():
# Shared library version
- if 'version' in kwargs:
- self.ltversion = kwargs['version']
- if not isinstance(self.ltversion, str):
- raise InvalidArguments('Shared library version needs to be a string, not ' + type(self.ltversion).__name__)
- if not re.fullmatch(r'[0-9]+(\.[0-9]+){0,2}', self.ltversion):
- raise InvalidArguments(f'Invalid Shared library version "{self.ltversion}". Must be of the form X.Y.Z where all three are numbers. Y and Z are optional.')
- # Try to extract/deduce the soversion
- if 'soversion' in kwargs:
- self.soversion = kwargs['soversion']
- if isinstance(self.soversion, int):
- self.soversion = str(self.soversion)
- if not isinstance(self.soversion, str):
- raise InvalidArguments('Shared library soversion is not a string or integer.')
- elif self.ltversion:
+ self.ltversion = T.cast('T.Optional[str]', kwargs.get('version'))
+ self.soversion = T.cast('T.Optional[str]', 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
- if 'darwin_versions' in kwargs:
- self.darwin_versions = self._validate_darwin_versions(kwargs['darwin_versions'])
- elif self.soversion:
+ self.darwin_versions = T.cast('T.Optional[T.Tuple[str, str]]', kwargs.get('darwin_versions'))
+ if self.darwin_versions is None and self.soversion is not None:
# If unspecified, pick the soversion
- self.darwin_versions = 2 * [self.soversion]
+ self.darwin_versions = (self.soversion, self.soversion)
# Visual Studio module-definitions file
- if 'vs_module_defs' in kwargs:
- path = kwargs['vs_module_defs']
- if isinstance(path, str):
- if os.path.isabs(path):
- self.vs_module_defs = File.from_absolute_file(path)
- else:
- self.vs_module_defs = File.from_source_file(self.environment.source_dir, self.subdir, path)
- elif isinstance(path, File):
- # When passing a generated file.
- self.vs_module_defs = path
- elif hasattr(path, 'get_filename'):
- # When passing output of a Custom Target
- self.vs_module_defs = File.from_built_file(path.subdir, path.get_filename())
- else:
- raise InvalidArguments(
- 'Shared library vs_module_defs must be either a string, '
- 'a file object or a Custom Target')
- self.process_link_depends(path)
-
- if 'rust_crate_type' in kwargs:
- rust_crate_type = kwargs['rust_crate_type']
- if isinstance(rust_crate_type, str):
+ 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'Invalid rust_crate_type "{rust_crate_type}": must be a string.')
- if rust_crate_type == 'proc-macro':
- FeatureNew.single_use('Rust crate type "proc-macro"', '0.62.0', self.subproject)
+ 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'
def get_import_filename(self) -> T.Optional[str]:
"""
"""
return self.debug_filename
- def get_import_filenameslist(self):
- if self.import_filename:
- return [self.vs_import_filename, self.gcc_import_filename]
- return []
-
def get_all_link_deps(self):
return [self] + self.get_transitive_link_deps()
typename = 'shared module'
- 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'], kwargs):
+ # Used by AIX to not archive shared library for dlopen mechanism
+ aix_so_archive = False
+
+ def __init__(
+ self,
+ name: str,
+ subdir: str,
+ subproject: SubProject,
+ for_machine: MachineChoice,
+ sources: T.List['SourceOutputs'],
+ structured_sources: T.Optional[StructuredSources],
+ objects: T.List[ObjectTypes],
+ environment: environment.Environment,
+ compilers: T.Dict[str, 'Compiler'],
+ kwargs):
if 'version' in kwargs:
raise MesonException('Shared modules must not specify the version kwarg.')
if 'soversion' in kwargs:
raise InvalidArguments(f'Argument {c!r} in "command" is invalid')
return final_cmd
-class CustomTarget(Target, CommandBase):
+class CustomTargetBase:
+ ''' Base class for CustomTarget and CustomTargetIndex
+
+ This base class can be used to provide a dummy implementation of some
+ private methods to avoid repeating `isinstance(t, BuildTarget)` when dealing
+ with custom targets.
+ '''
+
+ rust_crate_type = ''
+
+ def get_dependencies_recurse(self, result: OrderedSet[BuildTargetTypes], include_internals: bool = True) -> None:
+ pass
+
+ def get_internal_static_libraries(self) -> OrderedSet[BuildTargetTypes]:
+ return OrderedSet()
+
+ def get_internal_static_libraries_recurse(self, result: OrderedSet[BuildTargetTypes]) -> None:
+ pass
+
+class CustomTarget(Target, CustomTargetBase, CommandBase):
typename = 'custom'
install_tag: T.Optional[T.List[T.Optional[str]]] = None,
absolute_paths: bool = False,
backend: T.Optional['Backend'] = None,
+ description: str = 'Generating {} with a custom command',
):
# TODO expose keyword arg to make MachineChoice.HOST configurable
- super().__init__(name, subdir, subproject, False, MachineChoice.HOST, environment)
+ super().__init__(name, subdir, subproject, False, MachineChoice.HOST, environment,
+ install, build_always_stale)
self.sources = list(sources)
self.outputs = substitute_values(
outputs, get_filenames_templates_dict(
get_sources_string_names(sources, backend),
[]))
self.build_by_default = build_by_default if build_by_default is not None else install
- self.build_always_stale = build_always_stale
self.capture = capture
self.console = console
self.depend_files = list(depend_files or [])
self.env = env or EnvironmentVariables()
self.extra_depends = list(extra_depends or [])
self.feed = feed
- self.install = install
self.install_dir = list(install_dir or [])
self.install_mode = install_mode
self.install_tag = _process_install_tag(install_tag, len(self.outputs))
self.name = name if name else self.outputs[0]
+ self.description = description
# Whether to use absolute paths for all files on the commandline
self.absolute_paths = absolute_paths
raise InvalidArguments('Substitution in depfile for custom_target that does not have an input file.')
return self.depfile
+ def is_linkable_output(self, output: str) -> bool:
+ if output.endswith(('.a', '.dll', '.lib', '.so', '.dylib')):
+ return True
+ # libfoo.so.X soname
+ if re.search(r'\.so(\.\d+)*$', output):
+ return True
+ return False
+
def is_linkable_target(self) -> bool:
if len(self.outputs) != 1:
return False
- suf = os.path.splitext(self.outputs[0])[-1]
- return suf in {'.a', '.dll', '.lib', '.so', '.dylib'}
+ return self.is_linkable_output(self.outputs[0])
def links_dynamically(self) -> bool:
"""Whether this target links dynamically or statically
return False
return CustomTargetIndex(self, self.outputs[0]).is_internal()
- def extract_all_objects_recurse(self) -> T.List[T.Union[str, 'ExtractedObjects']]:
+ def extract_all_objects(self) -> T.List[T.Union[str, 'ExtractedObjects']]:
return self.get_outputs()
def type_suffix(self):
subdir: str,
subproject: str,
environment: environment.Environment,
- sources: T.List[File],
+ sources: T.List['SourceOutputs'],
output_templ: str,
compiler: Compiler,
- kwargs):
+ backend: Backend,
+ compile_args: T.List[str],
+ include_directories: T.List[IncludeDirs],
+ dependencies: T.List[dependencies.Dependency]):
compilers = {compiler.get_language(): compiler}
+ kwargs = {
+ 'build_by_default': False,
+ f'{compiler.language}_args': compile_args,
+ 'include_directories': include_directories,
+ 'dependencies': dependencies,
+ }
super().__init__(name, subdir, subproject, compiler.for_machine,
sources, None, [], environment, compilers, kwargs)
self.filename = name
self.compiler = compiler
self.output_templ = output_templ
self.outputs = []
- for f in sources:
- plainname = os.path.basename(f.fname)
- basename = os.path.splitext(plainname)[0]
- self.outputs.append(output_templ.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname))
- self.sources_map = dict(zip(sources, self.outputs))
+ self.sources_map: T.Dict[File, str] = {}
+ for f in self.sources:
+ self._add_output(f)
+ for gensrc in self.generated:
+ for s in gensrc.get_outputs():
+ rel_src = backend.get_target_generated_dir(self, gensrc, s)
+ self._add_output(File.from_built_relative(rel_src))
def type_suffix(self) -> str:
return "@compile"
def is_unity(self) -> bool:
return False
+ def _add_output(self, f: File) -> None:
+ plainname = os.path.basename(f.fname)
+ basename = os.path.splitext(plainname)[0]
+ o = self.output_templ.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname)
+ self.outputs.append(o)
+ self.sources_map[f] = o
+
class RunTarget(Target, CommandBase):
return "@run"
class AliasTarget(RunTarget):
+
+ typename = 'alias'
+
def __init__(self, name: str, dependencies: T.Sequence['Target'],
subdir: str, subproject: str, environment: environment.Environment):
super().__init__(name, [], dependencies, subdir, subproject, environment)
raise InvalidArguments('structured sources are not supported in Java targets.')
self.filename = self.name + '.jar'
self.outputs = [self.filename]
- self.java_args = kwargs.get('java_args', [])
+ self.java_args = self.extra_args['java']
+ self.main_class = kwargs.get('main_class', '')
self.java_resources: T.Optional[StructuredSources] = kwargs.get('java_resources', None)
def get_main_class(self):
return self.environment.get_jar_dir(), '{jardir}'
@dataclass(eq=False)
-class CustomTargetIndex(HoldableObject):
+class CustomTargetIndex(CustomTargetBase, HoldableObject):
"""A special opaque object returned by indexing a CustomTarget. This object
exists in Meson, but acts as a proxy in the backends, making targets depend
return self.target.get_link_dep_subdirs()
def is_linkable_target(self) -> bool:
- suf = os.path.splitext(self.output)[-1]
- return suf in {'.a', '.dll', '.lib', '.so', '.dylib'}
+ return self.target.is_linkable_output(self.output)
def links_dynamically(self) -> bool:
"""Whether this target links dynamically or statically
suf = os.path.splitext(self.output)[-1]
return suf in {'.a', '.lib'} and not self.should_install()
- def extract_all_objects_recurse(self) -> T.List[T.Union[str, 'ExtractedObjects']]:
- return self.target.extract_all_objects_recurse()
+ def extract_all_objects(self) -> T.List[T.Union[str, 'ExtractedObjects']]:
+ return self.target.extract_all_objects()
def get_custom_install_dir(self) -> T.List[T.Union[str, Literal[False]]]:
return self.target.get_custom_install_dir()
rename: T.List[str] = None
install_tag: T.Optional[str] = None
data_type: str = None
+ follow_symlinks: T.Optional[bool] = None
def __post_init__(self) -> None:
if self.rename is None:
def load(build_dir: str) -> Build:
filename = os.path.join(build_dir, 'meson-private', 'build.dat')
try:
- return pickle_load(filename, 'Build data', Build)
+ b = pickle_load(filename, 'Build data', Build)
+ # We excluded coredata when saving Build object, load it separately
+ b.environment.coredata = coredata.load(build_dir)
+ return b
except FileNotFoundError:
raise MesonException(f'No such build data file as {filename!r}.')
def save(obj: Build, filename: str) -> None:
- with open(filename, 'wb') as f:
- pickle.dump(obj, f)
+ # Exclude coredata because we pickle it separately already
+ cdata = obj.environment.coredata
+ obj.environment.coredata = None
+ try:
+ with open(filename, 'wb') as f:
+ pickle.dump(obj, f)
+ finally:
+ obj.environment.coredata = cdata
--- /dev/null
+__all__ = [
+ 'interpret'
+]
+
+from .interpreter import interpret
--- /dev/null
+# SPDX-License-Identifier: Apache-2.0
+# Copyright © 2022-2023 Intel Corporation
+
+"""Provides helpers for building AST
+
+This is meant to make building Meson AST from foreign (largely declarative)
+build descriptions easier.
+"""
+
+from __future__ import annotations
+import dataclasses
+import typing as T
+
+from .. import mparser
+
+if T.TYPE_CHECKING:
+ import builtins
+
+
+@dataclasses.dataclass
+class Builder:
+
+ filename: str
+
+ def _token(self, tid: str, value: mparser.TV_TokenTypes) -> mparser.Token[mparser.TV_TokenTypes]:
+ """Create a Token object, but with the line numbers stubbed out.
+
+ :param tid: the token id (such as string, number, etc)
+ :param filename: the filename that the token was generated from
+ :param value: the value of the token
+ :return: A Token object
+ """
+ return mparser.Token(tid, self.filename, -1, -1, -1, (-1, -1), value)
+
+ def _symbol(self, val: str) -> mparser.SymbolNode:
+ return mparser.SymbolNode(self._token('', val))
+
+ def assign(self, value: mparser.BaseNode, varname: str) -> mparser.AssignmentNode:
+ return mparser.AssignmentNode(self.identifier(varname), self._symbol('='), value)
+
+ def string(self, value: str) -> mparser.StringNode:
+ """Build A StringNode
+
+ :param value: the value of the string
+ :return: A StringNode
+ """
+ return mparser.StringNode(self._token('string', value))
+
+ def number(self, value: int) -> mparser.NumberNode:
+ """Build A NumberNode
+
+ :param value: the value of the number
+ :return: A NumberNode
+ """
+ return mparser.NumberNode(self._token('number', str(value)))
+
+ def bool(self, value: builtins.bool) -> mparser.BooleanNode:
+ """Build A BooleanNode
+
+ :param value: the value of the boolean
+ :return: A BooleanNode
+ """
+ return mparser.BooleanNode(self._token('bool', value))
+
+ def array(self, value: T.List[mparser.BaseNode]) -> mparser.ArrayNode:
+ """Build an Array Node
+
+ :param value: A list of nodes to insert into the array
+ :return: An ArrayNode built from the arguments
+ """
+ args = mparser.ArgumentNode(self._token('array', 'unused'))
+ args.arguments = value
+ return mparser.ArrayNode(self._symbol('['), args, self._symbol(']'))
+
+ def dict(self, value: T.Dict[mparser.BaseNode, mparser.BaseNode]) -> mparser.DictNode:
+ """Build an Dictionary Node
+
+ :param value: A dict of nodes to insert into the dictionary
+ :return: An DictNode built from the arguments
+ """
+ args = mparser.ArgumentNode(self._token('dict', 'unused'))
+ for key, val in value.items():
+ args.set_kwarg_no_check(key, val)
+ return mparser.DictNode(self._symbol('{'), args, self._symbol('}'))
+
+ def identifier(self, value: str) -> mparser.IdNode:
+ """Build A IdNode
+
+ :param value: the value of the boolean
+ :return: A BooleanNode
+ """
+ return mparser.IdNode(self._token('id', value))
+
+ def method(self, name: str, id_: mparser.IdNode,
+ pos: T.Optional[T.List[mparser.BaseNode]] = None,
+ kw: T.Optional[T.Mapping[str, mparser.BaseNode]] = None,
+ ) -> mparser.MethodNode:
+ """Create a method call.
+
+ :param name: the name of the method
+ :param id_: the object to call the method of
+ :param pos: a list of positional arguments, defaults to None
+ :param kw: a dictionary of keyword arguments, defaults to None
+ :return: a method call object
+ """
+ args = mparser.ArgumentNode(self._token('array', 'unused'))
+ if pos is not None:
+ args.arguments = pos
+ if kw is not None:
+ args.kwargs = {self.identifier(k): v for k, v in kw.items()}
+ return mparser.MethodNode(id_, self._symbol('.'), self.identifier(name), self._symbol('('), args, self._symbol(')'))
+
+ def function(self, name: str,
+ pos: T.Optional[T.List[mparser.BaseNode]] = None,
+ kw: T.Optional[T.Mapping[str, mparser.BaseNode]] = None,
+ ) -> mparser.FunctionNode:
+ """Create a function call.
+
+ :param name: the name of the function
+ :param pos: a list of positional arguments, defaults to None
+ :param kw: a dictionary of keyword arguments, defaults to None
+ :return: a method call object
+ """
+ args = mparser.ArgumentNode(self._token('array', 'unused'))
+ if pos is not None:
+ args.arguments = pos
+ if kw is not None:
+ args.kwargs = {self.identifier(k): v for k, v in kw.items()}
+ return mparser.FunctionNode(self.identifier(name), self._symbol('('), args, self._symbol(')'))
+
+ def equal(self, lhs: mparser.BaseNode, rhs: mparser.BaseNode) -> mparser.ComparisonNode:
+ """Create an equality operation
+
+ :param lhs: The left hand side of the equal
+ :param rhs: the right hand side of the equal
+ :return: A compraison node
+ """
+ return mparser.ComparisonNode('==', lhs, self._symbol('=='), rhs)
+
+ def or_(self, lhs: mparser.BaseNode, rhs: mparser.BaseNode) -> mparser.OrNode:
+ """Create and OrNode
+
+ :param lhs: The Left of the Node
+ :param rhs: The Right of the Node
+ :return: The OrNode
+ """
+ return mparser.OrNode(lhs, self._symbol('or'), rhs)
+
+ def and_(self, lhs: mparser.BaseNode, rhs: mparser.BaseNode) -> mparser.AndNode:
+ """Create an AndNode
+
+ :param lhs: The left of the And
+ :param rhs: The right of the And
+ :return: The AndNode
+ """
+ return mparser.AndNode(lhs, self._symbol('and'), rhs)
+
+ def not_(self, value: mparser.BaseNode) -> mparser.NotNode:
+ """Create a not node
+
+ :param value: The value to negate
+ :return: The NotNode
+ """
+ return mparser.NotNode(self._token('not', ''), self._symbol('not'), value)
+
+ def block(self, lines: T.List[mparser.BaseNode]) -> mparser.CodeBlockNode:
+ block = mparser.CodeBlockNode(self._token('node', ''))
+ block.lines = lines
+ return block
--- /dev/null
+# SPDX-License-Identifier: Apache-2.0
+# Copyright © 2022-2023 Intel Corporation
+
+"""Rust CFG parser.
+
+Rust uses its `cfg()` format in cargo.
+
+This may have the following functions:
+ - all()
+ - any()
+ - not()
+
+And additionally is made up of `identifier [ = str]`. Where the str is optional,
+so you could have examples like:
+```
+[target.`cfg(unix)`.dependencies]
+[target.'cfg(target_arch = "x86_64")'.dependencies]
+[target.'cfg(all(target_arch = "x86_64", target_arch = "x86"))'.dependencies]
+```
+"""
+
+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_AH = T.Iterator[T.Tuple[_LEX_TOKEN, T.Optional[_LEX_TOKEN]]]
+
+
+class TokenType(enum.Enum):
+
+ LPAREN = enum.auto()
+ RPAREN = enum.auto()
+ STRING = enum.auto()
+ IDENTIFIER = enum.auto()
+ ALL = enum.auto()
+ ANY = enum.auto()
+ NOT = enum.auto()
+ COMMA = enum.auto()
+ EQUAL = enum.auto()
+
+
+def lexer(raw: str) -> _LEX_STREAM:
+ """Lex a cfg() expression.
+
+ :param raw: The raw cfg() expression
+ :return: An iterable of tokens
+ """
+ buffer: T.List[str] = []
+ 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:
+ yield (TokenType.STRING, val)
+ elif val == 'any':
+ yield (TokenType.ANY, None)
+ elif val == 'all':
+ yield (TokenType.ALL, None)
+ elif val == 'not':
+ yield (TokenType.NOT, 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:
+ # This should always be an identifier
+ yield (TokenType.IDENTIFIER, ''.join(buffer))
+
+
+def lookahead(iter: T.Iterator[_T]) -> T.Iterator[T.Tuple[_T, T.Optional[_T]]]:
+ """Get the current value of the iterable, and the next if possible.
+
+ :param iter: The iterable to look into
+ :yield: A tuple of the current value, and, if possible, the next
+ :return: nothing
+ """
+ current: _T
+ next_: T.Optional[_T]
+ try:
+ next_ = next(iter)
+ except StopIteration:
+ # This is an empty iterator, there's nothing to look ahead to
+ return
+
+ while True:
+ current = next_
+ try:
+ next_ = next(iter)
+ except StopIteration:
+ next_ = None
+
+ yield current, next_
+
+ if next_ is None:
+ break
+
+
+@dataclasses.dataclass
+class IR:
+
+ """Base IR node for Cargo CFG."""
+
+
+@dataclasses.dataclass
+class String(IR):
+
+ value: str
+
+
+@dataclasses.dataclass
+class Identifier(IR):
+
+ value: str
+
+
+@dataclasses.dataclass
+class Equal(IR):
+
+ lhs: IR
+ rhs: IR
+
+
+@dataclasses.dataclass
+class Any(IR):
+
+ args: T.List[IR]
+
+
+@dataclasses.dataclass
+class All(IR):
+
+ args: T.List[IR]
+
+
+@dataclasses.dataclass
+class Not(IR):
+
+ value: IR
+
+
+def _parse(ast: _LEX_STREAM_AH) -> IR:
+ (token, value), n_stream = next(ast)
+ if n_stream is not None:
+ ntoken, _ = n_stream
+ else:
+ ntoken, _ = (None, None)
+
+ stream: T.List[_LEX_TOKEN]
+ if token is TokenType.IDENTIFIER:
+ 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}:
+ 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), _ = 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))))
+ 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}')
+
+
+def parse(ast: _LEX_STREAM) -> IR:
+ """Parse the tokenized list into Meson AST.
+
+ :param ast: An iterable of Tokens
+ :return: An mparser Node to be used as a conditional
+ """
+ ast_i: _LEX_STREAM_AH = lookahead(iter(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
+
+
+@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
--- /dev/null
+# SPDX-License-Identifier: Apache-2.0
+# Copyright © 2022-2023 Intel Corporation
+
+"""Interpreter for converting Cargo Toml definitions to Meson AST
+
+There are some notable limits here. We don't even try to convert something with
+a build.rs: there's so few limits on what Cargo allows a build.rs (basically
+none), and no good way for us to convert them. In that case, an actual meson
+port will be required.
+"""
+
+from __future__ import annotations
+import dataclasses
+import glob
+import importlib
+import itertools
+import json
+import os
+import shutil
+import typing as T
+
+from . import builder
+from . import version
+from ..mesonlib import MesonException, Popen_safe
+
+if T.TYPE_CHECKING:
+ from types import ModuleType
+
+ from . import manifest
+ from .. import mparser
+ from ..environment import Environment
+
+# 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')
+
+
+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 MesonException('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 Dependndency 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)
+
+
+@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.Dict[str, str]] = dataclasses.field(default_factory=dict)
+ default_run: T.Optional[str] = None
+ autobins: bool = True
+ autoexamples: bool = True
+ autotests: bool = True
+ autobenches: bool = True
+
+
+@dataclasses.dataclass
+class Dependency:
+
+ """Representation of a Cargo Dependency Entry."""
+
+ 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: T.Optional[str] = None
+ default_features: bool = False
+ features: T.List[str] = dataclasses.field(default_factory=list)
+
+ @classmethod
+ def from_raw(cls, raw: manifest.DependencyV) -> Dependency:
+ """Create a dependency from a raw cargo dictionary"""
+ if isinstance(raw, str):
+ return cls(version.convert(raw))
+ return cls(**_fixup_raw_mappings(raw))
+
+
+@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
+
+
+@dataclasses.dataclass
+class Library(BuildTarget):
+
+ """Representation of a Cargo Library Entry."""
+
+ doctest: bool = True
+ doc: bool = True
+ proc_macro: bool = False
+ crate_type: T.List[manifest.CRATE_TYPE] = dataclasses.field(default_factory=lambda: ['lib'])
+ doc_scrape_examples: bool = True
+
+
+@dataclasses.dataclass
+class Binary(BuildTarget):
+
+ """Representation of a Cargo Bin Entry."""
+
+ doc: bool = True
+
+
+@dataclasses.dataclass
+class Test(BuildTarget):
+
+ """Representation of a Cargo Test Entry."""
+
+ bench: bool = True
+
+
+@dataclasses.dataclass
+class Benchmark(BuildTarget):
+
+ """Representation of a Cargo Benchmark Entry."""
+
+ test: bool = True
+
+
+@dataclasses.dataclass
+class Example(BuildTarget):
+
+ """Representation of a Cargo Example Entry."""
+
+ crate_type: T.List[manifest.CRATE_TYPE] = dataclasses.field(default_factory=lambda: ['bin'])
+
+
+@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 subdir: the subdirectory that this cargo project is in
+ :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]
+ 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]]
+ subdir: str
+ path: str = ''
+
+
+def _convert_manifest(raw_manifest: manifest.Manifest, subdir: str, path: str = '') -> Manifest:
+ # This cast is a bit of a hack to deal with proc-macro
+ lib = _fixup_raw_mappings(raw_manifest.get('lib', {}))
+
+ # We need to set the name field if it's not set manually,
+ # including if other fields are set in the lib section
+ lib.setdefault('name', raw_manifest['package']['name'])
+
+ pkg = T.cast('manifest.FixedPackage',
+ {fixup_meson_varname(k): v for k, v in raw_manifest['package'].items()})
+
+ return Manifest(
+ Package(**pkg),
+ {k: Dependency.from_raw(v) for k, v in raw_manifest.get('dependencies', {}).items()},
+ {k: Dependency.from_raw(v) for k, v in raw_manifest.get('dev-dependencies', {}).items()},
+ {k: Dependency.from_raw(v) for k, v in raw_manifest.get('build-dependencies', {}).items()},
+ Library(**lib),
+ [Binary(**_fixup_raw_mappings(b)) for b in raw_manifest.get('bin', {})],
+ [Test(**_fixup_raw_mappings(b)) for b in raw_manifest.get('test', {})],
+ [Benchmark(**_fixup_raw_mappings(b)) for b in raw_manifest.get('bench', {})],
+ [Example(**_fixup_raw_mappings(b)) for b in raw_manifest.get('example', {})],
+ raw_manifest.get('features', {}),
+ {k: {k2: Dependency.from_raw(v2) for k2, v2 in v.get('dependencies', {}).items()}
+ for k, v in raw_manifest.get('target', {}).items()},
+ subdir,
+ path,
+ )
+
+
+def _load_manifests(subdir: str) -> T.Dict[str, Manifest]:
+ filename = os.path.join(subdir, 'Cargo.toml')
+ raw = load_toml(filename)
+
+ manifests: T.Dict[str, Manifest] = {}
+
+ raw_manifest: T.Union[manifest.Manifest, manifest.VirtualManifest]
+ if 'package' in raw:
+ raw_manifest = T.cast('manifest.Manifest', raw)
+ manifest_ = _convert_manifest(raw_manifest, subdir)
+ manifests[manifest_.package.name] = manifest_
+ else:
+ raw_manifest = T.cast('manifest.VirtualManifest', raw)
+
+ if 'workspace' in raw_manifest:
+ # XXX: need to verify that python glob and cargo globbing are the
+ # same and probably write a glob implementation. Blarg
+
+ # We need to chdir here to make the glob work correctly
+ pwd = os.getcwd()
+ os.chdir(subdir)
+ members: T.Iterable[str]
+ try:
+ members = itertools.chain.from_iterable(
+ glob.glob(m) for m in raw_manifest['workspace']['members'])
+ finally:
+ os.chdir(pwd)
+ if 'exclude' in raw_manifest['workspace']:
+ members = (x for x in members if x not in raw_manifest['workspace']['exclude'])
+
+ for m in members:
+ filename = os.path.join(subdir, m, 'Cargo.toml')
+ raw = load_toml(filename)
+
+ raw_manifest = T.cast('manifest.Manifest', raw)
+ man = _convert_manifest(raw_manifest, subdir, m)
+ manifests[man.package.name] = man
+
+ return manifests
+
+
+def _dependency_name(package_name: str) -> str:
+ return package_name if package_name.endswith('-rs') else f'{package_name}-rs'
+
+
+def _dependency_varname(package_name: str) -> str:
+ return f'{fixup_meson_varname(package_name)}_dep'
+
+
+def _create_project(cargo: Manifest, build: builder.Builder, env: Environment) -> T.List[mparser.BaseNode]:
+ """Create a function call
+
+ :param cargo: The Manifest to generate from
+ :param build: The AST builder
+ :param env: Meson environment
+ :return: a list nodes
+ """
+ args: T.List[mparser.BaseNode] = []
+ args.extend([
+ build.string(cargo.package.name),
+ build.string('rust'),
+ ])
+ kwargs: T.Dict[str, mparser.BaseNode] = {
+ 'version': build.string(cargo.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'>= {env.coredata.version}'),
+ 'default_options': build.array([build.string(f'rust_std={cargo.package.edition}')]),
+ }
+ if cargo.package.license:
+ kwargs['license'] = build.string(cargo.package.license)
+ elif cargo.package.license_file:
+ kwargs['license_files'] = build.string(cargo.package.license_file)
+
+ return [build.function('project', args, kwargs)]
+
+
+def _create_dependencies(cargo: Manifest, build: builder.Builder) -> T.List[mparser.BaseNode]:
+ ast: T.List[mparser.BaseNode] = []
+ for name, dep in cargo.dependencies.items():
+ package_name = dep.package or name
+ kw = {
+ 'version': build.array([build.string(s) for s in dep.version]),
+ }
+ ast.extend([
+ build.assign(
+ build.function(
+ 'dependency',
+ [build.string(_dependency_name(package_name))],
+ kw,
+ ),
+ _dependency_varname(package_name),
+ ),
+ ])
+ return ast
+
+
+def _create_lib(cargo: Manifest, build: builder.Builder, crate_type: manifest.CRATE_TYPE) -> T.List[mparser.BaseNode]:
+ dependencies: T.List[mparser.BaseNode] = []
+ dependency_map: T.Dict[mparser.BaseNode, mparser.BaseNode] = {}
+ for name, dep in cargo.dependencies.items():
+ package_name = dep.package or name
+ dependencies.append(build.identifier(_dependency_varname(package_name)))
+ if name != package_name:
+ dependency_map[build.string(fixup_meson_varname(package_name))] = build.string(name)
+
+ posargs: T.List[mparser.BaseNode] = [
+ build.string(fixup_meson_varname(cargo.package.name)),
+ build.string(os.path.join('src', 'lib.rs')),
+ ]
+
+ kwargs: T.Dict[str, mparser.BaseNode] = {
+ 'dependencies': build.array(dependencies),
+ 'rust_dependency_map': build.dict(dependency_map),
+ }
+
+ lib: mparser.BaseNode
+ if cargo.lib.proc_macro or crate_type == 'proc-macro':
+ kwargs['rust_args'] = build.array([build.string('--extern'), build.string('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'
+ 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)
+
+ return [
+ build.assign(lib, 'lib'),
+ build.assign(
+ build.function(
+ 'declare_dependency',
+ kw={
+ 'link_with': build.identifier('lib'),
+ },
+ ),
+ 'dep'
+ ),
+ build.method(
+ 'override_dependency',
+ build.identifier('meson'),
+ [
+ build.string(_dependency_name(cargo.package.name)),
+ build.identifier('dep'),
+ ],
+ ),
+ ]
+
+
+def interpret(subp_name: str, subdir: str, env: Environment) -> mparser.CodeBlockNode:
+ package_name = subp_name[:-3] if subp_name.endswith('-rs') else subp_name
+ manifests = _load_manifests(os.path.join(env.source_dir, subdir))
+ cargo = manifests.get(package_name)
+ if not cargo:
+ raise MesonException(f'Cargo package {package_name!r} not found in {subdir}')
+
+ filename = os.path.join(cargo.subdir, cargo.path, 'Cargo.toml')
+ build = builder.Builder(filename)
+
+ ast = _create_project(cargo, build, env)
+ ast += [build.assign(build.function('import', [build.string('rust')]), 'rust')]
+ ast += _create_dependencies(cargo, 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(env.source_dir, cargo.subdir, cargo.path, 'src', 'lib.rs')):
+ for crate_type in cargo.lib.crate_type:
+ ast.extend(_create_lib(cargo, build, crate_type))
+
+ return build.block(ast)
--- /dev/null
+# SPDX-License-Identifier: Apache-2.0
+# Copyright © 2022-2023 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']
+
+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,
+ 'autobins': bool,
+ 'autoexamples': bool,
+ 'autotests': bool,
+ 'autobenches': bool,
+ },
+ total=False,
+)
+"""A description of the Package Dictionary."""
+
+class FixedPackage(TypedDict, total=False):
+
+ """A description of the Package Dictionary, fixed up."""
+
+ 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
+ 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,
+)
+"""An entry in the *dependencies sections."""
+
+
+class FixedDependency(TypedDict, total=False):
+
+ """An entry in the *dependencies sections, fixed up."""
+
+ 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 BuildTarget(_BaseBuildTarget, total=False):
+
+ name: Required[str]
+
+class LibTarget(_BaseBuildTarget, total=False):
+
+ name: str
+
+
+class _BaseFixedBuildTarget(TypedDict, total=False):
+ 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]
+
+
+class FixedBuildTarget(_BaseFixedBuildTarget, total=False):
+
+ name: str
+
+class FixedLibTarget(_BaseFixedBuildTarget, total=False):
+
+ name: Required[str]
+ proc_macro: bool
+
+
+class Target(TypedDict):
+
+ """Target entry in the Manifest File."""
+
+ dependencies: T.Dict[str, DependencyV]
+
+
+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]
+
+
+Manifest = TypedDict(
+ 'Manifest',
+ {
+ 'package': 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."""
+
+
+class VirtualManifest(TypedDict):
+
+ """The Representation of a virtual manifest.
+
+ 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".
+ """
+
+ workspace: Workspace
--- /dev/null
+# SPDX-License-Identifier: Apache-2.0
+# Copyright © 2022-2023 Intel Corporation
+
+"""Convert Cargo versions into Meson compatible ones."""
+
+from __future__ import annotations
+import typing as T
+
+
+def convert(cargo_ver: str) -> T.List[str]:
+ """Convert a Cargo compatible version into a Meson compatible one.
+
+ :param cargo_ver: The version, as Cargo specifies
+ :return: A list of version constraints, as Meson understands them
+ """
+ # Cleanup, just for safety
+ cargo_ver = cargo_ver.strip()
+ cargo_vers = [c.strip() for c in cargo_ver.split(',')]
+
+ out: T.List[str] = []
+
+ for ver in cargo_vers:
+ # This covers >= and =< as well
+ # https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#comparison-requirements
+ if ver.startswith(('>', '<', '=')):
+ out.append(ver)
+
+ elif ver.startswith('~'):
+ # Rust has these tilde requirements, which means that it is >= to
+ # the version, but less than the next version
+ # https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#tilde-requirements
+ # we convert those into a pair of constraints
+ v = ver[1:].split('.')
+ out.append(f'>= {".".join(v)}')
+ if len(v) == 3:
+ out.append(f'< {v[0]}.{int(v[1]) + 1}.0')
+ elif len(v) == 2:
+ out.append(f'< {v[0]}.{int(v[1]) + 1}')
+ else:
+ out.append(f'< {int(v[0]) + 1}')
+
+ elif '*' in ver:
+ # Rust has astrisk requirements,, which are like 1.* == ~1
+ # https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#wildcard-requirements
+ v = ver.split('.')[:-1]
+ if v:
+ out.append(f'>= {".".join(v)}')
+ if len(v) == 2:
+ out.append(f'< {v[0]}.{int(v[1]) + 1}')
+ elif len(v) == 1:
+ out.append(f'< {int(v[0]) + 1}')
+
+ else:
+ # a Caret version is equivalent to the default strategy
+ # https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#caret-requirements
+ if ver.startswith('^'):
+ ver = ver[1:]
+
+ # If there is no qualifier, then it means this or the next non-zero version
+ # That means that if this is `1.1.0``, then we need `>= 1.1.0` && `< 2.0.0`
+ # Or if we have `0.1.0`, then we need `>= 0.1.0` && `< 0.2.0`
+ # Or if we have `0.1`, then we need `>= 0.1.0` && `< 0.2.0`
+ # Or if we have `0.0.0`, then we need `< 1.0.0`
+ # Or if we have `0.0`, then we need `< 1.0.0`
+ # Or if we have `0`, then we need `< 1.0.0`
+ # Or if we have `0.0.3`, then we need `>= 0.0.3` && `< 0.0.4`
+ # https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#specifying-dependencies-from-cratesio
+ #
+ # this works much like the ~ versions, but in reverse. Tilde starts
+ # at the patch version and works up, to the major version, while
+ # bare numbers start at the major version and work down to the patch
+ # version
+ vers = ver.split('.')
+ min_: T.List[str] = []
+ max_: T.List[str] = []
+ bumped = False
+ for v_ in vers:
+ if v_ != '0' and not bumped:
+ min_.append(v_)
+ max_.append(str(int(v_) + 1))
+ bumped = True
+ else:
+ min_.append(v_)
+ if not bumped:
+ max_.append('0')
+
+ # If there is no minimum, don't emit one
+ if set(min_) != {'0'}:
+ out.append('>= {}'.format('.'.join(min_)))
+ if set(max_) != {'0'}:
+ out.append('< {}'.format('.'.join(max_)))
+ else:
+ out.append('< 1')
+
+ return out
'CMakeExecutor',
'CMakeExecScope',
'CMakeException',
- 'CMakeFileAPI',
'CMakeInterpreter',
'CMakeTarget',
'CMakeToolchain',
- 'CMakeTraceLine',
'CMakeTraceParser',
- 'SingleTargetOptions',
'TargetOptions',
- 'parse_generator_expressions',
'language_map',
- 'backend_generator_map',
- 'cmake_get_generator_args',
'cmake_defines_to_args',
'check_cmake_args',
'cmake_is_debug',
'resolve_cmake_trace_targets',
- 'ResolvedTarget',
]
-from .common import CMakeException, SingleTargetOptions, TargetOptions, cmake_defines_to_args, language_map, backend_generator_map, cmake_get_generator_args, check_cmake_args, cmake_is_debug
+from .common import CMakeException, TargetOptions, cmake_defines_to_args, language_map, check_cmake_args, cmake_is_debug
from .executor import CMakeExecutor
-from .fileapi import CMakeFileAPI
-from .generator import parse_generator_expressions
from .interpreter import CMakeInterpreter
from .toolchain import CMakeToolchain, CMakeExecScope
-from .traceparser import CMakeTarget, CMakeTraceLine, CMakeTraceParser
-from .tracetargets import resolve_cmake_trace_targets, ResolvedTarget
+from .traceparser import CMakeTarget, CMakeTraceParser
+from .tracetargets import resolve_cmake_trace_targets
# This class contains the basic functionality needed to run any interpreter
# or an interpreter-based tool.
+from __future__ import annotations
from ..mesonlib import MesonException, OptionKey
from .. import mlog
if T.TYPE_CHECKING:
from ..environment import Environment
+ from ..interpreterbase import TYPE_var
language_map = {
'c': 'C',
assert backend_name in backend_generator_map
return ['-G', backend_generator_map[backend_name]]
-def cmake_defines_to_args(raw: T.Any, permissive: bool = False) -> T.List[str]:
- res = [] # type: T.List[str]
- if not isinstance(raw, list):
- raw = [raw]
+def cmake_defines_to_args(raw: T.List[T.Dict[str, TYPE_var]], permissive: bool = False) -> T.List[str]:
+ res: T.List[str] = []
for i in raw:
- if not isinstance(i, dict):
- raise MesonException('Invalid CMake defines. Expected a dict, but got a {}'.format(type(i).__name__))
for key, val in i.items():
- assert isinstance(key, str)
if key in blacklist_cmake_defs:
mlog.warning('Setting', mlog.bold(key), 'is not supported. See the meson docs for cross compilation support:')
mlog.warning(' - URL: https://mesonbuild.com/CMake-module.html#cross-compilation')
return res
-# TODO: this functuin will become obsolete once the `cmake_args` kwarg is dropped
+# TODO: this function will become obsolete once the `cmake_args` kwarg is dropped
def check_cmake_args(args: T.List[str]) -> T.List[str]:
- res = [] # type: T.List[str]
+ res: T.List[str] = []
dis = ['-D' + x for x in blacklist_cmake_defs]
assert dis # Ensure that dis is not empty.
for i in args:
class CMakeFileGroup:
def __init__(self, data: T.Dict[str, T.Any]) -> None:
- self.defines = data.get('defines', '') # type: str
- self.flags = _flags_to_list(data.get('compileFlags', '')) # type: T.List[str]
- self.is_generated = data.get('isGenerated', False) # type: bool
- self.language = data.get('language', 'C') # type: str
- self.sources = [Path(x) for x in data.get('sources', [])] # type: T.List[Path]
+ self.defines: str = data.get('defines', '')
+ self.flags = _flags_to_list(data.get('compileFlags', ''))
+ self.is_generated: bool = data.get('isGenerated', False)
+ self.language: str = data.get('language', 'C')
+ self.sources = [Path(x) for x in data.get('sources', [])]
# Fix the include directories
- self.includes = [] # type: T.List[CMakeInclude]
+ self.includes: T.List[CMakeInclude] = []
for i in data.get('includePath', []):
if isinstance(i, dict) and 'path' in i:
isSystem = i.get('isSystem', False)
class CMakeTarget:
def __init__(self, data: T.Dict[str, T.Any]) -> None:
- self.artifacts = [Path(x) for x in data.get('artifacts', [])] # type: T.List[Path]
- self.src_dir = Path(data.get('sourceDirectory', '')) # type: Path
- self.build_dir = Path(data.get('buildDirectory', '')) # type: Path
- self.name = data.get('name', '') # type: str
- self.full_name = data.get('fullName', '') # type: str
- self.install = data.get('hasInstallRule', False) # type: bool
- self.install_paths = [Path(x) for x in set(data.get('installPaths', []))] # type: T.List[Path]
- self.link_lang = data.get('linkerLanguage', '') # type: str
- self.link_libraries = _flags_to_list(data.get('linkLibraries', '')) # type: T.List[str]
- self.link_flags = _flags_to_list(data.get('linkFlags', '')) # type: T.List[str]
- self.link_lang_flags = _flags_to_list(data.get('linkLanguageFlags', '')) # type: T.List[str]
- # self.link_path = Path(data.get('linkPath', '')) # type: Path
- self.type = data.get('type', 'EXECUTABLE') # type: str
- # self.is_generator_provided = data.get('isGeneratorProvided', False) # type: bool
- self.files = [] # type: T.List[CMakeFileGroup]
+ self.artifacts = [Path(x) for x in data.get('artifacts', [])]
+ self.src_dir = Path(data.get('sourceDirectory', ''))
+ self.build_dir = Path(data.get('buildDirectory', ''))
+ self.name: str = data.get('name', '')
+ self.full_name: str = data.get('fullName', '')
+ self.install: bool = data.get('hasInstallRule', False)
+ self.install_paths = [Path(x) for x in set(data.get('installPaths', []))]
+ self.link_lang: str = data.get('linkerLanguage', '')
+ self.link_libraries = _flags_to_list(data.get('linkLibraries', ''))
+ self.link_flags = _flags_to_list(data.get('linkFlags', ''))
+ self.link_lang_flags = _flags_to_list(data.get('linkLanguageFlags', ''))
+ # self.link_path = Path(data.get('linkPath', ''))
+ self.type: str = data.get('type', 'EXECUTABLE')
+ # self.is_generator_provided: bool = data.get('isGeneratorProvided', False)
+ self.files: T.List[CMakeFileGroup] = []
for i in data.get('fileGroups', []):
self.files += [CMakeFileGroup(i)]
class CMakeProject:
def __init__(self, data: T.Dict[str, T.Any]) -> None:
- self.src_dir = Path(data.get('sourceDirectory', '')) # type: Path
- self.build_dir = Path(data.get('buildDirectory', '')) # type: Path
- self.name = data.get('name', '') # type: str
- self.targets = [] # type: T.List[CMakeTarget]
+ self.src_dir = Path(data.get('sourceDirectory', ''))
+ self.build_dir = Path(data.get('buildDirectory', ''))
+ self.name: str = data.get('name', '')
+ self.targets: T.List[CMakeTarget] = []
for i in data.get('targets', []):
self.targets += [CMakeTarget(i)]
class CMakeConfiguration:
def __init__(self, data: T.Dict[str, T.Any]) -> None:
- self.name = data.get('name', '') # type: str
- self.projects = [] # type: T.List[CMakeProject]
+ self.name: str = data.get('name', '')
+ self.projects: T.List[CMakeProject] = []
for i in data.get('projects', []):
self.projects += [CMakeProject(i)]
class SingleTargetOptions:
def __init__(self) -> None:
- self.opts = {} # type: T.Dict[str, str]
- self.lang_args = {} # type: T.Dict[str, T.List[str]]
- self.link_args = [] # type: T.List[str]
+ self.opts: T.Dict[str, str] = {}
+ self.lang_args: T.Dict[str, T.List[str]] = {}
+ self.link_args: T.List[str] = []
self.install = 'preserve'
def set_opt(self, opt: str, val: str) -> None:
self.install = 'true' if install else 'false'
def get_override_options(self, initial: T.List[str]) -> T.List[str]:
- res = [] # type: T.List[str]
+ res: T.List[str] = []
for i in initial:
opt = i[:i.find('=')]
if opt not in self.opts:
class TargetOptions:
def __init__(self) -> None:
self.global_options = SingleTargetOptions()
- self.target_options = {} # type: T.Dict[str, SingleTargetOptions]
+ self.target_options: T.Dict[str, SingleTargetOptions] = {}
def __getitem__(self, tgt: str) -> SingleTargetOptions:
if tgt not in self.target_options:
from ..mesonlib import MachineChoice
from ..programs import ExternalProgram
-TYPE_result = T.Tuple[int, T.Optional[str], T.Optional[str]]
-TYPE_cache_key = T.Tuple[str, T.Tuple[str, ...], str, T.FrozenSet[T.Tuple[str, str]]]
+ TYPE_result = T.Tuple[int, T.Optional[str], T.Optional[str]]
+ TYPE_cache_key = T.Tuple[str, T.Tuple[str, ...], str, T.FrozenSet[T.Tuple[str, str]]]
class CMakeExecutor:
# The class's copy of the CMake path. Avoids having to search for it
# multiple times in the same Meson invocation.
- class_cmakebin = PerMachine(None, None) # type: PerMachine[T.Optional[ExternalProgram]]
- class_cmakevers = PerMachine(None, None) # type: PerMachine[T.Optional[str]]
- class_cmake_cache = {} # type: T.Dict[T.Any, TYPE_result]
+ class_cmakebin: PerMachine[T.Optional[ExternalProgram]] = PerMachine(None, None)
+ class_cmakevers: PerMachine[T.Optional[str]] = PerMachine(None, None)
+ class_cmake_cache: T.Dict[T.Any, TYPE_result] = {}
def __init__(self, environment: 'Environment', version: str, for_machine: MachineChoice, silent: bool = False):
self.min_version = version
self.cmakebin, self.cmakevers = self.find_cmake_binary(self.environment, silent=silent)
self.always_capture_stderr = True
self.print_cmout = False
- self.prefix_paths = [] # type: T.List[str]
- self.extra_cmake_args = [] # type: T.List[str]
+ self.prefix_paths: T.List[str] = []
+ self.extra_cmake_args: T.List[str] = []
if self.cmakebin is None:
return
mlog.log(f'Did not find CMake {cmakebin.name!r}')
return None
try:
- p, out = Popen_safe(cmakebin.get_command() + ['--version'])[0:2]
+ cmd = cmakebin.get_command()
+ p, out = Popen_safe(cmd + ['--version'])[0:2]
if p.returncode != 0:
mlog.warning('Found CMake {!r} but couldn\'t run it'
- ''.format(' '.join(cmakebin.get_command())))
+ ''.format(' '.join(cmd)))
return None
except FileNotFoundError:
mlog.warning('We thought we found CMake {!r} but now it\'s not there. How odd!'
- ''.format(' '.join(cmakebin.get_command())))
+ ''.format(' '.join(cmd)))
return None
except PermissionError:
- msg = 'Found CMake {!r} but didn\'t have permissions to run it.'.format(' '.join(cmakebin.get_command()))
+ msg = 'Found CMake {!r} but didn\'t have permissions to run it.'.format(' '.join(cmd))
if not is_windows():
msg += '\n\nOn Unix-like systems this is often caused by scripts that are not executable.'
mlog.warning(msg)
return None
- cmvers = re.search(r'(cmake|cmake3)\s*version\s*([\d.]+)', out).group(2)
- return cmvers
+
+ cmvers = re.search(r'(cmake|cmake3)\s*version\s*([\d.]+)', out)
+ if cmvers is not None:
+ return cmvers.group(2)
+ mlog.warning(f'We thought we found CMake {cmd!r}, but it was missing the expected '
+ 'version string in its output.')
+ return None
def set_exec_mode(self, print_cmout: T.Optional[bool] = None, always_capture_stderr: T.Optional[bool] = None) -> None:
if print_cmout is not None:
# 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
from .common import CMakeException, CMakeBuildFile, CMakeConfiguration
import typing as T
self.api_base_dir = self.build_dir / '.cmake' / 'api' / 'v1'
self.request_dir = self.api_base_dir / 'query' / 'client-meson'
self.reply_dir = self.api_base_dir / 'reply'
- self.cmake_sources = [] # type: T.List[CMakeBuildFile]
- self.cmake_configurations = [] # type: T.List[CMakeConfiguration]
+ self.cmake_sources: T.List[CMakeBuildFile] = []
+ self.cmake_configurations: T.List[CMakeConfiguration] = []
self.kind_resolver_map = {
'codemodel': self._parse_codemodel,
'cmakeFiles': self._parse_cmakeFiles,
# 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
from .. import mesonlib
from .. import mlog
if '$<' not in raw:
return raw
- out = '' # type: str
- i = 0 # type: int
+ out = ''
+ i = 0
def equal(arg: str) -> str:
col_pos = arg.find(',')
return ';'.join([x for x in tgt.properties['IMPORTED_LOCATION'] if x])
return ''
- supported = {
+ supported: T.Dict[str, T.Callable[[str], str]] = {
# Boolean functions
- 'BOOL': lambda x: '0' if x.upper() in {'0', 'FALSE', 'OFF', 'N', 'NO', 'IGNORE', 'NOTFOUND'} or x.endswith('-NOTFOUND') else '1',
+ 'BOOL': lambda x: '0' if x.upper() in {'', '0', 'FALSE', 'OFF', 'N', 'NO', 'IGNORE', 'NOTFOUND'} or x.endswith('-NOTFOUND') else '1',
'AND': lambda x: '1' if all(y == '1' for y in x.split(',')) else '0',
'OR': lambda x: '1' if any(y == '1' for y in x.split(',')) else '0',
'NOT': lambda x: '0' if x == '1' else '1',
'TARGET_NAME_IF_EXISTS': lambda x: x if x in trace.targets else '',
'TARGET_PROPERTY': target_property,
'TARGET_FILE': target_file,
- } # type: T.Dict[str, T.Callable[[str], str]]
+ }
# Recursively evaluate generator expressions
def eval_generator_expressions() -> str:
nonlocal i
i += 2
- func = '' # type: str
- args = '' # type: str
- res = '' # type: str
- exp = '' # type: str
+ func = ''
+ args = ''
+ res = ''
+ exp = ''
# Determine the body of the expression
while i < len(raw):
IndexNode,
MethodNode,
NumberNode,
+ SymbolNode,
)
from .common import CMakeConfiguration, TargetOptions
from .traceparser import CMakeGeneratorTarget
from .._typing import ImmutableListProtocol
- from ..build import Build
from ..backend.backends import Backend
from ..environment import Environment
-TYPE_mixed = T.Union[str, int, bool, Path, BaseNode]
-TYPE_mixed_list = T.Union[TYPE_mixed, T.Sequence[TYPE_mixed]]
-TYPE_mixed_kwargs = T.Dict[str, TYPE_mixed_list]
+ TYPE_mixed = T.Union[str, int, bool, Path, BaseNode]
+ TYPE_mixed_list = T.Union[TYPE_mixed, T.Sequence[TYPE_mixed]]
+ TYPE_mixed_kwargs = T.Dict[str, TYPE_mixed_list]
# Disable all warnings automatically enabled with --trace and friends
# See https://cmake.org/cmake/help/latest/variable/CMAKE_POLICY_WARNING_CMPNNNN.html
mlog.log(' -- depends: ', mlog.bold(str(self.depends)))
class CMakeInterpreter:
- def __init__(self, build: 'Build', subdir: Path, src_dir: Path, install_prefix: Path, env: 'Environment', backend: 'Backend'):
- self.build = build
+ def __init__(self, subdir: Path, install_prefix: Path, env: 'Environment', backend: 'Backend'):
self.subdir = subdir
- self.src_dir = src_dir
+ 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
def token(tid: str = 'string', val: TYPE_mixed = '') -> Token:
return Token(tid, self.subdir.as_posix(), 0, 0, 0, None, val)
+ def symbol(val: str) -> SymbolNode:
+ return SymbolNode(token('', val))
+
def string(value: str) -> StringNode:
- return StringNode(token(val=value))
+ return StringNode(token(val=value), escape=False)
def id_node(value: str) -> IdNode:
return IdNode(token(val=value))
def number(value: int) -> NumberNode:
- return NumberNode(token(val=value))
+ return NumberNode(token(val=str(value)))
def nodeify(value: TYPE_mixed_list) -> BaseNode:
if isinstance(value, str):
raise RuntimeError('invalid type of value: {} ({})'.format(type(value).__name__, str(value)))
def indexed(node: BaseNode, index: int) -> IndexNode:
- return IndexNode(node, nodeify(index))
+ return IndexNode(node, symbol('['), nodeify(index), symbol(']'))
def array(elements: TYPE_mixed_list) -> ArrayNode:
args = ArgumentNode(token())
if not isinstance(elements, list):
elements = [args]
args.arguments += [nodeify(x) for x in elements if x is not None]
- return ArrayNode(args, 0, 0, 0, 0)
+ return ArrayNode(symbol('['), args, symbol(']'))
def function(name: str, args: T.Optional[TYPE_mixed_list] = None, kwargs: T.Optional[TYPE_mixed_kwargs] = None) -> FunctionNode:
args = [] if args is None else args
args = [args]
args_n.arguments = [nodeify(x) for x in args if x is not None]
args_n.kwargs = {id_node(k): nodeify(v) for k, v in kwargs.items() if v is not None}
- func_n = FunctionNode(self.subdir.as_posix(), 0, 0, 0, 0, name, args_n)
+ func_n = FunctionNode(id_node(name), symbol('('), args_n, symbol(')'))
return func_n
def method(obj: BaseNode, name: str, args: T.Optional[TYPE_mixed_list] = None, kwargs: T.Optional[TYPE_mixed_kwargs] = None) -> MethodNode:
args = [args]
args_n.arguments = [nodeify(x) for x in args if x is not None]
args_n.kwargs = {id_node(k): nodeify(v) for k, v in kwargs.items() if v is not None}
- return MethodNode(self.subdir.as_posix(), 0, 0, obj, name, args_n)
+ return MethodNode(obj, symbol('.'), id_node(name), symbol('('), args_n, symbol(')'))
def assign(var_name: str, value: BaseNode) -> AssignmentNode:
- return AssignmentNode(self.subdir.as_posix(), 0, 0, var_name, value)
+ return AssignmentNode(id_node(var_name), symbol('='), value)
# Generate the root code block and the project function call
root_cb = CodeBlockNode(token())
return res
def get_defaults(self) -> T.Dict[str, T.List[str]]:
- defaults = {} # type: T.Dict[str, T.List[str]]
+ defaults: T.Dict[str, T.List[str]] = {}
# Do nothing if the user does not want automatic defaults
if not self.properties.get_cmake_defaults():
# Best effort to map the meson system name to CMAKE_SYSTEM_NAME, which
# is not trivial since CMake lacks a list of all supported
# CMAKE_SYSTEM_NAME values.
- SYSTEM_MAP = {
+ SYSTEM_MAP: T.Dict[str, str] = {
'android': 'Android',
'linux': 'Linux',
'windows': 'Windows',
'freebsd': 'FreeBSD',
'darwin': 'Darwin',
- } # type: T.Dict[str, str]
+ }
# Only set these in a cross build. Otherwise CMake will trip up in native
# builds and thing they are cross (which causes TRY_RUN() to break)
# This class contains the basic functionality needed to run any interpreter
# or an interpreter-based tool.
+from __future__ import annotations
from .common import CMakeException
from .generator import parse_generator_expressions
self.properties = properties
self.imported = imported
self.tline = tline
- self.depends = [] # type: T.List[str]
- self.current_bin_dir = None # type: T.Optional[Path]
- self.current_src_dir = None # type: T.Optional[Path]
+ self.depends: T.List[str] = []
+ self.current_bin_dir: T.Optional[Path] = None
+ self.current_src_dir: T.Optional[Path] = None
def __repr__(self) -> str:
s = 'CMake TARGET:\n -- name: {}\n -- type: {}\n -- imported: {}\n -- properties: {{\n{} }}\n -- tline: {}'
class CMakeGeneratorTarget(CMakeTarget):
def __init__(self, name: str) -> None:
super().__init__(name, 'CUSTOM', {})
- self.outputs = [] # type: T.List[Path]
- self._outputs_str = [] # type: T.List[str]
- self.command = [] # type: T.List[T.List[str]]
- self.working_dir = None # type: T.Optional[Path]
+ self.outputs: T.List[Path] = []
+ self._outputs_str: T.List[str] = []
+ self.command: T.List[T.List[str]] = []
+ self.working_dir: T.Optional[Path] = None
class CMakeTraceParser:
def __init__(self, cmake_version: str, build_dir: Path, env: 'Environment', permissive: bool = True) -> None:
self.targets: T.Dict[str, CMakeTarget] = {}
self.cache: T.Dict[str, CMakeCacheEntry] = {}
- self.explicit_headers = set() # type: T.Set[Path]
+ self.explicit_headers: T.Set[Path] = set()
# T.List of targes that were added with add_custom_command to generate files
- self.custom_targets = [] # type: T.List[CMakeGeneratorTarget]
+ self.custom_targets: T.List[CMakeGeneratorTarget] = []
self.env = env
- self.permissive = permissive # type: bool
- self.cmake_version = cmake_version # type: str
+ self.permissive = permissive
+ self.cmake_version = cmake_version
self.trace_file = 'cmake_trace.txt'
self.trace_file_path = build_dir / self.trace_file
self.trace_format = 'json-v1' if version_compare(cmake_version, '>=3.17') else 'human'
# State for delayed command execution. Delayed command execution is realised
# with a custom CMake file that overrides some functions and adds some
# introspection information to the trace.
- self.delayed_commands = [] # type: T.List[str]
- self.stored_commands = [] # type: T.List[CMakeTraceLine]
+ self.delayed_commands: T.List[str] = []
+ self.stored_commands: T.List[CMakeTraceLine] = []
# All supported functions
- self.functions = {
+ self.functions: T.Dict[str, T.Callable[[CMakeTraceLine], None]] = {
'set': self._cmake_set,
'unset': self._cmake_unset,
'add_executable': self._cmake_add_executable,
'meson_ps_execute_delayed_calls': self._meson_ps_execute_delayed_calls,
'meson_ps_reload_vars': self._meson_ps_reload_vars,
'meson_ps_disabled_function': self._meson_ps_disabled_function,
- } # type: T.Dict[str, T.Callable[[CMakeTraceLine], None]]
+ }
if version_compare(self.cmake_version, '<3.17.0'):
mlog.deprecation(textwrap.dedent(f'''\
p: {k: strlist_gen(v) for k, v in d.items()}
for p, d in self.vars_by_file.items()
}
- self.explicit_headers = set(Path(parse_generator_expressions(str(x), self)) for x in self.explicit_headers)
+ self.explicit_headers = {Path(parse_generator_expressions(str(x), self)) for x in self.explicit_headers}
self.cache = {
k: CMakeCacheEntry(
strlist_gen(v.value),
raise CMakeException(f'CMake: {function}() {error}\n{tline}')
def _cmake_set(self, tline: CMakeTraceLine) -> None:
- """Handler for the CMake set() function in all variaties.
+ """Handler for the CMake set() function in all varieties.
comes in three flavors:
set(<var> <value> [PARENT_SCOPE])
targets += curr.split(';')
if not args:
- return self._gen_exception('set_property', 'faild to parse argument list', tline)
+ return self._gen_exception('set_property', 'failed to parse argument list', tline)
if len(args) == 1:
# Tries to set property to nothing so nothing has to be done
targets.append(curr)
- # Now we need to try to reconsitute the original quoted format of the
+ # Now we need to try to reconstitute the original quoted format of the
# arguments, as a property value could have spaces in it. Unlike
# set_property() this is not context free. There are two approaches I
# can think of, both have drawbacks:
#
# Neither of these is awesome for obvious reasons. I'm going to try
# option 1 first and fall back to 2, as 1 requires less code and less
- # synchroniztion for cmake changes.
+ # synchronization for cmake changes.
#
# With the JSON output format, introduced in CMake 3.17, spaces are
# handled properly and we don't have to do either options
- arglist = [] # type: T.List[T.Tuple[str, T.List[str]]]
+ arglist: T.List[T.Tuple[str, T.List[str]]] = []
if self.trace_format == 'human':
name = args.pop(0)
- values = [] # type: T.List[str]
+ values: T.List[str] = []
prop_regex = re.compile(r'^[A-Z_]+$')
for a in args:
if prop_regex.match(a):
def _flatten_args(self, args: T.List[str]) -> T.List[str]:
# Split lists in arguments
- res = [] # type: T.List[str]
+ res: T.List[str] = []
for i in args:
res += i.split(';')
return res
reg_start = re.compile(r'^([A-Za-z]:)?/(.*/)*[^./]+$')
reg_end = re.compile(r'^.*\.[a-zA-Z]+$')
- fixed_list = [] # type: T.List[str]
- curr_str = None # type: T.Optional[str]
- path_found = False # type: bool
+ fixed_list: T.List[str] = []
+ curr_str: T.Optional[str] = None
+ path_found = False
for i in broken_list:
if curr_str is None:
# SPDX-License-Identifer: Apache-2.0
# Copyright 2021 The Meson development team
+from __future__ import annotations
from .common import cmake_is_debug
from .. import mlog
res.libraries += [curr]
elif Path(curr).is_absolute() and Path(curr).exists():
res.libraries += [curr]
- elif env.machines.build.is_windows() and reg_is_maybe_bare_lib.match(curr) and clib_compiler:
- # On Windows, CMake library dependencies can be passed as bare library names,
+ elif reg_is_maybe_bare_lib.match(curr) and clib_compiler:
+ # CMake library dependencies can be passed as bare library names,
# 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.
+from __future__ import annotations
+
import os
import typing as T
-from ..mesonlib import EnvironmentException, get_meson_command
+from ..mesonlib import EnvironmentException, OptionKey, get_meson_command
from .compilers import Compiler
+from .mixins.metrowerks import MetrowerksCompiler, mwasmarm_instruction_set_args, mwasmeppc_instruction_set_args
if T.TYPE_CHECKING:
from ..environment import Environment
+ from ..linkers.linkers import DynamicLinker
+ from ..mesonlib import MachineChoice
+ from ..envconfig import MachineInfo
-nasm_optimization_args = {
+nasm_optimization_args: T.Dict[str, T.List[str]] = {
'plain': [],
'0': ['-O0'],
'g': ['-O0'],
'2': ['-Ox'],
'3': ['-Ox'],
's': ['-Ox'],
-} # type: T.Dict[str, T.List[str]]
+}
class NasmCompiler(Compiler):
language = 'nasm'
id = 'nasm'
+ # https://learn.microsoft.com/en-us/cpp/c-runtime-library/crt-library-features
+ crt_args: T.Dict[str, T.List[str]] = {
+ 'none': [],
+ 'md': ['/DEFAULTLIB:ucrt.lib', '/DEFAULTLIB:vcruntime.lib', '/DEFAULTLIB:msvcrt.lib'],
+ 'mdd': ['/DEFAULTLIB:ucrtd.lib', '/DEFAULTLIB:vcruntimed.lib', '/DEFAULTLIB:msvcrtd.lib'],
+ 'mt': ['/DEFAULTLIB:libucrt.lib', '/DEFAULTLIB:libvcruntime.lib', '/DEFAULTLIB:libcmt.lib'],
+ 'mtd': ['/DEFAULTLIB:libucrtd.lib', '/DEFAULTLIB:libvcruntimed.lib', '/DEFAULTLIB:libcmtd.lib'],
+ }
+
+ def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str,
+ for_machine: 'MachineChoice', info: 'MachineInfo',
+ 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)
+ if 'link' in self.linker.id:
+ self.base_options.add(OptionKey('b_vscrt'))
+
def needs_static_linker(self) -> bool:
return True
def get_output_args(self, outputname: str) -> T.List[str]:
return ['-o', outputname]
+ def unix_args_to_native(self, args: T.List[str]) -> T.List[str]:
+ outargs: T.List[str] = []
+ for arg in args:
+ if arg == '-pthread':
+ continue
+ outargs.append(arg)
+ return outargs
+
def get_optimization_args(self, optimization_level: str) -> T.List[str]:
return nasm_optimization_args[optimization_level]
return 'd'
def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]:
- return ['-MD', '-MQ', outtarget, '-MF', outfile]
+ 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'}:
def get_crt_compile_args(self, crt_val: str, buildtype: str) -> T.List[str]:
return []
+ # Linking ASM-only objects into an executable or DLL
+ # 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():
+ return []
+ return self.crt_args[self.get_crt_val(crt_val, buildtype)]
+
class YasmCompiler(NasmCompiler):
id = 'yasm'
+ def get_optimization_args(self, optimization_level: str) -> T.List[str]:
+ # Yasm is incompatible with Nasm optimization flags.
+ return []
+
def get_exelist(self, ccache: bool = True) -> T.List[str]:
# Wrap yasm executable with an internal script that will write depfile.
exelist = super().get_exelist(ccache)
def depfile_for_object(self, objfile: str) -> T.Optional[str]:
return None
+
+
+class MetrowerksAsmCompiler(MetrowerksCompiler, Compiler):
+ language = 'nasm'
+
+ def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str,
+ for_machine: 'MachineChoice', info: 'MachineInfo',
+ 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)
+ MetrowerksCompiler.__init__(self)
+
+ self.warn_args: T.Dict[str, T.List[str]] = {
+ '0': [],
+ '1': [],
+ '2': [],
+ '3': [],
+ 'everything': []}
+ self.can_compile_suffixes.add('s')
+
+ def get_crt_compile_args(self, crt_val: str, buildtype: str) -> T.List[str]:
+ return []
+
+ def get_optimization_args(self, optimization_level: str) -> T.List[str]:
+ return []
+
+ def get_pic_args(self) -> T.List[str]:
+ return []
+
+ def needs_static_linker(self) -> bool:
+ return True
+
+
+class MetrowerksAsmCompilerARM(MetrowerksAsmCompiler):
+ id = 'mwasmarm'
+
+ 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'
+
+ 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')
from .mixins.elbrus import ElbrusCompiler
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 .compilers import (
gnu_winlibs,
msvc_winlibs,
from ..dependencies import Dependency
from ..envconfig import MachineInfo
from ..environment import Environment
- from ..linkers import DynamicLinker
+ from ..linkers.linkers import DynamicLinker
from ..mesonlib import MachineChoice
from ..programs import ExternalProgram
from .compilers import CompileCheckMode
else:
CompilerMixinBase = object
+_ALL_STDS = ['c89', 'c9x', 'c90', 'c99', 'c1x', 'c11', 'c17', 'c18', 'c2x']
+_ALL_STDS += [f'gnu{std[1:]}' for std in _ALL_STDS]
+_ALL_STDS += ['iso9899:1990', 'iso9899:199409', 'iso9899:1999', 'iso9899:2011', 'iso9899:2017', 'iso9899:2018']
+
class CCompiler(CLikeCompiler, Compiler):
def attribute_check_func(self, name: str) -> str:
def get_options(self) -> 'MutableKeyedOptionDictType':
opts = super().get_options()
+ key = OptionKey('std', machine=self.for_machine, lang=self.language)
opts.update({
- OptionKey('std', machine=self.for_machine, lang=self.language): coredata.UserComboOption(
- 'C language standard to use',
- ['none'],
- 'none',
- )
+ key: coredata.UserStdOption('C', _ALL_STDS),
})
return opts
def get_options(self) -> 'MutableKeyedOptionDictType':
opts = super().get_options()
- c_stds = ['c89', 'c99', 'c11']
- g_stds = ['gnu89', 'gnu99', 'gnu11']
+ stds = ['c89', 'c99', 'c11']
# https://releases.llvm.org/6.0.0/tools/clang/docs/ReleaseNotes.html
# https://en.wikipedia.org/wiki/Xcode#Latest_versions
if version_compare(self.version, self._C17_VERSION):
- c_stds += ['c17']
- g_stds += ['gnu17']
+ stds += ['c17']
if version_compare(self.version, self._C18_VERSION):
- c_stds += ['c18']
- g_stds += ['gnu18']
+ stds += ['c18']
if version_compare(self.version, self._C2X_VERSION):
- c_stds += ['c2x']
- g_stds += ['gnu2x']
- opts[OptionKey('std', machine=self.for_machine, lang=self.language)].choices = ['none'] + c_stds + g_stds
+ stds += ['c2x']
+ std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)]
+ assert isinstance(std_opt, coredata.UserStdOption), 'for mypy'
+ std_opt.set_versions(stds, gnu=True)
return opts
full_version: T.Optional[str] = None):
if not is_cross:
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, exe_wrapper=exe_wrapper, linker=linker,
defines=defines, full_version=full_version)
def get_options(self) -> 'MutableKeyedOptionDictType':
opts = CCompiler.get_options(self)
- key = OptionKey('std', machine=self.for_machine, lang=self.language)
- opts[key].choices = ['none', 'c90', 'c99', 'c11', 'gnu90', 'gnu99', 'gnu11']
+ std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)]
+ assert isinstance(std_opt, coredata.UserStdOption), 'for mypy'
+ std_opt.set_versions(['c90', 'c99', 'c11'], gnu=True)
return opts
def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
def get_options(self) -> 'MutableKeyedOptionDictType':
opts = CCompiler.get_options(self)
- c_stds = ['c89', 'c99', 'c11']
- g_stds = ['gnu89', 'gnu99', 'gnu11']
+ stds = ['c89', 'c99', 'c11']
if version_compare(self.version, self._C18_VERSION):
- c_stds += ['c17', 'c18']
- g_stds += ['gnu17', 'gnu18']
+ stds += ['c17', 'c18']
if version_compare(self.version, self._C2X_VERSION):
- c_stds += ['c2x']
- g_stds += ['gnu2x']
+ stds += ['c2x']
key = OptionKey('std', machine=self.for_machine, lang=self.language)
- opts[key].choices = ['none'] + c_stds + g_stds
+ std_opt = opts[key]
+ assert isinstance(std_opt, coredata.UserStdOption), 'for mypy'
+ std_opt.set_versions(stds, gnu=True)
if self.info.is_windows() or self.info.is_cygwin():
opts.update({
key.evolve('winlibs'): coredata.UserArrayOption(
stds += ['c90', 'c1x', 'gnu90', 'gnu1x', 'iso9899:2011']
if version_compare(self.version, '>=1.26.00'):
stds += ['c17', 'c18', 'iso9899:2017', 'iso9899:2018', 'gnu17', 'gnu18']
- opts[OptionKey('std', machine=self.for_machine, lang=self.language)].choices = ['none'] + stds
+ std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)]
+ assert isinstance(std_opt, coredata.UserStdOption), 'for mypy'
+ std_opt.set_versions(stds)
return opts
# Elbrus C compiler does not have lchmod, but there is only linker warning, not compiler error.
def get_options(self) -> 'MutableKeyedOptionDictType':
opts = CCompiler.get_options(self)
- c_stds = ['c89', 'c99']
- g_stds = ['gnu89', 'gnu99']
+ stds = ['c89', 'c99']
if version_compare(self.version, '>=16.0.0'):
- c_stds += ['c11']
- opts[OptionKey('std', machine=self.for_machine, lang=self.language)].choices = ['none'] + c_stds + g_stds
+ stds += ['c11']
+ std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)]
+ assert isinstance(std_opt, coredata.UserStdOption), 'for mypy'
+ std_opt.set_versions(stds, gnu=True)
return opts
def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
def get_options(self) -> 'MutableKeyedOptionDictType':
opts = super().get_options()
- c_stds = ['c89', 'c99']
- # Need to have these to be compatible with projects
- # that set c_std to e.g. gnu99.
- # https://github.com/mesonbuild/meson/issues/7611
- g_stds = ['gnu89', 'gnu90', 'gnu9x', 'gnu99']
+ stds = ['c89', 'c99']
if version_compare(self.version, self._C11_VERSION):
- c_stds += ['c11']
- g_stds += ['gnu1x', 'gnu11']
+ stds += ['c11']
if version_compare(self.version, self._C17_VERSION):
- c_stds += ['c17', 'c18']
- g_stds += ['gnu17', 'gnu18']
- key = OptionKey('std', machine=self.for_machine, lang=self.language)
- opts[key].choices = ['none'] + c_stds + g_stds
+ stds += ['c17', 'c18']
+ std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)]
+ assert isinstance(std_opt, coredata.UserStdOption), 'for mypy'
+ std_opt.set_versions(stds, gnu=True, gnu_deprecated=True)
return opts
def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
args = []
std = options[OptionKey('std', machine=self.for_machine, lang=self.language)]
- if std.value.startswith('gnu'):
- mlog.log_once(
- 'cl.exe does not actually support gnu standards, and meson '
- 'will instead demote to the nearest ISO C standard. This '
- 'may cause compilation to fail.')
# As of MVSC 16.8, /std:c11 and /std:c17 are the only valid C standard options.
- if std.value in {'c11', 'gnu1x', 'gnu11'}:
+ if std.value in {'c11'}:
args.append('/std:c11')
- elif std.value in {'c17', 'c18', 'gnu17', 'gnu18'}:
+ elif std.value in {'c17', 'c18'}:
args.append('/std:c17')
return args
def get_options(self) -> 'MutableKeyedOptionDictType':
opts = super().get_options()
- key = OptionKey('std', machine=self.for_machine, lang=self.language)
- opts[key].choices = ['none', 'c89', 'c99', 'c11']
+ std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)]
+ assert isinstance(std_opt, coredata.UserStdOption), 'for mypy'
+ std_opt.set_versions(['c89', 'c99', 'c11'])
return opts
def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
key = OptionKey('std', machine=self.for_machine, lang=self.language)
std = options[key]
if std.value == 'c89':
- mlog.log_once("ICL doesn't explicitly implement c89, setting the standard to 'none', which is close.")
+ mlog.log("ICL doesn't explicitly implement c89, setting the standard to 'none', which is close.", once=True)
elif std.value != 'none':
args.append('/Qstd:' + std.value)
return args
def get_options(self) -> 'MutableKeyedOptionDictType':
opts = CCompiler.get_options(self)
- key = OptionKey('std', machine=self.for_machine, lang=self.language)
- opts[key].choices = ['none', 'c89', 'c99', 'c11']
+ std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)]
+ assert isinstance(std_opt, coredata.UserStdOption), 'for mypy'
+ std_opt.set_versions(['c89', 'c99', 'c11'])
return opts
def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
def get_options(self) -> 'MutableKeyedOptionDictType':
opts = CCompiler.get_options(self)
- key = OptionKey('std', machine=self.for_machine, lang=self.language)
- opts[key].choices = ['none', 'c89', 'c99']
+ std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)]
+ assert isinstance(std_opt, coredata.UserStdOption), 'for mypy'
+ std_opt.set_versions(['c89', 'c99'])
return opts
def get_no_stdinc_args(self) -> T.List[str]:
def get_options(self) -> 'MutableKeyedOptionDictType':
opts = CCompiler.get_options(self)
- key = OptionKey('std', machine=self.for_machine, lang=self.language)
- opts[key].choices = ['none', 'c89', 'c99', 'gnu89', 'gnu99']
+ std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)]
+ assert isinstance(std_opt, coredata.UserStdOption), 'for mypy'
+ std_opt.set_versions(['c89', 'c99'], gnu=True)
return opts
def get_no_stdinc_args(self) -> T.List[str]:
def get_options(self) -> 'MutableKeyedOptionDictType':
opts = CCompiler.get_options(self)
- key = OptionKey('std', machine=self.for_machine, lang=self.language)
- opts[key].choices = ['none', 'c89', 'c99']
+ std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)]
+ assert isinstance(std_opt, coredata.UserStdOption), 'for mypy'
+ std_opt.set_versions(['c89', 'c99'])
return opts
def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
def get_options(self) -> 'MutableKeyedOptionDictType':
opts = CCompiler.get_options(self)
- key = OptionKey('std', machine=self.for_machine, lang=self.language)
- opts[key].choices = ['none', 'c89', 'c99', 'c11']
+ std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)]
+ assert isinstance(std_opt, coredata.UserStdOption), 'for mypy'
+ std_opt.set_versions(['c89', 'c99', 'c11'])
return opts
def get_no_stdinc_args(self) -> T.List[str]:
class C2000CCompiler(TICCompiler):
# Required for backwards compat with projects created before ti-cgt support existed
id = 'c2000'
+
+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',
+ exe_wrapper: T.Optional['ExternalProgram'] = None,
+ linker: T.Optional['DynamicLinker'] = None,
+ full_version: T.Optional[str] = None):
+ CCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross,
+ info, exe_wrapper, linker=linker, full_version=full_version)
+ MetrowerksCompiler.__init__(self)
+
+ def get_instruction_set_args(self, instruction_set: str) -> T.Optional[T.List[str]]:
+ return mwccarm_instruction_set_args.get(instruction_set, None)
+
+ def get_options(self) -> 'MutableKeyedOptionDictType':
+ opts = CCompiler.get_options(self)
+ c_stds = ['c99']
+ opts[OptionKey('std', machine=self.for_machine, lang=self.language)].choices = ['none'] + c_stds
+ return opts
+
+ def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
+ args = []
+ std = options[OptionKey('std', machine=self.for_machine, lang=self.language)]
+ if std.value != 'none':
+ args.append('-lang')
+ args.append(std.value)
+ return args
+
+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',
+ exe_wrapper: T.Optional['ExternalProgram'] = None,
+ linker: T.Optional['DynamicLinker'] = None,
+ full_version: T.Optional[str] = None):
+ CCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross,
+ info, exe_wrapper, linker=linker, full_version=full_version)
+ MetrowerksCompiler.__init__(self)
+
+ def get_instruction_set_args(self, instruction_set: str) -> T.Optional[T.List[str]]:
+ return mwcceppc_instruction_set_args.get(instruction_set, None)
+
+ def get_options(self) -> 'MutableKeyedOptionDictType':
+ opts = CCompiler.get_options(self)
+ c_stds = ['c99']
+ opts[OptionKey('std', machine=self.for_machine, lang=self.language)].choices = ['none'] + c_stds
+ return opts
+
+ def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
+ args = []
+ std = options[OptionKey('std', machine=self.for_machine, lang=self.language)]
+ if std.value != 'none':
+ args.append('-lang ' + std.value)
+ return args
'int foo(void) __attribute__((unused));',
'used':
'int foo(void) __attribute__((used));',
+ 'vector_size':
+ '__attribute__((vector_size(32))); int foo(void) { return 0; }',
'visibility': '''
- int foo_def(void) __attribute__((visibility("default")));
- int foo_hid(void) __attribute__((visibility("hidden")));
- int foo_int(void) __attribute__((visibility("internal")));''',
+ int foo_def(void) __attribute__((visibility("default"))); int foo_def(void) { return 0; }
+ int foo_hid(void) __attribute__((visibility("hidden"))); int foo_hid(void) { return 0; }
+ int foo_int(void) __attribute__((visibility("internal"))); int foo_int(void) { return 0; }''',
'visibility:default':
- 'int foo(void) __attribute__((visibility("default")));',
+ 'int foo(void) __attribute__((visibility("default"))); int foo(void) { return 0; }',
'visibility:hidden':
- 'int foo(void) __attribute__((visibility("hidden")));',
+ 'int foo(void) __attribute__((visibility("hidden"))); int foo(void) { return 0; }',
'visibility:internal':
- 'int foo(void) __attribute__((visibility("internal")));',
+ 'int foo(void) __attribute__((visibility("internal"))); int foo(void) { return 0; }',
'visibility:protected':
- 'int foo(void) __attribute__((visibility("protected")));',
+ 'int foo(void) __attribute__((visibility("protected"))); int foo(void) { return 0; }',
'warning':
'int foo(void) __attribute__((warning("")));',
'warn_unused_result':
from ..mesonlib import (
HoldableObject,
EnvironmentException, MesonException,
- Popen_safe, LibType, TemporaryDirectoryWinProof, OptionKey,
+ Popen_safe_logged, LibType, TemporaryDirectoryWinProof, OptionKey,
)
from ..arglist import CompilerArgs
if T.TYPE_CHECKING:
- from ..build import BuildTarget
+ from ..build import BuildTarget, DFeatures
from ..coredata import MutableKeyedOptionDictType, KeyedOptionDictType
from ..envconfig import MachineInfo
from ..environment import Environment
- from ..linkers import DynamicLinker, RSPFileSyntax
+ from ..linkers import RSPFileSyntax
+ from ..linkers.linkers import DynamicLinker
from ..mesonlib import MachineChoice
from ..dependencies import Dependency
"""This file contains the data files of all compilers Meson knows
about. To support a new compiler, add its information below.
-Also add corresponding autodetection code in environment.py."""
+Also add corresponding autodetection code in detect.py."""
header_suffixes = {'h', 'hh', 'hpp', 'hxx', 'H', 'ipp', 'moc', 'vapi', 'di'}
obj_suffixes = {'o', 'obj', 'res'}
c_cpp_suffixes = {'h'}
cpp_suffixes = set(lang_suffixes['cpp']) | c_cpp_suffixes
c_suffixes = set(lang_suffixes['c']) | c_cpp_suffixes
-assembler_suffixes = {'s', 'S', 'asm', 'masm'}
+assembler_suffixes = {'s', 'S', 'sx', 'asm', 'masm'}
llvm_ir_suffixes = {'ll'}
all_suffixes = set(itertools.chain(*lang_suffixes.values(), assembler_suffixes, llvm_ir_suffixes, c_cpp_suffixes))
source_suffixes = all_suffixes - header_suffixes
# List of languages that by default consume and output libraries following the
# C ABI; these can generally be used interchangeably
# This must be sorted, see sort_clink().
-clib_langs = ('objcpp', 'cpp', 'objc', 'c', 'fortran')
+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
SUFFIX_TO_LANG = dict(itertools.chain(*(
- [(suffix, lang) for suffix in v] for lang, v in lang_suffixes.items()))) # type: T.Dict[str, str]
+ [(suffix, lang) for suffix in v] for lang, v in lang_suffixes.items())))
# Languages that should use LDFLAGS arguments when linking.
-LANGUAGES_USING_LDFLAGS = {'objcpp', 'cpp', 'objc', 'c', 'fortran', 'd', 'cuda'} # type: T.Set[str]
+LANGUAGES_USING_LDFLAGS = {'objcpp', 'cpp', 'objc', 'c', 'fortran', 'd', 'cuda'}
# Languages that should use CPPFLAGS arguments when linking.
-LANGUAGES_USING_CPPFLAGS = {'c', 'cpp', 'objc', 'objcpp'} # type: T.Set[str]
+LANGUAGES_USING_CPPFLAGS = {'c', 'cpp', 'objc', 'objcpp'}
soregex = re.compile(r'.*\.so(\.[0-9]+)?(\.[0-9]+)?(\.[0-9]+)?$')
# Environment variables that each lang uses.
suffix = fname.split('.')[-1]
return suffix in header_suffixes
+def is_source_suffix(suffix: str) -> bool:
+ return suffix in source_suffixes
+
def is_source(fname: 'mesonlib.FileOrString') -> bool:
if isinstance(fname, mesonlib.File):
fname = fname.fname
suffix = fname.split('.')[-1].lower()
- return suffix in source_suffixes
+ return is_source_suffix(suffix)
def is_assembly(fname: 'mesonlib.FileOrString') -> bool:
if isinstance(fname, mesonlib.File):
LINK = 'link'
-cuda_buildtype_args = {'plain': [],
- 'debug': ['-g', '-G'],
- 'debugoptimized': ['-g', '-lineinfo'],
- 'release': [],
- 'minsize': [],
- 'custom': [],
- } # type: T.Dict[str, T.List[str]]
-java_buildtype_args = {'plain': [],
- 'debug': ['-g'],
- 'debugoptimized': ['-g'],
- 'release': [],
- 'minsize': [],
- 'custom': [],
- } # type: T.Dict[str, T.List[str]]
-
-rust_buildtype_args = {'plain': [],
- 'debug': [],
- 'debugoptimized': [],
- 'release': [],
- 'minsize': [],
- 'custom': [],
- } # type: T.Dict[str, T.List[str]]
-
-d_gdc_buildtype_args = {'plain': [],
- 'debug': [],
- 'debugoptimized': ['-finline-functions'],
- 'release': ['-finline-functions'],
- 'minsize': [],
- 'custom': [],
- } # type: T.Dict[str, T.List[str]]
-
-d_ldc_buildtype_args = {'plain': [],
- 'debug': [],
- 'debugoptimized': ['-enable-inlining', '-Hkeep-all-bodies'],
- 'release': ['-enable-inlining', '-Hkeep-all-bodies'],
- 'minsize': [],
- 'custom': [],
- } # type: T.Dict[str, T.List[str]]
-
-d_dmd_buildtype_args = {'plain': [],
- 'debug': [],
- 'debugoptimized': ['-inline'],
- 'release': ['-inline'],
- 'minsize': [],
- 'custom': [],
- } # type: T.Dict[str, T.List[str]]
-
-mono_buildtype_args = {'plain': [],
- 'debug': [],
- 'debugoptimized': ['-optimize+'],
- 'release': ['-optimize+'],
- 'minsize': [],
- 'custom': [],
- } # type: T.Dict[str, T.List[str]]
-
-swift_buildtype_args = {'plain': [],
- 'debug': [],
- 'debugoptimized': [],
- 'release': [],
- 'minsize': [],
- 'custom': [],
- } # type: T.Dict[str, T.List[str]]
+cuda_buildtype_args: T.Dict[str, T.List[str]] = {
+ 'plain': [],
+ 'debug': ['-g', '-G'],
+ 'debugoptimized': ['-g', '-lineinfo'],
+ 'release': [],
+ 'minsize': [],
+ 'custom': [],
+}
+
+java_buildtype_args: T.Dict[str, T.List[str]] = {
+ 'plain': [],
+ 'debug': ['-g'],
+ 'debugoptimized': ['-g'],
+ 'release': [],
+ 'minsize': [],
+ 'custom': [],
+}
+
+rust_buildtype_args: T.Dict[str, T.List[str]] = {
+ 'plain': [],
+ 'debug': [],
+ 'debugoptimized': [],
+ 'release': [],
+ 'minsize': [],
+ 'custom': [],
+}
+
+d_gdc_buildtype_args: T.Dict[str, T.List[str]] = {
+ 'plain': [],
+ 'debug': [],
+ 'debugoptimized': ['-finline-functions'],
+ 'release': ['-finline-functions'],
+ 'minsize': [],
+ 'custom': [],
+}
+
+d_ldc_buildtype_args: T.Dict[str, T.List[str]] = {
+ 'plain': [],
+ 'debug': [],
+ 'debugoptimized': ['-enable-inlining', '-Hkeep-all-bodies'],
+ 'release': ['-enable-inlining', '-Hkeep-all-bodies'],
+ 'minsize': [],
+ 'custom': [],
+}
+
+d_dmd_buildtype_args: T.Dict[str, T.List[str]] = {
+ 'plain': [],
+ 'debug': [],
+ 'debugoptimized': ['-inline'],
+ 'release': ['-inline'],
+ 'minsize': [],
+ 'custom': [],
+}
+
+mono_buildtype_args: T.Dict[str, T.List[str]] = {
+ 'plain': [],
+ 'debug': [],
+ 'debugoptimized': ['-optimize+'],
+ 'release': ['-optimize+'],
+ 'minsize': [],
+ 'custom': [],
+}
+
+swift_buildtype_args: T.Dict[str, T.List[str]] = {
+ 'plain': [],
+ 'debug': [],
+ 'debugoptimized': [],
+ 'release': [],
+ 'minsize': [],
+ 'custom': [],
+}
gnu_winlibs = ['-lkernel32', '-luser32', '-lgdi32', '-lwinspool', '-lshell32',
- '-lole32', '-loleaut32', '-luuid', '-lcomdlg32', '-ladvapi32'] # type: T.List[str]
+ '-lole32', '-loleaut32', '-luuid', '-lcomdlg32', '-ladvapi32']
msvc_winlibs = ['kernel32.lib', 'user32.lib', 'gdi32.lib',
'winspool.lib', 'shell32.lib', 'ole32.lib', 'oleaut32.lib',
- 'uuid.lib', 'comdlg32.lib', 'advapi32.lib'] # type: T.List[str]
-
-clike_optimization_args = {'plain': [],
- '0': [],
- 'g': [],
- '1': ['-O1'],
- '2': ['-O2'],
- '3': ['-O3'],
- 's': ['-Os'],
- } # type: T.Dict[str, T.List[str]]
-
-cuda_optimization_args = {'plain': [],
- '0': [],
- 'g': ['-O0'],
- '1': ['-O1'],
- '2': ['-O2'],
- '3': ['-O3'],
- 's': ['-O3']
- } # type: T.Dict[str, T.List[str]]
-
-cuda_debug_args = {False: [],
- True: ['-g']} # type: T.Dict[bool, T.List[str]]
-
-clike_debug_args = {False: [],
- True: ['-g']} # type: T.Dict[bool, T.List[str]]
+ 'uuid.lib', 'comdlg32.lib', 'advapi32.lib']
+
+clike_optimization_args: T.Dict[str, T.List[str]] = {
+ 'plain': [],
+ '0': [],
+ 'g': [],
+ '1': ['-O1'],
+ '2': ['-O2'],
+ '3': ['-O3'],
+ 's': ['-Os'],
+}
+
+cuda_optimization_args: T.Dict[str, T.List[str]] = {
+ 'plain': [],
+ '0': [],
+ 'g': ['-O0'],
+ '1': ['-O1'],
+ '2': ['-O2'],
+ '3': ['-O3'],
+ 's': ['-O3']
+}
+
+cuda_debug_args: T.Dict[bool, T.List[str]] = {
+ False: [],
+ True: ['-g']
+}
+
+clike_debug_args: T.Dict[bool, T.List[str]] = {
+ False: [],
+ True: ['-g']
+}
+
+MSCRT_VALS = ['none', 'md', 'mdd', 'mt', 'mtd']
base_options: 'KeyedOptionDictType' = {
OptionKey('b_pch'): coredata.UserBooleanOption('Use precompiled headers', True),
OptionKey('b_pie'): coredata.UserBooleanOption('Build executables as position independent', False),
OptionKey('b_bitcode'): coredata.UserBooleanOption('Generate and embed bitcode (only macOS/iOS/tvOS)', False),
OptionKey('b_vscrt'): coredata.UserComboOption('VS run-time library type to use.',
- ['none', 'md', 'mdd', 'mt', 'mtd', 'from_buildtype', 'static_from_buildtype'],
+ MSCRT_VALS + ['from_buildtype', 'static_from_buildtype'],
'from_buildtype'),
}
return v
+def are_asserts_disabled(options: KeyedOptionDictType) -> bool:
+ """Should debug assertions be disabled
+
+ :param options: OptionDictionary
+ :return: whether to disable assertions or not
+ """
+ return (options[OptionKey('b_ndebug')].value == 'true' or
+ (options[OptionKey('b_ndebug')].value == 'if-release' and
+ options[OptionKey('buildtype')].value in {'release', 'plain'}))
+
+
def get_base_compile_args(options: 'KeyedOptionDictType', compiler: 'Compiler') -> T.List[str]:
- args = [] # type T.List[str]
+ args: T.List[str] = []
try:
if options[OptionKey('b_lto')].value:
args.extend(compiler.get_lto_compile_args(
except KeyError:
pass
try:
- if (options[OptionKey('b_ndebug')].value == 'true' or
- (options[OptionKey('b_ndebug')].value == 'if-release' and
- options[OptionKey('buildtype')].value in {'release', 'plain'})):
- args += compiler.get_disable_assert_args()
+ args += compiler.get_assert_args(are_asserts_disabled(options))
except KeyError:
pass
# This does not need a try...except
def get_base_link_args(options: 'KeyedOptionDictType', linker: 'Compiler',
is_shared_module: bool, build_dir: str) -> T.List[str]:
- args = [] # type: T.List[str]
+ args: T.List[str] = []
try:
if options[OptionKey('b_lto')].value:
thinlto_cache_dir = None
class RunResult(HoldableObject):
def __init__(self, compiled: bool, returncode: int = 999,
- stdout: str = 'UNDEFINED', stderr: str = 'UNDEFINED'):
+ stdout: str = 'UNDEFINED', stderr: str = 'UNDEFINED',
+ cached: bool = False):
self.compiled = compiled
self.returncode = returncode
self.stdout = stdout
self.stderr = stderr
+ self.cached = cached
class CompileResult(HoldableObject):
class Compiler(HoldableObject, metaclass=abc.ABCMeta):
# Libraries to ignore in find_library() since they are provided by the
# compiler or the C library. Currently only used for MSVC.
- ignore_libs = [] # type: T.List[str]
+ ignore_libs: T.List[str] = []
# Libraries that are internal compiler implementations, and must not be
# manually searched.
- internal_libs = [] # type: T.List[str]
+ internal_libs: T.List[str] = []
- LINKER_PREFIX = None # type: T.Union[None, str, T.List[str]]
+ LINKER_PREFIX: T.Union[None, str, T.List[str]] = None
INVOKES_LINKER = True
language: str
id: str
warn_args: T.Dict[str, T.List[str]]
- mode: str = 'COMPILER'
+ mode = 'COMPILER'
def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str,
for_machine: MachineChoice, info: 'MachineInfo',
if not hasattr(self, 'file_suffixes'):
self.file_suffixes = lang_suffixes[self.language]
if not hasattr(self, 'can_compile_suffixes'):
- self.can_compile_suffixes = set(self.file_suffixes)
+ self.can_compile_suffixes: T.Set[str] = set(self.file_suffixes)
self.default_suffix = self.file_suffixes[0]
self.version = version
self.full_version = full_version
return self.exelist.copy() if ccache else self.exelist_no_ccache.copy()
def get_linker_exelist(self) -> T.List[str]:
- return self.linker.get_exelist()
+ return self.linker.get_exelist() if self.linker else self.get_exelist()
@abc.abstractmethod
def get_output_args(self, outputname: str) -> T.List[str]:
dependencies: T.Optional[T.List['Dependency']] = None) -> RunResult:
raise EnvironmentException('Language %s does not support run checks.' % self.get_display_language())
+ # Caching run() in general seems too risky (no way to know what the program
+ # depends on), but some callers know more about the programs they intend to
+ # run.
+ # 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', *,
+ 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'))
+ key = (code, tuple(args))
+ if key in run_check_cache:
+ p = run_check_cache[key]
+ p.cached = True
+ mlog.debug('Using cached run result:')
+ mlog.debug('Code:\n', code)
+ mlog.debug('Args:\n', extra_args)
+ mlog.debug('Cached run returncode:\n', p.returncode)
+ 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)
+ run_check_cache[key] = p
+ return p
+
def 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:
+ 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', *,
extra_args: T.Optional[T.List[str]] = None,
- dependencies: T.Optional[T.List['Dependency']] = None) -> int:
+ 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', *,
return args.copy()
def find_library(self, libname: str, env: 'Environment', extra_dirs: T.List[str],
- libtype: LibType = LibType.PREFER_SHARED) -> T.Optional[T.List[str]]:
+ libtype: LibType = LibType.PREFER_SHARED, lib_prefix_warning: bool = True) -> 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,
def has_multi_link_arguments(self, args: T.List[str], env: 'Environment') -> T.Tuple[bool, bool]:
return self.linker.has_multi_arguments(args, env)
- def _get_compile_output(self, dirname: str, mode: str) -> str:
- # TODO: mode should really be an enum
- # In pre-processor mode, the output is sent to stdout and discarded
- if mode == 'preprocess':
- return None
+ 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'
# Extension only matters if running results; '.exe' is
# guaranteed to be executable on every platform.
- if mode == 'link':
+ if mode == CompileCheckMode.LINK:
suffix = 'exe'
else:
suffix = 'obj'
return os.path.join(dirname, 'output.' + suffix)
def get_compiler_args_for_mode(self, mode: CompileCheckMode) -> T.List[str]:
- # TODO: mode should really be an enum
- args = [] # type: T.List[str]
+ args: T.List[str] = []
args += self.get_always_args()
if mode is CompileCheckMode.COMPILE:
args += self.get_compile_only_args()
@contextlib.contextmanager
def compile(self, code: 'mesonlib.FileOrString',
extra_args: T.Union[None, CompilerArgs, T.List[str]] = None,
- *, mode: str = 'link', want_output: bool = False,
+ *, mode: CompileCheckMode = CompileCheckMode.LINK, want_output: bool = False,
temp_dir: T.Optional[str] = None) -> T.Iterator[T.Optional[CompileResult]]:
# TODO: there isn't really any reason for this to be a contextmanager
+
+ if mode == CompileCheckMode.PREPROCESS:
+ assert not want_output, 'In pre-processor mode, the output is sent to stdout and discarded'
+
if extra_args is None:
extra_args = []
commands.append(srcname)
# Preprocess mode outputs to stdout, so no output args
- output = self._get_compile_output(tmpdirname, mode)
- if mode != 'preprocess':
+ if mode != CompileCheckMode.PREPROCESS:
+ output = self._get_compile_output(tmpdirname, mode)
commands += self.get_output_args(output)
commands.extend(self.get_compiler_args_for_mode(CompileCheckMode(mode)))
command_list = self.get_exelist(ccache=not no_ccache) + commands.to_native()
mlog.debug('Running compile:')
mlog.debug('Working directory: ', tmpdirname)
- mlog.debug('Command line: ', ' '.join(command_list), '\n')
mlog.debug('Code:\n', contents)
os_env = os.environ.copy()
os_env['LC_ALL'] = 'C'
if no_ccache:
os_env['CCACHE_DISABLE'] = '1'
- p, stdo, stde = Popen_safe(command_list, cwd=tmpdirname, env=os_env)
- mlog.debug('Compiler stdout:\n', stdo)
- mlog.debug('Compiler stderr:\n', stde)
+ p, stdo, stde = Popen_safe_logged(command_list, msg='Command line', cwd=tmpdirname, env=os_env)
result = CompileResult(stdo, stde, command_list, p.returncode, input_name=srcname)
if want_output:
@contextlib.contextmanager
def cached_compile(self, code: 'mesonlib.FileOrString', cdata: coredata.CoreData, *,
extra_args: T.Union[None, T.List[str], CompilerArgs] = None,
- mode: str = 'link',
+ mode: CompileCheckMode = CompileCheckMode.LINK,
temp_dir: T.Optional[str] = None) -> T.Iterator[T.Optional[CompileResult]]:
# TODO: There's isn't really any reason for this to be a context manager
# Calculate the key
- textra_args = tuple(extra_args) if extra_args is not None else tuple() # type: T.Tuple[str, ...]
- key = (tuple(self.exelist), self.version, code, textra_args, mode) # type: coredata.CompilerCheckCacheKey
+ textra_args: T.Tuple[str, ...] = tuple(extra_args) if extra_args is not None else tuple()
+ 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] # type: CompileResult
+ p = cdata.compiler_check_cache[key]
p.cached = True
mlog.debug('Using cached compile:')
mlog.debug('Cached command line: ', ' '.join(p.command), '\n')
def get_compile_debugfile_args(self, rel_obj: str, pch: bool = False) -> T.List[str]:
return []
- def get_link_debugfile_name(self, targetfile: str) -> str:
+ def get_link_debugfile_name(self, targetfile: str) -> T.Optional[str]:
return self.linker.get_debugfile_name(targetfile)
def get_link_debugfile_args(self, targetfile: str) -> T.List[str]:
return self.linker.build_rpath_args(
env, build_dir, from_dir, rpath_paths, build_rpath, install_rpath)
+ def get_archive_name(self, filename: str) -> str:
+ return self.linker.get_archive_name(filename)
+
+ def get_command_to_archive_shlib(self) -> T.List[str]:
+ if not self.linker:
+ return []
+ return self.linker.get_command_to_archive_shlib()
+
def thread_flags(self, env: 'Environment') -> T.List[str]:
return []
def gnu_symbol_visibility_args(self, vistype: str) -> T.List[str]:
return []
- def get_gui_app_args(self, value: bool) -> T.List[str]:
- # Only used on Windows
- return self.linker.get_gui_app_args(value)
-
def get_win_subsystem_args(self, value: str) -> T.List[str]:
# By default the dynamic linker is going to return an empty
# array in case it either doesn't support Windows subsystems
rm_exact = ('-headerpad_max_install_names',)
rm_prefixes = ('-Wl,', '-L',)
rm_next = ('-L', '-framework',)
- ret = [] # T.List[str]
+ ret: T.List[str] = []
iargs = iter(args)
for arg in iargs:
# Remove this argument
def get_coverage_link_args(self) -> T.List[str]:
return self.linker.get_coverage_args()
- def get_disable_assert_args(self) -> T.List[str]:
+ def get_assert_args(self, disable: bool) -> T.List[str]:
+ """Get arguments to enable or disable assertion.
+
+ :param disable: Whether to disable assertions
+ :return: A list of string arguments for this compiler
+ """
return []
+ def get_crt_val(self, crt_val: str, buildtype: str) -> str:
+ if crt_val in MSCRT_VALS:
+ return crt_val
+ assert crt_val in {'from_buildtype', 'static_from_buildtype'}
+
+ dbg = 'mdd'
+ rel = 'md'
+ if crt_val == 'static_from_buildtype':
+ dbg = 'mtd'
+ rel = 'mt'
+
+ # Match what build type flags used to do.
+ if buildtype == 'plain':
+ return 'none'
+ elif buildtype == 'debug':
+ return dbg
+ elif buildtype in {'debugoptimized', 'release', 'minsize'}:
+ return rel
+ else:
+ assert buildtype == 'custom'
+ raise EnvironmentException('Requested C runtime based on buildtype, but buildtype is "custom".')
+
def get_crt_compile_args(self, crt_val: str, buildtype: str) -> T.List[str]:
raise EnvironmentException('This compiler does not support Windows CRT selection')
mode: CompileCheckMode = CompileCheckMode.COMPILE) -> CompilerArgs:
"""Arguments to pass the build_wrapper helper.
- This generally needs to be set on a per-language baises. It provides
+ This generally needs to be set on a per-language basis. It provides
a hook for languages to handle dependencies and extra args. The base
implementation handles the most common cases, namely adding the
check_arguments, unwrapping dependencies, and appending extra args.
def _build_wrapper(self, code: 'mesonlib.FileOrString', env: 'Environment',
extra_args: T.Union[None, CompilerArgs, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None,
dependencies: T.Optional[T.List['Dependency']] = None,
- mode: str = 'compile', want_output: bool = False,
- disable_cache: bool = False,
- temp_dir: str = None) -> T.Iterator[T.Optional[CompileResult]]:
- """Helper for getting a cacched value when possible.
+ mode: CompileCheckMode = CompileCheckMode.COMPILE, want_output: bool = False,
+ disable_cache: bool = False) -> T.Iterator[T.Optional[CompileResult]]:
+ """Helper for getting a cached value when possible.
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, CompileCheckMode(mode))
+ args = self.build_wrapper_args(env, 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:
yield r
def compiles(self, code: 'mesonlib.FileOrString', env: 'Environment', *,
extra_args: T.Union[None, T.List[str], CompilerArgs, T.Callable[[CompileCheckMode], T.List[str]]] = None,
dependencies: T.Optional[T.List['Dependency']] = None,
- mode: str = 'compile',
+ mode: CompileCheckMode = CompileCheckMode.COMPILE,
disable_cache: bool = False) -> T.Tuple[bool, bool]:
with self._build_wrapper(code, env, extra_args, dependencies, mode, disable_cache=disable_cache) as p:
return p.returncode == 0, p.cached
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,
- mode: str = 'compile',
disable_cache: bool = False) -> T.Tuple[bool, bool]:
if compiler:
with compiler._build_wrapper(code, env, 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,
- dependencies=dependencies, mode='link', disable_cache=True)
+ dependencies=dependencies, mode=CompileCheckMode.LINK, disable_cache=True)
return self.compiles(code, env, extra_args=extra_args,
- dependencies=dependencies, mode='link', disable_cache=disable_cache)
+ dependencies=dependencies, mode=CompileCheckMode.LINK, disable_cache=disable_cache)
- def get_feature_args(self, kwargs: T.Dict[str, T.Any], build_to_src: str) -> T.List[str]:
+ def get_feature_args(self, kwargs: DFeatures, build_to_src: str) -> T.List[str]:
"""Used by D for extra language features."""
# TODO: using a TypeDict here would improve this
raise EnvironmentException(f'{self.id} does not implement get_feature_args')
cargs = coredata.UserArrayOption(
description + ' compiler',
- comp_options, split_args=True, user_input=True, allow_dups=True)
+ comp_options, split_args=True, allow_dups=True)
largs = coredata.UserArrayOption(
description + ' linker',
- link_options, split_args=True, user_input=True, allow_dups=True)
+ 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 freature is for
+ # This is how autotools works, and the env vars feature is for
# autotools compatibility.
largs.extend_value(comp_options)
gnu_winlibs,
msvc_winlibs,
Compiler,
+ CompileCheckMode,
)
from .c_function_attributes import CXX_FUNC_ATTRIBUTES, C_FUNC_ATTRIBUTES
from .mixins.clike import CLikeCompiler
from .mixins.elbrus import ElbrusCompiler
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
if T.TYPE_CHECKING:
- from .compilers import CompileCheckMode
from ..coredata import MutableKeyedOptionDictType, KeyedOptionDictType
from ..dependencies import Dependency
from ..envconfig import MachineInfo
from ..environment import Environment
- from ..linkers import DynamicLinker
+ from ..linkers.linkers import DynamicLinker
from ..mesonlib import MachineChoice
from ..programs import ExternalProgram
CompilerMixinBase = CLikeCompiler
else:
CompilerMixinBase = object
+_ALL_STDS = ['c++98', 'c++0x', 'c++03', 'c++1y', 'c++1z', 'c++11', 'c++14', 'c++17', 'c++2a', 'c++20', 'c++23', 'c++26']
+_ALL_STDS += [f'gnu{std[1:]}' for std in _ALL_STDS]
+_ALL_STDS += ['vc++11', 'vc++14', 'vc++17', 'vc++20', 'vc++latest', 'c++latest']
+
def non_msvc_eh_options(eh: str, args: T.List[str]) -> None:
if eh == 'none':
args.append('-fno-exceptions')
elif eh in {'s', 'c'}:
- mlog.warning('non-MSVC compilers do not support ' + eh + ' exception handling.' +
- 'You may want to set eh to \'default\'.')
+ mlog.warning(f'non-MSVC compilers do not support {eh} exception handling. '
+ 'You may want to set eh to \'default\'.', fatal=False)
class CPPCompiler(CLikeCompiler, Compiler):
def attribute_check_func(self, name: str) -> str:
full_version=full_version)
CLikeCompiler.__init__(self, exe_wrapper)
- @staticmethod
- def get_display_language() -> str:
+ @classmethod
+ def get_display_language(cls) -> str:
return 'C++'
def get_no_stdinc_args(self) -> T.List[str]:
return ['-nostdinc++']
+ def get_no_stdlib_link_args(self) -> T.List[str]:
+ return ['-nostdlib++']
+
def sanity_check(self, work_dir: str, environment: 'Environment') -> None:
code = 'class breakCCompiler;int main(void) { return 0; }\n'
return self._sanity_check_impl(work_dir, environment, 'sanitycheckcpp.cc', code)
# 2. even if it did have an env object, that might contain another more
# recent -std= argument, which might lead to a cascaded failure.
CPP_TEST = 'int i = static_cast<int>(0);'
- with self.compile(CPP_TEST, extra_args=[cpp_std_value], mode='compile') as p:
+ with self.compile(CPP_TEST, extra_args=[cpp_std_value], mode=CompileCheckMode.COMPILE) as p:
if p.returncode == 0:
mlog.debug(f'Compiler accepts {cpp_std_value}:', 'YES')
return True
'gnu++17': 'gnu++1z',
'c++20': 'c++2a',
'gnu++20': 'gnu++2a',
+ 'c++23': 'c++2b',
+ 'gnu++23': 'gnu++2b',
+ 'c++26': 'c++2c',
+ 'gnu++26': 'gnu++2c',
}
# Currently, remapping is only supported for Clang, Elbrus and GCC
opts = super().get_options()
key = OptionKey('std', machine=self.for_machine, lang=self.language)
opts.update({
- key: coredata.UserComboOption(
- 'C++ language standard to use',
- ['none'],
- 'none',
- ),
+ key: coredata.UserStdOption('C++', _ALL_STDS),
})
return opts
-class ClangCPPCompiler(ClangCompiler, CPPCompiler):
+class _StdCPPLibMixin(CompilerMixinBase):
+
+ """Detect whether to use libc++ or libstdc++."""
+
+ @functools.lru_cache(None)
+ def language_stdlib_only_link_flags(self, env: Environment) -> 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
+
+ :param env: An Environment object
+ :raises MesonException: If a stdlib cannot be determined
+ """
+
+ # 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 = [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'
+
+ # We need to determine whether to use libc++ or libstdc++. We can't
+ # really know the answer in most cases, only the most likely answer,
+ # because a user can install things themselves or build custom images.
+ search_order: T.List[str] = []
+ if machine.system in {'android', 'darwin', 'dragonfly', 'freebsd', 'netbsd', 'openbsd'}:
+ search_order = ['c++', 'stdc++']
+ else:
+ search_order = ['stdc++', 'c++']
+ for lib in search_order:
+ if self.find_library(lib, env, []) is not None:
+ return search_dirs + [f'-l{lib}']
+ # TODO: maybe a bug exception?
+ raise MesonException('Could not detect either libc++ or libstdc++ as your C++ stdlib implementation.')
+
+
+class ClangCPPCompiler(_StdCPPLibMixin, ClangCompiler, CPPCompiler):
+
+ _CPP23_VERSION = '>=12.0.0'
+ _CPP26_VERSION = '>=17.0.0'
+
def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool,
info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None,
linker: T.Optional['DynamicLinker'] = None,
),
key.evolve('rtti'): coredata.UserBooleanOption('Enable RTTI', True),
})
- opts[key.evolve('std')].choices = [
- 'none', 'c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'c++1z',
- 'c++2a', 'c++20', 'gnu++11', 'gnu++14', 'gnu++17', 'gnu++1z',
- 'gnu++2a', 'gnu++20',
+ cppstd_choices = [
+ 'c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'c++1z', 'c++2a', 'c++20',
]
+ if version_compare(self.version, self._CPP23_VERSION):
+ cppstd_choices.append('c++23')
+ if version_compare(self.version, self._CPP26_VERSION):
+ cppstd_choices.append('c++26')
+ std_opt = opts[key.evolve('std')]
+ assert isinstance(std_opt, coredata.UserStdOption), 'for mypy'
+ std_opt.set_versions(cppstd_choices, gnu=True)
if self.info.is_windows() or self.info.is_cygwin():
opts.update({
key.evolve('winlibs'): coredata.UserArrayOption(
return opts
def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
- args = []
+ args: T.List[str] = []
key = OptionKey('std', machine=self.for_machine, lang=self.language)
std = options[key]
if std.value != 'none':
return libs
return []
- def language_stdlib_only_link_flags(self, env: 'Environment') -> 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'):
- search_dirs.append(f'-L{d}')
- return search_dirs + ['-lstdc++']
-
class ArmLtdClangCPPCompiler(ClangCPPCompiler):
class AppleClangCPPCompiler(ClangCPPCompiler):
- def language_stdlib_only_link_flags(self, env: 'Environment') -> 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'):
- search_dirs.append(f'-L{d}')
- return search_dirs + ['-lc++']
+
+ _CPP23_VERSION = '>=13.0.0'
+ # TODO: We don't know which XCode version will include LLVM 17 yet, so
+ # use something absurd.
+ _CPP26_VERSION = '>=99.0.0'
class EmscriptenCPPCompiler(EmscriptenMixin, ClangCPPCompiler):
full_version: T.Optional[str] = None):
if not is_cross:
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, exe_wrapper=exe_wrapper, linker=linker,
defines=defines, full_version=full_version)
def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
- args = []
+ args: T.List[str] = []
key = OptionKey('std', machine=self.for_machine, lang=self.language)
std = options[key]
if std.value != 'none':
'default',
),
})
- opts[key].choices = [
- 'none', 'c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'gnu++98',
- 'gnu++03', 'gnu++11', 'gnu++14', 'gnu++17',
- ]
+ std_opt = opts[key]
+ assert isinstance(std_opt, coredata.UserStdOption), 'for mypy'
+ std_opt.set_versions(['c++98', 'c++03', 'c++11', 'c++14', 'c++17'], gnu=True)
return opts
def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
- args = []
+ args: T.List[str] = []
key = OptionKey('std', machine=self.for_machine, lang=self.language)
std = options[key]
if std.value != 'none':
return []
-class GnuCPPCompiler(GnuCompiler, CPPCompiler):
+class GnuCPPCompiler(_StdCPPLibMixin, GnuCompiler, CPPCompiler):
def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool,
info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None,
linker: T.Optional['DynamicLinker'] = None,
False,
)
})
- opts[key].choices = [
- 'none', 'c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'c++1z',
- 'c++2a', 'c++20', 'gnu++03', 'gnu++11', 'gnu++14', 'gnu++17',
- 'gnu++1z', 'gnu++2a', 'gnu++20',
+ cppstd_choices = [
+ 'c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'c++1z',
+ 'c++2a', 'c++20',
]
+ if version_compare(self.version, '>=11.0.0'):
+ cppstd_choices.append('c++23')
+ if version_compare(self.version, '>=14.0.0'):
+ cppstd_choices.append('c++26')
+ std_opt = opts[key]
+ assert isinstance(std_opt, coredata.UserStdOption), 'for mypy'
+ std_opt.set_versions(cppstd_choices, gnu=True)
if self.info.is_windows() or self.info.is_cygwin():
opts.update({
key.evolve('winlibs'): coredata.UserArrayOption(
return opts
def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
- args = []
+ args: T.List[str] = []
key = OptionKey('std', machine=self.for_machine, lang=self.language)
std = options[key]
if std.value != 'none':
def get_pch_use_args(self, pch_dir: str, header: str) -> T.List[str]:
return ['-fpch-preprocess', '-include', os.path.basename(header)]
- def language_stdlib_only_link_flags(self, env: 'Environment') -> 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'):
- search_dirs.append(f'-L{d}')
- return ['-lstdc++']
-
class PGICPPCompiler(PGICompiler, CPPCompiler):
def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool,
def get_options(self) -> 'MutableKeyedOptionDictType':
opts = CPPCompiler.get_options(self)
- cpp_stds = ['none', 'c++98', 'gnu++98']
+ cpp_stds = ['c++98']
if version_compare(self.version, '>=1.20.00'):
- cpp_stds += ['c++03', 'c++0x', 'c++11', 'gnu++03', 'gnu++0x', 'gnu++11']
+ cpp_stds += ['c++03', 'c++0x', 'c++11']
if version_compare(self.version, '>=1.21.00') and version_compare(self.version, '<1.22.00'):
- cpp_stds += ['c++14', 'gnu++14', 'c++1y', 'gnu++1y']
+ cpp_stds += ['c++14', 'c++1y']
if version_compare(self.version, '>=1.22.00'):
- cpp_stds += ['c++14', 'gnu++14']
+ cpp_stds += ['c++14']
if version_compare(self.version, '>=1.23.00'):
- cpp_stds += ['c++1y', 'gnu++1y']
+ cpp_stds += ['c++1y']
if version_compare(self.version, '>=1.24.00'):
- cpp_stds += ['c++1z', 'c++17', 'gnu++1z', 'gnu++17']
+ cpp_stds += ['c++1z', 'c++17']
if version_compare(self.version, '>=1.25.00'):
- cpp_stds += ['c++2a', 'gnu++2a']
+ cpp_stds += ['c++2a']
if version_compare(self.version, '>=1.26.00'):
- cpp_stds += ['c++20', 'gnu++20']
+ cpp_stds += ['c++20']
key = OptionKey('std', machine=self.for_machine, lang=self.language)
opts.update({
False,
),
})
- opts[key].choices = cpp_stds
+ std_opt = opts[key]
+ assert isinstance(std_opt, coredata.UserStdOption), 'for mypy'
+ std_opt.set_versions(cpp_stds, gnu=True)
return opts
# Elbrus C++ compiler does not have lchmod, but there is only linker warning, not compiler error.
# Elbrus C++ compiler does not support RTTI, so don't check for it.
def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
- args = []
+ args: T.List[str] = []
key = OptionKey('std', machine=self.for_machine, lang=self.language)
std = options[key]
if std.value != 'none':
key.evolve('rtti'): coredata.UserBooleanOption('Enable RTTI', True),
key.evolve('debugstl'): coredata.UserBooleanOption('STL debug mode', False),
})
- opts[key].choices = ['none'] + c_stds + g_stds
+ std_opt = opts[key]
+ assert isinstance(std_opt, coredata.UserStdOption), 'for mypy'
+ std_opt.set_versions(c_stds + g_stds)
return opts
def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
- args = []
+ args: T.List[str] = []
key = OptionKey('std', machine=self.for_machine, lang=self.language)
std = options[key]
if std.value != 'none':
msvc_winlibs,
),
})
- opts[key.evolve('std')].choices = cpp_stds
+ std_opt = opts[key]
+ assert isinstance(std_opt, coredata.UserStdOption), 'for mypy'
+ std_opt.set_versions(cpp_stds)
return opts
def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
- args = []
+ args: T.List[str] = []
key = OptionKey('std', machine=self.for_machine, lang=self.language)
eh = options[key.evolve('eh')]
key = OptionKey('std', machine=self.for_machine, lang=self.language)
if options[key].value 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)
+ 'attempting best effort; setting the standard to C++14',
+ once=True, fatal=False)
# Don't mutate anything we're going to change, we need to use
# deepcopy since we're messing with members, and we can't simply
# copy the members because the option proxy doesn't support it.
info, exe_wrapper, linker=linker, full_version=full_version)
MSVCCompiler.__init__(self, target)
+ # By default, MSVC has a broken __cplusplus define that pretends to be c++98:
+ # https://docs.microsoft.com/en-us/cpp/build/reference/zc-cplusplus?view=msvc-160
+ # Pass the flag to enable a truthful define, if possible.
+ if version_compare(self.version, '>= 19.14.26428'):
+ self.always_args = self.always_args + ['/Zc:__cplusplus']
+
def get_options(self) -> 'MutableKeyedOptionDictType':
cpp_stds = ['none', 'c++11', 'vc++11']
# Visual Studio 2015 and later
def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
key = OptionKey('std', machine=self.for_machine, lang=self.language)
if options[key].value != 'none' and version_compare(self.version, '<19.00.24210'):
- mlog.warning('This version of MSVC does not support cpp_std arguments')
+ mlog.warning('This version of MSVC does not support cpp_std arguments', fatal=False)
options = copy.copy(options)
options[key].value = 'none'
del args[i]
return args
- def get_always_args(self) -> T.List[str]:
- args = super().get_always_args()
-
- # By default, MSVC has a broken __cplusplus define that pretends to be c++98:
- # https://docs.microsoft.com/en-us/cpp/build/reference/zc-cplusplus?view=msvc-160
- # Pass the flag to enable a truthful define, if possible.
- if version_compare(self.version, '>= 19.14.26428') and '/Zc:__cplusplus' not in args:
- return args + ['/Zc:__cplusplus']
- return args
-
class ClangClCPPCompiler(CPP11AsCPP14Mixin, VisualStudioLikeCPPCompilerMixin, ClangClCompiler, CPPCompiler):
id = 'clang-cl'
ClangClCompiler.__init__(self, target)
def get_options(self) -> 'MutableKeyedOptionDictType':
- cpp_stds = ['none', 'c++11', 'vc++11', 'c++14', 'vc++14', 'c++17', 'vc++17', 'c++latest']
+ cpp_stds = ['none', 'c++11', 'vc++11', 'c++14', 'vc++14', 'c++17', 'vc++17', 'c++20', 'vc++20', 'c++latest']
return self._get_options_impl(super().get_options(), cpp_stds)
def get_options(self) -> 'MutableKeyedOptionDictType':
opts = CPPCompiler.get_options(self)
- key = OptionKey('std', machine=self.for_machine, lang=self.language)
- opts[key].choices = ['none', 'c++03', 'c++11']
+ std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)]
+ assert isinstance(std_opt, coredata.UserStdOption), 'for mypy'
+ std_opt.set_versions(['c++03', 'c++11'])
return opts
def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
- args = []
+ args: T.List[str] = []
key = OptionKey('std', machine=self.for_machine, lang=self.language)
std = options[key]
if std.value == 'c++11':
def get_compile_only_args(self) -> T.List[str]:
return []
- def get_output_args(self, target: str) -> T.List[str]:
- return [f'-output=obj={target}']
+ def get_output_args(self, outputname: str) -> T.List[str]:
+ return [f'-output=obj={outputname}']
def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
return []
def get_options(self) -> 'MutableKeyedOptionDictType':
opts = CPPCompiler.get_options(self)
- key = OptionKey('std', machine=self.for_machine, lang=self.language)
- opts[key].choices = ['none', 'c++03']
+ std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)]
+ assert isinstance(std_opt, coredata.UserStdOption), 'for mypy'
+ std_opt.set_versions(['c++03'])
return opts
def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
- args = []
+ args: T.List[str] = []
key = OptionKey('std', machine=self.for_machine, lang=self.language)
std = options[key]
if std.value != 'none':
class C2000CPPCompiler(TICPPCompiler):
# Required for backwards compat with projects created before ti-cgt support existed
id = 'c2000'
+
+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',
+ exe_wrapper: T.Optional['ExternalProgram'] = None,
+ linker: T.Optional['DynamicLinker'] = None,
+ full_version: T.Optional[str] = None):
+ CPPCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross,
+ info, exe_wrapper, linker=linker, full_version=full_version)
+ MetrowerksCompiler.__init__(self)
+
+ def get_instruction_set_args(self, instruction_set: str) -> T.Optional[T.List[str]]:
+ return mwccarm_instruction_set_args.get(instruction_set, None)
+
+ def get_options(self) -> 'MutableKeyedOptionDictType':
+ opts = CPPCompiler.get_options(self)
+ key = OptionKey('std', machine=self.for_machine, lang=self.language)
+ opts[key].choices = ['none']
+ return opts
+
+ def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
+ args: T.List[str] = []
+ std = options[OptionKey('std', machine=self.for_machine, lang=self.language)]
+ if std.value != 'none':
+ args.append('-lang')
+ args.append(std.value)
+ return args
+
+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',
+ exe_wrapper: T.Optional['ExternalProgram'] = None,
+ linker: T.Optional['DynamicLinker'] = None,
+ full_version: T.Optional[str] = None):
+ CPPCompiler.__init__(self, ccache, exelist, version, for_machine, is_cross,
+ info, exe_wrapper, linker=linker, full_version=full_version)
+ MetrowerksCompiler.__init__(self)
+
+ def get_instruction_set_args(self, instruction_set: str) -> T.Optional[T.List[str]]:
+ return mwcceppc_instruction_set_args.get(instruction_set, None)
+
+ def get_options(self) -> 'MutableKeyedOptionDictType':
+ opts = CPPCompiler.get_options(self)
+ key = OptionKey('std', machine=self.for_machine, lang=self.language)
+ opts[key].choices = ['none']
+ return opts
+
+ def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
+ args: T.List[str] = []
+ std = options[OptionKey('std', machine=self.for_machine, lang=self.language)]
+ if std.value != 'none':
+ args.append('-lang ' + std.value)
+ return args
from ..environment import Environment
from ..mesonlib import MachineChoice
-cs_optimization_args = {
+cs_optimization_args: T.Dict[str, T.List[str]] = {
'plain': [],
'0': [],
'g': [],
'2': ['-optimize+'],
'3': ['-optimize+'],
's': ['-optimize+'],
- } # type: T.Dict[str, T.List[str]]
+ }
class CsCompiler(BasicLinkerIsCompilerMixin, Compiler):
pc = subprocess.Popen(self.exelist + self.get_always_args() + [src], cwd=work_dir)
pc.wait()
if pc.returncode != 0:
- raise EnvironmentException('C# compiler %s can not compile programs.' % self.name_string())
+ raise EnvironmentException('C# compiler %s cannot compile programs.' % self.name_string())
if self.runner:
cmdlist = [self.runner, obj]
else:
from .. import coredata
from .. import mlog
from ..mesonlib import (
- EnvironmentException, Popen_safe, OptionOverrideProxy,
- is_windows, LibType, OptionKey,
+ EnvironmentException, Popen_safe,
+ is_windows, LibType, OptionKey, version_compare,
)
from .compilers import (Compiler, cuda_buildtype_args, cuda_optimization_args,
cuda_debug_args)
from ..dependencies import Dependency
from ..environment import Environment # noqa: F401
from ..envconfig import MachineInfo
- from ..linkers import DynamicLinker
+ from ..linkers.linkers import DynamicLinker
from ..mesonlib import MachineChoice
from ..programs import ExternalProgram
mlog.debug(stde)
mlog.debug('-----')
if pc.returncode != 0:
- raise EnvironmentException(f'Compiler {self.name_string()} can not compile programs.')
+ raise EnvironmentException(f'Compiler {self.name_string()} cannot compile programs.')
# Run sanity check (if possible)
if self.is_cross:
}}'''
return self.compiles(t.format_map(fargs), env, extra_args=extra_args, dependencies=dependencies)
+ _CPP14_VERSION = '>=9.0'
+ _CPP17_VERSION = '>=11.0'
+ _CPP20_VERSION = '>=12.0'
+
def get_options(self) -> 'MutableKeyedOptionDictType':
opts = super().get_options()
std_key = OptionKey('std', machine=self.for_machine, lang=self.language)
ccbindir_key = OptionKey('ccbindir', machine=self.for_machine, lang=self.language)
+
+ cpp_stds = ['none', 'c++03', 'c++11']
+ if version_compare(self.version, self._CPP14_VERSION):
+ cpp_stds += ['c++14']
+ if version_compare(self.version, self._CPP17_VERSION):
+ cpp_stds += ['c++17']
+ if version_compare(self.version, self._CPP20_VERSION):
+ cpp_stds += ['c++20']
+
opts.update({
std_key: coredata.UserComboOption('C++ language standard to use with CUDA',
- ['none', 'c++03', 'c++11', 'c++14', 'c++17'], 'none'),
+ cpp_stds, 'none'),
ccbindir_key: coredata.UserStringOption('CUDA non-default toolchain directory to use (-ccbin)',
''),
})
host_options = {key: options.get(key, opt) for key, opt in self.host_compiler.get_options().items()}
std_key = OptionKey('std', machine=self.for_machine, lang=self.host_compiler.language)
overrides = {std_key: 'none'}
- return OptionOverrideProxy(overrides, host_options)
+ return coredata.OptionsView(host_options, overrides=overrides)
def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
args = self.get_ccbin_args(options)
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) -> T.Optional[T.List[str]]:
+ libtype: LibType = LibType.PREFER_SHARED, lib_prefix_warning: bool = True) -> T.Optional[T.List[str]]:
return ['-l' + libname] # FIXME
def get_crt_compile_args(self, crt_val: str, buildtype: str) -> T.List[str]:
def get_profile_use_args(self) -> T.List[str]:
return ['-Xcompiler=' + x for x in self.host_compiler.get_profile_use_args()]
- def get_disable_assert_args(self) -> T.List[str]:
- return self.host_compiler.get_disable_assert_args()
+ def get_assert_args(self, disable: bool) -> T.List[str]:
+ return self.host_compiler.get_assert_args(disable)
# SPDX-License-Identifier: Apache-2.0
# Copyright © 2021 Intel Corporation
+from __future__ import annotations
"""Abstraction for Cython language compilers."""
import typing as T
from .. import coredata
-from ..mesonlib import EnvironmentException, OptionKey
+from ..mesonlib import EnvironmentException, OptionKey, version_compare
from .compilers import Compiler
if T.TYPE_CHECKING:
# compiler might though
return []
+ def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]:
+ if version_compare(self.version, '>=0.29.33'):
+ return ['-M']
+ return []
+
+ def get_depfile_suffix(self) -> str:
+ return 'dep'
+
def sanity_check(self, work_dir: str, environment: 'Environment') -> None:
code = 'print("hello world")'
with self.cached_compile(code, environment.coredata) as p:
from .mixins.gnu import gnu_common_warning_args
if T.TYPE_CHECKING:
+ from ..build import DFeatures
from ..dependencies import Dependency
from ..programs import ExternalProgram
from ..envconfig import MachineInfo
from ..environment import Environment
- from ..linkers import DynamicLinker
+ from ..linkers.linkers import DynamicLinker
from ..mesonlib import MachineChoice
CompilerMixinBase = Compiler
else:
CompilerMixinBase = object
-d_feature_args = {'gcc': {'unittest': '-funittest',
- 'debug': '-fdebug',
- 'version': '-fversion',
- 'import_dir': '-J'
- },
- 'llvm': {'unittest': '-unittest',
- 'debug': '-d-debug',
- 'version': '-d-version',
- 'import_dir': '-J'
- },
- 'dmd': {'unittest': '-unittest',
- 'debug': '-debug',
- 'version': '-version',
- 'import_dir': '-J'
- }
- } # type: T.Dict[str, T.Dict[str, str]]
-
-ldc_optimization_args = {'plain': [],
- '0': [],
- 'g': [],
- '1': ['-O1'],
- '2': ['-O2'],
- '3': ['-O3'],
- 's': ['-Oz'],
- } # type: T.Dict[str, T.List[str]]
-
-dmd_optimization_args = {'plain': [],
- '0': [],
- 'g': [],
- '1': ['-O'],
- '2': ['-O'],
- '3': ['-O'],
- 's': ['-O'],
- } # type: T.Dict[str, T.List[str]]
+d_feature_args: T.Dict[str, T.Dict[str, str]] = {
+ 'gcc': {
+ 'unittest': '-funittest',
+ 'debug': '-fdebug',
+ 'version': '-fversion',
+ 'import_dir': '-J'
+ },
+ 'llvm': {
+ 'unittest': '-unittest',
+ 'debug': '-d-debug',
+ 'version': '-d-version',
+ 'import_dir': '-J'
+ },
+ 'dmd': {
+ 'unittest': '-unittest',
+ 'debug': '-debug',
+ 'version': '-version',
+ 'import_dir': '-J'
+ }
+}
+
+ldc_optimization_args: T.Dict[str, T.List[str]] = {
+ 'plain': [],
+ '0': [],
+ 'g': [],
+ '1': ['-O1'],
+ '2': ['-O2'],
+ '3': ['-O3'],
+ 's': ['-Oz'],
+}
+
+dmd_optimization_args: T.Dict[str, T.List[str]] = {
+ 'plain': [],
+ '0': [],
+ 'g': [],
+ '1': ['-O'],
+ '2': ['-O'],
+ '3': ['-O'],
+ 's': ['-O'],
+}
class DmdLikeCompilerMixin(CompilerMixinBase):
self._dmd_has_depfile = version_compare(dmd_frontend_version, ">=2.095.0")
if T.TYPE_CHECKING:
- mscrt_args = {} # type: T.Dict[str, T.List[str]]
+ mscrt_args: T.Dict[str, T.List[str]] = {}
def _get_target_arch_args(self) -> T.List[str]: ...
return []
return ['-fPIC']
- def get_feature_args(self, kwargs: T.Dict[str, T.Any], build_to_src: str) -> T.List[str]:
- # TODO: using a TypeDict here would improve this
- res = []
- # get_feature_args can be called multiple times for the same target when there is generated source
- # so we have to copy the kwargs (target.d_features) dict before popping from it
- kwargs = kwargs.copy()
- if 'unittest' in kwargs:
- unittest = kwargs.pop('unittest')
- unittest_arg = d_feature_args[self.id]['unittest']
- if not unittest_arg:
- raise EnvironmentException('D compiler %s does not support the "unittest" feature.' % self.name_string())
- if unittest:
- res.append(unittest_arg)
-
- if 'debug' in kwargs:
- debug_level = -1
- debugs = kwargs.pop('debug')
- if not isinstance(debugs, list):
- debugs = [debugs]
-
- debug_arg = d_feature_args[self.id]['debug']
- if not debug_arg:
- raise EnvironmentException('D compiler %s does not support conditional debug identifiers.' % self.name_string())
-
- # Parse all debug identifiers and the largest debug level identifier
- for d in debugs:
- if isinstance(d, int):
- if d > debug_level:
- debug_level = d
- elif isinstance(d, str) and d.isdigit():
- if int(d) > debug_level:
- debug_level = int(d)
- else:
- res.append(f'{debug_arg}={d}')
-
- if debug_level >= 0:
- res.append(f'{debug_arg}={debug_level}')
-
- if 'versions' in kwargs:
- version_level = -1
- versions = kwargs.pop('versions')
- if not isinstance(versions, list):
- versions = [versions]
-
- version_arg = d_feature_args[self.id]['version']
- if not version_arg:
- raise EnvironmentException('D compiler %s does not support conditional version identifiers.' % self.name_string())
-
- # Parse all version identifiers and the largest version level identifier
- for v in versions:
- if isinstance(v, int):
- if v > version_level:
- version_level = v
- elif isinstance(v, str) and v.isdigit():
- if int(v) > version_level:
- version_level = int(v)
- else:
- res.append(f'{version_arg}={v}')
-
- if version_level >= 0:
- res.append(f'{version_arg}={version_level}')
-
- if 'import_dirs' in kwargs:
- import_dirs = kwargs.pop('import_dirs')
- if not isinstance(import_dirs, list):
- import_dirs = [import_dirs]
-
- import_dir_arg = d_feature_args[self.id]['import_dir']
- if not import_dir_arg:
- raise EnvironmentException('D compiler %s does not support the "string import directories" feature.' % self.name_string())
- for idir_obj in import_dirs:
- basedir = idir_obj.get_curdir()
- for idir in idir_obj.get_incdirs():
- bldtreedir = os.path.join(basedir, idir)
- # Avoid superfluous '/.' at the end of paths when d is '.'
- if idir not in ('', '.'):
- expdir = bldtreedir
- else:
- expdir = basedir
- srctreedir = os.path.join(build_to_src, expdir)
- res.append(f'{import_dir_arg}{srctreedir}')
- res.append(f'{import_dir_arg}{bldtreedir}')
-
- if kwargs:
- raise EnvironmentException('Unknown D compiler feature(s) selected: %s' % ', '.join(kwargs.keys()))
-
- return res
-
def get_buildtype_linker_args(self, buildtype: str) -> T.List[str]:
if buildtype != 'plain':
return self._get_target_arch_args()
# The way that dmd and ldc pass rpath to gcc is different than we would
# do directly, each argument -rpath and the value to rpath, need to be
# split into two separate arguments both prefaced with the -L=.
- args = []
+ 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)
for r in rpath_args:
# can understand.
# The flags might have been added by pkg-config files,
# and are therefore out of the user's control.
- dcargs = []
+ dcargs: T.List[str] = []
# whether we hit a linker argument that expect another arg
# see the comment in the "-L" section
link_expect_arg = False
]
for arg in args:
# Translate OS specific arguments first.
- osargs = [] # type: T.List[str]
+ osargs: T.List[str] = []
if info.is_windows():
osargs = cls.translate_arg_to_windows(arg)
elif info.is_darwin():
@classmethod
def translate_arg_to_windows(cls, arg: str) -> T.List[str]:
- args = []
+ args: T.List[str] = []
if arg.startswith('-Wl,'):
# Translate linker arguments here.
linkargs = arg[arg.index(',') + 1:].split(',')
@classmethod
def _translate_arg_to_osx(cls, arg: str) -> T.List[str]:
- args = []
+ args: T.List[str] = []
if arg.startswith('-install_name'):
args.append('-L=' + arg)
return args
def _get_crt_args(self, crt_val: str, buildtype: str) -> T.List[str]:
if not self.info.is_windows():
return []
-
- if crt_val in self.mscrt_args:
- return self.mscrt_args[crt_val]
- assert crt_val in {'from_buildtype', 'static_from_buildtype'}
-
- dbg = 'mdd'
- rel = 'md'
- if crt_val == 'static_from_buildtype':
- dbg = 'mtd'
- rel = 'mt'
-
- # Match what build type flags used to do.
- if buildtype == 'plain':
- return []
- elif buildtype == 'debug':
- return self.mscrt_args[dbg]
- elif buildtype == 'debugoptimized':
- return self.mscrt_args[rel]
- elif buildtype == 'release':
- return self.mscrt_args[rel]
- elif buildtype == 'minsize':
- return self.mscrt_args[rel]
- else:
- assert buildtype == 'custom'
- raise EnvironmentException('Requested C runtime based on buildtype, but buildtype is "custom".')
+ 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,
# LDC and DMD actually do use a linker, but they proxy all of that with
# their own arguments
+ soargs: T.List[str] = []
if self.linker.id.startswith('ld.'):
- soargs = []
for arg in sargs:
a, b = arg.split(',', maxsplit=1)
soargs.append(a)
soargs.append(self.LINKER_PREFIX + b)
return soargs
elif self.linker.id.startswith('ld64'):
- soargs = []
for arg in sargs:
if not arg.startswith(self.LINKER_PREFIX):
soargs.append(self.LINKER_PREFIX + arg)
pc = subprocess.Popen(self.exelist + self.get_output_args(output_name) + self._get_target_arch_args() + [source_name], cwd=work_dir)
pc.wait()
if pc.returncode != 0:
- raise EnvironmentException('D compiler %s can not compile programs.' % self.name_string())
+ raise EnvironmentException('D compiler %s cannot compile programs.' % self.name_string())
if self.is_cross:
if self.exe_wrapper is None:
# Can't check if the binaries run so we have to assume they do
return []
return ['-fPIC']
- def get_feature_args(self, kwargs: T.Dict[str, T.Any], build_to_src: str) -> T.List[str]:
- # TODO: using a TypeDict here would improve this
- res = []
- # get_feature_args can be called multiple times for the same target when there is generated source
- # so we have to copy the kwargs (target.d_features) dict before popping from it
- kwargs = kwargs.copy()
- if 'unittest' in kwargs:
- unittest = kwargs.pop('unittest')
- unittest_arg = d_feature_args[self.id]['unittest']
- if not unittest_arg:
- raise EnvironmentException('D compiler %s does not support the "unittest" feature.' % self.name_string())
- if unittest:
- res.append(unittest_arg)
-
- if 'debug' in kwargs:
- debug_level = -1
- debugs = kwargs.pop('debug')
- if not isinstance(debugs, list):
- debugs = [debugs]
-
- debug_arg = d_feature_args[self.id]['debug']
- if not debug_arg:
- raise EnvironmentException('D compiler %s does not support conditional debug identifiers.' % self.name_string())
-
- # Parse all debug identifiers and the largest debug level identifier
- for d in debugs:
- if isinstance(d, int):
- if d > debug_level:
- debug_level = d
- elif isinstance(d, str) and d.isdigit():
- if int(d) > debug_level:
- debug_level = int(d)
- else:
- res.append(f'{debug_arg}={d}')
-
- if debug_level >= 0:
- res.append(f'{debug_arg}={debug_level}')
-
- if 'versions' in kwargs:
- version_level = -1
- versions = kwargs.pop('versions')
- if not isinstance(versions, list):
- versions = [versions]
-
- version_arg = d_feature_args[self.id]['version']
- if not version_arg:
- raise EnvironmentException('D compiler %s does not support conditional version identifiers.' % self.name_string())
-
- # Parse all version identifiers and the largest version level identifier
- for v in versions:
- if isinstance(v, int):
- if v > version_level:
- version_level = v
- elif isinstance(v, str) and v.isdigit():
- if int(v) > version_level:
- version_level = int(v)
+ def get_feature_args(self, kwargs: DFeatures, build_to_src: str) -> T.List[str]:
+ res: T.List[str] = []
+ unittest_arg = d_feature_args[self.id]['unittest']
+ if not unittest_arg:
+ raise EnvironmentException('D compiler %s does not support the "unittest" feature.' % self.name_string())
+ if kwargs['unittest']:
+ res.append(unittest_arg)
+
+ debug_level = -1
+ debug_arg = d_feature_args[self.id]['debug']
+ if not debug_arg:
+ raise EnvironmentException('D compiler %s does not support conditional debug identifiers.' % self.name_string())
+
+ # Parse all debug identifiers and the largest debug level identifier
+ for d in kwargs['debug']:
+ if isinstance(d, int):
+ if d > debug_level:
+ debug_level = d
+ elif isinstance(d, str) and d.isdigit():
+ if int(d) > debug_level:
+ debug_level = int(d)
+ else:
+ res.append(f'{debug_arg}={d}')
+
+ if debug_level >= 0:
+ res.append(f'{debug_arg}={debug_level}')
+
+ version_level = -1
+ version_arg = d_feature_args[self.id]['version']
+ if not version_arg:
+ raise EnvironmentException('D compiler %s does not support conditional version identifiers.' % self.name_string())
+
+ # Parse all version identifiers and the largest version level identifier
+ for v in kwargs['versions']:
+ if isinstance(v, int):
+ if v > version_level:
+ version_level = v
+ elif isinstance(v, str) and v.isdigit():
+ if int(v) > version_level:
+ version_level = int(v)
+ else:
+ res.append(f'{version_arg}={v}')
+
+ if version_level >= 0:
+ res.append(f'{version_arg}={version_level}')
+
+ import_dir_arg = d_feature_args[self.id]['import_dir']
+ if not import_dir_arg:
+ raise EnvironmentException('D compiler %s does not support the "string import directories" feature.' % self.name_string())
+ # TODO: ImportDirs.to_string_list(), but we need both the project source
+ # root and project build root for that.
+ for idir_obj in kwargs['import_dirs']:
+ basedir = idir_obj.get_curdir()
+ for idir in idir_obj.get_incdirs():
+ bldtreedir = os.path.join(basedir, idir)
+ # Avoid superfluous '/.' at the end of paths when d is '.'
+ if idir not in ('', '.'):
+ expdir = bldtreedir
else:
- res.append(f'{version_arg}={v}')
-
- if version_level >= 0:
- res.append(f'{version_arg}={version_level}')
-
- if 'import_dirs' in kwargs:
- import_dirs = kwargs.pop('import_dirs')
- if not isinstance(import_dirs, list):
- import_dirs = [import_dirs]
-
- import_dir_arg = d_feature_args[self.id]['import_dir']
- if not import_dir_arg:
- raise EnvironmentException('D compiler %s does not support the "string import directories" feature.' % self.name_string())
- for idir_obj in import_dirs:
- basedir = idir_obj.get_curdir()
- for idir in idir_obj.get_incdirs():
- bldtreedir = os.path.join(basedir, idir)
- # Avoid superfluous '/.' at the end of paths when d is '.'
- if idir not in ('', '.'):
- expdir = bldtreedir
- else:
- expdir = basedir
- srctreedir = os.path.join(build_to_src, expdir)
- res.append(f'{import_dir_arg}{srctreedir}')
- res.append(f'{import_dir_arg}{bldtreedir}')
-
- if kwargs:
- raise EnvironmentException('Unknown D compiler feature(s) selected: %s' % ', '.join(kwargs.keys()))
+ expdir = basedir
+ srctreedir = os.path.join(build_to_src, expdir)
+ res.append(f'{import_dir_arg}{srctreedir}')
+ res.append(f'{import_dir_arg}{bldtreedir}')
return res
if need_exe_wrapper and self.exe_wrapper is None:
raise compilers.CrossNoRunException('Can not run test applications in this cross environment.')
extra_args = self._get_compile_extra_args(extra_args)
- with self._build_wrapper(code, env, extra_args, dependencies, mode='link', want_output=True) as p:
+ with self._build_wrapper(code, env, 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 compilers.RunResult(False)
def 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:
+ dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[int, bool]:
if extra_args is None:
extra_args = []
t = f'''
writeln(({typename}).sizeof);
}}
'''
- res = self.run(t, env, extra_args=extra_args,
- dependencies=dependencies)
+ res = self.cached_run(t, env, extra_args=extra_args,
+ dependencies=dependencies)
if not res.compiled:
- return -1
+ return -1, False
if res.returncode != 0:
raise mesonlib.EnvironmentException('Could not run sizeof test binary.')
- return int(res.stdout)
+ return int(res.stdout), res.cached
def alignment(self, typename: str, prefix: str, env: 'Environment', *,
extra_args: T.Optional[T.List[str]] = None,
- dependencies: T.Optional[T.List['Dependency']] = None) -> int:
+ dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[int, bool]:
if extra_args is None:
extra_args = []
t = f'''
align = int(res.stdout)
if align == 0:
raise mesonlib.EnvironmentException(f'Could not determine alignment of {typename}. Sorry. You might want to file a bug.')
- return align
+ return align, res.cached
def has_header(self, hname: str, prefix: str, env: 'Environment', *,
extra_args: T.Union[None, T.List[str], T.Callable[['CompileCheckMode'], T.List[str]]] = None,
import {hname};
'''
return self.compiles(code, env, extra_args=extra_args,
- dependencies=dependencies, mode='compile', disable_cache=disable_cache)
+ dependencies=dependencies, mode=CompileCheckMode.COMPILE, disable_cache=disable_cache)
class GnuDCompiler(GnuCompiler, DCompiler):
return args
return args + ['-shared-libphobos']
- def get_disable_assert_args(self) -> T.List[str]:
- return ['-frelease']
+ def get_assert_args(self, disable: bool) -> T.List[str]:
+ if disable:
+ return ['-frelease']
+ return []
# LDC uses the DMD frontend code to parse and analyse the code.
# It then uses LLVM for the binary code generation and optimizations.
return args
return args + ['-link-defaultlib-shared']
- def get_disable_assert_args(self) -> T.List[str]:
- return ['--release']
+ def get_assert_args(self, disable: bool) -> T.List[str]:
+ if disable:
+ return ['--release']
+ return []
def rsp_file_syntax(self) -> RSPFileSyntax:
# We use `mesonlib.is_windows` here because we want to know what the
return args
return args + ['-defaultlib=phobos2', '-debuglib=phobos2']
- def get_disable_assert_args(self) -> T.List[str]:
- return ['-release']
+ def get_assert_args(self, disable: bool) -> T.List[str]:
+ if disable:
+ return ['-release']
+ return []
def rsp_file_syntax(self) -> RSPFileSyntax:
return RSPFileSyntax.MSVC
from ..mesonlib import (
MesonException, EnvironmentException, MachineChoice, join_args,
- search_version, is_windows, Popen_safe, windows_proof_rm,
+ search_version, is_windows, Popen_safe, Popen_safe_logged, windows_proof_rm,
)
from ..envconfig import BinaryTable
from .. import mlog
from .cpp import CPPCompiler
from .fortran import FortranCompiler
from .rust import RustCompiler
- from ..linkers import StaticLinker
+ from ..linkers.linkers import StaticLinker, DynamicLinker
from ..environment import Environment
from ..programs import ExternalProgram
}
return lang_map[lang](env, for_machine) if lang in lang_map else None
-def detect_compiler_for(env: 'Environment', lang: str, for_machine: MachineChoice) -> T.Optional[Compiler]:
+def detect_compiler_for(env: 'Environment', lang: str, for_machine: MachineChoice, skip_sanity_check: bool) -> T.Optional[Compiler]:
comp = compiler_from_language(env, lang, for_machine)
- if comp is not None:
- assert comp.for_machine == for_machine
- env.coredata.process_new_compiler(lang, comp, env)
+ if comp is None:
+ return comp
+ assert comp.for_machine == for_machine
+ env.coredata.process_new_compiler(lang, comp, env)
+ if not skip_sanity_check:
+ comp.sanity_check(env.get_scratch_dir(), env)
+ env.coredata.compilers[comp.for_machine][lang] = comp
return comp
trials = [['xilib']]
elif is_windows() and compiler.id == 'pgi': # this handles cpp / nvidia HPC, in addition to just c/fortran
trials = [['ar']] # For PGI on Windows, "ar" is just a wrapper calling link/lib.
+ elif is_windows() and compiler.id == 'nasm':
+ # This may well be LINK.EXE if it's under a MSVC environment
+ trials = [defaults['vs_static_linker'], defaults['clang_cl_static_linker']] + default_linkers
else:
trials = default_linkers
popen_exceptions = {}
else:
arg = '--version'
try:
- p, out, err = Popen_safe(linker + [arg])
+ p, out, err = Popen_safe_logged(linker + [arg], msg='Detecting linker via')
except OSError as e:
popen_exceptions[join_args(linker + [arg])] = e
continue
return linkers.TILinker(linker)
if out.startswith('The CompCert'):
return linkers.CompCertLinker(linker)
+ if out.strip().startswith('Metrowerks') or out.strip().startswith('Freescale'):
+ if 'ARM' in out:
+ return linkers.MetrowerksStaticLinkerARM(linker)
+ else:
+ return linkers.MetrowerksStaticLinkerEmbeddedPowerPC(linker)
if p.returncode == 0:
return linkers.ArLinker(compiler.for_machine, linker)
if p.returncode == 1 and err.startswith('usage'): # OSX
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]]
for compiler in compilers:
if isinstance(compiler, str):
cmd = compiler + [arg]
try:
- mlog.debug('-----')
- mlog.debug(f'Detecting compiler via: {join_args(cmd)}')
- p, out, err = Popen_safe(cmd)
- mlog.debug(f'compiler returned {p}')
- mlog.debug(f'compiler stdout:\n{out}')
- mlog.debug(f'compiler stderr:\n{err}')
+ p, out, err = Popen_safe_logged(cmd, msg='Detecting compiler via')
except OSError as e:
popen_exceptions[join_args(cmd)] = e
continue
version = search_version(out)
guess_gcc_or_lcc: T.Optional[str] = None
- if 'Free Software Foundation' in out or 'xt-' in out:
+ if 'Free Software Foundation' in out or out.startswith('xt-'):
guess_gcc_or_lcc = 'gcc'
if 'e2k' in out and 'lcc' in out:
guess_gcc_or_lcc = 'lcc'
# emcc requires a file input in order to pass arguments to the
# linker. It'll exit with an error code, but still print the
- # linker version. Old emcc versions ignore -Wl,--version completely,
- # however. We'll report "unknown version" in that case.
+ # linker version.
with tempfile.NamedTemporaryFile(suffix='.c') as f:
cmd = compiler + [cls.LINKER_PREFIX + "--version", f.name]
_, o, _ = Popen_safe(cmd)
if 'Arm C/C++/Fortran Compiler' in out:
arm_ver_match = re.search(r'version (\d+)\.(\d+)\.?(\d+)? \(build number (\d+)\)', out)
- assert arm_ver_match is not None, 'for mypy' # because mypy *should* be complaning that this could be None
+ assert arm_ver_match is not None, 'for mypy' # because mypy *should* be complaining that this could be None
version = '.'.join([x for x in arm_ver_match.groups() if x is not None])
if lang == 'c':
cls = c.ArmLtdClangCCompiler
ccache, compiler, version, for_machine, is_cross, info,
exe_wrap, full_version=full_version, linker=l)
if 'TMS320C2000 C/C++' in out or 'MSP430 C/C++' in out or 'TI ARM C/C++ Compiler' in out:
- lnk: T.Union[T.Type[linkers.C2000DynamicLinker], T.Type[linkers.TIDynamicLinker]]
if 'TMS320C2000 C/C++' in out:
cls = c.C2000CCompiler if lang == 'c' else cpp.C2000CPPCompiler
lnk = linkers.C2000DynamicLinker
return cls(
ccache, compiler, version, for_machine, is_cross, info,
exe_wrap, full_version=full_version, linker=linker)
- if 'ARM' in out:
+ 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)
ccache, compiler, version, for_machine, is_cross, info,
exe_wrap, full_version=full_version, linker=linker)
+ if 'Metrowerks C/C++' in out or 'Freescale C/C++' in out:
+ if 'ARM' in out:
+ cls = c.MetrowerksCCompilerARM if lang == 'c' else cpp.MetrowerksCPPCompilerARM
+ lnk = linkers.MetrowerksLinkerARM
+ else:
+ cls = c.MetrowerksCCompilerEmbeddedPowerPC if lang == 'c' else cpp.MetrowerksCPPCompilerEmbeddedPowerPC
+ lnk = linkers.MetrowerksLinkerEmbeddedPowerPC
+
+ mwcc_ver_match = re.search(r'Version (\d+)\.(\d+)\.?(\d+)? build (\d+)', out)
+ assert mwcc_ver_match is not None, 'for mypy' # because mypy *should* be complaning 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)
+ ld = env.lookup_binary_entry(for_machine, cls.language + '_ld')
+
+ if ld is not None:
+ _, o_ld, _ = Popen_safe(ld + ['--version'])
+
+ mwld_ver_match = re.search(r'Version (\d+)\.(\d+)\.?(\d+)? build (\d+)', o_ld)
+ assert mwld_ver_match is not None, 'for mypy' # because mypy *should* be complaning 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)
+ 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,
+ exe_wrap, full_version=full_version, linker=linker)
+
_handle_exceptions(popen_exceptions, compilers)
raise EnvironmentException(f'Unknown compiler {compilers}')
for compiler in compilers:
arg = '--version'
try:
- p, out, err = Popen_safe(compiler + [arg])
+ p, out, err = Popen_safe_logged(compiler + [arg], msg='Detecting compiler via')
except OSError as e:
popen_exceptions[join_args(compiler + [arg])] = e
continue
for compiler in compilers:
for arg in ['--version', '-V']:
try:
- p, out, err = Popen_safe(compiler + [arg])
+ p, out, err = Popen_safe_logged(compiler + [arg], msg='Detecting compiler via')
except OSError as e:
popen_exceptions[join_args(compiler + [arg])] = e
continue
if 'Arm C/C++/Fortran Compiler' in out:
cls = fortran.ArmLtdFlangFortranCompiler
arm_ver_match = re.search(r'version (\d+)\.(\d+)\.?(\d+)? \(build number (\d+)\)', out)
- assert arm_ver_match is not None, 'for mypy' # because mypy *should* be complaning that this could be None
+ assert arm_ver_match is not None, 'for mypy' # because mypy *should* be complaining that this could be None
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,
exe_wrap, full_version=full_version, linker=linker)
- if 'ifx (IFORT)' in out:
+ 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(
for compiler in compilers:
arg = ['--version']
try:
- p, out, err = Popen_safe(compiler + arg)
+ p, out, err = Popen_safe_logged(compiler + arg, msg='Detecting compiler via')
except OSError as e:
popen_exceptions[join_args(compiler + arg)] = e
continue
exelist = [defaults['java'][0]]
try:
- p, out, err = Popen_safe(exelist + ['-version'])
+ p, out, err = Popen_safe_logged(exelist + ['-version'], msg='Detecting compiler via')
except OSError:
raise EnvironmentException('Could not execute Java compiler: {}'.format(join_args(exelist)))
if 'javac' in out or 'javac' in err:
info = env.machines[for_machine]
for comp in compilers:
try:
- p, out, err = Popen_safe(comp + ['--version'])
+ p, out, err = Popen_safe_logged(comp + ['--version'], msg='Detecting compiler via')
except OSError as e:
popen_exceptions[join_args(comp + ['--version'])] = e
continue
popen_exceptions: T.Dict[str, Exception] = {}
for comp in compilers:
try:
- err = Popen_safe(comp + ['-V'])[2]
+ _, out, err = Popen_safe_logged(comp + ['-V'], msg='Detecting compiler via')
except OSError as e:
popen_exceptions[join_args(comp + ['-V'])] = e
continue
- version = search_version(err)
- if 'Cython' in err:
+ version: T.Optional[str] = None
+ # 3.0
+ if 'Cython' in out:
+ version = search_version(out)
+ # older
+ elif 'Cython' in err:
+ 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)
exelist = [defaults['vala'][0]]
try:
- p, out = Popen_safe(exelist + ['--version'])[0:2]
+ p, out = Popen_safe_logged(exelist + ['--version'], msg='Detecting compiler via')[0:2]
except OSError:
raise EnvironmentException('Could not execute Vala compiler: {}'.format(join_args(exelist)))
version = search_version(out)
def detect_rust_compiler(env: 'Environment', for_machine: MachineChoice) -> RustCompiler:
from . import rust
from ..linkers import linkers
- popen_exceptions = {} # type: T.Dict[str, Exception]
+ popen_exceptions: T.Dict[str, Exception] = {}
compilers, _, exe_wrap = _get_compilers(env, 'rust', for_machine)
is_cross = env.is_cross_build(for_machine)
info = env.machines[for_machine]
for compiler in compilers:
arg = ['--version']
try:
- out = Popen_safe(compiler + arg)[1]
+ out = Popen_safe_logged(compiler + arg, msg='Detecting compiler via')[1]
except OSError as e:
popen_exceptions[join_args(compiler + arg)] = e
continue
# Clippy is a wrapper around rustc, but it doesn't have rustc in it's
# output. We can otherwise treat it as rustc.
if 'clippy' in out:
- out = 'rustc'
+ # clippy returns its own version and not the rustc version by
+ # default so try harder here to get the correct version.
+ # Also replace the whole output with the rustc output in
+ # case this is later used for other purposes.
+ arg = ['--rustc', '--version']
+ try:
+ out = Popen_safe(compiler + arg)[1]
+ except OSError as e:
+ popen_exceptions[join_args(compiler + arg)] = e
+ continue
+ version = search_version(out)
+
cls = rust.ClippyRustCompiler
if 'rustc' in out:
mlog.warning(
'Please do not put -C linker= in your compiler '
'command, set rust_ld=command in your cross file '
- 'or use the RUST_LD environment variable, otherwise meson '
+ 'or use the RUSTC_LD environment variable, otherwise meson '
'will override your selection.')
compiler = compiler.copy() # avoid mutating the original list
extra_args['machine'] = cc.linker.machine
else:
exelist = cc.linker.exelist + cc.linker.get_always_args()
- if 'ccache' in exelist[0]:
+ if os.path.basename(exelist[0]) in {'ccache', 'sccache'}:
del exelist[0]
c = exelist.pop(0)
compiler.extend(cls.use_linker_args(c, ''))
if 'LLVM D compiler' in out:
cls = d.LLVMDCompiler
# LDC seems to require a file
- # We cannot use NamedTemproraryFile on windows, its documented
+ # We cannot use NamedTemporaryFile on windows, its documented
# to not work for our uses. So, just use mkstemp and only have
# one path for simplicity.
o, f = tempfile.mkstemp('.d')
elif 'The D Language Foundation' in out or 'Digital Mars' in out:
cls = d.DmdDCompiler
# DMD seems to require a file
- # We cannot use NamedTemproraryFile on windows, its documented
+ # We cannot use NamedTemporaryFile on windows, its documented
# to not work for our uses. So, just use mkstemp and only have
# one path for simplicity.
o, f = tempfile.mkstemp('.d')
exelist = [defaults['swift'][0]]
try:
- p, _, err = Popen_safe(exelist + ['-v'])
+ p, _, err = Popen_safe_logged(exelist + ['-v'], msg='Detecting compiler via')
except OSError:
raise EnvironmentException('Could not execute Swift compiler: {}'.format(join_args(exelist)))
version = search_version(err)
raise EnvironmentException('Unknown compiler: ' + join_args(exelist))
def detect_nasm_compiler(env: 'Environment', for_machine: MachineChoice) -> Compiler:
- from .asm import NasmCompiler, YasmCompiler
+ from .asm import NasmCompiler, YasmCompiler, MetrowerksAsmCompilerARM, MetrowerksAsmCompilerEmbeddedPowerPC
compilers, _, _ = _get_compilers(env, 'nasm', for_machine)
is_cross = env.is_cross_build(for_machine)
default_path = os.path.join(os.environ['ProgramFiles'], 'NASM')
comp[0] = shutil.which(comp[0], path=default_path) or comp[0]
try:
- output = Popen_safe(comp + ['--version'])[1]
+ output = Popen_safe_logged(comp + ['--version'], msg='Detecting compiler via')[1]
except OSError as e:
popen_exceptions[' '.join(comp + ['--version'])] = e
continue
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)
+ 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)
+ 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)
+
_handle_exceptions(popen_exceptions, compilers)
raise EnvironmentException('Unreachable code (exception to make mypy happy)')
from .compilers import (
clike_debug_args,
Compiler,
+ CompileCheckMode,
)
from .mixins.clike import CLikeCompiler
from .mixins.gnu import (
from ..dependencies import Dependency
from ..envconfig import MachineInfo
from ..environment import Environment
- from ..linkers import DynamicLinker
+ from ..linkers.linkers import DynamicLinker
from ..mesonlib import MachineChoice
from ..programs import ExternalProgram
- from .compilers import CompileCheckMode
class FortranCompiler(CLikeCompiler, Compiler):
return filename
def find_library(self, libname: str, env: 'Environment', extra_dirs: T.List[str],
- libtype: LibType = LibType.PREFER_SHARED) -> T.Optional[T.List[str]]:
+ libtype: LibType = LibType.PREFER_SHARED, lib_prefix_warning: bool = True) -> T.Optional[T.List[str]]:
code = 'stop; end program'
- return self._find_library_impl(libname, env, extra_dirs, code, libtype)
+ return self._find_library_impl(libname, env, extra_dirs, code, libtype, lib_prefix_warning)
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')
return opts
def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
- args = []
+ args: T.List[str] = []
key = OptionKey('std', machine=self.for_machine, lang=self.language)
std = options[key]
if std.value != 'none':
'''
code = f'{prefix}\n#include <{hname}>'
return self.compiles(code, env, extra_args=extra_args,
- dependencies=dependencies, mode='preprocess', disable_cache=disable_cache)
+ dependencies=dependencies, mode=CompileCheckMode.PREPROCESS, disable_cache=disable_cache)
class ElbrusFortranCompiler(ElbrusCompiler, FortranCompiler):
return opts
def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
- args = []
+ args: T.List[str] = []
key = OptionKey('std', machine=self.for_machine, lang=self.language)
std = options[key]
stds = {'legacy': 'none', 'f95': 'f95', 'f2003': 'f03', 'f2008': 'f08', 'f2018': 'f18'}
return opts
def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
- args = []
+ args: T.List[str] = []
key = OptionKey('std', machine=self.for_machine, lang=self.language)
std = options[key]
stds = {'legacy': 'none', 'f95': 'f95', 'f2003': 'f03', 'f2008': 'f08', 'f2018': 'f18'}
pc = subprocess.Popen(self.exelist + [src], cwd=work_dir)
pc.wait()
if pc.returncode != 0:
- raise EnvironmentException(f'Java compiler {self.name_string()} can not compile programs.')
+ raise EnvironmentException(f'Java compiler {self.name_string()} cannot compile programs.')
runner = shutil.which(self.javarunner)
if runner:
- cmdlist = [runner, obj]
+ cmdlist = [runner, '-cp', '.', obj]
pe = subprocess.Popen(cmdlist, cwd=work_dir)
pe.wait()
if pe.returncode != 0:
# 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
"""Representations specific to the arm family of compilers."""
import typing as T
from ... import mesonlib
-from ...linkers import ArmClangDynamicLinker
+from ...linkers.linkers import ArmClangDynamicLinker
from ...mesonlib import OptionKey
from ..compilers import clike_debug_args
from .clang import clang_color_args
# do). This gives up DRYer type checking, with no runtime impact
Compiler = object
-arm_buildtype_args = {
+arm_buildtype_args: T.Dict[str, T.List[str]] = {
'plain': [],
'debug': [],
'debugoptimized': [],
'release': [],
'minsize': [],
'custom': [],
-} # type: T.Dict[str, T.List[str]]
+}
-arm_optimization_args = {
+arm_optimization_args: T.Dict[str, T.List[str]] = {
'plain': [],
'0': ['-O0'],
'g': ['-g'],
'2': [], # Compiler defaults to -O2
'3': ['-O3', '-Otime'],
's': ['-O3'], # Compiler defaults to -Ospace
-} # type: T.Dict[str, T.List[str]]
+}
-armclang_buildtype_args = {
+armclang_buildtype_args: T.Dict[str, T.List[str]] = {
'plain': [],
'debug': [],
'debugoptimized': [],
'release': [],
'minsize': [],
'custom': [],
-} # type: T.Dict[str, T.List[str]]
+}
-armclang_optimization_args = {
+armclang_optimization_args: T.Dict[str, T.List[str]] = {
'plain': [],
'0': [], # Compiler defaults to -O0
'g': ['-g'],
'2': ['-O2'],
'3': ['-O3'],
's': ['-Oz']
-} # type: T.Dict[str, T.List[str]]
+}
class ArmCompiler(Compiler):
def __init__(self) -> None:
if not self.is_cross:
raise mesonlib.EnvironmentException('armcc supports only cross-compilation.')
- default_warn_args = [] # type: T.List[str]
+ default_warn_args: T.List[str] = []
self.warn_args = {'0': [],
'1': default_warn_args,
'2': default_warn_args + [],
'3': default_warn_args + [],
- 'everything': default_warn_args + []} # type: T.Dict[str, T.List[str]]
+ 'everything': default_warn_args + []}
# Assembly
self.can_compile_suffixes.add('s')
+ self.can_compile_suffixes.add('sx')
def get_pic_args(self) -> T.List[str]:
# FIXME: Add /ropi, /rwpi, /fpic etc. qualifiers to --apcs
'b_ndebug', 'b_staticpic', 'b_colorout']}
# Assembly
self.can_compile_suffixes.add('s')
+ self.can_compile_suffixes.add('sx')
def get_pic_args(self) -> T.List[str]:
# PIC support is not enabled by default for ARM,
# do). This gives up DRYer type checking, with no runtime impact
Compiler = object
-ccrx_buildtype_args = {
+ccrx_buildtype_args: T.Dict[str, T.List[str]] = {
'plain': [],
'debug': [],
'debugoptimized': [],
'release': [],
'minsize': [],
'custom': [],
-} # type: T.Dict[str, T.List[str]]
+}
-ccrx_optimization_args = {
+ccrx_optimization_args: T.Dict[str, T.List[str]] = {
'0': ['-optimize=0'],
'g': ['-optimize=0'],
'1': ['-optimize=1'],
'2': ['-optimize=2'],
'3': ['-optimize=max'],
's': ['-optimize=2', '-size']
-} # type: T.Dict[str, T.List[str]]
+}
-ccrx_debug_args = {
+ccrx_debug_args: T.Dict[bool, T.List[str]] = {
False: [],
True: ['-debug']
-} # type: T.Dict[bool, T.List[str]]
+}
class CcrxCompiler(Compiler):
if T.TYPE_CHECKING:
is_cross = True
- can_compile_suffixes = set() # type: T.Set[str]
+ can_compile_suffixes: T.Set[str] = set()
id = 'ccrx'
raise EnvironmentException('ccrx supports only cross-compilation.')
# Assembly
self.can_compile_suffixes.add('src')
- default_warn_args = [] # type: T.List[str]
- self.warn_args = {'0': [],
- '1': default_warn_args,
- '2': default_warn_args + [],
- '3': default_warn_args + [],
- 'everything': default_warn_args + []} # type: T.Dict[str, T.List[str]]
+ default_warn_args: T.List[str] = []
+ self.warn_args: T.Dict[str, T.List[str]] = {
+ '0': [],
+ '1': default_warn_args,
+ '2': default_warn_args + [],
+ '3': default_warn_args + [],
+ 'everything': default_warn_args + []}
def get_pic_args(self) -> T.List[str]:
# PIC support is not enabled by default for CCRX,
@classmethod
def _unix_args_to_native(cls, args: T.List[str], info: MachineInfo) -> T.List[str]:
- result = []
+ result: T.List[str] = []
for i in args:
if i.startswith('-D'):
i = '-define=' + i[2:]
# 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
"""Abstractions for the LLVM/Clang compiler family."""
import typing as T
from ... import mesonlib
-from ...linkers import AppleDynamicLinker, ClangClDynamicLinker, LLVMDynamicLinker, GnuGoldDynamicLinker, \
+from ...linkers.linkers import AppleDynamicLinker, ClangClDynamicLinker, LLVMDynamicLinker, GnuGoldDynamicLinker, \
MoldDynamicLinker
from ...mesonlib import OptionKey
from ..compilers import CompileCheckMode
from ...environment import Environment
from ...dependencies import Dependency # noqa: F401
-clang_color_args = {
- 'auto': ['-fcolor-diagnostics'],
- 'always': ['-fcolor-diagnostics'],
- 'never': ['-fno-color-diagnostics'],
-} # type: T.Dict[str, T.List[str]]
+clang_color_args: T.Dict[str, T.List[str]] = {
+ 'auto': ['-fdiagnostics-color=auto'],
+ 'always': ['-fdiagnostics-color=always'],
+ 'never': ['-fdiagnostics-color=never'],
+}
-clang_optimization_args = {
+clang_optimization_args: T.Dict[str, T.List[str]] = {
'plain': [],
'0': ['-O0'],
'g': ['-Og'],
'2': ['-O2'],
'3': ['-O3'],
's': ['-Oz'],
-} # type: T.Dict[str, T.List[str]]
+}
class ClangCompiler(GnuLikeCompiler):
# 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
"""Mixin classes to be shared between C and C++ compilers.
-Without this we'll end up with awful diamond inherintance problems. The goal
+Without this we'll end up with awful diamond inheritance problems. The goal
of this is to have mixin's, which are classes that are designed *not* to be
standalone, they only work through inheritance.
"""
from ... import arglist
from ... import mesonlib
from ... import mlog
-from ...linkers import GnuLikeDynamicLinkerMixin, SolarisDynamicLinker, CompCertDynamicLinker
-from ...mesonlib import LibType
-from ...coredata import OptionKey
+from ...linkers.linkers import GnuLikeDynamicLinkerMixin, SolarisDynamicLinker, CompCertDynamicLinker
+from ...mesonlib import LibType, OptionKey
from .. import compilers
from ..compilers import CompileCheckMode
from .visualstudio import VisualStudioLikeCompiler
default_dirs = self.compiler.get_default_include_dirs()
if default_dirs:
real_default_dirs = [self._cached_realpath(i) for i in default_dirs]
- bad_idx_list = [] # type: T.List[int]
+ bad_idx_list: T.List[int] = []
for i, each in enumerate(new):
if not each.startswith('-isystem'):
continue
"""Shared bits for the C and CPP Compilers."""
if T.TYPE_CHECKING:
- warn_args = {} # type: T.Dict[str, T.List[str]]
+ warn_args: T.Dict[str, T.List[str]] = {}
# TODO: Replace this manual cache with functools.lru_cache
- find_library_cache = {} # type: T.Dict[T.Tuple[T.Tuple[str, ...], str, T.Tuple[str, ...], str, LibType], T.Optional[T.List[str]]]
- find_framework_cache = {} # type: T.Dict[T.Tuple[T.Tuple[str, ...], str, T.Tuple[str, ...], bool], T.Optional[T.List[str]]]
+ find_library_cache: T.Dict[T.Tuple[T.Tuple[str, ...], str, T.Tuple[str, ...], str, LibType], 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
def __init__(self, exe_wrapper: T.Optional['ExternalProgram'] = None):
# system directories aren't mixed, we only need to check one file for each
# directory and go by that. If we can't check the file for some reason, assume
# the compiler knows what it's doing, and accept the directory anyway.
- retval = []
+ retval: T.List[str] = []
for d in dirs:
files = [f for f in os.listdir(d) if f.endswith('.so') and os.path.isfile(os.path.join(d, f))]
# if no files, accept directory and move on
mlog.debug(stde)
mlog.debug('-----')
if pc.returncode != 0:
- raise mesonlib.EnvironmentException(f'Compiler {self.name_string()} can not compile programs.')
+ raise mesonlib.EnvironmentException(f'Compiler {self.name_string()} cannot compile programs.')
# Run sanity check
if self.is_cross:
if self.exe_wrapper is None:
#include <{hname}>
#endif'''
return self.compiles(code, env, extra_args=extra_args,
- dependencies=dependencies, mode='preprocess', disable_cache=disable_cache)
+ dependencies=dependencies, mode=CompileCheckMode.PREPROCESS, disable_cache=disable_cache)
def has_header_symbol(self, hname: str, symbol: str, prefix: str,
env: 'Environment', *,
dependencies=dependencies)
def _get_basic_compiler_args(self, env: 'Environment', mode: CompileCheckMode) -> T.Tuple[T.List[str], T.List[str]]:
- cargs = [] # type: T.List[str]
- largs = [] # type: T.List[str]
+ cargs: T.List[str] = []
+ largs: T.List[str] = []
if mode is CompileCheckMode.LINK:
# Sometimes we need to manually select the CRT to use with MSVC.
# One example is when trying to do a compiler check that involves
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:
- # TODO: the caller should handle the listfing of these arguments
+ # TODO: the caller should handle the listing of these arguments
if extra_args is None:
extra_args = []
else:
# TODO: we want to ensure the front end does the listifing here
dependencies = [dependencies]
# Collect compiler arguments
- cargs = self.compiler_args() # type: arglist.CompilerArgs
- largs = [] # type: T.List[str]
+ cargs: arglist.CompilerArgs = self.compiler_args()
+ largs: T.List[str] = []
for d in dependencies:
# Add compile flags needed by dependencies
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()):
+ 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()
need_exe_wrapper = env.need_exe_wrapper(self.for_machine)
if need_exe_wrapper and self.exe_wrapper is None:
raise compilers.CrossNoRunException('Can not run test applications in this cross environment.')
- with self._build_wrapper(code, env, extra_args, dependencies, mode='link', want_output=True) as p:
+ with self._build_wrapper(code, env, 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 compilers.RunResult(False)
def _compile_int(self, expression: str, prefix: str, env: 'Environment',
extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]],
dependencies: T.Optional[T.List['Dependency']]) -> bool:
- t = f'''#include <stdio.h>
- {prefix}
+ 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]
extra_args = []
if self.is_cross:
return self.cross_compute_int(expression, low, high, guess, prefix, env, extra_args, dependencies)
- t = f'''#include<stdio.h>
- {prefix}
+ t = f'''{prefix}
+ #include<stddef.h>
+ #include<stdio.h>
int main(void) {{
printf("%ld\\n", (long)({expression}));
return 0;
dependencies: T.Optional[T.List['Dependency']] = None) -> int:
if extra_args is None:
extra_args = []
- t = f'''#include <stdio.h>
- {prefix}
+ t = f'''{prefix}
+ #include <stddef.h>
int main(void) {{
{typename} something;
return 0;
def 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:
+ dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[int, bool]:
if extra_args is None:
extra_args = []
if self.is_cross:
- return self.cross_sizeof(typename, prefix, env, extra_args=extra_args,
- dependencies=dependencies)
- t = f'''#include<stdio.h>
- {prefix}
+ r = self.cross_sizeof(typename, prefix, env, extra_args=extra_args,
+ dependencies=dependencies)
+ return r, False
+ t = f'''{prefix}
+ #include<stddef.h>
+ #include<stdio.h>
int main(void) {{
printf("%ld\\n", (long)(sizeof({typename})));
return 0;
}}'''
- res = self.run(t, env, extra_args=extra_args,
- dependencies=dependencies)
+ res = self.cached_run(t, env, extra_args=extra_args,
+ dependencies=dependencies)
if not res.compiled:
- return -1
+ return -1, False
if res.returncode != 0:
raise mesonlib.EnvironmentException('Could not run sizeof test binary.')
- return int(res.stdout)
+ 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:
if extra_args is None:
extra_args = []
- t = f'''#include <stdio.h>
- {prefix}
+ t = f'''{prefix}
+ #include <stddef.h>
int main(void) {{
{typename} something;
return 0;
if not self.compiles(t, env, extra_args=extra_args,
dependencies=dependencies)[0]:
return -1
- t = f'''#include <stddef.h>
- {prefix}
+ t = f'''{prefix}
+ #include <stddef.h>
struct tmp {{
char c;
{typename} target;
def alignment(self, typename: str, prefix: str, env: 'Environment', *,
extra_args: T.Optional[T.List[str]] = None,
- dependencies: T.Optional[T.List['Dependency']] = None) -> int:
+ dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[int, bool]:
if extra_args is None:
extra_args = []
if self.is_cross:
- return self.cross_alignment(typename, prefix, env, extra_args=extra_args,
- dependencies=dependencies)
- t = f'''#include <stdio.h>
+ r = self.cross_alignment(typename, prefix, env, extra_args=extra_args,
+ dependencies=dependencies)
+ return r, False
+ t = f'''{prefix}
+ #include <stdio.h>
#include <stddef.h>
- {prefix}
struct tmp {{
char c;
{typename} target;
printf("%d", (int)offsetof(struct tmp, target));
return 0;
}}'''
- res = self.run(t, env, extra_args=extra_args,
- dependencies=dependencies)
+ res = self.cached_run(t, env, extra_args=extra_args,
+ dependencies=dependencies)
if not res.compiled:
raise mesonlib.EnvironmentException('Could not compile alignment test.')
if res.returncode != 0:
align = int(res.stdout)
if align == 0:
raise mesonlib.EnvironmentException(f'Could not determine alignment of {typename}. Sorry. You might want to file a bug.')
- return align
+ return align, res.cached
def get_define(self, dname: str, prefix: str, env: 'Environment',
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]:
- delim = '"MESON_GET_DEFINE_DELIMITER"'
+ delim_start = '"MESON_GET_DEFINE_DELIMITER_START"\n'
+ delim_end = '\n"MESON_GET_DEFINE_DELIMITER_END"'
+ sentinel_undef = '"MESON_GET_DEFINE_UNDEFINED_SENTINEL"'
code = f'''
{prefix}
#ifndef {dname}
- # define {dname}
+ # define {dname} {sentinel_undef}
#endif
- {delim}\n{dname}'''
+ {delim_start}{dname}{delim_end}'''
args = self.build_wrapper_args(env, extra_args, dependencies,
mode=CompileCheckMode.PREPROCESS).to_native()
- func = functools.partial(self.cached_compile, code, env.coredata, extra_args=args, mode='preprocess')
+ func = functools.partial(self.cached_compile, code, env.coredata, extra_args=args, mode=CompileCheckMode.PREPROCESS)
if disable_cache:
- func = functools.partial(self.compile, code, extra_args=args, mode='preprocess', temp_dir=env.scratch_dir)
+ func = functools.partial(self.compile, code, extra_args=args, mode=CompileCheckMode.PREPROCESS)
with func() as p:
cached = p.cached
if p.returncode != 0:
raise mesonlib.EnvironmentException(f'Could not get define {dname!r}')
- # Get the preprocessed value after the delimiter,
- # minus the extra newline at the end and
- # merge string literals.
- return self._concatenate_string_literals(p.stdout.split(delim + '\n')[-1][:-1]), cached
+
+ # Get the preprocessed value between the delimiters
+ star_idx = p.stdout.find(delim_start)
+ end_idx = p.stdout.rfind(delim_end)
+ if (star_idx == -1) or (end_idx == -1) or (star_idx == end_idx):
+ raise mesonlib.MesonBugException('Delimiters not found in preprocessor output.')
+ define_value = p.stdout[star_idx + len(delim_start):end_idx]
+
+ if define_value == sentinel_undef:
+ define_value = None
+ else:
+ # Merge string literals
+ define_value = self._concatenate_string_literals(define_value).strip()
+
+ return define_value, cached
def get_return_value(self, fname: str, rtype: str, prefix: str,
env: 'Environment', extra_args: T.Optional[T.List[str]],
#
# class StrProto(typing.Protocol):
# def __str__(self) -> str: ...
- fargs = {'prefix': prefix, 'func': funcname} # type: T.Dict[str, T.Union[str, bool, int]]
+ fargs: T.Dict[str, T.Union[str, bool, int]] = {'prefix': prefix, 'func': funcname}
# glibc defines functions that are not available on Linux as stubs that
# fail with ENOSYS (such as e.g. lchmod). In this case we want to fail
if extra_args is None:
extra_args = []
# Create code that accesses all members
- members = ''
- for member in membernames:
- members += f'foo.{member};\n'
+ members = ''.join(f'foo.{member};\n' for member in membernames)
t = f'''{prefix}
void bar(void) {{
{typename} foo;
'''
args = self.get_compiler_check_args(CompileCheckMode.COMPILE)
n = '_symbols_have_underscore_prefix_searchbin'
- with self._build_wrapper(code, env, extra_args=args, mode='compile', want_output=True, temp_dir=env.scratch_dir) as p:
+ with self._build_wrapper(code, env, 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):
#endif
{delim}MESON_UNDERSCORE_PREFIX
'''
- with self._build_wrapper(code, env, mode='preprocess', want_output=False, temp_dir=env.scratch_dir) as p:
+ with self._build_wrapper(code, env, 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()
return self._symbols_have_underscore_prefix_searchbin(env)
def _get_patterns(self, env: 'Environment', prefixes: T.List[str], suffixes: T.List[str], shared: bool = False) -> T.List[str]:
- patterns = [] # type: T.List[str]
+ patterns: T.List[str] = []
for p in prefixes:
for s in suffixes:
patterns.append(p + '{}.' + s)
@staticmethod
def _sort_shlibs_openbsd(libs: T.List[str]) -> T.List[str]:
- filtered = [] # type: T.List[str]
+ def tuple_key(x: str) -> T.Tuple[int, ...]:
+ ver = x.rsplit('.so.', maxsplit=1)[1]
+ return tuple(int(i) for i in ver.split('.'))
+
+ filtered: T.List[str] = []
for lib in libs:
# Validate file as a shared library of type libfoo.so.X.Y
ret = lib.rsplit('.so.', maxsplit=1)
if len(ret) != 2:
continue
try:
- float(ret[1])
+ tuple(int(i) for i in ret[1].split('.'))
except ValueError:
continue
filtered.append(lib)
- float_cmp = lambda x: float(x.rsplit('.so.', maxsplit=1)[1])
- return sorted(filtered, key=float_cmp, reverse=True)
+ return sorted(filtered, key=tuple_key, reverse=True)
@classmethod
def _get_trials_from_pattern(cls, pattern: str, directory: str, libname: str) -> T.List[Path]:
return [f]
@staticmethod
- def _get_file_from_list(env: 'Environment', paths: T.List[Path]) -> Path:
+ def _get_file_from_list(env: Environment, paths: T.List[Path]) -> 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.
'''
- # If not building on macOS for Darwin, do a simple file check
- if not env.machines.host.is_darwin() or not env.machines.build.is_darwin():
- for p in paths:
- if p.is_file():
- return p
- # Run `lipo` and check if the library supports the arch we want
for p in paths:
- if not p.is_file():
- continue
- archs = mesonlib.darwin_get_object_archs(str(p))
- if archs and env.machines.host.cpu_family in archs:
+ if p.is_file():
+
+ if env.machines.host.is_darwin() and env.machines.build.is_darwin():
+ # Run `lipo` and check if the library supports the arch we want
+ archs = mesonlib.darwin_get_object_archs(str(p))
+ if not archs or env.machines.host.cpu_family not in archs:
+ mlog.debug(f'Rejected {p}, supports {archs} but need {env.machines.host.cpu_family}')
+ continue
+
return p
- else:
- mlog.debug(f'Rejected {p}, supports {archs} but need {env.machines.host.cpu_family}')
+
return None
@functools.lru_cache()
'''
returns true if the output produced is 64-bit, false if 32-bit
'''
- return self.sizeof('void *', '', env) == 8
+ return self.sizeof('void *', '', env)[0] == 8
- def _find_library_real(self, libname: str, env: 'Environment', extra_dirs: T.List[str], code: str, libtype: LibType) -> T.Optional[T.List[str]]:
+ 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]]:
# 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.
trial = self._get_file_from_list(env, trials)
if not trial:
continue
- if libname.startswith('lib') and trial.name.startswith(libname):
+ if libname.startswith('lib') and trial.name.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 None
def _find_library_impl(self, libname: str, env: 'Environment', extra_dirs: T.List[str],
- code: str, libtype: LibType) -> T.Optional[T.List[str]]:
+ code: str, libtype: LibType, lib_prefix_warning: bool) -> T.Optional[T.List[str]]:
# These libraries are either built-in or invalid
if libname in self.ignore_libs:
return []
extra_dirs = [extra_dirs]
key = (tuple(self.exelist), libname, tuple(extra_dirs), code, libtype)
if key not in self.find_library_cache:
- value = self._find_library_real(libname, env, extra_dirs, code, libtype)
+ value = self._find_library_real(libname, env, extra_dirs, code, libtype, lib_prefix_warning)
self.find_library_cache[key] = value
else:
value = self.find_library_cache[key]
return value.copy()
def find_library(self, libname: str, env: 'Environment', extra_dirs: T.List[str],
- libtype: LibType = LibType.PREFER_SHARED) -> T.Optional[T.List[str]]:
+ libtype: LibType = LibType.PREFER_SHARED, lib_prefix_warning: bool = True) -> T.Optional[T.List[str]]:
code = 'int main(void) { return 0; }\n'
- return self._find_library_impl(libname, env, extra_dirs, code, libtype)
+ return self._find_library_impl(libname, env, extra_dirs, code, libtype, lib_prefix_warning)
def find_framework_paths(self, env: 'Environment') -> T.List[str]:
'''
os_env = os.environ.copy()
os_env['LC_ALL'] = 'C'
_, _, stde = mesonlib.Popen_safe(commands, env=os_env, stdin=subprocess.PIPE)
- paths = [] # T.List[str]
+ paths: T.List[str] = []
for line in stde.split('\n'):
if '(framework directory)' not in line:
continue
def _find_framework_real(self, name: str, env: 'Environment', extra_dirs: T.List[str], allow_system: bool) -> T.Optional[T.List[str]]:
code = 'int main(void) { return 0; }'
- link_args = []
+ link_args: T.List[str] = []
for d in extra_dirs:
link_args += ['-F' + d]
# We can pass -Z to disable searching in the system frameworks, but
return args.copy()
def has_arguments(self, args: T.List[str], env: 'Environment', code: str,
- mode: str) -> T.Tuple[bool, bool]:
+ mode: CompileCheckMode) -> T.Tuple[bool, bool]:
return self.compiles(code, env, extra_args=args, mode=mode)
def _has_multi_arguments(self, args: T.List[str], env: 'Environment', code: str) -> T.Tuple[bool, bool]:
- new_args = [] # type: T.List[str]
+ new_args: T.List[str] = []
for arg in args:
# some compilers, e.g. GCC, don't warn for unsupported warning-disable
# flags, so when we are testing a flag like "-Wno-forgotten-towel", also
'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='compile')
+ return self.has_arguments(new_args, env, 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')
# false positive.
args = self.linker.fatal_warnings() + args
args = self.linker_to_compiler_args(args)
- return self.has_arguments(args, env, code, mode='link')
+ return self.has_arguments(args, env, 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')
return self.compiles(self.attribute_check_func(name), env,
extra_args=self.get_has_func_attribute_extra_args(name))
- def get_disable_assert_args(self) -> T.List[str]:
- return ['-DNDEBUG']
+ def get_assert_args(self, disable: bool) -> T.List[str]:
+ if disable:
+ return ['-DNDEBUG']
+ return []
@functools.lru_cache(maxsize=None)
def can_compile(self, src: 'mesonlib.FileOrString') -> bool:
# do). This gives up DRYer type checking, with no runtime impact
Compiler = object
-ccomp_buildtype_args = {
+ccomp_buildtype_args: T.Dict[str, T.List[str]] = {
'plain': [''],
'debug': ['-O0', '-g'],
'debugoptimized': ['-O0', '-g'],
'release': ['-O3'],
'minsize': ['-Os'],
'custom': ['-Obranchless'],
-} # type: T.Dict[str, T.List[str]]
+}
-ccomp_optimization_args = {
+ccomp_optimization_args: T.Dict[str, T.List[str]] = {
'plain': [],
'0': ['-O0'],
'g': ['-O0'],
'2': ['-O2'],
'3': ['-O3'],
's': ['-Os']
-} # type: T.Dict[str, T.List[str]]
+}
-ccomp_debug_args = {
+ccomp_debug_args: T.Dict[bool, T.List[str]] = {
False: [],
True: ['-g']
-} # type: T.Dict[bool, T.List[str]]
+}
# As of CompCert 20.04, these arguments should be passed to the underlying gcc linker (via -WUl,<arg>)
# There are probably (many) more, but these are those used by picolibc
-ccomp_args_to_wul = [
+ccomp_args_to_wul: T.List[str] = [
r"^-ffreestanding$",
r"^-r$"
-] # type: T.List[str]
+]
class CompCertCompiler(Compiler):
def __init__(self) -> None:
# Assembly
self.can_compile_suffixes.add('s')
- default_warn_args = [] # type: T.List[str]
- self.warn_args = {'0': [],
- '1': default_warn_args,
- '2': default_warn_args + [],
- '3': default_warn_args + [],
- 'everything': default_warn_args + []} # type: T.Dict[str, T.List[str]]
+ self.can_compile_suffixes.add('sx')
+ default_warn_args: T.List[str] = []
+ self.warn_args: T.Dict[str, T.List[str]] = {
+ '0': [],
+ '1': default_warn_args,
+ '2': default_warn_args + [],
+ '3': default_warn_args + [],
+ 'everything': default_warn_args + []}
def get_always_args(self) -> T.List[str]:
return []
@classmethod
def _unix_args_to_native(cls, args: T.List[str], info: MachineInfo) -> T.List[str]:
"Always returns a copy that can be independently mutated"
- patched_args = [] # type: T.List[str]
+ patched_args: T.List[str] = []
for arg in args:
added = 0
for ptrn in ccomp_args_to_wul:
# 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
"""Abstractions for the Elbrus family of compilers."""
os_env['LC_ALL'] = 'C'
p = subprocess.Popen(self.get_exelist(ccache=False) + ['-xc', '-E', '-v', '-'], env=os_env, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stderr = p.stderr.read().decode('utf-8', errors='replace')
- includes = []
+ includes: T.List[str] = []
for line in stderr.split('\n'):
if line.lstrip().startswith('--sys_include'):
includes.append(re.sub(r'\s*\\$', '', re.sub(r'^\s*--sys_include\s*', '', line)))
return 'pch'
def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
- args = []
+ args: T.List[str] = []
std = options[OptionKey('std', lang=self.language, machine=self.for_machine)]
if std.value != 'none':
args.append('-std=' + std.value)
# 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
"""Provides a mixin for shared code between C and C++ Emscripten compilers."""
from ... import mesonlib
from ...mesonlib import OptionKey
from ...mesonlib import LibType
+from mesonbuild.compilers.compilers import CompileCheckMode
if T.TYPE_CHECKING:
from ...environment import Environment
def wrap_js_includes(args: T.List[str]) -> T.List[str]:
- final_args = []
+ final_args: T.List[str] = []
for i in args:
if i.endswith('.js') and not i.startswith('-'):
final_args += ['--js-library', i]
class EmscriptenMixin(Compiler):
- def _get_compile_output(self, dirname: str, mode: str) -> str:
- # In pre-processor mode, the output is sent to stdout and discarded
- if mode == 'preprocess':
- return None
+ 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'
# Unlike sane toolchains, emcc infers the kind of output from its name.
# This is the only reason why this method is overridden; compiler tests
# do not work well with the default exe/obj suffices.
- if mode == 'link':
+ if mode == CompileCheckMode.LINK:
suffix = 'js'
else:
suffix = 'o'
return os.path.join(dirname, 'output.' + suffix)
- def thread_flags(self, env: 'Environment') -> T.List[str]:
- return ['-s', 'USE_PTHREADS=1']
-
def thread_link_flags(self, env: 'Environment') -> T.List[str]:
- args = ['-s', 'USE_PTHREADS=1']
+ args = ['-pthread']
count: int = env.coredata.options[OptionKey('thread_count', lang=self.language, machine=self.for_machine)].value
if count:
- args.extend(['-s', f'PTHREAD_POOL_SIZE={count}'])
+ args.append(f'-sPTHREAD_POOL_SIZE={count}')
return args
def get_options(self) -> 'coredata.MutableKeyedOptionDictType':
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) -> T.Optional[T.List[str]]:
+ libtype: LibType = LibType.PREFER_SHARED, lib_prefix_warning: bool = True) -> T.Optional[T.List[str]]:
if not libname.endswith('.js'):
- return super().find_library(libname, env, extra_dirs, libtype)
+ return super().find_library(libname, env, extra_dirs, libtype, lib_prefix_warning)
if os.path.isabs(libname):
if os.path.exists(libname):
return [libname]
# 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
"""Provides mixins for GNU compilers and GNU-like compilers."""
from ... import mesonlib
from ... import mlog
from ...mesonlib import OptionKey
+from mesonbuild.compilers.compilers import CompileCheckMode
if T.TYPE_CHECKING:
from ..._typing import ImmutableListProtocol
# XXX: prevent circular references.
# FIXME: this really is a posix interface not a c-like interface
-clike_debug_args = {
+clike_debug_args: T.Dict[bool, T.List[str]] = {
False: [],
True: ['-g'],
-} # type: T.Dict[bool, T.List[str]]
+}
-gnulike_buildtype_args = {
+gnulike_buildtype_args: T.Dict[str, T.List[str]] = {
'plain': [],
'debug': [],
'debugoptimized': [],
'release': [],
'minsize': [],
'custom': [],
-} # type: T.Dict[str, T.List[str]]
+}
-gnu_optimization_args = {
+gnu_optimization_args: T.Dict[str, T.List[str]] = {
'plain': [],
'0': ['-O0'],
'g': ['-Og'],
'2': ['-O2'],
'3': ['-O3'],
's': ['-Os'],
-} # type: T.Dict[str, T.List[str]]
+}
-gnulike_instruction_set_args = {
+gnulike_instruction_set_args: T.Dict[str, T.List[str]] = {
'mmx': ['-mmmx'],
'sse': ['-msse'],
'sse2': ['-msse2'],
'avx': ['-mavx'],
'avx2': ['-mavx2'],
'neon': ['-mfpu=neon'],
-} # type: T.Dict[str, T.List[str]]
+}
-gnu_symbol_visibility_args = {
+gnu_symbol_visibility_args: T.Dict[str, T.List[str]] = {
'': [],
'default': ['-fvisibility=default'],
'internal': ['-fvisibility=internal'],
'hidden': ['-fvisibility=hidden'],
'protected': ['-fvisibility=protected'],
'inlineshidden': ['-fvisibility=hidden', '-fvisibility-inlines-hidden'],
-} # type: T.Dict[str, T.List[str]]
+}
-gnu_color_args = {
+gnu_color_args: T.Dict[str, T.List[str]] = {
'auto': ['-fdiagnostics-color=auto'],
'always': ['-fdiagnostics-color=always'],
'never': ['-fdiagnostics-color=never'],
-} # type: T.Dict[str, T.List[str]]
+}
# Warnings collected from the GCC source and documentation. This is an
# objective set of all the warnings flags that apply to general projects: the
#
# Omitted warnings enabled elsewhere in meson:
# -Winvalid-pch (GCC 3.4.0)
-gnu_common_warning_args = {
+gnu_common_warning_args: T.Dict[str, T.List[str]] = {
"0.0.0": [
"-Wcast-qual",
"-Wconversion",
"-Wduplicated-branches",
],
"8.1.0": [
- "-Wattribute-alias=2",
"-Wcast-align=strict",
"-Wsuggest-attribute=cold",
"-Wsuggest-attribute=malloc",
],
+ "9.1.0": [
+ "-Wattribute-alias=2",
+ ],
"10.1.0": [
"-Wanalyzer-too-complex",
"-Warith-conversion",
"-Wopenacc-parallelism",
"-Wtrivial-auto-var-init",
],
-} # type: T.Dict[str, T.List[str]]
+}
# GCC warnings for C
# Omitted non-general or legacy warnings:
# -Wdeclaration-after-statement
# -Wtraditional
# -Wtraditional-conversion
-gnu_c_warning_args = {
+gnu_c_warning_args: T.Dict[str, T.List[str]] = {
"0.0.0": [
"-Wbad-function-cast",
"-Wmissing-prototypes",
"4.5.0": [
"-Wunsuffixed-float-constants",
],
-} # type: T.Dict[str, T.List[str]]
+}
# GCC warnings for C++
# Omitted non-general or legacy warnings:
# -Wctad-maybe-unsupported
# -Wnamespaces
# -Wtemplates
-gnu_cpp_warning_args = {
+gnu_cpp_warning_args: T.Dict[str, T.List[str]] = {
"0.0.0": [
"-Wctor-dtor-privacy",
"-Weffc++",
"-Wdeprecated-enum-float-conversion",
"-Winvalid-imported-macros",
],
-} # type: T.Dict[str, T.List[str]]
+}
# GCC warnings for Objective C and Objective C++
# Omitted non-general or legacy warnings:
# -Wtraditional
# -Wtraditional-conversion
-gnu_objc_warning_args = {
+gnu_objc_warning_args: T.Dict[str, T.List[str]] = {
"0.0.0": [
"-Wselector",
],
"-Wassign-intercept",
"-Wstrict-selector-match",
],
-} # type: T.Dict[str, T.List[str]]
+}
+_LANG_MAP = {
+ 'c': 'c',
+ 'cpp': 'c++',
+ 'objc': 'objective-c',
+ 'objcpp': 'objective-c++'
+}
@functools.lru_cache(maxsize=None)
def gnulike_default_include_dirs(compiler: T.Tuple[str, ...], lang: str) -> 'ImmutableListProtocol[str]':
- lang_map = {
- 'c': 'c',
- 'cpp': 'c++',
- 'objc': 'objective-c',
- 'objcpp': 'objective-c++'
- }
- if lang not in lang_map:
+ if lang not in _LANG_MAP:
return []
- lang = lang_map[lang]
+ lang = _LANG_MAP[lang]
env = os.environ.copy()
env["LC_ALL"] = 'C'
cmd = list(compiler) + [f'-x{lang}', '-E', '-v', '-']
_, stdout, _ = mesonlib.Popen_safe(cmd, stderr=subprocess.STDOUT, env=env)
parse_state = 0
- paths = [] # type: T.List[str]
+ paths: T.List[str] = []
for line in stdout.split('\n'):
line = line.strip(' \n\r\t')
if parse_state == 0:
self.base_options.add(OptionKey('b_sanitize'))
# All GCC-like backends can do assembly
self.can_compile_suffixes.add('s')
+ 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 ['-fprofile-generate']
def get_profile_use_args(self) -> T.List[str]:
- return ['-fprofile-use', '-fprofile-correction']
-
- def get_gui_app_args(self, value: bool) -> T.List[str]:
- return ['-mwindows' if value else '-mconsole']
+ return ['-fprofile-use']
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):
def _get_search_dirs(self, env: 'Environment') -> str:
extra_args = ['--print-search-dirs']
with self._build_wrapper('', env, extra_args=extra_args,
- dependencies=None, mode='compile',
+ dependencies=None, mode=CompileCheckMode.COMPILE,
want_output=True) as p:
return p.stdout
# pathlib treats empty paths as '.', so filter those out
paths = [p for p in pathstr.split(pathsep) if p]
- result = []
+ result: T.List[str] = []
for p in paths:
# GCC returns paths like this:
# /usr/lib/gcc/x86_64-linux-gnu/8/../../../../x86_64-linux-gnu/lib
args.append('-fno-omit-frame-pointer')
return args
- def get_output_args(self, target: str) -> T.List[str]:
- return ['-o', target]
+ def get_output_args(self, outputname: str) -> T.List[str]:
+ return ['-o', outputname]
def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]:
return ['-MD', '-MQ', outtarget, '-MF', outfile]
# 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.
- return self.get_preprocess_only_args() + ['-x', 'assembler-with-cpp']
+ lang = _LANG_MAP.get(self.language, 'assembler-with-cpp')
+ return self.get_preprocess_only_args() + [f'-x{lang}']
class GnuCompiler(GnuLikeCompiler):
return args
def supported_warn_args(self, warn_args_by_version: T.Dict[str, T.List[str]]) -> T.List[str]:
- result = []
+ result: T.List[str] = []
for version, warn_args in warn_args_by_version.items():
if mesonlib.version_compare(self.version, '>=' + version):
result += warn_args
return ['-fopenmp']
def has_arguments(self, args: T.List[str], env: 'Environment', code: str,
- mode: str) -> T.Tuple[bool, bool]:
+ 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
if linker == 'mold' and mesonlib.version_compare(version, '>=12.0.1'):
return ['-fuse-ld=mold']
return super().use_linker_args(linker, version)
+
+ def get_profile_use_args(self) -> T.List[str]:
+ return super().get_profile_use_args() + ['-fprofile-correction']
# 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
"""Abstractions for the Intel Compiler families.
minsize: -O2
"""
- BUILD_ARGS = {
+ BUILD_ARGS: T.Dict[str, T.List[str]] = {
'plain': [],
'debug': ["-g", "-traceback"],
'debugoptimized': ["-g", "-traceback"],
'release': [],
'minsize': [],
'custom': [],
- } # type: T.Dict[str, T.List[str]]
+ }
OPTIM_ARGS: T.Dict[str, T.List[str]] = {
'plain': [],
return ['-pch', '-pch_dir', os.path.join(pch_dir), '-x',
self.lang_header, '-include', header, '-x', 'none']
- def get_pch_name(self, header_name: str) -> str:
- return os.path.basename(header_name) + '.' + self.get_pch_suffix()
+ def get_pch_name(self, name: str) -> str:
+ return os.path.basename(name) + '.' + self.get_pch_suffix()
def openmp_flags(self) -> T.List[str]:
if mesonlib.version_compare(self.version, '>=15.0.0'):
"""Abstractions for ICL, the Intel compiler on Windows."""
- BUILD_ARGS = {
+ BUILD_ARGS: T.Dict[str, T.List[str]] = {
'plain': [],
'debug': ["/Zi", "/traceback"],
'debugoptimized': ["/Zi", "/traceback"],
'release': [],
'minsize': [],
'custom': [],
- } # type: T.Dict[str, T.List[str]]
+ }
OPTIM_ARGS: T.Dict[str, T.List[str]] = {
'plain': [],
# 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
"""Mixins for compilers that *are* linkers.
def get_buildtype_linker_args(self, buildtype: str) -> T.List[str]:
return []
- def get_link_debugfile_name(self, targetfile: str) -> str:
- return ''
+ def get_link_debugfile_name(self, targetfile: str) -> T.Optional[str]:
+ return None
def thread_flags(self, env: 'Environment') -> T.List[str]:
return []
--- /dev/null
+# Copyright 2012-2019 The Meson development team
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from __future__ import annotations
+
+"""Representations specific to the Metrowerks/Freescale Embedded C/C++ compiler family."""
+
+import os
+import typing as T
+
+from ...mesonlib import EnvironmentException, OptionKey
+
+if T.TYPE_CHECKING:
+ from ...envconfig import MachineInfo
+ from ...compilers.compilers import Compiler, CompileCheckMode
+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
+
+mwcc_buildtype_args: T.Dict[str, T.List[str]] = {
+ 'plain': [],
+ 'debug': [],
+ 'debugoptimized': [],
+ 'release': [],
+ 'minsize': [],
+ 'custom': [],
+}
+
+mwccarm_instruction_set_args: T.Dict[str, T.List[str]] = {
+ 'generic': ['-proc', 'generic'],
+ 'v4': ['-proc', 'v4'],
+ 'v4t': ['-proc', 'v4t'],
+ 'v5t': ['-proc', 'v5t'],
+ 'v5te': ['-proc', 'v5te'],
+ 'v6': ['-proc', 'v6'],
+ 'arm7tdmi': ['-proc', 'arm7tdmi'],
+ 'arm710t': ['-proc', 'arm710t'],
+ 'arm720t': ['-proc', 'arm720t'],
+ 'arm740t': ['-proc', 'arm740t'],
+ 'arm7ej': ['-proc', 'arm7ej'],
+ 'arm9tdmi': ['-proc', 'arm9tdmi'],
+ 'arm920t': ['-proc', 'arm920t'],
+ 'arm922t': ['-proc', 'arm922t'],
+ 'arm940t': ['-proc', 'arm940t'],
+ 'arm9ej': ['-proc', 'arm9ej'],
+ 'arm926ej': ['-proc', 'arm926ej'],
+ 'arm946e': ['-proc', 'arm946e'],
+ 'arm966e': ['-proc', 'arm966e'],
+ 'arm1020e': ['-proc', 'arm1020e'],
+ 'arm1022e': ['-proc', 'arm1022e'],
+ 'arm1026ej': ['-proc', 'arm1026ej'],
+ 'dbmx1': ['-proc', 'dbmx1'],
+ 'dbmxl': ['-proc', 'dbmxl'],
+ 'XScale': ['-proc', 'XScale'],
+ 'pxa255': ['-proc', 'pxa255'],
+ 'pxa261': ['-proc', 'pxa261'],
+ 'pxa262': ['-proc', 'pxa262'],
+ 'pxa263': ['-proc', 'pxa263']
+}
+
+mwcceppc_instruction_set_args: T.Dict[str, T.List[str]] = {
+ 'generic': ['-proc', 'generic'],
+ '401': ['-proc', '401'],
+ '403': ['-proc', '403'],
+ '505': ['-proc', '505'],
+ '509': ['-proc', '509'],
+ '555': ['-proc', '555'],
+ '601': ['-proc', '601'],
+ '602': ['-proc', '602'],
+ '603': ['-proc', '603'],
+ '603e': ['-proc', '603e'],
+ '604': ['-proc', '604'],
+ '604e': ['-proc', '604e'],
+ '740': ['-proc', '740'],
+ '750': ['-proc', '750'],
+ '801': ['-proc', '801'],
+ '821': ['-proc', '821'],
+ '823': ['-proc', '823'],
+ '850': ['-proc', '850'],
+ '860': ['-proc', '860'],
+ '7400': ['-proc', '7400'],
+ '7450': ['-proc', '7450'],
+ '8240': ['-proc', '8240'],
+ '8260': ['-proc', '8260'],
+ 'e500': ['-proc', 'e500'],
+ 'gekko': ['-proc', 'gekko'],
+}
+
+mwasmarm_instruction_set_args: T.Dict[str, T.List[str]] = {
+ 'arm4': ['-proc', 'arm4'],
+ 'arm4t': ['-proc', 'arm4t'],
+ 'arm4xm': ['-proc', 'arm4xm'],
+ 'arm4txm': ['-proc', 'arm4txm'],
+ 'arm5': ['-proc', 'arm5'],
+ 'arm5T': ['-proc', 'arm5T'],
+ 'arm5xM': ['-proc', 'arm5xM'],
+ 'arm5TxM': ['-proc', 'arm5TxM'],
+ 'arm5TE': ['-proc', 'arm5TE'],
+ 'arm5TExP': ['-proc', 'arm5TExP'],
+ 'arm6': ['-proc', 'arm6'],
+ 'xscale': ['-proc', 'xscale']
+}
+
+mwasmeppc_instruction_set_args: T.Dict[str, T.List[str]] = {
+ '401': ['-proc', '401'],
+ '403': ['-proc', '403'],
+ '505': ['-proc', '505'],
+ '509': ['-proc', '509'],
+ '555': ['-proc', '555'],
+ '56X': ['-proc', '56X'],
+ '601': ['-proc', '601'],
+ '602': ['-proc', '602'],
+ '603': ['-proc', '603'],
+ '603e': ['-proc', '603e'],
+ '604': ['-proc', '604'],
+ '604e': ['-proc', '604e'],
+ '740': ['-proc', '740'],
+ '74X': ['-proc', '74X'],
+ '750': ['-proc', '750'],
+ '75X': ['-proc', '75X'],
+ '801': ['-proc', '801'],
+ '821': ['-proc', '821'],
+ '823': ['-proc', '823'],
+ '850': ['-proc', '850'],
+ '85X': ['-proc', '85X'],
+ '860': ['-proc', '860'],
+ '86X': ['-proc', '86X'],
+ '87X': ['-proc', '87X'],
+ '88X': ['-proc', '88X'],
+ '5100': ['-proc', '5100'],
+ '5200': ['-proc', '5200'],
+ '7400': ['-proc', '7400'],
+ '744X': ['-proc', '744X'],
+ '7450': ['-proc', '7450'],
+ '745X': ['-proc', '745X'],
+ '82XX': ['-proc', '82XX'],
+ '8240': ['-proc', '8240'],
+ '824X': ['-proc', '824X'],
+ '8260': ['-proc', '8260'],
+ '827X': ['-proc', '827X'],
+ '8280': ['-proc', '8280'],
+ 'e300': ['-proc', 'e300'],
+ 'e300c2': ['-proc', 'e300c2'],
+ 'e300c3': ['-proc', 'e300c3'],
+ 'e300c4': ['-proc', 'e300c4'],
+ 'e600': ['-proc', 'e600'],
+ '85xx': ['-proc', '85xx'],
+ 'e500': ['-proc', 'e500'],
+ 'e500v2': ['-proc', 'e500v2'],
+ 'Zen': ['-proc', 'Zen'],
+ '5565': ['-proc', '5565'],
+ '5674': ['-proc', '5674'],
+ 'gekko': ['-proc', 'gekko'],
+ 'generic': ['-proc', 'generic'],
+}
+
+mwcc_optimization_args: T.Dict[str, T.List[str]] = {
+ 'plain': [],
+ '0': ['-O0'],
+ 'g': ['-Op'],
+ '1': ['-O1'],
+ '2': ['-O2'],
+ '3': ['-O4,p'],
+ 's': ['-Os']
+}
+
+mwcc_debug_args: T.Dict[bool, T.List[str]] = {
+ False: [],
+ True: ['-g']
+}
+
+
+class MetrowerksCompiler(Compiler):
+ id = 'mwcc'
+
+ # These compilers can actually invoke the linker, but they choke on
+ # linker-specific flags. So it's best to invoke the linker directly
+ INVOKES_LINKER = False
+
+ def __init__(self) -> None:
+ if not self.is_cross:
+ raise EnvironmentException(f'{id} supports only cross-compilation.')
+
+ self.base_options = {
+ OptionKey(o) for o in ['b_pch', 'b_ndebug']}
+
+ default_warn_args: T.List[str] = []
+ self.warn_args: T.Dict[str, T.List[str]] = {
+ '0': ['-w', 'off'],
+ '1': default_warn_args,
+ '2': default_warn_args + ['-w', 'most'],
+ '3': default_warn_args + ['-w', 'all'],
+ 'everything': default_warn_args + ['-w', 'full']}
+
+ def depfile_for_object(self, objfile: str) -> T.Optional[str]:
+ # Earlier versions of these compilers do not support specifying
+ # a custom name for a depfile, and can only generate '<input_file>.d'
+ return os.path.splitext(objfile)[0] + '.' + self.get_depfile_suffix()
+
+ def get_always_args(self) -> T.List[str]:
+ return ['-gccinc']
+
+ def get_buildtype_args(self, buildtype: str) -> T.List[str]:
+ return mwcc_buildtype_args[buildtype]
+
+ def get_compiler_check_args(self, mode: CompileCheckMode) -> T.List[str]:
+ return []
+
+ def get_compile_only_args(self) -> T.List[str]:
+ return ['-c']
+
+ def get_debug_args(self, is_debug: bool) -> T.List[str]:
+ return mwcc_debug_args[is_debug]
+
+ def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]:
+ # Check comment in depfile_for_object()
+ return ['-gccdep', '-MD']
+
+ def get_depfile_suffix(self) -> str:
+ return 'd'
+
+ def get_include_args(self, path: str, is_system: bool) -> T.List[str]:
+ if not path:
+ path = '.'
+ return ['-I' + path]
+
+ def get_no_optimization_args(self) -> T.List[str]:
+ return ['-opt', 'off']
+
+ def get_no_stdinc_args(self) -> T.List[str]:
+ return ['-nostdinc']
+
+ def get_no_stdlib_link_args(self) -> T.List[str]:
+ return ['-nostdlib']
+
+ def get_optimization_args(self, optimization_level: str) -> T.List[str]:
+ return mwcc_optimization_args[optimization_level]
+
+ def get_output_args(self, outputname: str) -> T.List[str]:
+ return ['-o', outputname]
+
+ def get_pic_args(self) -> T.List[str]:
+ return ['-pic']
+
+ def get_preprocess_only_args(self) -> T.List[str]:
+ return ['-E']
+
+ def get_preprocess_to_file_args(self) -> T.List[str]:
+ return ['-P']
+
+ def get_pch_use_args(self, pch_dir: str, header: str) -> T.List[str]:
+ return ['-prefix', self.get_pch_name(header)]
+
+ def get_pch_name(self, name: str) -> str:
+ return os.path.basename(name) + '.' + self.get_pch_suffix()
+
+ def get_pch_suffix(self) -> str:
+ return 'mch'
+
+ def get_warn_args(self, level: str) -> T.List[str]:
+ return self.warn_args[level]
+
+ def get_werror_args(self) -> T.List[str]:
+ return ['-w', 'error']
+
+ @classmethod
+ def _unix_args_to_native(cls, args: T.List[str], info: MachineInfo) -> T.List[str]:
+ result: T.List[str] = []
+ for i in args:
+ if i.startswith('-D'):
+ i = '-D' + i[2:]
+ if i.startswith('-I'):
+ i = '-I' + i[2:]
+ if i.startswith('-Wl,-rpath='):
+ continue
+ elif i == '--print-search-dirs':
+ continue
+ elif i.startswith('-L'):
+ continue
+ result.append(i)
+ return result
+
+ 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):
+ if i[:2] == '-I':
+ parameter_list[idx] = i[:9] + os.path.normpath(os.path.join(build_dir, i[9:]))
+
+ return parameter_list
# 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
"""Abstractions for the PGI family of compilers."""
# do). This gives up DRYer type checking, with no runtime impact
Compiler = object
-pgi_buildtype_args = {
+pgi_buildtype_args: T.Dict[str, T.List[str]] = {
'plain': [],
'debug': [],
'debugoptimized': [],
'release': [],
'minsize': [],
'custom': [],
-} # type: T.Dict[str, T.List[str]]
+}
class PGICompiler(Compiler):
# do). This gives up DRYer type checking, with no runtime impact
Compiler = object
-ti_buildtype_args = {
+ti_buildtype_args: T.Dict[str, T.List[str]] = {
'plain': [],
'debug': [],
'debugoptimized': [],
'release': [],
'minsize': [],
'custom': [],
-} # type: T.Dict[str, T.List[str]]
+}
-ti_optimization_args = {
+ti_optimization_args: T.Dict[str, T.List[str]] = {
'plain': [],
'0': ['-O0'],
'g': ['-Ooff'],
'2': ['-O2'],
'3': ['-O3'],
's': ['-O4']
-} # type: T.Dict[str, T.List[str]]
+}
-ti_debug_args = {
+ti_debug_args: T.Dict[bool, T.List[str]] = {
False: [],
True: ['-g']
-} # type: T.Dict[bool, T.List[str]]
+}
class TICompiler(Compiler):
self.can_compile_suffixes.add('asm') # Assembly
self.can_compile_suffixes.add('cla') # Control Law Accelerator (CLA) used in C2000
- default_warn_args = [] # type: T.List[str]
- self.warn_args = {'0': [],
- '1': default_warn_args,
- '2': default_warn_args + [],
- '3': default_warn_args + [],
- 'everything': default_warn_args + []} # type: T.Dict[str, T.List[str]]
+ default_warn_args: T.List[str] = []
+ self.warn_args: T.Dict[str, T.List[str]] = {
+ '0': [],
+ '1': default_warn_args,
+ '2': default_warn_args + [],
+ '3': default_warn_args + [],
+ 'everything': default_warn_args + []}
def get_pic_args(self) -> T.List[str]:
# PIC support is not enabled by default for TI compilers,
def get_no_optimization_args(self) -> T.List[str]:
return ['-Ooff']
- def get_output_args(self, target: str) -> T.List[str]:
- return [f'--output_file={target}']
+ def get_output_args(self, outputname: str) -> T.List[str]:
+ return [f'--output_file={outputname}']
def get_werror_args(self) -> T.List[str]:
return ['--emit_warnings_as_errors']
@classmethod
def _unix_args_to_native(cls, args: T.List[str], info: MachineInfo) -> T.List[str]:
- result = []
+ result: T.List[str] = []
for i in args:
if i.startswith('-D'):
i = '--define=' + i[2:]
# 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
"""Abstractions to simplify compilers that implement an MSVC compatible
interface.
from ... import arglist
from ... import mesonlib
from ... import mlog
+from mesonbuild.compilers.compilers import CompileCheckMode
if T.TYPE_CHECKING:
from ...environment import Environment
# do). This gives up DRYer type checking, with no runtime impact
Compiler = object
-vs32_instruction_set_args = {
+vs32_instruction_set_args: T.Dict[str, T.Optional[T.List[str]]] = {
'mmx': ['/arch:SSE'], # There does not seem to be a flag just for MMX
'sse': ['/arch:SSE'],
'sse2': ['/arch:SSE2'],
'avx': ['/arch:AVX'],
'avx2': ['/arch:AVX2'],
'neon': None,
-} # T.Dicst[str, T.Optional[T.List[str]]]
+}
# The 64 bit compiler defaults to /arch:avx.
-vs64_instruction_set_args = {
+vs64_instruction_set_args: T.Dict[str, T.Optional[T.List[str]]] = {
'mmx': ['/arch:AVX'],
'sse': ['/arch:AVX'],
'sse2': ['/arch:AVX'],
'avx': ['/arch:AVX'],
'avx2': ['/arch:AVX2'],
'neon': None,
-} # T.Dicst[str, T.Optional[T.List[str]]]
+}
-msvc_optimization_args = {
+msvc_optimization_args: T.Dict[str, T.List[str]] = {
'plain': [],
'0': ['/Od'],
'g': [], # No specific flag to optimize debugging, /Zi or /ZI will create debug information
'2': ['/O2'],
'3': ['/O2', '/Gw'],
's': ['/O1', '/Gw'],
-} # type: T.Dict[str, T.List[str]]
+}
-msvc_debug_args = {
+msvc_debug_args: T.Dict[bool, T.List[str]] = {
False: [],
True: ['/Zi']
-} # type: T.Dict[bool, T.List[str]]
+}
class VisualStudioLikeCompiler(Compiler, metaclass=abc.ABCMeta):
std_warn_args = ['/W3']
std_opt_args = ['/O2']
ignore_libs = arglist.UNIXY_COMPILER_INTERNAL_LIBS + ['execinfo']
- internal_libs = [] # type: T.List[str]
+ internal_libs: T.List[str] = []
- crt_args = {
+ crt_args: T.Dict[str, T.List[str]] = {
'none': [],
'md': ['/MD'],
'mdd': ['/MDd'],
'mt': ['/MT'],
'mtd': ['/MTd'],
- } # type: T.Dict[str, T.List[str]]
+ }
# /showIncludes is needed for build dependency tracking in Ninja
# See: https://ninja-build.org/manual.html#_deps
# Assume UTF-8 sources by default, but self.unix_args_to_native() removes it
# if `/source-charset` is set too.
+ # It is also dropped if Visual Studio 2013 or earlier is used, since it would
+ # not be supported in that case.
always_args = ['/nologo', '/showIncludes', '/utf-8']
- warn_args = {
+ warn_args: T.Dict[str, T.List[str]] = {
'0': [],
'1': ['/W2'],
'2': ['/W3'],
'3': ['/W4'],
'everything': ['/Wall'],
- } # type: T.Dict[str, T.List[str]]
+ }
INVOKES_LINKER = False
# Override CCompiler.get_always_args
def get_always_args(self) -> T.List[str]:
- return self.always_args
+ # TODO: use ImmutableListProtocol[str] here instead
+ return self.always_args.copy()
def get_pch_suffix(self) -> str:
return 'pch'
- def get_pch_name(self, header: str) -> str:
- chopped = os.path.basename(header).split('.')[:-1]
+ def get_pch_name(self, name: str) -> str:
+ chopped = os.path.basename(name).split('.')[:-1]
chopped.append(self.get_pch_suffix())
pchname = '.'.join(chopped)
return pchname
raise mesonlib.MesonException('VS only supports address sanitizer at the moment.')
return ['/fsanitize=address']
- def get_output_args(self, target: str) -> T.List[str]:
+ def get_output_args(self, outputname: str) -> T.List[str]:
if self.mode == 'PREPROCESSOR':
- return ['/Fi' + target]
- if target.endswith('.exe'):
- return ['/Fe' + target]
- return ['/Fo' + target]
+ return ['/Fi' + outputname]
+ if outputname.endswith('.exe'):
+ return ['/Fe' + outputname]
+ return ['/Fo' + outputname]
def get_buildtype_args(self, buildtype: str) -> T.List[str]:
return []
return ['/DEF:' + defsfile]
def gen_pch_args(self, header: str, source: str, pchname: str) -> T.Tuple[str, T.List[str]]:
- objname = os.path.splitext(pchname)[0] + '.obj'
+ objname = os.path.splitext(source)[0] + '.obj'
return objname, ['/Yc' + header, '/Fp' + pchname, '/Fo' + objname]
def openmp_flags(self) -> T.List[str]:
continue
# cl.exe does not allow specifying both, so remove /utf-8 that we
# added automatically in the case the user overrides it manually.
- elif i.startswith('/source-charset:') or i.startswith('/execution-charset:'):
+ elif (i.startswith('/source-charset:')
+ or i.startswith('/execution-charset:')
+ or i == '/validate-charset-'):
try:
result.remove('/utf-8')
except ValueError:
@classmethod
def native_args_to_unix(cls, args: T.List[str]) -> T.List[str]:
- result = []
+ result: T.List[str] = []
for arg in args:
if arg.startswith(('/LIBPATH:', '-LIBPATH:')):
result.append('-L' + arg[9:])
# 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: str) -> T.Tuple[bool, bool]:
- warning_text = '4044' if mode == 'link' else '9002'
+ def has_arguments(self, args: T.List[str], env: 'Environment', 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:
if p.returncode != 0:
return False, p.cached
return os.environ['INCLUDE'].split(os.pathsep)
def get_crt_compile_args(self, crt_val: str, buildtype: str) -> T.List[str]:
- if crt_val in self.crt_args:
- return self.crt_args[crt_val]
- assert crt_val in {'from_buildtype', 'static_from_buildtype'}
- dbg = 'mdd'
- rel = 'md'
- if crt_val == 'static_from_buildtype':
- dbg = 'mtd'
- rel = 'mt'
- # Match what build type flags used to do.
- if buildtype == 'plain':
- return []
- elif buildtype == 'debug':
- return self.crt_args[dbg]
- elif buildtype == 'debugoptimized':
- return self.crt_args[rel]
- elif buildtype == 'release':
- return self.crt_args[rel]
- elif buildtype == 'minsize':
- return self.crt_args[rel]
- else:
- assert buildtype == 'custom'
- raise mesonlib.EnvironmentException('Requested C runtime based on buildtype, but buildtype is "custom".')
+ 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]:
# MSVC doesn't have __attribute__ like Clang and GCC do, so just return
id = 'msvc'
+ def __init__(self, target: str):
+ super().__init__(target)
+
+ # Visual Studio 2013 and earlier don't support the /utf-8 argument.
+ # We want to remove it. We also want to make an explicit copy so we
+ # don't mutate class constant state
+ if mesonlib.version_compare(self.version, '<19.00') and '/utf-8' in self.always_args:
+ self.always_args = [r for r in self.always_args if r != '/utf-8']
+
def get_compile_debugfile_args(self, rel_obj: str, pch: bool = False) -> T.List[str]:
args = super().get_compile_debugfile_args(rel_obj, pch)
# When generating a PDB file with PCH, all compile commands write
args = ['/FS'] + args
return args
+ # Override CCompiler.get_always_args
+ # We want to drop '/utf-8' for Visual Studio 2013 and earlier
+ def get_always_args(self) -> T.List[str]:
+ return self.always_args
+
def get_instruction_set_args(self, instruction_set: str) -> T.Optional[T.List[str]]:
if self.version.split('.')[0] == '16' and instruction_set == 'avx':
# VS documentation says that this exists and should work, but
# it does not. The headers do not contain AVX intrinsics
- # and they can not be called.
+ # and they cannot be called.
return None
return super().get_instruction_set_args(instruction_set)
# Assembly
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: str) -> T.Tuple[bool, bool]:
- if mode != 'link':
+ def has_arguments(self, args: T.List[str], env: 'Environment', 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)
def get_dependency_compile_args(self, dep: 'Dependency') -> T.List[str]:
if dep.get_include_type() == 'system':
- converted = []
+ converted: T.List[str] = []
for i in dep.get_compile_args():
if i.startswith('-isystem'):
converted += ['/clang:' + i]
# do). This gives up DRYer type checking, with no runtime impact
Compiler = object
-xc16_buildtype_args = {
+xc16_buildtype_args: T.Dict[str, T.List[str]] = {
'plain': [],
'debug': [],
'debugoptimized': [],
'release': [],
'minsize': [],
'custom': [],
-} # type: T.Dict[str, T.List[str]]
+}
-xc16_optimization_args = {
+xc16_optimization_args: T.Dict[str, T.List[str]] = {
'plain': [],
'0': ['-O0'],
'g': ['-O0'],
'2': ['-O2'],
'3': ['-O3'],
's': ['-Os']
-} # type: T.Dict[str, T.List[str]]
+}
-xc16_debug_args = {
+xc16_debug_args: T.Dict[bool, T.List[str]] = {
False: [],
True: []
-} # type: T.Dict[bool, T.List[str]]
+}
class Xc16Compiler(Compiler):
raise EnvironmentException('xc16 supports only cross-compilation.')
# Assembly
self.can_compile_suffixes.add('s')
- default_warn_args = [] # type: T.List[str]
+ self.can_compile_suffixes.add('sx')
+ default_warn_args: T.List[str] = []
self.warn_args = {'0': [],
'1': default_warn_args,
'2': default_warn_args + [],
'3': default_warn_args + [],
- 'everything': default_warn_args + []} # type: T.Dict[str, T.List[str]]
+ 'everything': default_warn_args + []}
def get_always_args(self) -> T.List[str]:
return []
from ..programs import ExternalProgram
from ..envconfig import MachineInfo
from ..environment import Environment
- from ..linkers import DynamicLinker
+ from ..linkers.linkers import DynamicLinker
from ..mesonlib import MachineChoice
from ..programs import ExternalProgram
from ..envconfig import MachineInfo
from ..environment import Environment
- from ..linkers import DynamicLinker
+ from ..linkers.linkers import DynamicLinker
from ..mesonlib import MachineChoice
class ObjCPPCompiler(CLikeCompiler, Compiler):
opts.update({
OptionKey('std', machine=self.for_machine, lang='cpp'): coredata.UserComboOption(
'C++ language standard to use',
- ['none', 'c++98', 'c++11', 'c++14', 'c++17', 'gnu++98', 'gnu++11', 'gnu++14', 'gnu++17'],
+ ['none', 'c++98', 'c++11', 'c++14', 'c++17', 'c++20', 'c++2b',
+ 'gnu++98', 'gnu++11', 'gnu++14', 'gnu++17', 'gnu++20',
+ 'gnu++2b'],
'none',
)
})
import subprocess, os.path
import textwrap
+import re
import typing as T
from .. import coredata
-from ..mesonlib import EnvironmentException, MesonException, Popen_safe, OptionKey
+from ..mesonlib import EnvironmentException, MesonException, Popen_safe_logged, OptionKey
from .compilers import Compiler, rust_buildtype_args, clike_debug_args
if T.TYPE_CHECKING:
from ..coredata import MutableKeyedOptionDictType, KeyedOptionDictType
from ..envconfig import MachineInfo
from ..environment import Environment # noqa: F401
- from ..linkers import DynamicLinker
+ from ..linkers.linkers import DynamicLinker
from ..mesonlib import MachineChoice
from ..programs import ExternalProgram
+ from ..dependencies import Dependency
-rust_optimization_args = {
+rust_optimization_args: T.Dict[str, T.List[str]] = {
'plain': [],
'0': [],
'g': ['-C', 'opt-level=0'],
'2': ['-C', 'opt-level=2'],
'3': ['-C', 'opt-level=3'],
's': ['-C', 'opt-level=s'],
-} # type: T.Dict[str, T.List[str]]
+}
class RustCompiler(Compiler):
'3': ['-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
+ 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'],
+ }
+
def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice,
is_cross: bool, info: 'MachineInfo',
exe_wrapper: T.Optional['ExternalProgram'] = None,
is_cross=is_cross, full_version=full_version,
linker=linker)
self.exe_wrapper = exe_wrapper
- self.base_options.add(OptionKey('b_colorout'))
+ self.base_options.update({OptionKey(o) for o in ['b_colorout', 'b_ndebug']})
if 'link' in self.linker.id:
self.base_options.add(OptionKey('b_vscrt'))
+ self.native_static_libs: T.List[str] = []
def needs_static_linker(self) -> bool:
return False
'''fn main() {
}
'''))
- pc = subprocess.Popen(self.exelist + ['-o', output_name, source_name],
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- cwd=work_dir)
- _stdo, _stde = pc.communicate()
- assert isinstance(_stdo, bytes)
- assert isinstance(_stde, bytes)
- stdo = _stdo.decode('utf-8', errors='replace')
- stde = _stde.decode('utf-8', errors='replace')
+
+ cmdlist = self.exelist + ['-o', output_name, source_name]
+ pc, stdo, stde = Popen_safe_logged(cmdlist, cwd=work_dir)
if pc.returncode != 0:
- raise EnvironmentException('Rust compiler {} can not compile programs.\n{}\n{}'.format(
- self.name_string(),
- stdo,
- stde))
+ raise EnvironmentException(f'Rust compiler {self.name_string()} cannot compile programs.')
if self.is_cross:
if self.exe_wrapper is None:
# Can't check if the binaries run so we have to assume they do
pe = subprocess.Popen(cmdlist, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
pe.wait()
if pe.returncode != 0:
- raise EnvironmentException('Executables created by Rust compiler %s are not runnable.' % self.name_string())
+ raise EnvironmentException(f'Executables created by Rust compiler {self.name_string()} are not runnable.')
+ # Get libraries needed to link with a Rust staticlib
+ cmdlist = self.exelist + ['--crate-type', 'staticlib', '--print', 'native-static-libs', source_name]
+ p, stdo, stde = Popen_safe_logged(cmdlist, cwd=work_dir)
+ if p.returncode == 0:
+ match = re.search('native-static-libs: (.*)$', stde, re.MULTILINE)
+ if match:
+ # Exclude some well known libraries that we don't need because they
+ # are always part of C/C++ linkers. Rustc probably should not print
+ # them, pkg-config for example never specify them.
+ # FIXME: https://github.com/rust-lang/rust/issues/55120
+ exclude = {'-lc', '-lgcc_s', '-lkernel32', '-ladvapi32'}
+ self.native_static_libs = [i for i in match.group(1).split() if i not in exclude]
def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]:
return ['--dep-info', outfile]
def get_sysroot(self) -> str:
cmd = self.get_exelist(ccache=False) + ['--print', 'sysroot']
- p, stdo, stde = Popen_safe(cmd)
+ p, stdo, stde = Popen_safe_logged(cmd)
return stdo.split('\n', maxsplit=1)[0]
def get_debug_args(self, is_debug: bool) -> T.List[str]:
),
}
+ def get_dependency_compile_args(self, dep: 'Dependency') -> T.List[str]:
+ # Rust doesn't have dependency compile arguments so simply return
+ # nothing here. Dependencies are linked and all required metadata is
+ # provided by the linker flags.
+ return []
+
def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
args = []
key = OptionKey('std', machine=self.for_machine, lang=self.language)
# Rust handles this for us, we don't need to do anything
return []
+ def get_crt_link_args(self, crt_val: str, buildtype: str) -> T.List[str]:
+ if self.linker.id not in {'link', 'lld-link'}:
+ return []
+ return self.MSVCRT_ARGS[self.get_crt_val(crt_val, buildtype)]
+
def get_colorout_args(self, colortype: str) -> T.List[str]:
if colortype in {'always', 'never', 'auto'}:
return [f'--color={colortype}']
return self._WARNING_LEVELS["0"]
def get_pic_args(self) -> T.List[str]:
- # This defaults to
- return ['-C', 'relocation-model=pic']
+ # relocation-model=pic is rustc's default already.
+ return []
def get_pie_args(self) -> T.List[str]:
# Rustc currently has no way to toggle this, it's controlled by whether
# pic is on by rustc
return []
+ 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']
+
class ClippyRustCompiler(RustCompiler):
if T.TYPE_CHECKING:
from ..envconfig import MachineInfo
from ..environment import Environment
- from ..linkers import DynamicLinker
+ from ..linkers.linkers import DynamicLinker
from ..mesonlib import MachineChoice
-swift_optimization_args = {
+swift_optimization_args: T.Dict[str, T.List[str]] = {
'plain': [],
'0': [],
'g': [],
'2': ['-O'],
'3': ['-O'],
's': ['-O'],
-} # type: T.Dict[str, T.List[str]]
+}
class SwiftCompiler(Compiler):
pc = subprocess.Popen(self.exelist + extra_flags + ['-emit-executable', '-o', output_name, src], cwd=work_dir)
pc.wait()
if pc.returncode != 0:
- raise EnvironmentException('Swift compiler %s can not compile programs.' % self.name_string())
+ raise EnvironmentException('Swift compiler %s cannot compile programs.' % self.name_string())
if self.is_cross:
# Can't check if the binaries run so we have to assume they do
return
import typing as T
from .. import mlog
-from ..mesonlib import EnvironmentException, version_compare, OptionKey
-
-from .compilers import Compiler, LibType
+from ..mesonlib import EnvironmentException, version_compare, LibType, OptionKey
+from .compilers import CompileCheckMode, Compiler
if T.TYPE_CHECKING:
from ..envconfig import MachineInfo
def get_debug_args(self, is_debug: bool) -> T.List[str]:
return ['--debug'] if is_debug else []
- def get_output_args(self, target: str) -> T.List[str]:
+ def get_output_args(self, outputname: str) -> T.List[str]:
return [] # Because compiles into C.
def get_compile_only_args(self) -> T.List[str]:
def get_always_args(self) -> T.List[str]:
return ['-C']
- def get_warn_args(self, warning_level: str) -> T.List[str]:
+ def get_warn_args(self, level: str) -> T.List[str]:
return []
def get_no_warn_args(self) -> T.List[str]:
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='compile') as p:
+ with self.cached_compile(code, environment.coredata, extra_args=extra_flags, mode=CompileCheckMode.COMPILE) as p:
if p.returncode != 0:
- msg = f'Vala compiler {self.name_string()!r} can not compile programs'
+ msg = f'Vala compiler {self.name_string()!r} cannot compile programs'
raise EnvironmentException(msg)
def get_buildtype_args(self, buildtype: str) -> T.List[str]:
return []
def find_library(self, libname: str, env: 'Environment', extra_dirs: T.List[str],
- libtype: LibType = LibType.PREFER_SHARED) -> T.Optional[T.List[str]]:
+ libtype: LibType = LibType.PREFER_SHARED, lib_prefix_warning: bool = True) -> 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
args += env.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='compile') as p:
+ with self.cached_compile(code, env.coredata, extra_args=args, mode=CompileCheckMode.COMPILE) as p:
if p.returncode == 0:
return vapi_args
# Not found? Try to find the vapi file itself.
-# Copyright 2012-2022 The Meson development team
-
+# Copyright 2013-2023 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
# 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 copy
from . import mlog, mparser
import pickle, os, uuid
import sys
from itertools import chain
from pathlib import PurePath
-from collections import OrderedDict
+from collections import OrderedDict, abc
+from dataclasses import dataclass
+
from .mesonlib import (
HoldableObject,
MesonException, EnvironmentException, MachineChoice, PerMachine,
PerMachineDefaultable, default_libdir, default_libexecdir,
- default_prefix, split_args, OptionKey, OptionType, stringlistify,
- pickle_load, replace_if_different
+ default_prefix, default_datadir, default_includedir, default_infodir,
+ default_localedir, default_mandir, default_sbindir, default_sysconfdir,
+ split_args, OptionKey, OptionType, stringlistify,
+ pickle_load
)
from .wrap import WrapMode
import ast
if T.TYPE_CHECKING:
from . import dependencies
- from .compilers.compilers import Compiler, CompileResult
+ from .compilers.compilers import Compiler, CompileResult, RunResult, CompileCheckMode
from .dependencies.detect import TV_DepID
from .environment import Environment
- from .mesonlib import OptionOverrideProxy, FileOrString
+ from .mesonlib import FileOrString
from .cmake.traceparser import CMakeCacheEntry
- OptionDictType = T.Union[T.Dict[str, 'UserOption[T.Any]'], OptionOverrideProxy]
+ OptionDictType = T.Union[T.Dict[str, 'UserOption[T.Any]'], 'OptionsView']
MutableKeyedOptionDictType = T.Dict['OptionKey', 'UserOption[T.Any]']
- KeyedOptionDictType = T.Union[MutableKeyedOptionDictType, OptionOverrideProxy]
- CompilerCheckCacheKey = T.Tuple[T.Tuple[str, ...], str, FileOrString, T.Tuple[str, ...], str]
+ KeyedOptionDictType = T.Union[MutableKeyedOptionDictType, 'OptionsView']
+ 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]]
#
# 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.0.0'
+version = '1.3.0'
+
+# 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
+# using a feature introduced in 1.2.0 when using Meson 1.1.99.
+stable_version = version
+if stable_version.endswith('.99'):
+ stable_version_array = stable_version.split('.')
+ stable_version_array[-1] = '0'
+ stable_version_array[-2] = str(int(stable_version_array[-2]) + 1)
+ stable_version = '.'.join(stable_version_array)
-backendlist = ['ninja', 'vs', 'vs2010', 'vs2012', 'vs2013', 'vs2015', 'vs2017', 'vs2019', 'vs2022', 'xcode']
+backendlist = ['ninja', 'vs', 'vs2010', 'vs2012', 'vs2013', 'vs2015', 'vs2017', 'vs2019', 'vs2022', 'xcode', 'none']
+genvslitelist = ['vs2022']
+buildtypelist = ['plain', 'debug', 'debugoptimized', 'release', 'minsize', 'custom']
-default_yielding = False
+DEFAULT_YIELDING = False
# Can't bind this near the class method it seems, sadly.
_T = T.TypeVar('_T')
+def get_genvs_default_buildtype_list() -> list[str]:
+ # just debug, debugoptimized, and release for now
+ # but this should probably be configurable through some extra option, alongside --genvslite.
+ return buildtypelist[1:-2]
+
+
class MesonVersionMismatchException(MesonException):
'''Build directory generated with Meson version is incompatible with current version'''
- def __init__(self, old_version: str, current_version: str) -> None:
+ def __init__(self, old_version: str, current_version: str, extra_msg: str = '') -> None:
super().__init__(f'Build directory has been generated with Meson version {old_version}, '
- f'which is incompatible with the current version {current_version}.')
+ f'which is incompatible with the current version {current_version}.'
+ + extra_msg)
self.old_version = old_version
self.current_version = current_version
class UserOption(T.Generic[_T], HoldableObject):
- def __init__(self, description: str, choices: T.Optional[T.Union[str, T.List[_T]]], yielding: T.Optional[bool]):
+ def __init__(self, description: str, choices: T.Optional[T.Union[str, T.List[_T]]],
+ yielding: bool,
+ deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False):
super().__init__()
self.choices = choices
self.description = description
- if yielding is None:
- yielding = default_yielding
if not isinstance(yielding, bool):
raise MesonException('Value of "yielding" must be a boolean.')
self.yielding = yielding
- self.deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False
+ self.deprecated = deprecated
+ self.readonly = False
def listify(self, value: T.Any) -> T.List[T.Any]:
return [value]
def validate_value(self, value: T.Any) -> _T:
raise RuntimeError('Derived option class did not override validate_value.')
- def set_value(self, newvalue: T.Any) -> None:
+ def set_value(self, newvalue: T.Any) -> bool:
+ oldvalue = getattr(self, 'value', None)
self.value = self.validate_value(newvalue)
+ return self.value != oldvalue
class UserStringOption(UserOption[str]):
- def __init__(self, description: str, value: T.Any, yielding: T.Optional[bool] = None):
- super().__init__(description, None, yielding)
+ def __init__(self, description: str, value: T.Any, yielding: bool = DEFAULT_YIELDING,
+ deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False):
+ super().__init__(description, None, yielding, deprecated)
self.set_value(value)
def validate_value(self, value: T.Any) -> str:
return value
class UserBooleanOption(UserOption[bool]):
- def __init__(self, description: str, value, yielding: T.Optional[bool] = None) -> None:
- super().__init__(description, [True, False], yielding)
+ def __init__(self, description: str, value: bool, yielding: bool = DEFAULT_YIELDING,
+ deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False):
+ super().__init__(description, [True, False], yielding, deprecated)
self.set_value(value)
def __bool__(self) -> bool:
raise MesonException('Value %s is not boolean (true or false).' % value)
class UserIntegerOption(UserOption[int]):
- def __init__(self, description: str, value: T.Any, yielding: T.Optional[bool] = None):
+ def __init__(self, description: str, value: T.Any, yielding: bool = DEFAULT_YIELDING,
+ deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False):
min_value, max_value, default_value = value
self.min_value = min_value
self.max_value = max_value
- c = []
+ c: T.List[str] = []
if min_value is not None:
c.append('>=' + str(min_value))
if max_value is not None:
c.append('<=' + str(max_value))
choices = ', '.join(c)
- super().__init__(description, choices, yielding)
+ super().__init__(description, choices, yielding, deprecated)
self.set_value(default_value)
def validate_value(self, value: T.Any) -> int:
return oct(int(self))
class UserUmaskOption(UserIntegerOption, UserOption[T.Union[str, OctalInt]]):
- def __init__(self, description: str, value: T.Any, yielding: T.Optional[bool] = None):
- super().__init__(description, (0, 0o777, value), yielding)
+ def __init__(self, description: str, value: T.Any, yielding: bool = DEFAULT_YIELDING,
+ deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False):
+ super().__init__(description, (0, 0o777, value), yielding, deprecated)
self.choices = ['preserve', '0000-0777']
def printable_value(self) -> str:
return format(self.value, '04o')
def validate_value(self, value: T.Any) -> T.Union[str, OctalInt]:
- if value is None or value == 'preserve':
+ if value == 'preserve':
return 'preserve'
return OctalInt(super().validate_value(value))
raise MesonException(f'Invalid mode: {e}')
class UserComboOption(UserOption[str]):
- def __init__(self, description: str, choices: T.List[str], value: T.Any, yielding: T.Optional[bool] = None):
- super().__init__(description, choices, yielding)
+ def __init__(self, description: str, choices: T.List[str], value: T.Any,
+ yielding: bool = DEFAULT_YIELDING,
+ deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False):
+ super().__init__(description, choices, yielding, deprecated)
if not isinstance(self.choices, list):
raise MesonException('Combo choices must be an array.')
for i in self.choices:
return value
class UserArrayOption(UserOption[T.List[str]]):
- def __init__(self, description: str, value: T.Union[str, T.List[str]], split_args: bool = False, user_input: bool = False, allow_dups: bool = False, **kwargs: T.Any) -> None:
- super().__init__(description, kwargs.get('choices', []), yielding=kwargs.get('yielding', None))
+ def __init__(self, description: str, value: T.Union[str, T.List[str]],
+ split_args: bool = False,
+ allow_dups: bool = False, yielding: bool = DEFAULT_YIELDING,
+ choices: T.Optional[T.List[str]] = None,
+ deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False):
+ super().__init__(description, choices if choices is not None else [], yielding, deprecated)
self.split_args = split_args
self.allow_dups = allow_dups
- self.value = self.validate_value(value, user_input=user_input)
-
- def listify(self, value: T.Union[str, T.List[str]], user_input: bool = True) -> T.List[str]:
- # User input is for options defined on the command line (via -D
- # options). Users can put their input in as a comma separated
- # string, but for defining options in meson_options.txt the format
- # should match that of a combo
- if not user_input and isinstance(value, str) and not value.startswith('['):
- raise MesonException('Value does not define an array: ' + value)
+ self.set_value(value)
+ @staticmethod
+ def listify_value(value: T.Union[str, T.List[str]], shlex_split_args: bool = False) -> T.List[str]:
if isinstance(value, str):
if value.startswith('['):
try:
elif value == '':
newvalue = []
else:
- if self.split_args:
+ if shlex_split_args:
newvalue = split_args(value)
else:
newvalue = [v.strip() for v in value.split(',')]
raise MesonException(f'"{value}" should be a string array, but it is not')
return newvalue
- def validate_value(self, value: T.Union[str, T.List[str]], user_input: bool = True) -> T.List[str]:
- newvalue = self.listify(value, user_input)
+ def listify(self, value: T.Any) -> T.List[T.Any]:
+ return self.listify_value(value, self.split_args)
+
+ def validate_value(self, value: T.Union[str, T.List[str]]) -> T.List[str]:
+ newvalue = self.listify(value)
if not self.allow_dups and len(set(newvalue)) != len(newvalue):
msg = 'Duplicated values in array option is deprecated. ' \
class UserFeatureOption(UserComboOption):
static_choices = ['enabled', 'disabled', 'auto']
- def __init__(self, description: str, value: T.Any, yielding: T.Optional[bool] = None):
- super().__init__(description, self.static_choices, value, yielding)
+ def __init__(self, description: str, value: T.Any, yielding: bool = DEFAULT_YIELDING,
+ deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False):
+ super().__init__(description, self.static_choices, value, yielding, deprecated)
self.name: T.Optional[str] = None # TODO: Refactor options to all store their name
def is_enabled(self) -> bool:
def is_auto(self) -> bool:
return self.value == 'auto'
+class UserStdOption(UserComboOption):
+ '''
+ UserOption specific to c_std and cpp_std options. User can set a list of
+ STDs in preference order and it selects the first one supported by current
+ compiler.
+
+ For historical reasons, some compilers (msvc) allowed setting a GNU std and
+ silently fell back to C std. This is now deprecated. Projects that support
+ both GNU and MSVC compilers should set e.g. c_std=gnu11,c11.
+
+ This is not using self.deprecated mechanism we already have for project
+ options because we want to print a warning if ALL values are deprecated, not
+ if SOME values are deprecated.
+ '''
+ def __init__(self, lang: str, all_stds: T.List[str]) -> None:
+ self.lang = lang.lower()
+ self.all_stds = ['none'] + all_stds
+ # Map a deprecated std to its replacement. e.g. gnu11 -> c11.
+ self.deprecated_stds: T.Dict[str, str] = {}
+ super().__init__(f'{lang} language standard to use', ['none'], 'none')
+
+ def set_versions(self, versions: T.List[str], gnu: bool = False, gnu_deprecated: bool = False) -> None:
+ assert all(std in self.all_stds for std in versions)
+ self.choices += versions
+ if gnu:
+ gnu_stds_map = {f'gnu{std[1:]}': std for std in versions}
+ if gnu_deprecated:
+ self.deprecated_stds.update(gnu_stds_map)
+ else:
+ self.choices += gnu_stds_map.keys()
+
+ def validate_value(self, value: T.Union[str, T.List[str]]) -> str:
+ candidates = UserArrayOption.listify_value(value)
+ unknown = [std for std in candidates if std not in self.all_stds]
+ if unknown:
+ raise MesonException(f'Unknown {self.lang.upper()} std {unknown}. Possible values are {self.all_stds}.')
+ # Check first if any of the candidates are not deprecated
+ for std in candidates:
+ if std in self.choices:
+ return std
+ # Fallback to a deprecated std if any
+ for std in candidates:
+ newstd = self.deprecated_stds.get(std)
+ if newstd is not None:
+ mlog.deprecation(
+ f'None of the values {candidates} are supported by the {self.lang} compiler.\n' +
+ f'However, the deprecated {std} std currently falls back to {newstd}.\n' +
+ 'This will be an error in the future.\n' +
+ 'If the project supports both GNU and MSVC compilers, a value such as\n' +
+ '"c_std=gnu11,c11" specifies that GNU is prefered but it can safely fallback to plain c11.')
+ return newstd
+ raise MesonException(f'None of values {candidates} are supported by the {self.lang.upper()} compiler. ' +
+ f'Possible values are {self.choices}')
+
+@dataclass
+class OptionsView(abc.Mapping):
+ '''A view on an options dictionary for a given subproject and with overrides.
+ '''
+
+ # TODO: the typing here could be made more explicit using a TypeDict from
+ # python 3.8 or typing_extensions
+ options: KeyedOptionDictType
+ subproject: T.Optional[str] = None
+ overrides: T.Optional[T.Mapping[OptionKey, T.Union[str, int, bool, T.List[str]]]] = None
+
+ def __getitem__(self, key: OptionKey) -> UserOption:
+ # FIXME: This is fundamentally the same algorithm than interpreter.get_option_internal().
+ # We should try to share the code somehow.
+ key = key.evolve(subproject=self.subproject)
+ if not key.is_project():
+ opt = self.options.get(key)
+ if opt is None or opt.yielding:
+ opt = self.options[key.as_root()]
+ else:
+ opt = self.options[key]
+ if opt.yielding:
+ opt = self.options.get(key.as_root(), opt)
+ if self.overrides:
+ override_value = self.overrides.get(key.as_root())
+ if override_value is not None:
+ opt = copy.copy(opt)
+ opt.set_value(override_value)
+ return opt
+
+ def __iter__(self) -> T.Iterator[OptionKey]:
+ return iter(self.options)
+
+ def __len__(self) -> int:
+ return len(self.options)
class DependencyCacheType(enum.Enum):
@classmethod
def from_type(cls, dep: 'dependencies.Dependency') -> 'DependencyCacheType':
- from . import dependencies
# As more types gain search overrides they'll need to be added here
- if isinstance(dep, dependencies.PkgConfigDependency):
+ if dep.type_name == 'pkgconfig':
return cls.PKG_CONFIG
- if isinstance(dep, dependencies.CMakeDependency):
+ if dep.type_name == 'cmake':
return cls.CMAKE
return cls.OTHER
"""
def __init__(self, builtins: 'KeyedOptionDictType', for_machine: MachineChoice):
- self.__cache = OrderedDict() # type: T.MutableMapping[TV_DepID, DependencySubCache]
+ self.__cache: T.MutableMapping[TV_DepID, DependencySubCache] = OrderedDict()
self.__builtins = builtins
self.__pkg_conf_key = OptionKey('pkg_config_path', machine=for_machine)
self.__cmake_key = OptionKey('cmake_prefix_path', machine=for_machine)
def __calculate_subkey(self, type_: DependencyCacheType) -> T.Tuple[str, ...]:
- data: T.Dict[str, T.List[str]] = {
+ data: T.Dict[DependencyCacheType, T.List[str]] = {
DependencyCacheType.PKG_CONFIG: stringlistify(self.__builtins[self.__pkg_conf_key].value),
DependencyCacheType.CMAKE: stringlistify(self.__builtins[self.__cmake_key].value),
DependencyCacheType.OTHER: [],
def items(self) -> T.Iterator[T.Tuple['TV_DepID', T.List['dependencies.Dependency']]]:
for k, v in self.__cache.items():
- vs = []
+ vs: T.List[dependencies.Dependency] = []
for t in v.types:
subkey = self.__calculate_subkey(t)
if subkey in v:
self.version = version
self.options: 'MutableKeyedOptionDictType' = {}
self.cross_files = self.__load_config_files(options, scratch_dir, 'cross')
- self.compilers = PerMachine(OrderedDict(), OrderedDict()) # type: PerMachine[T.Dict[str, Compiler]]
+ self.compilers: PerMachine[T.Dict[str, Compiler]] = PerMachine(OrderedDict(), OrderedDict())
# Set of subprojects that have already been initialized once, this is
# required to be stored and reloaded with the coredata, as we don't
# want to overwrite options for such subprojects.
self.initialized_subprojects: T.Set[str] = set()
- # For host == build configuraitons these caches should be the same.
+ # For host == build configurations these caches should be the same.
self.deps: PerMachine[DependencyCache] = PerMachineDefaultable.default(
self.is_cross_build(),
DependencyCache(self.options, MachineChoice.BUILD),
DependencyCache(self.options, MachineChoice.HOST))
self.compiler_check_cache: T.Dict['CompilerCheckCacheKey', 'CompileResult'] = OrderedDict()
+ self.run_check_cache: T.Dict['RunCheckCacheKey', 'RunResult'] = OrderedDict()
# CMake cache
self.cmake_cache: PerMachine[CMakeStateCache] = PerMachine(CMakeStateCache(), CMakeStateCache())
if not filenames:
return []
- found_invalid = [] # type: T.List[str]
- missing = [] # type: T.List[str]
- real = [] # type: T.List[str]
+ found_invalid: T.List[str] = []
+ missing: T.List[str] = []
+ real: T.List[str] = []
for i, f in enumerate(filenames):
f = os.path.expanduser(os.path.expandvars(f))
if os.path.exists(f):
if self.cross_files:
BUILTIN_OPTIONS[OptionKey('libdir')].default = 'lib'
- def sanitize_prefix(self, prefix):
+ def sanitize_prefix(self, prefix: str) -> str:
prefix = os.path.expanduser(prefix)
if not os.path.isabs(prefix):
raise MesonException(f'prefix value {prefix!r} must be an absolute path')
except TypeError:
return value
if option.name.endswith('dir') and value.is_absolute() and \
- option not in BULITIN_DIR_NOPREFIX_OPTIONS:
+ option not in BUILTIN_DIR_NOPREFIX_OPTIONS:
try:
# Try to relativize the path.
value = value.relative_to(prefix)
raise MesonException(f'Tried to get unknown builtin option {str(key)}')
- def set_option(self, key: OptionKey, value) -> None:
+ def set_option(self, key: OptionKey, value, first_invocation: bool = False) -> bool:
+ dirty = False
if key.is_builtin():
if key.name == 'prefix':
value = self.sanitize_prefix(value)
newname = opt.deprecated
newkey = OptionKey.from_string(newname).evolve(subproject=key.subproject)
mlog.deprecation(f'Option {key.name!r} is replaced by {newname!r}')
- self.set_option(newkey, value)
+ dirty |= self.set_option(newkey, value, first_invocation)
- opt.set_value(value)
+ changed = opt.set_value(value)
+ if changed and opt.readonly and not first_invocation:
+ raise MesonException(f'Tried modify read only option {str(key)!r}')
+ dirty |= changed
if key.name == 'buildtype':
- self._set_others_from_buildtype(value)
- elif key.name in {'wrap_mode', 'force_fallback_for'}:
- # We could have the system dependency cached for a dependency that
- # is now forced to use subproject fallback. We probably could have
- # more fine grained cache invalidation, but better be safe.
- self.clear_deps_cache()
-
- def clear_deps_cache(self):
+ dirty |= self._set_others_from_buildtype(value)
+
+ return dirty
+
+ def clear_cache(self) -> None:
self.deps.host.clear()
self.deps.build.clear()
+ self.compiler_check_cache.clear()
+ self.run_check_cache.clear()
- def get_nondefault_buildtype_args(self):
- result = []
+ def get_nondefault_buildtype_args(self) -> T.List[T.Union[T.Tuple[str, str, str], T.Tuple[str, bool, bool]]]:
+ result: T.List[T.Union[T.Tuple[str, str, str], T.Tuple[str, bool, bool]]] = []
value = self.options[OptionKey('buildtype')].value
if value == 'plain':
opt = 'plain'
result.append(('debug', actual_debug, debug))
return result
- def _set_others_from_buildtype(self, value: str) -> None:
+ def _set_others_from_buildtype(self, value: str) -> bool:
+ dirty = False
+
if value == 'plain':
opt = 'plain'
debug = False
debug = True
else:
assert value == 'custom'
- return
- self.options[OptionKey('optimization')].set_value(opt)
- self.options[OptionKey('debug')].set_value(debug)
+ return False
+
+ dirty |= self.options[OptionKey('optimization')].set_value(opt)
+ dirty |= self.options[OptionKey('debug')].set_value(debug)
+
+ return dirty
@staticmethod
def is_per_machine_option(optname: OptionKey) -> bool:
return True
return optname.lang is not None
- def validate_option_value(self, option_name: OptionKey, override_value):
- try:
- opt = self.options[option_name]
- except KeyError:
- raise MesonException(f'Tried to validate unknown option {str(option_name)}')
- try:
- return opt.validate_value(override_value)
- except MesonException as e:
- raise type(e)(('Validation failed for option %s: ' % option_name) + str(e)) \
- .with_traceback(sys.exc_info()[2])
-
def get_external_args(self, for_machine: MachineChoice, lang: str) -> T.List[str]:
return self.options[OptionKey('args', machine=for_machine, lang=lang)].value
continue
oldval = self.options[key]
- if type(oldval) != type(value):
+ if type(oldval) is not type(value):
self.options[key] = value
elif oldval.choices != value.choices:
# If the choices have changed, use the new value, but attempt
try:
value.set_value(oldval.value)
except MesonException:
- mlog.warning(f'Old value(s) of {key} are no longer valid, resetting to default ({value.value}).')
+ mlog.warning(f'Old value(s) of {key} are no longer valid, resetting to default ({value.value}).',
+ fatal=False)
def is_cross_build(self, when_building_for: MachineChoice = MachineChoice.HOST) -> bool:
if when_building_for == MachineChoice.BUILD:
return False
return len(self.cross_files) > 0
- def copy_build_options_from_regular_ones(self) -> None:
+ def copy_build_options_from_regular_ones(self) -> bool:
+ dirty = False
assert not self.is_cross_build()
for k in BUILTIN_OPTIONS_PER_MACHINE:
o = self.options[k]
- self.options[k.as_build()].set_value(o.value)
+ dirty |= self.options[k.as_build()].set_value(o.value)
for bk, bv in self.options.items():
if bk.machine is MachineChoice.BUILD:
hk = bk.as_host()
try:
hv = self.options[hk]
- bv.set_value(hv.value)
+ dirty |= bv.set_value(hv.value)
except KeyError:
continue
- def set_options(self, options: T.Dict[OptionKey, T.Any], subproject: str = '') -> None:
+ return dirty
+
+ def set_options(self, options: T.Dict[OptionKey, T.Any], subproject: str = '', first_invocation: bool = False) -> bool:
+ dirty = False
if not self.is_cross_build():
options = {k: v for k, v in options.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 options:
prefix = self.sanitize_prefix(options[pfk])
- self.options[OptionKey('prefix')].set_value(prefix)
- for key in BULITIN_DIR_NOPREFIX_OPTIONS:
+ dirty |= self.options[OptionKey('prefix')].set_value(prefix)
+ for key in BUILTIN_DIR_NOPREFIX_OPTIONS:
if key not in options:
- self.options[key].set_value(BUILTIN_OPTIONS[key].prefixed_default(key, prefix))
+ dirty |= self.options[key].set_value(BUILTIN_OPTIONS[key].prefixed_default(key, prefix))
unknown_options: T.List[OptionKey] = []
for k, v in options.items():
if k == pfk:
continue
elif k in self.options:
- self.set_option(k, v)
+ dirty |= self.set_option(k, v, first_invocation)
elif k.machine != MachineChoice.BUILD and k.type != OptionType.COMPILER:
unknown_options.append(k)
if unknown_options:
raise MesonException(f'{sub}Unknown options: "{unknown_options_str}"')
if not self.is_cross_build():
- self.copy_build_options_from_regular_ones()
+ dirty |= self.copy_build_options_from_regular_ones()
+
+ return dirty
def set_default_options(self, default_options: T.MutableMapping[OptionKey, str], subproject: str, env: 'Environment') -> None:
# Main project can set default options on subprojects, but subprojects
- # can only set default options on themself.
+ # can only set default options on themselves.
# Preserve order: if env.options has 'buildtype' it must come after
# 'optimization' if it is in default_options.
options: T.MutableMapping[OptionKey, T.Any] = OrderedDict()
continue
options[k] = v
- self.set_options(options, subproject=subproject)
+ self.set_options(options, subproject=subproject, first_invocation=env.first_invocation)
def add_compiler_options(self, options: 'MutableKeyedOptionDictType', lang: str, for_machine: MachineChoice,
env: 'Environment') -> None:
def process_new_compiler(self, lang: str, comp: 'Compiler', env: 'Environment') -> None:
from . import compilers
- self.compilers[comp.for_machine][lang] = comp
self.add_compiler_options(comp.get_options(), lang, comp.for_machine, env)
enabled_opts: T.List[OptionKey] = []
for key in comp.base_options:
if key in self.options:
continue
- oobj = compilers.base_options[key]
+ oobj = copy.deepcopy(compilers.base_options[key])
if key in env.options:
oobj.set_value(env.options[key])
enabled_opts.append(key)
# storing subproject options like "subproject:option=value"
super().__init__(delimiters=['='], interpolation=None)
- def read(self, filenames: T.Union['StrOrBytesPath', T.Iterable['StrOrBytesPath']], encoding: str = 'utf-8') -> T.List[str]:
+ 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, option: str) -> str:
+ def optionxform(self, optionstr: str) -> str:
# Don't call str.lower() on keys
- return option
+ return optionstr
class MachineFileParser():
- def __init__(self, filenames: T.List[str]) -> None:
+ def __init__(self, filenames: T.List[str], sourcedir: str) -> None:
self.parser = CmdLineFileParser()
- self.constants = {'True': True, 'False': False}
- self.sections = {}
-
- self.parser.read(filenames)
+ self.constants: T.Dict[str, T.Union[str, bool, int, T.List[str]]] = {'True': True, 'False': False}
+ self.sections: T.Dict[str, T.Dict[str, T.Union[str, bool, int, T.List[str]]]] = {}
+
+ for fname in filenames:
+ with open(fname, encoding='utf-8') as f:
+ content = f.read()
+ content = content.replace('@GLOBAL_SOURCE_ROOT@', sourcedir)
+ content = content.replace('@DIRNAME@', os.path.dirname(fname))
+ try:
+ self.parser.read_string(content, fname)
+ except configparser.Error as e:
+ raise EnvironmentException(f'Malformed machine file: {e}')
# Parse [constants] first so they can be used in other sections
if self.parser.has_section('constants'):
continue
self.sections[s] = self._parse_section(s)
- def _parse_section(self, s):
+ def _parse_section(self, s: str) -> T.Dict[str, T.Union[str, bool, int, T.List[str]]]:
self.scope = self.constants.copy()
- section = {}
+ section: T.Dict[str, T.Union[str, bool, int, T.List[str]]] = {}
for entry, value in self.parser.items(s):
if ' ' in entry or '\t' in entry or "'" in entry or '"' in entry:
raise EnvironmentException(f'Malformed variable name {entry!r} in machine file.')
value = value.replace('\\', '\\\\')
try:
ast = mparser.Parser(value, 'machinefile').parse()
+ if not ast.lines:
+ raise EnvironmentException('value cannot be empty')
res = self._evaluate_statement(ast.lines[0])
- except MesonException:
- raise EnvironmentException(f'Malformed value in machine file variable {entry!r}.')
+ except MesonException as e:
+ raise EnvironmentException(f'Malformed value in machine file variable {entry!r}: {str(e)}.')
except KeyError as e:
raise EnvironmentException(f'Undefined constant {e.args[0]!r} in machine file variable {entry!r}.')
section[entry] = res
self.scope[entry] = res
return section
- def _evaluate_statement(self, node):
- if isinstance(node, (mparser.StringNode)):
+ def _evaluate_statement(self, node: mparser.BaseNode) -> T.Union[str, bool, int, T.List[str]]:
+ if isinstance(node, (mparser.BaseStringNode)):
return node.value
elif isinstance(node, mparser.BooleanNode):
return node.value
elif isinstance(node, mparser.NumberNode):
return node.value
elif isinstance(node, mparser.ArrayNode):
+ # TODO: This is where recursive types would come in handy
return [self._evaluate_statement(arg) for arg in node.args.arguments]
elif isinstance(node, mparser.IdNode):
return self.scope[node.value]
return os.path.join(l, r)
raise EnvironmentException('Unsupported node type')
-def parse_machine_files(filenames):
- parser = MachineFileParser(filenames)
+def parse_machine_files(filenames: T.List[str], sourcedir: str):
+ parser = MachineFileParser(filenames, sourcedir)
return parser.sections
def get_cmd_line_file(build_dir: str) -> str:
filename = get_cmd_line_file(build_dir)
config = CmdLineFileParser()
- properties = OrderedDict()
+ properties: OrderedDict[str, str] = OrderedDict()
if options.cross_file:
properties['cross_file'] = options.cross_file
if options.native_file:
# Major version differ, or one is development version but not the other.
return v1_major != v2_major or ('99' in {v1_minor, v2_minor} and v1_minor != v2_minor)
-def load(build_dir: str) -> CoreData:
+def load(build_dir: str, suggest_reconfigure: bool = True) -> CoreData:
filename = os.path.join(build_dir, 'meson-private', 'coredata.dat')
- obj = pickle_load(filename, 'Coredata', CoreData)
- assert isinstance(obj, CoreData), 'for mypy'
- return obj
+ return pickle_load(filename, 'Coredata', CoreData, suggest_reconfigure)
def save(obj: CoreData, build_dir: str) -> str:
pickle.dump(obj, f)
f.flush()
os.fsync(f.fileno())
- replace_if_different(filename, tempfilename)
+ os.replace(tempfilename, filename)
return filename
"""
def __init__(self, opt_type: T.Type[_U], description: str, default: T.Any, yielding: bool = True, *,
- choices: T.Any = None):
+ choices: T.Any = None, readonly: bool = False):
self.opt_type = opt_type
self.description = description
self.default = default
self.choices = choices
self.yielding = yielding
+ self.readonly = readonly
def init_option(self, name: 'OptionKey', value: T.Optional[T.Any], prefix: str) -> _U:
"""Create an instance of opt_type and return it."""
keywords = {'yielding': self.yielding, 'value': value}
if self.choices:
keywords['choices'] = self.choices
- return self.opt_type(self.description, **keywords)
+ o = self.opt_type(self.description, **keywords)
+ o.readonly = self.readonly
+ return o
def _argparse_action(self) -> T.Optional[str]:
# If the type is a boolean, the presence of the argument in --foo form
if self.opt_type in [UserComboOption, UserIntegerOption]:
return self.default
try:
- return BULITIN_DIR_NOPREFIX_OPTIONS[name][prefix]
+ return BUILTIN_DIR_NOPREFIX_OPTIONS[name][prefix]
except KeyError:
pass
return self.default
# Update `docs/markdown/Builtin-options.md` after changing the options below
# Also update mesonlib._BUILTIN_NAMES. See the comment there for why this is required.
+# Please also update completion scripts in $MESONSRC/data/shell-completions/
BUILTIN_DIR_OPTIONS: 'MutableKeyedOptionDictType' = OrderedDict([
(OptionKey('prefix'), BuiltinOption(UserStringOption, 'Installation prefix', default_prefix())),
(OptionKey('bindir'), BuiltinOption(UserStringOption, 'Executable directory', 'bin')),
- (OptionKey('datadir'), BuiltinOption(UserStringOption, 'Data file directory', 'share')),
- (OptionKey('includedir'), BuiltinOption(UserStringOption, 'Header file directory', 'include')),
- (OptionKey('infodir'), BuiltinOption(UserStringOption, 'Info page directory', 'share/info')),
+ (OptionKey('datadir'), BuiltinOption(UserStringOption, 'Data file directory', default_datadir())),
+ (OptionKey('includedir'), BuiltinOption(UserStringOption, 'Header file directory', default_includedir())),
+ (OptionKey('infodir'), BuiltinOption(UserStringOption, 'Info page directory', default_infodir())),
(OptionKey('libdir'), BuiltinOption(UserStringOption, 'Library directory', default_libdir())),
+ (OptionKey('licensedir'), BuiltinOption(UserStringOption, 'Licenses directory', '')),
(OptionKey('libexecdir'), BuiltinOption(UserStringOption, 'Library executable directory', default_libexecdir())),
- (OptionKey('localedir'), BuiltinOption(UserStringOption, 'Locale data directory', 'share/locale')),
+ (OptionKey('localedir'), BuiltinOption(UserStringOption, 'Locale data directory', default_localedir())),
(OptionKey('localstatedir'), BuiltinOption(UserStringOption, 'Localstate data directory', 'var')),
- (OptionKey('mandir'), BuiltinOption(UserStringOption, 'Manual page directory', 'share/man')),
- (OptionKey('sbindir'), BuiltinOption(UserStringOption, 'System executable directory', 'sbin')),
+ (OptionKey('mandir'), BuiltinOption(UserStringOption, 'Manual page directory', default_mandir())),
+ (OptionKey('sbindir'), BuiltinOption(UserStringOption, 'System executable directory', default_sbindir())),
(OptionKey('sharedstatedir'), BuiltinOption(UserStringOption, 'Architecture-independent data directory', 'com')),
- (OptionKey('sysconfdir'), BuiltinOption(UserStringOption, 'Sysconf data directory', 'etc')),
+ (OptionKey('sysconfdir'), BuiltinOption(UserStringOption, 'Sysconf data directory', default_sysconfdir())),
])
BUILTIN_CORE_OPTIONS: 'MutableKeyedOptionDictType' = OrderedDict([
(OptionKey('auto_features'), BuiltinOption(UserFeatureOption, "Override value of all 'auto' features", 'auto')),
- (OptionKey('backend'), BuiltinOption(UserComboOption, 'Backend to use', 'ninja', choices=backendlist)),
+ (OptionKey('backend'), BuiltinOption(UserComboOption, 'Backend to use', 'ninja', choices=backendlist,
+ readonly=True)),
+ (OptionKey('genvslite'),
+ BuiltinOption(
+ UserComboOption,
+ 'Setup multiple buildtype-suffixed ninja-backend build directories, '
+ 'and a [builddir]_vs containing a Visual Studio meta-backend with multiple configurations that calls into them',
+ 'vs2022',
+ choices=genvslitelist)
+ ),
(OptionKey('buildtype'), BuiltinOption(UserComboOption, 'Build type to use', 'debug',
- choices=['plain', 'debug', 'debugoptimized', 'release', 'minsize', 'custom'])),
+ choices=buildtypelist)),
(OptionKey('debug'), BuiltinOption(UserBooleanOption, 'Enable debug symbols and other information', True)),
(OptionKey('default_library'), BuiltinOption(UserComboOption, 'Default library type', 'shared', choices=['shared', 'static', 'both'],
yielding=False)),
(OptionKey('werror'), BuiltinOption(UserBooleanOption, 'Treat warnings as errors', False, yielding=False)),
(OptionKey('wrap_mode'), BuiltinOption(UserComboOption, 'Wrap mode', 'default', choices=['default', 'nofallback', 'nodownload', 'forcefallback', 'nopromote'])),
(OptionKey('force_fallback_for'), BuiltinOption(UserArrayOption, 'Force fallback for those subprojects', [])),
+ (OptionKey('vsenv'), BuiltinOption(UserBooleanOption, 'Activate Visual Studio environment', False, readonly=True)),
# Pkgconfig module
(OptionKey('relocatable', module='pkgconfig'),
BuiltinOption(UserBooleanOption, 'Generate pkgconfig files as relocatable', False)),
# Python module
+ (OptionKey('bytecompile', module='python'),
+ BuiltinOption(UserIntegerOption, 'Whether to compile bytecode', (-1, 2, 0))),
(OptionKey('install_env', module='python'),
BuiltinOption(UserComboOption, 'Which python environment to install to', 'prefix', choices=['auto', 'prefix', 'system', 'venv'])),
(OptionKey('platlibdir', module='python'),
BuiltinOption(UserStringOption, 'Directory for site-specific, platform-specific files.', '')),
(OptionKey('purelibdir', module='python'),
BuiltinOption(UserStringOption, 'Directory for site-specific, non-platform-specific files.', '')),
+ (OptionKey('allow_limited_api', module='python'),
+ BuiltinOption(UserBooleanOption, 'Whether to allow use of the Python Limited API', True)),
])
BUILTIN_OPTIONS = OrderedDict(chain(BUILTIN_DIR_OPTIONS.items(), BUILTIN_CORE_OPTIONS.items()))
# Special prefix-dependent defaults for installation directories that reside in
# a path outside of the prefix in FHS and common usage.
-BULITIN_DIR_NOPREFIX_OPTIONS: T.Dict[OptionKey, T.Dict[str, str]] = {
+BUILTIN_DIR_NOPREFIX_OPTIONS: T.Dict[OptionKey, T.Dict[str, str]] = {
OptionKey('sysconfdir'): {'/usr': '/etc'},
OptionKey('localstatedir'): {'/usr': '/var', '/usr/local': '/var/local'},
OptionKey('sharedstatedir'): {'/usr': '/var/lib', '/usr/local': '/var/local/lib'},
OptionKey('purelibdir', module='python'): {},
}
-FORBIDDEN_TARGET_NAMES = {'clean': None,
- 'clean-ctlist': None,
- 'clean-gcno': None,
- 'clean-gcda': None,
- 'coverage': None,
- 'coverage-text': None,
- 'coverage-xml': None,
- 'coverage-html': None,
- 'phony': None,
- 'PHONY': None,
- 'all': None,
- 'test': None,
- 'benchmark': None,
- 'install': None,
- 'uninstall': None,
- 'build.ninja': None,
- 'scan-build': None,
- 'reconfigure': None,
- 'dist': None,
- 'distcheck': None,
- }
+FORBIDDEN_TARGET_NAMES = frozenset({
+ 'clean',
+ 'clean-ctlist',
+ 'clean-gcno',
+ 'clean-gcda',
+ 'coverage',
+ 'coverage-text',
+ 'coverage-xml',
+ 'coverage-html',
+ 'phony',
+ 'PHONY',
+ 'all',
+ 'test',
+ 'benchmark',
+ 'install',
+ 'uninstall',
+ 'build.ninja',
+ 'scan-build',
+ 'reconfigure',
+ 'dist',
+ 'distcheck',
+})
# See the License for the specific language governing permissions and
# limitations under the License.
-from .boost import BoostDependency
-from .cuda import CudaDependency
-from .hdf5 import hdf5_factory
from .base import Dependency, InternalDependency, ExternalDependency, NotFoundDependency, MissingCompiler
from .base import (
ExternalLibrary, DependencyException, DependencyMethods,
BuiltinDependency, SystemDependency, get_leaf_external_dependencies)
-from .cmake import CMakeDependency
-from .configtool import ConfigToolDependency
-from .dub import DubDependency
-from .framework import ExtraFrameworkDependency
-from .pkgconfig import PkgConfigDependency
-from .factory import DependencyFactory
from .detect import find_external_dependency, get_dep_identifier, packages, _packages_accept_language
-from .dev import (
- ValgrindDependency, JNISystemDependency, JDKSystemDependency, gmock_factory, gtest_factory,
- llvm_factory, zlib_factory)
-from .coarrays import coarray_factory
-from .mpi import mpi_factory
-from .scalapack import scalapack_factory
-from .misc import (
- BlocksDependency, OpenMPDependency, cups_factory, curses_factory, gpgme_factory,
- libgcrypt_factory, libwmf_factory, netcdf_factory, pcap_factory, python3_factory,
- shaderc_factory, threads_factory, ThreadDependency, iconv_factory, intl_factory,
- dl_factory, openssl_factory, libcrypto_factory, libssl_factory,
-)
-from .platform import AppleFrameworks
-from .qt import qt4_factory, qt5_factory, qt6_factory
-from .ui import GnuStepDependency, WxDependency, gl_factory, sdl2_factory, vulkan_factory
__all__ = [
'Dependency',
'DependencyMethods',
'MissingCompiler',
- 'CMakeDependency',
- 'ConfigToolDependency',
- 'DubDependency',
- 'ExtraFrameworkDependency',
- 'PkgConfigDependency',
-
- 'DependencyFactory',
-
- 'ThreadDependency',
-
'find_external_dependency',
'get_dep_identifier',
'get_leaf_external_dependencies',
# - An ExternalDependency subclass
# - A DependencyFactory object
# - A callable with a signature of (Environment, MachineChoice, Dict[str, Any]) -> List[Callable[[], ExternalDependency]]
-packages.update({
+#
+# The internal "defaults" attribute contains a separate dictionary mapping
+# for lazy imports. The values must be:
+# - a string naming the submodule that should be imported from `mesonbuild.dependencies` to populate the dependency
+packages.defaults.update({
# From dev:
- 'gtest': gtest_factory,
- 'gmock': gmock_factory,
- 'llvm': llvm_factory,
- 'valgrind': ValgrindDependency,
- 'zlib': zlib_factory,
- 'jni': JNISystemDependency,
- 'jdk': JDKSystemDependency,
+ 'gtest': 'dev',
+ 'gmock': 'dev',
+ 'llvm': 'dev',
+ 'valgrind': 'dev',
+ 'zlib': 'dev',
+ 'jni': 'dev',
+ 'jdk': 'dev',
- 'boost': BoostDependency,
- 'cuda': CudaDependency,
+ 'boost': 'boost',
+ 'cuda': 'cuda',
# per-file
- 'coarray': coarray_factory,
- 'hdf5': hdf5_factory,
- 'mpi': mpi_factory,
- 'scalapack': scalapack_factory,
+ 'coarray': 'coarrays',
+ 'hdf5': 'hdf5',
+ 'mpi': 'mpi',
+ 'scalapack': 'scalapack',
# From misc:
- 'blocks': BlocksDependency,
- 'curses': curses_factory,
- 'netcdf': netcdf_factory,
- 'openmp': OpenMPDependency,
- 'python3': python3_factory,
- 'threads': threads_factory,
- 'pcap': pcap_factory,
- 'cups': cups_factory,
- 'libwmf': libwmf_factory,
- 'libgcrypt': libgcrypt_factory,
- 'gpgme': gpgme_factory,
- 'shaderc': shaderc_factory,
- 'iconv': iconv_factory,
- 'intl': intl_factory,
- 'dl': dl_factory,
- 'openssl': openssl_factory,
- 'libcrypto': libcrypto_factory,
- 'libssl': libssl_factory,
+ 'blocks': 'misc',
+ 'curses': 'misc',
+ 'netcdf': 'misc',
+ 'openmp': 'misc',
+ 'threads': 'misc',
+ 'pcap': 'misc',
+ 'cups': 'misc',
+ 'libwmf': 'misc',
+ 'libgcrypt': 'misc',
+ 'gpgme': 'misc',
+ 'shaderc': 'misc',
+ 'iconv': 'misc',
+ 'intl': 'misc',
+ 'dl': 'misc',
+ 'openssl': 'misc',
+ 'libcrypto': 'misc',
+ 'libssl': 'misc',
# From platform:
- 'appleframeworks': AppleFrameworks,
+ 'appleframeworks': 'platform',
+
+ # from python:
+ 'python3': 'python',
+ 'pybind11': 'python',
# From ui:
- 'gl': gl_factory,
- 'gnustep': GnuStepDependency,
- 'qt4': qt4_factory,
- 'qt5': qt5_factory,
- 'qt6': qt6_factory,
- 'sdl2': sdl2_factory,
- 'wxwidgets': WxDependency,
- 'vulkan': vulkan_factory,
+ 'gl': 'ui',
+ 'gnustep': 'ui',
+ 'sdl2': 'ui',
+ 'wxwidgets': 'ui',
+ 'vulkan': 'ui',
+
+ # from qt
+ 'qt4': 'qt',
+ 'qt5': 'qt',
+ 'qt6': 'qt',
})
_packages_accept_language.update({
'hdf5',
#from ..interpreterbase import FeatureDeprecated, FeatureNew
if T.TYPE_CHECKING:
- from .._typing import ImmutableListProtocol
- from ..build import StructuredSources
from ..compilers.compilers import Compiler
from ..environment import Environment
from ..interpreterbase import FeatureCheckBase
from ..build import (
CustomTarget, IncludeDirs, CustomTargetIndex, LibTypes,
- StaticLibrary
+ StaticLibrary, StructuredSources, ExtractedObjects, GeneratedTypes
)
- from ..mesonlib import FileOrString
+ from ..interpreter.type_checking import PkgConfigDefineType
class DependencyException(MesonException):
return kwargs['include_type']
def __init__(self, type_name: DependencyTypeName, kwargs: T.Dict[str, T.Any]) -> None:
- self.name = "null"
+ self.name = f'dep{id(self)}'
self.version: T.Optional[str] = None
self.language: T.Optional[str] = None # None means C-like
self.is_found = False
# Raw -L and -l arguments without manual library searching
# If None, self.link_args will be used
self.raw_link_args: T.Optional[T.List[str]] = None
- self.sources: T.List[T.Union['FileOrString', 'CustomTarget', 'StructuredSources']] = []
+ 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.ext_deps: T.List[Dependency] = []
self.d_features: T.DefaultDict[str, T.List[T.Any]] = collections.defaultdict(list)
def found(self) -> bool:
return self.is_found
- def get_sources(self) -> T.List[T.Union['FileOrString', 'CustomTarget', 'StructuredSources']]:
+ def get_sources(self) -> T.List[T.Union[mesonlib.File, GeneratedTypes, 'StructuredSources']]:
"""Source files that need to be added to the target.
As an example, gtest-all.cc when using GTest."""
return self.sources
+ def get_extra_files(self) -> T.List[mesonlib.File]:
+ """Mostly for introspection and IDEs"""
+ return self.extra_files
+
def get_name(self) -> str:
return self.name
def get_exe_args(self, compiler: 'Compiler') -> T.List[str]:
return []
- def get_pkgconfig_variable(self, variable_name: str,
- define_variable: 'ImmutableListProtocol[str]',
- default: T.Optional[str]) -> str:
- raise DependencyException(f'{self.name!r} is not a pkgconfig dependency')
-
- def get_configtool_variable(self, variable_name: str) -> str:
- raise DependencyException(f'{self.name!r} is not a config-tool dependency')
-
def get_partial_dependency(self, *, compile_args: bool = False,
link_args: bool = False, links: bool = False,
includes: bool = False, sources: bool = False) -> 'Dependency':
def get_variable(self, *, cmake: T.Optional[str] = None, pkgconfig: T.Optional[str] = None,
configtool: T.Optional[str] = None, internal: T.Optional[str] = None,
default_value: T.Optional[str] = None,
- pkgconfig_define: T.Optional[T.List[str]] = None) -> str:
+ pkgconfig_define: PkgConfigDefineType = None) -> str:
if default_value is not None:
return default_value
raise DependencyException(f'No default provided for dependency {self!r}, which is not pkg-config, cmake, or config-tool based.')
link_args: T.List[str],
libraries: T.List[LibTypes],
whole_libraries: T.List[T.Union[StaticLibrary, CustomTarget, CustomTargetIndex]],
- sources: T.Sequence[T.Union[FileOrString, CustomTarget, StructuredSources]],
+ sources: T.Sequence[T.Union[mesonlib.File, GeneratedTypes, StructuredSources]],
+ extra_files: T.Sequence[mesonlib.File],
ext_deps: T.List[Dependency], variables: T.Dict[str, str],
- d_module_versions: T.List[T.Union[str, int]], d_import_dirs: T.List['IncludeDirs']):
+ d_module_versions: T.List[T.Union[str, int]], d_import_dirs: T.List['IncludeDirs'],
+ objects: T.List['ExtractedObjects']):
super().__init__(DependencyTypeName('internal'), {})
self.version = version
self.is_found = True
self.libraries = libraries
self.whole_libraries = whole_libraries
self.sources = list(sources)
+ self.extra_files = list(extra_files)
self.ext_deps = ext_deps
self.variables = variables
+ self.objects = objects
if d_module_versions:
self.d_features['versions'] = d_module_versions
if d_import_dirs:
return True
return any(d.is_built() for d in self.ext_deps)
- def get_pkgconfig_variable(self, variable_name: str,
- define_variable: 'ImmutableListProtocol[str]',
- default: T.Optional[str]) -> str:
- raise DependencyException('Method "get_pkgconfig_variable()" is '
- 'invalid for an internal dependency')
-
- def get_configtool_variable(self, variable_name: str) -> str:
- raise DependencyException('Method "get_configtool_variable()" is '
- 'invalid for an internal dependency')
-
def get_partial_dependency(self, *, compile_args: bool = False,
link_args: bool = False, links: bool = False,
- includes: bool = False, sources: bool = False) -> 'InternalDependency':
+ includes: bool = False, sources: bool = False,
+ extra_files: bool = False) -> InternalDependency:
final_compile_args = self.compile_args.copy() if compile_args else []
final_link_args = self.link_args.copy() if link_args else []
final_libraries = self.libraries.copy() if links else []
final_whole_libraries = self.whole_libraries.copy() if links else []
final_sources = self.sources.copy() if sources else []
+ final_extra_files = self.extra_files.copy() if extra_files else []
final_includes = self.include_directories.copy() if includes else []
final_deps = [d.get_partial_dependency(
compile_args=compile_args, link_args=link_args, links=links,
return InternalDependency(
self.version, final_includes, final_compile_args,
final_link_args, final_libraries, final_whole_libraries,
- final_sources, final_deps, self.variables, [], [])
+ final_sources, final_extra_files, final_deps, self.variables, [], [], [])
def get_include_dirs(self) -> T.List['IncludeDirs']:
return self.include_directories
def get_variable(self, *, cmake: T.Optional[str] = None, pkgconfig: T.Optional[str] = None,
configtool: T.Optional[str] = None, internal: T.Optional[str] = None,
default_value: T.Optional[str] = None,
- pkgconfig_define: T.Optional[T.List[str]] = None) -> str:
+ pkgconfig_define: PkgConfigDefineType = None) -> str:
val = self.variables.get(internal, default_value)
if val is not None:
return val
in the system path, and a different version not in the system path if they
want to link against the non-system path version.
"""
- exclude = {f'-L{p}' for p in environment.get_compiler_system_dirs(for_machine)}
+ exclude = {f'-L{p}' for p in environment.get_compiler_system_lib_dirs(for_machine)}
return [l for l in link_args if l not in exclude]
+def strip_system_includedirs(environment: 'Environment', for_machine: MachineChoice, include_args: T.List[str]) -> T.List[str]:
+ """Remove -I<system path> arguments.
+
+ leaving these in will break builds where user want dependencies with system
+ include-type used in rust.bindgen targets as if will cause system headers
+ to not be found.
+ """
+
+ 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 = kwargs.get('method', 'auto') # type: T.Union[DependencyMethods, str]
+ method: T.Union[DependencyMethods, str] = kwargs.get('method', 'auto')
if isinstance(method, DependencyMethods):
return [method]
# TODO: try/except?
from .. import mesonlib
from .base import DependencyException, SystemDependency
+from .detect import packages
from .pkgconfig import PkgConfigDependency
from .misc import threads_factory
if T.TYPE_CHECKING:
- from ..environment import Environment, Properties
+ from ..envconfig import Properties
+ from ..environment import Environment
# On windows 3 directory layouts are supported:
# * The default layout (versioned) installed:
# 2. Find all boost libraries
# 2.1 Add all libraries in lib*
# 2.2 Filter out non boost libraries
-# 2.3 Filter the renaining libraries based on the meson requirements (static/shared, etc.)
+# 2.3 Filter the remaining libraries based on the meson requirements (static/shared, etc.)
# 2.4 Ensure that all libraries have the same boost tag (and are thus compatible)
# 3. Select the libraries matching the requested modules
return any(self.mod_name.startswith(x) for x in BoostLibraryFile.boost_python_libs)
def fix_python_name(self, tags: T.List[str]) -> T.List[str]:
- # Handle the boost_python naming madeness.
+ # Handle the boost_python naming madness.
# See https://github.com/mesonbuild/meson/issues/4788 for some distro
# specific naming variations.
- other_tags = [] # type: T.List[str]
+ other_tags: T.List[str] = []
# Split the current modname into the base name and the version
m_cur = BoostLibraryFile.reg_python_mod_split.match(self.mod_name)
return True
def get_compiler_args(self) -> T.List[str]:
- args = [] # type: T.List[str]
+ args: T.List[str] = []
if self.mod_name in boost_libraries:
- libdef = boost_libraries[self.mod_name] # type: BoostLibrary
+ libdef = boost_libraries[self.mod_name]
if self.static:
args += libdef.static
else:
self.debug = buildtype.startswith('debug')
self.multithreading = kwargs.get('threading', 'multi') == 'multi'
- self.boost_root = None # type: T.Optional[Path]
+ self.boost_root: T.Optional[Path] = None
self.explicit_static = 'static' in kwargs
# Extract and validate modules
- self.modules = mesonlib.extract_as_list(kwargs, 'modules') # type: T.List[str]
+ self.modules: T.List[str] = mesonlib.extract_as_list(kwargs, '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')
- self.modules_found = [] # type: T.List[str]
- self.modules_missing = [] # type: T.List[str]
+ self.modules_found: T.List[str] = []
+ self.modules_missing: T.List[str] = []
# Do we need threads?
if 'thread' in self.modules:
mlog.debug(' - potential include dirs: {}'.format([x.path.as_posix() for x in inc_dirs]))
# 2. Find all boost libraries
- libs = [] # type: T.List[BoostLibraryFile]
+ libs: T.List[BoostLibraryFile] = []
for i in lib_dirs:
libs = self.detect_libraries(i)
if libs:
mlog.debug(f' - {j}')
# 3. Select the libraries matching the requested modules
- not_found = [] # type: T.List[str]
- selected_modules = [] # type: T.List[BoostLibraryFile]
+ not_found: T.List[str] = []
+ selected_modules: T.List[BoostLibraryFile] = []
for mod in modules:
found = False
for l in f_libs:
# log the result
mlog.debug(' - found:')
- comp_args = [] # type: T.List[str]
- link_args = [] # type: T.List[str]
+ comp_args: T.List[str] = []
+ link_args: T.List[str] = []
for j in selected_modules:
c_args = j.get_compiler_args()
l_args = j.get_link_args()
return False
def detect_inc_dirs(self, root: Path) -> T.List[BoostIncludeDir]:
- candidates = [] # type: T.List[Path]
+ candidates: T.List[Path] = []
inc_root = root / 'include'
candidates += [root / 'boost']
# No system include paths were found --> fall back to manually looking
# for library dirs in root
- dirs = [] # type: T.List[Path]
- subdirs = [] # type: T.List[Path]
+ dirs: T.List[Path] = []
+ subdirs: T.List[Path] = []
for i in root.iterdir():
if i.is_dir() and i.name.startswith('lib'):
dirs += [i]
raw_list = dirs + subdirs
no_arch = [x for x in raw_list if not any(y in x.name for y in arch_list_32 + arch_list_64)]
- matching_arch = [] # type: T.List[Path]
+ matching_arch: T.List[Path] = []
if '32' in self.arch:
matching_arch = [x for x in raw_list if any(y in x.name for y in arch_list_32)]
elif '64' in self.arch:
return libs
def detect_libraries(self, libdir: Path) -> T.List[BoostLibraryFile]:
- libs = set() # type: T.Set[BoostLibraryFile]
+ libs: T.Set[BoostLibraryFile] = set()
for i in libdir.iterdir():
if not i.is_file():
continue
self.is_found = self.run_check([boost_inc_dir], [lib_dir])
def detect_roots(self) -> None:
- roots = [] # type: T.List[Path]
+ roots: T.List[Path] = []
# Try getting the BOOST_ROOT from a boost.pc if it exists. This primarily
# allows BoostDependency to find boost from Conan. See #5438
try:
boost_pc = PkgConfigDependency('boost', self.env, {'required': False})
if boost_pc.found():
- boost_root = boost_pc.get_pkgconfig_variable('prefix', [], None)
+ boost_root = boost_pc.get_variable(pkgconfig='prefix')
if boost_root:
roots += [Path(boost_root)]
except DependencyException:
# Where boost prebuilt binaries are
local_boost = Path('C:/local')
- candidates = [] # type: T.List[Path]
+ candidates: T.List[Path] = []
if prog_files.is_dir():
candidates += [*prog_files.iterdir()]
if local_boost.is_dir():
roots += [x for x in candidates if x.name.lower().startswith('boost') and x.is_dir()]
else:
- tmp = [] # type: T.List[Path]
+ tmp: T.List[Path] = []
# Add some default system paths
tmp += [Path('/opt/local')]
# BOOST_ALL_DYN_LINK should not be required with the known defines below
return ['-DBOOST_ALL_NO_LIB'] # Disable automatic linking
+packages['boost'] = BoostDependency
# See https://www.boost.org/doc/libs/1_72_0/more/getting_started/unix-variants.html#library-naming
# See https://mesonbuild.com/Reference-tables.html#cpu-families
from ..cmake import CMakeTarget
from ..environment import Environment
from ..envconfig import MachineInfo
+ from ..interpreter.type_checking import PkgConfigDefineType
class CMakeInfo(T.NamedTuple):
module_paths: T.List[str]
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:
# Gather a list of all languages to support
- self.language_list = [] # type: T.List[str]
+ self.language_list: T.List[str] = []
if language is None or force_use_global_compilers:
compilers = None
if kwargs.get('native', False):
module_paths = [x for x in module_paths if os.path.isdir(x)]
archs = temp_parser.get_cmake_var('MESON_ARCH_LIST')
- common_paths = ['lib', 'lib32', 'lib64', 'libx32', 'share']
+ common_paths = ['lib', 'lib32', 'lib64', 'libx32', 'share', '']
for i in archs:
common_paths += [os.path.join('lib', i)]
return True
# Check PATH
- system_env = [] # type: T.List[str]
+ system_env: T.List[str] = []
for i in os.environ.get('PATH', '').split(os.pathsep):
if i.endswith('/bin') or i.endswith('\\bin'):
i = i[:-4]
cmake_opts += ['-DARCHS={}'.format(';'.join(self.cmakeinfo.archs))]
cmake_opts += [f'-DVERSION={package_version}']
cmake_opts += ['-DCOMPS={}'.format(';'.join([x[0] for x in comp_mapped]))]
+ cmake_opts += [f'-DSTATIC={self.static}']
cmake_opts += args
cmake_opts += self.traceparser.trace_args()
cmake_opts += toolchain.get_cmake_args()
libs_raw = [x for x in self.traceparser.get_cmake_var('PACKAGE_LIBRARIES') if x]
# CMake has a "fun" API, where certain keywords describing
- # configurations can be in the *_LIBRARIES vraiables. See:
+ # configurations can be in the *_LIBRARIES variables. See:
# - https://github.com/mesonbuild/meson/issues/9197
# - https://gitlab.freedesktop.org/libnice/libnice/-/issues/140
# - https://cmake.org/cmake/help/latest/command/target_link_libraries.html#overview (the last point in the section)
libs += [i]
# According to the CMake docs, a keyword only works for the
# directly the following item and all items without a keyword
- # are implizitly `general`
+ # are implicitly `general`
cfg_matches = True
# Try to use old style variables if no module is specified
def get_variable(self, *, cmake: T.Optional[str] = None, pkgconfig: T.Optional[str] = None,
configtool: T.Optional[str] = None, internal: T.Optional[str] = None,
default_value: T.Optional[str] = None,
- pkgconfig_define: T.Optional[T.List[str]] = None) -> str:
+ pkgconfig_define: PkgConfigDefineType = None) -> str:
if cmake and self.traceparser is not None:
try:
v = self.traceparser.vars[cmake]
if default_value is not None:
return default_value
raise DependencyException(f'Could not get cmake variable and no default provided for {self!r}')
+
+
+class CMakeDependencyFactory:
+
+ def __init__(self, name: T.Optional[str] = None, modules: T.Optional[T.List[str]] = None):
+ 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:
+ if self.modules:
+ kwargs['modules'] = self.modules
+ return CMakeDependency(self.name or name, env, kwargs, language, force_use_global_compilers)
+
+ @staticmethod
+ def log_tried() -> str:
+ return CMakeDependency.log_tried()
# 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 functools
import typing as T
from .base import DependencyMethods, detect_compiler, SystemDependency
from .cmake import CMakeDependency
+from .detect import packages
from .pkgconfig import PkgConfigDependency
from .factory import factory_methods
if T.TYPE_CHECKING:
from . factory import DependencyGenerator
- from ..environment import Environment, MachineChoice
+ from ..environment import Environment
+ from ..mesonlib import MachineChoice
@factory_methods({DependencyMethods.PKGCONFIG, DependencyMethods.CMAKE, DependencyMethods.SYSTEM})
candidates.append(functools.partial(CoarrayDependency, env, kwargs))
return candidates
+packages['coarray'] = coarray_factory
class CoarrayDependency(SystemDependency):
# 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
from .base import ExternalDependency, DependencyException, DependencyTypeName
-from ..mesonlib import listify, Popen_safe, split_args, version_compare, version_compare_many
+from ..mesonlib import listify, Popen_safe, Popen_safe_logged, split_args, version_compare, version_compare_many
from ..programs import find_external_program
from .. import mlog
import re
if T.TYPE_CHECKING:
from ..environment import Environment
+ from ..interpreter.type_checking import PkgConfigDefineType
class ConfigToolDependency(ExternalDependency):
Takes the following extra keys in kwargs that it uses internally:
:tools List[str]: A list of tool names to use
:version_arg str: The argument to pass to the tool to get it's version
+ :skip_version str: The argument to pass to the tool to ignore its version
+ (if ``version_arg`` fails, but it may start accepting it in the future)
+ Because some tools are stupid and don't accept --version
:returncode_value int: The value of the correct returncode
Because some tools are stupid and don't return 0
"""
tools: T.Optional[T.List[str]] = None
tool_name: T.Optional[str] = None
version_arg = '--version'
+ skip_version: T.Optional[str] = None
+ 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):
best_match: T.Tuple[T.Optional[T.List[str]], T.Optional[str]] = (None, None)
for potential_bin in find_external_program(
self.env, self.for_machine, self.tool_name,
- self.tool_name, self.tools, allow_default_for_cross=False):
+ self.tool_name, self.tools, allow_default_for_cross=self.allow_default_for_cross):
if not potential_bin.found():
continue
tool = potential_bin.get_command()
except (FileNotFoundError, PermissionError):
continue
if p.returncode != returncode:
- continue
+ if self.skip_version:
+ # maybe the executable is valid even if it doesn't support --version
+ p = Popen_safe(tool + [self.skip_version])[0]
+ if p.returncode != returncode:
+ continue
+ else:
+ continue
out = self._sanitize_version(out.strip())
# Some tools, like pcap-config don't supply a version, but also
return self.config is not None
def get_config_value(self, args: T.List[str], stage: str) -> T.List[str]:
- p, out, err = Popen_safe(self.config + args)
+ p, out, err = Popen_safe_logged(self.config + args)
if p.returncode != 0:
if self.required:
raise DependencyException(f'Could not generate {stage} for {self.name}.\n{err}')
return []
return split_args(out)
- def get_configtool_variable(self, variable_name: str) -> str:
- p, out, _ = Popen_safe(self.config + [f'--{variable_name}'])
- if p.returncode != 0:
- if self.required:
- raise DependencyException(
- 'Could not get variable "{}" for dependency {}'.format(
- variable_name, self.name))
- variable = out.strip()
- mlog.debug(f'Got config-tool variable {variable_name} : {variable}')
- return variable
+ def get_variable_args(self, variable_name: str) -> T.List[str]:
+ return [f'--{variable_name}']
@staticmethod
def log_tried() -> str:
def get_variable(self, *, cmake: T.Optional[str] = None, pkgconfig: T.Optional[str] = None,
configtool: T.Optional[str] = None, internal: T.Optional[str] = None,
default_value: T.Optional[str] = None,
- pkgconfig_define: T.Optional[T.List[str]] = None) -> str:
+ pkgconfig_define: PkgConfigDefineType = None) -> str:
if configtool:
- # In the not required case '' (empty string) will be returned if the
- # variable is not found. Since '' is a valid value to return we
- # set required to True here to force and error, and use the
- # finally clause to ensure it's restored.
- restore = self.required
- self.required = True
- try:
- return self.get_configtool_variable(configtool)
- except DependencyException:
- pass
- finally:
- self.required = restore
+ p, out, _ = Popen_safe(self.config + self.get_variable_args(configtool))
+ if p.returncode == 0:
+ variable = out.strip()
+ mlog.debug(f'Got config-tool variable {configtool} : {variable}')
+ return variable
if default_value is not None:
return default_value
raise DependencyException(f'Could not get config-tool variable and no default provided for {self!r}')
# 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 glob
import re
from .. import mlog
from ..environment import detect_cpu_family
from .base import DependencyException, SystemDependency
+from .detect import packages
if T.TYPE_CHECKING:
from ..environment import Environment
from ..compilers import Compiler
-TV_ResultTuple = T.Tuple[T.Optional[str], T.Optional[str], bool]
+ TV_ResultTuple = T.Tuple[T.Optional[str], T.Optional[str], bool]
class CudaDependency(SystemDependency):
super().__init__('cuda', environment, kwargs, language=language)
self.lib_modules: T.Dict[str, T.List[str]] = {}
self.requested_modules = self.get_requested(kwargs)
- if 'cudart' not in self.requested_modules:
- self.requested_modules = ['cudart'] + self.requested_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):
+ req_modules = ['cudart_static']
+ machine = self.env.machines[self.for_machine]
+ if machine.is_linux():
+ # extracted by running
+ # nvcc -v foo.o
+ req_modules += ['rt', 'pthread', 'dl']
+ self.requested_modules = req_modules + self.requested_modules
(self.cuda_path, self.version, self.is_found) = self._detect_cuda_path_and_version()
if not self.is_found:
except ValueError:
continue
# use // for floor instead of / which produces a float
- major = vers_int // 1000 # type: int
- minor = (vers_int - major * 1000) // 10 # type: int
+ major = vers_int // 1000
+ minor = (vers_int - major * 1000) // 10
return f'{major}.{minor}'
return None
for lib in self.requested_modules:
args += self.lib_modules[lib]
return args
+
+packages['cuda'] = CudaDependency
set(_packageName "${NAME}")
string(TOUPPER "${_packageName}" PACKAGE_NAME)
+if("${STATIC}" STREQUAL "True")
+ set("${NAME}_USE_STATIC_LIBS" "ON")
+endif()
+
while(TRUE)
if ("${VERSION}" STREQUAL "")
find_package("${NAME}" QUIET COMPONENTS ${COMPS})
+# fail noisily if attempt to use this file without setting:
+# cmake_minimum_required(VERSION ${CMAKE_VERSION})
+# project(... LANGUAGES ...)
+
+cmake_policy(SET CMP0000 NEW)
set(PACKAGE_FOUND FALSE)
+list(REMOVE_DUPLICATES LLVM_MESON_VERSIONS)
+
while(TRUE)
- find_package(LLVM REQUIRED CONFIG QUIET)
+ #Activate CMake version selection
+ foreach(i IN LISTS LLVM_MESON_VERSIONS)
+ find_package(LLVM ${i}
+ CONFIG
+ NAMES ${LLVM_MESON_PACKAGE_NAMES}
+ QUIET)
+ if(LLVM_FOUND)
+ break()
+ endif()
+ endforeach()
# ARCHS has to be set via the CMD interface
if(LLVM_FOUND OR "${ARCHS}" STREQUAL "")
list(REMOVE_AT ARCHS 0)
endwhile()
+function(meson_llvm_cmake_dynamic_available mod out)
+ # Check if we can only compare LLVM_DYLIB_COMPONENTS, because
+ # we do not need complex component translation logic, if all
+ # is covered by one variable
+ if(mod IN_LIST LLVM_DYLIB_COMPONENTS)
+ set(${out} TRUE PARENT_SCOPE)
+ return()
+ elseif((NOT (mod IN_LIST LLVM_DYLIB_COMPONENTS))
+ AND (NOT("${LLVM_DYLIB_COMPONENTS}" STREQUAL "all")))
+ set(${out} FALSE PARENT_SCOPE)
+ return()
+ endif()
+
+ # Complex heuristic to filter all pseudo-components and skip invalid names
+ # LLVM_DYLIB_COMPONENTS will be 'all', because in other case we returned
+ # in previous check. 'all' is also handled there.
+ set(llvm_pseudo_components "native" "backend" "engine" "all-targets")
+ is_llvm_target_specifier(${mod} mod_spec INCLUDED_TARGETS)
+ string(TOUPPER "${LLVM_AVAILABLE_LIBS}" capitalized_libs)
+ string(TOUPPER "${LLVM_TARGETS_TO_BUILD}" capitalized_tgts)
+ if(mod_spec)
+ set(${out} TRUE PARENT_SCOPE)
+ elseif(mod IN_LIST capitalized_tgts)
+ set(${out} TRUE PARENT_SCOPE)
+ elseif(mod IN_LIST llvm_pseudo_components)
+ set(${out} TRUE PARENT_SCOPE)
+ elseif(LLVM${mod} IN_LIST capitalized_libs)
+ set(${out} TRUE PARENT_SCOPE)
+ else()
+ set(${out} FALSE PARENT_SCOPE)
+ endif()
+endfunction()
+
+function(is_static target ret)
+ if(TARGET ${target})
+ get_target_property(target_type ${target} TYPE)
+ if(target_type STREQUAL "STATIC_LIBRARY")
+ set(${ret} TRUE PARENT_SCOPE)
+ return()
+ endif()
+ endif()
+ set(${ret} FALSE PARENT_SCOPE)
+endfunction()
+
+# Concatenate LLVM_MESON_REQUIRED_MODULES and LLVM_MESON_OPTIONAL_MODULES
+set(LLVM_MESON_MODULES ${LLVM_MESON_REQUIRED_MODULES} ${LLVM_MESON_OPTIONAL_MODULES})
+
+
+# Check if LLVM exists in dynamic world
+# Initialization before modules checking
if(LLVM_FOUND)
- set(PACKAGE_FOUND TRUE)
+ if(LLVM_MESON_DYLIB AND TARGET LLVM)
+ set(PACKAGE_FOUND TRUE)
+ elseif(NOT LLVM_MESON_DYLIB)
+ # Use LLVMSupport to check if static targets exist
+ set(static_tg FALSE)
+ is_static(LLVMSupport static_tg)
+ if(static_tg)
+ set(PACKAGE_FOUND TRUE)
+ endif(static_tg)
+ endif()
+endif()
+if(PACKAGE_FOUND)
foreach(mod IN LISTS LLVM_MESON_MODULES)
# Reset variables
set(out_mods)
string(TOLOWER "${mod}" mod_L)
string(TOUPPER "${mod}" mod_U)
- # Get the mapped components
- llvm_map_components_to_libnames(out_mods ${mod} ${mod_L} ${mod_U})
- list(SORT out_mods)
- list(REMOVE_DUPLICATES out_mods)
-
- # Make sure that the modules exist
- foreach(i IN LISTS out_mods)
- if(TARGET ${i})
- list(APPEND real_mods ${i})
- endif()
- endforeach()
-
- # Set the output variables
- set(MESON_LLVM_TARGETS_${mod} ${real_mods})
- foreach(i IN LISTS real_mods)
- set(MESON_TARGET_TO_LLVM_${i} ${mod})
- endforeach()
+ # Special case - "all-targets" pseudo target
+ # Just append all targets, if pseudo-target exists
+ if("${mod}" STREQUAL "all-targets")
+ set(mod_L ${LLVM_TARGETS_TO_BUILD})
+ string(TOUPPER "${LLVM_TARGETS_TO_BUILD}" mod_U)
+ endif()
+
+ # Check if required module is linked is inside libLLVM.so.
+ # If not, skip this module
+ if(LLVM_MESON_DYLIB
+ AND DEFINED LLVM_DYLIB_COMPONENTS)
+ meson_llvm_cmake_dynamic_available(${mod} MOD_F)
+ meson_llvm_cmake_dynamic_available(${mod_L} MOD_L_F)
+ meson_llvm_cmake_dynamic_available(${mod_U} MOD_U_F)
+ if(MOD_F OR MOD_L_F OR MOD_U_F)
+ set(MESON_LLVM_TARGETS_${mod} LLVM)
+ endif()
+ elseif(LLVM_MESON_DYLIB AND (mod IN_LIST LLVM_MESON_REQUIRED_MODULES))
+ # Dynamic was requested, but no required variables set, we cannot continue
+ set(PACKAGE_FOUND FALSE)
+ break()
+ elseif(LLVM_MESON_DYLIB)
+ # Dynamic was requested, and we request optional modules only. Continue
+ continue()
+ else()
+ # CMake only do this for static components, and we
+ # replicate its behaviour
+ # Get the mapped components
+ llvm_map_components_to_libnames(out_mods ${mod} ${mod_L} ${mod_U})
+ list(SORT out_mods)
+ list(REMOVE_DUPLICATES out_mods)
+
+ # Make sure that the modules exist
+ foreach(i IN LISTS out_mods)
+ set(static_tg FALSE)
+ is_static(${i} static_tg)
+ if(static_tg)
+ list(APPEND real_mods ${i})
+ endif()
+ endforeach()
+
+ # Set the output variables
+ set(MESON_LLVM_TARGETS_${mod} ${real_mods})
+ foreach(i IN LISTS real_mods)
+ set(MESON_TARGET_TO_LLVM_${i} ${mod})
+ endforeach()
+ endif()
endforeach()
# Check the following variables:
# LLVM_LIBRARIES
# LLVM_LIBS
set(libs)
- if(DEFINED LLVM_LIBRARIES)
+ #Hardcode LLVM, because we links with libLLVM.so when dynamic
+ if(LLVM_MESON_DYLIB)
+ get_target_property(libs LLVM IMPORTED_LOCATION)
+ elseif(DEFINED LLVM_LIBRARIES)
set(libs LLVM_LIBRARIES)
elseif(DEFINED LLVM_LIBS)
set(libs LLVM_LIBS)
# 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 collections, functools, importlib
+import typing as T
from .base import ExternalDependency, DependencyException, DependencyMethods, NotFoundDependency
-from .cmake import CMakeDependency
-from .dub import DubDependency
-from .framework import ExtraFrameworkDependency
-from .pkgconfig import PkgConfigDependency
from ..mesonlib import listify, MachineChoice, PerMachine
from .. import mlog
-import functools
-import typing as T
if T.TYPE_CHECKING:
from ..environment import Environment
TV_DepIDEntry = T.Union[str, bool, int, T.Tuple[str, ...]]
TV_DepID = T.Tuple[T.Tuple[str, TV_DepIDEntry], ...]
+ PackageTypes = T.Union[T.Type[ExternalDependency], DependencyFactory, WrappedFactoryFunc]
+
+class DependencyPackages(collections.UserDict):
+ data: T.Dict[str, PackageTypes]
+ defaults: T.Dict[str, str] = {}
+
+ def __missing__(self, key: str) -> PackageTypes:
+ if key in self.defaults:
+ modn = self.defaults[key]
+ importlib.import_module(f'mesonbuild.dependencies.{modn}')
+
+ return self.data[key]
+ raise KeyError(key)
+
+ def __contains__(self, key: object) -> bool:
+ return key in self.defaults or key in self.data
# These must be defined in this file to avoid cyclical references.
-packages: T.Dict[
- str,
- T.Union[T.Type[ExternalDependency], 'DependencyFactory', 'WrappedFactoryFunc']
-] = {}
+packages = DependencyPackages()
_packages_accept_language: T.Set[str] = set()
def get_dep_identifier(name: str, kwargs: T.Dict[str, T.Any]) -> 'TV_DepID':
'wxwidgets': 'WxWidgets',
}
-def find_external_dependency(name: str, env: 'Environment', kwargs: T.Dict[str, object]) -> T.Union['ExternalDependency', NotFoundDependency]:
+def find_external_dependency(name: str, env: 'Environment', kwargs: T.Dict[str, object], candidates: T.Optional[T.List['DependencyGenerator']] = None) -> T.Union['ExternalDependency', NotFoundDependency]:
assert name
required = kwargs.get('required', True)
if not isinstance(required, bool):
type_text = PerMachine('Build-time', 'Run-time')[for_machine] + ' dependency'
# build a list of dependency methods to try
- candidates = _build_external_dependency_list(name, env, for_machine, kwargs)
+ if candidates is None:
+ candidates = _build_external_dependency_list(name, env, for_machine, kwargs)
pkg_exc: T.List[DependencyException] = []
pkgdep: T.List[ExternalDependency] = []
candidates: T.List['DependencyGenerator'] = []
- # If it's explicitly requested, use the dub detection method (only)
- if 'dub' == kwargs.get('method', ''):
- candidates.append(functools.partial(DubDependency, name, env, kwargs))
- return candidates
-
- # If it's explicitly requested, use the pkgconfig detection method (only)
- if 'pkg-config' == kwargs.get('method', ''):
- candidates.append(functools.partial(PkgConfigDependency, name, env, kwargs))
- return candidates
-
- # If it's explicitly requested, use the CMake detection method (only)
- if 'cmake' == kwargs.get('method', ''):
- candidates.append(functools.partial(CMakeDependency, name, env, kwargs))
- return candidates
+ if kwargs.get('method', 'auto') == 'auto':
+ # Just use the standard detection methods.
+ methods = ['pkg-config', 'extraframework', 'cmake']
+ else:
+ # If it's explicitly requested, use that detection method (only).
+ methods = [kwargs['method']]
- # If it's explicitly requested, use the Extraframework detection method (only)
- if 'extraframework' == kwargs.get('method', ''):
- # On OSX, also try framework dependency detector
- if env.machines[for_machine].is_darwin():
- candidates.append(functools.partial(ExtraFrameworkDependency, name, env, kwargs))
- return candidates
+ # Exclusive to when it is explicitly requested
+ if 'dub' in methods:
+ from .dub import DubDependency
+ candidates.append(functools.partial(DubDependency, name, env, kwargs))
- # Otherwise, just use the pkgconfig and cmake dependency detector
- if 'auto' == kwargs.get('method', 'auto'):
+ # Preferred first candidate for auto.
+ if 'pkg-config' in methods:
+ from .pkgconfig import PkgConfigDependency
candidates.append(functools.partial(PkgConfigDependency, name, env, kwargs))
- # On OSX, also try framework dependency detector
+ # On OSX only, try framework dependency detector.
+ if 'extraframework' in methods:
if env.machines[for_machine].is_darwin():
+ from .framework import ExtraFrameworkDependency
candidates.append(functools.partial(ExtraFrameworkDependency, name, env, kwargs))
- # Only use CMake as a last resort, since it might not work 100% (see #6113)
+ # Only use CMake:
+ # - if it's explicitly requested
+ # - as a last resort, since it might not work 100% (see #6113)
+ if 'cmake' in methods:
+ from .cmake import CMakeDependency
candidates.append(functools.partial(CMakeDependency, name, env, kwargs))
return candidates
import shutil
import subprocess
import typing as T
+import functools
from mesonbuild.interpreterbase.decorators import FeatureDeprecated
from .. import mesonlib, mlog
from ..environment import get_llvm_tool_names
-from ..mesonlib import version_compare, stringlistify, extract_as_list
-from .base import DependencyException, DependencyMethods, detect_compiler, strip_system_libdirs, SystemDependency, ExternalDependency, DependencyTypeName
+from ..mesonlib import version_compare, version_compare_many, search_version, stringlistify, extract_as_list
+from .base import DependencyException, DependencyMethods, detect_compiler, strip_system_includedirs, strip_system_libdirs, SystemDependency, ExternalDependency, DependencyTypeName
from .cmake import CMakeDependency
from .configtool import ConfigToolDependency
+from .detect import packages
from .factory import DependencyFactory
from .misc import threads_factory
from .pkgconfig import PkgConfigDependency
super().__init__(name, env, kwargs, language='cpp', force_use_global_compilers=True)
- # Cmake will always create a statically linked binary, so don't use
- # cmake if dynamic is required
- if not self.static:
- self.is_found = False
- mlog.warning('Ignoring LLVM CMake dependency because dynamic was requested')
+ if self.traceparser is None:
return
- if self.traceparser is None:
+ if not self.is_found:
+ return
+
+ #CMake will return not found due to not defined LLVM_DYLIB_COMPONENTS
+ if not self.static and version_compare(self.version, '< 7.0') and self.llvm_modules:
+ mlog.warning('Before version 7.0 cmake does not export modules for dynamic linking, cannot check required modules')
return
# Extract extra include directories and definitions
defs = defs[0].split(' ')
temp = ['-I' + x for x in inc_dirs] + defs
self.compile_args += [x for x in temp if x not in self.compile_args]
+ self.compile_args = strip_system_includedirs(env, self.for_machine, self.compile_args)
if not self._add_sub_dependency(threads_factory(env, self.for_machine, {})):
self.is_found = False
return
# Use a custom CMakeLists.txt for LLVM
return 'CMakeListsLLVM.txt'
+ # Check version in CMake to return exact version as config tool (latest allowed)
+ # It is safe to add .0 to latest argument, it will discarded if we use search_version
+ def llvm_cmake_versions(self) -> T.List[str]:
+
+ def ver_from_suf(req: str) -> str:
+ return search_version(req.strip('-')+'.0')
+
+ def version_sorter(a: str, b: str) -> int:
+ if version_compare(a, "="+b):
+ return 0
+ if version_compare(a, "<"+b):
+ return 1
+ return -1
+
+ llvm_requested_versions = [ver_from_suf(x) for x in get_llvm_tool_names('') if version_compare(ver_from_suf(x), '>=0')]
+ if self.version_reqs:
+ llvm_requested_versions = [ver_from_suf(x) for x in get_llvm_tool_names('') if version_compare_many(ver_from_suf(x), self.version_reqs)]
+ # CMake sorting before 3.18 is incorrect, sort it here instead
+ return sorted(llvm_requested_versions, key=functools.cmp_to_key(version_sorter))
+
+ # Split required and optional modules to distinguish it in CMake
def _extra_cmake_opts(self) -> T.List[str]:
- return ['-DLLVM_MESON_MODULES={}'.format(';'.join(self.llvm_modules + self.llvm_opt_modules))]
+ return ['-DLLVM_MESON_REQUIRED_MODULES={}'.format(';'.join(self.llvm_modules)),
+ '-DLLVM_MESON_OPTIONAL_MODULES={}'.format(';'.join(self.llvm_opt_modules)),
+ '-DLLVM_MESON_PACKAGE_NAMES={}'.format(';'.join(get_llvm_tool_names(self.name))),
+ '-DLLVM_MESON_VERSIONS={}'.format(';'.join(self.llvm_cmake_versions())),
+ '-DLLVM_MESON_DYLIB={}'.format('OFF' if self.static else 'ON')]
def _map_module_list(self, modules: T.List[T.Tuple[str, bool]], components: T.List[T.Tuple[str, bool]]) -> T.List[T.Tuple[str, bool]]:
res = []
if required:
raise self._gen_exception(f'LLVM module {mod} was not found')
else:
- mlog.warning('Optional LLVM module', mlog.bold(mod), 'was not found')
+ mlog.warning('Optional LLVM module', mlog.bold(mod), 'was not found', fatal=False)
continue
for i in cm_targets:
res += [(i, required)]
def get_link_args(self, language: T.Optional[str] = None, raw: bool = False) -> T.List[str]:
return []
+packages['valgrind'] = ValgrindDependency
+
class ZlibSystemDependency(SystemDependency):
else:
libs = ['z']
for lib in libs:
- l = self.clib_compiler.find_library(lib, environment, [])
+ l = self.clib_compiler.find_library(lib, environment, [], self.libtype)
h = self.clib_compiler.has_header('zlib.h', '', environment, dependencies=[self])
if l and h[0]:
self.is_found = True
modules: T.List[str] = mesonlib.listify(kwargs.get('modules', []))
for module in modules:
if module not in {'jvm', 'awt'}:
- log = mlog.error if self.required else mlog.debug
- log(f'Unknown JNI module ({module})')
+ msg = f'Unknown JNI module ({module})'
+ if self.required:
+ mlog.error(msg)
+ else:
+ mlog.debug(msg)
self.is_found = False
return
res = subprocess.run(['/usr/libexec/java_home', '--failfast', '--arch', m.cpu_family],
stdout=subprocess.PIPE)
if res.returncode != 0:
- log = mlog.error if self.required else mlog.debug
- log('JAVA_HOME could not be discovered on the system. Please set it explicitly.')
+ msg = 'JAVA_HOME could not be discovered on the system. Please set it explicitly.'
+ if self.required:
+ mlog.error(msg)
+ else:
+ mlog.debug(msg)
self.is_found = False
return
self.java_home = pathlib.Path(res.stdout.decode().strip())
return None
+packages['jni'] = JNISystemDependency
+
class JDKSystemDependency(JNISystemDependency):
def __init__(self, environment: 'Environment', kwargs: JNISystemDependencyKW):
'Use the jni system dependency instead'
))
+packages['jdk'] = JDKSystemDependency
+
-llvm_factory = DependencyFactory(
+packages['llvm'] = llvm_factory = DependencyFactory(
'LLVM',
[DependencyMethods.CMAKE, DependencyMethods.CONFIG_TOOL],
cmake_class=LLVMDependencyCMake,
configtool_class=LLVMDependencyConfigTool,
)
-gtest_factory = DependencyFactory(
+packages['gtest'] = gtest_factory = DependencyFactory(
'gtest',
[DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM],
pkgconfig_class=GTestDependencyPC,
system_class=GTestDependencySystem,
)
-gmock_factory = DependencyFactory(
+packages['gmock'] = gmock_factory = DependencyFactory(
'gmock',
[DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM],
pkgconfig_class=GMockDependencyPC,
system_class=GMockDependencySystem,
)
-zlib_factory = DependencyFactory(
+packages['zlib'] = zlib_factory = DependencyFactory(
'zlib',
[DependencyMethods.PKGCONFIG, DependencyMethods.CMAKE, DependencyMethods.SYSTEM],
cmake_name='ZLIB',
# 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
from .base import ExternalDependency, DependencyException, DependencyTypeName
from .pkgconfig import PkgConfigDependency
-from ..mesonlib import (Popen_safe, OptionKey, join_args)
+from ..mesonlib import (Popen_safe, OptionKey, join_args, version_compare)
from ..programs import ExternalProgram
from .. import mlog
import re
if T.TYPE_CHECKING:
from ..environment import Environment
+
class DubDependency(ExternalDependency):
- class_dubbin = None
+ # dub program and version
+ class_dubbin: T.Optional[T.Tuple[ExternalProgram, str]] = None
+ class_dubbin_searched = False
def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]):
super().__init__(DependencyTypeName('dub'), environment, kwargs, language='d')
if 'required' in kwargs:
self.required = kwargs.get('required')
+ if DubDependency.class_dubbin is None and not DubDependency.class_dubbin_searched:
+ DubDependency.class_dubbin = self._check_dub()
+ DubDependency.class_dubbin_searched = True
if DubDependency.class_dubbin is None:
- self.dubbin = self._check_dub()
- DubDependency.class_dubbin = self.dubbin
- else:
- self.dubbin = DubDependency.class_dubbin
-
- if not self.dubbin:
if self.required:
raise DependencyException('DUB not found.')
self.is_found = False
return
+ (self.dubbin, dubver) = DubDependency.class_dubbin # pylint: disable=unpacking-non-sequence
+
assert isinstance(self.dubbin, ExternalProgram)
+
+ # Check if Dub version is compatible with Meson
+ if version_compare(dubver, '>1.31.1'):
+ if self.required:
+ raise DependencyException(
+ f"DUB version {dubver} is not compatible with Meson (can't locate artifacts in Dub cache)")
+ self.is_found = False
+ return
+
mlog.debug('Determining dependency {!r} with DUB executable '
'{!r}'.format(name, self.dubbin.get_path()))
elif 'compiler' not in compatibilities:
mlog.error(mlog.bold(pack_id), 'found but not compiled with ', mlog.bold(dub_comp_id))
elif dub_comp_id != 'gdc' and 'compiler_version' not in compatibilities:
- mlog.error(mlog.bold(pack_id), 'found but not compiled with', mlog.bold(f'{dub_comp_id}-{self.compiler.version}'))
+ mlog.error(mlog.bold(pack_id), 'found but not compiled with',
+ mlog.bold(f'{dub_comp_id}-{self.compiler.version}'))
elif 'arch' not in compatibilities:
mlog.error(mlog.bold(pack_id), 'found but not compiled for', mlog.bold(dub_arch))
elif 'platform' not in compatibilities:
- mlog.error(mlog.bold(pack_id), 'found but not compiled for', mlog.bold(description['platform'].join('.')))
+ mlog.error(mlog.bold(pack_id), 'found but not compiled for',
+ mlog.bold(description['platform'].join('.')))
elif 'configuration' not in compatibilities:
- mlog.error(mlog.bold(pack_id), 'found but not compiled for the', mlog.bold(pkg['configuration']), 'configuration')
+ mlog.error(mlog.bold(pack_id), 'found but not compiled for the',
+ mlog.bold(pkg['configuration']), 'configuration')
else:
mlog.error(mlog.bold(pack_id), 'not found')
self.is_found = False
return
- ## check that the main dependency is indeed a library
+ # check that the main dependency is indeed a library
if pkg['name'] == name:
self.is_found = True
if ret != 0:
mlog.error('Failed to run {!r}', mlog.bold(dub_comp_id))
return (None, None)
- d_ver_reg = re.search('v[0-9].[0-9][0-9][0-9].[0-9]', res) # Ex.: v2.081.2
+ d_ver_reg = re.search('v[0-9].[0-9][0-9][0-9].[0-9]', res) # Ex.: v2.081.2
if d_ver_reg is not None:
frontend_version = d_ver_reg.group()
- frontend_id = frontend_version.rsplit('.', 1)[0].replace('v', '').replace('.', '') # Fix structure. Ex.: 2081
+ frontend_id = frontend_version.rsplit('.', 1)[0].replace(
+ 'v', '').replace('.', '') # Fix structure. Ex.: 2081
comp_versions.extend([frontend_version, frontend_id])
compatibilities: T.Set[str] = set()
p, out, err = Popen_safe(self.compiler.get_exelist() + args, env=env)
return p.returncode, out.strip(), err.strip()
- def _check_dub(self) -> T.Union[bool, ExternalProgram]:
- dubbin: T.Union[bool, ExternalProgram] = ExternalProgram('dub', silent=True)
- assert isinstance(dubbin, ExternalProgram)
- if dubbin.found():
+ def _check_dub(self) -> T.Optional[T.Tuple[ExternalProgram, str]]:
+
+ def find() -> T.Optional[T.Tuple[ExternalProgram, str]]:
+ dubbin = ExternalProgram('dub', silent=True)
+
+ if not dubbin.found():
+ return None
+
try:
p, out = Popen_safe(dubbin.get_command() + ['--version'])[0:2]
if p.returncode != 0:
mlog.warning('Found dub {!r} but couldn\'t run it'
''.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
+ return None
+
except (FileNotFoundError, PermissionError):
- dubbin = False
+ return None
+
+ vermatch = re.search(r'DUB version (\d+\.\d+\.\d+.*), ', out.strip())
+ if vermatch:
+ dubver = vermatch.group(1)
+ else:
+ mlog.warning(f"Found dub {' '.join(dubbin.get_command())} but couldn't parse version in {out.strip()}")
+ return None
+
+ return (dubbin, dubver)
+
+ found = find()
+
+ if found is None:
+ mlog.log('Found DUB:', mlog.red('NO'))
else:
- dubbin = False
- if isinstance(dubbin, ExternalProgram):
+ (dubbin, dubver) = found
mlog.log('Found DUB:', mlog.bold(dubbin.get_path()),
- '(%s)' % out.strip())
- else:
- mlog.log('Found DUB:', mlog.red('NO'))
- return dubbin
+ '(version %s)' % dubver)
+
+ return found
# 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
from .base import DependencyTypeName, ExternalDependency, DependencyException
from ..mesonlib import MesonException, Version, stringlistify
# https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPFrameworks/Tasks/IncludingFrameworks.html
incdir = self._get_framework_include_path(framework_path)
if incdir:
- self.compile_args += ['-I' + incdir]
+ self.compile_args += ['-idirafter' + incdir]
self.is_found = True
return
# limitations under the License.
# This file contains the detection logic for miscellaneous external dependencies.
+from __future__ import annotations
import functools
import os
import re
-import subprocess
from pathlib import Path
-from ..mesonlib import Popen_safe, OrderedSet, join_args
-from ..programs import ExternalProgram
+from ..mesonlib import OrderedSet, join_args
from .base import DependencyException, DependencyMethods
from .configtool import ConfigToolDependency
-from .pkgconfig import PkgConfigDependency
+from .detect import packages
+from .pkgconfig import PkgConfigDependency, PkgConfigInterface
from .factory import factory_methods
import typing as T
return
# some broken pkgconfig don't actually list the full path to the needed includes
- newinc = [] # type: T.List[str]
+ newinc: T.List[str] = []
for arg in self.compile_args:
if arg.startswith('-I'):
stem = 'static' if self.static else 'shared'
newinc.append('-I' + str(Path(arg[2:]) / stem))
self.compile_args += newinc
- link_args = [] # type: T.List[str]
+ link_args: T.List[str] = []
for larg in self.get_link_args():
lpath = Path(larg)
# some pkg-config hdf5.pc (e.g. Ubuntu) don't include the commonly-used HL HDF5 libraries,
if language == 'c':
cenv = 'CC'
+ lenv = 'C'
tools = ['h5cc', 'h5pcc']
elif language == 'cpp':
cenv = 'CXX'
+ lenv = 'CXX'
tools = ['h5c++', 'h5pc++']
elif language == 'fortran':
cenv = 'FC'
+ lenv = 'F'
tools = ['h5fc', 'h5pfc']
else:
raise DependencyException('How did you get here?')
compiler = environment.coredata.compilers[for_machine][language]
try:
os.environ[f'HDF5_{cenv}'] = join_args(compiler.get_exelist())
- os.environ[f'HDF5_{cenv}LINKER'] = join_args(compiler.get_linker_exelist())
+ os.environ[f'HDF5_{lenv}LINKER'] = join_args(compiler.get_linker_exelist())
super().__init__(name, environment, nkwargs, language)
finally:
del os.environ[f'HDF5_{cenv}']
- del os.environ[f'HDF5_{cenv}LINKER']
+ del os.environ[f'HDF5_{lenv}LINKER']
if not self.is_found:
return
elif Path(arg).is_file():
self.link_args.append(arg)
- # If the language is not C we need to add C as a subdependency
- if language != 'c':
- nkwargs = kwargs.copy()
- nkwargs['language'] = 'c'
- # I'm being too clever for mypy and pylint
- self.is_found = self._add_sub_dependency(hdf5_factory(environment, for_machine, nkwargs)) # pylint: disable=no-value-for-parameter
-
def _sanitize_version(self, ver: str) -> str:
v = re.search(r'\s*HDF5 Version: (\d+\.\d+\.\d+)', ver)
return v.group(1)
if DependencyMethods.PKGCONFIG in methods:
# Use an ordered set so that these remain the first tried pkg-config files
pkgconfig_files = OrderedSet(['hdf5', 'hdf5-serial'])
- PCEXE = PkgConfigDependency._detect_pkgbin(False, env, for_machine)
- pcenv = PkgConfigDependency.setup_env(os.environ, env, for_machine)
- if PCEXE:
- assert isinstance(PCEXE, ExternalProgram)
+ pkg = PkgConfigInterface.instance(env, for_machine, silent=False)
+ if pkg:
# some distros put hdf5-1.2.3.pc with version number in .pc filename.
- ret, stdout, _ = Popen_safe(PCEXE.get_command() + ['--list-all'], stderr=subprocess.DEVNULL, env=pcenv)
- if ret.returncode == 0:
- for pkg in stdout.split('\n'):
- if pkg.startswith('hdf5'):
- pkgconfig_files.add(pkg.split(' ', 1)[0])
-
- for pkg in pkgconfig_files:
- candidates.append(functools.partial(HDF5PkgConfigDependency, pkg, env, kwargs, language))
+ for mod in pkg.list_all():
+ if mod.startswith('hdf5'):
+ pkgconfig_files.add(mod)
+ for mod in pkgconfig_files:
+ candidates.append(functools.partial(HDF5PkgConfigDependency, mod, env, kwargs, language))
if DependencyMethods.CONFIG_TOOL in methods:
candidates.append(functools.partial(HDF5ConfigToolDependency, 'hdf5', env, kwargs, language))
return candidates
+
+packages['hdf5'] = hdf5_factory
# limitations under the License.
# This file contains the detection logic for miscellaneous external dependencies.
+from __future__ import annotations
-from pathlib import Path
import functools
import re
-import sysconfig
import typing as T
from .. import mesonlib
from .. import mlog
-from ..environment import detect_cpu_family
from .base import DependencyException, DependencyMethods
from .base import BuiltinDependency, SystemDependency
-from .cmake import CMakeDependency
+from .cmake import CMakeDependency, CMakeDependencyFactory
from .configtool import ConfigToolDependency
+from .detect import packages
from .factory import DependencyFactory, factory_methods
from .pkgconfig import PkgConfigDependency
if T.TYPE_CHECKING:
- from ..environment import Environment, MachineChoice
+ from ..environment import Environment
from .factory import DependencyGenerator
@factory_methods({DependencyMethods.PKGCONFIG, DependencyMethods.CMAKE})
def netcdf_factory(env: 'Environment',
- for_machine: 'MachineChoice',
+ for_machine: 'mesonlib.MachineChoice',
kwargs: T.Dict[str, T.Any],
methods: T.List[DependencyMethods]) -> T.List['DependencyGenerator']:
language = kwargs.get('language', 'c')
return candidates
+packages['netcdf'] = netcdf_factory
+
class DlBuiltinDependency(BuiltinDependency):
def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]):
class OpenMPDependency(SystemDependency):
# Map date of specification release (which is the macro value) to a version.
VERSIONS = {
+ '202111': '5.2',
+ '202011': '5.1',
'201811': '5.0',
'201611': '5.0-revision1', # This is supported by ICC 19.x
'201511': '4.5',
if not self.is_found:
mlog.log(mlog.yellow('WARNING:'), 'OpenMP found but omp.h missing.')
+packages['openmp'] = OpenMPDependency
+
class ThreadDependency(SystemDependency):
def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]) -> None:
self.is_found = True
+packages['blocks'] = BlocksDependency
-class Python3DependencySystem(SystemDependency):
- def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]) -> None:
- super().__init__(name, environment, kwargs)
-
- if not environment.machines.matches_build_machine(self.for_machine):
- return
- if not environment.machines[self.for_machine].is_windows():
- return
-
- self.name = 'python3'
- # We can only be sure that it is Python 3 at this point
- self.version = '3'
- self._find_libpy3_windows(environment)
-
- @staticmethod
- def get_windows_python_arch() -> T.Optional[str]:
- pyplat = sysconfig.get_platform()
- if pyplat == 'mingw':
- pycc = sysconfig.get_config_var('CC')
- if pycc.startswith('x86_64'):
- return '64'
- elif pycc.startswith(('i686', 'i386')):
- return '32'
- else:
- mlog.log(f'MinGW Python built with unknown CC {pycc!r}, please file a bug')
- return None
- elif pyplat == 'win32':
- return '32'
- elif pyplat in {'win64', 'win-amd64'}:
- return '64'
- mlog.log(f'Unknown Windows Python platform {pyplat!r}')
- return None
-
- def get_windows_link_args(self) -> T.Optional[T.List[str]]:
- pyplat = sysconfig.get_platform()
- if pyplat.startswith('win'):
- vernum = sysconfig.get_config_var('py_version_nodot')
- if self.static:
- libpath = Path('libs') / f'libpython{vernum}.a'
- else:
- comp = self.get_compiler()
- if comp.id == "gcc":
- libpath = Path(f'python{vernum}.dll')
- else:
- libpath = Path('libs') / f'python{vernum}.lib'
- lib = Path(sysconfig.get_config_var('base')) / libpath
- elif pyplat == 'mingw':
- if self.static:
- libname = sysconfig.get_config_var('LIBRARY')
- else:
- libname = sysconfig.get_config_var('LDLIBRARY')
- lib = Path(sysconfig.get_config_var('LIBDIR')) / libname
- if not lib.exists():
- mlog.log('Could not find Python3 library {!r}'.format(str(lib)))
- return None
- return [str(lib)]
-
- def _find_libpy3_windows(self, env: 'Environment') -> None:
- '''
- Find python3 libraries on Windows and also verify that the arch matches
- what we are building for.
- '''
- pyarch = self.get_windows_python_arch()
- if pyarch is None:
- self.is_found = False
- return
- arch = detect_cpu_family(env.coredata.compilers.host)
- if arch == 'x86':
- arch = '32'
- elif arch == 'x86_64':
- arch = '64'
- else:
- # We can't cross-compile Python 3 dependencies on Windows yet
- mlog.log(f'Unknown architecture {arch!r} for',
- mlog.bold(self.name))
- self.is_found = False
- return
- # Pyarch ends in '32' or '64'
- if arch != pyarch:
- mlog.log('Need', mlog.bold(self.name), 'for {}-bit, but '
- 'found {}-bit'.format(arch, pyarch))
- self.is_found = False
- return
- # This can fail if the library is not found
- largs = self.get_windows_link_args()
- if largs is None:
- self.is_found = False
- return
- self.link_args = largs
- # Compile args
- inc = sysconfig.get_path('include')
- platinc = sysconfig.get_path('platinclude')
- self.compile_args = ['-I' + inc]
- if inc != platinc:
- self.compile_args.append('-I' + platinc)
- self.version = sysconfig.get_config_var('py_version')
- self.is_found = True
-
- @staticmethod
- def log_tried() -> str:
- return 'sysconfig'
class PcapDependencyConfigTool(ConfigToolDependency):
tools = ['pcap-config']
tool_name = 'pcap-config'
+ # version 1.10.2 added error checking for invalid arguments
+ # 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]):
super().__init__(name, environment, kwargs)
if not self.is_found:
return
self.compile_args = self.get_config_value(['--cflags'], 'compile_args')
self.link_args = self.get_config_value(['--libs'], 'link_args')
- self.version = self.get_pcap_lib_version()
+ if self.version is None:
+ # older pcap-config versions don't support this
+ self.version = self.get_pcap_lib_version()
def get_pcap_lib_version(self) -> T.Optional[str]:
# Since we seem to need to run a program to discover the pcap version,
('curses', ['curses.h']),
]
- # Not sure how else to elegently break out of both loops
+ # Not sure how else to elegantly break out of both loops
for lib, headers in candidates:
l = self.clib_compiler.find_library(lib, env, [])
if l:
@factory_methods({DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL, DependencyMethods.SYSTEM})
def curses_factory(env: 'Environment',
- for_machine: 'MachineChoice',
+ for_machine: 'mesonlib.MachineChoice',
kwargs: T.Dict[str, T.Any],
methods: T.List[DependencyMethods]) -> T.List['DependencyGenerator']:
candidates: T.List['DependencyGenerator'] = []
candidates.append(functools.partial(CursesSystemDependency, 'curses', env, kwargs))
return candidates
+packages['curses'] = curses_factory
@factory_methods({DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM})
def shaderc_factory(env: 'Environment',
- for_machine: 'MachineChoice',
+ for_machine: 'mesonlib.MachineChoice',
kwargs: T.Dict[str, T.Any],
methods: T.List[DependencyMethods]) -> T.List['DependencyGenerator']:
"""Custom DependencyFactory for ShaderC.
candidates.append(functools.partial(ShadercDependency, env, kwargs))
return candidates
+packages['shaderc'] = shaderc_factory
-cups_factory = DependencyFactory(
+packages['cups'] = cups_factory = DependencyFactory(
'cups',
[DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL, DependencyMethods.EXTRAFRAMEWORK, DependencyMethods.CMAKE],
configtool_class=CupsDependencyConfigTool,
cmake_name='Cups',
)
-dl_factory = DependencyFactory(
+packages['dl'] = dl_factory = DependencyFactory(
'dl',
[DependencyMethods.BUILTIN, DependencyMethods.SYSTEM],
builtin_class=DlBuiltinDependency,
system_class=DlSystemDependency,
)
-gpgme_factory = DependencyFactory(
+packages['gpgme'] = gpgme_factory = DependencyFactory(
'gpgme',
[DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL],
configtool_class=GpgmeDependencyConfigTool,
)
-libgcrypt_factory = DependencyFactory(
+packages['libgcrypt'] = libgcrypt_factory = DependencyFactory(
'libgcrypt',
[DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL],
configtool_class=LibGCryptDependencyConfigTool,
)
-libwmf_factory = DependencyFactory(
+packages['libwmf'] = libwmf_factory = DependencyFactory(
'libwmf',
[DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL],
configtool_class=LibWmfDependencyConfigTool,
)
-pcap_factory = DependencyFactory(
+packages['pcap'] = pcap_factory = DependencyFactory(
'pcap',
[DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL],
configtool_class=PcapDependencyConfigTool,
pkgconfig_name='libpcap',
)
-python3_factory = DependencyFactory(
- 'python3',
- [DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM, DependencyMethods.EXTRAFRAMEWORK],
- system_class=Python3DependencySystem,
- # There is no version number in the macOS version number
- framework_name='Python',
- # There is a python in /System/Library/Frameworks, but that's python 2.x,
- # Python 3 will always be in /Library
- extra_kwargs={'paths': ['/Library/Frameworks']},
-)
-
-threads_factory = DependencyFactory(
+packages['threads'] = threads_factory = DependencyFactory(
'threads',
[DependencyMethods.SYSTEM, DependencyMethods.CMAKE],
cmake_name='Threads',
system_class=ThreadDependency,
)
-iconv_factory = DependencyFactory(
+packages['iconv'] = iconv_factory = DependencyFactory(
'iconv',
[DependencyMethods.BUILTIN, DependencyMethods.SYSTEM],
builtin_class=IconvBuiltinDependency,
system_class=IconvSystemDependency,
)
-intl_factory = DependencyFactory(
+packages['intl'] = intl_factory = DependencyFactory(
'intl',
[DependencyMethods.BUILTIN, DependencyMethods.SYSTEM],
builtin_class=IntlBuiltinDependency,
system_class=IntlSystemDependency,
)
-openssl_factory = DependencyFactory(
+packages['openssl'] = openssl_factory = DependencyFactory(
'openssl',
[DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM, DependencyMethods.CMAKE],
system_class=OpensslSystemDependency,
- cmake_class=lambda name, env, kwargs: CMakeDependency('OpenSSL', env, dict(kwargs, modules=['OpenSSL::Crypto', 'OpenSSL::SSL'])),
+ cmake_class=CMakeDependencyFactory('OpenSSL', modules=['OpenSSL::Crypto', 'OpenSSL::SSL']),
)
-libcrypto_factory = DependencyFactory(
+packages['libcrypto'] = libcrypto_factory = DependencyFactory(
'libcrypto',
[DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM, DependencyMethods.CMAKE],
system_class=OpensslSystemDependency,
- cmake_class=lambda name, env, kwargs: CMakeDependency('OpenSSL', env, dict(kwargs, modules=['OpenSSL::Crypto'])),
+ cmake_class=CMakeDependencyFactory('OpenSSL', modules=['OpenSSL::Crypto']),
)
-libssl_factory = DependencyFactory(
+packages['libssl'] = libssl_factory = DependencyFactory(
'libssl',
[DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM, DependencyMethods.CMAKE],
system_class=OpensslSystemDependency,
- cmake_class=lambda name, env, kwargs: CMakeDependency('OpenSSL', env, dict(kwargs, modules=['OpenSSL::SSL'])),
+ cmake_class=CMakeDependencyFactory('OpenSSL', modules=['OpenSSL::SSL']),
)
# 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 functools
import typing as T
from ..environment import detect_cpu_family
from .base import DependencyMethods, detect_compiler, SystemDependency
from .configtool import ConfigToolDependency
+from .detect import packages
from .factory import factory_methods
from .pkgconfig import PkgConfigDependency
if T.TYPE_CHECKING:
from .factory import DependencyGenerator
- from ..environment import Environment, MachineChoice
+ from ..environment import Environment
+ from ..mesonlib import MachineChoice
@factory_methods({DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL, DependencyMethods.SYSTEM})
elif language == 'fortran':
tool_names = [os.environ.get('I_MPI_F90'), 'mpiifort']
- cls = IntelMPIConfigToolDependency # type: T.Type[ConfigToolDependency]
+ cls: T.Type[ConfigToolDependency] = IntelMPIConfigToolDependency
else: # OpenMPI, which doesn't work with intel
#
# We try the environment variables for the tools first, but then
return candidates
+packages['mpi'] = mpi_factory
+
class _MPIConfigToolDependency(ConfigToolDependency):
from pathlib import Path
from .base import ExternalDependency, DependencyException, sort_libpaths, DependencyTypeName
-from ..mesonlib import OptionKey, OrderedSet, PerMachine, Popen_safe
+from ..mesonlib import EnvironmentVariables, OptionKey, OrderedSet, PerMachine, Popen_safe, Popen_safe_logged, MachineChoice, join_args
from ..programs import find_external_program, ExternalProgram
from .. import mlog
from pathlib import PurePath
+from functools import lru_cache
import re
import os
import shlex
import typing as T
if T.TYPE_CHECKING:
- from ..environment import Environment
- from ..mesonlib import MachineChoice
+ from typing_extensions import Literal
from .._typing import ImmutableListProtocol
- from ..build import EnvironmentVariables
-class PkgConfigDependency(ExternalDependency):
- # The class's copy of the pkg-config path. Avoids having to search for it
- # multiple times in the same Meson invocation.
- class_pkgbin: PerMachine[T.Union[None, bool, ExternalProgram]] = PerMachine(None, None)
- # We cache all pkg-config subprocess invocations to avoid redundant calls
- pkgbin_cache: T.Dict[
- T.Tuple[ExternalProgram, T.Tuple[str, ...], T.FrozenSet[T.Tuple[str, str]]],
- T.Tuple[int, str, str]
- ] = {}
-
- def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any], language: T.Optional[str] = None) -> None:
- super().__init__(DependencyTypeName('pkgconfig'), environment, kwargs, language=language)
- self.name = name
- self.is_libtool = False
- # Store a copy of the pkg-config path on the object itself so it is
- # stored in the pickled coredata and recovered.
- self.pkgbin = self._detect_pkgbin(self.silent, self.env, self.for_machine)
- if self.pkgbin is False:
- self.pkgbin = None
- msg = f'Pkg-config binary for machine {self.for_machine} not found. Giving up.'
- if self.required:
- raise DependencyException(msg)
- else:
- mlog.debug(msg)
- return
+ from ..environment import Environment
+ from ..utils.core import EnvironOrDict
+ from ..interpreter.type_checking import PkgConfigDefineType
- assert isinstance(self.pkgbin, ExternalProgram)
- mlog.debug('Determining dependency {!r} with pkg-config executable '
- '{!r}'.format(name, self.pkgbin.get_path()))
- ret, self.version, _ = self._call_pkgbin(['--modversion', name])
- if ret != 0:
- return
+class PkgConfigInterface:
+ '''Base class wrapping a pkg-config implementation'''
- self.is_found = True
+ 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)
- try:
- # Fetch cargs to be used while using this dependency
- self._set_cargs()
- # Fetch the libraries and library paths needed for using this
- self._set_libs()
- except DependencyException as e:
- mlog.debug(f"pkg-config error with '{name}': {e}")
- if self.required:
- raise
- else:
- self.compile_args = []
- self.link_args = []
- self.is_found = False
- self.reason = e
+ @staticmethod
+ def instance(env: Environment, for_machine: MachineChoice, silent: bool) -> 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]
+ if impl is False:
+ impl = PkgConfigCLI(env, for_machine, silent)
+ 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
+ return impl
- def __repr__(self) -> str:
- s = '<{0} {1}: {2} {3}>'
- return s.format(self.__class__.__name__, self.name, self.is_found,
- self.version_reqs)
+ @staticmethod
+ def _cli(env: Environment, for_machine: MachineChoice, 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.
+ '''
+ for_machine = for_machine if env.is_cross_build() else MachineChoice.HOST
+ 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]
+ if impl is False:
+ impl = PkgConfigCLI(env, for_machine, silent)
+ if not impl.found():
+ impl = None
+ PkgConfigInterface.class_cli_impl[for_machine] = impl
+ return T.cast('T.Optional[PkgConfigCLI]', impl) # Trust me, mypy
- @classmethod
- def _detect_pkgbin(cls, silent: bool, env: Environment,
- for_machine: MachineChoice) -> T.Union[None, bool, ExternalProgram]:
- # Only search for pkg-config for each machine the first time and store
- # the result in the class definition
- if cls.class_pkgbin[for_machine] is False:
- mlog.debug(f'Pkg-config binary for {for_machine} is cached as not found.')
- elif cls.class_pkgbin[for_machine] is not None:
- mlog.debug(f'Pkg-config binary for {for_machine} is cached.')
- else:
- assert cls.class_pkgbin[for_machine] is None, 'for mypy'
- mlog.debug(f'Pkg-config binary for {for_machine} is not cached.')
- for potential_pkgbin in find_external_program(
- env, for_machine, 'pkgconfig', 'Pkg-config',
- env.default_pkgconfig, allow_default_for_cross=False):
- version_if_ok = cls.check_pkgconfig(env, potential_pkgbin)
- if not version_if_ok:
- continue
- if not silent:
- mlog.log('Found pkg-config:', mlog.bold(potential_pkgbin.get_path()),
- f'({version_if_ok})')
- cls.class_pkgbin[for_machine] = potential_pkgbin
- break
- else:
- if not silent:
- mlog.log('Found Pkg-config:', mlog.red('NO'))
- # Set to False instead of None to signify that we've already
- # searched for it and not found it
- cls.class_pkgbin[for_machine] = False
+ @staticmethod
+ def get_env(env: Environment, for_machine: MachineChoice, uninstalled: bool = False) -> EnvironmentVariables:
+ cli = PkgConfigInterface._cli(env, for_machine)
+ return cli._get_env(uninstalled) if cli else EnvironmentVariables()
+
+ @staticmethod
+ def setup_env(environ: EnvironOrDict, env: Environment, for_machine: MachineChoice,
+ uninstalled: bool = False) -> EnvironOrDict:
+ cli = PkgConfigInterface._cli(env, for_machine)
+ return cli._setup_env(environ, uninstalled) if cli else environ
+
+ def __init__(self, env: Environment, for_machine: MachineChoice) -> None:
+ self.env = env
+ self.for_machine = for_machine
+
+ def found(self) -> bool:
+ '''Return whether pkg-config is supported'''
+ raise NotImplementedError
+
+ def version(self, name: str) -> T.Optional[str]:
+ '''Return module version or None if not found'''
+ raise NotImplementedError
+
+ def cflags(self, name: str, allow_system: bool = False,
+ define_variable: PkgConfigDefineType = None) -> ImmutableListProtocol[str]:
+ '''Return module cflags
+ @allow_system: If False, remove default system include paths
+ '''
+ raise NotImplementedError
- return cls.class_pkgbin[for_machine]
+ def libs(self, name: str, static: bool = False, allow_system: bool = False,
+ define_variable: PkgConfigDefineType = None) -> ImmutableListProtocol[str]:
+ '''Return module libs
+ @static: If True, also include private libraries
+ @allow_system: If False, remove default system libraries search paths
+ '''
+ raise NotImplementedError
- def _call_pkgbin_real(self, args: T.List[str], env: T.Dict[str, str]) -> T.Tuple[int, str, str]:
- assert isinstance(self.pkgbin, ExternalProgram)
- cmd = self.pkgbin.get_command() + args
- p, out, err = Popen_safe(cmd, env=env)
- rc, out, err = p.returncode, out.strip(), err.strip()
- call = ' '.join(cmd)
- mlog.debug(f"Called `{call}` -> {rc}\n{out}")
- return rc, out, err
+ def variable(self, name: str, variable_name: str,
+ define_variable: PkgConfigDefineType) -> T.Optional[str]:
+ '''Return module variable or None if variable is not defined'''
+ raise NotImplementedError
+
+ def list_all(self) -> ImmutableListProtocol[str]:
+ '''Return all available pkg-config modules'''
+ raise NotImplementedError
+
+class PkgConfigCLI(PkgConfigInterface):
+ '''pkg-config CLI implementation'''
+
+ def __init__(self, env: Environment, for_machine: MachineChoice, silent: bool) -> None:
+ super().__init__(env, for_machine)
+ self._detect_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))
+
+ def found(self) -> bool:
+ return bool(self.pkgbin)
+
+ @lru_cache(maxsize=None)
+ def version(self, name: str) -> T.Optional[str]:
+ mlog.debug(f'Determining dependency {name!r} with pkg-config executable {self.pkgbin.get_path()!r}')
+ ret, version, _ = self._call_pkgbin(['--modversion', name])
+ return version if ret == 0 else None
+
+ @staticmethod
+ def _define_variable_args(define_variable: PkgConfigDefineType) -> T.List[str]:
+ ret = []
+ if define_variable:
+ for pair in define_variable:
+ ret.append('--define-variable=' + '='.join(pair))
+ return ret
+
+ @lru_cache(maxsize=None)
+ def cflags(self, name: str, allow_system: bool = False,
+ define_variable: PkgConfigDefineType = None) -> ImmutableListProtocol[str]:
+ env = None
+ if allow_system:
+ env = os.environ.copy()
+ env['PKG_CONFIG_ALLOW_SYSTEM_CFLAGS'] = '1'
+ args: T.List[str] = []
+ args += self._define_variable_args(define_variable)
+ args += ['--cflags', name]
+ ret, out, err = self._call_pkgbin(args, env=env)
+ if ret != 0:
+ raise DependencyException(f'Could not generate cflags for {name}:\n{err}\n')
+ return self._split_args(out)
+
+ @lru_cache(maxsize=None)
+ def libs(self, name: str, static: bool = False, allow_system: bool = False,
+ define_variable: PkgConfigDefineType = None) -> ImmutableListProtocol[str]:
+ env = None
+ if allow_system:
+ env = os.environ.copy()
+ env['PKG_CONFIG_ALLOW_SYSTEM_LIBS'] = '1'
+ args: T.List[str] = []
+ args += self._define_variable_args(define_variable)
+ if static:
+ args.append('--static')
+ args += ['--libs', name]
+ ret, out, err = self._call_pkgbin(args, env=env)
+ if ret != 0:
+ raise DependencyException(f'Could not generate libs for {name}:\n{err}\n')
+ return self._split_args(out)
+
+ @lru_cache(maxsize=None)
+ def variable(self, name: str, variable_name: str,
+ define_variable: PkgConfigDefineType) -> T.Optional[str]:
+ args: T.List[str] = []
+ args += self._define_variable_args(define_variable)
+ args += ['--variable=' + variable_name, name]
+ ret, out, err = self._call_pkgbin(args)
+ if ret != 0:
+ raise DependencyException(f'Could not get variable for {name}:\n{err}\n')
+ variable = out.strip()
+ # pkg-config doesn't distinguish between empty and nonexistent variables
+ # use the variable list to check for variable existence
+ if not variable:
+ ret, out, _ = self._call_pkgbin(['--print-variables', name])
+ if not re.search(rf'^{variable_name}$', out, re.MULTILINE):
+ return None
+ mlog.debug(f'Got pkg-config variable {variable_name} : {variable}')
+ return variable
+
+ @lru_cache(maxsize=None)
+ def list_all(self) -> ImmutableListProtocol[str]:
+ ret, out, err = self._call_pkgbin(['--list-all'])
+ if ret != 0:
+ raise DependencyException(f'could not list modules:\n{err}\n')
+ return [i.split(' ', 1)[0] for i in out.splitlines()]
@staticmethod
- def get_env(environment: 'Environment', for_machine: MachineChoice,
- uninstalled: bool = False) -> 'EnvironmentVariables':
- from ..build import EnvironmentVariables
+ def _split_args(cmd: str) -> T.List[str]:
+ # pkg-config paths follow Unix conventions, even on Windows; split the
+ # output using shlex.split rather than mesonlib.split_args
+ return shlex.split(cmd)
+
+ def _detect_pkgbin(self) -> None:
+ for potential_pkgbin in find_external_program(
+ self.env, self.for_machine, 'pkg-config', 'Pkg-config',
+ self.env.default_pkgconfig, allow_default_for_cross=False):
+ version_if_ok = self._check_pkgconfig(potential_pkgbin)
+ if version_if_ok:
+ self.pkgbin = potential_pkgbin
+ self.pkgbin_version = version_if_ok
+ return
+ self.pkgbin = None
+
+ def _check_pkgconfig(self, pkgbin: ExternalProgram) -> T.Optional[str]:
+ if not pkgbin.found():
+ mlog.log(f'Did not find pkg-config by name {pkgbin.name!r}')
+ return None
+ command_as_string = ' '.join(pkgbin.get_command())
+ try:
+ helptext = Popen_safe(pkgbin.get_command() + ['--help'])[1]
+ if 'Pure-Perl' in helptext:
+ mlog.log(f'Found pkg-config {command_as_string!r} but it is Strawberry Perl and thus broken. Ignoring...')
+ return None
+ p, out = Popen_safe(pkgbin.get_command() + ['--version'])[0:2]
+ if p.returncode != 0:
+ mlog.warning(f'Found pkg-config {command_as_string!r} but it failed when ran')
+ return None
+ except FileNotFoundError:
+ mlog.warning(f'We thought we found pkg-config {command_as_string!r} but now it\'s not there. How odd!')
+ return None
+ except PermissionError:
+ msg = f'Found pkg-config {command_as_string!r} but didn\'t have permissions to run it.'
+ if not self.env.machines.build.is_windows():
+ msg += '\n\nOn Unix-like systems this is often caused by scripts that are not executable.'
+ mlog.warning(msg)
+ return None
+ return out.strip()
+
+ def _get_env(self, uninstalled: bool = False) -> EnvironmentVariables:
env = EnvironmentVariables()
- key = OptionKey('pkg_config_path', machine=for_machine)
- extra_paths: T.List[str] = environment.coredata.options[key].value[:]
+ key = OptionKey('pkg_config_path', machine=self.for_machine)
+ extra_paths: T.List[str] = self.env.coredata.options[key].value[:]
if uninstalled:
- uninstalled_path = Path(environment.get_build_dir(), 'meson-uninstalled').as_posix()
+ uninstalled_path = Path(self.env.get_build_dir(), 'meson-uninstalled').as_posix()
if uninstalled_path not in extra_paths:
extra_paths.append(uninstalled_path)
env.set('PKG_CONFIG_PATH', extra_paths)
- sysroot = environment.properties[for_machine].get_sys_root()
+ sysroot = self.env.properties[self.for_machine].get_sys_root()
if sysroot:
env.set('PKG_CONFIG_SYSROOT_DIR', [sysroot])
- pkg_config_libdir_prop = environment.properties[for_machine].get_pkg_config_libdir()
+ pkg_config_libdir_prop = self.env.properties[self.for_machine].get_pkg_config_libdir()
if pkg_config_libdir_prop:
env.set('PKG_CONFIG_LIBDIR', pkg_config_libdir_prop)
+ env.set('PKG_CONFIG', [join_args(self.pkgbin.get_command())])
return env
- @staticmethod
- def setup_env(env: T.MutableMapping[str, str], environment: 'Environment', for_machine: MachineChoice,
- uninstalled: bool = False) -> T.Dict[str, str]:
- envvars = PkgConfigDependency.get_env(environment, for_machine, uninstalled)
+ def _setup_env(self, env: EnvironOrDict, uninstalled: bool = False) -> T.Dict[str, str]:
+ envvars = self._get_env(uninstalled)
env = envvars.get_env(env)
# Dump all PKG_CONFIG environment variables
for key, value in env.items():
mlog.debug(f'env[{key}]: {value}')
return env
- def _call_pkgbin(self, args: T.List[str], env: T.Optional[T.MutableMapping[str, str]] = None) -> T.Tuple[int, str, str]:
+ def _call_pkgbin(self, args: T.List[str], env: T.Optional[EnvironOrDict] = None) -> T.Tuple[int, str, str]:
assert isinstance(self.pkgbin, ExternalProgram)
env = env or os.environ
- env = PkgConfigDependency.setup_env(env, self.env, self.for_machine)
+ env = self._setup_env(env)
+ cmd = self.pkgbin.get_command() + args
+ p, out, err = Popen_safe_logged(cmd, env=env)
+ return p.returncode, out.strip(), err.strip()
+
+
+class PkgConfigDependency(ExternalDependency):
- fenv = frozenset(env.items())
- targs = tuple(args)
- cache = PkgConfigDependency.pkgbin_cache
- if (self.pkgbin, targs, fenv) not in cache:
- cache[(self.pkgbin, targs, fenv)] = self._call_pkgbin_real(args, env)
- return cache[(self.pkgbin, targs, fenv)]
+ def __init__(self, name: str, environment: Environment, kwargs: T.Dict[str, T.Any], language: T.Optional[str] = None) -> None:
+ super().__init__(DependencyTypeName('pkgconfig'), environment, kwargs, language=language)
+ self.name = name
+ self.is_libtool = False
+ self.pkgconfig = PkgConfigInterface.instance(self.env, self.for_machine, self.silent)
+ if not self.pkgconfig:
+ msg = f'Pkg-config for machine {self.for_machine} not found. Giving up.'
+ if self.required:
+ raise DependencyException(msg)
+ mlog.debug(msg)
+ return
- def _convert_mingw_paths(self, args: T.List[str]) -> T.List[str]:
+ version = self.pkgconfig.version(name)
+ if version is None:
+ return
+
+ self.version = version
+ self.is_found = True
+
+ try:
+ # Fetch cargs to be used while using this dependency
+ self._set_cargs()
+ # Fetch the libraries and library paths needed for using this
+ self._set_libs()
+ except DependencyException as e:
+ mlog.debug(f"Pkg-config error with '{name}': {e}")
+ if self.required:
+ raise
+ else:
+ self.compile_args = []
+ self.link_args = []
+ self.is_found = False
+ self.reason = e
+
+ def __repr__(self) -> str:
+ s = '<{0} {1}: {2} {3}>'
+ return s.format(self.__class__.__name__, self.name, self.is_found,
+ self.version_reqs)
+
+ def _convert_mingw_paths(self, args: ImmutableListProtocol[str]) -> T.List[str]:
'''
Both MSVC and native Python on Windows cannot handle MinGW-esque /c/foo
paths so convert them to C:/foo. We cannot resolve other paths starting
error/warning from the compiler/linker.
'''
if not self.env.machines.build.is_windows():
- return args
+ return args.copy()
converted = []
for arg in args:
pargs: T.Tuple[str, ...] = tuple()
converted.append(arg)
return converted
- def _split_args(self, cmd: str) -> T.List[str]:
- # pkg-config paths follow Unix conventions, even on Windows; split the
- # output using shlex.split rather than mesonlib.split_args
- return shlex.split(cmd)
-
def _set_cargs(self) -> None:
- env = None
+ allow_system = False
if self.language == 'fortran':
# gfortran doesn't appear to look in system paths for INCLUDE files,
# so don't allow pkg-config to suppress -I flags for system paths
- env = os.environ.copy()
- env['PKG_CONFIG_ALLOW_SYSTEM_CFLAGS'] = '1'
- ret, out, err = self._call_pkgbin(['--cflags', self.name], env=env)
- if ret != 0:
- raise DependencyException(f'Could not generate cargs for {self.name}:\n{err}\n')
- self.compile_args = self._convert_mingw_paths(self._split_args(out))
+ allow_system = True
+ cflags = self.pkgconfig.cflags(self.name, allow_system)
+ self.compile_args = self._convert_mingw_paths(cflags)
- def _search_libs(self, out: str, out_raw: str) -> T.Tuple[T.List[str], T.List[str]]:
+ def _search_libs(self, libs_in: ImmutableListProtocol[str], raw_libs_in: ImmutableListProtocol[str]) -> T.Tuple[T.List[str], T.List[str]]:
'''
- @out: PKG_CONFIG_ALLOW_SYSTEM_LIBS=1 pkg-config --libs
- @out_raw: pkg-config --libs
+ @libs_in: PKG_CONFIG_ALLOW_SYSTEM_LIBS=1 pkg-config --libs
+ @raw_libs_in: pkg-config --libs
We always look for the file ourselves instead of depending on the
compiler to find it with -lfoo or foo.lib (if possible) because:
# always searched first.
prefix_libpaths: OrderedSet[str] = OrderedSet()
# We also store this raw_link_args on the object later
- raw_link_args = self._convert_mingw_paths(self._split_args(out_raw))
+ raw_link_args = self._convert_mingw_paths(raw_libs_in)
for arg in raw_link_args:
if arg.startswith('-L') and not arg.startswith(('-L-l', '-L-L')):
path = arg[2:]
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()
- full_args = self._convert_mingw_paths(self._split_args(out))
+ full_args = self._convert_mingw_paths(libs_in)
for arg in full_args:
if arg.startswith(('-L-l', '-L-L')):
# These are D language arguments, not library paths
continue
if self.clib_compiler:
args = self.clib_compiler.find_library(lib[2:], self.env,
- libpaths, self.libtype)
+ 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.
return link_args, raw_link_args
def _set_libs(self) -> None:
- env = None
- libcmd = ['--libs']
-
- if self.static:
- libcmd.append('--static')
-
- libcmd.append(self.name)
-
# Force pkg-config to output -L fields even if they are system
# paths so we can do manual searching with cc.find_library() later.
- env = os.environ.copy()
- env['PKG_CONFIG_ALLOW_SYSTEM_LIBS'] = '1'
- ret, out, err = self._call_pkgbin(libcmd, env=env)
- if ret != 0:
- raise DependencyException(f'Could not generate libs for {self.name}:\n{err}\n')
+ libs = self.pkgconfig.libs(self.name, self.static, allow_system=True)
# Also get the 'raw' output without -Lfoo system paths for adding -L
# args with -lfoo when a library can't be found, and also in
# gnome.generate_gir + gnome.gtkdoc which need -L -l arguments.
- ret, out_raw, err_raw = self._call_pkgbin(libcmd)
- if ret != 0:
- raise DependencyException(f'Could not generate libs for {self.name}:\n\n{out_raw}')
- self.link_args, self.raw_link_args = self._search_libs(out, out_raw)
-
- def get_pkgconfig_variable(self, variable_name: str,
- define_variable: 'ImmutableListProtocol[str]',
- default: T.Optional[str]) -> str:
- options = ['--variable=' + variable_name, self.name]
-
- if define_variable:
- options = ['--define-variable=' + '='.join(define_variable)] + options
-
- ret, out, err = self._call_pkgbin(options)
- variable = ''
- if ret != 0:
- if self.required:
- raise DependencyException(f'dependency {self.name} not found:\n{err}\n')
- else:
- variable = out.strip()
-
- # pkg-config doesn't distinguish between empty and non-existent variables
- # use the variable list to check for variable existence
- if not variable:
- ret, out, _ = self._call_pkgbin(['--print-variables', self.name])
- if not re.search(r'^' + variable_name + r'$', out, re.MULTILINE):
- if default is not None:
- variable = default
- else:
- mlog.warning(f"pkgconfig variable '{variable_name}' not defined for dependency {self.name}.")
-
- mlog.debug(f'Got pkgconfig variable {variable_name} : {variable}')
- return variable
-
- @staticmethod
- def check_pkgconfig(env: Environment, pkgbin: ExternalProgram) -> T.Optional[str]:
- if not pkgbin.found():
- mlog.log(f'Did not find pkg-config by name {pkgbin.name!r}')
- return None
- command_as_string = ' '.join(pkgbin.get_command())
- try:
- helptext = Popen_safe(pkgbin.get_command() + ['--help'])[1]
- if 'Pure-Perl' in helptext:
- mlog.log(f'found pkg-config {command_as_string!r} but it is Strawberry Perl and thus broken. Ignoring...')
- return None
- p, out = Popen_safe(pkgbin.get_command() + ['--version'])[0:2]
- if p.returncode != 0:
- mlog.warning(f'Found pkg-config {command_as_string!r} but it failed when run')
- return None
- except FileNotFoundError:
- mlog.warning(f'We thought we found pkg-config {command_as_string!r} but now it\'s not there. How odd!')
- return None
- except PermissionError:
- msg = f'Found pkg-config {command_as_string!r} but didn\'t have permissions to run it.'
- if not env.machines.build.is_windows():
- msg += '\n\nOn Unix-like systems this is often caused by scripts that are not executable.'
- mlog.warning(msg)
- return None
- return out.strip()
+ raw_libs = self.pkgconfig.libs(self.name, self.static, allow_system=False)
+ self.link_args, self.raw_link_args = self._search_libs(libs, raw_libs)
def extract_field(self, la_file: str, fieldname: str) -> T.Optional[str]:
with open(la_file, encoding='utf-8') as f:
def get_variable(self, *, cmake: T.Optional[str] = None, pkgconfig: T.Optional[str] = None,
configtool: T.Optional[str] = None, internal: T.Optional[str] = None,
default_value: T.Optional[str] = None,
- pkgconfig_define: T.Optional[T.List[str]] = None) -> str:
+ pkgconfig_define: PkgConfigDefineType = None) -> str:
if pkgconfig:
try:
- return self.get_pkgconfig_variable(pkgconfig, pkgconfig_define or [], default_value)
+ variable = self.pkgconfig.variable(self.name, pkgconfig, pkgconfig_define)
+ if variable is not None:
+ return variable
except DependencyException:
pass
if default_value is not None:
# This file contains the detection logic for external dependencies that are
# platform-specific (generally speaking).
+from __future__ import annotations
from .base import DependencyTypeName, ExternalDependency, DependencyException
+from .detect import packages
from ..mesonlib import MesonException
import typing as T
@staticmethod
def log_tried() -> str:
return 'framework'
+
+packages['appleframeworks'] = AppleFrameworks
--- /dev/null
+# Copyright 2022 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 functools, json, os, textwrap
+from pathlib import Path
+import typing as T
+
+from .. import mesonlib, mlog
+from .base import process_method_kw, DependencyMethods, DependencyTypeName, 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 ..programs import ExternalProgram
+
+if T.TYPE_CHECKING:
+ from typing_extensions import TypedDict
+
+ from .factory import DependencyGenerator
+ from ..environment import Environment
+ from ..mesonlib import MachineChoice
+
+ class PythonIntrospectionDict(TypedDict):
+
+ install_paths: T.Dict[str, str]
+ is_pypy: bool
+ is_venv: bool
+ link_libpython: bool
+ sysconfig_paths: T.Dict[str, str]
+ paths: T.Dict[str, str]
+ platform: str
+ suffix: str
+ limited_api_suffix: str
+ variables: T.Dict[str, str]
+ version: str
+
+ _Base = ExternalDependency
+else:
+ _Base = object
+
+
+class Pybind11ConfigToolDependency(ConfigToolDependency):
+
+ tools = ['pybind11-config']
+
+ # any version of the tool is valid, since this is header-only
+ allow_default_for_cross = True
+
+ # pybind11 in 2.10.4 added --version, sanity-check another flag unique to it
+ # in the meantime
+ skip_version = '--pkgconfigdir'
+
+ def __init__(self, name: str, environment: Environment, kwargs: T.Dict[str, T.Any]):
+ super().__init__(name, environment, kwargs)
+ if not self.is_found:
+ return
+ self.compile_args = self.get_config_value(['--includes'], 'compile_args')
+
+
+class BasicPythonExternalProgram(ExternalProgram):
+ def __init__(self, name: str, command: T.Optional[T.List[str]] = None,
+ ext_prog: T.Optional[ExternalProgram] = None):
+ if ext_prog is None:
+ super().__init__(name, command=command, silent=True)
+ else:
+ self.name = name
+ self.command = ext_prog.command
+ self.path = ext_prog.path
+ self.cached_version = 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
+ # complete
+ self.info: 'PythonIntrospectionDict' = {
+ 'install_paths': {},
+ 'is_pypy': False,
+ 'is_venv': False,
+ 'link_libpython': False,
+ 'sysconfig_paths': {},
+ 'paths': {},
+ 'platform': 'sentinel',
+ 'suffix': 'sentinel',
+ 'limited_api_suffix': 'sentinel',
+ 'variables': {},
+ 'version': '0.0',
+ }
+ self.pure: bool = True
+
+ def _check_version(self, version: str) -> bool:
+ if self.name == 'python2':
+ return mesonlib.version_compare(version, '< 3.0')
+ elif self.name == 'python3':
+ return mesonlib.version_compare(version, '>= 3.0')
+ return True
+
+ def sanity(self) -> bool:
+ # Sanity check, we expect to have something that at least quacks in tune
+
+ import importlib.resources
+
+ with importlib.resources.path('mesonbuild.scripts', 'python_info.py') as f:
+ cmd = self.get_command() + [str(f)]
+ env = os.environ.copy()
+ env['SETUPTOOLS_USE_DISTUTILS'] = 'stdlib'
+ p, stdout, stderr = mesonlib.Popen_safe(cmd, env=env)
+
+ try:
+ info = json.loads(stdout)
+ except json.JSONDecodeError:
+ info = None
+ mlog.debug('Could not introspect Python (%s): exit code %d' % (str(p.args), p.returncode))
+ mlog.debug('Program stdout:\n')
+ mlog.debug(stdout)
+ mlog.debug('Program stderr:\n')
+ mlog.debug(stderr)
+
+ if info is not None and self._check_version(info['version']):
+ self.info = T.cast('PythonIntrospectionDict', info)
+ return True
+ else:
+ return False
+
+
+class _PythonDependencyBase(_Base):
+
+ def __init__(self, python_holder: 'BasicPythonExternalProgram', embed: bool):
+ self.embed = embed
+ self.version: str = python_holder.info['version']
+ self.platform = python_holder.info['platform']
+ self.variables = python_holder.info['variables']
+ self.paths = python_holder.info['paths']
+ self.is_pypy = python_holder.info['is_pypy']
+ # 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
+ # because that is the only one that exists.
+ #
+ # 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
+ self.info: T.Optional[T.Dict[str, str]] = None
+ if mesonlib.version_compare(self.version, '>= 3.0'):
+ self.major_version = 3
+ else:
+ self.major_version = 2
+
+
+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')
+ else:
+ mlog.debug(f'Searching for {name!r} via fallback pkgconfig lookup in default paths')
+
+ PkgConfigDependency.__init__(self, name, environment, kwargs)
+ _PythonDependencyBase.__init__(self, installation, kwargs.get('embed', False))
+
+ 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')
+
+ # pkg-config files are usually accurate starting with python 3.8
+ if not self.link_libpython and mesonlib.version_compare(self.version, '< 3.8'):
+ self.link_args = []
+
+
+class PythonFrameworkDependency(ExtraFrameworkDependency, _PythonDependencyBase):
+
+ def __init__(self, name: str, environment: 'Environment',
+ kwargs: T.Dict[str, T.Any], installation: 'BasicPythonExternalProgram'):
+ ExtraFrameworkDependency.__init__(self, name, environment, kwargs)
+ _PythonDependencyBase.__init__(self, installation, kwargs.get('embed', False))
+
+
+class PythonSystemDependency(SystemDependency, _PythonDependencyBase):
+
+ def __init__(self, name: str, environment: 'Environment',
+ kwargs: T.Dict[str, T.Any], installation: 'BasicPythonExternalProgram'):
+ SystemDependency.__init__(self, name, environment, kwargs)
+ _PythonDependencyBase.__init__(self, installation, kwargs.get('embed', False))
+
+ # match pkg-config behavior
+ if self.link_libpython:
+ # link args
+ if mesonlib.is_windows():
+ self.find_libpy_windows(environment, limited_api=False)
+ else:
+ self.find_libpy(environment)
+ else:
+ self.is_found = True
+
+ # compile args
+ 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]
+
+ # https://sourceforge.net/p/mingw-w64/mailman/message/30504611/
+ # https://github.com/python/cpython/pull/100137
+ 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):
+ self.is_found = False
+
+ def find_libpy(self, environment: 'Environment') -> None:
+ if self.is_pypy:
+ if self.major_version == 3:
+ libname = 'pypy3-c'
+ else:
+ libname = 'pypy-c'
+ libdir = os.path.join(self.variables.get('base'), 'bin')
+ libdirs = [libdir]
+ else:
+ libname = f'python{self.version}'
+ if 'DEBUG_EXT' in self.variables:
+ libname += self.variables['DEBUG_EXT']
+ if 'ABIFLAGS' in self.variables:
+ libname += self.variables['ABIFLAGS']
+ libdirs = []
+
+ largs = self.clib_compiler.find_library(libname, environment, libdirs)
+ if largs is not None:
+ self.link_args = largs
+ self.is_found = True
+
+ def get_windows_python_arch(self) -> T.Optional[str]:
+ if self.platform == 'mingw':
+ pycc = self.variables.get('CC')
+ if pycc.startswith('x86_64'):
+ return 'x86_64'
+ elif pycc.startswith(('i686', 'i386')):
+ return 'x86'
+ else:
+ mlog.log(f'MinGW Python built with unknown CC {pycc!r}, please file a bug')
+ return None
+ elif self.platform == 'win32':
+ return 'x86'
+ elif self.platform in {'win64', 'win-amd64'}:
+ return 'x86_64'
+ elif self.platform in {'win-arm64'}:
+ return 'aarch64'
+ mlog.log(f'Unknown Windows Python platform {self.platform!r}')
+ return None
+
+ def get_windows_link_args(self, limited_api: bool) -> T.Optional[T.List[str]]:
+ if self.platform.startswith('win'):
+ vernum = self.variables.get('py_version_nodot')
+ verdot = self.variables.get('py_version_short')
+ imp_lower = self.variables.get('implementation_lower', 'python')
+ if self.static:
+ libpath = Path('libs') / f'libpython{vernum}.a'
+ else:
+ comp = self.get_compiler()
+ if comp.id == "gcc":
+ if imp_lower == 'pypy' and verdot == '3.8':
+ # The naming changed between 3.8 and 3.9
+ libpath = Path('libpypy3-c.dll')
+ elif imp_lower == 'pypy':
+ libpath = Path(f'libpypy{verdot}-c.dll')
+ else:
+ libpath = Path(f'python{vernum}.dll')
+ else:
+ if limited_api:
+ vernum = vernum[0]
+ libpath = Path('libs') / f'python{vernum}.lib'
+ # For a debug build, pyconfig.h may force linking with
+ # pythonX_d.lib (see meson#10776). This cannot be avoided
+ # and won't work unless we also have a debug build of
+ # Python itself (except with pybind11, which has an ugly
+ # hack to work around this) - so emit a warning to explain
+ # the cause of the expected link error.
+ buildtype = self.env.coredata.get_option(mesonlib.OptionKey('buildtype'))
+ assert isinstance(buildtype, str)
+ debug = self.env.coredata.get_option(mesonlib.OptionKey('debug'))
+ # `debugoptimized` buildtype may not set debug=True currently, see gh-11645
+ is_debug_build = debug or buildtype == 'debug'
+ vscrt_debug = False
+ if mesonlib.OptionKey('b_vscrt') in self.env.coredata.options:
+ vscrt = self.env.coredata.options[mesonlib.OptionKey('b_vscrt')].value
+ 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'):
+ mlog.warning(textwrap.dedent('''\
+ Using a debug build type with MSVC or an MSVC-compatible compiler
+ when the Python interpreter is not also a debug build will almost
+ certainly result in a failed build. Prefer using a release build
+ type or a debug Python interpreter.
+ '''))
+ # base_prefix to allow for virtualenvs.
+ lib = Path(self.variables.get('base_prefix')) / libpath
+ elif self.platform == 'mingw':
+ if self.static:
+ libname = self.variables.get('LIBRARY')
+ else:
+ libname = self.variables.get('LDLIBRARY')
+ lib = Path(self.variables.get('LIBDIR')) / libname
+ else:
+ raise mesonlib.MesonBugException(
+ 'On a Windows path, but the OS doesn\'t appear to be Windows or MinGW.')
+ if not lib.exists():
+ mlog.log('Could not find Python3 library {!r}'.format(str(lib)))
+ return None
+ return [str(lib)]
+
+ def find_libpy_windows(self, env: 'Environment', limited_api: bool = False) -> None:
+ '''
+ Find python3 libraries on Windows and also verify that the arch matches
+ what we are building for.
+ '''
+ pyarch = self.get_windows_python_arch()
+ if pyarch is None:
+ self.is_found = False
+ return
+ arch = detect_cpu_family(env.coredata.compilers.host)
+ if arch != pyarch:
+ mlog.log('Need', mlog.bold(self.name), f'for {arch}, but found {pyarch}')
+ self.is_found = False
+ return
+ # This can fail if the library is not found
+ largs = self.get_windows_link_args(limited_api)
+ if largs is None:
+ self.is_found = False
+ return
+ self.link_args = largs
+ self.is_found = True
+
+ @staticmethod
+ def log_tried() -> str:
+ return 'sysconfig'
+
+def python_factory(env: 'Environment', for_machine: 'MachineChoice',
+ kwargs: T.Dict[str, T.Any],
+ 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
+ methods = process_method_kw({DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM}, kwargs)
+ embed = kwargs.get('embed', False)
+ candidates: T.List['DependencyGenerator'] = []
+ from_installation = installation is not None
+ # When not invoked through the python module, default installation.
+ 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)
+
+ 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))
+ else:
+ candidates.append(functools.partial(PkgConfigDependency, 'python3', env, kwargs))
+
+ if DependencyMethods.SYSTEM in methods:
+ candidates.append(functools.partial(PythonSystemDependency, 'python', env, kwargs, installation))
+
+ if DependencyMethods.EXTRAFRAMEWORK in methods:
+ nkwargs = kwargs.copy()
+ if mesonlib.version_compare(pkg_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))
+
+ return candidates
+
+packages['python3'] = python_factory
+
+packages['pybind11'] = pybind11_factory = DependencyFactory(
+ 'pybind11',
+ [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL, DependencyMethods.CMAKE],
+ configtool_class=Pybind11ConfigToolDependency,
+)
# 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
"""Dependency finders for the Qt framework."""
from .base import DependencyException, DependencyMethods
from .configtool import ConfigToolDependency
+from .detect import packages
from .framework import ExtraFrameworkDependency
from .pkgconfig import PkgConfigDependency
from .factory import DependencyFactory
private_dir = os.path.join(mod_inc_dir, mod_version)
# fallback, let's try to find a directory with the latest version
- if not os.path.exists(private_dir):
+ if os.path.isdir(mod_inc_dir) and not os.path.exists(private_dir):
dirs = [filename for filename in os.listdir(mod_inc_dir)
if os.path.isdir(os.path.join(mod_inc_dir, filename))]
class QtExtraFrameworkDependency(ExtraFrameworkDependency):
- 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: T.Dict[str, T.Any], 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']
def get_compile_args(self, with_private_headers: bool = False, qt_version: str = "0") -> T.List[str]:
if self.found():
args = ['-I' + mod_inc_dir]
if with_private_headers:
args += ['-I' + dirname for dirname in _qt_get_private_includes(mod_inc_dir, self.mod_name, qt_version)]
+ if self.qt_extra_include_directory:
+ args += ['-I' + self.qt_extra_include_directory]
return args
return []
self.is_found = False
return
if self.private_headers:
- qt_inc_dir = mod.get_pkgconfig_variable('includedir', [], None)
+ qt_inc_dir = mod.get_variable(pkgconfig='includedir')
mod_private_dir = os.path.join(qt_inc_dir, 'Qt' + m)
if not os.path.isdir(mod_private_dir):
# At least some versions of homebrew don't seem to set this
if arg == f'-l{debug_lib_name}' or arg.endswith(f'{debug_lib_name}.lib') or arg.endswith(f'{debug_lib_name}.a'):
is_debug = True
break
- libdir = self.get_pkgconfig_variable('libdir', [], None)
+ libdir = self.get_variable(pkgconfig='libdir')
if not self._link_with_qt_winmain(is_debug, libdir):
self.is_found = False
return
self.bindir = self.get_pkgconfig_host_bins(self)
if not self.bindir:
# If exec_prefix is not defined, the pkg-config file is broken
- prefix = self.get_pkgconfig_variable('exec_prefix', [], None)
+ prefix = self.get_variable(pkgconfig='exec_prefix')
if prefix:
self.bindir = os.path.join(prefix, 'bin')
"""Find Qt using Qmake as a config-tool."""
- tool_name = 'qmake'
version_arg = '-v'
def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]):
_QtBase.__init__(self, name, kwargs)
+ self.tool_name = f'qmake{self.qtver}'
self.tools = [f'qmake{self.qtver}', f'qmake-{self.name}', 'qmake']
# Add additional constraints that the Qt version is met, but preserve
- # any version requrements the user has set as well. For example, if Qt5
+ # any version requirements the user has set as well. For example, if Qt5
# is requested, add "">= 5, < 6", but if the user has ">= 5.6", don't
# lose that.
kwargs = kwargs.copy()
self.compile_args.append('-I' + directory)
libfiles = self.clib_compiler.find_library(
self.qtpkgname + module + modules_lib_suffix, self.env,
- mesonlib.listify(libdir)) # TODO: shouldn't be necissary
+ mesonlib.listify(libdir)) # TODO: shouldn't be necessary
if libfiles:
libfile = libfiles[0]
else:
return m.group(0).rstrip('.')
return version
+ def get_variable_args(self, variable_name: str) -> T.List[str]:
+ return ['-query', f'{variable_name}']
+
@abc.abstractmethod
def get_private_includes(self, mod_inc_dir: str, module: str) -> T.List[str]:
pass
for m in modules:
fname = 'Qt' + m
mlog.debug('Looking for qt framework ' + fname)
- fwdep = QtExtraFrameworkDependency(fname, self.env, fw_kwargs, language=self.language)
+ fwdep = QtExtraFrameworkDependency(fname, self.env, fw_kwargs, qvars, language=self.language)
if fwdep.found():
self.compile_args.append('-F' + libdir)
self.compile_args += fwdep.get_compile_args(with_private_headers=self.private_headers,
applications = ['moc', 'uic', 'rcc', 'lupdate', 'lrelease']
for application in applications:
try:
- return os.path.dirname(core.get_pkgconfig_variable(f'{application}_location', [], None))
+ return os.path.dirname(core.get_variable(pkgconfig=f'{application}_location'))
except mesonlib.MesonException:
pass
return None
@staticmethod
def get_pkgconfig_host_bins(core: PkgConfigDependency) -> str:
- return core.get_pkgconfig_variable('host_bins', [], None)
+ return core.get_variable(pkgconfig='host_bins')
@staticmethod
def get_pkgconfig_host_libexecs(core: PkgConfigDependency) -> str:
@staticmethod
def get_pkgconfig_host_bins(core: PkgConfigDependency) -> str:
- return core.get_pkgconfig_variable('bindir', [], None)
+ return core.get_variable(pkgconfig='bindir')
@staticmethod
def get_pkgconfig_host_libexecs(core: PkgConfigDependency) -> str:
# Qt6 pkg-config for Qt defines libexecdir from 6.3+
- return core.get_pkgconfig_variable('libexecdir', [], None)
+ return core.get_variable(pkgconfig='libexecdir')
def get_private_includes(self, mod_inc_dir: str, module: str) -> T.List[str]:
return _qt_get_private_includes(mod_inc_dir, module, self.version)
-qt4_factory = DependencyFactory(
+packages['qt4'] = qt4_factory = DependencyFactory(
'qt4',
[DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL],
pkgconfig_class=Qt4PkgConfigDependency,
configtool_class=Qt4ConfigToolDependency,
)
-qt5_factory = DependencyFactory(
+packages['qt5'] = qt5_factory = DependencyFactory(
'qt5',
[DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL],
pkgconfig_class=Qt5PkgConfigDependency,
configtool_class=Qt5ConfigToolDependency,
)
-qt6_factory = DependencyFactory(
+packages['qt6'] = qt6_factory = DependencyFactory(
'qt6',
[DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL],
pkgconfig_class=Qt6PkgConfigDependency,
# 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
from pathlib import Path
import functools
from ..mesonlib import OptionKey
from .base import DependencyMethods
-from .base import DependencyException
from .cmake import CMakeDependency
+from .detect import packages
from .pkgconfig import PkgConfigDependency
from .factory import factory_methods
if T.TYPE_CHECKING:
- from ..environment import Environment, MachineChoice
+ from ..environment import Environment
+ from ..mesonlib import MachineChoice
from .factory import DependencyGenerator
return candidates
+packages['scalapack'] = scalapack_factory
+
class MKLPkgConfigDependency(PkgConfigDependency):
self.link_args.insert(i + 1, '-lmkl_blacs_intelmpi_lp64')
def _set_cargs(self) -> None:
- env = None
+ allow_system = False
if self.language == 'fortran':
# gfortran doesn't appear to look in system paths for INCLUDE files,
# so don't allow pkg-config to suppress -I flags for system paths
- env = os.environ.copy()
- env['PKG_CONFIG_ALLOW_SYSTEM_CFLAGS'] = '1'
- ret, out, err = self._call_pkgbin([
- '--cflags', self.name,
- '--define-variable=prefix=' + self.__mklroot.as_posix()],
- env=env)
- if ret != 0:
- raise DependencyException('Could not generate cargs for %s:\n%s\n' %
- (self.name, err))
- self.compile_args = self._convert_mingw_paths(self._split_args(out))
+ allow_system = True
+ cflags = self.pkgconfig.cflags(self.name, allow_system, define_variable=(('prefix', self.__mklroot.as_posix()),))
+ self.compile_args = self._convert_mingw_paths(cflags)
# This file contains the detection logic for external dependencies that
# are UI-related.
+from __future__ import annotations
+
import os
+import re
import subprocess
import typing as T
from .. import mlog
from .. import mesonlib
+from ..compilers.compilers import CrossNoRunException
from ..mesonlib import (
Popen_safe, extract_as_list, version_compare_many
)
from .base import DependencyException, DependencyMethods, DependencyTypeName, SystemDependency
from .configtool import ConfigToolDependency
+from .detect import packages
from .factory import DependencyFactory
if T.TYPE_CHECKING:
self.link_args = ['-framework', 'OpenGL']
# FIXME: Detect version using self.clib_compiler
return
- if self.env.machines[self.for_machine].is_windows():
+ elif self.env.machines[self.for_machine].is_windows():
self.is_found = True
# FIXME: Use self.clib_compiler.find_library()
self.link_args = ['-lopengl32']
# 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]
+ if links and has_header:
+ self.is_found = True
+ self.link_args = links
+ elif links:
+ raise DependencyException('Found GL runtime library but no development header files')
class GnuStepDependency(ConfigToolDependency):
version = '1'
return version
+packages['gnustep'] = GnuStepDependency
+
class SDL2DependencyConfigTool(ConfigToolDependency):
raise DependencyException('wxwidgets module argument is not a string')
return candidates
+packages['wxwidgets'] = WxDependency
class VulkanDependencySystem(SystemDependency):
self.compile_args.append('-I' + inc_path)
self.link_args.append('-L' + lib_path)
self.link_args.append('-l' + lib_name)
-
- # TODO: find a way to retrieve the version from the sdk?
- # Usually it is a part of the path to it (but does not have to be)
- return
else:
# simply try to guess it, usually works on linux
libs = self.clib_compiler.find_library('vulkan', environment, [])
self.is_found = True
for lib in libs:
self.link_args.append(lib)
- return
-gl_factory = DependencyFactory(
+ if self.is_found:
+ get_version = '''\
+#include <stdio.h>
+#include <vulkan/vulkan.h>
+
+int main() {
+ printf("%i.%i.%i", VK_VERSION_MAJOR(VK_HEADER_VERSION_COMPLETE),
+ VK_VERSION_MINOR(VK_HEADER_VERSION_COMPLETE),
+ VK_VERSION_PATCH(VK_HEADER_VERSION_COMPLETE));
+ return 0;
+}
+'''
+ try:
+ run = self.clib_compiler.run(get_version, environment, extra_args=self.compile_args)
+ except CrossNoRunException:
+ run = None
+ if run and run.compiled and run.returncode == 0:
+ self.version = run.stdout
+ elif self.vulkan_sdk:
+ # fall back to heuristics: detect version number in path
+ # matches the default install path on Windows
+ match = re.search(rf'VulkanSDK{re.escape(os.path.sep)}([0-9]+(?:\.[0-9]+)+)', self.vulkan_sdk)
+ if match:
+ self.version = match.group(1)
+ else:
+ mlog.warning(f'Environment variable VULKAN_SDK={self.vulkan_sdk} is present, but Vulkan version could not be extracted.')
+
+packages['gl'] = gl_factory = DependencyFactory(
'gl',
[DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM],
system_class=GLDependencySystem,
)
-sdl2_factory = DependencyFactory(
+packages['sdl2'] = sdl2_factory = DependencyFactory(
'sdl2',
- [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL, DependencyMethods.EXTRAFRAMEWORK],
+ [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL, DependencyMethods.EXTRAFRAMEWORK, DependencyMethods.CMAKE],
configtool_class=SDL2DependencyConfigTool,
+ cmake_name='SDL2',
)
-vulkan_factory = DependencyFactory(
+packages['vulkan'] = vulkan_factory = DependencyFactory(
'vulkan',
[DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM],
system_class=VulkanDependencySystem,
# 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 typing as T
# 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
from dataclasses import dataclass
import subprocess
# and cross file currently), and also assists with the reading environment
# variables.
#
-# At this time there isn't an ironclad difference between this an other sources
+# At this time there isn't an ironclad difference between this and other sources
# of state like `coredata`. But one rough guide is much what is in `coredata` is
# the *output* of the configuration process: the final decisions after tests.
# This, on the other hand has *inputs*. The config files are parsed, but
'sh4',
'sparc',
'sparc64',
+ 'sw_64',
'wasm32',
'wasm64',
'x86',
'riscv64',
's390x',
'sparc64',
+ 'sw_64',
'wasm64',
'x86_64',
]
# Other tools
'cmake': 'CMAKE',
'qmake': 'QMAKE',
- 'pkgconfig': 'PKG_CONFIG',
'pkg-config': 'PKG_CONFIG',
'make': 'MAKE',
'vapigen': 'VAPIGEN',
self,
properties: T.Optional[T.Dict[str, T.Optional[T.Union[str, bool, int, T.List[str]]]]] = None,
):
- self.properties = properties or {} # type: T.Dict[str, T.Optional[T.Union[str, bool, int, T.List[str]]]]
+ self.properties = properties or {}
def has_stdlib(self, language: str) -> bool:
return language + '_stdlib' in self.properties
# Some of get_stdlib, get_root, get_sys_root are wider than is actually
- # true, but without heterogenious dict annotations it's not practical to
+ # true, but without heterogeneous dict annotations it's not practical to
# narrow them
def get_stdlib(self, language: str) -> T.Union[str, T.List[str]]:
stdlib = self.properties[language + '_stdlib']
value = T.cast('T.Optional[str]', self.properties.get('java_home'))
return Path(value) if value else None
+ def get_bindgen_clang_args(self) -> T.List[str]:
+ value = mesonlib.listify(self.properties.get('bindgen_clang_arguments', []))
+ if not all(isinstance(v, str) for v in value):
+ raise EnvironmentException('bindgen_clang_arguments must be a string or an array of strings')
+ return T.cast('T.List[str]', value)
+
def __eq__(self, other: object) -> bool:
if isinstance(other, type(self)):
return self.properties == other.properties
cpu_family: str
cpu: str
endian: str
+ kernel: T.Optional[str]
+ subsystem: T.Optional[str]
def __post_init__(self) -> None:
self.is_64_bit: bool = self.cpu_family in CPU_FAMILIES_64_BIT
if endian not in ('little', 'big'):
mlog.warning(f'Unknown endian {endian}')
- return cls(literal['system'], cpu_family, literal['cpu'], endian)
+ system = literal['system']
+ kernel = literal.get('kernel', None)
+ subsystem = literal.get('subsystem', None)
+
+ return cls(system, cpu_family, literal['cpu'], endian, kernel, subsystem)
def is_windows(self) -> bool:
"""
"""
return self.system == 'gnu'
+ def is_aix(self) -> bool:
+ """
+ Machine is aix?
+ """
+ return self.system == 'aix'
+
def is_irix(self) -> bool:
"""Machine is IRIX?"""
return self.system.startswith('irix')
raise mesonlib.MesonException(
f'Invalid type {command!r} for entry {name!r} in cross file')
self.binaries[name] = mesonlib.listify(command)
+ if 'pkgconfig' in self.binaries:
+ if 'pkg-config' not in self.binaries:
+ mlog.deprecation('"pkgconfig" entry is deprecated and should be replaced by "pkg-config"', fatal=False)
+ self.binaries['pkg-config'] = self.binaries['pkgconfig']
+ elif self.binaries['pkgconfig'] != self.binaries['pkg-config']:
+ raise mesonlib.MesonException('Mismatched pkgconfig and pkg-config binaries in the machine file.')
+ else:
+ # Both are defined with the same value, this is allowed
+ # for backward compatibility.
+ # FIXME: We should still print deprecation warning if the
+ # project targets Meson >= 1.3.0, but we have no way to know
+ # that here.
+ pass
+ del self.binaries['pkgconfig']
@staticmethod
def detect_ccache() -> T.List[str]:
class CMakeVariables:
def __init__(self, variables: T.Optional[T.Dict[str, T.Any]] = None) -> None:
variables = variables or {}
- self.variables = {} # type: T.Dict[str, T.List[str]]
+ self.variables: T.Dict[str, T.List[str]] = {}
for key, value in variables.items():
value = mesonlib.listify(value)
# 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 itertools
import os, platform, re, sys, shutil
)
from . import compilers
from .compilers import (
- Compiler,
is_assembly,
is_header,
is_library,
import argparse
from configparser import ConfigParser
+ from .compilers import Compiler
from .wrap.wrap import Resolver
-build_filename = 'meson.build'
+ CompilersDict = T.Dict[str, Compiler]
+
-CompilersDict = T.Dict[str, Compiler]
+build_filename = 'meson.build'
def _get_env_var(for_machine: MachineChoice, is_cross: bool, var_name: str) -> T.Optional[str]:
return value
-def detect_gcovr(min_version='3.3', log=False):
+def detect_gcovr(min_version: str = '3.3', log: bool = False):
gcovr_exe = 'gcovr'
try:
p, found = Popen_safe([gcovr_exe, '--version'])[0:2]
return gcovr_exe, found
return None, None
+def detect_lcov(log: bool = False):
+ lcov_exe = 'lcov'
+ 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():
tools = get_llvm_tool_names('llvm-cov')
for tool in tools:
return tool
return None
-def find_coverage_tools() -> T.Tuple[T.Optional[str], T.Optional[str], T.Optional[str], T.Optional[str], T.Optional[str]]:
+def find_coverage_tools() -> 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()
- lcov_exe = 'lcov'
+ lcov_exe, lcov_version = detect_lcov()
genhtml_exe = 'genhtml'
- if not mesonlib.exe_exists([lcov_exe, '--version']):
- lcov_exe = None
if not mesonlib.exe_exists([genhtml_exe, '--version']):
genhtml_exe = None
- return gcovr_exe, gcovr_version, lcov_exe, genhtml_exe, llvm_cov_exe
+ 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.List[str]:
r = detect_ninja_command_and_version(version, log)
# unless it becomes a stable release.
suffixes = [
'', # base (no suffix)
+ '-17', '17',
+ '-16', '16',
+ '-15', '15',
'-14', '14',
'-13', '13',
'-12', '12',
'-15', # Debian development snapshot
'-devel', # FreeBSD development snapshot
]
- names = []
+ names: T.List[str] = []
for suffix in suffixes:
names.append(tool + suffix)
return names
Return: a single-element list of the found scan-build binary ready to be
passed to Popen()
"""
- exelist = []
+ 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:
- if shutil.which(tool) is not None:
- exelist = [shutil.which(tool)]
+ which = shutil.which(tool)
+ if which is not None:
+ exelist = [which]
break
if exelist:
return 'x86'
return os_arch
-def any_compiler_has_define(compilers: CompilersDict, define):
+def any_compiler_has_define(compilers: CompilersDict, define: str) -> bool:
for c in compilers.values():
try:
if c.has_builtin_define(define):
trial = platform.processor().lower()
else:
trial = platform.machine().lower()
- mlog.debug(f'detecting CPU family based on trial={trial!r}')
if trial.startswith('i') and trial.endswith('86'):
trial = 'x86'
elif trial == 'bepc':
# 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 '
if '64' not in trial:
trial = 'mips'
else:
- trial = 'mips64'
+ 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__'):
# 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',
+ 'dragonfly': 'dragonfly',
+ 'haiku': 'haiku',
+ }
+
+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, expcted 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'
underlying ''detect_*'' method can be called to explicitly use the
partial information.
"""
+ system = detect_system()
return MachineInfo(
- detect_system(),
+ system,
detect_cpu_family(compilers) if compilers is not None else None,
detect_cpu(compilers) if compilers is not None else None,
- sys.byteorder)
+ 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.
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')) or \
((true_build_cpu_family == 'aarch64') and (machine_info.cpu_family == 'arm'))
class Environment:
log_dir = 'meson-logs'
info_dir = 'meson-info'
- def __init__(self, source_dir: T.Optional[str], build_dir: T.Optional[str], options: 'argparse.Namespace') -> None:
+ def __init__(self, source_dir: str, build_dir: str, options: 'argparse.Namespace') -> None:
self.source_dir = source_dir
self.build_dir = build_dir
# Do not try to create build directories when build_dir is none.
os.makedirs(self.log_dir, exist_ok=True)
os.makedirs(self.info_dir, exist_ok=True)
try:
- self.coredata = coredata.load(self.get_build_dir()) # type: coredata.CoreData
+ self.coredata: coredata.CoreData = coredata.load(self.get_build_dir(), suggest_reconfigure=False)
self.first_invocation = False
except FileNotFoundError:
self.create_new_coredata(options)
# 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)):
- mlog.warning('Regenerating configuration from scratch.')
+ mlog.warning('Regenerating configuration from scratch.', fatal=False)
mlog.log('Reason:', mlog.red(str(e)))
coredata.read_cmd_line_file(self.build_dir, options)
self.create_new_coredata(options)
else:
- raise e
+ raise MesonException(f'{str(e)} Try regenerating using "meson setup --wipe".')
else:
# Just create a fresh coredata in this case
self.scratch_dir = ''
# Similar to coredata.compilers, but lower level in that there is no
# meta data, only names/paths.
- binaries = PerMachineDefaultable() # type: PerMachineDefaultable[BinaryTable]
+ binaries: PerMachineDefaultable[BinaryTable] = PerMachineDefaultable()
# Misc other properties about each machine.
- properties = PerMachineDefaultable() # type: PerMachineDefaultable[Properties]
+ properties: PerMachineDefaultable[Properties] = PerMachineDefaultable()
# CMake toolchain variables
- cmakevars = PerMachineDefaultable() # type: PerMachineDefaultable[CMakeVariables]
+ cmakevars: PerMachineDefaultable[CMakeVariables] = PerMachineDefaultable()
## Setup build machine defaults
## Read in native file(s) to override build machine configuration
if self.coredata.config_files is not None:
- config = coredata.parse_machine_files(self.coredata.config_files)
+ config = coredata.parse_machine_files(self.coredata.config_files, self.source_dir)
binaries.build = BinaryTable(config.get('binaries', {}))
properties.build = Properties(config.get('properties', {}))
cmakevars.build = CMakeVariables(config.get('cmake', {}))
## Read in cross file(s) to override host machine configuration
if self.coredata.cross_files:
- config = coredata.parse_machine_files(self.coredata.cross_files)
+ config = coredata.parse_machine_files(self.coredata.cross_files, self.source_dir)
properties.host = Properties(config.get('properties', {}))
binaries.host = BinaryTable(config.get('binaries', {}))
cmakevars.host = CMakeVariables(config.get('cmake', {}))
if bt in self.options and (db in self.options or op in self.options):
mlog.warning('Recommend using either -Dbuildtype or -Doptimization + -Ddebug. '
'Using both is redundant since they override each other. '
- 'See: https://mesonbuild.com/Builtin-options.html#build-type-options')
+ 'See: https://mesonbuild.com/Builtin-options.html#build-type-options',
+ fatal=False)
exe_wrapper = self.lookup_binary_entry(MachineChoice.HOST, 'exe_wrapper')
if exe_wrapper is not None:
# time) until we're instantiating that `Compiler`
# object. This is required so that passing
# `-Dc_args=` on the command line and `$CFLAGS`
- # have subtely different behavior. `$CFLAGS` will be
+ # 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 (name, evar), for_machine in itertools.product(opts, MachineChoice):
p_env = _get_env_var(for_machine, self.is_cross_build(), evar)
if p_env is not None:
- self.binaries[for_machine].binaries.setdefault(name, mesonlib.split_args(p_env))
+ if os.path.exists(p_env):
+ self.binaries[for_machine].binaries.setdefault(name, [p_env])
+ else:
+ self.binaries[for_machine].binaries.setdefault(name, mesonlib.split_args(p_env))
def _set_default_properties_from_env(self) -> None:
"""Properties which can also be set from the environment."""
return is_object(fname)
@lru_cache(maxsize=None)
- def is_library(self, fname):
+ def is_library(self, fname: mesonlib.FileOrString):
return is_library(fname)
def lookup_binary_entry(self, for_machine: MachineChoice, name: str) -> T.Optional[T.List[str]]:
def get_datadir(self) -> str:
return self.coredata.get_option(OptionKey('datadir'))
- def get_compiler_system_dirs(self, for_machine: MachineChoice):
+ def get_compiler_system_lib_dirs(self, for_machine: MachineChoice) -> T.List[str]:
for comp in self.coredata.compilers[for_machine].values():
if comp.id == 'clang':
index = 1
out = out.split('\n')[index].lstrip('libraries: =').split(':')
return [os.path.normpath(p) for p in out]
+ def get_compiler_system_include_dirs(self, for_machine: MachineChoice) -> T.List[str]:
+ for comp in self.coredata.compilers[for_machine].values():
+ if comp.id == 'clang':
+ break
+ elif comp.id == 'gcc':
+ break
+ else:
+ # This option is only supported by gcc and clang. If we don't get a
+ # GCC or Clang compiler return and empty list.
+ return []
+ return comp.get_default_include_dirs()
+
def need_exe_wrapper(self, for_machine: MachineChoice = MachineChoice.HOST):
value = self.properties[for_machine].get('needs_exe_wrapper', None)
if value is not None:
-# SPDX-Licnese-Identifier: Apache-2.0
+# SPDX-License-Identifier: Apache-2.0
# Copyright 2012-2021 The Meson development team
# Copyright © 2021 Intel Corporation
+from __future__ import annotations
+import collections
import enum
import functools
import os
+import itertools
import typing as T
from .. import build
from ..compilers import Compiler, RunResult
from ..interpreterbase import TYPE_var, TYPE_kwargs
from .kwargs import ExtractRequired, ExtractSearchDirs
+ from .interpreter.interpreter import SourceOutputs
+ from ..mlog import TV_LoggableList
from typing_extensions import TypedDict, Literal
args: T.List[str]
dependencies: T.List[dependencies.Dependency]
- class CompileKW(TypedDict):
-
- name: str
+ class BaseCompileKW(TypedDict):
no_builtin_args: bool
include_directories: T.List[build.IncludeDirs]
args: T.List[str]
+
+ class CompileKW(BaseCompileKW):
+
+ name: str
dependencies: T.List[dependencies.Dependency]
+ werror: bool
- class CommonKW(TypedDict):
+ class CommonKW(BaseCompileKW):
prefix: str
- no_builtin_args: bool
- include_directories: T.List[build.IncludeDirs]
- args: T.List[str]
dependencies: T.List[dependencies.Dependency]
- class CompupteIntKW(CommonKW):
+ class ComputeIntKW(CommonKW):
guess: T.Optional[int]
high: T.Optional[int]
class HeaderKW(CommonKW, ExtractRequired):
pass
+ class HasKW(CommonKW, ExtractRequired):
+ pass
+
+ class HasArgumentKW(ExtractRequired):
+ pass
+
class FindLibraryKW(ExtractRequired, ExtractSearchDirs):
disabler: bool
output: str
compile_args: T.List[str]
include_directories: T.List[build.IncludeDirs]
+ dependencies: T.List[dependencies.Dependency]
class _TestMode(enum.Enum):
_NO_BUILTIN_ARGS_KW = KwargInfo('no_builtin_args', bool, default=False)
_NAME_KW = KwargInfo('name', str, default='')
+_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 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_DIRS_KW, _NO_BUILTIN_ARGS_KW,
+ _WERROR_KW]
_HEADER_KWS: T.List[KwargInfo] = [REQUIRED_KW.evolve(since='0.50.0', default=False), *_COMMON_KWS]
+_HAS_REQUIRED_KW = REQUIRED_KW.evolve(since='1.3.0', default=False)
class CompilerHolder(ObjectHolder['Compiler']):
+ preprocess_uid: T.Dict[str, itertools.count] = collections.defaultdict(itertools.count)
+
def __init__(self, compiler: 'Compiler', interpreter: 'Interpreter'):
super().__init__(compiler, interpreter)
self.environment = self.env
'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,
def cmd_array_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> T.List[str]:
return self.compiler.exelist
- def _determine_args(self, nobuiltins: bool,
- incdirs: T.List[build.IncludeDirs],
- extra_args: T.List[str],
+ def _determine_args(self, kwargs: BaseCompileKW,
mode: CompileCheckMode = CompileCheckMode.LINK) -> T.List[str]:
args: T.List[str] = []
- for i in incdirs:
- for idir in i.to_string_list(self.environment.get_source_dir()):
+ for i in kwargs['include_directories']:
+ 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 nobuiltins:
- opts = self.environment.coredata.options
+ if not kwargs['no_builtin_args']:
+ opts = coredata.OptionsView(self.environment.coredata.options, self.subproject)
args += self.compiler.get_option_compile_args(opts)
if mode is CompileCheckMode.LINK:
args.extend(self.compiler.get_option_link_args(opts))
- args.extend(extra_args)
+ if kwargs.get('werror', False):
+ args.extend(self.compiler.get_werror_args())
+ args.extend(kwargs['args'])
return args
def _determine_dependencies(self, deps: T.List['dependencies.Dependency'], compile_only: bool = False, endl: str = ':') -> T.Tuple[T.List['dependencies.Dependency'], str]:
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 = self.compiler.alignment(typename, kwargs['prefix'], self.environment,
- extra_args=kwargs['args'],
- dependencies=deps)
- mlog.log('Checking for alignment of', mlog.bold(typename, True), msg, result)
+ result, cached = self.compiler.alignment(typename, kwargs['prefix'], self.environment,
+ extra_args=kwargs['args'],
+ dependencies=deps)
+ cached_msg = mlog.blue('(cached)') if cached else ''
+ mlog.log('Checking for alignment of',
+ mlog.bold(typename, True), msg, mlog.bold(str(result)), cached_msg)
return result
@typed_pos_args('compiler.run', (str, mesonlib.File))
code = mesonlib.File.from_absolute_file(
code.rel_to_builddir(self.environment.source_dir))
testname = kwargs['name']
- extra_args = functools.partial(self._determine_args, kwargs['no_builtin_args'], kwargs['include_directories'], kwargs['args'])
+ 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)
return self.compiler.symbols_have_underscore_prefix(self.environment)
@typed_pos_args('compiler.has_member', str, str)
- @typed_kwargs('compiler.has_member', *_COMMON_KWS)
- def has_member_method(self, args: T.Tuple[str, str], kwargs: 'CommonKW') -> bool:
+ @typed_kwargs('compiler.has_member', _HAS_REQUIRED_KW, *_COMMON_KWS)
+ def has_member_method(self, args: T.Tuple[str, str], kwargs: 'HasKW') -> bool:
typename, membername = args
- extra_args = functools.partial(self._determine_args, kwargs['no_builtin_args'], kwargs['include_directories'], kwargs['args'])
+ disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False)
+ if disabled:
+ mlog.log('Type', mlog.bold(typename, True), 'has member', mlog.bold(membername, True), 'skipped: feature', mlog.bold(feature), 'disabled')
+ return False
+ 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)
cached_msg = mlog.blue('(cached)') if cached else ''
- if had:
+ if required and not had:
+ raise InterpreterException(f'{self.compiler.get_display_language()} member {membername!r} of type {typename!r} not usable')
+ elif had:
hadtxt = mlog.green('YES')
else:
hadtxt = mlog.red('NO')
return had
@typed_pos_args('compiler.has_members', str, varargs=str, min_varargs=1)
- @typed_kwargs('compiler.has_members', *_COMMON_KWS)
- def has_members_method(self, args: T.Tuple[str, T.List[str]], kwargs: 'CommonKW') -> bool:
+ @typed_kwargs('compiler.has_members', _HAS_REQUIRED_KW, *_COMMON_KWS)
+ def has_members_method(self, args: T.Tuple[str, T.List[str]], kwargs: 'HasKW') -> bool:
typename, membernames = args
- extra_args = functools.partial(self._determine_args, kwargs['no_builtin_args'], kwargs['include_directories'], kwargs['args'])
+ members = mlog.bold(', '.join([f'"{m}"' for m in membernames]))
+ disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False)
+ if disabled:
+ mlog.log('Type', mlog.bold(typename, True), 'has members', members, 'skipped: feature', mlog.bold(feature), 'disabled')
+ return False
+ 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)
cached_msg = mlog.blue('(cached)') if cached else ''
- if had:
+ if required and not had:
+ # print members as array: ['member1', 'member2']
+ raise InterpreterException(f'{self.compiler.get_display_language()} members {membernames!r} of type {typename!r} not usable')
+ elif had:
hadtxt = mlog.green('YES')
else:
hadtxt = mlog.red('NO')
- members = mlog.bold(', '.join([f'"{m}"' for m in membernames]))
mlog.log('Checking whether type', mlog.bold(typename, True),
'has members', members, msg, hadtxt, cached_msg)
return had
@typed_pos_args('compiler.has_function', str)
- @typed_kwargs('compiler.has_function', *_COMMON_KWS)
- def has_function_method(self, args: T.Tuple[str], kwargs: 'CommonKW') -> bool:
+ @typed_kwargs('compiler.has_function', _HAS_REQUIRED_KW, *_COMMON_KWS)
+ def has_function_method(self, args: T.Tuple[str], kwargs: 'HasKW') -> bool:
funcname = args[0]
- extra_args = self._determine_args(kwargs['no_builtin_args'], kwargs['include_directories'], kwargs['args'])
+ disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False)
+ if disabled:
+ mlog.log('Has function', mlog.bold(funcname, True), 'skipped: feature', mlog.bold(feature), 'disabled')
+ 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,
extra_args=extra_args,
dependencies=deps)
cached_msg = mlog.blue('(cached)') if cached else ''
- if had:
+ if required and not had:
+ raise InterpreterException(f'{self.compiler.get_display_language()} function {funcname!r} not usable')
+ elif had:
hadtxt = mlog.green('YES')
else:
hadtxt = mlog.red('NO')
return had
@typed_pos_args('compiler.has_type', str)
- @typed_kwargs('compiler.has_type', *_COMMON_KWS)
- def has_type_method(self, args: T.Tuple[str], kwargs: 'CommonKW') -> bool:
+ @typed_kwargs('compiler.has_type', _HAS_REQUIRED_KW, *_COMMON_KWS)
+ def has_type_method(self, args: T.Tuple[str], kwargs: 'HasKW') -> bool:
typename = args[0]
- extra_args = functools.partial(self._determine_args, kwargs['no_builtin_args'], kwargs['include_directories'], kwargs['args'])
+ disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False)
+ if disabled:
+ mlog.log('Has type', mlog.bold(typename, True), 'skipped: feature', mlog.bold(feature), 'disabled')
+ 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,
extra_args=extra_args, dependencies=deps)
cached_msg = mlog.blue('(cached)') if cached else ''
- if had:
+ if required and not had:
+ raise InterpreterException(f'{self.compiler.get_display_language()} type {typename!r} not usable')
+ elif had:
hadtxt = mlog.green('YES')
else:
hadtxt = mlog.red('NO')
KwargInfo('guess', (int, NoneType)),
*_COMMON_KWS,
)
- def compute_int_method(self, args: T.Tuple[str], kwargs: 'CompupteIntKW') -> int:
+ def compute_int_method(self, args: T.Tuple[str], kwargs: 'ComputeIntKW') -> int:
expression = args[0]
- extra_args = functools.partial(self._determine_args, kwargs['no_builtin_args'], kwargs['include_directories'], kwargs['args'])
+ 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'],
@typed_kwargs('compiler.sizeof', *_COMMON_KWS)
def sizeof_method(self, args: T.Tuple[str], kwargs: 'CommonKW') -> int:
element = args[0]
- extra_args = functools.partial(self._determine_args, kwargs['no_builtin_args'], kwargs['include_directories'], kwargs['args'])
+ extra_args = functools.partial(self._determine_args, kwargs)
deps, msg = self._determine_dependencies(kwargs['dependencies'], compile_only=self.compiler.is_cross)
- esize = self.compiler.sizeof(element, kwargs['prefix'], self.environment,
- extra_args=extra_args, dependencies=deps)
- mlog.log('Checking for size of', mlog.bold(element, True), msg, esize)
+ esize, cached = self.compiler.sizeof(element, kwargs['prefix'], self.environment,
+ extra_args=extra_args, dependencies=deps)
+ cached_msg = mlog.blue('(cached)') if cached else ''
+ mlog.log('Checking for size of',
+ mlog.bold(element, True), msg, mlog.bold(str(esize)), cached_msg)
return esize
@FeatureNew('compiler.get_define', '0.40.0')
@typed_kwargs('compiler.get_define', *_COMMON_KWS)
def get_define_method(self, args: T.Tuple[str], kwargs: 'CommonKW') -> str:
element = args[0]
- extra_args = functools.partial(self._determine_args, kwargs['no_builtin_args'], kwargs['include_directories'], kwargs['args'])
+ 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)
cached_msg = mlog.blue('(cached)') if cached else ''
- mlog.log('Fetching value of define', mlog.bold(element, True), msg, value, cached_msg)
- return value
+ value_msg = '(undefined)' if value is None else value
+ mlog.log('Fetching value of define', mlog.bold(element, True), msg, value_msg, cached_msg)
+ return value if value is not None else ''
+
+ @FeatureNew('compiler.has_define', '1.3.0')
+ @typed_pos_args('compiler.has_define', str)
+ @typed_kwargs('compiler.has_define', *_COMMON_KWS)
+ 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)
+ 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)
+
+ return value is not None
@typed_pos_args('compiler.compiles', (str, mesonlib.File))
@typed_kwargs('compiler.compiles', *_COMPILES_KWS)
def compiles_method(self, args: T.Tuple['mesonlib.FileOrString'], kwargs: 'CompileKW') -> bool:
code = args[0]
if isinstance(code, mesonlib.File):
+ if code.is_built:
+ FeatureNew.single_use('compiler.compiles with file created at setup time', '1.2.0', self.subproject,
+ 'It was broken and either errored or returned false.', self.current_node)
self.interpreter.add_build_def_file(code)
code = mesonlib.File.from_absolute_file(
- code.rel_to_builddir(self.environment.source_dir))
+ code.absolute_path(self.environment.source_dir, self.environment.build_dir))
testname = kwargs['name']
- extra_args = functools.partial(self._determine_args, kwargs['no_builtin_args'], kwargs['include_directories'], kwargs['args'])
+ 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,
extra_args=extra_args,
code = args[0]
compiler = None
if isinstance(code, mesonlib.File):
+ if code.is_built:
+ FeatureNew.single_use('compiler.links with file created at setup time', '1.2.0', self.subproject,
+ 'It was broken and either errored or returned false.', self.current_node)
self.interpreter.add_build_def_file(code)
code = mesonlib.File.from_absolute_file(
- code.rel_to_builddir(self.environment.source_dir))
+ code.absolute_path(self.environment.source_dir, self.environment.build_dir))
suffix = code.suffix
if suffix not in self.compiler.file_suffixes:
for_machine = self.compiler.for_machine
compiler = clist[SUFFIX_TO_LANG[suffix]]
testname = kwargs['name']
- extra_args = functools.partial(self._determine_args, kwargs['no_builtin_args'], kwargs['include_directories'], kwargs['args'])
+ extra_args = functools.partial(self._determine_args, kwargs)
deps, msg = self._determine_dependencies(kwargs['dependencies'], compile_only=False)
result, cached = self.compiler.links(code, self.environment,
compiler=compiler,
if disabled:
mlog.log('Check usable header', mlog.bold(hname, True), 'skipped: feature', mlog.bold(feature), 'disabled')
return False
- extra_args = functools.partial(self._determine_args, kwargs['no_builtin_args'], kwargs['include_directories'], kwargs['args'])
+ 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,
extra_args=extra_args,
if disabled:
mlog.log('Has header', mlog.bold(hname, True), 'skipped: feature', mlog.bold(feature), 'disabled')
return False
- extra_args = functools.partial(self._determine_args, kwargs['no_builtin_args'], kwargs['include_directories'], kwargs['args'])
+ 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,
extra_args=extra_args, dependencies=deps)
if disabled:
mlog.log('Header', mlog.bold(hname, True), 'has symbol', mlog.bold(symbol, True), 'skipped: feature', mlog.bold(feature), 'disabled')
return False
- extra_args = functools.partial(self._determine_args, kwargs['no_builtin_args'], kwargs['include_directories'], kwargs['args'])
+ 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,
extra_args=extra_args,
return lib
def _has_argument_impl(self, arguments: T.Union[str, T.List[str]],
- mode: _TestMode = _TestMode.COMPILER) -> bool:
+ mode: _TestMode = _TestMode.COMPILER,
+ kwargs: T.Optional['ExtractRequired'] = None) -> bool:
"""Shared implementation for methods checking compiler and linker arguments."""
# This simplifies the callers
if isinstance(arguments, str):
arguments = [arguments]
- test = self.compiler.has_multi_link_arguments if mode is _TestMode.LINKER else self.compiler.has_multi_arguments
- result, cached = test(arguments, self.environment)
- cached_msg = mlog.blue('(cached)') if cached else ''
- mlog.log(
+ logargs: TV_LoggableList = [
'Compiler for',
self.compiler.get_display_language(),
'supports{}'.format(' link' if mode is _TestMode.LINKER else ''),
'arguments {}:'.format(' '.join(arguments)),
+ ]
+ kwargs = kwargs or {'required': False}
+ disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False)
+ if disabled:
+ logargs += ['skipped: feature', mlog.bold(feature), 'disabled']
+ 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)
+ if required and not result:
+ logargs += ['not usable']
+ raise InterpreterException(*logargs)
+ logargs += [
mlog.green('YES') if result else mlog.red('NO'),
- cached_msg)
+ mlog.blue('(cached)') if cached else '',
+ ]
+ mlog.log(*logargs)
return result
- @noKwargs
@typed_pos_args('compiler.has_argument', str)
- def has_argument_method(self, args: T.Tuple[str], kwargs: 'TYPE_kwargs') -> bool:
- return self._has_argument_impl([args[0]])
+ @typed_kwargs('compiler.has_argument', _HAS_REQUIRED_KW)
+ def has_argument_method(self, args: T.Tuple[str], kwargs: 'HasArgumentKW') -> bool:
+ return self._has_argument_impl([args[0]], kwargs=kwargs)
- @noKwargs
@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')
- def has_multi_arguments_method(self, args: T.Tuple[T.List[str]], kwargs: 'TYPE_kwargs') -> bool:
- return self._has_argument_impl(args[0])
+ def has_multi_arguments_method(self, args: T.Tuple[T.List[str]], kwargs: 'HasArgumentKW') -> bool:
+ return self._has_argument_impl(args[0], kwargs=kwargs)
@FeatureNew('compiler.get_supported_arguments', '0.43.0')
@typed_pos_args('compiler.get_supported_arguments', varargs=str)
return []
@FeatureNew('compiler.has_link_argument', '0.46.0')
- @noKwargs
@typed_pos_args('compiler.has_link_argument', str)
- def has_link_argument_method(self, args: T.Tuple[str], kwargs: 'TYPE_kwargs') -> bool:
- return self._has_argument_impl([args[0]], mode=_TestMode.LINKER)
+ @typed_kwargs('compiler.has_link_argument', _HAS_REQUIRED_KW)
+ 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')
- @noKwargs
@typed_pos_args('compiler.has_multi_link_argument', varargs=str)
- def has_multi_link_arguments_method(self, args: T.Tuple[T.List[str]], kwargs: 'TYPE_kwargs') -> bool:
- return self._has_argument_impl(args[0], mode=_TestMode.LINKER)
+ @typed_kwargs('compiler.has_multi_link_argument', _HAS_REQUIRED_KW)
+ 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
mlog.log('First supported link argument:', mlog.red('None'))
return []
- def _has_function_attribute_impl(self, attr: str) -> bool:
+ def _has_function_attribute_impl(self, attr: str, kwargs: T.Optional['ExtractRequired'] = None) -> bool:
"""Common helper for function attribute testing."""
- result, cached = self.compiler.has_func_attribute(attr, self.environment)
- cached_msg = mlog.blue('(cached)') if cached else ''
- h = mlog.green('YES') if result else mlog.red('NO')
- mlog.log(f'Compiler for {self.compiler.get_display_language()} supports function attribute {attr}:', h, cached_msg)
- return result
+ logargs: TV_LoggableList = [
+ f'Compiler for {self.compiler.get_display_language()} supports function attribute {attr}:',
+ ]
+ kwargs = kwargs or {'required': False}
+ disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False)
+ if disabled:
+ logargs += ['skipped: feature', mlog.bold(feature), 'disabled']
+ mlog.log(*logargs)
+ return False
+ had, cached = self.compiler.has_func_attribute(attr, self.environment)
+ if required and not had:
+ logargs += ['not usable']
+ raise InterpreterException(*logargs)
+ logargs += [
+ mlog.green('YES') if had else mlog.red('NO'),
+ mlog.blue('(cached)') if cached else ''
+ ]
+ mlog.log(*logargs)
+ return had
@FeatureNew('compiler.has_function_attribute', '0.48.0')
- @noKwargs
@typed_pos_args('compiler.has_function_attribute', str)
- def has_func_attribute_method(self, args: T.Tuple[str], kwargs: 'TYPE_kwargs') -> bool:
- return self._has_function_attribute_impl(args[0])
+ @typed_kwargs('compiler.has_function_attribute', _HAS_REQUIRED_KW)
+ 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
return self.compiler.get_argument_syntax()
@FeatureNew('compiler.preprocess', '0.64.0')
- @typed_pos_args('compiler.preprocess', varargs=(mesonlib.File, str), min_varargs=1)
+ @typed_pos_args('compiler.preprocess', varargs=(str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList), min_varargs=1)
@typed_kwargs(
'compiler.preprocess',
KwargInfo('output', str, default='@PLAINNAME@.i'),
KwargInfo('compile_args', ContainerTypeInfo(list, str), listify=True, default=[]),
_INCLUDE_DIRS_KW,
+ _DEPENDENCIES_KW.evolve(since='1.1.0'),
)
def preprocess_method(self, args: T.Tuple[T.List['mesonlib.FileOrString']], kwargs: 'PreprocessKW') -> T.List[build.CustomTargetIndex]:
compiler = self.compiler.get_preprocessor()
- sources = self.interpreter.source_strings_to_files(args[0])
- tg_kwargs = {
- f'{self.compiler.language}_args': kwargs['compile_args'],
- 'build_by_default': False,
- 'include_directories': kwargs['include_directories'],
- }
+ sources: 'SourceOutputs' = self.interpreter.source_strings_to_files(args[0])
+ if any(isinstance(s, (build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)) for s in sources):
+ FeatureNew.single_use('compiler.preprocess with generated sources', '1.1.0', self.subproject,
+ location=self.current_node)
+
+ tg_counter = next(self.preprocess_uid[self.interpreter.subdir])
+ if tg_counter > 0:
+ FeatureNew.single_use('compiler.preprocess used multiple times', '1.1.0', self.subproject,
+ location=self.current_node)
+ tg_name = f'preprocessor_{tg_counter}'
tg = build.CompileTarget(
- 'preprocessor',
+ tg_name,
self.interpreter.subdir,
self.subproject,
self.environment,
sources,
kwargs['output'],
compiler,
- tg_kwargs)
+ self.interpreter.backend,
+ kwargs['compile_args'],
+ kwargs['include_directories'],
+ kwargs['dependencies'])
self.interpreter.add_target(tg.name, tg)
# Expose this target as list of its outputs, so user can pass them to
# other targets, list outputs, etc.
class DependencyFallbacksHolder(MesonInterpreterObject):
def __init__(self, interpreter: 'Interpreter', names: T.List[str], allow_fallback: T.Optional[bool] = None,
- default_options: T.Optional[T.List[str]] = None) -> None:
+ default_options: T.Optional[T.Dict[OptionKey, str]] = None) -> None:
super().__init__(subproject=interpreter.subproject)
self.interpreter = interpreter
self.subproject = interpreter.subproject
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.subproject_kwargs = {'default_options': default_options or {}}
self.names: T.List[str] = []
self.forcefallback: bool = False
self.nofallback: bool = False
# dependency('foo', static: true) should implicitly add
# default_options: ['default_library=static']
static = kwargs.get('static')
- default_options = stringlistify(func_kwargs.get('default_options', []))
- if static is not None and not any('default_library' in i for i in default_options):
+ default_options = func_kwargs.get('default_options', {})
+ if static is not None and 'default_library' not in default_options:
default_library = 'static' if static else 'shared'
- opt = f'default_library={default_library}'
- mlog.log(f'Building fallback subproject with {opt}')
- default_options.append(opt)
+ mlog.log(f'Building fallback subproject with default_library={default_library}')
+ default_options[OptionKey('default_library')] = default_library
func_kwargs['default_options'] = default_options
# Configure the subproject
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, 'meson', func_kwargs)
+ self.interpreter.do_subproject(subp_name, func_kwargs)
return self._get_subproject_dep(subp_name, varname, kwargs)
def _get_subproject(self, subp_name: str) -> T.Optional[SubprojectHolder]:
mlog.log('Dependency', mlog.bold(self._display_name),
'found:', mlog.red('NO'), *info)
return cached_dep
+ elif self.forcefallback and self.subproject_name:
+ cached_dep = None
else:
info = [mlog.blue('(cached)')]
cached_dep = self.coredata.deps[for_machine].get(identifier)
from .. import envconfig
from ..wrap import wrap, WrapMode
from .. import mesonlib
-from ..mesonlib import (MesonBugException, HoldableObject, FileMode, MachineChoice, OptionKey,
- listify, extract_as_list, has_path_sep, PerMachine)
+from ..mesonlib import (EnvironmentVariables, ExecutableSerialisation, MesonBugException, MesonException, HoldableObject,
+ FileMode, MachineChoice, OptionKey, listify,
+ extract_as_list, has_path_sep, path_is_in_root, PerMachine)
from ..programs import ExternalProgram, NonExistingExternalProgram
from ..dependencies import Dependency
from ..depfile import DepFile
from ..interpreterbase import noPosargs, noKwargs, permittedKwargs, noArgsFlattening, noSecondLevelHolderResolving, unholder_return
from ..interpreterbase import InterpreterException, InvalidArguments, InvalidCode, SubdirDoneRequest
from ..interpreterbase import Disabler, disablerIfNotFound
-from ..interpreterbase import FeatureNew, FeatureDeprecated, FeatureNewKwargs, FeatureDeprecatedKwargs
-from ..interpreterbase import ObjectHolder
+from ..interpreterbase import FeatureNew, FeatureDeprecated, FeatureBroken, FeatureNewKwargs
+from ..interpreterbase import ObjectHolder, ContextManagerObject
+from ..interpreterbase import stringifyUserArguments
from ..modules import ExtensionModule, ModuleObject, MutableModuleObject, NewExtensionModule, NotFoundExtensionModule
-from ..cmake import CMakeInterpreter
-from ..backend.backends import ExecutableSerialisation
+from ..optinterpreter import optname_regex
from . import interpreterobjects as OBJ
from . import compiler as compilerOBJ
NullSubprojectInterpreter,
)
from .type_checking import (
+ BUILD_TARGET_KWS,
COMMAND_KW,
CT_BUILD_ALWAYS,
CT_BUILD_ALWAYS_STALE,
CT_BUILD_BY_DEFAULT,
CT_INPUT_KW,
CT_INSTALL_DIR_KW,
+ EXECUTABLE_KWS,
+ JAR_KWS,
+ LIBRARY_KWS,
MULTI_OUTPUT_KW,
OUTPUT_KW,
DEFAULT_OPTIONS,
INSTALL_KW,
INSTALL_DIR_KW,
INSTALL_MODE_KW,
+ INSTALL_FOLLOW_SYMLINKS,
LINK_WITH_KW,
LINK_WHOLE_KW,
CT_INSTALL_TAG_KW,
NATIVE_KW,
PRESERVE_PATH_KW,
REQUIRED_KW,
- SOURCES_KW,
+ SHARED_LIB_KWS,
+ SHARED_MOD_KWS,
+ DEPENDENCY_SOURCES_KW,
+ SOURCES_VARARGS,
+ STATIC_LIB_KWS,
VARIABLES_KW,
TEST_KWS,
NoneType,
if T.TYPE_CHECKING:
import argparse
- from typing_extensions import Literal
-
from . import kwargs as kwtypes
from ..backend.backends import Backend
from ..interpreterbase.baseobjects import InterpreterObject, TYPE_var, TYPE_kwargs
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.BuildTarget, build.CustomTargetIndex, build.CustomTarget,
build.ExtractedObjects, build.GeneratedList, build.StructuredSources]
+ BuildTargetSource = T.Union[mesonlib.FileOrString, build.GeneratedTypes, build.StructuredSources]
+
+ ProgramVersionFunc = T.Callable[[T.Union[ExternalProgram, build.Executable, OverrideProgram]], str]
+
def _project_version_validator(value: T.Union[T.List, str, mesonlib.File, None]) -> T.Optional[str]:
if isinstance(value, list):
return 'when passed as array must contain a File'
return None
-
-def stringifyUserArguments(args: T.List[T.Any], quote: bool = False) -> str:
- if isinstance(args, list):
- return '[%s]' % ', '.join([stringifyUserArguments(x, True) for x in args])
- elif isinstance(args, dict):
- return '{%s}' % ', '.join(['{} : {}'.format(stringifyUserArguments(k, True), stringifyUserArguments(v, True)) for k, v in args.items()])
- elif isinstance(args, bool):
- return 'true' if args else 'false'
- elif isinstance(args, int):
- return str(args)
- elif isinstance(args, str):
- return f"'{args}'" if quote else args
- raise InvalidArguments('Function accepts only strings, integers, bools, lists, dictionaries and lists thereof.')
-
class Summary:
def __init__(self, project_name: str, project_version: str):
self.project_name = project_name
raise InterpreterException(f'Summary section {section!r} already have key {k!r}')
formatted_values = []
for i in listify(v):
- if isinstance(i, bool) and bool_yn:
- formatted_values.append(mlog.green('YES') if i else mlog.red('NO'))
- elif isinstance(i, (str, int, bool)):
+ if isinstance(i, bool):
+ if bool_yn:
+ formatted_values.append(mlog.green('YES') if i else mlog.red('NO'))
+ else:
+ formatted_values.append('true' if i else 'false')
+ elif isinstance(i, (str, int)):
formatted_values.append(str(i))
elif isinstance(i, (ExternalProgram, Dependency)):
FeatureNew.single_use('dependency or external program in summary', '0.57.0', subproject)
def dump_value(self, arr, list_sep, indent):
lines_sep = '\n' + ' ' * indent
if list_sep is None:
- mlog.log(*arr, sep=lines_sep)
+ mlog.log(*arr, sep=lines_sep, display_timestamp=False)
return
max_len = shutil.get_terminal_size().columns
line = []
line = []
line.append(v)
line_len += v_len
- mlog.log(*line, sep=list_sep)
+ mlog.log(*line, sep=list_sep, display_timestamp=False)
known_library_kwargs = (
build.known_shlib_kwargs |
- build.known_stlib_kwargs
+ build.known_stlib_kwargs |
+ {f'{l}_shared_args' for l in compilers.all_languages - {'java'}} |
+ {f'{l}_static_args' for l in compilers.all_languages - {'java'}}
)
known_build_target_kwargs = (
generate a Meson AST via introspection, etc.
'''
- ALLOW_BUILD_DIR_FILE_REFFERENCES = 1
+ ALLOW_BUILD_DIR_FILE_REFERENCES = 1
permitted_dependency_kwargs = {
'allow_fallback',
# be different for dependencies provided by wrap files.
self.subproject_directory_name = subdir.split(os.path.sep)[-1]
self.subproject_dir = subproject_dir
- self.option_file = os.path.join(self.source_root, self.subdir, 'meson_options.txt')
self.relaxations = relaxations or set()
if not mock and ast is None:
self.load_root_meson_file()
self.sanity_check_ast()
self.builtin.update({'meson': MesonMain(self.build, self)})
self.generators: T.List[build.Generator] = []
- self.processed_buildfiles = set() # type: T.Set[str]
+ self.processed_buildfiles: T.Set[str] = set()
self.project_args_frozen = False
self.global_args_frozen = False # implies self.project_args_frozen
self.subprojects: T.Dict[str, SubprojectHolder] = {}
'error': self.func_error,
'executable': self.func_executable,
'files': self.func_files,
- 'find_library': self.func_find_library,
'find_program': self.func_find_program,
'generator': self.func_generator,
'get_option': self.func_get_option,
'get_variable': self.func_get_variable,
- 'gettext': self.func_gettext,
'import': self.func_import,
'include_directories': self.func_include_directories,
'install_data': self.func_install_data,
})
if 'MESON_UNIT_TEST' in os.environ:
self.funcs.update({'exception': self.func_exception})
+ if 'MESON_RUNNING_IN_PROJECT_TESTS' in os.environ:
+ self.funcs.update({'expect_error': self.func_expect_error})
def build_holder_map(self) -> None:
'''
build.SymlinkData: OBJ.SymlinkDataHolder,
build.InstallDir: OBJ.InstallDirHolder,
build.IncludeDirs: OBJ.IncludeDirsHolder,
- build.EnvironmentVariables: OBJ.EnvironmentVariablesHolder,
+ mesonlib.EnvironmentVariables: OBJ.EnvironmentVariablesHolder,
build.StructuredSources: OBJ.StructuredSourcesHolder,
compilers.RunResult: compilerOBJ.TryRunResultHolder,
dependencies.ExternalLibrary: OBJ.ExternalLibraryHolder,
else:
raise InterpreterException(f'Module returned a value of unknown type {v!r}.')
+ def handle_meson_version(self, pv: str, location: mparser.BaseNode) -> None:
+ if not mesonlib.version_compare(coredata.stable_version, pv):
+ raise InterpreterException.from_node(f'Meson version is {coredata.version} but project requires {pv}', node=location)
+ mesonlib.project_meson_versions[self.subproject] = pv
+
+ def handle_meson_version_from_ast(self) -> None:
+ if not self.ast.lines:
+ return
+ project = self.ast.lines[0]
+ # first line is always project()
+ if not isinstance(project, mparser.FunctionNode):
+ return
+ for kw, val in project.args.kwargs.items():
+ assert isinstance(kw, mparser.IdNode), 'for mypy'
+ if kw.value == 'meson_version':
+ # mypy does not understand "and isinstance"
+ if isinstance(val, mparser.BaseStringNode):
+ self.handle_meson_version(val.value, val)
+
def get_build_def_files(self) -> mesonlib.OrderedSet[str]:
return self.build_def_files
if f.is_built:
return
f = os.path.normpath(f.relative_name())
- elif os.path.isfile(f) and not f.startswith('/dev'):
+ elif os.path.isfile(f) and not f.startswith('/dev/'):
srcdir = Path(self.environment.get_source_dir())
builddir = Path(self.environment.get_build_dir())
try:
INCLUDE_DIRECTORIES,
LINK_WITH_KW,
LINK_WHOLE_KW.evolve(since='0.46.0'),
- SOURCES_KW,
+ DEPENDENCY_SOURCES_KW,
+ KwargInfo('extra_files', ContainerTypeInfo(list, (mesonlib.File, str)), listify=True, default=[], since='1.2.0'),
VARIABLES_KW.evolve(since='0.54.0', since_values={list: '0.56.0'}),
KwargInfo('version', (str, NoneType)),
+ KwargInfo('objects', ContainerTypeInfo(list, build.ExtractedObjects), listify=True, default=[], since='1.1.0'),
)
- def func_declare_dependency(self, node, args, kwargs):
+ 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)
libs = kwargs['link_with']
libs_whole = kwargs['link_whole']
+ objects = kwargs['objects']
sources = self.source_strings_to_files(kwargs['sources'])
+ extra_files = self.source_strings_to_files(kwargs['extra_files'])
compile_args = kwargs['compile_args']
link_args = kwargs['link_args']
variables = kwargs['variables']
continue
if p.is_absolute() and p.is_dir() and srcdir / self.root_subdir in [p] + list(Path(os.path.abspath(p)).parents):
variables[k] = P_OBJ.DependencyVariableString(v)
- for d in deps:
- if not isinstance(d, dependencies.Dependency):
- raise InterpreterException('Invalid dependency')
dep = dependencies.InternalDependency(version, incs, compile_args,
- link_args, libs, libs_whole, sources, deps,
- variables, d_module_versions, d_import_dirs)
+ link_args, libs, libs_whole, sources, extra_files,
+ deps, variables, d_module_versions, d_import_dirs,
+ objects)
return dep
@typed_pos_args('assert', bool, optargs=[str])
args: T.Tuple[T.Union[build.Executable, ExternalProgram, compilers.Compiler, mesonlib.File, str],
T.List[T.Union[build.Executable, ExternalProgram, compilers.Compiler, mesonlib.File, str]]],
kwargs: 'kwtypes.RunCommand') -> RunProcess:
- return self.run_command_impl(node, args, kwargs)
+ return self.run_command_impl(args, kwargs)
def run_command_impl(self,
- node: mparser.BaseNode,
args: T.Tuple[T.Union[build.Executable, ExternalProgram, compilers.Compiler, mesonlib.File, str],
T.List[T.Union[build.Executable, ExternalProgram, compilers.Compiler, mesonlib.File, str]]],
kwargs: 'kwtypes.RunCommand',
progname = name
break
else:
- raise MesonBugException('cmd was a built executable but not found in overrides table')
+ raise InterpreterException(f'Program {cmd.description()!r} is a compiled executable and therefore cannot be used during configuration')
raise InterpreterException(overridden_msg.format(progname, cmd.description()))
if isinstance(cmd, ExternalProgram):
if not cmd.found():
elif isinstance(a, ExternalProgram):
expanded_args.append(a.get_path())
elif isinstance(a, compilers.Compiler):
- FeatureNew.single_use('Compiler object as a variadic argument to `run_command`', '0.61.0', self.subproject, location=node)
+ FeatureNew.single_use('Compiler object as a variadic argument to `run_command`', '0.61.0', self.subproject, location=self.current_node)
prog = ExternalProgram(a.exelist[0], silent=True)
if not prog.found():
raise InterpreterException(f'Program {cmd!r} not found or not executable')
self.environment.get_build_command() + ['introspect'],
in_builddir=in_builddir, check=check, capture=capture)
- def func_gettext(self, nodes, args, kwargs):
- raise InterpreterException('Gettext() function has been moved to module i18n. Import it and use i18n.gettext() instead')
-
def func_option(self, nodes, args, kwargs):
raise InterpreterException('Tried to call option() in build description file. All options must be in the option file.')
'options': None,
'cmake_options': [],
}
- return self.do_subproject(args[0], 'meson', kw)
+ return self.do_subproject(args[0], kw)
def disabled_subproject(self, subp_name: str, disabled_feature: T.Optional[str] = None,
exception: T.Optional[Exception] = None) -> SubprojectHolder:
self.subprojects[subp_name] = sub
return sub
- def do_subproject(self, subp_name: str, method: Literal['meson', 'cmake'], kwargs: kwtypes.DoSubproject) -> SubprojectHolder:
+ def do_subproject(self, subp_name: str, kwargs: kwtypes.DoSubproject, force_method: T.Optional[wrap.Method] = None) -> SubprojectHolder:
disabled, required, feature = extract_required_kwarg(kwargs, self.subproject)
if disabled:
mlog.log('Subproject', mlog.bold(subp_name), ':', 'skipped: feature', mlog.bold(feature), 'disabled')
return self.disabled_subproject(subp_name, disabled_feature=feature)
- default_options = coredata.create_options_dict(kwargs['default_options'], subp_name)
+ default_options = {k.evolve(subproject=subp_name): v for k, v in kwargs['default_options'].items()}
if subp_name == '':
raise InterpreterException('Subproject name must not be empty.')
r = self.environment.wrap_resolver
try:
- subdir = r.resolve(subp_name, method)
+ subdir, method = r.resolve(subp_name, force_method)
except wrap.WrapException as e:
if not required:
mlog.log(e)
return self.disabled_subproject(subp_name, exception=e)
raise e
- subdir_abs = os.path.join(self.environment.get_source_dir(), subdir)
os.makedirs(os.path.join(self.build.environment.get_build_dir(), subdir), exist_ok=True)
self.global_args_frozen = True
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]] = {
+ 'meson': self._do_subproject_meson,
+ 'cmake': self._do_subproject_cmake,
+ 'cargo': self._do_subproject_cargo,
+ }
+
try:
- if method == 'meson':
- return self._do_subproject_meson(subp_name, subdir, default_options, kwargs)
- elif method == 'cmake':
- return self._do_subproject_cmake(subp_name, subdir, subdir_abs, default_options, kwargs)
- else:
- raise mesonlib.MesonBugException(f'The method {method} is invalid for the subproject {subp_name}')
+ return methods_map[method](subp_name, subdir, default_options, kwargs)
# Invalid code is always an error
except InvalidCode:
raise
kwargs: kwtypes.DoSubproject,
ast: T.Optional[mparser.CodeBlockNode] = None,
build_def_files: T.Optional[T.List[str]] = None,
- is_translated: bool = False,
relaxations: T.Optional[T.Set[InterpreterRuleRelaxation]] = 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)
+
new_build = self.build.copy()
subi = Interpreter(new_build, self.backend, subp_name, subdir, self.subproject_dir,
- default_options, ast=ast, is_translated=is_translated,
+ default_options, ast=ast, is_translated=(ast is not None),
relaxations=relaxations,
user_defined_options=self.user_defined_options)
# Those lists are shared by all interpreters. That means that
subi.subproject_stack = self.subproject_stack + [subp_name]
current_active = self.active_projectname
- current_warnings_counter = mlog.log_warnings_counter
- mlog.log_warnings_counter = 0
- subi.run()
- subi_warnings = mlog.log_warnings_counter
- mlog.log_warnings_counter = current_warnings_counter
-
+ with mlog.nested_warnings():
+ subi.run()
+ subi_warnings = mlog.get_warning_count()
mlog.log('Subproject', mlog.bold(subp_name), 'finished.')
mlog.log()
raise InterpreterException(f'Subproject {subp_name} version is {pv} but {wanted} required.')
self.active_projectname = current_active
self.subprojects.update(subi.subprojects)
- self.subprojects[subp_name] = SubprojectHolder(subi, subdir, warnings=subi_warnings)
+ self.subprojects[subp_name] = SubprojectHolder(subi, subdir, warnings=subi_warnings,
+ callstack=self.subproject_stack)
# Duplicates are possible when subproject uses files from project root
if build_def_files:
self.build_def_files.update(build_def_files)
- # We always need the subi.build_def_files, to propgate sub-sub-projects
+ # We always need the subi.build_def_files, to propagate sub-sub-projects
self.build_def_files.update(subi.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, subdir_abs: str,
+ def _do_subproject_cmake(self, subp_name: str, subdir: str,
default_options: T.Dict[OptionKey, str],
kwargs: kwtypes.DoSubproject) -> SubprojectHolder:
+ from ..cmake import CMakeInterpreter
with mlog.nested(subp_name):
- new_build = self.build.copy()
prefix = self.coredata.options[OptionKey('prefix')].value
from ..modules.cmake import CMakeSubprojectOptions
- options = kwargs['options'] or CMakeSubprojectOptions()
- cmake_options = kwargs['cmake_options'] + options.cmake_options
- cm_int = CMakeInterpreter(new_build, Path(subdir), Path(subdir_abs), Path(prefix), new_build.environment, self.backend)
+ options = kwargs.get('options') or CMakeSubprojectOptions()
+ cmake_options = kwargs.get('cmake_options', []) + options.cmake_options
+ cm_int = CMakeInterpreter(Path(subdir), Path(prefix), self.build.environment, self.backend)
cm_int.initialise(cmake_options)
cm_int.analyse()
# Generate a meson ast and execute it with the normal do_subproject_meson
ast = cm_int.pretend_to_be_meson(options.target_options)
-
- mlog.log()
- with mlog.nested('cmake-ast'):
- mlog.log('Processing generated meson 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('Build file:', meson_filename)
- mlog.cmd_ci_include(meson_filename)
- mlog.log()
-
result = self._do_subproject_meson(
subp_name, subdir, default_options,
kwargs, ast,
[str(f) for f in cm_int.bs_files],
- is_translated=True,
relaxations={
- InterpreterRuleRelaxation.ALLOW_BUILD_DIR_FILE_REFFERENCES,
+ InterpreterRuleRelaxation.ALLOW_BUILD_DIR_FILE_REFERENCES,
}
)
result.cm_interpreter = cm_int
-
- mlog.log()
return result
+ def _do_subproject_cargo(self, subp_name: str, subdir: str,
+ default_options: T.Dict[OptionKey, str],
+ kwargs: kwtypes.DoSubproject) -> SubprojectHolder:
+ from .. import cargo
+ FeatureNew.single_use('Cargo subproject', '1.3.0', self.subproject, location=self.current_node)
+ with mlog.nested(subp_name):
+ ast = cargo.interpret(subp_name, subdir, self.environment)
+ 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')])
+
def get_option_internal(self, optname: str) -> coredata.UserOption:
key = OptionKey.from_string(optname).evolve(subproject=self.subproject)
raise InterpreterException('Having a colon in option name is forbidden, '
'projects are not allowed to directly access '
'options of other subprojects.')
+
+ if optname_regex.search(optname.split('.', maxsplit=1)[-1]) is not None:
+ raise InterpreterException(f'Invalid option name {optname!r}')
+
opt = self.get_option_internal(optname)
if isinstance(opt, coredata.UserFeatureOption):
opt.name = optname
# The backend is already set when parsing subprojects
if self.backend is not None:
return
- backend = self.coredata.get_option(OptionKey('backend'))
from ..backend import backends
- self.backend = backends.get_backend_from_name(backend, self.build, self)
+
+ if OptionKey('genvslite') in self.user_defined_options.cmd_line_options.keys():
+ # Use of the '--genvslite vsxxxx' option ultimately overrides any '--backend xxx'
+ # option the user may specify.
+ backend_name = self.coredata.get_option(OptionKey('genvslite'))
+ self.backend = backends.get_genvslite_backend(backend_name, self.build, self)
+ else:
+ backend_name = self.coredata.get_option(OptionKey('backend'))
+ self.backend = backends.get_backend_from_name(backend_name, self.build, self)
if self.backend is None:
- raise InterpreterException(f'Unknown backend "{backend}".')
- if backend != self.backend.name:
+ raise InterpreterException(f'Unknown backend "{backend_name}".')
+ if backend_name != self.backend.name:
if self.backend.name.startswith('vs'):
mlog.log('Auto detected Visual Studio backend:', mlog.bold(self.backend.name))
- self.coredata.set_option(OptionKey('backend'), 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)
# 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)
+ self.coredata.init_backend_options(backend_name)
options = {k: v for k, v in self.environment.options.items() if k.is_backend()}
self.coredata.set_options(options)
validator=_project_version_validator,
convertor=lambda x: x[0] if isinstance(x, list) else x,
),
- KwargInfo('license', ContainerTypeInfo(list, str), default=['unknown'], listify=True),
+ KwargInfo('license', (ContainerTypeInfo(list, str), NoneType), default=None, listify=True),
+ KwargInfo('license_files', ContainerTypeInfo(list, str), default=[], listify=True, since='1.1.0'),
KwargInfo('subproject_dir', str, default='subprojects'),
)
def func_project(self, node: mparser.FunctionNode, args: T.Tuple[str, T.List[str]], kwargs: 'kwtypes.Project') -> None:
# This needs to be evaluated as early as possible, as meson uses this
# for things like deprecation testing.
if kwargs['meson_version']:
- cv = coredata.version
- pv = kwargs['meson_version']
- if not mesonlib.version_compare(cv, pv):
- raise InterpreterException(f'Meson version is {cv} but project requires {pv}')
- mesonlib.project_meson_versions[self.subproject] = kwargs['meson_version']
-
- if os.path.exists(self.option_file):
+ self.handle_meson_version(kwargs['meson_version'], node)
+
+ # Load "meson.options" before "meson_options.txt", and produce a warning if
+ # it is being used with an old version. I have added check that if both
+ # exist the warning isn't raised
+ option_file = os.path.join(self.source_root, self.subdir, 'meson.options')
+ old_option_file = os.path.join(self.source_root, self.subdir, 'meson_options.txt')
+
+ if os.path.exists(option_file):
+ if os.path.exists(old_option_file):
+ if os.path.samefile(option_file, old_option_file):
+ mlog.debug("Not warning about meson.options with version minimum < 1.1 because meson_options.txt also exists")
+ else:
+ raise MesonException("meson.options and meson_options.txt both exist, but are not the same file.")
+ else:
+ FeatureNew.single_use('meson.options file', '1.1', self.subproject, 'Use meson_options.txt instead')
+ else:
+ option_file = old_option_file
+ if os.path.exists(option_file):
oi = optinterpreter.OptionInterpreter(self.subproject)
- oi.process(self.option_file)
+ oi.process(option_file)
self.coredata.update_project_options(oi.options)
- self.add_build_def_file(self.option_file)
+ self.add_build_def_file(option_file)
+
+ if self.subproject:
+ self.project_default_options = {k.evolve(subproject=self.subproject): v
+ for k, v in kwargs['default_options'].items()}
+ else:
+ self.project_default_options = kwargs['default_options']
# Do not set default_options on reconfigure otherwise it would override
# values previously set from command line. That means that changing
# default_options in a project will trigger a reconfigure but won't
# have any effect.
- self.project_default_options = coredata.create_options_dict(
- kwargs['default_options'], self.subproject)
-
+ #
# If this is the first invocation we always need to initialize
# builtins, if this is a subproject that is new in a re-invocation we
# need to initialize builtins for that
if self.build.project_version is None:
self.build.project_version = self.project_version
- proj_license = kwargs['license']
- self.build.dep_manifest[proj_name] = build.DepManifest(self.project_version, proj_license)
+
+ if kwargs['license'] is None:
+ proj_license = ['unknown']
+ if kwargs['license_files']:
+ raise InvalidArguments('Project `license` name must be specified when `license_files` is set')
+ else:
+ proj_license = kwargs['license']
+ proj_license_files = []
+ for i in self.source_strings_to_files(kwargs['license_files']):
+ ifname = i.absolute_path(self.environment.source_dir,
+ self.environment.build_dir)
+ proj_license_files.append((ifname, i))
+ self.build.dep_manifest[proj_name] = build.DepManifest(self.project_version, proj_license,
+ proj_license_files, self.subproject)
if self.subproject in self.build.projects:
raise InvalidCode('Second call to project().')
# vs backend version we need. But after setting default_options in case
# the project sets vs backend by default.
backend = self.coredata.get_option(OptionKey('backend'))
- force_vsenv = self.user_defined_options.vsenv or backend.startswith('vs')
- if mesonlib.setup_vsenv(force_vsenv):
- self.build.need_vsenv = True
+ vsenv = self.coredata.get_option(OptionKey('vsenv'))
+ force_vsenv = vsenv or backend.startswith('vs')
+ mesonlib.setup_vsenv(force_vsenv)
self.add_languages(proj_langs, True, MachineChoice.HOST)
self.add_languages(proj_langs, False, MachineChoice.BUILD)
success &= self.add_languages(langs, required, MachineChoice.HOST)
return success
+ def _stringify_user_arguments(self, args: T.List[TYPE_var], func_name: str) -> T.List[str]:
+ try:
+ return [stringifyUserArguments(i, self.subproject) for i in args]
+ except InvalidArguments as e:
+ raise InvalidArguments(f'{func_name}(): {str(e)}')
+
@noArgsFlattening
@noKwargs
def func_message(self, node: mparser.BaseNode, args, kwargs):
if len(args) > 1:
FeatureNew.single_use('message with more than one argument', '0.54.0', self.subproject, location=node)
- args_str = [stringifyUserArguments(i) for i in args]
+ args_str = self._stringify_user_arguments(args, 'message')
self.message_impl(args_str)
def message_impl(self, args):
section, values, kwargs['bool_yn'], kwargs['list_sep'], self.subproject)
def _print_summary(self) -> None:
- # Add automatic 'Supbrojects' section in main project.
+ # Add automatic 'Subprojects' section in main project.
all_subprojects = collections.OrderedDict()
for name, subp in sorted(self.subprojects.items()):
- value = subp.found()
+ value = [subp.found()]
if subp.disabled_feature:
- value = [value, f'Feature {subp.disabled_feature!r} disabled']
+ value += [f'Feature {subp.disabled_feature!r} disabled']
elif subp.exception:
- value = [value, str(subp.exception)]
+ value += [str(subp.exception)]
elif subp.warnings > 0:
- value = [value, f'{subp.warnings} warnings']
+ value += [f'{subp.warnings} warnings']
+ if subp.callstack:
+ stack = ' => '.join(subp.callstack)
+ value += [f'(from {stack})']
all_subprojects[name] = value
if all_subprojects:
self.summary_impl('Subprojects', all_subprojects,
def func_warning(self, node, args, kwargs):
if len(args) > 1:
FeatureNew.single_use('warning with more than one argument', '0.54.0', self.subproject, location=node)
- args_str = [stringifyUserArguments(i) for i in args]
+ args_str = self._stringify_user_arguments(args, 'warning')
mlog.warning(*args_str, location=node)
@noArgsFlattening
def func_error(self, node, args, kwargs):
if len(args) > 1:
FeatureNew.single_use('error with more than one argument', '0.58.0', self.subproject, location=node)
- args_str = [stringifyUserArguments(i) for i in args]
+ args_str = self._stringify_user_arguments(args, 'error')
raise InterpreterException('Problem encountered: ' + ' '.join(args_str))
@noArgsFlattening
@FeatureNew('debug', '0.63.0')
@noKwargs
def func_debug(self, node, args, kwargs):
- args_str = [stringifyUserArguments(i) for i in args]
+ args_str = self._stringify_user_arguments(args, 'debug')
mlog.debug('Debug:', *args_str)
@noKwargs
@noPosargs
def func_exception(self, node, args, kwargs):
- raise Exception()
+ raise RuntimeError('unit test traceback :)')
+
+ @typed_pos_args('expect_error', str)
+ @typed_kwargs(
+ 'expect_error',
+ KwargInfo('how', str, default='literal', validator=in_set_validator({'literal', 're'})),
+ )
+ def func_expect_error(self, node: mparser.BaseNode, args: T.Tuple[str], kwargs: TYPE_kwargs) -> ContextManagerObject:
+ class ExpectErrorObject(ContextManagerObject):
+ def __init__(self, msg: str, how: str, subproject: str) -> None:
+ super().__init__(subproject)
+ self.msg = msg
+ self.how = how
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ if exc_val is None:
+ raise InterpreterException('Expecting an error but code block succeeded')
+ if isinstance(exc_val, mesonlib.MesonException):
+ msg = str(exc_val)
+ if (self.how == 'literal' and self.msg != msg) or \
+ (self.how == 're' and not re.match(self.msg, msg)):
+ raise InterpreterException(f'Expecting error {self.msg!r} but got {msg!r}')
+ return True
+ return ExpectErrorObject(args[0], kwargs['how'], self.subproject)
def add_languages(self, args: T.List[str], required: bool, for_machine: MachineChoice) -> bool:
success = self.add_languages_for(args, required, for_machine)
comp = self.coredata.compilers[for_machine].get(lang)
if not comp:
try:
- comp = compilers.detect_compiler_for(self.environment, lang, for_machine)
+ skip_sanity_check = self.should_skip_sanity_check(for_machine)
+ if skip_sanity_check:
+ mlog.log('Cross compiler sanity tests disabled via the cross file.', once=True)
+ comp = compilers.detect_compiler_for(self.environment, lang, for_machine, skip_sanity_check)
if comp is None:
raise InvalidArguments(f'Tried to use unknown language "{lang}".')
- if self.should_skip_sanity_check(for_machine):
- mlog.log_once('Cross compiler sanity tests disabled via the cross file.')
- else:
- comp.sanity_check(self.environment.get_scratch_dir(), self.environment)
- except Exception:
+ except mesonlib.MesonException:
if not required:
mlog.log('Compiler for language',
mlog.bold(lang), 'for the', machine_name,
if not isinstance(p, str):
raise InterpreterException('Executable name must be a string')
prog = ExternalProgram.from_bin_list(self.environment, for_machine, p)
- if prog.found():
+ # if the machine file specified something, it may be a regular
+ # not-found program but we still want to return that
+ if not isinstance(prog, NonExistingExternalProgram):
return prog
return None
# 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, T.Union[str, int, bool, T.List[str]]]] = None,
required: bool = True, silent: bool = True,
wanted: T.Union[str, T.List[str]] = '',
search_dirs: T.Optional[T.List[str]] = None,
- version_func: T.Optional[T.Callable[[T.Union['ExternalProgram', 'build.Executable', 'OverrideProgram']], str]] = None
+ version_func: T.Optional[ProgramVersionFunc] = None
) -> T.Union['ExternalProgram', 'build.Executable', 'OverrideProgram']:
args = mesonlib.listify(args)
extra_info: T.List[mlog.TV_Loggable] = []
- progobj = self.program_lookup(args, for_machine, required, search_dirs, extra_info)
- if progobj is None:
+ progobj = self.program_lookup(args, for_machine, default_options, required, search_dirs, wanted, version_func, extra_info)
+ if progobj is None or not self.check_program_version(progobj, wanted, version_func, extra_info):
progobj = self.notfound_program(args)
if isinstance(progobj, ExternalProgram) and not progobj.found():
if not silent:
- mlog.log('Program', mlog.bold(progobj.get_name()), 'found:', mlog.red('NO'))
+ mlog.log('Program', mlog.bold(progobj.get_name()), 'found:', mlog.red('NO'), *extra_info)
if required:
m = 'Program {!r} not found or not executable'
raise InterpreterException(m.format(progobj.get_name()))
return progobj
- if wanted:
- if version_func:
- version = version_func(progobj)
- elif isinstance(progobj, build.Executable):
- if progobj.subproject:
- interp = self.subprojects[progobj.subproject].held_object
- else:
- interp = self
- assert isinstance(interp, Interpreter)
- version = interp.project_version
- else:
- version = progobj.get_version(self)
- is_found, not_found, _ = mesonlib.version_compare_many(version, wanted)
- if not is_found:
- mlog.log('Program', mlog.bold(progobj.name), 'found:', mlog.red('NO'),
- 'found', mlog.normal_cyan(version), 'but need:',
- mlog.bold(', '.join([f"'{e}'" for e in not_found])), *extra_info)
- if required:
- m = 'Invalid version of program, need {!r} {!r} found {!r}.'
- raise InterpreterException(m.format(progobj.name, not_found, version))
- return self.notfound_program(args)
- extra_info.insert(0, mlog.normal_cyan(version))
-
# Only store successful lookups
self.store_name_lookups(args)
if not silent:
return progobj
def program_lookup(self, args: T.List[mesonlib.FileOrString], for_machine: MachineChoice,
- required: bool, search_dirs: T.List[str], extra_info: T.List[mlog.TV_Loggable]
+ default_options: T.Optional[T.Dict[OptionKey, T.Union[str, int, bool, T.List[str]]]],
+ required: bool,
+ search_dirs: T.List[str],
+ wanted: T.Union[str, T.List[str]],
+ version_func: T.Optional[ProgramVersionFunc],
+ extra_info: T.List[mlog.TV_Loggable]
) -> T.Optional[T.Union[ExternalProgram, build.Executable, OverrideProgram]]:
progobj = self.program_from_overrides(args, extra_info)
if progobj:
return progobj
+ if args[0] == 'meson':
+ # Override find_program('meson') to return what we were invoked with
+ return ExternalProgram('meson', self.environment.get_build_command(), silent=True)
+
fallback = None
wrap_mode = self.coredata.get_option(OptionKey('wrap_mode'))
if wrap_mode != WrapMode.nofallback and self.environment.wrap_resolver:
fallback = self.environment.wrap_resolver.find_program_provider(args)
if fallback and wrap_mode == WrapMode.forcefallback:
- return self.find_program_fallback(fallback, args, required, extra_info)
+ return self.find_program_fallback(fallback, args, default_options, required, extra_info)
progobj = self.program_from_file_for(for_machine, args)
if progobj is None:
if progobj is None and args[0].endswith('python3'):
prog = ExternalProgram('python3', mesonlib.python_command, silent=True)
progobj = prog if prog.found() else None
+
+ if progobj and not self.check_program_version(progobj, wanted, version_func, extra_info):
+ progobj = None
+
if progobj is None and fallback and required:
- progobj = self.find_program_fallback(fallback, args, required, extra_info)
+ progobj = self.notfound_program(args)
+ mlog.log('Program', mlog.bold(progobj.get_name()), 'found:', mlog.red('NO'), *extra_info)
+ extra_info.clear()
+ progobj = self.find_program_fallback(fallback, args, default_options, required, extra_info)
return progobj
+ def check_program_version(self, progobj: T.Union[ExternalProgram, build.Executable, OverrideProgram],
+ wanted: T.Union[str, T.List[str]],
+ version_func: T.Optional[ProgramVersionFunc],
+ extra_info: T.List[mlog.TV_Loggable]) -> bool:
+ if wanted:
+ if version_func:
+ version = version_func(progobj)
+ elif isinstance(progobj, build.Executable):
+ if progobj.subproject:
+ interp = self.subprojects[progobj.subproject].held_object
+ else:
+ interp = self
+ assert isinstance(interp, Interpreter)
+ version = interp.project_version
+ else:
+ version = progobj.get_version(self)
+ is_found, not_found, _ = mesonlib.version_compare_many(version, wanted)
+ if not is_found:
+ extra_info[:0] = ['found', mlog.normal_cyan(version), 'but need:',
+ mlog.bold(', '.join([f"'{e}'" for e in not_found]))]
+ return False
+ extra_info.insert(0, mlog.normal_cyan(version))
+ return True
+
def find_program_fallback(self, fallback: str, args: T.List[mesonlib.FileOrString],
+ default_options: T.Dict[OptionKey, T.Union[str, int, bool, T.List[str]]],
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',
mlog.bold(' '.join(args)))
sp_kwargs: kwtypes.DoSubproject = {
'required': required,
- 'default_options': [],
+ 'default_options': default_options or {},
'version': [],
'cmake_options': [],
'options': None,
}
- self.do_subproject(fallback, 'meson', sp_kwargs)
+ self.do_subproject(fallback, sp_kwargs)
return self.program_from_overrides(args, extra_info)
@typed_pos_args('find_program', varargs=(str, mesonlib.File), min_varargs=1)
REQUIRED_KW,
KwargInfo('dirs', ContainerTypeInfo(list, str), default=[], listify=True, since='0.53.0'),
KwargInfo('version', ContainerTypeInfo(list, str), default=[], listify=True, since='0.52.0'),
+ DEFAULT_OPTIONS.evolve(since='1.3.0')
)
@disablerIfNotFound
def func_find_program(self, node: mparser.BaseNode, args: T.Tuple[T.List[mesonlib.FileOrString]],
return self.notfound_program(args[0])
search_dirs = extract_search_dirs(kwargs)
- return self.find_program_impl(args[0], kwargs['native'], required=required,
+ default_options = kwargs['default_options']
+ return self.find_program_impl(args[0], kwargs['native'], default_options=default_options, required=required,
silent=False, wanted=kwargs['version'],
search_dirs=search_dirs)
- def func_find_library(self, node, args, kwargs):
- raise InvalidCode('find_library() is removed, use meson.get_compiler(\'name\').find_library() instead.\n'
- 'Look here for documentation: http://mesonbuild.com/Reference-manual.html#compiler-object\n'
- 'Look here for example: http://mesonbuild.com/howtox.html#add-math-library-lm-portably\n'
- )
-
# 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.50.0', ['not_found_message', 'cmake_module_path', 'cmake_args'])
@FeatureNewKwargs('dependency', '0.49.0', ['disabler'])
@FeatureNewKwargs('dependency', '0.40.0', ['method'])
- @FeatureNewKwargs('dependency', '0.38.0', ['default_options'])
@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:
# Replace '' by empty list of names
names = [n for n in args[0] if n]
def func_disabler(self, node, args, kwargs):
return Disabler()
- @FeatureNewKwargs('executable', '0.42.0', ['implib'])
- @FeatureNewKwargs('executable', '0.56.0', ['win_subsystem'])
- @FeatureDeprecatedKwargs('executable', '0.56.0', ['gui_app'], extra_message="Use 'win_subsystem' instead.")
@permittedKwargs(build.known_exe_kwargs)
- def func_executable(self, node, args, 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:
return self.build_target(node, args, kwargs, build.Executable)
@permittedKwargs(build.known_stlib_kwargs)
- def func_static_lib(self, node, args, kwargs):
+ @typed_pos_args('static_library', str, varargs=SOURCES_VARARGS)
+ @typed_kwargs('static_library', *STATIC_LIB_KWS, allow_unknown=True)
+ def func_static_lib(self, node: mparser.BaseNode,
+ args: T.Tuple[str, SourcesVarargsType],
+ kwargs: kwtypes.StaticLibrary) -> build.StaticLibrary:
return self.build_target(node, args, kwargs, build.StaticLibrary)
@permittedKwargs(build.known_shlib_kwargs)
- def func_shared_lib(self, node, args, kwargs):
+ @typed_pos_args('shared_library', str, varargs=SOURCES_VARARGS)
+ @typed_kwargs('shared_library', *SHARED_LIB_KWS, allow_unknown=True)
+ def func_shared_lib(self, node: mparser.BaseNode,
+ args: T.Tuple[str, SourcesVarargsType],
+ kwargs: kwtypes.SharedLibrary) -> build.SharedLibrary:
holder = self.build_target(node, args, kwargs, build.SharedLibrary)
holder.shared_library_only = True
return holder
@permittedKwargs(known_library_kwargs)
- def func_both_lib(self, node, args, kwargs):
+ @typed_pos_args('both_libraries', str, varargs=SOURCES_VARARGS)
+ @typed_kwargs('both_libraries', *LIBRARY_KWS, allow_unknown=True)
+ def func_both_lib(self, node: mparser.BaseNode,
+ args: T.Tuple[str, SourcesVarargsType],
+ kwargs: kwtypes.Library) -> build.BothLibraries:
return self.build_both_libraries(node, args, kwargs)
@FeatureNew('shared_module', '0.37.0')
@permittedKwargs(build.known_shmod_kwargs)
- def func_shared_module(self, node, args, kwargs):
+ @typed_pos_args('shared_module', str, varargs=SOURCES_VARARGS)
+ @typed_kwargs('shared_module', *SHARED_MOD_KWS, allow_unknown=True)
+ def func_shared_module(self, node: mparser.BaseNode,
+ args: T.Tuple[str, SourcesVarargsType],
+ kwargs: kwtypes.SharedModule) -> build.SharedModule:
return self.build_target(node, args, kwargs, build.SharedModule)
@permittedKwargs(known_library_kwargs)
- def func_library(self, node, args, kwargs):
+ @typed_pos_args('library', str, varargs=SOURCES_VARARGS)
+ @typed_kwargs('library', *LIBRARY_KWS, allow_unknown=True)
+ def func_library(self, node: mparser.BaseNode,
+ args: T.Tuple[str, SourcesVarargsType],
+ kwargs: kwtypes.Library) -> build.Executable:
return self.build_library(node, args, kwargs)
@permittedKwargs(build.known_jar_kwargs)
- def func_jar(self, node, args, kwargs):
+ @typed_pos_args('jar', str, varargs=(str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList, build.ExtractedObjects, build.BuildTarget))
+ @typed_kwargs('jar', *JAR_KWS, allow_unknown=True)
+ def func_jar(self, node: mparser.BaseNode,
+ args: T.Tuple[str, T.List[T.Union[str, mesonlib.File, build.GeneratedTypes]]],
+ kwargs: kwtypes.Jar) -> build.Jar:
return self.build_target(node, args, kwargs, build.Jar)
@FeatureNewKwargs('build_target', '0.40.0', ['link_whole', 'override_options'])
@permittedKwargs(known_build_target_kwargs)
- def func_build_target(self, node, args, kwargs):
- if 'target_type' not in kwargs:
- raise InterpreterException('Missing target_type keyword argument')
- target_type = kwargs.pop('target_type')
+ @typed_pos_args('build_target', str, varargs=SOURCES_VARARGS)
+ @typed_kwargs('build_target', *BUILD_TARGET_KWS, allow_unknown=True)
+ def func_build_target(self, node: mparser.BaseNode,
+ args: T.Tuple[str, SourcesVarargsType],
+ kwargs: kwtypes.BuildTarget
+ ) -> T.Union[build.Executable, build.StaticLibrary, build.SharedLibrary,
+ build.SharedModule, build.BothLibraries, build.Jar]:
+ target_type = kwargs['target_type']
if target_type == 'executable':
return self.build_target(node, args, kwargs, build.Executable)
elif target_type == 'shared_library':
return self.build_target(node, args, kwargs, build.SharedLibrary)
elif target_type == 'shared_module':
- FeatureNew.single_use(
- 'build_target(target_type: \'shared_module\')',
- '0.51.0', self.subproject, location=node)
return self.build_target(node, args, kwargs, build.SharedModule)
elif target_type == 'static_library':
return self.build_target(node, args, kwargs, build.StaticLibrary)
return self.build_both_libraries(node, args, kwargs)
elif target_type == 'library':
return self.build_library(node, args, kwargs)
- elif target_type == 'jar':
- return self.build_target(node, args, kwargs, build.Jar)
- else:
- raise InterpreterException('Unknown target_type.')
+ return self.build_target(node, args, kwargs, build.Jar)
@noPosargs
@typed_kwargs(
build_by_default = kwargs['build_always']
build_always_stale = kwargs['build_by_default']
- # These are are nullaable so that we can know whether they're explicitly
+ # These are nullable so that we can know whether they're explicitly
# set or not. If they haven't been overwritten, set them to their true
# default
if build_by_default is None:
command[0] = self.find_program_impl([command[0]])
if len(inputs) > 1 and kwargs['feed']:
- raise InvalidArguments('custom_target: "feed" keyword argument can only be used used with a single input')
+ raise InvalidArguments('custom_target: "feed" keyword argument can only be used with a single input')
if len(kwargs['output']) > 1 and kwargs['capture']:
- raise InvalidArguments('custom_target: "capture" keyword argument can only be used used with a single output')
+ raise InvalidArguments('custom_target: "capture" keyword argument can only be used with a single output')
if kwargs['capture'] and kwargs['console']:
raise InvalidArguments('custom_target: "capture" and "console" keyword arguments are mutually exclusive')
for c in command:
kwargs: 'kwtypes.FuncTest') -> None:
self.add_test(node, args, kwargs, True)
- def unpack_env_kwarg(self, kwargs: T.Union[build.EnvironmentVariables, T.Dict[str, 'TYPE_var'], T.List['TYPE_var'], str]) -> build.EnvironmentVariables:
+ def unpack_env_kwarg(self, kwargs: T.Union[EnvironmentVariables, T.Dict[str, 'TYPE_var'], T.List['TYPE_var'], str]) -> EnvironmentVariables:
envlist = kwargs.get('env')
if envlist is None:
- return build.EnvironmentVariables()
+ return EnvironmentVariables()
msg = ENV_KW.validator(envlist)
if msg:
raise InvalidArguments(f'"env": {msg}')
kwargs['priority'],
kwargs['verbose'])
- def add_test(self, node: mparser.BaseNode, args: T.List, kwargs: T.Dict[str, T.Any], is_base_test: bool):
+ def add_test(self, node: mparser.BaseNode,
+ args: T.Tuple[str, T.Union[build.Executable, build.Jar, ExternalProgram, mesonlib.File]],
+ kwargs: T.Dict[str, T.Any], is_base_test: bool):
t = self.make_test(node, args, kwargs)
if is_base_test:
self.build.tests.append(t)
KwargInfo('subdir', (str, NoneType)),
INSTALL_MODE_KW.evolve(since='0.47.0'),
INSTALL_DIR_KW,
+ INSTALL_FOLLOW_SYMLINKS,
)
def func_install_headers(self, node: mparser.BaseNode,
args: T.Tuple[T.List['mesonlib.FileOrString']],
for childdir in dirs:
h = build.Headers(dirs[childdir], os.path.join(install_subdir, childdir), kwargs['install_dir'],
- install_mode, self.subproject)
+ install_mode, self.subproject,
+ follow_symlinks=kwargs['follow_symlinks'])
ret_headers.append(h)
self.build.headers.append(h)
absname = os.path.join(self.environment.get_source_dir(), buildfilename)
if not os.path.isfile(absname):
self.subdir = prev_subdir
- raise InterpreterException(f"Non-existent build file '{buildfilename!s}'")
+ raise InterpreterException(f"Nonexistent build file '{buildfilename!s}'")
with open(absname, encoding='utf-8') as f:
code = f.read()
assert isinstance(code, str)
pass
self.subdir = prev_subdir
- def _get_kwarg_install_mode(self, kwargs: T.Dict[str, T.Any]) -> T.Optional[FileMode]:
- if kwargs.get('install_mode', None) is None:
- return None
- if isinstance(kwargs['install_mode'], FileMode):
- return kwargs['install_mode']
- install_mode: T.List[str] = []
- mode = mesonlib.typeslistify(kwargs.get('install_mode', []), (str, int))
- for m in mode:
- # We skip any arguments that are set to `false`
- if m is False:
- m = None
- install_mode.append(m)
- if len(install_mode) > 3:
- raise InvalidArguments('Keyword argument install_mode takes at '
- 'most 3 arguments.')
- if len(install_mode) > 0 and install_mode[0] is not None and \
- not isinstance(install_mode[0], str):
- raise InvalidArguments('Keyword argument install_mode requires the '
- 'permissions arg to be a string or false')
- return FileMode(*install_mode)
-
# This is either ignored on basically any OS nowadays, or silently gets
# ignored (Solaris) or triggers an "illegal operation" error (FreeBSD).
# It was likely added "because it exists", but should never be used. In
INSTALL_TAG_KW.evolve(since='0.60.0'),
INSTALL_DIR_KW,
PRESERVE_PATH_KW.evolve(since='0.64.0'),
+ INSTALL_FOLLOW_SYMLINKS,
)
def func_install_data(self, node: mparser.BaseNode,
args: T.Tuple[T.List['mesonlib.FileOrString']],
'"rename" and "sources" argument lists must be the same length if "rename" is given. '
f'Rename has {len(rename)} elements and sources has {len(sources)}.')
+ install_dir = kwargs['install_dir']
+ if not install_dir:
+ subdir = self.active_projectname
+ install_dir = P_OBJ.OptionString(os.path.join(self.environment.get_datadir(), subdir), os.path.join('{datadir}', subdir))
+ if self.is_subproject():
+ FeatureNew.single_use('install_data() without install_dir inside of a subproject', '1.3.0', self.subproject,
+ 'This was broken and would install to the project name of the parent project instead',
+ node)
+ if kwargs['preserve_path']:
+ FeatureNew.single_use('install_data() with preserve_path and without install_dir', '1.3.0', self.subproject,
+ 'This was broken and would not add the project name to the install path',
+ node)
+
install_mode = self._warn_kwarg_install_mode_sticky(kwargs['install_mode'])
- return self.install_data_impl(sources, kwargs['install_dir'], install_mode,
- rename, kwargs['install_tag'],
- preserve_path=kwargs['preserve_path'])
+ return self.install_data_impl(sources, install_dir, install_mode, rename, kwargs['install_tag'],
+ preserve_path=kwargs['preserve_path'],
+ follow_symlinks=kwargs['follow_symlinks'])
- def install_data_impl(self, sources: T.List[mesonlib.File], install_dir: T.Optional[str],
+ def install_data_impl(self, sources: T.List[mesonlib.File], install_dir: str,
install_mode: FileMode, rename: T.Optional[str],
tag: T.Optional[str],
- install_dir_name: T.Optional[str] = None,
install_data_type: T.Optional[str] = None,
- preserve_path: bool = False) -> build.Data:
-
- """Just the implementation with no validation."""
- idir = install_dir or ''
- idir_name = install_dir_name or idir or '{datadir}'
- if isinstance(idir_name, P_OBJ.OptionString):
- idir_name = idir_name.optname
+ preserve_path: bool = False,
+ follow_symlinks: T.Optional[bool] = None) -> build.Data:
+ install_dir_name = install_dir.optname if isinstance(install_dir, P_OBJ.OptionString) else install_dir
dirs = collections.defaultdict(list)
- ret_data = []
if preserve_path:
for file in sources:
dirname = os.path.dirname(file.fname)
else:
dirs[''].extend(sources)
+ ret_data = []
for childdir, files in dirs.items():
- d = build.Data(files, os.path.join(idir, childdir), os.path.join(idir_name, childdir),
- install_mode, self.subproject, rename, tag, install_data_type)
+ d = build.Data(files, os.path.join(install_dir, childdir), os.path.join(install_dir_name, childdir),
+ install_mode, self.subproject, rename, tag, install_data_type,
+ follow_symlinks)
ret_data.append(d)
self.build.data.extend(ret_data)
validator=lambda x: 'cannot be absolute' if any(os.path.isabs(d) for d in x) else None),
INSTALL_MODE_KW.evolve(since='0.38.0'),
INSTALL_TAG_KW.evolve(since='0.60.0'),
+ INSTALL_FOLLOW_SYMLINKS,
)
def func_install_subdir(self, node: mparser.BaseNode, args: T.Tuple[str],
kwargs: 'kwtypes.FuncInstallSubdir') -> build.InstallDir:
exclude = (set(kwargs['exclude_files']), set(kwargs['exclude_directories']))
srcdir = os.path.join(self.environment.source_dir, self.subdir, args[0])
- if not os.path.isdir(srcdir) or not any(os.scandir(srcdir)):
+ if not os.path.isdir(srcdir) or not any(os.listdir(srcdir)):
FeatureNew.single_use('install_subdir with empty directory', '0.47.0', self.subproject, location=node)
FeatureDeprecated.single_use('install_subdir with empty directory', '0.60.0', self.subproject,
'It worked by accident and is buggy. Use install_emptydir instead.', node)
exclude,
kwargs['strip_directory'],
self.subproject,
- install_tag=kwargs['install_tag'])
+ install_tag=kwargs['install_tag'],
+ follow_symlinks=kwargs['follow_symlinks'])
self.build.install_dirs.append(idir)
return idir
KwargInfo('install_dir', (str, bool), default='',
validator=lambda x: 'must be `false` if boolean' if x is True else None),
OUTPUT_KW,
- KwargInfo('output_format', str, default='c', since='0.47.0',
- validator=in_set_validator({'c', 'nasm'})),
+ 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'),
)
def func_configure_file(self, node: mparser.BaseNode, args: T.List[TYPE_var],
kwargs: kwtypes.ConfigureFile):
file_encoding = kwargs['encoding']
missing_variables, confdata_useless = \
mesonlib.do_conf_file(inputs_abs[0], ofile_abs, conf,
- fmt, file_encoding)
+ fmt, file_encoding, self.subproject)
if missing_variables:
var_list = ", ".join(repr(m) for m in sorted(missing_variables))
mlog.warning(
'copy a file to the build dir, use the \'copy:\' keyword '
'argument added in 0.47.0', location=node)
else:
- mesonlib.dump_conf_header(ofile_abs, conf, output_format)
+ macro_name = kwargs['macro_name']
+ mesonlib.dump_conf_header(ofile_abs, conf, output_format, macro_name)
conf.used = True
elif kwargs['command'] is not None:
if len(inputs) > 1:
_cmd = mesonlib.substitute_values(kwargs['command'], values)
mlog.log('Configuring', mlog.bold(output), 'with command')
cmd, *args = _cmd
- res = self.run_command_impl(node, (cmd, args),
- {'capture': True, 'check': True, 'env': build.EnvironmentVariables()},
+ res = self.run_command_impl((cmd, args),
+ {'capture': True, 'check': True, 'env': EnvironmentVariables()},
True)
if kwargs['capture']:
dst_tmp = ofile_abs + '~'
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'):
+ def extract_incdirs(self, kwargs, key: str = 'include_directories') -> T.List[build.IncludeDirs]:
prospectives = extract_as_list(kwargs, key)
if key == 'include_directories':
for i in prospectives:
f'Use include_directories({i!r}) instead', location=self.current_node)
break
- result = []
+ result: T.List[build.IncludeDirs] = []
for p in prospectives:
if isinstance(p, build.IncludeDirs):
result.append(p)
absbase_build = os.path.join(build_root, self.subdir)
for a in incdir_strings:
- if a.startswith(src_root):
+ if path_is_in_root(Path(a), Path(src_root)):
raise InvalidArguments(textwrap.dedent('''\
Tried to form an absolute path to a dir in the source tree.
You should not do that but use relative paths instead, for
def _add_global_arguments(self, node: mparser.FunctionNode, argsdict: T.Dict[str, T.List[str]],
args: T.List[str], kwargs: 'kwtypes.FuncAddProjectArgs') -> None:
if self.is_subproject():
- msg = f'Function \'{node.func_name}\' cannot be used in subprojects because ' \
+ msg = f'Function \'{node.func_name.value}\' cannot be used in subprojects because ' \
'there is no way to make that reliable.\nPlease only call ' \
'this if is_subproject() returns false. Alternatively, ' \
'define a variable that\ncontains your language-specific ' \
def _add_arguments(self, node: mparser.FunctionNode, argsdict: T.Dict[str, T.List[str]],
args_frozen: bool, args: T.List[str], kwargs: 'kwtypes.FuncAddProjectArgs') -> None:
if args_frozen:
- msg = f'Tried to use \'{node.func_name}\' after a build target has been declared.\n' \
+ msg = f'Tried to use \'{node.func_name.value}\' after a build target has been declared.\n' \
'This is not permitted. Please declare all arguments before your targets.'
raise InvalidCode(msg)
@typed_pos_args('environment', optargs=[(str, list, dict)])
@typed_kwargs('environment', ENV_METHOD_KW, ENV_SEPARATOR_KW.evolve(since='0.62.0'))
def func_environment(self, node: mparser.FunctionNode, args: T.Tuple[T.Union[None, str, T.List['TYPE_var'], T.Dict[str, 'TYPE_var']]],
- kwargs: 'TYPE_kwargs') -> build.EnvironmentVariables:
+ kwargs: 'TYPE_kwargs') -> EnvironmentVariables:
init = args[0]
if init is not None:
FeatureNew.single_use('environment positional arguments', '0.52.0', self.subproject, location=node)
if isinstance(init, dict) and any(i for i in init.values() if isinstance(i, list)):
FeatureNew.single_use('List of string in dictionary value', '0.62.0', self.subproject, location=node)
return env_convertor_with_method(init, kwargs['method'], kwargs['separator'])
- return build.EnvironmentVariables()
+ return EnvironmentVariables()
@typed_pos_args('join_paths', varargs=str, min_varargs=1)
@noKwargs
mlog.log('Build targets in project:', mlog.bold(str(len(self.build.targets))))
FeatureNew.report(self.subproject)
FeatureDeprecated.report(self.subproject)
+ FeatureBroken.report(self.subproject)
if not self.is_subproject():
self.print_extra_warnings()
self._print_summary()
return
if (self.coredata.options[OptionKey('b_lundef')].value and
self.coredata.options[OptionKey('b_sanitize')].value != 'none'):
- mlog.warning('''Trying to use {} sanitizer on Clang with b_lundef.
-This will probably not work.
-Try setting b_lundef to false instead.'''.format(self.coredata.options[OptionKey('b_sanitize')].value),
- location=self.current_node)
+ value = self.coredata.options[OptionKey('b_sanitize')].value
+ mlog.warning(textwrap.dedent(f'''\
+ Trying to use {value} sanitizer on Clang with b_lundef.
+ This will probably not work.
+ Try setting b_lundef to false instead.'''),
+ location=self.current_node) # noqa: E128
# Check that the indicated file is within the same subproject
# as we currently are. This is to stop people doing
inputtype = 'directory'
else:
inputtype = 'file'
- if InterpreterRuleRelaxation.ALLOW_BUILD_DIR_FILE_REFFERENCES in self.relaxations and builddir in norm.parents:
+ if InterpreterRuleRelaxation.ALLOW_BUILD_DIR_FILE_REFERENCES in self.relaxations and builddir in norm.parents:
return
if srcdir not in norm.parents:
# Grabbing files outside the source tree is ok.
def source_strings_to_files(self, sources: T.List['mesonlib.FileOrString'], strict: bool = False) -> T.List['mesonlib.FileOrString']: ... # noqa: F811
@T.overload
- def source_strings_to_files(self, sources: T.List[mesonlib.FileOrString, build.GeneratedTypes]) -> T.List[T.Union[mesonlib.File, build.GeneratedTypes]]: ... # noqa: F811
+ def source_strings_to_files(self, sources: T.List[T.Union[mesonlib.FileOrString, build.GeneratedTypes]]) -> T.List[T.Union[mesonlib.File, build.GeneratedTypes]]: ... # noqa: F811
@T.overload
def source_strings_to_files(self, sources: T.List['SourceInputs'], strict: bool = True) -> T.List['SourceOutputs']: ... # noqa: F811
+ @T.overload
+ def source_strings_to_files(self, sources: T.List[SourcesVarargsType], strict: bool = True) -> T.List['SourceOutputs']: ... # noqa: F811
+
def source_strings_to_files(self, sources: T.List['SourceInputs'], strict: bool = True) -> T.List['SourceOutputs']: # noqa: F811
"""Lower inputs to a list of Targets and Files, replacing any strings.
"internal use. Please rename.")
def add_target(self, name: str, tobj: build.Target) -> None:
+ if self.backend.name == 'none':
+ raise InterpreterException('Install-only backend cannot generate target rules, try using `--backend=ninja`.')
if name == '':
raise InterpreterException('Target name must not be empty.')
if name.strip() == '':
# 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()
+ namedir = (name, subdir)
+
if idname in self.build.targets:
raise InvalidCode(f'Tried to create target "{name}", but a target of that name already exists.')
+ if isinstance(tobj, build.Executable) and namedir in self.build.targetnames:
+ FeatureNew.single_use(f'multiple executables with the same name, "{tobj.name}", but different suffixes in the same directory',
+ '1.3.0', self.subproject, location=self.current_node)
+
if isinstance(tobj, build.BuildTarget):
- missing_languages = tobj.process_compilers()
- self.add_languages(missing_languages, True, tobj.for_machine)
- tobj.process_compilers_late(missing_languages)
+ self.add_languages(tobj.missing_languages, True, tobj.for_machine)
+ tobj.process_compilers_late()
self.add_stdlib_info(tobj)
self.build.targets[idname] = tobj
+ # Only need to add executables to this set
+ if isinstance(tobj, build.Executable):
+ self.build.targetnames.update([namedir])
if idname not in self.coredata.target_guids:
self.coredata.target_guids[idname] = str(uuid.uuid4()).upper()
@FeatureNew('both_libraries', '0.46.0')
- def build_both_libraries(self, node, args, kwargs):
+ 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)
- # Check if user forces non-PIC static library.
- pic = True
- key = OptionKey('b_staticpic')
- if 'pic' in kwargs:
- pic = kwargs['pic']
- elif key in self.environment.coredata.options:
- pic = self.environment.coredata.options[key].value
-
if self.backend.name == 'xcode':
# Xcode is a bit special in that you can't (at least for the moment)
# form a library only from object file inputs. The simple but inefficient
# Feel free to submit patches to get this fixed if it is an
# issue for you.
reuse_object_files = False
+ elif shared_lib.uses_rust():
+ # FIXME: rustc supports generating both libraries in a single invocation,
+ # but for now compile twice.
+ reuse_object_files = False
+ elif any(k.endswith(('static_args', 'shared_args')) and v for k, v in kwargs.items()):
+ # Ensure not just the keyword arguments exist, but that they are non-empty.
+ reuse_object_files = False
else:
- reuse_object_files = pic
+ reuse_object_files = static_lib.pic
if reuse_object_files:
# Replace sources with objects from the shared library to avoid
return build.BothLibraries(shared_lib, static_lib)
- def build_library(self, node, args, kwargs):
+ def build_library(self, node: mparser.BaseNode, args: T.Tuple[str, SourcesVarargsType], kwargs: kwtypes.Library):
default_library = self.coredata.get_option(OptionKey('default_library', subproject=self.subproject))
+ assert isinstance(default_library, str), 'for mypy'
if default_library == 'shared':
- return self.build_target(node, args, kwargs, build.SharedLibrary)
+ return self.build_target(node, args, T.cast('kwtypes.StaticLibrary', kwargs), build.SharedLibrary)
elif default_library == 'static':
- return self.build_target(node, args, kwargs, build.StaticLibrary)
+ return self.build_target(node, args, T.cast('kwtypes.SharedLibrary', kwargs), build.StaticLibrary)
elif default_library == 'both':
return self.build_both_libraries(node, args, kwargs)
else:
raise InterpreterException(f'Unknown default_library value: {default_library}.')
- def build_target(self, node: mparser.BaseNode, args, kwargs, targetclass):
- @FeatureNewKwargs('build target', '0.42.0', ['rust_crate_type', 'build_rpath', 'implicit_include_directories'])
- @FeatureNewKwargs('build target', '0.41.0', ['rust_args'])
- @FeatureNewKwargs('build target', '0.38.0', ['build_by_default'])
- @FeatureNewKwargs('build target', '0.48.0', ['gnu_symbol_visibility'])
- def build_target_decorator_caller(self, node, args, kwargs):
- return True
-
- build_target_decorator_caller(self, node, args, kwargs)
-
- if not args:
- raise InterpreterException('Target does not have a name.')
- name, *sources = args
- for_machine = self.machine_from_native_kwarg(kwargs)
- if 'sources' in kwargs:
- sources += listify(kwargs['sources'])
+ def __convert_file_args(self, raw: T.List[mesonlib.FileOrString]) -> T.Tuple[T.List[mesonlib.File], T.List[str]]:
+ """Convert raw target arguments from File | str to File.
+
+ This removes files from the command line and replaces them with string
+ values, but adds the files to depends list
+
+ :param raw: the raw arguments
+ :return: A tuple of file dependencies and raw arguments
+ """
+ depend_files: T.List[mesonlib.File] = []
+ args: T.List[str] = []
+ build_to_source = mesonlib.relpath(self.environment.get_source_dir(),
+ self.environment.get_build_dir())
+
+ for a in raw:
+ if isinstance(a, mesonlib.File):
+ depend_files.append(a)
+ args.append(a.rel_to_builddir(build_to_source))
+ else:
+ args.append(a)
+
+ return depend_files, args
+
+ def __process_language_args(self, kwargs: T.Dict[str, T.List[mesonlib.FileOrString]]) -> None:
+ """Convert split language args into a combined dictionary.
+
+ The Meson DSL takes arguments in the form `<lang>_args : args`, but in the
+ build layer we store these in a single dictionary as `{<lang>: args}`.
+ This function extracts the arguments from the DSL format and prepares
+ them for the IR.
+ """
+ d = kwargs.setdefault('depend_files', [])
+ new_args: T.DefaultDict[str, T.List[str]] = collections.defaultdict(list)
+
+ for l in compilers.all_languages:
+ deps, args = self.__convert_file_args(kwargs[f'{l}_args'])
+ new_args[l] = args
+ d.extend(deps)
+ kwargs['language_args'] = new_args
+
+ @T.overload
+ def build_target(self, node: mparser.BaseNode, args: T.Tuple[str, SourcesVarargsType],
+ kwargs: kwtypes.Executable, targetclass: T.Type[build.Executable]) -> build.Executable: ...
+
+ @T.overload
+ def build_target(self, node: mparser.BaseNode, args: T.Tuple[str, SourcesVarargsType],
+ kwargs: kwtypes.StaticLibrary, targetclass: T.Type[build.StaticLibrary]) -> build.StaticLibrary: ...
+
+ @T.overload
+ def build_target(self, node: mparser.BaseNode, args: T.Tuple[str, SourcesVarargsType],
+ kwargs: kwtypes.SharedLibrary, targetclass: T.Type[build.SharedLibrary]) -> build.SharedLibrary: ...
+
+ @T.overload
+ def build_target(self, node: mparser.BaseNode, args: T.Tuple[str, SourcesVarargsType],
+ kwargs: kwtypes.SharedModule, targetclass: T.Type[build.SharedModule]) -> build.SharedModule: ...
+
+ @T.overload
+ def build_target(self, node: mparser.BaseNode, args: T.Tuple[str, SourcesVarargsType],
+ kwargs: kwtypes.Jar, targetclass: T.Type[build.Jar]) -> build.Jar: ...
+
+ def build_target(self, node: mparser.BaseNode, args: T.Tuple[str, SourcesVarargsType],
+ 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]:
+ name, sources = args
+ for_machine = kwargs['native']
+ if kwargs.get('rust_crate_type') == 'proc-macro':
+ # Silently force to native because that's the only sensible value
+ # and rust_crate_type is deprecated any way.
+ for_machine = MachineChoice.BUILD
+ # Avoid mutating, since there could be other references to sources
+ sources = sources + kwargs['sources']
+ if any(isinstance(s, build.BuildTarget) for s in sources):
+ FeatureBroken.single_use('passing references to built targets as a source file', '1.1.0', self.subproject,
+ 'Consider using `link_with` or `link_whole` if you meant to link, or dropping them as otherwise they are ignored.',
+ node)
+ if any(isinstance(s, build.ExtractedObjects) for s in sources):
+ FeatureBroken.single_use('passing object files as sources', '1.1.0', self.subproject,
+ 'Pass these to the `objects` keyword instead, they are ignored when passed as sources.',
+ node)
+ # Go ahead and drop these here, since they're only allowed through for
+ # 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 = extract_as_list(kwargs, 'objects')
+ objs = kwargs['objects']
kwargs['dependencies'] = extract_as_list(kwargs, 'dependencies')
- kwargs['install_mode'] = self._get_kwarg_install_mode(kwargs)
- if 'extra_files' in kwargs:
- ef = extract_as_list(kwargs, 'extra_files')
- kwargs['extra_files'] = self.source_strings_to_files(ef)
+ kwargs['extra_files'] = 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.kwarg_strings_to_includedirs(kwargs)
+ self.__process_language_args(kwargs)
+ if targetclass is build.StaticLibrary:
+ 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)
+ elif targetclass is build.SharedLibrary:
+ 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)
+ if targetclass is not build.Jar:
+ self.kwarg_strings_to_includedirs(kwargs)
# Filter out kwargs from other target types. For example 'soversion'
# passed to library() when default_library == 'static'.
- kwargs = {k: v for k, v in kwargs.items() if k in targetclass.known_kwargs}
+ kwargs = {k: v for k, v in kwargs.items() if k in targetclass.known_kwargs | {'language_args'}}
srcs: T.List['SourceInputs'] = []
struct: T.Optional[build.StructuredSources] = build.StructuredSources()
outputs.update(o)
kwargs['include_directories'] = self.extract_incdirs(kwargs)
+
+ if targetclass is build.Executable:
+ kwargs = T.cast('kwtypes.Executable', kwargs)
+ if kwargs['gui_app'] is not None:
+ if kwargs['win_subsystem'] is not None:
+ raise InvalidArguments.from_node(
+ 'Executable got both "gui_app", and "win_subsystem" arguments, which are mutually exclusive',
+ node=node)
+ if kwargs['gui_app']:
+ kwargs['win_subsystem'] = 'windows'
+ if kwargs['win_subsystem'] is None:
+ kwargs['win_subsystem'] = 'console'
+
+ if kwargs['implib']:
+ if kwargs['export_dynamic'] is False:
+ FeatureDeprecated.single_use('implib overrides explict export_dynamic off', '1.3.0', self.subprojct,
+ 'Do not set ths if want export_dynamic disabled if implib is enabled',
+ location=node)
+ kwargs['export_dynamic'] = True
+ 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
+
target = targetclass(name, self.subdir, self.subproject, for_machine, srcs, struct, objs,
self.environment, self.compilers[for_machine], kwargs)
- target.project_version = self.project_version
self.add_target(name, target)
self.project_args_frozen = True
return target
- def kwarg_strings_to_includedirs(self, kwargs):
- if 'd_import_dirs' in kwargs:
- items = mesonlib.extract_as_list(kwargs, 'd_import_dirs')
- cleaned_items = []
+ def kwarg_strings_to_includedirs(self, kwargs: kwtypes._BuildTarget) -> None:
+ if kwargs['d_import_dirs']:
+ items = kwargs['d_import_dirs']
+ cleaned_items: T.List[build.IncludeDirs] = []
for i in items:
if isinstance(i, str):
# BW compatibility. This was permitted so we must support it
typed_pos_args, typed_kwargs, typed_operator,
noArgsFlattening, noPosargs, noKwargs, unholder_return,
flatten, resolve_second_level_holders, InterpreterException, InvalidArguments, InvalidCode)
-from ..interpreter.type_checking import NoneType, ENV_SEPARATOR_KW
+from ..interpreter.type_checking import NoneType, ENV_KW, ENV_SEPARATOR_KW, PKGCONFIG_DEFINE_KW
from ..dependencies import Dependency, ExternalLibrary, InternalDependency
from ..programs import ExternalProgram
from ..mesonlib import HoldableObject, OptionKey, listify, Popen_safe
separator: str
+_ERROR_MSG_KW: KwargInfo[T.Optional[str]] = KwargInfo('error_message', (str, NoneType))
+
def extract_required_kwarg(kwargs: 'kwargs.ExtractRequired',
subproject: 'SubProject',
'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
disabled.value = 'disabled'
return disabled
+ def as_enabled(self) -> coredata.UserFeatureOption:
+ enabled = copy.deepcopy(self.held_object)
+ enabled.value = 'enabled'
+ return enabled
+
@noPosargs
@noKwargs
def enabled_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool:
def auto_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool:
return self.value == 'auto'
+ def _disable_if(self, condition: bool, message: T.Optional[str]) -> coredata.UserFeatureOption:
+ if not condition:
+ return copy.deepcopy(self.held_object)
+
+ if self.value == 'enabled':
+ err_msg = f'Feature {self.held_object.name} cannot be enabled'
+ if message:
+ err_msg += f': {message}'
+ raise InterpreterException(err_msg)
+ return self.as_disabled()
+
@FeatureNew('feature_option.require()', '0.59.0')
@typed_pos_args('feature_option.require', bool)
@typed_kwargs(
'feature_option.require',
- KwargInfo('error_message', (str, NoneType))
+ _ERROR_MSG_KW,
)
def require_method(self, args: T.Tuple[bool], kwargs: 'kwargs.FeatureOptionRequire') -> coredata.UserFeatureOption:
- if args[0]:
+ return self._disable_if(not args[0], kwargs['error_message'])
+
+ @FeatureNew('feature_option.disable_if()', '1.1.0')
+ @typed_pos_args('feature_option.disable_if', bool)
+ @typed_kwargs(
+ 'feature_option.disable_if',
+ _ERROR_MSG_KW,
+ )
+ def disable_if_method(self, args: T.Tuple[bool], kwargs: 'kwargs.FeatureOptionRequire') -> coredata.UserFeatureOption:
+ return self._disable_if(args[0], kwargs['error_message'])
+
+ @FeatureNew('feature_option.enable_if()', '1.1.0')
+ @typed_pos_args('feature_option.enable_if', bool)
+ @typed_kwargs(
+ 'feature_option.enable_if',
+ _ERROR_MSG_KW,
+ )
+ def enable_if_method(self, args: T.Tuple[bool], kwargs: 'kwargs.FeatureOptionRequire') -> coredata.UserFeatureOption:
+ if not args[0]:
return copy.deepcopy(self.held_object)
- if self.value == 'enabled':
- err_msg = f'Feature {self.held_object.name} cannot be enabled'
+ if self.value == 'disabled':
+ err_msg = f'Feature {self.held_object.name} cannot be disabled'
if kwargs['error_message']:
err_msg += f': {kwargs["error_message"]}'
raise InterpreterException(err_msg)
- return self.as_disabled()
+ return self.as_enabled()
@FeatureNew('feature_option.disable_auto_if()', '0.59.0')
@noKwargs
def disable_auto_if_method(self, args: T.Tuple[bool], kwargs: TYPE_kwargs) -> coredata.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)
+ def enable_auto_if_method(self, args: T.Tuple[bool], kwargs: TYPE_kwargs) -> coredata.UserFeatureOption:
+ return self.as_enabled() if self.value == 'auto' and args[0] else copy.deepcopy(self.held_object)
+
class RunProcess(MesonInterpreterObject):
def __init__(self,
cmd: ExternalProgram,
args: T.List[str],
- env: build.EnvironmentVariables,
+ env: mesonlib.EnvironmentVariables,
source_dir: str,
build_dir: str,
subdir: str,
def run_command(self,
cmd: ExternalProgram,
args: T.List[str],
- env: build.EnvironmentVariables,
+ env: mesonlib.EnvironmentVariables,
source_dir: str,
build_dir: str,
subdir: str,
child_env.update(menv)
child_env = env.get_env(child_env)
stdout = subprocess.PIPE if self.capture else subprocess.DEVNULL
- mlog.debug('Running command:', ' '.join(command_array))
+ mlog.debug('Running command:', mesonlib.join_args(command_array))
try:
p, o, e = Popen_safe(command_array, stdout=stdout, env=child_env, cwd=cwd)
if self.capture:
mlog.debug('')
if check and p.returncode != 0:
- raise InterpreterException('Command "{}" failed with status {}.'.format(' '.join(command_array), p.returncode))
+ raise InterpreterException('Command `{}` failed with status {}.'.format(mesonlib.join_args(command_array), p.returncode))
return p.returncode, o, e
except FileNotFoundError:
- raise InterpreterException('Could not execute command "%s".' % ' '.join(command_array))
+ raise InterpreterException('Could not execute command `%s`.' % mesonlib.join_args(command_array))
@noPosargs
@noKwargs
def stderr_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
return self.stderr
-class EnvironmentVariablesHolder(ObjectHolder[build.EnvironmentVariables], MutableInterpreterObject):
+class EnvironmentVariablesHolder(ObjectHolder[mesonlib.EnvironmentVariables], MutableInterpreterObject):
- def __init__(self, obj: build.EnvironmentVariables, interpreter: 'Interpreter'):
+ def __init__(self, obj: mesonlib.EnvironmentVariables, interpreter: 'Interpreter'):
super().__init__(obj, interpreter)
self.methods.update({'set': self.set_method,
'append': self.append_method,
@typed_pos_args('dependency.get_pkgconfig_variable', str)
@typed_kwargs(
'dependency.get_pkgconfig_variable',
- KwargInfo('default', (str, NoneType)),
- KwargInfo(
- 'define_variable',
- ContainerTypeInfo(list, str, pairs=True),
- default=[],
- listify=True,
- validator=lambda x: 'must be of length 2 or empty' if len(x) not in {0, 2} else None,
- ),
+ KwargInfo('default', str, default=''),
+ PKGCONFIG_DEFINE_KW.evolve(name='define_variable')
)
def pkgconfig_method(self, args: T.Tuple[str], kwargs: 'kwargs.DependencyPkgConfigVar') -> str:
- return self.held_object.get_pkgconfig_variable(args[0], **kwargs)
+ from ..dependencies.pkgconfig import PkgConfigDependency
+ if not isinstance(self.held_object, PkgConfigDependency):
+ raise InvalidArguments(f'{self.held_object.get_name()!r} is not a pkgconfig dependency')
+ if kwargs['define_variable'] and len(kwargs['define_variable']) > 1:
+ FeatureNew.single_use('dependency.get_pkgconfig_variable keyword argument "define_variable" with more than one pair',
+ '1.3.0', self.subproject, location=self.current_node)
+ return self.held_object.get_variable(
+ pkgconfig=args[0],
+ default_value=kwargs['default'],
+ pkgconfig_define=kwargs['define_variable'],
+ )
@FeatureNew('dependency.get_configtool_variable', '0.44.0')
@FeatureDeprecated('dependency.get_configtool_variable', '0.56.0',
@noKwargs
@typed_pos_args('dependency.get_config_tool_variable', str)
def configtool_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> str:
- return self.held_object.get_configtool_variable(args[0])
+ from ..dependencies.configtool import ConfigToolDependency
+ if not isinstance(self.held_object, ConfigToolDependency):
+ raise InvalidArguments(f'{self.held_object.get_name()!r} is not a config-tool dependency')
+ return self.held_object.get_variable(
+ configtool=args[0],
+ default_value='',
+ )
@FeatureNew('dependency.partial_dependency', '0.46.0')
@noPosargs
KwargInfo('configtool', (str, NoneType)),
KwargInfo('internal', (str, NoneType), since='0.54.0'),
KwargInfo('default_value', (str, NoneType)),
- KwargInfo('pkgconfig_define', ContainerTypeInfo(list, str, pairs=True), default=[], listify=True),
+ PKGCONFIG_DEFINE_KW,
)
def variable_method(self, args: T.Tuple[T.Optional[str]], kwargs: 'kwargs.DependencyGetVariable') -> str:
default_varname = args[0]
if default_varname is not None:
FeatureNew('Positional argument to dependency.get_variable()', '0.58.0').use(self.subproject, self.current_node)
+ if kwargs['pkgconfig_define'] and len(kwargs['pkgconfig_define']) > 1:
+ FeatureNew.single_use('dependency.get_variable keyword argument "pkgconfig_define" with more than one pair',
+ '1.3.0', self.subproject, 'In previous versions, this silently returned a malformed value.',
+ self.current_node)
return self.held_object.get_variable(
cmake=kwargs['cmake'] or default_varname,
pkgconfig=kwargs['pkgconfig'] or default_varname,
new_dep = self.held_object.generate_link_whole_dependency()
return new_dep
-class ExternalProgramHolder(ObjectHolder[ExternalProgram]):
- def __init__(self, ep: ExternalProgram, interpreter: 'Interpreter') -> None:
+_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,
def found(self) -> bool:
return self.held_object.found()
+class ExternalProgramHolder(_ExternalProgramHolder[ExternalProgram]):
+ pass
+
class ExternalLibraryHolder(ObjectHolder[ExternalLibrary]):
def __init__(self, el: ExternalLibrary, interpreter: 'Interpreter'):
super().__init__(el, interpreter)
'cpu': self.cpu_method,
'cpu_family': self.cpu_family_method,
'endian': self.endian_method,
+ 'kernel': self.kernel_method,
+ 'subsystem': self.subsystem_method,
})
@noPosargs
def endian_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
return self.held_object.endian
+ @noPosargs
+ @noKwargs
+ 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
+ raise InterpreterException('Kernel not defined or could not be autodetected.')
+
+ @noPosargs
+ @noKwargs
+ 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
+ raise InterpreterException('Subsystem not defined or could not be autodetected.')
+
+
class IncludeDirsHolder(ObjectHolder[build.IncludeDirs]):
pass
depends: T.List[T.Union[build.CustomTarget, build.BuildTarget]],
is_parallel: bool,
cmd_args: T.List[T.Union[str, mesonlib.File, build.Target]],
- env: build.EnvironmentVariables,
+ env: mesonlib.EnvironmentVariables,
should_fail: bool, timeout: int, workdir: T.Optional[str], protocol: str,
priority: int, verbose: bool):
super().__init__()
subdir: str,
warnings: int = 0,
disabled_feature: T.Optional[str] = None,
- exception: T.Optional[Exception] = None) -> None:
+ exception: T.Optional[Exception] = None,
+ callstack: T.Optional[T.List[str]] = None) -> None:
super().__init__()
self.held_object = subinterpreter
self.warnings = warnings
self.exception = exception
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
+ @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')
def get_id_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
return self._target_object.get_id()
assert self.interpreter.backend is not None
return self.interpreter.backend.get_target_filename_abs(self.held_object)
-class CustomTargetHolder(ObjectHolder[build.CustomTarget]):
- def __init__(self, target: 'build.CustomTarget', interp: 'Interpreter'):
+_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,
except IndexError:
raise InvalidArguments(f'Index {other} out of bounds of custom target {self.held_object.name} output of size {len(self.held_object)}.')
+class CustomTargetHolder(_CustomTargetHolder[build.CustomTarget]):
+ pass
+
class RunTargetHolder(ObjectHolder[build.RunTarget]):
pass
'generator.process',
KwargInfo('preserve_path_from', (str, NoneType), since='0.45.0'),
KwargInfo('extra_args', ContainerTypeInfo(list, str), listify=True, default=[]),
+ ENV_KW.evolve(since='1.3.0')
)
def process_method(self,
args: T.Tuple[T.List[T.Union[str, mesonlib.File, 'build.GeneratedTypes']]],
'0.57.0', self.interpreter.subproject)
gl = self.held_object.process_files(args[0], self.interpreter,
- preserve_path_from, extra_args=kwargs['extra_args'])
+ preserve_path_from, extra_args=kwargs['extra_args'], env=kwargs['env'])
return gl
# SPDX-License-Identifier: Apache-2.0
# Copyright © 2021 The Meson Developers
# Copyright © 2021 Intel Corporation
+from __future__ import annotations
"""Keyword Argument type annotations."""
import typing as T
-from typing_extensions import TypedDict, Literal, Protocol
+from typing_extensions import TypedDict, Literal, Protocol, NotRequired
from .. import build
from .. import coredata
from ..compilers import Compiler
-from ..mesonlib import MachineChoice, File, FileMode, FileOrString
+from ..dependencies.base import Dependency
+from ..mesonlib import EnvironmentVariables, MachineChoice, File, FileMode, FileOrString, OptionKey
from ..modules.cmake import CMakeSubprojectOptions
from ..programs import ExternalProgram
-
+from .type_checking import PkgConfigDefineType, SourcesVarargsType
class FuncAddProjectArgs(TypedDict):
workdir: T.Optional[str]
depends: T.List[T.Union[build.CustomTarget, build.BuildTarget]]
priority: int
- env: build.EnvironmentVariables
+ env: EnvironmentVariables
suite: T.List[str]
"""Keyword Arguments for `test`
- `test` only adds the `is_prallel` argument over benchmark, so inherintance
+ `test` only adds the `is_parallel` argument over benchmark, so inheritance
is helpful here.
"""
preserve_path_from: T.Optional[str]
extra_args: T.List[str]
+ env: EnvironmentVariables
class DependencyMethodPartialDependency(TypedDict):
exclude_files: T.List[str]
exclude_directories: T.List[str]
install_mode: FileMode
+ follow_symlinks: T.Optional[bool]
class FuncInstallData(TypedDict):
sources: T.List[FileOrString]
rename: T.List[str]
install_mode: FileMode
+ follow_symlinks: T.Optional[bool]
class FuncInstallHeaders(TypedDict):
install_dir: T.Optional[str]
install_mode: FileMode
subdir: T.Optional[str]
+ follow_symlinks: T.Optional[bool]
class FuncInstallMan(TypedDict):
command: T.List[T.Union[str, build.BuildTarget, build.CustomTarget, ExternalProgram, File]]
depends: T.List[T.Union[build.BuildTarget, build.CustomTarget]]
- env: build.EnvironmentVariables
+ env: EnvironmentVariables
class CustomTarget(TypedDict):
depend_files: T.List[FileOrString]
depends: T.List[T.Union[build.BuildTarget, build.CustomTarget]]
depfile: T.Optional[str]
- env: build.EnvironmentVariables
+ env: EnvironmentVariables
feed: bool
input: T.List[T.Union[str, build.BuildTarget, build.CustomTarget, build.CustomTargetIndex,
build.ExtractedObjects, build.GeneratedList, ExternalProgram, File]]
timeout_multiplier: int
is_default: bool
exclude_suites: T.List[str]
- env: build.EnvironmentVariables
+ env: EnvironmentVariables
class Project(TypedDict):
version: T.Optional[FileOrString]
meson_version: T.Optional[str]
- default_options: T.List[str]
+ default_options: T.Dict[OptionKey, T.Union[str, int, bool, T.List[str]]]
license: T.List[str]
subproject_dir: str
class FindProgram(ExtractRequired, ExtractSearchDirs):
+ default_options: T.Dict[OptionKey, T.Union[str, int, bool, T.List[str]]]
native: MachineChoice
version: T.List[str]
check: bool
capture: T.Optional[bool]
- env: build.EnvironmentVariables
+ env: EnvironmentVariables
class FeatureOptionRequire(TypedDict):
class DependencyPkgConfigVar(TypedDict):
default: T.Optional[str]
- define_variable: T.List[str]
+ define_variable: PkgConfigDefineType
class DependencyGetVariable(TypedDict):
configtool: T.Optional[str]
internal: T.Optional[str]
default_value: T.Optional[str]
- pkgconfig_define: T.List[str]
+ pkgconfig_define: PkgConfigDefineType
class ConfigurationDataSet(TypedDict):
output: str
capture: bool
format: T.Literal['meson', 'cmake', 'cmake@']
- output_format: T.Literal['c', 'nasm']
+ output_format: T.Literal['c', 'json', 'nasm']
depfile: T.Optional[str]
install: T.Optional[bool]
install_dir: T.Union[str, T.Literal[False]]
command: T.Optional[T.List[T.Union[build.Executable, ExternalProgram, Compiler, File, str]]]
input: T.List[FileOrString]
configuration: T.Optional[T.Union[T.Dict[str, T.Union[str, int, bool]], build.ConfigurationData]]
+ macro_name: T.Optional[str]
class Subproject(ExtractRequired):
- default_options: T.List[str]
+ default_options: T.Dict[OptionKey, T.Union[str, int, bool, T.List[str]]]
version: T.List[str]
class DoSubproject(ExtractRequired):
- default_options: T.List[str]
+ default_options: T.Dict[OptionKey, T.Union[str, int, bool, T.List[str]]]
version: T.List[str]
cmake_options: T.List[str]
options: T.Optional[CMakeSubprojectOptions]
+
+
+class _BaseBuildTarget(TypedDict):
+
+ """Arguments used by all BuildTarget like functions.
+
+ This really exists because Jar is so different than all of the other
+ BuildTarget functions.
+ """
+
+ build_by_default: bool
+ build_rpath: str
+ extra_files: T.List[FileOrString]
+ gnu_symbol_visibility: str
+ install: bool
+ install_mode: FileMode
+ install_rpath: str
+ implicit_include_directories: bool
+ link_depends: T.List[T.Union[str, File, build.CustomTarget, build.CustomTargetIndex, build.BuildTarget]]
+ 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, T.Union[str, int, bool, T.List[str]]]
+ depend_files: NotRequired[T.List[File]]
+ resources: T.List[str]
+
+
+class _BuildTarget(_BaseBuildTarget):
+
+ """Arguments shared by non-JAR functions"""
+
+ d_debug: T.List[T.Union[str, int]]
+ d_import_dirs: T.List[T.Union[str, build.IncludeDirs]]
+ d_module_versions: T.List[T.Union[str, int]]
+ d_unittest: bool
+ rust_dependency_map: T.Dict[str, str]
+ sources: SourcesVarargsType
+ c_args: T.List[str]
+ cpp_args: T.List[str]
+ cuda_args: T.List[str]
+ fortran_args: T.List[str]
+ d_args: T.List[str]
+ objc_args: T.List[str]
+ objcpp_args: T.List[str]
+ rust_args: T.List[str]
+ vala_args: T.List[T.Union[str, File]] # Yes, Vala is really special
+ cs_args: T.List[str]
+ swift_args: T.List[str]
+ cython_args: T.List[str]
+ nasm_args: T.List[str]
+ masm_args: T.List[str]
+
+
+class _LibraryMixin(TypedDict):
+
+ rust_abi: T.Optional[Literal['c', 'rust']]
+
+
+class Executable(_BuildTarget):
+
+ export_dynamic: T.Optional[bool]
+ gui_app: T.Optional[bool]
+ implib: T.Optional[T.Union[str, bool]]
+ pie: T.Optional[bool]
+ vs_module_defs: T.Optional[T.Union[str, File, build.CustomTarget, build.CustomTargetIndex]]
+ win_subsystem: T.Optional[str]
+
+
+class _StaticLibMixin(TypedDict):
+
+ prelink: bool
+ pic: T.Optional[bool]
+
+
+class StaticLibrary(_BuildTarget, _StaticLibMixin, _LibraryMixin):
+ pass
+
+
+class _SharedLibMixin(TypedDict):
+
+ darwin_versions: T.Optional[T.Tuple[str, str]]
+ soversion: T.Optional[str]
+ version: T.Optional[str]
+ vs_module_defs: T.Optional[T.Union[str, File, build.CustomTarget, build.CustomTargetIndex]]
+
+
+class SharedLibrary(_BuildTarget, _SharedLibMixin, _LibraryMixin):
+ pass
+
+
+class SharedModule(_BuildTarget, _LibraryMixin):
+
+ vs_module_defs: T.Optional[T.Union[str, File, build.CustomTarget, build.CustomTargetIndex]]
+
+
+class Library(_BuildTarget, _SharedLibMixin, _StaticLibMixin, _LibraryMixin):
+
+ """For library, both_library, and as a base for build_target"""
+
+ c_static_args: NotRequired[T.List[str]]
+ c_shared_args: NotRequired[T.List[str]]
+ cpp_static_args: NotRequired[T.List[str]]
+ cpp_shared_args: NotRequired[T.List[str]]
+ cuda_static_args: NotRequired[T.List[str]]
+ cuda_shared_args: NotRequired[T.List[str]]
+ fortran_static_args: NotRequired[T.List[str]]
+ fortran_shared_args: NotRequired[T.List[str]]
+ d_static_args: NotRequired[T.List[str]]
+ d_shared_args: NotRequired[T.List[str]]
+ objc_static_args: NotRequired[T.List[str]]
+ objc_shared_args: NotRequired[T.List[str]]
+ objcpp_static_args: NotRequired[T.List[str]]
+ objcpp_shared_args: NotRequired[T.List[str]]
+ rust_static_args: NotRequired[T.List[str]]
+ rust_shared_args: NotRequired[T.List[str]]
+ vala_static_args: NotRequired[T.List[T.Union[str, File]]] # Yes, Vala is really special
+ vala_shared_args: NotRequired[T.List[T.Union[str, File]]] # Yes, Vala is really special
+ cs_static_args: NotRequired[T.List[str]]
+ cs_shared_args: NotRequired[T.List[str]]
+ swift_static_args: NotRequired[T.List[str]]
+ swift_shared_args: NotRequired[T.List[str]]
+ cython_static_args: NotRequired[T.List[str]]
+ cython_shared_args: NotRequired[T.List[str]]
+ nasm_static_args: NotRequired[T.List[str]]
+ nasm_shared_args: NotRequired[T.List[str]]
+ masm_static_args: NotRequired[T.List[str]]
+ masm_shared_args: NotRequired[T.List[str]]
+
+
+class BuildTarget(Library):
+
+ target_type: Literal['executable', 'shared_library', 'static_library',
+ 'shared_module', 'both_libraries', 'library', 'jar']
+
+
+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]
+ java_args: T.List[str]
+
+
+class FuncDeclareDependency(TypedDict):
+
+ compile_args: T.List[str]
+ d_import_dirs: T.List[T.Union[build.IncludeDirs, str]]
+ d_module_versions: T.List[T.Union[str, int]]
+ dependencies: T.List[Dependency]
+ extra_files: T.List[FileOrString]
+ include_directories: T.List[T.Union[build.IncludeDirs, str]]
+ link_args: T.List[str]
+ link_whole: T.List[T.Union[build.StaticLibrary, build.CustomTarget, build.CustomTargetIndex]]
+ link_with: T.List[build.LibTypes]
+ objects: T.List[build.ExtractedObjects]
+ sources: T.List[T.Union[FileOrString, build.GeneratedTypes]]
+ variables: T.Dict[str, str]
+ version: T.Optional[str]
# SPDX-License-Identifier: Apache-2.0
# Copyright 2012-2021 The Meson development team
# Copyright © 2021 Intel Corporation
+from __future__ import annotations
import os
import typing as T
from .. import mesonlib
from .. import dependencies
from .. import build
-from .. import mlog
+from .. import mlog, coredata
from ..mesonlib import MachineChoice, OptionKey
from ..programs import OverrideProgram, ExternalProgram
from .type_checking import NATIVE_KW, NoneType
if T.TYPE_CHECKING:
- from typing_extensions import Literal
- from ..backend.backends import ExecutableSerialisation
+ from typing_extensions import Literal, TypedDict
+
from ..compilers import Compiler
from ..interpreterbase import TYPE_kwargs, TYPE_var
+ from ..mesonlib import ExecutableSerialisation
from .interpreter import Interpreter
- from typing_extensions import TypedDict
-
class FuncOverrideDependency(TypedDict):
native: mesonlib.MachineChoice
skip_if_destdir: bool
install_tag: str
+ dry_run: bool
class NativeKW(TypedDict):
super().__init__(subproject=interpreter.subproject)
self.build = build
self.interpreter = interpreter
- self.methods.update({'get_compiler': self.get_compiler_method,
- 'is_cross_build': self.is_cross_build_method,
- 'has_exe_wrapper': self.has_exe_wrapper_method,
+ 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,
- 'is_unity': self.is_unity_method,
- 'is_subproject': self.is_subproject_method,
'current_source_dir': self.current_source_dir_method,
'current_build_dir': self.current_build_dir_method,
- 'source_root': self.source_root_method,
- 'build_root': self.build_root_method,
- 'project_source_root': self.project_source_root_method,
- 'project_build_root': self.project_build_root_method,
- 'global_source_root': self.global_source_root_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,
- 'add_install_script': self.add_install_script_method,
- 'add_postconf_script': self.add_postconf_script_method,
- 'add_dist_script': self.add_dist_script_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_version': self.project_version_method,
+ 'project_build_root': self.project_build_root_method,
'project_license': self.project_license_method,
- 'version': self.version_method,
+ 'project_license_files': self.project_license_files_method,
'project_name': self.project_name_method,
- 'get_cross_property': self.get_cross_property_method,
- 'get_external_property': self.get_external_property_method,
- 'has_external_property': self.has_external_property_method,
- 'backend': self.backend_method,
- 'add_devenv': self.add_devenv_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(
largs.append(found)
largs.extend(args)
- es = self.interpreter.backend.get_executable_serialisation(largs)
+ es = self.interpreter.backend.get_executable_serialisation(largs, verbose=True)
es.subproject = self.interpreter.subproject
return es
'meson.add_install_script',
KwargInfo('skip_if_destdir', bool, default=False, since='0.57.0'),
KwargInfo('install_tag', (str, NoneType), since='0.60.0'),
+ KwargInfo('dry_run', bool, default=False, since='1.1.0'),
)
def add_install_script_method(
self,
script = self._find_source_script('add_install_script', args[0], script_args)
script.skip_if_destdir = kwargs['skip_if_destdir']
script.tag = kwargs['install_tag']
+ script.dry_run = kwargs['dry_run']
self.build.install_scripts.append(script)
@typed_pos_args(
def _override_dependency_impl(self, name: str, dep: dependencies.Dependency, kwargs: 'FuncOverrideDependency',
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 fgure that out
+ # 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']
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
+ 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
def version_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> MesonVersionString:
@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, build.EnvironmentVariables))
- def add_devenv_method(self, args: T.Tuple[T.Union[str, list, dict, build.EnvironmentVariables]],
+ @typed_pos_args('add_devenv', (str, list, dict, mesonlib.EnvironmentVariables))
+ def add_devenv_method(self, args: T.Tuple[T.Union[str, list, dict, mesonlib.EnvironmentVariables]],
kwargs: 'AddDevenvKW') -> None:
env = args[0]
msg = ENV_KW.validator(env)
if msg:
raise build.InvalidArguments(f'"add_devenv": {msg}')
converted = env_convertor_with_method(env, kwargs['method'], kwargs['separator'])
- assert isinstance(converted, build.EnvironmentVariables)
+ assert isinstance(converted, mesonlib.EnvironmentVariables)
self.build.devenv.append(converted)
+
+ @noPosargs
+ @noKwargs
+ @FeatureNew('meson.build_options', '1.1.0')
+ 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)
from __future__ import annotations
from ...interpreterbase import (
- ObjectHolder,
- MesonOperator,
- typed_operator,
- noKwargs,
- noPosargs,
-
- InvalidArguments
+ FeatureBroken, InvalidArguments, MesonOperator, ObjectHolder, KwargInfo,
+ noKwargs, noPosargs, typed_operator, typed_kwargs
)
import typing as T
def display_name(self) -> str:
return 'int'
+ def operator_call(self, operator: MesonOperator, other: TYPE_var) -> TYPE_var:
+ if isinstance(other, bool):
+ FeatureBroken.single_use('int operations with non-int', '1.2.0', self.subproject,
+ 'It is not commutative and only worked because of leaky Python abstractions.',
+ location=self.current_node)
+ return super().operator_call(operator, other)
+
@noKwargs
@noPosargs
def is_even_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool:
def is_odd_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool:
return self.held_object % 2 != 0
- @noKwargs
+ @typed_kwargs(
+ 'to_string',
+ KwargInfo('fill', int, default=0, since='1.3.0')
+ )
@noPosargs
- def to_string_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
- return str(self.held_object)
+ 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)
def op_div(self, other: int) -> int:
# Copyright 2021 The Meson development team
# SPDX-license-identifier: Apache-2.0
+from __future__ import annotations
import typing as T
noKwargs,
noPosargs,
typed_pos_args,
-
InvalidArguments,
+ FeatureBroken,
+ stringifyUserArguments,
)
'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,
@noArgsFlattening
@noKwargs
@typed_pos_args('str.format', varargs=object)
- def format_method(self, args: T.Tuple[T.List[object]], kwargs: TYPE_kwargs) -> str:
+ 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]:
- if isinstance(arg, bool): # Python boolean is upper case.
- arg = str(arg).lower()
- arg_strings.append(str(arg))
+ try:
+ arg_strings.append(stringifyUserArguments(arg, self.subproject))
+ except InvalidArguments as e:
+ FeatureBroken.single_use(f'str.format: {str(e)}', '1.3.0', self.subproject, location=self.current_node)
+ arg_strings.append(str(arg))
def arg_replace(match: T.Match[str]) -> str:
idx = int(match.group(1))
return re.sub(r'@(\d+)@', arg_replace, self.held_object)
@noKwargs
+ @noPosargs
+ @FeatureNew('str.splitlines', '1.2.0')
+ 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)
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)
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.strip', optargs=[str])
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)
return self.held_object.strip(args[0])
@noKwargs
+ @FeatureNew('str.substring', '0.56.0')
@typed_pos_args('str.substring', optargs=[int, int])
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
"""Helpers for strict type checking."""
from __future__ import annotations
-import os
+import itertools, os, re
import typing as T
from .. import compilers
from ..build import (CustomTarget, BuildTarget,
CustomTargetIndex, ExtractedObjects, GeneratedList, IncludeDirs,
- BothLibraries, SharedLibrary, StaticLibrary, Jar, Executable)
+ BothLibraries, SharedLibrary, StaticLibrary, Jar, Executable, StructuredSources)
from ..coredata import UserFeatureOption
from ..dependencies import Dependency, InternalDependency
-from ..interpreterbase import FeatureNew
from ..interpreterbase.decorators import KwargInfo, ContainerTypeInfo
-from ..mesonlib import (
- File, FileMode, MachineChoice, listify, has_path_sep, OptionKey,
- EnvInitValueType, EnvironmentVariables)
+from ..mesonlib import (File, FileMode, MachineChoice, listify, has_path_sep,
+ OptionKey, EnvironmentVariables)
from ..programs import ExternalProgram
# Helper definition for type checks that are `Optional[T]`
if T.TYPE_CHECKING:
from typing_extensions import Literal
+ from ..build import ObjectTypes
from ..interpreterbase import TYPE_var
- from ..interpreterbase.decorators import FeatureCheckBase
+ from ..mesonlib import EnvInitValueType
+
+ _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]]
+
def in_set_validator(choices: T.Set[str]) -> T.Callable[[str], T.Optional[str]]:
"""Check that the choice given was one of the given set."""
return f'permission character 9 must be "-", "t", "T", or "x", not {perms[8]}'
if len(mode) >= 2 and not isinstance(mode[1], (int, str, bool)):
- return 'second componenent can only be a string, number, or False'
+ return 'second component can only be a string, number, or False'
if len(mode) >= 3 and not isinstance(mode[2], (int, str, bool)):
- return 'third componenent can only be a string, number, or False'
+ return 'third component can only be a string, number, or False'
return None
def _install_mode_convertor(mode: T.Optional[T.List[T.Union[str, bool, int]]]) -> FileMode:
- """Convert the DSL form of the `install_mode` keyword argument to `FileMode`
+ """Convert the DSL form of the `install_mode` keyword argument to `FileMode`"""
- This is not required, and if not required returns None
+ if not mode:
+ return FileMode()
- TODO: It's not clear to me why this needs to be None and not just return an
- empty FileMode.
- """
- # this has already been validated by the validator
- return FileMode(*(m if isinstance(m, str) else None for m in mode))
+ # This has already been validated by the validator. False denotes "use
+ # default". mypy is totally incapable of understanding it, because
+ # generators clobber types via homogeneous return. But also we *must*
+ # convert the first element different from the rest
+ m1 = mode[0] if isinstance(mode[0], str) else None
+ rest = (m if isinstance(m, (str, int)) else None for m in mode[1:])
+
+ return FileMode(m1, *rest)
def _lower_strlist(input: T.List[str]) -> T.List[str]:
return [i.lower() for i in input]
+def _validate_shlib_version(val: T.Optional[str]) -> T.Optional[str]:
+ if val is not None and not re.fullmatch(r'[0-9]+(\.[0-9]+){0,2}', val):
+ return (f'Invalid Shared library version "{val}". '
+ 'Must be of the form X.Y.Z where all three are numbers. Y and Z are optional.')
+ return None
+
+
def variables_validator(contents: T.Union[str, T.List[str], T.Dict[str, str]]) -> T.Optional[str]:
if isinstance(contents, str):
contents = [contents]
DISABLER_KW: KwargInfo[bool] = KwargInfo('disabler', bool, default=False)
def _env_validator(value: T.Union[EnvironmentVariables, T.List['TYPE_var'], T.Dict[str, 'TYPE_var'], str, None],
- allow_dict_list: bool = True) -> T.Optional[str]:
+ only_dict_str: bool = True) -> T.Optional[str]:
def _splitter(v: str) -> T.Optional[str]:
split = v.split('=', 1)
if len(split) == 1:
elif isinstance(value, dict):
# We don't need to spilt here, just do the type checking
for k, dv in value.items():
- if allow_dict_list:
+ if only_dict_str:
if any(i for i in listify(dv) if not isinstance(i, str)):
return f"Dictionary element {k} must be a string or list of strings not {dv!r}"
- elif not isinstance(dv, str):
- return f"Dictionary element {k} must be a string not {dv!r}"
+ elif isinstance(dv, list):
+ if any(not isinstance(i, str) for i in dv):
+ return f"Dictionary element {k} must be a string, bool, integer or list of strings, not {dv!r}"
+ elif not isinstance(dv, (str, bool, int)):
+ return f"Dictionary element {k} must be a string, bool, integer or list of strings, not {dv!r}"
# We know that otherwise we have an EnvironmentVariables object or None, and
# we're okay at this point
return None
def _options_validator(value: T.Union[EnvironmentVariables, T.List['TYPE_var'], T.Dict[str, 'TYPE_var'], str, None]) -> T.Optional[str]:
- # Reusing the env validator is a littl overkill, but nicer than duplicating the code
- return _env_validator(value, allow_dict_list=False)
+ # Reusing the env validator is a little overkill, but nicer than duplicating the code
+ return _env_validator(value, only_dict_str=False)
def split_equal_string(input: str) -> T.Tuple[str, str]:
"""Split a string in the form `x=y`
a, b = input.split('=', 1)
return (a, b)
-_FullEnvInitValueType = T.Union[EnvironmentVariables, T.List[str], T.List[T.List[str]], EnvInitValueType, str, None]
-
# Split _env_convertor() and env_convertor_with_method() to make mypy happy.
# It does not want extra arguments in KwargInfo convertor callable.
def env_convertor_with_method(value: _FullEnvInitValueType,
default=[],
)
-def _override_options_convertor(raw: T.List[str]) -> T.Dict[OptionKey, str]:
- output: T.Dict[OptionKey, str] = {}
- for each in raw:
- k, v = split_equal_string(each)
- output[OptionKey.from_string(k)] = v
- return output
+def _override_options_convertor(raw: T.Union[str, T.List[str], T.Dict[str, T.Union[str, int, bool, T.List[str]]]]) -> T.Dict[OptionKey, T.Union[str, int, bool, T.List[str]]]:
+ if isinstance(raw, str):
+ raw = [raw]
+ if isinstance(raw, list):
+ output: T.Dict[OptionKey, T.Union[str, int, bool, T.List[str]]] = {}
+ for each in raw:
+ k, v = split_equal_string(each)
+ output[OptionKey.from_string(k)] = v
+ return output
+ return {OptionKey.from_string(k): v for k, v in raw.items()}
-OVERRIDE_OPTIONS_KW: KwargInfo[T.List[str]] = KwargInfo(
+OVERRIDE_OPTIONS_KW: KwargInfo[T.Union[str, T.Dict[str, T.Union[str, int, bool, T.List[str]]], T.List[str]]] = KwargInfo(
'override_options',
- ContainerTypeInfo(list, str),
- listify=True,
- default=[],
+ (str, ContainerTypeInfo(list, str), ContainerTypeInfo(dict, (str, int, bool, list))),
+ default={},
validator=_options_validator,
convertor=_override_options_convertor,
+ since_values={dict: '1.2.0'},
)
def _output_validator(outputs: T.List[str]) -> T.Optional[str]:
+ output_set = set(outputs)
+ if len(output_set) != len(outputs):
+ seen = set()
+ for el in outputs:
+ if el in seen:
+ return f"contains {el!r} multiple times, but no duplicates are allowed."
+ seen.add(el)
for i in outputs:
if i == '':
return 'Output must not be empty.'
INSTALL_TAG_KW: KwargInfo[T.Optional[str]] = KwargInfo('install_tag', (str, NoneType))
+INSTALL_FOLLOW_SYMLINKS: KwargInfo[T.Optional[bool]] = KwargInfo(
+ 'follow_symlinks',
+ (bool, NoneType),
+ since='1.3.0',
+)
+
INSTALL_KW = KwargInfo('install', bool, default=False)
CT_INSTALL_DIR_KW: KwargInfo[T.List[T.Union[str, Literal[False]]]] = KwargInfo(
default=[],
)
-def include_dir_string_new(val: T.List[T.Union[str, IncludeDirs]]) -> T.Iterable[FeatureCheckBase]:
- strs = [v for v in val if isinstance(v, str)]
- if strs:
- str_msg = ", ".join(f"'{s}'" for s in strs)
- yield FeatureNew('include_directories kwarg of type string', '1.0.0',
- f'Use include_directories({str_msg}) instead')
-
-# for cases like default_options and override_options
-DEFAULT_OPTIONS: KwargInfo[T.List[str]] = KwargInfo(
- 'default_options',
- ContainerTypeInfo(list, str),
- listify=True,
- default=[],
- validator=_options_validator,
-)
+DEFAULT_OPTIONS = OVERRIDE_OPTIONS_KW.evolve(name='default_options')
ENV_METHOD_KW = KwargInfo('method', str, default='set', since='0.62.0',
validator=in_set_validator({'set', 'prepend', 'append'}))
validator=link_whole_validator,
)
-SOURCES_KW: KwargInfo[T.List[T.Union[str, File, CustomTarget, CustomTargetIndex, GeneratedList]]] = KwargInfo(
+DEPENDENCY_SOURCES_KW: KwargInfo[T.List[T.Union[str, File, CustomTarget, CustomTargetIndex, GeneratedList]]] = KwargInfo(
'sources',
ContainerTypeInfo(list, (str, File, CustomTarget, CustomTargetIndex, GeneratedList)),
listify=True,
default=[],
)
+SOURCES_VARARGS = (str, File, CustomTarget, CustomTargetIndex, GeneratedList, StructuredSources, ExtractedObjects, BuildTarget)
+
+BT_SOURCES_KW: KwargInfo[SourcesVarargsType] = KwargInfo(
+ 'sources',
+ (NoneType, ContainerTypeInfo(list, SOURCES_VARARGS)),
+ listify=True,
+ default=[],
+)
+
VARIABLES_KW: KwargInfo[T.Dict[str, str]] = KwargInfo(
'variables',
# str is listified by validator/convertor, cannot use listify=True here because
KwargInfo('suite', ContainerTypeInfo(list, str), listify=True, default=['']), # yes, a list of empty string
KwargInfo('verbose', bool, default=False, since='0.62.0'),
]
+
+# Cannot have a default value because we need to check that rust_crate_type and
+# rust_abi are mutually exclusive.
+RUST_CRATE_TYPE_KW: KwargInfo[T.Union[str, None]] = KwargInfo(
+ 'rust_crate_type', (str, NoneType),
+ since='0.42.0',
+ since_values={'proc-macro': '0.62.0'},
+ deprecated='1.3.0',
+ deprecated_message='Use rust_abi or rust.proc_macro() instead.',
+ validator=in_set_validator({'bin', 'lib', 'rlib', 'dylib', 'cdylib', 'staticlib', 'proc-macro'}))
+
+RUST_ABI_KW: KwargInfo[T.Union[str, None]] = KwargInfo(
+ 'rust_abi', (str, NoneType),
+ since='1.3.0',
+ validator=in_set_validator({'rust', 'c'}))
+
+_VS_MODULE_DEFS_KW: KwargInfo[T.Optional[T.Union[str, File, CustomTarget, CustomTargetIndex]]] = KwargInfo(
+ 'vs_module_defs',
+ (str, File, CustomTarget, CustomTargetIndex, NoneType),
+ since_values={CustomTargetIndex: '1.3.0'}
+)
+
+_BASE_LANG_KW: KwargInfo[T.List[str]] = KwargInfo(
+ 'UNKNOWN',
+ ContainerTypeInfo(list, (str)),
+ listify=True,
+ default=[],
+)
+
+_LANGUAGE_KWS: T.List[KwargInfo[T.List[str]]] = [
+ _BASE_LANG_KW.evolve(name=f'{lang}_args')
+ for lang in compilers.all_languages - {'rust', 'vala', 'java'}
+]
+# Cannot use _BASE_LANG_KW here because Vala is special for types
+_LANGUAGE_KWS.append(KwargInfo(
+ 'vala_args', ContainerTypeInfo(list, (str, File)), listify=True, default=[]))
+_LANGUAGE_KWS.append(_BASE_LANG_KW.evolve(name='rust_args', since='0.41.0'))
+
+# We need this deprecated values more than the non-deprecated values. So we'll evolve them out elsewhere.
+_JAVA_LANG_KW: KwargInfo[T.List[str]] = _BASE_LANG_KW.evolve(
+ name='java_args',
+ deprecated='1.3.0',
+ deprecated_message='This does not, and never has, done anything. It should be removed'
+)
+
+def _objects_validator(vals: T.List[ObjectTypes]) -> T.Optional[str]:
+ non_objects: T.List[str] = []
+
+ for val in vals:
+ if isinstance(val, (str, File, ExtractedObjects)):
+ continue
+ else:
+ non_objects.extend(o for o in val.get_outputs() if not compilers.is_object(o))
+
+ if non_objects:
+ return f'{", ".join(non_objects)!r} are not objects'
+
+ 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),
+ INSTALL_MODE_KW,
+ KwargInfo('implicit_include_directories', bool, default=True, since='0.42.0'),
+ NATIVE_KW,
+ KwargInfo('resources', ContainerTypeInfo(list, str), default=[], listify=True),
+ KwargInfo(
+ 'objects',
+ ContainerTypeInfo(list, (str, File, CustomTarget, CustomTargetIndex, GeneratedList, ExtractedObjects)),
+ listify=True,
+ default=[],
+ validator=_objects_validator,
+ since_values={
+ ContainerTypeInfo(list, (GeneratedList, CustomTarget, CustomTargetIndex)):
+ ('1.1.0', 'generated sources as positional "objects" arguments')
+ },
+ ),
+]
+
+
+def _name_validator(arg: T.Optional[T.Union[str, T.List]]) -> T.Optional[str]:
+ if isinstance(arg, list) and arg:
+ return 'must be empty when passed as an array to signify the default value.'
+ return None
+
+
+def _name_suffix_validator(arg: T.Optional[T.Union[str, T.List]]) -> T.Optional[str]:
+ if arg == '':
+ return 'must not be a empty string. An empty array may be passed if you want Meson to use the default behavior.'
+ return _name_validator(arg)
+
+
+_NAME_PREFIX_KW: KwargInfo[T.Optional[T.Union[str, T.List]]] = KwargInfo(
+ 'name_prefix',
+ (str, NoneType, list),
+ validator=_name_validator,
+ convertor=lambda x: None if isinstance(x, list) else x,
+)
+
+
+# Applies to all build_target classes except jar
+_BUILD_TARGET_KWS: T.List[KwargInfo] = [
+ *_ALL_TARGET_KWS,
+ *_LANGUAGE_KWS,
+ BT_SOURCES_KW,
+ INCLUDE_DIRECTORIES.evolve(name='d_import_dirs'),
+ _NAME_PREFIX_KW,
+ _NAME_PREFIX_KW.evolve(name='name_suffix', validator=_name_suffix_validator),
+ RUST_CRATE_TYPE_KW,
+ KwargInfo('d_debug', ContainerTypeInfo(list, (str, int)), default=[], listify=True),
+ D_MODULE_VERSIONS_KW,
+ KwargInfo('d_unittest', bool, default=False),
+ KwargInfo(
+ 'rust_dependency_map',
+ ContainerTypeInfo(dict, str),
+ default={},
+ since='1.2.0',
+ ),
+ KwargInfo('build_rpath', str, default='', since='0.42.0'),
+ KwargInfo(
+ 'gnu_symbol_visibility',
+ str,
+ default='',
+ validator=in_set_validator({'', 'default', 'internal', 'hidden', 'protected', 'inlineshidden'}),
+ since='0.48.0',
+ ),
+ KwargInfo('install_rpath', str, default=''),
+ KwargInfo(
+ 'link_depends',
+ ContainerTypeInfo(list, (str, File, CustomTarget, CustomTargetIndex, BuildTarget)),
+ default=[],
+ listify=True,
+ ),
+ KwargInfo(
+ 'link_language',
+ (str, NoneType),
+ validator=in_set_validator(set(compilers.all_languages)),
+ since='0.51.0',
+ ),
+]
+
+def _validate_win_subsystem(value: T.Optional[str]) -> T.Optional[str]:
+ if value is not None:
+ if re.fullmatch(r'(boot_application|console|efi_application|efi_boot_service_driver|efi_rom|efi_runtime_driver|native|posix|windows)(,\d+(\.\d+)?)?', value) is None:
+ return f'Invalid value for win_subsystem: {value}.'
+ return None
+
+
+def _validate_darwin_versions(darwin_versions: T.List[T.Union[str, int]]) -> T.Optional[str]:
+ if len(darwin_versions) > 2:
+ return f"Must contain between 0 and 2 elements, not {len(darwin_versions)}"
+ if len(darwin_versions) == 1:
+ darwin_versions = 2 * darwin_versions
+ for v in darwin_versions:
+ if isinstance(v, int):
+ v = str(v)
+ if not re.fullmatch(r'[0-9]+(\.[0-9]+){0,2}', v):
+ return 'must be X.Y.Z where X, Y, Z are numbers, and Y and Z are optional'
+ try:
+ parts = v.split('.')
+ except ValueError:
+ return f'badly formed value: "{v}, not in X.Y.Z form'
+ if len(parts) in {1, 2, 3} and int(parts[0]) > 65535:
+ return 'must be X.Y.Z where X is [0, 65535] and Y, Z are optional'
+ if len(parts) in {2, 3} and int(parts[1]) > 255:
+ return 'must be X.Y.Z where Y is [0, 255] and Y, Z are optional'
+ if len(parts) == 3 and int(parts[2]) > 255:
+ return 'must be X.Y.Z where Z is [0, 255] and Y, Z are optional'
+ return None
+
+
+def _convert_darwin_versions(val: T.List[T.Union[str, int]]) -> T.Optional[T.Tuple[str, str]]:
+ if not val:
+ return None
+ elif len(val) == 1:
+ v = str(val[0])
+ return (v, v)
+ return (str(val[0]), str(val[1]))
+
+
+_DARWIN_VERSIONS_KW: KwargInfo[T.List[T.Union[str, int]]] = KwargInfo(
+ 'darwin_versions',
+ ContainerTypeInfo(list, (str, int)),
+ default=[],
+ listify=True,
+ validator=_validate_darwin_versions,
+ convertor=_convert_darwin_versions,
+ since='0.48.0',
+)
+
+# Arguments exclusive to Executable. These are separated to make integrating
+# them into build_target easier
+_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('pie', (bool, NoneType)),
+ KwargInfo(
+ 'win_subsystem',
+ (str, NoneType),
+ convertor=lambda x: x.lower() if isinstance(x, str) else None,
+ validator=_validate_win_subsystem,
+ ),
+]
+
+# The total list of arguments used by Executable
+EXECUTABLE_KWS = [
+ *_BUILD_TARGET_KWS,
+ *_EXCLUSIVE_EXECUTABLE_KWS,
+ _VS_MODULE_DEFS_KW.evolve(since='1.3.0', since_values=None),
+ _JAVA_LANG_KW,
+]
+
+# Arguments exclusive to library types
+_EXCLUSIVE_LIB_KWS: T.List[KwargInfo] = [
+ RUST_ABI_KW,
+]
+
+# Arguments exclusive to StaticLibrary. These are separated to make integrating
+# them into build_target easier
+_EXCLUSIVE_STATIC_LIB_KWS: T.List[KwargInfo] = [
+ KwargInfo('prelink', bool, default=False, since='0.57.0'),
+ KwargInfo('pic', (bool, NoneType), since='0.36.0'),
+]
+
+# The total list of arguments used by StaticLibrary
+STATIC_LIB_KWS = [
+ *_BUILD_TARGET_KWS,
+ *_EXCLUSIVE_STATIC_LIB_KWS,
+ *_EXCLUSIVE_LIB_KWS,
+ _JAVA_LANG_KW,
+]
+
+# 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),
+]
+
+# The total list of arguments used by SharedLibrary
+SHARED_LIB_KWS = [
+ *_BUILD_TARGET_KWS,
+ *_EXCLUSIVE_SHARED_LIB_KWS,
+ *_EXCLUSIVE_LIB_KWS,
+ _VS_MODULE_DEFS_KW,
+ _JAVA_LANG_KW,
+]
+
+# Arguments exclusive to SharedModule. These are separated to make integrating
+# them into build_target easier
+_EXCLUSIVE_SHARED_MOD_KWS: T.List[KwargInfo] = []
+
+# The total list of arguments used by SharedModule
+SHARED_MOD_KWS = [
+ *_BUILD_TARGET_KWS,
+ *_EXCLUSIVE_SHARED_MOD_KWS,
+ *_EXCLUSIVE_LIB_KWS,
+ _VS_MODULE_DEFS_KW,
+ _JAVA_LANG_KW,
+]
+
+# Arguments exclusive to JAR. These are separated to make integrating
+# them into build_target easier
+_EXCLUSIVE_JAR_KWS: T.List[KwargInfo] = [
+ KwargInfo('main_class', str, default=''),
+ KwargInfo('java_resources', (StructuredSources, NoneType), since='0.62.0'),
+ _JAVA_LANG_KW.evolve(deprecated=None, deprecated_message=None),
+]
+
+# The total list of arguments used by JAR
+JAR_KWS = [
+ *_ALL_TARGET_KWS,
+ *_EXCLUSIVE_JAR_KWS,
+ KwargInfo(
+ 'sources',
+ ContainerTypeInfo(list, (str, File, CustomTarget, CustomTargetIndex, GeneratedList, ExtractedObjects, BuildTarget)),
+ listify=True,
+ default=[],
+ ),
+ *[a.evolve(deprecated='1.3.0', deprecated_message='This argument has never done anything in jar(), and should be removed')
+ for a in _LANGUAGE_KWS],
+]
+
+_SHARED_STATIC_ARGS: T.List[KwargInfo[T.List[str]]] = [
+ *[l.evolve(name=l.name.replace('_', '_static_'), since='1.3.0')
+ for l in _LANGUAGE_KWS],
+ *[l.evolve(name=l.name.replace('_', '_shared_'), since='1.3.0')
+ for l in _LANGUAGE_KWS],
+]
+
+# Arguments used by both_library and library
+LIBRARY_KWS = [
+ *_BUILD_TARGET_KWS,
+ *_EXCLUSIVE_LIB_KWS,
+ *_EXCLUSIVE_SHARED_LIB_KWS,
+ *_EXCLUSIVE_SHARED_MOD_KWS,
+ *_EXCLUSIVE_STATIC_LIB_KWS,
+ *_SHARED_STATIC_ARGS,
+ _VS_MODULE_DEFS_KW,
+ _JAVA_LANG_KW,
+]
+
+# Arguments used by build_Target
+BUILD_TARGET_KWS = [
+ *_BUILD_TARGET_KWS,
+ *_EXCLUSIVE_SHARED_LIB_KWS,
+ *_EXCLUSIVE_SHARED_MOD_KWS,
+ *_EXCLUSIVE_STATIC_LIB_KWS,
+ *_EXCLUSIVE_EXECUTABLE_KWS,
+ *_SHARED_STATIC_ARGS,
+ *[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(
+ 'target_type',
+ str,
+ required=True,
+ validator=in_set_validator({
+ 'executable', 'shared_library', 'static_library', 'shared_module',
+ 'both_libraries', 'library', 'jar'
+ }),
+ since_values={
+ 'shared_module': '0.51.0',
+ },
+ deprecated_values={
+ 'jar': ('1.3.0', 'use the "jar()" function directly'),
+ }
+ )
+]
+
+def _pkgconfig_define_convertor(x: T.List[str]) -> PkgConfigDefineType:
+ if x:
+ keys = itertools.islice(x, 0, None, 2)
+ vals = itertools.islice(x, 1, None, 2)
+ return tuple(zip(keys, vals))
+ return None
+
+PKGCONFIG_DEFINE_KW: KwargInfo = KwargInfo(
+ 'pkgconfig_define',
+ ContainerTypeInfo(list, str, pairs=True),
+ default=[],
+ convertor=_pkgconfig_define_convertor,
+)
'ObjectHolder',
'IterableObject',
'MutableInterpreterObject',
+ 'ContextManagerObject',
'MesonOperator',
'default_resolve_key',
'flatten',
'resolve_second_level_holders',
+ 'stringifyUserArguments',
'noPosargs',
'noKwargs',
'disablerIfNotFound',
'permittedKwargs',
'typed_operator',
- 'unary_operator',
'typed_pos_args',
'ContainerTypeInfo',
'KwargInfo',
'FeatureCheckBase',
'FeatureNew',
'FeatureDeprecated',
+ 'FeatureBroken',
'FeatureNewKwargs',
'FeatureDeprecatedKwargs',
'SubProject',
- 'TV_fw_var',
- 'TV_fw_args',
- 'TV_fw_kwargs',
'TV_func',
'TYPE_elementary',
'TYPE_var',
ObjectHolder,
IterableObject,
MutableInterpreterObject,
+ ContextManagerObject,
- TV_fw_var,
- TV_fw_args,
- TV_fw_kwargs,
TV_func,
TYPE_elementary,
TYPE_var,
ContainerTypeInfo,
KwargInfo,
typed_operator,
- unary_operator,
typed_kwargs,
FeatureCheckBase,
FeatureNew,
FeatureDeprecated,
+ FeatureBroken,
FeatureNewKwargs,
FeatureDeprecatedKwargs,
)
)
from .disabler import Disabler, is_disabled
-from .helpers import default_resolve_key, flatten, resolve_second_level_holders
+from .helpers import (
+ default_resolve_key,
+ flatten,
+ resolve_second_level_holders,
+ stringifyUserArguments,
+)
from .interpreterbase import InterpreterBase
from .operator import MesonOperator
# 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
from .. import mparser
from .exceptions import InvalidCode, InvalidArguments
import typing as T
from abc import ABCMeta
+from contextlib import AbstractContextManager
if T.TYPE_CHECKING:
from typing_extensions import Protocol
class OperatorCall(Protocol[__T]):
def __call__(self, other: __T) -> 'TYPE_var': ...
-TV_fw_var = T.Union[str, int, bool, list, dict, 'InterpreterObject']
-TV_fw_args = T.List[T.Union[mparser.BaseNode, TV_fw_var]]
-TV_fw_kwargs = T.Dict[str, T.Union[mparser.BaseNode, TV_fw_var]]
TV_func = T.TypeVar('TV_func', bound=T.Callable[..., T.Any])
# We use `type(...) == type(...)` here to enforce an *exact* match for comparison. We
# don't want comparisons to be possible where `isinstance(derived_obj, type(base_obj))`
# would pass because this comparison must never be true: `derived_obj == base_obj`
- if type(self) != type(other):
+ if type(self) is not type(other):
self._throw_comp_exception(other, '==')
return self == other
def op_not_equals(self, other: TYPE_var) -> bool:
- if type(self) != type(other):
+ if type(self) is not type(other):
self._throw_comp_exception(other, '!=')
return self != other
# Override default comparison operators for the held object
def op_equals(self, other: TYPE_var) -> bool:
# See the comment from InterpreterObject why we are using `type()` here.
- if type(self.held_object) != type(other):
+ if type(self.held_object) is not type(other):
self._throw_comp_exception(other, '==')
return self.held_object == other
def op_not_equals(self, other: TYPE_var) -> bool:
- if type(self.held_object) != type(other):
+ if type(self.held_object) is not type(other):
self._throw_comp_exception(other, '!=')
return self.held_object != other
def size(self) -> int:
raise MesonBugException(f'size not implemented for {self.__class__.__name__}')
+
+class ContextManagerObject(MesonInterpreterObject, AbstractContextManager):
+ def __init__(self, subproject: 'SubProject') -> None:
+ super().__init__(subproject=subproject)
from typing_extensions import Protocol
from .. import mparser
- from .baseobjects import InterpreterObject, TV_func, TYPE_var, TYPE_kwargs
- from .interpreterbase import SubProject
+ from .baseobjects import InterpreterObject, SubProject, TV_func, TYPE_var, TYPE_kwargs
from .operator import MesonOperator
_TV_IntegerObject = T.TypeVar('_TV_IntegerObject', bound=InterpreterObject, contravariant=True)
return T.cast('_TV_FN_Operator', wrapper)
return inner
-def unary_operator(operator: MesonOperator) -> T.Callable[['_TV_FN_Operator'], '_TV_FN_Operator']:
- """Decorator that does type checking for unary operator calls.
-
- This decorator is for unary operators that do not take any other objects.
- It should be impossible for a user to accidentally break this. Triggering
- this check always indicates a bug in the Meson interpreter.
- """
- def inner(f: '_TV_FN_Operator') -> '_TV_FN_Operator':
- @wraps(f)
- def wrapper(self: 'InterpreterObject', other: TYPE_var) -> TYPE_var:
- if other is not None:
- raise mesonlib.MesonBugException(f'The unary operator `{operator.value}` of {self.display_name()} was passed the object {other} of type {type(other).__name__}')
- return f(self, other)
- return T.cast('_TV_FN_Operator', wrapper)
- return inner
-
def typed_pos_args(name: str, *types: T.Union[T.Type, T.Tuple[T.Type, ...]],
varargs: T.Optional[T.Union[T.Type, T.Tuple[T.Type, ...]]] = None,
diff = num_types + len(optargs) - num_args
nargs[i] = tuple(list(args) + [None] * diff)
else:
- nargs[i] = args
+ nargs[i] = tuple(args)
else:
nargs[i] = tuple(args)
return f(*nargs, **wrapped_kwargs)
if not isinstance(value, self.container):
return False
iter_ = iter(value.values()) if isinstance(value, dict) else iter(value)
- for each in iter_:
- if not isinstance(each, self.contains):
- return False
+ if any(not isinstance(i, self.contains) for i in iter_):
+ return False
if self.pairs and len(value) % 2 != 0:
return False
if not value and not self.allow_empty:
return False
return True
+ def check_any(self, value: T.Any) -> bool:
+ """Check a value should emit new/deprecated feature.
+
+ :param value: A value to check
+ :return: True if any of the items in value matches, False otherwise
+ """
+ if not isinstance(value, self.container):
+ return False
+ iter_ = iter(value.values()) if isinstance(value, dict) else iter(value)
+ return any(isinstance(i, self.contains) for i in iter_)
+
def description(self) -> str:
"""Human readable description of this container type.
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],
default: T.Optional[_T] = None,
since: T.Optional[str] = None,
since_message: T.Optional[str] = None,
- since_values: T.Optional[T.Dict[T.Union[_T, T.Type[T.List], T.Type[T.Dict]], T.Union[str, T.Tuple[str, str]]]] = None,
+ since_values: T.Optional[T.Dict[T.Union[_T, ContainerTypeInfo, type], T.Union[str, T.Tuple[str, str]]]] = None,
deprecated: T.Optional[str] = None,
deprecated_message: T.Optional[str] = None,
- deprecated_values: T.Optional[T.Dict[T.Union[_T, T.Type[T.List], T.Type[T.Dict]], T.Union[str, T.Tuple[str, str]]]] = None,
- feature_validator: T.Optional[T.Callable[[_T], T.Iterable[FeatureCheckBase]]] = None,
+ deprecated_values: T.Optional[T.Dict[T.Union[_T, ContainerTypeInfo, type], T.Union[str, T.Tuple[str, str]]]] = 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):
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
default: T.Union[_T, None, _NULL_T] = _NULL,
since: T.Union[str, None, _NULL_T] = _NULL,
since_message: T.Union[str, None, _NULL_T] = _NULL,
- since_values: T.Union[T.Dict[T.Union[_T, T.Type[T.List], T.Type[T.Dict]], T.Union[str, T.Tuple[str, str]]], None, _NULL_T] = _NULL,
+ since_values: T.Union[T.Dict[T.Union[_T, ContainerTypeInfo, type], T.Union[str, T.Tuple[str, str]]], None, _NULL_T] = _NULL,
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, T.Type[T.List], T.Type[T.Dict]], 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,
+ deprecated_values: T.Union[T.Dict[T.Union[_T, ContainerTypeInfo, type], T.Union[str, T.Tuple[str, str]]], 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':
"""Create a shallow copy of this KwargInfo, with modifications.
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,
)
information. For non-required values it sets the value to a default, which
means the value will always be provided.
- If type tyhpe is a :class:ContainerTypeInfo, then the default value will be
+ If type is a :class:ContainerTypeInfo, then the default value will be
passed as an argument to the container initializer, making a shallow copy
:param name: the name of the function, including the object it's attached to
def emit_feature_change(values: T.Dict[_T, T.Union[str, T.Tuple[str, str]]], feature: T.Union[T.Type['FeatureDeprecated'], T.Type['FeatureNew']]) -> None:
for n, version in values.items():
- warn = False
if isinstance(version, tuple):
version, msg = version
else:
msg = None
- if n in {dict, list}:
- assert isinstance(n, type), 'for mypy'
+ warning: T.Optional[str] = None
+ if isinstance(n, ContainerTypeInfo):
+ if n.check_any(value):
+ warning = f'of type {n.description()}'
+ elif isinstance(n, type):
if isinstance(value, n):
- feature.single_use(f'"{name}" keyword argument "{info.name}" of type {n.__name__}', version, subproject, msg, location=node)
- elif isinstance(value, (dict, list)):
- warn = n in value
- else:
- warn = n == value
-
- if warn:
- feature.single_use(f'"{name}" keyword argument "{info.name}" value "{n}"', version, subproject, msg, location=node)
+ warning = f'of type {n.__name__}'
+ elif isinstance(value, list):
+ if n in value:
+ warning = f'value "{n}" in list'
+ elif isinstance(value, dict):
+ if n in value.keys():
+ warning = f'value "{n}" in dict keys'
+ elif n == value:
+ warning = f'value "{n}"'
+ if warning:
+ feature.single_use(f'"{name}" keyword argument "{info.name}" {warning}', version, subproject, msg, location=node)
node, _, _kwargs, subproject = get_callee_args(wrapped_args)
# Cast here, as the convertor function may place something other than a TYPE_var in the kwargs
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)
else:
# set the value to the default, this ensuring all kwargs are present
# This both simplifies the typing checking and the usage
- assert check_value_type(types_tuple, info.default), f'In funcion {name} default value of {info.name} is not a valid type, got {type(info.default)} expected {types_description(types_tuple)}'
+ assert check_value_type(types_tuple, info.default), f'In function {name} default value of {info.name} is not a valid type, got {type(info.default)} expected {types_description(types_tuple)}'
# Create a shallow copy of the container. This allows mutable
# types to be used safely as default values
kwargs[info.name] = copy.copy(info.default)
feature_registry: T.ClassVar[T.Dict[str, T.Dict[str, T.Set[T.Tuple[str, T.Optional['mparser.BaseNode']]]]]]
emit_notice = False
+ unconditional = False
def __init__(self, feature_name: str, feature_version: str, extra_message: str = ''):
- self.feature_name = feature_name # type: str
- self.feature_version = feature_version # type: str
- self.extra_message = extra_message # type: str
+ self.feature_name = feature_name
+ self.feature_version = feature_version
+ self.extra_message = extra_message
@staticmethod
def get_target_version(subproject: str) -> str:
def use(self, subproject: 'SubProject', location: T.Optional['mparser.BaseNode'] = None) -> None:
tv = self.get_target_version(subproject)
# No target version
- if tv == '':
+ if tv == '' and not self.unconditional:
return
# Target version is new enough, don't warn
if self.check_version(tv, self.feature_version) and not self.emit_notice:
fv = cls.feature_registry[subproject]
tv = cls.get_target_version(subproject)
for version in sorted(fv.keys()):
+ message = ', '.join(sorted({f"'{i[0]}'" for i in fv[version]}))
if cls.check_version(tv, version):
- notice_str += '\n * {}: {}'.format(version, {i[0] for i in fv[version]})
+ notice_str += '\n * {}: {{{}}}'.format(version, message)
else:
- warning_str += '\n * {}: {}'.format(version, {i[0] for i in fv[version]})
+ warning_str += '\n * {}: {{{}}}'.format(version, message)
if '\n' in notice_str:
mlog.notice(notice_str, fatal=False)
if '\n' in warning_str:
# Class variable, shared across all instances
#
# Format: {subproject: {feature_version: set(feature_names)}}
- feature_registry = {} # type: T.ClassVar[T.Dict[str, T.Dict[str, T.Set[T.Tuple[str, T.Optional[mparser.BaseNode]]]]]]
+ feature_registry = {}
@staticmethod
def check_version(target_version: str, feature_version: str) -> bool:
# Class variable, shared across all instances
#
# Format: {subproject: {feature_version: set(feature_names)}}
- feature_registry = {} # type: T.ClassVar[T.Dict[str, T.Dict[str, T.Set[T.Tuple[str, T.Optional[mparser.BaseNode]]]]]]
+ feature_registry = {}
emit_notice = True
@staticmethod
mlog.warning(*args, location=location)
+class FeatureBroken(FeatureCheckBase):
+ """Checks for broken features"""
+
+ # Class variable, shared across all instances
+ #
+ # Format: {subproject: {feature_version: set(feature_names)}}
+ feature_registry = {}
+ unconditional = True
+
+ @staticmethod
+ def check_version(target_version: str, feature_version: str) -> bool:
+ # always warn for broken stuff
+ return False
+
+ @staticmethod
+ def get_warning_str_prefix(tv: str) -> str:
+ return 'Broken features used:'
+
+ @staticmethod
+ def get_notice_str_prefix(tv: str) -> str:
+ return ''
+
+ def log_usage_warning(self, tv: str, location: T.Optional['mparser.BaseNode']) -> None:
+ args = [
+ 'Project uses feature that was always broken,',
+ 'and is now deprecated since',
+ f"'{self.feature_version}':",
+ f'{self.feature_name}.',
+ ]
+ if self.extra_message:
+ args.append(self.extra_message)
+ mlog.deprecation(*args, location=location)
+
+
# This cannot be a dataclass due to https://github.com/python/mypy/issues/5374
class FeatureCheckKwargsBase(metaclass=abc.ABCMeta):
# 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
from .. import mesonlib, mparser
-from .exceptions import InterpreterException
+from .exceptions import InterpreterException, InvalidArguments
+from ..coredata import UserOption
+
import collections.abc
import typing as T
if T.TYPE_CHECKING:
- from .baseobjects import TYPE_var, TYPE_kwargs
+ from .baseobjects import TYPE_var, TYPE_kwargs, SubProject
def flatten(args: T.Union['TYPE_var', T.List['TYPE_var']]) -> T.List['TYPE_var']:
- if isinstance(args, mparser.StringNode):
+ if isinstance(args, mparser.BaseStringNode):
assert isinstance(args.value, str)
return [args.value]
if not isinstance(args, collections.abc.Sequence):
if isinstance(a, list):
rest = flatten(a)
result = result + rest
- elif isinstance(a, mparser.StringNode):
+ elif isinstance(a, mparser.BaseStringNode):
result.append(a.value)
else:
result.append(a)
if not isinstance(key, mparser.IdNode):
raise InterpreterException('Invalid kwargs format.')
return key.value
+
+def stringifyUserArguments(args: TYPE_var, subproject: SubProject, quote: bool = False) -> str:
+ if isinstance(args, str):
+ return f"'{args}'" if quote else args
+ elif isinstance(args, bool):
+ return 'true' if args else 'false'
+ elif isinstance(args, int):
+ return str(args)
+ elif isinstance(args, list):
+ return '[%s]' % ', '.join([stringifyUserArguments(x, subproject, True) for x in args])
+ elif isinstance(args, dict):
+ l = ['{} : {}'.format(stringifyUserArguments(k, subproject, True),
+ stringifyUserArguments(v, subproject, True)) for k, v in args.items()]
+ return '{%s}' % ', '.join(l)
+ elif isinstance(args, UserOption):
+ from .decorators import FeatureNew
+ FeatureNew.single_use('User option in string format', '1.3.0', subproject)
+ return stringifyUserArguments(args.printable_value(), subproject)
+ raise InvalidArguments('Value other than strings, integers, bools, options, dictionaries and lists thereof.')
# or an interpreter-based tool.
from __future__ import annotations
-from .. import mparser, mesonlib
-from .. import environment
+from .. import environment, mparser, mesonlib
from .baseobjects import (
InterpreterObject,
MesonInterpreterObject,
MutableInterpreterObject,
- InterpreterObjectTypeVar,
ObjectHolder,
IterableObject,
-
- TYPE_var,
+ ContextManagerObject,
HoldableTypes,
)
from .exceptions import (
+ BreakRequest,
+ ContinueRequest,
InterpreterException,
- InvalidCode,
InvalidArguments,
+ InvalidCode,
SubdirDoneRequest,
- ContinueRequest,
- BreakRequest
)
from .decorators import FeatureNew
from .disabler import Disabler, is_disabled
-from .helpers import default_resolve_key, flatten, resolve_second_level_holders
+from .helpers import default_resolve_key, flatten, resolve_second_level_holders, stringifyUserArguments
from .operator import MesonOperator
from ._unholder import _unholder
import textwrap
if T.TYPE_CHECKING:
- from .baseobjects import SubProject, TYPE_kwargs
+ from .baseobjects import InterpreterObjectTypeVar, SubProject, TYPE_kwargs, TYPE_var
from ..interpreter import Interpreter
-HolderMapType = T.Dict[
- T.Union[
- T.Type[mesonlib.HoldableObject],
- T.Type[int],
- T.Type[bool],
- T.Type[str],
- T.Type[list],
- T.Type[dict],
- ],
- # For some reason, this has to be a callable and can't just be ObjectHolder[InterpreterObjectTypeVar]
- T.Callable[[InterpreterObjectTypeVar, 'Interpreter'], ObjectHolder[InterpreterObjectTypeVar]]
-]
-
-FunctionType = T.Dict[
- str,
- T.Callable[[mparser.BaseNode, T.List[TYPE_var], T.Dict[str, TYPE_var]], TYPE_var]
-]
+ HolderMapType = T.Dict[
+ T.Union[
+ T.Type[mesonlib.HoldableObject],
+ T.Type[int],
+ T.Type[bool],
+ T.Type[str],
+ T.Type[list],
+ T.Type[dict],
+ ],
+ # For some reason, this has to be a callable and can't just be ObjectHolder[InterpreterObjectTypeVar]
+ T.Callable[[InterpreterObjectTypeVar, 'Interpreter'], ObjectHolder[InterpreterObjectTypeVar]]
+ ]
+
+ FunctionType = T.Dict[
+ str,
+ T.Callable[[mparser.BaseNode, T.List[TYPE_var], T.Dict[str, TYPE_var]], TYPE_var]
+ ]
+
+
+class InvalidCodeOnVoid(InvalidCode):
+
+ def __init__(self, op_type: str) -> None:
+ super().__init__(f'Cannot perform {op_type!r} operation on void statement.')
+
class InterpreterBase:
def __init__(self, source_root: str, subdir: str, subproject: 'SubProject'):
self.current_lineno = -1
# Current node set during a function call. This can be used as location
# when printing a warning message during a method call.
- self.current_node = None # type: mparser.BaseNode
+ self.current_node: mparser.BaseNode = None
# This is set to `version_string` when this statement is evaluated:
# meson.version().compare_version(version_string)
# If it was part of a if-clause, it is used to temporally override the
# current meson version target within that if-block.
- self.tmp_meson_version = None # type: T.Optional[str]
+ self.tmp_meson_version: T.Optional[str] = None
+
+ def handle_meson_version_from_ast(self, strict: bool = True) -> None:
+ # do nothing in an AST interpreter
+ return
def load_root_meson_file(self) -> None:
mesonfile = os.path.join(self.source_root, self.subdir, environment.build_filename)
assert isinstance(code, str)
try:
self.ast = mparser.Parser(code, mesonfile).parse()
- except mesonlib.MesonException as me:
+ self.handle_meson_version_from_ast()
+ except mparser.ParseException as me:
me.file = mesonfile
+ # try to detect parser errors from new syntax added by future
+ # meson versions, and just tell the user to update meson
+ self.ast = me.ast
+ self.handle_meson_version_from_ast()
raise me
def parse_project(self) -> None:
self.evaluate_codeblock(self.ast, end=1)
def sanity_check_ast(self) -> None:
- if not isinstance(self.ast, mparser.CodeBlockNode):
- raise InvalidCode('AST is of invalid type. Possibly a bug in the parser.')
- if not self.ast.lines:
- raise InvalidCode('No statements in code.')
- first = self.ast.lines[0]
- if not isinstance(first, mparser.FunctionNode) or first.func_name != 'project':
+ def _is_project(ast: mparser.CodeBlockNode) -> object:
+ if not isinstance(ast, mparser.CodeBlockNode):
+ raise InvalidCode('AST is of invalid type. Possibly a bug in the parser.')
+ if not ast.lines:
+ raise InvalidCode('No statements in code.')
+ first = ast.lines[0]
+ return isinstance(first, mparser.FunctionNode) and first.func_name.value == 'project'
+
+ if not _is_project(self.ast):
p = pathlib.Path(self.source_root).resolve()
found = p
for parent in p.parents:
if (parent / 'meson.build').is_file():
with open(parent / 'meson.build', encoding='utf-8') as f:
- if f.readline().startswith('project('):
- found = parent
- break
+ code = f.read()
+
+ try:
+ ast = mparser.Parser(code, 'empty').parse()
+ except mparser.ParseException:
+ continue
+
+ if _is_project(ast):
+ found = parent
+ break
else:
break
except Exception as e:
if getattr(e, 'lineno', None) is None:
# We are doing the equivalent to setattr here and mypy does not like it
- e.lineno = cur.lineno # type: ignore
- e.colno = cur.colno # type: ignore
+ # NOTE: self.current_node is continually updated during processing
+ e.lineno = self.current_node.lineno # type: ignore
+ e.colno = self.current_node.colno # type: ignore
e.file = os.path.join(self.source_root, self.subdir, environment.build_filename) # type: ignore
raise e
i += 1 # In THE FUTURE jump over blocks and stuff.
self.current_node = cur
if isinstance(cur, mparser.FunctionNode):
return self.function_call(cur)
+ elif isinstance(cur, mparser.PlusAssignmentNode):
+ self.evaluate_plusassign(cur)
elif isinstance(cur, mparser.AssignmentNode):
self.assignment(cur)
elif isinstance(cur, mparser.MethodNode):
return self.method_call(cur)
- elif isinstance(cur, mparser.StringNode):
- return self._holderify(cur.value)
+ elif isinstance(cur, mparser.BaseStringNode):
+ if isinstance(cur, mparser.MultilineFormatStringNode):
+ return self.evaluate_multiline_fstring(cur)
+ elif isinstance(cur, mparser.FormatStringNode):
+ return self.evaluate_fstring(cur)
+ else:
+ return self._holderify(cur.value)
elif isinstance(cur, mparser.BooleanNode):
return self._holderify(cur.value)
elif isinstance(cur, mparser.IfClauseNode):
return self.evaluate_arithmeticstatement(cur)
elif isinstance(cur, mparser.ForeachClauseNode):
self.evaluate_foreach(cur)
- elif isinstance(cur, mparser.PlusAssignmentNode):
- self.evaluate_plusassign(cur)
elif isinstance(cur, mparser.IndexNode):
return self.evaluate_indexing(cur)
elif isinstance(cur, mparser.TernaryNode):
return self.evaluate_ternary(cur)
- elif isinstance(cur, mparser.FormatStringNode):
- if isinstance(cur, mparser.MultilineFormatStringNode):
- return self.evaluate_multiline_fstring(cur)
- else:
- return self.evaluate_fstring(cur)
elif isinstance(cur, mparser.ContinueNode):
raise ContinueRequest()
elif isinstance(cur, mparser.BreakNode):
raise BreakRequest()
+ elif isinstance(cur, mparser.ParenthesizedNode):
+ return self.evaluate_statement(cur.inner)
+ elif isinstance(cur, mparser.TestCaseClauseNode):
+ return self.evaluate_testcase(cur)
else:
raise InvalidCode("Unknown statement.")
return None
@FeatureNew('dict', '0.47.0')
def evaluate_dictstatement(self, cur: mparser.DictNode) -> InterpreterObject:
def resolve_key(key: mparser.BaseNode) -> str:
- if not isinstance(key, mparser.StringNode):
+ if not isinstance(key, mparser.BaseStringNode):
FeatureNew.single_use('Dictionary entry using non literal key', '0.53.0', self.subproject)
- str_key = _unholder(self.evaluate_statement(key))
+ key_holder = self.evaluate_statement(key)
+ if key_holder is None:
+ raise InvalidArguments('Key cannot be void.')
+ str_key = _unholder(key_holder)
if not isinstance(str_key, str):
raise InvalidArguments('Key must be a string')
return str_key
def evaluate_notstatement(self, cur: mparser.NotNode) -> InterpreterObject:
v = self.evaluate_statement(cur.value)
+ if v is None:
+ raise InvalidCodeOnVoid('not')
if isinstance(v, Disabler):
return v
return self._holderify(v.operator_call(MesonOperator.NOT, None))
# statement evaluation.
self.tmp_meson_version = None
result = self.evaluate_statement(i.condition)
+ if result is None:
+ raise InvalidCodeOnVoid('if')
if isinstance(result, Disabler):
return result
if not isinstance(result, InterpreterObject):
- raise mesonlib.MesonBugException(f'Argument to not ({result}) is not an InterpreterObject but {type(result).__name__}.')
+ raise mesonlib.MesonBugException(f'Argument to if ({result}) is not an InterpreterObject but {type(result).__name__}.')
res = result.operator_call(MesonOperator.BOOL, None)
if not isinstance(res, bool):
raise InvalidCode(f'If clause {result!r} does not evaluate to true or false.')
mesonlib.project_meson_versions[self.subproject] = prev_meson_version
return None
if not isinstance(node.elseblock, mparser.EmptyNode):
- self.evaluate_codeblock(node.elseblock)
+ self.evaluate_codeblock(node.elseblock.block)
+ return None
+
+ def evaluate_testcase(self, node: mparser.TestCaseClauseNode) -> T.Optional[Disabler]:
+ result = self.evaluate_statement(node.condition)
+ if isinstance(result, Disabler):
+ return result
+ if not isinstance(result, ContextManagerObject):
+ raise InvalidCode(f'testcase clause {result!r} does not evaluate to a context manager.')
+ with result:
+ self.evaluate_codeblock(node.block)
return None
def evaluate_comparison(self, node: mparser.ComparisonNode) -> InterpreterObject:
val1 = self.evaluate_statement(node.left)
+ if val1 is None:
+ raise mesonlib.MesonException('Cannot compare a void statement on the left-hand side')
if isinstance(val1, Disabler):
return val1
val2 = self.evaluate_statement(node.right)
+ if val2 is None:
+ raise mesonlib.MesonException('Cannot compare a void statement on the right-hand side')
if isinstance(val2, Disabler):
return val2
def evaluate_andstatement(self, cur: mparser.AndNode) -> InterpreterObject:
l = self.evaluate_statement(cur.left)
+ if l is None:
+ raise mesonlib.MesonException('Cannot compare a void statement on the left-hand side')
if isinstance(l, Disabler):
return l
l_bool = l.operator_call(MesonOperator.BOOL, None)
if not l_bool:
return self._holderify(l_bool)
r = self.evaluate_statement(cur.right)
+ if r is None:
+ raise mesonlib.MesonException('Cannot compare a void statement on the right-hand side')
if isinstance(r, Disabler):
return r
return self._holderify(r.operator_call(MesonOperator.BOOL, None))
def evaluate_orstatement(self, cur: mparser.OrNode) -> InterpreterObject:
l = self.evaluate_statement(cur.left)
+ if l is None:
+ raise mesonlib.MesonException('Cannot compare a void statement on the left-hand side')
if isinstance(l, Disabler):
return l
l_bool = l.operator_call(MesonOperator.BOOL, None)
if l_bool:
return self._holderify(l_bool)
r = self.evaluate_statement(cur.right)
+ if r is None:
+ raise mesonlib.MesonException('Cannot compare a void statement on the right-hand side')
if isinstance(r, Disabler):
return r
return self._holderify(r.operator_call(MesonOperator.BOOL, None))
def evaluate_uminusstatement(self, cur: mparser.UMinusNode) -> InterpreterObject:
v = self.evaluate_statement(cur.value)
+ if v is None:
+ raise InvalidCodeOnVoid('unary minus')
if isinstance(v, Disabler):
return v
v.current_node = cur
r = self.evaluate_statement(cur.right)
if isinstance(r, Disabler):
return r
+ if l is None or r is None:
+ raise InvalidCodeOnVoid(cur.operation)
mapping: T.Dict[str, MesonOperator] = {
'add': MesonOperator.PLUS,
def evaluate_ternary(self, node: mparser.TernaryNode) -> T.Optional[InterpreterObject]:
assert isinstance(node, mparser.TernaryNode)
result = self.evaluate_statement(node.condition)
+ if result is None:
+ raise mesonlib.MesonException('Cannot use a void statement as condition for ternary operator.')
if isinstance(result, Disabler):
return result
result.current_node = node
return self.evaluate_fstring(node)
@FeatureNew('format strings', '0.58.0')
- def evaluate_fstring(self, node: mparser.FormatStringNode) -> InterpreterObject:
- assert isinstance(node, mparser.FormatStringNode)
-
+ def evaluate_fstring(self, node: T.Union[mparser.FormatStringNode, mparser.MultilineFormatStringNode]) -> InterpreterObject:
def replace(match: T.Match[str]) -> str:
var = str(match.group(1))
try:
val = _unholder(self.variables[var])
- if not isinstance(val, (str, int, float, bool)):
- raise InvalidCode(f'Identifier "{var}" does not name a formattable variable ' +
- '(has to be an integer, a string, a floating point number or a boolean).')
-
- return str(val)
+ if isinstance(val, (list, dict)):
+ FeatureNew.single_use('List or dictionary in f-string', '1.3.0', self.subproject, location=self.current_node)
+ try:
+ return stringifyUserArguments(val, self.subproject)
+ except InvalidArguments as e:
+ raise InvalidArguments(f'f-string: {str(e)}')
except KeyError:
raise InvalidCode(f'Identifier "{var}" does not name a variable.')
if tsize is None:
if isinstance(i, tuple):
raise mesonlib.MesonBugException(f'Iteration of {items} returned a tuple even though iter_tuple_size() is None')
- self.set_variable(node.varnames[0], self._holderify(i))
+ self.set_variable(node.varnames[0].value, self._holderify(i))
else:
if not isinstance(i, tuple):
raise mesonlib.MesonBugException(f'Iteration of {items} did not return a tuple even though iter_tuple_size() is {tsize}')
if len(i) != tsize:
raise mesonlib.MesonBugException(f'Iteration of {items} did not return a tuple even though iter_tuple_size() is {tsize}')
for j in range(tsize):
- self.set_variable(node.varnames[j], self._holderify(i[j]))
+ self.set_variable(node.varnames[j].value, self._holderify(i[j]))
try:
self.evaluate_codeblock(node.block)
except ContinueRequest:
def evaluate_plusassign(self, node: mparser.PlusAssignmentNode) -> None:
assert isinstance(node, mparser.PlusAssignmentNode)
- varname = node.var_name
+ varname = node.var_name.value
addition = self.evaluate_statement(node.value)
+ if addition is None:
+ raise InvalidCodeOnVoid('plus assign')
# Remember that all variables are immutable. We must always create a
# full new variable and then assign it.
def evaluate_indexing(self, node: mparser.IndexNode) -> InterpreterObject:
assert isinstance(node, mparser.IndexNode)
iobject = self.evaluate_statement(node.iobject)
+ if iobject is None:
+ raise InterpreterException('Tried to evaluate indexing on void.')
if isinstance(iobject, Disabler):
return iobject
- index = _unholder(self.evaluate_statement(node.index))
+ index_holder = self.evaluate_statement(node.index)
+ if index_holder is None:
+ raise InvalidArguments('Cannot use void statement as index.')
+ index = _unholder(index_holder)
- if iobject is None:
- raise InterpreterException('Tried to evaluate indexing on None')
iobject.current_node = node
return self._holderify(iobject.operator_call(MesonOperator.INDEX, index))
def function_call(self, node: mparser.FunctionNode) -> T.Optional[InterpreterObject]:
- func_name = node.func_name
+ func_name = node.func_name.value
(h_posargs, h_kwargs) = self.reduce_arguments(node.args)
(posargs, kwargs) = self._unholder_args(h_posargs, h_kwargs)
if is_disabled(posargs, kwargs) and func_name not in {'get_variable', 'set_variable', 'unset_variable', 'is_disabler'}:
func_args = flatten(posargs)
if not getattr(func, 'no-second-level-holder-flattening', False):
func_args, kwargs = resolve_second_level_holders(func_args, kwargs)
+ self.current_node = node
res = func(node, func_args, kwargs)
return self._holderify(res) if res is not None else None
else:
return None
def method_call(self, node: mparser.MethodNode) -> T.Optional[InterpreterObject]:
- invokable = node.source_object
+ invocable = node.source_object
obj: T.Optional[InterpreterObject]
- if isinstance(invokable, mparser.IdNode):
- object_display_name = f'variable "{invokable.value}"'
- obj = self.get_variable(invokable.value)
+ if isinstance(invocable, mparser.IdNode):
+ object_display_name = f'variable "{invocable.value}"'
+ obj = self.get_variable(invocable.value)
else:
- object_display_name = invokable.__class__.__name__
- obj = self.evaluate_statement(invokable)
- method_name = node.name
+ object_display_name = invocable.__class__.__name__
+ obj = self.evaluate_statement(invocable)
+ method_name = node.name.value
(h_args, h_kwargs) = self.reduce_arguments(node.args)
(args, kwargs) = self._unholder_args(h_args, h_kwargs)
if is_disabled(args, kwargs):
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 = node
+ 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
reduced_val = self.evaluate_statement(val)
if reduced_val is None:
raise InvalidArguments(f'Value of key {reduced_key} is void.')
+ self.current_node = key
if duplicate_key_error and reduced_key in reduced_kw:
raise InvalidArguments(duplicate_key_error.format(reduced_key))
reduced_kw[reduced_key] = reduced_val
Tried to assign values inside an argument list.
To specify a keyword argument, use : instead of =.
'''))
- var_name = node.var_name
+ var_name = node.var_name.value
if not isinstance(var_name, str):
raise InvalidArguments('Tried to assign value to a non-variable.')
value = self.evaluate_statement(node.value)
def set_variable(self, varname: str, variable: T.Union[TYPE_var, InterpreterObject], *, holderify: bool = False) -> None:
if variable is None:
- raise InvalidCode('Can not assign None to variable.')
+ raise InvalidCode('Can not assign void to variable.')
if holderify:
variable = self._holderify(variable)
else:
# See the License for the specific language governing permissions and
# limitations under the License.
+
+from .base import ArLikeLinker, RSPFileSyntax
from .detect import (
defaults,
guess_win_linker,
guess_nix_linker,
)
-from .linkers import (
- RSPFileSyntax,
-
- StaticLinker,
- VisualStudioLikeLinker,
- VisualStudioLinker,
- IntelVisualStudioLinker,
- AppleArLinker,
- ArLinker,
- ArmarLinker,
- DLinker,
- CcrxLinker,
- Xc16Linker,
- CompCertLinker,
- C2000Linker,
- TILinker,
- AIXArLinker,
- PGIStaticLinker,
- NvidiaHPC_StaticLinker,
-
- DynamicLinker,
- PosixDynamicLinkerMixin,
- GnuLikeDynamicLinkerMixin,
- AppleDynamicLinker,
- GnuDynamicLinker,
- GnuGoldDynamicLinker,
- GnuBFDDynamicLinker,
- LLVMDynamicLinker,
- MoldDynamicLinker,
- WASMDynamicLinker,
- CcrxDynamicLinker,
- Xc16DynamicLinker,
- CompCertDynamicLinker,
- C2000DynamicLinker,
- TIDynamicLinker,
- ArmDynamicLinker,
- ArmClangDynamicLinker,
- QualcommLLVMDynamicLinker,
- PGIDynamicLinker,
- NvidiaHPC_DynamicLinker,
- NAGDynamicLinker,
-
- VisualStudioLikeLinkerMixin,
- MSVCDynamicLinker,
- ClangClDynamicLinker,
- XilinkDynamicLinker,
- SolarisDynamicLinker,
- AIXDynamicLinker,
- OptlinkDynamicLinker,
- CudaLinker,
-
- prepare_rpaths,
- order_rpaths,
- evaluate_rpath,
-)
__all__ = [
+ # base.py
+ 'ArLikeLinker',
+ 'RSPFileSyntax',
+
# detect.py
'defaults',
'guess_win_linker',
'guess_nix_linker',
-
- # linkers.py
- 'RSPFileSyntax',
-
- 'StaticLinker',
- 'VisualStudioLikeLinker',
- 'VisualStudioLinker',
- 'IntelVisualStudioLinker',
- 'ArLinker',
- 'ArmarLinker',
- 'DLinker',
- 'CcrxLinker',
- 'Xc16Linker',
- 'CompCertLinker',
- 'C2000Linker',
- 'TILinker',
- 'AIXArLinker',
- 'AppleArLinker',
- 'PGIStaticLinker',
- 'NvidiaHPC_StaticLinker',
-
- 'DynamicLinker',
- 'PosixDynamicLinkerMixin',
- 'GnuLikeDynamicLinkerMixin',
- 'AppleDynamicLinker',
- 'GnuDynamicLinker',
- 'GnuGoldDynamicLinker',
- 'GnuBFDDynamicLinker',
- 'LLVMDynamicLinker',
- 'MoldDynamicLinker',
- 'WASMDynamicLinker',
- 'CcrxDynamicLinker',
- 'Xc16DynamicLinker',
- 'CompCertDynamicLinker',
- 'C2000DynamicLinker',
- 'TIDynamicLinker',
- 'ArmDynamicLinker',
- 'ArmClangDynamicLinker',
- 'QualcommLLVMDynamicLinker',
- 'PGIDynamicLinker',
- 'NvidiaHPC_DynamicLinker',
- 'NAGDynamicLinker',
-
- 'VisualStudioLikeLinkerMixin',
- 'MSVCDynamicLinker',
- 'ClangClDynamicLinker',
- 'XilinkDynamicLinker',
- 'SolarisDynamicLinker',
- 'AIXDynamicLinker',
- 'OptlinkDynamicLinker',
- 'CudaLinker',
-
- 'prepare_rpaths',
- 'order_rpaths',
- 'evaluate_rpath',
]
--- /dev/null
+# Copyright 2012-2023 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
+
+# https://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.
+
+# Core public classes for linkers.
+from __future__ import annotations
+
+import enum
+import typing as T
+
+if T.TYPE_CHECKING:
+ from ..environment import Environment
+
+
+@enum.unique
+class RSPFileSyntax(enum.Enum):
+
+ """Which RSP file syntax the compiler supports."""
+
+ MSVC = enum.auto()
+ GCC = enum.auto()
+
+
+class ArLikeLinker:
+ # POSIX requires supporting the dash, GNU permits omitting it
+ std_args = ['-csr']
+
+ def can_linker_accept_rsp(self) -> bool:
+ # armar / AIX can't accept arguments using the @rsp syntax
+ # in fact, only the 'ar' id can
+ return False
+
+ def get_std_link_args(self, env: 'Environment', is_thin: bool) -> T.List[str]:
+ return self.std_args
+
+ def get_output_args(self, target: str) -> T.List[str]:
+ return [target]
+
+ def rsp_file_syntax(self) -> RSPFileSyntax:
+ return RSPFileSyntax.GCC
from .. import mlog
from ..mesonlib import (
EnvironmentException,
- Popen_safe, join_args, search_version
-)
-from .linkers import (
- AppleDynamicLinker,
- GnuGoldDynamicLinker,
- GnuBFDDynamicLinker,
- MoldDynamicLinker,
- LLVMDynamicLinker,
- QualcommLLVMDynamicLinker,
- MSVCDynamicLinker,
- ClangClDynamicLinker,
- SolarisDynamicLinker,
- AIXDynamicLinker,
- OptlinkDynamicLinker,
+ Popen_safe, Popen_safe_logged, join_args, search_version
)
import re
comp_version: str, for_machine: MachineChoice, *,
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)
# Explicitly pass logo here so that we can get the version of link.exe
check_args += env.coredata.get_external_link_args(for_machine, comp_class.language)
- override = [] # type: T.List[str]
+ override: T.List[str] = []
value = env.lookup_binary_entry(for_machine, comp_class.language + '_ld')
if value is not None:
override = comp_class.use_linker_args(value[0], comp_version)
p, o, _ = Popen_safe(compiler + check_args)
if 'LLD' in o.split('\n', maxsplit=1)[0]:
if '(compatible with GNU linkers)' in o:
- return LLVMDynamicLinker(
+ return linkers.LLVMDynamicLinker(
compiler, for_machine, comp_class.LINKER_PREFIX,
override, version=search_version(o))
elif not invoked_directly:
- return ClangClDynamicLinker(
+ return linkers.ClangClDynamicLinker(
for_machine, override, exelist=compiler, prefix=comp_class.LINKER_PREFIX,
version=search_version(o), direct=False, machine=None)
if value is not None and invoked_directly:
compiler = value
- # We've already hanedled the non-direct case above
+ # We've already handled the non-direct case above
p, o, e = Popen_safe(compiler + check_args)
if 'LLD' in o.split('\n', maxsplit=1)[0]:
- return ClangClDynamicLinker(
+ return linkers.ClangClDynamicLinker(
for_machine, [],
prefix=comp_class.LINKER_PREFIX if use_linker_prefix else [],
exelist=compiler, version=search_version(o), direct=invoked_directly)
elif 'OPTLINK' in o:
# Optlink's stdout *may* begin with a \r character.
- return OptlinkDynamicLinker(compiler, for_machine, version=search_version(o))
+ return linkers.OptlinkDynamicLinker(compiler, 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)
else:
target = 'x86'
- return MSVCDynamicLinker(
+ return linkers.MSVCDynamicLinker(
for_machine, [], machine=target, exelist=compiler,
prefix=comp_class.LINKER_PREFIX if use_linker_prefix else [],
version=search_version(out), direct=invoked_directly)
:for_machine: which machine this linker targets
: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)
extra_args = extra_args or []
else:
check_args = comp_class.LINKER_PREFIX + ['--version'] + extra_args
- override = [] # type: T.List[str]
+ override: T.List[str] = []
value = env.lookup_binary_entry(for_machine, comp_class.language + '_ld')
if value is not None:
override = comp_class.use_linker_args(value[0], comp_version)
check_args += override
mlog.debug('-----')
- mlog.debug(f'Detecting linker via: {join_args(compiler + check_args)}')
- p, o, e = Popen_safe(compiler + check_args)
- mlog.debug(f'linker returned {p}')
- mlog.debug(f'linker stdout:\n{o}')
- mlog.debug(f'linker stderr:\n{e}')
+ p, o, e = Popen_safe_logged(compiler + check_args, msg='Detecting linker via')
v = search_version(o + e)
linker: DynamicLinker
if 'LLD' in o.split('\n', maxsplit=1)[0]:
- linker = LLVMDynamicLinker(
+ if isinstance(comp_class.LINKER_PREFIX, str):
+ cmd = compiler + override + [comp_class.LINKER_PREFIX + '-v'] + extra_args
+ else:
+ cmd = compiler + override + comp_class.LINKER_PREFIX + ['-v'] + extra_args
+ _, newo, newerr = Popen_safe_logged(cmd, msg='Detecting LLD linker via')
+
+ lld_cls: T.Type[DynamicLinker]
+ if 'ld64.lld' in newerr:
+ lld_cls = linkers.LLVMLD64DynamicLinker
+ else:
+ lld_cls = linkers.LLVMDynamicLinker
+
+ linker = lld_cls(
compiler, for_machine, comp_class.LINKER_PREFIX, override, version=v)
elif 'Snapdragon' in e and 'LLVM' in e:
- linker = QualcommLLVMDynamicLinker(
+ linker = linkers.QualcommLLVMDynamicLinker(
compiler, 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,
_, o, e = Popen_safe([linker_cmd, '--version'])
v = search_version(o)
- linker = LLVMDynamicLinker(compiler, for_machine, comp_class.LINKER_PREFIX, override, version=v)
+ linker = linkers.LLVMDynamicLinker(compiler, for_machine, comp_class.LINKER_PREFIX, override, version=v)
# first might be apple clang, second is for real gcc, the third is icc
elif e.endswith('(use -v to see invocation)\n') or 'macosx_version' in e or 'ld: unknown option:' in e:
if isinstance(comp_class.LINKER_PREFIX, str):
cmd = compiler + [comp_class.LINKER_PREFIX + '-v'] + extra_args
else:
cmd = compiler + comp_class.LINKER_PREFIX + ['-v'] + extra_args
- mlog.debug('-----')
- mlog.debug(f'Detecting Apple linker via: {join_args(cmd)}')
- _, newo, newerr = Popen_safe(cmd)
- mlog.debug(f'linker stdout:\n{newo}')
- mlog.debug(f'linker stderr:\n{newerr}')
+ _, newo, newerr = Popen_safe_logged(cmd, msg='Detecting Apple linker via')
for line in newerr.split('\n'):
- if 'PROJECT:ld' in line:
+ if 'PROJECT:ld' in line or 'PROJECT:dyld' in line:
v = line.split('-')[1]
break
else:
__failed_to_detect_linker(compiler, check_args, o, e)
- linker = AppleDynamicLinker(compiler, for_machine, comp_class.LINKER_PREFIX, override, version=v)
+ linker = linkers.AppleDynamicLinker(compiler, for_machine, comp_class.LINKER_PREFIX, override, version=v)
elif 'GNU' in o or 'GNU' in e:
- cls: T.Type[GnuDynamicLinker]
+ gnu_cls: T.Type[GnuDynamicLinker]
# this is always the only thing on stdout, except for swift
# which may or may not redirect the linker stdout to stderr
if o.startswith('GNU gold') or e.startswith('GNU gold'):
- cls = GnuGoldDynamicLinker
+ gnu_cls = linkers.GnuGoldDynamicLinker
elif o.startswith('mold') or e.startswith('mold'):
- cls = MoldDynamicLinker
+ gnu_cls = linkers.MoldDynamicLinker
else:
- cls = GnuBFDDynamicLinker
- linker = cls(compiler, for_machine, comp_class.LINKER_PREFIX, override, version=v)
+ gnu_cls = linkers.GnuBFDDynamicLinker
+ linker = gnu_cls(compiler, 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:
break
else:
v = 'unknown version'
- linker = SolarisDynamicLinker(
+ linker = linkers.SolarisDynamicLinker(
compiler, for_machine, comp_class.LINKER_PREFIX, override,
version=v)
elif 'ld: 0706-012 The -- flag is not recognized' in e:
_, _, e = Popen_safe(compiler + [comp_class.LINKER_PREFIX + '-V'] + extra_args)
else:
_, _, e = Popen_safe(compiler + comp_class.LINKER_PREFIX + ['-V'] + extra_args)
- linker = AIXDynamicLinker(
+ linker = linkers.AIXDynamicLinker(
compiler, for_machine, comp_class.LINKER_PREFIX, override,
version=search_version(e))
else:
# 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 abc
-import enum
import os
import typing as T
+import re
+from .base import ArLikeLinker, RSPFileSyntax
from .. import mesonlib
from ..mesonlib import EnvironmentException, MesonException
from ..arglist import CompilerArgs
from ..mesonlib import MachineChoice
-@enum.unique
-class RSPFileSyntax(enum.Enum):
-
- """Which RSP file syntax the compiler supports."""
-
- MSVC = enum.auto()
- GCC = enum.auto()
-
-
class StaticLinker:
id: str
def native_args_to_unix(cls, args: T.List[str]) -> T.List[str]:
return args[:]
- def get_link_debugfile_name(self, targetfile: str) -> str:
+ def get_link_debugfile_name(self, targetfile: str) -> T.Optional[str]:
return None
def get_link_debugfile_args(self, targetfile: str) -> T.List[str]:
raise EnvironmentException(f'{self.id} does not implement rsp format, this shouldn\'t be called')
-class VisualStudioLikeLinker:
+class DynamicLinker(metaclass=abc.ABCMeta):
+
+ """Base class for dynamic linkers."""
+
+ _BUILDTYPE_ARGS: T.Dict[str, T.List[str]] = {
+ 'plain': [],
+ 'debug': [],
+ 'debugoptimized': [],
+ 'release': [],
+ 'minsize': [],
+ 'custom': [],
+ }
+
+ @abc.abstractproperty
+ def id(self) -> str:
+ pass
+
+ def _apply_prefix(self, arg: T.Union[str, T.List[str]]) -> T.List[str]:
+ args = [arg] if isinstance(arg, str) else arg
+ if self.prefix_arg is None:
+ return args
+ elif isinstance(self.prefix_arg, str):
+ return [self.prefix_arg + arg for arg in args]
+ ret: T.List[str] = []
+ for arg in args:
+ ret += self.prefix_arg + [arg]
+ return ret
+
+ 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'):
+ self.exelist = exelist
+ self.for_machine = for_machine
+ self.version = version
+ self.prefix_arg = prefix_arg
+ self.always_args = always_args
+ self.machine: T.Optional[str] = None
+
+ def __repr__(self) -> str:
+ return '<{}: v{} `{}`>'.format(type(self).__name__, self.version, ' '.join(self.exelist))
+
+ def get_id(self) -> str:
+ return self.id
+
+ def get_version_string(self) -> str:
+ return f'({self.id} {self.version})'
+
+ def get_exelist(self) -> T.List[str]:
+ return self.exelist.copy()
+
+ def get_accepts_rsp(self) -> bool:
+ # rsp files are only used when building on Windows because we want to
+ # avoid issues with quoting and max argument length
+ return mesonlib.is_windows()
+
+ def rsp_file_syntax(self) -> RSPFileSyntax:
+ """The format of the RSP file that this compiler supports.
+
+ If `self.can_linker_accept_rsp()` returns True, then this needs to
+ be implemented
+ """
+ return RSPFileSyntax.GCC
+
+ def get_always_args(self) -> T.List[str]:
+ return self.always_args.copy()
+
+ def get_lib_prefix(self) -> str:
+ return ''
+
+ # XXX: is use_ldflags a compiler or a linker attribute?
+
+ def get_option_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
+ return []
+
+ def has_multi_arguments(self, args: T.List[str], env: 'Environment') -> 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]:
+ '''Name of debug file written out (see below)'''
+ return None
+
+ def get_debugfile_args(self, targetfile: str) -> T.List[str]:
+ """Some compilers (MSVC) write debug into a separate file.
+
+ This method takes the target object path and returns a list of
+ commands to append to the linker invocation to control where that
+ file is written.
+ """
+ return []
+
+ def get_std_shared_lib_args(self) -> T.List[str]:
+ return []
+
+ def get_std_shared_module_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
+ return self.get_std_shared_lib_args()
+
+ def get_pie_args(self) -> T.List[str]:
+ # TODO: this really needs to take a boolean and return the args to
+ # disable pie, otherwise it only acts to enable pie if pie *isn't* the
+ # default.
+ raise EnvironmentException(f'Linker {self.id} does not support position-independent executable')
+
+ def get_lto_args(self) -> T.List[str]:
+ return []
+
+ def get_thinlto_cache_args(self, path: str) -> T.List[str]:
+ return []
+
+ def sanitizer_args(self, value: str) -> T.List[str]:
+ return []
+
+ def get_buildtype_args(self, buildtype: str) -> T.List[str]:
+ # We can override these in children by just overriding the
+ # _BUILDTYPE_ARGS value.
+ return self._BUILDTYPE_ARGS[buildtype]
+
+ def get_asneeded_args(self) -> T.List[str]:
+ return []
+
+ def get_link_whole_for(self, args: T.List[str]) -> T.List[str]:
+ raise EnvironmentException(
+ f'Linker {self.id} does not support link_whole')
+
+ def get_allow_undefined_args(self) -> T.List[str]:
+ raise EnvironmentException(
+ f'Linker {self.id} does not support allow undefined')
+
+ @abc.abstractmethod
+ def get_output_args(self, outputname: str) -> T.List[str]:
+ pass
+
+ def get_coverage_args(self) -> T.List[str]:
+ raise EnvironmentException(f"Linker {self.id} doesn't implement coverage data generation.")
+
+ @abc.abstractmethod
+ def get_search_args(self, dirname: str) -> T.List[str]:
+ pass
+
+ def export_dynamic_args(self, env: 'Environment') -> T.List[str]:
+ return []
+
+ def import_library_args(self, implibname: str) -> T.List[str]:
+ """The name of the outputted import library.
+
+ This implementation is used only on Windows by compilers that use GNU ld
+ """
+ return []
+
+ def thread_flags(self, env: 'Environment') -> T.List[str]:
+ return []
+
+ def no_undefined_args(self) -> T.List[str]:
+ """Arguments to error if there are any undefined symbols at link time.
+
+ This is the inverse of get_allow_undefined_args().
+
+ TODO: A future cleanup might merge this and
+ get_allow_undefined_args() into a single method taking a
+ boolean
+ """
+ return []
+
+ def fatal_warnings(self) -> T.List[str]:
+ """Arguments to make all warnings errors."""
+ return []
+
+ def headerpad_args(self) -> T.List[str]:
+ # Only used by the Apple linker
+ return []
+
+ def get_win_subsystem_args(self, value: str) -> T.List[str]:
+ # Only used if supported by the dynamic linker and
+ # only when targeting Windows
+ return []
+
+ 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]]:
+ return ([], set())
+
+ 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]:
+ return []
+
+ def get_archive_name(self, filename: str) -> str:
+ #Only used by AIX.
+ return str()
+
+ def get_command_to_archive_shlib(self) -> T.List[str]:
+ #Only used by AIX.
+ return []
+
+
+if T.TYPE_CHECKING:
+ StaticLinkerBase = StaticLinker
+ DynamicLinkerBase = DynamicLinker
+else:
+ StaticLinkerBase = DynamicLinkerBase = object
+
+
+class VisualStudioLikeLinker(StaticLinkerBase):
always_args = ['/NOLOGO']
def __init__(self, machine: str):
return self.always_args.copy()
def get_output_args(self, target: str) -> T.List[str]:
- args = [] # type: T.List[str]
+ args: T.List[str] = []
if self.machine:
args += ['/MACHINE:' + self.machine]
args += ['/OUT:' + target]
"""Microsoft's lib static linker."""
+ id = 'lib'
+
def __init__(self, exelist: T.List[str], machine: str):
StaticLinker.__init__(self, exelist)
VisualStudioLikeLinker.__init__(self, machine)
"""Intel's xilib static linker."""
+ id = 'xilib'
+
def __init__(self, exelist: T.List[str], machine: str):
StaticLinker.__init__(self, exelist)
VisualStudioLikeLinker.__init__(self, machine)
-class ArLikeLinker(StaticLinker):
- # POSIX requires supporting the dash, GNU permits omitting it
- std_args = ['-csr']
-
- def can_linker_accept_rsp(self) -> bool:
- # armar / AIX can't accept arguments using the @rsp syntax
- # in fact, only the 'ar' id can
- return False
-
- def get_std_link_args(self, env: 'Environment', is_thin: bool) -> T.List[str]:
- return self.std_args
-
- def get_output_args(self, target: str) -> T.List[str]:
- return [target]
-
- def rsp_file_syntax(self) -> RSPFileSyntax:
- return RSPFileSyntax.GCC
-
-
-class ArLinker(ArLikeLinker):
+class ArLinker(ArLikeLinker, StaticLinker):
id = 'ar'
def __init__(self, for_machine: mesonlib.MachineChoice, exelist: T.List[str]):
id = 'applear'
-class ArmarLinker(ArLikeLinker):
+class ArmarLinker(ArLikeLinker, StaticLinker):
id = 'armar'
id = 'ar2000'
-class AIXArLinker(ArLikeLinker):
+class AIXArLinker(ArLikeLinker, StaticLinker):
id = 'aixar'
std_args = ['-csr', '-Xany']
+class MetrowerksStaticLinker(StaticLinker):
+
+ def can_linker_accept_rsp(self) -> bool:
+ return True
+
+ def get_linker_always_args(self) -> T.List[str]:
+ return ['-library']
+
+ def get_output_args(self, target: str) -> T.List[str]:
+ return ['-o', target]
+
+ def rsp_file_syntax(self) -> RSPFileSyntax:
+ return RSPFileSyntax.GCC
+
+
+class MetrowerksStaticLinkerARM(MetrowerksStaticLinker):
+ id = 'mwldarm'
+
+
+class MetrowerksStaticLinkerEmbeddedPowerPC(MetrowerksStaticLinker):
+ id = 'mwldeppc'
+
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
else:
return os.path.relpath(os.path.join(build_dir, p), os.path.join(build_dir, from_dir))
-class DynamicLinker(metaclass=abc.ABCMeta):
- """Base class for dynamic linkers."""
-
- _BUILDTYPE_ARGS = {
- 'plain': [],
- 'debug': [],
- 'debugoptimized': [],
- 'release': [],
- 'minsize': [],
- 'custom': [],
- } # type: T.Dict[str, T.List[str]]
-
- @abc.abstractproperty
- def id(self) -> str:
- pass
-
- def _apply_prefix(self, arg: T.Union[str, T.List[str]]) -> T.List[str]:
- args = [arg] if isinstance(arg, str) else arg
- if self.prefix_arg is None:
- return args
- elif isinstance(self.prefix_arg, str):
- return [self.prefix_arg + arg for arg in args]
- ret = []
- for arg in args:
- ret += self.prefix_arg + [arg]
- return ret
-
- 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'):
- self.exelist = exelist
- self.for_machine = for_machine
- self.version = version
- self.prefix_arg = prefix_arg
- self.always_args = always_args
- self.machine = None # type: T.Optional[str]
-
- def __repr__(self) -> str:
- return '<{}: v{} `{}`>'.format(type(self).__name__, self.version, ' '.join(self.exelist))
-
- def get_id(self) -> str:
- return self.id
-
- def get_version_string(self) -> str:
- return f'({self.id} {self.version})'
-
- def get_exelist(self) -> T.List[str]:
- return self.exelist.copy()
-
- def get_accepts_rsp(self) -> bool:
- # rsp files are only used when building on Windows because we want to
- # avoid issues with quoting and max argument length
- return mesonlib.is_windows()
-
- def rsp_file_syntax(self) -> RSPFileSyntax:
- """The format of the RSP file that this compiler supports.
-
- If `self.can_linker_accept_rsp()` returns True, then this needs to
- be implemented
- """
- return RSPFileSyntax.GCC
-
- def get_always_args(self) -> T.List[str]:
- return self.always_args.copy()
-
- def get_lib_prefix(self) -> str:
- return ''
-
- # XXX: is use_ldflags a compiler or a linker attribute?
-
- def get_option_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
- return []
-
- def has_multi_arguments(self, args: T.List[str], env: 'Environment') -> T.Tuple[bool, bool]:
- raise EnvironmentException(f'Language {self.id} does not support has_multi_link_arguments.')
-
- def get_debugfile_name(self, targetfile: str) -> str:
- '''Name of debug file written out (see below)'''
- return None
-
- def get_debugfile_args(self, targetfile: str) -> T.List[str]:
- """Some compilers (MSVC) write debug into a separate file.
-
- This method takes the target object path and returns a list of
- commands to append to the linker invocation to control where that
- file is written.
- """
- return []
-
- def get_std_shared_lib_args(self) -> T.List[str]:
- return []
-
- def get_std_shared_module_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
- return self.get_std_shared_lib_args()
-
- def get_pie_args(self) -> T.List[str]:
- # TODO: this really needs to take a boolean and return the args to
- # disable pie, otherwise it only acts to enable pie if pie *isn't* the
- # default.
- raise EnvironmentException(f'Linker {self.id} does not support position-independent executable')
-
- def get_lto_args(self) -> T.List[str]:
- return []
-
- def get_thinlto_cache_args(self, path: str) -> T.List[str]:
- return []
-
- def sanitizer_args(self, value: str) -> T.List[str]:
- return []
-
- def get_buildtype_args(self, buildtype: str) -> T.List[str]:
- # We can override these in children by just overriding the
- # _BUILDTYPE_ARGS value.
- return self._BUILDTYPE_ARGS[buildtype]
-
- def get_asneeded_args(self) -> T.List[str]:
- return []
-
- def get_link_whole_for(self, args: T.List[str]) -> T.List[str]:
- raise EnvironmentException(
- f'Linker {self.id} does not support link_whole')
-
- def get_allow_undefined_args(self) -> T.List[str]:
- raise EnvironmentException(
- f'Linker {self.id} does not support allow undefined')
-
- @abc.abstractmethod
- def get_output_args(self, outname: str) -> T.List[str]:
- pass
-
- def get_coverage_args(self) -> T.List[str]:
- raise EnvironmentException(f"Linker {self.id} doesn't implement coverage data generation.")
-
- @abc.abstractmethod
- def get_search_args(self, dirname: str) -> T.List[str]:
- pass
-
- def export_dynamic_args(self, env: 'Environment') -> T.List[str]:
- return []
-
- def import_library_args(self, implibname: str) -> T.List[str]:
- """The name of the outputted import library.
-
- This implementation is used only on Windows by compilers that use GNU ld
- """
- return []
-
- def thread_flags(self, env: 'Environment') -> T.List[str]:
- return []
-
- def no_undefined_args(self) -> T.List[str]:
- """Arguments to error if there are any undefined symbols at link time.
-
- This is the inverse of get_allow_undefined_args().
-
- TODO: A future cleanup might merge this and
- get_allow_undefined_args() into a single method taking a
- boolean
- """
- return []
-
- def fatal_warnings(self) -> T.List[str]:
- """Arguments to make all warnings errors."""
- return []
-
- def headerpad_args(self) -> T.List[str]:
- # Only used by the Apple linker
- return []
-
- def get_gui_app_args(self, value: bool) -> T.List[str]:
- # Only used by VisualStudioLikeLinkers
- return []
-
- def get_win_subsystem_args(self, value: str) -> T.List[str]:
- # Only used if supported by the dynamic linker and
- # only when targeting Windows
- return []
-
- 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]]:
- return ([], set())
-
- 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]:
- return []
-
-
-class PosixDynamicLinkerMixin:
+class PosixDynamicLinkerMixin(DynamicLinkerBase):
"""Mixin class for POSIX-ish linkers.
GNU-like that it makes sense to split this out.
"""
- def get_output_args(self, outname: str) -> T.List[str]:
- return ['-o', outname]
+ def get_output_args(self, outputname: str) -> T.List[str]:
+ return ['-o', outputname]
def get_std_shared_lib_args(self) -> T.List[str]:
return ['-shared']
return ['-L' + dirname]
-class GnuLikeDynamicLinkerMixin:
+class GnuLikeDynamicLinkerMixin(DynamicLinkerBase):
"""Mixin class for dynamic linkers that provides gnu-like interface.
for_machine = MachineChoice.HOST
def _apply_prefix(self, arg: T.Union[str, T.List[str]]) -> T.List[str]: ...
- _BUILDTYPE_ARGS = {
+ _BUILDTYPE_ARGS: T.Dict[str, T.List[str]] = {
'plain': [],
'debug': [],
'debugoptimized': [],
'release': ['-O1'],
'minsize': [],
'custom': [],
- } # type: T.Dict[str, T.List[str]]
+ }
+
+ _SUBSYSTEMS: T.Dict[str, str] = {
+ "native": "1",
+ "windows": "windows",
+ "console": "console",
+ "posix": "7",
+ "efi_application": "10",
+ "efi_boot_service_driver": "11",
+ "efi_runtime_driver": "12",
+ "efi_rom": "13",
+ "boot_application": "16",
+ }
def get_buildtype_args(self, buildtype: str) -> T.List[str]:
# We can override these in children by just overriding the
return ([], set())
if not rpath_paths and not install_rpath and not build_rpath:
return ([], set())
- args = []
+ args: T.List[str] = []
origin_placeholder = '$ORIGIN'
processed_rpaths = prepare_rpaths(rpath_paths, build_dir, from_dir)
# Need to deduplicate rpaths, as macOS's install_name_tool
# is *very* allergic to duplicate -delete_rpath arguments
# when calling depfixer on installation.
all_paths = mesonlib.OrderedSet([os.path.join(origin_placeholder, p) for p in processed_rpaths])
- rpath_dirs_to_remove = set()
+ rpath_dirs_to_remove: T.Set[bytes] = set()
for p in all_paths:
rpath_dirs_to_remove.add(p.encode('utf8'))
# Build_rpath is used as-is (it is usually absolute).
# as well, and is always accepted, so we manually map the
# other types here. List of all types:
# https://github.com/wine-mirror/wine/blob/3ded60bd1654dc689d24a23305f4a93acce3a6f2/include/winnt.h#L2492-L2507
- subsystems = {
- "native": "1",
- "windows": "windows",
- "console": "console",
- "posix": "7",
- "efi_application": "10",
- "efi_boot_service_driver": "11",
- "efi_runtime_driver": "12",
- "efi_rom": "13",
- "boot_application": "16",
- }
versionsuffix = None
if ',' in value:
value, versionsuffix = value.split(',', 1)
- newvalue = subsystems.get(value)
+ newvalue = self._SUBSYSTEMS.get(value)
if newvalue is not None:
if versionsuffix is not None:
newvalue += f':{versionsuffix}'
return []
def get_link_whole_for(self, args: T.List[str]) -> T.List[str]:
- result = [] # type: T.List[str]
+ result: T.List[str] = []
for a in args:
result.extend(self._apply_prefix('-force_load'))
result.append(a)
install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]:
if not rpath_paths and not install_rpath and not build_rpath:
return ([], set())
- args = []
+ args: T.List[str] = []
# @loader_path is the equivalent of $ORIGIN on macOS
# https://stackoverflow.com/q/26280738
origin_placeholder = '@loader_path'
return ["-Wl,-cache_path_lto," + path]
+class LLVMLD64DynamicLinker(AppleDynamicLinker):
+
+ id = 'ld64.lld'
+
+
class GnuDynamicLinker(GnuLikeDynamicLinkerMixin, PosixDynamicLinkerMixin, DynamicLinker):
"""Representation of GNU ld.bfd and ld.gold."""
super().__init__(exelist, for_machine, prefix_arg, always_args, version=version)
# Some targets don't seem to support this argument (windows, wasm, ...)
- _, _, e = mesonlib.Popen_safe(self.exelist + self._apply_prefix('--allow-shlib-undefined'))
+ _, _, e = mesonlib.Popen_safe(self.exelist + always_args + self._apply_prefix('--allow-shlib-undefined'))
# Versions < 9 do not have a quoted argument
self.has_allow_shlib_undefined = ('unknown argument: --allow-shlib-undefined' not in e) and ("unknown argument: '--allow-shlib-undefined'" not in e)
def get_thinlto_cache_args(self, path: str) -> T.List[str]:
return ['-Wl,--thinlto-cache-dir=' + path]
+ def get_win_subsystem_args(self, value: str) -> T.List[str]:
+ # lld does not support a numeric subsystem value
+ version = None
+ if ',' in value:
+ value, version = value.split(',', 1)
+ if value in self._SUBSYSTEMS:
+ if version is not None:
+ value += f':{version}'
+ return self._apply_prefix([f'--subsystem,{value}'])
+ else:
+ raise mesonlib.MesonBugException(f'win_subsystem: {value} not handled in lld linker. This should not be possible.')
+
class WASMDynamicLinker(GnuLikeDynamicLinkerMixin, PosixDynamicLinkerMixin, DynamicLinker):
id = 'ld.wasm'
def get_allow_undefined_args(self) -> T.List[str]:
- return ['-s', 'ERROR_ON_UNDEFINED_SYMBOLS=0']
+ return ['-sERROR_ON_UNDEFINED_SYMBOLS=0']
def no_undefined_args(self) -> T.List[str]:
- return ['-s', 'ERROR_ON_UNDEFINED_SYMBOLS=1']
+ 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]:
class CcrxDynamicLinker(DynamicLinker):
- """Linker for Renesis CCrx compiler."""
+ """Linker for Renesas CCrx compiler."""
id = 'rlink'
install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]:
if not rpath_paths and not install_rpath and not build_rpath:
return ([], set())
- args = []
+ 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])
NvidiaHPC_StaticLinker = PGIStaticLinker
-class VisualStudioLikeLinkerMixin:
+class VisualStudioLikeLinkerMixin(DynamicLinkerBase):
- """Mixin class for for dynamic linkers that act like Microsoft's link.exe."""
+ """Mixin class for dynamic linkers that act like Microsoft's link.exe."""
if T.TYPE_CHECKING:
for_machine = MachineChoice.HOST
def _apply_prefix(self, arg: T.Union[str, T.List[str]]) -> T.List[str]: ...
- _BUILDTYPE_ARGS = {
+ _BUILDTYPE_ARGS: T.Dict[str, T.List[str]] = {
'plain': [],
'debug': [],
'debugoptimized': [],
'release': ['/OPT:REF'],
'minsize': ['/INCREMENTAL:NO', '/OPT:REF'],
'custom': [],
- } # type: T.Dict[str, T.List[str]]
+ }
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'):
# 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) # type: ignore
+ super().__init__(exelist, for_machine, prefix_arg, always_args, version=version)
self.machine = machine
self.direct = direct
return self._apply_prefix(['/MACHINE:' + self.machine, '/OUT:' + outputname])
def get_always_args(self) -> T.List[str]:
- parent = super().get_always_args() # type: ignore
- return self._apply_prefix('/nologo') + T.cast('T.List[str]', parent)
+ parent = super().get_always_args()
+ return self._apply_prefix('/nologo') + parent
def get_search_args(self, dirname: str) -> T.List[str]:
return self._apply_prefix('/LIBPATH:' + dirname)
return self._apply_prefix('/DLL')
def get_debugfile_name(self, targetfile: str) -> str:
- basename = targetfile.rsplit('.', maxsplit=1)[0]
- return basename + '.pdb'
+ return targetfile
def get_debugfile_args(self, targetfile: str) -> T.List[str]:
return self._apply_prefix(['/DEBUG', '/PDB:' + self.get_debugfile_name(targetfile)])
def get_link_whole_for(self, args: T.List[str]) -> T.List[str]:
# Only since VS2015
args = mesonlib.listify(args)
- l = [] # T.List[str]
+ l: T.List[str] = []
for a in args:
l.extend(self._apply_prefix('/WHOLEARCHIVE:' + a))
return l
prefix, always_args, machine=machine, version=version, direct=direct)
def get_always_args(self) -> T.List[str]:
- return self._apply_prefix(['/nologo', '/release']) + super().get_always_args()
-
- def get_gui_app_args(self, value: bool) -> T.List[str]:
- return self.get_win_subsystem_args("windows" if value else "console")
+ return self._apply_prefix(['/release']) + super().get_always_args()
def get_win_subsystem_args(self, value: str) -> T.List[str]:
return self._apply_prefix([f'/SUBSYSTEM:{value.upper()}'])
return super().get_output_args(outputname)
- def get_gui_app_args(self, value: bool) -> T.List[str]:
- return self.get_win_subsystem_args("windows" if value else "console")
-
def get_win_subsystem_args(self, value: str) -> T.List[str]:
return self._apply_prefix([f'/SUBSYSTEM:{value.upper()}'])
direct: bool = True):
super().__init__(['xilink.exe'], for_machine, '', always_args, version=version)
- def get_gui_app_args(self, value: bool) -> T.List[str]:
- return self.get_win_subsystem_args("windows" if value else "console")
-
def get_win_subsystem_args(self, value: str) -> T.List[str]:
return self._apply_prefix([f'/SUBSYSTEM:{value.upper()}'])
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 = set()
+ rpath_dirs_to_remove: T.Set[bytes] = set()
for p in all_paths:
rpath_dirs_to_remove.add(p.encode('utf8'))
if build_rpath != '':
def get_allow_undefined_args(self) -> T.List[str]:
return self._apply_prefix(['-berok'])
+ def get_archive_name(self, filename: str) -> str:
+ # In AIX we allow the shared library name to have the lt_version and so_version.
+ # But the archive name must just be .a .
+ # For Example shared object can have the name libgio.so.0.7200.1 but the archive
+ # must have the name libgio.a having libgio.a (libgio.so.0.7200.1) in the
+ # archive. This regular expression is to do the same.
+ filename = re.sub('[.][a]([.]?([0-9]+))*([.]?([a-z]+))*', '.a', filename.replace('.so', '.a'))
+ return filename
+
+ def get_command_to_archive_shlib(self) -> T.List[str]:
+ # Archive shared library object and remove the shared library object,
+ # since it already exists in the archive.
+ command = ['ar', '-q', '-v', '$out', '$in', '&&', 'rm', '-f', '$in']
+ return command
+
def get_link_whole_for(self, args: T.List[str]) -> T.List[str]:
# AIX's linker always links the whole archive: "The ld command
# processes all input files in the same manner, whether they are
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]]:
- all_paths = mesonlib.OrderedSet() # type: mesonlib.OrderedSet[str]
+ 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)
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_dirs(self.for_machine)
+ sys_path = env.get_compiler_system_lib_dirs(self.for_machine)
if len(sys_path) == 0:
- # get_compiler_system_dirs doesn't support our compiler.
+ # get_compiler_system_lib_dirs doesn't support our compiler.
# Use the default system library path
all_paths.update(['/usr/lib', '/lib'])
else:
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]:
return []
+
+
+class MetrowerksLinker(DynamicLinker):
+
+ def __init__(self, exelist: T.List[str], for_machine: mesonlib.MachineChoice,
+ *, version: str = 'unknown version'):
+ super().__init__(exelist, for_machine, '', [],
+ version=version)
+
+ def fatal_warnings(self) -> T.List[str]:
+ return ['-w', 'error']
+
+ def get_allow_undefined_args(self) -> T.List[str]:
+ return []
+
+ def get_accepts_rsp(self) -> bool:
+ return True
+
+ def get_lib_prefix(self) -> str:
+ return ""
+
+ def get_linker_always_args(self) -> T.List[str]:
+ return []
+
+ def get_output_args(self, outputname: str) -> T.List[str]:
+ return ['-o', outputname]
+
+ def get_search_args(self, dirname: str) -> T.List[str]:
+ return self._apply_prefix('-L' + dirname)
+
+ def invoked_by_compiler(self) -> bool:
+ return False
+
+ def rsp_file_syntax(self) -> RSPFileSyntax:
+ return RSPFileSyntax.GCC
+
+
+class MetrowerksLinkerARM(MetrowerksLinker):
+ id = 'mwldarm'
+
+
+class MetrowerksLinkerEmbeddedPowerPC(MetrowerksLinker):
+ id = 'mwldeppc'
# 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
"""Entrypoint script for backend agnostic compile."""
from . import mlog
from . import mesonlib
-from . import coredata
from .mesonlib import MesonException, RealPathAction, join_args, setup_vsenv
from mesonbuild.environment import detect_ninja
from mesonbuild.coredata import UserArrayOption
import argparse
def array_arg(value: str) -> T.List[str]:
- return UserArrayOption(None, value, allow_dups=True, user_input=True).value
+ return UserArrayOption.listify_value(value)
def validate_builddir(builddir: Path) -> None:
if not (builddir / 'meson-private' / 'coredata.dat').is_file():
with path_to_intro.open(encoding='utf-8') as f:
schema = json.load(f)
- parsed_data = defaultdict(list) # type: T.Dict[str, T.List[dict]]
+ parsed_data: T.Dict[str, T.List[dict]] = defaultdict(list)
for target in schema:
parsed_data[target['name']] += [target]
return parsed_data
class ParsedTargetName:
full_name = ''
+ base_name = ''
name = ''
type = ''
path = ''
+ suffix = ''
def __init__(self, target: str):
self.full_name = target
else:
self.name = split[0]
+ split = self.name.rsplit('.', 1)
+ if len(split) > 1:
+ self.base_name = split[0]
+ self.suffix = split[1]
+ else:
+ self.base_name = split[0]
+
@staticmethod
def _is_valid_type(type: str) -> bool:
# Amend docs in Commands.md when editing this list
'shared_library',
'shared_module',
'custom',
+ 'alias',
'run',
'jar',
}
return type in allowed_types
def get_target_from_intro_data(target: ParsedTargetName, builddir: Path, introspect_data: T.Dict[str, T.Any]) -> T.Dict[str, T.Any]:
- if target.name not in introspect_data:
+ if target.name not in introspect_data and target.base_name not in introspect_data:
raise MesonException(f'Can\'t invoke target `{target.full_name}`: target not found')
intro_targets = introspect_data[target.name]
- found_targets = [] # type: T.List[T.Dict[str, T.Any]]
+ # if target.name doesn't find anything, try just the base name
+ if not intro_targets:
+ intro_targets = introspect_data[target.base_name]
+ found_targets: T.List[T.Dict[str, T.Any]] = []
resolved_bdir = builddir.resolve()
- if not target.type and not target.path:
+ if not target.type and not target.path and not target.suffix:
found_targets = intro_targets
else:
for intro_target in intro_targets:
- if (intro_target['subproject'] or
- (target.type and target.type != intro_target['type'].replace(' ', '_')) or
- (target.path
- and intro_target['filename'] != 'no_name'
- and Path(target.path) != Path(intro_target['filename'][0]).relative_to(resolved_bdir).parent)):
+ # Parse out the name from the id if needed
+ intro_target_name = intro_target['name']
+ split = intro_target['id'].rsplit('@', 1)
+ if len(split) > 1:
+ split = split[0].split('@@', 1)
+ if len(split) > 1:
+ intro_target_name = split[1]
+ else:
+ intro_target_name = split[0]
+ if ((target.type and target.type != intro_target['type'].replace(' ', '_')) or
+ (target.name != intro_target_name) or
+ (target.path and intro_target['filename'] != 'no_name' and
+ Path(target.path) != Path(intro_target['filename'][0]).relative_to(resolved_bdir).parent)):
continue
found_targets += [intro_target]
elif len(found_targets) > 1:
suggestions: T.List[str] = []
for i in found_targets:
- p = Path(i['filename'][0]).relative_to(resolved_bdir)
+ i_name = i['name']
+ split = i['id'].rsplit('@', 1)
+ if len(split) > 1:
+ split = split[0].split('@@', 1)
+ if len(split) > 1:
+ i_name = split[1]
+ else:
+ i_name = split[0]
+ p = Path(i['filename'][0]).relative_to(resolved_bdir).parent / i_name
t = i['type'].replace(' ', '_')
suggestions.append(f'- ./{p}:{t}')
suggestions_str = '\n'.join(suggestions)
raise MesonException(f'Can\'t invoke target `{target.full_name}`: ambiguous name.'
- f'Add target type and/or path:\n{suggestions_str}')
+ f' Add target type and/or path:\n{suggestions_str}')
return found_targets[0]
def generate_target_names_ninja(target: ParsedTargetName, builddir: Path, introspect_data: dict) -> T.List[str]:
intro_target = get_target_from_intro_data(target, builddir, introspect_data)
- if intro_target['type'] == 'run':
+ if intro_target['type'] in {'alias', 'run'}:
return [target.name]
else:
return [str(Path(out_file).relative_to(builddir.resolve())) for out_file in intro_target['filename']]
def generate_target_name_vs(target: ParsedTargetName, builddir: Path, introspect_data: dict) -> str:
intro_target = get_target_from_intro_data(target, builddir, introspect_data)
- assert intro_target['type'] != 'run', 'Should not reach here: `run` targets must be handle above'
+ assert intro_target['type'] not in {'alias', 'run'}, 'Should not reach here: `run` targets must be handle above'
# Normalize project name
# Source: https://docs.microsoft.com/en-us/visualstudio/msbuild/how-to-build-specific-targets-in-solutions-by-using-msbuild-exe
- target_name = re.sub(r"[\%\$\@\;\.\(\)']", '_', intro_target['id']) # type: str
+ target_name = re.sub(r"[\%\$\@\;\.\(\)']", '_', intro_target['id'])
rel_path = Path(intro_target['filename'][0]).relative_to(builddir.resolve()).parent
if rel_path != Path('.'):
target_name = str(rel_path / target_name)
if options.targets:
intro_data = parse_introspect_data(builddir)
has_run_target = any(
- get_target_from_intro_data(ParsedTargetName(t), builddir, intro_data)['type'] == 'run'
+ get_target_from_intro_data(ParsedTargetName(t), builddir, intro_data)['type'] in {'alias', 'run'}
for t in options.targets)
if has_run_target:
cmd += options.xcode_args
return cmd, None
+# Note: when adding arguments, please also add them to the completion
+# scripts in $MESONSRC/data/shell-completions/
def add_arguments(parser: 'argparse.ArgumentParser') -> None:
"""Add compile specific arguments."""
parser.add_argument(
metavar='TARGET',
nargs='*',
default=None,
- help='Targets to build. Target has the following format: [PATH_TO_TARGET/]TARGET_NAME[:TARGET_TYPE].')
+ help='Targets to build. Target has the following format: [PATH_TO_TARGET/]TARGET_NAME.TARGET_SUFFIX[:TARGET_TYPE].')
parser.add_argument(
'--clean',
action='store_true',
if options.targets and options.clean:
raise MesonException('`TARGET` and `--clean` can\'t be used simultaneously')
- cdata = coredata.load(options.wd)
b = build.load(options.wd)
- vsenv_active = setup_vsenv(b.need_vsenv)
- if vsenv_active:
+ cdata = b.environment.coredata
+ need_vsenv = T.cast('bool', cdata.get_option(mesonlib.OptionKey('vsenv')))
+ if setup_vsenv(need_vsenv):
mlog.log(mlog.green('INFO:'), 'automatically activated MSVC compiler environment')
- cmd = [] # type: T.List[str]
- env = None # type: T.Optional[T.Dict[str, str]]
+ cmd: T.List[str] = []
+ env: T.Optional[T.Dict[str, str]] = None
backend = cdata.get_option(mesonlib.OptionKey('backend'))
assert isinstance(backend, str)
# 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 itertools
import shutil
from . import mesonlib
from . import mintro
from . import mlog
-from .ast import AstIDGenerator
+from .ast import AstIDGenerator, IntrospectionInterpreter
from .mesonlib import MachineChoice, OptionKey
if T.TYPE_CHECKING:
import argparse
+ # cannot be TV_Loggable, because non-ansidecorators do direct string concat
+ LOGLINE = T.Union[str, mlog.AnsiDecorator]
+
+# 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)
parser.add_argument('builddir', nargs='?', default='.')
parser.add_argument('--no-pager', action='store_false', dest='pager',
help='Do not redirect output to a pager')
-def stringify(val: T.Any) -> T.Union[str, T.List[T.Any]]: # T.Any because of recursion...
+def stringify(val: T.Any) -> str:
if isinstance(val, bool):
return str(val).lower()
elif isinstance(val, list):
class Conf:
- def __init__(self, build_dir):
+ def __init__(self, build_dir: str):
self.build_dir = os.path.abspath(os.path.realpath(build_dir))
if 'meson.build' in [os.path.basename(self.build_dir), self.build_dir]:
self.build_dir = os.path.dirname(self.build_dir)
self.build = None
self.max_choices_line_length = 60
- self.name_col = []
- self.value_col = []
- self.choices_col = []
- self.descr_col = []
+ self.name_col: T.List[LOGLINE] = []
+ self.value_col: T.List[LOGLINE] = []
+ self.choices_col: T.List[LOGLINE] = []
+ self.descr_col: T.List[LOGLINE] = []
self.all_subprojects: T.Set[str] = set()
if os.path.isdir(os.path.join(self.build_dir, 'meson-private')):
self.build = build.load(self.build_dir)
self.source_dir = self.build.environment.get_source_dir()
- self.coredata = coredata.load(self.build_dir)
+ self.coredata = self.build.environment.coredata
self.default_values_only = False
elif os.path.isfile(os.path.join(self.build_dir, environment.build_filename)):
# Make sure that log entries in other parts of meson don't interfere with the JSON output
- mlog.disable()
- self.source_dir = os.path.abspath(os.path.realpath(self.build_dir))
- intr = mintro.IntrospectionInterpreter(self.source_dir, '', 'ninja', visitors = [AstIDGenerator()])
- intr.analyze()
- # Re-enable logging just in case
- mlog.enable()
+ with mlog.no_logging():
+ self.source_dir = os.path.abspath(os.path.realpath(self.build_dir))
+ intr = IntrospectionInterpreter(self.source_dir, '', 'ninja', visitors = [AstIDGenerator()])
+ intr.analyze()
self.coredata = intr.coredata
self.default_values_only = True
else:
raise ConfException(f'Directory {build_dir} is neither a Meson build directory nor a project source directory.')
- def clear_cache(self):
- self.coredata.clear_deps_cache()
+ def clear_cache(self) -> None:
+ self.coredata.clear_cache()
- def set_options(self, options):
- self.coredata.set_options(options)
+ def set_options(self, options: T.Dict[OptionKey, str]) -> bool:
+ return self.coredata.set_options(options)
- def save(self):
+ def save(self) -> None:
# Do nothing when using introspection
if self.default_values_only:
return
mlog.log(line[0])
continue
- def wrap_text(text, width):
+ def wrap_text(text: LOGLINE, width: int) -> mlog.TV_LoggableList:
raw = text.text if isinstance(text, mlog.AnsiDecorator) else text
indent = ' ' if raw.startswith('[') else ''
- wrapped = textwrap.wrap(raw, width, subsequent_indent=indent)
+ wrapped_ = textwrap.wrap(raw, width, subsequent_indent=indent)
+ # We cast this because https://github.com/python/mypy/issues/1965
+ # mlog.TV_LoggableList does not provide __len__ for stringprotocol
if isinstance(text, mlog.AnsiDecorator):
- wrapped = [mlog.AnsiDecorator(i, text.code) for i in wrapped]
+ wrapped = T.cast('T.List[LOGLINE]', [mlog.AnsiDecorator(i, text.code) for i in wrapped_])
+ else:
+ wrapped = T.cast('T.List[LOGLINE]', wrapped_)
# Add padding here to get even rows, as `textwrap.wrap()` will
# only shorten, not lengthen each item
return [str(i) + ' ' * (width - len(i)) for i in wrapped]
items = [l[i] if l[i] else ' ' * four_column[i] for i in range(4)]
mlog.log(*items)
- def split_options_per_subproject(self, options: 'coredata.KeyedOptionDictType') -> T.Dict[str, 'coredata.KeyedOptionDictType']:
- result: T.Dict[str, 'coredata.KeyedOptionDictType'] = {}
+ def split_options_per_subproject(self, options: 'coredata.KeyedOptionDictType') -> T.Dict[str, 'coredata.MutableKeyedOptionDictType']:
+ result: T.Dict[str, 'coredata.MutableKeyedOptionDictType'] = {}
for k, o in options.items():
if k.subproject:
self.all_subprojects.add(k.subproject)
result.setdefault(k.subproject, {})[k] = o
return result
- def _add_line(self, name, value, choices, descr) -> None:
+ def _add_line(self, name: LOGLINE, value: LOGLINE, choices: LOGLINE, descr: LOGLINE) -> None:
if isinstance(name, mlog.AnsiDecorator):
name.text = ' ' * self.print_margin + name.text
else:
self.choices_col.append(choices)
self.descr_col.append(descr)
- def add_option(self, name, descr, value, choices):
+ def add_option(self, name: str, descr: str, value: T.Any, choices: T.Any) -> None:
value = stringify(value)
choices = stringify(choices)
self._add_line(mlog.green(name), mlog.yellow(value), mlog.blue(choices), descr)
- def add_title(self, title):
- title = mlog.cyan(title)
+ def add_title(self, title: str) -> None:
+ newtitle = mlog.cyan(title)
descr = mlog.cyan('Description')
value = mlog.cyan('Default Value' if self.default_values_only else 'Current Value')
choices = mlog.cyan('Possible Values')
self._add_line('', '', '', '')
- self._add_line(title, value, choices, descr)
- self._add_line('-' * len(title), '-' * len(value), '-' * len(choices), '-' * len(descr))
+ self._add_line(newtitle, value, choices, descr)
+ self._add_line('-' * len(newtitle), '-' * len(value), '-' * len(choices), '-' * len(descr))
- def add_section(self, section):
+ def add_section(self, section: str) -> None:
self.print_margin = 0
self._add_line('', '', '', '')
self._add_line(mlog.normal_yellow(section + ':'), '', '', '')
return
if title:
self.add_title(title)
+ auto = T.cast('coredata.UserFeatureOption', self.coredata.options[OptionKey('auto_features')])
for k, o in sorted(options.items()):
printable_value = o.printable_value()
root = k.as_root()
if o.yielding and k.subproject and root in self.coredata.options:
printable_value = '<inherited from main project>'
+ if isinstance(o, coredata.UserFeatureOption) and o.is_auto():
+ printable_value = auto.printable_value()
self.add_option(str(root), o.description, printable_value, o.choices)
- def print_conf(self, pager: bool):
+ def print_conf(self, pager: bool) -> None:
if pager:
mlog.start_pager()
- def print_default_values_warning():
+ def print_default_values_warning() -> None:
mlog.warning('The source directory instead of the build directory was specified.')
- mlog.warning('Only the default values for the project are printed, and all command line parameters are ignored.')
+ mlog.warning('Only the default values for the project are printed.')
if self.default_values_only:
print_default_values_warning()
test_option_names = {OptionKey('errorlogs'),
OptionKey('stdsplit')}
- dir_options: 'coredata.KeyedOptionDictType' = {}
- test_options: 'coredata.KeyedOptionDictType' = {}
- core_options: 'coredata.KeyedOptionDictType' = {}
- module_options: T.Dict[str, 'coredata.KeyedOptionDictType'] = collections.defaultdict(dict)
+ dir_options: 'coredata.MutableKeyedOptionDictType' = {}
+ test_options: 'coredata.MutableKeyedOptionDictType' = {}
+ core_options: 'coredata.MutableKeyedOptionDictType' = {}
+ module_options: T.Dict[str, 'coredata.MutableKeyedOptionDictType'] = collections.defaultdict(dict)
for k, v in self.coredata.options.items():
if k in dir_option_names:
dir_options[k] = v
self.print_nondefault_buildtype_options()
- def print_nondefault_buildtype_options(self):
+ def print_nondefault_buildtype_options(self) -> None:
mismatching = self.coredata.get_nondefault_buildtype_args()
if not mismatching:
return
for m in mismatching:
mlog.log(f'{m[0]:21}{m[1]:10}{m[2]:10}')
-def run(options):
- coredata.parse_cmd_line_options(options)
- builddir = os.path.abspath(os.path.realpath(options.builddir))
+def run_impl(options: argparse.Namespace, builddir: str) -> int:
+ print_only = not options.cmd_line_options and not options.clearcache
c = None
try:
c = Conf(builddir)
- if c.default_values_only:
+ if c.default_values_only and not print_only:
+ raise mesonlib.MesonException('No valid build directory found, cannot modify options.')
+ if c.default_values_only or print_only:
c.print_conf(options.pager)
return 0
save = False
if options.cmd_line_options:
- c.set_options(options.cmd_line_options)
+ save = c.set_options(options.cmd_line_options)
coredata.update_cmd_line_file(builddir, options)
- save = True
- elif options.clearcache:
+ if options.clearcache:
c.clear_cache()
save = True
- else:
- c.print_conf(options.pager)
if save:
c.save()
mintro.update_build_options(c.coredata, c.build.environment.info_dir)
# Pager quit before we wrote everything.
pass
return 0
+
+def run(options: argparse.Namespace) -> int:
+ coredata.parse_cmd_line_options(options)
+ builddir = os.path.abspath(os.path.realpath(options.builddir))
+ return run_impl(options, builddir)
+from __future__ import annotations
+
import os, subprocess
import argparse
import tempfile
import itertools
from pathlib import Path
-from . import build, minstall, dependencies
-from .mesonlib import (MesonException, is_windows, setup_vsenv, OptionKey,
+from . import build, minstall
+from .mesonlib import (EnvironmentVariables, MesonException, is_windows, setup_vsenv, OptionKey,
get_wine_shortpath, MachineChoice)
from . import mlog
POWERSHELL_EXES = {'pwsh.exe', 'powershell.exe'}
+# Note: when adding arguments, please also add them to the completion
+# scripts in $MESONSRC/data/shell-completions/
def add_arguments(parser: argparse.ArgumentParser) -> None:
parser.add_argument('-C', dest='builddir', type=Path, default='.',
help='Path to build directory')
parser.add_argument('--workdir', '-w', type=Path, default=None,
help='Directory to cd into before running (default: builddir, Since 1.0.0)')
- parser.add_argument('--dump', action='store_true',
- help='Only print required environment (Since 0.62.0)')
+ parser.add_argument('--dump', nargs='?', const=True,
+ help='Only print required environment (Since 0.62.0) ' +
+ 'Takes an optional file path (Since 1.1.0)')
+ parser.add_argument('--dump-format', default='export',
+ choices=['sh', 'export', 'vscode'],
+ help='Format used with --dump (Since 1.1.0)')
parser.add_argument('devcmd', nargs=argparse.REMAINDER, metavar='command',
help='Command to run in developer environment (default: interactive shell)')
env['WINEPATH'] = get_wine_shortpath([winecmd], winepath.split(';'))
mlog.log('Meson detected wine and has set WINEPATH accordingly')
-def get_env(b: build.Build, dump: bool) -> T.Tuple[T.Dict[str, str], T.Set[str]]:
- extra_env = build.EnvironmentVariables()
+def get_env(b: build.Build, dump_fmt: T.Optional[str]) -> T.Tuple[T.Dict[str, str], T.Set[str]]:
+ extra_env = EnvironmentVariables()
extra_env.set('MESON_DEVENV', ['1'])
extra_env.set('MESON_PROJECT_NAME', [b.project_name])
if sysroot:
extra_env.set('QEMU_LD_PREFIX', [sysroot])
- env = {} if dump else os.environ.copy()
+ env = {} if dump_fmt else os.environ.copy()
+ default_fmt = '${0}' if dump_fmt in {'sh', 'export'} else None
varnames = set()
for i in itertools.chain(b.devenv, {extra_env}):
- env = i.get_env(env, dump)
+ env = i.get_env(env, default_fmt)
varnames |= i.get_names()
reduce_winepath(env)
return env, varnames
def bash_completion_files(b: build.Build, install_data: 'InstallData') -> T.List[str]:
+ from .dependencies.pkgconfig import PkgConfigDependency
result = []
- dep = dependencies.PkgConfigDependency('bash-completion', b.environment,
- {'required': False, 'silent': True, 'version': '>=2.10'})
+ dep = PkgConfigDependency('bash-completion', b.environment,
+ {'required': False, 'silent': True, 'version': '>=2.10'})
if dep.found():
prefix = b.environment.coredata.get_option(OptionKey('prefix'))
assert isinstance(prefix, str), 'for mypy'
datadir = b.environment.coredata.get_option(OptionKey('datadir'))
assert isinstance(datadir, str), 'for mypy'
datadir_abs = os.path.join(prefix, datadir)
- completionsdir = dep.get_variable(pkgconfig='completionsdir', pkgconfig_define=['datadir', datadir_abs])
+ completionsdir = dep.get_variable(pkgconfig='completionsdir', pkgconfig_define=(('datadir', datadir_abs),))
assert isinstance(completionsdir, str), 'for mypy'
completionsdir_path = Path(completionsdir)
for f in install_data.data:
mlog.log(' - Change current workdir to', mlog.bold(str(rel_path.parent)),
'or use', mlog.bold(f'--init-command {rel_path}'))
+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)
+ if dump_format == 'export':
+ print(f'export {name}', file=output)
+
def run(options: argparse.Namespace) -> int:
privatedir = Path(options.builddir) / 'meson-private'
buildfile = privatedir / 'build.dat'
b = build.load(options.builddir)
workdir = options.workdir or options.builddir
- devenv, varnames = get_env(b, options.dump)
+ need_vsenv = T.cast('bool', b.environment.coredata.get_option(OptionKey('vsenv')))
+ setup_vsenv(need_vsenv) # Call it before get_env to get vsenv vars as well
+ dump_fmt = options.dump_format if options.dump else None
+ devenv, varnames = get_env(b, dump_fmt)
if options.dump:
if options.devcmd:
raise MesonException('--dump option does not allow running other command.')
- for name in varnames:
- print(f'{name}="{devenv[name]}"')
- print(f'export {name}')
+ if options.dump is True:
+ dump(devenv, varnames, dump_fmt)
+ else:
+ with open(options.dump, "w", encoding='utf-8') as output:
+ dump(devenv, varnames, dump_fmt, output)
return 0
if b.environment.need_exe_wrapper():
install_data = minstall.load_install_data(str(privatedir / 'install.dat'))
write_gdb_script(privatedir, install_data, workdir)
- setup_vsenv(b.need_vsenv)
-
args = options.devcmd
if not args:
prompt_prefix = f'[{b.project_name}]'
# 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 abc
import argparse
import gzip
import os
import tarfile
import tempfile
import hashlib
+import typing as T
+
+from dataclasses import dataclass
from glob import glob
from pathlib import Path
from mesonbuild.environment import detect_ninja
from mesonbuild.mesonlib import (MesonException, RealPathAction, quiet_git,
- windows_proof_rmtree, setup_vsenv)
+ windows_proof_rmtree, setup_vsenv, OptionKey)
from mesonbuild.msetup import add_arguments as msetup_argparse
from mesonbuild.wrap import wrap
from mesonbuild import mlog, build, coredata
from .scripts.meson_exe import run_exe
+if T.TYPE_CHECKING:
+ from ._typing import ImmutableListProtocol
+ from .mesonlib import ExecutableSerialisation
+
archive_choices = ['gztar', 'xztar', 'zip']
archive_extension = {'gztar': '.tar.gz',
'xztar': '.tar.xz',
'zip': '.zip'}
-def add_arguments(parser):
+# Note: when adding arguments, please also add them to the completion
+# scripts in $MESONSRC/data/shell-completions/
+def add_arguments(parser: argparse.ArgumentParser) -> None:
parser.add_argument('-C', dest='wd', action=RealPathAction,
help='directory to cd into before running')
parser.add_argument('--allow-dirty', action='store_true',
help='Do not build and test generated packages.')
-def create_hash(fname):
+def create_hash(fname: str) -> None:
hashname = fname + '.sha256sum'
m = hashlib.sha256()
m.update(open(fname, 'rb').read())
f.write('{} *{}\n'.format(m.hexdigest(), os.path.basename(fname)))
-def copy_git(src, distdir, revision='HEAD', prefix=None, subdir=None):
- cmd = ['git', 'archive', '--format', 'tar', revision]
- if prefix is not None:
- cmd.insert(2, f'--prefix={prefix}/')
- if subdir is not None:
- cmd.extend(['--', subdir])
- with tempfile.TemporaryFile() as f:
- subprocess.check_call(cmd, cwd=src, stdout=f)
- f.seek(0)
- t = tarfile.open(fileobj=f) # [ignore encoding]
- t.extractall(path=distdir)
-
msg_uncommitted_changes = 'Repository has uncommitted changes that will not be included in the dist tarball'
-def handle_dirty_opt(msg, allow_dirty: bool):
+def handle_dirty_opt(msg: str, allow_dirty: bool) -> None:
if allow_dirty:
mlog.warning(msg)
else:
mlog.error(msg + '\n' + 'Use --allow-dirty to ignore the warning and proceed anyway')
sys.exit(1)
-def process_submodules(src, distdir, options):
- module_file = os.path.join(src, '.gitmodules')
- if not os.path.exists(module_file):
- return
- cmd = ['git', 'submodule', 'status', '--cached', '--recursive']
- modlist = subprocess.check_output(cmd, cwd=src, universal_newlines=True).splitlines()
- for submodule in modlist:
- status = submodule[:1]
- sha1, rest = submodule[1:].split(' ', 1)
- subpath = rest.rsplit(' ', 1)[0]
-
- if status == '-':
- mlog.warning(f'Submodule {subpath!r} is not checked out and cannot be added to the dist')
- continue
- elif status in {'+', 'U'}:
- handle_dirty_opt(f'Submodule {subpath!r} has uncommitted changes that will not be included in the dist tarball', options.allow_dirty)
-
- copy_git(os.path.join(src, subpath), distdir, revision=sha1, prefix=subpath)
-
-
-def run_dist_scripts(src_root, bld_root, dist_root, dist_scripts, subprojects):
- assert os.path.isabs(dist_root)
- env = {}
- env['MESON_DIST_ROOT'] = dist_root
- env['MESON_SOURCE_ROOT'] = src_root
- env['MESON_BUILD_ROOT'] = bld_root
- for d in dist_scripts:
- if d.subproject and d.subproject not in subprojects:
- continue
- subdir = subprojects.get(d.subproject, '')
- env['MESON_PROJECT_DIST_ROOT'] = os.path.join(dist_root, subdir)
- env['MESON_PROJECT_SOURCE_ROOT'] = os.path.join(src_root, subdir)
- env['MESON_PROJECT_BUILD_ROOT'] = os.path.join(bld_root, subdir)
- name = ' '.join(d.cmd_args)
- print(f'Running custom dist script {name!r}')
- try:
- rc = run_exe(d, env)
- if rc != 0:
- sys.exit('Dist script errored out')
- except OSError:
- print(f'Failed to run dist script {name!r}')
- sys.exit(1)
-
-def git_root(src_root):
- # Cannot use --show-toplevel here because git in our CI prints cygwin paths
- # that python cannot resolve. Workaround this by taking parent of src_root.
- prefix = quiet_git(['rev-parse', '--show-prefix'], src_root, check=True)[1].strip()
- if not prefix:
- return Path(src_root)
- prefix_level = len(Path(prefix).parents)
- return Path(src_root).parents[prefix_level - 1]
-
-def is_git(src_root):
+def is_git(src_root: str) -> bool:
'''
Checks if meson.build file at the root source directory is tracked by git.
It could be a subproject part of the parent project git repository.
'''
return quiet_git(['ls-files', '--error-unmatch', 'meson.build'], src_root)[0]
-def git_have_dirty_index(src_root):
- '''Check whether there are uncommitted changes in git'''
- ret = subprocess.call(['git', '-C', src_root, 'diff-index', '--quiet', 'HEAD'])
- return ret == 1
-
-def process_git_project(src_root, distdir, options):
- if git_have_dirty_index(src_root):
- handle_dirty_opt(msg_uncommitted_changes, options.allow_dirty)
- if os.path.exists(distdir):
- windows_proof_rmtree(distdir)
- repo_root = git_root(src_root)
- if repo_root.samefile(src_root):
- os.makedirs(distdir)
- copy_git(src_root, distdir)
- else:
- subdir = Path(src_root).relative_to(repo_root)
- tmp_distdir = distdir + '-tmp'
- if os.path.exists(tmp_distdir):
- windows_proof_rmtree(tmp_distdir)
- os.makedirs(tmp_distdir)
- copy_git(repo_root, tmp_distdir, subdir=str(subdir))
- Path(tmp_distdir, subdir).rename(distdir)
- windows_proof_rmtree(tmp_distdir)
- process_submodules(src_root, distdir, options)
-
-def create_dist_git(dist_name, archives, src_root, bld_root, dist_sub, dist_scripts, subprojects, options):
- distdir = os.path.join(dist_sub, dist_name)
- process_git_project(src_root, distdir, options)
- for path in subprojects.values():
- sub_src_root = os.path.join(src_root, path)
- sub_distdir = os.path.join(distdir, path)
- if os.path.exists(sub_distdir):
- continue
- if is_git(sub_src_root):
- process_git_project(sub_src_root, sub_distdir, options)
- else:
- shutil.copytree(sub_src_root, sub_distdir)
- run_dist_scripts(src_root, bld_root, distdir, dist_scripts, subprojects)
- output_names = []
- for a in archives:
- compressed_name = distdir + archive_extension[a]
- shutil.make_archive(distdir, a, root_dir=dist_sub, base_dir=dist_name)
- output_names.append(compressed_name)
- windows_proof_rmtree(distdir)
- return output_names
-
-def is_hg(src_root):
+def is_hg(src_root: str) -> bool:
return os.path.isdir(os.path.join(src_root, '.hg'))
-def hg_have_dirty_index(src_root):
- '''Check whether there are uncommitted changes in hg'''
- out = subprocess.check_output(['hg', '-R', src_root, 'summary'])
- return b'commit: (clean)' not in out
-
-def create_dist_hg(dist_name, archives, src_root, bld_root, dist_sub, dist_scripts, options):
- if hg_have_dirty_index(src_root):
- handle_dirty_opt(msg_uncommitted_changes, options.allow_dirty)
- if dist_scripts:
- mlog.warning('dist scripts are not supported in Mercurial projects')
-
- os.makedirs(dist_sub, exist_ok=True)
- tarname = os.path.join(dist_sub, dist_name + '.tar')
- xzname = tarname + '.xz'
- gzname = tarname + '.gz'
- zipname = os.path.join(dist_sub, dist_name + '.zip')
- # Note that -X interprets relative paths using the current working
- # directory, not the repository root, so this must be an absolute path:
- # https://bz.mercurial-scm.org/show_bug.cgi?id=6267
- #
- # .hg[a-z]* is used instead of .hg* to keep .hg_archival.txt, which may
- # be useful to link the tarball to the Mercurial revision for either
- # manual inspection or in case any code interprets it for a --version or
- # similar.
- subprocess.check_call(['hg', 'archive', '-R', src_root, '-S', '-t', 'tar',
- '-X', src_root + '/.hg[a-z]*', tarname])
- output_names = []
- if 'xztar' in archives:
- import lzma
- with lzma.open(xzname, 'wb') as xf, open(tarname, 'rb') as tf:
- shutil.copyfileobj(tf, xf)
- output_names.append(xzname)
- if 'gztar' in archives:
- with gzip.open(gzname, 'wb') as zf, open(tarname, 'rb') as tf:
- shutil.copyfileobj(tf, zf)
- output_names.append(gzname)
- os.unlink(tarname)
- if 'zip' in archives:
- subprocess.check_call(['hg', 'archive', '-R', src_root, '-S', '-t', 'zip', zipname])
- output_names.append(zipname)
- return output_names
-
-def run_dist_steps(meson_command, unpacked_src_dir, builddir, installdir, ninja_args):
+
+@dataclass
+class Dist(metaclass=abc.ABCMeta):
+ dist_name: str
+ src_root: str
+ bld_root: str
+ dist_scripts: T.List[ExecutableSerialisation]
+ subprojects: T.Dict[str, str]
+ options: argparse.Namespace
+
+ def __post_init__(self) -> None:
+ self.dist_sub = os.path.join(self.bld_root, 'meson-dist')
+ self.distdir = os.path.join(self.dist_sub, self.dist_name)
+
+ @abc.abstractmethod
+ def create_dist(self, archives: T.List[str]) -> T.List[str]:
+ pass
+
+ def run_dist_scripts(self) -> None:
+ assert os.path.isabs(self.distdir)
+ env = {}
+ env['MESON_DIST_ROOT'] = self.distdir
+ env['MESON_SOURCE_ROOT'] = self.src_root
+ env['MESON_BUILD_ROOT'] = self.bld_root
+ for d in self.dist_scripts:
+ if d.subproject and d.subproject not in self.subprojects:
+ continue
+ subdir = self.subprojects.get(d.subproject, '')
+ env['MESON_PROJECT_DIST_ROOT'] = os.path.join(self.distdir, subdir)
+ env['MESON_PROJECT_SOURCE_ROOT'] = os.path.join(self.src_root, subdir)
+ env['MESON_PROJECT_BUILD_ROOT'] = os.path.join(self.bld_root, subdir)
+ name = ' '.join(d.cmd_args)
+ print(f'Running custom dist script {name!r}')
+ try:
+ rc = run_exe(d, env)
+ if rc != 0:
+ sys.exit('Dist script errored out')
+ except OSError:
+ print(f'Failed to run dist script {name!r}')
+ sys.exit(1)
+
+
+class GitDist(Dist):
+ def git_root(self, dir_: str) -> Path:
+ # Cannot use --show-toplevel here because git in our CI prints cygwin paths
+ # that python cannot resolve. Workaround this by taking parent of src_root.
+ prefix = quiet_git(['rev-parse', '--show-prefix'], dir_, check=True)[1].strip()
+ if not prefix:
+ return Path(dir_)
+ prefix_level = len(Path(prefix).parents)
+ return Path(dir_).parents[prefix_level - 1]
+
+ def have_dirty_index(self) -> bool:
+ '''Check whether there are uncommitted changes in git'''
+ ret = subprocess.call(['git', '-C', self.src_root, 'diff-index', '--quiet', 'HEAD'])
+ return ret == 1
+
+ def copy_git(self, src: T.Union[str, os.PathLike], distdir: str, revision: str = 'HEAD',
+ prefix: T.Optional[str] = None, subdir: T.Optional[str] = None) -> None:
+ cmd = ['git', 'archive', '--format', 'tar', revision]
+ if prefix is not None:
+ cmd.insert(2, f'--prefix={prefix}/')
+ if subdir is not None:
+ cmd.extend(['--', subdir])
+ with tempfile.TemporaryFile() as f:
+ subprocess.check_call(cmd, cwd=src, stdout=f)
+ f.seek(0)
+ t = tarfile.open(fileobj=f) # [ignore encoding]
+ t.extractall(path=distdir)
+
+ def process_git_project(self, src_root: str, distdir: str) -> None:
+ if self.have_dirty_index():
+ handle_dirty_opt(msg_uncommitted_changes, self.options.allow_dirty)
+ if os.path.exists(distdir):
+ windows_proof_rmtree(distdir)
+ repo_root = self.git_root(src_root)
+ if repo_root.samefile(src_root):
+ os.makedirs(distdir)
+ self.copy_git(src_root, distdir)
+ else:
+ subdir = Path(src_root).relative_to(repo_root)
+ tmp_distdir = distdir + '-tmp'
+ if os.path.exists(tmp_distdir):
+ windows_proof_rmtree(tmp_distdir)
+ os.makedirs(tmp_distdir)
+ self.copy_git(repo_root, tmp_distdir, subdir=str(subdir))
+ Path(tmp_distdir, subdir).rename(distdir)
+ windows_proof_rmtree(tmp_distdir)
+ self.process_submodules(src_root, distdir)
+
+ def process_submodules(self, src: str, distdir: str) -> None:
+ module_file = os.path.join(src, '.gitmodules')
+ if not os.path.exists(module_file):
+ return
+ cmd = ['git', 'submodule', 'status', '--cached', '--recursive']
+ modlist = subprocess.check_output(cmd, cwd=src, universal_newlines=True).splitlines()
+ for submodule in modlist:
+ status = submodule[:1]
+ sha1, rest = submodule[1:].split(' ', 1)
+ subpath = rest.rsplit(' ', 1)[0]
+
+ if status == '-':
+ mlog.warning(f'Submodule {subpath!r} is not checked out and cannot be added to the dist')
+ continue
+ elif status in {'+', 'U'}:
+ handle_dirty_opt(f'Submodule {subpath!r} has uncommitted changes that will not be included in the dist tarball', self.options.allow_dirty)
+
+ self.copy_git(os.path.join(src, subpath), distdir, revision=sha1, prefix=subpath)
+
+ def create_dist(self, archives: T.List[str]) -> T.List[str]:
+ self.process_git_project(self.src_root, self.distdir)
+ for path in self.subprojects.values():
+ sub_src_root = os.path.join(self.src_root, path)
+ sub_distdir = os.path.join(self.distdir, path)
+ if os.path.exists(sub_distdir):
+ continue
+ if is_git(sub_src_root):
+ self.process_git_project(sub_src_root, sub_distdir)
+ else:
+ shutil.copytree(sub_src_root, sub_distdir)
+ self.run_dist_scripts()
+ output_names = []
+ for a in archives:
+ compressed_name = self.distdir + archive_extension[a]
+ shutil.make_archive(self.distdir, a, root_dir=self.dist_sub, base_dir=self.dist_name)
+ output_names.append(compressed_name)
+ windows_proof_rmtree(self.distdir)
+ return output_names
+
+
+class HgDist(Dist):
+ def have_dirty_index(self) -> bool:
+ '''Check whether there are uncommitted changes in hg'''
+ out = subprocess.check_output(['hg', '-R', self.src_root, 'summary'])
+ return b'commit: (clean)' not in out
+
+ def create_dist(self, archives: T.List[str]) -> T.List[str]:
+ if self.have_dirty_index():
+ handle_dirty_opt(msg_uncommitted_changes, self.options.allow_dirty)
+ if self.dist_scripts:
+ mlog.warning('dist scripts are not supported in Mercurial projects')
+
+ os.makedirs(self.dist_sub, exist_ok=True)
+ tarname = os.path.join(self.dist_sub, self.dist_name + '.tar')
+ xzname = tarname + '.xz'
+ gzname = tarname + '.gz'
+ zipname = os.path.join(self.dist_sub, self.dist_name + '.zip')
+ # Note that -X interprets relative paths using the current working
+ # directory, not the repository root, so this must be an absolute path:
+ # https://bz.mercurial-scm.org/show_bug.cgi?id=6267
+ #
+ # .hg[a-z]* is used instead of .hg* to keep .hg_archival.txt, which may
+ # be useful to link the tarball to the Mercurial revision for either
+ # manual inspection or in case any code interprets it for a --version or
+ # similar.
+ subprocess.check_call(['hg', 'archive', '-R', self.src_root, '-S', '-t', 'tar',
+ '-X', self.src_root + '/.hg[a-z]*', tarname])
+ output_names = []
+ if 'xztar' in archives:
+ import lzma
+ with lzma.open(xzname, 'wb') as xf, open(tarname, 'rb') as tf:
+ shutil.copyfileobj(tf, xf)
+ output_names.append(xzname)
+ if 'gztar' in archives:
+ with gzip.open(gzname, 'wb') as zf, open(tarname, 'rb') as tf:
+ shutil.copyfileobj(tf, zf)
+ output_names.append(gzname)
+ os.unlink(tarname)
+ if 'zip' in archives:
+ subprocess.check_call(['hg', 'archive', '-R', self.src_root, '-S', '-t', 'zip', zipname])
+ output_names.append(zipname)
+ return output_names
+
+
+def run_dist_steps(meson_command: T.List[str], unpacked_src_dir: str, builddir: str, installdir: str, ninja_args: T.List[str]) -> int:
if subprocess.call(meson_command + ['--backend=ninja', unpacked_src_dir, builddir]) != 0:
print('Running Meson on distribution package failed')
return 1
return 1
return 0
-def check_dist(packagename, meson_command, extra_meson_args, bld_root, privdir):
+def check_dist(packagename: str, meson_command: ImmutableListProtocol[str], extra_meson_args: T.List[str], bld_root: str, privdir: str) -> int:
print(f'Testing distribution package {packagename}')
unpackdir = os.path.join(privdir, 'dist-unpack')
builddir = os.path.join(privdir, 'dist-build')
unpacked_files = glob(os.path.join(unpackdir, '*'))
assert len(unpacked_files) == 1
unpacked_src_dir = unpacked_files[0]
+ meson_command += ['setup']
meson_command += create_cmdline_args(bld_root)
meson_command += extra_meson_args
print(f'Distribution package {packagename} tested')
return ret
-def create_cmdline_args(bld_root):
+def create_cmdline_args(bld_root: str) -> T.List[str]:
parser = argparse.ArgumentParser()
msetup_argparse(parser)
args = parser.parse_args([])
coredata.parse_cmd_line_options(args)
coredata.read_cmd_line_file(bld_root, args)
- args.cmd_line_options.pop(coredata.OptionKey('backend'), '')
+ args.cmd_line_options.pop(OptionKey('backend'), '')
return shlex.split(coredata.format_cmd_line_options(args))
-def determine_archives_to_generate(options):
+def determine_archives_to_generate(options: argparse.Namespace) -> T.List[str]:
result = []
for i in options.formats.split(','):
if i not in archive_choices:
sys.exit('No archive types specified.')
return result
-def run(options):
+def run(options: argparse.Namespace) -> int:
buildfile = Path(options.wd) / 'meson-private' / 'build.dat'
if not buildfile.is_file():
raise MesonException(f'Directory {options.wd!r} does not seem to be a Meson build directory.')
b = build.load(options.wd)
- setup_vsenv(b.need_vsenv)
+ need_vsenv = T.cast('bool', b.environment.coredata.get_option(OptionKey('vsenv')))
+ setup_vsenv(need_vsenv)
# This import must be load delayed, otherwise it will get the default
# value of None.
from mesonbuild.mesonlib import get_meson_command
src_root = b.environment.source_dir
bld_root = b.environment.build_dir
priv_dir = os.path.join(bld_root, 'meson-private')
- dist_sub = os.path.join(bld_root, 'meson-dist')
dist_name = b.project_name + '-' + b.project_version
subprojects[sub] = os.path.join(b.subproject_dir, directory)
extra_meson_args.append('-Dwrap_mode=nodownload')
+ cls: T.Type[Dist]
if is_git(src_root):
- names = create_dist_git(dist_name, archives, src_root, bld_root, dist_sub, b.dist_scripts, subprojects, options)
+ cls = GitDist
elif is_hg(src_root):
if subprojects:
print('--include-subprojects option currently not supported with Mercurial')
return 1
- names = create_dist_hg(dist_name, archives, src_root, bld_root, dist_sub, b.dist_scripts, options)
+ cls = HgDist
else:
print('Dist currently only works with Git or Mercurial repos')
return 1
+
+ project = cls(dist_name, src_root, bld_root, b.dist_scripts, subprojects, options)
+ names = project.create_dist(archives)
+
if names is None:
return 1
rc = 0
# 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 importlib.resources
# 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
# Work around some pathlib bugs...
parser.add_argument('script_args', nargs=argparse.REMAINDER)
def run_runpython_command(self, options):
- import runpy
+ sys.argv[1:] = options.script_args
if options.eval_arg:
exec(options.script_file)
else:
- sys.argv[1:] = options.script_args
+ import runpy
sys.path.insert(0, os.path.dirname(options.script_file))
runpy.run_path(options.script_file, run_name='__main__')
return 0
mesonlib.set_meson_command(mainfile)
def run(original_args, mainfile):
+ if os.environ.get('MESON_SHOW_DEPRECATIONS'):
+ # workaround for https://bugs.python.org/issue34624
+ import warnings
+ for typ in [DeprecationWarning, SyntaxWarning, FutureWarning, PendingDeprecationWarning]:
+ warnings.filterwarnings('error', category=typ, module='mesonbuild')
+ warnings.filterwarnings('ignore', message=".*importlib-resources.*")
+
if sys.version_info >= (3, 10) and os.environ.get('MESON_RUNNING_IN_PROJECT_TESTS'):
# workaround for https://bugs.python.org/issue34624
import warnings
assert os.path.isabs(sys.executable)
launcher = sys.executable
else:
- launcher = os.path.realpath(sys.argv[0])
+ launcher = os.path.abspath(sys.argv[0])
return run(sys.argv[1:], launcher)
if __name__ == '__main__':
"""Code that creates simple startup projects."""
+from __future__ import annotations
+
from pathlib import Path
from enum import Enum
import subprocess
import os
import re
from glob import glob
-from mesonbuild import mesonlib
+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.templates.samplefactory import sameple_generator
-import typing as T
+from mesonbuild.templates.mesontemplates import create_meson_build
+from mesonbuild.templates.samplefactory import sample_generator
if T.TYPE_CHECKING:
import argparse
-'''
-we currently have one meson template at this time.
-'''
-from mesonbuild.templates.mesontemplates import create_meson_build
+ from typing_extensions import Protocol, Literal
+
+ class Arguments(Protocol):
+
+ srcfiles: T.List[Path]
+ wd: str
+ name: str
+ executable: str
+ deps: str
+ language: Literal['c', 'cpp', 'cs', 'cuda', 'd', 'fortran', 'java', 'rust', 'objc', 'objcpp', 'vala']
+ build: bool
+ builddir: str
+ force: bool
+ type: Literal['executable', 'library']
+ version: str
+
FORTRAN_SUFFIXES = {'.f', '.for', '.F', '.f90', '.F90'}
LANG_SUFFIXES = {'.c', '.cc', '.cpp', '.cs', '.cu', '.d', '.m', '.mm', '.rs', '.java', '.vala'} | FORTRAN_SUFFIXES
'''
-def create_sample(options: 'argparse.Namespace') -> None:
+def create_sample(options: Arguments) -> None:
'''
Based on what arguments are passed we check for a match in language
then check for project type and create new Meson samples project.
'''
- sample_gen = sameple_generator(options)
+ sample_gen = sample_generator(options)
if options.type == DEFAULT_TYPES['EXE'].value:
sample_gen.create_executable()
elif options.type == DEFAULT_TYPES['LIB'].value:
raise RuntimeError('Unreachable code')
print(INFO_MESSAGE)
-def autodetect_options(options: 'argparse.Namespace', sample: bool = False) -> None:
+def autodetect_options(options: Arguments, sample: bool = False) -> None:
'''
Here we autodetect options for args not passed in so don't have to
think about it.
# The rest of the autodetection is not applicable to generating sample projects.
return
if not options.srcfiles:
- srcfiles = []
+ srcfiles: T.List[Path] = []
for f in (f for f in Path().iterdir() if f.is_file()):
if f.suffix in LANG_SUFFIXES:
srcfiles.append(f)
'Run meson init in an empty directory to create a sample project.')
options.srcfiles = srcfiles
print("Detected source files: " + ' '.join(str(s) for s in srcfiles))
- options.srcfiles = [Path(f) for f in options.srcfiles]
if not options.language:
for f in options.srcfiles:
if f.suffix == '.c':
raise SystemExit("Can't autodetect language, please specify it with -l.")
print("Detected language: " + options.language)
+# Note: when adding arguments, please also add them to the completion
+# scripts in $MESONSRC/data/shell-completions/
def add_arguments(parser: 'argparse.ArgumentParser') -> None:
'''
Here we add args for that the user can passed when making a new
Meson project.
'''
- parser.add_argument("srcfiles", metavar="sourcefile", nargs="*", help="source files. default: all recognized files in current directory")
+ parser.add_argument("srcfiles", metavar="sourcefile", nargs="*", type=Path, help="source files. default: all recognized files in current directory")
parser.add_argument('-C', dest='wd', action=mesonlib.RealPathAction,
help='directory to cd into before running')
parser.add_argument("-n", "--name", help="project name. default: name of current directory")
parser.add_argument('--type', default=DEFAULT_PROJECT, choices=('executable', 'library'), help=f"project type. default: {DEFAULT_PROJECT} based project")
parser.add_argument('--version', default=DEFAULT_VERSION, help=f"project version. default: {DEFAULT_VERSION}")
-def run(options: 'argparse.Namespace') -> int:
+def run(options: Arguments) -> int:
'''
Here we generate the new Meson sample project.
'''
print('Build directory already exists, deleting it.')
shutil.rmtree(options.builddir)
print('Building...')
- cmd = mesonlib.get_meson_command() + [options.builddir]
+ cmd = mesonlib.get_meson_command() + ['setup', options.builddir]
ret = subprocess.run(cmd)
if ret.returncode:
raise SystemExit
+
+ b = build.load(options.builddir)
+ need_vsenv = T.cast('bool', b.environment.coredata.get_option(mesonlib.OptionKey('vsenv')))
+ vsenv_active = mesonlib.setup_vsenv(need_vsenv)
+ if vsenv_active:
+ mlog.log(mlog.green('INFO:'), 'automatically activated MSVC compiler environment')
+
cmd = detect_ninja() + ['-C', options.builddir]
ret = subprocess.run(cmd)
if ret.returncode:
from __future__ import annotations
from glob import glob
-from pathlib import Path
import argparse
import errno
import os
+import selectors
import shlex
import shutil
import subprocess
import sys
import typing as T
+import re
-from . import build
-from . import environment
+from . import build, environment
from .backend.backends import InstallData
-from .mesonlib import MesonException, Popen_safe, RealPathAction, is_windows, setup_vsenv, pickle_load, is_osx
+from .mesonlib import (MesonException, Popen_safe, RealPathAction, is_windows,
+ is_aix, setup_vsenv, pickle_load, is_osx, OptionKey)
from .scripts import depfixer, destdir_join
from .scripts.meson_exe import run_exe
try:
if T.TYPE_CHECKING:
from .backend.backends import (
- ExecutableSerialisation, InstallDataBase, InstallEmptyDir,
+ InstallDataBase, InstallEmptyDir,
InstallSymlinkData, TargetInstallData
)
- from .mesonlib import FileMode
+ from .mesonlib import FileMode, EnvironOrDict, ExecutableSerialisation
try:
from typing import Protocol
strip: bool
-symlink_warning = '''Warning: trying to copy a symlink that points to a file. This will copy the file,
-but this will be changed in a future version of Meson to copy the symlink as is. Please update your
-build definitions so that it will not break when the change happens.'''
+symlink_warning = '''\
+Warning: trying to copy a symlink that points to a file. This currently copies
+the file by default, but will be changed in a future version of Meson to copy
+the link instead. Set follow_symlinks to true to preserve current behavior, or
+false to copy the link.'''
selinux_updates: T.List[str] = []
+# Note: when adding arguments, please also add them to the completion
+# scripts in $MESONSRC/data/shell-completions/
def add_arguments(parser: argparse.ArgumentParser) -> None:
parser.add_argument('-C', dest='wd', action=RealPathAction,
help='directory to cd into before running')
def load_install_data(fname: str) -> InstallData:
- obj = pickle_load(fname, 'InstallData', InstallData)
- assert isinstance(obj, InstallData), 'fo mypy'
- return obj
+ return pickle_load(fname, 'InstallData', InstallData)
def is_executable(path: str, follow_symlinks: bool = False) -> bool:
'''Checks whether any of the "x" bits are set in the source file mode.'''
except PermissionError as e:
print(f'{path!r}: Unable to set owner {mode.owner!r} and group {mode.group!r}: {e.strerror}, ignoring...')
except LookupError:
- print(f'{path!r}: Non-existent owner {mode.owner!r} or group {mode.group!r}: ignoring...')
+ print(f'{path!r}: Nonexistent owner {mode.owner!r} or group {mode.group!r}: ignoring...')
except OSError as e:
if e.errno == errno.EINVAL:
- print(f'{path!r}: Non-existent numeric owner {mode.owner!r} or group {mode.group!r}: ignoring...')
+ print(f'{path!r}: Nonexistent numeric owner {mode.owner!r} or group {mode.group!r}: ignoring...')
else:
raise
# Must set permissions *after* setting owner/group otherwise the
return p.returncode, o, e
return 0, '', ''
- def run_exe(self, *args: T.Any, **kwargs: T.Any) -> int:
- if not self.dry_run:
- return run_exe(*args, **kwargs)
+ def run_exe(self, exe: ExecutableSerialisation, extra_env: T.Optional[T.Dict[str, str]] = None) -> int:
+ if (not self.dry_run) or exe.dry_run:
+ return run_exe(exe, extra_env)
return 0
def should_install(self, d: T.Union[TargetInstallData, InstallEmptyDir,
return from_time <= to_time
def do_copyfile(self, from_file: str, to_file: str,
- makedirs: T.Optional[T.Tuple[T.Any, str]] = None) -> bool:
+ makedirs: T.Optional[T.Tuple[T.Any, str]] = None,
+ follow_symlinks: T.Optional[bool] = None) -> bool:
outdir = os.path.split(to_file)[0]
if not os.path.isfile(from_file) and not os.path.islink(from_file):
raise MesonException(f'Tried to install something that isn\'t a file: {from_file!r}')
append_to_log(self.lf, f'# Preserving old file {to_file}\n')
self.preserved_file_count += 1
return False
+ self.log(f'Installing {from_file} to {outdir}')
self.remove(to_file)
- elif makedirs:
- # Unpack tuple
- dirmaker, outdir = makedirs
- # Create dirs if needed
- dirmaker.makedirs(outdir, exist_ok=True)
- self.log(f'Installing {from_file} to {outdir}')
+ else:
+ self.log(f'Installing {from_file} to {outdir}')
+ if makedirs:
+ # Unpack tuple
+ dirmaker, outdir = makedirs
+ # Create dirs if needed
+ dirmaker.makedirs(outdir, exist_ok=True)
if os.path.islink(from_file):
if not os.path.exists(from_file):
# Dangling symlink. Replicate as is.
self.copy(from_file, outdir, follow_symlinks=False)
else:
- # Remove this entire branch when changing the behaviour to duplicate
- # symlinks rather than copying what they point to.
- print(symlink_warning)
- self.copy2(from_file, to_file)
+ if follow_symlinks is None:
+ follow_symlinks = True # TODO: change to False when removing the warning
+ print(symlink_warning)
+ self.copy2(from_file, to_file, follow_symlinks=follow_symlinks)
else:
self.copy2(from_file, to_file)
selinux_updates.append(to_file)
def do_copydir(self, data: InstallData, src_dir: str, dst_dir: str,
exclude: T.Optional[T.Tuple[T.Set[str], T.Set[str]]],
- install_mode: 'FileMode', dm: DirMaker) -> None:
+ install_mode: 'FileMode', dm: DirMaker, follow_symlinks: T.Optional[bool] = None) -> None:
'''
Copies the contents of directory @src_dir into @dst_dir.
raise ValueError(f'dst_dir must be absolute, got {dst_dir}')
if exclude is not None:
exclude_files, exclude_dirs = exclude
+ exclude_files = {os.path.normpath(x) for x in exclude_files}
+ exclude_dirs = {os.path.normpath(x) for x in exclude_dirs}
else:
exclude_files = exclude_dirs = set()
for root, dirs, files in os.walk(src_dir):
dm.makedirs(parent_dir)
self.copystat(os.path.dirname(abs_src), parent_dir)
# FIXME: what about symlinks?
- self.do_copyfile(abs_src, abs_dst)
+ self.do_copyfile(abs_src, abs_dst, follow_symlinks=follow_symlinks)
self.set_mode(abs_dst, install_mode, data.install_umask)
def do_install(self, datafilename: str) -> None:
self.log('Preserved {} unchanged files, see {} for the full list'
.format(self.preserved_file_count, os.path.normpath(self.lf.name)))
except PermissionError:
- if shutil.which('pkexec') is not None and 'PKEXEC_UID' not in os.environ and destdir == '':
- print('Installation failed due to insufficient permissions.')
- print('Attempting to use polkit to gain elevated privileges...')
- os.execlp('pkexec', 'pkexec', sys.executable, main_file, *sys.argv[1:],
- '-C', os.getcwd())
- else:
+ if is_windows() or destdir != '' or not os.isatty(sys.stdout.fileno()) or not os.isatty(sys.stderr.fileno()):
+ # can't elevate to root except in an interactive unix environment *and* when not doing a destdir install
raise
+ rootcmd = os.environ.get('MESON_ROOT_CMD') or shutil.which('sudo') or shutil.which('doas')
+ pkexec = shutil.which('pkexec')
+ if rootcmd is None and pkexec is not None and 'PKEXEC_UID' not in os.environ:
+ rootcmd = pkexec
+
+ if rootcmd is not None:
+ print('Installation failed due to insufficient permissions.')
+ s = selectors.DefaultSelector()
+ s.register(sys.stdin, selectors.EVENT_READ)
+ ans = None
+ for attempt in range(5):
+ print(f'Attempt to use {rootcmd} to gain elevated privileges? [y/n] ', end='', flush=True)
+ if s.select(30):
+ # we waited on sys.stdin *only*
+ ans = sys.stdin.readline().rstrip('\n')
+ else:
+ print()
+ break
+ if ans in {'y', 'n'}:
+ break
+ else:
+ if ans is not None:
+ raise MesonException('Answer not one of [y/n]')
+ if ans == 'y':
+ os.execlp(rootcmd, rootcmd, sys.executable, main_file, *sys.argv[1:],
+ '-C', os.getcwd(), '--no-rebuild')
+ raise
def do_strip(self, strip_bin: T.List[str], fname: str, outname: str) -> None:
self.log(f'Stripping target {fname!r}.')
if is_osx():
# macOS expects dynamic objects to be stripped with -x maximum.
- # To also strip the debug info, -S must be added.
+ # To also strip the debug info, -S must be added.
# See: https://www.unix.com/man-page/osx/1/strip/
returncode, stdo, stde = self.Popen_safe(strip_bin + ['-S', '-x', outname])
else:
full_dst_dir = get_destdir_path(destdir, fullprefix, i.install_path)
self.log(f'Installing subdir {i.path} to {full_dst_dir}')
dm.makedirs(full_dst_dir, exist_ok=True)
- self.do_copydir(d, i.path, full_dst_dir, i.exclude, i.install_mode, dm)
+ self.do_copydir(d, i.path, full_dst_dir, i.exclude, i.install_mode, dm,
+ follow_symlinks=i.follow_symlinks)
def install_data(self, d: InstallData, dm: DirMaker, destdir: str, fullprefix: str) -> None:
for i in d.data:
fullfilename = i.path
outfilename = get_destdir_path(destdir, fullprefix, i.install_path)
outdir = os.path.dirname(outfilename)
- if self.do_copyfile(fullfilename, outfilename, makedirs=(dm, outdir)):
+ if self.do_copyfile(fullfilename, outfilename, makedirs=(dm, outdir), follow_symlinks=i.follow_symlinks):
self.did_install_something = True
self.set_mode(outfilename, i.install_mode, d.install_umask)
fname = os.path.basename(fullfilename)
outdir = get_destdir_path(destdir, fullprefix, t.install_path)
outfilename = os.path.join(outdir, fname)
- if self.do_copyfile(fullfilename, outfilename, makedirs=(dm, outdir)):
+ if self.do_copyfile(fullfilename, outfilename, makedirs=(dm, outdir),
+ follow_symlinks=t.follow_symlinks):
self.did_install_something = True
self.set_mode(outfilename, t.install_mode, d.install_umask)
def run_install_script(self, d: InstallData, destdir: str, fullprefix: str) -> None:
env = {'MESON_SOURCE_ROOT': d.source_dir,
'MESON_BUILD_ROOT': d.build_dir,
- 'MESON_INSTALL_PREFIX': d.prefix,
- 'MESON_INSTALL_DESTDIR_PREFIX': fullprefix,
'MESONINTROSPECT': ' '.join([shlex.quote(x) for x in d.mesonintrospect]),
}
if self.options.quiet:
env['MESON_INSTALL_QUIET'] = '1'
+ if self.dry_run:
+ env['MESON_INSTALL_DRY_RUN'] = '1'
for i in d.install_scripts:
if not self.should_install(i):
continue
+
+ if i.installdir_map is not None:
+ mapp = i.installdir_map
+ else:
+ mapp = {'prefix': d.prefix}
+ localenv = env.copy()
+ localenv.update({'MESON_INSTALL_'+k.upper(): os.path.join(d.prefix, v) for k, v in mapp.items()})
+ localenv.update({'MESON_INSTALL_DESTDIR_'+k.upper(): get_destdir_path(destdir, fullprefix, v) for k, v in mapp.items()})
+
name = ' '.join(i.cmd_args)
if i.skip_if_destdir and destdir:
self.log(f'Skipping custom install script because DESTDIR is set {name!r}')
self.did_install_something = True # Custom script must report itself if it does nothing.
self.log(f'Running custom install script {name!r}')
try:
- rc = self.run_exe(i, env)
+ rc = self.run_exe(i, localenv)
except OSError:
print(f'FAILED: install script \'{name}\' could not be run, stopped')
# POSIX shells return 127 when a command could not be found
def install_targets(self, d: InstallData, dm: DirMaker, destdir: str, fullprefix: str) -> None:
for t in d.targets:
+ # In AIX, we archive our shared libraries. When we install any package in AIX we need to
+ # install the archive in which the shared library exists. The below code does the same.
+ # We change the .so files having lt_version or so_version to archive file install.
+ # If .so does not exist then it means it is in the archive. Otherwise it is a .so that exists.
+ if is_aix():
+ if not os.path.exists(t.fname) and '.so' in t.fname:
+ t.fname = re.sub('[.][a]([.]?([0-9]+))*([.]?([a-z]+))*', '.a', t.fname.replace('.so', '.a'))
if not self.should_install(t):
continue
if not os.path.exists(t.fname):
# file mode needs to be set last, after strip/depfixer editing
self.set_mode(outname, install_mode, d.install_umask)
-def rebuild_all(wd: str) -> bool:
- if not (Path(wd) / 'build.ninja').is_file():
+def rebuild_all(wd: str, backend: str) -> bool:
+ if backend == 'none':
+ # nothing to build...
+ return True
+ if backend != 'ninja':
print('Only ninja backend is supported to rebuild the project before installation.')
return True
print("Can't find ninja, can't rebuild test.")
return False
- ret = subprocess.run(ninja + ['-C', wd]).returncode
+ def drop_privileges() -> T.Tuple[T.Optional[EnvironOrDict], T.Optional[T.Callable[[], None]]]:
+ if not is_windows() and os.geteuid() == 0:
+ import pwd
+ env = os.environ.copy()
+
+ if os.environ.get('SUDO_USER') is not None:
+ orig_user = env.pop('SUDO_USER')
+ orig_uid = env.pop('SUDO_UID', 0)
+ orig_gid = env.pop('SUDO_GID', 0)
+ try:
+ homedir = pwd.getpwuid(int(orig_uid)).pw_dir
+ except KeyError:
+ # `sudo chroot` leaves behind stale variable and builds as root without a user
+ return None, None
+ elif os.environ.get('DOAS_USER') is not None:
+ orig_user = env.pop('DOAS_USER')
+ try:
+ pwdata = pwd.getpwnam(orig_user)
+ except KeyError:
+ # `doas chroot` leaves behind stale variable and builds as root without a user
+ return None, None
+ orig_uid = pwdata.pw_uid
+ orig_gid = pwdata.pw_gid
+ homedir = pwdata.pw_dir
+ else:
+ return None, None
+
+ if os.stat(os.path.join(wd, 'build.ninja')).st_uid != int(orig_uid):
+ # the entire build process is running with sudo, we can't drop privileges
+ return None, None
+
+ env['USER'] = orig_user
+ env['HOME'] = homedir
+
+ def wrapped() -> None:
+ print(f'Dropping privileges to {orig_user!r} before running ninja...')
+ if orig_gid is not None:
+ os.setgid(int(orig_gid))
+ if orig_uid is not None:
+ os.setuid(int(orig_uid))
+
+ return env, wrapped
+ else:
+ return None, None
+
+ env, preexec_fn = drop_privileges()
+ ret = subprocess.run(ninja + ['-C', wd], env=env, preexec_fn=preexec_fn).returncode
if ret != 0:
print(f'Could not rebuild {wd}')
return False
sys.exit('Install data not found. Run this command in build directory root.')
if not opts.no_rebuild:
b = build.load(opts.wd)
- setup_vsenv(b.need_vsenv)
- if not rebuild_all(opts.wd):
+ need_vsenv = T.cast('bool', b.environment.coredata.get_option(OptionKey('vsenv')))
+ setup_vsenv(need_vsenv)
+ backend = T.cast('str', b.environment.coredata.get_option(OptionKey('backend')))
+ if not rebuild_all(opts.wd, backend):
sys.exit(-1)
os.chdir(opts.wd)
with open(os.path.join(log_dir, 'install-log.txt'), 'w', encoding='utf-8') as lf:
Currently only works for the Ninja backend. Others use generated
project files and don't need this info."""
+from contextlib import redirect_stdout
import collections
+import dataclasses
import json
import os
from pathlib import Path, PurePath
+import sys
import typing as T
-from . import build, mesonlib, mlog, coredata as cdata
+from . import build, mesonlib, coredata as cdata
from .ast import IntrospectionInterpreter, BUILD_TARGET_FUNCTIONS, AstConditionLevel, AstIDGenerator, AstIndentationGenerator, AstJSONPrinter
from .backend import backends
+from .dependencies import Dependency
+from . import environment
+from .interpreterbase import ObjectHolder
from .mesonlib import OptionKey
-from .mparser import FunctionNode, ArrayNode, ArgumentNode, StringNode
+from .mparser import FunctionNode, ArrayNode, ArgumentNode, BaseStringNode
if T.TYPE_CHECKING:
import argparse
def get_meson_introspection_types(coredata: T.Optional[cdata.CoreData] = None,
builddata: T.Optional[build.Build] = None,
- backend: T.Optional[backends.Backend] = None,
- sourcedir: T.Optional[str] = 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())
('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))),
- ('dependencies', IntroCommand('List external dependencies', func=lambda: list_deps(coredata), no_bd=list_deps_from_source)),
+ ('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)),
('installed', IntroCommand('List all installed files and directories', func=lambda: list_installed(installdata))),
('install_plan', IntroCommand('List all installed files and directories with their details', func=lambda: list_install_plan(installdata))),
+ ('machines', IntroCommand('Information about host, build, and target machines', func=lambda: list_machines(builddata))),
('projectinfo', IntroCommand('Information about projects', func=lambda: list_projinfo(builddata), no_bd=list_projinfo_from_source)),
('targets', IntroCommand('List top level targets', func=lambda: list_targets(builddata, installdata, backend), no_bd=list_targets_from_source)),
('tests', IntroCommand('List all unit tests', func=lambda: list_tests(testdata))),
])
+# Note: when adding arguments, please also add them to the completion
+# scripts in $MESONSRC/data/shell-completions/
def add_arguments(parser: argparse.ArgumentParser) -> None:
intro_types = get_meson_introspection_types()
for key, val in intro_types.items():
return res
def list_install_plan(installdata: backends.InstallData) -> T.Dict[str, T.Dict[str, T.Dict[str, T.Optional[str]]]]:
- plan = {
+ plan: T.Dict[str, T.Dict[str, T.Dict[str, T.Optional[str]]]] = {
'targets': {
os.path.join(installdata.build_dir, target.fname): {
'destination': target.out_name,
'tag': target.tag or None,
+ 'subproject': target.subproject or None,
}
for target in installdata.targets
},
- } # type: T.Dict[str, T.Dict[str, T.Dict[str, T.Optional[str]]]]
+ }
for key, data_list in {
'data': installdata.data,
'man': installdata.man,
if key == 'headers': # in the headers, install_path_name is the directory
install_path_name = os.path.join(install_path_name, os.path.basename(data.path))
- plan[data_type] = plan.get(data_type, {})
- plan[data_type][data.path] = {
+ entry = {
'destination': install_path_name,
'tag': data.tag or None,
+ 'subproject': data.subproject or None,
}
+
+ if key == 'install_subdirs':
+ exclude_files, exclude_dirs = data.exclude or ([], [])
+ entry['exclude_dirs'] = list(exclude_dirs)
+ entry['exclude_files'] = list(exclude_files)
+
+ plan[data_type] = plan.get(data_type, {})
+ plan[data_type][data.path] = entry
+
return plan
def get_target_dir(coredata: cdata.CoreData, subdir: str) -> str:
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 = [] # type: 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 = [] # type: T.List[Path]
+ res: T.List[Path] = []
for n in node_list:
- args = [] # type: T.List[BaseNode]
+ args: T.List[BaseNode] = []
if isinstance(n, FunctionNode):
args = list(n.args.arguments)
- if n.func_name in BUILD_TARGET_FUNCTIONS:
+ 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):
+ if isinstance(j, BaseStringNode):
assert isinstance(j.value, str)
res += [Path(j.value)]
elif isinstance(j, str):
'sources': [str(x) for x in sources],
'generated_sources': []
}],
+ 'depends': [],
'extra_files': [str(x) for x in extra_f],
'subproject': None, # Subprojects are not supported
'installed': i['installed']
return tlist
def list_targets(builddata: build.Build, installdata: backends.InstallData, backend: backends.Backend) -> T.List[T.Any]:
- tlist = [] # type: T.List[T.Any]
+ tlist: T.List[T.Any] = []
build_dir = builddata.environment.get_build_dir()
src_dir = builddata.environment.get_source_dir()
'name': target.get_basename(),
'id': idname,
'type': target.get_typename(),
- 'defined_in': os.path.normpath(os.path.join(src_dir, target.subdir, 'meson.build')),
+ 'defined_in': os.path.normpath(os.path.join(src_dir, target.subdir, environment.build_filename)),
'filename': [os.path.join(build_dir, outdir, x) for x in target.get_outputs()],
'build_by_default': target.build_by_default,
'target_sources': backend.get_introspection_data(idname, target),
'extra_files': [os.path.normpath(os.path.join(src_dir, x.subdir, x.fname)) for x in target.extra_files],
- 'subproject': target.subproject or None
+ 'subproject': target.subproject or None,
+ 'dependencies': [d.name for d in getattr(target, 'external_deps', [])],
+ 'depends': [lib.get_id() for lib in getattr(target, 'dependencies', [])]
}
+ vs_module_defs = getattr(target, 'vs_module_defs', None)
+ if vs_module_defs is not None:
+ t['vs_module_defs'] = vs_module_defs.relative_name()
+ win_subsystem = getattr(target, 'win_subsystem', None)
+ if win_subsystem is not None:
+ t['win_subsystem'] = win_subsystem
+
if installdata and target.should_install():
t['installed'] = True
ifn = [install_lookuptable.get(x, [None]) for x in target.get_outputs()]
return list_buildoptions(intr.coredata, subprojects)
def list_buildoptions(coredata: cdata.CoreData, subprojects: T.Optional[T.List[str]] = None) -> T.List[T.Dict[str, T.Union[str, bool, int, T.List[str]]]]:
- optlist = [] # type: T.List[T.Dict[str, T.Union[str, bool, int, T.List[str]]]]
+ optlist: T.List[T.Dict[str, T.Union[str, bool, int, T.List[str]]]] = []
subprojects = subprojects or []
dir_option_names = set(cdata.BUILTIN_DIR_OPTIONS)
return optlist
def find_buildsystem_files_list(src_dir: str) -> T.List[str]:
+ build_files = frozenset({'meson.build', 'meson.options', 'meson_options.txt'})
# I feel dirty about this. But only slightly.
- filelist = [] # type: T.List[str]
+ filelist: T.List[str] = []
for root, _, files in os.walk(src_dir):
- for f in files:
- if f in {'meson.build', 'meson_options.txt'}:
- filelist.append(os.path.relpath(os.path.join(root, f), src_dir))
+ filelist.extend(os.path.relpath(os.path.join(root, f), src_dir)
+ for f in build_files.intersection(files))
return filelist
def list_buildsystem_files(builddata: build.Build, interpreter: Interpreter) -> T.List[str]:
filelist = [PurePath(src_dir, x).as_posix() for x in filelist]
return filelist
+def list_compilers(coredata: cdata.CoreData) -> T.Dict[str, T.Dict[str, T.Dict[str, str]]]:
+ compilers: T.Dict[str, T.Dict[str, T.Dict[str, str]]] = {}
+ for machine in ('host', 'build'):
+ compilers[machine] = {}
+ for language, compiler in getattr(coredata.compilers, machine).items():
+ compilers[machine][language] = {
+ 'id': compiler.get_id(),
+ 'exelist': compiler.get_exelist(),
+ 'linker_exelist': compiler.get_linker_exelist(),
+ 'file_suffixes': compiler.file_suffixes,
+ 'default_suffix': compiler.get_default_suffix(),
+ 'version': compiler.version,
+ 'full_version': compiler.full_version,
+ 'linker_id': compiler.get_linker_id(),
+ }
+ return compilers
+
def list_deps_from_source(intr: IntrospectionInterpreter) -> T.List[T.Dict[str, T.Union[str, bool]]]:
- result = [] # type: T.List[T.Dict[str, T.Union[str, bool]]]
+ result: T.List[T.Dict[str, T.Union[str, bool]]] = []
for i in intr.dependencies:
keys = [
'name',
result += [{k: v for k, v in i.items() if k in keys}]
return result
-def list_deps(coredata: cdata.CoreData) -> T.List[T.Dict[str, T.Union[str, T.List[str]]]]:
- result = [] # type: T.List[T.Dict[str, T.Union[str, T.List[str]]]]
+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]:
+ if isinstance(src_file, str):
+ return [src_file]
+ if isinstance(src_file, mesonlib.File):
+ return [src_file.absolute_path(backend.source_dir, backend.build_dir)]
+ if isinstance(src_file, (build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)):
+ return src_file.get_outputs()
+ if isinstance(src_file, build.StructuredSources):
+ 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]:
+ return {
+ 'name': d.name,
+ 'type': d.type_name,
+ 'version': d.get_version(),
+ 'compile_args': d.get_compile_args(),
+ 'link_args': d.get_link_args(),
+ 'include_directories': [i for idirs in d.get_include_dirs() for i in idirs.to_string_list(backend.source_dir, backend.build_dir)],
+ 'sources': [f for s in d.get_sources() for f in _src_to_str(s)],
+ '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 [],
+ }
+
for d in coredata.deps.host.values():
if d.found():
- result += [{'name': d.name,
- 'version': d.get_version(),
- 'compile_args': d.get_compile_args(),
- 'link_args': d.get_link_args()}]
- return result
+ 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]]]]:
- result = [] # type: T.List[T.Dict[str, T.Union[str, int, T.List[str], T.Dict[str, str]]]]
+ result: T.List[T.Dict[str, T.Union[str, int, T.List[str], T.Dict[str, str]]]] = []
for t in testdata:
- to = {} # type: T.Dict[str, T.Union[str, int, T.List[str], T.Dict[str, str]]]
+ to: T.Dict[str, T.Union[str, int, T.List[str], T.Dict[str, str]]] = {}
if isinstance(t.fname, str):
fname = [t.fname]
else:
fname = t.fname
to['cmd'] = fname + t.cmd_args
- if isinstance(t.env, build.EnvironmentVariables):
+ if isinstance(t.env, mesonlib.EnvironmentVariables):
to['env'] = t.env.get_env({})
else:
to['env'] = t.env
to['priority'] = t.priority
to['protocol'] = str(t.protocol)
to['depends'] = t.depends
+ to['extra_paths'] = t.extra_paths
result.append(to)
return result
def list_benchmarks(benchdata: T.List[backends.TestSerialisation]) -> T.List[T.Dict[str, T.Union[str, int, T.List[str], T.Dict[str, str]]]]:
return get_test_list(benchdata)
+def list_machines(builddata: build.Build) -> T.Dict[str, T.Dict[str, T.Union[str, bool]]]:
+ machines: T.Dict[str, T.Dict[str, T.Union[str, bool]]] = {}
+ for m in ('host', 'build', 'target'):
+ machine = getattr(builddata.environment.machines, m)
+ machines[m] = dataclasses.asdict(machine)
+ machines[m]['is_64_bit'] = machine.is_64_bit
+ machines[m]['exe_suffix'] = machine.get_exe_suffix()
+ machines[m]['object_suffix'] = machine.get_object_suffix()
+ return machines
+
def list_projinfo(builddata: build.Build) -> T.Dict[str, T.Union[str, T.List[T.Dict[str, str]]]]:
- result = {'version': builddata.project_version,
- 'descriptive_name': builddata.project_name,
- 'subproject_dir': builddata.subproject_dir} # type: T.Dict[str, T.Union[str, T.List[T.Dict[str, str]]]]
+ result: T.Dict[str, T.Union[str, T.List[T.Dict[str, str]]]] = {
+ 'version': builddata.project_version,
+ 'descriptive_name': builddata.project_name,
+ 'subproject_dir': builddata.subproject_dir,
+ }
subprojects = []
for k, v in builddata.subprojects.items():
- c = {'name': k,
- 'version': v,
- 'descriptive_name': builddata.projects.get(k)} # type: T.Dict[str, str]
+ c: T.Dict[str, str] = {
+ 'name': k,
+ 'version': v,
+ 'descriptive_name': builddata.projects.get(k),
+ }
subprojects.append(c)
result['subprojects'] = subprojects
return result
intr.project_data['subproject_dir'] = intr.subproject_dir
return intr.project_data
-def print_results(options: argparse.Namespace, results: T.Sequence[T.Tuple[str, T.Union[dict, T.List[T.Any]]]], indent: int) -> int:
+def print_results(options: argparse.Namespace, results: T.Sequence[T.Tuple[str, T.Union[dict, T.List[T.Any]]]], indent: T.Optional[int]) -> int:
if not results and not options.force_dict:
print('No command specified')
return 1
if options.builddir is not None:
datadir = os.path.join(options.builddir, datadir)
indent = 4 if options.indent else None
- results = [] # type: T.List[T.Tuple[str, T.Union[dict, T.List[T.Any]]]]
+ 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(sourcedir=sourcedir)
+ intro_types = get_meson_introspection_types()
if 'meson.build' in [os.path.basename(options.builddir), options.builddir]:
# Make sure that log entries in other parts of meson don't interfere with the JSON output
- mlog.disable()
- backend = backends.get_backend_from_name(options.backend)
- assert backend is not None
- intr = IntrospectionInterpreter(sourcedir, '', backend.name, visitors = [AstIDGenerator(), AstIndentationGenerator(), AstConditionLevel()])
- intr.analyze()
- # Re-enable logging just in case
- mlog.enable()
+ with redirect_stdout(sys.stderr):
+ backend = backends.get_backend_from_name(options.backend)
+ assert backend is not None
+ intr = IntrospectionInterpreter(sourcedir, '', backend.name, visitors = [AstIDGenerator(), AstIndentationGenerator(), AstConditionLevel()])
+ intr.analyze()
+
for key, val in intro_types.items():
if (not options.all and not getattr(options, key, False)) or not val.no_bd:
continue
return print_results(options, results, indent)
-updated_introspection_files = [] # type: T.List[str]
+updated_introspection_files: T.List[str] = []
def write_intro_info(intro_info: T.Sequence[T.Tuple[str, T.Union[dict, T.List[T.Any]]]], info_dir: str) -> None:
for kind, data in intro_info:
def generate_introspection_file(builddata: build.Build, backend: backends.Backend) -> None:
coredata = builddata.environment.get_coredata()
intro_types = get_meson_introspection_types(coredata=coredata, builddata=builddata, backend=backend)
- intro_info = [] # type: T.List[T.Tuple[str, T.Union[dict, T.List[T.Any]]]]
+ intro_info: T.List[T.Tuple[str, T.Union[dict, T.List[T.Any]]]] = []
for key, val in intro_types.items():
if not val.func:
# See the License for the specific language governing permissions and
# limitations under the License.
+"""This is (mostly) a standalone module used to write logging
+information about Meson runs. Some output goes to screen,
+some to logging dir and some goes to both."""
+
+from __future__ import annotations
+
+import enum
import os
import io
import sys
import shutil
import typing as T
from contextlib import contextmanager
+from dataclasses import dataclass, field
from pathlib import Path
if T.TYPE_CHECKING:
from ._typing import StringProtocol, SizedStringProtocol
-"""This is (mostly) a standalone module used to write logging
-information about Meson runs. Some output goes to screen,
-some to logging dir and some goes to both."""
+ from .mparser import BaseNode
+
+ TV_Loggable = T.Union[str, 'AnsiDecorator', StringProtocol]
+ TV_LoggableList = T.List[TV_Loggable]
def is_windows() -> bool:
platname = platform.system().lower()
return bool(kernel.SetConsoleMode(stdout, mode.value | 0x4) or os.environ.get('ANSICON'))
def colorize_console() -> bool:
- _colorize_console = getattr(sys.stdout, 'colorize_console', None) # type: bool
+ _colorize_console: bool = getattr(sys.stdout, 'colorize_console', None)
if _colorize_console is not None:
return _colorize_console
except AttributeError:
pass
-log_dir = None # type: T.Optional[str]
-log_file = None # type: T.Optional[T.TextIO]
-log_fname = 'meson-log.txt' # type: str
-log_depth = [] # type: T.List[str]
-log_timestamp_start = None # type: T.Optional[float]
-log_fatal_warnings = False # type: bool
-log_disable_stdout = False # type: bool
-log_errors_only = False # type: bool
-_in_ci = 'CI' in os.environ # type: bool
-_logged_once = set() # type: T.Set[T.Tuple[str, ...]]
-log_warnings_counter = 0 # type: int
-log_pager: T.Optional['subprocess.Popen'] = None
-
-def disable() -> None:
- global log_disable_stdout # pylint: disable=global-statement
- log_disable_stdout = True
-
-def enable() -> None:
- global log_disable_stdout # pylint: disable=global-statement
- log_disable_stdout = False
+_in_ci = 'CI' in os.environ
+
+
+class _Severity(enum.Enum):
+
+ NOTICE = enum.auto()
+ WARNING = enum.auto()
+ ERROR = enum.auto()
+ DEPRECATION = enum.auto()
-def set_quiet() -> None:
- global log_errors_only # pylint: disable=global-statement
- log_errors_only = True
+@dataclass
+class _Logger:
-def set_verbose() -> None:
- global log_errors_only # pylint: disable=global-statement
+ log_dir: T.Optional[str] = None
+ log_depth: T.List[str] = field(default_factory=list)
+ log_file: T.Optional[T.TextIO] = None
+ log_timestamp_start: T.Optional[float] = None
+ log_fatal_warnings = False
+ log_disable_stdout = False
log_errors_only = False
+ logged_once: T.Set[T.Tuple[str, ...]] = field(default_factory=set)
+ log_warnings_counter = 0
+ log_pager: T.Optional['subprocess.Popen'] = None
-def initialize(logdir: str, fatal_warnings: bool = False) -> None:
- global log_dir, log_file, log_fatal_warnings # pylint: disable=global-statement
- log_dir = logdir
- log_file = open(os.path.join(logdir, log_fname), 'w', encoding='utf-8')
- log_fatal_warnings = fatal_warnings
-
-def set_timestamp_start(start: float) -> None:
- global log_timestamp_start # pylint: disable=global-statement
- log_timestamp_start = start
-
-def shutdown() -> T.Optional[str]:
- global log_file # pylint: disable=global-statement
- if log_file is not None:
- path = log_file.name
- exception_around_goer = log_file
- log_file = None
- exception_around_goer.close()
- return path
- stop_pager()
- return None
+ _LOG_FNAME: T.ClassVar[str] = 'meson-log.txt'
+
+ @contextmanager
+ def no_logging(self) -> T.Iterator[None]:
+ self.log_disable_stdout = True
+ try:
+ yield
+ finally:
+ self.log_disable_stdout = False
+
+ @contextmanager
+ def force_logging(self) -> T.Iterator[None]:
+ restore = self.log_disable_stdout
+ self.log_disable_stdout = False
+ try:
+ yield
+ finally:
+ self.log_disable_stdout = restore
+
+ def set_quiet(self) -> None:
+ self.log_errors_only = True
+
+ def set_verbose(self) -> None:
+ self.log_errors_only = False
+
+ def set_timestamp_start(self, start: float) -> None:
+ self.log_timestamp_start = start
+
+ def shutdown(self) -> T.Optional[str]:
+ if self.log_file is not None:
+ path = self.log_file.name
+ exception_around_goer = self.log_file
+ self.log_file = None
+ exception_around_goer.close()
+ return path
+ self.stop_pager()
+ return None
+
+ def start_pager(self) -> None:
+ if not colorize_console():
+ return
+ pager_cmd = []
+ if 'PAGER' in os.environ:
+ pager_cmd = shlex.split(os.environ['PAGER'])
+ else:
+ less = shutil.which('less')
+ if not less and is_windows():
+ git = shutil.which('git')
+ if git:
+ path = Path(git).parents[1] / 'usr' / 'bin'
+ less = shutil.which('less', path=str(path))
+ if less:
+ pager_cmd = [less]
+ if not pager_cmd:
+ return
+ try:
+ # Set 'LESS' environment variable, rather than arguments in
+ # pager_cmd, to also support the case where the user has 'PAGER'
+ # set to 'less'. Arguments set are:
+ # "R" : support color
+ # "X" : do not clear the screen when leaving the pager
+ # "F" : skip the pager if content fits into the screen
+ env = os.environ.copy()
+ if 'LESS' not in env:
+ env['LESS'] = 'RXF'
+ # Set "-c" for lv to support color
+ if 'LV' not in env:
+ env['LV'] = '-c'
+ self.log_pager = subprocess.Popen(pager_cmd, stdin=subprocess.PIPE,
+ text=True, encoding='utf-8', env=env)
+ except Exception as e:
+ # Ignore errors, unless it is a user defined pager.
+ if 'PAGER' in os.environ:
+ from .mesonlib import MesonException
+ raise MesonException(f'Failed to start pager: {str(e)}')
+
+ def stop_pager(self) -> None:
+ if self.log_pager:
+ try:
+ self.log_pager.stdin.flush()
+ self.log_pager.stdin.close()
+ except BrokenPipeError:
+ pass
+ self.log_pager.wait()
+ self.log_pager = None
+
+ 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.log_fatal_warnings = fatal_warnings
+
+ def process_markup(self, args: T.Sequence[TV_Loggable], keep: bool, display_timestamp: bool = True) -> T.List[str]:
+ arr: T.List[str] = []
+ if self.log_timestamp_start is not None and display_timestamp:
+ arr = ['[{:.3f}]'.format(time.monotonic() - self.log_timestamp_start)]
+ for arg in args:
+ if arg is None:
+ continue
+ if isinstance(arg, str):
+ arr.append(arg)
+ elif isinstance(arg, AnsiDecorator):
+ arr.append(arg.get_text(keep))
+ else:
+ arr.append(str(arg))
+ return arr
+
+ def force_print(self, *args: str, nested: bool, sep: T.Optional[str] = None,
+ end: T.Optional[str] = None) -> None:
+ if self.log_disable_stdout:
+ return
+ iostr = io.StringIO()
+ print(*args, sep=sep, end=end, file=iostr)
+
+ raw = iostr.getvalue()
+ if self.log_depth:
+ prepend = self.log_depth[-1] + '| ' if nested else ''
+ lines = []
+ for l in raw.split('\n'):
+ l = l.strip()
+ lines.append(prepend + l if l else '')
+ raw = '\n'.join(lines)
+
+ # _Something_ is going to get printed.
+ try:
+ output = self.log_pager.stdin if self.log_pager else None
+ print(raw, end='', file=output)
+ except UnicodeEncodeError:
+ cleaned = raw.encode('ascii', 'replace').decode('ascii')
+ print(cleaned, end='')
+
+ def debug(self, *args: TV_Loggable, sep: T.Optional[str] = None,
+ end: T.Optional[str] = None, display_timestamp: bool = True) -> None:
+ arr = process_markup(args, False, display_timestamp)
+ if self.log_file is not None:
+ print(*arr, file=self.log_file, sep=sep, end=end)
+ self.log_file.flush()
+
+ def _log(self, *args: TV_Loggable, is_error: bool = False,
+ nested: bool = True, sep: T.Optional[str] = None,
+ end: T.Optional[str] = None, display_timestamp: bool = True) -> None:
+ arr = process_markup(args, False, display_timestamp)
+ if self.log_file is not None:
+ print(*arr, file=self.log_file, sep=sep, end=end)
+ self.log_file.flush()
+ if colorize_console():
+ arr = process_markup(args, True, display_timestamp)
+ if not self.log_errors_only or is_error:
+ force_print(*arr, nested=nested, sep=sep, end=end)
+
+ def _debug_log_cmd(self, cmd: str, args: T.List[str]) -> None:
+ if not _in_ci:
+ return
+ args = [f'"{x}"' for x in args] # Quote all args, just in case
+ self.debug('!meson_ci!/{} {}'.format(cmd, ' '.join(args)))
+
+ def cmd_ci_include(self, file: str) -> None:
+ self._debug_log_cmd('ci_include', [file])
+
+ def log(self, *args: TV_Loggable, is_error: bool = False,
+ once: bool = False, nested: bool = True,
+ sep: T.Optional[str] = None,
+ end: T.Optional[str] = None,
+ display_timestamp: bool = True) -> None:
+ if once:
+ self._log_once(*args, is_error=is_error, nested=nested, sep=sep, end=end, display_timestamp=display_timestamp)
+ else:
+ self._log(*args, is_error=is_error, nested=nested, sep=sep, end=end, display_timestamp=display_timestamp)
+
+ def log_timestamp(self, *args: TV_Loggable) -> None:
+ if self.log_timestamp_start:
+ self.log(*args)
+
+ def _log_once(self, *args: TV_Loggable, is_error: bool = False,
+ nested: bool = True, sep: T.Optional[str] = None,
+ end: T.Optional[str] = None, display_timestamp: bool = True) -> None:
+ """Log variant that only prints a given message one time per meson invocation.
+
+ This considers ansi decorated values by the values they wrap without
+ regard for the AnsiDecorator itself.
+ """
+ def to_str(x: TV_Loggable) -> str:
+ if isinstance(x, str):
+ return x
+ if isinstance(x, AnsiDecorator):
+ return x.text
+ return str(x)
+ t = tuple(to_str(a) for a in args)
+ if t in self.logged_once:
+ return
+ self.logged_once.add(t)
+ self._log(*args, is_error=is_error, nested=nested, sep=sep, end=end, display_timestamp=display_timestamp)
+
+ def _log_error(self, severity: _Severity, *rargs: TV_Loggable,
+ once: bool = False, fatal: bool = True,
+ location: T.Optional[BaseNode] = None,
+ nested: bool = True, sep: T.Optional[str] = None,
+ end: T.Optional[str] = None,
+ is_error: bool = True) -> None:
+ from .mesonlib import MesonException, relpath
+
+ # The typing requirements here are non-obvious. Lists are invariant,
+ # therefore T.List[A] and T.List[T.Union[A, B]] are not able to be joined
+ if severity is _Severity.NOTICE:
+ label: TV_LoggableList = [bold('NOTICE:')]
+ elif severity is _Severity.WARNING:
+ label = [yellow('WARNING:')]
+ elif severity is _Severity.ERROR:
+ label = [red('ERROR:')]
+ elif severity is _Severity.DEPRECATION:
+ label = [red('DEPRECATION:')]
+ # rargs is a tuple, not a list
+ args = label + list(rargs)
+
+ if location is not None:
+ location_file = relpath(location.filename, os.getcwd())
+ location_str = get_error_location_string(location_file, location.lineno)
+ # Unions are frankly awful, and we have to T.cast here to get mypy
+ # to understand that the list concatenation is safe
+ location_list = T.cast('TV_LoggableList', [location_str])
+ args = location_list + args
+
+ log(*args, once=once, nested=nested, sep=sep, end=end, is_error=is_error)
+
+ self.log_warnings_counter += 1
+
+ if self.log_fatal_warnings and fatal:
+ raise MesonException("Fatal warnings enabled, aborting")
+
+ def error(self, *args: TV_Loggable,
+ once: bool = False, fatal: bool = True,
+ location: T.Optional[BaseNode] = None,
+ nested: bool = True, sep: T.Optional[str] = None,
+ end: T.Optional[str] = None) -> None:
+ return self._log_error(_Severity.ERROR, *args, once=once, fatal=fatal, location=location,
+ nested=nested, sep=sep, end=end, is_error=True)
+
+ def warning(self, *args: TV_Loggable,
+ once: bool = False, fatal: bool = True,
+ location: T.Optional[BaseNode] = None,
+ nested: bool = True, sep: T.Optional[str] = None,
+ end: T.Optional[str] = None) -> None:
+ return self._log_error(_Severity.WARNING, *args, once=once, fatal=fatal, location=location,
+ nested=nested, sep=sep, end=end, is_error=True)
+
+ def deprecation(self, *args: TV_Loggable,
+ once: bool = False, fatal: bool = True,
+ location: T.Optional[BaseNode] = None,
+ nested: bool = True, sep: T.Optional[str] = None,
+ end: T.Optional[str] = None) -> None:
+ return self._log_error(_Severity.DEPRECATION, *args, once=once, fatal=fatal, location=location,
+ nested=nested, sep=sep, end=end, is_error=True)
+
+ def notice(self, *args: TV_Loggable,
+ once: bool = False, fatal: bool = True,
+ location: T.Optional[BaseNode] = None,
+ nested: bool = True, sep: T.Optional[str] = None,
+ end: T.Optional[str] = None) -> None:
+ return self._log_error(_Severity.NOTICE, *args, once=once, fatal=fatal, location=location,
+ nested=nested, sep=sep, end=end, is_error=False)
+
+ def exception(self, e: Exception, prefix: T.Optional[AnsiDecorator] = None) -> None:
+ if prefix is None:
+ prefix = red('ERROR:')
+ self.log()
+ args: T.List[T.Union[AnsiDecorator, str]] = []
+ if all(getattr(e, a, None) is not None for a in ['file', 'lineno', 'colno']):
+ # Mypy doesn't follow hasattr, and it's pretty easy to visually inspect
+ # that this is correct, so we'll just ignore it.
+ path = get_relative_path(Path(e.file), Path(os.getcwd())) # type: ignore
+ args.append(f'{path}:{e.lineno}:{e.colno}:') # type: ignore
+ if prefix:
+ args.append(prefix)
+ args.append(str(e))
+
+ with self.force_logging():
+ self.log(*args, is_error=True)
+
+ @contextmanager
+ def nested(self, name: str = '') -> T.Generator[None, None, None]:
+ self.log_depth.append(name)
+ try:
+ yield
+ finally:
+ self.log_depth.pop()
+
+ def get_log_dir(self) -> str:
+ return self.log_dir
+
+ def get_log_depth(self) -> int:
+ return len(self.log_depth)
+
+ @contextmanager
+ def nested_warnings(self) -> T.Iterator[None]:
+ old = self.log_warnings_counter
+ self.log_warnings_counter = 0
+ try:
+ yield
+ finally:
+ self.log_warnings_counter = old
+
+ def get_warning_count(self) -> int:
+ return self.log_warnings_counter
+
+_logger = _Logger()
+cmd_ci_include = _logger.cmd_ci_include
+debug = _logger.debug
+deprecation = _logger.deprecation
+error = _logger.error
+exception = _logger.exception
+force_print = _logger.force_print
+get_log_depth = _logger.get_log_depth
+get_log_dir = _logger.get_log_dir
+get_warning_count = _logger.get_warning_count
+initialize = _logger.initialize
+log = _logger.log
+log_timestamp = _logger.log_timestamp
+nested = _logger.nested
+nested_warnings = _logger.nested_warnings
+no_logging = _logger.no_logging
+notice = _logger.notice
+process_markup = _logger.process_markup
+set_quiet = _logger.set_quiet
+set_timestamp_start = _logger.set_timestamp_start
+set_verbose = _logger.set_verbose
+shutdown = _logger.shutdown
+start_pager = _logger.start_pager
+stop_pager = _logger.stop_pager
+warning = _logger.warning
class AnsiDecorator:
plain_code = "\033[0m"
def __str__(self) -> str:
return self.get_text(colorize_console())
-TV_Loggable = T.Union[str, AnsiDecorator, 'StringProtocol']
-TV_LoggableList = T.List[TV_Loggable]
-
class AnsiText:
def __init__(self, *args: 'SizedStringProtocol'):
self.args = args
def normal_cyan(text: str) -> AnsiDecorator:
return AnsiDecorator(text, "\033[36m")
-# This really should be AnsiDecorator or anything that implements
-# __str__(), but that requires protocols from typing_extensions
-def process_markup(args: T.Sequence[TV_Loggable], keep: bool) -> T.List[str]:
- arr = [] # type: T.List[str]
- if log_timestamp_start is not None:
- arr = ['[{:.3f}]'.format(time.monotonic() - log_timestamp_start)]
- for arg in args:
- if arg is None:
- continue
- if isinstance(arg, str):
- arr.append(arg)
- elif isinstance(arg, AnsiDecorator):
- arr.append(arg.get_text(keep))
- else:
- arr.append(str(arg))
- return arr
-
-def force_print(*args: str, nested: bool, **kwargs: T.Any) -> None:
- if log_disable_stdout:
- return
- iostr = io.StringIO()
- kwargs['file'] = iostr
- print(*args, **kwargs)
-
- raw = iostr.getvalue()
- if log_depth:
- prepend = log_depth[-1] + '| ' if nested else ''
- lines = []
- for l in raw.split('\n'):
- l = l.strip()
- lines.append(prepend + l if l else '')
- raw = '\n'.join(lines)
-
- # _Something_ is going to get printed.
- try:
- output = log_pager.stdin if log_pager else None
- print(raw, end='', file=output)
- except UnicodeEncodeError:
- cleaned = raw.encode('ascii', 'replace').decode('ascii')
- print(cleaned, end='')
-
-# We really want a heterogeneous dict for this, but that's in typing_extensions
-def debug(*args: TV_Loggable, **kwargs: T.Any) -> None:
- arr = process_markup(args, False)
- if log_file is not None:
- print(*arr, file=log_file, **kwargs)
- log_file.flush()
-
-def _debug_log_cmd(cmd: str, args: T.List[str]) -> None:
- if not _in_ci:
- return
- args = [f'"{x}"' for x in args] # Quote all args, just in case
- debug('!meson_ci!/{} {}'.format(cmd, ' '.join(args)))
-
-def cmd_ci_include(file: str) -> None:
- _debug_log_cmd('ci_include', [file])
-
-
-def log(*args: TV_Loggable, is_error: bool = False,
- once: bool = False, **kwargs: T.Any) -> None:
- if once:
- log_once(*args, is_error=is_error, **kwargs)
- else:
- _log(*args, is_error=is_error, **kwargs)
-
-
-def _log(*args: TV_Loggable, is_error: bool = False,
- **kwargs: T.Any) -> None:
- nested = kwargs.pop('nested', True)
- arr = process_markup(args, False)
- if log_file is not None:
- print(*arr, file=log_file, **kwargs)
- log_file.flush()
- if colorize_console():
- arr = process_markup(args, True)
- if not log_errors_only or is_error:
- force_print(*arr, nested=nested, **kwargs)
-
-def log_once(*args: TV_Loggable, is_error: bool = False,
- **kwargs: T.Any) -> None:
- """Log variant that only prints a given message one time per meson invocation.
-
- This considers ansi decorated values by the values they wrap without
- regard for the AnsiDecorator itself.
- """
- def to_str(x: TV_Loggable) -> str:
- if isinstance(x, str):
- return x
- if isinstance(x, AnsiDecorator):
- return x.text
- return str(x)
- t = tuple(to_str(a) for a in args)
- if t in _logged_once:
- return
- _logged_once.add(t)
- _log(*args, is_error=is_error, **kwargs)
-
-# This isn't strictly correct. What we really want here is something like:
-# class StringProtocol(typing_extensions.Protocol):
-#
-# def __str__(self) -> str: ...
-#
-# This would more accurately embody what this function can handle, but we
-# don't have that yet, so instead we'll do some casting to work around it
-def get_error_location_string(fname: str, lineno: int) -> str:
+def get_error_location_string(fname: StringProtocol, lineno: int) -> str:
return f'{fname}:{lineno}:'
-def _log_error(severity: str, *rargs: TV_Loggable,
- once: bool = False, fatal: bool = True, **kwargs: T.Any) -> None:
- from .mesonlib import MesonException, relpath
-
- # The typing requirements here are non-obvious. Lists are invariant,
- # therefore T.List[A] and T.List[T.Union[A, B]] are not able to be joined
- if severity == 'notice':
- label = [bold('NOTICE:')] # type: TV_LoggableList
- elif severity == 'warning':
- label = [yellow('WARNING:')]
- elif severity == 'error':
- label = [red('ERROR:')]
- elif severity == 'deprecation':
- label = [red('DEPRECATION:')]
- else:
- raise MesonException('Invalid severity ' + severity)
- # rargs is a tuple, not a list
- args = label + list(rargs)
-
- location = kwargs.pop('location', None)
- if location is not None:
- location_file = relpath(location.filename, os.getcwd())
- location_str = get_error_location_string(location_file, location.lineno)
- # Unions are frankly awful, and we have to T.cast here to get mypy
- # to understand that the list concatenation is safe
- location_list = T.cast('TV_LoggableList', [location_str])
- args = location_list + args
-
- log(*args, once=once, **kwargs)
-
- global log_warnings_counter # pylint: disable=global-statement
- log_warnings_counter += 1
-
- if log_fatal_warnings and fatal:
- raise MesonException("Fatal warnings enabled, aborting")
-
-def error(*args: TV_Loggable, **kwargs: T.Any) -> None:
- return _log_error('error', *args, **kwargs, is_error=True)
-
-def warning(*args: TV_Loggable, **kwargs: T.Any) -> None:
- return _log_error('warning', *args, **kwargs, is_error=True)
-
-def deprecation(*args: TV_Loggable, **kwargs: T.Any) -> None:
- return _log_error('deprecation', *args, **kwargs, is_error=True)
-
-def notice(*args: TV_Loggable, **kwargs: T.Any) -> None:
- return _log_error('notice', *args, **kwargs, is_error=False)
-
def get_relative_path(target: Path, current: Path) -> Path:
"""Get the path to target from current"""
# Go up "current" until we find a common ancestor to target
# we failed, should not get here
return target
-def exception(e: Exception, prefix: T.Optional[AnsiDecorator] = None) -> None:
- if prefix is None:
- prefix = red('ERROR:')
- log()
- args = [] # type: T.List[T.Union[AnsiDecorator, str]]
- if all(getattr(e, a, None) is not None for a in ['file', 'lineno', 'colno']):
- # Mypy doesn't follow hasattr, and it's pretty easy to visually inspect
- # that this is correct, so we'll just ignore it.
- path = get_relative_path(Path(e.file), Path(os.getcwd())) # type: ignore
- args.append(f'{path}:{e.lineno}:{e.colno}:') # type: ignore
- if prefix:
- args.append(prefix)
- args.append(str(e))
- log(*args)
-
# Format a list for logging purposes as a string. It separates
# all but the last item with commas, and the last with 'and'.
def format_list(input_list: T.List[str]) -> str:
else:
return ''
-@contextmanager
-def nested(name: str = '') -> T.Generator[None, None, None]:
- log_depth.append(name)
- try:
- yield
- finally:
- log_depth.pop()
-
-def start_pager() -> None:
- if not colorize_console():
- return
- pager_cmd = []
- if 'PAGER' in os.environ:
- pager_cmd = shlex.split(os.environ['PAGER'])
- else:
- less = shutil.which('less')
- if not less and is_windows():
- git = shutil.which('git')
- if git:
- path = Path(git).parents[1] / 'usr' / 'bin'
- less = shutil.which('less', path=str(path))
- if less:
- pager_cmd = [less]
- if not pager_cmd:
- return
- global log_pager # pylint: disable=global-statement
- assert log_pager is None
- try:
- # Set 'LESS' environment variable, rather than arguments in
- # pager_cmd, to also support the case where the user has 'PAGER'
- # set to 'less'. Arguments set are:
- # "R" : support color
- # "X" : do not clear the screen when leaving the pager
- # "F" : skip the pager if content fits into the screen
- env = os.environ.copy()
- if 'LESS' not in env:
- env['LESS'] = 'RXF'
- # Set "-c" for lv to support color
- if 'LV' not in env:
- env['LV'] = '-c'
- log_pager = subprocess.Popen(pager_cmd, stdin=subprocess.PIPE,
- text=True, encoding='utf-8', env=env)
- except Exception as e:
- # Ignore errors, unless it is a user defined pager.
- if 'PAGER' in os.environ:
- from .mesonlib import MesonException
- raise MesonException(f'Failed to start pager: {str(e)}')
-def stop_pager() -> None:
- global log_pager # pylint: disable=global-statement
- if log_pager:
- try:
- log_pager.stdin.flush()
- log_pager.stdin.close()
- except BrokenPipeError:
- pass
- log_pager.wait()
- log_pager = None
+def code_line(text: str, line: str, colno: int) -> str:
+ """Print a line with a caret pointing to the colno
+
+ :param text: A message to display before the line
+ :param line: The line of code to be pointed to
+ :param colno: The column number to point at
+ :return: A formatted string of the text, line, and a caret
+ """
+ return f'{text}\n{line}\n{" " * colno}^'
import dataclasses
import typing as T
-from .. import mesonlib
+from .. import build, mesonlib
from ..build import IncludeDirs
from ..interpreterbase.decorators import noKwargs, noPosargs
from ..mesonlib import relpath, HoldableObject, MachineChoice
from ..programs import ExternalProgram
if T.TYPE_CHECKING:
- from .. import build
from ..interpreter import Interpreter
+ from ..interpreter.interpreter import ProgramVersionFunc
from ..interpreter.interpreterobjects import MachineHolder
from ..interpreterbase import TYPE_var, TYPE_kwargs
from ..programs import OverrideProgram
from ..wrap import WrapMode
- from ..build import EnvironmentVariables, Executable
from ..dependencies import Dependency
class ModuleState:
return dirs_str
- def find_program(self, prog: T.Union[str, T.List[str]], required: bool = True,
- version_func: T.Optional[T.Callable[['ExternalProgram'], str]] = None,
+ def find_program(self, prog: T.Union[mesonlib.FileOrString, T.List[mesonlib.FileOrString]],
+ required: bool = True,
+ version_func: T.Optional[ProgramVersionFunc] = None,
wanted: T.Optional[str] = None, silent: bool = False,
- for_machine: MachineChoice = MachineChoice.HOST) -> 'ExternalProgram':
+ for_machine: MachineChoice = MachineChoice.HOST) -> T.Union[ExternalProgram, build.Executable, 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['Executable', ExternalProgram, 'OverrideProgram']:
+ wanted: T.Optional[str] = None) -> T.Union['build.Executable', ExternalProgram, 'OverrideProgram']:
# Look in overrides in case it's built as subproject
progobj = self._interpreter.program_from_overrides([name], [])
if progobj is not None:
if dep.found() and dep.type_name == 'pkgconfig':
value = dep.get_variable(pkgconfig=varname)
if value:
- return ExternalProgram(name, [value])
+ progobj = ExternalProgram(value)
+ if not progobj.found():
+ msg = (f'Dependency {depname!r} tool variable {varname!r} contains erroneous value: {value!r}\n\n'
+ 'This is a distributor issue -- please report it to your {depname} provider.')
+ raise mesonlib.MesonException(msg)
+ return progobj
# Normal program lookup
return self.find_program(name, required=required, wanted=wanted)
def dependency(self, depname: str, native: bool = False, required: bool = True,
wanted: T.Optional[str] = None) -> 'Dependency':
- kwargs = {'native': native, 'required': required}
+ kwargs: T.Dict[str, object] = {'native': native, 'required': required}
if wanted:
kwargs['version'] = wanted
# FIXME: Even if we fix the function, mypy still can't figure out what's
def get_option(self, name: str, subproject: str = '',
machine: MachineChoice = MachineChoice.HOST,
lang: T.Optional[str] = None,
- module: T.Optional[str] = None) -> T.Union[str, int, bool, 'WrapMode']:
+ module: T.Optional[str] = None) -> T.Union[T.List[str], str, int, bool, 'WrapMode']:
return self.environment.coredata.get_option(mesonlib.OptionKey(name, subproject, machine, lang, module))
def is_user_defined_option(self, name: str, subproject: str = '',
else:
yield self._interpreter.build_incdir_object([d])
+ def add_language(self, lang: str, for_machine: MachineChoice) -> None:
+ self._interpreter.add_languages([lang], True, for_machine)
class ModuleObject(HoldableObject):
"""Base class for all objects returned by modules
def found() -> bool:
return True
- def get_devenv(self) -> T.Optional['EnvironmentVariables']:
- return None
+ def postconf_hook(self, b: build.Build) -> None:
+ pass
# FIXME: Port all modules to stop using self.interpreter and use API on
# ModuleState instead. Modules should stop using this class and instead use
return False
-def is_module_library(fname):
+def is_module_library(fname: mesonlib.FileOrString) -> bool:
'''
Check if the file is a library-like file generated by a module-specific
target, such as GirTarget or TypelibTarget
'''
- if hasattr(fname, 'fname'):
- fname = fname.fname
suffix = fname.split('.')[-1]
return suffix in {'gir', 'typelib'}
class ModuleReturnValue:
def __init__(self, return_value: T.Optional['TYPE_var'],
- new_objects: T.Sequence[T.Union['TYPE_var', 'build.ExecutableSerialisation']]) -> None:
+ new_objects: T.Sequence[T.Union['TYPE_var', 'mesonlib.ExecutableSerialisation']]) -> None:
self.return_value = return_value
assert isinstance(new_objects, list)
- self.new_objects: T.List[T.Union['TYPE_var', 'build.ExecutableSerialisation']] = new_objects
+ self.new_objects: T.List[T.Union['TYPE_var', 'mesonlib.ExecutableSerialisation']] = new_objects
+
+class GResourceTarget(build.CustomTarget):
+ pass
+
+class GResourceHeaderTarget(build.CustomTarget):
+ pass
+
+class GirTarget(build.CustomTarget):
+ pass
+
+class TypelibTarget(build.CustomTarget):
+ pass
+
+class VapiTarget(build.CustomTarget):
+ pass
from . import ModuleState
from ..cmake import SingleTargetOptions
- from ..interpreter import kwargs
+ from ..environment import Environment
+ from ..interpreter import Interpreter, kwargs
+ from ..interpreterbase import TYPE_kwargs, TYPE_var
class WriteBasicPackageVersionFile(TypedDict):
'found': self.found_method,
})
- def _args_to_info(self, args):
+ def _args_to_info(self, args: T.List[str]) -> T.Dict[str, str]:
if len(args) != 1:
raise InterpreterException('Exactly one argument is required.')
@noKwargs
@stringArgs
- def get_variable(self, state, args, kwargs):
+ def get_variable(self, state: ModuleState, args: T.List[str], kwargs: TYPE_kwargs) -> TYPE_var:
return self.subp.get_variable_method(args, kwargs)
@FeatureNewKwargs('dependency', '0.56.0', ['include_type'])
@permittedKwargs({'include_type'})
@stringArgs
- def dependency(self, state, args, kwargs):
+ def dependency(self, state: ModuleState, args: T.List[str], kwargs: T.Dict[str, str]) -> dependencies.Dependency:
info = self._args_to_info(args)
if info['func'] == 'executable':
raise InvalidArguments(f'{args[0]} is an executable and does not support the dependency() method. Use target() instead.')
@noKwargs
@stringArgs
- def include_directories(self, state, args, kwargs):
+ def include_directories(self, state: ModuleState, args: T.List[str], kwargs: TYPE_kwargs) -> build.IncludeDirs:
info = self._args_to_info(args)
return self.get_variable(state, [info['inc']], kwargs)
@noKwargs
@stringArgs
- def target(self, state, args, kwargs):
+ def target(self, state: ModuleState, args: T.List[str], kwargs: TYPE_kwargs) -> build.Target:
info = self._args_to_info(args)
return self.get_variable(state, [info['tgt']], kwargs)
@noKwargs
@stringArgs
- def target_type(self, state, args, kwargs):
+ def target_type(self, state: ModuleState, args: T.List[str], kwargs: TYPE_kwargs) -> str:
info = self._args_to_info(args)
return info['func']
@noPosargs
@noKwargs
- def target_list(self, state, args, kwargs):
+ def target_list(self, state: ModuleState, args: TYPE_var, kwargs: TYPE_kwargs) -> T.List[str]:
return self.cm_interpreter.target_list()
@noPosargs
@noKwargs
@FeatureNew('CMakeSubproject.found()', '0.53.2')
- def found_method(self, state, args, kwargs):
+ def found_method(self, state: ModuleState, args: TYPE_var, kwargs: TYPE_kwargs) -> bool:
return self.subp is not None
class CMakeSubprojectOptions(ModuleObject):
def __init__(self) -> None:
super().__init__()
- self.cmake_options = [] # type: T.List[str]
+ self.cmake_options: T.List[str] = []
self.target_options = TargetOptions()
self.methods.update(
return self.target_options[kwargs['target']]
return self.target_options.global_options
+ @typed_pos_args('subproject_options.add_cmake_defines', varargs=dict)
@noKwargs
- def add_cmake_defines(self, state, args, kwargs) -> None:
- self.cmake_options += cmake_defines_to_args(args)
+ def add_cmake_defines(self, state: ModuleState, args: T.Tuple[T.List[T.Dict[str, TYPE_var]]], kwargs: TYPE_kwargs) -> None:
+ self.cmake_options += cmake_defines_to_args(args[0])
- @stringArgs
+ @typed_pos_args('subproject_options.set_override_option', str, str)
@permittedKwargs({'target'})
- def set_override_option(self, state, args, kwargs) -> None:
- if len(args) != 2:
- raise InvalidArguments('set_override_option takes exactly 2 positional arguments')
+ def set_override_option(self, state: ModuleState, args: T.Tuple[str, str], kwargs: TYPE_kwargs) -> None:
self._get_opts(kwargs).set_opt(args[0], args[1])
+ @typed_pos_args('subproject_options.set_install', bool)
@permittedKwargs({'target'})
- def set_install(self, state, args, kwargs) -> None:
- if len(args) != 1 or not isinstance(args[0], bool):
- raise InvalidArguments('set_install takes exactly 1 boolean argument')
+ def set_install(self, state: ModuleState, args: T.Tuple[bool], kwargs: TYPE_kwargs) -> None:
self._get_opts(kwargs).set_install(args[0])
- @stringArgs
+ @typed_pos_args('subproject_options.append_compile_args', str, varargs=str, min_varargs=1)
@permittedKwargs({'target'})
- def append_compile_args(self, state, args, kwargs) -> None:
- if len(args) < 2:
- raise InvalidArguments('append_compile_args takes at least 2 positional arguments')
- self._get_opts(kwargs).append_args(args[0], args[1:])
+ def append_compile_args(self, state: ModuleState, args: T.Tuple[str, T.List[str]], kwargs: TYPE_kwargs) -> None:
+ self._get_opts(kwargs).append_args(args[0], args[1])
- @stringArgs
+ @typed_pos_args('subproject_options.append_compile_args', varargs=str, min_varargs=1)
@permittedKwargs({'target'})
- def append_link_args(self, state, args, kwargs) -> None:
- if not args:
- raise InvalidArguments('append_link_args takes at least 1 positional argument')
- self._get_opts(kwargs).append_link_args(args)
+ def append_link_args(self, state: ModuleState, args: T.Tuple[T.List[str]], kwargs: TYPE_kwargs) -> None:
+ self._get_opts(kwargs).append_link_args(args[0])
@noPosargs
@noKwargs
- def clear(self, state, args, kwargs) -> None:
+ def clear(self, state: ModuleState, args: TYPE_var, kwargs: TYPE_kwargs) -> None:
self.cmake_options.clear()
self.target_options = TargetOptions()
INFO = ModuleInfo('cmake', '0.50.0')
- def __init__(self, interpreter):
+ def __init__(self, interpreter: Interpreter) -> None:
super().__init__(interpreter)
self.methods.update({
'write_basic_package_version_file': self.write_basic_package_version_file,
'subproject_options': self.subproject_options,
})
- def detect_voidp_size(self, env):
+ def detect_voidp_size(self, env: Environment) -> int:
compilers = env.coredata.compilers.host
compiler = compilers.get('c', None)
if not compiler:
if not compiler:
raise mesonlib.MesonException('Requires a C or C++ compiler to compute sizeof(void *).')
- return compiler.sizeof('void *', '', env)
+ return compiler.sizeof('void *', '', env)[0]
- def detect_cmake(self, state):
+ def detect_cmake(self, state: ModuleState) -> bool:
if self.cmake_detected:
return True
KwargInfo('version', str, required=True),
INSTALL_DIR_KW,
)
- def write_basic_package_version_file(self, state, args, kwargs: 'WriteBasicPackageVersionFile'):
+ def write_basic_package_version_file(self, state: ModuleState, args: TYPE_var, kwargs: 'WriteBasicPackageVersionFile') -> ModuleReturnValue:
arch_independent = kwargs['arch_independent']
compatibility = kwargs['compatibility']
name = kwargs['name']
version_file = os.path.join(state.environment.scratch_dir, f'{name}ConfigVersion.cmake')
- conf = {
- 'CVF_VERSION': (version, ''),
- 'CMAKE_SIZEOF_VOID_P': (str(self.detect_voidp_size(state.environment)), ''),
- 'CVF_ARCH_INDEPENDENT': (arch_independent, ''),
+ conf: T.Dict[str, T.Union[str, bool, int]] = {
+ 'CVF_VERSION': version,
+ 'CMAKE_SIZEOF_VOID_P': str(self.detect_voidp_size(state.environment)),
+ 'CVF_ARCH_INDEPENDENT': arch_independent,
}
- mesonlib.do_conf_file(template_file, version_file, conf, 'meson')
+ mesonlib.do_conf_file(template_file, version_file, build.ConfigurationData(conf), 'meson')
res = build.Data([mesonlib.File(True, state.environment.get_scratch_dir(), version_file)], pkgroot, pkgroot_name, None, state.subproject)
return ModuleReturnValue(res, [res])
- def create_package_file(self, infile, outfile, PACKAGE_RELATIVE_PATH, extra, confdata):
+ def create_package_file(self, infile: str, outfile: str, PACKAGE_RELATIVE_PATH: str, extra: str, confdata: build.ConfigurationData) -> None:
package_init = PACKAGE_INIT_BASE.replace('@PACKAGE_RELATIVE_PATH@', PACKAGE_RELATIVE_PATH)
package_init = package_init.replace('@inputFileName@', os.path.basename(infile))
package_init += extra
KwargInfo('name', str, required=True),
INSTALL_DIR_KW,
)
- def configure_package_config_file(self, state, args, kwargs: 'ConfigurePackageConfigFile'):
+ def configure_package_config_file(self, state: ModuleState, args: TYPE_var, kwargs: 'ConfigurePackageConfigFile') -> build.Data:
inputfile = kwargs['input']
if isinstance(inputfile, str):
inputfile = mesonlib.File.from_source_file(state.environment.source_dir, state.subdir, inputfile)
if not os.path.isabs(abs_install_dir):
abs_install_dir = os.path.join(prefix, install_dir)
- PACKAGE_RELATIVE_PATH = os.path.relpath(prefix, abs_install_dir)
+ # path used in cmake scripts are POSIX even on Windows
+ PACKAGE_RELATIVE_PATH = pathlib.PurePath(os.path.relpath(prefix, abs_install_dir)).as_posix()
extra = ''
if re.match('^(/usr)?/lib(64)?/.+', abs_install_dir):
extra = PACKAGE_INIT_EXT.replace('@absInstallDir@', abs_install_dir)
'required': kwargs_['required'],
'options': kwargs_['options'],
'cmake_options': kwargs_['cmake_options'],
- 'default_options': [],
+ 'default_options': {},
'version': [],
}
- subp = self.interpreter.do_subproject(dirname, 'cmake', kw)
+ subp = self.interpreter.do_subproject(dirname, kw, force_method='cmake')
if not subp.found():
return subp
return CMakeSubproject(subp)
@FeatureNew('subproject_options', '0.55.0')
@noKwargs
@noPosargs
- def subproject_options(self, state, args, kwargs) -> CMakeSubprojectOptions:
+ def subproject_options(self, state: ModuleState, args: TYPE_var, kwargs: TYPE_kwargs) -> CMakeSubprojectOptions:
return CMakeSubprojectOptions()
-def initialize(*args, **kwargs):
+def initialize(*args: T.Any, **kwargs: T.Any) -> CmakeModule:
return CmakeModule(*args, **kwargs)
cuda_common_gpu_architectures += ['7.5+PTX'] # noqa: E221
cuda_hi_limit_gpu_architecture = '8.0' # noqa: E221
+ # need to account for the fact that Ampere is commonly assumed to include
+ # SM8.0 and SM8.6 even though CUDA 11.0 doesn't support SM8.6
+ cuda_ampere_bin = ['8.0']
+ cuda_ampere_ptx = ['8.0']
if version_compare(cuda_version, '>=11.0'):
cuda_known_gpu_architectures += ['Ampere'] # noqa: E221
cuda_common_gpu_architectures += ['8.0'] # noqa: E221
cuda_hi_limit_gpu_architecture = '8.6' # noqa: E221
if version_compare(cuda_version, '>=11.1'):
+ cuda_ampere_bin += ['8.6'] # noqa: E221
+ cuda_ampere_ptx = ['8.6'] # noqa: E221
+
cuda_common_gpu_architectures += ['8.6'] # noqa: E221
cuda_all_gpu_architectures += ['8.6'] # noqa: E221
'Volta': (['7.0'], ['7.0']),
'Xavier': (['7.2'], []),
'Turing': (['7.5'], ['7.5']),
- 'Ampere': (['8.0'], ['8.0']),
+ 'Ampere': (cuda_ampere_bin, cuda_ampere_ptx),
'Orin': (['8.7'], []),
'Lovelace': (['8.9'], ['8.9']),
'Hopper': (['9.0'], ['9.0']),
arch_ptx = arch_bin
cuda_arch_ptx += arch_ptx
- cuda_arch_bin = sorted(list(set(cuda_arch_bin)))
- cuda_arch_ptx = sorted(list(set(cuda_arch_ptx)))
+ cuda_arch_bin = sorted(set(cuda_arch_bin))
+ cuda_arch_ptx = sorted(set(cuda_arch_ptx))
nvcc_flags = []
nvcc_archs_readable = []
# This file contains the detection logic for external dependencies that
# are UI-related.
+from __future__ import annotations
import json
import os
from . import ExtensionModule, ModuleInfo
-from .. import dependencies
from .. import mlog
+from ..dependencies import Dependency
+from ..dependencies.dub import DubDependency
from ..interpreterbase import typed_pos_args
from ..mesonlib import Popen_safe, MesonException
def _init_dub(self, state):
if DlangModule.class_dubbin is None:
- self.dubbin = dependencies.DubDependency.class_dubbin
+ self.dubbin = DubDependency.class_dubbin
DlangModule.class_dubbin = self.dubbin
else:
self.dubbin = DlangModule.class_dubbin
config[key] = {}
if isinstance(value, list):
for dep in value:
- if isinstance(dep, dependencies.Dependency):
+ if isinstance(dep, Dependency):
name = dep.get_name()
ret, res = self._call_dubbin(['describe', name])
if ret == 0:
config[key][name] = ''
else:
config[key][name] = version
- elif isinstance(value, dependencies.Dependency):
+ elif isinstance(value, Dependency):
name = value.get_name()
ret, res = self._call_dubbin(['describe', name])
if ret == 0:
# 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
from pathlib import Path
import os
from .. import mlog, build
from ..compilers.compilers import CFLAGS_MAPPING
from ..envconfig import ENV_VAR_PROG_MAP
-from ..dependencies import InternalDependency, PkgConfigDependency
+from ..dependencies import InternalDependency
+from ..dependencies.pkgconfig import PkgConfigInterface
from ..interpreterbase import FeatureNew
from ..interpreter.type_checking import ENV_KW, DEPENDS_KW
from ..interpreterbase.decorators import ContainerTypeInfo, KwargInfo, typed_kwargs, typed_pos_args
from typing_extensions import TypedDict
from . import ModuleState
+ from .._typing import ImmutableListProtocol
+ from ..build import BuildTarget, CustomTarget
from ..interpreter import Interpreter
from ..interpreterbase import TYPE_var
- from ..build import BuildTarget, CustomTarget
+ from ..mesonlib import EnvironmentVariables
+ from ..utils.core import EnvironOrDict
class Dependency(TypedDict):
configure_options: T.List[str]
cross_configure_options: T.List[str]
verbose: bool
- env: build.EnvironmentVariables
+ env: EnvironmentVariables
depends: T.List[T.Union[BuildTarget, CustomTarget]]
class ExternalProject(NewExtensionModule):
+
+ make: ImmutableListProtocol[str]
+
def __init__(self,
state: 'ModuleState',
configure_command: str,
configure_options: T.List[str],
cross_configure_options: T.List[str],
- env: build.EnvironmentVariables,
+ env: EnvironmentVariables,
verbose: bool,
extra_depends: T.List[T.Union['BuildTarget', 'CustomTarget']]):
super().__init__()
# Set common env variables like CFLAGS, CC, etc.
link_exelist: T.List[str] = []
link_args: T.List[str] = []
- self.run_env = os.environ.copy()
+ self.run_env: EnvironOrDict = os.environ.copy()
for lang, compiler in self.env.coredata.compilers[MachineChoice.HOST].items():
if any(lang not in i for i in (ENV_VAR_PROG_MAP, CFLAGS_MAPPING)):
continue
self.run_env['LDFLAGS'] = self._quote_and_join(link_args)
self.run_env = self.user_env.get_env(self.run_env)
- self.run_env = PkgConfigDependency.setup_env(self.run_env, self.env, MachineChoice.HOST,
- uninstalled=True)
+ self.run_env = PkgConfigInterface.setup_env(self.run_env, self.env, MachineChoice.HOST,
+ uninstalled=True)
self.build_dir.mkdir(parents=True, exist_ok=True)
self._run('configure', configure_cmd, workdir)
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.log_dir, f'{self.name}-{step}.log')
+ log_filename = Path(mlog.get_log_dir(), f'{self.name}-{step}.log')
output = None
if not self.verbose:
output = open(log_filename, 'w', encoding='utf-8')
'--srcdir', self.src_dir.as_posix(),
'--builddir', self.build_dir.as_posix(),
'--installdir', self.install_dir.as_posix(),
- '--logdir', mlog.log_dir,
+ '--logdir', mlog.get_log_dir(),
'--make', join_args(self.make),
]
if self.verbose:
depfile=f'{self.name}.d',
console=True,
extra_depends=extra_depends,
+ description='Generating external project {}',
)
idir = build.InstallDir(self.subdir.as_posix(),
link_args = [f'-L{abs_libdir}', f'-l{libname}']
sources = self.target
dep = InternalDependency(version, [], compile_args, link_args, [],
- [], [sources], [], {}, [], [])
+ [], [sources], [], [], {}, [], [], [])
return dep
from . import ExtensionModule, ModuleReturnValue, ModuleInfo
from .. import mlog
-from ..build import CustomTarget, InvalidArguments
+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,
-)
+from ..mesonlib import File, MesonException, has_path_sep, path_is_in_root, relpath
if T.TYPE_CHECKING:
from . import ModuleState
+ from ..build import BuildTargetTypes
from ..interpreter import Interpreter
+ from ..interpreterbase import TYPE_kwargs
from ..mesonlib import FileOrString, FileMode
from typing_extensions import TypedDict
'stem': self.stem,
'read': self.read,
'copyfile': self.copyfile,
+ 'relative_to': self.relative_to,
})
def _absolute_dir(self, state: 'ModuleState', arg: 'FileOrString') -> Path:
try:
with open(path, encoding=encoding) as f:
data = f.read()
+ except FileNotFoundError:
+ raise MesonException(f'File {args[0]} does not exist.')
except UnicodeDecodeError:
- raise MesonException(f'decoding failed for {path}')
+ raise MesonException(f'decoding failed for {args[0]}')
# Reconfigure when this file changes as it can contain data used by any
# part of the build configuration (e.g. `project(..., version:
# fs.read_file('VERSION')` or `configure_file(...)`
install_mode=kwargs['install_mode'],
install_tag=[kwargs['install_tag']],
backend=state.backend,
+ description='Copying file {}',
)
return ModuleReturnValue(ct, [ct])
+ @FeatureNew('fs.relative_to', '1.3.0')
+ @typed_pos_args('fs.relative_to', (str, File, CustomTarget, CustomTargetIndex, BuildTarget), (str, File, CustomTarget, CustomTargetIndex, BuildTarget))
+ @noKwargs
+ def relative_to(self, state: ModuleState, args: T.Tuple[T.Union[FileOrString, BuildTargetTypes], T.Union[FileOrString, BuildTargetTypes]], kwargs: TYPE_kwargs) -> str:
+ def to_path(arg: T.Union[FileOrString, CustomTarget, CustomTargetIndex, BuildTarget]) -> str:
+ if isinstance(arg, File):
+ return arg.absolute_path(state.environment.source_dir, state.environment.build_dir)
+ elif isinstance(arg, (CustomTarget, CustomTargetIndex, BuildTarget)):
+ return state.backend.get_target_filename_abs(arg)
+ else:
+ return os.path.join(state.environment.source_dir, state.subdir, arg)
+
+ t = to_path(args[0])
+ f = to_path(args[1])
+
+ return relpath(t, f)
+
def initialize(*args: T.Any, **kwargs: T.Any) -> FSModule:
return FSModule(*args, **kwargs)
import textwrap
import typing as T
-from . import ExtensionModule, ModuleInfo
-from . import ModuleReturnValue
+from . import (
+ ExtensionModule, GirTarget, GResourceHeaderTarget, GResourceTarget, ModuleInfo,
+ ModuleReturnValue, TypelibTarget, VapiTarget,
+)
from .. import build
from .. import interpreter
from .. import mesonlib
from .. import mlog
from ..build import CustomTarget, CustomTargetIndex, Executable, GeneratedList, InvalidArguments
-from ..dependencies import Dependency, PkgConfigDependency, InternalDependency
-from ..interpreter.type_checking import DEPENDS_KW, DEPEND_FILES_KW, INSTALL_DIR_KW, INSTALL_KW, NoneType, SOURCES_KW, in_set_validator
+from ..dependencies import Dependency, InternalDependency
+from ..dependencies.pkgconfig import PkgConfigDependency, PkgConfigInterface
+from ..interpreter.type_checking import DEPENDS_KW, DEPEND_FILES_KW, ENV_KW, INSTALL_DIR_KW, INSTALL_KW, NoneType, DEPENDENCY_SOURCES_KW, in_set_validator
from ..interpreterbase import noPosargs, noKwargs, FeatureNew, FeatureDeprecated
from ..interpreterbase import typed_kwargs, KwargInfo, ContainerTypeInfo
from ..interpreterbase.decorators import typed_pos_args
from ..mesonlib import (
- MachineChoice, MesonException, OrderedSet, Popen_safe, join_args,
+ MachineChoice, MesonException, OrderedSet, Popen_safe, join_args, quote_arg
)
from ..programs import OverrideProgram
from ..scripts.gettext import read_linguas
build_by_default: bool
c_name: T.Optional[str]
- dependencies: T.List[T.Union[mesonlib.File, build.CustomTarget, build.CustomTargetIndex]]
+ dependencies: T.List[T.Union[mesonlib.File, CustomTarget, CustomTargetIndex]]
export: bool
extra_args: T.List[str]
gresource_bundle: bool
class _MkEnumsCommon(TypedDict):
- sources: T.List[T.Union[FileOrString, build.GeneratedTypes]]
install_header: bool
install_dir: T.Optional[str]
identifier_prefix: T.Optional[str]
class MkEnumsSimple(_MkEnumsCommon):
+ sources: T.List[FileOrString]
header_prefix: str
decorator: str
function_prefix: str
class MkEnums(_MkEnumsCommon):
+ sources: T.List[T.Union[FileOrString, build.GeneratedTypes]]
c_template: T.Optional[FileOrString]
h_template: T.Optional[FileOrString]
comments: T.Optional[str]
vtail: T.Optional[str]
depends: T.List[T.Union[BuildTarget, CustomTarget, CustomTargetIndex]]
+ ToolType = T.Union[Executable, ExternalProgram, OverrideProgram]
+
# Differs from the CustomTarget version in that it straight defaults to True
_BUILD_BY_DEFAULT: KwargInfo[bool] = KwargInfo(
_MK_ENUMS_COMMON_KWS: T.List[KwargInfo] = [
INSTALL_KW.evolve(name='install_header'),
INSTALL_DIR_KW,
- KwargInfo(
- 'sources',
- ContainerTypeInfo(list, (str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)),
- listify=True,
- required=True,
- ),
KwargInfo('identifier_prefix', (str, NoneType)),
KwargInfo('symbol_prefix', (str, NoneType)),
]
return f'element {c+1} {badlist}'
return None
-class GResourceTarget(build.CustomTarget):
- pass
-
-class GResourceHeaderTarget(build.CustomTarget):
- pass
-
-class GirTarget(build.CustomTarget):
- pass
-
-class TypelibTarget(build.CustomTarget):
- pass
-
-class VapiTarget(build.CustomTarget):
- pass
-
# gresource compilation is broken due to the way
# the resource compiler and Ninja clash about it
#
def __init__(self, interpreter: 'Interpreter') -> None:
super().__init__(interpreter)
self.gir_dep: T.Optional[Dependency] = None
- self.giscanner: T.Optional[T.Union[ExternalProgram, build.Executable, OverrideProgram]] = None
- self.gicompiler: T.Optional[T.Union[ExternalProgram, build.Executable, OverrideProgram]] = None
+ self.giscanner: T.Optional[T.Union[ExternalProgram, Executable, OverrideProgram]] = None
+ self.gicompiler: T.Optional[T.Union[ExternalProgram, Executable, OverrideProgram]] = None
self.install_glib_compile_schemas = False
self.install_gio_querymodules: T.List[str] = []
self.install_gtk_update_icon_cache = False
self.install_update_desktop_database = False
self.install_update_mime_database = False
- self.devenv: T.Optional[build.EnvironmentVariables] = None
+ self.devenv: T.Optional[mesonlib.EnvironmentVariables] = None
self.native_glib_version: T.Optional[str] = None
self.methods.update({
'post_install': self.post_install,
gresource_dep_needed_version):
mlog.warning('GLib compiled dependencies do not work reliably with \n'
'the current version of GLib. See the following upstream issue:',
- mlog.bold('https://bugzilla.gnome.org/show_bug.cgi?id=774368'))
+ mlog.bold('https://bugzilla.gnome.org/show_bug.cgi?id=774368'),
+ once=True, fatal=False)
@staticmethod
def _print_gdbus_warning() -> None:
mlog.warning('Code generated with gdbus_codegen() requires the root directory be added to\n'
' include_directories of targets with GLib < 2.51.3:',
mlog.bold('https://github.com/mesonbuild/meson/issues/1387'),
- once=True)
+ once=True, fatal=False)
+
+ @staticmethod
+ def _find_tool(state: 'ModuleState', tool: str) -> 'ToolType':
+ tool_map = {
+ 'gio-querymodules': 'gio-2.0',
+ 'glib-compile-schemas': 'gio-2.0',
+ 'glib-compile-resources': 'gio-2.0',
+ 'gdbus-codegen': 'gio-2.0',
+ 'glib-genmarshal': 'glib-2.0',
+ 'glib-mkenums': 'glib-2.0',
+ 'g-ir-scanner': 'gobject-introspection-1.0',
+ 'g-ir-compiler': 'gobject-introspection-1.0',
+ }
+ depname = tool_map[tool]
+ varname = tool.replace('-', '_')
+ return state.find_tool(tool, depname, varname)
@typed_kwargs(
'gnome.post_install',
@noPosargs
@FeatureNew('gnome.post_install', '0.57.0')
def post_install(self, state: 'ModuleState', args: T.List['TYPE_var'], kwargs: 'PostInstall') -> ModuleReturnValue:
- rv: T.List['build.ExecutableSerialisation'] = []
+ rv: T.List['mesonlib.ExecutableSerialisation'] = []
datadir_abs = os.path.join(state.environment.get_prefix(), state.environment.get_datadir())
if kwargs['glib_compile_schemas'] and not self.install_glib_compile_schemas:
self.install_glib_compile_schemas = True
- prog = state.find_tool('glib-compile-schemas', 'gio-2.0', 'glib_compile_schemas')
+ prog = self._find_tool(state, 'glib-compile-schemas')
schemasdir = os.path.join(datadir_abs, 'glib-2.0', 'schemas')
script = state.backend.get_executable_serialisation([prog, schemasdir])
script.skip_if_destdir = True
for d in kwargs['gio_querymodules']:
if d not in self.install_gio_querymodules:
self.install_gio_querymodules.append(d)
- prog = state.find_tool('gio-querymodules', 'gio-2.0', 'gio_querymodules')
+ prog = self._find_tool(state, 'gio-querymodules')
moduledir = os.path.join(state.environment.get_prefix(), d)
script = state.backend.get_executable_serialisation([prog, moduledir])
script.skip_if_destdir = True
if kwargs['gtk_update_icon_cache'] and not self.install_gtk_update_icon_cache:
self.install_gtk_update_icon_cache = True
prog = state.find_program('gtk4-update-icon-cache', required=False)
- found = isinstance(prog, build.Executable) or prog.found()
+ found = isinstance(prog, Executable) or prog.found()
if not found:
prog = state.find_program('gtk-update-icon-cache')
icondir = os.path.join(datadir_abs, 'icons', 'hicolor')
rv.append(script)
return ModuleReturnValue(None, rv)
- @typed_pos_args('gnome.compile_resources', str, (str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList))
+ @typed_pos_args('gnome.compile_resources', str, (str, mesonlib.File, CustomTarget, CustomTargetIndex, GeneratedList))
@typed_kwargs(
'gnome.compile_resources',
_BUILD_BY_DEFAULT,
INSTALL_KW.evolve(name='install_header', since='0.37.0'),
INSTALL_DIR_KW,
KwargInfo('c_name', (str, NoneType)),
- KwargInfo('dependencies', ContainerTypeInfo(list, (mesonlib.File, build.CustomTarget, build.CustomTargetIndex)), default=[], listify=True),
+ KwargInfo('dependencies', ContainerTypeInfo(list, (mesonlib.File, CustomTarget, CustomTargetIndex)), default=[], listify=True),
KwargInfo('export', bool, default=False, since='0.37.0'),
KwargInfo('gresource_bundle', bool, default=False, since='0.37.0'),
KwargInfo('source_dir', ContainerTypeInfo(list, str), default=[], listify=True),
self.__print_gresources_warning(state)
glib_version = self._get_native_glib_version(state)
- glib_compile_resources = state.find_program('glib-compile-resources')
- cmd: T.List[T.Union[ExternalProgram, str]] = [glib_compile_resources, '@INPUT@']
+ glib_compile_resources = self._find_tool(state, 'glib-compile-resources')
+ cmd: T.List[T.Union['ToolType', str]] = [glib_compile_resources, '@INPUT@']
source_dirs = kwargs['source_dir']
dependencies = kwargs['dependencies']
# Validate dependencies
subdirs: T.List[str] = []
- depends: T.List[T.Union[build.CustomTarget, build.CustomTargetIndex]] = []
+ depends: T.List[T.Union[CustomTarget, CustomTargetIndex]] = []
for dep in dependencies:
if isinstance(dep, mesonlib.File):
subdirs.append(dep.subdir)
depends.append(dep)
subdirs.append(dep.get_subdir())
if not mesonlib.version_compare(glib_version, gresource_dep_needed_version):
- m = 'The "dependencies" argument of gnome.compile_resources() can not\n' \
+ m = 'The "dependencies" argument of gnome.compile_resources() cannot\n' \
'be used with the current version of glib-compile-resources due to\n' \
'<https://bugzilla.gnome.org/show_bug.cgi?id=774368>'
raise MesonException(m)
else:
ifile = os.path.join(input_file.subdir, input_file.fname)
- elif isinstance(input_file, (build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)):
+ elif isinstance(input_file, (CustomTarget, CustomTargetIndex, GeneratedList)):
raise MesonException('Resource xml files generated at build-time cannot be used with '
'gnome.compile_resources() in the current version of glib-compile-resources '
'because we need to scan the xml for dependencies due to '
raise MesonException('GResource header is installed yet export is not enabled')
depfile: T.Optional[str] = None
- target_cmd: T.List[T.Union[ExternalProgram, str]]
+ target_cmd: T.List[T.Union['ToolType', str]]
if not mesonlib.version_compare(glib_version, gresource_dep_needed_version):
# This will eventually go out of sync if dependencies are added
target_cmd = cmd
@staticmethod
def _get_gresource_dependencies(
state: 'ModuleState', input_file: str, source_dirs: T.List[str],
- dependencies: T.Sequence[T.Union[mesonlib.File, build.CustomTarget, build.CustomTargetIndex]]
- ) -> T.Tuple[T.List[mesonlib.FileOrString], T.List[T.Union[build.CustomTarget, build.CustomTargetIndex]], T.List[str]]:
+ dependencies: T.Sequence[T.Union[mesonlib.File, CustomTarget, CustomTargetIndex]]
+ ) -> T.Tuple[T.List[mesonlib.FileOrString], T.List[T.Union[CustomTarget, CustomTargetIndex]], T.List[str]]:
cmd = ['glib-compile-resources',
input_file,
raw_dep_files: T.List[str] = stdout.split('\n')[:-1]
- depends: T.List[T.Union[build.CustomTarget, build.CustomTargetIndex]] = []
+ depends: T.List[T.Union[CustomTarget, CustomTargetIndex]] = []
subdirs: T.List[str] = []
dep_files: T.List[mesonlib.FileOrString] = []
for resfile in raw_dep_files.copy():
dep_files.append(dep)
subdirs.append(dep.subdir)
break
- elif isinstance(dep, (build.CustomTarget, build.CustomTargetIndex)):
+ elif isinstance(dep, (CustomTarget, CustomTargetIndex)):
fname = None
outputs = {(o, os.path.basename(o)) for o in dep.get_outputs()}
for o, baseo in outputs:
return link_command, new_depends
def _get_dependencies_flags_raw(
- self, deps: T.Sequence[T.Union['Dependency', build.BuildTarget, build.CustomTarget, build.CustomTargetIndex]],
+ self, deps: T.Sequence[T.Union['Dependency', build.BuildTarget, CustomTarget, CustomTargetIndex]],
state: 'ModuleState',
depends: T.Sequence[T.Union[build.BuildTarget, 'build.GeneratedTypes', 'FileOrString', build.StructuredSources]],
include_rpath: bool,
return cflags, internal_ldflags, external_ldflags, gi_includes, depends
def _get_dependencies_flags(
- self, deps: T.Sequence[T.Union['Dependency', build.BuildTarget, build.CustomTarget, build.CustomTargetIndex]],
+ self, deps: T.Sequence[T.Union['Dependency', build.BuildTarget, CustomTarget, CustomTargetIndex]],
state: 'ModuleState',
depends: T.Sequence[T.Union[build.BuildTarget, 'build.GeneratedTypes', 'FileOrString', build.StructuredSources]],
include_rpath: bool = False,
return cflags, internal_ldflags, external_ldflags, gi_includes, depends
- def _unwrap_gir_target(self, girtarget: T.Union[build.Executable, build.StaticLibrary, build.SharedLibrary], state: 'ModuleState'
- ) -> T.Union[build.Executable, build.StaticLibrary, build.SharedLibrary]:
- if not isinstance(girtarget, (build.Executable, build.SharedLibrary,
+ def _unwrap_gir_target(self, girtarget: T.Union[Executable, build.StaticLibrary, build.SharedLibrary], state: 'ModuleState'
+ ) -> T.Union[Executable, build.StaticLibrary, build.SharedLibrary]:
+ if not isinstance(girtarget, (Executable, build.SharedLibrary,
build.StaticLibrary)):
raise MesonException(f'Gir target must be an executable or library but is "{girtarget}" of type {type(girtarget).__name__}')
def _devenv_prepend(self, varname: str, value: str) -> None:
if self.devenv is None:
- self.devenv = build.EnvironmentVariables()
+ self.devenv = mesonlib.EnvironmentVariables()
self.devenv.prepend(varname, [value])
- def get_devenv(self) -> T.Optional[build.EnvironmentVariables]:
- return self.devenv
+ def postconf_hook(self, b: build.Build) -> None:
+ if self.devenv is not None:
+ b.devenv.append(self.devenv)
- def _get_gir_dep(self, state: 'ModuleState') -> T.Tuple[Dependency, T.Union[build.Executable, 'ExternalProgram', 'OverrideProgram'],
- T.Union[build.Executable, 'ExternalProgram', 'OverrideProgram']]:
+ def _get_gir_dep(self, state: 'ModuleState') -> T.Tuple[Dependency, T.Union[Executable, 'ExternalProgram', 'OverrideProgram'],
+ T.Union[Executable, 'ExternalProgram', 'OverrideProgram']]:
if not self.gir_dep:
self.gir_dep = state.dependency('gobject-introspection-1.0')
- self.giscanner = state.find_tool('g-ir-scanner', 'gobject-introspection-1.0', 'g_ir_scanner')
- self.gicompiler = state.find_tool('g-ir-compiler', 'gobject-introspection-1.0', 'g_ir_compiler')
+ self.giscanner = self._find_tool(state, 'g-ir-scanner')
+ self.gicompiler = self._find_tool(state, 'g-ir-compiler')
return self.gir_dep, self.giscanner, self.gicompiler
@functools.lru_cache(maxsize=None)
return ret
@staticmethod
- def _scan_gir_targets(state: 'ModuleState', girtargets: T.Sequence[build.BuildTarget]) -> T.List[T.Union[str, build.Executable]]:
- ret: T.List[T.Union[str, build.Executable]] = []
+ def _scan_gir_targets(state: 'ModuleState', girtargets: T.Sequence[build.BuildTarget]) -> T.List[T.Union[str, Executable]]:
+ ret: T.List[T.Union[str, Executable]] = []
for girtarget in girtargets:
- if isinstance(girtarget, build.Executable):
+ if isinstance(girtarget, Executable):
ret += ['--program', girtarget]
else:
# Because of https://gitlab.gnome.org/GNOME/gobject-introspection/merge_requests/72
@staticmethod
def _get_gir_targets_deps(girtargets: T.Sequence[build.BuildTarget]
- ) -> T.List[T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex, Dependency]]:
- ret: T.List[T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex, Dependency]] = []
+ ) -> T.List[T.Union[build.BuildTarget, CustomTarget, CustomTargetIndex, Dependency]]:
+ ret: T.List[T.Union[build.BuildTarget, CustomTarget, CustomTargetIndex, Dependency]] = []
for girtarget in girtargets:
ret += girtarget.get_all_link_deps()
ret += girtarget.get_external_deps()
return ret
@staticmethod
- def _get_gir_targets_inc_dirs(girtargets: T.Sequence[build.BuildTarget]) -> T.List[build.IncludeDirs]:
- ret: T.List[build.IncludeDirs] = []
+ def _get_gir_targets_inc_dirs(girtargets: T.Sequence[build.BuildTarget]) -> OrderedSet[build.IncludeDirs]:
+ ret: OrderedSet = OrderedSet()
for girtarget in girtargets:
- ret += girtarget.get_include_dirs()
+ ret.update(girtarget.get_include_dirs())
return ret
@staticmethod
def _make_gir_filelist(state: 'ModuleState', srcdir: str, ns: str,
nsversion: str, girtargets: T.Sequence[build.BuildTarget],
libsources: T.Sequence[T.Union[
- str, mesonlib.File, build.GeneratedList,
- build.CustomTarget, build.CustomTargetIndex]]
+ str, mesonlib.File, GeneratedList,
+ CustomTarget, CustomTargetIndex]]
) -> str:
gir_filelist_dir = state.backend.get_target_private_dir_abs(girtargets[0])
if not os.path.isdir(gir_filelist_dir):
with open(gir_filelist_filename, 'w', encoding='utf-8') as gir_filelist:
for s in libsources:
- if isinstance(s, (build.CustomTarget, build.CustomTargetIndex)):
+ if isinstance(s, (CustomTarget, CustomTargetIndex)):
for custom_output in s.get_outputs():
gir_filelist.write(os.path.join(state.environment.get_build_dir(),
state.backend.get_target_dir(s),
custom_output) + '\n')
elif isinstance(s, mesonlib.File):
gir_filelist.write(s.rel_to_builddir(state.build_to_src) + '\n')
- elif isinstance(s, build.GeneratedList):
+ elif isinstance(s, GeneratedList):
for gen_src in s.get_outputs():
gir_filelist.write(os.path.join(srcdir, gen_src) + '\n')
else:
state: 'ModuleState',
girfile: str,
scan_command: T.Sequence[T.Union['FileOrString', Executable, ExternalProgram, OverrideProgram]],
- generated_files: T.Sequence[T.Union[str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList]],
+ generated_files: T.Sequence[T.Union[str, mesonlib.File, CustomTarget, CustomTargetIndex, GeneratedList]],
depends: T.Sequence[T.Union['FileOrString', build.BuildTarget, 'build.GeneratedTypes', build.StructuredSources]],
kwargs: T.Dict[str, T.Any]) -> GirTarget:
install = kwargs['install_gir']
# -uninstalled.pc files Meson generated. It also must respect pkgconfig
# settings user could have set in machine file, like PKG_CONFIG_LIBDIR,
# SYSROOT, etc.
- run_env = PkgConfigDependency.get_env(state.environment, MachineChoice.HOST, uninstalled=True)
+ run_env = PkgConfigInterface.get_env(state.environment, MachineChoice.HOST, uninstalled=True)
+ # g-ir-scanner uses Python's distutils to find the compiler, which uses 'CC'
+ cc_exelist = state.environment.coredata.compilers.host['c'].get_exelist()
+ run_env.set('CC', [quote_arg(x) for x in cc_exelist], ' ')
+ run_env.merge(kwargs['env'])
return GirTarget(
girfile,
@staticmethod
def _make_typelib_target(state: 'ModuleState', typelib_output: str,
- typelib_cmd: T.Sequence[T.Union[str, build.Executable, ExternalProgram, build.CustomTarget]],
- generated_files: T.Sequence[T.Union[str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList]],
+ typelib_cmd: T.Sequence[T.Union[str, Executable, ExternalProgram, CustomTarget]],
+ generated_files: T.Sequence[T.Union[str, mesonlib.File, CustomTarget, CustomTargetIndex, GeneratedList]],
kwargs: T.Dict[str, T.Any]) -> TypelibTarget:
install = kwargs['install_typelib']
if install is None:
install_dir=[install_dir],
install_tag=['typelib'],
build_by_default=kwargs['build_by_default'],
+ env=kwargs['env'],
)
@staticmethod
def _gather_typelib_includes_and_update_depends(
state: 'ModuleState',
- deps: T.Sequence[T.Union[Dependency, build.BuildTarget, build.CustomTarget, build.CustomTargetIndex]],
+ deps: T.Sequence[T.Union[Dependency, build.BuildTarget, CustomTarget, CustomTargetIndex]],
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
if f.startswith(('-L', '-l', '--extra-library')):
yield f
- @typed_pos_args('gnome.generate_gir', varargs=(build.Executable, build.SharedLibrary, build.StaticLibrary), min_varargs=1)
+ @typed_pos_args('gnome.generate_gir', varargs=(Executable, build.SharedLibrary, build.StaticLibrary), min_varargs=1)
@typed_kwargs(
'gnome.generate_gir',
INSTALL_KW,
_BUILD_BY_DEFAULT.evolve(since='0.40.0'),
_EXTRA_ARGS_KW,
+ ENV_KW.evolve(since='1.2.0'),
KwargInfo('dependencies', ContainerTypeInfo(list, Dependency), default=[], listify=True),
KwargInfo('export_packages', ContainerTypeInfo(list, str), default=[], listify=True),
KwargInfo('fatal_warnings', bool, default=False, since='0.55.0'),
KwargInfo('link_with', ContainerTypeInfo(list, (build.SharedLibrary, build.StaticLibrary)), default=[], listify=True),
KwargInfo('namespace', str, required=True),
KwargInfo('nsversion', str, required=True),
- KwargInfo('sources', ContainerTypeInfo(list, (str, mesonlib.File, build.GeneratedList, build.CustomTarget, build.CustomTargetIndex)), default=[], listify=True),
+ KwargInfo('sources', ContainerTypeInfo(list, (str, mesonlib.File, GeneratedList, CustomTarget, CustomTargetIndex)), default=[], listify=True),
KwargInfo('symbol_prefix', ContainerTypeInfo(list, str), default=[], listify=True),
)
- def generate_gir(self, state: 'ModuleState', args: T.Tuple[T.List[T.Union[build.Executable, build.SharedLibrary, build.StaticLibrary]]],
+ def generate_gir(self, state: 'ModuleState', args: T.Tuple[T.List[T.Union[Executable, build.SharedLibrary, build.StaticLibrary]]],
kwargs: 'GenerateGir') -> ModuleReturnValue:
+ # Ensure we have a C compiler even in C++ projects.
+ state.add_language('c', MachineChoice.HOST)
+
girtargets = [self._unwrap_gir_target(arg, state) for arg in args[0]]
- if len(girtargets) > 1 and any(isinstance(el, build.Executable) for el in girtargets):
+ if len(girtargets) > 1 and any(isinstance(el, Executable) for el in girtargets):
raise MesonException('generate_gir only accepts a single argument when one of the arguments is an executable')
gir_dep, giscanner, gicompiler = self._get_gir_dep(state)
gir_inc_dirs: T.List[str] = []
- scan_command: T.List[T.Union[str, build.Executable, 'ExternalProgram', 'OverrideProgram']] = [giscanner]
+ scan_command: T.List[T.Union[str, Executable, 'ExternalProgram', 'OverrideProgram']] = [giscanner]
scan_command += ['--quiet']
scan_command += ['--no-libtool']
scan_command += ['--namespace=' + ns, '--nsversion=' + nsversion]
srcdir = os.path.join(state.build_to_src, state.subdir)
outdir = state.subdir
- cmd: T.List[T.Union[ExternalProgram, str]] = [state.find_program('glib-compile-schemas'), '--targetdir', outdir, srcdir]
+ cmd: T.List[T.Union['ToolType', str]] = [self._find_tool(state, 'glib-compile-schemas'), '--targetdir', outdir, srcdir]
if state.subdir == '':
targetname = 'gsettings-compile'
else:
targetname = 'gsettings-compile-' + state.subdir.replace('/', '_')
- target_g = build.CustomTarget(
+ target_g = CustomTarget(
targetname,
state.subdir,
state.subproject,
['gschemas.compiled'],
build_by_default=kwargs['build_by_default'],
depend_files=kwargs['depend_files'],
+ description='Compiling gschemas {}',
)
self._devenv_prepend('GSETTINGS_SCHEMA_DIR', os.path.join(state.environment.get_build_dir(), state.subdir))
return ModuleReturnValue(target_g, [target_g])
pot_file = os.path.join('@SOURCE_ROOT@', state.subdir, 'C', project_id + '.pot')
pot_sources = [os.path.join('@SOURCE_ROOT@', state.subdir, 'C', s) for s in sources]
- pot_args: T.List[T.Union['ExternalProgram', str]] = [itstool, '-o', pot_file]
+ pot_args: T.List[T.Union[ExternalProgram, Executable, OverrideProgram, str]] = [itstool, '-o', pot_file]
pot_args.extend(pot_sources)
pottarget = build.RunTarget(f'help-{project_id}-pot', pot_args, [],
os.path.join(state.subdir, 'C'), state.subproject,
targets.append(l_data)
po_file = l + '.po'
- po_args: T.List[T.Union['ExternalProgram', str]] = [
+ po_args: T.List[T.Union[ExternalProgram, Executable, OverrideProgram, str]] = [
msgmerge, '-q', '-o',
os.path.join('@SOURCE_ROOT@', l_subdir, po_file),
os.path.join('@SOURCE_ROOT@', l_subdir, po_file), pot_file]
potargets.append(potarget)
gmo_file = project_id + '-' + l + '.gmo'
- gmotarget = build.CustomTarget(
+ gmotarget = CustomTarget(
f'help-{project_id}-{l}-gmo',
l_subdir,
state.subproject,
[po_file],
[gmo_file],
install_tag=['doc'],
+ description='Generating yelp doc {}',
)
targets.append(gmotarget)
- mergetarget = build.CustomTarget(
+ mergetarget = CustomTarget(
f'help-{project_id}-{l}',
l_subdir,
state.subproject,
install=True,
install_dir=[l_install_dir],
install_tag=['doc'],
+ description='Generating yelp doc {}',
)
targets.append(mergetarget)
'gnome.gtkdoc',
KwargInfo('c_args', ContainerTypeInfo(list, str), since='0.48.0', default=[], listify=True),
KwargInfo('check', bool, default=False, since='0.52.0'),
- KwargInfo('content_files', ContainerTypeInfo(list, (str, mesonlib.File, build.GeneratedList, build.CustomTarget, build.CustomTargetIndex)), default=[], listify=True),
+ KwargInfo('content_files', ContainerTypeInfo(list, (str, mesonlib.File, GeneratedList, CustomTarget, CustomTargetIndex)), default=[], listify=True),
KwargInfo(
'dependencies',
ContainerTypeInfo(list, (Dependency, build.SharedLibrary, build.StaticLibrary)),
namespace = kwargs['namespace']
+ # Ensure we have a C compiler even in C++ projects.
+ state.add_language('c', MachineChoice.HOST)
+
def abs_filenames(files: T.Iterable['FileOrString']) -> T.Iterator[str]:
for f in files:
if isinstance(f, mesonlib.File):
program_name = 'gtkdoc-' + tool
program = state.find_program(program_name)
path = program.get_path()
+ assert path is not None, "This shouldn't be possible since program should be found"
t_args.append(f'--{program_name}={path}')
if namespace:
t_args.append('--namespace=' + namespace)
depends: T.List['build.GeneratedTypes'] = []
content_files = []
for s in kwargs['content_files']:
- if isinstance(s, (build.CustomTarget, build.CustomTargetIndex)):
+ if isinstance(s, (CustomTarget, CustomTargetIndex)):
depends.append(s)
for o in s.get_outputs():
content_files.append(os.path.join(state.environment.get_build_dir(),
elif isinstance(s, mesonlib.File):
content_files.append(s.absolute_path(state.environment.get_source_dir(),
state.environment.get_build_dir()))
- elif isinstance(s, build.GeneratedList):
+ elif isinstance(s, GeneratedList):
depends.append(s)
for gen_src in s.get_outputs():
content_files.append(os.path.join(state.environment.get_source_dir(),
kwargs['dependencies'], state, depends)
t_args.extend(build_args)
new_depends.extend(depends)
- custom_target = build.CustomTarget(
+ custom_target = CustomTarget(
targetname,
state.subdir,
state.subproject,
[f'{modulename}-decl.txt'],
build_always_stale=True,
extra_depends=new_depends,
+ description='Generating gtkdoc {}',
)
alias_target = build.AliasTarget(targetname, [custom_target], state.subdir, state.subproject, state.environment)
if kwargs['check']:
check_args = (targetname + '-check', check_cmd)
check_workdir = os.path.join(state.environment.get_build_dir(), state.subdir)
state.test(check_args, env=check_env, workdir=check_workdir, depends=[custom_target])
- res: T.List[T.Union[build.Target, build.ExecutableSerialisation]] = [custom_target, alias_target]
+ res: T.List[T.Union[build.Target, mesonlib.ExecutableSerialisation]] = [custom_target, alias_target]
if kwargs['install']:
res.append(state.backend.get_executable_serialisation(command + t_args, tag='doc'))
return ModuleReturnValue(custom_target, res)
def gtkdoc_html_dir(self, state: 'ModuleState', args: T.Tuple[str], kwargs: 'TYPE_kwargs') -> str:
return os.path.join('share/gtk-doc/html', args[0])
- @typed_pos_args('gnome.gdbus_codegen', str, optargs=[(str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)])
+ @typed_pos_args('gnome.gdbus_codegen', str, optargs=[(str, mesonlib.File, CustomTarget, CustomTargetIndex, GeneratedList)])
@typed_kwargs(
'gnome.gdbus_codegen',
_BUILD_BY_DEFAULT.evolve(since='0.40.0'),
- SOURCES_KW.evolve(since='0.46.0'),
+ DEPENDENCY_SOURCES_KW.evolve(since='0.46.0'),
KwargInfo('extra_args', ContainerTypeInfo(list, str), since='0.47.0', default=[], listify=True),
KwargInfo('interface_prefix', (str, NoneType)),
KwargInfo('namespace', (str, NoneType)),
kwargs: 'GdbusCodegen') -> ModuleReturnValue:
namebase = args[0]
xml_files: T.List[T.Union['FileOrString', build.GeneratedTypes]] = [args[1]] if args[1] else []
- cmd: T.List[T.Union['ExternalProgram', str]] = [state.find_program('gdbus-codegen')]
+ cmd: T.List[T.Union['ToolType', str]] = [self._find_tool(state, 'gdbus-codegen')]
cmd.extend(kwargs['extra_args'])
# Autocleanup supported?
cmd += ['--generate-c-code', '@OUTDIR@/' + namebase, '@INPUT@']
c_cmd = cmd
- cfile_custom_target = build.CustomTarget(
+ cfile_custom_target = CustomTarget(
output,
state.subdir,
state.subproject,
xml_files,
[output],
build_by_default=build_by_default,
+ description='Generating gdbus source {}',
)
targets.append(cfile_custom_target)
hfile_cmd = cmd
depends = [cfile_custom_target]
- hfile_custom_target = build.CustomTarget(
+ hfile_custom_target = CustomTarget(
output,
state.subdir,
state.subproject,
install=install_header,
install_dir=[install_dir],
install_tag=['devel'],
+ description='Generating gdbus header {}',
)
targets.append(hfile_custom_target)
docbook_cmd = cmd
depends = [cfile_custom_target]
- docbook_custom_target = build.CustomTarget(
+ docbook_custom_target = CustomTarget(
output,
state.subdir,
state.subproject,
outputs,
build_by_default=build_by_default,
extra_depends=depends,
+ description='Generating gdbus docbook {}',
)
targets.append(docbook_custom_target)
'gnome.mkenums',
*_MK_ENUMS_COMMON_KWS,
DEPENDS_KW,
+ KwargInfo(
+ 'sources',
+ ContainerTypeInfo(list, (str, mesonlib.File, CustomTarget, CustomTargetIndex,
+ GeneratedList)),
+ listify=True,
+ required=True,
+ ),
KwargInfo('c_template', (str, mesonlib.File, NoneType)),
KwargInfo('h_template', (str, mesonlib.File, NoneType)),
KwargInfo('comments', (str, NoneType)),
'identifier_prefix', 'symbol_prefix',
'vhead', 'vprod', 'vtail']
for arg in known_kwargs:
- # mypy can't figure this out
+ # Mypy can't figure out that this TypedDict index is correct, without repeating T.Literal for the entire list
if kwargs[arg]: # type: ignore
cmd += ['--' + arg.replace('_', '-'), kwargs[arg]] # type: ignore
@typed_kwargs(
'gnome.mkenums_simple',
*_MK_ENUMS_COMMON_KWS,
+ KwargInfo(
+ 'sources',
+ ContainerTypeInfo(list, (str, mesonlib.File)),
+ listify=True,
+ required=True,
+ ),
KwargInfo('header_prefix', str, default=''),
KwargInfo('function_prefix', str, default=''),
KwargInfo('body_prefix', str, default=''),
if body_prefix != '':
fhead += '%s\n' % body_prefix
fhead += '#include "%s"\n' % hdr_filename
- for hdr in kwargs['sources']:
- fhead += '#include "{}"\n'.format(os.path.basename(str(hdr)))
+ for hdr in self.interpreter.source_strings_to_files(kwargs['sources']):
+ hdr_path = os.path.relpath(hdr.relative_name(), state.subdir)
+ fhead += f'#include "{hdr_path}"\n'
fhead += textwrap.dedent(
'''
#define C_ENUM(v) ((gint) v)
return ModuleReturnValue([c_file, h_file], [c_file, h_file])
- @staticmethod
def _make_mkenum_impl(
+ self,
state: 'ModuleState',
- sources: T.Sequence[T.Union[str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList]],
+ sources: T.Sequence[T.Union[str, mesonlib.File, CustomTarget, CustomTargetIndex, GeneratedList]],
output: str,
cmd: T.List[str],
*,
install_dir: T.Optional[T.Sequence[T.Union[str, bool]]] = None,
depends: T.Optional[T.Sequence[T.Union[CustomTarget, CustomTargetIndex, BuildTarget]]] = None
) -> build.CustomTarget:
- real_cmd: T.List[T.Union[str, ExternalProgram]] = [state.find_program(['glib-mkenums', 'mkenums'])]
+ real_cmd: T.List[T.Union[str, 'ToolType']] = [self._find_tool(state, 'glib-mkenums')]
real_cmd.extend(cmd)
_install_dir = install_dir or state.environment.coredata.get_option(mesonlib.OptionKey('includedir'))
assert isinstance(_install_dir, str), 'for mypy'
- return build.CustomTarget(
+ return CustomTarget(
output,
state.subdir,
state.subproject,
extra_depends=depends,
# https://github.com/mesonbuild/meson/issues/973
absolute_paths=True,
+ description='Generating GObject enum file {}',
)
@typed_pos_args('gnome.genmarshal', str)
new_genmarshal = mesonlib.version_compare(self._get_native_glib_version(state), '>= 2.53.3')
- cmd: T.List[T.Union['ExternalProgram', str]] = [state.find_program('glib-genmarshal')]
+ cmd: T.List[T.Union['ToolType', str]] = [self._find_tool(state, 'glib-genmarshal')]
if kwargs['prefix']:
cmd.extend(['--prefix', kwargs['prefix']])
if kwargs['extra_args']:
else:
mlog.warning('The current version of GLib does not support extra arguments \n'
'for glib-genmarshal. You need at least GLib 2.53.3. See ',
- mlog.bold('https://github.com/mesonbuild/meson/pull/2049'))
+ mlog.bold('https://github.com/mesonbuild/meson/pull/2049'),
+ once=True, fatal=False)
for k in ['internal', 'nostdinc', 'skip_source', 'stdinc', 'valist_marshallers']:
- # Mypy can't figure out that this is correct
+ # Mypy can't figure out that this TypedDict index is correct, without repeating T.Literal for the entire list
if kwargs[k]: # type: ignore
cmd.append(f'--{k.replace("_", "-")}')
h_cmd = cmd + ['--header', '@INPUT@']
if new_genmarshal:
h_cmd += ['--pragma-once']
- header = build.CustomTarget(
+ header = CustomTarget(
output + '_h',
state.subdir,
state.subproject,
install_tag=['devel'],
capture=capture,
depend_files=kwargs['depend_files'],
+ description='Generating glib marshaller header {}',
)
c_cmd = cmd + ['--body', '@INPUT@']
- extra_deps: T.List[build.CustomTarget] = []
+ extra_deps: T.List[CustomTarget] = []
if mesonlib.version_compare(self._get_native_glib_version(state), '>= 2.53.4'):
# Silence any warnings about missing prototypes
c_cmd += ['--include-header', header_file]
extra_deps.append(header)
- body = build.CustomTarget(
+ body = CustomTarget(
output + '_c',
state.subdir,
state.subproject,
capture=capture,
depend_files=kwargs['depend_files'],
extra_depends=extra_deps,
+ description='Generating glib marshaller source {}',
)
rv = [body, header]
ofile.write(package + '\n')
return build.Data([mesonlib.File(True, outdir, fname)], install_dir, install_dir, mesonlib.FileMode(), state.subproject)
- def _get_vapi_link_with(self, target: build.CustomTarget) -> T.List[build.LibTypes]:
+ def _get_vapi_link_with(self, target: CustomTarget) -> T.List[build.LibTypes]:
link_with: T.List[build.LibTypes] = []
for dep in target.get_target_dependencies():
if isinstance(dep, build.SharedLibrary):
build_dir = os.path.join(state.environment.get_build_dir(), state.subdir)
source_dir = os.path.join(state.environment.get_source_dir(), state.subdir)
pkg_cmd, vapi_depends, vapi_packages, vapi_includes, packages = self._extract_vapi_packages(state, kwargs['packages'])
- cmd: T.List[T.Union[str, 'ExternalProgram']]
+ cmd: T.List[T.Union[ExternalProgram, Executable, OverrideProgram, str]]
cmd = [state.find_program('vapigen'), '--quiet', f'--library={library}', f'--directory={build_dir}']
cmd.extend([f'--vapidir={d}' for d in kwargs['vapi_dirs']])
cmd.extend([f'--metadatadir={d}' for d in kwargs['metadata_dirs']])
)
# So to try our best to get this to just work we need:
- # - link with with the correct library
+ # - link with the correct library
# - include the vapi and dependent vapi files in sources
# - add relevant directories to include dirs
incs = [build.IncludeDirs(state.subdir, ['.'] + vapi_includes, False)]
sources = [vapi_target] + vapi_depends
- rv = InternalDependency(None, incs, [], [], link_with, [], sources, [], {}, [], [])
+ rv = InternalDependency(None, incs, [], [], link_with, [], sources, [], [], {}, [], [], [])
created_values.append(rv)
return ModuleReturnValue(rv, created_values)
# 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
'''This module provides helper functions for generating documentation using hotdoc'''
-import os
-import subprocess
+import os, subprocess
+import typing as T
-from mesonbuild import mesonlib
-from mesonbuild import mlog, build
-from mesonbuild.coredata import MesonException
-from . import ModuleReturnValue, ModuleInfo
-from . import ExtensionModule
+from . import ExtensionModule, ModuleReturnValue, ModuleInfo
+from .. import build, mesonlib, mlog
+from ..build import CustomTarget, CustomTargetIndex
from ..dependencies import Dependency, InternalDependency
from ..interpreterbase import (
InvalidArguments, noPosargs, noKwargs, typed_kwargs, FeatureDeprecated,
ContainerTypeInfo, KwargInfo, typed_pos_args
)
-from ..interpreter import CustomTargetHolder
+from ..interpreter.interpreterobjects import _CustomTargetHolder
from ..interpreter.type_checking import NoneType
+from ..mesonlib import File, MesonException
from ..programs import ExternalProgram
-
-def ensure_list(value):
+if T.TYPE_CHECKING:
+ from typing_extensions import TypedDict
+
+ from . import ModuleState
+ from ..environment import Environment
+ from ..interpreter import Interpreter
+ from ..interpreterbase import TYPE_kwargs, TYPE_var
+
+ _T = T.TypeVar('_T')
+
+ class GenerateDocKwargs(TypedDict):
+ sitemap: T.Union[str, File, CustomTarget, CustomTargetIndex]
+ index: T.Union[str, File, CustomTarget, CustomTargetIndex]
+ project_version: str
+ html_extra_theme: T.Optional[str]
+ include_paths: T.List[str]
+ dependencies: T.List[T.Union[Dependency, build.StaticLibrary, build.SharedLibrary, CustomTarget, CustomTargetIndex]]
+ depends: T.List[T.Union[CustomTarget, CustomTargetIndex]]
+ gi_c_source_roots: T.List[str]
+ extra_assets: T.List[str]
+ extra_extension_paths: T.List[str]
+ subprojects: T.List['HotdocTarget']
+ install: bool
+
+def ensure_list(value: T.Union[_T, T.List[_T]]) -> T.List[_T]:
if not isinstance(value, list):
return [value]
return value
MIN_HOTDOC_VERSION = '0.8.100'
-file_types = (str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex)
+file_types = (str, File, CustomTarget, CustomTargetIndex)
+
+
+class HotdocExternalProgram(ExternalProgram):
+ def run_hotdoc(self, cmd: T.List[str]) -> int:
+ return subprocess.run(self.get_command() + cmd, stdout=subprocess.DEVNULL).returncode
class HotdocTargetBuilder:
- def __init__(self, name, state, hotdoc, interpreter, kwargs):
+ def __init__(self, name: str, state: ModuleState, hotdoc: HotdocExternalProgram, interpreter: Interpreter, kwargs):
self.hotdoc = hotdoc
self.build_by_default = kwargs.pop('build_by_default', False)
self.kwargs = kwargs
self.name = name
self.state = state
self.interpreter = interpreter
- self.include_paths = mesonlib.OrderedSet()
+ self.include_paths: mesonlib.OrderedSet[str] = mesonlib.OrderedSet()
self.builddir = state.environment.get_build_dir()
self.sourcedir = state.environment.get_source_dir()
self.subdir = state.subdir
self.build_command = state.environment.get_build_command()
- self.cmd = ['conf', '--project-name', name, "--disable-incremental-build",
- '--output', os.path.join(self.builddir, self.subdir, self.name + '-doc')]
+ self.cmd: T.List[TYPE_var] = ['conf', '--project-name', name, "--disable-incremental-build",
+ '--output', os.path.join(self.builddir, self.subdir, self.name + '-doc')]
self._extra_extension_paths = set()
self.extra_assets = set()
self.extra_depends = []
self._subprojects = []
- def process_known_arg(self, option, argname=None, value_processor=None):
+ def process_known_arg(self, option: str, argname: T.Optional[str] = None, value_processor: T.Optional[T.Callable] = None) -> None:
if not argname:
argname = option.strip("-").replace("-", "_")
self.set_arg_value(option, value)
- def set_arg_value(self, option, value):
+ def set_arg_value(self, option: str, value: TYPE_var) -> None:
if value is None:
return
else:
self.cmd.extend([option, value])
- def check_extra_arg_type(self, arg, value):
+ def check_extra_arg_type(self, arg: str, value: TYPE_var) -> None:
if isinstance(value, list):
for v in value:
self.check_extra_arg_type(arg, v)
return
- valid_types = (str, bool, mesonlib.File, build.IncludeDirs, build.CustomTarget, build.CustomTargetIndex, build.BuildTarget)
+ valid_types = (str, bool, File, build.IncludeDirs, CustomTarget, CustomTargetIndex, build.BuildTarget)
if not isinstance(value, valid_types):
raise InvalidArguments('Argument "{}={}" should be of type: {}.'.format(
arg, value, [t.__name__ for t in valid_types]))
- def process_extra_args(self):
+ def process_extra_args(self) -> None:
for arg, value in self.kwargs.items():
option = "--" + arg.replace("_", "-")
self.check_extra_arg_type(arg, value)
return None, None
- def add_extension_paths(self, paths):
+ 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:
continue
self._extra_extension_paths.add(path)
self.cmd.extend(["--extra-extension-path", path])
- def replace_dirs_in_string(self, string):
+ def replace_dirs_in_string(self, string: str) -> str:
return string.replace("@SOURCE_ROOT@", self.sourcedir).replace("@BUILD_ROOT@", self.builddir)
- def process_gi_c_source_roots(self):
+ def process_gi_c_source_roots(self) -> None:
if self.hotdoc.run_hotdoc(['--has-extension=gi-extension']) != 0:
return
self.cmd += ['--gi-c-source-roots'] + value
- def process_dependencies(self, deps):
+ def process_dependencies(self, deps: T.List[T.Union[Dependency, build.StaticLibrary, build.SharedLibrary, CustomTarget, CustomTargetIndex]]) -> T.List[str]:
cflags = set()
for dep in mesonlib.listify(ensure_list(deps)):
if isinstance(dep, InternalDependency):
self.include_paths.add(os.path.join(self.builddir, dep.hotdoc_conf.subdir))
self.cmd += ['--extra-assets=' + p for p in dep.extra_assets]
self.add_extension_paths(dep.extra_extension_paths)
- elif isinstance(dep, (build.CustomTarget, build.BuildTarget)):
+ elif isinstance(dep, (CustomTarget, build.BuildTarget)):
self.extra_depends.append(dep)
- elif isinstance(dep, build.CustomTargetIndex):
+ elif isinstance(dep, CustomTargetIndex):
self.extra_depends.append(dep.target)
return [f.strip('-I') for f in cflags]
- def process_extra_assets(self):
+ def process_extra_assets(self) -> None:
self._extra_assets = self.kwargs.pop('extra_assets')
for assets_path in self._extra_assets:
self.cmd.extend(["--extra-assets", assets_path])
- def process_subprojects(self):
+ def process_subprojects(self) -> None:
value = self.kwargs.pop('subprojects')
self.process_dependencies(value)
self._subprojects.extend(value)
- def flatten_config_command(self):
+ def flatten_config_command(self) -> T.List[str]:
cmd = []
for arg in mesonlib.listify(self.cmd, flatten=True):
- if isinstance(arg, mesonlib.File):
+ if isinstance(arg, File):
arg = arg.absolute_path(self.state.environment.get_source_dir(),
self.state.environment.get_build_dir())
elif isinstance(arg, build.IncludeDirs):
cmd.append(os.path.join(self.builddir, arg.get_curdir(), inc_dir))
continue
- elif isinstance(arg, (build.BuildTarget, build.CustomTarget)):
+ elif isinstance(arg, (build.BuildTarget, CustomTarget)):
self.extra_depends.append(arg)
arg = self.interpreter.backend.get_target_filename_abs(arg)
- elif isinstance(arg, build.CustomTargetIndex):
+ elif isinstance(arg, CustomTargetIndex):
self.extra_depends.append(arg.target)
arg = self.interpreter.backend.get_target_filename_abs(arg)
return cmd
- def generate_hotdoc_config(self):
+ def generate_hotdoc_config(self) -> None:
cwd = os.path.abspath(os.curdir)
ncwd = os.path.join(self.sourcedir, self.subdir)
mlog.log('Generating Hotdoc configuration for: ', mlog.bold(self.name))
os.chdir(ncwd)
- self.hotdoc.run_hotdoc(self.flatten_config_command())
+ if self.hotdoc.run_hotdoc(self.flatten_config_command()) != 0:
+ raise MesonException('hotdoc failed to configure')
os.chdir(cwd)
- def ensure_file(self, value):
+ def ensure_file(self, value: T.Union[str, File, CustomTarget, CustomTargetIndex]) -> T.Union[File, CustomTarget, CustomTargetIndex]:
if isinstance(value, list):
res = []
for val in value:
return res
if isinstance(value, str):
- return mesonlib.File.from_source_file(self.sourcedir, self.subdir, value)
+ return File.from_source_file(self.sourcedir, self.subdir, value)
return value
- def ensure_dir(self, value):
+ def ensure_dir(self, value: str) -> str:
if os.path.isabs(value):
_dir = value
else:
return os.path.relpath(_dir, os.path.join(self.builddir, self.subdir))
- def check_forbidden_args(self):
+ def check_forbidden_args(self) -> None:
for arg in ['conf_file']:
if arg in self.kwargs:
raise InvalidArguments(f'Argument "{arg}" is forbidden.')
- def make_targets(self):
+ def make_targets(self) -> T.Tuple[HotdocTarget, mesonlib.ExecutableSerialisation]:
self.check_forbidden_args()
self.process_known_arg("--index", value_processor=self.ensure_file)
self.process_known_arg("--project-version")
subdir=self.subdir,
subproject=self.state.subproject,
environment=self.state.environment,
- hotdoc_conf=mesonlib.File.from_built_file(
+ hotdoc_conf=File.from_built_file(
self.subdir, hotdoc_config_name),
extra_extension_paths=self._extra_extension_paths,
extra_assets=self._extra_assets,
install_script = None
if install:
+ datadir = os.path.join(self.state.get_option('prefix'), self.state.get_option('datadir'))
+ devhelp = self.kwargs.get('devhelp_activate', False)
+ if not isinstance(devhelp, bool):
+ FeatureDeprecated.single_use('hotdoc.generate_doc() devhelp_activate must be boolean', '1.1.0', self.state.subproject)
+ devhelp = False
+ if devhelp:
+ install_from = os.path.join(fullname, 'devhelp')
+ install_to = os.path.join(datadir, 'devhelp')
+ else:
+ install_from = os.path.join(fullname, 'html')
+ install_to = os.path.join(datadir, 'doc', self.name, 'html')
+
install_script = self.state.backend.get_executable_serialisation(self.build_command + [
"--internal", "hotdoc",
- "--install", os.path.join(fullname, 'html'),
+ "--install", install_from,
+ "--docdir", install_to,
'--name', self.name,
'--builddir', os.path.join(self.builddir, self.subdir)] +
self.hotdoc.get_command() +
return (target, install_script)
-class HotdocTargetHolder(CustomTargetHolder):
- def __init__(self, target, interp):
+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
- def config_path_method(self, *args, **kwargs):
+ 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)
return conf
-class HotdocTarget(build.CustomTarget):
- def __init__(self, name, subdir, subproject, hotdoc_conf, extra_extension_paths, extra_assets,
- subprojects, environment, **kwargs):
+class HotdocTarget(CustomTarget):
+ def __init__(self, name: str, subdir: str, subproject: str, hotdoc_conf: File,
+ extra_extension_paths: T.Set[str], extra_assets: T.List[str],
+ subprojects: T.List['HotdocTarget'], environment: Environment, **kwargs: T.Any):
super().__init__(name, subdir, subproject, environment, **kwargs, absolute_paths=True)
self.hotdoc_conf = hotdoc_conf
self.extra_extension_paths = extra_extension_paths
self.extra_assets = extra_assets
self.subprojects = subprojects
- def __getstate__(self):
+ def __getstate__(self) -> dict:
# Make sure we do not try to pickle subprojects
res = self.__dict__.copy()
res['subprojects'] = []
INFO = ModuleInfo('hotdoc', '0.48.0')
- def __init__(self, interpreter):
+ def __init__(self, interpreter: Interpreter):
super().__init__(interpreter)
- self.hotdoc = ExternalProgram('hotdoc')
+ self.hotdoc = HotdocExternalProgram('hotdoc')
if not self.hotdoc.found():
raise MesonException('hotdoc executable not found')
version = self.hotdoc.get_version(interpreter)
if not mesonlib.version_compare(version, f'>={MIN_HOTDOC_VERSION}'):
raise MesonException(f'hotdoc {MIN_HOTDOC_VERSION} required but not found.)')
- def run_hotdoc(cmd):
- return subprocess.run(self.hotdoc.get_command() + cmd, stdout=subprocess.DEVNULL).returncode
-
- self.hotdoc.run_hotdoc = run_hotdoc
self.methods.update({
'has_extensions': self.has_extensions,
'generate_doc': self.generate_doc,
@noKwargs
@typed_pos_args('hotdoc.has_extensions', varargs=str, min_varargs=1)
- def has_extensions(self, state, args, kwargs):
+ def has_extensions(self, state: ModuleState, args: T.Tuple[T.List[str]], kwargs: TYPE_kwargs) -> bool:
return self.hotdoc.run_hotdoc([f'--has-extension={extension}' for extension in args[0]]) == 0
@typed_pos_args('hotdoc.generate_doc', str)
KwargInfo(
'dependencies',
ContainerTypeInfo(list, (Dependency, build.StaticLibrary, build.SharedLibrary,
- build.CustomTarget, build.CustomTargetIndex)),
+ CustomTarget, CustomTargetIndex)),
listify=True,
default=[],
),
KwargInfo(
'depends',
- ContainerTypeInfo(list, (build.CustomTarget, build.CustomTargetIndex)),
+ ContainerTypeInfo(list, (CustomTarget, CustomTargetIndex)),
listify=True,
default=[],
since='0.64.1',
KwargInfo('install', bool, default=False),
allow_unknown=True
)
- def generate_doc(self, state, args, kwargs):
+ def generate_doc(self, state: ModuleState, args: T.Tuple[str], kwargs: GenerateDocKwargs) -> ModuleReturnValue:
project_name = args[0]
- if any(isinstance(x, (build.CustomTarget, build.CustomTargetIndex)) for x in kwargs['dependencies']):
+ if any(isinstance(x, (CustomTarget, CustomTargetIndex)) for x in kwargs['dependencies']):
FeatureDeprecated.single_use('hotdoc.generate_doc dependencies argument with custom_target',
'0.64.1', state.subproject, 'use `depends`', state.current_node)
builder = HotdocTargetBuilder(project_name, state, self.hotdoc, self.interpreter, kwargs)
target, install_script = builder.make_targets()
- targets = [target]
+ targets: T.List[T.Union[HotdocTarget, mesonlib.ExecutableSerialisation]] = [target]
if install_script:
targets.append(install_script)
- return ModuleReturnValue(targets[0], targets)
+ return ModuleReturnValue(target, targets)
-def initialize(interpreter):
+def initialize(interpreter: Interpreter) -> HotDocModule:
mod = HotDocModule(interpreter)
mod.interpreter.append_holder_map(HotdocTarget, HotdocTargetHolder)
return mod
from __future__ import annotations
from os import path
+import shlex
import typing as T
from . import ExtensionModule, ModuleReturnValue, ModuleInfo
from ..interpreter.type_checking import CT_BUILD_BY_DEFAULT, CT_INPUT_KW, INSTALL_TAG_KW, OUTPUT_KW, INSTALL_DIR_KW, INSTALL_KW, NoneType, in_set_validator
from ..interpreterbase import FeatureNew
from ..interpreterbase.decorators import ContainerTypeInfo, KwargInfo, noPosargs, typed_kwargs, typed_pos_args
+from ..programs import ExternalProgram
from ..scripts.gettext import read_linguas
if T.TYPE_CHECKING:
from ..build import Target
from ..interpreter import Interpreter
from ..interpreterbase import TYPE_var
- from ..programs import ExternalProgram
class MergeFile(TypedDict):
'gettext': self.gettext,
'itstool_join': self.itstool_join,
})
- self.tools: T.Dict[str, T.Optional[ExternalProgram]] = {
+ self.tools: T.Dict[str, T.Optional[T.Union[ExternalProgram, build.Executable]]] = {
'itstool': None,
'msgfmt': None,
'msginit': None,
def merge_file(self, state: 'ModuleState', args: T.List['TYPE_var'], kwargs: 'MergeFile') -> ModuleReturnValue:
if self.tools['msgfmt'] is None or not self.tools['msgfmt'].found():
self.tools['msgfmt'] = state.find_program('msgfmt', for_machine=mesonlib.MachineChoice.BUILD)
+ if isinstance(self.tools['msgfmt'], ExternalProgram):
+ try:
+ have_version = self.tools['msgfmt'].get_version()
+ except mesonlib.MesonException as e:
+ raise mesonlib.MesonException('i18n.merge_file requires GNU msgfmt') from e
+ want_version = '>=0.19' if kwargs['type'] == 'desktop' else '>=0.19.7'
+ if not mesonlib.version_compare(have_version, want_version):
+ msg = f'i18n.merge_file requires GNU msgfmt {want_version} to produce files of type: ' + kwargs['type'] + f' (got: {have_version})'
+ raise mesonlib.MesonException(msg)
podir = path.join(state.build_to_src, state.subdir, kwargs['po_dir'])
ddirs = self._get_data_dirs(state, kwargs['data_dirs'])
install=kwargs['install'],
install_dir=[kwargs['install_dir']] if kwargs['install_dir'] is not None else None,
install_tag=install_tag,
+ description='Merging translations for {}',
)
return ModuleReturnValue(ct, [ct])
- @typed_pos_args('i81n.gettext', str)
+ @typed_pos_args('i18n.gettext', str)
@typed_kwargs(
'i18n.gettext',
_ARGS,
path.join(state.subdir, l, 'LC_MESSAGES'),
state.subproject,
state.environment,
- [self.tools['msgfmt'], '@INPUT@', '-o', '@OUTPUT@'],
+ [self.tools['msgfmt'], '-o', '@OUTPUT@', '@INPUT@'],
[po_file],
[f'{packagename}.mo'],
install=install,
# Bonus: the build tree has something usable as an uninstalled bindtextdomain() target dir.
install_dir=[path.join(install_dir, l, 'LC_MESSAGES')],
install_tag=['i18n'],
+ description='Building translation {}',
)
targets.append(gmotarget)
gmotargets.append(gmotarget)
command: T.List[T.Union[str, build.BuildTarget, build.CustomTarget,
build.CustomTargetIndex, 'ExternalProgram', mesonlib.File]] = []
command.extend(state.environment.get_build_command())
+
+ itstool_cmd = self.tools['itstool'].get_command()
+ # TODO: python 3.8 can use shlex.join()
command.extend([
'--internal', 'itstool', 'join',
'-i', '@INPUT@',
'-o', '@OUTPUT@',
- '--itstool=' + self.tools['itstool'].get_path(),
+ '--itstool=' + ' '.join(shlex.quote(c) for c in itstool_cmd),
])
if its_files:
for fname in its_files:
install=kwargs['install'],
install_dir=[kwargs['install_dir']] if kwargs['install_dir'] is not None else None,
install_tag=install_tag,
+ description='Merging translations for {}',
)
return ModuleReturnValue(ct, [ct])
def __init__(self, interpreter: Interpreter) -> None:
super().__init__(interpreter)
- self.tools: T.Dict[str, ExternalProgram] = {}
+ self.tools: T.Dict[str, T.Union[ExternalProgram, build.Executable]] = {}
self.methods.update({
'project': self.project,
})
def __get_java_compiler(self, state: ModuleState) -> Compiler:
if 'java' not in state.environment.coredata.compilers[MachineChoice.BUILD]:
- detect_compiler_for(state.environment, 'java', MachineChoice.BUILD)
+ detect_compiler_for(state.environment, 'java', MachineChoice.BUILD, False)
return state.environment.coredata.compilers[MachineChoice.BUILD]['java']
@FeatureNew('java.generate_native_headers', '0.62.0')
classes = T.cast('T.List[str]', kwargs.get('classes'))
package = kwargs.get('package')
+ if package:
+ sanitized_package = package.replace("-", "_").replace(".", "_")
+
headers: T.List[str] = []
for clazz in classes:
- underscore_clazz = clazz.replace(".", "_")
+ sanitized_clazz = clazz.replace(".", "_")
if package:
- headers.append(f'{package.replace(".", "_")}_{underscore_clazz}.h')
+ headers.append(f'{sanitized_package}_{sanitized_clazz}.h')
else:
- headers.append(f'{underscore_clazz}.h')
+ headers.append(f'{sanitized_clazz}.h')
javac = self.__get_java_compiler(state)
# 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 os
import typing as T
return result
@noKwargs
- @typed_pos_args('keyval.laod', (str, mesonlib.File))
+ @typed_pos_args('keyval.load', (str, mesonlib.File))
def load(self, state: 'ModuleState', args: T.Tuple['mesonlib.FileOrString'], kwargs: T.Dict[str, T.Any]) -> T.Dict[str, str]:
s = args[0]
is_built = False
from __future__ import annotations
import typing as T
-from . import ExtensionModule, ModuleInfo
+from . import NewExtensionModule, ModuleInfo
from ..interpreterbase import noKwargs, noPosargs
if T.TYPE_CHECKING:
from ..interpreterbase.baseobjects import TYPE_kwargs, TYPE_var
-class TestModule(ExtensionModule):
+class TestModule(NewExtensionModule):
INFO = ModuleInfo('modtest')
def __init__(self, interpreter: Interpreter) -> None:
- super().__init__(interpreter)
+ super().__init__()
self.methods.update({
'print_hello': self.print_hello,
})
from .. import mesonlib
from .. import mlog
from ..coredata import BUILTIN_DIR_OPTIONS
-from ..dependencies import ThreadDependency
+from ..dependencies.pkgconfig import PkgConfigDependency, PkgConfigInterface
from ..interpreter.type_checking import D_MODULE_VERSIONS_KW, INSTALL_DIR_KW, VARIABLES_KW, NoneType
from ..interpreterbase import FeatureNew, FeatureDeprecated
from ..interpreterbase.decorators import ContainerTypeInfo, KwargInfo, typed_kwargs, typed_pos_args
def __init__(self, state: ModuleState, name: str, metadata: T.Dict[str, MetaData]) -> None:
self.state = state
self.name = name
+ self.metadata = metadata
self.pub_libs: T.List[LIBS] = []
self.pub_reqs: T.List[str] = []
self.priv_libs: T.List[LIBS] = []
self.cflags: T.List[str] = []
self.version_reqs: T.DefaultDict[str, T.Set[str]] = defaultdict(set)
self.link_whole_targets: T.List[T.Union[build.CustomTarget, build.CustomTargetIndex, build.StaticLibrary]] = []
- self.metadata = metadata
+ self.uninstalled_incdirs: mesonlib.OrderedSet[str] = mesonlib.OrderedSet()
def add_pub_libs(self, libs: T.List[ANY_DEP]) -> None:
p_libs, reqs, cflags = self._process_libs(libs, True)
and obj.get_id() in self.metadata):
self._check_generated_pc_deprecation(obj)
processed_reqs.append(self.metadata[obj.get_id()].filebase)
- elif isinstance(obj, dependencies.PkgConfigDependency):
+ elif isinstance(obj, PkgConfigDependency):
if obj.found():
processed_reqs.append(obj.name)
self.add_version_reqs(obj.name, obj.version_reqs)
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, ThreadDependency):
+ elif isinstance(obj, dependencies.ExternalDependency) and obj.name == 'threads':
pass
else:
raise mesonlib.MesonException('requires argument not a string, '
def add_cflags(self, cflags: T.List[str]) -> None:
self.cflags += mesonlib.stringlistify(cflags)
+ def _add_uninstalled_incdirs(self, incdirs: T.List[build.IncludeDirs], subdir: T.Optional[str] = None) -> None:
+ for i in incdirs:
+ curdir = i.get_curdir()
+ for d in i.get_incdirs():
+ path = os.path.join(curdir, d)
+ self.uninstalled_incdirs.add(path)
+ if subdir is not None:
+ self.uninstalled_incdirs.add(subdir)
+
def _process_libs(
self, libs: T.List[ANY_DEP], public: bool
) -> T.Tuple[T.List[T.Union[str, build.SharedLibrary, build.StaticLibrary, build.CustomTarget, build.CustomTargetIndex]], T.List[str], T.List[str]]:
and obj.get_id() in self.metadata):
self._check_generated_pc_deprecation(obj)
processed_reqs.append(self.metadata[obj.get_id()].filebase)
- elif isinstance(obj, dependencies.ValgrindDependency):
+ elif isinstance(obj, dependencies.ExternalDependency) and obj.name == 'valgrind':
pass
- elif isinstance(obj, dependencies.PkgConfigDependency):
+ elif isinstance(obj, PkgConfigDependency):
if obj.found():
processed_reqs.append(obj.name)
self.add_version_reqs(obj.name, obj.version_reqs)
elif isinstance(obj, dependencies.InternalDependency):
if obj.found():
+ if obj.objects:
+ raise mesonlib.MesonException('.pc file cannot refer to individual object files.')
processed_libs += obj.get_link_args()
processed_cflags += obj.get_compile_args()
self._add_lib_dependencies(obj.libraries, obj.whole_libraries, obj.ext_deps, public, private_external_deps=True)
+ self._add_uninstalled_incdirs(obj.get_include_dirs())
elif isinstance(obj, dependencies.Dependency):
if obj.found():
processed_libs += obj.get_link_args()
# than needed build deps.
# See https://bugs.freedesktop.org/show_bug.cgi?id=105572
processed_libs.append(obj)
+ self._add_uninstalled_incdirs(obj.get_include_dirs(), obj.get_subdir())
elif isinstance(obj, (build.SharedLibrary, build.StaticLibrary)):
processed_libs.append(obj)
+ self._add_uninstalled_incdirs(obj.get_include_dirs(), obj.get_subdir())
# If there is a static library in `Libs:` all its deps must be
# public too, otherwise the generated pc file will never be
# usable without --static.
# Track already generated pkg-config files This is stored as a class
# variable so that multiple `import()`s share metadata
+ devenv: T.Optional[mesonlib.EnvironmentVariables] = None
_metadata: T.ClassVar[T.Dict[str, MetaData]] = {}
def __init__(self) -> None:
'generate': self.generate,
})
+ def postconf_hook(self, b: build.Build) -> None:
+ if self.devenv is not None:
+ b.devenv.append(self.devenv)
+
def _get_lname(self, l: T.Union[build.SharedLibrary, build.StaticLibrary, build.CustomTarget, build.CustomTargetIndex],
msg: str, pcfile: str) -> str:
if isinstance(l, (build.CustomTargetIndex, build.CustomTarget)):
return ('${prefix}' / libdir).as_posix()
def _generate_pkgconfig_file(self, state: ModuleState, deps: DependenciesHelper,
- subdirs: T.List[str], name: T.Optional[str],
- description: T.Optional[str], url: str, version: str,
+ subdirs: T.List[str], name: str,
+ description: str, url: str, version: str,
pcfile: str, conflicts: T.List[str],
variables: T.List[T.Tuple[str, str]],
unescaped_variables: T.List[T.Tuple[str, str]],
if isinstance(l, (build.CustomTarget, build.CustomTargetIndex)) or 'cs' not in l.compilers:
yield f'-l{lname}'
- def get_uninstalled_include_dirs(libs: T.List[LIBS]) -> T.List[str]:
- result: T.List[str] = []
- for l in libs:
- if isinstance(l, (str, build.CustomTarget, build.CustomTargetIndex)):
- continue
- if l.get_subdir() not in result:
- result.append(l.get_subdir())
- for i in l.get_include_dirs():
- curdir = i.get_curdir()
- for d in i.get_incdirs():
- path = os.path.join(curdir, d)
- if path not in result:
- result.append(path)
- return result
-
- def generate_uninstalled_cflags(libs: T.List[LIBS]) -> T.Iterable[str]:
- for d in get_uninstalled_include_dirs(libs):
- for basedir in ['${prefix}', '${srcdir}']:
- path = PurePath(basedir, d)
- yield '-I%s' % self._escape(path.as_posix())
-
if len(deps.pub_libs) > 0:
ofile.write('Libs: {}\n'.format(' '.join(generate_libs_flags(deps.pub_libs))))
if len(deps.priv_libs) > 0:
cflags: T.List[str] = []
if uninstalled:
- cflags += generate_uninstalled_cflags(deps.pub_libs + deps.priv_libs)
+ for d in deps.uninstalled_incdirs:
+ for basedir in ['${prefix}', '${srcdir}']:
+ path = self._escape(PurePath(basedir, d).as_posix())
+ cflags.append(f'-I{path}')
else:
for d in subdirs:
if d == '.':
else:
if kwargs['version'] is None:
FeatureNew.single_use('pkgconfig.generate implicit version keyword', '0.46.0', state.subproject)
+ msg = ('pkgconfig.generate: if a library is not passed as a '
+ 'positional argument, the {!r} keyword argument is '
+ 'required.')
if kwargs['name'] is None:
- raise build.InvalidArguments(
- 'pkgconfig.generate: if a library is not passed as a '
- 'positional argument, the name keyword argument is '
- 'required.')
+ raise build.InvalidArguments(msg.format('name'))
+ if kwargs['description'] is None:
+ raise build.InvalidArguments(msg.format('description'))
dataonly = kwargs['dataonly']
if dataonly:
default_subdirs = []
blocked_vars = ['libraries', 'libraries_private', 'requires_private', 'extra_cflags', 'subdirs']
+ # Mypy can't figure out that this TypedDict index is correct, without repeating T.Literal for the entire list
if any(kwargs[k] for k in blocked_vars): # type: ignore
raise mesonlib.MesonException(f'Cannot combine dataonly with any of {blocked_vars}')
default_install_dir = os.path.join(state.environment.get_datadir(), 'pkgconfig')
if dversions:
compiler = state.environment.coredata.compilers.host.get('d')
if compiler:
- deps.add_cflags(compiler.get_feature_args({'versions': dversions}, None))
+ deps.add_cflags(compiler.get_feature_args(
+ {'versions': dversions, 'import_dirs': [], 'debug': [], 'unittest': False}, None))
deps.remove_dups()
if mesonlib.is_freebsd():
pkgroot = os.path.join(_as_str(state.environment.coredata.get_option(mesonlib.OptionKey('prefix'))), 'libdata', 'pkgconfig')
pkgroot_name = os.path.join('{prefix}', 'libdata', 'pkgconfig')
+ elif mesonlib.is_haiku():
+ pkgroot = os.path.join(_as_str(state.environment.coredata.get_option(mesonlib.OptionKey('prefix'))), 'develop', 'lib', 'pkgconfig')
+ pkgroot_name = os.path.join('{prefix}', 'develop', 'lib', 'pkgconfig')
else:
pkgroot = os.path.join(_as_str(state.environment.coredata.get_option(mesonlib.OptionKey('libdir'))), 'pkgconfig')
pkgroot_name = os.path.join('{libdir}', 'pkgconfig')
if not isinstance(lib, str) and lib.get_id() not in self._metadata:
self._metadata[lib.get_id()] = MetaData(
filebase, name, state.current_node)
+ if self.devenv is None:
+ self.devenv = PkgConfigInterface.get_env(state.environment, mesonlib.MachineChoice.HOST, uninstalled=True)
return ModuleReturnValue(res, [res])
# limitations under the License.
from __future__ import annotations
-from pathlib import Path
-import copy
-import functools
-import json
-import os
-import shutil
+import copy, json, os, shutil, re
import typing as T
from . import ExtensionModule, ModuleInfo
from .. import mesonlib
from .. import mlog
from ..coredata import UserFeatureOption
-from ..build import known_shmod_kwargs
-from ..dependencies import DependencyMethods, PkgConfigDependency, NotFoundDependency, SystemDependency, ExtraFrameworkDependency
-from ..dependencies.base import process_method_kw
-from ..dependencies.detect import get_dep_identifier
-from ..environment import detect_cpu_family
-from ..interpreter import ExternalProgramHolder, extract_required_kwarg, permitted_dependency_kwargs
-from ..interpreter import primitives as P_OBJ
-from ..interpreter.type_checking import NoneType, PRESERVE_PATH_KW
+from ..build import known_shmod_kwargs, CustomTarget, CustomTargetIndex, BuildTarget, GeneratedList, StructuredSources, ExtractedObjects, SharedModule
+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.interpreterobjects import _ExternalProgramHolder
+from ..interpreter.type_checking import NoneType, PRESERVE_PATH_KW, SHARED_MOD_KWS
from ..interpreterbase import (
noPosargs, noKwargs, permittedKwargs, ContainerTypeInfo,
InvalidArguments, typed_pos_args, typed_kwargs, KwargInfo,
FeatureNew, FeatureNewKwargs, disablerIfNotFound
)
-from ..mesonlib import MachineChoice
+from ..mesonlib import MachineChoice, OptionKey
from ..programs import ExternalProgram, NonExistingExternalProgram
if T.TYPE_CHECKING:
- from typing_extensions import TypedDict
+ from typing_extensions import TypedDict, NotRequired
from . import ModuleState
- from ..build import SharedModule, Data
- from ..dependencies import ExternalDependency, Dependency
- from ..dependencies.factory import DependencyGenerator
- from ..environment import Environment
+ from ..build import Build, Data
+ from ..dependencies import Dependency
from ..interpreter import Interpreter
- from ..interpreter.kwargs import ExtractRequired
- from ..interpreterbase.interpreterbase import TYPE_var, TYPE_kwargs
-
- class PythonIntrospectionDict(TypedDict):
-
- install_paths: T.Dict[str, str]
- is_pypy: bool
- is_venv: bool
- link_libpython: bool
- sysconfig_paths: T.Dict[str, str]
- paths: T.Dict[str, str]
- platform: str
- suffix: str
- variables: T.Dict[str, str]
- version: str
+ from ..interpreter.interpreter import BuildTargetSource
+ from ..interpreter.kwargs import ExtractRequired, SharedModule as SharedModuleKw
+ from ..interpreterbase.baseobjects import TYPE_var, TYPE_kwargs
class PyInstallKw(TypedDict):
modules: T.List[str]
pure: T.Optional[bool]
- _Base = ExternalDependency
-else:
- _Base = object
+ class ExtensionModuleKw(SharedModuleKw):
+ subdir: NotRequired[T.Optional[str]]
-mod_kwargs = {'subdir'}
-mod_kwargs.update(known_shmod_kwargs)
-mod_kwargs -= {'name_prefix', 'name_suffix'}
-
-
-class _PythonDependencyBase(_Base):
-
- def __init__(self, python_holder: 'PythonInstallation', embed: bool):
- self.embed = embed
- self.version: str = python_holder.version
- self.platform = python_holder.platform
- self.variables = python_holder.variables
- self.paths = python_holder.paths
- self.link_libpython = python_holder.link_libpython
- self.info: T.Optional[T.Dict[str, str]] = None
- if mesonlib.version_compare(self.version, '>= 3.0'):
- self.major_version = 3
- else:
- self.major_version = 2
-
-
-class PythonPkgConfigDependency(PkgConfigDependency, _PythonDependencyBase):
-
- def __init__(self, name: str, environment: 'Environment',
- kwargs: T.Dict[str, T.Any], installation: 'PythonInstallation',
- libpc: bool = False):
- if libpc:
- mlog.debug(f'Searching for {name!r} via pkgconfig lookup in LIBPC')
- else:
- mlog.debug(f'Searching for {name!r} via fallback pkgconfig lookup in default paths')
-
- PkgConfigDependency.__init__(self, name, environment, kwargs)
- _PythonDependencyBase.__init__(self, installation, kwargs.get('embed', False))
-
- 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')
-
- # The "-embed" version of python.pc 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 file because that is the
- # only one that exists,
- #
- # 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
- if not self.embed and not self.link_libpython and mesonlib.version_compare(self.version, '< 3.8'):
- self.link_args = []
+ MaybePythonProg = T.Union[NonExistingExternalProgram, 'PythonExternalProgram']
-class PythonFrameworkDependency(ExtraFrameworkDependency, _PythonDependencyBase):
-
- def __init__(self, name: str, environment: 'Environment',
- kwargs: T.Dict[str, T.Any], installation: 'PythonInstallation'):
- ExtraFrameworkDependency.__init__(self, name, environment, kwargs)
- _PythonDependencyBase.__init__(self, installation, kwargs.get('embed', False))
+mod_kwargs = {'subdir', 'limited_api'}
+mod_kwargs.update(known_shmod_kwargs)
+mod_kwargs -= {'name_prefix', 'name_suffix'}
+_MOD_KWARGS = [k for k in SHARED_MOD_KWS if k.name not in {'name_prefix', 'name_suffix'}]
-class PythonSystemDependency(SystemDependency, _PythonDependencyBase):
- def __init__(self, name: str, environment: 'Environment',
- kwargs: T.Dict[str, T.Any], installation: 'PythonInstallation'):
- SystemDependency.__init__(self, name, environment, kwargs)
- _PythonDependencyBase.__init__(self, installation, kwargs.get('embed', False))
+class PythonExternalProgram(BasicPythonExternalProgram):
- if mesonlib.is_windows():
- self._find_libpy_windows(environment)
- else:
- self._find_libpy(installation, environment)
-
- def _find_libpy(self, python_holder: 'PythonInstallation', environment: 'Environment') -> None:
- if python_holder.is_pypy:
- if self.major_version == 3:
- libname = 'pypy3-c'
- else:
- libname = 'pypy-c'
- libdir = os.path.join(self.variables.get('base'), 'bin')
- libdirs = [libdir]
- else:
- libname = f'python{self.version}'
- if 'DEBUG_EXT' in self.variables:
- libname += self.variables['DEBUG_EXT']
- if 'ABIFLAGS' in self.variables:
- libname += self.variables['ABIFLAGS']
- libdirs = []
-
- largs = self.clib_compiler.find_library(libname, environment, libdirs)
- if largs is not None:
- self.link_args = largs
-
- self.is_found = largs is not None or not self.link_libpython
-
- 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]
-
- def _get_windows_python_arch(self) -> T.Optional[str]:
- if self.platform == 'mingw':
- pycc = self.variables.get('CC')
- if pycc.startswith('x86_64'):
- return '64'
- elif pycc.startswith(('i686', 'i386')):
- return '32'
- else:
- mlog.log(f'MinGW Python built with unknown CC {pycc!r}, please file a bug')
- return None
- elif self.platform == 'win32':
- return '32'
- elif self.platform in {'win64', 'win-amd64'}:
- return '64'
- mlog.log(f'Unknown Windows Python platform {self.platform!r}')
- return None
-
- def _get_windows_link_args(self) -> T.Optional[T.List[str]]:
- if self.platform.startswith('win'):
- vernum = self.variables.get('py_version_nodot')
- verdot = self.variables.get('py_version_short')
- imp_lower = self.variables.get('implementation_lower', 'python')
- if self.static:
- libpath = Path('libs') / f'libpython{vernum}.a'
- else:
- comp = self.get_compiler()
- if comp.id == "gcc":
- if imp_lower == 'pypy' and verdot == '3.8':
- # The naming changed between 3.8 and 3.9
- libpath = Path('libpypy3-c.dll')
- elif imp_lower == 'pypy':
- libpath = Path(f'libpypy{verdot}-c.dll')
- else:
- libpath = Path(f'python{vernum}.dll')
- else:
- libpath = Path('libs') / f'python{vernum}.lib'
- # base_prefix to allow for virtualenvs.
- lib = Path(self.variables.get('base_prefix')) / libpath
- elif self.platform == 'mingw':
- if self.static:
- libname = self.variables.get('LIBRARY')
- else:
- libname = self.variables.get('LDLIBRARY')
- lib = Path(self.variables.get('LIBDIR')) / libname
- else:
- raise mesonlib.MesonBugException(
- 'On a Windows path, but the OS doesn\'t appear to be Windows or MinGW.')
- if not lib.exists():
- mlog.log('Could not find Python3 library {!r}'.format(str(lib)))
- return None
- return [str(lib)]
-
- def _find_libpy_windows(self, env: 'Environment') -> None:
- '''
- Find python3 libraries on Windows and also verify that the arch matches
- what we are building for.
- '''
- pyarch = self._get_windows_python_arch()
- if pyarch is None:
- self.is_found = False
- return
- arch = detect_cpu_family(env.coredata.compilers.host)
- if arch == 'x86':
- arch = '32'
- elif arch == 'x86_64':
- arch = '64'
- else:
- # We can't cross-compile Python 3 dependencies on Windows yet
- mlog.log(f'Unknown architecture {arch!r} for',
- mlog.bold(self.name))
- self.is_found = False
- return
- # Pyarch ends in '32' or '64'
- if arch != pyarch:
- mlog.log('Need', mlog.bold(self.name), f'for {arch}-bit, but found {pyarch}-bit')
- self.is_found = False
- return
- # This can fail if the library is not found
- largs = self._get_windows_link_args()
- if largs is None:
- self.is_found = False
- return
- self.link_args = largs
- # Compile args
- 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]
-
- # https://sourceforge.net/p/mingw-w64/mailman/message/30504611/
- if pyarch == '64' and self.major_version == 2:
- self.compile_args += ['-DMS_WIN64']
-
- self.is_found = True
-
-
-def python_factory(env: 'Environment', for_machine: 'MachineChoice',
- kwargs: T.Dict[str, T.Any], methods: T.List[DependencyMethods],
- installation: 'PythonInstallation') -> T.List['DependencyGenerator']:
- # We can't use the factory_methods decorator here, as we need to pass the
- # extra installation argument
- embed = kwargs.get('embed', False)
- candidates: T.List['DependencyGenerator'] = []
- pkg_version = installation.variables.get('LDVERSION') or installation.version
-
- if DependencyMethods.PKGCONFIG in methods:
- pkg_libdir = installation.variables.get('LIBPC')
- pkg_embed = '-embed' if embed and mesonlib.version_compare(installation.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: 'PythonInstallation') -> 'ExternalDependency':
- if not pkg_libdir:
- # there is no LIBPC, so we can't search in it
- return NotFoundDependency('python', env)
-
- 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, value):
- 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)
-
- 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))
-
- if DependencyMethods.SYSTEM in methods:
- candidates.append(functools.partial(PythonSystemDependency, 'python', env, kwargs, installation))
-
- if DependencyMethods.EXTRAFRAMEWORK in methods:
- nkwargs = kwargs.copy()
- if mesonlib.version_compare(pkg_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))
-
- return candidates
-
-
-INTROSPECT_COMMAND = '''\
-import os.path
-import sysconfig
-import json
-import sys
-import distutils.command.install
-
-def get_distutils_paths(scheme=None, prefix=None):
- import distutils.dist
- distribution = distutils.dist.Distribution()
- install_cmd = distribution.get_command_obj('install')
- if prefix is not None:
- install_cmd.prefix = prefix
- if scheme:
- install_cmd.select_scheme(scheme)
- install_cmd.finalize_options()
- return {
- 'data': install_cmd.install_data,
- 'include': os.path.dirname(install_cmd.install_headers),
- 'platlib': install_cmd.install_platlib,
- 'purelib': install_cmd.install_purelib,
- 'scripts': install_cmd.install_scripts,
- }
-
-# On Debian derivatives, the Python interpreter shipped by the distribution uses
-# a custom install scheme, deb_system, for the system install, and changes the
-# default scheme to a custom one pointing to /usr/local and replacing
-# site-packages with dist-packages.
-# See https://github.com/mesonbuild/meson/issues/8739.
-# XXX: We should be using sysconfig, but Debian only patches distutils.
-
-if 'deb_system' in distutils.command.install.INSTALL_SCHEMES:
- paths = get_distutils_paths(scheme='deb_system')
- install_paths = get_distutils_paths(scheme='deb_system', prefix='')
-else:
- paths = sysconfig.get_paths()
- empty_vars = {'base': '', 'platbase': '', 'installed_base': ''}
- install_paths = sysconfig.get_paths(vars=empty_vars)
-
-def links_against_libpython():
- from distutils.core import Distribution, Extension
- cmd = Distribution().get_command_obj('build_ext')
- cmd.ensure_finalized()
- return bool(cmd.get_libraries(Extension('dummy', [])))
-
-variables = sysconfig.get_config_vars()
-variables.update({'base_prefix': getattr(sys, 'base_prefix', sys.prefix)})
-
-if sys.version_info < (3, 0):
- suffix = variables.get('SO')
-elif sys.version_info < (3, 8, 7):
- # https://bugs.python.org/issue?@action=redirect&bpo=39825
- from distutils.sysconfig import get_config_var
- suffix = get_config_var('EXT_SUFFIX')
-else:
- suffix = variables.get('EXT_SUFFIX')
-
-print(json.dumps({
- 'variables': variables,
- 'paths': paths,
- 'sysconfig_paths': sysconfig.get_paths(),
- 'install_paths': install_paths,
- 'version': sysconfig.get_python_version(),
- 'platform': sysconfig.get_platform(),
- 'is_pypy': '__pypy__' in sys.builtin_module_names,
- 'is_venv': sys.prefix != variables['base_prefix'],
- 'link_libpython': links_against_libpython(),
- 'suffix': suffix,
-}))
-'''
-
-
-class PythonExternalProgram(ExternalProgram):
- def __init__(self, name: str, command: T.Optional[T.List[str]] = None,
- ext_prog: T.Optional[ExternalProgram] = None):
- if ext_prog is None:
- super().__init__(name, command=command, silent=True)
- else:
- self.name = name
- self.command = ext_prog.command
- self.path = ext_prog.path
-
- # 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
- # complete
- self.info: 'PythonIntrospectionDict' = {
- 'install_paths': {},
- 'is_pypy': False,
- 'is_venv': False,
- 'link_libpython': False,
- 'sysconfig_paths': {},
- 'paths': {},
- 'platform': 'sentinal',
- 'variables': {},
- 'version': '0.0',
- }
- self.pure: bool = True
-
- def _check_version(self, version: str) -> bool:
- if self.name == 'python2':
- return mesonlib.version_compare(version, '< 3.0')
- elif self.name == 'python3':
- return mesonlib.version_compare(version, '>= 3.0')
- return True
+ # This is a ClassVar instead of an instance bool, because although an
+ # installation is cached, we actually copy it, modify attributes such as pure,
+ # and return a temporary one rather than the cached object.
+ run_bytecompile: T.ClassVar[T.Dict[str, bool]] = {}
def sanity(self, state: T.Optional['ModuleState'] = None) -> bool:
- # Sanity check, we expect to have something that at least quacks in tune
- from tempfile import NamedTemporaryFile
- with NamedTemporaryFile(suffix='.py', delete=False, mode='w', encoding='utf-8') as tf:
- tmpfilename = tf.name
- tf.write(INTROSPECT_COMMAND)
- cmd = self.get_command() + [tmpfilename]
- p, stdout, stderr = mesonlib.Popen_safe(cmd)
- os.unlink(tmpfilename)
- try:
- info = json.loads(stdout)
- except json.JSONDecodeError:
- info = None
- mlog.debug('Could not introspect Python (%s): exit code %d' % (str(p.args), p.returncode))
- mlog.debug('Program stdout:\n')
- mlog.debug(stdout)
- mlog.debug('Program stderr:\n')
- mlog.debug(stderr)
-
- if info is not None and self._check_version(info['version']):
- self.info = T.cast('PythonIntrospectionDict', info)
+ ret = super().sanity()
+ if ret:
self.platlib = self._get_path(state, 'platlib')
self.purelib = self._get_path(state, 'purelib')
- return True
- else:
- return False
+ return ret
- def _get_path(self, state: T.Optional['ModuleState'], key: str) -> None:
+ def _get_path(self, state: T.Optional['ModuleState'], key: str) -> str:
rel_path = self.info['install_paths'][key][1:]
if not state:
# This happens only from run_project_tests.py
return rel_path
- value = state.get_option(f'{key}dir', module='python')
+ value = T.cast('str', state.get_option(f'{key}dir', module='python'))
if value:
if state.is_user_defined_option('install_env', module='python'):
raise mesonlib.MesonException(f'python.{key}dir and python.install_env are mutually exclusive')
_PURE_KW = KwargInfo('pure', (bool, NoneType))
_SUBDIR_KW = KwargInfo('subdir', str, default='')
+_LIMITED_API_KW = KwargInfo('limited_api', str, default='', since='1.3.0')
+_DEFAULTABLE_SUBDIR_KW = KwargInfo('subdir', (str, NoneType))
-
-class PythonInstallation(ExternalProgramHolder):
+class PythonInstallation(_ExternalProgramHolder['PythonExternalProgram']):
def __init__(self, python: 'PythonExternalProgram', interpreter: 'Interpreter'):
- ExternalProgramHolder.__init__(self, python, interpreter)
+ _ExternalProgramHolder.__init__(self, python, interpreter)
info = python.info
prefix = self.interpreter.environment.coredata.get_option(mesonlib.OptionKey('prefix'))
assert isinstance(prefix, str), 'for mypy'
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)
})
@permittedKwargs(mod_kwargs)
- def extension_module_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> 'SharedModule':
+ @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)
+ def extension_module_method(self, args: T.Tuple[str, T.List[BuildTargetSource]], kwargs: ExtensionModuleKw) -> 'SharedModule':
if 'install_dir' in kwargs:
- if 'subdir' in kwargs:
+ if kwargs['subdir'] is not None:
raise InvalidArguments('"subdir" and "install_dir" are mutually exclusive')
else:
- subdir = kwargs.pop('subdir', '')
- if not isinstance(subdir, str):
- raise InvalidArguments('"subdir" argument must be a string.')
+ # We want to remove 'subdir', but it may be None and we want to replace it with ''
+ # It must be done this way since we don't allow both `install_dir`
+ # and `subdir` to be set at the same time
+ subdir = kwargs.pop('subdir') or ''
kwargs['install_dir'] = self._get_install_dir_impl(False, subdir)
+ target_suffix = self.suffix
+
new_deps = mesonlib.extract_as_list(kwargs, 'dependencies')
- has_pydep = any(isinstance(dep, _PythonDependencyBase) for dep in new_deps)
- if not has_pydep:
+ pydep = next((dep for dep in new_deps if isinstance(dep, _PythonDependencyBase)), None)
+ if pydep is None:
pydep = self._dependency_method_impl({})
if not pydep.found():
raise mesonlib.MesonException('Python dependency not found')
FeatureNew.single_use('python_installation.extension_module with implicit dependency on python',
'0.63.0', self.subproject, 'use python_installation.dependency()',
self.current_node)
+
+ limited_api_version = kwargs.pop('limited_api')
+ allow_limited_api = self.interpreter.environment.coredata.get_option(OptionKey('allow_limited_api', module='python'))
+ if limited_api_version != '' and allow_limited_api:
+
+ target_suffix = self.limited_api_suffix
+
+ limited_api_version_hex = self._convert_api_version_to_py_version_hex(limited_api_version, pydep.version)
+ limited_api_definition = f'-DPy_LIMITED_API={limited_api_version_hex}'
+
+ new_c_args = mesonlib.extract_as_list(kwargs, 'c_args')
+ new_c_args.append(limited_api_definition)
+ kwargs['c_args'] = new_c_args
+
+ new_cpp_args = mesonlib.extract_as_list(kwargs, 'cpp_args')
+ new_cpp_args.append(limited_api_definition)
+ kwargs['cpp_args'] = new_cpp_args
+
+ # When compiled under MSVC, Python's PC/pyconfig.h forcibly inserts pythonMAJOR.MINOR.lib
+ # into the linker path when not running in debug mode via a series #pragma comment(lib, "")
+ # directives. We manually override these here as this interferes with the intended
+ # use of the 'limited_api' kwarg
+ for_machine = kwargs['native']
+ compilers = self.interpreter.environment.coredata.compilers[for_machine]
+ if any(compiler.get_id() == 'msvc' for compiler in compilers.values()):
+ pydep_copy = copy.copy(pydep)
+ pydep_copy.find_libpy_windows(self.env, limited_api=True)
+ if not pydep_copy.found():
+ raise mesonlib.MesonException('Python dependency supporting limited API not found')
+
+ new_deps.remove(pydep)
+ new_deps.append(pydep_copy)
+
+ pyver = pydep.version.replace('.', '')
+ python_windows_debug_link_exception = f'/NODEFAULTLIB:python{pyver}_d.lib'
+ python_windows_release_link_exception = f'/NODEFAULTLIB:python{pyver}.lib'
+
+ new_link_args = mesonlib.extract_as_list(kwargs, 'link_args')
+
+ is_debug = self.interpreter.environment.coredata.options[OptionKey('debug')].value
+ if is_debug:
+ new_link_args.append(python_windows_debug_link_exception)
+ else:
+ new_link_args.append(python_windows_release_link_exception)
+
+ kwargs['link_args'] = new_link_args
+
kwargs['dependencies'] = new_deps
# msys2's python3 has "-cpython-36m.dll", we have to be clever
# FIXME: explain what the specific cleverness is here
- split, suffix = self.suffix.rsplit('.', 1)
- args[0] += split
+ split, target_suffix = target_suffix.rsplit('.', 1)
+ args = (args[0] + split, args[1])
kwargs['name_prefix'] = ''
- kwargs['name_suffix'] = suffix
+ kwargs['name_suffix'] = target_suffix
- if 'gnu_symbol_visibility' not in kwargs and \
+ if kwargs['gnu_symbol_visibility'] == '' and \
(self.is_pypy or mesonlib.version_compare(self.version, '>=3.9')):
kwargs['gnu_symbol_visibility'] = 'inlineshidden'
- return self.interpreter.func_shared_module(None, args, kwargs)
+ return self.interpreter.build_target(self.current_node, args, kwargs, SharedModule)
+
+ def _convert_api_version_to_py_version_hex(self, api_version: str, detected_version: str) -> str:
+ python_api_version_format = re.compile(r'[0-9]\.[0-9]{1,2}')
+ decimal_match = python_api_version_format.fullmatch(api_version)
+ if not decimal_match:
+ raise InvalidArguments(f'Python API version invalid: "{api_version}".')
+ if mesonlib.version_compare(api_version, '<3.2'):
+ raise InvalidArguments(f'Python Limited API version invalid: {api_version} (must be greater than 3.2)')
+ if mesonlib.version_compare(api_version, '>' + detected_version):
+ raise InvalidArguments(f'Python Limited API version too high: {api_version} (detected {detected_version})')
+
+ version_components = api_version.split('.')
+ major = int(version_components[0])
+ minor = int(version_components[1])
+
+ 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)
new_kwargs = kwargs.copy()
new_kwargs['required'] = False
- methods = process_method_kw({DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM}, kwargs)
- # it's theoretically (though not practically) possible to not bind dep, let's ensure it is.
- dep: Dependency = NotFoundDependency('python', self.interpreter.environment)
- for d in python_factory(self.interpreter.environment, for_machine, new_kwargs, methods, self):
- dep = d()
- if dep.found():
- break
+ 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
)
def install_sources_method(self, args: T.Tuple[T.List[T.Union[str, mesonlib.File]]],
kwargs: 'PyInstallKw') -> 'Data':
- tag = kwargs['install_tag'] or 'runtime'
+ self.held_object.run_bytecompile[self.version] = True
+ tag = kwargs['install_tag'] or 'python-runtime'
pure = kwargs['pure'] if kwargs['pure'] is not None else self.pure
install_dir = self._get_install_dir_impl(pure, kwargs['subdir'])
return self.interpreter.install_data_impl(
self.interpreter.source_strings_to_files(args[0]),
install_dir,
mesonlib.FileMode(), rename=None, tag=tag, install_data_type='python',
- install_dir_name=install_dir.optname,
preserve_path=kwargs['preserve_path'])
@noPosargs
@typed_kwargs('python_installation.install_dir', _PURE_KW, _SUBDIR_KW)
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
return self._get_install_dir_impl(pure, kwargs['subdir'])
def __init__(self, interpreter: 'Interpreter') -> None:
super().__init__(interpreter)
- self.installations: T.Dict[str, ExternalProgram] = {}
+ self.installations: T.Dict[str, MaybePythonProg] = {}
self.methods.update({
'find_installation': self.find_installation,
})
+ def _get_install_scripts(self) -> T.List[mesonlib.ExecutableSerialisation]:
+ backend = self.interpreter.backend
+ ret = []
+ optlevel = self.interpreter.environment.coredata.get_option(mesonlib.OptionKey('bytecompile', module='python'))
+ if optlevel == -1:
+ return ret
+ if not any(PythonExternalProgram.run_bytecompile.values()):
+ return ret
+
+ installdata = backend.create_install_data()
+ py_files = []
+
+ def should_append(f, isdir: bool = False):
+ # This uses the install_plan decorated names to see if the original source was propagated via
+ # install_sources() or get_install_dir().
+ return f.startswith(('{py_platlib}', '{py_purelib}')) and (f.endswith('.py') or isdir)
+
+ for t in installdata.targets:
+ if should_append(t.out_name):
+ py_files.append((t.out_name, os.path.join(installdata.prefix, t.outdir, os.path.basename(t.fname))))
+ for d in installdata.data:
+ if should_append(d.install_path_name):
+ py_files.append((d.install_path_name, os.path.join(installdata.prefix, d.install_path)))
+ for d in installdata.install_subdirs:
+ if should_append(d.install_path_name, True):
+ py_files.append((d.install_path_name, os.path.join(installdata.prefix, d.install_path)))
+
+ import importlib.resources
+ pycompile = os.path.join(self.interpreter.environment.get_scratch_dir(), 'pycompile.py')
+ with open(pycompile, 'wb') as f:
+ f.write(importlib.resources.read_binary('mesonbuild.scripts', 'pycompile.py'))
+
+ for i in self.installations.values():
+ if isinstance(i, PythonExternalProgram) and i.run_bytecompile[i.info['version']]:
+ i = T.cast('PythonExternalProgram', i)
+ manifest = f'python-{i.info["version"]}-installed.json'
+ manifest_json = []
+ for name, f in py_files:
+ if f.startswith((os.path.join(installdata.prefix, i.platlib), os.path.join(installdata.prefix, i.purelib))):
+ manifest_json.append(name)
+ with open(os.path.join(self.interpreter.environment.get_scratch_dir(), manifest), 'w', encoding='utf-8') as f:
+ json.dump(manifest_json, f)
+ cmd = i.command + [pycompile, manifest, str(optlevel)]
+
+ script = backend.get_executable_serialisation(cmd, verbose=True, tag='python-runtime',
+ installdir_map={'py_purelib': i.purelib, 'py_platlib': i.platlib})
+ ret.append(script)
+ return ret
+
+ def postconf_hook(self, b: Build) -> None:
+ b.install_scripts.extend(self._get_install_scripts())
+
# https://www.python.org/dev/peps/pep-0397/
@staticmethod
def _get_win_pythonpath(name_or_path: str) -> T.Optional[str]:
- if name_or_path not in ['python2', 'python3']:
+ if not name_or_path.startswith(('python2', 'python3')):
return None
if not shutil.which('py'):
# program not installed, return without an exception
return None
- ver = {'python2': '-2', 'python3': '-3'}[name_or_path]
+ ver = f'-{name_or_path[6:]}'
cmd = ['py', ver, '-c', "import sysconfig; print(sysconfig.get_config_var('BINDIR'))"]
_, stdout, _ = mesonlib.Popen_safe(cmd)
directory = stdout.strip()
else:
return None
- def _find_installation_impl(self, state: 'ModuleState', display_name: str, name_or_path: str, required: bool) -> ExternalProgram:
+ def _find_installation_impl(self, state: 'ModuleState', display_name: str, name_or_path: str, required: bool) -> MaybePythonProg:
if not name_or_path:
python = PythonExternalProgram('python3', mesonlib.python_command)
else:
# named python is available and has a compatible version, let's use
# it
if not python.found() and name_or_path in {'python2', 'python3'}:
- python = PythonExternalProgram('python')
+ tmp_python = ExternalProgram.from_entry(display_name, 'python')
+ python = PythonExternalProgram(name_or_path, ext_prog=tmp_python)
if python.found():
if python.sanity(state):
else:
mlog.warning(sanitymsg, location=state.current_node)
- return NonExistingExternalProgram()
+ return NonExistingExternalProgram(python.name)
@disablerIfNotFound
@typed_pos_args('python.find_installation', optargs=[str])
_PURE_KW.evolve(default=True, since='0.64.0'),
)
def find_installation(self, state: 'ModuleState', args: T.Tuple[T.Optional[str]],
- kwargs: 'FindInstallationKw') -> ExternalProgram:
+ kwargs: 'FindInstallationKw') -> MaybePythonProg:
feature_check = FeatureNew('Passing "feature" option to find_installation', '0.48.0')
disabled, required, feature = extract_required_kwarg(kwargs, state.subproject, feature_check)
if not python.found():
if required:
raise mesonlib.MesonException('{} not found'.format(name_or_path or 'python'))
- return NonExistingExternalProgram()
+ return NonExistingExternalProgram(python.name)
elif missing_modules:
if required:
raise mesonlib.MesonException('{} is missing modules: {}'.format(name_or_path or 'python', ', '.join(missing_modules)))
- return NonExistingExternalProgram()
+ return NonExistingExternalProgram(python.name)
else:
+ assert isinstance(python, PythonExternalProgram), 'for mypy'
python = copy.copy(python)
python.pure = kwargs['pure']
+ python.run_bytecompile.setdefault(python.info['version'], False)
return python
raise mesonlib.MesonBugException('Unreachable code was reached (PythonModule.find_installation).')
# 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 sysconfig
-from .. import mesonlib
+import typing as T
-from . import ExtensionModule, ModuleInfo
-from ..interpreterbase import typed_pos_args, noPosargs, noKwargs, permittedKwargs
-from ..build import known_shmod_kwargs
+from .. import mesonlib
+from . import ExtensionModule, ModuleInfo, ModuleState
+from ..build import (
+ BuildTarget, CustomTarget, CustomTargetIndex, ExtractedObjects,
+ GeneratedList, SharedModule, StructuredSources, known_shmod_kwargs
+)
+from ..interpreter.type_checking import SHARED_MOD_KWS
+from ..interpreterbase import typed_kwargs, typed_pos_args, noPosargs, noKwargs, permittedKwargs
from ..programs import ExternalProgram
+if T.TYPE_CHECKING:
+ from ..interpreter.interpreter import BuildTargetSource
+ from ..interpreter.kwargs import SharedModule as SharedModuleKW
+
+
+_MOD_KWARGS = [k for k in SHARED_MOD_KWS if k.name not in {'name_prefix', 'name_suffix'}]
+
class Python3Module(ExtensionModule):
'sysconfig_path': self.sysconfig_path,
})
- @permittedKwargs(known_shmod_kwargs)
- def extension_module(self, state, args, kwargs):
- if 'name_prefix' in kwargs:
- raise mesonlib.MesonException('Name_prefix is set automatically, specifying it is forbidden.')
- if 'name_suffix' in kwargs:
- raise mesonlib.MesonException('Name_suffix is set automatically, specifying it is forbidden.')
+ @permittedKwargs(known_shmod_kwargs - {'name_prefix', 'name_suffix'})
+ @typed_pos_args('python3.extension_module', str, varargs=(str, mesonlib.File, CustomTarget, CustomTargetIndex, GeneratedList, StructuredSources, ExtractedObjects, BuildTarget))
+ @typed_kwargs('python3.extension_module', *_MOD_KWARGS, allow_unknown=True)
+ def extension_module(self, state: ModuleState, args: T.Tuple[str, T.List[BuildTargetSource]], kwargs: SharedModuleKW):
host_system = state.host_machine.system
if host_system == 'darwin':
# Default suffix is 'dylib' but Python does not use it for extensions.
suffix = []
kwargs['name_prefix'] = ''
kwargs['name_suffix'] = suffix
- return self.interpreter.func_shared_module(None, args, kwargs)
+ return self.interpreter.build_target(state.current_node, args, kwargs, SharedModule)
@noPosargs
@noKwargs
from .. import build
from .. import coredata
from .. import mlog
-from ..dependencies import find_external_dependency, Dependency, ExternalLibrary
+from ..dependencies import find_external_dependency, Dependency, ExternalLibrary, InternalDependency
from ..mesonlib import MesonException, File, version_compare, Popen_safe
from ..interpreter import extract_required_kwarg
from ..interpreter.type_checking import INSTALL_DIR_KW, INSTALL_KW, NoneType
self.qt_version = qt_version
# It is important that this list does not change order as the order of
# the returned ExternalPrograms will change as well
- self.tools: T.Dict[str, ExternalProgram] = {
+ self.tools: T.Dict[str, T.Union[ExternalProgram, build.Executable]] = {
'moc': NonExistingExternalProgram('moc'),
'uic': NonExistingExternalProgram('uic'),
'rcc': NonExistingExternalProgram('rcc'),
arg = ['-v']
# Ensure that the version of qt and each tool are the same
- def get_version(p: ExternalProgram) -> str:
+ def get_version(p: T.Union[ExternalProgram, build.Executable]) -> str:
_, out, err = Popen_safe(p.get_command() + arg)
if name == 'lrelease' or not qt_dep.version.startswith('4'):
care = out
[f'{name}.cpp'],
depend_files=qrc_deps,
depfile=f'{name}.d',
+ description='Compiling Qt resources {}',
)
targets.append(res_target)
else:
[f'{name}.cpp'],
depend_files=qrc_deps,
depfile=f'{name}.d',
+ description='Compiling Qt resources {}',
)
targets.append(res_target)
inc = state.get_include_args(include_dirs=kwargs['include_directories'])
compile_args: T.List[str] = []
for dep in kwargs['dependencies']:
- compile_args.extend([a for a in dep.get_all_compile_args() if a.startswith(('-I', '-D'))])
+ 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))
output: T.List[build.GeneratedList] = []
if kwargs['sources']:
moc_gen = build.Generator(
self.tools['moc'], arguments, ['@BASENAME@.moc'],
- depfile='@BASENAME.moc.d@',
+ depfile='@BASENAME@.moc.d',
name=f'Qt{self.qt_version} moc source')
output.append(moc_gen.process_files(kwargs['sources'], state))
ts = os.path.basename(ts)
else:
outdir = state.subdir
- cmd: T.List[T.Union[ExternalProgram, str]] = [self.tools['lrelease'], '@INPUT@', '-qm', '@OUTPUT@']
+ cmd: T.List[T.Union[ExternalProgram, build.Executable, str]] = [self.tools['lrelease'], '@INPUT@', '-qm', '@OUTPUT@']
lrelease_target = build.CustomTarget(
f'qt{self.qt_version}-compile-{ts}',
outdir,
install_dir=[kwargs['install_dir']],
install_tag=['i18n'],
build_by_default=kwargs['build_by_default'],
+ description='Compiling Qt translations {}',
)
translations.append(lrelease_target)
if qresource:
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import annotations
+import typing as T
+
from .qt import QtBaseModule
from . import ModuleInfo
+if T.TYPE_CHECKING:
+ from ..interpreter import Interpreter
+
class Qt4Module(QtBaseModule):
INFO = ModuleInfo('qt4')
- def __init__(self, interpreter):
+ def __init__(self, interpreter: Interpreter):
QtBaseModule.__init__(self, interpreter, qt_version=4)
-def initialize(*args, **kwargs):
- return Qt4Module(*args, **kwargs)
+def initialize(interp: Interpreter) -> Qt4Module:
+ return Qt4Module(interp)
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import annotations
+import typing as T
+
from .qt import QtBaseModule
from . import ModuleInfo
+if T.TYPE_CHECKING:
+ from ..interpreter import Interpreter
+
class Qt5Module(QtBaseModule):
INFO = ModuleInfo('qt5')
- def __init__(self, interpreter):
+ def __init__(self, interpreter: Interpreter):
QtBaseModule.__init__(self, interpreter, qt_version=5)
-def initialize(*args, **kwargs):
- return Qt5Module(*args, **kwargs)
+def initialize(interp: Interpreter) -> Qt5Module:
+ return Qt5Module(interp)
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import annotations
+import typing as T
+
from .qt import QtBaseModule
from . import ModuleInfo
+if T.TYPE_CHECKING:
+ from ..interpreter import Interpreter
class Qt6Module(QtBaseModule):
INFO = ModuleInfo('qt6', '0.57.0')
- def __init__(self, interpreter):
+ def __init__(self, interpreter: Interpreter):
QtBaseModule.__init__(self, interpreter, qt_version=6)
-def initialize(*args, **kwargs):
- return Qt6Module(*args, **kwargs)
+def initialize(interp: Interpreter) -> Qt6Module:
+ return Qt6Module(interp)
-# Copyright © 2020-2022 Intel Corporation
+# Copyright © 2020-2023 Intel Corporation
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# 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 itertools
import os
import typing as T
+from mesonbuild.interpreterbase.decorators import FeatureNew
+
from . import ExtensionModule, ModuleReturnValue, ModuleInfo
from .. import mlog
-from ..build import BothLibraries, BuildTarget, CustomTargetIndex, Executable, ExtractedObjects, GeneratedList, IncludeDirs, CustomTarget, StructuredSources
-from ..dependencies import Dependency, ExternalLibrary
-from ..interpreter.type_checking import DEPENDENCIES_KW, TEST_KWS, OUTPUT_KW, INCLUDE_DIRECTORIES, include_dir_string_new
-from ..interpreterbase import ContainerTypeInfo, InterpreterException, KwargInfo, typed_kwargs, typed_pos_args, noPosargs
+from ..build import (BothLibraries, BuildTarget, CustomTargetIndex, Executable, ExtractedObjects, GeneratedList,
+ CustomTarget, InvalidArguments, Jar, StructuredSources, SharedLibrary)
+from ..compilers.compilers import are_asserts_disabled
+from ..interpreter.type_checking import DEPENDENCIES_KW, LINK_WITH_KW, SHARED_LIB_KWS, TEST_KWS, OUTPUT_KW, INCLUDE_DIRECTORIES, SOURCES_VARARGS
+from ..interpreterbase import ContainerTypeInfo, InterpreterException, KwargInfo, typed_kwargs, typed_pos_args, noPosargs, permittedKwargs
from ..mesonlib import File
if T.TYPE_CHECKING:
from . import ModuleState
+ from ..build import IncludeDirs, LibTypes
+ from ..dependencies import Dependency, ExternalLibrary
from ..interpreter import Interpreter
from ..interpreter import kwargs as _kwargs
from ..interpreter.interpreter import SourceInputs, SourceOutputs
- from ..programs import ExternalProgram
+ from ..programs import ExternalProgram, OverrideProgram
+ from ..interpreter.type_checking import SourcesVarargsType
from typing_extensions import TypedDict
dependencies: T.List[T.Union[Dependency, ExternalLibrary]]
is_parallel: bool
+ link_with: T.List[LibTypes]
+ rust_args: T.List[str]
class FuncBindgen(TypedDict):
def __init__(self, interpreter: Interpreter) -> None:
super().__init__(interpreter)
- self._bindgen_bin: T.Optional[ExternalProgram] = None
+ self._bindgen_bin: T.Optional[T.Union[ExternalProgram, Executable, OverrideProgram]] = None
self.methods.update({
'test': self.test,
'bindgen': self.bindgen,
+ 'proc_macro': self.proc_macro,
})
@typed_pos_args('rust.test', str, BuildTarget)
'rust.test',
*TEST_KWS,
DEPENDENCIES_KW,
+ LINK_WITH_KW.evolve(since='1.2.0'),
+ KwargInfo(
+ 'rust_args',
+ ContainerTypeInfo(list, str),
+ listify=True,
+ default=[],
+ since='1.2.0',
+ ),
KwargInfo('is_parallel', bool, default=False),
)
def test(self, state: ModuleState, args: T.Tuple[str, BuildTarget], kwargs: FuncTest) -> ModuleReturnValue:
rust.test('rust_lib_test', rust_lib)
```
"""
+ if any(isinstance(t, Jar) for t in kwargs.get('link_with', [])):
+ raise InvalidArguments('Rust tests cannot link with Jar targets')
+
name = args[0]
base_target: BuildTarget = args[1]
if not base_target.uses_rust():
tkwargs['args'] = extra_args + ['--test', '--format', 'pretty']
tkwargs['protocol'] = 'rust'
- new_target_kwargs = base_target.kwargs.copy()
+ 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['rust_args'] = new_target_kwargs.get('rust_args', []) + ['--test']
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']
+ del new_target_kwargs['rust_crate_type']
+
+ lang_args = base_target.extra_args.copy()
+ lang_args['rust'] = base_target.extra_args['rust'] + kwargs['rust_args'] + ['--test']
+ new_target_kwargs['language_args'] = lang_args
sources = T.cast('T.List[SourceOutputs]', base_target.sources.copy())
sources.extend(base_target.generated)
listify=True,
required=True,
),
- INCLUDE_DIRECTORIES.evolve(feature_validator=include_dir_string_new),
+ INCLUDE_DIRECTORIES.evolve(since_values={ContainerTypeInfo(list, str): '1.0.0'}),
OUTPUT_KW,
DEPENDENCIES_KW.evolve(since='1.0.0'),
)
else:
depends.append(d)
- clang_args: T.List[str] = []
+ # Copy to avoid subsequent calls mutating the original
+ # 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()
+
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(
state.environment.get_source_dir(), state.environment.get_build_dir())])
+ if are_asserts_disabled(state.environment.coredata.options):
+ clang_args.append('-DNDEBUG')
for de in kwargs['dependencies']:
for i in de.get_include_dirs():
elif isinstance(s, CustomTarget):
depends.append(s)
+ # We only want include directories and defines, other things may not be valid
+ cargs = state.get_option('args', state.subproject, lang='c')
+ assert isinstance(cargs, list), 'for mypy'
+ for a in itertools.chain(state.global_args.get('c', []), state.project_args.get('c', []), cargs):
+ if a.startswith(('-I', '/I', '-D', '/D', '-U', '/U')):
+ clang_args.append(a)
+
if self._bindgen_bin is None:
self._bindgen_bin = state.find_program('bindgen')
extra_depends=depends,
depend_files=depend_files,
backend=state.backend,
+ description='Generating bindings for Rust {}',
)
return ModuleReturnValue([target], [target])
+ # Allow a limited set of kwargs, but still use the full set of typed_kwargs()
+ # because it could be setting required default values.
+ @FeatureNew('rust.proc_macro', '1.3.0')
+ @permittedKwargs({'rust_args', 'rust_dependency_map', 'sources', 'dependencies', 'extra_files',
+ 'link_args', 'link_depends', 'link_with', 'override_options'})
+ @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
+ target = state._interpreter.build_target(state.current_node, args, kwargs, SharedLibrary)
+ return target
+
def initialize(interp: Interpreter) -> RustModule:
return RustModule(interp)
# 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
-from .. import mesonlib, compilers, mlog
+import typing as T
+
+from .. import mesonlib, mlog
from .. import build
+from ..compilers import Compiler
+from ..interpreter.type_checking import BT_SOURCES_KW, STATIC_LIB_KWS
+from ..interpreterbase.decorators import KwargInfo, permittedKwargs, typed_pos_args, typed_kwargs
from . import ExtensionModule, ModuleInfo
+if T.TYPE_CHECKING:
+ from . import ModuleState
+ from ..interpreter import Interpreter, kwargs as kwtypes
+ from ..interpreter.type_checking import SourcesVarargsType
+
+ class CheckKw(kwtypes.StaticLibrary):
+
+ compiler: Compiler
+ mmx: SourcesVarargsType
+ sse: SourcesVarargsType
+ sse2: SourcesVarargsType
+ sse3: SourcesVarargsType
+ ssse3: SourcesVarargsType
+ sse41: SourcesVarargsType
+ sse42: SourcesVarargsType
+ avx: SourcesVarargsType
+ avx2: SourcesVarargsType
+ neon: SourcesVarargsType
+
+
+# FIXME add Altivec and AVX512.
+ISETS = (
+ 'mmx',
+ 'sse',
+ 'sse2',
+ 'sse3',
+ 'ssse3',
+ 'sse41',
+ 'sse42',
+ 'avx',
+ 'avx2',
+ 'neon',
+)
+
+
class SimdModule(ExtensionModule):
INFO = ModuleInfo('SIMD', '0.42.0', unstable=True)
- def __init__(self, interpreter):
+ def __init__(self, interpreter: Interpreter):
super().__init__(interpreter)
- # FIXME add Altivec and AVX512.
- self.isets = ('mmx',
- 'sse',
- 'sse2',
- 'sse3',
- 'ssse3',
- 'sse41',
- 'sse42',
- 'avx',
- 'avx2',
- 'neon',
- )
self.methods.update({
'check': self.check,
})
- def check(self, state, args, kwargs):
- result = []
- if len(args) != 1:
- raise mesonlib.MesonException('Check requires one argument, a name prefix for checks.')
- prefix = args[0]
- if not isinstance(prefix, str):
- raise mesonlib.MesonException('Argument must be a string.')
- if 'compiler' not in kwargs:
- raise mesonlib.MesonException('Must specify compiler keyword')
+ @typed_pos_args('simd.check', str)
+ @typed_kwargs('simd.check',
+ KwargInfo('compiler', Compiler, required=True),
+ *[BT_SOURCES_KW.evolve(name=iset, default=None) for iset in ISETS],
+ *[a for a in STATIC_LIB_KWS if a.name != 'sources'],
+ allow_unknown=True) # Because we also accept STATIC_LIB_KWS, but build targets have not been completely ported to typed_pos_args/typed_kwargs.
+ @permittedKwargs({'compiler', *ISETS, *build.known_stlib_kwargs}) # Also remove this, per above comment
+ def check(self, state: ModuleState, args: T.Tuple[str], kwargs: CheckKw) -> T.List[T.Union[T.List[build.StaticLibrary], build.ConfigurationData]]:
+ result: T.List[build.StaticLibrary] = []
+
if 'sources' in kwargs:
raise mesonlib.MesonException('SIMD module does not support the "sources" keyword')
- basic_kwargs = {}
- for key, value in kwargs.items():
- if key not in self.isets and key != 'compiler':
- basic_kwargs[key] = value
+
+ local_kwargs = set((*ISETS, 'compiler'))
+ static_lib_kwargs = T.cast('kwtypes.StaticLibrary', {k: v for k, v in kwargs.items() if k not in local_kwargs})
+
+ prefix = args[0]
compiler = kwargs['compiler']
- if not isinstance(compiler, compilers.compilers.Compiler):
- raise mesonlib.MesonException('Compiler argument must be a compiler object.')
conf = build.ConfigurationData()
- for iset in self.isets:
- if iset not in kwargs:
+
+ for iset in ISETS:
+ sources = kwargs[iset]
+ if sources is None:
continue
- iset_fname = kwargs[iset] # Might also be an array or Files. static_library will validate.
- args = compiler.get_instruction_set_args(iset)
- if args is None:
- mlog.log('Compiler supports %s:' % iset, mlog.red('NO'))
+
+ compile_args = compiler.get_instruction_set_args(iset)
+ if compile_args is None:
+ mlog.log(f'Compiler supports {iset}:', mlog.red('NO'))
+ continue
+
+ if not compiler.has_multi_arguments(compile_args, state.environment)[0]:
+ mlog.log(f'Compiler supports {iset}:', mlog.red('NO'))
continue
- if args:
- if not compiler.has_multi_arguments(args, state.environment)[0]:
- mlog.log('Compiler supports %s:' % iset, mlog.red('NO'))
- continue
- mlog.log('Compiler supports %s:' % iset, mlog.green('YES'))
- conf.values['HAVE_' + iset.upper()] = ('1', 'Compiler supports %s.' % iset)
+ mlog.log(f'Compiler supports {iset}:', mlog.green('YES'))
+ conf.values['HAVE_' + iset.upper()] = ('1', f'Compiler supports {iset}.')
+
libname = prefix + '_' + iset
- lib_kwargs = {'sources': iset_fname,
- }
- lib_kwargs.update(basic_kwargs)
+ lib_kwargs = static_lib_kwargs.copy()
+ lib_kwargs['sources'] = sources
+
+ # Add compile args we derived above to those the user provided us
langarg_key = compiler.get_language() + '_args'
old_lang_args = mesonlib.extract_as_list(lib_kwargs, langarg_key)
- all_lang_args = old_lang_args + args
+ all_lang_args = old_lang_args + compile_args
lib_kwargs[langarg_key] = all_lang_args
- result.append(self.interpreter.func_static_lib(None, [libname], lib_kwargs))
+
+ lib = self.interpreter.build_target(state.current_node, (libname, []), lib_kwargs, build.StaticLibrary)
+
+ result.append(lib)
+
return [result, conf]
-def initialize(*args, **kwargs):
- return SimdModule(*args, **kwargs)
+def initialize(interp: Interpreter) -> SimdModule:
+ return SimdModule(interp)
raise InterpreterException('add_all called with both positional and keyword arguments')
keys, dependencies = self.check_conditions(when)
for s in if_true:
- if not isinstance(s, SourceSetImpl):
- raise InvalidCode('Arguments to \'add_all\' after the first must be source sets')
s.frozen = True
self.rules.append(SourceSetRule(keys, dependencies, [], [], if_true, []))
# 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 enum
import os
if not rescomp or not rescomp.found():
comp = self.detect_compiler(state.environment.coredata.compilers[for_machine])
- if comp.id in {'msvc', 'clang-cl', 'intel-cl'}:
+ 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)
else:
rescomp = ExternalProgram('windres', silent=True)
for (arg, match, rc_type) in [
('/?', '^.*Microsoft.*Resource Compiler.*$', ResourceCompilerType.rc),
+ ('/?', 'LLVM Resource Converter.*$', ResourceCompilerType.rc),
('--version', '^.*GNU windres.*$', ResourceCompilerType.windres),
('--version', '^.*Wine Resource Compiler.*$', ResourceCompilerType.wrc),
]:
elif isinstance(src, build.CustomTargetIndex):
FeatureNew.single_use('windows.compile_resource CustomTargetIndex in positional arguments', '0.61.0',
state.subproject, location=state.current_node)
- # This dance avoids a case where two indexs of the same
+ # This dance avoids a case where two indexes of the same
# target are given as separate arguments.
yield (f'{src.get_id()}_{src.target.get_outputs().index(src.output)}',
f'windows_compile_resources_{src.get_filename()}', src)
depfile=depfile,
depend_files=wrc_depend_files,
extra_depends=wrc_depends,
+ description='Compiling Windows resource {}',
))
return ModuleReturnValue(res_targets, [res_targets])
# 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 dataclasses import dataclass
+from __future__ import annotations
+from dataclasses import dataclass, field
import re
import codecs
-import types
+import os
import typing as T
+
from .mesonlib import MesonException
from . import mlog
if T.TYPE_CHECKING:
+ from typing_extensions import Literal
+
from .ast import AstVisitor
+ BaseNodeT = T.TypeVar('BaseNodeT', bound='BaseNode')
+
# This is the regex for the supported escape sequences of a regular string
# literal, like 'abc\x00'
ESCAPE_SEQUENCE_SINGLE_RE = re.compile(r'''
| \\[\\'abfnrtv] # Single-character escapes
)''', re.UNICODE | re.VERBOSE)
-class MesonUnicodeDecodeError(MesonException):
- def __init__(self, match: str) -> None:
- super().__init__(match)
- self.match = match
-
def decode_match(match: T.Match[str]) -> str:
- try:
- return codecs.decode(match.group(0).encode(), 'unicode_escape')
- except UnicodeDecodeError:
- raise MesonUnicodeDecodeError(match.group(0))
+ return codecs.decode(match.group(0).encode(), 'unicode_escape')
class ParseException(MesonException):
+
+ ast: T.Optional[CodeBlockNode] = None
+
def __init__(self, text: str, line: str, lineno: int, colno: int) -> None:
# Format as error message, followed by the line with the error, followed by a caret to show the error column.
- super().__init__("{}\n{}\n{}".format(text, line, '{}^'.format(' ' * colno)))
+ super().__init__(mlog.code_line(text, line, colno))
self.lineno = lineno
self.colno = colno
-class BlockParseException(MesonException):
+class BlockParseException(ParseException):
def __init__(
self,
text: str,
# Followed by a caret to show the block start
# Followed by underscores
# Followed by a caret to show the block end.
- super().__init__("{}\n{}\n{}".format(text, line, '{}^{}^'.format(' ' * start_colno, '_' * (colno - start_colno - 1))))
+ MesonException.__init__(self, "{}\n{}\n{}".format(text, line, '{}^{}^'.format(' ' * start_colno, '_' * (colno - start_colno - 1))))
else:
# If block start and end are on different lines, it is formatted as:
# Error message
# Followed by a message saying where the block started.
# Followed by the line of the block start.
# Followed by a caret for the block start.
- super().__init__("%s\n%s\n%s\nFor a block that started at %d,%d\n%s\n%s" % (text, line, '%s^' % (' ' * colno), start_lineno, start_colno, start_line, "%s^" % (' ' * start_colno)))
+ MesonException.__init__(self, "%s\n%s\n%s\nFor a block that started at %d,%d\n%s\n%s" % (text, line, '%s^' % (' ' * colno), start_lineno, start_colno, start_line, "%s^" % (' ' * start_colno)))
self.lineno = lineno
self.colno = colno
'endif', 'and', 'or', 'not', 'foreach', 'endforeach',
'in', 'continue', 'break'}
self.future_keywords = {'return'}
+ self.in_unit_test = 'MESON_RUNNING_IN_PROJECT_TESTS' in os.environ
+ if self.in_unit_test:
+ self.keywords.update({'testcase', 'endtestcase'})
self.token_specification = [
# Need to be sorted longest to shortest.
- ('ignore', re.compile(r'[ \t]')),
+ ('whitespace', re.compile(r'[ \t]+')),
('multiline_fstring', re.compile(r"f'''(.|\n)*?'''", re.M)),
('fstring', re.compile(r"f'([^'\\]|(\\.))*'")),
('id', re.compile('[_a-zA-Z][_0-9a-zA-Z]*')),
('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'\\\n')),
+ ('eol_cont', re.compile(r'\\[ \t]*(#.*)?\n')),
('eol', re.compile(r'\n')),
('multiline_string', re.compile(r"'''(.|\n)*?'''", re.M)),
('comment', re.compile(r'#.*')),
col = 0
while loc < len(self.code):
matched = False
- value = None # type: T.Union[str, bool, int]
+ value: str = ''
for (tid, reg) in self.token_specification:
mo = reg.match(self.code, loc)
if mo:
loc = mo.end()
span_end = loc
bytespan = (span_start, span_end)
- match_text = mo.group()
- if tid in {'ignore', 'comment'}:
- break
- elif tid == 'lparen':
+ value = mo.group()
+ if tid == 'lparen':
par_count += 1
elif tid == 'rparen':
par_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'}:
- # Handle here and not on the regexp to give a better error message.
- if match_text.find("\n") != -1:
- msg = ParseException("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.",
- self.getline(line_start), lineno, col)
- mlog.warning(msg, location=BaseNode(lineno, col, filename))
- value = match_text[2 if tid == 'fstring' else 1:-1]
- try:
- value = ESCAPE_SEQUENCE_SINGLE_RE.sub(decode_match, value)
- except MesonUnicodeDecodeError as err:
- raise MesonException(f"Failed to parse escape sequence: '{err.match}' in string:\n {match_text}")
+ 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'}:
- # For multiline strings, parse out the value and pass
- # through the normal string logic.
- # For multiline format strings, we have to emit a
- # different AST node so we can add a feature check,
- # but otherwise, it follows the normal fstring logic.
- if tid == 'multiline_string':
- value = match_text[3:-3]
- tid = 'string'
- else:
- value = match_text[4:-3]
- lines = match_text.split('\n')
+ 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 == 'number':
- value = int(match_text, base=0)
elif tid == 'eol_cont':
lineno += 1
line_start = loc
- break
+ tid = 'whitespace'
elif tid == 'eol':
lineno += 1
line_start = loc
if par_count > 0 or bracket_count > 0 or curl_count > 0:
- break
+ tid = 'whitespace'
elif tid == 'id':
- if match_text in self.keywords:
- tid = match_text
+ if value in self.keywords:
+ tid = value
else:
- if match_text in self.future_keywords:
- mlog.warning(f"Identifier '{match_text}' will become a reserved keyword in a future release. Please rename it.",
- location=types.SimpleNamespace(filename=filename, lineno=lineno))
- value = match_text
+ 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)
-@dataclass(eq=False)
+@dataclass
class BaseNode:
lineno: int
colno: int
- filename: str
- end_lineno: T.Optional[int] = None
- end_colno: T.Optional[int] = None
+ filename: str = field(hash=False)
+ end_lineno: int = field(hash=False)
+ end_colno: int = field(hash=False)
+ whitespaces: T.Optional[WhitespaceNode] = field(hash=False)
- def __post_init__(self) -> None:
- if self.end_lineno is None:
- self.end_lineno = self.lineno
- if self.end_colno is None:
- self.end_colno = self.colno
+ def __init__(self, lineno: int, colno: int, filename: str,
+ end_lineno: T.Optional[int] = None, end_colno: T.Optional[int] = None) -> None:
+ self.lineno = lineno
+ self.colno = colno
+ self.filename = filename
+ self.end_lineno = end_lineno if end_lineno is not None else lineno
+ self.end_colno = end_colno if end_colno is not None else colno
+ self.whitespaces = None
# Attributes for the visitors
- self.level = 0 # type: int
- self.ast_id = '' # type: str
- self.condition_level = 0 # type: int
+ self.level = 0
+ self.ast_id = ''
+ self.condition_level = 0
def accept(self, visitor: 'AstVisitor') -> None:
fname = 'visit_{}'.format(type(self).__name__)
if callable(func):
func(self)
+ def append_whitespaces(self, token: Token) -> None:
+ if self.whitespaces is None:
+ self.whitespaces = WhitespaceNode(token)
+ else:
+ self.whitespaces.append(token)
+
+
+@dataclass(unsafe_hash=True)
+class WhitespaceNode(BaseNode):
+
+ value: str
+
+ def __init__(self, token: Token[str]):
+ super().__init__(token.lineno, token.colno, token.filename)
+ self.value = ''
+ self.append(token)
+
+ def append(self, token: Token[str]) -> None:
+ self.value += token.value
+
+@dataclass(unsafe_hash=True)
class ElementaryNode(T.Generic[TV_TokenTypes], BaseNode):
+
+ value: TV_TokenTypes
+ bytespan: T.Tuple[int, int] = field(hash=False)
+
def __init__(self, token: Token[TV_TokenTypes]):
super().__init__(token.lineno, token.colno, token.filename)
- self.value = token.value # type: TV_TokenTypes
- self.bytespan = token.bytespan # type: T.Tuple[int, int]
+ self.value = token.value
+ self.bytespan = token.bytespan
class BooleanNode(ElementaryNode[bool]):
- def __init__(self, token: Token[bool]):
- super().__init__(token)
- assert isinstance(self.value, bool)
+ pass
class IdNode(ElementaryNode[str]):
- def __init__(self, token: Token[str]):
- super().__init__(token)
- assert isinstance(self.value, str)
-
- def __str__(self) -> str:
- return "Id node: '%s' (%d, %d)." % (self.value, self.lineno, self.colno)
+ pass
+@dataclass(unsafe_hash=True)
class NumberNode(ElementaryNode[int]):
- def __init__(self, token: Token[int]):
- super().__init__(token)
- assert isinstance(self.value, int)
-class StringNode(ElementaryNode[str]):
+ raw_value: str = field(hash=False)
+
def __init__(self, token: Token[str]):
+ BaseNode.__init__(self, token.lineno, token.colno, token.filename)
+ self.raw_value = token.value
+ self.value = int(token.value, base=0)
+ self.bytespan = token.bytespan
+
+class BaseStringNode(ElementaryNode[str]):
+ pass
+
+@dataclass(unsafe_hash=True)
+class StringNode(BaseStringNode):
+
+ raw_value: str = field(hash=False)
+
+ def __init__(self, token: Token[str], escape: bool = True):
super().__init__(token)
- assert isinstance(self.value, str)
+ self.value = ESCAPE_SEQUENCE_SINGLE_RE.sub(decode_match, token.value) if escape else token.value
+ self.raw_value = token.value
+
+class FormatStringNode(StringNode):
+ pass
- def __str__(self) -> str:
- return "String node: '%s' (%d, %d)." % (self.value, self.lineno, self.colno)
+@dataclass(unsafe_hash=True)
+class MultilineStringNode(BaseStringNode):
-class FormatStringNode(ElementaryNode[str]):
def __init__(self, token: Token[str]):
super().__init__(token)
- assert isinstance(self.value, str)
+ self.value = token.value
- def __str__(self) -> str:
- return f"Format string node: '{self.value}' ({self.lineno}, {self.colno})."
-
-class MultilineFormatStringNode(FormatStringNode):
- def __str__(self) -> str:
- return f"Multiline Format string node: '{self.value}' ({self.lineno}, {self.colno})."
+class MultilineFormatStringNode(MultilineStringNode):
+ pass
class ContinueNode(ElementaryNode):
pass
class BreakNode(ElementaryNode):
pass
+class SymbolNode(ElementaryNode[str]):
+ pass
+
+@dataclass(unsafe_hash=True)
class ArgumentNode(BaseNode):
+
+ arguments: T.List[BaseNode] = field(hash=False)
+ commas: T.List[SymbolNode] = field(hash=False)
+ columns: T.List[SymbolNode] = field(hash=False)
+ kwargs: T.Dict[BaseNode, BaseNode] = field(hash=False)
+
def __init__(self, token: Token[TV_TokenTypes]):
super().__init__(token.lineno, token.colno, token.filename)
- self.arguments = [] # type: T.List[BaseNode]
- self.commas = [] # type: T.List[Token[TV_TokenTypes]]
- self.kwargs = {} # type: T.Dict[BaseNode, BaseNode]
+ self.arguments = []
+ self.commas = []
+ self.columns = []
+ self.kwargs = {}
self.order_error = False
def prepend(self, statement: BaseNode) -> None:
def __len__(self) -> int:
return self.num_args() # Fixme
+@dataclass(unsafe_hash=True)
class ArrayNode(BaseNode):
- def __init__(self, args: ArgumentNode, lineno: int, colno: int, end_lineno: int, end_colno: int):
- super().__init__(lineno, colno, args.filename, end_lineno=end_lineno, end_colno=end_colno)
- self.args = args # type: ArgumentNode
+ lbracket: SymbolNode
+ args: ArgumentNode
+ rbracket: SymbolNode
+
+ def __init__(self, lbracket: SymbolNode, args: ArgumentNode, rbracket: SymbolNode):
+ super().__init__(lbracket.lineno, lbracket.colno, args.filename, end_lineno=rbracket.lineno, end_colno=rbracket.colno+1)
+ self.lbracket = lbracket
+ self.args = args
+ self.rbracket = rbracket
+
+@dataclass(unsafe_hash=True)
class DictNode(BaseNode):
- def __init__(self, args: ArgumentNode, lineno: int, colno: int, end_lineno: int, end_colno: int):
- super().__init__(lineno, colno, args.filename, end_lineno=end_lineno, end_colno=end_colno)
+
+ lcurl: SymbolNode
+ args: ArgumentNode
+ rcurl: SymbolNode
+
+ def __init__(self, lcurl: SymbolNode, args: ArgumentNode, rcurl: SymbolNode):
+ super().__init__(lcurl.lineno, lcurl.colno, args.filename, end_lineno=rcurl.lineno, end_colno=rcurl.colno+1)
+ self.lcurl = lcurl
self.args = args
+ self.rcurl = rcurl
class EmptyNode(BaseNode):
- def __init__(self, lineno: int, colno: int, filename: str):
- super().__init__(lineno, colno, filename)
- self.value = None
+ pass
-class OrNode(BaseNode):
- def __init__(self, left: BaseNode, right: BaseNode):
- super().__init__(left.lineno, left.colno, left.filename)
- self.left = left # type: BaseNode
- self.right = right # type: BaseNode
+@dataclass(unsafe_hash=True)
+class BinaryOperatorNode(BaseNode):
-class AndNode(BaseNode):
- def __init__(self, left: BaseNode, right: BaseNode):
- super().__init__(left.lineno, left.colno, left.filename)
- self.left = left # type: BaseNode
- self.right = right # type: BaseNode
+ left: BaseNode
+ operator: SymbolNode
+ right: BaseNode
-class ComparisonNode(BaseNode):
- def __init__(self, ctype: str, left: BaseNode, right: BaseNode):
+ def __init__(self, left: BaseNode, operator: SymbolNode, right: BaseNode):
super().__init__(left.lineno, left.colno, left.filename)
- self.left = left # type: BaseNode
- self.right = right # type: BaseNode
- self.ctype = ctype # type: str
+ self.left = left
+ self.operator = operator
+ self.right = right
-class ArithmeticNode(BaseNode):
- def __init__(self, operation: str, left: BaseNode, right: BaseNode):
- super().__init__(left.lineno, left.colno, left.filename)
- self.left = left # type: BaseNode
- self.right = right # type: BaseNode
- self.operation = operation # type: str
+class OrNode(BinaryOperatorNode):
+ pass
+
+class AndNode(BinaryOperatorNode):
+ pass
+
+@dataclass(unsafe_hash=True)
+class ComparisonNode(BinaryOperatorNode):
+
+ ctype: COMPARISONS
+
+ def __init__(self, ctype: COMPARISONS, left: BaseNode, operator: SymbolNode, right: BaseNode):
+ super().__init__(left, operator, right)
+ self.ctype = ctype
+
+@dataclass(unsafe_hash=True)
+class ArithmeticNode(BinaryOperatorNode):
+
+ # TODO: use a Literal for operation
+ operation: str
-class NotNode(BaseNode):
- def __init__(self, token: Token[TV_TokenTypes], value: BaseNode):
+ def __init__(self, operation: str, left: BaseNode, operator: SymbolNode, right: BaseNode):
+ super().__init__(left, operator, right)
+ self.operation = operation
+
+@dataclass(unsafe_hash=True)
+class UnaryOperatorNode(BaseNode):
+
+ operator: SymbolNode
+ value: BaseNode
+
+ def __init__(self, token: Token[TV_TokenTypes], operator: SymbolNode, value: BaseNode):
super().__init__(token.lineno, token.colno, token.filename)
- self.value = value # type: BaseNode
+ self.operator = operator
+ self.value = value
+
+class NotNode(UnaryOperatorNode):
+ pass
+
+class UMinusNode(UnaryOperatorNode):
+ pass
+@dataclass(unsafe_hash=True)
class CodeBlockNode(BaseNode):
+
+ pre_whitespaces: T.Optional[WhitespaceNode] = field(hash=False)
+ lines: T.List[BaseNode] = field(hash=False)
+
def __init__(self, token: Token[TV_TokenTypes]):
super().__init__(token.lineno, token.colno, token.filename)
- self.lines = [] # type: T.List[BaseNode]
+ self.pre_whitespaces = None
+ self.lines = []
+
+ def append_whitespaces(self, token: Token) -> None:
+ if self.lines:
+ self.lines[-1].append_whitespaces(token)
+ elif self.pre_whitespaces is None:
+ self.pre_whitespaces = WhitespaceNode(token)
+ else:
+ self.pre_whitespaces.append(token)
+@dataclass(unsafe_hash=True)
class IndexNode(BaseNode):
- def __init__(self, iobject: BaseNode, index: BaseNode):
+
+ iobject: BaseNode
+ lbracket: SymbolNode
+ index: BaseNode
+ rbracket: SymbolNode
+
+ def __init__(self, iobject: BaseNode, lbracket: SymbolNode, index: BaseNode, rbracket: SymbolNode):
super().__init__(iobject.lineno, iobject.colno, iobject.filename)
- self.iobject = iobject # type: BaseNode
- self.index = index # type: BaseNode
+ self.iobject = iobject
+ self.lbracket = lbracket
+ self.index = index
+ self.rbracket = rbracket
+@dataclass(unsafe_hash=True)
class MethodNode(BaseNode):
- def __init__(self, filename: str, lineno: int, colno: int, source_object: BaseNode, name: str, args: ArgumentNode):
- super().__init__(lineno, colno, filename)
- self.source_object = source_object # type: BaseNode
- self.name = name # type: str
- assert isinstance(self.name, str)
- self.args = args # type: ArgumentNode
+ source_object: BaseNode
+ dot: SymbolNode
+ name: IdNode
+ lpar: SymbolNode
+ args: ArgumentNode
+ rpar: SymbolNode
+
+ def __init__(self, source_object: BaseNode, dot: SymbolNode, name: IdNode, lpar: SymbolNode, args: ArgumentNode, rpar: SymbolNode):
+ super().__init__(name.lineno, name.colno, name.filename, end_lineno=rpar.lineno, end_colno=rpar.colno+1)
+ self.source_object = source_object
+ self.dot = dot
+ self.name = name
+ self.lpar = lpar
+ self.args = args
+ self.rpar = rpar
+
+@dataclass(unsafe_hash=True)
class FunctionNode(BaseNode):
- def __init__(self, filename: str, lineno: int, colno: int, end_lineno: int, end_colno: int, func_name: str, args: ArgumentNode):
- super().__init__(lineno, colno, filename, end_lineno=end_lineno, end_colno=end_colno)
- self.func_name = func_name # type: str
- assert isinstance(func_name, str)
- self.args = args # type: ArgumentNode
+ func_name: IdNode
+ lpar: SymbolNode
+ args: ArgumentNode
+ rpar: SymbolNode
+
+ def __init__(self, func_name: IdNode, lpar: SymbolNode, args: ArgumentNode, rpar: SymbolNode):
+ super().__init__(func_name.lineno, func_name.colno, func_name.filename, end_lineno=rpar.end_lineno, end_colno=rpar.end_colno+1)
+ self.func_name = func_name
+ self.lpar = lpar
+ self.args = args
+ self.rpar = rpar
+
+@dataclass(unsafe_hash=True)
class AssignmentNode(BaseNode):
- def __init__(self, filename: str, lineno: int, colno: int, var_name: str, value: BaseNode):
- super().__init__(lineno, colno, filename)
- self.var_name = var_name # type: str
- assert isinstance(var_name, str)
- self.value = value # type: BaseNode
-
-class PlusAssignmentNode(BaseNode):
- def __init__(self, filename: str, lineno: int, colno: int, var_name: str, value: BaseNode):
- super().__init__(lineno, colno, filename)
- self.var_name = var_name # type: str
- assert isinstance(var_name, str)
- self.value = value # type: BaseNode
+ var_name: IdNode
+ operator: SymbolNode
+ value: BaseNode
+
+ def __init__(self, var_name: IdNode, operator: SymbolNode, value: BaseNode):
+ super().__init__(var_name.lineno, var_name.colno, var_name.filename)
+ self.var_name = var_name
+ self.operator = operator
+ self.value = value
+
+class PlusAssignmentNode(AssignmentNode):
+ pass
+
+@dataclass(unsafe_hash=True)
class ForeachClauseNode(BaseNode):
- def __init__(self, token: Token, varnames: T.List[str], items: BaseNode, block: CodeBlockNode):
- super().__init__(token.lineno, token.colno, token.filename)
- self.varnames = varnames # type: T.List[str]
- self.items = items # type: BaseNode
- self.block = block # type: CodeBlockNode
+ foreach_: SymbolNode = field(hash=False)
+ varnames: T.List[IdNode] = field(hash=False)
+ commas: T.List[SymbolNode] = field(hash=False)
+ column: SymbolNode = field(hash=False)
+ items: BaseNode
+ block: CodeBlockNode
+ endforeach: SymbolNode = field(hash=False)
+
+ def __init__(self, foreach_: SymbolNode, varnames: T.List[IdNode], commas: T.List[SymbolNode], column: SymbolNode, items: BaseNode, block: CodeBlockNode, endforeach: SymbolNode):
+ super().__init__(foreach_.lineno, foreach_.colno, foreach_.filename)
+ self.foreach_ = foreach_
+ self.varnames = varnames
+ self.commas = commas
+ self.column = column
+ self.items = items
+ self.block = block
+ self.endforeach = endforeach
+
+
+@dataclass(unsafe_hash=True)
class IfNode(BaseNode):
- def __init__(self, linenode: BaseNode, condition: BaseNode, block: CodeBlockNode):
+
+ if_: SymbolNode
+ condition: BaseNode
+ block: CodeBlockNode
+
+ def __init__(self, linenode: BaseNode, if_node: SymbolNode, condition: BaseNode, block: CodeBlockNode):
super().__init__(linenode.lineno, linenode.colno, linenode.filename)
- self.condition = condition # type: BaseNode
- self.block = block # type: CodeBlockNode
+ self.if_ = if_node
+ self.condition = condition
+ self.block = block
+@dataclass(unsafe_hash=True)
+class ElseNode(BaseNode):
+
+ else_: SymbolNode
+ block: CodeBlockNode
+
+ def __init__(self, else_: SymbolNode, block: CodeBlockNode):
+ super().__init__(block.lineno, block.colno, block.filename)
+ self.else_ = else_
+ self.block = block
+
+@dataclass(unsafe_hash=True)
class IfClauseNode(BaseNode):
+
+ ifs: T.List[IfNode] = field(hash=False)
+ elseblock: T.Union[EmptyNode, ElseNode]
+ endif: SymbolNode
+
def __init__(self, linenode: BaseNode):
super().__init__(linenode.lineno, linenode.colno, linenode.filename)
- self.ifs = [] # type: T.List[IfNode]
- self.elseblock = None # type: T.Union[EmptyNode, CodeBlockNode]
+ self.ifs = []
+ self.elseblock = EmptyNode(linenode.lineno, linenode.colno, linenode.filename)
+ self.endif = None
+
+@dataclass(unsafe_hash=True)
+class TestCaseClauseNode(BaseNode):
+
+ testcase: SymbolNode
+ condition: BaseNode
+ block: CodeBlockNode
+ endtestcase: SymbolNode
-class UMinusNode(BaseNode):
- def __init__(self, current_location: Token, value: BaseNode):
- super().__init__(current_location.lineno, current_location.colno, current_location.filename)
- self.value = value # type: BaseNode
+ def __init__(self, testcase: SymbolNode, condition: BaseNode, block: CodeBlockNode, endtestcase: SymbolNode):
+ super().__init__(condition.lineno, condition.colno, condition.filename)
+ self.testcase = testcase
+ self.condition = condition
+ self.block = block
+ self.endtestcase = endtestcase
+@dataclass(unsafe_hash=True)
class TernaryNode(BaseNode):
- def __init__(self, condition: BaseNode, trueblock: BaseNode, falseblock: BaseNode):
+
+ condition: BaseNode
+ questionmark: SymbolNode
+ trueblock: BaseNode
+ column: SymbolNode
+ falseblock: BaseNode
+
+ def __init__(self, condition: BaseNode, questionmark: SymbolNode, trueblock: BaseNode, column: SymbolNode, falseblock: BaseNode):
super().__init__(condition.lineno, condition.colno, condition.filename)
- self.condition = condition # type: BaseNode
- self.trueblock = trueblock # type: BaseNode
- self.falseblock = falseblock # type: BaseNode
-
-comparison_map = {'equal': '==',
- 'nequal': '!=',
- 'lt': '<',
- 'le': '<=',
- 'gt': '>',
- 'ge': '>=',
- 'in': 'in',
- 'notin': 'not in',
- }
+ self.condition = condition
+ self.questionmark = questionmark
+ self.trueblock = trueblock
+ self.column = column
+ self.falseblock = falseblock
+
+
+@dataclass(unsafe_hash=True)
+class ParenthesizedNode(BaseNode):
+
+ lpar: SymbolNode = field(hash=False)
+ inner: BaseNode
+ rpar: SymbolNode = field(hash=False)
+
+ 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
+
+
+if T.TYPE_CHECKING:
+ COMPARISONS = Literal['==', '!=', '<', '<=', '>=', '>', 'in', 'notin']
+
+comparison_map: T.Mapping[str, COMPARISONS] = {
+ 'equal': '==',
+ 'nequal': '!=',
+ 'lt': '<',
+ 'le': '<=',
+ 'gt': '>',
+ 'ge': '>=',
+ 'in': 'in',
+ 'not in': 'notin',
+}
# Recursive descent parser for Meson's definition language.
# Very basic apart from the fact that we have many precedence
def __init__(self, code: str, filename: str):
self.lexer = Lexer(code)
self.stream = self.lexer.lex(filename)
- self.current = Token('eof', '', 0, 0, 0, (0, 0), None) # type: Token
+ self.current: Token = Token('eof', '', 0, 0, 0, (0, 0), None)
+ self.previous = self.current
+ self.current_ws: T.List[Token] = []
+
self.getsym()
self.in_ternary = False
+ def create_node(self, node_type: T.Type[BaseNodeT], *args: T.Any, **kwargs: T.Any) -> BaseNodeT:
+ node = node_type(*args, **kwargs)
+ for ws_token in self.current_ws:
+ node.append_whitespaces(ws_token)
+ self.current_ws = []
+ return node
+
def getsym(self) -> None:
+ self.previous = self.current
try:
self.current = next(self.stream)
+
+ while self.current.tid in {'eol', 'comment', 'whitespace'}:
+ self.current_ws.append(self.current)
+ if self.current.tid == 'eol':
+ break
+ self.current = next(self.stream)
+
except StopIteration:
self.current = Token('eof', '', self.current.line_start, self.current.lineno, self.current.colno + self.current.bytespan[1] - self.current.bytespan[0], (0, 0), None)
def parse(self) -> CodeBlockNode:
block = self.codeblock()
- self.expect('eof')
+ try:
+ self.expect('eof')
+ except ParseException as e:
+ e.ast = block
+ raise
return block
def statement(self) -> BaseNode:
def e1(self) -> BaseNode:
left = self.e2()
if self.accept('plusassign'):
+ operator = self.create_node(SymbolNode, self.previous)
value = self.e1()
if not isinstance(left, IdNode):
raise ParseException('Plusassignment target must be an id.', self.getline(), left.lineno, left.colno)
assert isinstance(left.value, str)
- return PlusAssignmentNode(left.filename, left.lineno, left.colno, left.value, value)
+ return self.create_node(PlusAssignmentNode, left, operator, value)
elif self.accept('assign'):
+ operator = self.create_node(SymbolNode, self.previous)
value = self.e1()
if not isinstance(left, IdNode):
raise ParseException('Assignment target must be an id.',
self.getline(), left.lineno, left.colno)
assert isinstance(left.value, str)
- return AssignmentNode(left.filename, left.lineno, left.colno, left.value, value)
+ return self.create_node(AssignmentNode, left, operator, value)
elif self.accept('questionmark'):
if self.in_ternary:
raise ParseException('Nested ternary operators are not allowed.',
self.getline(), left.lineno, left.colno)
+
+ qm_node = self.create_node(SymbolNode, self.previous)
self.in_ternary = True
trueblock = self.e1()
self.expect('colon')
+ column_node = self.create_node(SymbolNode, self.previous)
falseblock = self.e1()
self.in_ternary = False
- return TernaryNode(left, trueblock, falseblock)
+ return self.create_node(TernaryNode, left, qm_node, trueblock, column_node, falseblock)
return left
def e2(self) -> BaseNode:
left = self.e3()
while self.accept('or'):
+ operator = self.create_node(SymbolNode, self.previous)
if isinstance(left, EmptyNode):
raise ParseException('Invalid or clause.',
self.getline(), left.lineno, left.colno)
- left = OrNode(left, self.e3())
+ left = self.create_node(OrNode, left, operator, self.e3())
return left
def e3(self) -> BaseNode:
left = self.e4()
while self.accept('and'):
+ operator = self.create_node(SymbolNode, self.previous)
if isinstance(left, EmptyNode):
raise ParseException('Invalid and clause.',
self.getline(), left.lineno, left.colno)
- left = AndNode(left, self.e4())
+ left = self.create_node(AndNode, left, operator, self.e4())
return left
def e4(self) -> BaseNode:
left = self.e5()
for nodename, operator_type in comparison_map.items():
if self.accept(nodename):
- return ComparisonNode(operator_type, left, self.e5())
- if self.accept('not') and self.accept('in'):
- return ComparisonNode('notin', left, self.e5())
+ operator = self.create_node(SymbolNode, self.previous)
+ return self.create_node(ComparisonNode, operator_type, left, operator, self.e5())
+ if self.accept('not'):
+ ws = self.current_ws.copy()
+ not_token = self.previous
+ if self.accept('in'):
+ in_token = self.previous
+ self.current_ws = self.current_ws[len(ws):] # remove whitespaces between not and in
+ temp_node = EmptyNode(in_token.lineno, in_token.colno, in_token.filename)
+ for w in ws:
+ temp_node.append_whitespaces(w)
+
+ 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 left
def e5(self) -> BaseNode:
while True:
op = self.accept_any(tuple(op_map.keys()))
if op:
- left = ArithmeticNode(op_map[op], left, self.e5muldiv())
+ operator = self.create_node(SymbolNode, self.previous)
+ left = self.create_node(ArithmeticNode, op_map[op], left, operator, self.e5muldiv())
else:
break
return left
while True:
op = self.accept_any(tuple(op_map.keys()))
if op:
- left = ArithmeticNode(op_map[op], left, self.e6())
+ operator = self.create_node(SymbolNode, self.previous)
+ left = self.create_node(ArithmeticNode, op_map[op], left, operator, self.e6())
else:
break
return left
def e6(self) -> BaseNode:
if self.accept('not'):
- return NotNode(self.current, self.e7())
+ operator = self.create_node(SymbolNode, self.previous)
+ return self.create_node(NotNode, self.current, operator, self.e7())
if self.accept('dash'):
- return UMinusNode(self.current, self.e7())
+ operator = self.create_node(SymbolNode, self.previous)
+ return self.create_node(UMinusNode, self.current, operator, self.e7())
return self.e7()
def e7(self) -> BaseNode:
left = self.e8()
block_start = self.current
if self.accept('lparen'):
+ lpar = self.create_node(SymbolNode, block_start)
args = self.args()
self.block_expect('rparen', block_start)
+ rpar = self.create_node(SymbolNode, self.previous)
if not isinstance(left, IdNode):
raise ParseException('Function call must be applied to plain id',
self.getline(), left.lineno, left.colno)
assert isinstance(left.value, str)
- left = FunctionNode(left.filename, left.lineno, left.colno, self.current.lineno, self.current.colno, left.value, args)
+ left = self.create_node(FunctionNode, left, lpar, args, rpar)
go_again = True
while go_again:
go_again = False
def e8(self) -> BaseNode:
block_start = self.current
if self.accept('lparen'):
+ lpar = self.create_node(SymbolNode, block_start)
e = self.statement()
self.block_expect('rparen', block_start)
- return e
+ rpar = self.create_node(SymbolNode, self.previous)
+ return ParenthesizedNode(lpar, e, rpar)
elif self.accept('lbracket'):
+ lbracket = self.create_node(SymbolNode, block_start)
args = self.args()
self.block_expect('rbracket', block_start)
- return ArrayNode(args, block_start.lineno, block_start.colno, self.current.lineno, self.current.colno)
+ rbracket = self.create_node(SymbolNode, self.previous)
+ return self.create_node(ArrayNode, lbracket, args, rbracket)
elif self.accept('lcurl'):
+ lcurl = self.create_node(SymbolNode, block_start)
key_values = self.key_values()
self.block_expect('rcurl', block_start)
- return DictNode(key_values, block_start.lineno, block_start.colno, self.current.lineno, self.current.colno)
+ rcurl = self.create_node(SymbolNode, self.previous)
+ return self.create_node(DictNode, lcurl, key_values, rcurl)
else:
return self.e9()
t = self.current
if self.accept('true'):
t.value = True
- return BooleanNode(t)
+ return self.create_node(BooleanNode, t)
if self.accept('false'):
t.value = False
- return BooleanNode(t)
+ return self.create_node(BooleanNode, t)
if self.accept('id'):
- return IdNode(t)
+ return self.create_node(IdNode, t)
if self.accept('number'):
- return NumberNode(t)
+ return self.create_node(NumberNode, t)
if self.accept('string'):
- return StringNode(t)
+ return self.create_node(StringNode, t)
if self.accept('fstring'):
- return FormatStringNode(t)
+ return self.create_node(FormatStringNode, t)
+ if self.accept('multiline_string'):
+ return self.create_node(MultilineStringNode, t)
if self.accept('multiline_fstring'):
- return MultilineFormatStringNode(t)
+ return self.create_node(MultilineFormatStringNode, t)
return EmptyNode(self.current.lineno, self.current.colno, self.current.filename)
def key_values(self) -> ArgumentNode:
- s = self.statement() # type: BaseNode
- a = ArgumentNode(self.current)
+ s = self.statement()
+ a = self.create_node(ArgumentNode, self.current)
while not isinstance(s, EmptyNode):
if self.accept('colon'):
+ a.columns.append(self.create_node(SymbolNode, self.previous))
a.set_kwarg_no_check(s, self.statement())
- potential = self.current
if not self.accept('comma'):
return a
- a.commas.append(potential)
+ a.commas.append(self.create_node(SymbolNode, self.previous))
else:
raise ParseException('Only key:value pairs are valid in dict construction.',
self.getline(), s.lineno, s.colno)
return a
def args(self) -> ArgumentNode:
- s = self.statement() # type: BaseNode
- a = ArgumentNode(self.current)
+ s = self.statement()
+ a = self.create_node(ArgumentNode, self.current)
while not isinstance(s, EmptyNode):
- potential = self.current
if self.accept('comma'):
- a.commas.append(potential)
+ a.commas.append(self.create_node(SymbolNode, self.previous))
a.append(s)
elif self.accept('colon'):
+ a.columns.append(self.create_node(SymbolNode, self.previous))
if not isinstance(s, IdNode):
raise ParseException('Dictionary key must be a plain identifier.',
self.getline(), s.lineno, s.colno)
a.set_kwarg(s, self.statement())
- potential = self.current
if not self.accept('comma'):
return a
- a.commas.append(potential)
+ a.commas.append(self.create_node(SymbolNode, self.previous))
else:
a.append(s)
return a
return a
def method_call(self, source_object: BaseNode) -> MethodNode:
+ dot = self.create_node(SymbolNode, self.previous)
methodname = self.e9()
if not isinstance(methodname, IdNode):
+ if isinstance(source_object, NumberNode) and isinstance(methodname, NumberNode):
+ raise ParseException('meson does not support float numbers',
+ self.getline(), source_object.lineno, source_object.colno)
raise ParseException('Method name must be plain id',
self.getline(), self.current.lineno, self.current.colno)
assert isinstance(methodname.value, str)
self.expect('lparen')
+ lpar = self.create_node(SymbolNode, self.previous)
args = self.args()
+ rpar = self.create_node(SymbolNode, self.current)
self.expect('rparen')
- method = MethodNode(methodname.filename, methodname.lineno, methodname.colno, source_object, methodname.value, args)
+ method = self.create_node(MethodNode, source_object, dot, methodname, lpar, args, rpar)
if self.accept('dot'):
return self.method_call(method)
return method
def index_call(self, source_object: BaseNode) -> IndexNode:
+ lbracket = self.create_node(SymbolNode, self.previous)
index_statement = self.statement()
self.expect('rbracket')
- return IndexNode(source_object, index_statement)
+ rbracket = self.create_node(SymbolNode, self.previous)
+ return self.create_node(IndexNode, source_object, lbracket, index_statement, rbracket)
def foreachblock(self) -> ForeachClauseNode:
- t = self.current
+ foreach_ = self.create_node(SymbolNode, self.previous)
self.expect('id')
- assert isinstance(t.value, str)
- varname = t
- varnames = [t.value] # type: T.List[str]
+ assert isinstance(self.previous.value, str)
+ varnames = [self.create_node(IdNode, self.previous)]
+ commas = []
if self.accept('comma'):
- t = self.current
+ commas.append(self.create_node(SymbolNode, self.previous))
self.expect('id')
- assert isinstance(t.value, str)
- varnames.append(t.value)
+ assert isinstance(self.previous.value, str)
+ varnames.append(self.create_node(IdNode, self.previous))
self.expect('colon')
+ column = self.create_node(SymbolNode, self.previous)
items = self.statement()
block = self.codeblock()
- return ForeachClauseNode(varname, varnames, items, block)
+ endforeach = self.create_node(SymbolNode, self.current)
+ return self.create_node(ForeachClauseNode, foreach_, varnames, commas, column, items, block, endforeach)
def ifblock(self) -> IfClauseNode:
+ if_node = self.create_node(SymbolNode, self.previous)
condition = self.statement()
- clause = IfClauseNode(condition)
+ clause = self.create_node(IfClauseNode, condition)
self.expect('eol')
block = self.codeblock()
- clause.ifs.append(IfNode(clause, condition, block))
+ clause.ifs.append(self.create_node(IfNode, clause, if_node, condition, block))
self.elseifblock(clause)
clause.elseblock = self.elseblock()
+ clause.endif = self.create_node(SymbolNode, self.current)
return clause
def elseifblock(self, clause: IfClauseNode) -> None:
while self.accept('elif'):
+ elif_ = self.create_node(SymbolNode, self.previous)
s = self.statement()
self.expect('eol')
b = self.codeblock()
- clause.ifs.append(IfNode(s, s, b))
+ clause.ifs.append(self.create_node(IfNode, s, elif_, s, b))
- def elseblock(self) -> T.Union[CodeBlockNode, EmptyNode]:
+ def elseblock(self) -> T.Union[ElseNode, EmptyNode]:
if self.accept('else'):
+ else_ = self.create_node(SymbolNode, self.previous)
self.expect('eol')
- return self.codeblock()
+ block = self.codeblock()
+ return ElseNode(else_, block)
return EmptyNode(self.current.lineno, self.current.colno, self.current.filename)
+ def testcaseblock(self) -> TestCaseClauseNode:
+ testcase = self.create_node(SymbolNode, self.previous)
+ condition = self.statement()
+ self.expect('eol')
+ block = self.codeblock()
+ endtestcase = SymbolNode(self.current)
+ return self.create_node(TestCaseClauseNode, testcase, condition, block, endtestcase)
+
def line(self) -> BaseNode:
block_start = self.current
if self.current == 'eol':
self.block_expect('endforeach', block_start)
return forblock
if self.accept('continue'):
- return ContinueNode(self.current)
+ return self.create_node(ContinueNode, self.current)
if self.accept('break'):
- return BreakNode(self.current)
+ return self.create_node(BreakNode, self.current)
+ if self.lexer.in_unit_test and self.accept('testcase'):
+ block = self.testcaseblock()
+ self.block_expect('endtestcase', block_start)
+ return block
return self.statement()
def codeblock(self) -> CodeBlockNode:
- block = CodeBlockNode(self.current)
+ block = self.create_node(CodeBlockNode, self.current)
cond = True
- while cond:
- curline = self.line()
- if not isinstance(curline, EmptyNode):
- block.lines.append(curline)
- cond = self.accept('eol')
+
+ try:
+ while cond:
+ for ws_token in self.current_ws:
+ block.append_whitespaces(ws_token)
+ self.current_ws = []
+
+ curline = self.line()
+
+ if not isinstance(curline, EmptyNode):
+ block.lines.append(curline)
+
+ cond = self.accept('eol')
+
+ except ParseException as e:
+ e.ast = block
+ raise
+
+ # Remaining whitespaces will not be catched since there are no more nodes
+ for ws_token in self.current_ws:
+ block.append_whitespaces(ws_token)
+ self.current_ws = []
+
return block
# 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 typing as T
-import time
-import sys, stat
-import datetime
-import os.path
-import platform
+import argparse, datetime, glob, json, os, platform, shutil, sys, tempfile, time
import cProfile as profile
-import argparse
-import tempfile
-import shutil
-import glob
-
-from . import environment, interpreter, mesonlib
-from . import build
-from . import mlog, coredata
-from . import mintro
-from .mesonlib import MesonException, MachineChoice
-from .dependencies import PkgConfigDependency
+from pathlib import Path
+import typing as T
+
+from . import build, coredata, environment, interpreter, mesonlib, mintro, mlog
+from .mesonlib import MesonException
git_ignore_file = '''# This file is autogenerated by Meson. If you change or delete it, it won't be recreated.
*
'''
+# 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)
parser.add_argument('--native-file',
default=[],
action='append',
help='File describing cross compilation environment.')
- parser.add_argument('--vsenv', action='store_true',
- help='Setup Visual Studio environment even when other compilers are found, ' +
- 'abort if Visual Studio is not found. This option has no effect on other ' +
- 'platforms than Windows. Defaults to True when using "vs" backend.')
parser.add_argument('-v', '--version', action='version',
version=coredata.version)
parser.add_argument('--profile-self', action='store_true', dest='profile',
help='Wipe build directory and reconfigure using previous command line options. ' +
'Useful when build directory got corrupted, or when rebuilding with a ' +
'newer version of meson.')
+ parser.add_argument('--clearcache', action='store_true', default=False,
+ help='Clear cached state (e.g. found dependencies). Since 1.3.0.')
parser.add_argument('builddir', nargs='?', default=None)
parser.add_argument('sourcedir', nargs='?', default=None)
class MesonApp:
def __init__(self, options: argparse.Namespace) -> None:
- (self.source_dir, self.build_dir) = self.validate_dirs(options.builddir,
- options.sourcedir,
- options.reconfigure,
- options.wipe)
+ self.options = options
+ (self.source_dir, self.build_dir) = self.validate_dirs()
if options.wipe:
# Make a copy of the cmd line file to make sure we can always
# restore that file if anything bad happens. For example if
try:
restore.append((shutil.copy(filename, d), filename))
except FileNotFoundError:
- raise MesonException(
- 'Cannot find cmd_line.txt. This is probably because this '
- 'build directory was configured with a meson version < 0.49.0.')
+ # validate_dirs() already verified that build_dir has
+ # a partial build or is empty.
+ pass
coredata.read_cmd_line_file(self.build_dir, options)
os.makedirs(os.path.dirname(f), exist_ok=True)
shutil.move(b, f)
- self.options = options
-
def has_build_file(self, dirname: str) -> bool:
fname = os.path.join(dirname, environment.build_filename)
return os.path.exists(fname)
- def validate_core_dirs(self, dir1: str, dir2: str) -> T.Tuple[str, str]:
+ def validate_core_dirs(self, dir1: T.Optional[str], dir2: T.Optional[str]) -> T.Tuple[str, str]:
+ invalid_msg_prefix = f'Neither source directory {dir1!r} nor build directory {dir2!r}'
if dir1 is None:
if dir2 is None:
- if not os.path.exists('meson.build') and os.path.exists('../meson.build'):
+ if not self.has_build_file('.') and self.has_build_file('..'):
dir2 = '..'
else:
raise MesonException('Must specify at least one directory name.')
dir2 = os.getcwd()
ndir1 = os.path.abspath(os.path.realpath(dir1))
ndir2 = os.path.abspath(os.path.realpath(dir2))
- if not os.path.exists(ndir1):
- os.makedirs(ndir1)
- if not os.path.exists(ndir2):
- os.makedirs(ndir2)
- if not stat.S_ISDIR(os.stat(ndir1).st_mode):
- raise MesonException(f'{dir1} is not a directory')
- if not stat.S_ISDIR(os.stat(ndir2).st_mode):
- raise MesonException(f'{dir2} is not a directory')
+ if not os.path.exists(ndir1) and not os.path.exists(ndir2):
+ raise MesonException(f'{invalid_msg_prefix} exist.')
+ try:
+ os.makedirs(ndir1, exist_ok=True)
+ except FileExistsError as e:
+ raise MesonException(f'{dir1} is not a directory') from e
+ try:
+ os.makedirs(ndir2, exist_ok=True)
+ except FileExistsError as e:
+ raise MesonException(f'{dir2} is not a directory') from e
if os.path.samefile(ndir1, ndir2):
# Fallback to textual compare if undefined entries found
has_undefined = any((s.st_ino == 0 and s.st_dev == 0) for s in (os.stat(ndir1), os.stat(ndir2)))
return ndir1, ndir2
if self.has_build_file(ndir2):
return ndir2, ndir1
- raise MesonException(f'Neither directory contains a build file {environment.build_filename}.')
+ raise MesonException(f'{invalid_msg_prefix} contain a build file {environment.build_filename}.')
def add_vcs_ignore_files(self, build_dir: str) -> None:
- if os.listdir(build_dir):
- return
with open(os.path.join(build_dir, '.gitignore'), 'w', encoding='utf-8') as ofile:
ofile.write(git_ignore_file)
with open(os.path.join(build_dir, '.hgignore'), 'w', encoding='utf-8') as ofile:
ofile.write(hg_ignore_file)
- def validate_dirs(self, dir1: str, dir2: str, reconfigure: bool, wipe: bool) -> T.Tuple[str, str]:
- (src_dir, build_dir) = self.validate_core_dirs(dir1, dir2)
- self.add_vcs_ignore_files(build_dir)
- priv_dir = os.path.join(build_dir, 'meson-private/coredata.dat')
- if os.path.exists(priv_dir):
- if not reconfigure and not wipe:
- print('Directory already configured.\n'
- '\nJust run your build command (e.g. ninja) and Meson will regenerate as necessary.\n'
- 'If ninja fails, run "ninja reconfigure" or "meson setup --reconfigure"\n'
- 'to force Meson to regenerate.\n'
- '\nIf build failures persist, run "meson setup --wipe" to rebuild from scratch\n'
- 'using the same options as passed when configuring the build.'
- '\nTo change option values, run "meson configure" instead.')
- raise SystemExit
- else:
- has_cmd_line_file = os.path.exists(coredata.get_cmd_line_file(build_dir))
- if (wipe and not has_cmd_line_file) or (not wipe and reconfigure):
- raise SystemExit(f'Directory does not contain a valid build tree:\n{build_dir}')
+ def validate_dirs(self) -> T.Tuple[str, str]:
+ (src_dir, build_dir) = self.validate_core_dirs(self.options.builddir, self.options.sourcedir)
+ if Path(build_dir) in Path(src_dir).parents:
+ raise MesonException(f'Build directory {build_dir} cannot be a parent of source directory {src_dir}')
+ if not os.listdir(build_dir):
+ self.add_vcs_ignore_files(build_dir)
+ return src_dir, build_dir
+ priv_dir = os.path.join(build_dir, 'meson-private')
+ has_valid_build = os.path.exists(os.path.join(priv_dir, 'coredata.dat'))
+ has_partial_build = os.path.isdir(priv_dir)
+ if has_valid_build:
+ if not self.options.reconfigure and not self.options.wipe:
+ print('Directory already configured.\n\n'
+ 'Just run your build command (e.g. ninja) and Meson will regenerate as necessary.\n'
+ 'Run "meson setup --reconfigure to force Meson to regenerate.\n\n'
+ 'If build failures persist, run "meson setup --wipe" to rebuild from scratch\n'
+ 'using the same options as passed when configuring the build.')
+ if self.options.cmd_line_options:
+ from . import mconf
+ raise SystemExit(mconf.run_impl(self.options, build_dir))
+ raise SystemExit(0)
+ elif not has_partial_build and self.options.wipe:
+ raise MesonException(f'Directory is not empty and does not contain a previous build:\n{build_dir}')
return src_dir, build_dir
- def generate(self) -> None:
+ # 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)
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):
- self._generate(env)
+ return self._generate(env, capture, vslite_ctx)
- def _generate(self, env: environment.Environment) -> None:
+ 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 = argparse.Namespace(**vars(self.options))
b = build.Build(env)
intr = interpreter.Interpreter(b, user_defined_options=user_defined_options)
- if env.is_cross_build():
- logger_fun = mlog.log
- else:
- logger_fun = mlog.debug
+ # Super hack because mlog.log and mlog.debug have different signatures,
+ # and there is currently no way to annotate them correctly, unionize them, or
+ # even to write `T.Callable[[*mlog.TV_Loggable], None]`
+ logger_fun = T.cast('T.Callable[[mlog.TV_Loggable, mlog.TV_Loggable], None]',
+ (mlog.log if env.is_cross_build() else mlog.debug))
build_machine = intr.builtin['build_machine']
host_machine = intr.builtin['host_machine']
target_machine = intr.builtin['target_machine']
logger_fun('Target machine cpu:', mlog.bold(target_machine.cpu_method([], {})))
try:
if self.options.profile:
- fname = os.path.join(self.build_dir, 'meson-private', 'profile-interpreter.log')
+ fname = os.path.join(self.build_dir, 'meson-logs', 'profile-interpreter.log')
profile.runctx('intr.run()', globals(), locals(), filename=fname)
else:
intr.run()
raise
cdf: T.Optional[str] = None
+ captured_compile_args: T.Optional[dict] = None
try:
dumpfile = os.path.join(env.get_scratch_dir(), 'build.dat')
# We would like to write coredata as late as possible since we use the existence of
# sync with the time that gets applied to any files. Thus, we dump this file as late as
# possible, but before build files, and if any error occurs, delete it.
cdf = env.dump_coredata()
+
+ self.finalize_postconf_hooks(b, intr)
if self.options.profile:
fname = f'profile-{intr.backend.name}-backend.log'
- fname = os.path.join(self.build_dir, 'meson-private', fname)
- profile.runctx('intr.backend.generate()', globals(), locals(), filename=fname)
+ fname = os.path.join(self.build_dir, 'meson-logs', fname)
+ profile.runctx('gen_result = intr.backend.generate(capture, vslite_ctx)', globals(), locals(), filename=fname)
+ captured_compile_args = locals()['gen_result']
+ assert captured_compile_args is None or isinstance(captured_compile_args, dict)
else:
- intr.backend.generate()
- self._finalize_devenv(b, intr)
+ captured_compile_args = intr.backend.generate(capture, vslite_ctx)
+
build.save(b, dumpfile)
if env.first_invocation:
# Use path resolved by coredata because they could have been
# Generate an IDE introspection file with the same syntax as the already existing API
if self.options.profile:
- fname = os.path.join(self.build_dir, 'meson-private', 'profile-introspector.log')
+ fname = os.path.join(self.build_dir, 'meson-logs', 'profile-introspector.log')
profile.runctx('mintro.generate_introspection_file(b, intr.backend)', globals(), locals(), filename=fname)
else:
mintro.generate_introspection_file(b, intr.backend)
'Please consider using `meson devenv` instead. See https://github.com/mesonbuild/meson/pull/9243 '
'for details.')
+ if self.options.profile:
+ fname = os.path.join(self.build_dir, 'meson-logs', 'profile-startup-modules.json')
+ mods = set(sys.modules.keys())
+ mesonmods = {mod for mod in mods if (mod+'.').startswith('mesonbuild.')}
+ stdmods = sorted(mods - mesonmods)
+ data = {'stdlib': {'modules': stdmods, 'count': len(stdmods)}, 'meson': {'modules': sorted(mesonmods), 'count': len(mesonmods)}}
+ with open(fname, 'w', encoding='utf-8') as f:
+ json.dump(data, f)
+
+ mlog.log("meson setup completed") # Display timestamp
+
except Exception as e:
mintro.write_meson_info_file(b, [e])
if cdf is not None:
os.unlink(cdf)
raise
- def _finalize_devenv(self, b: build.Build, intr: interpreter.Interpreter) -> None:
+ return captured_compile_args
+
+ def finalize_postconf_hooks(self, b: build.Build, intr: interpreter.Interpreter) -> None:
b.devenv.append(intr.backend.get_devenv())
- b.devenv.append(PkgConfigDependency.get_env(intr.environment, MachineChoice.HOST, uninstalled=True))
for mod in intr.modules.values():
- devenv = mod.get_devenv()
- if devenv:
- b.devenv.append(devenv)
+ mod.postconf_hook(b)
+
+def run_genvslite_setup(options: argparse.Namespace) -> None:
+ # With --genvslite, we essentially want to invoke multiple 'setup' iterations. I.e. -
+ # meson setup ... builddirprefix_debug
+ # meson setup ... builddirprefix_debugoptimized
+ # meson setup ... builddirprefix_release
+ # along with also setting up a new, thin/lite visual studio solution and projects with the multiple debug/opt/release configurations that
+ # 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(mesonlib.OptionKey('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 = mesonlib.OptionKey('backend')
+ if k_backend in options.cmd_line_options.keys():
+ 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'
+ buildtypes_list = coredata.get_genvs_default_buildtype_list()
+ vslite_ctx = {}
+
+ for buildtypestr in buildtypes_list:
+ options.builddir = f'{builddir_prefix}_{buildtypestr}' # E.g. builddir_release
+ options.cmd_line_options[mesonlib.OptionKey('buildtype')] = buildtypestr
+ app = MesonApp(options)
+ 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[mesonlib.OptionKey('genvslite')] = genvsliteval
+ app = MesonApp(options)
+ app.generate(capture=False, vslite_ctx=vslite_ctx)
def run(options: T.Union[argparse.Namespace, T.List[str]]) -> int:
if not isinstance(options, argparse.Namespace):
add_arguments(parser)
options = parser.parse_args(options)
coredata.parse_cmd_line_options(options)
- app = MesonApp(options)
- app.generate()
+
+ if mesonlib.OptionKey('genvslite') in options.cmd_line_options.keys():
+ run_genvslite_setup(options)
+ else:
+ app = MesonApp(options)
+ app.generate()
+
return 0
import zipfile
from . import mlog
+from .ast import IntrospectionInterpreter
from .mesonlib import quiet_git, GitException, Popen_safe, MesonException, windows_proof_rmtree
-from .wrap.wrap import (Resolver, WrapException, ALL_TYPES, PackageDefinition,
+from .wrap.wrap import (Resolver, WrapException, ALL_TYPES,
parse_patch_url, update_wrap_file, get_releases)
if T.TYPE_CHECKING:
from typing_extensions import Protocol
+ from .wrap.wrap import PackageDefinition
+
SubParsers = argparse._SubParsersAction[argparse.ArgumentParser]
class Arguments(Protocol):
try:
wrapdb_version = self.wrap.get('wrapdb_version')
branch, revision = wrapdb_version.split('-', 1)
+ except ValueError:
+ if not options.force:
+ self.log(' ->', mlog.red('Malformed wrapdb_version field, use --force to update anyway'))
+ return False
+ branch = revision = None
except WrapException:
# Fallback to parsing the patch URL to determine current version.
# This won't work for projects that have upstream Meson support.
branch, revision = parse_patch_url(patch_url)
except WrapException:
if not options.force:
- self.log(' ->', mlog.red('Could not determine current version, use --force to update any way'))
+ self.log(' ->', mlog.red('Could not determine current version, use --force to update anyway'))
return False
branch = revision = None
# cached.
windows_proof_rmtree(self.repo_dir)
try:
- self.wrap_resolver.resolve(self.wrap.name, 'meson')
+ self.wrap_resolver.resolve(self.wrap.name)
self.log(' -> New version extracted')
return True
except WrapException as e:
self.log(self.git_output(cmd))
def git_stash(self) -> None:
- # That git command return 1 (failure) when there is something to stash.
+ # That git command return some output when there is something to stash.
# We don't want to stash when there is nothing to stash because that would
# print spurious "No local changes to save".
- if not quiet_git(['diff', '--quiet', 'HEAD'], self.repo_dir)[0]:
+ if quiet_git(['status', '--porcelain', ':!/.meson-subproject-wrap-hash.txt'], self.repo_dir)[1].strip():
# Don't pipe stdout here because we want the user to see their changes have
# been saved.
- self.git_verbose(['stash'])
+ # Note: `--all` is used, and not `--include-untracked`, to prevent
+ # a potential error if `.meson-subproject-wrap-hash.txt` matches a
+ # gitignore pattern.
+ # We must add the dot in addition to the negation, because older versions of git have a bug.
+ self.git_verbose(['stash', 'push', '--all', ':!/.meson-subproject-wrap-hash.txt', '.'])
def git_show(self) -> None:
commit_message = self.git_output(['show', '--quiet', '--pretty=format:%h%n%d%n%s%n[%an]'])
try:
self.git_output(['-c', 'rebase.autoStash=true', 'rebase', 'FETCH_HEAD'])
except GitException as e:
- self.log(' -> Could not rebase', mlog.bold(self.repo_dir), 'onto', mlog.bold(revision))
+ self.git_output(['-c', 'rebase.autoStash=true', 'rebase', '--abort'])
+ self.log(' -> Could not rebase', mlog.bold(self.repo_dir), 'onto', mlog.bold(revision),
+ '-- aborted')
self.log(mlog.red(e.output))
self.log(mlog.red(str(e)))
return False
return True
def git_checkout(self, revision: str, create: bool = False) -> bool:
- cmd = ['checkout', '--ignore-other-worktrees', revision, '--']
+ cmd = ['checkout', '--ignore-other-worktrees']
if create:
- cmd.insert(1, '-b')
+ cmd.append('-b')
+ cmd += [revision, '--']
try:
# Stash local changes, commits can always be found back in reflog, to
# avoid any data lost by mistake.
success = self.git_rebase(revision)
return success
+ def git_branch_has_upstream(self, urls: set) -> bool:
+ cmd = ['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{upstream}']
+ ret, upstream = quiet_git(cmd, self.repo_dir)
+ if not ret:
+ return False
+ try:
+ remote = upstream.split('/', maxsplit=1)[0]
+ except IndexError:
+ return False
+ cmd = ['remote', 'get-url', remote]
+ ret, remote_url = quiet_git(cmd, self.repo_dir)
+ return remote_url.strip() in urls
+
def update_git(self) -> bool:
options = T.cast('UpdateArguments', self.options)
if not os.path.exists(os.path.join(self.repo_dir, '.git')):
# Delete existing directory and redownload
windows_proof_rmtree(self.repo_dir)
try:
- self.wrap_resolver.resolve(self.wrap.name, 'meson')
+ self.wrap_resolver.resolve(self.wrap.name)
self.update_git_done()
return True
except WrapException as e:
success = self.git_rebase(revision)
else:
# We are in another branch, either the user created their own branch and
- # we should rebase it, or revision changed in the wrap file and we need
- # to checkout the new branch.
+ # we should rebase it, or revision changed in the wrap file (we
+ # know this when the current branch has an upstream) and we need to
+ # checkout the new branch.
if options.reset:
success = self.git_checkout_and_reset(revision)
else:
- success = self.git_rebase(revision)
+ if self.git_branch_has_upstream({url, push_url}):
+ success = self.git_checkout_and_rebase(revision)
+ else:
+ success = self.git_rebase(revision)
if success:
self.update_git_done()
return success
self.log(' -> Already downloaded')
return True
try:
- self.wrap_resolver.resolve(self.wrap.name, 'meson')
+ self.wrap_resolver.resolve(self.wrap.name)
self.log(' -> done')
except WrapException as e:
self.log(' ->', mlog.red(str(e)))
help='Path to source directory')
p.add_argument('--types', default='',
help=f'Comma-separated list of subproject types. Supported types are: {ALL_TYPES_STRING} (default: all)')
- p.add_argument('--num-processes', default=None, type=int,
+ p.add_argument('-j', '--num-processes', default=None, type=int,
help='How many parallel processes to use (Since 0.59.0).')
p.add_argument('--allow-insecure', default=False, action='store_true',
help='Allow insecure server connections.')
p.set_defaults(pre_func=Runner.pre_update_wrapdb)
return p
+# Note: when adding arguments, please also add them to the completion
+# scripts in $MESONSRC/data/shell-completions/
def add_arguments(parser: argparse.ArgumentParser) -> None:
subparsers = parser.add_subparsers(title='Commands', dest='command')
subparsers.required = True
p.set_defaults(subprojects_func=Runner.packagefiles)
def run(options: 'Arguments') -> int:
- src_dir = os.path.relpath(os.path.realpath(options.sourcedir))
- if not os.path.isfile(os.path.join(src_dir, 'meson.build')):
- mlog.error('Directory', mlog.bold(src_dir), 'does not seem to be a Meson source directory.')
+ source_dir = os.path.relpath(os.path.realpath(options.sourcedir))
+ if not os.path.isfile(os.path.join(source_dir, 'meson.build')):
+ mlog.error('Directory', mlog.bold(source_dir), 'does not seem to be a Meson source directory.')
return 1
- subprojects_dir = os.path.join(src_dir, 'subprojects')
- if not os.path.isdir(subprojects_dir):
- mlog.log('Directory', mlog.bold(src_dir), 'does not seem to have subprojects.')
+ with mlog.no_logging():
+ intr = IntrospectionInterpreter(source_dir, '', 'none')
+ intr.load_root_meson_file()
+ subproject_dir = intr.extract_subproject_dir() or 'subprojects'
+ if not os.path.isdir(os.path.join(source_dir, subproject_dir)):
+ mlog.log('Directory', mlog.bold(source_dir), 'does not seem to have subprojects.')
return 0
- r = Resolver(src_dir, 'subprojects', wrap_frontend=True, allow_insecure=options.allow_insecure)
+ 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]
else:
raise MesonException(f'Unknown subproject type {t!r}, supported types are: {ALL_TYPES_STRING}')
tasks: T.List[T.Awaitable[bool]] = []
task_names: T.List[str] = []
- loop = asyncio.get_event_loop()
+ loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(loop)
executor = ThreadPoolExecutor(options.num_processes)
if types:
wraps = [wrap for wrap in wraps if wrap.type in types]
pre_func(options)
logger = Logger(len(wraps))
for wrap in wraps:
- dirname = Path(subprojects_dir, wrap.directory).as_posix()
+ dirname = Path(source_dir, subproject_dir, wrap.directory).as_posix()
runner = Runner(logger, r, wrap, dirname, options)
task = loop.run_in_executor(executor, runner.run)
tasks.append(task)
# limitations under the License.
# A tool to run tests in many different ways.
+from __future__ import annotations
from pathlib import Path
from collections import deque
from contextlib import suppress
from copy import deepcopy
+from fnmatch import fnmatch
import argparse
import asyncio
import datetime
from . import build
from . import environment
from . import mlog
-from .coredata import major_versions_differ, MesonVersionMismatchException
+from .coredata import MesonVersionMismatchException, major_versions_differ
from .coredata import version as coredata_version
-from .mesonlib import (MesonException, OrderedSet, RealPathAction,
+from .mesonlib import (MesonException, OptionKey, OrderedSet, RealPathAction,
get_wine_shortpath, join_args, split_args, setup_vsenv)
from .mintro import get_infodir, load_info_file
from .programs import ExternalProgram
from .backend.backends import TestProtocol, TestSerialisation
+if T.TYPE_CHECKING:
+ TYPE_TAPResult = T.Union['TAPParser.Test',
+ 'TAPParser.Error',
+ 'TAPParser.Version',
+ 'TAPParser.Plan',
+ 'TAPParser.UnknownLine',
+ 'TAPParser.Bailout']
+
+
# GNU autotools interprets a return code of 77 from tests it executes to
# mean that the test should be skipped.
GNU_SKIP_RETURNCODE = 77
# Exit if 3 Ctrl-C's are received within one second
MAX_CTRLC = 3
+# Define unencodable xml characters' regex for replacing them with their
+# printable representation
+UNENCODABLE_XML_UNICHRS: T.List[T.Tuple[int, int]] = [
+ (0x00, 0x08), (0x0B, 0x0C), (0x0E, 0x1F), (0x7F, 0x84),
+ (0x86, 0x9F), (0xFDD0, 0xFDEF), (0xFFFE, 0xFFFF)]
+# Not narrow build
+if sys.maxunicode >= 0x10000:
+ UNENCODABLE_XML_UNICHRS.extend([
+ (0x1FFFE, 0x1FFFF), (0x2FFFE, 0x2FFFF),
+ (0x3FFFE, 0x3FFFF), (0x4FFFE, 0x4FFFF),
+ (0x5FFFE, 0x5FFFF), (0x6FFFE, 0x6FFFF),
+ (0x7FFFE, 0x7FFFF), (0x8FFFE, 0x8FFFF),
+ (0x9FFFE, 0x9FFFF), (0xAFFFE, 0xAFFFF),
+ (0xBFFFE, 0xBFFFF), (0xCFFFE, 0xCFFFF),
+ (0xDFFFE, 0xDFFFF), (0xEFFFE, 0xEFFFF),
+ (0xFFFFE, 0xFFFFF), (0x10FFFE, 0x10FFFF)])
+UNENCODABLE_XML_CHR_RANGES = [fr'{chr(low)}-{chr(high)}' for (low, high) in UNENCODABLE_XML_UNICHRS]
+UNENCODABLE_XML_CHRS_RE = re.compile('([' + ''.join(UNENCODABLE_XML_CHR_RANGES) + '])')
+
+
def is_windows() -> bool:
platname = platform.system().lower()
return platname == 'windows'
num_workers = 1
return num_workers
+# Note: when adding arguments, please also add them to the completion
+# scripts in $MESONSRC/data/shell-completions/
def add_arguments(parser: argparse.ArgumentParser) -> None:
parser.add_argument('--maxfail', default=0, type=int,
help='Number of failing tests before aborting the '
parser.add_argument('--wrapper', default=None, dest='wrapper', type=split_args,
help='wrapper to run tests with (e.g. Valgrind)')
parser.add_argument('-C', dest='wd', action=RealPathAction,
- # https://github.com/python/typeshed/issues/3107
- # https://github.com/python/mypy/issues/7177
- type=os.path.abspath, # type: ignore
help='directory to cd into before running')
parser.add_argument('--suite', default=[], dest='include_suites', action='append', metavar='SUITE',
help='Only run tests belonging to the given suite.')
help="Run benchmarks instead of tests.")
parser.add_argument('--logbase', default='testlog',
help="Base name for log file.")
- parser.add_argument('--num-processes', default=determine_worker_count(), type=int,
+ parser.add_argument('-j', '--num-processes', default=determine_worker_count(), type=int,
help='How many parallel processes to use.')
parser.add_argument('-v', '--verbose', default=False, action='store_true',
help='Do not redirect stdout and stderr')
# functions here because the status returned by subprocess is munged. It
# returns a negative value if the process was killed by a signal rather than
# the raw status returned by `wait()`. Also, If a shell sits between Meson
- # the the actual unit test that shell is likely to convert a termination due
+ # the actual unit test that shell is likely to convert a termination due
# to a signal into an exit status of 128 plus the signal number.
if retcode < 0:
signum = -retcode
return str(self.colorize('>>> '))
-TYPE_TAPResult = T.Union['TAPParser.Test',
- 'TAPParser.Error',
- 'TAPParser.Version',
- 'TAPParser.Plan',
- 'TAPParser.UnknownLine',
- 'TAPParser.Bailout']
-
class TAPParser:
class Plan(T.NamedTuple):
num_tests: int
yield self.Error('more than one plan found')
else:
num_tests = int(m.group(1))
- skipped = (num_tests == 0)
+ skipped = num_tests == 0
if m.group(2):
if m.group(2).upper().startswith('SKIP'):
if num_tests > 0:
RTRI = "\u25B6 "
def __init__(self) -> None:
- self.running_tests = OrderedSet() # type: OrderedSet['TestRun']
- self.progress_test = None # type: T.Optional['TestRun']
- self.progress_task = None # type: T.Optional[asyncio.Future]
- self.max_left_width = 0 # type: int
+ self.running_tests: OrderedSet['TestRun'] = OrderedSet()
+ self.progress_test: T.Optional['TestRun'] = None
+ self.progress_task: T.Optional[asyncio.Future] = None
+ self.max_left_width = 0
self.stop = False
- self.update = asyncio.Event()
+ # TODO: before 3.10 this cannot be created immediately, because
+ # it will create a new event loop
+ self.update: asyncio.Event
self.should_erase_line = ''
self.test_count = 0
self.started_tests = 0
def start(self, harness: 'TestHarness') -> None:
async def report_progress() -> None:
- loop = asyncio.get_event_loop()
+ loop = asyncio.get_running_loop()
next_update = 0.0
self.request_update()
while not self.stop:
self.emit_progress(harness)
self.flush()
+ self.update = asyncio.Event()
self.test_count = harness.test_count
self.cols = max(self.cols, harness.max_left_width + 30)
class JsonLogfileBuilder(TestFileLogger):
def log(self, harness: 'TestHarness', result: 'TestRun') -> None:
- jresult = {'name': result.name,
- 'stdout': result.stdo,
- 'result': result.res.value,
- 'starttime': result.starttime,
- 'duration': result.duration,
- 'returncode': result.returncode,
- 'env': result.env,
- 'command': result.cmd} # type: T.Dict[str, T.Any]
+ jresult: T.Dict[str, T.Any] = {
+ 'name': result.name,
+ 'stdout': result.stdo,
+ 'result': result.res.value,
+ 'starttime': result.starttime,
+ 'duration': result.duration,
+ 'returncode': result.returncode,
+ 'env': result.env,
+ 'command': result.cmd,
+ }
if result.stde:
jresult['stderr'] = result.stde
self.file.write(json.dumps(jresult) + '\n')
self.filename = filename
self.root = et.Element(
'testsuites', tests='0', errors='0', failures='0')
- self.suites = {} # type: T.Dict[str, et.Element]
+ self.suites: T.Dict[str, et.Element] = {}
def log(self, harness: 'TestHarness', test: 'TestRun') -> None:
"""Log a single test case."""
et.SubElement(testcase, 'system-out').text = subtest.explanation
if test.stdo:
out = et.SubElement(suite, 'system-out')
- out.text = test.stdo.rstrip()
+ out.text = replace_unencodable_xml_chars(test.stdo.rstrip())
if test.stde:
err = et.SubElement(suite, 'system-err')
- err.text = test.stde.rstrip()
+ err.text = replace_unencodable_xml_chars(test.stde.rstrip())
else:
if test.project not in self.suites:
suite = self.suites[test.project] = et.Element(
suite.attrib['failures'] = str(int(suite.attrib['failures']) + 1)
if test.stdo:
out = et.SubElement(testcase, 'system-out')
- out.text = test.stdo.rstrip()
+ out.text = replace_unencodable_xml_chars(test.stdo.rstrip())
if test.stde:
err = et.SubElement(testcase, 'system-err')
- err.text = test.stde.rstrip()
+ err.text = replace_unencodable_xml_chars(test.stde.rstrip())
async def finish(self, harness: 'TestHarness') -> None:
"""Calculate total test counts and write out the xml result."""
name: str, timeout: T.Optional[int], is_parallel: bool, verbose: bool):
self.res = TestResult.PENDING
self.test = test
- self._num = None # type: T.Optional[int]
+ self._num: T.Optional[int] = None
self.name = name
self.timeout = timeout
- self.results = [] # type: T.List[TAPParser.Test]
- self.returncode = None # type: T.Optional[int]
- self.starttime = None # type: T.Optional[float]
- self.duration = None # type: T.Optional[float]
+ self.results: T.List[TAPParser.Test] = []
+ self.returncode: T.Optional[int] = None
+ self.starttime: T.Optional[float] = None
+ self.duration: T.Optional[float] = None
self.stdo = ''
self.stde = ''
self.additional_error = ''
- self.cmd = None # type: T.Optional[T.List[str]]
- self.env = test_env # type: T.Dict[str, str]
+ self.cmd: T.Optional[T.List[str]] = None
+ self.env = test_env
self.should_fail = test.should_fail
self.project = test.project_name
- self.junit = None # type: T.Optional[et.ElementTree]
+ self.junit: T.Optional[et.ElementTree] = None
self.is_parallel = is_parallel
self.verbose = verbose
- self.warnings = [] # type: T.List[str]
+ self.warnings: T.List[str] = []
def start(self, cmd: T.List[str]) -> None:
self.res = TestResult.RUNNING
filename = os.path.join(self.test.workdir, filename)
try:
- self.junit = et.parse(filename)
+ with open(filename, 'r', encoding='utf8', errors='replace') as f:
+ self.junit = et.parse(f)
except FileNotFoundError:
# This can happen if the test fails to run or complete for some
# reason, like the rpath for libgtest isn't properly set. ExitCode
# will handle the failure, don't generate a stacktrace.
pass
+ except et.ParseError as e:
+ # ExitCode will handle the failure, don't generate a stacktrace.
+ mlog.error(f'Unable to parse {filename}: {e!s}')
super().complete()
async def parse(self, harness: 'TestHarness', lines: T.AsyncIterator[str]) -> None:
res = None
- warnings = [] # type: T.List[TAPParser.UnknownLine]
- version: T.Optional[int] = None
+ warnings: T.List[TAPParser.UnknownLine] = []
+ version = 12
async for i in TAPParser().parse_async(lines):
if isinstance(i, TAPParser.Version):
self.additional_error += 'TAP parsing error: ' + i.message
res = TestResult.ERROR
- if version is None:
- self.warnings.append('Unknown TAP version. The first line MUST be `TAP version <int>`. Assuming version 12.')
- version = 12
if warnings:
unknown = str(mlog.yellow('UNKNOWN'))
width = len(str(max(i.lineno for i in warnings)))
TestRun.PROTOCOL_TO_CLASS[TestProtocol.RUST] = TestRunRust
+# Check unencodable characters in xml output and replace them with
+# their printable representation
+def replace_unencodable_xml_chars(original_str: str) -> str:
+ # [1:-1] is needed for removing `'` characters from both start and end
+ # of the string
+ replacement_lambda = lambda illegal_chr: repr(illegal_chr.group())[1:-1]
+ return UNENCODABLE_XML_CHRS_RE.sub(replacement_lambda, original_str)
def decode(stream: T.Union[None, bytes]) -> str:
if stream is None:
# Python is silly and does not have a variant of asyncio.wait with an
# absolute time as deadline.
- deadline = None if timeout is None else asyncio.get_event_loop().time() + timeout
+ loop = asyncio.get_running_loop()
+ deadline = None if timeout is None else loop.time() + timeout
while futures and (timeout is None or timeout > 0):
done, futures = await asyncio.wait(futures, timeout=timeout,
return_when=asyncio.FIRST_EXCEPTION)
check_futures(done)
if deadline:
- timeout = deadline - asyncio.get_event_loop().time()
+ timeout = deadline - loop.time()
check_futures(futures)
self._process = p
self.stdout = stdout
self.stderr = stderr
- self.stdo_task = None # type: T.Optional[asyncio.Future[str]]
- self.stde_task = None # type: T.Optional[asyncio.Future[str]]
- self.postwait_fn = postwait_fn # type: T.Callable[[], None]
- self.all_futures = [] # type: T.List[asyncio.Future]
- self.queue = None # type: T.Optional[asyncio.Queue[T.Optional[str]]]
+ self.stdo_task: T.Optional[asyncio.Task[None]] = None
+ self.stde_task: T.Optional[asyncio.Task[None]] = None
+ self.postwait_fn = postwait_fn
+ self.all_futures: T.List[asyncio.Future] = []
+ self.queue: T.Optional[asyncio.Queue[T.Optional[str]]] = None
def stdout_lines(self) -> T.AsyncIterator[str]:
self.queue = asyncio.Queue()
if ('MALLOC_PERTURB_' not in env or not env['MALLOC_PERTURB_']) and not options.benchmark:
env['MALLOC_PERTURB_'] = str(random.randint(1, 255))
+ # Sanitizers do not default to aborting on error. This is counter to
+ # expectations when using -Db_sanitize and has led to confusion in the wild
+ # in CI. Set our own values of {ASAN,UBSAN}_OPTOINS to rectify this, but
+ # only if the user has not defined them.
+ if ('ASAN_OPTIONS' not in env or not env['ASAN_OPTIONS']):
+ env['ASAN_OPTIONS'] = 'halt_on_error=1:abort_on_error=1:print_summary=1'
+ if ('UBSAN_OPTIONS' not in env or not env['UBSAN_OPTIONS']):
+ env['UBSAN_OPTIONS'] = 'halt_on_error=1:abort_on_error=1:print_summary=1:print_stacktrace=1'
+
if self.options.gdb or self.test.timeout is None or self.test.timeout <= 0:
timeout = None
elif self.options.timeout_multiplier is None:
async def run(self, harness: 'TestHarness') -> TestRun:
if self.cmd is None:
- self.stdo = 'Not run because can not execute cross compiled binaries.'
+ self.stdo = 'Not run because cannot execute cross compiled binaries.'
harness.log_start_test(self.runobj)
self.runobj.complete_skip()
else:
if not self.options.split and not self.runobj.needs_parsing \
else asyncio.subprocess.PIPE
- extra_cmd = [] # type: T.List[str]
+ extra_cmd: T.List[str] = []
if self.test.protocol is TestProtocol.GTEST:
gtestname = self.test.name
if self.test.workdir:
class TestHarness:
def __init__(self, options: argparse.Namespace):
self.options = options
- self.collected_failures = [] # type: T.List[TestRun]
+ self.collected_failures: T.List[TestRun] = []
self.fail_count = 0
self.expectedfail_count = 0
self.unexpectedpass_count = 0
self.test_count = 0
self.name_max_len = 0
self.is_run = False
- self.loggers = [] # type: T.List[TestLogger]
+ self.loggers: T.List[TestLogger] = []
self.console_logger = ConsoleLogger()
self.loggers.append(self.console_logger)
self.need_console = False
- self.ninja = None # type: T.List[str]
+ self.ninja: T.List[str] = None
- self.logfile_base = None # type: T.Optional[str]
+ self.logfile_base: T.Optional[str] = None
if self.options.logbase and not self.options.gdb:
namebase = None
self.logfile_base = os.path.join(self.options.wd, 'meson-logs', self.options.logbase)
if self.options.no_rebuild:
return
- if not (Path(self.options.wd) / 'build.ninja').is_file():
- print('Only ninja backend is supported to rebuild tests before running them.')
- # Disable, no point in trying to build anything later
- self.options.no_rebuild = True
- return
-
self.ninja = environment.detect_ninja()
if not self.ninja:
print("Can't find ninja, can't rebuild test.")
# happen before rebuild_deps(), because we need the correct list of
# tests and their dependencies to compute
if not self.options.no_rebuild:
- ret = subprocess.run(self.ninja + ['build.ninja']).returncode
- if ret != 0:
- raise TestException(f'Could not configure {self.options.wd!r}')
+ teststdo = subprocess.run(self.ninja + ['-n', 'build.ninja'], capture_output=True).stdout
+ if b'ninja: no work to do.' not in teststdo and b'samu: nothing to do' not in teststdo:
+ stdo = sys.stderr if self.options.list else sys.stdout
+ ret = subprocess.run(self.ninja + ['build.ninja'], stdout=stdo.fileno())
+ if ret.returncode != 0:
+ raise TestException(f'Could not configure {self.options.wd!r}')
self.build_data = build.load(os.getcwd())
if not self.options.setup:
startdir = os.getcwd()
try:
os.chdir(self.options.wd)
- runners = [] # type: T.List[SingleTestRunner]
+ runners: T.List[SingleTestRunner] = []
for i in range(self.options.repeat):
runners.extend(self.get_test_runner(test) for test in tests)
if i == 0:
run all tests with that name across all subprojects, which is
identical to "meson test foo1"
'''
+ patterns: T.Dict[T.Tuple[str, str], bool] = {}
for arg in self.options.args:
+ # Replace empty components by wildcards:
+ # '' -> '*:*'
+ # 'name' -> '*:name'
+ # ':name' -> '*:name'
+ # 'proj:' -> 'proj:*'
if ':' in arg:
subproj, name = arg.split(':', maxsplit=1)
+ if name == '':
+ name = '*'
+ if subproj == '': # in case arg was ':'
+ subproj = '*'
else:
- subproj, name = '', arg
- for t in tests:
- if subproj and t.project_name != subproj:
- continue
- if name and t.name != name:
- continue
- yield t
+ subproj, name = '*', arg
+ patterns[(subproj, name)] = False
+
+ for t in tests:
+ # For each test, find the first matching pattern
+ # and mark it as used. yield the matching tests.
+ for subproj, name in list(patterns):
+ if fnmatch(t.project_name, subproj) and fnmatch(t.name, name):
+ patterns[(subproj, name)] = True
+ yield t
+ break
+
+ for (subproj, name), was_used in patterns.items():
+ if not was_used:
+ # For each unused pattern...
+ arg = f'{subproj}:{name}'
+ for t in tests:
+ # ... if it matches a test, then it wasn't used because another
+ # pattern matched the same test before.
+ # Report it as a warning.
+ if fnmatch(t.project_name, subproj) and fnmatch(t.name, name):
+ mlog.warning(f'{arg} test name is redundant and was not used')
+ break
+ else:
+ # If the pattern doesn't match any test,
+ # report it as an error. We don't want the `test` command to
+ # succeed on an invalid pattern.
+ raise MesonException(f'{arg} test name does not match any test')
- def get_tests(self) -> T.List[TestSerialisation]:
+ def get_tests(self, errorfile: T.Optional[T.IO] = None) -> T.List[TestSerialisation]:
if not self.tests:
- print('No tests defined.')
+ print('No tests defined.', file=errorfile)
return []
tests = [t for t in self.tests if self.test_suitable(t)]
tests = list(self.tests_from_args(tests))
if not tests:
- print('No suitable tests defined.')
+ print('No suitable tests defined.', file=errorfile)
return []
return tests
@staticmethod
def get_wrapper(options: argparse.Namespace) -> T.List[str]:
- wrap = [] # type: T.List[str]
+ wrap: T.List[str] = []
if options.gdb:
wrap = [options.gdb_path, '--quiet']
if options.repeat > 1:
def run_tests(self, runners: T.List[SingleTestRunner]) -> None:
try:
self.open_logfiles()
- # Replace with asyncio.run once we can require Python 3.7
- loop = asyncio.get_event_loop()
- loop.run_until_complete(self._run_tests(runners))
+
+ # TODO: this is the default for python 3.8
+ if sys.platform == 'win32':
+ asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
+
+ asyncio.run(self._run_tests(runners))
finally:
self.close_logfiles()
async def _run_tests(self, runners: T.List[SingleTestRunner]) -> None:
semaphore = asyncio.Semaphore(self.options.num_processes)
- futures = deque() # type: T.Deque[asyncio.Future]
- running_tests = {} # type: T.Dict[asyncio.Future, str]
+ futures: T.Deque[asyncio.Future] = deque()
+ running_tests: T.Dict[asyncio.Future, str] = {}
interrupted = False
- ctrlc_times = deque(maxlen=MAX_CTRLC) # type: T.Deque[float]
+ ctrlc_times: T.Deque[float] = deque(maxlen=MAX_CTRLC)
+ loop = asyncio.get_running_loop()
async def run_test(test: SingleTestRunner) -> None:
async with semaphore:
nonlocal interrupted
if interrupted:
return
- ctrlc_times.append(asyncio.get_event_loop().time())
+ ctrlc_times.append(loop.time())
if len(ctrlc_times) == MAX_CTRLC and ctrlc_times[-1] - ctrlc_times[0] < 1:
self.flush_logfiles()
mlog.warning('CTRL-C detected, exiting')
if sys.platform != 'win32':
if os.getpgid(0) == os.getpid():
- asyncio.get_event_loop().add_signal_handler(signal.SIGINT, sigint_handler)
+ loop.add_signal_handler(signal.SIGINT, sigint_handler)
else:
- asyncio.get_event_loop().add_signal_handler(signal.SIGINT, sigterm_handler)
- asyncio.get_event_loop().add_signal_handler(signal.SIGTERM, sigterm_handler)
+ loop.add_signal_handler(signal.SIGINT, sigterm_handler)
+ loop.add_signal_handler(signal.SIGTERM, sigterm_handler)
try:
for runner in runners:
if not runner.is_parallel:
await complete_all(futures)
finally:
if sys.platform != 'win32':
- asyncio.get_event_loop().remove_signal_handler(signal.SIGINT)
- asyncio.get_event_loop().remove_signal_handler(signal.SIGTERM)
+ loop.remove_signal_handler(signal.SIGINT)
+ loop.remove_signal_handler(signal.SIGTERM)
for l in self.loggers:
await l.finish(self)
def list_tests(th: TestHarness) -> bool:
- tests = th.get_tests()
+ tests = th.get_tests(errorfile=sys.stderr)
for t in tests:
print(th.get_pretty_suite(t))
return not tests
assert len(ninja) > 0
- depends = set() # type: T.Set[str]
- targets = set() # type: T.Set[str]
- intro_targets = {} # type: T.Dict[str, T.List[str]]
+ depends: T.Set[str] = set()
+ targets: T.Set[str] = set()
+ intro_targets: T.Dict[str, T.List[str]] = {}
for target in load_info_file(get_infodir(wd), kind='targets'):
intro_targets[target['id']] = [
convert_path_to_target(f)
if options.wrapper:
check_bin = options.wrapper[0]
- if sys.platform == 'win32':
- loop = asyncio.ProactorEventLoop()
- asyncio.set_event_loop(loop)
-
if check_bin is not None:
exe = ExternalProgram(check_bin, silent=True)
if not exe.found():
return 1
b = build.load(options.wd)
- setup_vsenv(b.need_vsenv)
+ need_vsenv = T.cast('bool', b.environment.coredata.get_option(OptionKey('vsenv')))
+ setup_vsenv(need_vsenv)
+
+ if not options.no_rebuild:
+ backend = b.environment.coredata.get_option(OptionKey('backend'))
+ if backend == 'none':
+ # nothing to build...
+ options.no_rebuild = True
+ elif backend != 'ninja':
+ print('Only ninja backend is supported to rebuild tests before running them.')
+ # Disable, no point in trying to build anything later
+ options.no_rebuild = True
with TestHarness(options) as th:
try:
# 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
from . import coredata as cdata
import pprint
import textwrap
+# Note: when adding arguments, please also add them to the completion
+# scripts in $MESONSRC/data/shell-completions/
def add_arguments(parser):
parser.add_argument('--all', action='store_true', dest='all', default=False,
help='Show data not used by current backend.')
print(' version: ' + repr(dep.get_version()))
for for_machine in iter(MachineChoice):
- items_list = list(sorted(v[for_machine].items()))
+ items_list = sorted(v[for_machine].items())
if items_list:
print(f'Cached dependencies for {for_machine.get_lower_case_name()} machine')
for dep_key, deps in items_list:
# 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 re
import typing as T
from . import mesonlib
from . import mparser
from . import mlog
-from .interpreterbase import FeatureNew, typed_pos_args, typed_kwargs, ContainerTypeInfo, KwargInfo, permittedKwargs
+from .interpreterbase import FeatureNew, FeatureDeprecated, typed_pos_args, typed_kwargs, ContainerTypeInfo, KwargInfo
+from .interpreter.type_checking import NoneType, in_set_validator
+
if T.TYPE_CHECKING:
from .interpreterbase import TYPE_var, TYPE_kwargs
from .interpreterbase import SubProject
- from typing_extensions import TypedDict
+ from typing_extensions import TypedDict, Literal
+
+ _DEPRECATED_ARGS = T.Union[bool, str, T.Dict[str, str], T.List[str]]
+
FuncOptionArgs = TypedDict('FuncOptionArgs', {
'type': str,
'description': str,
'value': object,
'min': T.Optional[int],
'max': T.Optional[int],
- 'deprecated': T.Union[bool, str, T.Dict[str, str], T.List[str]],
- })
- ParserArgs = TypedDict('ParserArgs', {
- 'yield': bool,
- 'choices': T.Optional[T.List[str]],
- 'value': object,
- 'min': T.Optional[int],
- 'max': T.Optional[int],
+ 'deprecated': _DEPRECATED_ARGS,
})
+ class StringArgs(TypedDict):
+ value: str
+
+ class BooleanArgs(TypedDict):
+ value: bool
+
+ class ComboArgs(TypedDict):
+ value: str
+ choices: T.List[str]
+
+ class IntegerArgs(TypedDict):
+ value: int
+ min: T.Optional[int]
+ max: T.Optional[int]
+
+ class StringArrayArgs(TypedDict):
+ value: T.Optional[T.Union[str, T.List[str]]]
+ choices: T.List[str]
+
+ class FeatureArgs(TypedDict):
+ value: Literal['enabled', 'disabled', 'auto']
+ choices: T.List[str]
+
class OptionException(mesonlib.MesonException):
pass
def __init__(self, subproject: 'SubProject') -> None:
self.options: 'coredata.MutableKeyedOptionDictType' = {}
self.subproject = subproject
- self.option_types = {'string': self.string_parser,
- 'boolean': self.boolean_parser,
- 'combo': self.combo_parser,
- 'integer': self.integer_parser,
- 'array': self.string_array_parser,
- 'feature': self.feature_parser,
- }
+ self.option_types: T.Dict[str, T.Callable[..., coredata.UserOption]] = {
+ 'string': self.string_parser,
+ 'boolean': self.boolean_parser,
+ 'combo': self.combo_parser,
+ 'integer': self.integer_parser,
+ 'array': self.string_array_parser,
+ 'feature': self.feature_parser,
+ }
def process(self, option_file: str) -> None:
try:
def reduce_single(self, arg: T.Union[str, mparser.BaseNode]) -> 'TYPE_var':
if isinstance(arg, str):
return arg
- elif isinstance(arg, (mparser.StringNode, mparser.BooleanNode,
+ if isinstance(arg, mparser.ParenthesizedNode):
+ return self.reduce_single(arg.inner)
+ elif isinstance(arg, (mparser.BaseStringNode, mparser.BooleanNode,
mparser.NumberNode)):
return arg.value
elif isinstance(arg, mparser.ArrayNode):
elif isinstance(arg, mparser.DictNode):
d = {}
for k, v in arg.args.kwargs.items():
- if not isinstance(k, mparser.StringNode):
+ if not isinstance(k, mparser.BaseStringNode):
raise OptionException('Dictionary keys must be a string literal')
d[k.value] = self.reduce_single(v)
return d
def evaluate_statement(self, node: mparser.BaseNode) -> None:
if not isinstance(node, mparser.FunctionNode):
raise OptionException('Option file may only contain option definitions')
- func_name = node.func_name
+ func_name = node.func_name.value
if func_name != 'option':
raise OptionException('Only calls to option() are allowed in option files.')
(posargs, kwargs) = self.reduce_arguments(node.args)
self.func_option(posargs, kwargs)
- @typed_kwargs('option',
- KwargInfo('type', str, required=True),
- KwargInfo('description', str, default=''),
- KwargInfo('yield', bool, default=coredata.default_yielding, since='0.45.0'),
- KwargInfo('choices', (ContainerTypeInfo(list, str), type(None))),
- KwargInfo('value', object),
- KwargInfo('min', (int, type(None))),
- KwargInfo('max', (int, type(None))),
- KwargInfo('deprecated', (bool, str, ContainerTypeInfo(dict, str), ContainerTypeInfo(list, str)),
- default=False, since='0.60.0')
- )
+ @typed_kwargs(
+ 'option',
+ KwargInfo(
+ 'type',
+ str,
+ required=True,
+ validator=in_set_validator({'string', 'boolean', 'integer', 'combo', 'array', 'feature'})
+ ),
+ KwargInfo('description', str, default=''),
+ KwargInfo(
+ 'deprecated',
+ (bool, str, ContainerTypeInfo(dict, str), ContainerTypeInfo(list, str)),
+ default=False,
+ since='0.60.0',
+ since_values={str: '0.63.0'},
+ ),
+ KwargInfo('yield', bool, default=coredata.DEFAULT_YIELDING, since='0.45.0'),
+ allow_unknown=True,
+ )
@typed_pos_args('option', str)
def func_option(self, args: T.Tuple[str], kwargs: 'FuncOptionArgs') -> None:
opt_name = args[0]
raise OptionException('Option name %s is reserved.' % opt_name)
opt_type = kwargs['type']
- parser = self.option_types.get(opt_type)
- if not parser:
- raise OptionException(f'Unknown type {opt_type}.')
+ parser = self.option_types[opt_type]
description = kwargs['description'] or opt_name
- # Only keep in kwargs arguments that are used by option type's parser
- # because they use @permittedKwargs().
- known_parser_kwargs = {'value', 'choices', 'yield', 'min', 'max'}
- parser_kwargs = {k: v for k, v in kwargs.items() if k in known_parser_kwargs and v is not None}
- opt = parser(description, T.cast('ParserArgs', parser_kwargs))
- opt.deprecated = kwargs['deprecated']
- if isinstance(opt.deprecated, str):
- FeatureNew.single_use('String value to "deprecated" keyword argument', '0.63.0', self.subproject)
+ # Drop the arguments we've already consumed
+ n_kwargs = {k: v for k, v in kwargs.items()
+ if k not in {'type', 'description', 'deprecated', 'yield'}}
+
+ opt = parser(description, (kwargs['yield'], kwargs['deprecated']), n_kwargs)
if key in self.options:
mlog.deprecation(f'Option {opt_name} already exists.')
self.options[key] = opt
- @permittedKwargs({'value', 'yield'})
- def string_parser(self, description: str, kwargs: 'ParserArgs') -> coredata.UserOption:
- value = kwargs.get('value', '')
- return coredata.UserStringOption(description, value, kwargs['yield'])
-
- @permittedKwargs({'value', 'yield'})
- def boolean_parser(self, description: str, kwargs: 'ParserArgs') -> coredata.UserOption:
- value = kwargs.get('value', True)
- return coredata.UserBooleanOption(description, value, kwargs['yield'])
-
- @permittedKwargs({'value', 'yield', 'choices'})
- def combo_parser(self, description: str, kwargs: 'ParserArgs') -> coredata.UserOption:
- choices = kwargs.get('choices')
- if not choices:
- raise OptionException('Combo option missing "choices" keyword.')
- value = kwargs.get('value', choices[0])
- return coredata.UserComboOption(description, choices, value, kwargs['yield'])
-
- @permittedKwargs({'value', 'min', 'max', 'yield'})
- def integer_parser(self, description: str, kwargs: 'ParserArgs') -> coredata.UserOption:
- value = kwargs.get('value')
+ @typed_kwargs(
+ 'string option',
+ KwargInfo('value', str, default=''),
+ )
+ def string_parser(self, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: StringArgs) -> coredata.UserOption:
+ return coredata.UserStringOption(description, kwargs['value'], *args)
+
+ @typed_kwargs(
+ 'boolean option',
+ KwargInfo(
+ 'value',
+ (bool, str),
+ default=True,
+ validator=lambda x: None if isinstance(x, bool) or x in {'true', 'false'} else 'boolean options must have boolean values',
+ deprecated_values={str: ('1.1.0', 'use a boolean, not a string')},
+ ),
+ )
+ def boolean_parser(self, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: BooleanArgs) -> coredata.UserOption:
+ return coredata.UserBooleanOption(description, kwargs['value'], *args)
+
+ @typed_kwargs(
+ 'combo option',
+ KwargInfo('value', (str, NoneType)),
+ KwargInfo('choices', ContainerTypeInfo(list, str, allow_empty=False), required=True),
+ )
+ def combo_parser(self, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: ComboArgs) -> coredata.UserOption:
+ choices = kwargs['choices']
+ value = kwargs['value']
if value is None:
- raise OptionException('Integer option must contain value argument.')
- inttuple = (kwargs.get('min'), kwargs.get('max'), value)
- return coredata.UserIntegerOption(description, inttuple, kwargs['yield'])
-
- @permittedKwargs({'value', 'yield', 'choices'})
- def string_array_parser(self, description: str, kwargs: 'ParserArgs') -> coredata.UserOption:
- choices = kwargs.get('choices', [])
- value = kwargs.get('value', choices)
- if not isinstance(value, list):
- raise OptionException('Array choices must be passed as an array.')
+ value = kwargs['choices'][0]
+ return coredata.UserComboOption(description, choices, value, *args)
+
+ @typed_kwargs(
+ 'integer option',
+ KwargInfo(
+ 'value',
+ (int, str),
+ default=True,
+ deprecated_values={str: ('1.1.0', 'use an integer, not a string')},
+ convertor=int,
+ ),
+ KwargInfo('min', (int, NoneType)),
+ KwargInfo('max', (int, NoneType)),
+ )
+ def integer_parser(self, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: IntegerArgs) -> coredata.UserOption:
+ value = kwargs['value']
+ inttuple = (kwargs['min'], kwargs['max'], value)
+ return coredata.UserIntegerOption(description, inttuple, *args)
+
+ @typed_kwargs(
+ 'string array option',
+ KwargInfo('value', (ContainerTypeInfo(list, str), str, NoneType)),
+ KwargInfo('choices', ContainerTypeInfo(list, str), default=[]),
+ )
+ def string_array_parser(self, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: StringArrayArgs) -> coredata.UserOption:
+ choices = kwargs['choices']
+ value = kwargs['value'] if kwargs['value'] is not None else choices
+ if isinstance(value, str):
+ if value.startswith('['):
+ FeatureDeprecated('String value for array option', '1.3.0').use(self.subproject)
+ else:
+ raise mesonlib.MesonException('Value does not define an array: ' + value)
return coredata.UserArrayOption(description, value,
choices=choices,
- yielding=kwargs['yield'])
+ yielding=args[0],
+ deprecated=args[1])
- @permittedKwargs({'value', 'yield'})
- def feature_parser(self, description: str, kwargs: 'ParserArgs') -> coredata.UserOption:
- value = kwargs.get('value', 'auto')
- return coredata.UserFeatureOption(description, value, kwargs['yield'])
+ @typed_kwargs(
+ 'feature option',
+ KwargInfo('value', str, default='auto', validator=in_set_validator({'auto', 'enabled', 'disabled'})),
+ )
+ def feature_parser(self, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: FeatureArgs) -> coredata.UserOption:
+ return coredata.UserFeatureOption(description, kwargs['value'], *args)
# 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
"""Representations and logic for External and Internal Programs."""
from . import mesonlib
from . import mlog
-from .mesonlib import MachineChoice
+from .mesonlib import MachineChoice, OrderedSet
if T.TYPE_CHECKING:
from .environment import Environment
def get_version(self, interpreter: T.Optional['Interpreter'] = None) -> str:
if not self.cached_version:
- from . import build
raw_cmd = self.get_command() + ['--version']
if interpreter:
- res = interpreter.run_command_impl(interpreter.current_node, (self, ['--version']),
+ res = interpreter.run_command_impl((self, ['--version']),
{'capture': True,
'check': True,
- 'env': build.EnvironmentVariables()},
+ 'env': mesonlib.EnvironmentVariables()},
True)
o, e = res.stdout, res.stderr
else:
def find_external_program(env: 'Environment', for_machine: MachineChoice, name: str,
display_name: str, default_names: T.List[str],
allow_default_for_cross: bool = True) -> T.Generator['ExternalProgram', None, None]:
- """Find an external program, chcking the cross file plus any default options."""
+ """Find an external program, checking the cross file plus any default options."""
+ potential_names = OrderedSet(default_names)
+ potential_names.add(name)
# Lookup in cross or machine file.
- potential_cmd = env.lookup_binary_entry(for_machine, name)
- if potential_cmd is not None:
- mlog.debug(f'{display_name} binary for {for_machine} specified from cross file, native file, '
- f'or env var as {potential_cmd}')
- yield ExternalProgram.from_entry(name, potential_cmd)
- # We never fallback if the user-specified option is no good, so
- # stop returning options.
- return
+ for potential_name in potential_names:
+ potential_cmd = env.lookup_binary_entry(for_machine, potential_name)
+ if potential_cmd is not None:
+ mlog.debug(f'{display_name} binary for {for_machine} specified from cross file, native file, '
+ f'or env var as {potential_cmd}')
+ yield ExternalProgram.from_entry(potential_name, potential_cmd)
+ # We never fallback if the user-specified option is no good, so
+ # stop returning options.
+ return
mlog.debug(f'{display_name} binary missing from cross or native file, or env var undefined.')
# Fallback on hard-coded defaults, if a default binary is allowed for use
# with cross targets, or if this is not a cross target
-#!/usr/bin/env python3
# Copyright 2016 The Meson development team
# Licensed under the Apache License, Version 2.0 (the "License");
from __future__ import annotations
from .ast import IntrospectionInterpreter, BUILD_TARGET_FUNCTIONS, AstConditionLevel, AstIDGenerator, AstIndentationGenerator, AstPrinter
-from mesonbuild.mesonlib import MesonException
+from mesonbuild.mesonlib import MesonException, setup_vsenv
from . import mlog, environment
from functools import wraps
-from .mparser import Token, ArrayNode, ArgumentNode, AssignmentNode, BooleanNode, ElementaryNode, IdNode, FunctionNode, StringNode
+from .mparser import Token, ArrayNode, ArgumentNode, AssignmentNode, BaseStringNode, BooleanNode, ElementaryNode, IdNode, FunctionNode, StringNode, SymbolNode
import json, os, re, sys
import typing as T
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, formatter=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')
subparsers = parser.add_subparsers(dest='type', title='Rewriter commands', description='Rewrite command to execute')
# Target
- tgt_parser = subparsers.add_parser('target', help='Modify a target', formatter_class=formatter)
+ tgt_parser = subparsers.add_parser('target', aliases=['tgt'], help='Modify a target', formatter_class=formatter)
tgt_parser.add_argument('-s', '--subdir', default='', dest='subdir', help='Subdirectory of the new target (only for the "add_target" action)')
tgt_parser.add_argument('--type', dest='tgt_type', choices=rewriter_keys['target']['target_type'][2], default='executable',
help='Type of the target to add (only for the "add_target" action)')
kw_parser.add_argument('kwargs', nargs='*', help='Pairs of keyword and value')
# Default options
- def_parser = subparsers.add_parser('default-options', help='Modify the project default options', formatter_class=formatter)
+ 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')
# JSON file/command
- cmd_parser = subparsers.add_parser('command', help='Execute a JSON array of commands', formatter_class=formatter)
+ 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:
return wrapped
+def _symbol(val: str) -> SymbolNode:
+ return SymbolNode(Token('', '', 0, 0, 0, (0, 0), val))
+
class MTypeBase:
def __init__(self, node: T.Optional[BaseNode] = None):
if node is None:
super().__init__(node)
def _new_node(self):
- return ArrayNode(ArgumentNode(Token('', '', 0, 0, 0, None, '')), 0, 0, 0, 0)
+ return ArrayNode(_symbol('['), ArgumentNode(Token('', '', 0, 0, 0, None, '')), _symbol(']'))
def _new_element_node(self, value):
# Overwrite in derived class
return StringNode(Token('', '', 0, 0, 0, None, str(value)))
def _check_is_equal(self, node, value) -> bool:
- if isinstance(node, StringNode):
+ if isinstance(node, BaseStringNode):
return node.value == value
return False
def _check_regex_matches(self, node, regex: str) -> bool:
- if isinstance(node, StringNode):
+ if isinstance(node, BaseStringNode):
return re.match(regex, node.value) is not None
return False
return False
def _check_regex_matches(self, node, regex: str) -> bool:
- if isinstance(node, StringNode):
+ if isinstance(node, BaseStringNode):
return re.match(regex, node.value) is not None
return False
if target in self.interpreter.assignments:
node = self.interpreter.assignments[target]
if isinstance(node, FunctionNode):
- if node.func_name in {'executable', 'jar', 'library', 'shared_library', 'shared_module', 'static_library', 'both_libraries'}:
+ 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
if dependency in self.interpreter.assignments:
node = self.interpreter.assignments[dependency]
if isinstance(node, FunctionNode):
- if node.func_name == 'dependency':
+ if node.func_name.value == 'dependency':
name = self.interpreter.flatten_args(node.args)[0]
dep = check_list(name)
if key not in arg_node.kwargs:
arg_node.kwargs[key] = None
- modifyer = kwargs_def[key](arg_node.kwargs[key])
- if not modifyer.can_modify():
+ modifier = kwargs_def[key](arg_node.kwargs[key])
+ if not modifier.can_modify():
mlog.log(' -- Skipping', mlog.bold(key), 'because it is to complex to modify')
# Apply the operation
val_str = str(val)
if cmd['operation'] == 'set':
mlog.log(' -- Setting', mlog.bold(key), 'to', mlog.yellow(val_str))
- modifyer.set_value(val)
+ modifier.set_value(val)
elif cmd['operation'] == 'add':
mlog.log(' -- Adding', mlog.yellow(val_str), 'to', mlog.bold(key))
- modifyer.add_value(val)
+ modifier.add_value(val)
elif cmd['operation'] == 'remove':
mlog.log(' -- Removing', mlog.yellow(val_str), 'from', mlog.bold(key))
- modifyer.remove_value(val)
+ modifier.remove_value(val)
elif cmd['operation'] == 'remove_regex':
mlog.log(' -- Removing all values matching', mlog.yellow(val_str), 'from', mlog.bold(key))
- modifyer.remove_regex(val)
+ modifier.remove_regex(val)
# Write back the result
- arg_node.kwargs[key] = modifyer.get_node()
+ arg_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()}
+ 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]
args = []
if isinstance(n, FunctionNode):
args = list(n.args.arguments)
- if n.func_name in BUILD_TARGET_FUNCTIONS:
+ if n.func_name.value in BUILD_TARGET_FUNCTIONS:
args.pop(0)
elif isinstance(n, ArrayNode):
args = n.args.arguments
src_list = []
for i in target['sources']:
for j in arg_list_from_node(i):
- if isinstance(j, StringNode):
+ if isinstance(j, BaseStringNode):
src_list += [j.value]
# Generate the new String nodes
def find_node(src):
for i in target['sources']:
for j in arg_list_from_node(i):
- if isinstance(j, StringNode):
+ if isinstance(j, BaseStringNode):
if j.value == src:
return i, j
return None, None
node = tgt_function.args.kwargs[extra_files_key]
except StopIteration:
# Target has no extra_files kwarg, create one
- node = ArrayNode(ArgumentNode(Token('', tgt_function.filename, 0, 0, 0, None, '[]')), tgt_function.end_lineno, tgt_function.end_colno, tgt_function.end_lineno, tgt_function.end_colno)
+ 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:
extra_files_list = []
for i in target['extra_files']:
for j in arg_list_from_node(i):
- if isinstance(j, StringNode):
+ if isinstance(j, BaseStringNode):
extra_files_list += [j.value]
# Generate the new String nodes
def find_node(src):
for i in target['extra_files']:
for j in arg_list_from_node(i):
- if isinstance(j, StringNode):
+ if isinstance(j, BaseStringNode):
if j.value == src:
return i, j
return None, None
# Build src list
src_arg_node = ArgumentNode(Token('string', filename, 0, 0, 0, None, ''))
- src_arr_node = ArrayNode(src_arg_node, 0, 0, 0, 0)
+ src_arr_node = ArrayNode(_symbol('['), src_arg_node, _symbol(']'))
src_far_node = ArgumentNode(Token('string', filename, 0, 0, 0, None, ''))
- src_fun_node = FunctionNode(filename, 0, 0, 0, 0, 'files', src_far_node)
- src_ass_node = AssignmentNode(filename, 0, 0, source_id, src_fun_node)
+ src_fun_node = FunctionNode(IdNode(Token('id', filename, 0, 0, 0, (0, 0), 'files')), _symbol('('), src_far_node, _symbol(')'))
+ src_ass_node = AssignmentNode(IdNode(Token('id', filename, 0, 0, 0, (0, 0), source_id)), _symbol('='), src_fun_node)
src_arg_node.arguments = [StringNode(Token('string', filename, 0, 0, 0, None, x)) for x in cmd['sources']]
src_far_node.arguments = [src_arr_node]
# Build target
tgt_arg_node = ArgumentNode(Token('string', filename, 0, 0, 0, None, ''))
- tgt_fun_node = FunctionNode(filename, 0, 0, 0, 0, cmd['target_type'], tgt_arg_node)
- tgt_ass_node = AssignmentNode(filename, 0, 0, target_id, tgt_fun_node)
+ tgt_fun_node = FunctionNode(IdNode(Token('id', filename, 0, 0, 0, (0, 0), cmd['target_type'])), _symbol('('), tgt_arg_node, _symbol(')'))
+ tgt_ass_node = AssignmentNode(IdNode(Token('id', filename, 0, 0, 0, (0, 0), target_id)), _symbol('='), tgt_fun_node)
tgt_arg_node.arguments = [
StringNode(Token('string', filename, 0, 0, 0, None, cmd['target'])),
IdNode(Token('string', filename, 0, 0, 0, None, source_id))
src_list = []
for i in target['sources']:
for j in arg_list_from_node(i):
- if isinstance(j, StringNode):
+ if isinstance(j, BaseStringNode):
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):
+ if isinstance(j, BaseStringNode):
extra_files_list += [j.value]
test_data = {
'name': target['name'],
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('/'))])
- unknown = [x for x in i.arguments if not isinstance(x, StringNode)]
- sources = [x for x in i.arguments if isinstance(x, StringNode)]
+ unknown = [x for x in i.arguments if not isinstance(x, BaseStringNode)]
+ sources = [x for x in i.arguments if isinstance(x, BaseStringNode)]
sources = sorted(sources, key=lambda x: path_sorter(x.value))
i.arguments = unknown + sources
# 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 = list(sorted(work_nodes, key=lambda x: (x['node'].lineno, x['node'].colno), reverse=True))
+ work_nodes = sorted(work_nodes, key=lambda x: (x['node'].lineno, x['node'].colno), reverse=True)
work_nodes += [{'node': x, 'action': 'add'} for x in self.to_add_nodes]
# Generating the new replacement string
mlog.set_quiet()
try:
+ setup_vsenv()
rewriter = Rewriter(options.sourcedir, skip_errors=options.skip)
rewriter.analyze_meson()
# 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 argparse
import subprocess
from ..programs import ExternalProgram
import typing as T
-def run_clang_format(fname: Path, exelist: T.List[str], check: bool) -> subprocess.CompletedProcess:
+def run_clang_format(fname: Path, exelist: T.List[str], check: bool, cformat_ver: T.Optional[str]) -> subprocess.CompletedProcess:
clangformat_10 = False
- if check:
- cformat_ver = ExternalProgram('clang-format', exelist).get_version()
+ if check and cformat_ver:
if version_compare(cformat_ver, '>=10'):
clangformat_10 = True
exelist = exelist + ['--dry-run', '--Werror']
print('Could not execute clang-format "%s"' % ' '.join(exelist))
return 1
- return run_tool('clang-format', srcdir, builddir, run_clang_format, exelist, options.check)
+ if options.check:
+ cformat_ver = ExternalProgram('clang-format', exelist, silent=True).get_version()
+ else:
+ cformat_ver = None
+
+ return run_tool('clang-format', srcdir, builddir, run_clang_format, exelist, options.check, cformat_ver)
# 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 argparse
import subprocess
def run_clang_tidy(fname: Path, builddir: Path) -> subprocess.CompletedProcess:
return subprocess.run(['clang-tidy', '-p', str(builddir), str(fname)])
+def run_clang_tidy_fix(fname: Path, builddir: Path) -> subprocess.CompletedProcess:
+ return subprocess.run(['run-clang-tidy', '-fix', '-format', '-quiet', '-p', str(builddir), str(fname)])
+
def run(args: T.List[str]) -> int:
parser = argparse.ArgumentParser()
+ parser.add_argument('--fix', action='store_true')
parser.add_argument('sourcedir')
parser.add_argument('builddir')
options = parser.parse_args(args)
srcdir = Path(options.sourcedir)
builddir = Path(options.builddir)
- return run_tool('clang-tidy', srcdir, builddir, run_clang_tidy, builddir)
+ run_func = run_clang_tidy_fix if options.fix else run_clang_tidy
+ return run_tool('clang-tidy', srcdir, builddir, run_func, builddir)
# 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 os
import sys
#!/usr/bin/env python3
+from __future__ import annotations
import argparse
import subprocess
import typing as T
def run(argsv: T.List[str]) -> int:
- commands = [[]] # type: T.List[T.List[str]]
+ commands: T.List[T.List[str]] = [[]]
SEPARATOR = ';;;'
# Generate CMD parameters
commands += [[]]
continue
- i = i.replace('"', '') # Remove lefover quotes
+ i = i.replace('"', '') # Remove leftover quotes
commands[-1] += [i]
# Execute
# SPDX-License-Identifer: Apache-2.0
# Copyright © 2021 Intel Corporation
+from __future__ import annotations
"""Helper script to copy files at build time.
# 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
from mesonbuild import environment, mesonlib
outfiles = []
exitcode = 0
- (gcovr_exe, gcovr_version, lcov_exe, genhtml_exe, llvm_cov_exe) = environment.find_coverage_tools()
+ (gcovr_exe, gcovr_version, lcov_exe, lcov_version, genhtml_exe, llvm_cov_exe) = environment.find_coverage_tools()
# load config files for tools if available in the source tree
# - lcov requires manually specifying a per-project config
else:
lcov_config = []
+ if lcov_exe and mesonlib.version_compare(lcov_version, '>=2.0'):
+ lcov_exe_rc_branch_coverage = ['--rc', 'branch_coverage=1']
+ else:
+ lcov_exe_rc_branch_coverage = ['--rc', 'lcov_branch_coverage=1']
+
gcovr_config = ['-e', re.escape(subproject_root)]
# gcovr >= 4.2 requires a different syntax for out of source builds
initial_tracefile = covinfo + '.initial'
run_tracefile = covinfo + '.run'
raw_tracefile = covinfo + '.raw'
+ lcov_subpoject_exclude = []
+ if os.path.exists(subproject_root):
+ lcov_subpoject_exclude.append(os.path.join(subproject_root, '*'))
if use_llvm_cov:
# Create a shim to allow using llvm-cov as a gcov tool.
if mesonlib.is_windows():
'--capture',
'--output-file', run_tracefile,
'--no-checksum',
- '--rc', 'lcov_branch_coverage=1'] +
+ *lcov_exe_rc_branch_coverage] +
lcov_config +
gcov_tool_args)
# Join initial and test results.
subprocess.check_call([lcov_exe,
'-a', initial_tracefile,
'-a', run_tracefile,
- '--rc', 'lcov_branch_coverage=1',
+ *lcov_exe_rc_branch_coverage,
'-o', raw_tracefile] + lcov_config)
# Remove all directories outside the source_root from the covinfo
subprocess.check_call([lcov_exe,
'--extract', raw_tracefile,
os.path.join(source_root, '*'),
- '--rc', 'lcov_branch_coverage=1',
+ *lcov_exe_rc_branch_coverage,
'--output-file', covinfo] + lcov_config)
# Remove all directories inside subproject dir
subprocess.check_call([lcov_exe,
'--remove', covinfo,
- os.path.join(subproject_root, '*'),
- '--rc', 'lcov_branch_coverage=1',
+ *lcov_subpoject_exclude,
+ *lcov_exe_rc_branch_coverage,
'--output-file', covinfo] + lcov_config)
subprocess.check_call([genhtml_exe,
'--prefix', build_root,
'--html-details',
'--print-summary',
'-o', os.path.join(htmloutdir, 'index.html'),
- ])
+ ] + gcov_exe_args)
outfiles.append(('Html', pathlib.Path(htmloutdir, 'index.html')))
elif outputs:
print('lcov/genhtml or gcovr >= 3.3 needed to generate Html coverage report')
# 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 os, sys
import typing as T
# 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 sys
def __init__(self, bfile: str, verbose: bool = True) -> None:
self.bfile = bfile
self.verbose = verbose
- self.sections = [] # type: T.List[SectionHeader]
- self.dynamic = [] # type: T.List[DynamicEntry]
+ self.sections: T.List[SectionHeader] = []
+ self.dynamic: T.List[DynamicEntry] = []
self.open_bf(bfile)
try:
(self.ptrsize, self.is_le) = self.detect_elf_type()
def close_bf(self) -> None:
if self.bf is not None:
if self.bf_perms is not None:
- os.fchmod(self.bf.fileno(), self.bf_perms)
+ os.chmod(self.bf.fileno(), self.bf_perms)
self.bf_perms = None
self.bf.close()
self.bf = None
old_rpath = self.read_str()
# Some rpath entries may come from multiple sources.
# Only add each one once.
- new_rpaths = OrderedSet() # type: OrderedSet[bytes]
+ new_rpaths: OrderedSet[bytes] = OrderedSet()
if new_rpath:
new_rpaths.update(new_rpath.split(b':'))
if old_rpath:
sys.exit(msg)
# The linker does read-only string deduplication. If there is a
# string that shares a suffix with the rpath, they might get
- # dedupped. This means changing the rpath string might break something
+ # deduped. This means changing the rpath string might break something
# completely unrelated. This has already happened once with X.org.
# Thus we want to keep this change as small as possible to minimize
# the chance of obliterating other strings. It might still happen
# 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
'''CD into dir given as first argument and execute
the command given in the rest of the arguments.'''
return True
return False
+# Note: when adding arguments, please also add them to the completion
+# scripts in $MESONSRC/data/shell-completions/
def add_arguments(parser: 'argparse.ArgumentParser') -> None:
parser.add_argument('--debarch', default=None,
help='The dpkg architecture to generate.')
help='Generate a native compilation file.')
parser.add_argument('--system', default=None,
help='Define system for cross compilation.')
+ parser.add_argument('--subsystem', default=None,
+ help='Define subsystem for cross compilation.')
+ parser.add_argument('--kernel', default=None,
+ help='Define kernel for cross compilation.')
parser.add_argument('--cpu', default=None,
help='Define cpu for cross compilation.')
parser.add_argument('--cpu-family', default=None,
self.cmake: T.Dict[str, T.Union[str, T.List[str]]] = {}
self.system: T.Optional[str] = None
+ self.subsystem: T.Optional[str] = None
+ self.kernel: T.Optional[str] = None
self.cpu: T.Optional[str] = None
self.cpu_family: T.Optional[str] = None
self.endian: T.Optional[str] = None
if objcpp_link_args:
infos.link_args['objcpp'] = objcpp_link_args
-cpu_family_map = dict(mips64el="mips64",
- i686='x86')
-cpu_map = dict(armhf="arm7hlf",
- mips64el="mips64",
- powerpc64le="ppc64",
- )
+deb_cpu_family_map = {
+ 'mips64el': 'mips64',
+ 'i686': 'x86',
+ 'powerpc64le': 'ppc64',
+}
+
+deb_cpu_map = {
+ 'armhf': 'arm7hlf',
+ 'mips64el': 'mips64',
+ 'powerpc64le': 'ppc64',
+}
def deb_detect_cmake(infos: MachineInfo, data: T.Dict[str, str]) -> None:
- system_name_map = dict(linux="Linux", kfreebsd="kFreeBSD", hurd="GNU")
- system_processor_map = dict(arm='armv7l', mips64el='mips64', powerpc64le='ppc64le')
+ system_name_map = {'linux': 'Linux', 'kfreebsd': 'kFreeBSD', 'hurd': 'GNU'}
+ system_processor_map = {'arm': 'armv7l', 'mips64el': 'mips64', 'powerpc64le': 'ppc64le'}
infos.cmake["CMAKE_C_COMPILER"] = infos.compilers['c']
- infos.cmake["CMAKE_CXX_COMPILER"] = infos.compilers['cpp']
+ try:
+ infos.cmake["CMAKE_CXX_COMPILER"] = infos.compilers['cpp']
+ except KeyError:
+ pass
infos.cmake["CMAKE_SYSTEM_NAME"] = system_name_map[data['DEB_HOST_ARCH_OS']]
infos.cmake["CMAKE_SYSTEM_PROCESSOR"] = system_processor_map.get(data['DEB_HOST_GNU_CPU'],
data['DEB_HOST_GNU_CPU'])
pass
def detect_cross_debianlike(options: T.Any) -> MachineInfo:
- if options.debarch is None:
+ if options.debarch == 'auto':
cmd = ['dpkg-architecture']
else:
cmd = ['dpkg-architecture', '-a' + options.debarch]
data[k] = v
host_arch = data['DEB_HOST_GNU_TYPE']
host_os = data['DEB_HOST_ARCH_OS']
- host_cpu_family = cpu_family_map.get(data['DEB_HOST_GNU_CPU'],
- data['DEB_HOST_GNU_CPU'])
- host_cpu = cpu_map.get(data['DEB_HOST_ARCH'],
- data['DEB_HOST_ARCH'])
+ host_subsystem = host_os
+ host_kernel = 'linux'
+ host_cpu_family = deb_cpu_family_map.get(data['DEB_HOST_GNU_CPU'],
+ data['DEB_HOST_GNU_CPU'])
+ host_cpu = deb_cpu_map.get(data['DEB_HOST_ARCH'],
+ data['DEB_HOST_ARCH'])
host_endian = data['DEB_HOST_ARCH_ENDIAN']
compilerstems = [('c', 'gcc'),
except ValueError:
pass
try:
- infos.binaries['pkgconfig'] = locate_path("%s-pkg-config" % host_arch)
+ infos.binaries['pkg-config'] = locate_path("%s-pkg-config" % host_arch)
except ValueError:
pass # pkg-config is optional
try:
except ValueError:
pass
infos.system = host_os
+ infos.subsystem = host_subsystem
+ infos.kernel = host_kernel
infos.cpu_family = host_cpu_family
infos.cpu = host_cpu
infos.endian = host_endian
ofile.write(f"cpu_family = '{infos.cpu_family}'\n")
ofile.write(f"endian = '{infos.endian}'\n")
ofile.write(f"system = '{infos.system}'\n")
+ if infos.subsystem:
+ ofile.write(f"subsystem = '{infos.subsystem}'\n")
+ if infos.kernel:
+ ofile.write(f"kernel = '{infos.kernel}'\n")
+
os.replace(tmpfilename, ofilename)
def detect_language_args_from_envvars(langname: str, envvar_suffix: str = '') -> T.Tuple[T.List[str], T.List[str]]:
ldflags = tuple(shlex.split(os.environ.get('LDFLAGS' + envvar_suffix, '')))
- compile_args = shlex.split(os.environ.get(compilers.CFLAGS_MAPPING[langname] + envvar_suffix, ''))
+ compile_args = []
+ if langname in compilers.CFLAGS_MAPPING:
+ compile_args = shlex.split(os.environ.get(compilers.CFLAGS_MAPPING[langname] + envvar_suffix, ''))
if langname in compilers.LANGUAGES_USING_CPPFLAGS:
cppflags = tuple(shlex.split(os.environ.get('CPPFLAGS' + envvar_suffix, '')))
lang_compile_args = list(cppflags) + compile_args
compilerstr = os.environ.get(envvarname + envvar_suffix)
if not compilerstr:
continue
- compiler = shlex.split(compilerstr)
+ if os.path.exists(compilerstr):
+ compiler = [compilerstr]
+ else:
+ compiler = shlex.split(compilerstr)
infos.compilers[langname] = compiler
lang_compile_args, lang_link_args = detect_language_args_from_envvars(langname, envvar_suffix)
if lang_compile_args:
if binstr:
infos.binaries[binname] = shlex.split(binstr)
+def detect_properties_from_envvars(infos: MachineInfo, envvar_suffix: str = '') -> None:
+ var = os.environ.get('PKG_CONFIG_LIBDIR' + envvar_suffix)
+ if var is not None:
+ infos.properties['pkg_config_libdir'] = var
+ var = os.environ.get('PKG_CONFIG_SYSROOT_DIR' + envvar_suffix)
+ if var is not None:
+ infos.properties['sys_root'] = var
+
def detect_cross_system(infos: MachineInfo, options: T.Any) -> None:
- for optname in ('system', 'cpu', 'cpu_family', 'endian'):
+ for optname in ('system', 'subsystem', 'kernel', 'cpu', 'cpu_family', 'endian'):
v = getattr(options, optname)
if not v:
mlog.error(f'Cross property "{optname}" missing, set it with --{optname.replace("_", "-")}.')
print('Detecting cross environment via environment variables.')
infos = detect_compilers_from_envvars()
detect_cross_system(infos, options)
+ detect_binaries_from_envvars(infos)
+ detect_properties_from_envvars(infos)
return infos
def add_compiler_if_missing(infos: MachineInfo, langname: str, exe_names: T.List[str]) -> None:
detect_missing_native_compilers(infos)
detect_binaries_from_envvars(infos, esuffix)
detect_missing_native_binaries(infos)
+ detect_properties_from_envvars(infos, esuffix)
return infos
def run(options: T.Any) -> None:
# 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 os
import argparse
# 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 os
import argparse
# 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 sys, os
import subprocess
+from __future__ import annotations
+
import os
import shutil
import subprocess
parser.add_argument('--name')
parser.add_argument('--builddir')
parser.add_argument('--project-version')
+parser.add_argument('--docdir')
def run(argv: T.List[str]) -> int:
options, args = parser.parse_known_args(argv)
subenv = os.environ.copy()
- for ext_path in options.extra_extension_path:
- subenv['PYTHONPATH'] = subenv.get('PYTHONPATH', '') + ':' + ext_path
+ val = subenv.get('PYTHONPATH')
+ paths = [val] if val else []
+ subenv['PYTHONPATH'] = os.pathsep.join(paths + options.extra_extension_path)
res = subprocess.call(args, cwd=options.builddir, env=subenv)
if res != 0:
if options.install:
source_dir = os.path.join(options.builddir, options.install)
destdir = os.environ.get('DESTDIR', '')
- installdir = destdir_join(destdir,
- os.path.join(os.environ['MESON_INSTALL_PREFIX'],
- 'share/doc/', options.name, "html"))
+ installdir = destdir_join(destdir, options.docdir)
shutil.rmtree(installdir, ignore_errors=True)
shutil.copytree(source_dir, installdir)
# 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 os
import argparse
import subprocess
import tempfile
+import shlex
import shutil
import typing as T
shutil.copy(mo_file, tmp_mo_fname)
locale_mo_files.append(tmp_mo_fname)
- cmd = [itstool]
+ cmd = shlex.split(itstool)
if its_files:
for fname in its_files:
cmd.extend(['-i', fname])
# 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 os
import sys
if p.returncode == 0xc0000135:
# STATUS_DLL_NOT_FOUND on Windows indicating a common problem that is otherwise hard to diagnose
- raise FileNotFoundError('due to missing DLLs')
+ strerror = 'Failed to run due to missing DLLs, with path: ' + child_env['PATH']
+ raise FileNotFoundError(p.returncode, strerror, cmd_args)
if p.returncode != 0:
if exe.pickled:
# 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 argparse
import subprocess
--- /dev/null
+# Copyright 2016 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.
+
+# ignore all lints for this file, since it is run by python2 as well
+
+# type: ignore
+# pylint: disable=deprecated-module
+
+import json, os, subprocess, sys
+from compileall import compile_file
+
+quiet = int(os.environ.get('MESON_INSTALL_QUIET', 0))
+
+def compileall(files):
+ for f in files:
+ # f is prefixed by {py_xxxxlib}, both variants are 12 chars
+ # the key is the middle 10 chars of the prefix
+ key = f[1:11].upper()
+ f = f[12:]
+
+ ddir = None
+ fullpath = os.environ['MESON_INSTALL_DESTDIR_'+key] + f
+ f = os.environ['MESON_INSTALL_'+key] + f
+
+ if fullpath != f:
+ ddir = os.path.dirname(f)
+
+ if os.path.isdir(fullpath):
+ for root, _, files in os.walk(fullpath):
+ if ddir is not None:
+ ddir = root.replace(fullpath, f, 1)
+ for dirf in files:
+ if dirf.endswith('.py'):
+ fullpath = os.path.join(root, dirf)
+ compile_file(fullpath, ddir, force=True, quiet=quiet)
+ else:
+ compile_file(fullpath, ddir, force=True, quiet=quiet)
+
+def run(manifest):
+ data_file = os.path.join(os.path.dirname(__file__), manifest)
+ with open(data_file, 'rb') as f:
+ dat = json.load(f)
+ compileall(dat)
+
+if __name__ == '__main__':
+ manifest = sys.argv[1]
+ run(manifest)
+ if len(sys.argv) > 2:
+ optlevel = int(sys.argv[2])
+ # python2 only needs one or the other
+ if optlevel == 1 or (sys.version_info >= (3,) and optlevel > 0):
+ subprocess.check_call([sys.executable, '-O'] + sys.argv[:2])
+ if optlevel == 2:
+ subprocess.check_call([sys.executable, '-OO'] + sys.argv[:2])
--- /dev/null
+#!/usr/bin/env python
+
+# ignore all lints for this file, since it is run by python2 as well
+
+# type: ignore
+# pylint: disable=deprecated-module
+
+import sys
+
+# do not inject mesonbuild.scripts
+# python -P would work too, but is exclusive to >=3.11
+if sys.path[0].endswith('scripts'):
+ del sys.path[0]
+
+import json, os, sysconfig
+
+def get_distutils_paths(scheme=None, prefix=None):
+ import distutils.dist
+ distribution = distutils.dist.Distribution()
+ install_cmd = distribution.get_command_obj('install')
+ if prefix is not None:
+ install_cmd.prefix = prefix
+ if scheme:
+ install_cmd.select_scheme(scheme)
+ install_cmd.finalize_options()
+ return {
+ 'data': install_cmd.install_data,
+ 'include': os.path.dirname(install_cmd.install_headers),
+ 'platlib': install_cmd.install_platlib,
+ 'purelib': install_cmd.install_purelib,
+ 'scripts': install_cmd.install_scripts,
+ }
+
+# On Debian derivatives, the Python interpreter shipped by the distribution uses
+# a custom install scheme, deb_system, for the system install, and changes the
+# default scheme to a custom one pointing to /usr/local and replacing
+# site-packages with dist-packages.
+# See https://github.com/mesonbuild/meson/issues/8739.
+#
+# We should be using sysconfig, but before 3.10.3, Debian only patches distutils.
+# So we may end up falling back.
+
+def get_install_paths():
+ if sys.version_info >= (3, 10):
+ scheme = sysconfig.get_default_scheme()
+ else:
+ scheme = sysconfig._get_default_scheme()
+
+ if sys.version_info >= (3, 10, 3):
+ if 'deb_system' in sysconfig.get_scheme_names():
+ scheme = 'deb_system'
+ else:
+ import distutils.command.install
+ if 'deb_system' in distutils.command.install.INSTALL_SCHEMES:
+ paths = get_distutils_paths(scheme='deb_system')
+ install_paths = get_distutils_paths(scheme='deb_system', prefix='')
+ return paths, install_paths
+
+ paths = sysconfig.get_paths(scheme=scheme)
+ empty_vars = {'base': '', 'platbase': '', 'installed_base': ''}
+ install_paths = sysconfig.get_paths(scheme=scheme, vars=empty_vars)
+ return paths, install_paths
+
+paths, install_paths = get_install_paths()
+
+def links_against_libpython():
+ # on versions supporting python-embed.pc, this is the non-embed lib
+ #
+ # PyPy is not yet up to 3.12 and work is still pending to export the
+ # relevant information (it doesn't automatically provide arbitrary
+ # Makefile vars)
+ if sys.version_info >= (3, 8) and not is_pypy:
+ variables = sysconfig.get_config_vars()
+ return bool(variables.get('LIBPYTHON', 'yes'))
+ else:
+ from distutils.core import Distribution, Extension
+ cmd = Distribution().get_command_obj('build_ext')
+ cmd.ensure_finalized()
+ return bool(cmd.get_libraries(Extension('dummy', [])))
+
+variables = sysconfig.get_config_vars()
+variables.update({'base_prefix': getattr(sys, 'base_prefix', sys.prefix)})
+
+is_pypy = '__pypy__' in sys.builtin_module_names
+
+if sys.version_info < (3, 0):
+ suffix = variables.get('SO')
+elif sys.version_info < (3, 8, 7):
+ # https://bugs.python.org/issue?@action=redirect&bpo=39825
+ from distutils.sysconfig import get_config_var
+ suffix = get_config_var('EXT_SUFFIX')
+else:
+ suffix = variables.get('EXT_SUFFIX')
+
+limited_api_suffix = None
+if sys.version_info >= (3, 2):
+ try:
+ from importlib.machinery import EXTENSION_SUFFIXES
+ limited_api_suffix = EXTENSION_SUFFIXES[1]
+ except Exception:
+ pass
+
+# pypy supports modules targetting the limited api but
+# does not use a special suffix to distinguish them:
+# https://doc.pypy.org/en/latest/cpython_differences.html#permitted-abi-tags-in-extensions
+if is_pypy:
+ limited_api_suffix = suffix
+
+print(json.dumps({
+ 'variables': variables,
+ 'paths': paths,
+ 'sysconfig_paths': sysconfig.get_paths(),
+ 'install_paths': install_paths,
+ 'version': sysconfig.get_python_version(),
+ 'platform': sysconfig.get_platform(),
+ 'is_pypy': is_pypy,
+ 'is_venv': sys.prefix != variables['base_prefix'],
+ 'link_libpython': links_against_libpython(),
+ 'suffix': suffix,
+ 'limited_api_suffix': limited_api_suffix,
+}))
# 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 sys, os
import pickle, subprocess
from concurrent.futures import ThreadPoolExecutor
from ..compilers import lang_suffixes
-from ..mesonlib import Popen_safe
+from ..mesonlib import quiet_git
import typing as T
if T.TYPE_CHECKING:
if patterns:
globs = [srcdir.glob(p) for p in patterns]
else:
- p, o, _ = Popen_safe(['git', 'ls-files'], cwd=srcdir)
- if p.returncode == 0:
+ r, o = quiet_git(['ls-files'], srcdir)
+ if r:
globs = [[Path(srcdir, f) for f in o.splitlines()]]
else:
globs = [srcdir.glob('**/*')]
# 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 subprocess
import shutil
from ast import literal_eval
import os
-def scanbuild(exelist: T.List[str], srcdir: Path, blddir: Path, privdir: Path, logdir: Path, args: T.List[str]) -> int:
+def scanbuild(exelist: T.List[str], srcdir: Path, blddir: Path, privdir: Path, logdir: Path, subprojdir: Path, args: T.List[str]) -> int:
# In case of problems leave the temp directory around
# so it can be debugged.
scandir = tempfile.mkdtemp(dir=str(privdir))
meson_cmd = exelist + args
- build_cmd = exelist + ['-o', str(logdir)] + detect_ninja() + ['-C', scandir]
+ build_cmd = exelist + ['--exclude', str(subprojdir), '-o', str(logdir)] + detect_ninja() + ['-C', scandir]
rc = subprocess.call(meson_cmd + [str(srcdir), scandir])
if rc != 0:
return rc
def run(args: T.List[str]) -> int:
srcdir = Path(args[0])
bldpath = Path(args[1])
+ subprojdir = srcdir / Path(args[2])
blddir = args[1]
- meson_cmd = args[2:]
+ meson_cmd = args[3:]
privdir = bldpath / 'meson-private'
logdir = bldpath / 'meson-logs' / 'scanbuild'
shutil.rmtree(str(logdir), ignore_errors=True)
print('Could not execute scan-build "%s"' % ' '.join(exelist))
return 1
- return scanbuild(exelist, srcdir, bldpath, privdir, logdir, meson_cmd)
+ return scanbuild(exelist, srcdir, bldpath, privdir, logdir, subprojdir, meson_cmd)
# This file is basically a reimplementation of
# http://cgit.freedesktop.org/libreoffice/core/commit/?id=3213cd54b76bc80a6f0516aac75a48ff3b2ad67c
+from __future__ import annotations
import typing as T
import os, sys
# 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 os
import subprocess
+from __future__ import annotations
+
import sys
import json
import typing as T
+
from . import meson_exe
# This script is used by run_unittests.py to verify we don't load too many
# 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 os
import typing as T
# 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 sys, os, subprocess, re
import typing as T
new_data = f.read().replace(replace_string, new_string)
if os.path.exists(outfile):
with open(outfile, encoding='utf-8') as f:
- needs_update = (f.read() != new_data)
+ needs_update = f.read() != new_data
else:
needs_update = True
if needs_update:
+from __future__ import annotations
+
import argparse
import subprocess
import typing as T
# 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 mesonbuild.templates.sampleimpl import SampleImpl
-import re
+from __future__ import annotations
+
+from mesonbuild.templates.sampleimpl import FileHeaderImpl
hello_cpp_template = '''#include <iostream>
'''
-class CppProject(SampleImpl):
- def __init__(self, options):
- super().__init__()
- self.name = options.name
- self.version = options.version
-
- def create_executable(self) -> None:
- lowercase_token = re.sub(r'[^a-z0-9]', '_', self.name.lower())
- source_name = lowercase_token + '.cpp'
- open(source_name, 'w', encoding='utf-8').write(hello_cpp_template.format(project_name=self.name))
- open('meson.build', 'w', encoding='utf-8').write(
- hello_cpp_meson_template.format(project_name=self.name,
- exe_name=lowercase_token,
- source_name=source_name,
- version=self.version))
-
- def create_library(self) -> None:
- lowercase_token = re.sub(r'[^a-z0-9]', '_', self.name.lower())
- uppercase_token = lowercase_token.upper()
- class_name = uppercase_token[0] + lowercase_token[1:]
- test_exe_name = lowercase_token + '_test'
- namespace = lowercase_token
- lib_hpp_name = lowercase_token + '.hpp'
- lib_cpp_name = lowercase_token + '.cpp'
- test_cpp_name = lowercase_token + '_test.cpp'
- kwargs = {'utoken': uppercase_token,
- 'ltoken': lowercase_token,
- 'header_dir': lowercase_token,
- 'class_name': class_name,
- 'namespace': namespace,
- 'header_file': lib_hpp_name,
- 'source_file': lib_cpp_name,
- 'test_source_file': test_cpp_name,
- 'test_exe_name': test_exe_name,
- 'project_name': self.name,
- 'lib_name': lowercase_token,
- 'test_name': lowercase_token,
- 'version': self.version,
- }
- open(lib_hpp_name, 'w', encoding='utf-8').write(lib_hpp_template.format(**kwargs))
- open(lib_cpp_name, 'w', encoding='utf-8').write(lib_cpp_template.format(**kwargs))
- open(test_cpp_name, 'w', encoding='utf-8').write(lib_cpp_test_template.format(**kwargs))
- open('meson.build', 'w', encoding='utf-8').write(lib_cpp_meson_template.format(**kwargs))
+class CppProject(FileHeaderImpl):
+
+ source_ext = 'cpp'
+ header_ext = 'hpp'
+ exe_template = hello_cpp_template
+ exe_meson_template = hello_cpp_meson_template
+ lib_template = lib_cpp_template
+ lib_header_template = lib_hpp_template
+ lib_test_template = lib_cpp_test_template
+ lib_meson_template = lib_cpp_meson_template
# 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 mesonbuild.templates.sampleimpl import SampleImpl
-import re
+from __future__ import annotations
+
+from mesonbuild.templates.sampleimpl import ClassImpl
hello_cs_template = '''using System;
'''
-class CSharpProject(SampleImpl):
- def __init__(self, options):
- super().__init__()
- self.name = options.name
- self.version = options.version
-
- def create_executable(self) -> None:
- lowercase_token = re.sub(r'[^a-z0-9]', '_', self.name.lower())
- uppercase_token = lowercase_token.upper()
- class_name = uppercase_token[0] + lowercase_token[1:]
- source_name = uppercase_token[0] + lowercase_token[1:] + '.cs'
- open(source_name, 'w', encoding='utf-8').write(
- hello_cs_template.format(project_name=self.name,
- class_name=class_name))
- open('meson.build', 'w', encoding='utf-8').write(
- hello_cs_meson_template.format(project_name=self.name,
- exe_name=self.name,
- source_name=source_name,
- version=self.version))
-
- def create_library(self) -> None:
- lowercase_token = re.sub(r'[^a-z0-9]', '_', self.name.lower())
- uppercase_token = lowercase_token.upper()
- class_name = uppercase_token[0] + lowercase_token[1:]
- class_test = uppercase_token[0] + lowercase_token[1:] + '_test'
- project_test = lowercase_token + '_test'
- lib_cs_name = uppercase_token[0] + lowercase_token[1:] + '.cs'
- test_cs_name = uppercase_token[0] + lowercase_token[1:] + '_test.cs'
- kwargs = {'utoken': uppercase_token,
- 'ltoken': lowercase_token,
- 'class_test': class_test,
- 'class_name': class_name,
- 'source_file': lib_cs_name,
- 'test_source_file': test_cs_name,
- 'test_exe_name': project_test,
- 'project_name': self.name,
- 'lib_name': lowercase_token,
- 'test_name': lowercase_token,
- 'version': self.version,
- }
- open(lib_cs_name, 'w', encoding='utf-8').write(lib_cs_template.format(**kwargs))
- open(test_cs_name, 'w', encoding='utf-8').write(lib_cs_test_template.format(**kwargs))
- open('meson.build', 'w', encoding='utf-8').write(lib_cs_meson_template.format(**kwargs))
+class CSharpProject(ClassImpl):
+
+ source_ext = 'cs'
+ exe_template = hello_cs_template
+ exe_meson_template = hello_cs_meson_template
+ lib_template = lib_cs_template
+ lib_test_template = lib_cs_test_template
+ lib_meson_template = lib_cs_meson_template
# 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 mesonbuild.templates.sampleimpl import SampleImpl
-import re
+from __future__ import annotations
+
+from mesonbuild.templates.sampleimpl import FileHeaderImpl
lib_h_template = '''#pragma once
'''
-class CProject(SampleImpl):
- def __init__(self, options):
- super().__init__()
- self.name = options.name
- self.version = options.version
-
- def create_executable(self) -> None:
- lowercase_token = re.sub(r'[^a-z0-9]', '_', self.name.lower())
- source_name = lowercase_token + '.c'
- open(source_name, 'w', encoding='utf-8').write(hello_c_template.format(project_name=self.name))
- open('meson.build', 'w', encoding='utf-8').write(
- hello_c_meson_template.format(project_name=self.name,
- exe_name=lowercase_token,
- source_name=source_name,
- version=self.version))
-
- def create_library(self) -> None:
- lowercase_token = re.sub(r'[^a-z0-9]', '_', self.name.lower())
- uppercase_token = lowercase_token.upper()
- function_name = lowercase_token[0:3] + '_func'
- test_exe_name = lowercase_token + '_test'
- lib_h_name = lowercase_token + '.h'
- lib_c_name = lowercase_token + '.c'
- test_c_name = lowercase_token + '_test.c'
- kwargs = {'utoken': uppercase_token,
- 'ltoken': lowercase_token,
- 'header_dir': lowercase_token,
- 'function_name': function_name,
- 'header_file': lib_h_name,
- 'source_file': lib_c_name,
- 'test_source_file': test_c_name,
- 'test_exe_name': test_exe_name,
- 'project_name': self.name,
- 'lib_name': lowercase_token,
- 'test_name': lowercase_token,
- 'version': self.version,
- }
- open(lib_h_name, 'w', encoding='utf-8').write(lib_h_template.format(**kwargs))
- open(lib_c_name, 'w', encoding='utf-8').write(lib_c_template.format(**kwargs))
- open(test_c_name, 'w', encoding='utf-8').write(lib_c_test_template.format(**kwargs))
- open('meson.build', 'w', encoding='utf-8').write(lib_c_meson_template.format(**kwargs))
+class CProject(FileHeaderImpl):
+
+ source_ext = 'c'
+ header_ext = 'h'
+ exe_template = hello_c_template
+ exe_meson_template = hello_c_meson_template
+ lib_template = lib_c_template
+ lib_header_template = lib_h_template
+ lib_test_template = lib_c_test_template
+ lib_meson_template = lib_c_meson_template
# 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 mesonbuild.templates.sampleimpl import SampleImpl
-import re
+from __future__ import annotations
+
+from mesonbuild.templates.sampleimpl import FileHeaderImpl
hello_cuda_template = '''#include <iostream>
'''
-class CudaProject(SampleImpl):
- def __init__(self, options):
- super().__init__()
- self.name = options.name
- self.version = options.version
-
- def create_executable(self) -> None:
- lowercase_token = re.sub(r'[^a-z0-9]', '_', self.name.lower())
- source_name = lowercase_token + '.cu'
- open(source_name, 'w', encoding='utf-8').write(hello_cuda_template.format(project_name=self.name))
- open('meson.build', 'w', encoding='utf-8').write(
- hello_cuda_meson_template.format(project_name=self.name,
- exe_name=lowercase_token,
- source_name=source_name,
- version=self.version))
-
- def create_library(self) -> None:
- lowercase_token = re.sub(r'[^a-z0-9]', '_', self.name.lower())
- uppercase_token = lowercase_token.upper()
- class_name = uppercase_token[0] + lowercase_token[1:]
- test_exe_name = lowercase_token + '_test'
- namespace = lowercase_token
- lib_h_name = lowercase_token + '.h'
- lib_cuda_name = lowercase_token + '.cu'
- test_cuda_name = lowercase_token + '_test.cu'
- kwargs = {'utoken': uppercase_token,
- 'ltoken': lowercase_token,
- 'header_dir': lowercase_token,
- 'class_name': class_name,
- 'namespace': namespace,
- 'header_file': lib_h_name,
- 'source_file': lib_cuda_name,
- 'test_source_file': test_cuda_name,
- 'test_exe_name': test_exe_name,
- 'project_name': self.name,
- 'lib_name': lowercase_token,
- 'test_name': lowercase_token,
- 'version': self.version,
- }
- open(lib_h_name, 'w', encoding='utf-8').write(lib_h_template.format(**kwargs))
- open(lib_cuda_name, 'w', encoding='utf-8').write(lib_cuda_template.format(**kwargs))
- open(test_cuda_name, 'w', encoding='utf-8').write(lib_cuda_test_template.format(**kwargs))
- open('meson.build', 'w', encoding='utf-8').write(lib_cuda_meson_template.format(**kwargs))
+class CudaProject(FileHeaderImpl):
+
+ source_ext = 'cu'
+ header_ext = 'h'
+ exe_template = hello_cuda_template
+ exe_meson_template = hello_cuda_meson_template
+ lib_template = lib_cuda_template
+ lib_header_template = lib_h_template
+ lib_test_template = lib_cuda_test_template
+ lib_meson_template = lib_cuda_meson_template
# 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 mesonbuild.templates.sampleimpl import SampleImpl
-import re
+from __future__ import annotations
+
+from mesonbuild.templates.sampleimpl import FileImpl
+
+import typing as T
hello_d_template = '''module main;
'''
-class DlangProject(SampleImpl):
- def __init__(self, options):
- super().__init__()
- self.name = options.name
- self.version = options.version
-
- def create_executable(self) -> None:
- lowercase_token = re.sub(r'[^a-z0-9]', '_', self.name.lower())
- source_name = lowercase_token + '.d'
- open(source_name, 'w', encoding='utf-8').write(hello_d_template.format(project_name=self.name))
- open('meson.build', 'w', encoding='utf-8').write(
- hello_d_meson_template.format(project_name=self.name,
- exe_name=lowercase_token,
- source_name=source_name,
- version=self.version))
-
- def create_library(self) -> None:
- lowercase_token = re.sub(r'[^a-z0-9]', '_', self.name.lower())
- uppercase_token = lowercase_token.upper()
- function_name = lowercase_token[0:3] + '_func'
- test_exe_name = lowercase_token + '_test'
- lib_m_name = lowercase_token
- lib_d_name = lowercase_token + '.d'
- test_d_name = lowercase_token + '_test.d'
- kwargs = {'utoken': uppercase_token,
- 'ltoken': lowercase_token,
- 'header_dir': lowercase_token,
- 'function_name': function_name,
- 'module_file': lib_m_name,
- 'source_file': lib_d_name,
- 'test_source_file': test_d_name,
- 'test_exe_name': test_exe_name,
- 'project_name': self.name,
- 'lib_name': lowercase_token,
- 'test_name': lowercase_token,
- 'version': self.version,
- }
- open(lib_d_name, 'w', encoding='utf-8').write(lib_d_template.format(**kwargs))
- open(test_d_name, 'w', encoding='utf-8').write(lib_d_test_template.format(**kwargs))
- open('meson.build', 'w', encoding='utf-8').write(lib_d_meson_template.format(**kwargs))
+class DlangProject(FileImpl):
+
+ source_ext = 'd'
+ exe_template = hello_d_template
+ exe_meson_template = hello_d_meson_template
+ lib_template = lib_d_template
+ lib_test_template = lib_d_test_template
+ lib_meson_template = lib_d_meson_template
+
+ def lib_kwargs(self) -> T.Dict[str, str]:
+ kwargs = super().lib_kwargs()
+ kwargs['module_file'] = self.lowercase_token
+ return kwargs
# 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 mesonbuild.templates.sampleimpl import SampleImpl
-import re
+from __future__ import annotations
+
+from mesonbuild.templates.sampleimpl import FileImpl
lib_fortran_template = '''
! This procedure will not be exported and is not
'''
-class FortranProject(SampleImpl):
- def __init__(self, options):
- super().__init__()
- self.name = options.name
- self.version = options.version
-
- def create_executable(self) -> None:
- lowercase_token = re.sub(r'[^a-z0-9]', '_', self.name.lower())
- source_name = lowercase_token + '.f90'
- open(source_name, 'w', encoding='utf-8').write(hello_fortran_template.format(project_name=self.name))
- open('meson.build', 'w', encoding='utf-8').write(
- hello_fortran_meson_template.format(project_name=self.name,
- exe_name=lowercase_token,
- source_name=source_name,
- version=self.version))
-
- def create_library(self) -> None:
- lowercase_token = re.sub(r'[^a-z0-9]', '_', self.name.lower())
- uppercase_token = lowercase_token.upper()
- function_name = lowercase_token[0:3] + '_func'
- test_exe_name = lowercase_token + '_test'
- lib_fortran_name = lowercase_token + '.f90'
- test_fortran_name = lowercase_token + '_test.f90'
- kwargs = {'utoken': uppercase_token,
- 'ltoken': lowercase_token,
- 'header_dir': lowercase_token,
- 'function_name': function_name,
- 'source_file': lib_fortran_name,
- 'test_source_file': test_fortran_name,
- 'test_exe_name': test_exe_name,
- 'project_name': self.name,
- 'lib_name': lowercase_token,
- 'test_name': lowercase_token,
- 'version': self.version,
- }
- open(lib_fortran_name, 'w', encoding='utf-8').write(lib_fortran_template.format(**kwargs))
- open(test_fortran_name, 'w', encoding='utf-8').write(lib_fortran_test_template.format(**kwargs))
- open('meson.build', 'w', encoding='utf-8').write(lib_fortran_meson_template.format(**kwargs))
+class FortranProject(FileImpl):
+
+ source_ext = 'f90'
+ exe_template = hello_fortran_template
+ exe_meson_template = hello_fortran_meson_template
+ lib_template = lib_fortran_template
+ lib_meson_template = lib_fortran_meson_template
+ lib_test_template = lib_fortran_test_template
# 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 mesonbuild.templates.sampleimpl import SampleImpl
-import re
+from __future__ import annotations
+
+from mesonbuild.templates.sampleimpl import ClassImpl
hello_java_template = '''
'''
-class JavaProject(SampleImpl):
- def __init__(self, options):
- super().__init__()
- self.name = options.name
- self.version = options.version
-
- def create_executable(self) -> None:
- lowercase_token = re.sub(r'[^a-z0-9]', '_', self.name.lower())
- uppercase_token = lowercase_token.upper()
- class_name = uppercase_token[0] + lowercase_token[1:]
- source_name = uppercase_token[0] + lowercase_token[1:] + '.java'
- open(source_name, 'w', encoding='utf-8').write(
- hello_java_template.format(project_name=self.name,
- class_name=class_name))
- open('meson.build', 'w', encoding='utf-8').write(
- hello_java_meson_template.format(project_name=self.name,
- exe_name=class_name,
- source_name=source_name,
- version=self.version))
-
- def create_library(self) -> None:
- lowercase_token = re.sub(r'[^a-z0-9]', '_', self.name.lower())
- uppercase_token = lowercase_token.upper()
- class_name = uppercase_token[0] + lowercase_token[1:]
- class_test = uppercase_token[0] + lowercase_token[1:] + '_test'
- lib_java_name = uppercase_token[0] + lowercase_token[1:] + '.java'
- test_java_name = uppercase_token[0] + lowercase_token[1:] + '_test.java'
- kwargs = {'utoken': uppercase_token,
- 'ltoken': lowercase_token,
- 'class_test': class_test,
- 'class_name': class_name,
- 'source_file': lib_java_name,
- 'test_source_file': test_java_name,
- 'project_name': self.name,
- 'lib_name': lowercase_token,
- 'test_name': lowercase_token,
- 'version': self.version,
- }
- open(lib_java_name, 'w', encoding='utf-8').write(lib_java_template.format(**kwargs))
- open(test_java_name, 'w', encoding='utf-8').write(lib_java_test_template.format(**kwargs))
- open('meson.build', 'w', encoding='utf-8').write(lib_java_meson_template.format(**kwargs))
+class JavaProject(ClassImpl):
+
+ source_ext = 'java'
+ exe_template = hello_java_template
+ exe_meson_template = hello_java_meson_template
+ lib_template = lib_java_template
+ lib_test_template = lib_java_test_template
+ lib_meson_template = lib_java_meson_template
# 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 argparse
+import typing as T
+
+if T.TYPE_CHECKING:
+ from ..minit import Arguments
meson_executable_template = '''project('{project_name}', {language},
version : '{version}',
'''
-def create_meson_build(options: argparse.Namespace) -> None:
+def create_meson_build(options: Arguments) -> None:
if options.type != 'executable':
raise SystemExit('\nGenerating a meson.build file from existing sources is\n'
'supported only for project type "executable".\n'
# 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 mesonbuild.templates.sampleimpl import SampleImpl
-import re
+from __future__ import annotations
+
+from mesonbuild.templates.sampleimpl import FileHeaderImpl
lib_h_template = '''#pragma once
'''
-class ObjCppProject(SampleImpl):
- def __init__(self, options):
- super().__init__()
- self.name = options.name
- self.version = options.version
-
- def create_executable(self) -> None:
- lowercase_token = re.sub(r'[^a-z0-9]', '_', self.name.lower())
- source_name = lowercase_token + '.mm'
- open(source_name, 'w', encoding='utf-8').write(hello_objcpp_template.format(project_name=self.name))
- open('meson.build', 'w', encoding='utf-8').write(
- hello_objcpp_meson_template.format(project_name=self.name,
- exe_name=lowercase_token,
- source_name=source_name,
- version=self.version))
-
- def create_library(self) -> None:
- lowercase_token = re.sub(r'[^a-z0-9]', '_', self.name.lower())
- uppercase_token = lowercase_token.upper()
- function_name = lowercase_token[0:3] + '_func'
- test_exe_name = lowercase_token + '_test'
- lib_h_name = lowercase_token + '.h'
- lib_objcpp_name = lowercase_token + '.mm'
- test_objcpp_name = lowercase_token + '_test.mm'
- kwargs = {'utoken': uppercase_token,
- 'ltoken': lowercase_token,
- 'header_dir': lowercase_token,
- 'function_name': function_name,
- 'header_file': lib_h_name,
- 'source_file': lib_objcpp_name,
- 'test_source_file': test_objcpp_name,
- 'test_exe_name': test_exe_name,
- 'project_name': self.name,
- 'lib_name': lowercase_token,
- 'test_name': lowercase_token,
- 'version': self.version,
- }
- open(lib_h_name, 'w', encoding='utf-8').write(lib_h_template.format(**kwargs))
- open(lib_objcpp_name, 'w', encoding='utf-8').write(lib_objcpp_template.format(**kwargs))
- open(test_objcpp_name, 'w', encoding='utf-8').write(lib_objcpp_test_template.format(**kwargs))
- open('meson.build', 'w', encoding='utf-8').write(lib_objcpp_meson_template.format(**kwargs))
+class ObjCppProject(FileHeaderImpl):
+
+ source_ext = 'mm'
+ header_ext = 'h'
+ exe_template = hello_objcpp_template
+ exe_meson_template = hello_objcpp_meson_template
+ lib_template = lib_objcpp_template
+ lib_header_template = lib_h_template
+ lib_test_template = lib_objcpp_test_template
+ lib_meson_template = lib_objcpp_meson_template
# 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 mesonbuild.templates.sampleimpl import SampleImpl
-import re
+from __future__ import annotations
+
+from mesonbuild.templates.sampleimpl import FileHeaderImpl
lib_h_template = '''#pragma once
'''
-class ObjCProject(SampleImpl):
- def __init__(self, options):
- super().__init__()
- self.name = options.name
- self.version = options.version
-
- def create_executable(self) -> None:
- lowercase_token = re.sub(r'[^a-z0-9]', '_', self.name.lower())
- source_name = lowercase_token + '.m'
- open(source_name, 'w', encoding='utf-8').write(hello_objc_template.format(project_name=self.name))
- open('meson.build', 'w', encoding='utf-8').write(
- hello_objc_meson_template.format(project_name=self.name,
- exe_name=lowercase_token,
- source_name=source_name,
- version=self.version))
-
- def create_library(self) -> None:
- lowercase_token = re.sub(r'[^a-z0-9]', '_', self.name.lower())
- uppercase_token = lowercase_token.upper()
- function_name = lowercase_token[0:3] + '_func'
- test_exe_name = lowercase_token + '_test'
- lib_h_name = lowercase_token + '.h'
- lib_objc_name = lowercase_token + '.m'
- test_objc_name = lowercase_token + '_test.m'
- kwargs = {'utoken': uppercase_token,
- 'ltoken': lowercase_token,
- 'header_dir': lowercase_token,
- 'function_name': function_name,
- 'header_file': lib_h_name,
- 'source_file': lib_objc_name,
- 'test_source_file': test_objc_name,
- 'test_exe_name': test_exe_name,
- 'project_name': self.name,
- 'lib_name': lowercase_token,
- 'test_name': lowercase_token,
- 'version': self.version,
- }
- open(lib_h_name, 'w', encoding='utf-8').write(lib_h_template.format(**kwargs))
- open(lib_objc_name, 'w', encoding='utf-8').write(lib_objc_template.format(**kwargs))
- open(test_objc_name, 'w', encoding='utf-8').write(lib_objc_test_template.format(**kwargs))
- open('meson.build', 'w', encoding='utf-8').write(lib_objc_meson_template.format(**kwargs))
+class ObjCProject(FileHeaderImpl):
+
+ source_ext = 'm'
+ header_ext = 'h'
+ exe_template = hello_objc_template
+ exe_meson_template = hello_objc_meson_template
+ lib_template = lib_objc_template
+ lib_header_template = lib_h_template
+ lib_test_template = lib_objc_test_template
+ lib_meson_template = lib_objc_meson_template
# 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 mesonbuild.templates.sampleimpl import SampleImpl
-import re
+from __future__ import annotations
+
+import typing as T
+
+from mesonbuild.templates.sampleimpl import FileImpl
lib_rust_template = '''#![crate_name = "{crate_file}"]
'''
-class RustProject(SampleImpl):
- def __init__(self, options):
- super().__init__()
- self.name = options.name
- self.version = options.version
-
- def create_executable(self) -> None:
- lowercase_token = re.sub(r'[^a-z0-9]', '_', self.name.lower())
- source_name = lowercase_token + '.rs'
- open(source_name, 'w', encoding='utf-8').write(hello_rust_template.format(project_name=self.name))
- open('meson.build', 'w', encoding='utf-8').write(
- hello_rust_meson_template.format(project_name=self.name,
- exe_name=lowercase_token,
- source_name=source_name,
- version=self.version))
-
- def create_library(self) -> None:
- lowercase_token = re.sub(r'[^a-z0-9]', '_', self.name.lower())
- uppercase_token = lowercase_token.upper()
- function_name = lowercase_token[0:3] + '_func'
- test_exe_name = lowercase_token + '_test'
- lib_crate_name = lowercase_token
- lib_rs_name = lowercase_token + '.rs'
- test_rs_name = lowercase_token + '_test.rs'
- kwargs = {'utoken': uppercase_token,
- 'ltoken': lowercase_token,
- 'header_dir': lowercase_token,
- 'function_name': function_name,
- 'crate_file': lib_crate_name,
- 'source_file': lib_rs_name,
- 'test_source_file': test_rs_name,
- 'test_exe_name': test_exe_name,
- 'project_name': self.name,
- 'lib_name': lowercase_token,
- 'test_name': lowercase_token,
- 'version': self.version,
- }
- open(lib_rs_name, 'w', encoding='utf-8').write(lib_rust_template.format(**kwargs))
- open(test_rs_name, 'w', encoding='utf-8').write(lib_rust_test_template.format(**kwargs))
- open('meson.build', 'w', encoding='utf-8').write(lib_rust_meson_template.format(**kwargs))
+class RustProject(FileImpl):
+
+ source_ext = 'rs'
+ exe_template = hello_rust_template
+ exe_meson_template = hello_rust_meson_template
+ lib_template = lib_rust_template
+ lib_test_template = lib_rust_test_template
+ lib_meson_template = lib_rust_meson_template
+
+ def lib_kwargs(self) -> T.Dict[str, str]:
+ kwargs = super().lib_kwargs()
+ kwargs['crate_file'] = self.lowercase_token
+ return kwargs
# 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 typing as T
+
from mesonbuild.templates.valatemplates import ValaProject
from mesonbuild.templates.fortrantemplates import FortranProject
from mesonbuild.templates.objcpptemplates import ObjCppProject
from mesonbuild.templates.cpptemplates import CppProject
from mesonbuild.templates.cstemplates import CSharpProject
from mesonbuild.templates.ctemplates import CProject
-from mesonbuild.templates.sampleimpl import SampleImpl
-
-import argparse
-
-def sameple_generator(options: argparse.Namespace) -> SampleImpl:
- return {
- 'c': CProject,
- 'cpp': CppProject,
- 'cs': CSharpProject,
- 'cuda': CudaProject,
- 'objc': ObjCProject,
- 'objcpp': ObjCppProject,
- 'java': JavaProject,
- 'd': DlangProject,
- 'rust': RustProject,
- 'fortran': FortranProject,
- 'vala': ValaProject
- }[options.language](options)
+
+if T.TYPE_CHECKING:
+ from ..minit import Arguments
+ from .sampleimpl import ClassImpl, FileHeaderImpl, FileImpl, SampleImpl
+
+
+_IMPL: T.Mapping[str, T.Union[T.Type[ClassImpl], T.Type[FileHeaderImpl], T.Type[FileImpl]]] = {
+ 'c': CProject,
+ 'cpp': CppProject,
+ 'cs': CSharpProject,
+ 'cuda': CudaProject,
+ 'objc': ObjCProject,
+ 'objcpp': ObjCppProject,
+ 'java': JavaProject,
+ 'd': DlangProject,
+ 'rust': RustProject,
+ 'fortran': FortranProject,
+ 'vala': ValaProject,
+}
+
+
+def sample_generator(options: Arguments) -> SampleImpl:
+ return _IMPL[options.language](options)
# 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 abc
+import re
+import typing as T
+
+if T.TYPE_CHECKING:
+ from ..minit import Arguments
+
+
+class SampleImpl(metaclass=abc.ABCMeta):
+
+ def __init__(self, args: Arguments):
+ self.name = args.name
+ self.version = args.version
+ self.lowercase_token = re.sub(r'[^a-z0-9]', '_', self.name.lower())
+ self.uppercase_token = self.lowercase_token.upper()
+ self.capitalized_token = self.lowercase_token.capitalize()
+
+ @abc.abstractmethod
+ def create_executable(self) -> None:
+ pass
+
+ @abc.abstractmethod
+ def create_library(self) -> None:
+ pass
+
+ @abc.abstractproperty
+ def exe_template(self) -> str:
+ pass
+
+ @abc.abstractproperty
+ def exe_meson_template(self) -> str:
+ pass
+
+ @abc.abstractproperty
+ def lib_template(self) -> str:
+ pass
+
+ @abc.abstractproperty
+ def lib_test_template(self) -> str:
+ pass
+
+ @abc.abstractproperty
+ def lib_meson_template(self) -> str:
+ pass
+
+ @abc.abstractproperty
+ def source_ext(self) -> str:
+ pass
+
+
+class ClassImpl(SampleImpl):
+
+ """For Class based languages, like Java and C#"""
-class SampleImpl:
def create_executable(self) -> None:
- raise NotImplementedError('Sample implementation for "executable" not implemented!')
+ source_name = f'{self.capitalized_token}.{self.source_ext}'
+ with open(source_name, 'w', encoding='utf-8') as f:
+ f.write(self.exe_template.format(project_name=self.name,
+ class_name=self.capitalized_token))
+ 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,
+ source_name=source_name,
+ version=self.version))
+
+ 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,
+ }
+ with open(lib_name, 'w', encoding='utf-8') as f:
+ f.write(self.lib_template.format(**kwargs))
+ with open(test_name, 'w', encoding='utf-8') as f:
+ f.write(self.lib_test_template.format(**kwargs))
+ with open('meson.build', 'w', encoding='utf-8') as f:
+ f.write(self.lib_meson_template.format(**kwargs))
+
+
+class FileImpl(SampleImpl):
+
+ """File based languages without headers"""
+
+ def create_executable(self) -> None:
+ source_name = f'{self.lowercase_token}.{self.source_ext}'
+ with open(source_name, 'w', encoding='utf-8') as f:
+ f.write(self.exe_template.format(project_name=self.name))
+ 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,
+ source_name=source_name,
+ version=self.version))
+
+ def lib_kwargs(self) -> T.Dict[str, str]:
+ """Get Language specific keyword arguments
+
+ :return: A dictionary of key: values to fill in the templates
+ """
+ return {
+ 'utoken': self.uppercase_token,
+ 'ltoken': self.lowercase_token,
+ 'header_dir': self.lowercase_token,
+ 'class_name': self.capitalized_token,
+ 'function_name': f'{self.lowercase_token[0:3]}_func',
+ 'namespace': self.lowercase_token,
+ 'source_file': f'{self.lowercase_token}.{self.source_ext}',
+ 'test_source_file': f'{self.lowercase_token}_test.{self.source_ext}',
+ '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,
+ }
+
+ def create_library(self) -> None:
+ lib_name = f'{self.lowercase_token}.{self.source_ext}'
+ test_name = f'{self.lowercase_token}_test.{self.source_ext}'
+ kwargs = self.lib_kwargs()
+ with open(lib_name, 'w', encoding='utf-8') as f:
+ f.write(self.lib_template.format(**kwargs))
+ with open(test_name, 'w', encoding='utf-8') as f:
+ f.write(self.lib_test_template.format(**kwargs))
+ with open('meson.build', 'w', encoding='utf-8') as f:
+ f.write(self.lib_meson_template.format(**kwargs))
+
+
+class FileHeaderImpl(FileImpl):
+
+ @abc.abstractproperty
+ def header_ext(self) -> str:
+ pass
+
+ @abc.abstractproperty
+ def lib_header_template(self) -> str:
+ pass
+
+ def lib_kwargs(self) -> T.Dict[str, str]:
+ kwargs = super().lib_kwargs()
+ kwargs['header_file'] = f'{self.lowercase_token}.{self.header_ext}'
+ return kwargs
def create_library(self) -> None:
- raise NotImplementedError('Sample implementation for "library" not implemented!')
+ super().create_library()
+ kwargs = self.lib_kwargs()
+ with open(kwargs['header_file'], 'w', encoding='utf-8') as f:
+ f.write(self.lib_header_template.format_map(kwargs))
# 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 mesonbuild.templates.sampleimpl import SampleImpl
-import re
+from __future__ import annotations
+
+from mesonbuild.templates.sampleimpl import FileImpl
hello_vala_template = '''void main (string[] args) {{
'''
-class ValaProject(SampleImpl):
- def __init__(self, options):
- super().__init__()
- self.name = options.name
- self.version = options.version
-
- def create_executable(self) -> None:
- lowercase_token = re.sub(r'[^a-z0-9]', '_', self.name.lower())
- source_name = lowercase_token + '.vala'
- open(source_name, 'w', encoding='utf-8').write(hello_vala_template.format(project_name=self.name))
- open('meson.build', 'w', encoding='utf-8').write(
- hello_vala_meson_template.format(project_name=self.name,
- exe_name=lowercase_token,
- source_name=source_name,
- version=self.version))
-
- def create_library(self) -> None:
- lowercase_token = re.sub(r'[^a-z0-9]', '_', self.name.lower())
- uppercase_token = lowercase_token.upper()
- class_name = uppercase_token[0] + lowercase_token[1:]
- test_exe_name = lowercase_token + '_test'
- namespace = lowercase_token
- lib_vala_name = lowercase_token + '.vala'
- test_vala_name = lowercase_token + '_test.vala'
- kwargs = {'utoken': uppercase_token,
- 'ltoken': lowercase_token,
- 'header_dir': lowercase_token,
- 'class_name': class_name,
- 'namespace': namespace,
- 'source_file': lib_vala_name,
- 'test_source_file': test_vala_name,
- 'test_exe_name': test_exe_name,
- 'project_name': self.name,
- 'lib_name': lowercase_token,
- 'test_name': lowercase_token,
- 'version': self.version,
- }
- open(lib_vala_name, 'w', encoding='utf-8').write(lib_vala_template.format(**kwargs))
- open(test_vala_name, 'w', encoding='utf-8').write(lib_vala_test_template.format(**kwargs))
- open('meson.build', 'w', encoding='utf-8').write(lib_vala_meson_template.format(**kwargs))
+class ValaProject(FileImpl):
+
+ source_ext = 'vala'
+ exe_template = hello_vala_template
+ exe_meson_template = hello_vala_meson_template
+ lib_template = lib_vala_template
+ lib_test_template = lib_vala_test_template
+ lib_meson_template = lib_vala_meson_template
import typing as T
if T.TYPE_CHECKING:
+ from hashlib import _Hash
from typing_extensions import Literal
from ..mparser import BaseNode
- from . import programs
+ from .. import programs
+ EnvironOrDict = T.Union[T.Dict[str, str], os._Environ[str]]
-__all__ = [
- 'MesonException',
- 'MesonBugException',
- 'HoldableObject',
- 'EnvInitValueType',
- 'EnvironmentVariables',
- 'ExecutableSerialisation',
-]
+ EnvInitValueType = T.Dict[str, T.Union[str, T.List[str]]]
class MesonException(Exception):
''' Dummy base class for all objects that can be
held by an interpreter.baseobjects.ObjectHolder '''
-EnvInitValueType = T.Dict[str, T.Union[str, T.List[str]]]
-
class EnvironmentVariables(HoldableObject):
def __init__(self, values: T.Optional[EnvInitValueType] = None,
init_method: Literal['set', 'prepend', 'append'] = 'set', separator: str = os.pathsep) -> None:
- self.envvars: T.List[T.Tuple[T.Callable[[T.Dict[str, str], str, T.List[str], str], str], str, T.List[str], str]] = []
+ self.envvars: T.List[T.Tuple[T.Callable[[T.Dict[str, str], str, T.List[str], str, T.Optional[str]], str], str, T.List[str], str]] = []
# The set of all env vars we have operations for. Only used for self.has_name()
self.varnames: T.Set[str] = set()
repr_str = "<{0}: {1}>"
return repr_str.format(self.__class__.__name__, self.envvars)
- def hash(self, hasher: T.Any):
+ def hash(self, hasher: _Hash) -> None:
myenv = self.get_env({})
for key in sorted(myenv.keys()):
hasher.update(bytes(key, encoding='utf-8'))
def get_names(self) -> T.Set[str]:
return self.varnames
+ def merge(self, other: EnvironmentVariables) -> None:
+ for method, name, values, separator in other.envvars:
+ self.varnames.add(name)
+ self.envvars.append((method, name, values, separator))
+
def set(self, name: str, values: T.List[str], separator: str = os.pathsep) -> None:
self.varnames.add(name)
self.envvars.append((self._set, name, values, separator))
curr = env.get(name, default_value)
return separator.join(values if curr is None else values + [curr])
- def get_env(self, full_env: T.MutableMapping[str, str], dump: bool = False) -> T.Dict[str, str]:
+ def get_env(self, full_env: EnvironOrDict, default_fmt: T.Optional[str] = None) -> T.Dict[str, str]:
env = full_env.copy()
for method, name, values, separator in self.envvars:
- default_value = f'${name}' if dump else None
+ default_value = default_fmt.format(name) if default_fmt else None
env[name] = method(env, name, values, separator, default_value)
return env
@dataclass(eq=False)
class ExecutableSerialisation:
- # XXX: should capture and feed default to False, instead of None?
-
cmd_args: T.List[str]
env: T.Optional[EnvironmentVariables] = None
exe_wrapper: T.Optional['programs.ExternalProgram'] = None
workdir: T.Optional[str] = None
extra_paths: T.Optional[T.List] = None
- capture: T.Optional[bool] = None
- feed: T.Optional[bool] = None
+ capture: T.Optional[str] = None
+ feed: T.Optional[str] = None
tag: T.Optional[str] = None
verbose: bool = False
+ installdir_map: T.Optional[T.Dict[str, str]] = None
def __post_init__(self) -> None:
self.pickled = False
self.skip_if_destdir = False
self.subproject = ''
+ self.dry_run = False
# 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
"""base classes providing no-op functionality.."""
# 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
"""Posix specific implementations of mesonlib functionality."""
import fcntl
import typing as T
-from .universal import MesonException
+from .core import MesonException
from .platform import BuildDirLock as BuildDirLockBase
__all__ = ['BuildDirLock']
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)
from tempfile import TemporaryDirectory, NamedTemporaryFile
import typing as T
import textwrap
-import copy
import pickle
import errno
+import json
from mesonbuild import mlog
from .core import MesonException, HoldableObject
if T.TYPE_CHECKING:
- from typing_extensions import Literal
+ from typing_extensions import Literal, Protocol
from .._typing import ImmutableListProtocol
from ..build import ConfigurationData
- from ..coredata import KeyedOptionDictType, UserOption
+ from ..coredata import StrOrBytesPath
+ from ..environment import Environment
from ..compilers.compilers import Compiler
+ from ..interpreterbase.baseobjects import SubProject
+
+ class _EnvPickleLoadable(Protocol):
+
+ environment: Environment
+
+ class _VerPickleLoadable(Protocol):
+
+ version: str
+
+ # A generic type for pickle_load. This allows any type that has either a
+ # .version or a .environment to be passed.
+ _PL = T.TypeVar('_PL', bound=T.Union[_EnvPickleLoadable, _VerPickleLoadable])
FileOrString = T.Union['File', str]
'GitException',
'OptionKey',
'dump_conf_header',
- 'OptionOverrideProxy',
'OptionType',
'OrderedSet',
'PerMachine',
'default_libdir',
'default_libexecdir',
'default_prefix',
+ 'default_datadir',
+ 'default_includedir',
+ 'default_infodir',
+ 'default_localedir',
+ 'default_mandir',
+ 'default_sbindir',
+ 'default_sysconfdir',
'detect_subprojects',
'detect_vcs',
'do_conf_file',
'generate_list',
'get_compiler_for_source',
'get_filenames_templates_dict',
- 'get_library_dirs',
'get_variable_regex',
'get_wine_shortpath',
'git',
'path_is_in_root',
'pickle_load',
'Popen_safe',
+ 'Popen_safe_logged',
'quiet_git',
'quote_arg',
'relative_to_if_possible',
# TODO: this is such a hack, this really should be either in coredata or in the
# interpreter
# {subproject: project_meson_version}
-project_meson_versions = collections.defaultdict(str) # type: T.DefaultDict[str, str]
+project_meson_versions: T.DefaultDict[str, str] = collections.defaultdict(str)
from glob import glob
-if os.path.basename(sys.executable) == 'meson.exe':
- # In Windows and using the MSI installed executable.
+if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
+ # using a PyInstaller bundle, e.g. the MSI installed executable
python_command = [sys.executable, 'runpython']
else:
python_command = [sys.executable]
self.output = output.strip() if output else ''
GIT = shutil.which('git')
-def git(cmd: T.List[str], workingdir: T.Union[str, bytes, os.PathLike], check: bool = False, **kwargs: T.Any) -> T.Tuple[subprocess.Popen, str, str]:
- cmd = [GIT] + cmd
+def git(cmd: T.List[str], workingdir: StrOrBytesPath, check: bool = False, **kwargs: T.Any) -> T.Tuple[subprocess.Popen[str], str, str]:
+ assert GIT is not None, 'Callers should make sure it exists'
+ cmd = [GIT, *cmd]
p, o, e = Popen_safe(cmd, cwd=workingdir, **kwargs)
if check and p.returncode != 0:
raise GitException('Git command failed: ' + str(cmd), e)
return p, o, e
-def quiet_git(cmd: T.List[str], workingdir: T.Union[str, bytes, os.PathLike], check: bool = False) -> T.Tuple[bool, str]:
+def quiet_git(cmd: T.List[str], workingdir: StrOrBytesPath, check: bool = False) -> T.Tuple[bool, str]:
if not GIT:
m = 'Git program not found.'
if check:
return False, e
return True, o
-def verbose_git(cmd: T.List[str], workingdir: T.Union[str, bytes, os.PathLike], check: bool = False) -> bool:
+def verbose_git(cmd: T.List[str], workingdir: StrOrBytesPath, check: bool = False) -> bool:
if not GIT:
m = 'Git program not found.'
if check:
You are using {e!r} which is not a Unicode-compatible
locale but you are trying to access a file system entry called {de!r} which is
not pure ASCII. This may cause problems.
- '''), file=sys.stderr)
+ '''))
class SecondLevelHolder(HoldableObject, metaclass=abc.ABCMeta):
''' A second level object holder. The primary purpose
BUILD = 0
HOST = 1
+ def __str__(self) -> str:
+ return f'{self.get_lower_case_name()} machine'
+
def get_lower_case_name(self) -> str:
return PerMachine('build', 'host')[self]
machines, we can elaborate the original and then redefault them and thus
avoid repeating the elaboration explicitly.
"""
- unfreeze = PerMachineDefaultable() # type: PerMachineDefaultable[T.Optional[_T]]
+ unfreeze: PerMachineDefaultable[T.Optional[_T]] = PerMachineDefaultable()
unfreeze.build = self.build
unfreeze.host = self.host
if unfreeze.host == unfreeze.build:
unfreeze.host = None
return unfreeze
+ def assign(self, build: _T, host: _T) -> None:
+ self.build = build
+ self.host = host
+
def __repr__(self) -> str:
return f'PerMachine({self.build!r}, {self.host!r})'
machines, we can elaborate the original and then redefault them and thus
avoid repeating the elaboration explicitly.
"""
- unfreeze = PerThreeMachineDefaultable() # type: PerThreeMachineDefaultable[T.Optional[_T]]
+ unfreeze: PerThreeMachineDefaultable[T.Optional[_T]] = PerThreeMachineDefaultable()
unfreeze.build = self.build
unfreeze.host = self.host
unfreeze.target = self.target
return m.default_missing()
-class PerThreeMachineDefaultable(PerMachineDefaultable, PerThreeMachine[T.Optional[_T]]):
+class PerThreeMachineDefaultable(PerMachineDefaultable[T.Optional[_T]], PerThreeMachine[T.Optional[_T]]):
"""Extends `PerThreeMachine` with the ability to default from `None`s.
"""
def __init__(self) -> None:
mlog.debug(f'lipo {objpath}: {stderr}')
return None
stdo = stdo.rsplit(': ', 1)[1]
+
# Convert from lipo-style archs to meson-style CPUs
- stdo = stdo.replace('i386', 'x86')
- stdo = stdo.replace('arm64', 'aarch64')
- stdo = stdo.replace('ppc7400', 'ppc')
- stdo = stdo.replace('ppc970', 'ppc')
+ map_arch = {
+ 'i386': 'x86',
+ 'arm64': 'aarch64',
+ 'arm64e': 'aarch64',
+ 'ppc7400': 'ppc',
+ 'ppc970': 'ppc',
+ }
+ lipo_archs = stdo.split()
+ meson_archs = [map_arch.get(lipo_arch, lipo_arch) for lipo_arch in lipo_archs]
+
# Add generic name for armv7 and armv7s
if 'armv7' in stdo:
- stdo += ' arm'
- return stdo.split()
+ meson_archs.append('arm')
+
+ return meson_archs
def windows_detect_native_arch() -> str:
"""
def detect_vcs(source_dir: T.Union[str, Path]) -> T.Optional[T.Dict[str, str]]:
vcs_systems = [
- dict(name = 'git', cmd = 'git', repo_dir = '.git', get_rev = 'git describe --dirty=+', rev_regex = '(.*)', dep = '.git/logs/HEAD'),
- dict(name = 'mercurial', cmd = 'hg', repo_dir = '.hg', get_rev = 'hg id -i', rev_regex = '(.*)', dep = '.hg/dirstate'),
- dict(name = 'subversion', cmd = 'svn', repo_dir = '.svn', get_rev = 'svn info', rev_regex = 'Revision: (.*)', dep = '.svn/wc.db'),
- dict(name = 'bazaar', cmd = 'bzr', repo_dir = '.bzr', get_rev = 'bzr revno', rev_regex = '(.*)', dep = '.bzr'),
+ {
+ 'name': 'git',
+ 'cmd': 'git',
+ 'repo_dir': '.git',
+ 'get_rev': 'git describe --dirty=+ --always',
+ 'rev_regex': '(.*)',
+ 'dep': '.git/logs/HEAD'
+ },
+ {
+ 'name': 'mercurial',
+ 'cmd': 'hg',
+ 'repo_dir': '.hg',
+ 'get_rev': 'hg id -i',
+ 'rev_regex': '(.*)',
+ 'dep': '.hg/dirstate'
+ },
+ {
+ 'name': 'subversion',
+ 'cmd': 'svn',
+ 'repo_dir': '.svn',
+ 'get_rev': 'svn info',
+ 'rev_regex': 'Revision: (.*)',
+ 'dep': '.svn/wc.db'
+ },
+ {
+ 'name': 'bazaar',
+ 'cmd': 'bzr',
+ 'repo_dir': '.bzr',
+ 'get_rev': 'bzr revno',
+ 'rev_regex': '(.*)',
+ 'dep': '.bzr'
+ },
]
if isinstance(source_dir, str):
source_dir = Path(source_dir)
def version_compare_many(vstr1: str, conditions: T.Union[str, T.Iterable[str]]) -> T.Tuple[bool, T.List[str], T.List[str]]:
if isinstance(conditions, str):
conditions = [conditions]
- found = []
- not_found = []
+ found: T.List[str] = []
+ not_found: T.List[str] = []
for req in conditions:
if not version_compare(vstr1, req):
not_found.append(req)
def default_libexecdir() -> str:
+ if is_haiku():
+ return 'lib'
# There is no way to auto-detect this, so it must be set at build time
return 'libexec'
def default_prefix() -> str:
- return 'c:/' if is_windows() else '/usr/local'
+ if is_windows():
+ return 'c:/'
+ if is_haiku():
+ return '/boot/system/non-packaged'
+ return '/usr/local'
-def get_library_dirs() -> T.List[str]:
- if is_windows():
- return ['C:/mingw/lib'] # TODO: get programmatically
- if is_osx():
- return ['/usr/lib'] # TODO: get programmatically
- # The following is probably Debian/Ubuntu specific.
- # /usr/local/lib is first because it contains stuff
- # installed by the sysadmin and is probably more up-to-date
- # than /usr/lib. If you feel that this search order is
- # problematic, please raise the issue on the mailing list.
- unixdirs = ['/usr/local/lib', '/usr/lib', '/lib']
-
- if is_freebsd():
- return unixdirs
- # FIXME: this needs to be further genericized for aarch64 etc.
- machine = platform.machine()
- if machine in {'i386', 'i486', 'i586', 'i686'}:
- plat = 'i386'
- elif machine.startswith('arm'):
- plat = 'arm'
- else:
- plat = ''
+def default_datadir() -> str:
+ if is_haiku():
+ return 'data'
+ return 'share'
+
+
+def default_includedir() -> str:
+ if is_haiku():
+ return 'develop/headers'
+ return 'include'
+
- # Solaris puts 32-bit libraries in the main /lib & /usr/lib directories
- # and 64-bit libraries in platform specific subdirectories.
- if is_sunos():
- if machine == 'i86pc':
- plat = 'amd64'
- elif machine.startswith('sun4'):
- plat = 'sparcv9'
+def default_infodir() -> str:
+ if is_haiku():
+ return 'documentation/info'
+ return 'share/info'
- usr_platdir = Path('/usr/lib/') / plat
- if usr_platdir.is_dir():
- unixdirs += [str(x) for x in (usr_platdir).iterdir() if x.is_dir()]
- if os.path.exists('/usr/lib64'):
- unixdirs.append('/usr/lib64')
- lib_platdir = Path('/lib/') / plat
- if lib_platdir.is_dir():
- unixdirs += [str(x) for x in (lib_platdir).iterdir() if x.is_dir()]
- if os.path.exists('/lib64'):
- unixdirs.append('/lib64')
+def default_localedir() -> str:
+ if is_haiku():
+ return 'data/locale'
+ return 'share/locale'
- return unixdirs
+
+def default_mandir() -> str:
+ if is_haiku():
+ return 'documentation/man'
+ return 'share/man'
+
+
+def default_sbindir() -> str:
+ if is_haiku():
+ return 'bin'
+ return 'sbin'
+
+
+def default_sysconfdir() -> str:
+ if is_haiku():
+ return 'settings'
+ return 'etc'
def has_path_sep(name: str, sep: str = '/\\') -> bool:
return result
def split_args(cmd: str) -> T.List[str]:
- result = []
+ result: T.List[str] = []
arg = ''
num_backslashes = 0
num_quotes = 0
def do_replacement(regex: T.Pattern[str], line: str,
variable_format: Literal['meson', 'cmake', 'cmake@'],
confdata: T.Union[T.Dict[str, T.Tuple[str, T.Optional[str]]], 'ConfigurationData']) -> T.Tuple[str, T.Set[str]]:
- missing_variables = set() # type: T.Set[str]
+ missing_variables: T.Set[str] = set()
if variable_format == 'cmake':
start_tag = '${'
backslash_tag = '\\${'
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:
return re.sub(regex, variable_replace, line), missing_variables
def do_define(regex: T.Pattern[str], line: str, confdata: 'ConfigurationData',
- variable_format: Literal['meson', 'cmake', 'cmake@']) -> str:
+ variable_format: Literal['meson', 'cmake', 'cmake@'], subproject: T.Optional[SubProject] = None) -> str:
+ cmake_bool_define = False
+ if variable_format != "meson":
+ cmake_bool_define = "cmakedefine01" in line
+
def get_cmake_define(line: str, confdata: 'ConfigurationData') -> str:
arr = line.split()
- define_value = []
+
+ if cmake_bool_define:
+ (v, desc) = confdata.get(arr[1])
+ return str(int(bool(v)))
+
+ define_value: T.List[str] = []
for token in arr[2:]:
try:
- (v, desc) = confdata.get(token)
+ v, _ = confdata.get(token)
define_value += [str(v)]
except KeyError:
define_value += [token]
return ' '.join(define_value)
arr = line.split()
- if variable_format == 'meson' and len(arr) != 2:
- raise MesonException('#mesondefine does not contain exactly two tokens: %s' % line.strip())
+ if len(arr) != 2:
+ if variable_format == 'meson':
+ raise MesonException('#mesondefine does not contain exactly two tokens: %s' % line.strip())
+ elif subproject is not None:
+ from ..interpreterbase.decorators import FeatureNew
+ FeatureNew.single_use('cmakedefine without exactly two tokens', '0.54.1', subproject)
varname = arr[1]
try:
- (v, desc) = confdata.get(varname)
+ v, _ = confdata.get(varname)
except KeyError:
- return '/* #undef %s */\n' % varname
- if isinstance(v, bool):
- if v:
- return '#define %s\n' % varname
+ if cmake_bool_define:
+ return '#define %s 0\n' % varname
else:
- return '#undef %s\n' % varname
- elif isinstance(v, int):
- return '#define %s %d\n' % (varname, v)
- elif isinstance(v, str):
+ return '/* #undef %s */\n' % varname
+
+ if isinstance(v, str) or variable_format != "meson":
if variable_format == 'meson':
result = v
else:
+ if not cmake_bool_define and not v:
+ return '/* #undef %s */\n' % varname
+
result = get_cmake_define(line, confdata)
- result = f'#define {varname} {result}\n'
- (result, missing_variable) = do_replacement(regex, result, variable_format, confdata)
+ result = f'#define {varname} {result}'.strip() + '\n'
+ result, _ = do_replacement(regex, result, variable_format, confdata)
return result
+ elif isinstance(v, bool):
+ if v:
+ return '#define %s\n' % varname
+ else:
+ return '#undef %s\n' % varname
+ elif isinstance(v, int):
+ return '#define %s %d\n' % (varname, v)
else:
raise MesonException('#mesondefine argument "%s" is of unknown type.' % varname)
regex = re.compile(r'(?:\\\\)+(?=\\?\$)|\\\${|\${([-a-zA-Z0-9_]+)}')
return regex
-def do_conf_str(src: str, data: list, confdata: 'ConfigurationData',
+def do_conf_str(src: str, data: T.List[str], confdata: 'ConfigurationData',
variable_format: Literal['meson', 'cmake', 'cmake@'],
- encoding: str = 'utf-8') -> T.Tuple[T.List[str], T.Set[str], bool]:
+ subproject: T.Optional[SubProject] = None) -> T.Tuple[T.List[str], T.Set[str], bool]:
def line_is_valid(line: str, variable_format: str) -> bool:
if variable_format == 'meson':
if '#cmakedefine' in line:
if variable_format != 'meson':
search_token = '#cmakedefine'
- result = []
- missing_variables = set()
+ result: T.List[str] = []
+ missing_variables: T.Set[str] = set()
# Detect when the configuration data is empty and no tokens were found
# during substitution so we can warn the user to use the `copy:` kwarg.
confdata_useless = not confdata.keys()
for line in data:
- if line.startswith(search_token):
+ if line.lstrip().startswith(search_token):
confdata_useless = False
- line = do_define(regex, line, confdata, variable_format)
+ line = do_define(regex, line, confdata, variable_format, subproject)
else:
if not line_is_valid(line, variable_format):
raise MesonException(f'Format error in {src}: saw "{line.strip()}" when format set to "{variable_format}"')
def do_conf_file(src: str, dst: str, confdata: 'ConfigurationData',
variable_format: Literal['meson', 'cmake', 'cmake@'],
- encoding: str = 'utf-8') -> T.Tuple[T.Set[str], bool]:
+ encoding: str = 'utf-8', subproject: T.Optional[SubProject] = None) -> T.Tuple[T.Set[str], bool]:
try:
with open(src, encoding=encoding, newline='') as f:
data = f.readlines()
except Exception as e:
raise MesonException(f'Could not read input file {src}: {e!s}')
- (result, missing_variables, confdata_useless) = do_conf_str(src, data, confdata, variable_format, encoding)
+ (result, missing_variables, confdata_useless) = do_conf_str(src, data, confdata, variable_format, subproject)
dst_tmp = dst + '~'
try:
with open(dst_tmp, 'w', encoding=encoding, newline='') as f:
* Do not edit, your changes will be lost.
*/
-#pragma once
+{}
'''
'''
-def dump_conf_header(ofilename: str, cdata: 'ConfigurationData', output_format: T.Literal['c', 'nasm']) -> None:
+def _dump_c_header(ofile: T.TextIO,
+ cdata: ConfigurationData,
+ output_format: Literal['c', 'nasm'],
+ macro_name: T.Optional[str]) -> None:
+ format_desc: T.Callable[[str], str]
if output_format == 'c':
- prelude = CONF_C_PRELUDE
+ if macro_name:
+ prelude = CONF_C_PRELUDE.format('#ifndef {0}\n#define {0}'.format(macro_name))
+ else:
+ prelude = CONF_C_PRELUDE.format('#pragma once')
prefix = '#'
- else:
+ format_desc = lambda desc: f'/* {desc} */\n'
+ else: # nasm
prelude = CONF_NASM_PRELUDE
prefix = '%'
+ format_desc = lambda desc: '; ' + '\n; '.join(desc.splitlines()) + '\n'
+
+ ofile.write(prelude)
+ for k in sorted(cdata.keys()):
+ (v, desc) = cdata.get(k)
+ if desc:
+ ofile.write(format_desc(desc))
+ if isinstance(v, bool):
+ if v:
+ ofile.write(f'{prefix}define {k}\n\n')
+ else:
+ ofile.write(f'{prefix}undef {k}\n\n')
+ elif isinstance(v, (int, str)):
+ ofile.write(f'{prefix}define {k} {v}\n\n')
+ else:
+ raise MesonException('Unknown data type in configuration file entry: ' + k)
+ if output_format == 'c' and macro_name:
+ ofile.write('#endif\n')
+
+def dump_conf_header(ofilename: str, cdata: ConfigurationData,
+ output_format: Literal['c', 'nasm', 'json'],
+ macro_name: T.Optional[str]) -> None:
ofilename_tmp = ofilename + '~'
with open(ofilename_tmp, 'w', encoding='utf-8') as ofile:
- ofile.write(prelude)
- for k in sorted(cdata.keys()):
- (v, desc) = cdata.get(k)
- if desc:
- if output_format == 'c':
- ofile.write('/* %s */\n' % desc)
- elif output_format == 'nasm':
- for line in desc.split('\n'):
- ofile.write('; %s\n' % line)
- if isinstance(v, bool):
- if v:
- ofile.write(f'{prefix}define {k}\n\n')
- else:
- ofile.write(f'{prefix}undef {k}\n\n')
- elif isinstance(v, (int, str)):
- ofile.write(f'{prefix}define {k} {v}\n\n')
- else:
- raise MesonException('Unknown data type in configuration file entry: ' + k)
+ if output_format == 'json':
+ data = {k: v[0] for k, v in cdata.values.items()}
+ json.dump(data, ofile, sort_keys=True)
+ else: # c, nasm
+ _dump_c_header(ofile, cdata, output_format, macro_name)
+
replace_if_different(ofilename, ofilename_tmp)
'''
if not isinstance(item, list):
return [item]
- result = [] # type: T.List[T.Any]
+ result: T.List[T.Any] = []
for i in item:
if flatten and isinstance(i, list):
result += listify(i, flatten=True)
def expand_arguments(args: T.Iterable[str]) -> T.Optional[T.List[str]]:
- expended_args = [] # type: T.List[str]
+ expended_args: T.List[str] = []
for arg in args:
if not arg.startswith('@'):
expended_args.append(arg)
**kwargs: T.Any) -> T.Tuple['subprocess.Popen[str]', str, str]:
p = subprocess.Popen(args, universal_newlines=False, close_fds=False,
stdin=stdin, stdout=stdout, stderr=stderr, **kwargs)
- input_ = None # type: T.Optional[bytes]
+ input_: T.Optional[bytes] = None
if write is not None:
input_ = write.encode('utf-8')
o, e = p.communicate(input_)
return p, o, e
+def Popen_safe_logged(args: T.List[str], msg: str = 'Called', **kwargs: T.Any) -> T.Tuple['subprocess.Popen[str]', str, str]:
+ '''
+ Wrapper around Popen_safe that assumes standard piped o/e and logs this to the meson log.
+ '''
+ try:
+ p, o, e = Popen_safe(args, **kwargs)
+ except Exception as excp:
+ mlog.debug('-----------')
+ mlog.debug(f'{msg}: `{join_args(args)}` -> {excp}')
+ raise
+
+ rc, out, err = p.returncode, o.strip(), e.strip()
+ mlog.debug('-----------')
+ mlog.debug(f'{msg}: `{join_args(args)}` -> {rc}')
+ if out:
+ mlog.debug(f'stdout:\n{out}\n-----------')
+ if err:
+ mlog.debug(f'stderr:\n{err}\n-----------')
+ return p, o, e
+
+
def iter_regexin_iter(regexiter: T.Iterable[str], initer: T.Iterable[str]) -> T.Optional[str]:
'''
Takes each regular expression in @regexiter and tries to search for it in
def _substitute_values_check_errors(command: T.List[str], values: T.Dict[str, T.Union[str, T.List[str]]]) -> None:
# Error checking
- inregex = ['@INPUT([0-9]+)?@', '@PLAINNAME@', '@BASENAME@'] # type: T.List[str]
- outregex = ['@OUTPUT([0-9]+)?@', '@OUTDIR@'] # type: T.List[str]
+ inregex: T.List[str] = ['@INPUT([0-9]+)?@', '@PLAINNAME@', '@BASENAME@']
+ outregex: T.List[str] = ['@OUTPUT([0-9]+)?@', '@OUTDIR@']
if '@INPUT@' not in values:
# Error out if any input-derived templates are present in the command
match = iter_regexin_iter(inregex, command)
_substitute_values_check_errors(command, values)
# Substitution
- outcmd = [] # type: T.List[str]
+ outcmd: T.List[str] = []
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:
@OUTPUT0@, @OUTPUT1@, ... one for each output file
'''
- values = {} # type: T.Dict[str, T.Union[str, T.List[str]]]
+ values: T.Dict[str, T.Union[str, T.List[str]]] = {}
# Gather values derived from the input
if inputs:
# We want to substitute all the inputs.
def __repr__(self) -> str:
# Don't print 'OrderedSet("")' for an empty set.
if self.__container:
- return 'OrderedSet("{}")'.format(
- '", "'.join(repr(e) for e in self.__container.keys()))
+ return 'OrderedSet([{}])'.format(
+ ', '.join(repr(e) for e in self.__container.keys()))
return 'OrderedSet()'
def __reversed__(self) -> T.Iterator[_T]:
__iter__ method' warning.
'''
def __init__(self, iterable: T.Optional[T.Iterable[str]] = None, total: T.Optional[int] = None,
- bar_type: T.Optional[str] = None, desc: T.Optional[str] = None):
+ bar_type: T.Optional[str] = None, desc: T.Optional[str] = None,
+ disable: T.Optional[bool] = None):
if iterable is not None:
self.iterable = iter(iterable)
return
self.total = total
self.done = 0
self.printed_dots = 0
- if self.total and bar_type == 'download':
- print('Download size:', self.total)
- if desc:
- print(f'{desc}: ', end='')
+ self.disable = not mlog.colorize_console() if disable is None else disable
+ if not self.disable:
+ if self.total and bar_type == 'download':
+ print('Download size:', self.total)
+ if desc:
+ print(f'{desc}: ', end='')
# Pretend to be an iterator when called as one and don't print any
# progress
return next(self.iterable)
def print_dot(self) -> None:
- print('.', end='')
- sys.stdout.flush()
+ if not self.disable:
+ print('.', end='')
+ sys.stdout.flush()
self.printed_dots += 1
def update(self, progress: int) -> None:
self.print_dot()
def close(self) -> None:
- print('')
+ if not self.disable:
+ print()
try:
from tqdm import tqdm
except ImportError:
# ideally we would use a typing.Protocol here, but it's part of typing_extensions until 3.8
- ProgressBar = ProgressBarFallback # type: T.Union[T.Type[ProgressBarFallback], T.Type[ProgressBarTqdm]]
+ ProgressBar: T.Union[T.Type[ProgressBarFallback], T.Type[ProgressBarTqdm]] = ProgressBarFallback
else:
class ProgressBarTqdm(tqdm):
def __init__(self, *args: T.Any, bar_type: T.Optional[str] = None, **kwargs: T.Any) -> None:
if bar_type == 'download':
- kwargs.update({'unit': 'bytes', 'leave': True})
+ kwargs.update({'unit': 'B',
+ 'unit_scale': True,
+ 'unit_divisor': 1024,
+ 'leave': True,
+ 'bar_format': '{l_bar}{bar}| {n_fmt}/{total_fmt} {rate_fmt} eta {remaining}',
+ })
+
else:
- kwargs.update({'leave': False})
- kwargs['ncols'] = 100
+ kwargs.update({'leave': False,
+ 'bar_format': '{l_bar}{bar}| {n_fmt}/{total_fmt} eta {remaining}',
+ })
super().__init__(*args, **kwargs)
ProgressBar = ProgressBarTqdm
super().__init__(option_strings, dest, nargs=None, default=default, **kwargs)
def __call__(self, parser: argparse.ArgumentParser, namespace: argparse.Namespace,
- values: T.Union[str, T.Sequence[T.Any], None], option_string: str = None) -> None:
+ values: T.Union[str, T.Sequence[T.Any], None], option_string: T.Optional[str] = None) -> None:
assert isinstance(values, str)
setattr(namespace, self.dest, os.path.abspath(os.path.realpath(values)))
return wine_path
# Check paths that can be reduced by making them relative to workdir.
- rel_paths = []
+ rel_paths: T.List[str] = []
if workdir:
- abs_paths = []
+ abs_paths: T.List[str] = []
for p in wine_paths:
try:
rel = Path(p).relative_to(workdir)
def run_once(func: T.Callable[..., _T]) -> T.Callable[..., _T]:
- ret = [] # type: T.List[_T]
+ ret: T.List[_T] = []
@wraps(func)
def wrapper(*args: T.Any, **kwargs: T.Any) -> _T:
return wrapper
-class OptionOverrideProxy(collections.abc.Mapping):
- '''Mimic an option list but transparently override selected option
- values.
- '''
-
- # TODO: the typing here could be made more explicit using a TypeDict from
- # python 3.8 or typing_extensions
-
- def __init__(self, overrides: T.Dict['OptionKey', T.Any], options: 'KeyedOptionDictType',
- subproject: T.Optional[str] = None):
- self.overrides = overrides
- self.options = options
- self.subproject = subproject
-
- def __getitem__(self, key: 'OptionKey') -> 'UserOption':
- # FIXME: This is fundamentally the same algorithm than interpreter.get_option_internal().
- # We should try to share the code somehow.
- key = key.evolve(subproject=self.subproject)
- if not key.is_project():
- opt = self.options.get(key)
- if opt is None or opt.yielding:
- opt = self.options[key.as_root()]
- else:
- opt = self.options[key]
- if opt.yielding:
- opt = self.options.get(key.as_root(), opt)
- override_value = self.overrides.get(key.as_root())
- if override_value is not None:
- opt = copy.copy(opt)
- opt.set_value(override_value)
- return opt
-
- def __iter__(self) -> T.Iterator['OptionKey']:
- return iter(self.options)
-
- def __len__(self) -> int:
- return len(self.options)
-
- def __eq__(self, other: object) -> bool:
- if not isinstance(other, OptionOverrideProxy):
- return NotImplemented
- t1 = (self.overrides, self.subproject, self.options)
- t2 = (other.overrides, other.subproject, other.options)
- return t1 == t2
-
-
class OptionType(enum.IntEnum):
"""Enum used to specify what kind of argument a thing is."""
'includedir',
'infodir',
'libdir',
+ 'licensedir',
'libexecdir',
'localedir',
'localstatedir',
'debug',
'default_library',
'errorlogs',
+ 'genvslite',
'install_umask',
'layout',
'optimization',
'force_fallback_for',
'pkg_config_path',
'cmake_prefix_path',
+ 'vsenv',
}
def evolve(self, name: T.Optional[str] = None, subproject: T.Optional[str] = None,
machine: T.Optional[MachineChoice] = None, lang: T.Optional[str] = '',
module: T.Optional[str] = '') -> 'OptionKey':
- """Create a new copy of this key, but with alterted members.
+ """Create a new copy of this key, but with altered members.
For example:
>>> a = OptionKey('foo', '', MachineChoice.Host)
return self.evolve(subproject='')
def as_build(self) -> 'OptionKey':
- """Convenience method for key.evolve(machine=MachinceChoice.BUILD)."""
+ """Convenience method for key.evolve(machine=MachineChoice.BUILD)."""
return self.evolve(machine=MachineChoice.BUILD)
def as_host(self) -> 'OptionKey':
- """Convenience method for key.evolve(machine=MachinceChoice.HOST)."""
+ """Convenience method for key.evolve(machine=MachineChoice.HOST)."""
return self.evolve(machine=MachineChoice.HOST)
def is_backend(self) -> bool:
"""Convenience method to check if this is a base option."""
return self.type is OptionType.BASE
-def pickle_load(filename: str, object_name: str, object_type: T.Type) -> T.Any:
- load_fail_msg = f'{object_name} file {filename!r} is corrupted. Try with a fresh build tree.'
+
+def pickle_load(filename: str, object_name: str, object_type: T.Type[_PL], suggest_reconfigure: bool = True) -> _PL:
+ load_fail_msg = f'{object_name} file {filename!r} is corrupted.'
+ extra_msg = ' Consider reconfiguring the directory with "meson setup --reconfigure".' if suggest_reconfigure else ''
try:
with open(filename, 'rb') as f:
obj = pickle.load(f)
except (pickle.UnpicklingError, EOFError):
- raise MesonException(load_fail_msg)
+ raise MesonException(load_fail_msg + extra_msg)
except (TypeError, ModuleNotFoundError, AttributeError):
- build_dir = os.path.dirname(os.path.dirname(filename))
raise MesonException(
f"{object_name} file {filename!r} references functions or classes that don't "
"exist. This probably means that it was generated with an old "
- "version of meson. Try running from the source directory "
- f'meson setup {build_dir} --wipe')
+ "version of meson." + extra_msg)
+
if not isinstance(obj, object_type):
- raise MesonException(load_fail_msg)
+ raise MesonException(load_fail_msg + extra_msg)
+
+ # Because these Protocols are not available at runtime (and cannot be made
+ # available at runtime until we drop support for Python < 3.8), we have to
+ # do a bit of hackery so that mypy understands what's going on here
+ version: str
+ if hasattr(obj, 'version'):
+ version = T.cast('_VerPickleLoadable', obj).version
+ else:
+ version = T.cast('_EnvPickleLoadable', obj).environment.coredata.version
+
from ..coredata import version as coredata_version
from ..coredata import major_versions_differ, MesonVersionMismatchException
- version = getattr(obj, 'version', None)
- if version is None:
- version = obj.environment.coredata.version
if major_versions_differ(version, coredata_version):
- raise MesonVersionMismatchException(version, coredata_version)
+ raise MesonVersionMismatchException(version, coredata_version, extra_msg)
return obj
+from __future__ import annotations
+
import os
import subprocess
import json
import pathlib
import shutil
import tempfile
+import locale
from .. import mlog
-from .universal import MesonException, is_windows, windows_detect_native_arch
+from .core import MesonException
+from .universal import is_windows, windows_detect_native_arch
__all__ = [
)
bat_info = json.loads(bat_json)
if not bat_info:
- # VS installer instelled but not VS itself maybe?
+ # VS installer installed but not VS itself maybe?
raise MesonException('Could not parse vswhere.exe output')
bat_root = pathlib.Path(bat_info[0]['installationPath'])
if windows_detect_native_arch() == 'arm64':
bat_file.write(bat_contents)
bat_file.flush()
bat_file.close()
- bat_output = subprocess.check_output(bat_file.name, universal_newlines=True)
+ 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
# 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
"""Windows specific implementations of mesonlib functionality."""
import msvcrt
import typing as T
-from .universal import MesonException
+from .core import MesonException
from .platform import BuildDirLock as BuildDirLockBase
__all__ = ['BuildDirLock']
# 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
from .. import mlog
import contextlib
from base64 import b64encode
from netrc import netrc
-from pathlib import Path
+from pathlib import Path, PurePath
from . import WrapMode
from .. import coredata
if T.TYPE_CHECKING:
import http.client
+ from typing_extensions import Literal
+
+ Method = Literal['meson', 'cmake', 'cargo']
try:
# Importing is just done to check if SSL exists, so all warnings
except ImportError:
has_ssl = False
-REQ_TIMEOUT = 600.0
-SSL_WARNING_PRINTED = False
+REQ_TIMEOUT = 30.0
WHITELIST_SUBDOMAIN = 'wrapdb.mesonbuild.com'
ALL_TYPES = ['file', 'git', 'hg', 'svn']
raise WrapException(f'SSL module not available in {sys.executable}: Cannot contact the WrapDB.{insecure_msg}')
else:
# following code is only for those without Python SSL
- global SSL_WARNING_PRINTED # pylint: disable=global-statement
- if not SSL_WARNING_PRINTED:
- mlog.warning(f'SSL module not available in {sys.executable}: WrapDB traffic not authenticated.')
- SSL_WARNING_PRINTED = True
+ 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')
def __init__(self, fname: str, subproject: str = ''):
self.filename = fname
self.subproject = SubProject(subproject)
- self.type = None # type: T.Optional[str]
- self.values = {} # type: T.Dict[str, str]
- self.provided_deps = {} # type: T.Dict[str, T.Optional[str]]
- self.provided_programs = [] # type: T.List[str]
- self.diff_files = [] # type: T.List[Path]
+ self.type: T.Optional[str] = None
+ self.values: T.Dict[str, str] = {}
+ self.provided_deps: T.Dict[str, T.Optional[str]] = {}
+ self.provided_programs: T.List[str] = []
+ self.diff_files: T.List[Path] = []
self.basename = os.path.basename(fname)
self.has_wrap = self.basename.endswith('.wrap')
self.name = self.basename[:-5] if self.has_wrap else self.basename
self.diff_files.append(path)
def parse_provide_section(self, config: configparser.ConfigParser) -> None:
+ if config.has_section('provides'):
+ raise WrapException('Unexpected "[provides]" section, did you mean "[provide]"?')
if config.has_section('provide'):
for k, v in config['provide'].items():
if k == 'dependency_names':
wrap_mode: WrapMode = WrapMode.default
wrap_frontend: bool = False
allow_insecure: bool = False
+ silent: bool = False
def __post_init__(self) -> None:
self.subdir_root = os.path.join(self.source_dir, self.subdir)
- self.cachedir = os.path.join(self.subdir_root, 'packagecache')
- self.wraps = {} # type: T.Dict[str, PackageDefinition]
+ self.cachedir = os.environ.get('MESON_PACKAGE_CACHE_DIR') or os.path.join(self.subdir_root, 'packagecache')
+ self.wraps: T.Dict[str, PackageDefinition] = {}
self.netrc: T.Optional[netrc] = None
- self.provided_deps = {} # type: T.Dict[str, PackageDefinition]
- self.provided_programs = {} # type: T.Dict[str, PackageDefinition]
+ self.provided_deps: T.Dict[str, PackageDefinition] = {}
+ self.provided_programs: T.Dict[str, PackageDefinition] = {}
self.wrapdb: T.Dict[str, T.Any] = {}
self.wrapdb_provided_deps: T.Dict[str, str] = {}
self.wrapdb_provided_programs: T.Dict[str, str] = {}
return wrap_name
return None
- def resolve(self, packagename: str, method: str) -> str:
+ def resolve(self, packagename: str, force_method: T.Optional[Method] = None) -> T.Tuple[str, Method]:
self.packagename = packagename
self.directory = packagename
self.wrap = self.wraps.get(packagename)
# Write a dummy wrap file in main project that redirect to the
# wrap we picked.
with open(main_fname, 'w', encoding='utf-8') as f:
- f.write(textwrap.dedent('''\
+ f.write(textwrap.dedent(f'''\
[wrap-redirect]
- filename = {}
- '''.format(os.path.relpath(self.wrap.filename, self.subdir_root))))
+ filename = {PurePath(os.path.relpath(self.wrap.filename, self.subdir_root)).as_posix()}
+ '''))
else:
# No wrap file, it's a dummy package definition for an existing
# directory. Use the source code in place.
self.dirname = self.wrap.filename
rel_path = os.path.relpath(self.dirname, self.source_dir)
- if method == 'meson':
- buildfile = os.path.join(self.dirname, 'meson.build')
- elif method == 'cmake':
- buildfile = os.path.join(self.dirname, 'CMakeLists.txt')
- else:
- raise WrapException('Only the methods "meson" and "cmake" are supported')
+ # Map each supported method to a file that must exist at the root of source tree.
+ methods_map: T.Dict[Method, str] = {
+ 'meson': 'meson.build',
+ 'cmake': 'CMakeLists.txt',
+ 'cargo': 'Cargo.toml',
+ }
+
+ # Check if this wrap forces a specific method, use meson otherwise.
+ method = T.cast('T.Optional[Method]', self.wrap.values.get('method', force_method))
+ if method and method not in methods_map:
+ allowed_methods = ', '.join(methods_map.keys())
+ raise WrapException(f'Wrap method {method!r} is not supported, must be one of: {allowed_methods}')
+ if force_method and method != force_method:
+ raise WrapException(f'Wrap method is {method!r} but we are trying to configure it with {force_method}')
+ method = method or 'meson'
+
+ def has_buildfile() -> bool:
+ return os.path.exists(os.path.join(self.dirname, methods_map[method]))
# The directory is there and has meson.build? Great, use it.
- if os.path.exists(buildfile):
+ if has_buildfile():
self.validate()
- return rel_path
+ return rel_path, method
# Check if the subproject is a git submodule
self.resolve_git_submodule()
if not os.path.isdir(self.dirname):
raise WrapException('Path already exists but is not a directory')
else:
- if self.wrap.type == 'file':
+ # Check first if we have the extracted directory in our cache. This can
+ # happen for example when MESON_PACKAGE_CACHE_DIR=/usr/share/cargo/registry
+ # on distros that ships Rust source code.
+ # TODO: We don't currently clone git repositories into the cache
+ # directory, but we should to avoid cloning multiple times the same
+ # repository. In that case, we could do something smarter than
+ # copy_tree() here.
+ cached_directory = os.path.join(self.cachedir, self.directory)
+ if os.path.isdir(cached_directory):
+ self.copy_tree(cached_directory, self.dirname)
+ elif self.wrap.type == 'file':
self.get_file()
else:
self.check_can_download()
windows_proof_rmtree(self.dirname)
raise
- # A meson.build or CMakeLists.txt file is required in the directory
- if not os.path.exists(buildfile):
- raise WrapException(f'Subproject exists but has no {os.path.basename(buildfile)} file')
+ if not has_buildfile():
+ raise WrapException(f'Subproject exists but has no {methods_map[method]} file.')
# At this point, the subproject has been successfully resolved for the
# first time so save off the hash of the entire wrap file for future
# reference.
self.wrap.update_hash_cache(self.dirname)
-
- return rel_path
+ return rel_path, method
def check_can_download(self) -> None:
# Don't download subproject data based on wrap file if requested.
if 'lead_directory_missing' in self.wrap.values:
os.mkdir(self.dirname)
extract_dir = self.dirname
- shutil.unpack_archive(path, extract_dir)
+ try:
+ shutil.unpack_archive(path, extract_dir)
+ except OSError as e:
+ raise WrapException(f'failed to unpack archive with error: {str(e)}') from e
def get_git(self) -> None:
if not GIT:
revno = self.wrap.get('revision')
checkout_cmd = ['-c', 'advice.detachedHead=false', 'checkout', revno, '--']
is_shallow = False
- depth_option = [] # type: T.List[str]
+ depth_option: T.List[str] = []
if self.wrap.values.get('depth', '') != '':
is_shallow = True
depth_option = ['--depth', self.wrap.values.get('depth')]
revno = self.wrap.get('revision')
verbose_git(['fetch', *depth_option, 'origin', revno], self.dirname, check=True)
verbose_git(checkout_cmd, self.dirname, check=True)
- if self.wrap.values.get('clone-recursive', '').lower() == 'true':
- verbose_git(['submodule', 'update', '--init', '--checkout',
- '--recursive', *depth_option], self.dirname, check=True)
- push_url = self.wrap.values.get('push-url')
- if push_url:
- verbose_git(['remote', 'set-url', '--push', 'origin', push_url], self.dirname, check=True)
else:
if not is_shallow:
verbose_git(['clone', self.wrap.get('url'), self.directory], self.subdir_root, check=True)
args += ['--branch', revno]
args += [self.wrap.get('url'), self.directory]
verbose_git(args, self.subdir_root, check=True)
- if self.wrap.values.get('clone-recursive', '').lower() == 'true':
- verbose_git(['submodule', 'update', '--init', '--checkout', '--recursive', *depth_option],
- self.dirname, check=True)
- push_url = self.wrap.values.get('push-url')
- if push_url:
- verbose_git(['remote', 'set-url', '--push', 'origin', push_url], self.dirname, check=True)
+ if self.wrap.values.get('clone-recursive', '').lower() == 'true':
+ verbose_git(['submodule', 'update', '--init', '--checkout', '--recursive', *depth_option],
+ self.dirname, check=True)
+ push_url = self.wrap.values.get('push-url')
+ if push_url:
+ verbose_git(['remote', 'set-url', '--push', 'origin', push_url], self.dirname, check=True)
def validate(self) -> None:
# This check is only for subprojects with wraps.
except urllib.error.URLError as e:
mlog.log(str(e))
raise WrapException(f'could not get {urlstring} is the internet available?')
- with contextlib.closing(resp) as resp:
+ with contextlib.closing(resp) as resp, tmpfile as tmpfile:
try:
dlsize = int(resp.info()['Content-Length'])
except TypeError:
return hashvalue, tmpfile.name
sys.stdout.flush()
progress_bar = ProgressBar(bar_type='download', total=dlsize,
- desc='Downloading')
+ desc='Downloading',
+ disable=(self.silent or None))
while True:
block = resp.read(blocksize)
if block == b'':
if not path.exists():
raise WrapException(f'Diff file "{path}" does not exist')
relpath = os.path.relpath(str(path), self.dirname)
- if PATCH:
- cmd = [PATCH, '-f', '-p1', '-i', relpath]
- elif GIT:
- # If the `patch` command is not available, fall back to `git
- # apply`. The `--work-tree` is necessary in case we're inside a
+ if GIT:
+ # Git is more likely to be available on Windows and more likely
+ # to apply correctly assuming patches are often generated by git.
+ # See https://github.com/mesonbuild/meson/issues/12092.
+ # The `--work-tree` is necessary in case we're inside a
# Git repository: by default, Git will try to apply the patch to
# the repository root.
cmd = [GIT, '--work-tree', '.', 'apply', '-p1', relpath]
+ elif PATCH:
+ # Always pass a POSIX path to patch, because on Windows it's MSYS
+ cmd = [PATCH, '-f', '-p1', '-i', str(Path(relpath).as_posix())]
else:
raise WrapException('Missing "patch" or "git" commands to apply diff files')
# 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 sys, os
import configparser
if T.TYPE_CHECKING:
import argparse
+# Note: when adding arguments, please also add them to the completion
+# scripts in $MESONSRC/data/shell-completions/
def add_arguments(parser: 'argparse.ArgumentParser') -> None:
subparsers = parser.add_subparsers(title='Commands', dest='command')
subparsers.required = True
from mesonbuild import coredata
# Elementtree does not support CDATA. So hack it.
-WINVER_CHECK = '<![CDATA[Installed OR (VersionNT64 > 602)]]>'
+WINVER_CHECK = 'Installed OR (VersionNT64 > 602)>'
def gen_guid():
'''
'Title': 'Meson',
'Description': 'Meson executables',
'Level': '1',
- 'Absent': 'disallow',
+ 'AllowAbsent': 'no',
},
self.staging_dirs[1]: {
'Id': 'NinjaProgram',
'''
Generate package files for MSI installer package
'''
- self.root = ET.Element('Wix', {'xmlns': 'http://schemas.microsoft.com/wix/2006/wi'})
- product = ET.SubElement(self.root, 'Product', {
+ self.root = ET.Element('Wix', {
+ 'xmlns': 'http://wixtoolset.org/schemas/v4/wxs',
+ 'xmlns:ui': 'http://wixtoolset.org/schemas/v4/wxs/ui'
+ })
+
+ package = ET.SubElement(self.root, 'Package', {
'Name': self.product_name,
'Manufacturer': 'The Meson Development Team',
- 'Id': self.guid,
'UpgradeCode': self.update_guid,
'Language': '1033',
'Codepage': '1252',
'Version': self.version,
})
- package = ET.SubElement(product, 'Package', {
- 'Id': '*',
+ ET.SubElement(package, 'SummaryInformation', {
'Keywords': 'Installer',
'Description': f'Meson {self.version} installer',
- 'Comments': 'Meson is a high performance build system',
'Manufacturer': 'The Meson Development Team',
- 'InstallerVersion': '500',
- 'Languages': '1033',
- 'Compressed': 'yes',
- 'SummaryCodepage': '1252',
})
- condition = ET.SubElement(product, 'Condition', {'Message': 'This application is only supported on Windows 10 or higher.'})
+ ET.SubElement(package,
+ 'Launch',
+ {'Message': 'This application is only supported on Windows 10 or higher.',
+ 'Condition': 'X'*len(WINVER_CHECK)})
- condition.text = 'X'*len(WINVER_CHECK)
- ET.SubElement(product, 'MajorUpgrade',
- {'DowngradeErrorMessage': 'A newer version of Meson is already installed.'})
+ ET.SubElement(package, 'MajorUpgrade',
+ {'DowngradeErrorMessage':
+ 'A newer version of Meson is already installed.'})
- package.set('Platform', 'x64')
- ET.SubElement(product, 'Media', {
+ ET.SubElement(package, 'Media', {
'Id': '1',
'Cabinet': 'meson.cab',
'EmbedCab': 'yes',
})
- targetdir = ET.SubElement(product, 'Directory', {
- 'Id': 'TARGETDIR',
- 'Name': 'SourceDir',
+ targetdir = ET.SubElement(package, 'StandardDirectory', {
+ 'Id': 'ProgramFiles64Folder',
})
- progfiledir = ET.SubElement(targetdir, 'Directory', {
- 'Id': self.progfile_dir,
- })
- installdir = ET.SubElement(progfiledir, 'Directory', {
+ installdir = ET.SubElement(targetdir, 'Directory', {
'Id': 'INSTALLDIR',
'Name': 'Meson',
})
'Language': '0',
})
- ET.SubElement(product, 'Property', {
- 'Id': 'WIXUI_INSTALLDIR',
- 'Value': 'INSTALLDIR',
- })
- ET.SubElement(product, 'UIRef', {
+ ET.SubElement(package, 'ui:WixUI', {
'Id': 'WixUI_FeatureTree',
})
for s_d in self.staging_dirs:
assert os.path.isdir(s_d)
- top_feature = ET.SubElement(product, 'Feature', {
+ top_feature = ET.SubElement(package, 'Feature', {
'Id': 'Complete',
'Title': 'Meson ' + self.version,
'Description': 'The complete package',
})
ET.SubElement(vcredist_feature, 'MergeRef', {'Id': 'VCRedist'})
ET.ElementTree(self.root).write(self.main_xml, encoding='utf-8', xml_declaration=True)
- # ElementTree can not do prettyprinting so do it manually
+ # ElementTree cannot do pretty-printing, so do it manually
import xml.dom.minidom
doc = xml.dom.minidom.parse(self.main_xml)
with open(self.main_xml, 'w') as open_file:
component_id = f'ApplicationFiles{self.component_num}'
comp_xml_node = ET.SubElement(parent_xml_node, 'Component', {
'Id': component_id,
+ 'Bitness': 'always64',
'Guid': gen_guid(),
})
self.feature_components[staging_dir].append(component_id)
- comp_xml_node.set('Win64', 'yes')
if self.component_num == 0:
ET.SubElement(comp_xml_node, 'Environment', {
'Id': 'Environment',
})
for dirname in cur_node.dirs:
- dir_id = os.path.join(current_dir, dirname).replace('\\', '_').replace('/', '_')
+ dir_id = os.path.join(current_dir, dirname).replace('\\', '_').replace('/', '_').replace('-', '_')
dir_node = ET.SubElement(parent_xml_node, 'Directory', {
'Id': dir_id,
'Name': dirname,
'''
Generate the Meson build MSI package.
'''
- wixdir = 'c:\\Program Files\\Wix Toolset v3.11\\bin'
- if not os.path.isdir(wixdir):
- wixdir = 'c:\\Program Files (x86)\\Wix Toolset v3.11\\bin'
- if not os.path.isdir(wixdir):
- print("ERROR: This script requires WIX")
- sys.exit(1)
- subprocess.check_call([os.path.join(wixdir, 'candle'), self.main_xml])
- subprocess.check_call([os.path.join(wixdir, 'light'),
- '-ext', 'WixUIExtension',
- '-cultures:en-us',
- '-dWixUILicenseRtf=packaging\\License.rtf',
- '-out', self.final_output,
- self.main_o])
+ subprocess.check_call(['wix',
+ 'build',
+ '-bindvariable', 'WixUILicenseRtf=packaging\\License.rtf',
+ '-ext', 'WixToolset.UI.wixext',
+ '-culture', 'en-us',
+ '-arch', 'x64',
+ '-o',
+ self.final_output,
+ self.main_xml,
+ ])
+
+
+def install_wix():
+ subprocess.check_call(['dotnet',
+ 'nuget',
+ 'add',
+ 'source',
+ 'https://api.nuget.org/v3/index.json'])
+ subprocess.check_call(['dotnet',
+ 'tool',
+ 'install',
+ '--global',
+ 'wix'])
+ subprocess.check_call(['wix',
+ 'extension',
+ 'add',
+ 'WixToolset.UI.wixext',
+ ])
if __name__ == '__main__':
if not os.path.exists('meson.py'):
sys.exit(print('Run me in the top level source dir.'))
+ if not shutil.which('wix'):
+ install_wix()
subprocess.check_call(['pip', 'install', '--upgrade', 'pyinstaller'])
p = PackageGenerator()
import subprocess
import shutil, sys, os
+from glob import glob
import xml.etree.ElementTree as ET
if os.path.exists(self.pkg_dir):
shutil.rmtree(self.pkg_dir)
os.mkdir(self.pkg_dir)
- pyinstaller_bin = '/Users/jpakkane/Library/Python/3.8/bin/pyinstaller'
+ pyinstaller_bin = glob('/Users/jpakkane/Library/Python/*/bin/pyinstaller')
+ if len(pyinstaller_bin) != 1:
+ sys.exit('Could not determine unique installer.')
+ pyinstaller_bin = pyinstaller_bin[0]
pyinst_cmd = [pyinstaller_bin,
'--clean',
'--additional-hooks-dir=packaging',
ET.SubElement(root, 'pkg-ref', {'id': self.identifier})
ET.SubElement(root, 'options', {'customize': 'never',
'require-scripts': 'false',
- 'hostArhcitectures': 'x86_64,arm64'})
+ 'hostArchitectures': 'x86_64,arm64'})
choices_outline = ET.SubElement(root, 'choices-outline')
line = ET.SubElement(choices_outline, 'line', {'choice': 'default'})
ET.SubElement(line, 'line', {'choice': self.identifier})
'version': '0', # self.version,
'onConclusion': 'none'}).text = self.pkgname
ET.ElementTree(root).write(self.distribution_file, encoding='utf-8', xml_declaration=True)
- # ElementTree can not do prettyprinting so do it manually
+ # ElementTree cannot do pretty-printing, so do it manually
import xml.dom.minidom
doc = xml.dom.minidom.parse(self.distribution_file)
with open(self.distribution_file, 'w') as open_file:
modules = ['mesonbuild.' + modname + '.' + x for x in modules if not x.startswith('_')]
return modules
-datas += collect_data_files('mesonbuild.scripts')
+datas += collect_data_files('mesonbuild.scripts', include_py_files=True, excludes=['**/__pycache__'])
datas += collect_data_files('mesonbuild.cmake.data')
datas += collect_data_files('mesonbuild.dependencies.data')
+# lazy-loaded
+hiddenimports += get_all_modules_from_dir('mesonbuild/dependencies')
+# imported by meson.build files
hiddenimports += get_all_modules_from_dir('mesonbuild/modules')
+# executed when named on CLI
hiddenimports += get_all_modules_from_dir('mesonbuild/scripts')
# Python packagers want to be minimal and only copy the things
'work area',
'.eggs', '_cache', # e.g. .mypy_cache
'venv', # virtualenvs have DOS line endings
+ '120 rewrite', # we explicitly test for tab in meson.build file
}
for (root, _, filenames) in os.walk('.'):
if any([x in root for x in skip_dirs]):
continue
check_file(root / file)
+def check_symlinks():
+ for f in Path('test cases').glob('**/*'):
+ if f.is_symlink():
+ if 'boost symlinks' in str(f):
+ continue
+ raise SystemExit(f'Test data dir contains symlink: {f}.')
+
if __name__ == '__main__':
script_dir = os.path.split(__file__)[0]
if script_dir != '':
os.chdir(script_dir)
check_format()
+ check_symlinks()
os.chdir(str(self.orig_dir))
super().tearDown()
- def _run(self, command, workdir=None):
+ def _run(self, command, workdir=None, env=None):
'''
Run a command while printing the stdout, and also return a copy of it
'''
# between CI issue and test bug in that case. Set timeout and fail loud
# instead.
p = subprocess.run(command, stdout=subprocess.PIPE,
- env=os.environ.copy(), text=True,
+ env=env, text=True,
cwd=workdir, timeout=60 * 5)
print(p.stdout)
if p.returncode != 0:
(bindir / 'python3').symlink_to(python_command[0])
os.environ['PATH'] = str(bindir) + os.pathsep + os.environ['PATH']
# use our overridden PATH-compatible python
- path_resolved_meson_command = resolved_meson_command.copy()
- path_resolved_meson_command[0] = str(bindir / 'python3')
+ path_resolved_meson_command = [str(bindir / 'meson')]
# See if it works!
meson_py = 'meson'
meson_setup = [meson_py, 'setup']
self._run([script.as_posix(), source, '--outfile', target, '--interpreter', python_command[0]])
self._run([target.as_posix(), '--help'])
+ def test_meson_runpython(self):
+ meson_command = str(self.src_root / 'meson.py')
+ script_file = str(self.src_root / 'foo.py')
+ test_command = 'import sys; print(sys.argv[1])'
+ env = os.environ.copy()
+ del env['MESON_COMMAND_TESTS']
+ with open(script_file, 'w') as f:
+ f.write('#!/usr/bin/env python3\n\n')
+ f.write(f'{test_command}\n')
+
+ for cmd in [['-c', test_command, 'fake argument'], [script_file, 'fake argument']]:
+ pyout = self._run(python_command + cmd)
+ mesonout = self._run(python_command + [meson_command, 'runpython'] + cmd, env=env)
+ self.assertEqual(pyout, mesonout)
+
if __name__ == '__main__':
print('Meson build system', meson_version, 'Command Tests')
modules = [
# fully typed submodules
# 'mesonbuild/ast/',
+ 'mesonbuild/cargo/',
'mesonbuild/cmake/',
'mesonbuild/compilers/',
'mesonbuild/dependencies/',
'mesonbuild/interpreterbase/',
'mesonbuild/linkers/',
'mesonbuild/scripts/',
+ 'mesonbuild/templates/',
'mesonbuild/wrap/',
# specific files
'mesonbuild/interpreter/type_checking.py',
'mesonbuild/mcompile.py',
'mesonbuild/mdevenv.py',
+ 'mesonbuild/utils/core.py',
'mesonbuild/utils/platform.py',
'mesonbuild/utils/universal.py',
+ 'mesonbuild/mconf.py',
+ 'mesonbuild/mdist.py',
'mesonbuild/minit.py',
'mesonbuild/minstall.py',
'mesonbuild/mintro.py',
'mesonbuild/mlog.py',
'mesonbuild/msubprojects.py',
+ 'mesonbuild/modules/__init__.py',
'mesonbuild/modules/external_project.py',
'mesonbuild/modules/fs.py',
'mesonbuild/modules/gnome.py',
'mesonbuild/modules/modtest.py',
'mesonbuild/modules/pkgconfig.py',
'mesonbuild/modules/qt.py',
+ 'mesonbuild/modules/qt4.py',
+ 'mesonbuild/modules/qt5.py',
+ 'mesonbuild/modules/qt6.py',
'mesonbuild/modules/rust.py',
+ 'mesonbuild/modules/simd.py',
'mesonbuild/modules/sourceset.py',
'mesonbuild/modules/wayland.py',
'mesonbuild/modules/windows.py',
check_mypy()
root = Path(__file__).absolute().parent
- args = [] # type: T.List[str]
parser = argparse.ArgumentParser(description='Process some integers.')
parser.add_argument('files', nargs='*')
+ parser.add_argument('--mypy', help='path to mypy executable')
parser.add_argument('-q', '--quiet', action='store_true', help='do not print informational messages')
parser.add_argument('-p', '--pretty', action='store_true', help='pretty print mypy errors')
parser.add_argument('-C', '--clear', action='store_true', help='clear the terminal before running mypy')
+ parser.add_argument('--allver', action='store_true', help='Check all supported versions of python')
- opts = parser.parse_args()
+ opts, args = parser.parse_known_args()
if opts.pretty:
args.append('--pretty')
to_check.extend(modules)
if to_check:
+ command = [opts.mypy] if opts.mypy else [sys.executable, '-m', 'mypy']
if not opts.quiet:
print('Running mypy (this can take some time) ...')
- p = subprocess.run(
- [sys.executable, '-m', 'mypy', '--python-version', '3.10'] + args + to_check,
- cwd=root,
- )
- return p.returncode
+ retcode = subprocess.run(command + args + to_check, cwd=root).returncode
+ if opts.allver and retcode == 0:
+ for minor in range(7, sys.version_info[1]):
+ if not opts.quiet:
+ print(f'Checking mypy with python version: 3.{minor}')
+ p = subprocess.run(command + args + to_check + [f'--python-version=3.{minor}'], cwd=root)
+ if p.returncode != 0:
+ retcode = p.returncode
+ return retcode
else:
if not opts.quiet:
print('nothing to do...')
import typing as T
import xml.etree.ElementTree as ET
import collections
+import importlib.util
from mesonbuild import build
from mesonbuild import environment
from mesonbuild.mlog import blue, bold, cyan, green, red, yellow, normal_green
from mesonbuild.coredata import backendlist, version as meson_version
from mesonbuild.modules.python import PythonExternalProgram
-from run_tests import get_fake_options, run_configure, get_meson_script
-from run_tests import get_backend_commands, get_backend_args_for_dir, Backend
-from run_tests import ensure_backend_detects_changes
-from run_tests import guess_backend
+from run_tests import (
+ get_fake_options, run_configure, get_meson_script, get_backend_commands,
+ get_backend_args_for_dir, Backend, ensure_backend_detects_changes,
+ guess_backend, handle_meson_skip_test,
+)
+
if T.TYPE_CHECKING:
from types import FrameType
from mesonbuild.environment import Environment
from mesonbuild._typing import Protocol
from concurrent.futures import Future
- from mesonbuild.modules.python import PythonIntrospectionDict
class CompilerArgumentType(Protocol):
cross_file: str
self.path = raw['file']
self.typ = raw['type']
self.platform = raw.get('platform', None)
- self.language = raw.get('language', 'c') # type: str
+ self.language = raw.get('language', 'c')
- version = raw.get('version', '') # type: str
+ version = raw.get('version', '')
if version:
- self.version = version.split('.') # type: T.List[str]
+ self.version = version.split('.')
else:
# split on '' will return [''], we want an empty list though
self.version = []
canonical_compiler = 'msvc'
python_suffix = python.info['suffix']
-
+ python_limited_suffix = python.info['limited_api_suffix']
has_pdb = False
if self.language in {'c', 'cpp'}:
has_pdb = canonical_compiler == 'msvc'
elif self.language == 'd':
- # dmd's optlink does not genearte pdb iles
+ # dmd's optlink does not generate pdb files
has_pdb = env.coredata.compilers.host['d'].linker.id in {'link', 'lld-link'}
# Abort if the platform does not match
return None
# Handle the different types
- if self.typ in {'py_implib', 'python_lib', 'python_file'}:
+ if self.typ in {'py_implib', 'py_limited_implib', 'python_lib', 'python_limited_lib', 'python_file', 'python_bytecode'}:
val = p.as_posix()
val = val.replace('@PYTHON_PLATLIB@', python.platlib)
val = val.replace('@PYTHON_PURELIB@', python.purelib)
return p
if self.typ == 'python_lib':
return p.with_suffix(python_suffix)
+ if self.typ == 'python_limited_lib':
+ return p.with_suffix(python_limited_suffix)
if self.typ == 'py_implib':
p = p.with_suffix(python_suffix)
if env.machines.host.is_windows() and canonical_compiler == 'msvc':
return p.with_suffix('.dll.a')
else:
return None
- elif self.typ in {'file', 'dir'}:
+ if self.typ == 'py_limited_implib':
+ p = p.with_suffix(python_limited_suffix)
+ if env.machines.host.is_windows() and canonical_compiler == 'msvc':
+ return p.with_suffix('.lib')
+ elif env.machines.host.is_windows() or env.machines.host.is_cygwin():
+ return p.with_suffix('.dll.a')
+ else:
+ return None
+ if self.typ == 'python_bytecode':
+ return p.parent / importlib.util.cache_from_source(p.name)
+ elif self.typ in {'file', 'dir', 'link'}:
return p
elif self.typ == 'shared_lib':
if env.machines.host.is_windows() or env.machines.host.is_cygwin():
suffix = '{}.{}'.format(suffix, '.'.join(self.version))
return p.with_suffix(suffix)
elif self.typ == 'exe':
- if env.machines.host.is_windows() or env.machines.host.is_cygwin():
+ if 'mwcc' in canonical_compiler:
+ return p.with_suffix('.nef')
+ elif env.machines.host.is_windows() or env.machines.host.is_cygwin():
return p.with_suffix('.exe')
elif self.typ == 'pdb':
if self.version:
if not abs_p.is_dir():
raise RuntimeError(f'{p} is not a directory')
return [x.relative_to(installdir) for x in abs_p.rglob('*') if x.is_file() or x.is_symlink()]
+ elif self.typ == 'link':
+ abs_p = installdir / p
+ if not abs_p.is_symlink():
+ raise RuntimeError(f'{p} is not a symlink')
+ return [p]
else:
return [p]
@functools.total_ordering
class TestDef:
- def __init__(self, path: Path, name: T.Optional[str], args: T.List[str], skip: bool = False):
+ def __init__(self, path: Path, name: T.Optional[str], args: T.List[str], skip: bool = False, skip_category: bool = False):
self.category = path.parts[1]
self.path = path
self.name = name
self.args = args
self.skip = skip
self.env = os.environ.copy()
- self.installed_files = [] # type: T.List[InstalledFile]
- self.do_not_set_opts = [] # type: T.List[str]
- self.stdout = [] # type: T.List[T.Dict[str, str]]
+ self.installed_files: T.List[InstalledFile] = []
+ self.do_not_set_opts: T.List[str] = []
+ self.stdout: T.List[T.Dict[str, str]] = []
+ self.skip_category = skip_category
self.skip_expected = False
# Always print a stack trace for Meson exceptions
return (s_id, self.path, self.name or '') < (o_id, other.path, other.name or '')
return NotImplemented
+failing_testcases: T.List[str] = []
failing_logs: T.List[str] = []
print_debug = 'MESON_PRINT_TEST_OUTPUT' in os.environ
under_ci = 'CI' in os.environ
+ci_is_github = 'GITHUB_ACTIONS' in os.environ
raw_ci_jobname = os.environ.get('MESON_CI_JOBNAME', None)
ci_jobname = raw_ci_jobname if raw_ci_jobname != 'thirdparty' else None
do_debug = under_ci or print_debug
def validate_install(test: TestDef, installdir: Path, env: environment.Environment) -> str:
ret_msg = ''
- expected_raw = [] # type: T.List[Path]
+ expected_raw: T.List[Path] = []
for i in test.installed_files:
try:
expected_raw += i.get_paths(host_c_compiler, env, installdir)
def _run_ci_include(args: T.List[str]) -> str:
+ header = f'Included file {args[0]}:'
+ footer = ''
+ if ci_is_github:
+ header = f'::group::==== {header} ===='
+ footer = '::endgroup::'
if not args:
return 'At least one parameter required'
try:
data = Path(args[0]).read_text(errors='ignore', encoding='utf-8')
- return 'Included file {}:\n{}\n'.format(args[0], data)
+ return f'{header}\n{data}\n{footer}\n'
except Exception:
- return 'Failed to open {}'.format(args[0])
+ return 'Failed to open {}\n'.format(args[0])
ci_commands = {
'ci_include': _run_ci_include
cmd = shlex.split(l[11:])
if not cmd or cmd[0] not in ci_commands:
continue
- res += ['CI COMMAND {}:\n{}\n'.format(cmd[0], ci_commands[cmd[0]](cmd[1:]))]
+ res += ['CI COMMAND {}:\n{}'.format(cmd[0], ci_commands[cmd[0]](cmd[1:]))]
return res
class OutputMatch:
# coded to run as a batch process.
def clear_internal_caches() -> None:
import mesonbuild.interpreterbase
- from mesonbuild.dependencies import CMakeDependency
+ from mesonbuild.dependencies.cmake import CMakeDependency
from mesonbuild.mesonlib import PerMachine
mesonbuild.interpreterbase.FeatureNew.feature_registry = {}
CMakeDependency.class_cmakeinfo = PerMachine(None, None)
sys.stderr = mystderr = StringIO()
old_cwd = os.getcwd()
os.chdir(testdir)
- test_log_fname = Path('meson-logs', 'testlog.txt')
+ test_log_fname = os.path.join('meson-logs', 'testlog.txt')
try:
returncode_test = mtest.run_with_args(['--no-rebuild'])
- if test_log_fname.exists():
- test_log = test_log_fname.open(encoding='utf-8', errors='ignore').read()
+ if os.path.exists(test_log_fname):
+ test_log = _run_ci_include([test_log_fname])
else:
test_log = ''
returncode_benchmark = mtest.run_with_args(['--no-rebuild', '--benchmark', '--logbase', 'benchmarklog'])
gen_args.extend(['--native-file', nativefile.as_posix()])
if crossfile.exists():
gen_args.extend(['--cross-file', crossfile.as_posix()])
- (returncode, stdo, stde) = run_configure(gen_args, env=test.env, catch_exception=True)
- try:
- logfile = Path(test_build_dir, 'meson-logs', 'meson-log.txt')
- with logfile.open(errors='ignore', encoding='utf-8') as fid:
- mesonlog = fid.read()
- except Exception:
+ inprocess, res = run_configure(gen_args, env=test.env, catch_exception=True)
+ returncode, stdo, stde = res
+ cmd = '(inprocess) $ ' if inprocess else '$ '
+ cmd += mesonlib.join_args(gen_args)
+ logfile = os.path.join(test_build_dir, 'meson-logs', 'meson-log.txt')
+ if os.path.exists(logfile):
+ mesonlog = '\n'.join((cmd, _run_ci_include([logfile])))
+ else:
mesonlog = no_meson_log_msg
cicmds = run_ci_commands(mesonlog)
testresult = TestResult(cicmds)
- testresult.add_step(BuildStep.configure, stdo, stde, mesonlog, time.time() - gen_start)
+ testresult.add_step(BuildStep.configure, '\n'.join((cmd, stdo)), stde, mesonlog, time.time() - gen_start)
output_msg = validate_output(test, stdo, stde)
testresult.mlog += output_msg
if output_msg:
# Build with subprocess
def build_step() -> None:
build_start = time.time()
- pc, o, e = Popen_safe(compile_commands + dir_args, cwd=test_build_dir)
- testresult.add_step(BuildStep.build, o, e, '', time.time() - build_start)
+ pc, o, _ = Popen_safe(compile_commands + dir_args, cwd=test_build_dir, stderr=subprocess.STDOUT)
+ testresult.add_step(BuildStep.build, o, '', '', time.time() - build_start)
if should_fail == 'build':
if pc.returncode != 0:
raise testresult
# Test is expected to skip if os matches
if 'skip_on_os' in test_def:
- mesonenv = environment.Environment(None, None, get_fake_options('/'))
+ mesonenv = environment.Environment('', '', get_fake_options('/'))
for skip_os in test_def['skip_on_os']:
if skip_os.startswith('!'):
if mesonenv.machines.host.system != skip_os[1:]:
return (skip, skip_expected)
-def load_test_json(t: TestDef, stdout_mandatory: bool) -> T.List[TestDef]:
+def load_test_json(t: TestDef, stdout_mandatory: bool, skip_category: bool = False) -> T.List[TestDef]:
all_tests: T.List[TestDef] = []
test_def = {}
test_def_file = t.path / 'test.json'
test_def = json.loads(test_def_file.read_text(encoding='utf-8'))
# Handle additional environment variables
- env = {} # type: T.Dict[str, str]
+ env: T.Dict[str, str] = {}
if 'env' in test_def:
assert isinstance(test_def['env'], dict)
env = test_def['env']
env[key] = val
# Handle installed files
- installed = [] # type: T.List[InstalledFile]
+ installed: T.List[InstalledFile] = []
if 'installed' in test_def:
installed = [InstalledFile(x) for x in test_def['installed']]
raise RuntimeError(f"{test_def_file} must contain a non-empty stdout key")
# Handle the do_not_set_opts list
- do_not_set_opts = test_def.get('do_not_set_opts', []) # type: T.List[str]
+ do_not_set_opts: T.List[str] = test_def.get('do_not_set_opts', [])
(t.skip, t.skip_expected) = _skip_keys(test_def)
new_opt_list: T.List[T.List[T.Tuple[str, str, bool, bool]]]
# 'matrix; entry is present, so build multiple tests from matrix definition
- opt_list = [] # type: T.List[T.List[T.Tuple[str, str, bool, bool]]]
+ opt_list: T.List[T.List[T.Tuple[str, str, bool, bool]]] = []
matrix = test_def['matrix']
assert "options" in matrix
for key, val in matrix["options"].items():
assert isinstance(val, list)
- tmp_opts = [] # type: T.List[T.Tuple[str, str, bool, bool]]
+ tmp_opts: T.List[T.Tuple[str, str, bool, bool]] = []
for i in val:
assert isinstance(i, dict)
assert "val" in i
opts = [f'-D{x[0]}={x[1]}' for x in i if x[1] is not None]
skip = any([x[2] for x in i])
skip_expected = any([x[3] for x in i])
- test = TestDef(t.path, name, opts, skip or t.skip)
+ test = TestDef(t.path, name, opts, skip or t.skip, skip_category)
test.env.update(env)
test.installed_files = installed
test.do_not_set_opts = do_not_set_opts
return all_tests
-def gather_tests(testdir: Path, stdout_mandatory: bool, only: T.List[str]) -> T.List[TestDef]:
+def gather_tests(testdir: Path, stdout_mandatory: bool, only: T.List[str], skip_category: bool) -> T.List[TestDef]:
all_tests: T.List[TestDef] = []
for t in testdir.iterdir():
# Filter non-tests files (dot files, etc)
if not t.is_dir() or t.name.startswith('.'):
continue
+ if t.name in {'18 includedirxyz'}:
+ continue
if only and not any(t.name.startswith(prefix) for prefix in only):
continue
- test_def = TestDef(t, None, [])
- all_tests.extend(load_test_json(test_def, stdout_mandatory))
+ test_def = TestDef(t, None, [], skip_category=skip_category)
+ all_tests.extend(load_test_json(test_def, stdout_mandatory, skip_category))
return sorted(all_tests)
def have_objc_compiler(use_tmp: bool) -> bool:
with TemporaryDirectoryWinProof(prefix='b ', dir=None if use_tmp else '.') as build_dir:
- env = environment.Environment(None, build_dir, get_fake_options('/'))
+ env = environment.Environment('', build_dir, get_fake_options('/'))
try:
objc_comp = detect_objc_compiler(env, MachineChoice.HOST)
except mesonlib.MesonException:
def have_objcpp_compiler(use_tmp: bool) -> bool:
with TemporaryDirectoryWinProof(prefix='b ', dir=None if use_tmp else '.') as build_dir:
- env = environment.Environment(None, build_dir, get_fake_options('/'))
+ env = environment.Environment('', build_dir, get_fake_options('/'))
try:
objcpp_comp = detect_objcpp_compiler(env, MachineChoice.HOST)
except mesonlib.MesonException:
TestCategory('swift', 'swift', backend not in (Backend.ninja, Backend.xcode) or not shutil.which('swiftc')),
# CUDA tests on Windows: use Ninja backend: python run_project_tests.py --only cuda --backend ninja
TestCategory('cuda', 'cuda', backend not in (Backend.ninja, Backend.xcode) or not shutil.which('nvcc')),
- TestCategory('python3', 'python3', backend is not Backend.ninja),
+ TestCategory('python3', 'python3', backend is not Backend.ninja or 'python3' not in sys.executable),
TestCategory('python', 'python'),
TestCategory('fpga', 'fpga', shutil.which('yosys') is None),
TestCategory('frameworks', 'frameworks'),
assert key in categories, f'key `{key}` is not a recognized category'
all_tests = [t for t in all_tests if t.category in only.keys()]
- gathered_tests = [(t.category, gather_tests(Path('test cases', t.subdir), t.stdout_mandatory, only[t.category]), t.skip) for t in all_tests]
+ gathered_tests = [(t.category, gather_tests(Path('test cases', t.subdir), t.stdout_mandatory, only[t.category], t.skip), t.skip) for t in all_tests]
return gathered_tests
def run_tests(all_tests: T.List[T.Tuple[str, T.List[TestDef], bool]],
RunFutureUnion = T.Union[TestRunFuture, LogRunFuture]
-def test_emits_skip_msg(line: str) -> bool:
- for prefix in {'Problem encountered', 'Assert failed', 'Failed to configure the CMake subproject'}:
- if f'{prefix}: MESON_SKIP_TEST' in line:
- return True
- return False
-
def _run_tests(all_tests: T.List[T.Tuple[str, T.List[TestDef], bool]],
log_name_base: str,
failfast: bool,
skip_as_expected = True
else:
# skipped due to test outputting 'MESON_SKIP_TEST'
- for l in result.stdo.splitlines():
- if test_emits_skip_msg(l):
- is_skipped = True
- offset = l.index('MESON_SKIP_TEST') + 16
- skip_reason = l[offset:].strip()
- break
- else:
- is_skipped = False
- skip_reason = ''
+ is_skipped, skip_reason = handle_meson_skip_test(result.stdo)
if not skip_dont_care(t):
skip_as_expected = (is_skipped == t.skip_expected)
else:
if is_skipped and skip_as_expected:
f.update_log(TestStatus.SKIP)
- safe_print(bold('Reason:'), skip_reason)
+ if not t.skip_category:
+ safe_print(bold('Reason:'), skip_reason)
current_test = ET.SubElement(current_suite, 'testcase', {'name': testname, 'classname': t.category})
ET.SubElement(current_test, 'skipped', {})
continue
if not skip_as_expected:
failing_tests += 1
if is_skipped:
- skip_msg = 'Test asked to be skipped, but was not expected to'
+ skip_msg = f'Test asked to be skipped ({skip_reason}), but was not expected to'
status = TestStatus.UNEXSKIP
else:
skip_msg = 'Test ran, but was expected to be skipped'
result.msg = f"{skip_msg} for MESON_CI_JOBNAME '{ci_jobname}'"
f.update_log(status)
+ safe_print(bold('Reason:'), result.msg)
current_test = ET.SubElement(current_suite, 'testcase', {'name': testname, 'classname': t.category})
ET.SubElement(current_test, 'failure', {'message': result.msg})
continue
left_w = max(3, left_w)
right_w = cols - left_w - name_len - 2
right_w = max(3, right_w)
+ failing_testcases.append(name_str)
failing_logs.append(f'\n\x1b[31m{"="*left_w}\x1b[0m {name_str} \x1b[31m{"="*right_w}\x1b[0m\n')
+ _during = bold('Failed during:')
+ _reason = bold('Reason:')
+ failing_logs.append(f'{_during} {result.step.name}\n{_reason} {result.msg}\n')
if result.step == BuildStep.configure and result.mlog != no_meson_log_msg:
# For configure failures, instead of printing stdout,
# print the meson log if available since it's a superset
if options.native_file:
fake_opts.native_file = [options.native_file]
- env = environment.Environment(None, None, fake_opts)
+ env = environment.Environment('', '', fake_opts)
print_compilers(env, MachineChoice.HOST)
if options.cross_file:
print('{0:<{2}}: {1}'.format(tool.tool, get_version(tool), max_width))
print()
+tmpdir = list(Path('.').glob('**/*install functions and follow symlinks'))
+print(tmpdir)
+assert(len(tmpdir) == 1)
+symlink_test_dir = tmpdir[0]
+symlink_file1 = symlink_test_dir / 'foo/link1'
+symlink_file2 = symlink_test_dir / 'foo/link2.h'
+del tmpdir
+
def clear_transitive_files() -> None:
a = Path('test cases/common')
for d in a.glob('*subproject subdir/subprojects/subsubsub*'):
mesonlib.windows_proof_rmtree(str(d))
else:
mesonlib.windows_proof_rm(str(d))
+ try:
+ symlink_file1.unlink()
+ except FileNotFoundError:
+ pass
+ try:
+ symlink_file2.unlink()
+ except FileNotFoundError:
+ pass
+
+def setup_symlinks() -> None:
+ symlink_file1.symlink_to('file1')
+ symlink_file2.symlink_to('file1')
if __name__ == '__main__':
if under_ci and not raw_ci_jobname:
options.extra_args += ['--native-file', options.native_file]
clear_transitive_files()
+ setup_symlinks()
print('Meson build system', meson_version, 'Project Tests')
- print('Using python', sys.version.split('\n')[0])
+ print('Using python', sys.version.split('\n')[0], f'({sys.executable!r})')
if 'VSCMD_VER' in os.environ:
print('VSCMD version', os.environ['VSCMD_VER'])
setup_commands(options.backend)
(passing_tests, failing_tests, skipped_tests) = res
except StopException:
pass
- print()
- print('Total passed tests: ', green(str(passing_tests)))
- print('Total failed tests: ', red(str(failing_tests)))
- print('Total skipped tests:', yellow(str(skipped_tests)))
if failing_tests > 0:
print('\nMesonlogs of failing tests\n')
for l in failing_logs:
print(l, '\n')
except UnicodeError:
print(l.encode('ascii', errors='replace').decode(), '\n')
+ print()
+ print('Total passed tests: ', green(str(passing_tests)))
+ print('Total failed tests: ', red(str(failing_tests)))
+ print('Total skipped tests:', yellow(str(skipped_tests)))
+ if failing_tests > 0:
+ print('\nAll failures:')
+ for c in failing_testcases:
+ print(f' -> {c}')
for name, dirs, _ in all_tests:
dir_names = list({x.path.name for x in dirs})
for k, g in itertools.groupby(dir_names, key=lambda x: x.split()[0]):
import typing as T
from mesonbuild import mlog
-from run_project_tests import TestDef, load_test_json, run_test, BuildStep, test_emits_skip_msg
+from run_tests import handle_meson_skip_test
+from run_project_tests import TestDef, load_test_json, run_test, BuildStep
from run_project_tests import setup_commands, detect_system_compiler, print_tool_versions
if T.TYPE_CHECKING:
subtests: T.List[int]
backend: str
extra_args: T.List[str]
+ quick: bool
def main() -> None:
parser.add_argument('--cross-file', action='store', help='File describing cross compilation environment.')
parser.add_argument('--native-file', action='store', help='File describing native compilation environment.')
parser.add_argument('--use-tmpdir', action='store_true', help='Use tmp directory for temporary files.')
+ parser.add_argument('--quick', action='store_true', help='Skip some compiler and tool checking')
args = T.cast('ArgumentType', parser.parse_args())
setup_commands(args.backend)
- detect_system_compiler(args)
- print_tool_versions()
+ if not args.quick:
+ detect_system_compiler(args)
+ print_tool_versions()
test = TestDef(args.case, args.case.stem, [])
tests = load_test_json(test, False)
is_skipped = True
skip_reason = 'not run because preconditions were not met'
else:
- for l in result.stdo.splitlines():
- if test_emits_skip_msg(l):
- is_skipped = True
- offset = l.index('MESON_SKIP_TEST') + 16
- skip_reason = l[offset:].strip()
- break
- else:
- is_skipped = False
- skip_reason = ''
+ is_skipped, skip_reason = handle_meson_skip_test(result.stdo)
if is_skipped:
msg = mlog.yellow('SKIP:')
from mesonbuild.compilers.c import CCompiler
from mesonbuild.compilers.detect import detect_c_compiler
-from mesonbuild import dependencies
+from mesonbuild.dependencies.pkgconfig import PkgConfigInterface
from mesonbuild import mesonlib
from mesonbuild import mesonmain
from mesonbuild import mtest
env = Environment(sdir, bdir, opts)
env.coredata.options[OptionKey('args', lang='c')] = 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()
return env
def get_convincing_fake_env_and_cc(bdir, prefix):
else:
exe_suffix = ''
+def handle_meson_skip_test(out: str) -> T.Tuple[bool, str]:
+ for line in out.splitlines():
+ for prefix in {'Problem encountered', 'Assert failed', 'Failed to configure the CMake subproject'}:
+ if f'{prefix}: MESON_SKIP_TEST' in line:
+ offset = line.index('MESON_SKIP_TEST') + 16
+ reason = line[offset:].strip()
+ return (True, reason)
+ return (False, '')
+
def get_meson_script() -> str:
'''
Guess the meson that corresponds to the `mesonbuild` that has been imported
out = StringIO()
with mock.patch.object(sys, 'stdout', out), mock.patch.object(sys, 'stderr', out):
returncode = mtest.run_with_args(commandlist)
- return returncode, stdout.getvalue()
+ return returncode, out.getvalue()
def clear_meson_configure_class_caches() -> None:
- CCompiler.find_library_cache = {}
- CCompiler.find_framework_cache = {}
- dependencies.PkgConfigDependency.pkgbin_cache = {}
- dependencies.PkgConfigDependency.class_pkgbin = mesonlib.PerMachine(None, None)
- mesonlib.project_meson_versions = collections.defaultdict(str)
+ CCompiler.find_library_cache.clear()
+ CCompiler.find_framework_cache.clear()
+ PkgConfigInterface.class_impl.assign(False, False)
+ 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]:
stderr = StringIO()
pc, o, e = mesonlib.Popen_safe(full_command, env=env)
return pc.returncode, o, e
-def run_configure(commandlist: T.List[str], env: T.Optional[T.Dict[str, str]] = None, catch_exception: bool = False) -> T.Tuple[int, str, str]:
+def run_configure(commandlist: T.List[str], env: T.Optional[T.Dict[str, str]] = None, catch_exception: bool = False) -> T.Tuple[bool, T.Tuple[int, str, str]]:
global meson_exe
if meson_exe:
- return run_configure_external(meson_exe + commandlist, env=env)
- return run_configure_inprocess(commandlist, env=env, catch_exception=catch_exception)
+ return (False, run_configure_external(meson_exe + commandlist, env=env))
+ return (True, run_configure_inprocess(commandlist, env=env, catch_exception=catch_exception))
def print_system_info():
print(mlog.bold('System information.'))
print('')
print(flush=True)
+def subprocess_call(cmd, **kwargs):
+ print(f'$ {mesonlib.join_args(cmd)}')
+ return subprocess.call(cmd, **kwargs)
+
def main():
print_system_info()
parser = argparse.ArgumentParser()
parser.add_argument('--no-unittests', action='store_true', default=False)
(options, _) = parser.parse_known_args()
returncode = 0
- backend, _ = guess_backend(options.backend, shutil.which('msbuild'))
+ _, backend_flags = guess_backend(options.backend, shutil.which('msbuild'))
no_unittests = options.no_unittests
# Running on a developer machine? Be nice!
if not mesonlib.is_windows() and not mesonlib.is_haiku() and 'CI' not in os.environ:
cmd = mesonlib.python_command + ['run_meson_command_tests.py', '-v']
if options.failfast:
cmd += ['--failfast']
- returncode += subprocess.call(cmd, env=env)
+ returncode += subprocess_call(cmd, env=env)
if options.failfast and returncode != 0:
return returncode
if no_unittests:
else:
print(mlog.bold('Running unittests.'))
print(flush=True)
- cmd = mesonlib.python_command + ['run_unittests.py', '--backend=' + backend.name, '-v']
+ cmd = mesonlib.python_command + ['run_unittests.py', '-v'] + backend_flags
if options.failfast:
cmd += ['--failfast']
- returncode += subprocess.call(cmd, env=env)
+ returncode += subprocess_call(cmd, env=env)
if options.failfast and returncode != 0:
return returncode
cmd = mesonlib.python_command + ['run_project_tests.py'] + sys.argv[1:]
- returncode += subprocess.call(cmd, env=env)
+ returncode += subprocess_call(cmd, env=env)
else:
cross_test_args = mesonlib.python_command + ['run_cross_test.py']
for cf in options.cross:
cmd += ['--failfast']
if options.cross_only:
cmd += ['--cross-only']
- returncode += subprocess.call(cmd, env=env)
+ returncode += subprocess_call(cmd, env=env)
if options.failfast and returncode != 0:
return returncode
return returncode
import mesonbuild.modules.pkgconfig
from unittests.allplatformstests import AllPlatformTests
+from unittests.cargotests import CargoVersionTest, CargoCfgTest
from unittests.darwintests import DarwinTests
from unittests.failuretests import FailureTests
from unittests.linuxcrosstests import LinuxCrossArmTests, LinuxCrossMingwTests
# Let there be colors!
if 'CI' in os.environ:
pytest_args += ['--color=yes']
- pytest_args += ['./run_unittests.py']
+ pytest_args += ['unittests']
pytest_args += convert_args(sys.argv[1:])
# Always disable pytest-cov because we use a custom setup
try:
author = Jussi Pakkanen
author_email = jpakkane@gmail.com
url = https://mesonbuild.com
+project_urls =
+ Source = https://github.com/mesonbuild/meson
keywords =
meson
mesonbuild
build system
cmake
license = Apache License, Version 2.0
-license_file = COPYING
+license_files = COPYING
classifiers =
Development Status :: 5 - Production/Stable
Environment :: Console
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
+ Programming Language :: Python :: 3.12
Topic :: Software Development :: Build Tools
long_description = Meson is a cross-platform build system designed to be both as fast and as user friendly as possible. It supports many languages and compilers, including GCC, Clang, PGI, Intel, and Visual Studio. Its build definitions are written in a simple non-Turing complete DSL.
[tool:pytest]
python_classes =
python_files =
- run_unittests.py
+ unittests/*tests.py
--- /dev/null
+@PACKAGE_INIT@
--- /dev/null
+project('cmakePackagePrefixDir', 'c', version: '1.0.0')
+
+cmake = import('cmake')
+
+cmake.configure_package_config_file(
+ name: 'cmakePackagePrefixDir',
+ input: 'cmakePackagePrefixDirConfig.cmake.in',
+ configuration: configuration_data(),
+)
+
+# NOTE: can't use fs.read because cmakePackagePrefixDirConfig.cmake is in build_dir
+python = find_program('python3')
+lines = run_command(python, '-c',
+ '[print(line, end="") for line in open("@0@")]'.format(meson.current_build_dir() / 'cmakePackagePrefixDirConfig.cmake'), check : true,
+).stdout().split('\n')
+
+message(lines)
+
+assert(lines[5] == 'get_filename_component(PACKAGE_PREFIX_DIR "${CMAKE_CURRENT_LIST_DIR}/../../.." ABSOLUTE)')
--- /dev/null
+{
+ "installed": [
+ {"type": "file", "file": "usr/lib/cmake/cmakePackagePrefixDir/cmakePackagePrefixDirConfig.cmake"}
+ ]
+}
--- /dev/null
+#include <iostream>
+#include <cmMod.hpp>
+
+using namespace std;
+
+int main(void) {
+ cmModClass obj("Hello");
+ cout << obj.getStr() << endl;
+ return 0;
+}
--- /dev/null
+project('cmakeSubTest', ['c', 'cpp'])
+
+# Fallback to a CMake subproject
+sub_dep = dependency('cmModLib++')
+exe1 = executable('main', ['main.cpp'], dependencies: [sub_dep])
+test('test1', exe1)
+
+# Subproject contains both meson.build and CMakeLists.txt. It should default
+# to meson but wrap force cmake.
+subproject('force_cmake')
+
+testcase expect_error('Wrap method \'notfound\' is not supported, must be one of: meson, cmake, cargo')
+ subproject('broken_method')
+endtestcase
+
+# With method=meson we can't use cmake.subproject()
+cmake = import('cmake')
+testcase expect_error('Wrap method is \'meson\' but we are trying to configure it with cmake')
+ cmake.subproject('meson_method')
+endtestcase
+
+# cmake.subproject() force cmake method even if meson.build exists.
+testcase expect_error('Subproject exists but has no CMakeLists.txt file.')
+ cmake.subproject('meson_subp')
+endtestcase
+
+# Without specifying the method it defaults to meson even if CMakeLists.txt exists.
+testcase expect_error('Subproject exists but has no meson.build file.')
+ subproject('cmake_subp')
+endtestcase
--- /dev/null
+[wrap-file]
+method=notfound
--- /dev/null
+[wrap-file]
+method = cmake
+
+[provide]
+cmModLib++ = cmModLib___dep
--- /dev/null
+cmake_minimum_required(VERSION 3.5)
+
+project(cmMod)
+set(CMAKE_CXX_STANDARD 14)
+
+include_directories(${CMAKE_CURRENT_BINARY_DIR})
+
+add_definitions("-DDO_NOTHING_JUST_A_FLAG=1")
+
+add_library(cmModLib++ SHARED cmMod.cpp)
+target_compile_definitions(cmModLib++ PRIVATE MESON_MAGIC_FLAG=21)
+target_compile_definitions(cmModLib++ INTERFACE MESON_MAGIC_FLAG=42)
+
+# Test PCH support
+if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.16.0")
+ target_precompile_headers(cmModLib++ PRIVATE "cpp_pch.hpp")
+endif()
+
+include(GenerateExportHeader)
+generate_export_header(cmModLib++)
--- /dev/null
+#include "cmMod.hpp"
+
+using namespace std;
+
+#if MESON_MAGIC_FLAG != 21
+#error "Invalid MESON_MAGIC_FLAG (private)"
+#endif
+
+cmModClass::cmModClass(string foo) {
+ str = foo + " World";
+}
+
+string cmModClass::getStr() const {
+ return str;
+}
--- /dev/null
+#pragma once
+
+#include "cmmodlib++_export.h"
+#include <string>
+
+#if MESON_MAGIC_FLAG != 42 && MESON_MAGIC_FLAG != 21
+#error "Invalid MESON_MAGIC_FLAG"
+#endif
+
+class CMMODLIB___EXPORT cmModClass {
+private:
+ std::string str;
+
+public:
+ cmModClass(std::string foo);
+
+ std::string getStr() const;
+};
--- /dev/null
+#include <vector>
+#include <string>
--- /dev/null
+cmake_minimum_required(VERSION 3.5)
+project(cmModDummy)
--- /dev/null
+[wrap-file]
+method=cmake
--- /dev/null
+cmake_minimum_required(VERSION 3.5)
+project(cmModBoth)
--- /dev/null
+project('both methods')
+
+# Ensure the meson method is not used.
+notfound()
--- /dev/null
+[wrap-file]
+method=meson
--- /dev/null
+project('dummy')
cm = import('cmake')
-sub_pro = cm.subproject('nothinig', required: false)
+sub_pro = cm.subproject('nothing', required: false)
assert(not sub_pro.found(), 'subproject found() reports wrong value')
input_file = os.path.join(os.environ['MESON_SOURCE_ROOT'], 'raw.dat')
output_file = os.path.join(os.environ['MESON_BUILD_ROOT'], 'generated.h')
-with open(input_file) as f:
+with open(input_file, encoding='utf-8') as f:
data = f.readline().strip()
-with open(output_file, 'w') as f:
+with open(output_file, 'w', encoding='utf-8') as f:
f.write(template.format(data, sys.argv[1], sys.argv[2]))
#
# No-one has reverse engineered the naming scheme so we would access them.
# IF you feel up to the challenge, patches welcome.
- error('MESON_SKIP_TEST, Xcode can not extract objs when they would have the same filename.')
+ error('MESON_SKIP_TEST, Xcode cannot extract objs when they would have the same filename.')
endif
lib = library('somelib', ['lib.c', 'src/lib.c'])
--- /dev/null
+/* SPDX-License-Identifier: Apache-2.0 */
+/* Copyright © 2023 Intel Corporation */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+int main(int argc, const char ** argv) {
+ if (argc != 3) {
+ fprintf(stderr, "%s %i %s\n", "Got incorrect number of arguments, got ", argc - 1, ", but expected 2");
+ exit(1);
+ }
+
+ FILE * input, * output;
+
+ if ((input = fopen(argv[1], "rb")) == NULL) {
+ exit(1);
+ }
+ if ((output = fopen(argv[2], "wb")) == NULL) {
+ exit(1);
+ }
+
+ fprintf(output, "#pragma once\n");
+ fprintf(output, "#define ");
+
+ int bytes_copied = 0;
+ int c;
+ while((c = fgetc(input)) != EOF) {
+ if(fputc(c, output) == EOF) {
+ fprintf(stderr, "Writing to output file failed.\n");
+ return 1;
+ }
+ if(++bytes_copied > 10000) {
+ fprintf(stderr, "File copy stuck in an eternal loop!\n");
+ return 1;
+ }
+ }
+ fputc('\n', output);
+
+ fclose(input);
+ fclose(output);
+
+ return 0;
+}
--- /dev/null
+#include "res1-cpp.h"
+
+int main(void) {
+ #ifdef res1
+ return 0;
+ #else
+ return 1;
+ #endif
+}
proggie = executable('proggie', 'main.c', allinone)
test('proggie', proggie)
+
+# specifically testing that cross binaries are run with an exe_wrapper
+if meson.can_run_host_binaries()
+ gen_tool = executable('generator', 'gen.c', native : false)
+
+ c_gen = generator(
+ gen_tool,
+ output : '@BASENAME@-cpp.h',
+ arguments : ['@INPUT@', '@OUTPUT@']
+ )
+
+ hs2 = c_gen.process('res1.txt')
+
+ host_exe = executable('host_test', 'host.c', hs2, native : false)
+ test('compiled generator', host_exe)
+endif
# Shared module that does not export any symbols
shared_module('nosyms', 'nosyms.c',
+ override_options: ['werror=false'],
install : true,
install_dir : join_paths(get_option('libdir'), 'modules'))
install_dir : 'share/renamed',
rename : ['renamed 2.txt', 'renamed 3.txt'])
install_data(sources : 'to_be_renamed_4.txt', rename : 'some/nested/path.txt')
+
+install_data('subdir/data.txt', preserve_path : true)
+
+subproject('moredata')
--- /dev/null
+project('moredata')
+
+install_data('data.txt')
{"type": "file", "file": "usr/share/renamed/renamed 2.txt"},
{"type": "file", "file": "usr/share/renamed/renamed 3.txt"},
{"type": "file", "file": "etc/etcfile.dat"},
- {"type": "file", "file": "usr/bin/runscript.sh"}
+ {"type": "file", "file": "usr/bin/runscript.sh"},
+ {"type": "file", "file": "usr/share/moredata/data.txt"},
+ {"type": "file", "file": "usr/share/data install test/subdir/data.txt"}
]
}
subdir('generated')
subdir('userDefined')
subdir('withIncludeDirectories')
-subdir('withIncludeFile')
+if meson.backend() == 'xcode'
+ warning('Xcode backend does not support forced includes. Skipping "withIncludeFile" which requires this.')
+else
+ subdir('withIncludeFile')
+endif
if meson.backend() == 'xcode'
warning('Xcode backend only supports one precompiled header per target. Skipping "mixed" which has various precompiled headers.')
--- /dev/null
+configure_file(output: 'prefer-build-dir-over-src-dir.h',
+ configuration: configuration_data())
--- /dev/null
+#error "inc3/prefer-build-dir-over-src-dir.h included"
# Custom target dir with a built header
subdir('ctsub')
+# Configures a header file
+subdir('inc3')
# Defines an internal dep
subdir('sub1')
# Defines a per-target include path
test('oh', f)
# Test that the order in include_directories() is maintained
-incs = include_directories('inc1', 'inc2')
+incs = include_directories('inc1', 'inc2', 'inc3')
executable('ordertest', 'ordertest.c', include_directories: incs)
#include "hdr.h"
+#include "prefer-build-dir-over-src-dir.h"
#if !defined(SOME_DEFINE) || SOME_DEFINE != 42
#error "Should have picked up hdr.h from inc1/hdr.h"
host_system = host_machine.system()
+system_define_map = {
+ 'linux' : ['__linux__', '1'],
+ 'darwin' : ['__APPLE__', '1'],
+ 'windows' : ['_WIN32', '1'],
+ 'cygwin' : ['__CYGWIN__', '1'],
+ 'haiku' : ['__HAIKU__', '1'],
+ 'dragonfly' : ['__DragonFly__', '1'],
+ 'netbsd' : ['__NetBSD__', '1'],
+ 'openbsd' : ['__OpenBSD__', '1'],
+ 'gnu' : ['__GNU__', '1'],
+ 'sunos' : ['__sun__', '1'],
+
+ # The __FreeBSD__ define will be equal to the major version of the release
+ # (ex, in FreeBSD 11.x, __FreeBSD__ == 11). To make the test robust when
+ # being run on various versions of FreeBSD, just test that the define is
+ # set.
+ 'freebsd' : ['__FreeBSD__'],
+}
+
foreach lang : ['c', 'cpp']
cc = meson.get_compiler(lang)
- if host_system == 'linux'
- d = cc.get_define('__linux__')
- assert(d == '1', '__linux__ value is @0@ instead of 1'.format(d))
- elif host_system == 'darwin'
- d = cc.get_define('__APPLE__')
- assert(d == '1', '__APPLE__ value is @0@ instead of 1'.format(d))
- elif host_system == 'windows'
- d = cc.get_define('_WIN32')
- assert(d == '1', '_WIN32 value is @0@ instead of 1'.format(d))
- elif host_system == 'cygwin'
- d = cc.get_define('__CYGWIN__')
- assert(d == '1', '__CYGWIN__ value is @0@ instead of 1'.format(d))
- elif host_system == 'haiku'
- d = cc.get_define('__HAIKU__')
- assert(d == '1', '__HAIKU__ value is @0@ instead of 1'.format(d))
- elif host_system == 'freebsd'
- # the __FreeBSD__ define will be equal to the major version of the release
- # (ex, in FreeBSD 11.x, __FreeBSD__ == 11). To make the test robust when
- # being run on various versions of FreeBSD, just test that the define is
- # set.
- d = cc.get_define('__FreeBSD__')
- assert(d != '', '__FreeBSD__ value is unset')
- elif host_system == 'dragonfly'
- d = cc.get_define('__DragonFly__')
- assert(d == '1', '__DragonFly__ value is @0@ instead of 1'.format(d))
- elif host_system == 'netbsd'
- d = cc.get_define('__NetBSD__')
- assert(d == '1', '__NetBSD__ value is @0@ instead of 1'.format(d))
- elif host_system == 'openbsd'
- d = cc.get_define('__OpenBSD__')
- assert(d == '1', '__OpenBSD__ value is @0@ instead of 1'.format(d))
- elif host_system == 'gnu'
- d = cc.get_define('__GNU__')
- assert(d == '1', '__GNU__ value is @0@ instead of 1'.format(d))
- elif host_system == 'sunos'
- d = cc.get_define('__sun__')
- assert(d == '1', '__sun__ value is @0@ instead of 1'.format(d))
- else
+
+ if not system_define_map.has_key(host_system)
error('Please report a bug and help us improve support for this platform')
endif
+ system_define = system_define_map.get(host_system)
+
+ def_name = system_define[0]
+ def_val = cc.get_define(system_define[0])
+ def_exist = cc.has_define(system_define[0])
+
+ assert((def_val != '') == def_exist,
+ 'The has_define and get_define results for @0@ disagree with each other'.format(def_name))
+
+ if system_define.length() == 2
+ assert(def_val == system_define[1],
+ '@0@ value is @1@ instead of @2@'.format(def_name, def_val, system_define[1]))
+ elif system_define.length() == 1
+ assert(def_val != '', '@0@ value is unset'.format(def_name))
+ else
+ error('Invalid number of items in system_define array, this is a bug in the test!')
+ endif
+
if cc.find_library('z', required : false).found()
# When a C file containing #include <foo.h> is pre-processed and foo.h is
# found in the compiler's default search path, GCC inserts an extra comment
endif
# Check that an undefined value is empty.
- have = cc.get_define('MESON_FAIL_VALUE')
- assert(have == '', 'MESON_FAIL_VALUE value is "@0@" instead of ""'.format(have))
+ have_val = cc.get_define('MESON_FAIL_VALUE')
+ have = cc.has_define('MESON_FAIL_VALUE')
+ assert(have_val == '', 'MESON_FAIL_VALUE value is "@0@" instead of ""'.format(have_val))
+ assert(not have, 'MESON_FAIL_VALUE was found even though it should not have been')
+
+ # Check that an empty define is reported as existing.
+ have_val = cc.get_define('MESON_EMPTY_VALUE', prefix: ['#define MESON_EMPTY_VALUE'])
+ have = cc.has_define('MESON_EMPTY_VALUE', prefix: ['#define MESON_EMPTY_VALUE'])
+ assert(have_val == '', 'MESON_EMPTY_VALUE value is "@0@" instead of ""'.format(have_val))
+ assert(have, 'MESON_EMPTY_VALUE was not found even though it should have been')
# Check if prefix array works properly and has the expected order
have = cc.get_define('MESON_FAIL_VALUE', prefix: ['#define MESON_FAIL_VALUE 1', '#undef MESON_FAIL_VALUE'])
--- /dev/null
+{
+ "matrix": {
+ "options": {
+ "c_std": [
+ { "val": "none" },
+ { "val": "c11" }
+ ]
+ }
+ }
+}
install_dir : false,
configuration : conf)
-# test intsall_dir with install: false
+# test install_dir with install: false
cfile = configure_file(input : 'config.h.in',
output : 'do_not_get_installed_in_install_dir.h',
install : false,
* enough to fit in int16;
*/
int i;
+ /* This is unused due to below comment about GCC 8.
__m64 packed = _mm_set_pi16(arr[3], arr[2], arr[1], arr[0]);
__m64 incr = _mm_set1_pi16(1);
__m64 result = _mm_add_pi16(packed, incr);
- /* Should be
+ int64_t unpacker = (int64_t)(result);
+ */
+ /* The above should be
* int64_t unpacker = _m_to_int64(result);
* but it does not exist on 32 bit platforms for some reason.
*/
- int64_t unpacker = (int64_t)(result);
_mm_empty();
for(i=0; i<4; i++) {
/* This fails on GCC 8 when optimizations are enabled.
int expected, actual;
fptr importedfunc;
- if (argc=0) {}; // noop
+ (void)argc; // noop
#ifdef _WIN32
HMODULE h = LoadLibraryA(argv[1]);
#
# No-one has reverse engineered the naming scheme so we would access them.
# IF you feel up to the challenge, patches welcome.
- error('MESON_SKIP_TEST, Xcode can not extract objs when they would have the same filename.')
+ error('MESON_SKIP_TEST, Xcode cannot extract objs when they would have the same filename.')
endif
sources = []
get_d = get_variable('d6')
assert(is_disabler(get_d), 'get_variable should yield a disabler')
-get_fallback_d = get_variable('nonexistant', disabler())
+get_fallback_d = get_variable('nonexistent', disabler())
assert(is_disabler(get_fallback_d), 'get_variable fallback should yield a disabler')
var_true = true
# both_libraries the static has no sources and thus no compilers, resulting in
# the executable linking using the C compiler.
# https://github.com/Netflix/vmaf/issues/1107
-libccpp = both_libraries('ccpp', 'foo.cpp', 'libfile.c')
+libccpp = both_libraries('ccpp', 'foo.cpp', 'libfile.c',
+ cpp_args : ['-std=c++11'],
+ c_static_args : ['-DSTATIC_COMPILATION'],
+ cpp_static_args : ['-DSTATIC_COMPILATION'],
+)
exe = executable('prog-ccpp', 'main2.c',
link_with: libccpp.get_static_lib(),
c_args : ['-DSTATIC_COMPILATION'],
)
test('runtest-ccpp', exe)
+
+exe = executable('prog-ccpp-shared', 'main2.c',
+ link_with: libccpp.get_shared_lib(),
+)
+test('runtest-ccpp-shared', exe)
inc = include_directories('include')
subdir('src')
+
+errormsg = '''Tried to form an absolute path to a dir in the source tree.
+You should not do that but use relative paths instead, for
+directories that are part of your project.
+
+To get include path to any directory relative to the current dir do
+
+incdir = include_directories(dirname)
+
+After this incdir will contain both the current source dir as well as the
+corresponding build dir. It can then be used in any subdirectory and
+Meson will take care of all the busywork to make paths work.
+
+Dirname can even be '.' to mark the current directory. Though you should
+remember that the current source and build directories are always
+put in the include directories by default so you only need to do
+include_directories('.') if you intend to use the result in a
+different subdirectory.
+
+Note that this error message can also be triggered by
+external dependencies being installed within your source
+tree - it's not recommended to do this.
+'''
+testcase expect_error(errormsg)
+ include_directories(meson.current_source_dir() / 'include')
+endtestcase
+# Test for issue #12217
+include_directories(meson.current_source_dir() + 'xyz')
--- /dev/null
+This file is to ensure this directory exists
assert(tool.full_path() != '')
assert(tool.full_path() == tool.path())
-# six_meson_exe is an overritten project executable
+# six_meson_exe is an overridden project executable
six_prog = find_program('six_meson_exe')
assert(six_prog.found())
assert(six_prog.full_path() != '')
assert(six_prog.full_path() == six_prog.path())
+
+# We have prog-version.py in current directory, but it's version 1.0.
+# This needs to use fallback for "prog-version" name which will be version 2.0.
+prog = find_program('prog-version.py', 'prog-version', version: '>= 2.0')
+assert(prog.found())
+assert(prog.version() == '2.0')
--- /dev/null
+#! /usr/bin/env python3
+
+print('1.0')
--- /dev/null
+[wrap-file]
+directory = sub2
+
+[provide]
+program_names = prog-version
--- /dev/null
+project('sub2')
+
+prog = find_program('prog-version.py')
+meson.override_find_program('prog-version', prog)
--- /dev/null
+#! /usr/bin/env python3
+
+print('2.0')
# This header exists in the source and the builddir, but we still must not
# find it since we are looking in the system directories.
assert(not comp.check_header(non_existent_header, prefix : fallback),
- 'Found non-existent header.')
+ 'Found nonexistent header.')
endforeach
assert(not required_opt.auto(), 'Should be enabled option')
assert(required_opt.allowed(), 'Should be enabled option')
assert(required_opt.require(true, error_message: 'xyz').enabled(), 'Should be enabled option')
+assert(required_opt.enable_if(true, error_message: 'xyz').enabled(), 'Should be enabled option')
+assert(required_opt.enable_if(false, error_message: 'xyz').enabled(), 'Should be enabled option')
+assert(required_opt.disable_if(false, error_message: 'xyz').enabled(), 'Should be enabled option')
assert(required_opt.disable_auto_if(true).enabled(), 'Should be enabled option')
assert(required_opt.disable_auto_if(false).enabled(), 'Should be enabled option')
+assert(required_opt.enable_auto_if(true).enabled(), 'Should be enabled option')
+assert(required_opt.enable_auto_if(false).enabled(), 'Should be enabled option')
assert(not optional_opt.enabled(), 'Should be auto option')
assert(not optional_opt.disabled(), 'Should be auto option')
assert(optional_opt.allowed(), 'Should be auto option')
assert(optional_opt.require(true).auto(), 'Should be auto option')
assert(optional_opt.require(false, error_message: 'xyz').disabled(), 'Should be disabled auto option')
+assert(optional_opt.enable_if(true).enabled(), 'Should be enabled option')
+assert(optional_opt.enable_if(false).auto(), 'Should be auto option')
+assert(optional_opt.disable_if(true).disabled(), 'Should be disabled auto option')
+assert(optional_opt.disable_if(false).auto(), 'Should be auto option')
assert(optional_opt.disable_auto_if(true).disabled(), 'Should be disabled auto option')
assert(optional_opt.disable_auto_if(false).auto(), 'Should be auto option')
+assert(optional_opt.enable_auto_if(true).enabled(), 'Should be disabled auto option')
+assert(optional_opt.enable_auto_if(false).auto(), 'Should be auto option')
assert(not disabled_opt.enabled(), 'Should be disabled option')
assert(disabled_opt.disabled(), 'Should be disabled option')
assert(not disabled_opt.allowed(), 'Should be disabled option')
assert(disabled_opt.require(true).disabled(), 'Should be disabled option')
assert(disabled_opt.require(false, error_message: 'xyz').disabled(), 'Should be disabled option')
+assert(disabled_opt.enable_if(false).disabled(), 'Should be disabled option')
+assert(disabled_opt.disable_if(true).disabled(), 'Should be disabled option')
+assert(disabled_opt.disable_if(false).disabled(), 'Should be disabled option')
assert(disabled_opt.disable_auto_if(true).disabled(), 'Should be disabled option')
assert(disabled_opt.disable_auto_if(false).disabled(), 'Should be disabled option')
+assert(disabled_opt.enable_auto_if(true).disabled(), 'Should be disabled option')
+assert(disabled_opt.enable_auto_if(false).disabled(), 'Should be disabled option')
dep = dependency('threads', required : required_opt)
assert(dep.found(), 'Should find required "threads" dep')
-gprog = find_program('genprog.py')
+gprog = find_program('tooldir/genprog.py')
gen = generator(gprog, \
output : ['@BASENAME@.c', '@BASENAME@.h'],
assert(disabled_subproj.found() == false, 'Disabled subproject should be NOT found')
disabled_dep = dependency('', fallback: ['disabled_sub', 'libSub'], required: false)
-assert(disabled_dep.found() == false, 'Subprojetc was disabled, it should never be built.')
+assert(disabled_dep.found() == false, 'Subproject was disabled, it should never be built.')
nothing = executable('nothing', 'nothing.c', dependencies: [disabled_dep])
subproj_with_missing_dep = subproject('auto_sub_with_missing_dep', required: get_option('auto-sub-with-missing-dep'))
error('MESON_SKIP_TEST: PGI supports its own set of features, will need a separate list for PGI to test it.')
endif
-expected_result = not ['msvc', 'clang-cl', 'intel-cl'].contains(c.get_id())
+expected = {}
+expected_default = not ['msvc', 'clang-cl', 'intel-cl'].contains(c.get_id())
# Q: Why is ifunc not in this list or any of the below lists?
# A: It's too damn hard to figure out if you actually support it, since it
'sentinel',
'unused',
'used',
+ 'vector_size',
'warn_unused_result',
'weak',
+ 'dllexport',
+ 'dllimport',
]
+expected += {
+ 'dllexport': ['windows', 'cygwin'].contains(host_machine.system()),
+ 'dllimport': ['windows', 'cygwin'].contains(host_machine.system()),
+}
+
+if c.get_id() == 'gcc' and ['windows', 'cygwin'].contains(host_machine.system())
+ expected += {
+ 'visibility': false,
+ 'visibility:hidden': false,
+ 'visibility:internal': false,
+ 'visibility:protected': false,
+ }
+endif
+
if c.get_id() != 'intel'
# not supported by icc as of 19.0.0
attributes += 'weakref'
if host_machine.system() != 'darwin'
attributes += 'alias'
attributes += 'visibility'
+ attributes += 'visibility:default'
+ attributes += 'visibility:hidden'
+ attributes += 'visibility:internal'
+ attributes += 'visibility:protected'
attributes += 'alloc_size'
endif
if get_option('mode') == 'single'
foreach a : attributes
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))
endforeach
-
- win_expect = ['windows', 'cygwin'].contains(host_machine.system())
- foreach a : ['dllexport', 'dllimport']
- assert(c.has_function_attribute(a) == win_expect,
- '@0@: @1@'.format(c.get_id(), a))
- assert(cpp.has_function_attribute(a) == win_expect,
- '@0@: @1@'.format(cpp.get_id(), a))
- endforeach
else
- if not ['msvc', 'clang-cl', 'intel-cl'].contains(c.get_id())
- multi_expected = attributes
- else
- multi_expected = []
- endif
+ multi_expected = []
+ foreach a : attributes
+ if expected.get(a, expected_default)
+ multi_expected += a
+ endif
+ endforeach
multi_check = c.get_supported_function_attributes(attributes)
assert(multi_check == multi_expected, 'get_supported_function_arguments works (C)')
#endif
#if !defined(GLOBAL_HOST) && !defined(GLOBAL_BUILD)
- #error "Neither global_host nor glogal_build is set."
+ #error "Neither global_host nor global_build is set."
#endif
#if defined(GLOBAL_HOST) && defined(GLOBAL_BUILD)
test('custom_target_1', exe1)
exe1_2 = executable('exe1_2', 'custom_target.c', link_with: lib2)
-test('custom_target_2', exe2)
\ No newline at end of file
+test('custom_target_2', exe2)
+
+# Link with custom target containing a SONAME
+dummy3 = shared_library('dummy3', 'dummy.c', version: '1.0')
+t = custom_target(input: dummy, output: 'libcustom@PLAINNAME@', command: [custom_prog, '@INPUT@', '@OUTPUT@'])
+lib3 = static_library('lib3', 'outerlib.c', link_with: t)
+exe3 = executable('exe3', 'custom_target.c', link_with: lib3)
+test('custom_target_3', exe3)
project('custom target input extracted objects', 'c')
if meson.backend() == 'xcode'
- error('MESON_SKIP_TEST: sometimes Xcode puts object files in weird paths and we can not extract them.')
+ error('MESON_SKIP_TEST: sometimes Xcode puts object files in weird paths and we cannot extract them.')
endif
-project('fs module test')
+project('fs module test', 'c')
is_windows = build_machine.system() == 'windows'
# -- as_posix
assert(fs.as_posix('/') == '/', 'as_posix idempotent')
assert(fs.as_posix('\\') == '/', 'as_posix simple')
-assert(fs.as_posix('\\\\') == '/', 'as_posix simple')
+# Python 3.12 changed how these paths are handled, so deal with both.
+drivepath = fs.as_posix('\\\\')
+assert(drivepath == '/' or drivepath == '//', 'as_posix simple')
assert(fs.as_posix('foo\\bar/baz') == 'foo/bar/baz', 'as_posix mixed slash')
# -- is_absolute
assert(not fs.is_samepath('not-a-path', f2), 'is_samepath should not error if path(s) do not exist')
f = files('meson.build', 'subdir/../meson.build')
-assert(fs.is_samepath(f[0], f[1]), 'is_samepath not detercting same files')
+assert(fs.is_samepath(f[0], f[1]), 'is_samepath not detecting same files')
if not is_windows and build_machine.system() != 'cygwin'
assert(fs.is_samepath(symlink, 'meson.build'), 'symlink is_samepath fail')
assert(fs.stem('foo/bar/baz.dll') == 'baz', 'failed to get stem with suffix')
assert(fs.stem('foo/bar/baz.dll.a') == 'baz.dll', 'failed to get stem with compound suffix')
+# relative_to
+if build_machine.system() == 'windows'
+ # strings
+ assert(fs.relative_to('c:\\prefix\\lib\\foo', 'c:\\prefix') == 'lib\\foo')
+ assert(fs.relative_to('c:\\prefix\\lib', 'c:\\prefix\\bin') == '..\\lib')
+ assert(fs.relative_to('c:\\proj1\\foo', 'd:\\proj1\\bar') == 'c:\\proj1\\foo')
+ assert(fs.relative_to('prefix\\lib\\foo', 'prefix') == 'lib\\foo')
+ assert(fs.relative_to('prefix\\lib', 'prefix\\bin') == '..\\lib')
+ assert(fs.relative_to('proj1\\foo', 'proj1\\bar') == '..\\foo')
+ assert(fs.relative_to('subdir/subdirfile.txt', meson.current_source_dir()) == 'subdir\\subdirfile.txt')
+ assert(fs.relative_to(files('meson.build'), files('subdir/meson.build')) == '..\\..\\meson.build')
+ assert(fs.relative_to(files('meson.build'), 'subdir/meson.build') == '..\\..\\meson.build')
+else
+ # strings
+ assert(fs.relative_to('/prefix/lib/foo', '/prefix') == 'lib/foo')
+ assert(fs.relative_to('/prefix/lib', '/prefix/bin') == '../lib')
+ assert(fs.relative_to('prefix/lib/foo', 'prefix') == 'lib/foo')
+ assert(fs.relative_to('prefix/lib', 'prefix/bin') == '../lib')
+ assert(fs.relative_to('subdir/subdirfile.txt', meson.current_source_dir()) == 'subdir/subdirfile.txt')
+ assert(fs.relative_to(files('meson.build'), files('subdir/meson.build')) == '../../meson.build')
+ assert(fs.relative_to(files('meson.build'), 'subdir/meson.build') == '../../meson.build')
+endif
+
subdir('subdir')
subproject('subbie')
+
+testcase expect_error('File notfound does not exist.')
+ fs.read('notfound')
+endtestcase
--- /dev/null
+int
+main(void)
+{
+ return 0;
+}
assert(fs.is_samepath(meson.project_build_root(), meson.current_build_dir() / '..'), 'is_samepath not detecting same directory')
assert(fs.is_samepath(subdirfiles[0], 'subdirfile.txt'), 'is_samepath not detecting same directory when using File and str')
+
+# More relative_to to test subdir/builddir components
+
+build_to_src = fs.relative_to(meson.current_source_dir(), meson.current_build_dir())
+src_to_build = fs.relative_to(meson.current_build_dir(), meson.current_source_dir())
+
+btgt = executable('btgt', 'btgt.c')
+ctgt = fs.copyfile('subdirfile.txt')
+
+if build_machine.system() == 'windows'
+ # Test that CustomTarget works
+ assert(fs.relative_to('subdirfile.txt', ctgt) == '..\\@0@\\subdirfile.txt'.format(build_to_src))
+ assert(fs.relative_to(ctgt, 'subdirfile.txt') == '..\\@0@\\subdirfile.txt'.format(src_to_build))
+ # Test that CustomTargetIndex works
+ assert(fs.relative_to('subdirfile.txt', ctgt[0]) == '..\\@0@\\subdirfile.txt'.format(build_to_src))
+ assert(fs.relative_to(ctgt[0], 'subdirfile.txt') == '..\\@0@\\subdirfile.txt'.format(src_to_build))
+ # Test that BuildTarget works
+ assert(fs.relative_to('subdirfile.txt', btgt) == '..\\@0@\\subdirfile.txt'.format(build_to_src))
+ assert(fs.relative_to(btgt, 'subdirfile.txt') == '..\\@0@\\@1@'.format(src_to_build, fs.name(btgt.full_path())))
+else
+ # Test that CustomTarget works
+ assert(fs.relative_to('subdirfile.txt', ctgt) == '../@0@/subdirfile.txt'.format(build_to_src))
+ assert(fs.relative_to(ctgt, 'subdirfile.txt') == '../@0@/subdirfile.txt'.format(src_to_build))
+ # Test that CustomTargetIndex works
+ assert(fs.relative_to('subdirfile.txt', ctgt[0]) == '../@0@/subdirfile.txt'.format(build_to_src))
+ assert(fs.relative_to(ctgt[0], 'subdirfile.txt') == '../@0@/subdirfile.txt'.format(src_to_build))
+ # Test that BuildTarget works
+ assert(fs.relative_to('subdirfile.txt', btgt) == '../@0@/subdirfile.txt'.format(build_to_src))
+ assert(fs.relative_to(btgt, 'subdirfile.txt') == '../@0@/@1@'.format(src_to_build, fs.name(btgt.full_path())))
+endif
assert(x=='mystring', 'did not get native property with native:true and non-cross build.')
x = meson.get_external_property('astring', 'fallback', native: false)
-assert(x==ref, 'did not get get native property with native:false and non-cross build.')
+assert(x==ref, 'did not get native property with native:false and non-cross build.')
-x = meson.get_external_property('notexist', 'fallback')
+x = meson.get_external_property('nonexistent', 'fallback')
assert(x=='fallback', 'fallback did not work')
-x = meson.get_external_property('notexist', 'fallback', native: true)
+x = meson.get_external_property('nonexistent', 'fallback', native: true)
assert(x=='fallback', 'fallback native:true did not work')
-x = meson.get_external_property('notexist', 'fallback', native: false)
+x = meson.get_external_property('nonexistent', 'fallback', native: false)
assert(x=='fallback', 'fallback native:false did not work')
# Parent project is c++11 but this one uses c++14 to build.
libcpp14 = library('lib2', 'foo.cpp')
meson.override_dependency('libcpp14', declare_dependency(link_with: libcpp14))
+
+# Compiler checks should be using c++14 as well
+cxx = meson.get_compiler('cpp')
+assert(cxx.compiles(files('foo.cpp')))
limit = 32767
# NOTE: filename limit is 260 characters unless
# 1. Python >= 3.6 is being used
- # 2. Windows 10 registry has been edited to enable long pathnaems
+ # 2. Windows 10 registry has been edited to enable long pathnames
# ninja backend uses absolute filenames, so we ensure they don't exceed 260.
elif build_machine.system() == 'cygwin'
# cygwin-to-win32: see above
#pragma once
-// Git can not handle empty directories, so there must be something here.
+// Git cannot handle empty directories, so there must be something here.
#include <assert.h>
#include "gen.h"
-int main(int argc)
+int main(int argc, char **argv)
{
+ (void)argv;
+
assert(argc == 3);
return genfunc();
}
# A boolean option has been replaced by a feature with another name, old true/false values
# are accepted by the new option for backward compatibility.
-option('o6', type: 'boolean', value: 'true', deprecated: 'o7')
+option('o6', type: 'boolean', value: true, deprecated: 'o7')
option('o7', type: 'feature', value: 'enabled', deprecated: {'true': 'enabled', 'false': 'disabled'})
# A project option is replaced by a module option
-project('install structured data')
+project('install structured data', default_options: ['python.bytecompile=-1'])
install_data(
'dir1/file1',
project('long-stderr', 'c')
dumper = executable('dumper', 'dumper.c')
-test('dump-test', dumper)
-test('dump-test-TAP', dumper, protocol : 'tap')
+test('dump-test', dumper, timeout: 60)
+test('dump-test-TAP', dumper, protocol : 'tap', timeout: 60)
# dependency on the header file. This happened in GLib:
# https://gitlab.gnome.org/GNOME/glib/-/merge_requests/2917.
-python = import('python').find_installation()
+python = find_program('python3')
header = custom_target(
output: 'foo.h',
capture: true,
-int bar(void) {
- return BAR;
+int @BAR@(void) {
+ return BAR + PLOP;
}
--- /dev/null
+// Verify we preprocess as C language, otherwise including math.h would fail.
+// See https://github.com/mesonbuild/meson/issues/11940.
+#include <math.h>
add_project_arguments(['-DFOO=0', '-DBAR=0'], language: 'c')
-pp_files = cc.preprocess('foo.c', 'bar.c', output: '@PLAINNAME@')
+fs = import('fs')
+bar_content = fs.read('bar.c')
+bar_x = custom_target(
+ input: 'bar.c',
+ output: 'bar.x',
+ command: ['python3', '-c', '''import sys;print(sys.argv[1].replace('@BAR@', 'bar'))''', bar_content],
+ capture: true,
+)
+
+dep = declare_dependency(compile_args: '-DPLOP=0')
+
+pp_files = cc.preprocess('foo.c', bar_x, 'math.c', output: '@PLAINNAME@.c', dependencies: dep)
foreach f : pp_files
message(f.full_path())
prog = find_program('test_subdir.py', required : false)
assert(not prog.found(), 'Program should not be found')
-prog = find_program('test_subdir.py', dirs : ['/donotexist', meson.current_source_dir() / 'scripts'])
+prog = find_program('test_subdir.py', dirs : ['/nonexistent', meson.current_source_dir() / 'scripts'])
assert(prog.found(), 'Program should be found')
--- /dev/null
+void bar(void) {}
--- /dev/null
+extern void bar(void);
+
+void foo(void) { bar(); }
--- /dev/null
+# Test that declare_dependency(objects: ...) fixes issues with duplicated
+# objects in the final link line, thanks to deduplication of dependencies.
+# The commented declare_dependency() invocation using link_whole instead
+# fails thusly:
+#
+# ar csrDT libbar.a libfoo.a.p/foo.c.o libbar.a.p/bar.c.o
+# ar csrDT libfoo.a libfoo.a.p/foo.c.o
+# cc -o prog prog.p/prog.c.o -Wl,--as-needed -Wl,--no-undefined -Wl,--whole-archive -Wl,--start-group libfoo.a libbar.a -Wl,--end-group -Wl,--no-whole-archive
+# /usr/bin/ld: libfoo.a.p/foo.c.o: in function `foo':
+# ../foo.c:3: multiple definition of `foo'; libfoo.a.p/foo.c.o:../foo.c:3: first defined here
+
+project('Transitive declare_dependency(objects)', 'c')
+
+libfoo = static_library('foo', 'foo.c')
+#foo = declare_dependency(link_whole: libfoo)
+foo = declare_dependency(objects: libfoo.extract_all_objects(recursive: true))
+
+libbar = static_library('bar', 'bar.c', dependencies: foo)
+
+#bar = declare_dependency(link_whole: libbar, dependencies: foo)
+bar = declare_dependency(objects: libbar.extract_all_objects(recursive: true), dependencies: foo)
+
+executable('prog', sources: files('prog.c'), dependencies: [foo, bar])
--- /dev/null
+extern void foo(void);
+
+int main(void) { foo(); }
--- /dev/null
+project('testcase clause')
+
+# To make sure unreachable code is not executed.
+unreachable = true
+
+# Verify assertion exception gets catched and dropped.
+testcase expect_error('Assert failed: false')
+ assert(false)
+ unreachable = false
+endtestcase
+assert(unreachable)
+
+# The inner testcase raises an exception because it did not receive the expected
+# error message. The outer testcase catches the inner testcase exception and
+# drop it.
+testcase expect_error('Expecting error \'something\' but got \'Assert failed: false\'')
+ testcase expect_error('something')
+ assert(false)
+ unreachable = false
+ endtestcase
+ unreachable = false
+endtestcase
+assert(unreachable)
+
+# The inner testcase raises an exception because it did not receive an
+# exception. The outer testcase catches the inner testcase exception and
+# drop it.
+testcase expect_error('Expecting an error but code block succeeded')
+ testcase expect_error('something')
+ reached = true
+ endtestcase
+ unreachable = false
+endtestcase
+assert(reached)
+assert(unreachable)
+
+message('all good')
--- /dev/null
+{
+ "stdout": [
+ {
+ "line": ".*all good",
+ "match": "re",
+ "count": 1
+ }
+ ]
+}
--- /dev/null
+project('Generator Chain', 'c')
+
+stage1_exe = find_program('stage1.py')
+
+stage2_exe = find_program('stage2.py')
+
+stage1_gen = generator(stage1_exe,
+ output : '@PLAINNAME@.inter',
+ arguments : ['@INPUT@', '@OUTPUT@'])
+
+stage2_gen = generator(stage2_exe,
+ output : '@PLAINNAME@.c',
+ arguments : ['@INPUT@', '@OUTPUT@'])
+
+out = stage2_gen.process(stage1_gen.process('data.txt'))
+
+hello = executable('hello', out)
+
+test('basic', hello)
--- /dev/null
+#!/usr/bin/env python3
+import sys
+from pathlib import Path
+
+assert(Path(sys.argv[1]).read_text() == 'stage1\n')
+Path(sys.argv[2]).write_text('stage2\n')
--- /dev/null
+#!/usr/bin/env python3
+import sys
+from pathlib import Path
+
+assert(Path(sys.argv[1]).read_text() == 'stage2\n')
+Path(sys.argv[2]).write_text('int main(void){}\n')
--- /dev/null
+int foo(void);
--- /dev/null
+project('test 262', 'c')
+
+cc = meson.get_compiler('c')
+
+internal_dep = declare_dependency(include_directories: 'include')
+
+assert(cc.has_header_symbol('test_262_header.h', 'foo', dependencies: internal_dep))
--- /dev/null
+project('required keyword in has functions', 'c')
+
+cc = meson.get_compiler('c')
+opt = get_option('opt')
+
+cc.has_function('printf', prefix : '#include<stdio.h>', required : true)
+cc.has_type('time_t', prefix : '#include<time.h>', required : true)
+cc.has_member('struct tm', 'tm_sec', prefix : '#include<time.h>', required : true)
+cc.has_members('struct tm', ['tm_sec', 'tm_min'], prefix : '#include<time.h>', required : true)
+cc.has_header('time.h', required : true)
+cc.has_header_symbol('time.h', 'time', required : true)
+
+assert(not cc.has_function('printf', prefix : '#include<stdio.h>', required : opt))
+assert(not cc.has_type('time_t', prefix : '#include<time.h>', required : opt))
+assert(not cc.has_member('struct tm', 'tm_sec', prefix : '#include<time.h>', required : opt))
+assert(not cc.has_members('struct tm', ['tm_sec', 'tm_min'], prefix : '#include<time.h>', required : opt))
+assert(not cc.has_header('time.h', required : opt))
+assert(not cc.has_header_symbol('time.h', 'time', required : opt))
+
+# compiler.has_argument
+if cc.get_id() == 'msvc'
+ is_arg = '/O2'
+else
+ is_arg = '-O2'
+endif
+cc.has_argument(is_arg, required: true)
+assert(not cc.has_argument(is_arg, required: opt))
+
+# compiler.has_multi_arguments
+if cc.get_id() == 'gcc'
+ pre_arg = '-Wformat'
+ arg = '-Werror=format-security'
+ cc.has_multi_arguments([pre_arg, arg], required: true)
+ assert(not cc.has_multi_arguments(pre_arg, arg, required: opt))
+endif
+
+# compiler.has_link_argument
+if cc.get_argument_syntax() == 'msvc'
+ is_arg = '/OPT:REF'
+else
+ is_arg = '-Wl,-L/tmp'
+endif
+cc.has_link_argument(is_arg, required: true)
+assert(not cc.has_link_argument(is_arg, required: opt))
+
+# compiler.has_function_attribute
+if not ['pgi', 'msvc', 'clang-cl', 'intel-cl'].contains(cc.get_id())
+ a = 'aligned'
+ cc.has_function_attribute(a, required: true)
+ assert(not cc.has_function_attribute(a, required: opt))
+endif
+
+testcase expect_error('''compiler.has_function keyword argument 'required' was of type str but should have been one of: bool, UserFeatureOption''')
+ cc.has_function('printf', required : 'not a bool')
+endtestcase
+
+testcase expect_error('''C function 'asdfkawlegsdiovapfjhkr' not usable''')
+ cc.has_function('asdfkawlegsdiovapfjhkr', required : true)
+endtestcase
+
+testcase expect_error('''C header 'asdfkawlegsdiovapfjhkr.h' not found''')
+ cc.has_header('asdfkawlegsdiovapfjhkr.h', required : true)
+endtestcase
+
+testcase expect_error('''C symbol time_not_found not found in header time.h''')
+ cc.has_header_symbol('time.h', 'time_not_found', required : true)
+endtestcase
--- /dev/null
+option('opt', type: 'feature', value: 'disabled')
--- /dev/null
+#warning Make sure this is not fatal
--- /dev/null
+project('test default options', 'c',
+ default_options: {
+ 'bool': true,
+ 'int': 42,
+ 'str': 'foo',
+ 'array': ['foo'],
+ 'werror': true,
+ },
+)
+
+assert(get_option('bool') == true)
+assert(get_option('int') == 42)
+assert(get_option('str') == 'foo')
+assert(get_option('array') == ['foo'])
+assert(get_option('werror') == true)
+
+cc = meson.get_compiler('c')
+
+# MSVC does not support #warning
+if cc.get_id() != 'msvc'
+ static_library('foo', 'lib.c', override_options: {'werror': false})
+endif
--- /dev/null
+option('bool', type: 'boolean', value: false)
+option('int', type: 'integer', value: 0)
+option('str', type: 'string', value: 'bar')
+option('array', type: 'array', value: ['bar'])
--- /dev/null
+project('test format string')
+
+# Test all supported types in message(), format(), f-string.
+foreach t : [get_option('opt'), 42, true, false, 'str', ['list'], {'dict': 'value'}]
+ message(t, '@0@'.format(t), f'@t@', [t], {'key': t})
+endforeach
+
+# Deprecated but should work with str.format().
+env = environment()
+message('@0@'.format(env))
+
+# Should fail with f-string and message()
+error_msg = 'Value other than strings, integers, bools, options, dictionaries and lists thereof.'
+testcase expect_error('message(): ' + error_msg)
+ message(env)
+endtestcase
+
+testcase expect_error('f-string: ' + error_msg)
+ message(f'@env@')
+endtestcase
--- /dev/null
+option('opt', type: 'feature')
--- /dev/null
+{
+ "stdout": [
+ {
+ "line": "Message: auto auto auto [auto] {'key' : auto}"
+ },
+ {
+ "line": "Message: 42 42 42 [42] {'key' : 42}"
+ },
+ {
+ "line": "Message: true true true [true] {'key' : true}"
+ },
+ {
+ "line": "Message: false false false [false] {'key' : false}"
+ },
+ {
+ "line": "Message: str str str ['str'] {'key' : 'str'}"
+ },
+ {
+ "line": "Message: ['list'] ['list'] ['list'] [['list']] {'key' : ['list']}"
+ },
+ {
+ "line": "Message: {'dict' : 'value'} {'dict' : 'value'} {'dict' : 'value'} [{'dict' : 'value'}] {'key' : {'dict' : 'value'}}"
+ },
+ {
+ "line": "Message: <EnvironmentVariables: []>"
+ }
+ ]
+ }
--- /dev/null
+project('test default_options in find_program')
+
+dummy_exe = find_program('dummy', default_options: ['subproject_option=true'])
+
+test('test_dummy', dummy_exe)
--- /dev/null
+[wrap-file]
+directory = dummy
+
+[provide]
+program_names = dummy
\ No newline at end of file
--- /dev/null
+int main(void) {
+ return 0;
+}
\ No newline at end of file
--- /dev/null
+project('dummy', 'c')
+
+if get_option('subproject_option')
+ dummy_exe = executable('dummy', 'dummy.c')
+ meson.override_find_program('dummy', dummy_exe)
+endif
--- /dev/null
+option('subproject_option', type: 'boolean', value: false)
--- /dev/null
+project('install_data following symlinks')
+
+install_data(
+ 'foo/link1',
+ install_dir: get_option('datadir') / 'followed',
+ follow_symlinks: true,
+)
+
+install_headers(
+ 'foo/link2.h',
+ follow_symlinks: true,
+ subdir: 'followed'
+)
+
+install_data(
+ 'foo/link1',
+ install_dir: get_option('datadir'),
+ follow_symlinks: false,
+)
+
+install_headers(
+ 'foo/link2.h',
+ follow_symlinks: false,
+)
+
+install_subdir(
+ 'foo',
+ install_dir: get_option('datadir') / 'subdir',
+ strip_directory: true,
+ follow_symlinks: false,
+)
+
+install_subdir(
+ 'foo',
+ install_dir: get_option('datadir') / 'subdir_followed',
+ strip_directory: true,
+ follow_symlinks: true,
+)
--- /dev/null
+{
+ "installed": [
+ {"type": "link", "file": "usr/share/link1"},
+ {"type": "link", "file": "usr/include/link2.h"},
+ {"type": "file", "file": "usr/share/followed/link1"},
+ {"type": "file", "file": "usr/include/followed/link2.h"},
+ {"type": "link", "file": "usr/share/subdir/link1"},
+ {"type": "link", "file": "usr/share/subdir/link2.h"},
+ {"type": "file", "file": "usr/share/subdir/file1"},
+ {"type": "file", "file": "usr/share/subdir_followed/link1"},
+ {"type": "file", "file": "usr/share/subdir_followed/link2.h"},
+ {"type": "file", "file": "usr/share/subdir_followed/file1"}
+ ]
+}
--- /dev/null
+import sys
+
+with open(sys.argv[1], 'r', encoding='utf-8') as f, open(sys.argv[2], 'r', encoding='utf-8') as g:
+ if f.read() != g.read():
+ sys.exit('contents are not equal')
--- /dev/null
+/*
+ * Autogenerated by the Meson build system.
+ * Do not edit, your changes will be lost.
+ */
+
+#pragma once
+
+#define bool
+
+#undef false
+
+/* ultimate question of life, the universe, and everything */
+#define int 42
+
+/* This is
+a multiline
+description */
+#define str "hello world!"
+
+#define unquoted float
+
--- /dev/null
+{"bool": true, "false": false, "int": 42, "str": "\"hello world!\"", "unquoted": "float"}
\ No newline at end of file
--- /dev/null
+/*
+ * Autogenerated by the Meson build system.
+ * Do not edit, your changes will be lost.
+ */
+
+#ifndef CONFIG_MAGNESIUM_H
+#define CONFIG_MAGNESIUM_H
+
+#define bool
+
+#undef false
+
+/* ultimate question of life, the universe, and everything */
+#define int 42
+
+/* This is
+a multiline
+description */
+#define str "hello world!"
+
+#define unquoted float
+
+#endif
--- /dev/null
+; Autogenerated by the Meson build system.
+; Do not edit, your changes will be lost.
+
+%define bool
+
+%undef false
+
+; ultimate question of life, the universe, and everything
+%define int 42
+
+; This is
+; a multiline
+; description
+%define str "hello world!"
+
+%define unquoted float
+
--- /dev/null
+project('configure file output format')
+
+data = configuration_data()
+data.set_quoted('str', 'hello world!', description: '''This is
+a multiline
+description''')
+data.set('unquoted', 'float')
+data.set('int', 42, description: 'ultimate question of life, the universe, and everything')
+data.set('bool', true)
+data.set('false', false)
+
+config_h = configure_file(
+ configuration: data,
+ output_format: 'c',
+ output: 'config.h'
+)
+
+config_nasm = configure_file(
+ configuration: data,
+ output_format: 'nasm',
+ output: 'config.nasm'
+)
+
+config_json = configure_file(
+ configuration: data,
+ output_format: 'json',
+ output: 'config.json'
+)
+
+config_mg = configure_file(
+ configuration: data,
+ macro_name: 'CONFIG_MAGNESIUM_H',
+ output_format: 'c',
+ output: 'config_mg.h'
+)
+
+py = find_program('python3')
+compare_py = files('compare.py')
+expected_config_h = files('expected/config.h')
+expected_config_nasm = files('expected/config.nasm')
+expected_config_json = files('expected/config.json')
+expected_config_mg = files('expected/config.mg')
+
+test('c_output', py, args: [compare_py, expected_config_h, config_h])
+test('nasm_output', py, args: [compare_py, expected_config_nasm, config_nasm])
+test('json_output', py, args: [compare_py, expected_config_json, config_json])
+test('c_mg_output', py, args: [compare_py, expected_config_mg, config_mg])
return 0;
}'''
-assert(cc.compiles(prog), 'multline test compile failed')
+assert(cc.compiles(prog), 'multiline test compile failed')
--- /dev/null
+project('test_fill_in_int_to_string')
+
+n = 4
+assert(n.to_string() == '4')
+assert(n.to_string(fill: 3) == '004')
+assert(n.to_string(fill: -3) == '4')
+
+n = -4
+assert(n.to_string(fill: 3) == '-04')
--- /dev/null
+#!/usr/bin/env python3
+import os
+import sys
+
+ENV_VAR_VALUE = os.environ.get('ENV_VAR_VALUE')
+assert ENV_VAR_VALUE is not None
+
+with open(sys.argv[1], 'r') as infile, \
+ open(sys.argv[2], 'w') as outfile:
+
+ outfile.write(infile.read().replace('ENV_VAR_VALUE', ENV_VAR_VALUE))
--- /dev/null
+int main(void) {
+ return ENV_VAR_VALUE;
+}
\ No newline at end of file
--- /dev/null
+project('test_env_in_generator_process', 'c')
+
+generate_main_py = find_program('generate_main.py')
+
+main_generator = generator(generate_main_py,
+ arguments: ['@INPUT@', '@OUTPUT@'],
+ output: '@BASENAME@' + '.c'
+)
+
+main_template = files('main.template')
+
+# With explicit values
+my_executable = executable('myexecutable', main_generator.process(main_template, env: {'ENV_VAR_VALUE': '0'}))
+test('explicit_value', my_executable)
+
+# With env object
+env = environment()
+env.set('ENV_VAR_VALUE', '0')
+
+my_executable2 = executable('myexecutable2', main_generator.process(main_template, env: env))
+test('env_object', my_executable2)
--- /dev/null
+project('unity', 'c',
+ default_options : [
+ 'unity_size=2'])
+
+if get_option('unity') != 'on'
+ error('MESON_SKIP_TEST: unity builds not enabled')
+endif
+
+slib_notinstalled = static_library('slib_notinstalled',
+ # test depends on the number of files being divisible by unity_size
+ ['slib1.c', 'slib2.c'])
+
+slib_installed = static_library('slib_installed', ['slib1.c', 'slib2.c'], link_with : slib_notinstalled, install : true)
--- /dev/null
+int func1(void);
+int func2(void);
+
+int static_lib_func(void) {
+ return func1() + func2();
+}
--- /dev/null
+int func1(void) {
+ return 1;
+}
--- /dev/null
+int func2(void) {
+ return 2;
+}
--- /dev/null
+{
+ "installed": [
+ {"type": "file", "file": "usr/lib/libslib_installed.a"}
+ ]
+}
void func(void) { printf("This won't work.\n"); }
'''
-foreach compiler : [meson.get_compiler('c'), meson.get_compiler('cpp')]
- if compiler.compiles(code, name : 'should succeed') == false
+warncode = '''#warning This is a warning
+int main(void) { return 0; }
+'''
+
+configure_file(
+ input: 'foo.h.in',
+ output: 'foo.h',
+ configuration: {},
+)
+
+header_code = '#include "foo.h"'
+
+foreach lang : ['c', 'cpp']
+ compiler = meson.get_compiler(lang)
+
+ assert(not compiler.compiles(header_code, name: 'Should not include . by default'))
+ assert(compiler.compiles(header_code, name: 'Should include builddir',
+ include_directories: include_directories('.'),
+ ))
+
+ if compiler.compiles(code, name : 'code should succeed') == false
error('Compiler ' + compiler.get_id() + ' is fail.')
endif
- if compiler.compiles(files('valid.c'), name : 'should succeed') == false
+ if compiler.compiles(files('valid.c'), name : 'file should succeed') == false
error('Compiler ' + compiler.get_id() + ' is fail.')
endif
- if compiler.compiles(breakcode, name : 'should fail')
+ copied = configure_file(input: 'valid.c', output: lang + '-valid-copy.c', copy: true)
+ if compiler.compiles(copied, name : 'built file should succeed') == false
+ error('Compiler ' + compiler.get_id() + ' is fail.')
+ endif
+
+ if compiler.compiles(breakcode, name : 'code should fail')
error('Compiler ' + compiler.get_id() + ' returned true on broken code.')
endif
- if compiler.compiles(files('invalid.c'), name : 'should fail')
+ if compiler.compiles(files('invalid.c'), name : 'file should fail')
error('Compiler ' + compiler.get_id() + ' returned true on broken code.')
endif
+
+ # MSVC does not support #warning instruction
+ if compiler.get_id() != 'msvc'
+ # First check that all tests pass without werror, then check they fail with it.
+ foreach with_werror : [false, true]
+ expect_success = not with_werror
+ assert(compiler.compiles(warncode,
+ name: f'code with warning compiles with werror=@with_werror@',
+ werror: with_werror) == expect_success)
+ assert(compiler.links(warncode,
+ name: f'code with warning links with werror=@with_werror@',
+ werror: with_werror) == expect_success)
+ if meson.can_run_host_binaries()
+ assert((compiler.run(warncode,
+ name: f'code with warning runs with werror=@with_werror@',
+ werror: with_werror).returncode() == 0) == expect_success)
+ endif
+ endforeach
+ endif
endforeach
--- /dev/null
+int func3(const int x) {
+ return x + 1;
+}
+
+#ifndef WORK
+# error "did not get static only C args"
+#endif
+
+#ifdef BREAK
+# error "got shared only C args, but shouldn't have"
+#endif
-project('static library test', 'c')
+project('static library test', 'c', default_options : ['default_library=static'])
lib = static_library('mylib', get_option('source'),
link_args : '-THISMUSTNOBEUSED') # Static linker needs to ignore all link args.
assert(has_not_changed, 'Static library has changed.')
assert(not is_disabler(lib), 'Static library is a disabler.')
+
+if get_option('default_library') == 'static'
+ library('lib2', 'lib3.c', c_static_args : ['-DWORK'], c_shared_args : ['-DBREAK'])
+endif
+build_target('lib4', 'lib3.c', c_static_args : ['-DWORK'], target_type : 'static_library')
# This header exists in the source and the builddir, but we still must not
# find it since we are looking in the system directories.
assert(not comp.has_header(non_existent_header, prefix : fallback),
- 'Found non-existent header.')
+ 'Found nonexistent header.')
endforeach
endforeach
-project('run command', version : run_command('get-version.py', check : true).stdout().strip())
+project('run command', version : run_command('get-version.py', check : true).stdout().strip(), meson_version: '>=0.1.0')
if build_machine.system() == 'windows'
c = run_command('cmd', '/c', 'echo', 'hello', check: false)
--- /dev/null
+{
+ "stdout": [
+ {
+ "line": "test cases/common/33 run program/meson.build:1: WARNING: Project targets '>=0.1.0' but uses feature introduced in '0.47.0': check arg in run_command.",
+ "comment": "This triggers on line 1 -- the line with the project() function"
+ }
+ ]
+}
assert(mysubstring.substring(-4, 2) == '', 'substring is broken')
assert(mysubstring.substring(10, 9) == '', 'substring is broken')
assert(mysubstring.substring(8, 10) == 'z', 'substring is broken')
+
+# str.splitlines()
+assert('foo\nbar\nbaz'.splitlines() == ['foo', 'bar', 'baz'], 'splitlines is broken')
+assert(''.splitlines() == [], 'splitlines with empty string is broken')
+assert('foo\rbar\nbaz\n'.splitlines() == ['foo', 'bar', 'baz'], 'splitlines trailing newline is broken')
+assert('hello\r\nworld'.splitlines() == ['hello', 'world'])
+assert(
+ ' leading ws\nand trailing\t'.splitlines() == [' leading ws', 'and trailing\t'],
+ 'splitlines leading/trailing whitespace is broken',
+)
+assert('\n\r\n\r'.splitlines() == ['', '', ''], 'splitlines is broken')
+assert('foo'.splitlines() == ['foo'], 'splitlines is broken')
if cc.has_function('hfkerhisadf', prefix : '#include<stdio.h>',
args : unit_test_args)
- error('Found non-existent function "hfkerhisadf".')
+ error('Found nonexistent function "hfkerhisadf".')
endif
if cc.has_function('hfkerhisadf', args : unit_test_args)
- error('Found non-existent function "hfkerhisadf".')
+ error('Found nonexistent function "hfkerhisadf".')
endif
# With glibc (before 2.32, see below) on Linux, lchmod is a stub that will
--- /dev/null
+#if defined _WIN32 || defined __CYGWIN__
+ #define DLL_PUBLIC __declspec(dllexport)
+#else
+ #if defined __GNUC__
+ #define DLL_PUBLIC __attribute__ ((visibility("default")))
+ #else
+ #pragma message ("Compiler does not support symbol visibility.")
+ #define DLL_PUBLIC
+ #endif
+#endif
+
+#ifndef WORK
+# error "Did not get shared only arguments"
+#endif
+
+#ifdef BREAK
+# error "got static only C args, but shouldn't have"
+#endif
+
+int DLL_PUBLIC libfunc(void) {
+ return 3;
+}
-project('shared library test', 'c')
+project('shared library test', 'c', default_options : ['default_library=shared'])
lib = shared_library('mylib', 'libfile.c')
build_target('mylib2', 'libfile.c', target_type: 'shared_library')
assert(has_not_changed, 'Shared library has changed.')
assert(not is_disabler(lib), 'Shared library is a disabler.')
+
+if get_option('default_library') == 'shared'
+ library('mylib5', 'libfile2.c', c_shared_args : ['-DWORK'])
+endif
+build_target('mylib4', 'libfile2.c', target_type: 'shared_library', c_shared_args : ['-DWORK'], c_static_args : ['-DBREAK'])
-project('options', 'c')
+project('options', 'c', meson_version : '>= 1.0.0')
if get_option('testoption') != 'optval'
error('Incorrect value to test option')
endif
assert(get_option('wrap_mode') == 'default', 'Wrap mode option is broken.')
+assert(get_option('boolean_string') == false)
+assert(get_option('boolean_string2') == true)
+assert(get_option('integer_string') == 42)
+
+testcase expect_error('Invalid option name \'..invalid\'')
+ get_option('..invalid')
+endtestcase
+
+testcase expect_error('Invalid option name \'this.is.also.invalid\'')
+ get_option('this.is.also.invalid')
+endtestcase
option('free_array_opt', type : 'array')
option('integer_opt', type : 'integer', min : 0, max : -(-5), value : 3)
option('neg' + '_' + 'int' + '_' + 'opt', type : 'integer', min : -5, max : 5, value : -3)
-option('CaseSenSiTivE', type : 'string', value: 'Some CAPS', description : 'An option with mixed capitaliziation')
+option('CaseSenSiTivE', type : 'string', value: 'Some CAPS', description : 'An option with mixed capitalization')
option('CASESENSITIVE', type : 'string', value: 'ALL CAPS', description : 'An option with all caps')
+
+option('boolean_string', type : 'boolean', value : 'false')
+option('boolean_string2', type : 'boolean', value : 'true')
+option('integer_string', type : 'integer', value : '42')
--- /dev/null
+{
+ "stdout": [
+ {
+ "line": " * 1.1.0: {'\"boolean option\" keyword argument \"value\" of type str', '\"integer option\" keyword argument \"value\" of type str'}"
+ }
+ ]
+}
project('subproj user', 'c',
version : '2.3.4',
- license : 'mylicense')
+ license : 'mylicense',
+ license_files: 'mylicense.txt',
+)
assert(meson.project_name() == 'subproj user', 'Incorrect project name')
unknown_var = sub.get_variable('does-not-exist', [])
if unknown_var != []
- error ('unexpetced fallback value for subproject.get_variable()')
+ error ('unexpected fallback value for subproject.get_variable()')
endif
project('subproject', 'c',
version : '1.0.0',
- license : ['sublicense1', 'sublicense2'])
+ license : ['sublicense1', 'sublicense2'],
+ license_files: ['sublicense1.txt', 'sublicense2.txt'],
+)
if not meson.is_subproject()
error('Claimed to be master project even though we are a subproject.')
"installed": [
{"type": "exe", "file": "usr/bin/user"},
{"type": "pdb", "file": "usr/bin/user"},
- {"type": "file", "file": "usr/share/sublib/sublib.depmf"}
+ {"type": "file", "file": "usr/share/sublib/sublib.depmf"},
+ {"type": "file", "file": "usr/share/sublib/mylicense.txt"},
+ {"type": "file", "file": "usr/share/sublib/subprojects/sublib/sublicense1.txt"},
+ {"type": "file", "file": "usr/share/sublib/subprojects/sublib/sublicense2.txt"}
]
}
project('pkgconfig-gen-dependencies', 'c', version: '1.0')
+if find_program('pkg-config').version() == '2.0.1'
+ error('MESON_SKIP_TEST: cannot test uninstalled.pc due to https://github.com/pkgconf/pkgconf/issues/310#issuecomment-1677844842')
+endif
+
pkgg = import('pkgconfig')
# libmain internally use libinternal and expose libexpose in its API
libraries : internal_lib,
filebase : 'pub-lib-order',
)
+
+# This will be built against both simple7.pc and simple7-uninstalled.pc, check
+# that include directories works in both cases.
+simple7 = dependency('simple7')
+exe = executable('test2', 'test2.c',
+ dependencies: simple7,
+)
+test('Test2', exe)
--- /dev/null
+#include <inc1.h>
+#include <inc2.h>
+
+int main(void) {
+ if (INC1 + INC2 != 3)
+ return 1;
+ return 0;
+}
--- /dev/null
+#define INC1 1
--- /dev/null
+#define INC2 2
endif
# First check we have pkg-config >= 0.29
-pkgconfig = find_program('pkg-config', required: false)
+pkgconfig = find_program('pkg-config', native: true, required: false)
if not pkgconfig.found()
error('MESON_SKIP_TEST: pkg-config not found')
endif
error('MESON_SKIP_TEST: pkg-config version \'' + v + '\' too old')
endif
-python = import('python').find_installation()
+python = find_program('python3')
fs = import('fs')
pkgg = import('pkgconfig')
description : 'Check that variables can be single string',
variables: 'foo=bar',
)
+
+# without a mainlib, name/description are mandatory
+testcase expect_error('pkgconfig.generate: if a library is not passed as a positional argument, the \'name\' keyword argument is required.')
+ pkgg.generate(description: 'empty data')
+endtestcase
+
+testcase expect_error('pkgconfig.generate: if a library is not passed as a positional argument, the \'description\' keyword argument is required.')
+ pkgg.generate(name: 'foobar')
+endtestcase
+
+# Make sure the -uninstalled.pc file contains both include directories.
+# See dependencies/test2.c that gets built against both simple7.pc and
+# simple7-uninstalled.pc.
+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)
{
"installed": [
{"type": "file", "file": "usr/include/simple.h"},
+ {"type": "file", "file": "usr/include/inc1.h"},
+ {"type": "file", "file": "usr/include/inc2.h"},
{"type": "file", "file": "usr/lib/libstat2.a"},
{"type": "file", "file": "usr/lib/pkgconfig/simple.pc"},
{"type": "file", "file": "usr/lib/pkgconfig/libanswer.pc"},
{"type": "file", "file": "usr/lib/pkgconfig/simple3.pc"},
{"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/ct.pc"},
{"type": "file", "file": "usr/lib/pkgconfig/ct0.pc"},
{"type": "file", "file": "usr/share/pkgconfig/libhello_nolib.pc"}
],
"stdout": [
{
- "line": "test cases/common/44 pkgconfig-gen/meson.build:164: WARNING: Project targets '>=0.60.0' but uses feature introduced in '0.62.0': pkgconfig.generate implicit variable for builtin directories."
+ "line": "test cases/common/44 pkgconfig-gen/meson.build:160: WARNING: Project targets '>=0.60.0' but uses feature introduced in '0.62.0': pkgconfig.generate implicit variable for builtin directories."
},
{
"line": "test cases/common/44 pkgconfig-gen/meson.build:170: WARNING: Project targets '>=0.60.0' but uses feature introduced in '0.62.0': pkgconfig.generate implicit variable for builtin directories.",
int main(int argc, char **argv) {
if(argc != 2) {
- printf("I can not haz argument.\n");
+ printf("I cannot haz argument.\n");
return 1;
} else {
printf("I can haz argument: %s\n", argv[1]);
project('custom install script', 'c')
-meson.add_install_script('myinstall.py', 'diiba/daaba', 'file.dat')
+meson.add_install_script('myinstall.py', 'diiba/daaba', 'file.dat', dry_run: true)
meson.add_install_script('myinstall.py', 'this/should', 'also-work.dat')
subdir('src')
import shutil
prefix = os.environ['MESON_INSTALL_DESTDIR_PREFIX']
+dry_run = bool(os.environ.get('MESON_INSTALL_DRY_RUN'))
def main() -> None:
dirname = os.path.join(prefix, args.dirname)
if not os.path.exists(dirname):
- os.makedirs(dirname)
+ if dry_run:
+ print(f"DRYRUN: Creating directory {dirname}")
+ else:
+ os.makedirs(dirname)
if args.mode == 'create':
for name in args.files:
- with open(os.path.join(dirname, name), 'w') as f:
- f.write('')
+ if dry_run:
+ print(f'DRYRUN: Writing file {name}')
+ else:
+ with open(os.path.join(dirname, name), 'w') as f:
+ f.write('')
else:
for name in args.files:
- shutil.copy(name, dirname)
+ if dry_run:
+ print(f"DRYRUN: Copying file {name} to {dirname}")
+ else:
+ shutil.copy(name, dirname)
if __name__ == "__main__":
endif
if file_list.contains(file_c[0])
- error('Contains with ObjectHolder lists found non existent object')
+ error('Contains with ObjectHolder lists found nonexistent object')
endif
if empty.contains('abc')
exclude_directories : ['excluded'],
install_dir : 'share')
+# More exclusions
+install_subdir('sub3',
+ exclude_files : ['data/excluded.txt'],
+ exclude_directories : ['data/excluded'],
+ install_dir : 'share')
+
subdir('subdir')
# A subdir with write perms only for the owner
# and read-list perms for owner and group
{"type": "file", "file": "usr/share/sub1/sub2/data2.dat"},
{"type": "file", "file": "usr/share/sub2/one.dat"},
{"type": "file", "file": "usr/share/sub2/dircheck/excluded-three.dat"},
+ {"type": "file", "file": "usr/share/sub3/data/data.txt"},
{"type": "dir", "file": "usr/share/new_directory"}
]
}
project('threads', 'cpp', 'c',
default_options : ['cpp_std=c++11'])
+cc = meson.get_compiler('c')
+if cc.has_function_attribute('unused')
+ add_project_arguments('-DHAVE_UNUSED', language: 'c')
+endif
+
threaddep = dependency('threads')
test('cppthreadtest',
#include<windows.h>
#include<stdio.h>
-DWORD WINAPI thread_func(void) {
+DWORD WINAPI thread_func(void *arg) {
printf("Printing from a thread.\n");
return 0;
}
#include<pthread.h>
#include<stdio.h>
-void* main_func(void) {
+#ifdef HAVE_UNUSED
+ #define UNUSED_ATTR __attribute__((unused))
+#else
+ #define UNUSED_ATTR
+#endif
+
+void* main_func(void UNUSED_ATTR *arg) {
printf("Printing from a thread.\n");
return NULL;
}
assert(not d.found(), 'Dependency should be not-found')
# Verify that implicit fallback works because subprojects/sub_implicit directory exists
-d = dependency('sub_implicit', default_options: 'opt=overriden')
+d = dependency('sub_implicit', default_options: 'opt=overridden')
assert(d.found(), 'Should implicitly fallback')
# Verify that implicit fallback works because sub_implicit.wrap has
# This one is not overridden but the wrap file tells the variable name to use.
glib_dep = dep
-assert(get_option('opt') == 'overriden')
\ No newline at end of file
+assert(get_option('opt') == 'overridden')
input_file = os.path.join(os.environ['MESON_SOURCE_ROOT'], 'raw.dat')
output_file = os.path.join(os.environ['MESON_BUILD_ROOT'], 'generated.h')
-with open(input_file) as f:
+with open(input_file, encoding='utf-8') as f:
data = f.readline().strip()
-with open(output_file, 'w') as f:
+with open(output_file, 'w', encoding='utf-8') as f:
f.write(template.format(data))
project(
'basic cython project',
['cython', 'c'],
- default_options : ['warning_level=3']
+ default_options : ['warning_level=3', 'buildtype=release'],
)
-py_mod = import('python')
-py3 = py_mod.find_installation()
+if meson.backend() != 'ninja'
+ error('MESON_SKIP_TEST: Ninja backend required')
+endif
+
+py3 = import('python').find_installation()
py3_dep = py3.dependency(required : false)
if not py3_dep.found()
error('MESON_SKIP_TEST: Python library not found.')
--- /dev/null
+include "stuff.pxi"
project(
'generated cython sources',
- ['cython'],
+ ['cython', 'c'],
+ default_options : ['buildtype=release'],
)
-py_mod = import('python')
-py3 = py_mod.find_installation('python3')
+if meson.backend() != 'ninja'
+ error('MESON_SKIP_TEST: Ninja backend required')
+endif
+
+fs = import('fs')
+py3 = import('python').find_installation('python3')
py3_dep = py3.dependency(required : false)
if not py3_dep.found()
error('MESON_SKIP_TEST: Python library not found.')
command : [py3, '@INPUT@', '@OUTPUT@'],
)
-ct_ext = py3.extension_module('ct', ct, dependencies : py3_dep)
+ct_ext = py3.extension_module('ct', ct)
test(
'custom target',
command : [py3, '@INPUT@', '@OUTPUT@'],
)
-cti_ext = py3.extension_module('cti', cti[0], dependencies : py3_dep)
+cti_ext = py3.extension_module('cti', cti[0])
cf = configure_file(
input : 'configure.pyx.in',
copy : true,
)
-cf_ext = py3.extension_module('cf', cf, dependencies : py3_dep)
+cf_ext = py3.extension_module('cf', cf)
test(
'configure file',
g_ext = py3.extension_module(
'g',
gen.process('g.in'),
- dependencies : py3_dep,
)
test(
env : ['PYTHONPATH=' + meson.current_build_dir()]
)
+stuff_pxi = fs.copyfile(
+ 'stuff.pxi.in',
+ 'stuff.pxi'
+)
+
+# 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)
+
+includestuff_ext = py3.extension_module(
+ 'includestuff',
+ includestuff_pyx,
+ dependencies: stuff_pxi_dep
+)
+
+simpleinclude_ext = py3.extension_module(
+ 'simpleinclude',
+ 'simpleinclude.pyx',
+)
+
subdir('libdir')
test(
--- /dev/null
+include "simplestuff.pxi"
--- /dev/null
+def func2():
+ print("Hello world")
--- /dev/null
+def func():
+ print("Hello world")
-project('cython_args', ['cython', 'c'])
+project('cython_args', ['cython', 'c'],
+ # Needed because Windows Python builds are release-only and tend to be
+ # unhappy with a debug build type.
+ default_options : ['buildtype=release']
+)
+
+if meson.backend() != 'ninja'
+ error('MESON_SKIP_TEST: Ninja backend required')
+endif
-pymod = import('python')
-python = pymod.find_installation('python3')
+python = import('python').find_installation('python3')
python_dep = python.dependency()
if not python_dep.found()
error('MESON_SKIP_TEST: Python library not found.')
files('cythonargs.pyx'),
cython_args: [
'--compile-time-env',
- 'VALUE=1'
+ 'VALUE=1',
+ '-3',
],
- dependencies: [python_dep]
)
test(
project('dub-example', 'd')
+error('MESON_SKIP_TEST: Dub support is broken at the moment (#11773)')
+
dub_exe = find_program('dub', required : false)
if not dub_exe.found()
error('MESON_SKIP_TEST: Dub not found')
)
my_dep = declare_dependency(
- d_module_versions: ['TestVersion', 1],
+ d_module_versions: ['TestVersion'],
d_import_dirs: include_directories('views'),
)
project('dub-with-deps-example', ['d'])
+error('MESON_SKIP_TEST: Dub support is broken at the moment (#11773)')
+
dub_exe = find_program('dub', required : false)
if not dub_exe.found()
error('MESON_SKIP_TEST: Dub not found')
dc = meson.get_compiler('d')
if dc.get_id() == 'gcc'
if dc.version().version_compare('< 8')
- error('MESON_SKIP_TEST: GDC < 8.0 can not build shared libraries')
+ error('MESON_SKIP_TEST: GDC < 8.0 cannot build shared libraries')
endif
endif
dc = meson.get_compiler('d')
if dc.get_id() == 'gcc'
if dc.version().version_compare('< 8')
- error('MESON_SKIP_TEST: GDC < 8.0 can not build shared libraries')
+ error('MESON_SKIP_TEST: GDC < 8.0 cannot build shared libraries')
endif
endif
dc = meson.get_compiler('d')
if dc.get_id() == 'gcc'
if dc.version().version_compare('< 8')
- error('MESON_SKIP_TEST: GDC < 8.0 can not build shared libraries')
+ error('MESON_SKIP_TEST: GDC < 8.0 cannot build shared libraries')
endif
endif
project('D Features', 'd', default_options : ['debug=false'])
+dc = meson.get_compiler('d')
+
+# GDC 13 hard errors if options are given number values.
+# https://github.com/mesonbuild/meson/pull/11996
+
+if dc.get_id() == 'gcc' and dc.version().version_compare('>=13')
+ number_options_supported = false
+else
+ number_options_supported = true
+endif
+
# ONLY FOR BACKWARDS COMPATIBILITY.
# DO NOT DO THIS IN NEW CODE!
# USE include_directories() INSTEAD OF BUILDING
test('dapp_test', e_test)
# test version level
-e_version_int = executable('dapp_version_int',
- test_src,
- d_import_dirs: [data_dir],
- d_module_versions: ['With_VersionInteger', 3],
-)
-test('dapp_version_int_t', e_version_int, args: ['debug'])
+if number_options_supported
+ e_version_int = executable('dapp_version_int',
+ test_src,
+ d_import_dirs: [data_dir],
+ d_module_versions: ['With_VersionInteger', 3],
+ )
+ test('dapp_version_int_t', e_version_int, args: ['debug'])
# test version level failure
e_version_int_fail = executable('dapp_version_int_fail',
d_module_versions: ['With_VersionInteger', 2],
)
test('dapp_version_int_t_fail', e_version_int_fail, args: ['debug'], should_fail: true)
+endif
# test debug conditions: disabled
e_no_debug = executable('dapp_no_debug',
)
test('dapp_no_debug_t_fail', e_no_debug, args: ['debug'], should_fail: true)
-# test debug conditions: enabled
-e_debug = executable('dapp_debug',
- test_src,
- d_import_dirs: [data_dir],
- d_module_versions: ['With_Debug'],
- d_debug: 1,
-)
-test('dapp_debug_t', e_debug, args: ['debug'])
+if number_options_supported
+ # test debug conditions: enabled
+ e_debug = executable('dapp_debug',
+ test_src,
+ d_import_dirs: [data_dir],
+ d_module_versions: ['With_Debug'],
+ d_debug: 1,
+ )
+ test('dapp_debug_t', e_debug, args: ['debug'])
-# test debug conditions: integer
-e_debug_int = executable('dapp_debug_int',
- test_src,
- d_import_dirs: [data_dir],
- d_module_versions: ['With_DebugInteger'],
- d_debug: 3,
-)
-test('dapp_debug_int_t', e_debug_int, args: ['debug'])
+ # test debug conditions: integer
+ e_debug_int = executable('dapp_debug_int',
+ test_src,
+ d_import_dirs: [data_dir],
+ d_module_versions: ['With_DebugInteger'],
+ d_debug: 3,
+ )
+ test('dapp_debug_int_t', e_debug_int, args: ['debug'])
+
+ # test with all debug conditions at once, and with redundant values
+ e_debug_all = executable('dapp_debug_all',
+ test_src,
+ d_import_dirs: [data_dir],
+ d_module_versions: ['With_DebugAll'],
+ d_debug: ['4', 'DebugIdentifier', 2, 'DebugIdentifierUnused'],
+ )
+ test('dapp_debug_all_t', e_debug_all, args: ['debug'])
+endif
# test debug conditions: identifier
e_debug_ident = executable('dapp_debug_ident',
d_debug: 'DebugIdentifier',
)
test('dapp_debug_ident_t', e_debug_ident, args: ['debug'])
-
-# test with all debug conditions at once, and with redundant values
-e_debug_all = executable('dapp_debug_all',
- test_src,
- d_import_dirs: [data_dir],
- d_module_versions: ['With_DebugAll'],
- d_debug: ['4', 'DebugIdentifier', 2, 'DebugIdentifierUnused'],
-)
-test('dapp_debug_all_t', e_debug_all, args: ['debug'])
{
"stdout": [
{
- "line": "test cases/failing/10 out of bounds/meson.build:4:0: ERROR: Index 0 out of bounds of array of size 0."
+ "line": "test cases/failing/10 out of bounds/meson.build:4:6: ERROR: Index 0 out of bounds of array of size 0."
}
]
}
{
"stdout": [
- { "line": "test cases/failing/102 bool in combo/meson.build:1:0: ERROR: Value \"True\" (of type \"boolean\") for combo option \"opt\" is not one of the choices. Possible choices are (as string): \"true\", \"false\"." }
+ { "line": "test cases/failing/100 bool in combo/meson.build:1:0: ERROR: Value \"True\" (of type \"boolean\") for combo option \"opt\" is not one of the choices. Possible choices are (as string): \"true\", \"false\"." }
]
}
+++ /dev/null
-{
- "stdout": [
- {
- "line": "test cases/failing/100 no glib-compile-resources/meson.build:8:0: ERROR: Program 'glib-compile-resources' not found or not executable"
- }
- ]
-}
{
"stdout": [
{
- "line": "test cases/failing/103 compiler no lang/meson.build:2:6: ERROR: Tried to access compiler for language \"c\", not specified for host machine."
+ "line": "test cases/failing/101 compiler no lang/meson.build:2:6: ERROR: Tried to access compiler for language \"c\", not specified for host machine."
}
]
}
+++ /dev/null
-{
- "stdout": [
- { "line": "test cases/failing/101 number in combo/meson.build:1:0: ERROR: Value \"1\" (of type \"number\") for combo option \"Optimization level\" is not one of the choices. Possible choices are (as string): \"plain\", \"0\", \"g\", \"1\", \"2\", \"3\", \"s\"." }
- ]
-}
--- /dev/null
+{
+ "stdout": [
+ {
+ "match": "re",
+ "line": ".*/meson\\.build:2:11: ERROR: (Pkg-config binary for machine MachineChoice\\.HOST not found\\. Giving up\\.|Dependency \"foob\" not found, tried .*)"
+ }
+ ]
+}
--- /dev/null
+{
+ "stdout": [
+ {
+ "match": "re",
+ "line": ".*/meson\\.build:2:31: ERROR: Feature reqfeature cannot be enabled: frobnicator not available"
+ }
+ ]
+}
--- /dev/null
+{
+ "stdout": [
+ {
+ "match": "re",
+ "line": ".*/meson\\.build:2:31: ERROR: Feature reqfeature cannot be enabled"
+ }
+ ]
+}
+++ /dev/null
-{
- "stdout": [
- {
- "match": "re",
- "line": ".*/meson\\.build:2:0: ERROR: (Pkg-config binary for machine MachineChoice\\.HOST not found\\. Giving up\\.|Dependency \"foob\" not found, tried .*)"
- }
- ]
-}
+++ /dev/null
-{
- "stdout": [
- {
- "match": "re",
- "line": ".*/meson\\.build:2:0: ERROR: Feature reqfeature cannot be enabled: frobnicator not available"
- }
- ]
-}
--- /dev/null
+{
+ "stdout": [
+ {
+ "line": "test cases/failing/105 no build get_external_property/meson.build:3:14: ERROR: Unknown property for build machine: nonexisting"
+ }
+ ]
+}
{
"stdout": [
{
- "line": "test cases/failing/108 enter subdir twice/meson.build:3:0: ERROR: Tried to enter directory \"sub\", which has already been visited."
+ "line": "test cases/failing/106 enter subdir twice/meson.build:3:0: ERROR: Tried to enter directory \"sub\", which has already been visited."
}
]
}
+++ /dev/null
-{
- "stdout": [
- {
- "match": "re",
- "line": ".*/meson\\.build:2:0: ERROR: Feature reqfeature cannot be enabled"
- }
- ]
-}
--- /dev/null
+{
+ "stdout": [
+ {
+ "line": "test cases/failing/107 invalid fstring/meson.build:3:4: ERROR: Identifier \"foo\" does not name a variable."
+ }
+ ]
+}
+++ /dev/null
-{
- "stdout": [
- {
- "line": "test cases/failing/107 no build get_external_property/meson.build:3:0: ERROR: Unknown property for build machine: nonexisting"
- }
- ]
-}
--- /dev/null
+{
+ "stdout": [
+ {
+ "line": "test cases/failing/108 compiler argument checking/meson.build:4:25: ERROR: Compiler for C does not support \"-meson-goober-arg-for-testing\""
+ }
+ ]
+}
--- /dev/null
+{
+ "stdout": [
+ {
+ "match": "re",
+ "line": "test cases/failing/109 empty fallback/meson.build:6:0: ERROR: Dependency \"foo\" not found.*"
+ }
+ ]
+}
+++ /dev/null
-{
- "stdout": [
- {
- "line": "test cases/failing/109 invalid fstring/meson.build:3:0: ERROR: Identifier \"foo\" does not name a variable."
- }
- ]
-}
"stdout": [
{
"match": "re",
- "line": "test cases/failing/11 object arithmetic/meson\\.build:3:0: ERROR: The `\\+` operator of str does not accept objects of type MesonMain .*"
+ "line": "test cases/failing/11 object arithmetic/meson\\.build:3:12: ERROR: The `\\+` operator of str does not accept objects of type MesonMain .*"
}
]
}
--- /dev/null
+{
+ "stdout": [
+ {
+ "line": "test cases/failing/110 cmake executable dependency/meson.build:9:14: ERROR: main is an executable and does not support the dependency() method. Use target() instead."
+ }
+ ],
+ "tools": {
+ "cmake": ">=3.14"
+ }
+}
+++ /dev/null
-{
- "stdout": [
- {
- "line": "test cases/failing/110 compiler argument checking/meson.build:4:0: ERROR: Compiler for C does not support \"-meson-goober-arg-for-testing\""
- }
- ]
-}
{
"stdout": [
{
- "line": "test cases/failing/113 allow_fallback with fallback/meson.build:3:0: ERROR: \"fallback\" and \"allow_fallback\" arguments are mutually exclusive"
+ "line": "test cases/failing/111 allow_fallback with fallback/meson.build:3:0: ERROR: \"fallback\" and \"allow_fallback\" arguments are mutually exclusive"
}
]
}
+++ /dev/null
-{
- "stdout": [
- {
- "line": "test cases/failing/111 empty fallback/meson.build:6:0: ERROR: Dependency \"foo\" not found, tried pkgconfig and cmake"
- }
- ]
-}
+++ /dev/null
-{
- "stdout": [
- {
- "line": "test cases/failing/112 cmake executable dependency/meson.build:9:0: ERROR: main is an executable and does not support the dependency() method. Use target() instead."
- }
- ],
- "tools": {
- "cmake": ">=3.14"
- }
-}
{
"stdout": [
{
- "line": "test cases/failing/114 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/112 nonsensical bindgen/meson.build:17:24: ERROR: bindgen source file must be a C header, not an object or build target"
}
]
}
project('trivial test', 'c')
-py_inst = import('python').find_installation()
+python = find_program('python3')
exe = executable('trivialprog', 'trivial.c')
-runt = run_target('invalid', command: [py_inst, '--version'])
+runt = run_target('invalid', command: [python, '--version'])
test('runtest', exe, args: runt)
{
"stdout": [
{
- "line": "test cases/failing/115 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]"
+ "line": "test cases/failing/113 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]"
}
]
}
project('trivial test', 'c')
-py_inst = import('python').find_installation()
+python = find_program('python3')
exe = executable('trivialprog', 'trivial.c')
-runt = run_target('invalid', command: [py_inst, '--version'])
+runt = run_target('invalid', command: [python, '--version'])
meson.add_install_script(exe, runt)
{
"stdout": [
{
- "line": "test cases/failing/116 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/114 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\""
}
]
}
{
"stdout": [
{
- "line": "test cases/failing/117 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/115 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."
}
]
}
--- /dev/null
+{
+ "stdout": [
+ {
+ "line": "test cases/failing/116 subproject version conflict/meson.build:4:8: ERROR: Subproject B version is 100 but ['1'] required."
+ }
+ ]
+}
--- /dev/null
+{
+ "stdout": [
+ {
+ "line": "test cases/failing/117 structured source empty string/meson.build:9:2: ERROR: structured_sources: keys to dictionary argument may not be an empty string."
+ }
+ ]
+}
{
"stdout": [
{
- "line": "test cases/failing/120 structured_sources conflicts/meson.build:7:0: ERROR: Conflicting sources in structured sources: main.rs"
+ "line": "test cases/failing/118 structured_sources conflicts/meson.build:7:0: ERROR: Conflicting sources in structured sources: main.rs"
}
]
}
+++ /dev/null
-{
- "stdout": [
- {
- "line": "test cases/failing/118 subproject version conflict/meson.build:4:0: ERROR: Subproject B version is 100 but ['1'] required."
- }
- ]
-}
{
"stdout": [
{
- "line": "test cases/failing/121 missing compiler/subprojects/sub/meson.build:4:0: ERROR: No host machine compiler for 'subprojects/sub/main.c'"
+ "line": "test cases/failing/119 missing compiler/subprojects/sub/meson.build:4:0: ERROR: No host machine compiler for 'subprojects/sub/main.c'"
}
]
}
+++ /dev/null
-{
- "stdout": [
- {
- "line": "test cases/failing/119 structured source empty string/meson.build:7:0: ERROR: structured_sources: keys to dictionary argument may not be an empty string."
- }
- ]
-}
{
"stdout": [
{
- "line": "test cases/failing/12 string arithmetic/meson.build:3:0: ERROR: The `+` operator of str does not accept objects of type int (3)"
+ "line": "test cases/failing/12 string arithmetic/meson.build:3:12: ERROR: The `+` operator of str does not accept objects of type int (3)"
}
]
}
--- /dev/null
+{
+ "stdout": [
+ {
+ "line": "test cases/failing/120 cmake subproject error/meson.build:8:14: ERROR: Failed to configure the CMake subproject: Fancy error message"
+ }
+ ],
+ "tools": {
+ "cmake": ">=3.14"
+ }
+}
{
"stdout": [
{
- "line": "test cases/failing/123 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/121 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"
}
]
+++ /dev/null
-{
- "stdout": [
- {
- "line": "test cases/failing/122 cmake subproject error/meson.build:8:0: ERROR: Failed to configure the CMake subproject: Fancy error message"
- }
- ],
- "tools": {
- "cmake": ">=3.14"
- }
-}
},
"stdout": [
{
- "line": "test cases/failing/124 subproject sandbox violation/meson.build:24:0: ERROR: Sandbox violation: Tried to grab file file.txt from a nested subproject."
+ "line": "test cases/failing/122 subproject sandbox violation/meson.build:24:0: ERROR: Sandbox violation: Tried to grab file file.txt from a nested subproject."
}
]
}
{
"stdout": [
{
- "line": "test cases/failing/125 override and add_project_dependency/subprojects/a/meson.build:6:0: ERROR: Dependencies must be external dependencies"
+ "line": "test cases/failing/123 override and add_project_dependency/subprojects/a/meson.build:6:0: ERROR: Dependencies must be external dependencies"
}
]
}
{
"stdout": [
{
- "line": "test cases/failing/126 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/124 targets before add_project_dependency/meson.build:5:0: ERROR: Tried to use 'add_project_dependencies' after a build target has been declared."
}
]
}
--- /dev/null
+{
+ "stdout": [
+ {
+ "line": "test cases/failing/125 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."
+ }
+ ]
+}
{
"stdout": [
{
- "line": "test cases/failing/128 subproject object as a dependency/meson.build:3:0: ERROR: Tried to use subproject object as a dependency."
+ "line": "test cases/failing/126 subproject object as a dependency/meson.build:3:0: ERROR: Tried to use subproject object as a dependency."
}
]
}
+++ /dev/null
-{
- "stdout": [
- {
- "line": "test cases/failing/127 extract from unity/meson.build:4:0: ERROR: Single object files can not be extracted in Unity builds. You can only extract all the object files for each compiler at once."
- }
- ]
-}
--- /dev/null
+int foo(void) { return 7; }
--- /dev/null
+project('generator host binary no exe_wrapper')
+
+if meson.can_run_host_binaries()
+ error('MESON_SKIP_TEST: test requires that build machine cannot run host binaries')
+endif
+
+add_languages('c', native : false)
+
+exe = executable('exe', 'exe.c', native : false)
+
+gen = generator(exe, output : '@BASENAME@.c', arguments : ['@INPUT@', '@OUTPUT@'])
+foo = gen.process('lib.in')
+
+library('foo', foo)
--- /dev/null
+{
+ "stdout": [
+ { "line": "ERROR: An exe_wrapper is needed but was not found. Please define one in cross file and check the command and/or add it to PATH." }
+ ]
+}
--- /dev/null
+project('invalid ast crash', meson_version: '0.1.0')
+
+= >%@
--- /dev/null
+{
+ "stdout": [
+ {
+ "match": "re",
+ "line": "test cases/failing/128 invalid ast/meson.build:1:44: ERROR: Meson version is [0-9.]+(\\.rc[0-9]+)? but project requires 0.1.0"
+ }
+ ]
+}
+
--- /dev/null
+project('invalid project function with bad kwargs', meson_version: '0.1.0', unknown_kwarg: 'val')
--- /dev/null
+{
+ "stdout": [
+ {
+ "match": "re",
+ "line": "test cases/failing/129 invalid project function/meson.build:1:67: ERROR: Meson version is [0-9.]+(\\.rc[0-9]+)? but project requires 0.1.0"
+ }
+ ]
+}
+
{
"stdout": [
{
- "line": "test cases/failing/13 array arithmetic/meson.build:3:0: ERROR: Object <[ArrayHolder] holds [list]: ['a', 'b']> of type array does not support the `*` operator."
+ "line": "test cases/failing/13 array arithmetic/meson.build:3:19: ERROR: Object <[ArrayHolder] holds [list]: ['a', 'b']> of type array does not support the `*` operator."
}
]
}
{
"stdout": [
{
- "line": "test cases/failing/16 extract from subproject/meson.build:6:0: ERROR: Tried to extract objects from a different subproject."
+ "line": "test cases/failing/16 extract from subproject/meson.build:7:32: ERROR: Tried to extract objects from a different subproject."
}
]
}
"stdout": [
{
"match": "re",
- "line": "test cases/failing/20 version/meson\\.build:1:0: ERROR: Meson version is .* but project requires >100\\.0\\.0"
+ "line": "test cases/failing/20 version/meson\\.build:1:44: ERROR: Meson version is .* but project requires >100\\.0\\.0"
}
]
}
{
"stdout": [
{
- "line": "test cases/failing/21 subver/meson.build:3:0: ERROR: Subproject foo version is 1.0.0 but ['>1.0.0'] required."
+ "line": "test cases/failing/21 subver/meson.build:3:4: ERROR: Subproject foo version is 1.0.0 but ['>1.0.0'] required."
}
]
}
{
"stdout": [
{
- "line": "test cases/failing/28 no crossprop/meson.build:3:0: ERROR: Unknown property for host machine: nonexisting"
+ "line": "test cases/failing/28 no crossprop/meson.build:3:14: ERROR: Unknown property for host machine: nonexisting"
}
]
}
{
"comment": "'missing/meson.build' gets transformed with os.path.sep separators",
"match": "re",
- "line": "test cases/failing/3 missing subdir/meson\\.build:3:0: ERROR: Non\\-existent build file 'missing[\\\\/]meson\\.build'"
+ "line": "test cases/failing/3 missing subdir/meson\\.build:3:0: ERROR: Nonexistent build file 'missing[\\\\/]meson\\.build'"
}
]
}
{
"stdout": [
{
- "line": "test cases/failing/30 invalid man extension/meson.build:2:0: ERROR: Man file must have a file extension of a number between 1 and 9"
+ "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"
}
]
}
{
"stdout": [
{
- "line": "test cases/failing/31 no man extension/meson.build:2:0: ERROR: Man file must have a file extension of a number between 1 and 9"
+ "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"
}
]
}
{
"stdout": [
{
- "line": "test cases/failing/32 exe static shared/meson.build:9:0: ERROR: Can't link non-PIC static library 'stat' into shared library 'shr2'. Use the 'pic' option to static_library to build with PIC."
+ "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."
}
]
}
"stdout": [
{
"match": "re",
- "line": ".*/meson\\.build:4:0: ERROR: (Pkg-config binary for machine MachineChoice\\.HOST not found\\. Giving up\\.|Dependency \"foo\\-bar\\-xyz\\-12\\.3\" not found, tried .*)"
+ "line": ".*/meson\\.build:4:10: ERROR: (Pkg-config binary for machine MachineChoice\\.HOST not found\\. Giving up\\.|Dependency \"foo\\-bar\\-xyz\\-12\\.3\" not found, tried .*)"
}
]
}
{
"stdout": [
{
- "line": "test cases/failing/39 kwarg assign/meson.build:3:0: ERROR: Tried to assign values inside an argument list."
+ "line": "test cases/failing/39 kwarg assign/meson.build:3:30: ERROR: Tried to assign values inside an argument list."
}
]
}
{
"match": "re",
"comment": "'subdir/meson.build' gets transformed with os.path.sep separators",
- "line": "test cases/failing/4 missing meson\\.build/meson\\.build:3:0: ERROR: Non\\-existent build file 'subdir[\\\\/]meson\\.build'"
+ "line": "test cases/failing/4 missing meson\\.build/meson\\.build:3:0: ERROR: Nonexistent build file 'subdir[\\\\/]meson\\.build'"
}
]
}
"stdout": [
{
"match": "re",
- "line": "test cases/failing/49 executable comparison/meson.build:6:0: ERROR: Object <ExecutableHolder prog1@exe: prog1(.exe)?> of type Executable does not support the `<` operator."
+ "line": "test cases/failing/49 executable comparison/meson.build:6:14: ERROR: Object <ExecutableHolder prog1@exe: prog1(.exe)?> of type Executable does not support the `<` operator."
}
]
}
{
"stdout": [
{
- "line": "test cases/failing/50 inconsistent comparison/meson.build:5:0: ERROR: Object <[ArrayHolder] holds [list]: []> of type array does not support the `<` operator."
+ "line": "test cases/failing/50 inconsistent comparison/meson.build:5:12: ERROR: Object <[ArrayHolder] holds [list]: []> of type array does not support the `<` operator."
}
]
}
{
"stdout": [
{
- "line": "test cases/failing/55 or on new line/meson.build:4:8: ERROR: Invalid or clause."
+ "line": "test cases/failing/53 or on new line/meson.build:4:8: ERROR: Invalid or clause."
}
]
}
+++ /dev/null
-project('test')
-
-if not add_languages('rust', required: false)
- error('MESON_SKIP_TEST test requires rust compiler')
-endif
-
-shared_library('mytest', 'foo.rs', rust_crate_type : 'staticlib')
+++ /dev/null
-{
- "stdout": [
- {
- "line": "test cases/failing/53 wrong shared crate type/meson.build:7:0: ERROR: Crate type \"staticlib\" invalid for dynamic libraries; must be \"dylib\", \"cdylib\", or \"proc-macro\""
- }
- ]
-}
--- /dev/null
+{
+ "stdout": [
+ {
+ "line": "test cases/failing/54 link with executable/meson.build:4:4: ERROR: Link target 'prog' is not linkable."
+ }
+ ]
+}
+++ /dev/null
-project('test')
-
-if not add_languages('rust', required: false)
- error('MESON_SKIP_TEST test requires rust compiler')
-endif
-
-static_library('mytest', 'foo.rs', rust_crate_type : 'cdylib')
+++ /dev/null
-{
- "stdout": [
- {
- "line": "test cases/failing/54 wrong static crate type/meson.build:7:0: ERROR: Crate type \"cdylib\" invalid for static libraries; must be \"rlib\" or \"staticlib\""
- }
- ]
-}
{
"stdout": [
{
- "line": "test cases/failing/57 assign custom target index/meson.build:24:0: ERROR: Assignment target must be an id."
+ "line": "test cases/failing/55 assign custom target index/meson.build:24:0: ERROR: Assignment target must be an id."
}
]
}
{
"stdout": [
{
- "line": "test cases/failing/58 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/56 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."
}
]
}
+++ /dev/null
-{
- "stdout": [
- {
- "line": "test cases/failing/56 link with executable/meson.build:4:0: ERROR: Link target 'prog' is not linkable."
- }
- ]
-}
--- /dev/null
+option('name', type : 'string', value_ : 'foo')
--- /dev/null
+{
+ "stdout": [
+ {
+ "line": "test cases/failing/57 bad option argument/meson_options.txt:1:0: ERROR: string option got unknown keyword arguments \"value_\""
+ }
+ ]
+}
{
"stdout": [
{
- "line": "test cases/failing/60 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/58 subproj filegrab/subprojects/a/meson.build:3:0: ERROR: Sandbox violation: Tried to grab file prog.c outside current (sub)project."
}
]
}
+++ /dev/null
-option('name', type : 'string', vaule : 'foo')
+++ /dev/null
-{
- "stdout": [
- {
- "line": "test cases/failing/59 bad option argument/meson_options.txt:1:0: ERROR: option got unknown keyword arguments \"vaule\""
- }
- ]
-}
{
"stdout": [
{
- "line": "test cases/failing/61 grab subproj/meson.build:7:0: ERROR: Sandbox violation: Tried to grab file sub.c from a nested subproject."
+ "line": "test cases/failing/59 grab subproj/meson.build:7:0: ERROR: Sandbox violation: Tried to grab file sub.c from a nested subproject."
}
]
}
{
"stdout": [
{
- "line": "test cases/failing/6 missing incdir/meson.build:3:0: ERROR: Include dir nosuchdir does not exist."
+ "line": "test cases/failing/6 missing incdir/meson.build:3:6: ERROR: Include dir nosuchdir does not exist."
}
]
}
{
"stdout": [
{
- "line": "test cases/failing/62 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/60 grab sibling/subprojects/a/meson.build:3:0: ERROR: Sandbox violation: Tried to grab file sneaky.c outside current (sub)project."
}
]
}
{
"stdout": [
{
- "line": "test cases/failing/63 string as link target/meson.build:2:0: ERROR: '' is not a target."
+ "line": "test cases/failing/61 string as link target/meson.build:2:0: ERROR: '' is not a target."
}
]
}
--- /dev/null
+{
+ "stdout": [
+ {
+ "line": "test cases/failing/62 dependency not-found and required/meson.build:2:6: ERROR: Dependency is required but has no candidates."
+ }
+ ]
+}
--- /dev/null
+{
+ "stdout": [
+ {
+ "line": "test cases/failing/63 subproj different versions/subprojects/b/meson.build:3:8: ERROR: Dependency 'c' is required but not found."
+ }
+ ]
+}
+++ /dev/null
-{
- "stdout": [
- {
- "line": "test cases/failing/64 dependency not-found and required/meson.build:2:0: ERROR: Dependency is required but has no candidates."
- }
- ]
-}
--- /dev/null
+{
+ "stdout": [
+ {
+ "line": "test cases/failing/64 wrong boost module/meson.build:9:10: ERROR: Dependency \"boost\" not found, tried system"
+ }
+ ]
+}
{
"stdout": [
{
- "line": "test cases/failing/67 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/65 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."
}
]
}
+++ /dev/null
-{
- "stdout": [
- {
- "line": "test cases/failing/65 subproj different versions/subprojects/b/meson.build:3:0: ERROR: Dependency 'c' is required but not found."
- }
- ]
-}
{
"stdout": [
{
- "line": "test cases/failing/68 skip only subdir/meson.build:8:0: ERROR: File main.cpp does not exist."
+ "line": "test cases/failing/66 skip only subdir/meson.build:8:0: ERROR: File main.cpp does not exist."
}
]
}
+++ /dev/null
-{
- "stdout": [
- {
- "line": "test cases/failing/66 wrong boost module/meson.build:9:0: ERROR: Dependency \"boost\" not found, tried system"
- }
- ]
-}
{
"stdout": [
{
- "line": "test cases/failing/69 dual override/meson.build:5:6: ERROR: Tried to override executable \"override\" which has already been overridden."
+ "line": "test cases/failing/67 dual override/meson.build:5:6: ERROR: Tried to override executable \"override\" which has already been overridden."
}
]
}
-project('overridde an already found exe')
+project('override an already found exe')
old = find_program('something.py')
replacement = find_program('other.py')
{
"stdout": [
{
- "line": "test cases/failing/70 override used/meson.build:5:6: ERROR: Tried to override finding of executable \"something.py\" which has already been found."
+ "line": "test cases/failing/68 override used/meson.build:5:6: ERROR: Tried to override finding of executable \"something.py\" which has already been found."
}
]
}
--- /dev/null
+{
+ "stdout": [
+ {
+ "match": "re",
+ "line": "test cases/failing/69 run_command unclean exit/meson\\.build:4:0: ERROR: Command `.*['\"].*[\\\\/]test cases[\\\\/]failing[\\\\/]69 run_command unclean exit[\\\\/]\\.[\\\\/]returncode\\.py['\"] 1` failed with status 1\\."
+ }
+ ]
+}
"stdout": [
{
"comment": "this error message is not very informative",
- "line": "test cases/failing/72 int literal leading zero/meson.build:5:13: ERROR: Expecting eof got number."
+ "line": "test cases/failing/70 int literal leading zero/meson.build:5:13: ERROR: Expecting eof got number."
}
]
}
{
"stdout": [
{
- "line": "test cases/failing/73 configuration immutable/meson.build:12:16: ERROR: Can not set values on configuration object that has been used."
+ "line": "test cases/failing/71 configuration immutable/meson.build:12:16: ERROR: Can not set values on configuration object that has been used."
}
]
}
+++ /dev/null
-{
- "stdout": [
- {
- "match": "re",
- "line": "test cases/failing/71 run_command unclean exit/meson\\.build:4:0: ERROR: Command \".*[\\\\/]test cases[\\\\/]failing[\\\\/]71 run_command unclean exit[\\\\/]\\.[\\\\/]returncode\\.py 1\" failed with status 1\\."
- }
- ]
-}
--- /dev/null
+{
+ "stdout": [
+ {
+ "line": "test cases/failing/72 link with shared module on osx/meson.build:8:4: ERROR: target prog links against shared module mymodule. This is not permitted on OSX"
+ }
+ ]
+}
-project('non acsii to ascii encoding')
-# Writing a non ASCII character with a ASCII encoding should fail
+project('non-ascii to ascii encoding')
+# Writing a non-ASCII character with a ASCII encoding should fail
conf9 = configuration_data()
conf9.set('var', 'д')
configure_file(
"stdout": [
{
"match": "re",
- "line": "test cases/failing/75 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/73 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\\)"
}
]
}
+++ /dev/null
-{
- "stdout": [
- {
- "line": "test cases/failing/74 link with shared module on osx/meson.build:8:0: ERROR: target prog links against shared module mymodule. This is not permitted on OSX"
- }
- ]
-}
--- /dev/null
+{
+ "stdout": [
+ {
+ "line": "test cases/failing/74 subproj dependency not-found and required/meson.build:2:10: ERROR: Neither a subproject directory nor a missing.wrap file was found."
+ }
+ ]
+}
{
"stdout": [
{
- "line": "test cases/failing/77 unfound run/meson.build:4:0: ERROR: Tried to use non-existing executable 'nonexisting_prog'"
+ "line": "test cases/failing/75 unfound run/meson.build:4:0: ERROR: Tried to use non-existing executable 'nonexisting_prog'"
}
]
}
endif
# do individual frameworks have a meaningful version to test? And multiple frameworks might be listed...
-dep = dependency('appleframeworks', modules: 'foundation', version: '>0')
+dep = dependency('appleframeworks', modules: 'Foundation', version: '>0')
--- /dev/null
+{
+ "stdout": [
+ {
+ "line": "test cases/failing/76 framework dependency with version/meson.build:8:6: ERROR: Dependency lookup for appleframeworks with method 'framework' failed: Unknown version, but need ['>0']."
+ }
+ ]
+}
+++ /dev/null
-{
- "stdout": [
- {
- "line": "test cases/failing/76 subproj dependency not-found and required/meson.build:2:0: ERROR: Neither a subproject directory nor a missing.wrap file was found."
- }
- ]
-}
{
"stdout": [
{
- "line": "test cases/failing/79 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/77 override exe config/meson.build:6:0: ERROR: Program 'bar' was overridden with the compiled executable 'foo' and therefore cannot be used during configuration"
}
]
}
+++ /dev/null
-{
- "stdout": [
- {
- "line": "test cases/failing/78 framework dependency with version/meson.build:8:0: ERROR: Dependency lookup for appleframeworks with method 'framework' failed: Unknown version, but need ['>0']."
- }
- ]
-}
--- /dev/null
+{
+ "stdout": [
+ {
+ "line": "test cases/failing/78 gl dependency with version/meson.build:9:6: ERROR: Dependency lookup for gl with method 'system' failed: Unknown version, but need ['>0']."
+ }
+ ]
+}
--- /dev/null
+{
+ "stdout": [
+ {
+ "line": "test cases/failing/79 threads dependency with version/meson.build:3:6: ERROR: Dependency lookup for threads with method 'system' failed: Unknown version, but need ['>0']."
+ }
+ ]
+}
{
"stdout": [
{
- "line": "test cases/failing/8 recursive/subprojects/b/meson.build:3:0: ERROR: Recursive include of subprojects: a => b => a."
+ "line": "test cases/failing/8 recursive/subprojects/b/meson.build:3:4: ERROR: Recursive include of subprojects: a => b => a."
}
]
}
+++ /dev/null
-{
- "stdout": [
- {
- "line": "test cases/failing/80 gl dependency with version/meson.build:9:0: ERROR: Dependency lookup for gl with method 'system' failed: Unknown version, but need ['>0']."
- }
- ]
-}
--- /dev/null
+{
+ "stdout": [
+ {
+ "line": "test cases/failing/80 gtest dependency with version/meson.build:8:6: ERROR: Dependency 'gtest' is required but not found."
+ }
+ ]
+}
--- /dev/null
+{
+ "stdout": [
+ {
+ "line": "test cases/failing/81 dub library/meson.build:11:0: ERROR: Dependency \"dubtestproject\" not found"
+ }
+ ]
+}
+++ /dev/null
-{
- "stdout": [
- {
- "line": "test cases/failing/81 threads dependency with version/meson.build:3:0: ERROR: Dependency lookup for threads with method 'system' failed: Unknown version, but need ['>0']."
- }
- ]
-}
{
"stdout": [
{
- "line": "test cases/failing/84 dub executable/meson.build:11:0: ERROR: Dependency \"dubtestproject:test1\" not found"
+ "line": "test cases/failing/82 dub executable/meson.build:11:0: ERROR: Dependency \"dubtestproject:test1\" not found"
}
]
}
+++ /dev/null
-{
- "stdout": [
- {
- "line": "test cases/failing/82 gtest dependency with version/meson.build:8:0: ERROR: Dependency 'gtest' is required but not found."
- }
- ]
-}
},
"stdout": [
{
- "line": "test cases/failing/85 dub compiler/meson.build:17:0: ERROR: Dependency \"dubtestproject:test2\" not found"
+ "line": "test cases/failing/83 dub compiler/meson.build:17:0: ERROR: Dependency \"dubtestproject:test2\" not found"
}
]
}
+++ /dev/null
-{
- "stdout": [
- {
- "line": "test cases/failing/83 dub libray/meson.build:11:0: ERROR: Dependency \"dubtestproject\" not found"
- }
- ]
-}
--- /dev/null
+{
+ "stdout": [
+ {
+ "line": "test cases/failing/84 subproj not-found dep/meson.build:2:10: ERROR: Dependency '(anonymous)' is required but not found."
+ }
+ ]
+}
{
"stdout": [
{
- "line": "test cases/failing/87 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/85 invalid configure file/meson.build:3:0: ERROR: \"install_dir\" must be specified when \"install\" in a configure_file is true"
}
]
}
{
"stdout": [
{
- "line": "test cases/failing/88 kwarg dupe/meson.build:5:0: ERROR: Entry \"install\" defined both as a keyword argument and in a \"kwarg\" entry."
+ "line": "test cases/failing/86 kwarg dupe/meson.build:6:2: ERROR: Entry \"install\" defined both as a keyword argument and in a \"kwarg\" entry."
}
]
}
+++ /dev/null
-{
- "stdout": [
- {
- "line": "test cases/failing/86 subproj not-found dep/meson.build:2:0: ERROR: Dependency '(anonymous)' is required but not found."
- }
- ]
-}
"stdout": [
{
"comment": "literal 'pch/prog.h' from meson.build appears in output, irrespective of os.path.sep",
- "line": "test cases/failing/89 missing pch file/meson.build:2:0: ERROR: File pch/prog.h does not exist."
+ "line": "test cases/failing/87 missing pch file/meson.build:2:6: ERROR: File pch/prog.h does not exist."
}
]
}
--- /dev/null
+{
+ "stdout": [
+ {
+ "line": "test cases/failing/88 pch source different folder/meson.build:4:6: ERROR: PCH files must be stored in the same folder."
+ }
+ ]
+}
{
"stdout": [
{
- "line": "test cases/failing/91 unknown config tool/meson.build:2:0: ERROR: Dependency \"no-such-config-tool\" not found"
+ "line": "test cases/failing/89 unknown config tool/meson.build:2:0: ERROR: Dependency \"no-such-config-tool\" not found"
}
]
}
{
"stdout": [
{
- "line": "test cases/failing/92 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/90 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\""
}
]
}
+++ /dev/null
-{
- "stdout": [
- {
- "line": "test cases/failing/90 pch source different folder/meson.build:4:0: ERROR: PCH files must be stored in the same folder."
- }
- ]
-}
--- /dev/null
+{
+ "stdout": [
+ {
+ "line": "test cases/failing/91 add dict non string key/meson.build:9:9: ERROR: Key must be a string"
+ }
+ ]
+}
--- /dev/null
+{
+ "stdout": [
+ {
+ "line": "test cases/failing/92 add dict duplicate keys/meson.build:9:27: ERROR: Duplicate dictionary key: myKey"
+ }
+ ]
+}
+++ /dev/null
-{
- "stdout": [
- {
- "line": "test cases/failing/93 add dict non string key/meson.build:9:0: ERROR: Key must be a string"
- }
- ]
-}
--- /dev/null
+{
+ "stdout": [
+ {
+ "line": "test cases/failing/93 no host get_external_property/meson.build:3:14: ERROR: Unknown property for host machine: nonexisting"
+ }
+ ]
+}
+++ /dev/null
-{
- "stdout": [
- {
- "line": "test cases/failing/94 add dict duplicate keys/meson.build:9:0: ERROR: Duplicate dictionary key: myKey"
- }
- ]
-}
{
"stdout": [
{
- "line": "test cases/failing/96 no native compiler/meson.build:12:0: ERROR: No host machine compiler for \"main.c\""
+ "line": "test cases/failing/94 no native compiler/meson.build:12:0: ERROR: No host machine compiler for \"main.c\""
}
]
}
+++ /dev/null
-{
- "stdout": [
- {
- "line": "test cases/failing/95 no host get_external_property/meson.build:3:0: ERROR: Unknown property for host machine: nonexisting"
- }
- ]
-}
{
"stdout": [
{
- "line": "test cases/failing/97 subdir parse error/subdir/meson.build:1:0: ERROR: Plusassignment target must be an id."
+ "line": "test cases/failing/95 subdir parse error/subdir/meson.build:1:0: ERROR: Plusassignment target must be an id."
}
]
}
{
"stdout": [
{
- "line": "test cases/failing/98 invalid option file/meson_options.txt:1:0: ERROR: lexer"
+ "line": "test cases/failing/96 invalid option file/meson_options.txt:1:0: ERROR: lexer"
}
]
}
{
"stdout": [
{
- "line": "test cases/failing/99 no lang/meson.build:2:0: ERROR: No host machine compiler for 'main.c'"
+ "line": "test cases/failing/97 no lang/meson.build:2:0: ERROR: No host machine compiler for 'main.c'"
}
]
}
--- /dev/null
+{
+ "stdout": [
+ {
+ "line": "test cases/failing/98 no glib-compile-resources/meson.build:8:12: ERROR: Program 'glib-compile-resources' not found or not executable"
+ }
+ ]
+}
--- /dev/null
+{
+ "stdout": [
+ { "line": "test cases/failing/99 number in combo/meson.build:1:0: ERROR: Value \"1\" (of type \"number\") for combo option \"Optimization level\" is not one of the choices. Possible choices are (as string): \"plain\", \"0\", \"g\", \"1\", \"2\", \"3\", \"s\"." }
+ ]
+}
--- /dev/null
+project('test_project', 'fortran')
+
+if get_option('unity') == 'on'
+ error('MESON_SKIP_TEST: extract_objects does not work in unity builds')
+endif
+
+libfoo = static_library(
+ 'foo',
+ sources : ['foo1.f90', 'foo2.f90'])
+
+foo1_object = libfoo.extract_objects('foo1.f90')
+
+libfinal = library(
+ 'final',
+ sources : 'bar.f90',
+ objects : foo1_object,
+)
--- /dev/null
+#define MYDEF program
+MYDEF foo
+ write (*,*) 'Hello, world!'
+end MYDEF foo
--- /dev/null
+project('preprocess', 'fortran')
+
+fc = meson.get_compiler('fortran')
+
+pp_files = fc.preprocess('main.f90', output: '@PLAINNAME@')
+
+library('foo', pp_files)
testdep = dependency('boost', static: s, modules : ['unit_test_framework'])
nomoddep = dependency('boost', static: s)
extralibdep = dependency('boost', static: s, modules : ['thread', 'system', 'date_time', 'log_setup', 'log', 'filesystem', 'regex'])
-notfound = dependency('boost', static: s, modules : ['this_should_not_exist_on_any_systen'], required: false)
+notfound = dependency('boost', static: s, modules : ['this_should_not_exist_on_any_system'], required: false)
assert(not notfound.found())
{"type": "expr", "file": "usr/lib/?libgirlib.so"},
{"type": "file", "platform": "cygwin", "file": "usr/lib/libgirsubproject.dll.a"}
],
- "skip_on_jobname": ["azure", "cygwin", "macos", "msys2"]
+ "skip_on_jobname": ["azure", "cygwin", "macos", "msys2", "pypy"]
}
# entered, it will be relative to the location where doxygen was started. If
# left blank the current directory will be used.
-OUTPUT_DIRECTORY = doc
+OUTPUT_DIRECTORY = "@TOP_BUILDDIR@/doc"
# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
# directories (in 2 levels) under the output directory of each output format and
-cdata.set('TOP_SRCDIR', meson.source_root())
-cdata.set('TOP_BUILDDIR', meson.build_root())
+cdata.set('TOP_SRCDIR', meson.project_source_root())
+cdata.set('TOP_BUILDDIR', meson.project_build_root())
doxyfile = configure_file(input: 'Doxyfile.in',
output: 'Doxyfile',
-project('doxygen test', 'cpp', version : '0.1.0')
+project('doxygen test', 'cpp', version : '0.1.0', meson_version: '>=0.56')
spede_inc = include_directories('include')
method = get_option('method')
static = get_option('link-static')
-d = dependency('llvm', required : false, method : method, static : static)
+
+d = dependency('llvm', required : false, static : static)
if not d.found()
error('MESON_SKIP_TEST llvm not found.')
endif
-d = dependency('llvm', modules : 'not-found', required : false, static : static, method : method)
-assert(d.found() == false, 'not-found llvm module found')
+if method != 'config-tool' and d.version().startswith('17.0') and host_machine.system() == 'windows'
+ # https://github.com/llvm/llvm-project/commit/e7fc7540daa9333f0be4f380fc9c619236d17f57#r130257253
+ error('MESON_SKIP_TEST broken llvm cmake files on MSYS2')
+endif
-d = dependency('llvm', version : '<0.1', required : false, static : static, method : method)
-assert(d.found() == false, 'ancient llvm module found')
+modules_to_find = [
+ 'bitwriter', 'asmprinter', 'executionengine', 'mcjit', 'target',
+ 'nativecodegen', 'amdgpu', 'engine'
+]
-d = dependency('llvm', optional_modules : 'not-found', required : false, static : static, method : method)
-assert(d.found() == true, 'optional module stopped llvm from being found.')
+if method == 'combination'
+ if not d.version().version_compare(static ? '>0.1' : '>=7.0')
+ error('MESON_SKIP_TEST: llvm version is too low')
+ endif
+ llvm_ct_dep = dependency(
+ 'llvm',
+ modules : modules_to_find,
+ required : false,
+ static : static,
+ method : 'config-tool',
+ )
-# Check we can apply a version constraint
-d = dependency('llvm', version : ['< 500', '>=@0@'.format(d.version())], required: false, static : static, method : method)
-assert(d.found() == true, 'Cannot set version constraints')
+ if static and d.version().startswith('16.0') and d.version()[5].to_int() <= 6
+ message('Skipping modules with cmake due to an LLVM bug, see https://github.com/mesonbuild/meson/issues/11642')
+ llvm_cm_dep = dependency(
+ 'llvm',
+ required : false,
+ static : static,
+ method : 'cmake',
+ )
+ else
+ llvm_cm_dep = dependency(
+ 'llvm',
+ modules : modules_to_find,
+ required : false,
+ static : static,
+ method : 'cmake',
+ )
+ endif
-dep_tinfo = dependency('tinfo', required : false)
-if not dep_tinfo.found()
- cpp = meson.get_compiler('cpp')
- dep_tinfo = cpp.find_library('tinfo', required: false)
-endif
+ assert(llvm_ct_dep.found() and llvm_cm_dep.found(), 'config-tool and cmake both need to be found')
+ cm_version_major = llvm_cm_dep.version().split('.')[0].to_int()
+ cm_version_minor = llvm_cm_dep.version().split('.')[1].to_int()
+ ct_version_major = llvm_ct_dep.version().split('.')[0].to_int()
+ ct_version_minor = llvm_ct_dep.version().split('.')[1].to_int()
+ assert(cm_version_major == ct_version_major, 'config-tool and cmake returns different major versions')
+ assert(cm_version_minor == ct_version_minor, 'config-tool and cmake returns different minor versions')
+else
+ if not static and method == 'cmake'
+ d = dependency('llvm', version : '>=7.0', required : false, static : static)
+ if not d.found()
+ error('MESON_SKIP_TEST llvm version is too low for cmake dynamic link.')
+ endif
+ endif
-llvm_dep = dependency(
- 'llvm',
- modules : ['bitwriter', 'asmprinter', 'executionengine', 'target',
- 'mcjit', 'nativecodegen', 'amdgpu'],
- required : false,
- static : static,
- method : method,
-)
-
-if not llvm_dep.found()
- error('MESON_SKIP_TEST required llvm modules not found.')
-endif
+ d = dependency('llvm', modules : 'not-found', required : false, static : static, method : method)
+ assert(d.found() == false, 'not-found llvm module found')
-executable(
- 'sum',
- 'sum.c',
- dependencies : [
- llvm_dep, dep_tinfo,
- # zlib will be statically linked on windows
- dependency('zlib', required : host_machine.system() != 'windows'),
- meson.get_compiler('c').find_library('dl', required : false),
- ]
- )
+ d = dependency('llvm', version : '<0.1', required : false, static : static, method : method)
+ assert(d.found() == false, 'ancient llvm module found')
+
+ d = dependency('llvm', optional_modules : 'not-found', required : false, static : static, method : method)
+ assert(d.found() == true, 'optional module stopped llvm from being found.')
+
+ # Check we can apply a version constraint
+ d = dependency('llvm', version : ['< 500', '>=@0@'.format(d.version())], required: false, static : static, method : method)
+ assert(d.found() == true, 'Cannot set version constraints')
+
+ # Check if we have to get pseudo components
+ d = dependency('llvm', modules: ['all-targets','native','engine'], required: false, static : static, method : method)
+ assert(d.found() == true, 'Cannot find pseudo components')
+
+ dep_tinfo = dependency('tinfo', required : false)
+ if not dep_tinfo.found()
+ cpp = meson.get_compiler('cpp')
+ dep_tinfo = cpp.find_library('tinfo', required: false)
+ endif
+
+ if static and method == 'cmake' and d.version().startswith('16.0')
+ message('Skipping modules with cmake due to an LLVM bug, see https://github.com/mesonbuild/meson/issues/11642')
+ llvm_dep = dependency(
+ 'llvm',
+ required : false,
+ static : static,
+ method : method,
+ )
+ else
+ llvm_dep = dependency(
+ 'llvm',
+ modules : modules_to_find,
+ required : false,
+ static : static,
+ method : method,
+ )
+ endif
+
+ if not llvm_dep.found()
+ error('MESON_SKIP_TEST required llvm modules not found.')
+ endif
+
+ executable(
+ 'sum',
+ 'sum.c',
+ dependencies : [
+ llvm_dep, dep_tinfo,
+ # zlib will be statically linked on windows
+ dependency('zlib', required : host_machine.system() != 'windows'),
+ meson.get_compiler('c').find_library('dl', required : false),
+ ]
+ )
+endif
option(
'method',
type : 'combo',
- choices : ['config-tool', 'cmake']
+ choices : ['config-tool', 'cmake', 'combination']
)
option(
'link-static',
"matrix": {
"options": {
"method": [
- { "val": "config-tool", "skip_on_jobname": ["msys2-gcc"]},
- { "val": "cmake", "skip_on_jobname": ["msys2-gcc"] }
+ { "val": "config-tool", "skip_on_jobname": ["msys2-gcc"] },
+ { "val": "cmake", "skip_on_jobname": ["msys2"] },
+ { "val": "combination", "skip_on_jobname": ["msys2"] }
],
"link-static": [
{ "val": true, "skip_on_jobname": ["opensuse"] },
{ "val": false }
]
- },
- "exclude": [
- { "method": "cmake", "link-static": false }
- ]
+ }
},
- "skip_on_jobname": ["azure", "cygwin"]
+ "skip_on_jobname": ["azure", "cygwin"],
+ "tools": {
+ "cmake": ">=3.11"
+ }
}
option(
'method',
type : 'combo',
- choices : ['auto', 'pkg-config', 'config-tool', 'sdlconfig', 'extraframework'],
+ choices : ['auto', 'pkg-config', 'config-tool', 'sdlconfig', 'extraframework', 'cmake'],
value : 'auto',
)
{ "val": "pkg-config" },
{ "val": "config-tool" },
{ "val": "sdlconfig" },
- { "val": "extraframework", "skip_on_os": ["!darwin"], "skip_on_jobname": ["macos"] }
+ { "val": "extraframework", "skip_on_os": ["!darwin"], "skip_on_jobname": ["macos"] },
+ { "val": "cmake", "skip_on_jobname": ["bionic"] }
]
}
},
assert(libwmf_ver.split('.').length() > 1, 'libwmf version is "@0@"'.format(libwmf_ver))
message('libwmf version is "@0@"'.format(libwmf_ver))
# Workaround for Debian bug 912563 where libwmf-devel returns cflags
-# that do not not have Freetype include paths but their headers
+# that do not have Freetype include paths but their headers
# use them unconditionally.
ft_dep = dependency('freetype2')
e = executable('libwmf_prog', 'libwmf_prog.c', dependencies : [libwmf_dep, ft_dep])
#include <iostream>
-#include "hdf5.h"
+#include "H5Cpp.h"
int main(void)
{
-herr_t ier;
unsigned maj, min, rel;
-ier = H5open();
-if (ier) {
- std::cerr << "Unable to initialize HDF5: " << ier << std::endl;
+try {
+ H5::H5Library::open();
+ H5::H5Library::getLibVersion(maj, min, rel);
+ std::cout << "C++ HDF5 version " << maj << "." << min << "." << rel << std::endl;
+ H5::H5Library::close();
+ return EXIT_SUCCESS;
+} catch (H5::LibraryIException &e) {
+ std::cerr << "Exception caught from HDF5: " << e.getDetailMsg() << std::endl;
return EXIT_FAILURE;
}
-
-ier = H5get_libversion(&maj, &min, &rel);
-if (ier) {
- std::cerr << "HDF5 did not initialize!" << std::endl;
- return EXIT_FAILURE;
-}
-std::cout << "C++ HDF5 version " << maj << "." << min << "." << rel << std::endl;
-
-ier = H5close();
-if (ier) {
- std::cerr << "Unable to close HDF5: " << ier << std::endl;
- return EXIT_FAILURE;
-}
-return EXIT_SUCCESS;
}
# Search paths don't work correctly here and -lgfortran doesn't work
test_fortran = false
elif host_machine.system() == 'windows' and cpp.get_id() != 'gcc' and fc.get_id() == 'gcc'
- # mixing gfotran with non-gcc doesn't work on windows
+ # mixing gfortran with non-gcc doesn't work on windows
test_fortran = false
endif
{
- "skip_on_jobname": ["azure", "cygwin", "macos", "msys2"]
+ "skip_on_jobname": ["azure", "cygwin", "macos", "msys2", "linux-arch", "ubuntu"]
}
--- /dev/null
+<?xml version="1.0"?>
+<!DOCTYPE book PUBLIC '-//OASIS//DTD DocBook XML V4.5//EN'
+ 'http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd' [
+<!ENTITY % local.common.attrib "xmlns:xi CDATA #FIXED 'http://www.w3.org/2003/XInclude'">
+<!ENTITY version SYSTEM "version.xml">
+]>
+<book id="index" xmlns:xi="http://www.w3.org/2003/XInclude">
+ <bookinfo>
+ <title>Foo Reference Manual</title>
+ </bookinfo>
+
+ <chapter id="foo">
+ <title>GLib Core Application Support</title>
+ <xi:include href="xml/foo.xml" />
+ </chapter>
+</book>
--- /dev/null
+#include "foo.h"
+
+int foo_do_something(void) {
+ return 42;
+}
--- /dev/null
+int foo_do_something(void);
--- /dev/null
+project('gnome module without C', 'cpp')
+
+gtkdoc = find_program('gtkdoc-scan', required: false)
+if not gtkdoc.found()
+ error('MESON_SKIP_TEST gtkdoc not found.')
+endif
+
+gnome = import('gnome')
+
+lib = library('foo++', 'foo.cpp')
+gnome.gtkdoc('foo',
+ src_dir: '.',
+ main_xml : 'foo-docs.xml',)
--- /dev/null
+{
+ "installed": [
+ {"type": "file", "file": "usr/share/gtk-doc/html/foo/up-insensitive.png"},
+ {"type": "file", "file": "usr/share/gtk-doc/html/foo/home.png"},
+ {"type": "file", "file": "usr/share/gtk-doc/html/foo/foo.html"},
+ {"type": "file", "file": "usr/share/gtk-doc/html/foo/foo-foo.html"},
+ {"type": "file", "file": "usr/share/gtk-doc/html/foo/style.css"},
+ {"type": "file", "file": "usr/share/gtk-doc/html/foo/index.html"},
+ {"type": "file", "file": "usr/share/gtk-doc/html/foo/foo.devhelp2"},
+ {"type": "file", "file": "usr/share/gtk-doc/html/foo/left.png"},
+ {"type": "file", "file": "usr/share/gtk-doc/html/foo/left-insensitive.png"},
+ {"type": "file", "file": "usr/share/gtk-doc/html/foo/right-insensitive.png"},
+ {"type": "file", "file": "usr/share/gtk-doc/html/foo/up.png"},
+ {"type": "file", "file": "usr/share/gtk-doc/html/foo/right.png"}
+ ],
+ "skip_on_jobname": ["azure", "msys2"]
+}
--- /dev/null
+#include "foo.h"
+
+int foo_do_something(void) {
+ return 42;
+}
--- /dev/null
+int foo_do_something(void);
--- /dev/null
+project('gnome module without C', 'cpp')
+
+gi = dependency('gobject-introspection-1.0', required: false)
+if not gi.found()
+ error('MESON_SKIP_TEST gobject-introspection not found.')
+endif
+
+if host_machine.system() == 'cygwin'
+ # FIXME: g-ir-scanner seems broken on cygwin:
+ # ERROR: can't resolve libraries to shared libraries: foo++
+ error('MESON_SKIP_TEST g-ir-scanner is broken on cygwin.')
+endif
+
+gnome = import('gnome')
+
+lib = library('foo++', 'foo.cpp')
+gnome.generate_gir(
+ lib,
+ sources: ['foo.cpp', 'foo.h'],
+ namespace: 'foo',
+ nsversion: meson.project_version(),
+)
--- /dev/null
+{
+ "skip_on_jobname": ["azure", "macos", "msys2", "cygwin"]
+}
#include"manualinclude.h"
+#include <mocdep.h>
#include<QCoreApplication>
#include<QObject>
# Qt6 requires C++ 17 support
default_options : ['cpp_std=c++17'])
+# Visit the subdir before entering the loop
+subdir('mocdep')
+
qt5_modules = ['Widgets']
qt6_modules = ['Widgets']
foreach qt : ['qt4', 'qt5', 'qt6']
qtmodule = import(qt)
assert(qtmodule.has_tools())
+ # Test that fetching a variable works and yields a non-empty value
+ assert(qtdep.get_variable('prefix', configtool: 'QT_INSTALL_PREFIX') != '')
+
# The following has two resource files because having two in one target
# requires you to do it properly or you get linker symbol clashes.
extra_cpp_args = []
if meson.is_unity()
extra_cpp_args += '-DUNITY_BUILD'
- prep_rcc = qtmodule.preprocess(qt + '_unity_ressource', qresources : ['stuff.qrc', 'stuff2.qrc'], method : get_option('method'))
+ prep_rcc = qtmodule.preprocess(qt + '_unity_resource', qresources : ['stuff.qrc', 'stuff2.qrc'], method : get_option('method'))
else
prep_rcc = qtmodule.preprocess(qresources : ['stuff.qrc', 'stuff2.qrc'], method : get_option('method'))
endif
moc_extra_arguments : ['-DMOC_EXTRA_FLAG'], # This is just a random macro to test `extra_arguments`
moc_sources : 'manualinclude.cpp',
moc_headers : 'manualinclude.h',
- method : get_option('method'))
+ method : get_option('method'),
+ dependencies: mocdep,
+ )
manpreprocessed = qtmodule.compile_moc(
extra_args : ['-DMOC_EXTRA_FLAG'], # This is just a random macro to test `extra_arguments`
sources : 'manualinclude.cpp',
headers : 'manualinclude.h',
- method : get_option('method'))
+ method : get_option('method'),
+ dependencies: mocdep,
+ )
qtmaninclude = executable(qt + 'maninclude',
sources : ['manualinclude.cpp', manpreprocessed],
- dependencies : qtcore)
+ dependencies : [qtcore, mocdep])
test(qt + 'maninclude', qtmaninclude)
endif
# Check we can apply a version constraint
- dependency(qt, modules: qt_modules, version: '>=@0@'.format(qtdep.version()), method : get_option('method'))
+ accept_versions = ['>=@0@'.format(qtdep.version()), '<@0@'.format(qtdep.version()[0].to_int() + 1)]
+ dependency(qt, modules: qt_modules, version: accept_versions, method : get_option('method'))
endif
endforeach
--- /dev/null
+mocdep = declare_dependency(
+ include_directories: include_directories('.'),
+)
--- /dev/null
+#define MOC_DEP 1
extra_cpp_args = []
if meson.is_unity()
extra_cpp_args += '-DUNITY_BUILD'
- qresources = qtmodule.preprocess(qt + '_subfolder_unity_ressource',qresources : ['resources/stuff3.qrc', rc_file])
+ qresources = qtmodule.preprocess(qt + '_subfolder_unity_resource',qresources : ['resources/stuff3.qrc', rc_file])
else
qresources = qtmodule.preprocess(qresources : ['resources/stuff3.qrc', rc_file])
endif
-project('protocol buffer test', 'cpp', default_options: ['cpp_std=c++11'])
+project('protocol buffer test', 'cpp', default_options: ['cpp_std=c++14'])
protoc = find_program('protoc', required : false)
dep = dependency('protobuf', required : false)
#!/usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0
-# Copyright © 2021 Intel Corproation
+# Copyright © 2021 Intel Corporation
import argparse
import shutil
#ifndef MESON_DEP1_H
#define MESON_DEP1_H
-#if !defined (MESON_TEST)
-#error "MESON_TEST not defined."
+#if !defined (MESON_TEST_1)
+#error "MESON_TEST_1 not defined."
#endif
#include <glib-object.h>
#ifndef MESON_DEP2_H
#define MESON_DEP2_H
-#if !defined (MESON_TEST)
-#error "MESON_TEST not defined."
+#if !defined (MESON_TEST_1)
+#error "MESON_TEST_1 not defined."
#endif
#include <glib-object.h>
#ifndef MESON_DEP3_H
#define MESON_DEP3_H
-#if !defined (MESON_TEST)
-#error "MESON_TEST not defined."
+#if !defined (MESON_TEST_1)
+#error "MESON_TEST_1 not defined."
#endif
#include <glib-object.h>
#ifndef MESON_SAMPLE_H
#define MESON_SAMPLE_H
-#if !defined (MESON_TEST)
-#error "MESON_TEST not defined."
+#if !defined (MESON_TEST_1)
+#error "MESON_TEST_1 not defined."
+#endif
+
+#if !defined (MESON_TEST_2)
+#error "MESON_TEST_2 not defined."
#endif
#include <glib-object.h>
#ifndef MESON_SAMPLE2_H
#define MESON_SAMPLE2_H
-#if !defined (MESON_TEST)
-#error "MESON_TEST not defined."
+#if !defined (MESON_TEST_1)
+#error "MESON_TEST_1 not defined."
#endif
#include <glib-object.h>
lib2sources = ['meson-sample2.c', 'meson-sample2.h']
gen_source = custom_target(
- 'meson_smaple3.h',
+ 'meson_sample3.h',
input : 'meson-sample.h',
output : 'meson-sample3.h',
command : [find_program('copy.py'), '@INPUT@', '@OUTPUT@'],
girlib = shared_library(
'gir_lib',
sources : libsources,
+ c_args: '-DMESON_TEST_2',
dependencies : [gobj, dep1_dep],
install : true
)
girexe = executable(
'girprog',
sources : 'prog.c',
+ c_args: '-DMESON_TEST_2',
dependencies : [glib, gobj, gir, dep1_dep],
link_with : girlib
)
gnome.generate_gir(
girlib, girlib2,
sources : [libsources, lib2sources, gen_source],
+ env : {'CPPFLAGS': '-DMESON_TEST_2'},
nsversion : '1.0',
namespace : 'Meson',
symbol_prefix : 'meson',
cc = meson.get_compiler('c')
-add_global_arguments('-DMESON_TEST', language : 'c')
+add_global_arguments('-DMESON_TEST_1', language : 'c')
if cc.get_id() == 'intel'
# Ignore invalid GCC pragma warnings from glib
# https://bugzilla.gnome.org/show_bug.cgi?id=776562
gir = dependency('gobject-introspection-1.0')
gmod = dependency('gmodule-2.0')
+# GLib >= 2.76 removed slice allocator which causes a leak in g-i to now be
+# visible to asan. The leak should be fixed in g-i >= 1.76.2:
+# https://gitlab.gnome.org/GNOME/gobject-introspection/-/merge_requests/411
+if get_option('b_sanitize') != 'none' and \
+ gir.version().version_compare('<=1.76.1') and \
+ glib.version().version_compare('>=2.76')
+ error('MESON_SKIP_TEST gobject-introspection >=1.76.2 is required with address sanitizer.')
+endif
+
# Test that static deps don't error out when static libraries aren't found
glib_static = dependency('glib-2.0', static : true)
enumexe6 = executable('enumprog6', main, enums_c2, enums_h6,
dependencies : gobj)
test('enum test 4', enumexe6)
+
+# Test with headers coming from other directories
+# https://github.com/mesonbuild/meson/pull/10855
+subdir('subdir')
+enums7 = gnome.mkenums_simple('enums7', sources: ['meson-sample.h', h2, h3])
+main = configure_file(
+ input : 'main.c',
+ output : 'mai7.c',
+ configuration : {'ENUM_FILE': 'enums7.h'})
+test('enums7 test', executable('enumprog7', main, enums7, dependencies : gobj))
--- /dev/null
+#pragma once
+
+typedef enum {
+ MESON_SUBDIR_FOO,
+} MesonSubdir;
--- /dev/null
+#pragma once
+
+typedef enum {
+ MESON_SUBDIR2_FOO,
+} MesonSubdir2;
--- /dev/null
+h2 = configure_file(input: 'h2.h.in', output: 'h2.h', copy: true)
+h3 = files('h3.h')
{"type": "file", "file": "usr/include/simple-resources.h"},
{"type": "file", "file": "usr/include/generated-gdbus.h"}
],
- "skip_on_jobname": ["azure", "cygwin", "macos", "msys2"]
+ "skip_on_jobname": ["azure", "cygwin", "macos", "msys2", "pypy"]
}
project('simplejava', 'java')
-add_project_arguments('-target', '1.7', language : 'java')
+add_project_arguments('-target', '1.8', language : 'java')
javaprog = jar('myprog', 'com/mesonbuild/Simple.java',
main_class : 'com.mesonbuild.Simple',
- java_args : ['-source', '1.7'])
+ java_args : ['-source', '1.8'])
test('mytest', javaprog)
-python = import('python').find_installation('python3')
+python = find_program('python3')
config_file = custom_target('confgen',
input : 'Config.java.in',
assert(dep.get_pkgconfig_variable('nonexisting', default: 'foo') == 'foo', 'Value of unknown variable is not defaulted.')
# pkg-config is able to replace variables
assert(dep.get_pkgconfig_variable('prefix', define_variable: ['prefix', '/tmp']) == '/tmp', 'prefix variable has not been replaced.')
+assert(dep.get_variable('prefix', pkgconfig_define: ['prefix', '/tmp']) == '/tmp', 'prefix variable has not been replaced.')
+# pkg-config can replace multiple variables at once
+assert(dep.get_variable('prefix', pkgconfig_define: ['prefix', '/tmp', 'libdir', '/bad/libdir']) == '/tmp', 'first variable has not been replaced.')
+assert(dep.get_variable('prefix', pkgconfig_define: ['libdir', '/bad/libdir', 'prefix', '/tmp']) == '/tmp', 'second variable has not been replaced.')
+assert(dep.get_pkgconfig_variable('prefix', define_variable: ['prefix', '/tmp', 'libdir', '/bad/libdir']) == '/tmp', 'first variable has not been replaced.')
+assert(dep.get_pkgconfig_variable('prefix', define_variable: ['libdir', '/bad/libdir', 'prefix', '/tmp']) == '/tmp', 'second variable has not been replaced.')
+
# Test that dependencies of dependencies work.
dep2 = declare_dependency(dependencies : dep)
int some_symbol (void);
-int main () {
+int main (void) {
int ret = some_symbol ();
if (ret == 1)
return 0;
project('runpath rpath ldlibrarypath', 'c')
-error('MESON_SKIP_TEST test disabled due to bug #1635.')
-
libsrc = files('lib.c')
subdir('lib1')
set(cmMesonTestF1_LIBRARIES general ${ZLIB_LIBRARY})
set(cmMesonTestF1_INCLUDE_DIRS ${ZLIB_INCLUDE_DIR})
- add_library(CMMesonTESTf1::evil_non_standard_trget UNKNOWN IMPORTED)
+ add_library(CMMesonTESTf1::evil_non_standard_target UNKNOWN IMPORTED)
else()
set(cmMesonTestF1_FOUND OFF)
endif()
# due to use of setup_env.json
project('external CMake dependency', ['c', 'cpp'])
-if not find_program('cmake', required: false).found()
+cmake = find_program('cmake', required: false)
+if not cmake.found()
error('MESON_SKIP_TEST cmake binary not available.')
endif
# Zlib is probably on all dev machines.
dep = dependency('ZLIB', version : '>=1.2', method : 'cmake')
+
+if '#define' in dep.version() and cmake.version().version_compare('< 3.27.4')
+ # ZLIB 1.3 version is broken with those cmake versions
+ error('MESON_SKIP_TEST known bug in cmake (https://gitlab.kitware.com/cmake/cmake/-/issues/25200)')
+endif
+
exe = executable('zlibprog', 'prog-checkver.c',
dependencies : dep,
c_args : '-DFOUND_ZLIB="' + dep.version() + '"')
"line": "WARNING: Could not find and exact match for the CMake dependency cmMesonTestF1."
},
{
- "line": " ['CMMesonTESTf1::evil_non_standard_trget']"
+ "line": " ['CMMesonTESTf1::evil_non_standard_target']"
}
]
}
output : 'bob-conf.map',
configuration : conf,
)
-vflag = '-Wl,--version-script,@0@'.format(m)
+vflag = '-Wl,--version-script,@0@'.format(meson.current_build_dir() / 'bob-conf.map')
l = shared_library('bob-conf', 'bob.c', link_args : vflag, link_depends : m)
e = executable('prog-conf', 'prog.c', link_with : l)
e = executable('prog-ct', 'prog.c', link_with : l)
test('core', e)
-# File
-mapfile = files('bob.map')
-vflag = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), mapfile[0])
-
-l = shared_library('bob-files', 'bob.c', link_args : vflag, link_depends : mapfile)
-e = executable('prog-files', 'prog.c', link_with : l)
-test('core', e)
-
subdir('sub')
# With map file in subdir
# Verify that once we got a system dependency, we won't fallback if a newer
# version is requested.
d = dependency('zlib', version: '>= 999',
- fallback : ['donotexist', 'fakezlib_dep'],
+ fallback : ['nonexistent', 'fakezlib_dep'],
required: false)
assert(not d.found(), 'version should not match and it should not fallback')
error('MESON_SKIP_TEST: nasm only supported for x86 and x86_64')
endif
+if host_machine.system() == 'windows'
+ error('MESON_SKIP_TEST: this test asm is not made for Windows')
+elif host_machine.system() == 'sunos'
+ error('MESON_SKIP_TEST: this test asm is not made for Solaris or illumos')
+endif
+
+if meson.backend().startswith('vs')
+ error('MESON_SKIP_TEST: VS backend does not recognise NASM yet')
+endif
+
if not add_languages('nasm', required: false)
nasm = find_program('nasm', 'yasm', required: false)
assert(not nasm.found())
link_args: link_args,
)
test('hello', exe)
+
+#Test whether pthread dependency gets filtered out
+threads = dependency('threads')
+
+exe2 = executable('hello_w_threads', 'hello.asm',
+ config_file,
+ nasm_args: '-DFOO',
+ link_args: link_args,
+ dependencies: [threads]
+)
+
+test('hello_w_threads', exe2)
--- /dev/null
+global dummy
+section .rdata align=16
+dummy:
+ dd 0x00010203
--- /dev/null
+EXPORTS
+ dummy
--- /dev/null
+project('nasm only')
+
+if not add_languages('nasm', required: false)
+ error('MESON_SKIP_TEST: nasm not found')
+endif
+
+if meson.backend().startswith('vs')
+ error('MESON_SKIP_TEST: VS backend does not recognise NASM yet')
+endif
+
+sources = files('dummy.asm')
+
+dummy = library(
+ 'dummy',
+ sources,
+ vs_module_defs: 'dummy.def',
+)
#endif
#if !defined(GLOBAL_HOST) && !defined(GLOBAL_BUILD)
- #error "Neither global_host nor glogal_build is set."
+ #error "Neither global_host nor global_build is set."
#endif
#if defined(GLOBAL_HOST) && defined(GLOBAL_BUILD)
project('nsstring', 'objc')
if host_machine.system() == 'darwin'
- dep = dependency('appleframeworks', modules : 'foundation')
+ dep = dependency('appleframeworks', modules : 'Foundation')
elif host_machine.system() == 'cygwin'
error('MESON_SKIP_TEST GNUstep is not packaged for Cygwin.')
else
project('xcode extra framework test', 'c')
-dep_libs = dependency('OpenGL', method : 'extraframework')
-assert(dep_libs.type_name() == 'extraframeworks', 'type_name is ' + dep_libs.type_name())
+opengl_dep = dependency('OpenGL', method : 'extraframework')
+assert(opengl_dep.type_name() == 'extraframeworks', 'type_name is ' + opengl_dep.type_name())
dep_main = dependency('Foundation')
assert(dep_main.type_name() == 'extraframeworks', 'type_name is ' + dep_main.type_name())
-stlib = static_library('stat', 'stat.c', install : true, dependencies: dep_libs)
+# https://github.com/mesonbuild/meson/issues/10002
+ldap_dep = dependency('ldap', method : 'extraframework')
+assert(ldap_dep.type_name() == 'extraframeworks', 'type_name is ' + ldap_dep.type_name())
+
+stlib = static_library('stat', 'stat.c', install : true, dependencies: [opengl_dep, ldap_dep])
exe = executable('prog', 'prog.c', install : true, dependencies: dep_main)
+// https://github.com/mesonbuild/meson/issues/10002
+#include <ldap.h>
+
int func(void) { return 933; }
# that causes a build failure when defining two modules. The
# arguments for the latter module overwrote the arguments for
# the first one rather than adding to them.
-cocoa_dep = dependency('appleframeworks', modules : ['AppKit', 'foundation'])
+cocoa_dep = dependency('appleframeworks', modules : ['AppKit', 'Foundation'])
executable('deptester',
'main.m',
--- /dev/null
+// Source: https://lists.gnu.org/archive/html/libtool/2002-07/msg00025.html
+
+#include <stdio.h>
+
+extern int l2;
+void l1(void)
+{
+ printf("l1 %d\n", l2);
+}
--- /dev/null
+// Source: https://lists.gnu.org/archive/html/libtool/2002-07/msg00025.html
+
+int l2;
+void l2_func(void)
+{
+ l2 = 77;
+}
--- /dev/null
+# Source: https://lists.gnu.org/archive/html/libtool/2002-07/msg00025.html
+
+project('global variable test', 'c')
+
+lib = static_library('mylib', 'libfile.c', 'libfile2.c')
+test('global variable', executable('prog', 'prog.c', link_with: lib))
--- /dev/null
+[binaries]
+ar = 'ar'
--- /dev/null
+// Source: https://lists.gnu.org/archive/html/libtool/2002-07/msg00025.html
+
+extern void l1(void);
+int main(void)
+{
+ l1();
+}
error('MESON_SKIP_TEST python 3 required for tests')
endif
+py_full_version = py.version()
+message(f'Using python version: @py_full_version@')
+
py_purelib = py.get_path('purelib')
if not (py_purelib.endswith('site-packages') or py_purelib.endswith('dist-packages'))
error('Python3 purelib path seems invalid? ' + py_purelib)
--- /dev/null
+project('Python limited api disabled', 'c',
+ default_options : ['buildtype=release', 'werror=true', 'python.allow_limited_api=false'])
+
+py_mod = import('python')
+py = py_mod.find_installation()
+
+module = py.extension_module('my_module',
+ 'module.c',
+ limited_api: '3.7',
+)
--- /dev/null
+#include <Python.h>
+
+#if defined(Py_LIMITED_API)
+#error "Py_LIMITED_API's definition by Meson should have been disabled."
+#endif
+
+static struct PyModuleDef my_module = {
+ PyModuleDef_HEAD_INIT,
+ "my_module",
+ NULL,
+ -1,
+ NULL
+};
+
+PyMODINIT_FUNC PyInit_my_module(void) {
+ return PyModule_Create(&my_module);
+}
raise SystemExit('Returned result not an integer.')
if result != 1:
- raise SystemExit(f'Returned result {result} is not 1.')
+ raise SystemExit('Returned result {} is not 1.'.format(result))
install: true,
)
+pylib2 = py2.extension_module('tachyon',
+ 'tachyon_module.c',
+ c_args: '-DMESON_MODULENAME="tachyon"',
+ install: true,
+)
+
subdir('nested')
subdir('wrongdir')
pypathdir = meson.current_build_dir()
pure: false,
subdir: 'nested',
)
+
+
+py2.extension_module('tachyon',
+ '../tachyon_module.c',
+ c_args: '-DMESON_MODULENAME="nested.tachyon"',
+ install: true,
+ subdir: 'nested'
+)
+py2.install_sources(
+ configure_file(
+ input: '../../blaster.py.in',
+ output: 'blaster.py',
+ configuration: {'tachyon_module': 'nested.tachyon'}
+ ),
+ pure: false,
+ subdir: 'nested',
+)
/*
- Copyright 2016 The Meson development team
+ Copyright 2018 The Meson development team
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
return NULL;
result = strcmp(message, "shoot") ? 0 : 1;
+#if PY_VERSION_HEX < 0x03000000
+ return PyInt_FromLong(result);
+#else
return PyLong_FromLong(result);
+#endif
}
static PyMethodDef TachyonMethods[] = {
{NULL, NULL, 0, NULL}
};
+#if PY_VERSION_HEX < 0x03000000
+PyMODINIT_FUNC inittachyon(void) {
+ Py_InitModule("tachyon", TachyonMethods);
+}
+#else
static struct PyModuleDef tachyonmodule = {
PyModuleDef_HEAD_INIT,
- MESON_MODULENAME,
+ "tachyon",
NULL,
-1,
TachyonMethods
PyMODINIT_FUNC PyInit_tachyon(void) {
return PyModule_Create(&tachyonmodule);
}
+#endif
install: true,
install_dir: get_option('libdir')
)
+py2.extension_module('tachyon',
+ '../tachyon_module.c',
+ c_args: '-DMESON_MODULENAME="tachyon"',
+ install: true,
+ install_dir: get_option('libdir')
+)
project('Python extension module', 'c',
- default_options : ['buildtype=release'])
+ default_options : ['buildtype=release', 'werror=true', 'python.bytecompile=-1'])
# Because Windows Python ships only with optimized libs,
# we must build this project the same way.
py_mod = import('python')
py = py_mod.find_installation()
+py2 = py_mod.find_installation('python2', required: get_option('python2'), disabler: true)
py_dep = py.dependency(required: false)
if not py_dep.found()
py.install_sources(blaster, pure: false)
py.install_sources(blaster, subdir: 'pure')
+install_subdir('subinst', install_dir: py.get_install_dir(pure: false))
+
+py2.install_sources(blaster, pure: false)
+py2.install_sources(blaster, subdir: 'pure')
+install_subdir('subinst', install_dir: py2.get_install_dir(pure: false))
+
py3_pkg_dep = dependency('python3', method: 'pkg-config', required : false)
if py3_pkg_dep.found()
--- /dev/null
+option('python2', type: 'feature', value: 'disabled')
--- /dev/null
+#!/usr/bin/env python3
+
+print('subinst')
--- /dev/null
+#!/usr/bin/env python3
+
+print('subinst.submod')
{ "type": "python_file", "file": "usr/@PYTHON_PLATLIB@/nested/blaster.py" },
{ "type": "python_lib", "file": "usr/@PYTHON_PLATLIB@/nested/tachyon" },
{ "type": "py_implib", "file": "usr/@PYTHON_PLATLIB@/nested/tachyon" },
+ { "type": "python_file", "file": "usr/@PYTHON_PLATLIB@/subinst/printer.py" },
+ { "type": "python_file", "file": "usr/@PYTHON_PLATLIB@/subinst/submod/printer.py" },
{ "type": "python_lib", "file": "usr/lib/tachyon" },
{ "type": "py_implib", "file": "usr/lib/tachyon" }
]
pyx_c = custom_target('storer_pyx',
output : 'storer_pyx.c',
input : 'storer.pyx',
- command : [cython, '@INPUT@', '-o', '@OUTPUT@'],
+ command : [cython, '@INPUT@', '-o', '@OUTPUT@', '-3'],
)
slib = py3.extension_module('storer',
'storer.c', pyx_c,
- dependencies : py3_dep)
+)
pydir = meson.current_build_dir()
project('cython', 'c',
- default_options : ['warning_level=3'])
-
+ default_options : ['warning_level=3', 'buildtype=release']
+)
if meson.backend() != 'ninja'
error('MESON_SKIP_TEST: Ninja backend required')
endif
error('MESON_SKIP_TEST: Cython3 not found.')
endif
-py_mod = import('python')
-py3 = py_mod.find_installation()
+py3 = import('python').find_installation(pure: false)
py3_dep = py3.dependency(required: false)
if not py3_dep.found()
error('MESON_SKIP_TEST: Python library not found.')
if list(filedir.glob('ext/*tachyon*')):
sys.path.insert(0, (filedir / 'ext').as_posix())
+if hasattr(os, 'add_dll_directory'):
+ os.add_dll_directory(filedir / 'ext' / 'lib')
+
import tachyon
parser = argparse.ArgumentParser()
project('python kwarg')
py = import('python')
-prog_python = py.find_installation('python3', modules : ['distutils'])
+prog_python = py.find_installation('python3', modules : ['os', 'sys', 're'])
assert(prog_python.found() == true, 'python not found when should be')
prog_python = py.find_installation('python3', modules : ['thisbetternotexistmod'], required : false)
assert(prog_python.found() == false, 'python not found but reported as found')
project('bar', 'cpp')
python = import('python').find_installation('python3')
-dependency('nonexistant-dependency')
+dependency('nonexistent-dependency')
project('install path',
default_options: [
+ 'python.bytecompile=-1',
'python.purelibdir=/pure',
- 'python.platlibdir=/plat'
+ 'python.platlibdir=/plat',
]
)
install_data('test.py', install_dir: py_plat.get_install_dir() / 'kw/data')
install_data('test.py', install_dir: py_plat.get_install_dir(pure: true) / 'kwrevert/data')
+if get_option('backend') == 'none'
+ subdir('target')
+endif
+
subdir('structured')
--- /dev/null
+testcase expect_error('Install-only backend cannot generate target rules, try using `--backend=ninja`.')
+ import('fs').copyfile('../test.py')
+endtestcase
--- /dev/null
+#include <Python.h>
+
+#ifndef Py_LIMITED_API
+#error Py_LIMITED_API must be defined.
+#elif Py_LIMITED_API != 0x03070000
+#error Wrong value for Py_LIMITED_API
+#endif
+
+static struct PyModuleDef limited_module = {
+ PyModuleDef_HEAD_INIT,
+ "limited_api_test",
+ NULL,
+ -1,
+ NULL
+};
+
+PyMODINIT_FUNC PyInit_limited(void) {
+ return PyModule_Create(&limited_module);
+}
--- /dev/null
+project('Python limited api', 'c',
+ default_options : ['buildtype=release', 'werror=true'])
+
+py_mod = import('python')
+py = py_mod.find_installation()
+
+ext_mod_limited = py.extension_module('limited',
+ 'limited.c',
+ limited_api: '3.7',
+ install: true,
+)
+
+ext_mod = py.extension_module('not_limited',
+ 'not_limited.c',
+ install: true,
+)
--- /dev/null
+#include <Python.h>
+#include <stdio.h>
+
+#ifdef Py_LIMITED_API
+#error Py_LIMITED_API must not be defined.
+#endif
+
+/* This function explicitly calls functions whose declaration is elided when
+ * Py_LIMITED_API is defined. This is to test that the linker is actually
+ * linking to the right version of the library on Windows. */
+static PyObject *meth_not_limited(PyObject *self, PyObject *args)
+{
+ PyObject *list;
+ Py_ssize_t size;
+
+ if (!PyArg_ParseTuple(args, "o", & list))
+ return NULL;
+
+ if (!PyList_Check(list)) {
+ PyErr_Format(PyExc_TypeError, "expected 'list'");
+ return NULL;
+ }
+
+ /* PyList_GET_SIZE and PyList_GET_ITEM are only available if Py_LIMITED_API
+ * is not defined. It seems likely that they will remain excluded from the
+ * limited API as their checked counterparts (PyList_GetSize and
+ * PyList_GetItem) are made available in that mode instead. */
+ size = PyList_GET_SIZE(list);
+ for(Py_ssize_t i = 0; i < size; ++i) {
+ PyObject *element = PyList_GET_ITEM(list, i);
+ if (element == NULL) {
+ return NULL;
+ }
+
+ if(PyObject_Print(element, stdout, Py_PRINT_RAW) == -1) {
+ return NULL;
+ }
+ }
+
+ Py_RETURN_NONE;
+}
+
+static struct PyMethodDef not_limited_methods[] = {
+ { "not_limited", meth_not_limited, METH_VARARGS,
+ "Calls functions whose declaration is elided by Py_LIMITED_API" },
+ { NULL, NULL, 0, NULL }
+};
+
+static struct PyModuleDef not_limited_module = {
+ PyModuleDef_HEAD_INIT,
+ "not_limited_api_test",
+ NULL,
+ -1,
+ not_limited_methods
+};
+
+PyMODINIT_FUNC PyInit_not_limited(void) {
+ return PyModule_Create(¬_limited_module);
+}
--- /dev/null
+{
+ "installed": [
+ {"type": "python_limited_lib", "file": "usr/@PYTHON_PLATLIB@/limited"},
+ {"type": "py_limited_implib", "file": "usr/@PYTHON_PLATLIB@/limited"},
+ {"type": "python_lib", "file": "usr/@PYTHON_PLATLIB@/not_limited"},
+ {"type": "py_implib", "file": "usr/@PYTHON_PLATLIB@/not_limited"}
+ ]
+}
if list(filedir.glob('ext/*tachyon.*')):
sys.path.insert(0, (filedir / 'ext').as_posix())
+if hasattr(os, 'add_dll_directory'):
+ os.add_dll_directory(filedir / 'ext' / 'lib')
+
import tachyon
parser = argparse.ArgumentParser()
-project('rustprog', 'rust')
+project('rustprog', 'rust', default_options : ['b_ndebug=true'])
e = executable('rust-program', 'prog.rs',
rust_args : ['-C', 'lto'], # Just a test
test('rusttest', e)
subdir('subdir')
+
+# this should fail due to debug_assert
+test(
+ 'debug_assert_on',
+ executable(
+ 'rust-program2',
+ 'prog.rs',
+ override_options : ['b_ndebug=false'],
+ ),
+ should_fail : true,
+)
fn main() {
let foo = "rust compiler is working";
+ debug_assert!(false, "debug_asserts on!");
println!("{}", foo);
}
external_dep = static_library(
'external_dep',
[external_dep_rs],
+ # for generated code, do not lint
+ rust_args: ['-A', 'warnings'],
dependencies : dep_zlib.partial_dependency(links : true),
)
error('MESON_SKIP_TEST bindgen not found')
endif
+cc_id = meson.get_compiler('c').get_id()
+compiler_specific_args = []
+if cc_id == 'gcc'
+ compiler_specific_args = ['-mtls-dialect=gnu2']
+elif cc_id == 'msvc'
+ compiler_specific_args = ['/fp:fast']
+endif
+
+add_project_arguments(['-DPROJECT_ARG', compiler_specific_args], language : 'c')
+add_global_arguments(['-DGLOBAL_ARG', compiler_specific_args], language : 'c')
+
# This seems to happen on windows when libclang.dll is not in path or is not
# valid. We must try to process a header file for this to work.
#
subdir('sub')
subdir('dependencies')
+
+gp = rust.bindgen(
+ input : 'src/global-project.h',
+ output : 'global-project.rs',
+)
+
+gp_lib = static_library('gp_lib', 'src/global.c')
+
+gp_exe = executable(
+ 'gp_exe',
+ structured_sources(['src/global.rs', gp]),
+ link_with : gp_lib,
+)
+
+test('global and project arguments', gp_exe)
--- /dev/null
+#ifndef GLOBAL_ARG
+char * success(void);
+#endif
+#ifndef PROJECT_ARG
+char * success(void);
+#endif
+#ifndef CMD_ARG
+char * success(void);
+#endif
+int success(void);
--- /dev/null
+#include "src/global-project.h"
+
+int success(void) {
+ return 0;
+}
--- /dev/null
+// SPDX-license-identifer: Apache-2.0
+// Copyright © 2023 Intel Corporation
+
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+
+include!("global-project.rs");
+
+fn main() {
+ unsafe {
+ std::process::exit(success());
+ };
+}
{
+ "env": {
+ "CFLAGS": "-DCMD_ARG"
+ },
"stdout": [
{
- "line": "test cases/rust/12 bindgen/meson.build:30: WARNING: Project targets '>= 0.63' but uses feature introduced in '1.0.0': include_directories kwarg of type string. Use include_directories('include') instead"
+ "line": "test cases/rust/12 bindgen/meson.build:38: WARNING: Project targets '>= 0.63' but uses feature introduced in '1.0.0': \"rust.bindgen\" keyword argument \"include_directories\" of type array[str]."
}
]
}
]
},
"exclude": [
- { "static": true, "method": "pkg-config" }
+ { "static": true, "method": "pkg-config" },
+ { "static": true, "method": "cmake" }
]
}
}
return a;
}
-// adder_add is implemented in the Rust file.
+// adder_add_r is implemented in the Rust file.
+int adder_add_r(adder *a, int number);
+
+int adder_add(adder *a, int number)
+{
+ return adder_add_r(a, number);
+}
void adder_destroy(adder *a) {
free(a);
pub number: i32
}
+extern "C" {
+ pub fn zero() -> i32;
+ pub fn zero_static() -> i32;
+}
+
#[no_mangle]
-pub extern fn adder_add(a: &Adder, number: i32) -> i32 {
- return a.number + number;
+pub extern fn adder_add_r(a: &Adder, number: i32) -> i32 {
+ unsafe {
+ return a.number + number + zero() + zero_static();
+ }
}
project('adder', 'c', 'rust', version: '1.0.0')
-if build_machine.system() != 'linux'
- error('MESON_SKIP_TEST, this test only works on Linux. Patches welcome.')
-endif
+subdir('zero')
-thread_dep = dependency('threads')
-dl_dep = meson.get_compiler('c').find_library('dl', required: false)
-m_dep = meson.get_compiler('c').find_library('m', required: false)
-
-rl = static_library('radder', 'adder.rs', rust_crate_type: 'staticlib')
+rl = shared_library('radder', 'adder.rs',
+ rust_abi: 'c',
+ link_with: [zero_shared, zero_static])
l = shared_library('adder', 'adder.c',
- c_args: '-DBUILDING_ADDER',
- link_with: rl,
- version: '1.0.0',
- soversion: '1',
- link_args: '-Wl,-u,adder_add', # Ensure that Rust code is not removed as unused.
- dependencies: [thread_dep, dl_dep, m_dep])
+ c_args: '-DBUILDING_ADDER',
+ link_with: rl,
+ version: '1.0.0',
+ soversion: '1',
+)
test('adder', executable('addertest', 'addertest.c', link_with: l))
--- /dev/null
+# They both have the same name, this tests we use +verbatim to distinguish them
+# using their filename. It also ensures we pass the importlib on Windows.
+# Those libs are in a subdir as regression test:
+# https://github.com/mesonbuild/meson/issues/12484
+zero_shared = shared_library('zero', 'zero.c')
+zero_static = static_library('zero', 'zero_static.c')
--- /dev/null
+#if defined _WIN32 || defined __CYGWIN__
+#define EXPORT __declspec(dllexport)
+#else
+#define EXPORT
+#endif
+
+EXPORT int zero(void);
+
+int zero(void) {
+ return 0;
+}
--- /dev/null
+int zero_static(void);
+
+int zero_static(void)
+{
+ return 0;
+}
test_prog = find_program('test.py')
static = static_library('static', 'lib.c', c_args : '-DMODE="static"')
-exe = executable('static', 'main.rs', link_with : static)
+# Add some -I compiler arguments to make sure they're not passed to the Rust
+# compiler when handling the dependency.
+static_dep = declare_dependency(link_with : static, compile_args : ['-Idoesnotexist'])
+exe = executable('static', 'main.rs', dependencies : static_dep)
test('static linkage', test_prog, args : [exe, 'This is a static C library'])
# Shared linkage with rust doesn't work on macOS with meson, yet
if host_machine.system() != 'darwin'
shared = shared_library('shared', 'lib.c', c_args : '-DMODE="shared"')
- exe = executable('shared', 'main.rs', link_with : shared)
+ shared_dep = declare_dependency(link_with : shared, compile_args : ['-Idoesnotexist'])
+ exe = executable('shared', 'main.rs', dependencies : shared_dep)
test('shared linkage', test_prog, args : [exe, 'This is a shared C library'])
endif
#[no_mangle]
pub extern "C" fn what_have_we_here() -> i32 {
- leaf::HOW_MANY * leaf::HOW_MANY
+ myleaf::HOW_MANY * myleaf::HOW_MANY
}
leaf = static_library('leaf', 'leaf.rs', rust_crate_type : 'rlib')
# Even though leaf is linked using link_with, this gets implicitly promoted to link_whole because
# it is an internal Rust project.
-branch = static_library('branch', 'branch.rs', link_with: leaf, rust_crate_type : 'staticlib', install : true)
+branch = static_library('branch', 'branch.rs', link_with: leaf, rust_crate_type : 'staticlib', rust_dependency_map : { 'leaf' : 'myleaf' }, install : true)
e = executable('prog', 'prog.c', link_with : branch, install : true)
test('linktest', e)
main = executable(
'main',
'use.rs',
- link_with : pm
+ link_with : pm,
+ rust_args : ['-C', 'panic=abort'],
)
test('main_test', main)
+
+rust = import('rust')
+
+pm = rust.proc_macro('proc_macro_examples2', 'proc.rs')
+
+main = executable(
+ 'main2',
+ 'use.rs',
+ link_with : pm,
+ rust_dependency_map : {'proc_macro_examples2' : 'proc_macro_examples'}
+)
+
+test('main_test2', main)
),
)
-# Should not be coppied
+# Should not be copied
executable(
'no_copy_target',
structured_sources(
-project('rust shared library', 'rust')
+project('rust shared library', 'rust', 'c')
if host_machine.system() == 'darwin'
error('MESON_SKIP_TEST: does not work right on macos, please fix!')
endif
-l = shared_library('stuff', 'stuff.rs', install : true)
+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)
if build_machine.system() == 'windows'
#![crate_name = "stuff"]
-pub fn explore() -> &'static str { "librarystring" }
+extern "C" {
+ fn c_value() -> i32;
+}
+
+pub fn explore() -> String {
+ unsafe {
+ format!("library{}string", c_value())
+ }
+}
--- /dev/null
+int c_value(void) {
+ return 7;
+}
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0
+// Copyright © 2023 Intel Corporation
+
+#include "lib.hpp"
+
+#include <string>
+
+namespace {
+
+uint64_t priv_length(const std::string & str) {
+ return str.length();
+}
+
+}
+
+extern "C" uint64_t lib_length(const char * str) {
+ return priv_length(str);
+}
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0
+// Copyright © 2023 Intel Corporation
+
+#include <cstddef>
+#include <cstdint>
+
+extern "C" uint64_t lib_length(const char * str);
+
--- /dev/null
+// SPDX-License-Identifier: Apache-2.0
+// Copyright © 2023 Intel Corporation
+
+use std::ffi::CString;
+use std::os::raw::c_char;
+
+extern "C" {
+ fn lib_length(s: *const c_char) -> u64;
+}
+
+fn main() {
+ let len: u64;
+ unsafe {
+ let c_str = CString::new("Hello, world!").unwrap();
+ len = lib_length(c_str.as_ptr());
+ }
+
+ std::process::exit(if len == 13 { 0 } else { 1 })
+}
--- /dev/null
+# SPDX-License-Identifier: Apache-2.0
+# Copyright © 2023 Intel Corporation
+
+project(
+ 'Rust and C++',
+ 'rust', 'cpp',
+ default_options : ['cpp_std=c++14'],
+ meson_version : '>= 1.2.0',
+)
+
+cpplib = static_library('cpp', 'lib.cpp')
+exe = executable('main', 'main.rs', link_with : cpplib)
+
+test('main', exe)
--- /dev/null
+{
+ "matrix": {
+ "options": {
+ "b_vscrt": [
+ { "val": "none" },
+ { "val": "mdd" },
+ { "val": "md" },
+ { "val": "mtd" },
+ { "val": "mt" },
+ { "val": "from_buildtype" },
+ { "val": "static_from_buildtype" }
+ ]
+ }
+ }
+}
--- /dev/null
+extern "C" {
+ fn static2() -> i32;
+}
+
+fn main() {
+ unsafe {
+ static2();
+ }
+}
--- /dev/null
+int c_func(void);
+int c_func(void) {
+ return 123;
+}
--- /dev/null
+int r3(void);
+
+int main_func(void) {
+ return r3() == 246 ? 0 : 1;
+}
--- /dev/null
+# Regression test for a diamond dependency graph:
+# ┌►R1┐
+# main-►R3─┤ ├─►C1
+# └►R2┘
+# Both libr1.rlib and libr2.rlib used to contain func.c.o. That was causing
+# libr3.rlib to have duplicated func.c.o and then libmain.so failed to link:
+# multiple definition of `c_func'.
+
+libc1 = static_library('c1', 'func.c')
+libr1 = static_library('r1', 'r1.rs', link_with: libc1)
+libr2 = static_library('r2', 'r2.rs', link_with: libc1)
+libr3 = static_library('r3', 'r3.rs',
+ link_with: [libr1, libr2],
+ rust_abi: 'c',
+)
+shared_library('main', 'main.c', link_whole: [libr3])
+
+# Same dependency graph, but r3 is now installed. Since c1, r1 and r2 are
+# not installed, r3 must contain them.
+libr3 = static_library('r3-installed', 'r3.rs',
+ link_with: [libr1, libr2],
+ rust_abi: 'c',
+ install: true,
+)
+shared_library('main-installed', 'main.c', link_with: [libr3])
--- /dev/null
+extern "C" {
+ fn c_func() -> i32;
+}
+
+pub fn r1() -> i32 {
+ unsafe {
+ c_func()
+ }
+}
--- /dev/null
+extern "C" {
+ fn c_func() -> i32;
+}
+
+pub fn r2() -> i32 {
+ unsafe {
+ c_func()
+ }
+}
--- /dev/null
+#[no_mangle]
+pub fn r3() -> i32 {
+ r1::r1() + r2::r2()
+}
--- /dev/null
+#include <stdint.h>
+
+uint32_t foo_rs(void);
+
+int main(void)
+{
+ return foo_rs() == 42 ? 0 : 1;
+}
--- /dev/null
+extern crate pm;
+use pm::make_answer;
+
+make_answer!();
+
+#[no_mangle]
+pub fn foo_rs() -> u32 {
+ answer()
+}
--- /dev/null
+pub fn foo() -> i32 {
+ 123
+}
--- /dev/null
+liba = static_library('liba', 'lib.rs',
+ rust_crate_type : 'rlib',
+)
+
+liba_dep = declare_dependency(link_with : liba)
--- /dev/null
+pub fn bar() -> i32 {
+ 2 * liba::foo()
+}
--- /dev/null
+libb = static_library('libb', 'lib.rs',
+ rust_crate_type : 'rlib',
+ dependencies : [liba_dep],
+)
+
+libb_dep = declare_dependency(link_with : libb)
--- /dev/null
+fn main() {
+ println!("{}", libb::bar());
+}
--- /dev/null
+project('transitive dependencies', 'rust', 'c',
+ version : '1.0.0',
+ meson_version : '>= 1.0.0',
+ default_options : ['rust_std=2018'],
+)
+
+subdir('liba')
+subdir('libb')
+
+main = executable('main', 'main.rs',
+ dependencies : [libb_dep],
+)
+
+# Since foo-rs is a static library, its dependencies are normally added to
+# footest link command. However, since pm is a proc-macro, footest should not
+# link with it. In native build this is an harmless overlinking, but in cross
+# building foo and pm are for different arch and it would fail to link.
+rust = import('rust')
+pm = rust.proc_macro('pm', 'proc.rs')
+foo = static_library('foo-rs', 'foo.rs',
+ rust_abi: 'c',
+ link_with: pm,
+)
+exe = executable('footest', 'foo.c',
+ link_with: foo,
+)
+test('footest', exe)
+
+subdir('diamond')
+
+# The ninja rule for libstatic2.a does not depend on libstatic1.a because it
+# only need static2.c.o to create the archive. That means that the ninja rule
+# for app must depend on both, otherwise libstatic1.a won't be built and linking
+# will fail.
+static1 = static_library('static1', 'static1.c', build_by_default: false)
+static2 = static_library('static2', 'static2.c', link_with: static1)
+exe = executable('app', 'app.rs', link_with: static2)
--- /dev/null
+extern crate proc_macro;
+use proc_macro::TokenStream;
+
+#[proc_macro]
+pub fn make_answer(_item: TokenStream) -> TokenStream {
+ "fn answer() -> u32 { 42 }".parse().unwrap()
+}
--- /dev/null
+int static1(void);
+
+int static1(void){
+ return 1;
+}
--- /dev/null
+int static1(void);
+int static2(void);
+
+int static2(void)
+{
+ return 1 + static1();
+}
--- /dev/null
+{
+ "installed": [
+ {"type": "file", "file": "usr/lib/libr3-installed.a"}
+ ]
+}
--- /dev/null
+int rust_func(void);
+
+int main(int argc, char *argv[]) {
+ return rust_func();
+}
--- /dev/null
+project('cargo subproject', 'c')
+
+foo_dep = dependency('foo-rs')
+exe = executable('app', 'main.c',
+ dependencies: foo_dep,
+)
+test('cargo-test', exe)
--- /dev/null
+[wrap-file]
+method = cargo
--- /dev/null
+[package]
+name = "bar"
+version = "0.1"
--- /dev/null
+pub const VALUE: i32 = 0;
--- /dev/null
+[wrap-file]
+method = cargo
--- /dev/null
+[package]
+name = "foo"
+version = "0.0.1"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib"]
+
+[dependencies]
+mybar = { version = "0.1", package = "bar" }
--- /dev/null
+#[no_mangle]
+pub extern "C" fn rust_func() -> i32 {
+ mybar::VALUE
+}
-project('rust static library', 'rust')
+project('rust static library', 'rust', 'c')
-l = static_library('stuff', 'stuff.rs', install : true)
+o = static_library('other', 'other.rs')
+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)
--- /dev/null
+pub fn explore(
+ value: i32,
+) -> String {
+ format!("library{}string", value)
+}
extern crate stuff;
-fn main() { println!("printing: {}", stuff::explore()); }
+fn main() {
+ println!("printing: {}", stuff::explore());
+}
#![crate_name = "stuff"]
-pub fn explore() -> &'static str { "librarystring" }
+extern crate other;
+
+extern "C" {
+ fn c_explore_value() -> i32;
+}
+
+pub fn explore(
+) -> String {
+ unsafe {
+ other::explore(c_explore_value())
+ }
+}
--- /dev/null
+int
+c_explore_value (void)
+{
+ return 42;
+}
error('MESON_SKIP_TEST: does not work right on macos, please fix!')
endif
-l = shared_library('stuff', 'stuff.rs', rust_crate_type: 'cdylib', install : true)
-e = executable('prog', 'prog.c', link_with : l, install : true)
-test('polyglottest', e)
+cc = meson.get_compiler('c')
+
+# Test all combinations of crate and target types.
+# - 'clib' gets translated to `rust_abi: 'c'` instead.
+# - '' gets translated to no kwargs.
+allowed_map = {
+ 'static_library': ['rlib', 'staticlib', 'lib', 'clib', ''],
+ 'shared_library': ['dylib', 'cdylib', 'lib', 'proc-macro', 'clib', ''],
+ 'both_libraries': ['lib', 'clib', ''],
+}
+foreach crate_type : ['lib', 'rlib', 'dylib', 'cdylib', 'staticlib', 'proc-macro', 'clib', '', 'invalid']
+ foreach target_type, allowed : allowed_map
+ name = f'stuff_@crate_type@_@target_type@'.underscorify()
+ src = crate_type == 'proc-macro' ? 'proc.rs' : 'stuff.rs'
+ if crate_type not in allowed
+ # 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')
+ build_target(name, src,
+ target_type: target_type,
+ rust_crate_type: crate_type,
+ install: true)
+ endtestcase
+ continue
+ endif
+ rust_kwargs = {}
+ if crate_type == 'clib'
+ rust_kwargs = {'rust_abi': 'c'}
+ elif crate_type != ''
+ rust_kwargs = {'rust_crate_type': crate_type}
+ endif
+ l = build_target(name, src,
+ target_type: target_type,
+ kwargs: rust_kwargs,
+ install: true)
+ if crate_type in ['cdylib', 'staticlib', 'clib']
+ e = executable(f'prog-@name@', 'prog.c',
+ link_with: l,
+ rust_dependency_map: {name: 'stuff'},
+ install: true)
+ test(f'polyglottest-@name@', e)
+ else
+ testcase expect_error('Try to link Rust ABI library .*', how: 're')
+ executable(f'prog-@name@', 'prog.c', link_with: l)
+ endtestcase
+ endif
+ endforeach
+endforeach
--- /dev/null
+extern crate proc_macro;
+use proc_macro::TokenStream;
+
+#[proc_macro]
+pub fn make_answer(_item: TokenStream) -> TokenStream {
+ "fn answer() -> u32 { 42 }".parse().unwrap()
+}
-#![crate_name = "stuff"]
-
#[no_mangle]
pub extern fn f() {
println!("Hello from Rust!");
{
"installed": [
- {"type": "exe", "file": "usr/bin/prog"},
- {"type": "pdb", "file": "usr/bin/prog"},
- {"type": "file", "platform": "gcc", "file": "usr/lib/libstuff.so"},
- {"type": "file", "platform": "msvc", "file": "usr/bin/stuff.dll"},
- {"type": "file", "platform": "msvc", "file": "usr/lib/stuff.dll.lib"},
- {"type": "pdb", "file": "usr/bin/stuff"}
+ {"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": "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"},
+
+ {"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": "file", "platform": "msvc", "file": "usr/bin/stuff_clib_shared_library.dll"},
+ {"type": "file", "platform": "msvc", "file": "usr/lib/stuff_clib_shared_library.dll.lib"},
+
+ {"type": "exe", "file": "usr/bin/prog-stuff_clib_static_library"},
+ {"type": "pdb", "file": "usr/bin/prog-stuff_clib_static_library"},
+ {"type": "file", "file": "usr/lib/libstuff_clib_static_library.a"},
+
+ {"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": "file", "platform": "msvc", "file": "usr/bin/stuff_cdylib_shared_library.dll"},
+ {"type": "file", "platform": "msvc", "file": "usr/lib/stuff_cdylib_shared_library.dll.lib"},
+
+ {"type": "exe", "file": "usr/bin/prog-stuff_staticlib_static_library"},
+ {"type": "pdb", "file": "usr/bin/prog-stuff_staticlib_static_library"},
+ {"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": "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"},
+ {"type": "file", "platform": "msvc", "file": "usr/lib/stuff__both_libraries.dll.lib"},
+
+ {"type": "file", "platform": "gcc", "file": "usr/lib/libstuff__static_library.rlib"},
+ {"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": "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": "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"},
+
+ {"type": "file", "platform": "gcc", "file": "usr/lib/libstuff_rlib_static_library.rlib"},
+ {"type": "file", "platform": "msvc", "file": "usr/lib/libstuff_rlib_static_library.rlib"},
+
+ {"type": "file", "platform": "gcc", "file": "usr/lib/libstuff_lib_static_library.rlib"},
+ {"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": "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": "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": "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": "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": "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": "file", "platform": "msvc", "file": "usr/bin/stuff_dylib_both_libraries.dll"},
+ {"type": "file", "platform": "msvc", "file": "usr/lib/stuff_dylib_both_libraries.dll.lib"}
]
}
--- /dev/null
+#include <stdio.h>
+#include <stdint.h>
+
+int32_t hello_from_rust(const int32_t a, const int32_t b);
+
+static void hello_from_c(void) {
+ printf("Hello from C!\n");
+}
+
+void hello_from_both(void) {
+ hello_from_c();
+ if (hello_from_rust(2, 3) == 5)
+ printf("Hello from Rust!\n");
+}
project('static rust and c polyglot executable', 'c', 'rust')
-deps = [
- meson.get_compiler('c').find_library('dl', required: false),
- meson.get_compiler('c').find_library('m', required: false),
- dependency('threads'),
-]
+r = static_library('stuff', 'stuff.rs', rust_crate_type : 'staticlib')
-extra_winlibs = meson.get_compiler('c').get_id() in ['msvc', 'clang-cl'] ? ['userenv.lib', 'ws2_32.lib', 'bcrypt.lib'] : []
+# clib is installed static library and stuff is not installed. That means that
+# to be usable clib must link_whole stuff. Meson automatically promote to link_whole,
+# as it would do with C libraries, but then cannot extract objects from stuff and
+# thus should error out.
+# FIXME: We should support this use-case in the future.
+testcase expect_error('Cannot link_whole a custom or Rust target \'stuff\' into a static library \'clib\'. Instead, pass individual object files with the "objects:" keyword argument if possible. Meson had to promote link to link_whole because \'clib\' is installed but not \'stuff\', and thus has to include objects from \'stuff\' to be usable.')
+ l = static_library('clib', 'clib.c', link_with : r, install : true)
+endtestcase
+
+l = static_library('clib', 'clib.c', link_with : r)
-l = static_library('stuff', 'stuff.rs', rust_crate_type : 'staticlib', install : true)
e = executable('prog', 'prog.c',
- dependencies: deps,
link_with : l,
- link_args: extra_winlibs,
install : true)
test('polyglottest', e)
+
+# Create a version that has overflow-checks on, then run a test to ensure that
+# the overflow-checks is larger than the other version by some ammount
+r2 = static_library('stuff2', 'stuff.rs', rust_crate_type : 'staticlib', rust_args : ['-C', 'overflow-checks=on'])
+l2 = static_library('clib2', 'clib.c')
+e2 = executable('prog2', 'prog.c', link_with : [r2, l2])
+test('overflow-checks', find_program('overflow_size_checks.py'), args : [e, e2])
--- /dev/null
+#!/usr/bin/env python3
+# SPDX-License-Identifier: Apache-2.0
+# Copyright © 2023 Intel Corporation
+
+from __future__ import annotations
+import argparse
+import os
+import typing as T
+
+if T.TYPE_CHECKING:
+ class Arguments(T.Protocol):
+ checks_off: str
+ checks_on: str
+
+
+def main() -> None:
+ parser = argparse.ArgumentParser()
+ parser.add_argument('checks_off')
+ parser.add_argument('checks_on')
+ args: Arguments = parser.parse_args()
+
+ off = os.stat(args.checks_off).st_size
+ on = os.stat(args.checks_on).st_size
+
+ assert on > off, f'Expected binary built with overflow-checks to be bigger, but it was smaller. with: "{on}"B, without: "{off}"B'
+
+
+if __name__ == "__main__":
+ main()
#include <stdio.h>
-void f();
+void hello_from_both();
int main(void) {
- printf("Hello from C!\n");
- f();
+ hello_from_both();
}
-#![crate_name = "stuff"]
-
#[no_mangle]
-pub extern fn f() {
- println!("Hello from Rust!");
+pub extern "C" fn hello_from_rust(a: i32, b: i32) -> i32 {
+ a + b
}
{
"installed": [
{"type": "exe", "file": "usr/bin/prog"},
- {"type": "pdb", "file": "usr/bin/prog"},
- {"type": "file", "file": "usr/lib/libstuff.a"}
+ {"type": "pdb", "file": "usr/bin/prog"}
]
}
--- /dev/null
+pub fn subtract(a: i32, b: i32) -> i32 {
+ a - b
+}
suite : ['foo'],
)
-exe = executable('rust_exe', ['test2.rs', 'test.rs'])
+exe = executable('rust_exe', ['test2.rs', 'test.rs'], build_by_default : false)
rust = import('unstable-rust')
rust.test('rust_test_from_exe', exe, should_fail : true)
-lib = static_library('rust_static', ['test.rs'])
+lib = static_library('rust_static', ['test.rs'], build_by_default : false, rust_crate_type : 'lib')
rust.test('rust_test_from_static', lib, args: ['--skip', 'test_add_intentional_fail'])
-lib = shared_library('rust_shared', ['test.rs'])
+lib = shared_library('rust_shared', ['test.rs'], build_by_default : false)
rust.test('rust_test_from_shared', lib, args: ['--skip', 'test_add_intentional_fail'])
+
+helper = static_library('helper', 'helper.rs')
+lib = static_library('rust_link_with', 'test3.rs', build_by_default : false)
+rust.test('rust_test_link_with', lib, link_with : helper, rust_args : ['--cfg', 'broken="false"'])
fn main() {
let args: Vec<String> = env::args().collect();
- let first = args[1].parse::<i32>().expect("Invliad value for first argument.");
- let second = args[2].parse::<i32>().expect("Invliad value for second argument.");
+ let first = args[1].parse::<i32>().expect("Invalid value for first argument.");
+ let second = args[2].parse::<i32>().expect("Invalid value for second argument.");
let new = test::add(first, second);
println!("New value: {}", new);
--- /dev/null
+pub fn add(a: i32, b: i32) -> i32 {
+ a + b
+}
+
+#[cfg(test)]
+mod tests {
+ extern crate helper;
+
+ use super::*;
+
+ // This is an intentinally broken test that should be turned off by extra rust arguments
+ #[cfg(not(broken = "false"))]
+ #[test]
+ fn test_broken() {
+ assert_eq!(0, 5);
+ }
+
+ #[test]
+ fn test_add_sub() {
+ let x = helper::subtract(6, 5);
+ assert_eq!(add(x, 5), 6);
+ }
+}
project('custom target name', 'c')
-python = import('python').find_installation()
+python = find_program('python3')
# Name argument is optional and should default to 'file.txt'
custom_target(
+++ /dev/null
-option(
- 'opt',
- type : 'string',
- value: '',
-)
--- /dev/null
+option('string', type : 'string', value: '')
+option('boolean', type : 'boolean', value: false)
+option('combo', type : 'combo', choices : ['one', 'two', 'three'], value: 'one')
+option('integer', type : 'integer', value: 5)
+option('array', type : 'array', value: ['one', 'two'])
+option('feature', type : 'feature', value : 'enabled')
--- /dev/null
+project('replace unencodable xml chars')
+
+test_script = find_program('script.py')
+test('main', test_script, verbose: true)
--- /dev/null
+#!/usr/bin/env python3
+
+import sys
+
+# Print base string(\nHello Meson\n) to see valid chars are not replaced
+print('\n\x48\x65\x6c\x6c\x6f\x20\x4d\x65\x73\x6f\x6e\n')
+# Print invalid input from all known unencodable chars
+print(
+ '\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0e\x0f\x10\x11'
+ '\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f')
+
+# Cover for potential encoding issues
+try:
+ print(
+ '\x80\x81\x82\x83\x84\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f'
+ '\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e'
+ '\x9f\ufdd0\ufdd1\ufdd2\ufdd3\ufdd4\ufdd5\ufdd6\ufdd7\ufdd8'
+ '\ufdd9\ufdda\ufddb\ufddc\ufddd\ufdde\ufddf\ufde0\ufde1'
+ '\ufde2\ufde3\ufde4\ufde5\ufde6\ufde7\ufde8\ufde9\ufdea'
+ '\ufdeb\ufdec\ufded\ufdee\ufdef\ufffe\uffff')
+except:
+ pass
+
+# Cover for potential encoding issues
+try:
+ if sys.maxunicode >= 0x10000:
+ print(
+ '\U0001fffe\U0001ffff\U0002fffe\U0002ffff'
+ '\U0003fffe\U0003ffff\U0004fffe\U0004ffff'
+ '\U0005fffe\U0005ffff\U0006fffe\U0006ffff'
+ '\U0007fffe\U0007ffff\U0008fffe\U0008ffff'
+ '\U0009fffe\U0009ffff\U000afffe\U000affff'
+ '\U000bfffe\U000bffff\U000cfffe\U000cffff'
+ '\U000dfffe\U000dffff\U000efffe\U000effff'
+ '\U000ffffe\U000fffff\U0010fffe\U0010ffff')
+except:
+ pass
--- /dev/null
+project('feature user option', 'c')
+
+feature_opts = get_option('auto_features')
+optional_opt = get_option('optional')
+
+message('Build options:', meson.build_options())
--- /dev/null
+option('optional', type : 'feature', value : 'auto', description : 'An optional feature')
--- /dev/null
+int s3(void);
+
+int main(int argc, char *argv[])
+{
+ return s3();
+}
--- /dev/null
+project('complex link cases', 'c')
+
+cc = meson.get_compiler('c')
+
+# In all tests, e1 uses s3 which uses s2 which uses s1.
+
+# Executable links with s3 and s1 but not s2 because it is included in s3.
+s1 = static_library('t1-s1', 's1.c')
+s2 = static_library('t1-s2', 's2.c', link_with: s1)
+s3 = static_library('t1-s3', 's3.c', link_whole: s2)
+e = executable('t1-e1', 'main.c', link_with: s3)
+
+# s3 is installed but not s1 so it has to include s1 too.
+# Executable links only s3 because it contains s1 and s2.
+s1 = static_library('t2-s1', 's1.c')
+s2 = static_library('t2-s2', 's2.c', link_with: s1)
+s3 = static_library('t2-s3', 's3.c', link_whole: s2, install: true)
+e = executable('t2-e1', 'main.c', link_with: s3)
+
+# Executable needs to link with s3 only
+s1 = static_library('t3-s1', 's1.c')
+s2 = static_library('t3-s2', 's2.c', link_with: s1)
+s3 = shared_library('t3-s3', 's3.c', link_with: s2)
+e = executable('t3-e1', 'main.c', link_with: s3)
+
+# Executable needs to link with s3 and s2
+s1 = static_library('t4-s1', 's1.c')
+s2 = shared_library('t4-s2', 's2.c', link_with: s1)
+s3 = static_library('t4-s3', 's3.c', link_with: s2)
+e = executable('t4-e1', 'main.c', link_with: s3)
+
+# Executable needs to link with s3 and s1
+s1 = shared_library('t5-s1', 's1.c')
+s2 = static_library('t5-s2', 's2.c', link_with: s1)
+s3 = static_library('t5-s3', 's3.c', link_with: s2, install: true)
+e = executable('t5-e1', 'main.c', link_with: s3)
+
+# Executable needs to link with s3 and s2
+s1 = static_library('t6-s1', 's1.c')
+s2 = static_library('t6-s2', 's2.c', link_with: s1, install: true)
+s3 = static_library('t6-s3', 's3.c', link_with: s2, install: true)
+e = executable('t6-e1', 'main.c', link_with: s3)
+
+# Regression test: s1 gets promoted to link_whole and that used to make all other
+# libraries in the list (s2) to be ignored.
+# Executable only needs to link with s3.
+# See https://github.com/mesonbuild/meson/issues/11956.
+s1 = static_library('t7-s1', 's1.c')
+s2 = static_library('t7-s2', 's2.c')
+s3 = static_library('t7-s3', 's3.c', link_with: [s1, s2], install: true)
+e = executable('t7-e1', 'main.c', link_with: s3)
+
+# Regression test: s3 should come last in the linker command. This seems to be
+# required for at least backward compatibility reasons:
+# https://github.com/mesonbuild/meson/pull/11957#issuecomment-1629243208
+s1 = static_library('t8-s1', 's1.c')
+s2 = static_library('t8-s2', 's2.c')
+s3 = static_library('t8-s3', 's3.c')
+e = executable('t8-e1', 'main.c',
+ link_with: [s1, s2],
+ dependencies: declare_dependency(link_with: s3),
+)
+
+if cc.get_argument_syntax() == 'gcc'
+ # s1 is an internal static library, using custom target.
+ s1_o = custom_target(
+ input: 's1.c',
+ output: 's1.c.o',
+ command: [cc.cmd_array(), '-c', '-o', '@OUTPUT@', '@INPUT@']
+ )
+ s1 = custom_target(
+ output: 'libt9-s1.a',
+ command: ['ar', 'rcs', '@OUTPUT@', s1_o],
+ )
+
+ # Executable needs to link with s1, s2 and s3.
+ s2 = static_library('t9-s2', 's2.c', link_with: s1)
+ s3 = static_library('t9-s3', 's3.c', link_with: s2)
+ e = executable('t9-e1', 'main.c', link_with: s3)
+
+ # s2 cannot be installed because s1 is not being installed and Meson cannot
+ # extract object files from the custom target.
+ testcase expect_error('Cannot link_whole a custom or Rust target \'libt9-s1.a\' into a static library \'t10-s2\'. Instead, pass individual object files with the "objects:" keyword argument if possible. Meson had to promote link to link_whole because \'t10-s2\' is installed but not \'libt9-s1.a\', and thus has to include objects from \'libt9-s1.a\' to be usable.')
+ s2 = static_library('t10-s2', 's2.c', link_with: s1, install: true)
+ endtestcase
+
+ # s3 cannot be installed because s1 is not being installed and Meson cannot
+ # extract object files from the custom target.
+ testcase expect_error('Cannot link_whole a custom or Rust target \'libt9-s1.a\' into a static library \'t11-s3\'. Instead, pass individual object files with the "objects:" keyword argument if possible. Meson had to promote link to link_whole because \'t11-s3\' is installed but not \'libt9-s1.a\', and thus has to include objects from \'libt9-s1.a\' to be usable.')
+ s2 = static_library('t11-s2', 's2.c', link_with: s1)
+ s3 = static_library('t11-s3', 's3.c', link_with: s2, install: true)
+ endtestcase
+
+ # s1 is an installed static library, using custom target.
+ s1 = custom_target(
+ output: 'libt12-s1.a',
+ command: ['ar', 'rcs', '@OUTPUT@', s1_o],
+ install: true,
+ install_dir: get_option('libdir'),
+ )
+
+ # Executable needs to link with s1, s2 and s3.
+ s2 = static_library('t12-s2', 's2.c', link_with: s1, install: true)
+ s3 = static_library('t12-s3', 's3.c', link_with: s2)
+ e = executable('t12-e1', 'main.c', link_with: s3)
+
+ # Executable links with s3 and s1 but not s2 because it is included in s3.
+ s2 = static_library('t13-s2', 's2.c', link_with: s1)
+ s3 = static_library('t13-s3', 's3.c', link_with: s2, install: true)
+ e = executable('t13-e1', 'main.c', link_with: s3)
+endif
--- /dev/null
+int s1(void) {
+ return 1;
+}
--- /dev/null
+int s1(void);
+
+int s2(void) {
+ return s1() + 1;
+}
--- /dev/null
+int s2(void);
+
+int s3(void) {
+ return s2() + 1;
+}
--- /dev/null
+project('c cpp stds', 'c', 'cpp',
+ default_options: [
+ 'c_std=gnu89,c89',
+ 'cpp_std=gnu++98,vc++11',
+ ],
+)
--- /dev/null
+{
+ "stdlib": {
+ "modules": [
+ "__future__",
+ "__main__",
+ "_abc",
+ "_ast",
+ "_bisect",
+ "_blake2",
+ "_bz2",
+ "_codecs",
+ "_collections",
+ "_collections_abc",
+ "_compat_pickle",
+ "_compression",
+ "_datetime",
+ "_frozen_importlib",
+ "_frozen_importlib_external",
+ "_functools",
+ "_hashlib",
+ "_imp",
+ "_io",
+ "_json",
+ "_locale",
+ "_lsprof",
+ "_lzma",
+ "_opcode",
+ "_operator",
+ "_pickle",
+ "_posixsubprocess",
+ "_random",
+ "_sha512",
+ "_signal",
+ "_sitebuiltins",
+ "_socket",
+ "_sre",
+ "_ssl",
+ "_stat",
+ "_string",
+ "_struct",
+ "_thread",
+ "_uuid",
+ "_warnings",
+ "_weakref",
+ "_weakrefset",
+ "abc",
+ "argparse",
+ "array",
+ "ast",
+ "base64",
+ "binascii",
+ "bisect",
+ "builtins",
+ "bz2",
+ "cProfile",
+ "calendar",
+ "codecs",
+ "collections",
+ "collections.abc",
+ "configparser",
+ "contextlib",
+ "copy",
+ "copyreg",
+ "dataclasses",
+ "datetime",
+ "dis",
+ "email",
+ "email._encoded_words",
+ "email._parseaddr",
+ "email._policybase",
+ "email.base64mime",
+ "email.charset",
+ "email.encoders",
+ "email.errors",
+ "email.feedparser",
+ "email.header",
+ "email.iterators",
+ "email.message",
+ "email.parser",
+ "email.quoprimime",
+ "email.utils",
+ "encodings",
+ "encodings.aliases",
+ "encodings.utf_8",
+ "enum",
+ "errno",
+ "fcntl",
+ "fnmatch",
+ "functools",
+ "genericpath",
+ "gettext",
+ "glob",
+ "hashlib",
+ "http",
+ "http.client",
+ "importlib",
+ "importlib._bootstrap",
+ "importlib._bootstrap_external",
+ "importlib.machinery",
+ "inspect",
+ "io",
+ "itertools",
+ "json",
+ "json.decoder",
+ "json.encoder",
+ "json.scanner",
+ "keyword",
+ "linecache",
+ "locale",
+ "lzma",
+ "marshal",
+ "math",
+ "netrc",
+ "ntpath",
+ "opcode",
+ "operator",
+ "os",
+ "os.path",
+ "pathlib",
+ "pickle",
+ "platform",
+ "posix",
+ "posixpath",
+ "profile",
+ "quopri",
+ "random",
+ "re",
+ "reprlib",
+ "select",
+ "selectors",
+ "shlex",
+ "shutil",
+ "signal",
+ "site",
+ "socket",
+ "sre_compile",
+ "sre_constants",
+ "sre_parse",
+ "ssl",
+ "stat",
+ "string",
+ "struct",
+ "subprocess",
+ "sys",
+ "tempfile",
+ "textwrap",
+ "threading",
+ "time",
+ "token",
+ "tokenize",
+ "types",
+ "typing",
+ "typing.io",
+ "typing.re",
+ "urllib",
+ "urllib.error",
+ "urllib.parse",
+ "urllib.request",
+ "urllib.response",
+ "uu",
+ "uuid",
+ "warnings",
+ "weakref",
+ "zipimport",
+ "zlib"
+ ],
+ "count": 162
+ },
+ "meson": {
+ "modules": [
+ "mesonbuild",
+ "mesonbuild._pathlib",
+ "mesonbuild.arglist",
+ "mesonbuild.ast",
+ "mesonbuild.ast.interpreter",
+ "mesonbuild.ast.introspection",
+ "mesonbuild.ast.postprocess",
+ "mesonbuild.ast.printer",
+ "mesonbuild.ast.visitor",
+ "mesonbuild.backend",
+ "mesonbuild.backend.backends",
+ "mesonbuild.backend.ninjabackend",
+ "mesonbuild.build",
+ "mesonbuild.compilers",
+ "mesonbuild.compilers.compilers",
+ "mesonbuild.compilers.detect",
+ "mesonbuild.coredata",
+ "mesonbuild.dependencies",
+ "mesonbuild.dependencies.base",
+ "mesonbuild.dependencies.detect",
+ "mesonbuild.depfile",
+ "mesonbuild.envconfig",
+ "mesonbuild.environment",
+ "mesonbuild.interpreter",
+ "mesonbuild.interpreter.compiler",
+ "mesonbuild.interpreter.dependencyfallbacks",
+ "mesonbuild.interpreter.interpreter",
+ "mesonbuild.interpreter.interpreterobjects",
+ "mesonbuild.interpreter.mesonmain",
+ "mesonbuild.interpreter.primitives",
+ "mesonbuild.interpreter.primitives.array",
+ "mesonbuild.interpreter.primitives.boolean",
+ "mesonbuild.interpreter.primitives.dict",
+ "mesonbuild.interpreter.primitives.integer",
+ "mesonbuild.interpreter.primitives.range",
+ "mesonbuild.interpreter.primitives.string",
+ "mesonbuild.interpreter.type_checking",
+ "mesonbuild.interpreterbase",
+ "mesonbuild.interpreterbase._unholder",
+ "mesonbuild.interpreterbase.baseobjects",
+ "mesonbuild.interpreterbase.decorators",
+ "mesonbuild.interpreterbase.disabler",
+ "mesonbuild.interpreterbase.exceptions",
+ "mesonbuild.interpreterbase.helpers",
+ "mesonbuild.interpreterbase.interpreterbase",
+ "mesonbuild.interpreterbase.operator",
+ "mesonbuild.linkers",
+ "mesonbuild.linkers.base",
+ "mesonbuild.linkers.detect",
+ "mesonbuild.mesonlib",
+ "mesonbuild.mesonmain",
+ "mesonbuild.mintro",
+ "mesonbuild.mlog",
+ "mesonbuild.modules",
+ "mesonbuild.mparser",
+ "mesonbuild.msetup",
+ "mesonbuild.optinterpreter",
+ "mesonbuild.programs",
+ "mesonbuild.scripts",
+ "mesonbuild.scripts.meson_exe",
+ "mesonbuild.utils",
+ "mesonbuild.utils.core",
+ "mesonbuild.utils.platform",
+ "mesonbuild.utils.posix",
+ "mesonbuild.utils.universal",
+ "mesonbuild.utils.vsenv",
+ "mesonbuild.wrap",
+ "mesonbuild.wrap.wrap"
+ ],
+ "count": 68
+ }
+}
--- /dev/null
+project('empty project')
--- /dev/null
+#include<stdio.h>
+
+int main() {
+#ifdef NDEBUG
+ printf("Non-debug\n");
+#else
+ printf("Debug\n");
+#endif
+ return 0;
+}
--- /dev/null
+project('genvslite', 'cpp',
+ default_options : ['b_ndebug=if-release']
+ )
+
+exe = executable('genvslite', 'main.cpp')
--- /dev/null
+project('bar')
--- /dev/null
+project('meson package cache dir')
+
+subproject('foo')
+subproject('bar')
--- /dev/null
+[wrap-file]
+directory = bar
+
--- /dev/null
+[wrap-file]
+directory = foo
+source_url = http://server.invalid/foo.zip
+source_filename = foo.zip
+source_hash = c5dd7e8fca93045f736c83700686722b0fbc20b7dc4597b295060684c5b05b72
--- /dev/null
+project('bug', 'cpp')
+
+# When cmake is not available,
+# this triggers the bug described in #12098
+openssl_dep = dependency('openssl')
--- /dev/null
+[binaries]
+
+cmake = '/path/to/nothing'
+
+[built-in options]
+
+pkg_config_path = ''
\ No newline at end of file
--- /dev/null
+# This file should expose all possible meson syntaxes
+ # and ensure the AstInterpreter and RawPrinter are able
+
+ # to parse and write a file identical to the original.
+
+ project ( # project comment 1
+ # project comment 2
+ 'rewrite' , # argument comment
+ # project comment 3
+ 'cpp',
+ 'c',
+ default_options: [
+ 'unity=on',
+ 'unity_size=50', # number of cpp / unity. default is 4...
+ 'warning_level=2', # eqv to /W3
+ 'werror=true', # treat warnings as errors
+ 'b_ndebug=if-release', # disable assert in Release
+ 'cpp_eh=a', # /EHa exception handling
+ 'cpp_std=c++17',
+ 'cpp_winlibs=' + ','.join([ # array comment
+ # in array
+ # comment
+ 'kernel32.lib',
+ 'user32.lib',
+ 'gdi32.lib',
+ 'winspool.lib',
+ 'comdlg32.lib',
+ 'advapi32.lib',
+ 'shell32.lib'
+ # before comma comment
+ ,
+ # after comma comment
+ 'ole32.lib',
+ 'oleaut32.lib',
+ 'uuid.lib',
+ 'odbc32.lib',
+ 'odbccp32.lib',
+ 'Delayimp.lib', # For delay loaded dll
+ 'OLDNAMES.lib',
+ 'dbghelp.lib',
+ 'psapi.lib',
+ ]),
+ ],
+ meson_version: '>=1.2',
+ version: '1.0.0',
+ ) # project comment 4
+
+cppcoro_dep = dependency('andreasbuhr-cppcoro-cppcoro')
+cppcoro = declare_dependency(
+ dependencies: [cppcoro_dep.partial_dependency(
+ includes: true,
+ link_args: true,
+ links: true,
+ sources: true,
+ )],
+ # '/await:strict' allows to use <coroutine> rather than <experimental/coroutine> with C++17.
+ # We can remove '/await:strict' once we update to C++20.
+ compile_args: ['/await:strict'],
+ # includes:true doesn't work for now in partial_dependency()
+ # This line could be removed once https://github.com/mesonbuild/meson/pull/10122 is released.
+ include_directories: cppcoro_dep.get_variable('includedir1'),
+)
+
+
+if get_option('unicode') #if comment
+#if comment 2
+ mfc=cpp_compiler.find_library(get_option('debug')?'mfc140ud':'mfc140u')
+ # if comment 3
+else#elsecommentnowhitespaces
+ # else comment 1
+ mfc = cpp_compiler.find_library( get_option( 'debug' ) ? 'mfc140d' : 'mfc140')
+# else comment 2
+endif #endif comment
+
+
+assert(1 in [1, 2], '''1 should be in [1, 2]''')
+assert(3 not in [1, 2], '''3 shouldn't be in [1, 2]''')
+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']''')
+
+assert(exe1 in [exe1, exe2], ''''exe1 should be in [exe1, exe2]''')
+assert(exe3 not in [exe1, exe2], ''''exe3 shouldn't be in [exe1, exe2]''')
+
+assert('a' in {'a': 'b'}, '''1 should be in {'a': 'b'}''')
+assert('b'not in{'a':'b'}, '''1 should be in {'a': 'b'}''')
+
+assert('a'in'abc')
+assert('b' not in 'def')
+
+
+w = 'world'
+d = {'a': 1, 'b': 0b10101010, 'c': 'pi', 'd': '''a
+b
+c''', 'e': f'hello @w@', 'f': f'''triple
+ formatted
+ string # this is not a comment
+ hello @w@
+''', 'g': [1, 2, 3],
+
+ 'h' # comment a
+ : # comment b
+0xDEADBEEF # comment c
+, # comment d
+'hh': 0xfeedc0de, # lowercase hexa
+'hhh': 0XaBcD0123, # mixed case hexa
+'oo': 0O123456, # upper O octa
+'bb': 0B1111, # upper B binary
+'i': {'aa': 11, # this is a comment
+ 'bb': 22}, # a comment inside a dict
+'o': 0o754,
+'m': -12, # minus number
+'eq': 1 + 3 - 3 % 4 + -( 7 * 8 ),
+} # end of dict comment
+
+hw = d['e']
+one = d['g'][0]
+ w += '!'
+
+
+components = {
+ 'foo': ['foo.c'],
+ 'bar': ['bar.c'],
+ 'baz': ['baz.c'], # this line is indented with a tab!
+}
+
+# compute a configuration based on system dependencies, custom logic
+conf = configuration_data()
+conf.set('USE_FOO', 1)
+
+# Determine the sources to compile
+sources_to_compile = []
+foreach name, sources : components
+ if conf.get('USE_@0@'.format(name.to_upper()), 0) == 1
+ sources_to_compile += sources
+ endif
+endforeach
+
+
+items = ['a', 'continue', 'b', 'break', 'c']
+result = []
+foreach i : items
+ if i == 'continue'
+ continue
+ elif i == 'break'
+ break
+ endif
+ result += i
+endforeach
+# result is ['a', 'b']
+
+
+
+if a and b
+ # do something
+endif
+if c or d
+ # do something
+endif
+if not e
+ # do something
+endif
+if not (f or g)
+ # do something
+endif
+
+single_quote = 'contains a \' character'
+string_escapes = '\\\'\a\b\f\n\r\t\v\046\x26\u2D4d\U00002d4d\N{GREEK CAPITAL LETTER DELTA}'
+no_string_escapes = '''\\\'\a\b\f\n\r\t\v\046\x26\u2D4d\U00002d4d\N{GREEK CAPITAL LETTER DELTA}'''
+
+# FIXME: is it supposed to work? (cont_eol inside string)
+# cont_string = 'blablabla\
+# blablabla'
+
+# cont_eol with whitespace and comments after
+if a \ # comment in cont 1
+ and b \ # comment in cont 2
+ or c # comment in cont 3
+ message('ok')
+endif
+
+if a \
+ or b
+ debug('help!')
+endif
+
+
+# End of file comment with no linebreak
\ No newline at end of file
--- /dev/null
+int main(void) { return 0; }
--- /dev/null
+project('exectuable suffix', 'c')
+foo = executable('foo', 'main.c')
+foo_bin = executable('foo', 'main.c', name_suffix: 'bin')
sl2 = static_library('lib6', sources: ct)
e += executable('exe6', sources: 'main.c', objects: sl2.extract_all_objects(recursive: true))
+e += executable('exe7', sources: 'main.c', objects: ct)
+e += executable('exe8', sources: 'main.c', objects: ct[0])
+
+sl3 = static_library('lib9', objects: ct)
+e += executable('exe9', sources: 'main.c', objects: sl2.extract_all_objects(recursive: true))
+
foreach i : e
test(i.name(), i)
endforeach
rust = ['rustc', '--target', 'arm-unknown-linux-gnueabihf', '-C', 'linker=/usr/bin/arm-linux-gnueabihf-gcc-7']
ar = '/usr/arm-linux-gnueabihf/bin/ar'
strip = '/usr/arm-linux-gnueabihf/bin/strip'
-pkgconfig = '/usr/bin/arm-linux-gnueabihf-pkg-config'
+pkg-config = '/usr/bin/arm-linux-gnueabihf-pkg-config'
[properties]
root = '/usr/arm-linux-gnueabihf'
cpp = '/usr/bin/x86_64-w64-mingw32-g++'
ar = '/usr/bin/x86_64-w64-mingw32-ar'
strip = '/usr/bin/x86_64-w64-mingw32-strip'
-pkgconfig = '/usr/bin/x86_64-w64-mingw32-pkg-config'
+pkg-config = '/usr/bin/x86_64-w64-mingw32-pkg-config'
windres = '/usr/bin/x86_64-w64-mingw32-windres'
exe_wrapper = 'broken'
cc = meson.get_compiler('c')
+import('python').find_installation().install_sources('foo.py')
+
if host_machine.system() != 'cygwin'
# bar_in_system has undefined symbols, but still must be found
bar_system_dep = cc.find_library('bar_in_system')
assert(dep.get_variable(cmake : 'VAR_WITHOUT_SPACES_PS') == 'NoSpaces', 'set(PARENT_SCOPE) without spaces incorrect')
assert(dep.get_variable(cmake : 'VAR_WITH_SPACES_PS') == 'With Spaces', 'set(PARENT_SCOPE) with spaces incorrect')
-assert(dep.get_variable(cmake : 'VAR_THAT_IS_UNSET', default_value : 'sentinal') == 'sentinal', 'set() to unset is incorrect')
+assert(dep.get_variable(cmake : 'VAR_THAT_IS_UNSET', default_value : 'sentinel') == 'sentinel', 'set() to unset is incorrect')
assert(dep.get_variable(cmake : 'CACHED_STRING_NS') == 'foo', 'set(CACHED) without spaces is incorrect')
assert(dep.get_variable(cmake : 'CACHED_STRING_WS') == 'foo bar', 'set(CACHED STRING) with spaces is incorrect')
assert(dep.get_variable(cmake : 'CACHED_STRING_ARRAY_NS') == 'foo;bar', 'set(CACHED STRING) without spaces is incorrect')
assert(dep.get_variable(cmake : 'CACHED_STRING_ARRAY_WS') == 'foo;foo bar;bar', 'set(CACHED STRING[]) with spaces is incorrect')
# We don't support this, so it should be unset.
-assert(dep.get_variable(cmake : 'ENV{var}', default_value : 'sentinal') == 'sentinal', 'set(ENV) should be ignored')
+assert(dep.get_variable(cmake : 'ENV{var}', default_value : 'sentinel') == 'sentinel', 'set(ENV) should be ignored')
project('alias target', 'c')
-python3 = import('python').find_installation()
+python3 = find_program('python3')
exe_target = executable('prog', 'main.c',
build_by_default : false)
--- /dev/null
+#include<cstdio>
+
+int main(int, char**) {
+ bool intbool = true;
+ printf("Intbool is %d\n", (int)intbool);
+ return 0;
+}
e = executable('exec', 'src/main.c')
-py = import('python').find_installation()
+py = find_program('python3')
test('root', e)
test('main', py, args : [meson.current_source_dir() / 'script.py', e])
'enabled_opt': get_option('enabled_opt'),
}, section: 'Configuration')
summary({'missing prog': find_program('xyzzy', required: false),
- 'existing prog': import('python').find_installation(),
+ 'existing prog': find_program(get_option('python')).full_path(),
'missing dep': dependency('', required: false),
'external dep': dependency('zlib', required: false),
'internal dep': declare_dependency(),
option('enabled_opt', type: 'feature', value: 'auto')
+option('python', type: 'string')
project('test', 'c')
-python = import('python').find_installation()
+python = find_program('python3')
lib = library('foo', 'foo.c')
/* Do not use this as the basis of your own libc.
- * The code is probably unoptimal or wonky, as I
+ * The code is probably suboptimal or wonky, as I
* had no prior experience with this, but instead
* just fiddled with the code until it worked.
*/
project('run native test', ['c'])
-executable('terget_exe', 'main.c')
+executable('target_exe', 'main.c')
native_exe = executable('native_exe', 'main.c', native: true)
test('native_exe', native_exe, args: ['native_test_has_run.stamp'])
# This exe links on a library built in another directory. On Windows this means
# PATH must contain builddir/subprojects/sub to be able to run it.
executable('app', 'main.c', dependencies: foo_dep, install: true)
+
+env = environment({'TEST_C': ['/prefix']}, method: 'prepend')
+meson.add_devenv(env)
+env = environment({'TEST_C': ['/suffix']}, method: 'append')
+meson.add_devenv(env)
+
+# Reproducer for https://github.com/mesonbuild/meson/issues/12032
+pkgconf = import('pkgconfig')
assert os.environ['MESON_PROJECT_NAME'] == 'devenv'
assert os.environ['TEST_A'] == '1'
assert os.environ['TEST_B'] == '0+1+2+3+4'
+assert os.environ['TEST_C'] == os.pathsep.join(['/prefix', '/suffix'])
--- /dev/null
+int main(void) { return 0; }
#define CHECK_VALUE (100)
#define TEST_SUCCESS (0)
-#define TEST_FAILTURE (-1)
+#define TEST_FAILURE (-1)
int main(void) {
if (get_checked() == CHECK_VALUE) {
return TEST_SUCCESS;
}
fprintf(stdout,"bad\n");
- return TEST_FAILTURE;
+ return TEST_FAILURE;
}
--- /dev/null
+Placeholder
project('install tag', 'c')
+subproject('subproject')
+
subdir('subdir')
# Those files should not be tagged
install_dir: get_option('datadir'),
install_tag: 'custom',
)
+install_subdir('excludes',
+ install_dir: get_option('datadir'),
+ install_tag: 'custom',
+ exclude_directories: 'excluded',
+ exclude_files: 'excluded.txt',
+)
# First is custom, 2nd is devel, 3rd has no tag
custom_target('ct3',
--- /dev/null
+project('subproject')
+
+install_data('aaa.txt')
+
+install_data('bbb.txt', install_tag: 'data')
-extern int get_ret_code ();
+extern void * get_ret_code ();
public class MyThread : Object {
public int x_times { get; private set; }
-int
+void *
get_ret_code (void)
{
- return 42;
+ return (void *) (int) 42;
}
dependencies : valadeps,
install : true,
install_dir : [false, false, join_paths(get_option('datadir'), 'vala', 'vapi')])
+
+# Regression test: Vala libraries were broken when also installing python modules.
+# https://gitlab.gnome.org/GNOME/gitg/-/issues/412
+python = import('python').find_installation()
+python.install_sources('source.py')
{"type": "file", "file": "usr/include/installed_vala_onlyh.h"},
{"type": "file", "file": "usr/share/vala/vapi/installed_vala_all.vapi"},
{"type": "file", "file": "usr/share/vala-1.0/vapi/installed_vala_all_nolib.vapi"},
- {"type": "file", "file": "usr/share/vala/vapi/installed_vala_onlyvapi.vapi"}
+ {"type": "file", "file": "usr/share/vala/vapi/installed_vala_onlyvapi.vapi"},
+ {"type": "python_file", "file": "usr/@PYTHON_PURELIB@/source.py"},
+ {"type": "python_bytecode", "file": "usr/@PYTHON_PURELIB@/source.py"}
]
}
--- /dev/null
+project('options too old', meson_version : '>= 0.63')
+
+subproject('no-warn')
--- /dev/null
+option('foo', type : 'string')
--- /dev/null
+project('has both no warn', meson_version : '>= 0.63')
--- /dev/null
+option('foo', type : 'string')
--- /dev/null
+option('foo', type : 'string')
--- /dev/null
+{
+ "stdout": [
+ {
+ "line": "WARNING: Project targets '>= 0.63' but uses feature introduced in '1.1': meson.options file. Use meson_options.txt instead"
+ }
+ ]
+}
output: 'somedll.def')
shlib = shared_library('somedll', 'somedll.c', vs_module_defs: def_file)
+
+shared_library('somedll2', 'somedll.c', vs_module_defs: def_file[0])
-python3 = import('python').find_installation('')
+python3 = find_program('python3')
test('run_exe', python3, args: [files('test_run_exe.py')[0], barexe])
--- /dev/null
+EXPORTS
+ exefunc
project('generated_dll_module_defs', 'c')
subdir('subdir')
-exe = executable('prog', 'prog.c', link_with : shlib)
+exe = executable('prog', 'prog.c', link_with : shlib, vs_module_defs : 'exe.def')
test('runtest', exe)
int somedllfunc(void);
+int exefunc(void) {
+ return 42;
+}
+
int main(void) {
- return somedllfunc() == 42 ? 0 : 1;
+ return somedllfunc() == exefunc() ? 0 : 1;
}
'''))
for mod in modules:
- desc_excaped = re.sub(r"'", "\\'", mod.desc)
+ desc_escaped = re.sub(r"'", "\\'", mod.desc)
print(textwrap.indent(textwrap.dedent(f"""\
'{mod.key}': BoostModule(
name='{mod.name}',
key='{mod.key}',
- desc='{desc_excaped}',
+ desc='{desc_escaped}',
libs={[x.name for x in mod.libs]},
),\
"""), ' '))
return Statement(cur.value, args)
def arguments(self) -> T.List[T.Union[Token, T.Any]]:
- args = [] # type: T.List[T.Union[Token, T.Any]]
+ args: T.List[T.Union[Token, T.Any]] = []
if self.accept('lparen'):
args.append(self.arguments())
self.expect('rparen')
self.cmake_root = Path(cmake_root).expanduser()
self.indent_unit = ' '
self.indent_level = 0
- self.options = [] # type: T.List[tuple]
+ self.options: T.List[T.Tuple[str, str, T.Optional[str]]] = []
def convert_args(self, args: T.List[Token], as_array: bool = True) -> str:
res = []
numstr = int(number)
except ValueError:
raise SystemExit(f'Dir name {e} does not start with a number.')
+ if 'includedirxyz' in e:
+ continue
entries.append((numstr, rest))
entries.sort()
return entries
BuildDirLock, 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, OptionKey, ExecutableSerialisation, EnvironmentVariables,
+ MesonException, EnvironmentException, OptionKey,
windows_proof_rm
)
+from mesonbuild.programs import ExternalProgram
from mesonbuild.compilers.mixins.clang import ClangCompiler
from mesonbuild.compilers.mixins.gnu import GnuCompiler
detect_static_linker, detect_c_compiler, compiler_from_language,
detect_compiler_for
)
+from mesonbuild.linkers import linkers
-from mesonbuild.dependencies import PkgConfigDependency
+from mesonbuild.dependencies.pkgconfig import PkgConfigDependency
from mesonbuild.build import Target, ConfigurationData, Executable, SharedLibrary, StaticLibrary
+from mesonbuild import mtest
import mesonbuild.modules.pkgconfig
from mesonbuild.scripts import destdir_join
(result, missing_variables, confdata_useless) = do_conf_str('configuration_file', in_data, confdata, variable_format = vformat)
return '\n'.join(result)
- def check_formats(confdata, result):
+ def check_meson_format(confdata, result):
self.assertEqual(conf_str(['#mesondefine VAR'], confdata, 'meson'), result)
+
+ def check_cmake_format_simple(confdata, result):
+ self.assertEqual(conf_str(['#cmakedefine VAR'], confdata, 'cmake'), result)
+
+ def check_cmake_formats_full(confdata, result):
self.assertEqual(conf_str(['#cmakedefine VAR ${VAR}'], confdata, 'cmake'), result)
self.assertEqual(conf_str(['#cmakedefine VAR @VAR@'], confdata, 'cmake@'), result)
+ def check_formats(confdata, result):
+ check_meson_format(confdata, result)
+ check_cmake_formats_full(confdata, result)
+
confdata = ConfigurationData()
# Key error as they do not exists
check_formats(confdata, '/* #undef VAR */\n')
# Check boolean
confdata.values = {'VAR': (False, 'description')}
- check_formats(confdata, '#undef VAR\n')
+ check_meson_format(confdata, '#undef VAR\n')
+ check_cmake_formats_full(confdata, '/* #undef VAR */\n')
+
confdata.values = {'VAR': (True, 'description')}
- check_formats(confdata, '#define VAR\n')
+ check_meson_format(confdata, '#define VAR\n')
+ check_cmake_format_simple(confdata, '#define VAR\n')
+ check_cmake_formats_full(confdata, '#define VAR 1\n')
# Check string
confdata.values = {'VAR': ('value', 'description')}
confdata.values = {'VAR': (10, 'description')}
check_formats(confdata, '#define VAR 10\n')
+ # Checking if cmakedefine behaves as it does with cmake
+ confdata.values = {'VAR': ("var", 'description')}
+ self.assertEqual(conf_str(['#cmakedefine VAR @VAR@'], confdata, 'cmake@'), '#define VAR var\n')
+
+ confdata.values = {'VAR': (True, 'description')}
+ self.assertEqual(conf_str(['#cmakedefine01 VAR'], confdata, 'cmake'), '#define VAR 1\n')
+
+ confdata.values = {'VAR': (0, 'description')}
+ self.assertEqual(conf_str(['#cmakedefine01 VAR'], confdata, 'cmake'), '#define VAR 0\n')
+ confdata.values = {'VAR': (False, 'description')}
+ self.assertEqual(conf_str(['#cmakedefine01 VAR'], confdata, 'cmake'), '#define VAR 0\n')
+
+ confdata.values = {}
+ self.assertEqual(conf_str(['#cmakedefine01 VAR'], confdata, 'cmake'), '#define VAR 0\n')
+ self.assertEqual(conf_str(['#cmakedefine VAR @VAR@'], confdata, 'cmake@'), '/* #undef VAR */\n')
+
+ confdata.values = {'VAR': (5, 'description')}
+ self.assertEqual(conf_str(['#cmakedefine VAR'], confdata, 'cmake'), '#define VAR\n')
+
# Check multiple string with cmake formats
confdata.values = {'VAR': ('value', 'description')}
self.assertEqual(conf_str(['#cmakedefine VAR xxx @VAR@ yyy @VAR@'], confdata, 'cmake@'), '#define VAR xxx value yyy value\n')
static_linker = detect_static_linker(env, cc)
if is_windows():
raise SkipTest('https://github.com/mesonbuild/meson/issues/1526')
- if not isinstance(static_linker, mesonbuild.linkers.ArLinker):
+ if not isinstance(static_linker, linkers.ArLinker):
raise SkipTest('static linker is not `ar`')
# Configure
self.init(testdir)
# Get name of static library
targets = self.introspect('--targets')
- self.assertEqual(len(targets), 1)
+ self.assertGreaterEqual(len(targets), 1)
libname = targets[0]['filename'][0]
# Build and get contents of static library
self.build()
self.assertTrue(compdb[3]['file'].endswith("libfile4.c"))
# FIXME: We don't have access to the linker command
+ def test_replace_unencodable_xml_chars(self):
+ '''
+ Test that unencodable xml chars are replaced with their
+ printable representation
+ https://github.com/mesonbuild/meson/issues/9894
+ '''
+ # Create base string(\nHello Meson\n) to see valid chars are not replaced
+ base_string_invalid = '\n\x48\x65\x6c\x6c\x6f\x20\x4d\x65\x73\x6f\x6e\n'
+ base_string_valid = '\nHello Meson\n'
+ # Create invalid input from all known unencodable chars
+ invalid_string = (
+ '\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0e\x0f\x10\x11'
+ '\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f'
+ '\x80\x81\x82\x83\x84\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f'
+ '\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e'
+ '\x9f\ufdd0\ufdd1\ufdd2\ufdd3\ufdd4\ufdd5\ufdd6\ufdd7\ufdd8'
+ '\ufdd9\ufdda\ufddb\ufddc\ufddd\ufdde\ufddf\ufde0\ufde1'
+ '\ufde2\ufde3\ufde4\ufde5\ufde6\ufde7\ufde8\ufde9\ufdea'
+ '\ufdeb\ufdec\ufded\ufdee\ufdef\ufffe\uffff')
+ if sys.maxunicode >= 0x10000:
+ invalid_string = invalid_string + (
+ '\U0001fffe\U0001ffff\U0002fffe\U0002ffff'
+ '\U0003fffe\U0003ffff\U0004fffe\U0004ffff'
+ '\U0005fffe\U0005ffff\U0006fffe\U0006ffff'
+ '\U0007fffe\U0007ffff\U0008fffe\U0008ffff'
+ '\U0009fffe\U0009ffff\U000afffe\U000affff'
+ '\U000bfffe\U000bffff\U000cfffe\U000cffff'
+ '\U000dfffe\U000dffff\U000efffe\U000effff'
+ '\U000ffffe\U000fffff\U0010fffe\U0010ffff')
+
+ valid_string = base_string_valid + repr(invalid_string)[1:-1] + base_string_valid
+ invalid_string = base_string_invalid + invalid_string + base_string_invalid
+ fixed_string = mtest.replace_unencodable_xml_chars(invalid_string)
+ self.assertEqual(fixed_string, valid_string)
+
+ def test_replace_unencodable_xml_chars_unit(self):
+ '''
+ Test that unencodable xml chars are replaced with their
+ printable representation
+ https://github.com/mesonbuild/meson/issues/9894
+ '''
+ if not shutil.which('xmllint'):
+ raise SkipTest('xmllint not installed')
+ testdir = os.path.join(self.unit_test_dir, '111 replace unencodable xml chars')
+ self.init(testdir)
+ tests_command_output = self.run_tests()
+ junit_xml_logs = Path(self.logdir, 'testlog.junit.xml')
+ subprocess.run(['xmllint', junit_xml_logs], check=True)
+ # Ensure command output and JSON / text logs are not mangled.
+ raw_output_sample = '\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0b'
+ assert raw_output_sample in tests_command_output
+ text_log = Path(self.logdir, 'testlog.txt').read_text()
+ assert raw_output_sample in text_log
+ json_log = json.loads(Path(self.logdir, 'testlog.json').read_bytes())
+ assert raw_output_sample in json_log['stdout']
+
def test_run_target_files_path(self):
'''
Test that run_targets are run from the correct directory
self.init(testdir)
intro = self.introspect('--installed')
expected = {
+ 'nested_elided/sub': 'share',
+ 'new_directory': 'share/new_directory',
+ 'sub/sub1': 'share/sub1',
+ 'sub1': 'share/sub1',
'sub2': 'share/sub2',
+ 'sub3': '/usr/share/sub3',
+ 'sub_elided': 'share',
'subdir/sub1': 'share/sub1',
'subdir/sub_elided': 'share',
- 'sub1': 'share/sub1',
- 'sub/sub1': 'share/sub1',
- 'sub_elided': 'share',
- 'nested_elided/sub': 'share',
- 'new_directory': 'share/new_directory',
}
self.assertEqual(len(intro), len(expected))
self.run_tests()
def test_implicit_forcefallback(self):
- testdir = os.path.join(self.unit_test_dir, '95 implicit force fallback')
+ testdir = os.path.join(self.unit_test_dir, '96 implicit force fallback')
with self.assertRaises(subprocess.CalledProcessError):
self.init(testdir)
self.init(testdir, extra_args=['--wrap-mode=forcefallback'])
with open(os.path.join(self.logdir, 'testlog-good.txt'), encoding='utf-8') as f:
exclude_suites_log = f.read()
self.assertNotIn('buggy', exclude_suites_log)
- # --suite overrides add_test_setup(xclude_suites)
+ # --suite overrides add_test_setup(exclude_suites)
self._run(self.mtest_command + ['--setup=good', '--suite', 'buggy'])
with open(os.path.join(self.logdir, 'testlog-good.txt'), encoding='utf-8') as f:
include_suites_log = f.read()
o = self._run(self.mtest_command + ['--list', '--no-rebuild'])
self.assertNotIn('Regenerating build files.', o)
+ def test_unexisting_test_name(self):
+ testdir = os.path.join(self.unit_test_dir, '4 suite selection')
+ self.init(testdir)
+ self.build()
+
+ self.assertRaises(subprocess.CalledProcessError, self._run, self.mtest_command + ['notatest'])
+
+ def test_select_test_using_wildcards(self):
+ testdir = os.path.join(self.unit_test_dir, '4 suite selection')
+ self.init(testdir)
+ self.build()
+
+ o = self._run(self.mtest_command + ['--list', 'mainprj*'])
+ self.assertIn('mainprj-failing_test', o)
+ self.assertIn('mainprj-successful_test_no_suite', o)
+ self.assertNotIn('subprj', o)
+
+ o = self._run(self.mtest_command + ['--list', '*succ*', 'subprjm*:'])
+ self.assertIn('mainprj-successful_test_no_suite', o)
+ self.assertIn('subprjmix-failing_test', o)
+ self.assertIn('subprjmix-successful_test', o)
+ self.assertIn('subprjsucc-successful_test_no_suite', o)
+ self.assertNotIn('subprjfail-failing_test', o)
+
def test_build_by_default(self):
testdir = os.path.join(self.common_test_dir, '129 build by default')
self.init(testdir)
self.build(target=('barprog' + exe_suffix))
self.assertPathExists(exe2)
+ def test_build_generated_pyx_directly(self):
+ # Check that the transpile stage also includes
+ # dependencies for the compilation stage as dependencies
+ testdir = os.path.join("test cases/cython", '2 generated sources')
+ env = get_fake_env(testdir, self.builddir, self.prefix)
+ try:
+ detect_compiler_for(env, "cython", MachineChoice.HOST, True)
+ except EnvironmentException:
+ raise SkipTest("Cython is not installed")
+ self.init(testdir)
+ # Need to get the full target name of the pyx.c target
+ # (which is unfortunately not provided by introspection :( )
+ # We'll need to dig into the generated sources
+ targets = self.introspect('--targets')
+ name = None
+ for target in targets:
+ for target_sources in target["target_sources"]:
+ for generated_source in target_sources.get("generated_sources", []):
+ if "includestuff.pyx.c" in generated_source:
+ name = generated_source
+ break
+ # Split the path (we only want the includestuff.cpython-blahblah.so.p/includestuff.pyx.c)
+ name = os.path.normpath(name).split(os.sep)[-2:]
+ name = os.sep.join(name) # Glue list into a string
+ self.build(target=name)
+
+ def test_build_pyx_depfiles(self):
+ # building regularly and then touching a depfile dependency should rebuild
+ testdir = os.path.join("test cases/cython", '2 generated sources')
+ env = get_fake_env(testdir, self.builddir, self.prefix)
+ try:
+ cython = detect_compiler_for(env, "cython", MachineChoice.HOST, True)
+ if not version_compare(cython.version, '>=0.29.33'):
+ raise SkipTest('Cython is too old')
+ except EnvironmentException:
+ raise SkipTest("Cython is not installed")
+ self.init(testdir)
+
+ targets = self.introspect('--targets')
+ for target in targets:
+ if target['name'].startswith('simpleinclude'):
+ name = target['name']
+ self.build()
+ self.utime(os.path.join(testdir, 'simplestuff.pxi'))
+ self.assertBuildRelinkedOnlyTarget(name)
+
+
def test_internal_include_order(self):
if mesonbuild.environment.detect_msys2_arch() and ('MESON_RSP_THRESHOLD' in os.environ):
raise SkipTest('Test does not yet support gcc rsp files on msys2')
intel = IntelGnuLikeCompiler
msvc = (VisualStudioCCompiler, VisualStudioCPPCompiler)
clangcl = (ClangClCCompiler, ClangClCPPCompiler)
- ar = mesonbuild.linkers.ArLinker
- lib = mesonbuild.linkers.VisualStudioLinker
+ ar = linkers.ArLinker
+ lib = linkers.VisualStudioLinker
langs = [('c', 'CC'), ('cpp', 'CXX')]
if not is_windows() and platform.machine().lower() != 'e2k':
langs += [('objc', 'OBJC'), ('objcpp', 'OBJCXX')]
if isinstance(cc, gnu):
self.assertIsInstance(linker, ar)
if is_osx():
- self.assertIsInstance(cc.linker, mesonbuild.linkers.AppleDynamicLinker)
+ self.assertIsInstance(cc.linker, linkers.AppleDynamicLinker)
elif is_sunos():
- self.assertIsInstance(cc.linker, (mesonbuild.linkers.SolarisDynamicLinker, mesonbuild.linkers.GnuLikeDynamicLinkerMixin))
+ self.assertIsInstance(cc.linker, (linkers.SolarisDynamicLinker, linkers.GnuLikeDynamicLinkerMixin))
else:
- self.assertIsInstance(cc.linker, mesonbuild.linkers.GnuLikeDynamicLinkerMixin)
+ self.assertIsInstance(cc.linker, linkers.GnuLikeDynamicLinkerMixin)
if isinstance(cc, clangcl):
self.assertIsInstance(linker, lib)
- self.assertIsInstance(cc.linker, mesonbuild.linkers.ClangClDynamicLinker)
+ self.assertIsInstance(cc.linker, linkers.ClangClDynamicLinker)
if isinstance(cc, clang):
self.assertIsInstance(linker, ar)
if is_osx():
- self.assertIsInstance(cc.linker, mesonbuild.linkers.AppleDynamicLinker)
+ 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, (mesonbuild.linkers.MSVCDynamicLinker, mesonbuild.linkers.GnuLikeDynamicLinkerMixin))
+ self.assertIsInstance(cc.linker, (linkers.MSVCDynamicLinker, linkers.GnuLikeDynamicLinkerMixin))
else:
- self.assertIsInstance(cc.linker, mesonbuild.linkers.GnuLikeDynamicLinkerMixin)
+ self.assertIsInstance(cc.linker, linkers.GnuLikeDynamicLinkerMixin)
if isinstance(cc, intel):
self.assertIsInstance(linker, ar)
if is_osx():
- self.assertIsInstance(cc.linker, mesonbuild.linkers.AppleDynamicLinker)
+ self.assertIsInstance(cc.linker, linkers.AppleDynamicLinker)
elif is_windows():
- self.assertIsInstance(cc.linker, mesonbuild.linkers.XilinkDynamicLinker)
+ self.assertIsInstance(cc.linker, linkers.XilinkDynamicLinker)
else:
- self.assertIsInstance(cc.linker, mesonbuild.linkers.GnuDynamicLinker)
+ 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, mesonbuild.linkers.MSVCDynamicLinker)
+ 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':
for cmd in self.get_compdb():
# Get compiler
split = split_args(cmd['command'])
- if split[0] == 'ccache':
+ if split[0] in ('ccache', 'sccache'):
compiler = split[1]
else:
compiler = split[0]
def test_preprocessor_checks_CPPFLAGS(self):
'''
- Test that preprocessor compiler checks read CPPFLAGS and also CFLAGS but
+ Test that preprocessor compiler checks read CPPFLAGS and also CFLAGS/CXXFLAGS but
not LDFLAGS.
'''
testdir = os.path.join(self.common_test_dir, '132 get define')
# % and # confuse the MSVC preprocessor
# !, ^, *, and < confuse lcc preprocessor
value = 'spaces and fun@$&()-=_+{}[]:;>?,./~`'
- for env_var in ['CPPFLAGS', 'CFLAGS']:
+ for env_var in [{'CPPFLAGS'}, {'CFLAGS', 'CXXFLAGS'}]:
env = {}
- env[env_var] = f'-D{define}="{value}"'
+ for i in env_var:
+ env[i] = f'-D{define}="{value}"'
env['LDFLAGS'] = '-DMESON_FAIL_VALUE=cflags-read'
self.init(testdir, extra_args=[f'-D{define}={value}'], override_envvars=env)
+ self.new_builddir()
def test_custom_target_exe_data_deterministic(self):
testdir = os.path.join(self.common_test_dir, '109 custom target capture')
# This assumes all of the targets support lto
for t in targets:
for s in t['target_sources']:
+ if 'linker' in s:
+ continue
for e in expected:
self.assertIn(e, s['parameters'])
targets = self.introspect('--targets')
# This assumes all of the targets support lto
for t in targets:
- for s in t['target_sources']:
- self.assertTrue(expected.issubset(set(s['parameters'])), f'Incorrect values for {t["name"]}')
+ for src in t['target_sources']:
+ self.assertTrue(expected.issubset(set(src['parameters'])), f'Incorrect values for {t["name"]}')
def test_dist_git(self):
if not shutil.which('git'):
test. Needs to be a unit test because it accesses Meson internals.
'''
testdir = os.path.join(self.common_test_dir, '150 reserved targets')
- targets = mesonbuild.coredata.FORBIDDEN_TARGET_NAMES
+ targets = set(mesonbuild.coredata.FORBIDDEN_TARGET_NAMES)
# We don't actually define a target with this name
- targets.pop('build.ninja')
+ targets.remove('build.ninja')
# Remove this to avoid multiple entries with the same name
# but different case.
- targets.pop('PHONY')
+ targets.remove('PHONY')
for i in targets:
self.assertPathExists(os.path.join(testdir, i))
self.build()
self.run_tests()
+ def test_prebuilt_shared_lib_rpath_same_prefix(self) -> None:
+ (cc, _, object_suffix, shared_suffix) = self.detect_prebuild_env()
+ orig_tdir = os.path.join(self.unit_test_dir, '17 prebuilt shared')
+
+ # Put the shared library in a location that shares a common prefix with
+ # the source directory:
+ #
+ # .../
+ # foo-lib/
+ # libalexandria.so
+ # foo/
+ # meson.build
+ # ...
+ #
+ # This allows us to check that the .../foo-lib/libalexandria.so path is
+ # preserved correctly when meson processes it.
+ with tempfile.TemporaryDirectory() as d:
+ libdir = os.path.join(d, 'foo-lib')
+ os.mkdir(libdir)
+
+ source = os.path.join(orig_tdir, 'alexandria.c')
+ objectfile = os.path.join(libdir, 'alexandria.' + object_suffix)
+ impfile = os.path.join(libdir, 'alexandria.lib')
+ if cc.get_argument_syntax() == 'msvc':
+ shlibfile = os.path.join(libdir, 'alexandria.' + shared_suffix)
+ elif is_cygwin():
+ shlibfile = os.path.join(libdir, 'cygalexandria.' + shared_suffix)
+ else:
+ shlibfile = os.path.join(libdir, 'libalexandria.' + shared_suffix)
+ # Ensure MSVC extra files end up in the directory that gets deleted
+ # at the end
+ with chdir(libdir):
+ self.build_shared_lib(cc, source, objectfile, shlibfile, impfile)
+
+ tdir = os.path.join(d, 'foo')
+ shutil.copytree(orig_tdir, tdir)
+
+ # Run the test
+ self.init(tdir, extra_args=[f'-Dsearch_dir={libdir}'])
+ self.build()
+ self.run_tests()
+
def test_underscore_prefix_detection_list(self) -> None:
'''
Test the underscore detection hardcoded lookup list
check_pcfile('libvartest2.pc', relocatable=False)
self.wipe()
- testdir_abs = os.path.join(self.unit_test_dir, '105 pkgconfig relocatable with absolute path')
+ testdir_abs = os.path.join(self.unit_test_dir, '106 pkgconfig relocatable with absolute path')
self.init(testdir_abs)
check_pcfile('libsimple.pc', relocatable=True, levels=3)
original = get_opt()
self.assertDictEqual(original, expected)
+ def test_executable_names(self):
+ testdir = os.path.join(self.unit_test_dir, '121 executable suffix')
+ self.init(testdir)
+ self.build()
+ exe1 = os.path.join(self.builddir, 'foo' + exe_suffix)
+ exe2 = os.path.join(self.builddir, 'foo.bin')
+ self.assertPathExists(exe1)
+ self.assertPathExists(exe2)
+ self.assertNotEqual(exe1, exe2)
+
+ # Wipe and run the compile command against the target names
+ self.init(testdir, extra_args=['--wipe'])
+ self._run([*self.meson_command, 'compile', '-C', self.builddir, './foo'])
+ self._run([*self.meson_command, 'compile', '-C', self.builddir, './foo.bin'])
+ self.assertPathExists(exe1)
+ self.assertPathExists(exe2)
+ self.assertNotEqual(exe1, exe2)
+
+
def opt_has(self, name, value):
res = self.introspect('--buildoptions')
found = False
self.assertEqual(item['value'], ['b', 'c'])
self.assertEqual(item['choices'], ['b', 'c', 'd'])
+ def test_options_listed_in_build_options(self) -> None:
+ """Detect when changed options become listed in build options."""
+ testdir = os.path.join(self.unit_test_dir, '113 list build options')
+
+ out = self.init(testdir)
+ for line in out.splitlines():
+ if line.startswith('Message: Build options:'):
+ self.assertNotIn('-Dauto_features=auto', line)
+ self.assertNotIn('-Doptional=auto', line)
+
+ self.wipe()
+ self.mac_ci_delay()
+
+ out = self.init(testdir, extra_args=['-Dauto_features=disabled', '-Doptional=enabled'])
+ for line in out.splitlines():
+ if line.startswith('Message: Build options:'):
+ self.assertIn('-Dauto_features=disabled', line)
+ self.assertIn('-Doptional=enabled', line)
+
+ self.setconf('-Doptional=disabled')
+ out = self.build()
+ for line in out.splitlines():
+ if line.startswith('Message: Build options:'):
+ self.assertIn('-Dauto_features=disabled', line)
+ self.assertNotIn('-Doptional=enabled', line)
+ self.assertIn('-Doptional=disabled', line)
+
def test_subproject_promotion(self):
testdir = os.path.join(self.unit_test_dir, '12 promote')
workdir = os.path.join(self.builddir, 'work')
self.assertFalse(os.path.isfile(promoted_wrap))
subprocess.check_call(self.wrap_command + ['promote', 'athing'], cwd=workdir)
self.assertTrue(os.path.isfile(promoted_wrap))
+ self.new_builddir() # Ensure builddir is not parent or workdir
self.init(workdir)
self.build()
for (t, f) in [
('10 out of bounds', 'meson.build'),
('18 wrong plusassign', 'meson.build'),
- ('59 bad option argument', 'meson_options.txt'),
- ('97 subdir parse error', os.path.join('subdir', 'meson.build')),
- ('98 invalid option file', 'meson_options.txt'),
+ ('57 bad option argument', 'meson_options.txt'),
+ ('95 subdir parse error', os.path.join('subdir', 'meson.build')),
+ ('96 invalid option file', 'meson_options.txt'),
]:
tdir = os.path.join(self.src_root, 'test cases', 'failing', t)
langs = ['c']
env = get_fake_env()
- for l in ['cpp', 'cs', 'd', 'java', 'cuda', 'fortran', 'objc', 'objcpp', 'rust']:
+ for l in ['cpp', 'cs', 'd', 'java', 'cuda', 'fortran', 'objc', 'objcpp', 'rust', 'vala']:
try:
- comp = detect_compiler_for(env, l, MachineChoice.HOST)
+ comp = detect_compiler_for(env, l, MachineChoice.HOST, True)
with tempfile.TemporaryDirectory() as d:
comp.sanity_check(d, env)
langs.append(l)
for lang in langs:
for target_type in ('executable', 'library'):
- 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)
- 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)
+ 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 test_compiler_run_command(self):
'''
msg = ('''DEPRECATION: target prog links against shared module mymod, which is incorrect.
This will be an error in the future, so please use shared_library() for mymod instead.
If shared_module() was used for mymod because it has references to undefined symbols,
- use shared_libary() with `override_options: ['b_lundef=false']` instead.''')
+ use shared_library() with `override_options: ['b_lundef=false']` instead.''')
self.assertIn(msg, out)
def test_mixed_language_linker_check(self):
- testdir = os.path.join(self.unit_test_dir, '96 compiler.links file arg')
+ testdir = os.path.join(self.unit_test_dir, '97 compiler.links file arg')
self.init(testdir)
cmds = self.get_meson_log_compiler_checks()
self.assertEqual(len(cmds), 5)
def test_native_dep_pkgconfig(self):
testdir = os.path.join(self.unit_test_dir,
'45 native dep pkgconfig var')
- with tempfile.NamedTemporaryFile(mode='w', delete=False) as crossfile:
+ with tempfile.NamedTemporaryFile(mode='w', delete=False, encoding='utf-8') as crossfile:
crossfile.write(textwrap.dedent(
'''[binaries]
- pkgconfig = '{}'
+ pkg-config = '{}'
[properties]
def test_pkg_config_libdir(self):
testdir = os.path.join(self.unit_test_dir,
'45 native dep pkgconfig var')
- with tempfile.NamedTemporaryFile(mode='w', delete=False) as crossfile:
+ with tempfile.NamedTemporaryFile(mode='w', delete=False, encoding='utf-8') as crossfile:
crossfile.write(textwrap.dedent(
'''[binaries]
- pkgconfig = 'pkg-config'
+ pkg-config = 'pkg-config'
[properties]
pkg_config_libdir = ['{}']
self.assertIn('cttest.cpp:4:20', out)
self.assertNotIn(dummydir, out)
+ @skipIfNoExecutable('clang-tidy')
+ def test_clang_tidy_fix(self):
+ if self.backend is not Backend.ninja:
+ raise SkipTest(f'Clang-tidy is for now only supported on Ninja, not {self.backend.name}')
+ if shutil.which('c++') is None:
+ raise SkipTest('Clang-tidy breaks when ccache is used and "c++" not in path.')
+ if is_osx():
+ raise SkipTest('Apple ships a broken clang-tidy that chokes on -pipe.')
+ testdir = os.path.join(self.unit_test_dir, '68 clang-tidy')
+
+ # Ensure that test project is in git even when running meson from tarball.
+ srcdir = os.path.join(self.builddir, 'src')
+ shutil.copytree(testdir, srcdir)
+ git_init(srcdir)
+ testdir = srcdir
+ self.new_builddir()
+
+ dummydir = os.path.join(testdir, 'dummydir.h')
+ testfile = os.path.join(testdir, 'cttest.cpp')
+ fixedfile = os.path.join(testdir, 'cttest_fixed.cpp')
+ self.init(testdir, override_envvars={'CXX': 'c++'})
+ # Make sure test files are different
+ self.assertNotEqual(Path(testfile).read_text(encoding='utf-8'),
+ Path(fixedfile).read_text(encoding='utf-8'))
+ out = self.run_target('clang-tidy-fix')
+ self.assertIn('cttest.cpp:4:20', out)
+ self.assertNotIn(dummydir, out)
+ # Make sure the test file is fixed
+ self.assertEqual(Path(testfile).read_text(encoding='utf-8'),
+ Path(fixedfile).read_text(encoding='utf-8'))
+
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
('benchmarks', list),
('buildoptions', list),
('buildsystem_files', list),
+ ('compilers', dict),
('dependencies', list),
+ ('install_plan', dict),
('installed', dict),
+ ('machines', dict),
('projectinfo', dict),
('targets', list),
('tests', list),
('depends', list),
('workdir', (str, None)),
('priority', int),
+ ('extra_paths', list),
]
buildoptions_keylist = [
dependencies_typelist = [
('name', str),
+ ('type', str),
('version', str),
('compile_args', list),
('link_args', list),
+ ('include_directories', list),
+ ('sources', list),
+ ('extra_files', list),
+ ('dependencies', list),
+ ('depends', list),
+ ('meson_variables', list),
]
targets_typelist = [
('target_sources', list),
('extra_files', list),
('subproject', (str, None)),
+ ('dependencies', list),
+ ('depends', list),
('install_filename', (list, None)),
('installed', bool),
+ ('vs_module_defs', (str, None)),
+ ('win_subsystem', (str, None)),
]
targets_sources_typelist = [
('parameters', list),
('sources', list),
('generated_sources', list),
+ ('unity_sources', (list, None)),
+ ]
+
+ target_sources_linker_typelist = [
+ ('linker', list),
+ ('parameters', list),
]
# First load all files
name_to_out.update({i['name']: i['filename']})
for group in i['target_sources']:
src_to_id.update({os.path.relpath(src, testdir): i['id']
- for src in group['sources']})
+ for src in group.get('sources', [])})
# Check Tests and benchmarks
tests_to_find = ['test case 1', 'test case 2', 'benchmark 1']
self.assertPathEqual(i['defined_in'], os.path.join(testdir, tgt[3]))
targets_to_find.pop(i['name'], None)
for j in i['target_sources']:
- assertKeyTypes(targets_sources_typelist, j)
- self.assertEqual(j['sources'], [os.path.normpath(f) for f in tgt[4]])
+ if 'compiler' in j:
+ assertKeyTypes(targets_sources_typelist, j)
+ self.assertEqual(j['sources'], [os.path.normpath(f) for f in tgt[4]])
+ else:
+ assertKeyTypes(target_sources_linker_typelist, j)
self.assertDictEqual(targets_to_find, {})
def test_introspect_file_dump_equals_all(self):
'benchmarks',
'buildoptions',
'buildsystem_files',
+ 'compilers',
'dependencies',
'installed',
'install_plan',
+ 'machines',
'projectinfo',
'targets',
'tests',
res_wb = [i for i in res_wb if i['type'] != 'custom']
for i in res_wb:
i['filename'] = [os.path.relpath(x, self.builddir) for x in i['filename']]
- if 'install_filename' in i:
- del i['install_filename']
+ for k in ('install_filename', 'dependencies', 'win_subsystem'):
+ if k in i:
+ del i[k]
sources = []
for j in i['target_sources']:
- sources += j['sources']
+ sources += j.get('sources', [])
i['target_sources'] = [{
'language': 'unknown',
'compiler': [],
def test_summary(self):
testdir = os.path.join(self.unit_test_dir, '71 summary')
- out = self.init(testdir, extra_args=['-Denabled_opt=enabled'])
+ out = self.init(testdir, extra_args=['-Denabled_opt=enabled', f'-Dpython={sys.executable}'])
expected = textwrap.dedent(r'''
Some Subproject 2.0
string : bar
integer: 1
- boolean: True
+ boolean: true
subsub undefined
My Project 1.0
Configuration
- Some boolean : False
- Another boolean: True
+ Some boolean : false
+ Another boolean: true
Some string : Hello World
A list : string
1
- True
+ true
empty list :
enabled_opt : enabled
A number : 1
Stuff
missing prog : NO
- existing prog : ''' + sys.executable + '''
+ existing prog : ''' + ExternalProgram('python3', [sys.executable], silent=True).path + '''
missing dep : NO
external dep : YES 1.2.3
internal dep : YES
Subprojects
sub : YES
sub2 : NO Problem encountered: This subproject failed
- subsub : YES
+ subsub : YES (from sub2)
User defined options
- backend : ''' + self.backend.name + '''
+ backend : ''' + self.backend_name + '''
libdir : lib
prefix : /usr
enabled_opt : enabled
+ python : ''' + sys.executable + '''
''')
expected_lines = expected.split('\n')[1:]
out_start = out.find(expected_lines[0])
[properties]
c_args = common_flags + ['-DSOMETHING']
cpp_args = c_args + ['-DSOMETHING_ELSE']
+ rel_to_src = '@GLOBAL_SOURCE_ROOT@' / 'tool'
+ rel_to_file = '@DIRNAME@' / 'tool'
+ no_escaping = '@@DIRNAME@@' / 'tool'
[binaries]
c = toolchain / compiler
'''))
- values = mesonbuild.coredata.parse_machine_files([crossfile1, crossfile2])
+ values = mesonbuild.coredata.parse_machine_files([crossfile1, crossfile2], self.builddir)
self.assertEqual(values['binaries']['c'], '/toolchain/gcc')
self.assertEqual(values['properties']['c_args'],
['--sysroot=/toolchain/sysroot', '-DSOMETHING'])
self.assertEqual(values['properties']['cpp_args'],
['--sysroot=/toolchain/sysroot', '-DSOMETHING', '-DSOMETHING_ELSE'])
+ self.assertEqual(values['properties']['rel_to_src'], os.path.join(self.builddir, 'tool'))
+ self.assertEqual(values['properties']['rel_to_file'], os.path.join(os.path.dirname(crossfile2), 'tool'))
+ self.assertEqual(values['properties']['no_escaping'], os.path.join(f'@{os.path.dirname(crossfile2)}@', 'tool'))
@skipIf(is_windows(), 'Directory cleanup fails for some reason')
def test_wrap_git(self):
self.init(srcdir, extra_args=['-Dbuild.b_lto=true'])
def test_install_skip_subprojects(self):
- testdir = os.path.join(self.unit_test_dir, '91 install skip subprojects')
+ testdir = os.path.join(self.unit_test_dir, '92 install skip subprojects')
self.init(testdir)
self.build()
]
bar_expected = [
'bar',
- 'share/foo/bar.dat',
+ 'share/bar',
+ 'share/bar/bar.dat',
'include/bar.h',
'bin/bar' + exe_suffix,
'bar/barfile'
check_installed_files(['--skip-subprojects', 'another'], all_expected)
def test_adding_subproject_to_configure_project(self) -> None:
- srcdir = os.path.join(self.unit_test_dir, '92 new subproject in configured project')
+ srcdir = os.path.join(self.unit_test_dir, '93 new subproject in configured project')
self.init(srcdir)
self.build()
self.setconf('-Duse-sub=true')
self._run(cmd + python_command + [script])
self.assertEqual('This is text.', self._run(cmd + [app]).strip())
+ cmd = self.meson_command + ['devenv', '-C', self.builddir, '--dump']
+ o = self._run(cmd)
+ expected = os.pathsep.join(['/prefix', '$TEST_C', '/suffix'])
+ self.assertIn(f'TEST_C="{expected}"', o)
+ self.assertIn('export TEST_C', o)
+
+ cmd = self.meson_command + ['devenv', '-C', self.builddir, '--dump', '--dump-format', 'sh']
+ o = self._run(cmd)
+ expected = os.pathsep.join(['/prefix', '$TEST_C', '/suffix'])
+ self.assertIn(f'TEST_C="{expected}"', o)
+ self.assertNotIn('export', o)
+
+ cmd = self.meson_command + ['devenv', '-C', self.builddir, '--dump', '--dump-format', 'vscode']
+ o = self._run(cmd)
+ expected = os.pathsep.join(['/prefix', '/suffix'])
+ self.assertIn(f'TEST_C="{expected}"', o)
+ self.assertNotIn('export', o)
+
+ fname = os.path.join(self.builddir, 'dump.env')
+ cmd = self.meson_command + ['devenv', '-C', self.builddir, '--dump', fname]
+ o = self._run(cmd)
+ self.assertEqual(o, '')
+ o = Path(fname).read_text(encoding='utf-8')
+ expected = os.pathsep.join(['/prefix', '$TEST_C', '/suffix'])
+ self.assertIn(f'TEST_C="{expected}"', o)
+ self.assertIn('export TEST_C', o)
+
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')
if not shutil.which('clang-format'):
raise SkipTest('clang-format not found')
- testdir = os.path.join(self.unit_test_dir, '93 clangformat')
+ testdir = os.path.join(self.unit_test_dir, '94 clangformat')
newdir = os.path.join(self.builddir, 'testdir')
shutil.copytree(testdir, newdir)
self.new_builddir()
self.build('clang-format-check')
def test_custom_target_implicit_include(self):
- testdir = os.path.join(self.unit_test_dir, '94 custominc')
+ testdir = os.path.join(self.unit_test_dir, '95 custominc')
self.init(testdir)
self.build()
compdb = self.get_compdb()
env = get_fake_env()
# Get the compiler so we know which compiler class to mock.
- cc = detect_compiler_for(env, 'c', MachineChoice.HOST)
+ cc = detect_compiler_for(env, 'c', MachineChoice.HOST, True)
cc_type = type(cc)
# Test a compiler that acts as a linker
with mock.patch.object(cc_type, 'INVOKES_LINKER', True):
- cc = detect_compiler_for(env, 'c', MachineChoice.HOST)
+ cc = detect_compiler_for(env, 'c', MachineChoice.HOST, True)
link_args = env.coredata.get_external_link_args(cc.for_machine, cc.language)
self.assertEqual(sorted(link_args), sorted(['-DCFLAG', '-flto']))
# And one that doesn't
with mock.patch.object(cc_type, 'INVOKES_LINKER', False):
- cc = detect_compiler_for(env, 'c', MachineChoice.HOST)
+ cc = detect_compiler_for(env, 'c', MachineChoice.HOST, True)
link_args = env.coredata.get_external_link_args(cc.for_machine, cc.language)
self.assertEqual(sorted(link_args), sorted(['-flto']))
def test_install_tag(self) -> None:
- testdir = os.path.join(self.unit_test_dir, '98 install all targets')
+ testdir = os.path.join(self.unit_test_dir, '99 install all targets')
self.init(testdir)
self.build()
Path(installpath, 'usr/share/out3-custom.txt'),
Path(installpath, 'usr/share/custom_files'),
Path(installpath, 'usr/share/custom_files/data.txt'),
+ Path(installpath, 'usr/share/excludes'),
+ Path(installpath, 'usr/share/excludes/installed.txt'),
Path(installpath, 'usr/lib'),
Path(installpath, 'usr/lib/libbothcustom.a'),
Path(installpath, 'usr/' + shared_lib_name('bothcustom')),
Path(installpath, 'usr/share/foo2.h'),
Path(installpath, 'usr/share/out1.txt'),
Path(installpath, 'usr/share/out2.txt'),
+ Path(installpath, 'usr/share/subproject'),
+ Path(installpath, 'usr/share/subproject/aaa.txt'),
+ Path(installpath, 'usr/share/subproject/bbb.txt'),
}
def do_install(tags, expected_files, expected_scripts):
do_install('runtime,custom', expected_runtime_custom, 1)
do_install(None, expected_all, 2)
+
+ def test_install_script_dry_run(self):
+ testdir = os.path.join(self.common_test_dir, '53 install script')
+ self.init(testdir)
+ self.build()
+
+ cmd = self.meson_command + ['install', '--dry-run', '--destdir', self.installdir]
+ outputs = self._run(cmd, workdir=self.builddir)
+
+ installpath = Path(self.installdir)
+ self.assertFalse((installpath / 'usr/diiba/daaba/file.dat').exists())
+ self.assertIn("DRYRUN: Writing file file.dat", outputs)
+
+
def test_introspect_install_plan(self):
- testdir = os.path.join(self.unit_test_dir, '98 install all targets')
+ testdir = os.path.join(self.unit_test_dir, '99 install all targets')
introfile = os.path.join(self.builddir, 'meson-info', 'intro-install_plan.json')
self.init(testdir)
self.assertPathExists(introfile)
structured_sources=None,
objects=[], environment=env, compilers=env.coredata.compilers[MachineChoice.HOST],
kwargs={})
- target.process_compilers()
- target.process_compilers_late([])
+ target.process_compilers_late()
return target.filename
shared_lib_name = lambda name: output_name(name, SharedLibrary)
f'{self.builddir}/out1-notag.txt': {
'destination': '{datadir}/out1-notag.txt',
'tag': None,
+ 'subproject': None,
},
f'{self.builddir}/out2-notag.txt': {
'destination': '{datadir}/out2-notag.txt',
'tag': None,
+ 'subproject': None,
},
f'{self.builddir}/libstatic.a': {
'destination': '{libdir_static}/libstatic.a',
'tag': 'devel',
+ 'subproject': None,
},
f'{self.builddir}/' + exe_name('app'): {
'destination': '{bindir}/' + exe_name('app'),
'tag': 'runtime',
+ 'subproject': None,
},
f'{self.builddir}/' + exe_name('app-otherdir'): {
'destination': '{prefix}/otherbin/' + exe_name('app-otherdir'),
'tag': 'runtime',
+ 'subproject': None,
},
f'{self.builddir}/subdir/' + exe_name('app2'): {
'destination': '{bindir}/' + exe_name('app2'),
'tag': 'runtime',
+ 'subproject': None,
},
f'{self.builddir}/' + shared_lib_name('shared'): {
'destination': '{libdir_shared}/' + shared_lib_name('shared'),
'tag': 'runtime',
+ 'subproject': None,
},
f'{self.builddir}/' + shared_lib_name('both'): {
'destination': '{libdir_shared}/' + shared_lib_name('both'),
'tag': 'runtime',
+ 'subproject': None,
},
f'{self.builddir}/' + static_lib_name('both'): {
'destination': '{libdir_static}/' + static_lib_name('both'),
'tag': 'devel',
+ 'subproject': None,
},
f'{self.builddir}/' + shared_lib_name('bothcustom'): {
'destination': '{libdir_shared}/' + shared_lib_name('bothcustom'),
'tag': 'custom',
+ 'subproject': None,
},
f'{self.builddir}/' + static_lib_name('bothcustom'): {
'destination': '{libdir_static}/' + static_lib_name('bothcustom'),
'tag': 'custom',
+ 'subproject': None,
},
f'{self.builddir}/subdir/' + shared_lib_name('both2'): {
'destination': '{libdir_shared}/' + shared_lib_name('both2'),
'tag': 'runtime',
+ 'subproject': None,
},
f'{self.builddir}/subdir/' + static_lib_name('both2'): {
'destination': '{libdir_static}/' + static_lib_name('both2'),
'tag': 'devel',
+ 'subproject': None,
},
f'{self.builddir}/out1-custom.txt': {
'destination': '{datadir}/out1-custom.txt',
'tag': 'custom',
+ 'subproject': None,
},
f'{self.builddir}/out2-custom.txt': {
'destination': '{datadir}/out2-custom.txt',
'tag': 'custom',
+ 'subproject': None,
},
f'{self.builddir}/out3-custom.txt': {
'destination': '{datadir}/out3-custom.txt',
'tag': 'custom',
+ 'subproject': None,
},
f'{self.builddir}/subdir/out1.txt': {
'destination': '{datadir}/out1.txt',
'tag': None,
+ 'subproject': None,
},
f'{self.builddir}/subdir/out2.txt': {
'destination': '{datadir}/out2.txt',
'tag': None,
+ 'subproject': None,
},
f'{self.builddir}/out-devel.h': {
'destination': '{includedir}/out-devel.h',
'tag': 'devel',
+ 'subproject': None,
},
f'{self.builddir}/out3-notag.txt': {
'destination': '{datadir}/out3-notag.txt',
'tag': None,
+ 'subproject': None,
},
},
'configure': {
f'{self.builddir}/foo-notag.h': {
'destination': '{datadir}/foo-notag.h',
'tag': None,
+ 'subproject': None,
},
f'{self.builddir}/foo2-devel.h': {
'destination': '{includedir}/foo2-devel.h',
'tag': 'devel',
+ 'subproject': None,
},
f'{self.builddir}/foo-custom.h': {
'destination': '{datadir}/foo-custom.h',
'tag': 'custom',
+ 'subproject': None,
},
f'{self.builddir}/subdir/foo2.h': {
'destination': '{datadir}/foo2.h',
'tag': None,
+ 'subproject': None,
},
},
'data': {
f'{testdir}/bar-notag.txt': {
'destination': '{datadir}/bar-notag.txt',
'tag': None,
+ 'subproject': None,
},
f'{testdir}/bar-devel.h': {
'destination': '{includedir}/bar-devel.h',
'tag': 'devel',
+ 'subproject': None,
},
f'{testdir}/bar-custom.txt': {
'destination': '{datadir}/bar-custom.txt',
'tag': 'custom',
+ 'subproject': None,
},
f'{testdir}/subdir/bar2-devel.h': {
'destination': '{includedir}/bar2-devel.h',
'tag': 'devel',
+ 'subproject': None,
+ },
+ f'{testdir}/subprojects/subproject/aaa.txt': {
+ 'destination': '{datadir}/subproject/aaa.txt',
+ 'tag': None,
+ 'subproject': 'subproject',
+ },
+ f'{testdir}/subprojects/subproject/bbb.txt': {
+ 'destination': '{datadir}/subproject/bbb.txt',
+ 'tag': 'data',
+ 'subproject': 'subproject',
},
},
'headers': {
f'{testdir}/foo1-devel.h': {
'destination': '{includedir}/foo1-devel.h',
'tag': 'devel',
+ 'subproject': None,
},
f'{testdir}/subdir/foo3-devel.h': {
'destination': '{includedir}/foo3-devel.h',
'tag': 'devel',
+ 'subproject': None,
},
},
'install_subdirs': {
f'{testdir}/custom_files': {
'destination': '{datadir}/custom_files',
- 'tag': 'custom'
+ 'tag': 'custom',
+ 'subproject': None,
+ 'exclude_dirs': [],
+ 'exclude_files': [],
+ },
+ f'{testdir}/excludes': {
+ 'destination': '{datadir}/excludes',
+ 'tag': 'custom',
+ 'subproject': None,
+ 'exclude_dirs': ['excluded'],
+ 'exclude_files': ['excluded.txt'],
}
}
}
}}
''')
- testdir = os.path.join(self.unit_test_dir, '101 rlib linkage')
+ testdir = os.path.join(self.unit_test_dir, '102 rlib linkage')
gen_file = os.path.join(testdir, 'lib.rs')
- with open(gen_file, 'w') as f:
+ with open(gen_file, 'w', encoding='utf-8') as f:
f.write(template.format(0))
self.addCleanup(windows_proof_rm, gen_file)
self.build()
self.run_tests()
- with open(gen_file, 'w') as f:
+ with open(gen_file, 'w', encoding='utf-8') as f:
f.write(template.format(39))
self.build()
self.assertEqual(cm.exception.returncode, 1)
self.assertIn('exit status 39', cm.exception.stdout)
+ @skip_if_not_language('rust')
+ def test_bindgen_drops_invalid(self) -> None:
+ if self.backend is not Backend.ninja:
+ raise unittest.SkipTest('Rust is only supported with ninja currently')
+ testdir = os.path.join(self.rust_test_dir, '12 bindgen')
+ env = get_fake_env(testdir, self.builddir, self.prefix)
+ cc = detect_c_compiler(env, MachineChoice.HOST)
+ # bindgen understands compiler args that clang understands, but not
+ # flags by other compilers
+ if cc.get_id() == 'gcc':
+ bad_arg = '-fdse'
+ elif cc.get_id() == 'msvc':
+ bad_arg = '/fastfail'
+ else:
+ raise unittest.SkipTest('Test only supports GCC and MSVC')
+ self.init(testdir, extra_args=[f"-Dc_args=['-DCMD_ARG', '{bad_arg}']"])
+ intro = self.introspect(['--targets'])
+ for i in intro:
+ if i['type'] == 'custom' and i['id'].startswith('rustmod-bindgen'):
+ args = i['target_sources'][0]['compiler']
+ self.assertIn('-DCMD_ARG', args)
+ self.assertIn('-DPROJECT_ARG', args)
+ self.assertIn('-DGLOBAL_ARG', args)
+ self.assertNotIn(bad_arg, args)
+ self.assertNotIn('-mtls-dialect=gnu2', args)
+ self.assertNotIn('/fp:fast', args)
+ return
+
def test_custom_target_name(self):
- testdir = os.path.join(self.unit_test_dir, '99 custom target name')
+ testdir = os.path.join(self.unit_test_dir, '100 custom target name')
self.init(testdir)
out = self.build()
if self.backend is Backend.ninja:
self.assertIn('Generating subdir/file.txt with a custom command', out)
def test_symlinked_subproject(self):
- testdir = os.path.join(self.unit_test_dir, '106 subproject symlink')
+ testdir = os.path.join(self.unit_test_dir, '107 subproject symlink')
subproject_dir = os.path.join(testdir, 'subprojects')
subproject = os.path.join(testdir, 'symlinked_subproject')
symlinked_subproject = os.path.join(testdir, 'subprojects', 'symlinked_subproject')
if not os.path.exists(subproject_dir):
os.mkdir(subproject_dir)
- os.symlink(subproject, symlinked_subproject)
+ try:
+ os.symlink(subproject, symlinked_subproject)
+ except OSError:
+ raise SkipTest("Symlinks are not available on this machine")
self.addCleanup(os.remove, symlinked_subproject)
self.init(testdir)
self.build()
def test_configure_same_noop(self):
- testdir = os.path.join(self.unit_test_dir, '108 configure same noop')
- self.init(testdir, extra_args=['-Dopt=val'])
+ testdir = os.path.join(self.unit_test_dir, '109 configure same noop')
+ args = [
+ '-Dstring=val',
+ '-Dboolean=true',
+ '-Dcombo=two',
+ '-Dinteger=7',
+ '-Darray=[\'three\']',
+ '-Dfeature=disabled',
+ '--buildtype=plain',
+ '--prefix=/abc',
+ ]
+ self.init(testdir, extra_args=args)
- filename = os.path.join(self.privatedir, 'coredata.dat')
+ filename = Path(self.privatedir) / 'coredata.dat'
+
+ olddata = filename.read_bytes()
oldmtime = os.path.getmtime(filename)
- self.setconf(["-Dopt=val"])
- newmtime = os.path.getmtime(filename)
- self.assertEqual(oldmtime, newmtime)
- def test_scripts_loaded_modules(self):
- '''
- Simulate a wrapped command, as done for custom_target() that capture
- output. The script will print all python modules loaded and we verify
- that it contains only an acceptable subset. Loading too many modules
- slows down the build when many custom targets get wrapped.
- '''
- es = ExecutableSerialisation(python_command + ['-c', 'exit(0)'], env=EnvironmentVariables())
- p = Path(self.builddir, 'exe.dat')
- with p.open('wb') as f:
- pickle.dump(es, f)
- cmd = self.meson_command + ['--internal', 'test_loaded_modules', '--unpickle', str(p)]
- p = subprocess.run(cmd, stdout=subprocess.PIPE)
- all_modules = json.loads(p.stdout.splitlines()[0])
- meson_modules = [m for m in all_modules if 'meson' in m]
- expected_meson_modules = [
- 'mesonbuild',
- 'mesonbuild._pathlib',
- 'mesonbuild.utils',
- 'mesonbuild.utils.core',
- 'mesonbuild.mesonmain',
- 'mesonbuild.mlog',
- 'mesonbuild.scripts',
- 'mesonbuild.scripts.meson_exe',
- 'mesonbuild.scripts.test_loaded_modules'
- ]
- self.assertEqual(sorted(expected_meson_modules), sorted(meson_modules))
+ for opt in ('-Dstring=val', '--buildtype=plain', '-Dfeature=disabled', '-Dprefix=/abc'):
+ self.setconf([opt])
+ newdata = filename.read_bytes()
+ newmtime = os.path.getmtime(filename)
+ self.assertEqual(oldmtime, newmtime)
+ self.assertEqual(olddata, newdata)
+ olddata = newdata
+ oldmtime = newmtime
+
+ for opt in ('-Dstring=abc', '--buildtype=release', '-Dfeature=enabled', '-Dprefix=/def'):
+ self.setconf([opt])
+ newdata = filename.read_bytes()
+ newmtime = os.path.getmtime(filename)
+ self.assertGreater(newmtime, oldmtime)
+ self.assertNotEqual(olddata, newdata)
+ olddata = newdata
+ oldmtime = newmtime
+
+ def test_c_cpp_stds(self):
+ testdir = os.path.join(self.unit_test_dir, '115 c cpp stds')
+ self.init(testdir)
+ # Invalid values should fail whatever compiler we have
+ with self.assertRaises(subprocess.CalledProcessError):
+ self.setconf('-Dc_std=invalid')
+ with self.assertRaises(subprocess.CalledProcessError):
+ self.setconf('-Dc_std=c89,invalid')
+ with self.assertRaises(subprocess.CalledProcessError):
+ self.setconf('-Dc_std=c++11')
+ env = get_fake_env()
+ cc = detect_c_compiler(env, MachineChoice.HOST)
+ if cc.get_id() == 'msvc':
+ # default_option should have selected those
+ self.assertEqual(self.getconf('c_std'), 'c89')
+ self.assertEqual(self.getconf('cpp_std'), 'vc++11')
+ # This is deprecated but works for C
+ self.setconf('-Dc_std=gnu99')
+ self.assertEqual(self.getconf('c_std'), 'c99')
+ # C++ however never accepted that fallback
+ with self.assertRaises(subprocess.CalledProcessError):
+ self.setconf('-Dcpp_std=gnu++11')
+ # The first supported std should be selected
+ self.setconf('-Dcpp_std=gnu++11,vc++11,c++11')
+ self.assertEqual(self.getconf('cpp_std'), 'vc++11')
+ elif cc.get_id() == 'gcc':
+ # default_option should have selected those
+ self.assertEqual(self.getconf('c_std'), 'gnu89')
+ self.assertEqual(self.getconf('cpp_std'), 'gnu++98')
+ # The first supported std should be selected
+ self.setconf('-Dcpp_std=c++11,gnu++11,vc++11')
+ self.assertEqual(self.getconf('cpp_std'), 'c++11')
import mesonbuild.coredata
import mesonbuild.modules.gnome
from mesonbuild.mesonlib import (
- is_cygwin, join_args, windows_proof_rmtree, python_command
+ is_cygwin, join_args, split_args, windows_proof_rmtree, python_command
)
import mesonbuild.modules.pkgconfig
from run_tests import (
Backend, ensure_backend_detects_changes, get_backend_commands,
get_builddir_target_args, get_meson_script, run_configure_inprocess,
- run_mtest_inprocess
+ run_mtest_inprocess, handle_meson_skip_test,
)
+# magic attribute used by unittest.result.TestResult._is_relevant_tb_level
+# This causes tracebacks to hide these internal implementation details,
+# e.g. for assertXXX helpers.
+__unittest = True
+
class BasePlatformTests(TestCase):
prefix = '/usr'
libdir = 'lib'
src_root = str(PurePath(__file__).parents[1])
self.src_root = src_root
# Get the backend
- self.backend = getattr(Backend, os.environ['MESON_UNIT_TEST_BACKEND'])
- self.meson_args = ['--backend=' + self.backend.name]
+ self.backend_name = os.environ['MESON_UNIT_TEST_BACKEND']
+ backend_type = 'vs' if self.backend_name.startswith('vs') else self.backend_name
+ self.backend = getattr(Backend, backend_type)
+ self.meson_args = ['--backend=' + self.backend_name]
self.meson_native_files = []
self.meson_cross_files = []
self.meson_command = python_command + [get_meson_script()]
self.uninstall_command = get_backend_commands(self.backend)
# Test directories
self.common_test_dir = os.path.join(src_root, 'test cases/common')
+ self.python_test_dir = os.path.join(src_root, 'test cases/python')
self.rust_test_dir = os.path.join(src_root, 'test cases/rust')
self.vala_test_dir = os.path.join(src_root, 'test cases/vala')
self.framework_test_dir = os.path.join(src_root, 'test cases/frameworks')
# VS doesn't have a stable output when no changes are done
# XCode backend is untested with unit tests, help welcome!
self.no_rebuild_stdout = [f'UNKNOWN BACKEND {self.backend.name!r}']
+ os.environ['COLUMNS'] = '80'
self.builddirs = []
self.new_builddir()
env = os.environ.copy()
env.update(override_envvars)
- p = subprocess.run(command, stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT if stderr else subprocess.PIPE,
- env=env,
- encoding='utf-8',
- text=True, cwd=workdir, timeout=60 * 5)
+ proc = subprocess.run(command, stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT if stderr else subprocess.PIPE,
+ env=env,
+ encoding='utf-8',
+ text=True, cwd=workdir, timeout=60 * 5)
print('$', join_args(command))
print('stdout:')
- print(p.stdout)
+ print(proc.stdout)
if not stderr:
print('stderr:')
- print(p.stderr)
- if p.returncode != 0:
- if 'MESON_SKIP_TEST' in p.stdout:
- raise SkipTest('Project requested skipping.')
- raise subprocess.CalledProcessError(p.returncode, command, output=p.stdout)
- return p.stdout
+ print(proc.stderr)
+ if proc.returncode != 0:
+ skipped, reason = handle_meson_skip_test(proc.stdout)
+ if skipped:
+ raise SkipTest(f'Project requested skipping: {reason}')
+ raise subprocess.CalledProcessError(proc.returncode, command, output=proc.stdout)
+ return proc.stdout
def init(self, srcdir, *,
extra_args=None,
extra_args = []
if not isinstance(extra_args, list):
extra_args = [extra_args]
- args = [srcdir, self.builddir]
+ build_and_src_dir_args = [self.builddir, srcdir]
+ args = []
if default_args:
args += ['--prefix', self.prefix]
if self.libdir:
self.privatedir = os.path.join(self.builddir, 'meson-private')
if inprocess:
try:
- returncode, out, err = run_configure_inprocess(['setup'] + self.meson_args + args + extra_args, override_envvars)
+ returncode, out, err = run_configure_inprocess(['setup'] + self.meson_args + args + extra_args + build_and_src_dir_args, override_envvars)
except Exception as e:
if not allow_fail:
self._print_meson_log()
finally:
# Close log file to satisfy Windows file locking
mesonbuild.mlog.shutdown()
- mesonbuild.mlog.log_dir = None
- mesonbuild.mlog.log_file = None
+ mesonbuild.mlog._logger.log_dir = None
+ mesonbuild.mlog._logger.log_file = None
- if 'MESON_SKIP_TEST' in out:
- raise SkipTest('Project requested skipping.')
+ skipped, reason = handle_meson_skip_test(out)
+ if skipped:
+ raise SkipTest(f'Project requested skipping: {reason}')
if returncode != 0:
self._print_meson_log()
print('Stdout:\n')
raise RuntimeError('Configure failed')
else:
try:
- out = self._run(self.setup_command + args + extra_args, override_envvars=override_envvars, workdir=workdir)
- except SkipTest:
- raise SkipTest('Project requested skipping: ' + srcdir)
+ out = self._run(self.setup_command + args + extra_args + build_and_src_dir_args, override_envvars=override_envvars, workdir=workdir)
except Exception:
if not allow_fail:
self._print_meson_log()
ensure_backend_detects_changes(self.backend)
self._run(self.mconf_command + arg + [self.builddir])
+ def getconf(self, optname: str):
+ opts = self.introspect('--buildoptions')
+ for x in opts:
+ if x.get('name') == optname:
+ return x.get('value')
+ self.fail(f'Option {optname} not found')
+
def wipe(self):
windows_proof_rmtree(self.builddir)
Fetch a list command-lines run by meson for compiler checks.
Each command-line is returned as a list of arguments.
'''
- prefix = 'Command line:'
+ prefix = 'Command line: `'
+ suffix = '` -> 0\n'
with self._open_meson_log() as log:
- cmds = [l[len(prefix):].split() for l in log if l.startswith(prefix)]
+ cmds = [split_args(l[len(prefix):-len(suffix)]) for l in log if l.startswith(prefix)]
return cmds
def get_meson_log_sanitychecks(self):
def assertPathDoesNotExist(self, path):
m = f'Path {path!r} should not exist'
self.assertFalse(os.path.exists(path), msg=m)
+
+ def assertLength(self, val, length):
+ assert len(val) == length, f'{val} is not length {length}'
--- /dev/null
+# SPDX-License-Identifier: Apache-2.0
+# Copyright © 2022-2023 Intel Corporation
+
+from __future__ import annotations
+import unittest
+import typing as T
+
+from mesonbuild.cargo import builder, cfg
+from mesonbuild.cargo.cfg import TokenType
+from mesonbuild.cargo.version import convert
+
+
+class CargoVersionTest(unittest.TestCase):
+
+ def test_cargo_to_meson(self) -> None:
+ cases: T.List[T.Tuple[str, T.List[str]]] = [
+ # Basic requirements
+ ('>= 1', ['>= 1']),
+ ('> 1', ['> 1']),
+ ('= 1', ['= 1']),
+ ('< 1', ['< 1']),
+ ('<= 1', ['<= 1']),
+
+ # tilde tests
+ ('~1', ['>= 1', '< 2']),
+ ('~1.1', ['>= 1.1', '< 1.2']),
+ ('~1.1.2', ['>= 1.1.2', '< 1.2.0']),
+
+ # Wildcards
+ ('*', []),
+ ('1.*', ['>= 1', '< 2']),
+ ('2.3.*', ['>= 2.3', '< 2.4']),
+
+ # Unqualified
+ ('2', ['>= 2', '< 3']),
+ ('2.4', ['>= 2.4', '< 3']),
+ ('2.4.5', ['>= 2.4.5', '< 3']),
+ ('0.0.0', ['< 1']),
+ ('0.0', ['< 1']),
+ ('0', ['< 1']),
+ ('0.0.5', ['>= 0.0.5', '< 0.0.6']),
+ ('0.5.0', ['>= 0.5.0', '< 0.6']),
+ ('0.5', ['>= 0.5', '< 0.6']),
+ ('1.0.45', ['>= 1.0.45', '< 2']),
+
+ # Caret (Which is the same as unqualified)
+ ('^2', ['>= 2', '< 3']),
+ ('^2.4', ['>= 2.4', '< 3']),
+ ('^2.4.5', ['>= 2.4.5', '< 3']),
+ ('^0.0.0', ['< 1']),
+ ('^0.0', ['< 1']),
+ ('^0', ['< 1']),
+ ('^0.0.5', ['>= 0.0.5', '< 0.0.6']),
+ ('^0.5.0', ['>= 0.5.0', '< 0.6']),
+ ('^0.5', ['>= 0.5', '< 0.6']),
+
+ # Multiple requirements
+ ('>= 1.2.3, < 1.4.7', ['>= 1.2.3', '< 1.4.7']),
+ ]
+
+ for (data, expected) in cases:
+ with self.subTest():
+ self.assertListEqual(convert(data), expected)
+
+
+class CargoCfgTest(unittest.TestCase):
+
+ def test_lex(self) -> None:
+ cases: T.List[T.Tuple[str, T.List[T.Tuple[TokenType, T.Optional[str]]]]] = [
+ ('"unix"', [(TokenType.STRING, 'unix')]),
+ ('unix', [(TokenType.IDENTIFIER, 'unix')]),
+ ('not(unix)', [
+ (TokenType.NOT, None),
+ (TokenType.LPAREN, None),
+ (TokenType.IDENTIFIER, 'unix'),
+ (TokenType.RPAREN, None),
+ ]),
+ ('any(unix, windows)', [
+ (TokenType.ANY, None),
+ (TokenType.LPAREN, None),
+ (TokenType.IDENTIFIER, 'unix'),
+ (TokenType.COMMA, None),
+ (TokenType.IDENTIFIER, 'windows'),
+ (TokenType.RPAREN, None),
+ ]),
+ ('target_arch = "x86_64"', [
+ (TokenType.IDENTIFIER, 'target_arch'),
+ (TokenType.EQUAL, None),
+ (TokenType.STRING, 'x86_64'),
+ ]),
+ ('all(target_arch = "x86_64", unix)', [
+ (TokenType.ALL, None),
+ (TokenType.LPAREN, None),
+ (TokenType.IDENTIFIER, 'target_arch'),
+ (TokenType.EQUAL, None),
+ (TokenType.STRING, 'x86_64'),
+ (TokenType.COMMA, None),
+ (TokenType.IDENTIFIER, 'unix'),
+ (TokenType.RPAREN, None),
+ ]),
+ ]
+ for data, expected in cases:
+ with self.subTest():
+ self.assertListEqual(list(cfg.lexer(data)), expected)
+
+ def test_parse(self) -> None:
+ cases = [
+ ('target_os = "windows"', cfg.Equal(cfg.Identifier("target_os"), cfg.String("windows"))),
+ ('target_arch = "x86"', cfg.Equal(cfg.Identifier("target_arch"), cfg.String("x86"))),
+ ('target_family = "unix"', cfg.Equal(cfg.Identifier("target_family"), cfg.String("unix"))),
+ ('any(target_arch = "x86", target_arch = "x86_64")',
+ cfg.Any(
+ [
+ cfg.Equal(cfg.Identifier("target_arch"), cfg.String("x86")),
+ cfg.Equal(cfg.Identifier("target_arch"), cfg.String("x86_64")),
+ ])),
+ ('all(target_arch = "x86", target_os = "linux")',
+ cfg.All(
+ [
+ cfg.Equal(cfg.Identifier("target_arch"), cfg.String("x86")),
+ cfg.Equal(cfg.Identifier("target_os"), cfg.String("linux")),
+ ])),
+ ('not(all(target_arch = "x86", target_os = "linux"))',
+ cfg.Not(
+ cfg.All(
+ [
+ cfg.Equal(cfg.Identifier("target_arch"), cfg.String("x86")),
+ cfg.Equal(cfg.Identifier("target_os"), cfg.String("linux")),
+ ]))),
+ ]
+ 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')
+
+ 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'))))),
+ ]
+ for data, expected in cases:
+ with self.subTest():
+ value = cfg.ir_to_meson(cfg.parse(iter(cfg.lexer(data))), build)
+ self.assertEqual(value, expected)
def test_objc_versions(self):
# Objective-C always uses the C standard version.
- # Objecttive-C++ always uses the C++ standard version.
+ # Objective-C++ always uses the C++ standard version.
# This is what most people seem to want and in addition
# it is the only setup supported by Xcode.
testdir = os.path.join(self.objc_test_dir, '1 simple')
testdir = os.path.join(self.objcpp_test_dir, '1 simple')
self.init(testdir)
self.assertIn('-std=c++14', self.get_compdb()[0]['command'])
+
+ def test_darwin_get_object_archs(self):
+ from mesonbuild.mesonlib import darwin_get_object_archs
+ archs = darwin_get_object_archs('/System/Library/CoreServices/Encodings/libSymbolConverter.dylib')
+ self.assertEqual(archs, ['x86_64', 'aarch64'])
name = name.replace('_', '-')
self.assertIn(name, html)
+ @unittest.mock.patch.dict(os.environ)
def test_vim_syntax_highlighting(self):
'''
Ensure that vim syntax highlighting files were updated for new
functions in the global namespace in build files.
'''
+ # Disable unit test specific syntax
+ del os.environ['MESON_RUNNING_IN_PROJECT_TESTS']
env = get_fake_env()
interp = Interpreter(FakeBuild(env), mock=True)
with open('data/syntax-highlighting/vim/syntax/meson.vim', encoding='utf-8') as f:
defined = set([a.strip() for a in res.group().split('\\')][1:])
self.assertEqual(defined, set(chain(interp.funcs.keys(), interp.builtin.keys())))
+ @unittest.mock.patch.dict(os.environ)
def test_all_functions_defined_in_ast_interpreter(self):
'''
Ensure that the all functions defined in the Interpreter are also defined
in the AstInterpreter (and vice versa).
'''
+ # Disable unit test specific syntax
+ del os.environ['MESON_RUNNING_IN_PROJECT_TESTS']
env = get_fake_env()
interp = Interpreter(FakeBuild(env), mock=True)
astint = AstInterpreter('.', '', '')
super().setUp()
self.srcdir = os.path.realpath(tempfile.mkdtemp())
self.mbuild = os.path.join(self.srcdir, 'meson.build')
- self.moptions = os.path.join(self.srcdir, 'meson_options.txt')
+ self.moptions = os.path.join(self.srcdir, 'meson.options')
+ if not os.path.exists(self.moptions):
+ self.moptions = os.path.join(self.srcdir, 'meson_options.txt')
def tearDown(self):
super().tearDown()
dep = declare_dependency(dependencies : zlib_dep)
dep.get_pkgconfig_variable('foo')
'''
- self.assertMesonRaises(code, "Method.*pkgconfig.*is invalid.*internal")
+ self.assertMesonRaises(code, ".*is not a pkgconfig dependency")
code = '''zlib_dep = dependency('zlib', required : false)
dep = declare_dependency(dependencies : zlib_dep)
dep.get_configtool_variable('foo')
'''
- self.assertMesonRaises(code, "Method.*configtool.*is invalid.*internal")
+ self.assertMesonRaises(code, ".* is not a config-tool dependency")
def test_objc_cpp_detection(self):
'''
paths.discard(dirname)
path = pathsep.join([str(p) for p in paths])
return path
+
+def xfail_if_jobname(name: str):
+ if os.environ.get('MESON_CI_JOBNAME') == name:
+ return unittest.expectedFailure
+
+ def wrapper(func):
+ return func
+ return wrapper
import mesonbuild.modules.gnome
from mesonbuild import coredata
from mesonbuild.compilers.c import ClangCCompiler, GnuCCompiler
+from mesonbuild.compilers.cpp import VisualStudioCPPCompiler
from mesonbuild.compilers.d import DmdDCompiler
+from mesonbuild.linkers import linkers
from mesonbuild.interpreterbase import typed_pos_args, InvalidArguments, ObjectHolder
from mesonbuild.interpreterbase import typed_pos_args, InvalidArguments, typed_kwargs, ContainerTypeInfo, KwargInfo
from mesonbuild.mesonlib import (
OptionType
)
from mesonbuild.interpreter.type_checking import in_set_validator, NoneType
-from mesonbuild.dependencies import PkgConfigDependency
+from mesonbuild.dependencies.pkgconfig import PkgConfigDependency, PkgConfigInterface, PkgConfigCLI
from mesonbuild.programs import ExternalProgram
import mesonbuild.modules.pkgconfig
l.append_direct('/libbaz.a')
self.assertEqual(l, ['-Lfoodir', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a'])
+
+ def test_compiler_args_class_visualstudio(self):
+ linker = linkers.MSVCDynamicLinker(MachineChoice.HOST, [])
+ # Version just needs to be > 19.0.0
+ cc = VisualStudioCPPCompiler([], [], '20.00', MachineChoice.HOST, False, mock.Mock(), 'x64', linker=linker)
+
+ a = cc.compiler_args(cc.get_always_args())
+ self.assertEqual(a.to_native(copy=True), ['/nologo', '/showIncludes', '/utf-8', '/Zc:__cplusplus'])
+
+ # Ensure /source-charset: removes /utf-8
+ a.append('/source-charset:utf-8')
+ self.assertEqual(a.to_native(copy=True), ['/nologo', '/showIncludes', '/Zc:__cplusplus', '/source-charset:utf-8'])
+
+ # Ensure /execution-charset: removes /utf-8
+ a = cc.compiler_args(cc.get_always_args() + ['/execution-charset:utf-8'])
+ self.assertEqual(a.to_native(copy=True), ['/nologo', '/showIncludes', '/Zc:__cplusplus', '/execution-charset:utf-8'])
+
+ # Ensure /validate-charset- removes /utf-8
+ a = cc.compiler_args(cc.get_always_args() + ['/validate-charset-'])
+ self.assertEqual(a.to_native(copy=True), ['/nologo', '/showIncludes', '/Zc:__cplusplus', '/validate-charset-'])
+
+
def test_compiler_args_class_gnuld(self):
## Test --start/end-group
- linker = mesonbuild.linkers.GnuBFDDynamicLinker([], MachineChoice.HOST, '-Wl,', [])
+ linker = linkers.GnuBFDDynamicLinker([], MachineChoice.HOST, '-Wl,', [])
gcc = GnuCCompiler([], [], 'fake', False, MachineChoice.HOST, mock.Mock(), 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']
def test_compiler_args_remove_system(self):
## Test --start/end-group
- linker = mesonbuild.linkers.GnuBFDDynamicLinker([], MachineChoice.HOST, '-Wl,', [])
+ linker = linkers.GnuBFDDynamicLinker([], MachineChoice.HOST, '-Wl,', [])
gcc = GnuCCompiler([], [], 'fake', False, MachineChoice.HOST, mock.Mock(), 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']
# Can not be used as context manager because we need to
# open it a second time and this is not possible on
# Windows.
- configfile = tempfile.NamedTemporaryFile(mode='w+', delete=False)
+ configfile = tempfile.NamedTemporaryFile(mode='w+', delete=False, encoding='utf-8')
configfilename = configfile.name
config.write(configfile)
configfile.flush()
'needs_exe_wrapper': 'true' if desired_value else 'false'
}
- configfile = tempfile.NamedTemporaryFile(mode='w+', delete=False)
+ configfile = tempfile.NamedTemporaryFile(mode='w+', delete=False, encoding='utf-8')
configfilename = configfile.name
config.write(configfile)
configfile.close()
if platform != 'openbsd':
return
with tempfile.TemporaryDirectory() as tmpdir:
- 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']:
+ 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)
+ found = cc._find_library_real('foo', env, [tmpdir], '', LibType.PREFER_SHARED, lib_prefix_warning=True)
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)
+ self.assertEqual(os.path.basename(found[0]), 'libbar.so.7.10')
def test_find_library_patterns(self):
'''
create_static_lib(p1 / 'libdl.a')
create_static_lib(p1 / 'librt.a')
- def fake_call_pkgbin(self, args, env=None):
- if '--libs' not in args:
- return 0, '', ''
- if args[-1] == 'foo':
- return 0, f'-L{p2.as_posix()} -lfoo -L{p1.as_posix()} -lbar', ''
- if args[-1] == 'bar':
- return 0, f'-L{p2.as_posix()} -lbar', ''
- if args[-1] == 'internal':
- return 0, f'-L{p1.as_posix()} -lpthread -lm -lc -lrt -ldl', ''
-
- old_call = PkgConfigDependency._call_pkgbin
- old_check = PkgConfigDependency.check_pkgconfig
- PkgConfigDependency._call_pkgbin = fake_call_pkgbin
- PkgConfigDependency.check_pkgconfig = lambda x, _: pkgbin
- # Test begins
- try:
+ class FakeInstance(PkgConfigCLI):
+ def _call_pkgbin(self, args, env=None):
+ if '--libs' not in args:
+ return 0, '', ''
+ if args[-1] == 'foo':
+ return 0, f'-L{p2.as_posix()} -lfoo -L{p1.as_posix()} -lbar', ''
+ if args[-1] == 'bar':
+ return 0, f'-L{p2.as_posix()} -lbar', ''
+ if args[-1] == 'internal':
+ return 0, f'-L{p1.as_posix()} -lpthread -lm -lc -lrt -ldl', ''
+
+ with mock.patch.object(PkgConfigInterface, 'instance') as instance_method:
+ instance_method.return_value = FakeInstance(env, MachineChoice.HOST, silent=True)
kwargs = {'required': True, 'silent': True}
foo_dep = PkgConfigDependency('foo', env, kwargs)
self.assertEqual(foo_dep.get_link_args(),
for link_arg in link_args:
for lib in ('pthread', 'm', 'c', 'dl', 'rt'):
self.assertNotIn(f'lib{lib}.a', link_arg, msg=link_args)
- finally:
- # Test ends
- PkgConfigDependency._call_pkgbin = old_call
- PkgConfigDependency.check_pkgconfig = old_check
- # Reset dependency class to ensure that in-process configure doesn't mess up
- PkgConfigDependency.pkgbin_cache = {}
- PkgConfigDependency.class_pkgbin = PerMachine(None, None)
def test_version_compare(self):
comparefunc = mesonbuild.mesonlib.version_compare_many
def test_log_once(self):
f = io.StringIO()
- with mock.patch('mesonbuild.mlog.log_file', f), \
- mock.patch('mesonbuild.mlog._logged_once', set()):
- mesonbuild.mlog.log_once('foo')
- mesonbuild.mlog.log_once('foo')
+ with mock.patch('mesonbuild.mlog._logger.log_file', f), \
+ mock.patch('mesonbuild.mlog._logger.logged_once', set()):
+ mesonbuild.mlog.log('foo', once=True)
+ mesonbuild.mlog.log('foo', once=True)
actual = f.getvalue().strip()
self.assertEqual(actual, 'foo', actual)
def test_log_once_ansi(self):
f = io.StringIO()
- with mock.patch('mesonbuild.mlog.log_file', f), \
- mock.patch('mesonbuild.mlog._logged_once', set()):
- mesonbuild.mlog.log_once(mesonbuild.mlog.bold('foo'))
- mesonbuild.mlog.log_once(mesonbuild.mlog.bold('foo'))
+ with mock.patch('mesonbuild.mlog._logger.log_file', f), \
+ mock.patch('mesonbuild.mlog._logger.logged_once', set()):
+ mesonbuild.mlog.log(mesonbuild.mlog.bold('foo'), once=True)
+ mesonbuild.mlog.log(mesonbuild.mlog.bold('foo'), once=True)
actual = f.getvalue().strip()
self.assertEqual(actual.count('foo'), 1, actual)
- mesonbuild.mlog.log_once('foo')
+ mesonbuild.mlog.log('foo', once=True)
actual = f.getvalue().strip()
self.assertEqual(actual.count('foo'), 1, actual)
schema = json.loads(Path('data/test.schema.json').read_text(encoding='utf-8'))
- errors = [] # type: T.Tuple[str, Exception]
+ errors: T.List[T.Tuple[Path, Exception]] = []
for p in Path('test cases').glob('**/test.json'):
try:
validate(json.loads(p.read_text(encoding='utf-8')), schema=schema)
_(None, mock.Mock(), ['string', 'var', 'args', 0], None)
self.assertEqual(str(cm.exception), 'foo argument 4 was of type "int" but should have been "str"')
- def test_typed_pos_args_varargs_invalid_mulitple_types(self) -> None:
+ def test_typed_pos_args_varargs_invalid_multiple_types(self) -> None:
@typed_pos_args('foo', str, varargs=(str, list))
def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None:
self.assertTrue(False) # should not be reachable
since_values={list: '1.9'}),
KwargInfo('new_dict', (ContainerTypeInfo(list, str), ContainerTypeInfo(dict, str)), default={},
since_values={dict: '1.1'}),
+ KwargInfo('foo', (str, int, ContainerTypeInfo(list, str), ContainerTypeInfo(dict, str), ContainerTypeInfo(list, int)), default={},
+ since_values={str: '1.1', ContainerTypeInfo(list, str): '1.2', ContainerTypeInfo(dict, str): '1.3'},
+ deprecated_values={int: '0.8', ContainerTypeInfo(list, int): '0.9'}),
+ KwargInfo('tuple', (ContainerTypeInfo(list, (str, int))), default=[], listify=True,
+ since_values={ContainerTypeInfo(list, str): '1.1', ContainerTypeInfo(list, int): '1.2'}),
)
def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, str]) -> None:
pass
with self.subTest('deprecated dict string value with msg'), mock.patch('sys.stdout', io.StringIO()) as out:
_(None, mock.Mock(subproject=''), [], {'output': {'foo2': 'a'}})
- self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '1.0'.*deprecated since '0.9': "testfunc" keyword argument "output" value "foo2". dont use it.*""")
+ self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '1.0'.*deprecated since '0.9': "testfunc" keyword argument "output" value "foo2" in dict keys. dont use it.*""")
with self.subTest('new dict string value'), mock.patch('sys.stdout', io.StringIO()) as out:
_(None, mock.Mock(subproject=''), [], {'output': {'bar': 'b'}})
with self.subTest('new dict string value with msg'), mock.patch('sys.stdout', io.StringIO()) as out:
_(None, mock.Mock(subproject=''), [], {'output': {'bar2': 'a'}})
- self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '1.0'.*introduced in '1.1': "testfunc" keyword argument "output" value "bar2". use this.*""")
+ self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '1.0'.*introduced in '1.1': "testfunc" keyword argument "output" value "bar2" in dict keys. use this.*""")
+
+ with self.subTest('new string type'), mock.patch('sys.stdout', io.StringIO()) as out:
+ _(None, mock.Mock(subproject=''), [], {'foo': 'foo'})
+ self.assertRegex(out.getvalue(), r"""WARNING: Project targets '1.0'.*introduced in '1.1': "testfunc" keyword argument "foo" of type str.*""")
+
+ with self.subTest('new array of string type'), mock.patch('sys.stdout', io.StringIO()) as out:
+ _(None, mock.Mock(subproject=''), [], {'foo': ['foo']})
+ self.assertRegex(out.getvalue(), r"""WARNING: Project targets '1.0'.*introduced in '1.2': "testfunc" keyword argument "foo" of type array\[str\].*""")
+
+ with self.subTest('new dict of string type'), mock.patch('sys.stdout', io.StringIO()) as out:
+ _(None, mock.Mock(subproject=''), [], {'foo': {'plop': 'foo'}})
+ self.assertRegex(out.getvalue(), r"""WARNING: Project targets '1.0'.*introduced in '1.3': "testfunc" keyword argument "foo" of type dict\[str\].*""")
+
+ with self.subTest('deprecated int value'), mock.patch('sys.stdout', io.StringIO()) as out:
+ _(None, mock.Mock(subproject=''), [], {'foo': 1})
+ self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '1.0'.*deprecated since '0.8': "testfunc" keyword argument "foo" of type int.*""")
+
+ with self.subTest('deprecated array int value'), mock.patch('sys.stdout', io.StringIO()) as out:
+ _(None, mock.Mock(subproject=''), [], {'foo': [1]})
+ self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '1.0'.*deprecated since '0.9': "testfunc" keyword argument "foo" of type array\[int\].*""")
+
+ with self.subTest('new list[str] value'), mock.patch('sys.stdout', io.StringIO()) as out:
+ _(None, mock.Mock(subproject=''), [], {'tuple': ['foo', 42]})
+ self.assertRegex(out.getvalue(), r"""WARNING: Project targets '1.0'.*introduced in '1.1': "testfunc" keyword argument "tuple" of type array\[str\].*""")
+ self.assertRegex(out.getvalue(), r"""WARNING: Project targets '1.0'.*introduced in '1.2': "testfunc" keyword argument "tuple" of type array\[int\].*""")
+
+ with self.subTest('deprecated array string value'), mock.patch('sys.stdout', io.StringIO()) as out:
+ _(None, mock.Mock(subproject=''), [], {'input': 'foo'})
+ self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '1.0'.*deprecated since '0.9': "testfunc" keyword argument "input" value "foo".*""")
+
+ with self.subTest('new array string value'), mock.patch('sys.stdout', io.StringIO()) as out:
+ _(None, mock.Mock(subproject=''), [], {'input': 'bar'})
+ self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '1.0'.*introduced in '1.1': "testfunc" keyword argument "input" value "bar".*""")
with self.subTest('non string union'), mock.patch('sys.stdout', io.StringIO()) as out:
_(None, mock.Mock(subproject=''), [], {'install_dir': False})
('ppc', 'ppc'),
('macppc', 'ppc'),
('power macintosh', 'ppc'),
- ('mips64el', 'mips64'),
- ('mips64', 'mips64'),
+ ('mips64el', 'mips'),
+ ('mips64', 'mips'),
('mips', 'mips'),
('mipsel', 'mips'),
- ('ip30', 'mips64'),
- ('ip35', 'mips64'),
+ ('ip30', 'mips'),
+ ('ip35', 'mips'),
('parisc64', 'parisc'),
('sun4u', 'sparc64'),
('sun4v', 'sparc64'),
('aarch64_be', 'aarch64'),
]
+ cc = ClangCCompiler([], [], 'fake', MachineChoice.HOST, False, mock.Mock())
+
with mock.patch('mesonbuild.environment.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({})
+ actual = mesonbuild.environment.detect_cpu_family({'c': cc})
self.assertEqual(actual, expected)
with mock.patch('mesonbuild.environment.any_compiler_has_define', mock.Mock(return_value=True)):
- for test, expected in [('x86_64', 'x86'), ('aarch64', 'arm'), ('ppc', 'ppc64')]:
+ 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})
+ 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',
+ 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({})
self.assertEqual(actual, expected)
('x64', 'x86_64'),
('i86pc', 'x86_64'),
('earm', 'arm'),
- ('mips64el', 'mips64'),
- ('mips64', 'mips64'),
+ ('mips64el', 'mips'),
+ ('mips64', 'mips'),
('mips', 'mips'),
('mipsel', 'mips'),
('aarch64', 'aarch64'),
('aarch64_be', 'aarch64'),
]
+ cc = ClangCCompiler([], [], 'fake', MachineChoice.HOST, False, mock.Mock())
+
with mock.patch('mesonbuild.environment.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({})
+ actual = mesonbuild.environment.detect_cpu({'c': cc})
self.assertEqual(actual, expected)
with mock.patch('mesonbuild.environment.any_compiler_has_define', mock.Mock(return_value=True)):
- for test, expected in [('x86_64', 'i686'), ('aarch64', 'arm'), ('ppc', 'ppc64')]:
+ 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})
+ self.assertEqual(actual, expected)
+
+ with mock.patch(
+ 'mesonbuild.environment.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({})
self.assertEqual(actual, expected)
from mesonbuild.compilers.cpp import AppleClangCPPCompiler
from mesonbuild.compilers.objc import AppleClangObjCCompiler
from mesonbuild.compilers.objcpp import AppleClangObjCPPCompiler
-from mesonbuild.dependencies import PkgConfigDependency
+from mesonbuild.dependencies.pkgconfig import PkgConfigDependency, PkgConfigCLI, PkgConfigInterface
import mesonbuild.modules.pkgconfig
PKG_CONFIG = os.environ.get('PKG_CONFIG', 'pkg-config')
self.assertTrue(foo_dep.found())
self.assertEqual(foo_dep.get_version(), '1.0')
self.assertIn('-lfoo', foo_dep.get_link_args())
- self.assertEqual(foo_dep.get_pkgconfig_variable('foo', [], None), 'bar')
- self.assertPathEqual(foo_dep.get_pkgconfig_variable('datadir', [], None), '/usr/data')
+ self.assertEqual(foo_dep.get_variable(pkgconfig='foo'), 'bar')
+ self.assertPathEqual(foo_dep.get_variable(pkgconfig='datadir'), '/usr/data')
libhello_nolib = PkgConfigDependency('libhello_nolib', env, kwargs)
self.assertTrue(libhello_nolib.found())
self.assertEqual(libhello_nolib.get_link_args(), [])
self.assertEqual(libhello_nolib.get_compile_args(), [])
- self.assertEqual(libhello_nolib.get_pkgconfig_variable('foo', [], None), 'bar')
- self.assertEqual(libhello_nolib.get_pkgconfig_variable('prefix', [], None), self.prefix)
- if version_compare(PkgConfigDependency.check_pkgconfig(env, libhello_nolib.pkgbin),">=0.29.1"):
- self.assertEqual(libhello_nolib.get_pkgconfig_variable('escaped_var', [], None), r'hello\ world')
- self.assertEqual(libhello_nolib.get_pkgconfig_variable('unescaped_var', [], None), 'hello world')
+ self.assertEqual(libhello_nolib.get_variable(pkgconfig='foo'), 'bar')
+ self.assertEqual(libhello_nolib.get_variable(pkgconfig='prefix'), self.prefix)
+ impl = libhello_nolib.pkgconfig
+ if not isinstance(impl, PkgConfigCLI) or version_compare(impl.pkgbin_version, ">=0.29.1"):
+ self.assertEqual(libhello_nolib.get_variable(pkgconfig='escaped_var'), r'hello\ world')
+ self.assertEqual(libhello_nolib.get_variable(pkgconfig='unescaped_var'), 'hello world')
cc = detect_c_compiler(env, MachineChoice.HOST)
if cc.get_id() in {'gcc', 'clang'}:
has_cpp20 = (compiler.get_id() not in {'clang', 'gcc'} or
compiler.get_id() == 'clang' and _clang_at_least(compiler, '>=10.0.0', None) or
compiler.get_id() == 'gcc' and version_compare(compiler.version, '>=10.0.0'))
+ has_cpp2b = (compiler.get_id() not in {'clang', 'gcc'} or
+ compiler.get_id() == 'clang' and _clang_at_least(compiler, '>=12.0.0', None) or
+ compiler.get_id() == 'gcc' and version_compare(compiler.version, '>=11.0.0'))
+ has_cpp23 = (compiler.get_id() not in {'clang', 'gcc'} or
+ compiler.get_id() == 'clang' and _clang_at_least(compiler, '>=17.0.0', None) or
+ compiler.get_id() == 'gcc' and version_compare(compiler.version, '>=11.0.0'))
+ has_cpp26 = (compiler.get_id() not in {'clang', 'gcc'} or
+ compiler.get_id() == 'clang' and _clang_at_least(compiler, '>=17.0.0', None) or
+ compiler.get_id() == 'gcc' and version_compare(compiler.version, '>=14.0.0'))
has_c18 = (compiler.get_id() not in {'clang', 'gcc'} or
compiler.get_id() == 'clang' and _clang_at_least(compiler, '>=8.0.0', '>=11.0') or
compiler.get_id() == 'gcc' and version_compare(compiler.version, '>=8.0.0'))
continue
elif '++20' in v and not has_cpp20:
continue
+ elif '++2b' in v and not has_cpp2b:
+ continue
+ elif '++23' in v and not has_cpp23:
+ continue
+ elif ('++26' in v or '++2c' in v) and not has_cpp26:
+ continue
# now C
elif '17' in v and not has_cpp2a_c17:
continue
def test_cross_find_program(self):
testdir = os.path.join(self.unit_test_dir, '11 cross prog')
- crossfile = tempfile.NamedTemporaryFile(mode='w')
+ crossfile = tempfile.NamedTemporaryFile(mode='w', encoding='utf-8')
print(os.path.join(testdir, 'some_cross_tool.py'))
tool_path = os.path.join(testdir, 'some_cross_tool.py')
# Regression test: This used to modify the value of `pkg_config_path`
# option, adding the meson-uninstalled directory to it.
- PkgConfigDependency.setup_env({}, env, MachineChoice.HOST, uninstalled=True)
+ PkgConfigInterface.setup_env({}, env, MachineChoice.HOST, uninstalled=True)
pkg_config_path = env.coredata.options[OptionKey('pkg_config_path')].value
self.assertEqual(pkg_config_path, [pkg_dir])
see: https://github.com/mesonbuild/meson/issues/9000
https://stackoverflow.com/questions/48532868/gcc-library-option-with-a-colon-llibevent-a
'''
- testdir = os.path.join(self.unit_test_dir, '97 link full name','libtestprovider')
+ testdir = os.path.join(self.unit_test_dir, '98 link full name','libtestprovider')
oldprefix = self.prefix
# install into installdir without using DESTDIR
installdir = self.installdir
self.new_builddir()
env = {'LIBRARY_PATH': os.path.join(installdir, self.libdir),
'PKG_CONFIG_PATH': _prepend_pkg_config_path(os.path.join(installdir, self.libdir, 'pkgconfig'))}
- testdir = os.path.join(self.unit_test_dir, '97 link full name','proguser')
+ testdir = os.path.join(self.unit_test_dir, '98 link full name','proguser')
self.init(testdir,override_envvars=env)
# test for link with full path
def test_identity_cross(self):
testdir = os.path.join(self.unit_test_dir, '60 identity cross')
- constantsfile = tempfile.NamedTemporaryFile(mode='w')
+ constantsfile = tempfile.NamedTemporaryFile(mode='w', encoding='utf-8')
constantsfile.write(textwrap.dedent('''\
[constants]
py_ext = '.py'
'''))
constantsfile.flush()
- nativefile = tempfile.NamedTemporaryFile(mode='w')
+ nativefile = tempfile.NamedTemporaryFile(mode='w', encoding='utf-8')
nativefile.write(textwrap.dedent('''\
[binaries]
c = ['{}' + py_ext]
nativefile.flush()
self.meson_native_files = [constantsfile.name, nativefile.name]
- crossfile = tempfile.NamedTemporaryFile(mode='w')
+ crossfile = tempfile.NamedTemporaryFile(mode='w', encoding='utf-8')
crossfile.write(textwrap.dedent('''\
[binaries]
c = ['{}' + py_ext]
'CC_FOR_BUILD': '"' + os.path.join(testdir, 'build_wrapper.py') + '"',
'CC': '"' + os.path.join(testdir, 'host_wrapper.py') + '"',
}
- crossfile = tempfile.NamedTemporaryFile(mode='w')
+ crossfile = tempfile.NamedTemporaryFile(mode='w', encoding='utf-8')
crossfile.write('')
crossfile.flush()
self.meson_cross_files = [crossfile.name]
@skipUnless(is_linux() or is_osx(), 'Test only applicable to Linux and macOS')
def test_install_strip(self):
- testdir = os.path.join(self.unit_test_dir, '103 strip')
+ testdir = os.path.join(self.unit_test_dir, '104 strip')
self.init(testdir)
self.build()
self.assertFalse(cpp.compiler_args([f'-isystem{symlink}' for symlink in default_symlinks]).to_native())
def test_freezing(self):
- testdir = os.path.join(self.unit_test_dir, '109 freeze')
+ testdir = os.path.join(self.unit_test_dir, '110 freeze')
self.init(testdir)
self.build()
with self.assertRaises(subprocess.CalledProcessError) as e:
self.run_tests()
self.assertNotIn('Traceback', e.exception.output)
+
+ @skipUnless(is_linux(), "Ninja file differs on different platforms")
+ def test_complex_link_cases(self):
+ testdir = os.path.join(self.unit_test_dir, '114 complex link cases')
+ self.init(testdir)
+ self.build()
+ with open(os.path.join(self.builddir, 'build.ninja'), encoding='utf-8') as f:
+ content = f.read()
+ # Verify link dependencies, see comments in meson.build.
+ self.assertIn('build libt1-s3.a: STATIC_LINKER libt1-s2.a.p/s2.c.o libt1-s3.a.p/s3.c.o\n', content)
+ self.assertIn('build t1-e1: c_LINKER t1-e1.p/main.c.o | libt1-s1.a libt1-s3.a\n', content)
+ self.assertIn('build libt2-s3.a: STATIC_LINKER libt2-s2.a.p/s2.c.o libt2-s1.a.p/s1.c.o libt2-s3.a.p/s3.c.o\n', content)
+ self.assertIn('build t2-e1: c_LINKER t2-e1.p/main.c.o | libt2-s3.a\n', content)
+ self.assertIn('build t3-e1: c_LINKER t3-e1.p/main.c.o | libt3-s3.so.p/libt3-s3.so.symbols\n', content)
+ self.assertIn('build t4-e1: c_LINKER t4-e1.p/main.c.o | libt4-s2.so.p/libt4-s2.so.symbols libt4-s3.a\n', content)
+ self.assertIn('build t5-e1: c_LINKER t5-e1.p/main.c.o | libt5-s1.so.p/libt5-s1.so.symbols libt5-s3.a\n', content)
+ self.assertIn('build t6-e1: c_LINKER t6-e1.p/main.c.o | libt6-s2.a libt6-s3.a\n', content)
+ self.assertIn('build t7-e1: c_LINKER t7-e1.p/main.c.o | libt7-s3.a\n', content)
+ self.assertIn('build t8-e1: c_LINKER t8-e1.p/main.c.o | libt8-s1.a libt8-s2.a libt8-s3.a\n', content)
+ self.assertIn('build t9-e1: c_LINKER t9-e1.p/main.c.o | libt9-s1.a libt9-s2.a libt9-s3.a\n', content)
+ self.assertIn('build t12-e1: c_LINKER t12-e1.p/main.c.o | libt12-s1.a libt12-s2.a libt12-s3.a\n', content)
+ self.assertIn('build t13-e1: c_LINKER t13-e1.p/main.c.o | libt12-s1.a libt13-s3.a\n', content)
# See the License for the specific language governing permissions and
# limitations under the License.
+from __future__ import annotations
+
import subprocess
import tempfile
import textwrap
self.current_config = 0
self.current_wrapper = 0
- def helper_create_native_file(self, values):
+ def helper_create_native_file(self, values: T.Dict[str, T.Dict[str, T.Union[str, int, float, bool, T.Sequence[T.Union[str, int, float, bool]]]]]) -> str:
"""Create a config file as a temporary file.
values should be a nested dictionary structure of {section: {key:
for k, v in entries.items():
if isinstance(v, (bool, int, float)):
f.write(f"{k}={v}\n")
- elif isinstance(v, list):
- f.write("{}=[{}]\n".format(k, ', '.join([f"'{w}'" for w in v])))
- else:
+ elif isinstance(v, str):
f.write(f"{k}='{v}'\n")
+ else:
+ f.write("{}=[{}]\n".format(k, ', '.join([f"'{w}'" for w in v])))
return filename
def helper_create_binary_wrapper(self, binary, dir_=None, extra_args=None, **kwargs):
return batfile
def helper_for_compiler(self, lang, cb, for_machine = MachineChoice.HOST):
- """Helper for generating tests for overriding compilers for langaugages
+ """Helper for generating tests for overriding compilers for languages
with more than one implementation, such as C, C++, ObjC, ObjC++, and D.
"""
env = get_fake_env()
def test_java_classpath(self):
if self.backend is not Backend.ninja:
raise SkipTest('Jar is only supported with Ninja')
- testdir = os.path.join(self.unit_test_dir, '110 classpath')
+ testdir = os.path.join(self.unit_test_dir, '112 classpath')
self.init(testdir)
self.build()
one_build_path = get_classpath(os.path.join(self.builddir, 'one.jar'))
else:
self.fail('Did not find bindir in build options?')
+ @skip_if_not_language('rust')
+ def test_bindgen_clang_arguments(self) -> None:
+ if self.backend is not Backend.ninja:
+ raise SkipTest('Rust is only supported with Ninja')
+
+ testcase = os.path.join(self.rust_test_dir, '12 bindgen')
+ config = self.helper_create_native_file({
+ 'properties': {'bindgen_clang_arguments': 'sentinal'}
+ })
+
+ self.init(testcase, extra_args=['--native-file', config])
+ targets: T.List[T.Dict[str, T.Any]] = self.introspect('--targets')
+ for t in targets:
+ if t['id'].startswith('rustmod-bindgen'):
+ args: T.List[str] = t['target_sources'][0]['compiler']
+ self.assertIn('sentinal', args, msg="Did not find machine file value")
+ cargs_start = args.index('--')
+ sent_arg = args.index('sentinal')
+ self.assertLess(cargs_start, sent_arg, msg='sentinal argument does not come after "--"')
+ break
+ else:
+ self.fail('Did not find a bindgen target')
+
class CrossFileTests(BasePlatformTests):
# The test uses mocking and thus requires that the current process is the
# one to run the Meson steps. If we are using an external test executable
# (most commonly in Debian autopkgtests) then the mocking won't work.
- @skipIf('MESON_EXE' in os.environ, 'MESON_EXE is defined, can not use mocking.')
+ @skipIf('MESON_EXE' in os.environ, 'MESON_EXE is defined, cannot use mocking.')
def test_cross_file_system_paths(self):
if is_windows():
raise SkipTest('system crossfile paths not defined for Windows (yet)')
with tempfile.TemporaryDirectory() as d:
dir_ = os.path.join(d, 'meson', 'cross')
os.makedirs(dir_)
- with tempfile.NamedTemporaryFile('w', dir=dir_, delete=False) as f:
+ with tempfile.NamedTemporaryFile('w', dir=dir_, delete=False, encoding='utf-8') as f:
f.write(cross_content)
name = os.path.basename(f.name)
with tempfile.TemporaryDirectory() as d:
dir_ = os.path.join(d, '.local', 'share', 'meson', 'cross')
os.makedirs(dir_)
- with tempfile.NamedTemporaryFile('w', dir=dir_, delete=False) as f:
+ with tempfile.NamedTemporaryFile('w', dir=dir_, delete=False, encoding='utf-8') as f:
f.write(cross_content)
name = os.path.basename(f.name)
# See the License for the specific language governing permissions and
# limitations under the License.
+import json
import os
+import pickle
import tempfile
import subprocess
import textwrap
-from unittest import skipIf
+import shutil
+from unittest import skipIf, SkipTest
from pathlib import Path
from .baseplatformtests import BasePlatformTests
from .helpers import is_ci
-from mesonbuild.mesonlib import is_linux
+from mesonbuild.mesonlib import EnvironmentVariables, ExecutableSerialisation, is_linux, python_command
from mesonbuild.optinterpreter import OptionInterpreter, OptionException
+from run_tests import Backend
@skipIf(is_ci() and not is_linux(), "Run only on fast platforms")
class PlatformAgnosticTests(BasePlatformTests):
Tests that find_program() with a relative path does not find the program
in current workdir.
'''
- testdir = os.path.join(self.unit_test_dir, '100 relative find program')
+ testdir = os.path.join(self.unit_test_dir, '101 relative find program')
self.init(testdir, workdir=testdir)
def test_invalid_option_names(self):
interp.process(fname)
def test_python_dependency_without_pkgconfig(self):
- testdir = os.path.join(self.unit_test_dir, '102 python without pkgconfig')
+ testdir = os.path.join(self.unit_test_dir, '103 python without pkgconfig')
self.init(testdir, override_envvars={'PKG_CONFIG': 'notfound'})
def test_debug_function_outputs_to_meson_log(self):
- testdir = os.path.join(self.unit_test_dir, '104 debug function')
+ testdir = os.path.join(self.unit_test_dir, '105 debug function')
log_msg = 'This is an example debug output, should only end up in debug log'
output = self.init(testdir)
self.assertIn(log_msg, mesonlog)
def test_new_subproject_reconfigure(self):
- testdir = os.path.join(self.unit_test_dir, '107 new subproject on reconfigure')
+ testdir = os.path.join(self.unit_test_dir, '108 new subproject on reconfigure')
self.init(testdir)
self.build()
'''))
subprocess.check_call(self.wrap_command + ['update-db'], cwd=testdir)
self.init(testdir, workdir=testdir)
+
+ def test_none_backend(self):
+ testdir = os.path.join(self.python_test_dir, '7 install path')
+
+ self.init(testdir, extra_args=['--backend=none'], override_envvars={'NINJA': 'absolutely false command'})
+ self.assertPathDoesNotExist(os.path.join(self.builddir, 'build.ninja'))
+
+ self.run_tests(inprocess=True, override_envvars={})
+
+ out = self._run(self.meson_command + ['install', f'--destdir={self.installdir}'], workdir=self.builddir)
+ self.assertNotIn('Only ninja backend is supported to rebuild the project before installation.', out)
+
+ with open(os.path.join(testdir, 'test.json'), 'rb') as f:
+ dat = json.load(f)
+ for i in dat['installed']:
+ self.assertPathExists(os.path.join(self.installdir, i['file']))
+
+ def test_change_backend(self):
+ if self.backend != Backend.ninja:
+ raise SkipTest('Only useful to test if backend is ninja.')
+
+ testdir = os.path.join(self.python_test_dir, '7 install path')
+ self.init(testdir)
+
+ # no-op change works
+ self.setconf(f'--backend=ninja')
+ self.init(testdir, extra_args=['--reconfigure', '--backend=ninja'])
+
+ # Change backend option is not allowed
+ with self.assertRaises(subprocess.CalledProcessError) as cm:
+ self.setconf('-Dbackend=none')
+ self.assertIn("ERROR: Tried modify read only option 'backend'", cm.exception.stdout)
+
+ # Reconfigure with a different backend is not allowed
+ with self.assertRaises(subprocess.CalledProcessError) as cm:
+ self.init(testdir, extra_args=['--reconfigure', '--backend=none'])
+ self.assertIn("ERROR: Tried modify read only option 'backend'", cm.exception.stdout)
+
+ # Wipe with a different backend is allowed
+ self.init(testdir, extra_args=['--wipe', '--backend=none'])
+
+ def test_validate_dirs(self):
+ testdir = os.path.join(self.common_test_dir, '1 trivial')
+
+ # Using parent as builddir should fail
+ self.builddir = os.path.dirname(self.builddir)
+ with self.assertRaises(subprocess.CalledProcessError) as cm:
+ self.init(testdir)
+ self.assertIn('cannot be a parent of source directory', cm.exception.stdout)
+
+ # Reconfigure of empty builddir should work
+ self.new_builddir()
+ self.init(testdir, extra_args=['--reconfigure'])
+
+ # Reconfigure of not empty builddir should work
+ self.new_builddir()
+ Path(self.builddir, 'dummy').touch()
+ self.init(testdir, extra_args=['--reconfigure'])
+
+ # Setup a valid builddir should update options but not reconfigure
+ self.assertEqual(self.getconf('buildtype'), 'debug')
+ o = self.init(testdir, extra_args=['-Dbuildtype=release'])
+ self.assertIn('Directory already configured', o)
+ self.assertNotIn('The Meson build system', o)
+ self.assertEqual(self.getconf('buildtype'), 'release')
+
+ # Wipe of empty builddir should work
+ self.new_builddir()
+ self.init(testdir, extra_args=['--wipe'])
+
+ # Wipe of partial builddir should work
+ self.new_builddir()
+ Path(self.builddir, 'meson-private').mkdir()
+ Path(self.builddir, 'dummy').touch()
+ self.init(testdir, extra_args=['--wipe'])
+
+ # Wipe of not empty builddir should fail
+ self.new_builddir()
+ Path(self.builddir, 'dummy').touch()
+ with self.assertRaises(subprocess.CalledProcessError) as cm:
+ self.init(testdir, extra_args=['--wipe'])
+ self.assertIn('Directory is not empty', cm.exception.stdout)
+
+ def test_scripts_loaded_modules(self):
+ '''
+ Simulate a wrapped command, as done for custom_target() that capture
+ output. The script will print all python modules loaded and we verify
+ that it contains only an acceptable subset. Loading too many modules
+ slows down the build when many custom targets get wrapped.
+
+ This list must not be edited without a clear rationale for why it is
+ acceptable to do so!
+ '''
+ es = ExecutableSerialisation(python_command + ['-c', 'exit(0)'], env=EnvironmentVariables())
+ p = Path(self.builddir, 'exe.dat')
+ with p.open('wb') as f:
+ pickle.dump(es, f)
+ cmd = self.meson_command + ['--internal', 'test_loaded_modules', '--unpickle', str(p)]
+ p = subprocess.run(cmd, stdout=subprocess.PIPE)
+ all_modules = json.loads(p.stdout.splitlines()[0])
+ meson_modules = [m for m in all_modules if m.startswith('mesonbuild')]
+ expected_meson_modules = [
+ 'mesonbuild',
+ 'mesonbuild._pathlib',
+ 'mesonbuild.utils',
+ 'mesonbuild.utils.core',
+ 'mesonbuild.mesonmain',
+ 'mesonbuild.mlog',
+ 'mesonbuild.scripts',
+ 'mesonbuild.scripts.meson_exe',
+ 'mesonbuild.scripts.test_loaded_modules'
+ ]
+ self.assertEqual(sorted(expected_meson_modules), sorted(meson_modules))
+
+ def test_setup_loaded_modules(self):
+ '''
+ Execute a very basic meson.build and capture a list of all python
+ modules loaded. We verify that it contains only an acceptable subset.
+ Loading too many modules slows down `meson setup` startup time and
+ gives a perception that meson is slow.
+
+ Adding more modules to the default startup flow is not an unreasonable
+ thing to do as new features are added, but keeping track of them is
+ good.
+ '''
+ testdir = os.path.join(self.unit_test_dir, '116 empty project')
+
+ self.init(testdir)
+ self._run(self.meson_command + ['--internal', 'regenerate', '--profile-self', testdir, self.builddir])
+ with open(os.path.join(self.builddir, 'meson-logs', 'profile-startup-modules.json'), encoding='utf-8') as f:
+ 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']
+
+ self.assertEqual(data['modules'], expected)
+ self.assertEqual(data['count'], 68)
+
+ def test_meson_package_cache_dir(self):
+ # Copy testdir into temporary directory to not pollute meson source tree.
+ testdir = os.path.join(self.unit_test_dir, '118 meson package cache dir')
+ srcdir = os.path.join(self.builddir, 'srctree')
+ shutil.copytree(testdir, srcdir)
+ builddir = os.path.join(srcdir, '_build')
+ self.change_builddir(builddir)
+ self.init(srcdir, override_envvars={'MESON_PACKAGE_CACHE_DIR': os.path.join(srcdir, 'cache_dir')})
+
+ def test_cmake_openssl_not_found_bug(self):
+ """Issue #12098"""
+ testdir = os.path.join(self.unit_test_dir, '119 openssl cmake bug')
+ self.meson_native_files.append(os.path.join(testdir, 'nativefile.ini'))
+ out = self.init(testdir, allow_fail=True)
+ self.assertNotIn('Unhandled python exception', out)
+
+ def test_error_configuring_subdir(self):
+ testdir = os.path.join(self.common_test_dir, '152 index customtarget')
+ out = self.init(os.path.join(testdir, 'subdir'), allow_fail=True)
+
+ self.assertIn('first statement must be a call to project()', out)
+ # provide guidance diagnostics by finding a file whose first AST statement is project()
+ self.assertIn(f'Did you mean to run meson from the directory: "{testdir}"?', out)
# See the License for the specific language governing permissions and
# limitations under the License.
-import os
-import unittest
-import pathlib
-import subprocess
+import glob, os, pathlib, shutil, subprocess, unittest
from run_tests import (
Backend
)
from .allplatformstests import git_init
-
from .baseplatformtests import BasePlatformTests
-from mesonbuild.mesonlib import TemporaryDirectoryWinProof
+from .helpers import *
+
+from mesonbuild.mesonlib import MachineChoice, TemporaryDirectoryWinProof
+from mesonbuild.modules.python import PythonModule
class PythonTests(BasePlatformTests):
'''
git_init(dirstr)
self.init(dirstr)
subprocess.check_call(self.meson_command + ['dist', '-C', self.builddir], stdout=subprocess.DEVNULL)
+
+ def _test_bytecompile(self, py2=False):
+ testdir = os.path.join(self.src_root, 'test cases', 'python', '2 extmodule')
+
+ env = get_fake_env(testdir, self.builddir, self.prefix)
+ cc = detect_c_compiler(env, MachineChoice.HOST)
+
+ self.init(testdir, extra_args=['-Dpython2=auto', '-Dpython.bytecompile=1'])
+ self.build()
+ self.install()
+
+ count = 0
+ for root, dirs, files in os.walk(self.installdir):
+ for file in files:
+ realfile = os.path.join(root, file)
+ if file.endswith('.py'):
+ cached = glob.glob(realfile+'?') + glob.glob(os.path.join(root, '__pycache__', os.path.splitext(file)[0] + '*.pyc'))
+ if py2 and cc.get_id() == 'msvc':
+ # MSVC python installs python2/python3 into the same directory
+ self.assertLength(cached, 4)
+ else:
+ self.assertLength(cached, 2)
+ count += 1
+ # there are 5 files x 2 installations
+ if py2 and not cc.get_id() == 'msvc':
+ self.assertEqual(count, 10)
+ else:
+ self.assertEqual(count, 5)
+
+ def test_bytecompile_multi(self):
+ if not shutil.which('python2') and not PythonModule._get_win_pythonpath('python2'):
+ raise self.skipTest('python2 not installed')
+ self._test_bytecompile(True)
+
+ def test_bytecompile_single(self):
+ if shutil.which('python2') or PythonModule._get_win_pythonpath('python2'):
+ raise self.skipTest('python2 installed, already tested')
+ self._test_bytecompile()
# limitations under the License.
import subprocess
+from itertools import zip_longest
import json
import os
+from pathlib import Path
import shutil
import unittest
+from mesonbuild.ast import IntrospectionInterpreter, AstIDGenerator
+from mesonbuild.ast.printer import RawPrinter
from mesonbuild.mesonlib import windows_proof_rmtree
from .baseplatformtests import BasePlatformTests
}
self.assertDictEqual(out, expected)
- def test_tatrget_add(self):
+ def test_target_add(self):
self.prime('1 basic')
self.rewrite(self.builddir, os.path.join(self.builddir, 'addTgt.json'))
out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json'))
# Check the written file
out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json'))
self.assertDictEqual(out, expected)
+
+ def test_raw_printer_is_idempotent(self):
+ test_path = Path(self.unit_test_dir, '120 rewrite')
+ meson_build_file = test_path / 'meson.build'
+ # original_contents = meson_build_file.read_bytes()
+ original_contents = meson_build_file.read_text(encoding='utf-8')
+
+ interpreter = IntrospectionInterpreter(test_path, '', 'ninja', visitors = [AstIDGenerator()])
+ interpreter.analyze()
+
+ printer = RawPrinter()
+ interpreter.ast.accept(printer)
+ # new_contents = printer.result.encode('utf-8')
+ new_contents = printer.result
+
+ # Do it line per line because it is easier to debug like that
+ for orig_line, new_line in zip_longest(original_contents.splitlines(), new_contents.splitlines()):
+ self.assertEqual(orig_line, new_line)
self.assertEqual(self._git_local_commit(subp_name), self._git_remote_commit(subp_name, 'newbranch'))
self.assertTrue(self._git_local(['stash', 'list'], subp_name))
+ # Untracked files need to be stashed too, or (re-)applying a patch
+ # creating one of those untracked files will fail.
+ untracked = self.subprojects_dir / subp_name / 'untracked.c'
+ untracked.write_bytes(b'int main(void) { return 0; }')
+ self._subprojects_cmd(['update', '--reset'])
+ self.assertTrue(self._git_local(['stash', 'list'], subp_name))
+ assert not untracked.exists()
+ # Ensure it was indeed stashed, and we can get it back.
+ self.assertTrue(self._git_local(['stash', 'pop'], subp_name))
+ assert untracked.exists()
+
# Create a new remote tag and update the wrap file. Checks that
# "meson subprojects update --reset" checkout the new tag in detached mode.
self._git_create_remote_tag(subp_name, 'newtag')
# to the right reason).
return
self.build()
+
+ @skipIf(is_cygwin(), 'Test only applicable to Windows')
+ def test_genvslite(self):
+ # The test framework itself might be forcing a specific, non-ninja backend across a set of tests, which
+ # includes this test. E.g. -
+ # > python.exe run_unittests.py --backend=vs WindowsTests
+ # Since that explicitly specifies a backend that's incompatible with (and essentially meaningless in
+ # conjunction with) 'genvslite', we should skip further genvslite testing.
+ if self.backend is not Backend.ninja:
+ raise SkipTest('Test only applies when using the Ninja backend')
+
+ testdir = os.path.join(self.unit_test_dir, '117 genvslite')
+
+ env = get_fake_env(testdir, self.builddir, self.prefix)
+ cc = detect_c_compiler(env, MachineChoice.HOST)
+ if cc.get_argument_syntax() != 'msvc':
+ raise SkipTest('Test only applies when MSVC tools are available.')
+
+ # We want to run the genvslite setup. I.e. -
+ # meson setup --genvslite vs2022 ...
+ # which we should expect to generate the set of _debug/_debugoptimized/_release suffixed
+ # build directories. Then we want to check that the solution/project build hooks (like clean,
+ # build, and rebuild) end up ultimately invoking the 'meson compile ...' of the appropriately
+ # suffixed build dir, for which we need to use 'msbuild.exe'
+
+ # Find 'msbuild.exe'
+ msbuildprog = ExternalProgram('msbuild.exe')
+ self.assertTrue(msbuildprog.found(), msg='msbuild.exe not found')
+
+ # Setup with '--genvslite ...'
+ self.new_builddir()
+
+ # Firstly, we'd like to check that meson errors if the user explicitly specifies a non-ninja backend
+ # during setup.
+ with self.assertRaises(subprocess.CalledProcessError) as cm:
+ self.init(testdir, extra_args=['--genvslite', 'vs2022', '--backend', 'vs'])
+ self.assertIn("specifying a non-ninja backend conflicts with a 'genvslite' setup", cm.exception.stdout)
+
+ # Wrap the following bulk of setup and msbuild invocation testing in a try-finally because any exception,
+ # failure, or success must always clean up any of the suffixed build dir folders that may have been generated.
+ try:
+ # Since this
+ self.init(testdir, extra_args=['--genvslite', 'vs2022'])
+ # We need to bear in mind that the BasePlatformTests framework creates and cleans up its own temporary
+ # build directory. However, 'genvslite' creates a set of suffixed build directories which we'll have
+ # to clean up ourselves. See 'finally' block below.
+
+ # We intentionally skip the -
+ # self.build()
+ # step because we're wanting to test compilation/building through the solution/project's interface.
+
+ # Execute the debug and release builds through the projects 'Build' hooks
+ genvslite_vcxproj_path = str(os.path.join(self.builddir+'_vs', 'genvslite@exe.vcxproj'))
+ # This use-case of invoking the .sln/.vcxproj build hooks, not through Visual Studio itself, but through
+ # 'msbuild.exe', in a VS tools command prompt environment (e.g. "x64 Native Tools Command Prompt for VS 2022"), is a
+ # problem: Such an environment sets the 'VSINSTALLDIR' variable which, mysteriously, has the side-effect of causing
+ # the spawned 'meson compile' command to fail to find 'ninja' (and even when ninja can be found elsewhere, all the
+ # compiler binaries that ninja wants to run also fail to be found). The PATH environment variable in the child python
+ # (and ninja) processes are fundamentally stripped down of all the critical search paths required to run the ninja
+ # compile work ... ONLY when 'VSINSTALLDIR' is set; without 'VSINSTALLDIR' set, the meson compile command does search
+ # for and find ninja (ironically, it finds it under the path where VSINSTALLDIR pointed!).
+ # For the above reason, this testing works around this bizarre behaviour by temporarily removing any 'VSINSTALLDIR'
+ # variable, prior to invoking the builds -
+ current_env = os.environ.copy()
+ current_env.pop('VSINSTALLDIR', None)
+ subprocess.check_call(
+ ['msbuild', '-target:Build', '-property:Configuration=debug', genvslite_vcxproj_path],
+ stdout=subprocess.DEVNULL,
+ stderr=subprocess.DEVNULL,
+ env=current_env)
+ subprocess.check_call(
+ ['msbuild', '-target:Build', '-property:Configuration=release', genvslite_vcxproj_path],
+ stdout=subprocess.DEVNULL,
+ stderr=subprocess.DEVNULL,
+ env=current_env)
+
+ # Check this has actually built the appropriate exes
+ output_debug = subprocess.check_output(str(os.path.join(self.builddir+'_debug', 'genvslite.exe')))
+ self.assertEqual( output_debug, b'Debug\r\n' )
+ output_release = subprocess.check_output(str(os.path.join(self.builddir+'_release', 'genvslite.exe')))
+ self.assertEqual( output_release, b'Non-debug\r\n' )
+
+ finally:
+ # Clean up our special suffixed temporary build dirs
+ suffixed_build_dirs = glob(self.builddir+'_*', recursive=False)
+ for build_dir in suffixed_build_dirs:
+ shutil.rmtree(build_dir)
def test_install_pdb_introspection(self):
testdir = os.path.join(self.platform_test_dir, '1 basic')