|
|
|
#!/usr/bin/env python3
|
|
|
|
# Copyright 2016-2017 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.
|
|
|
|
|
|
|
|
import stat
|
|
|
|
import subprocess
|
|
|
|
import re
|
|
|
|
import json
|
|
|
|
import tempfile
|
cross: Implement support for loading cross files from system paths
One thing that makes cross compiling with meson a pain is the need for
cross files. The problem is not with cross files themselves (they're
actually rather brilliant in that they allow for a much greater deal of
flexibility than autotools hardcoded paths approach) but that each user
needs to reimplement them themselves, when for most people what they
really want is a cross file that could be provided by their distro, all
they really want is the correct toolchain.
This patch is the first stop to making it easier for distros to ship
their own cross files (and for users to put their's somewhere safe so
they don't get `git clean`ed. It allows the cross files (on Linux and
*BSD) to be stored in home and system paths (~/.config/meson/cross,
/usr/share/meson/cross, and /usr/local/share/meson/cross), and to be
loaded by simply by specificying --cross-file.
With this patch meson will check the locations its always checked first,
(is cross file absolute, or is it relative to $PWD), then will check
~/.config/meson/cross, /usr/local/share/meson/cross,
/usr/share/meson/cross, (or $XDG_CONFIG_PATH and $XDG_DATA_DIRS) for the
files, raising an exception if it cannot find the specified cross file.
Fixes #2283
7 years ago
|
|
|
import textwrap
|
|
|
|
import os
|
|
|
|
import shutil
|
|
|
|
import sys
|
|
|
|
import unittest
|
|
|
|
import platform
|
|
|
|
import pickle
|
|
|
|
import functools
|
|
|
|
import io
|
|
|
|
import operator
|
|
|
|
import threading
|
|
|
|
import urllib.error
|
|
|
|
import urllib.request
|
|
|
|
import zipfile
|
|
|
|
from itertools import chain
|
cross: Implement support for loading cross files from system paths
One thing that makes cross compiling with meson a pain is the need for
cross files. The problem is not with cross files themselves (they're
actually rather brilliant in that they allow for a much greater deal of
flexibility than autotools hardcoded paths approach) but that each user
needs to reimplement them themselves, when for most people what they
really want is a cross file that could be provided by their distro, all
they really want is the correct toolchain.
This patch is the first stop to making it easier for distros to ship
their own cross files (and for users to put their's somewhere safe so
they don't get `git clean`ed. It allows the cross files (on Linux and
*BSD) to be stored in home and system paths (~/.config/meson/cross,
/usr/share/meson/cross, and /usr/local/share/meson/cross), and to be
loaded by simply by specificying --cross-file.
With this patch meson will check the locations its always checked first,
(is cross file absolute, or is it relative to $PWD), then will check
~/.config/meson/cross, /usr/local/share/meson/cross,
/usr/share/meson/cross, (or $XDG_CONFIG_PATH and $XDG_DATA_DIRS) for the
files, raising an exception if it cannot find the specified cross file.
Fixes #2283
7 years ago
|
|
|
from unittest import mock
|
|
|
|
from configparser import ConfigParser
|
|
|
|
from contextlib import contextmanager
|
|
|
|
from glob import glob
|
|
|
|
from pathlib import (PurePath, Path)
|
|
|
|
from distutils.dir_util import copy_tree
|
|
|
|
|
|
|
|
import mesonbuild.mlog
|
|
|
|
import mesonbuild.depfile
|
|
|
|
import mesonbuild.compilers
|
|
|
|
import mesonbuild.environment
|
|
|
|
import mesonbuild.mesonlib
|
|
|
|
import mesonbuild.coredata
|
|
|
|
import mesonbuild.modules.gnome
|
|
|
|
from mesonbuild.interpreter import Interpreter, ObjectHolder
|
|
|
|
from mesonbuild.ast import AstInterpreter
|
|
|
|
from mesonbuild.mesonlib import (
|
|
|
|
BuildDirLock, LibType, MachineChoice, PerMachine, Version, is_windows,
|
|
|
|
is_osx, is_cygwin, is_dragonflybsd, is_openbsd, is_haiku, is_sunos,
|
|
|
|
windows_proof_rmtree, python_command, version_compare, split_args,
|
|
|
|
quote_arg
|
|
|
|
)
|
|
|
|
from mesonbuild.environment import detect_ninja
|
|
|
|
from mesonbuild.mesonlib import MesonException, EnvironmentException
|
|
|
|
from mesonbuild.dependencies import PkgConfigDependency, ExternalProgram
|
|
|
|
import mesonbuild.dependencies.base
|
|
|
|
from mesonbuild.build import Target
|
|
|
|
import mesonbuild.modules.pkgconfig
|
|
|
|
|
|
|
|
from mesonbuild.mtest import TAPParser, TestResult
|
|
|
|
|
|
|
|
from run_tests import (
|
|
|
|
Backend, FakeBuild, FakeCompilerOptions,
|
|
|
|
ensure_backend_detects_changes, exe_suffix, get_backend_commands,
|
|
|
|
get_builddir_target_args, get_fake_env, get_fake_options, get_meson_script,
|
|
|
|
run_configure_inprocess, run_mtest_inprocess
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
URLOPEN_TIMEOUT = 5
|
|
|
|
|
|
|
|
|
|
|
|
def get_dynamic_section_entry(fname, entry):
|
|
|
|
if is_cygwin() or is_osx():
|
|
|
|
raise unittest.SkipTest('Test only applicable to ELF platforms')
|
|
|
|
|
|
|
|
try:
|
|
|
|
raw_out = subprocess.check_output(['readelf', '-d', fname],
|
|
|
|
universal_newlines=True)
|
|
|
|
except FileNotFoundError:
|
|
|
|
# FIXME: Try using depfixer.py:Elf() as a fallback
|
|
|
|
raise unittest.SkipTest('readelf not found')
|
|
|
|
pattern = re.compile(entry + r': \[(.*?)\]')
|
|
|
|
for line in raw_out.split('\n'):
|
|
|
|
m = pattern.search(line)
|
|
|
|
if m is not None:
|
|
|
|
return m.group(1)
|
|
|
|
return None # The file did not contain the specified entry.
|
|
|
|
|
|
|
|
def get_soname(fname):
|
|
|
|
return get_dynamic_section_entry(fname, 'soname')
|
|
|
|
|
|
|
|
def get_rpath(fname):
|
|
|
|
return get_dynamic_section_entry(fname, r'(?:rpath|runpath)')
|
|
|
|
|
|
|
|
def is_tarball():
|
|
|
|
if not os.path.isdir('docs'):
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
def is_ci():
|
|
|
|
if 'CI' in os.environ:
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
def is_pull():
|
|
|
|
# Travis
|
|
|
|
if os.environ.get('TRAVIS_PULL_REQUEST', 'false') != 'false':
|
|
|
|
return True
|
|
|
|
# Azure
|
|
|
|
if 'SYSTEM_PULLREQUEST_ISFORK' in os.environ:
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
def _git_init(project_dir):
|
|
|
|
subprocess.check_call(['git', 'init'], cwd=project_dir, stdout=subprocess.DEVNULL)
|
|
|
|
subprocess.check_call(['git', 'config',
|
|
|
|
'user.name', 'Author Person'], cwd=project_dir)
|
|
|
|
subprocess.check_call(['git', 'config',
|
|
|
|
'user.email', 'teh_coderz@example.com'], cwd=project_dir)
|
|
|
|
subprocess.check_call('git add *', cwd=project_dir, shell=True,
|
|
|
|
stdout=subprocess.DEVNULL)
|
|
|
|
subprocess.check_call(['git', 'commit', '-a', '-m', 'I am a project'], cwd=project_dir,
|
|
|
|
stdout=subprocess.DEVNULL)
|
|
|
|
|
|
|
|
@functools.lru_cache()
|
|
|
|
def is_real_gnu_compiler(path):
|
|
|
|
'''
|
|
|
|
Check if the gcc we have is a real gcc and not a macOS wrapper around clang
|
|
|
|
'''
|
|
|
|
if not path:
|
|
|
|
return False
|
|
|
|
out = subprocess.check_output([path, '--version'], universal_newlines=True, stderr=subprocess.STDOUT)
|
|
|
|
return 'Free Software Foundation' in out
|
|
|
|
|
|
|
|
def skipIfNoExecutable(exename):
|
|
|
|
'''
|
|
|
|
Skip this test if the given executable is not found.
|
|
|
|
'''
|
|
|
|
def wrapper(func):
|
|
|
|
@functools.wraps(func)
|
|
|
|
def wrapped(*args, **kwargs):
|
|
|
|
if shutil.which(exename) is None:
|
|
|
|
raise unittest.SkipTest(exename + ' not found')
|
|
|
|
return func(*args, **kwargs)
|
|
|
|
return wrapped
|
|
|
|
return wrapper
|
|
|
|
|
|
|
|
def skipIfNoPkgconfig(f):
|
|
|
|
'''
|
|
|
|
Skip this test if no pkg-config is found, unless we're on CI.
|
|
|
|
This allows users to run our test suite without having
|
|
|
|
pkg-config installed on, f.ex., macOS, while ensuring that our CI does not
|
|
|
|
silently skip the test because of misconfiguration.
|
|
|
|
|
|
|
|
Note: Yes, we provide pkg-config even while running Windows CI
|
|
|
|
'''
|
|
|
|
@functools.wraps(f)
|
|
|
|
def wrapped(*args, **kwargs):
|
|
|
|
if not is_ci() and shutil.which('pkg-config') is None:
|
|
|
|
raise unittest.SkipTest('pkg-config not found')
|
|
|
|
return f(*args, **kwargs)
|
|
|
|
return wrapped
|
|
|
|
|
|
|
|
def skipIfNoPkgconfigDep(depname):
|
|
|
|
'''
|
|
|
|
Skip this test if the given pkg-config dep is not found, unless we're on CI.
|
|
|
|
'''
|
|
|
|
def wrapper(func):
|
|
|
|
@functools.wraps(func)
|
|
|
|
def wrapped(*args, **kwargs):
|
|
|
|
if not is_ci() and shutil.which('pkg-config') is None:
|
|
|
|
raise unittest.SkipTest('pkg-config not found')
|
|
|
|
if not is_ci() and subprocess.call(['pkg-config', '--exists', depname]) != 0:
|
|
|
|
raise unittest.SkipTest('pkg-config dependency {} not found.'.format(depname))
|
|
|
|
return func(*args, **kwargs)
|
|
|
|
return wrapped
|
|
|
|
return wrapper
|
|
|
|
|
|
|
|
def skip_if_no_cmake(f):
|
|
|
|
'''
|
|
|
|
Skip this test if no cmake is found, unless we're on CI.
|
|
|
|
This allows users to run our test suite without having
|
|
|
|
cmake installed on, f.ex., macOS, while ensuring that our CI does not
|
|
|
|
silently skip the test because of misconfiguration.
|
|
|
|
'''
|
|
|
|
@functools.wraps(f)
|
|
|
|
def wrapped(*args, **kwargs):
|
|
|
|
if not is_ci() and shutil.which('cmake') is None:
|
|
|
|
raise unittest.SkipTest('cmake not found')
|
|
|
|
return f(*args, **kwargs)
|
|
|
|
return wrapped
|
|
|
|
|
|
|
|
def skip_if_not_language(lang):
|
|
|
|
def wrapper(func):
|
|
|
|
@functools.wraps(func)
|
|
|
|
def wrapped(*args, **kwargs):
|
|
|
|
try:
|
|
|
|
env = get_fake_env()
|
|
|
|
f = getattr(env, 'detect_{}_compiler'.format(lang))
|
|
|
|
f(MachineChoice.HOST)
|
|
|
|
except EnvironmentException:
|
|
|
|
raise unittest.SkipTest('No {} compiler found.'.format(lang))
|
|
|
|
return func(*args, **kwargs)
|
|
|
|
return wrapped
|
|
|
|
return wrapper
|
|
|
|
|
|
|
|
def skip_if_env_set(key):
|
|
|
|
'''
|
|
|
|
Skip a test if a particular env is set, except when running under CI
|
|
|
|
'''
|
|
|
|
def wrapper(func):
|
|
|
|
@functools.wraps(func)
|
|
|
|
def wrapped(*args, **kwargs):
|
|
|
|
old = None
|
|
|
|
if key in os.environ:
|
|
|
|
if not is_ci():
|
|
|
|
raise unittest.SkipTest('Env var {!r} set, skipping'.format(key))
|
|
|
|
old = os.environ.pop(key)
|
|
|
|
try:
|
|
|
|
return func(*args, **kwargs)
|
|
|
|
finally:
|
|
|
|
if old is not None:
|
|
|
|
os.environ[key] = old
|
|
|
|
return wrapped
|
|
|
|
return wrapper
|
|
|
|
|
|
|
|
def skip_if_not_base_option(feature):
|
|
|
|
"""Skip tests if The compiler does not support a given base option.
|
|
|
|
|
|
|
|
for example, ICC doesn't currently support b_sanitize.
|
|
|
|
"""
|
|
|
|
def actual(f):
|
|
|
|
@functools.wraps(f)
|
|
|
|
def wrapped(*args, **kwargs):
|
|
|
|
env = get_fake_env()
|
|
|
|
cc = env.detect_c_compiler(MachineChoice.HOST)
|
|
|
|
if feature not in cc.base_options:
|
|
|
|
raise unittest.SkipTest(
|
|
|
|
'{} not available with {}'.format(feature, cc.id))
|
|
|
|
return f(*args, **kwargs)
|
|
|
|
return wrapped
|
|
|
|
return actual
|
|
|
|
|
|
|
|
|
|
|
|
@contextmanager
|
|
|
|
def temp_filename():
|
|
|
|
'''A context manager which provides a filename to an empty temporary file.
|
|
|
|
|
|
|
|
On exit the file will be deleted.
|
|
|
|
'''
|
|
|
|
|
|
|
|
fd, filename = tempfile.mkstemp()
|
|
|
|
os.close(fd)
|
|
|
|
try:
|
|
|
|
yield filename
|
|
|
|
finally:
|
|
|
|
try:
|
|
|
|
os.remove(filename)
|
|
|
|
except OSError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
@contextmanager
|
|
|
|
def no_pkgconfig():
|
|
|
|
'''
|
|
|
|
A context manager that overrides shutil.which and ExternalProgram to force
|
|
|
|
them to return None for pkg-config to simulate it not existing.
|
|
|
|
'''
|
|
|
|
old_which = shutil.which
|
|
|
|
old_search = ExternalProgram._search
|
|
|
|
|
|
|
|
def new_search(self, name, search_dir):
|
|
|
|
if name == 'pkg-config':
|
|
|
|
return [None]
|
|
|
|
return old_search(self, name, search_dir)
|
|
|
|
|
|
|
|
def new_which(cmd, *kwargs):
|
|
|
|
if cmd == 'pkg-config':
|
|
|
|
return None
|
|
|
|
return old_which(cmd, *kwargs)
|
|
|
|
|
|
|
|
shutil.which = new_which
|
|
|
|
ExternalProgram._search = new_search
|
|
|
|
try:
|
|
|
|
yield
|
|
|
|
finally:
|
|
|
|
shutil.which = old_which
|
|
|
|
ExternalProgram._search = old_search
|
|
|
|
|
|
|
|
|
|
|
|
class InternalTests(unittest.TestCase):
|
|
|
|
|
|
|
|
def test_version_number(self):
|
|
|
|
searchfunc = mesonbuild.environment.search_version
|
|
|
|
self.assertEqual(searchfunc('foobar 1.2.3'), '1.2.3')
|
|
|
|
self.assertEqual(searchfunc('1.2.3'), '1.2.3')
|
|
|
|
self.assertEqual(searchfunc('foobar 2016.10.28 1.2.3'), '1.2.3')
|
|
|
|
self.assertEqual(searchfunc('2016.10.28 1.2.3'), '1.2.3')
|
|
|
|
self.assertEqual(searchfunc('foobar 2016.10.128'), 'unknown version')
|
|
|
|
self.assertEqual(searchfunc('2016.10.128'), 'unknown version')
|
|
|
|
|
|
|
|
def test_mode_symbolic_to_bits(self):
|
|
|
|
modefunc = mesonbuild.mesonlib.FileMode.perms_s_to_bits
|
|
|
|
self.assertEqual(modefunc('---------'), 0)
|
|
|
|
self.assertEqual(modefunc('r--------'), stat.S_IRUSR)
|
|
|
|
self.assertEqual(modefunc('---r-----'), stat.S_IRGRP)
|
|
|
|
self.assertEqual(modefunc('------r--'), stat.S_IROTH)
|
|
|
|
self.assertEqual(modefunc('-w-------'), stat.S_IWUSR)
|
|
|
|
self.assertEqual(modefunc('----w----'), stat.S_IWGRP)
|
|
|
|
self.assertEqual(modefunc('-------w-'), stat.S_IWOTH)
|
|
|
|
self.assertEqual(modefunc('--x------'), stat.S_IXUSR)
|
|
|
|
self.assertEqual(modefunc('-----x---'), stat.S_IXGRP)
|
|
|
|
self.assertEqual(modefunc('--------x'), stat.S_IXOTH)
|
|
|
|
self.assertEqual(modefunc('--S------'), stat.S_ISUID)
|
|
|
|
self.assertEqual(modefunc('-----S---'), stat.S_ISGID)
|
|
|
|
self.assertEqual(modefunc('--------T'), stat.S_ISVTX)
|
|
|
|
self.assertEqual(modefunc('--s------'), stat.S_ISUID | stat.S_IXUSR)
|
|
|
|
self.assertEqual(modefunc('-----s---'), stat.S_ISGID | stat.S_IXGRP)
|
|
|
|
self.assertEqual(modefunc('--------t'), stat.S_ISVTX | stat.S_IXOTH)
|
|
|
|
self.assertEqual(modefunc('rwx------'), stat.S_IRWXU)
|
|
|
|
self.assertEqual(modefunc('---rwx---'), stat.S_IRWXG)
|
|
|
|
self.assertEqual(modefunc('------rwx'), stat.S_IRWXO)
|
|
|
|
# We could keep listing combinations exhaustively but that seems
|
|
|
|
# tedious and pointless. Just test a few more.
|
|
|
|
self.assertEqual(modefunc('rwxr-xr-x'),
|
|
|
|
stat.S_IRWXU |
|
|
|
|
stat.S_IRGRP | stat.S_IXGRP |
|
|
|
|
stat.S_IROTH | stat.S_IXOTH)
|
|
|
|
self.assertEqual(modefunc('rw-r--r--'),
|
|
|
|
stat.S_IRUSR | stat.S_IWUSR |
|
|
|
|
stat.S_IRGRP |
|
|
|
|
stat.S_IROTH)
|
|
|
|
self.assertEqual(modefunc('rwsr-x---'),
|
|
|
|
stat.S_IRWXU | stat.S_ISUID |
|
|
|
|
stat.S_IRGRP | stat.S_IXGRP)
|
|
|
|
|
|
|
|
def test_compiler_args_class(self):
|
|
|
|
cargsfunc = mesonbuild.compilers.CompilerArgs
|
|
|
|
cc = mesonbuild.compilers.CCompiler([], 'fake', False, MachineChoice.HOST, mock.Mock())
|
|
|
|
# Test that empty initialization works
|
|
|
|
a = cargsfunc(cc)
|
|
|
|
self.assertEqual(a, [])
|
|
|
|
# Test that list initialization works
|
|
|
|
a = cargsfunc(cc, ['-I.', '-I..'])
|
|
|
|
self.assertEqual(a, ['-I.', '-I..'])
|
|
|
|
# Test that there is no de-dup on initialization
|
|
|
|
self.assertEqual(cargsfunc(cc, ['-I.', '-I.']), ['-I.', '-I.'])
|
|
|
|
|
|
|
|
## Test that appending works
|
|
|
|
a.append('-I..')
|
|
|
|
self.assertEqual(a, ['-I..', '-I.'])
|
|
|
|
a.append('-O3')
|
|
|
|
self.assertEqual(a, ['-I..', '-I.', '-O3'])
|
|
|
|
|
|
|
|
## Test that in-place addition works
|
|
|
|
a += ['-O2', '-O2']
|
|
|
|
self.assertEqual(a, ['-I..', '-I.', '-O3', '-O2', '-O2'])
|
|
|
|
# Test that removal works
|
|
|
|
a.remove('-O2')
|
|
|
|
self.assertEqual(a, ['-I..', '-I.', '-O3', '-O2'])
|
|
|
|
# Test that de-dup happens on addition
|
|
|
|
a += ['-Ifoo', '-Ifoo']
|
|
|
|
self.assertEqual(a, ['-Ifoo', '-I..', '-I.', '-O3', '-O2'])
|
|
|
|
|
|
|
|
# .extend() is just +=, so we don't test it
|
|
|
|
|
|
|
|
## Test that addition works
|
|
|
|
# Test that adding a list with just one old arg works and yields the same array
|
|
|
|
a = a + ['-Ifoo']
|
|
|
|
self.assertEqual(a, ['-Ifoo', '-I..', '-I.', '-O3', '-O2'])
|
|
|
|
# Test that adding a list with one arg new and one old works
|
|
|
|
a = a + ['-Ifoo', '-Ibaz']
|
|
|
|
self.assertEqual(a, ['-Ifoo', '-Ibaz', '-I..', '-I.', '-O3', '-O2'])
|
|
|
|
# Test that adding args that must be prepended and appended works
|
|
|
|
a = a + ['-Ibar', '-Wall']
|
|
|
|
self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-O3', '-O2', '-Wall'])
|
|
|
|
|
|
|
|
## Test that reflected addition works
|
|
|
|
# Test that adding to a list with just one old arg works and yields the same array
|
|
|
|
a = ['-Ifoo'] + a
|
|
|
|
self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-O3', '-O2', '-Wall'])
|
|
|
|
# Test that adding to a list with just one new arg that is not pre-pended works
|
|
|
|
a = ['-Werror'] + a
|
|
|
|
self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-Werror', '-O3', '-O2', '-Wall'])
|
|
|
|
# Test that adding to a list with two new args preserves the order
|
|
|
|
a = ['-Ldir', '-Lbah'] + a
|
|
|
|
self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-Ldir', '-Lbah', '-Werror', '-O3', '-O2', '-Wall'])
|
|
|
|
# Test that adding to a list with old args does nothing
|
|
|
|
a = ['-Ibar', '-Ibaz', '-Ifoo'] + a
|
|
|
|
self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-Ldir', '-Lbah', '-Werror', '-O3', '-O2', '-Wall'])
|
|
|
|
|
|
|
|
## Test that adding libraries works
|
|
|
|
l = cargsfunc(cc, ['-Lfoodir', '-lfoo'])
|
|
|
|
self.assertEqual(l, ['-Lfoodir', '-lfoo'])
|
|
|
|
# Adding a library and a libpath appends both correctly
|
|
|
|
l += ['-Lbardir', '-lbar']
|
|
|
|
self.assertEqual(l, ['-Lbardir', '-Lfoodir', '-lfoo', '-lbar'])
|
|
|
|
# Adding the same library again does nothing
|
|
|
|
l += ['-lbar']
|
|
|
|
self.assertEqual(l, ['-Lbardir', '-Lfoodir', '-lfoo', '-lbar'])
|
|
|
|
|
|
|
|
## Test that 'direct' append and extend works
|
|
|
|
l = cargsfunc(cc, ['-Lfoodir', '-lfoo'])
|
|
|
|
self.assertEqual(l, ['-Lfoodir', '-lfoo'])
|
|
|
|
# Direct-adding a library and a libpath appends both correctly
|
|
|
|
l.extend_direct(['-Lbardir', '-lbar'])
|
|
|
|
self.assertEqual(l, ['-Lfoodir', '-lfoo', '-Lbardir', '-lbar'])
|
|
|
|
# Direct-adding the same library again still adds it
|
|
|
|
l.append_direct('-lbar')
|
|
|
|
self.assertEqual(l, ['-Lfoodir', '-lfoo', '-Lbardir', '-lbar', '-lbar'])
|
|
|
|
# Direct-adding with absolute path deduplicates
|
|
|
|
l.append_direct('/libbaz.a')
|
|
|
|
self.assertEqual(l, ['-Lfoodir', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a'])
|
|
|
|
# Adding libbaz again does nothing
|
|
|
|
l.append_direct('/libbaz.a')
|
|
|
|
self.assertEqual(l, ['-Lfoodir', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a'])
|
|
|
|
|
|
|
|
def test_compiler_args_class_gnuld(self):
|
|
|
|
cargsfunc = mesonbuild.compilers.CompilerArgs
|
|
|
|
## Test --start/end-group
|
|
|
|
linker = mesonbuild.linkers.GnuDynamicLinker([], MachineChoice.HOST, 'fake', '-Wl,', [])
|
|
|
|
gcc = mesonbuild.compilers.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']
|
|
|
|
## Test that 'direct' append and extend works
|
|
|
|
l = cargsfunc(gcc, ['-Lfoodir', '-lfoo'])
|
|
|
|
self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Wl,--end-group'])
|
|
|
|
# Direct-adding a library and a libpath appends both correctly
|
|
|
|
l.extend_direct(['-Lbardir', '-lbar'])
|
|
|
|
self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-Wl,--end-group'])
|
|
|
|
# Direct-adding the same library again still adds it
|
|
|
|
l.append_direct('-lbar')
|
|
|
|
self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '-Wl,--end-group'])
|
|
|
|
# Direct-adding with absolute path deduplicates
|
|
|
|
l.append_direct('/libbaz.a')
|
|
|
|
self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a', '-Wl,--end-group'])
|
|
|
|
# Adding libbaz again does nothing
|
|
|
|
l.append_direct('/libbaz.a')
|
|
|
|
self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a', '-Wl,--end-group'])
|
|
|
|
# Adding a non-library argument doesn't include it in the group
|
|
|
|
l += ['-Lfoo', '-Wl,--export-dynamic']
|
|
|
|
self.assertEqual(l.to_native(copy=True), ['-Lfoo', '-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a', '-Wl,--end-group', '-Wl,--export-dynamic'])
|
|
|
|
# -Wl,-lfoo is detected as a library and gets added to the group
|
|
|
|
l.append('-Wl,-ldl')
|
|
|
|
self.assertEqual(l.to_native(copy=True), ['-Lfoo', '-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a', '-Wl,--export-dynamic', '-Wl,-ldl', '-Wl,--end-group'])
|
|
|
|
|
|
|
|
def test_compiler_args_remove_system(self):
|
|
|
|
cargsfunc = mesonbuild.compilers.CompilerArgs
|
|
|
|
## Test --start/end-group
|
|
|
|
linker = mesonbuild.linkers.GnuDynamicLinker([], MachineChoice.HOST, 'fake', '-Wl,', [])
|
|
|
|
gcc = mesonbuild.compilers.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']
|
|
|
|
## Test that 'direct' append and extend works
|
|
|
|
l = cargsfunc(gcc, ['-Lfoodir', '-lfoo'])
|
|
|
|
self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Wl,--end-group'])
|
|
|
|
## Test that to_native removes all system includes
|
|
|
|
l += ['-isystem/usr/include', '-isystem=/usr/share/include', '-DSOMETHING_IMPORTANT=1', '-isystem', '/usr/local/include']
|
|
|
|
self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Wl,--end-group', '-DSOMETHING_IMPORTANT=1'])
|
|
|
|
|
|
|
|
def test_string_templates_substitution(self):
|
|
|
|
dictfunc = mesonbuild.mesonlib.get_filenames_templates_dict
|
|
|
|
substfunc = mesonbuild.mesonlib.substitute_values
|
|
|
|
ME = mesonbuild.mesonlib.MesonException
|
|
|
|
|
|
|
|
# Identity
|
|
|
|
self.assertEqual(dictfunc([], []), {})
|
|
|
|
|
|
|
|
# One input, no outputs
|
|
|
|
inputs = ['bar/foo.c.in']
|
|
|
|
outputs = []
|
|
|
|
ret = dictfunc(inputs, outputs)
|
|
|
|
d = {'@INPUT@': inputs, '@INPUT0@': inputs[0],
|
|
|
|
'@PLAINNAME@': 'foo.c.in', '@BASENAME@': 'foo.c'}
|
|
|
|
# Check dictionary
|
|
|
|
self.assertEqual(ret, d)
|
|
|
|
# Check substitutions
|
|
|
|
cmd = ['some', 'ordinary', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d), cmd)
|
|
|
|
cmd = ['@INPUT@.out', 'ordinary', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d), [inputs[0] + '.out'] + cmd[1:])
|
|
|
|
cmd = ['@INPUT0@.out', '@PLAINNAME@.ok', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d),
|
|
|
|
[inputs[0] + '.out'] + [d['@PLAINNAME@'] + '.ok'] + cmd[2:])
|
|
|
|
cmd = ['@INPUT@', '@BASENAME@.hah', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d),
|
|
|
|
inputs + [d['@BASENAME@'] + '.hah'] + cmd[2:])
|
|
|
|
cmd = ['@OUTPUT@']
|
|
|
|
self.assertRaises(ME, substfunc, cmd, d)
|
|
|
|
|
|
|
|
# One input, one output
|
|
|
|
inputs = ['bar/foo.c.in']
|
|
|
|
outputs = ['out.c']
|
|
|
|
ret = dictfunc(inputs, outputs)
|
|
|
|
d = {'@INPUT@': inputs, '@INPUT0@': inputs[0],
|
|
|
|
'@PLAINNAME@': 'foo.c.in', '@BASENAME@': 'foo.c',
|
|
|
|
'@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTDIR@': '.'}
|
|
|
|
# Check dictionary
|
|
|
|
self.assertEqual(ret, d)
|
|
|
|
# Check substitutions
|
|
|
|
cmd = ['some', 'ordinary', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d), cmd)
|
|
|
|
cmd = ['@INPUT@.out', '@OUTPUT@', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d),
|
|
|
|
[inputs[0] + '.out'] + outputs + cmd[2:])
|
|
|
|
cmd = ['@INPUT0@.out', '@PLAINNAME@.ok', '@OUTPUT0@']
|
|
|
|
self.assertEqual(substfunc(cmd, d),
|
|
|
|
[inputs[0] + '.out', d['@PLAINNAME@'] + '.ok'] + outputs)
|
|
|
|
cmd = ['@INPUT@', '@BASENAME@.hah', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d),
|
|
|
|
inputs + [d['@BASENAME@'] + '.hah'] + cmd[2:])
|
|
|
|
|
|
|
|
# One input, one output with a subdir
|
|
|
|
outputs = ['dir/out.c']
|
|
|
|
ret = dictfunc(inputs, outputs)
|
|
|
|
d = {'@INPUT@': inputs, '@INPUT0@': inputs[0],
|
|
|
|
'@PLAINNAME@': 'foo.c.in', '@BASENAME@': 'foo.c',
|
|
|
|
'@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTDIR@': 'dir'}
|
|
|
|
# Check dictionary
|
|
|
|
self.assertEqual(ret, d)
|
|
|
|
|
|
|
|
# Two inputs, no outputs
|
|
|
|
inputs = ['bar/foo.c.in', 'baz/foo.c.in']
|
|
|
|
outputs = []
|
|
|
|
ret = dictfunc(inputs, outputs)
|
|
|
|
d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], '@INPUT1@': inputs[1]}
|
|
|
|
# Check dictionary
|
|
|
|
self.assertEqual(ret, d)
|
|
|
|
# Check substitutions
|
|
|
|
cmd = ['some', 'ordinary', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d), cmd)
|
|
|
|
cmd = ['@INPUT@', 'ordinary', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d), inputs + cmd[1:])
|
|
|
|
cmd = ['@INPUT0@.out', 'ordinary', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d), [inputs[0] + '.out'] + cmd[1:])
|
|
|
|
cmd = ['@INPUT0@.out', '@INPUT1@.ok', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d), [inputs[0] + '.out', inputs[1] + '.ok'] + cmd[2:])
|
|
|
|
cmd = ['@INPUT0@', '@INPUT1@', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d), inputs + cmd[2:])
|
|
|
|
# Many inputs, can't use @INPUT@ like this
|
|
|
|
cmd = ['@INPUT@.out', 'ordinary', 'strings']
|
|
|
|
self.assertRaises(ME, substfunc, cmd, d)
|
|
|
|
# Not enough inputs
|
|
|
|
cmd = ['@INPUT2@.out', 'ordinary', 'strings']
|
|
|
|
self.assertRaises(ME, substfunc, cmd, d)
|
|
|
|
# Too many inputs
|
|
|
|
cmd = ['@PLAINNAME@']
|
|
|
|
self.assertRaises(ME, substfunc, cmd, d)
|
|
|
|
cmd = ['@BASENAME@']
|
|
|
|
self.assertRaises(ME, substfunc, cmd, d)
|
|
|
|
# No outputs
|
|
|
|
cmd = ['@OUTPUT@']
|
|
|
|
self.assertRaises(ME, substfunc, cmd, d)
|
|
|
|
cmd = ['@OUTPUT0@']
|
|
|
|
self.assertRaises(ME, substfunc, cmd, d)
|
|
|
|
cmd = ['@OUTDIR@']
|
|
|
|
self.assertRaises(ME, substfunc, cmd, d)
|
|
|
|
|
|
|
|
# Two inputs, one output
|
|
|
|
outputs = ['dir/out.c']
|
|
|
|
ret = dictfunc(inputs, outputs)
|
|
|
|
d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], '@INPUT1@': inputs[1],
|
|
|
|
'@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTDIR@': 'dir'}
|
|
|
|
# Check dictionary
|
|
|
|
self.assertEqual(ret, d)
|
|
|
|
# Check substitutions
|
|
|
|
cmd = ['some', 'ordinary', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d), cmd)
|
|
|
|
cmd = ['@OUTPUT@', 'ordinary', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d), outputs + cmd[1:])
|
|
|
|
cmd = ['@OUTPUT@.out', 'ordinary', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d), [outputs[0] + '.out'] + cmd[1:])
|
|
|
|
cmd = ['@OUTPUT0@.out', '@INPUT1@.ok', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d), [outputs[0] + '.out', inputs[1] + '.ok'] + cmd[2:])
|
|
|
|
# Many inputs, can't use @INPUT@ like this
|
|
|
|
cmd = ['@INPUT@.out', 'ordinary', 'strings']
|
|
|
|
self.assertRaises(ME, substfunc, cmd, d)
|
|
|
|
# Not enough inputs
|
|
|
|
cmd = ['@INPUT2@.out', 'ordinary', 'strings']
|
|
|
|
self.assertRaises(ME, substfunc, cmd, d)
|
|
|
|
# Not enough outputs
|
|
|
|
cmd = ['@OUTPUT2@.out', 'ordinary', 'strings']
|
|
|
|
self.assertRaises(ME, substfunc, cmd, d)
|
|
|
|
|
|
|
|
# Two inputs, two outputs
|
|
|
|
outputs = ['dir/out.c', 'dir/out2.c']
|
|
|
|
ret = dictfunc(inputs, outputs)
|
|
|
|
d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], '@INPUT1@': inputs[1],
|
|
|
|
'@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTPUT1@': outputs[1],
|
|
|
|
'@OUTDIR@': 'dir'}
|
|
|
|
# Check dictionary
|
|
|
|
self.assertEqual(ret, d)
|
|
|
|
# Check substitutions
|
|
|
|
cmd = ['some', 'ordinary', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d), cmd)
|
|
|
|
cmd = ['@OUTPUT@', 'ordinary', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d), outputs + cmd[1:])
|
|
|
|
cmd = ['@OUTPUT0@', '@OUTPUT1@', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d), outputs + cmd[2:])
|
|
|
|
cmd = ['@OUTPUT0@.out', '@INPUT1@.ok', '@OUTDIR@']
|
|
|
|
self.assertEqual(substfunc(cmd, d), [outputs[0] + '.out', inputs[1] + '.ok', 'dir'])
|
|
|
|
# Many inputs, can't use @INPUT@ like this
|
|
|
|
cmd = ['@INPUT@.out', 'ordinary', 'strings']
|
|
|
|
self.assertRaises(ME, substfunc, cmd, d)
|
|
|
|
# Not enough inputs
|
|
|
|
cmd = ['@INPUT2@.out', 'ordinary', 'strings']
|
|
|
|
self.assertRaises(ME, substfunc, cmd, d)
|
|
|
|
# Not enough outputs
|
|
|
|
cmd = ['@OUTPUT2@.out', 'ordinary', 'strings']
|
|
|
|
self.assertRaises(ME, substfunc, cmd, d)
|
|
|
|
# Many outputs, can't use @OUTPUT@ like this
|
|
|
|
cmd = ['@OUTPUT@.out', 'ordinary', 'strings']
|
|
|
|
self.assertRaises(ME, substfunc, cmd, d)
|
|
|
|
|
|
|
|
def test_needs_exe_wrapper_override(self):
|
|
|
|
config = ConfigParser()
|
|
|
|
config['binaries'] = {
|
|
|
|
'c': '\'/usr/bin/gcc\'',
|
|
|
|
}
|
|
|
|
config['host_machine'] = {
|
|
|
|
'system': '\'linux\'',
|
|
|
|
'cpu_family': '\'arm\'',
|
|
|
|
'cpu': '\'armv7\'',
|
|
|
|
'endian': '\'little\'',
|
|
|
|
}
|
|
|
|
# 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)
|
|
|
|
configfilename = configfile.name
|
|
|
|
config.write(configfile)
|
|
|
|
configfile.flush()
|
|
|
|
configfile.close()
|
|
|
|
opts = get_fake_options()
|
|
|
|
opts.cross_file = (configfilename,)
|
|
|
|
env = get_fake_env(opts=opts)
|
|
|
|
detected_value = env.need_exe_wrapper()
|
|
|
|
os.unlink(configfilename)
|
|
|
|
|
|
|
|
desired_value = not detected_value
|
|
|
|
config['properties'] = {
|
|
|
|
'needs_exe_wrapper': 'true' if desired_value else 'false'
|
|
|
|
}
|
|
|
|
|
|
|
|
configfile = tempfile.NamedTemporaryFile(mode='w+', delete=False)
|
|
|
|
configfilename = configfile.name
|
|
|
|
config.write(configfile)
|
|
|
|
configfile.close()
|
|
|
|
opts = get_fake_options()
|
|
|
|
opts.cross_file = (configfilename,)
|
|
|
|
env = get_fake_env(opts=opts)
|
|
|
|
forced_value = env.need_exe_wrapper()
|
|
|
|
os.unlink(configfilename)
|
|
|
|
|
|
|
|
self.assertEqual(forced_value, desired_value)
|
|
|
|
|
|
|
|
def test_listify(self):
|
|
|
|
listify = mesonbuild.mesonlib.listify
|
|
|
|
# Test sanity
|
|
|
|
self.assertEqual([1], listify(1))
|
|
|
|
self.assertEqual([], listify([]))
|
|
|
|
self.assertEqual([1], listify([1]))
|
|
|
|
# Test flattening
|
|
|
|
self.assertEqual([1, 2, 3], listify([1, [2, 3]]))
|
|
|
|
self.assertEqual([1, 2, 3], listify([1, [2, [3]]]))
|
|
|
|
self.assertEqual([1, [2, [3]]], listify([1, [2, [3]]], flatten=False))
|
|
|
|
# Test flattening and unholdering
|
|
|
|
holder1 = ObjectHolder(1)
|
|
|
|
holder3 = ObjectHolder(3)
|
|
|
|
self.assertEqual([holder1], listify(holder1))
|
|
|
|
self.assertEqual([holder1], listify([holder1]))
|
|
|
|
self.assertEqual([holder1, 2], listify([holder1, 2]))
|
|
|
|
self.assertEqual([holder1, 2, 3], listify([holder1, 2, [3]]))
|
|
|
|
self.assertEqual([1], listify(holder1, unholder=True))
|
|
|
|
self.assertEqual([1], listify([holder1], unholder=True))
|
|
|
|
self.assertEqual([1, 2], listify([holder1, 2], unholder=True))
|
|
|
|
self.assertEqual([1, 2, 3], listify([holder1, 2, [holder3]], unholder=True))
|
|
|
|
# Unholding doesn't work recursively when not flattening
|
|
|
|
self.assertEqual([1, [2], [holder3]], listify([holder1, [2], [holder3]], unholder=True, flatten=False))
|
|
|
|
|
|
|
|
def test_extract_as_list(self):
|
|
|
|
extract = mesonbuild.mesonlib.extract_as_list
|
|
|
|
# Test sanity
|
|
|
|
kwargs = {'sources': [1, 2, 3]}
|
|
|
|
self.assertEqual([1, 2, 3], extract(kwargs, 'sources'))
|
|
|
|
self.assertEqual(kwargs, {'sources': [1, 2, 3]})
|
|
|
|
self.assertEqual([1, 2, 3], extract(kwargs, 'sources', pop=True))
|
|
|
|
self.assertEqual(kwargs, {})
|
|
|
|
# Test unholding
|
|
|
|
holder3 = ObjectHolder(3)
|
|
|
|
kwargs = {'sources': [1, 2, holder3]}
|
|
|
|
self.assertEqual([1, 2, 3], extract(kwargs, 'sources', unholder=True))
|
|
|
|
self.assertEqual(kwargs, {'sources': [1, 2, holder3]})
|
|
|
|
self.assertEqual([1, 2, 3], extract(kwargs, 'sources', unholder=True, pop=True))
|
|
|
|
self.assertEqual(kwargs, {})
|
|
|
|
# Test listification
|
|
|
|
kwargs = {'sources': [1, 2, 3], 'pch_sources': [4, 5, 6]}
|
|
|
|
self.assertEqual([[1, 2, 3], [4, 5, 6]], extract(kwargs, 'sources', 'pch_sources'))
|
|
|
|
|
|
|
|
def test_pkgconfig_module(self):
|
|
|
|
|
|
|
|
class Mock:
|
|
|
|
pass
|
|
|
|
|
|
|
|
dummystate = Mock()
|
|
|
|
dummystate.subproject = 'dummy'
|
|
|
|
mock = Mock()
|
|
|
|
mock.pcdep = Mock()
|
|
|
|
mock.pcdep.name = "some_name"
|
|
|
|
mock.version_reqs = []
|
|
|
|
|
|
|
|
# pkgconfig dependency as lib
|
|
|
|
deps = mesonbuild.modules.pkgconfig.DependenciesHelper(dummystate, "thislib")
|
|
|
|
deps.add_pub_libs([mock])
|
|
|
|
self.assertEqual(deps.format_reqs(deps.pub_reqs), "some_name")
|
|
|
|
|
|
|
|
# pkgconfig dependency as requires
|
|
|
|
deps = mesonbuild.modules.pkgconfig.DependenciesHelper(dummystate, "thislib")
|
|
|
|
deps.add_pub_reqs([mock])
|
|
|
|
self.assertEqual(deps.format_reqs(deps.pub_reqs), "some_name")
|
|
|
|
|
|
|
|
def _test_all_naming(self, cc, env, patterns, platform):
|
|
|
|
shr = patterns[platform]['shared']
|
|
|
|
stc = patterns[platform]['static']
|
|
|
|
shrstc = shr + tuple([x for x in stc if x not in shr])
|
|
|
|
stcshr = stc + tuple([x for x in shr if x not in stc])
|
|
|
|
p = cc.get_library_naming(env, LibType.SHARED)
|
|
|
|
self.assertEqual(p, shr)
|
|
|
|
p = cc.get_library_naming(env, LibType.STATIC)
|
|
|
|
self.assertEqual(p, stc)
|
|
|
|
p = cc.get_library_naming(env, LibType.PREFER_STATIC)
|
|
|
|
self.assertEqual(p, stcshr)
|
|
|
|
p = cc.get_library_naming(env, LibType.PREFER_SHARED)
|
|
|
|
self.assertEqual(p, shrstc)
|
|
|
|
# Test find library by mocking up openbsd
|
|
|
|
if platform != 'openbsd':
|
|
|
|
return
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
|
|
with open(os.path.join(tmpdir, 'libfoo.so.6.0'), 'w') as f:
|
|
|
|
f.write('')
|
|
|
|
with open(os.path.join(tmpdir, 'libfoo.so.5.0'), 'w') as f:
|
|
|
|
f.write('')
|
|
|
|
with open(os.path.join(tmpdir, 'libfoo.so.54.0'), 'w') as f:
|
|
|
|
f.write('')
|
|
|
|
with open(os.path.join(tmpdir, 'libfoo.so.66a.0b'), 'w') as f:
|
|
|
|
f.write('')
|
|
|
|
with open(os.path.join(tmpdir, 'libfoo.so.70.0.so.1'), 'w') as f:
|
|
|
|
f.write('')
|
|
|
|
found = cc.find_library_real('foo', env, [tmpdir], '', LibType.PREFER_SHARED)
|
|
|
|
self.assertEqual(os.path.basename(found[0]), 'libfoo.so.54.0')
|
|
|
|
|
|
|
|
def test_find_library_patterns(self):
|
|
|
|
'''
|
|
|
|
Unit test for the library search patterns used by find_library()
|
|
|
|
'''
|
|
|
|
unix_static = ('lib{}.a', '{}.a')
|
|
|
|
msvc_static = ('lib{}.a', 'lib{}.lib', '{}.a', '{}.lib')
|
|
|
|
# This is the priority list of pattern matching for library searching
|
|
|
|
patterns = {'openbsd': {'shared': ('lib{}.so', '{}.so', 'lib{}.so.[0-9]*.[0-9]*', '{}.so.[0-9]*.[0-9]*'),
|
|
|
|
'static': unix_static},
|
|
|
|
'linux': {'shared': ('lib{}.so', '{}.so'),
|
|
|
|
'static': unix_static},
|
|
|
|
'darwin': {'shared': ('lib{}.dylib', 'lib{}.so', '{}.dylib', '{}.so'),
|
|
|
|
'static': unix_static},
|
|
|
|
'cygwin': {'shared': ('cyg{}.dll', 'cyg{}.dll.a', 'lib{}.dll',
|
|
|
|
'lib{}.dll.a', '{}.dll', '{}.dll.a'),
|
|
|
|
'static': ('cyg{}.a',) + unix_static},
|
|
|
|
'windows-msvc': {'shared': ('lib{}.lib', '{}.lib'),
|
|
|
|
'static': msvc_static},
|
|
|
|
'windows-mingw': {'shared': ('lib{}.dll.a', 'lib{}.lib', 'lib{}.dll',
|
|
|
|
'{}.dll.a', '{}.lib', '{}.dll'),
|
|
|
|
'static': msvc_static}}
|
|
|
|
env = get_fake_env()
|
|
|
|
cc = env.detect_c_compiler(MachineChoice.HOST)
|
|
|
|
if is_osx():
|
|
|
|
self._test_all_naming(cc, env, patterns, 'darwin')
|
|
|
|
elif is_cygwin():
|
|
|
|
self._test_all_naming(cc, env, patterns, 'cygwin')
|
|
|
|
elif is_windows():
|
|
|
|
if cc.get_argument_syntax() == 'msvc':
|
|
|
|
self._test_all_naming(cc, env, patterns, 'windows-msvc')
|
|
|
|
else:
|
|
|
|
self._test_all_naming(cc, env, patterns, 'windows-mingw')
|
|
|
|
elif is_openbsd():
|
|
|
|
self._test_all_naming(cc, env, patterns, 'openbsd')
|
|
|
|
else:
|
|
|
|
self._test_all_naming(cc, env, patterns, 'linux')
|
|
|
|
env.machines.host.system = 'openbsd'
|
|
|
|
self._test_all_naming(cc, env, patterns, 'openbsd')
|
|
|
|
env.machines.host.system = 'darwin'
|
|
|
|
self._test_all_naming(cc, env, patterns, 'darwin')
|
|
|
|
env.machines.host.system = 'cygwin'
|
|
|
|
self._test_all_naming(cc, env, patterns, 'cygwin')
|
|
|
|
env.machines.host.system = 'windows'
|
|
|
|
self._test_all_naming(cc, env, patterns, 'windows-mingw')
|
|
|
|
|
|
|
|
def test_pkgconfig_parse_libs(self):
|
|
|
|
'''
|
|
|
|
Unit test for parsing of pkg-config output to search for libraries
|
|
|
|
|
|
|
|
https://github.com/mesonbuild/meson/issues/3951
|
|
|
|
'''
|
|
|
|
def create_static_lib(name):
|
|
|
|
if not is_osx():
|
|
|
|
name.open('w').close()
|
|
|
|
return
|
|
|
|
src = name.with_suffix('.c')
|
|
|
|
out = name.with_suffix('.o')
|
|
|
|
with src.open('w') as f:
|
|
|
|
f.write('int meson_foobar (void) { return 0; }')
|
|
|
|
subprocess.check_call(['clang', '-c', str(src), '-o', str(out)])
|
|
|
|
subprocess.check_call(['ar', 'csr', str(name), str(out)])
|
|
|
|
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
|
|
pkgbin = ExternalProgram('pkg-config', command=['pkg-config'], silent=True)
|
|
|
|
env = get_fake_env()
|
|
|
|
compiler = env.detect_c_compiler(MachineChoice.HOST)
|
|
|
|
env.coredata.compilers.host = {'c': compiler}
|
|
|
|
env.coredata.compiler_options.host['c_link_args'] = FakeCompilerOptions()
|
|
|
|
p1 = Path(tmpdir) / '1'
|
|
|
|
p2 = Path(tmpdir) / '2'
|
|
|
|
p1.mkdir()
|
|
|
|
p2.mkdir()
|
|
|
|
# libfoo.a is in one prefix
|
|
|
|
create_static_lib(p1 / 'libfoo.a')
|
|
|
|
# libbar.a is in both prefixes
|
|
|
|
create_static_lib(p1 / 'libbar.a')
|
|
|
|
create_static_lib(p2 / 'libbar.a')
|
|
|
|
# Ensure that we never statically link to these
|
|
|
|
create_static_lib(p1 / 'libpthread.a')
|
|
|
|
create_static_lib(p1 / 'libm.a')
|
|
|
|
create_static_lib(p1 / 'libc.a')
|
|
|
|
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[0] == 'foo':
|
|
|
|
return 0, '-L{} -lfoo -L{} -lbar'.format(p2.as_posix(), p1.as_posix()), ''
|
|
|
|
if args[0] == 'bar':
|
|
|
|
return 0, '-L{} -lbar'.format(p2.as_posix()), ''
|
|
|
|
if args[0] == 'internal':
|
|
|
|
return 0, '-L{} -lpthread -lm -lc -lrt -ldl'.format(p1.as_posix()), ''
|
|
|
|
|
|
|
|
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:
|
|
|
|
kwargs = {'required': True, 'silent': True}
|
|
|
|
foo_dep = PkgConfigDependency('foo', env, kwargs)
|
|
|
|
self.assertEqual(foo_dep.get_link_args(),
|
|
|
|
[(p1 / 'libfoo.a').as_posix(), (p2 / 'libbar.a').as_posix()])
|
|
|
|
bar_dep = PkgConfigDependency('bar', env, kwargs)
|
|
|
|
self.assertEqual(bar_dep.get_link_args(), [(p2 / 'libbar.a').as_posix()])
|
|
|
|
internal_dep = PkgConfigDependency('internal', env, kwargs)
|
|
|
|
if compiler.get_argument_syntax() == 'msvc':
|
|
|
|
self.assertEqual(internal_dep.get_link_args(), [])
|
|
|
|
else:
|
|
|
|
link_args = internal_dep.get_link_args()
|
|
|
|
for link_arg in link_args:
|
|
|
|
for lib in ('pthread', 'm', 'c', 'dl', 'rt'):
|
|
|
|
self.assertNotIn('lib{}.a'.format(lib), 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
|
|
|
|
for (a, b, result) in [
|
|
|
|
('0.99.beta19', '>= 0.99.beta14', True),
|
|
|
|
]:
|
|
|
|
self.assertEqual(comparefunc(a, b)[0], result)
|
|
|
|
|
|
|
|
for (a, b, op) in [
|
|
|
|
# examples from https://fedoraproject.org/wiki/Archive:Tools/RPM/VersionComparison
|
|
|
|
("1.0010", "1.9", operator.gt),
|
|
|
|
("1.05", "1.5", operator.eq),
|
|
|
|
("1.0", "1", operator.gt),
|
|
|
|
("2.50", "2.5", operator.gt),
|
|
|
|
("fc4", "fc.4", operator.eq),
|
|
|
|
("FC5", "fc4", operator.lt),
|
|
|
|
("2a", "2.0", operator.lt),
|
|
|
|
("1.0", "1.fc4", operator.gt),
|
|
|
|
("3.0.0_fc", "3.0.0.fc", operator.eq),
|
|
|
|
# from RPM tests
|
|
|
|
("1.0", "1.0", operator.eq),
|
|
|
|
("1.0", "2.0", operator.lt),
|
|
|
|
("2.0", "1.0", operator.gt),
|
|
|
|
("2.0.1", "2.0.1", operator.eq),
|
|
|
|
("2.0", "2.0.1", operator.lt),
|
|
|
|
("2.0.1", "2.0", operator.gt),
|
|
|
|
("2.0.1a", "2.0.1a", operator.eq),
|
|
|
|
("2.0.1a", "2.0.1", operator.gt),
|
|
|
|
("2.0.1", "2.0.1a", operator.lt),
|
|
|
|
("5.5p1", "5.5p1", operator.eq),
|
|
|
|
("5.5p1", "5.5p2", operator.lt),
|
|
|
|
("5.5p2", "5.5p1", operator.gt),
|
|
|
|
("5.5p10", "5.5p10", operator.eq),
|
|
|
|
("5.5p1", "5.5p10", operator.lt),
|
|
|
|
("5.5p10", "5.5p1", operator.gt),
|
|
|
|
("10xyz", "10.1xyz", operator.lt),
|
|
|
|
("10.1xyz", "10xyz", operator.gt),
|
|
|
|
("xyz10", "xyz10", operator.eq),
|
|
|
|
("xyz10", "xyz10.1", operator.lt),
|
|
|
|
("xyz10.1", "xyz10", operator.gt),
|
|
|
|
("xyz.4", "xyz.4", operator.eq),
|
|
|
|
("xyz.4", "8", operator.lt),
|
|
|
|
("8", "xyz.4", operator.gt),
|
|
|
|
("xyz.4", "2", operator.lt),
|
|
|
|
("2", "xyz.4", operator.gt),
|
|
|
|
("5.5p2", "5.6p1", operator.lt),
|
|
|
|
("5.6p1", "5.5p2", operator.gt),
|
|
|
|
("5.6p1", "6.5p1", operator.lt),
|
|
|
|
("6.5p1", "5.6p1", operator.gt),
|
|
|
|
("6.0.rc1", "6.0", operator.gt),
|
|
|
|
("6.0", "6.0.rc1", operator.lt),
|
|
|
|
("10b2", "10a1", operator.gt),
|
|
|
|
("10a2", "10b2", operator.lt),
|
|
|
|
("1.0aa", "1.0aa", operator.eq),
|
|
|
|
("1.0a", "1.0aa", operator.lt),
|
|
|
|
("1.0aa", "1.0a", operator.gt),
|
|
|
|
("10.0001", "10.0001", operator.eq),
|
|
|
|
("10.0001", "10.1", operator.eq),
|
|
|
|
("10.1", "10.0001", operator.eq),
|
|
|
|
("10.0001", "10.0039", operator.lt),
|
|
|
|
("10.0039", "10.0001", operator.gt),
|
|
|
|
("4.999.9", "5.0", operator.lt),
|
|
|
|
("5.0", "4.999.9", operator.gt),
|
|
|
|
("20101121", "20101121", operator.eq),
|
|
|
|
("20101121", "20101122", operator.lt),
|
|
|
|
("20101122", "20101121", operator.gt),
|
|
|
|
("2_0", "2_0", operator.eq),
|
|
|
|
("2.0", "2_0", operator.eq),
|
|
|
|
("2_0", "2.0", operator.eq),
|
|
|
|
("a", "a", operator.eq),
|
|
|
|
("a+", "a+", operator.eq),
|
|
|
|
("a+", "a_", operator.eq),
|
|
|
|
("a_", "a+", operator.eq),
|
|
|
|
("+a", "+a", operator.eq),
|
|
|
|
("+a", "_a", operator.eq),
|
|
|
|
("_a", "+a", operator.eq),
|
|
|
|
("+_", "+_", operator.eq),
|
|
|
|
("_+", "+_", operator.eq),
|
|
|
|
("_+", "_+", operator.eq),
|
|
|
|
("+", "_", operator.eq),
|
|
|
|
("_", "+", operator.eq),
|
|
|
|
# other tests
|
|
|
|
('0.99.beta19', '0.99.beta14', operator.gt),
|
|
|
|
("1.0.0", "2.0.0", operator.lt),
|
|
|
|
(".0.0", "2.0.0", operator.lt),
|
|
|
|
("alpha", "beta", operator.lt),
|
|
|
|
("1.0", "1.0.0", operator.lt),
|
|
|
|
("2.456", "2.1000", operator.lt),
|
|
|
|
("2.1000", "3.111", operator.lt),
|
|
|
|
("2.001", "2.1", operator.eq),
|
|
|
|
("2.34", "2.34", operator.eq),
|
|
|
|
("6.1.2", "6.3.8", operator.lt),
|
|
|
|
("1.7.3.0", "2.0.0", operator.lt),
|
|
|
|
("2.24.51", "2.25", operator.lt),
|
|
|
|
("2.1.5+20120813+gitdcbe778", "2.1.5", operator.gt),
|
|
|
|
("3.4.1", "3.4b1", operator.gt),
|
|
|
|
("041206", "200090325", operator.lt),
|
|
|
|
("0.6.2+git20130413", "0.6.2", operator.gt),
|
|
|
|
("2.6.0+bzr6602", "2.6.0", operator.gt),
|
|
|
|
("2.6.0", "2.6b2", operator.gt),
|
|
|
|
("2.6.0+bzr6602", "2.6b2x", operator.gt),
|
|
|
|
("0.6.7+20150214+git3a710f9", "0.6.7", operator.gt),
|
|
|
|
("15.8b", "15.8.0.1", operator.lt),
|
|
|
|
("1.2rc1", "1.2.0", operator.lt),
|
|
|
|
]:
|
|
|
|
ver_a = Version(a)
|
|
|
|
ver_b = Version(b)
|
|
|
|
if op is operator.eq:
|
|
|
|
for o, name in [(op, 'eq'), (operator.ge, 'ge'), (operator.le, 'le')]:
|
|
|
|
self.assertTrue(o(ver_a, ver_b), '{} {} {}'.format(ver_a, name, ver_b))
|
|
|
|
if op is operator.lt:
|
|
|
|
for o, name in [(op, 'lt'), (operator.le, 'le'), (operator.ne, 'ne')]:
|
|
|
|
self.assertTrue(o(ver_a, ver_b), '{} {} {}'.format(ver_a, name, ver_b))
|
|
|
|
for o, name in [(operator.gt, 'gt'), (operator.ge, 'ge'), (operator.eq, 'eq')]:
|
|
|
|
self.assertFalse(o(ver_a, ver_b), '{} {} {}'.format(ver_a, name, ver_b))
|
|
|
|
if op is operator.gt:
|
|
|
|
for o, name in [(op, 'gt'), (operator.ge, 'ge'), (operator.ne, 'ne')]:
|
|
|
|
self.assertTrue(o(ver_a, ver_b), '{} {} {}'.format(ver_a, name, ver_b))
|
|
|
|
for o, name in [(operator.lt, 'lt'), (operator.le, 'le'), (operator.eq, 'eq')]:
|
|
|
|
self.assertFalse(o(ver_a, ver_b), '{} {} {}'.format(ver_a, name, ver_b))
|
|
|
|
|
|
|
|
def test_msvc_toolset_version(self):
|
|
|
|
'''
|
|
|
|
Ensure that the toolset version returns the correct value for this MSVC
|
|
|
|
'''
|
|
|
|
env = get_fake_env()
|
|
|
|
cc = env.detect_c_compiler(MachineChoice.HOST)
|
|
|
|
if cc.get_argument_syntax() != 'msvc':
|
|
|
|
raise unittest.SkipTest('Test only applies to MSVC-like compilers')
|
|
|
|
toolset_ver = cc.get_toolset_version()
|
|
|
|
self.assertIsNotNone(toolset_ver)
|
|
|
|
# Visual Studio 2015 and older versions do not define VCToolsVersion
|
|
|
|
# TODO: ICL doesn't set this in the VSC2015 profile either
|
|
|
|
if cc.id == 'msvc' and int(''.join(cc.version.split('.')[0:2])) < 1910:
|
|
|
|
return
|
|
|
|
if 'VCToolsVersion' in os.environ:
|
|
|
|
vctools_ver = os.environ['VCToolsVersion']
|
|
|
|
else:
|
|
|
|
self.assertIn('VCINSTALLDIR', os.environ)
|
|
|
|
# See https://devblogs.microsoft.com/cppblog/finding-the-visual-c-compiler-tools-in-visual-studio-2017/
|
|
|
|
vctools_ver = (Path(os.environ['VCINSTALLDIR']) / 'Auxiliary' / 'Build' / 'Microsoft.VCToolsVersion.default.txt').read_text()
|
|
|
|
self.assertTrue(vctools_ver.startswith(toolset_ver),
|
|
|
|
msg='{!r} does not start with {!r}'.format(vctools_ver, toolset_ver))
|
|
|
|
|
|
|
|
def test_split_args(self):
|
|
|
|
split_args = mesonbuild.mesonlib.split_args
|
|
|
|
join_args = mesonbuild.mesonlib.join_args
|
|
|
|
if is_windows():
|
|
|
|
test_data = [
|
|
|
|
# examples from https://docs.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments
|
|
|
|
(r'"a b c" d e', ['a b c', 'd', 'e'], True),
|
|
|
|
(r'"ab\"c" "\\" d', ['ab"c', '\\', 'd'], False),
|
|
|
|
(r'a\\\b d"e f"g h', [r'a\\\b', 'de fg', 'h'], False),
|
|
|
|
(r'a\\\"b c d', [r'a\"b', 'c', 'd'], False),
|
|
|
|
(r'a\\\\"b c" d e', [r'a\\b c', 'd', 'e'], False),
|
|
|
|
# other basics
|
|
|
|
(r'""', [''], True),
|
|
|
|
(r'a b c d "" e', ['a', 'b', 'c', 'd', '', 'e'], True),
|
|
|
|
(r"'a b c' d e", ["'a", 'b', "c'", 'd', 'e'], True),
|
|
|
|
(r"'a&b&c' d e", ["'a&b&c'", 'd', 'e'], True),
|
|
|
|
(r"a & b & c d e", ['a', '&', 'b', '&', 'c', 'd', 'e'], True),
|
|
|
|
(r"'a & b & c d e'", ["'a", '&', 'b', '&', 'c', 'd', "e'"], True),
|
|
|
|
('a b\nc\rd \n\re', ['a', 'b', 'c', 'd', 'e'], False),
|
|
|
|
# more illustrative tests
|
|
|
|
(r'cl test.cpp /O1 /Fe:test.exe', ['cl', 'test.cpp', '/O1', '/Fe:test.exe'], True),
|
|
|
|
(r'cl "test.cpp /O1 /Fe:test.exe"', ['cl', 'test.cpp /O1 /Fe:test.exe'], True),
|
|
|
|
(r'cl /DNAME=\"Bob\" test.cpp', ['cl', '/DNAME="Bob"', 'test.cpp'], False),
|
|
|
|
(r'cl "/DNAME=\"Bob\"" test.cpp', ['cl', '/DNAME="Bob"', 'test.cpp'], True),
|
|
|
|
(r'cl /DNAME=\"Bob, Alice\" test.cpp', ['cl', '/DNAME="Bob,', 'Alice"', 'test.cpp'], False),
|
|
|
|
(r'cl "/DNAME=\"Bob, Alice\"" test.cpp', ['cl', '/DNAME="Bob, Alice"', 'test.cpp'], True),
|
|
|
|
(r'cl C:\path\with\backslashes.cpp', ['cl', r'C:\path\with\backslashes.cpp'], True),
|
|
|
|
(r'cl C:\\path\\with\\double\\backslashes.cpp', ['cl', r'C:\\path\\with\\double\\backslashes.cpp'], True),
|
|
|
|
(r'cl "C:\\path\\with\\double\\backslashes.cpp"', ['cl', r'C:\\path\\with\\double\\backslashes.cpp'], False),
|
|
|
|
(r'cl C:\path with spaces\test.cpp', ['cl', r'C:\path', 'with', r'spaces\test.cpp'], False),
|
|
|
|
(r'cl "C:\path with spaces\test.cpp"', ['cl', r'C:\path with spaces\test.cpp'], True),
|
|
|
|
(r'cl /DPATH="C:\path\with\backslashes test.cpp', ['cl', r'/DPATH=C:\path\with\backslashes test.cpp'], False),
|
|
|
|
(r'cl /DPATH=\"C:\\ends\\with\\backslashes\\\" test.cpp', ['cl', r'/DPATH="C:\\ends\\with\\backslashes\"', 'test.cpp'], False),
|
|
|
|
(r'cl /DPATH="C:\\ends\\with\\backslashes\\" test.cpp', ['cl', '/DPATH=C:\\\\ends\\\\with\\\\backslashes\\', 'test.cpp'], False),
|
|
|
|
(r'cl "/DNAME=\"C:\\ends\\with\\backslashes\\\"" test.cpp', ['cl', r'/DNAME="C:\\ends\\with\\backslashes\"', 'test.cpp'], True),
|
|
|
|
(r'cl "/DNAME=\"C:\\ends\\with\\backslashes\\\\"" test.cpp', ['cl', r'/DNAME="C:\\ends\\with\\backslashes\\ test.cpp'], False),
|
|
|
|
(r'cl "/DNAME=\"C:\\ends\\with\\backslashes\\\\\"" test.cpp', ['cl', r'/DNAME="C:\\ends\\with\\backslashes\\"', 'test.cpp'], True),
|
|
|
|
]
|
|
|
|
else:
|
|
|
|
test_data = [
|
|
|
|
(r"'a b c' d e", ['a b c', 'd', 'e'], True),
|
|
|
|
(r"a/b/c d e", ['a/b/c', 'd', 'e'], True),
|
|
|
|
(r"a\b\c d e", [r'abc', 'd', 'e'], False),
|
|
|
|
(r"a\\b\\c d e", [r'a\b\c', 'd', 'e'], False),
|
|
|
|
(r'"a b c" d e', ['a b c', 'd', 'e'], False),
|
|
|
|
(r'"a\\b\\c\\" d e', ['a\\b\\c\\', 'd', 'e'], False),
|
|
|
|
(r"'a\b\c\' d e", ['a\\b\\c\\', 'd', 'e'], True),
|
|
|
|
(r"'a&b&c' d e", ['a&b&c', 'd', 'e'], True),
|
|
|
|
(r"a & b & c d e", ['a', '&', 'b', '&', 'c', 'd', 'e'], False),
|
|
|
|
(r"'a & b & c d e'", ['a & b & c d e'], True),
|
|
|
|
(r"abd'e f'g h", [r'abde fg', 'h'], False),
|
|
|
|
('a b\nc\rd \n\re', ['a', 'b', 'c', 'd', 'e'], False),
|
|
|
|
|
|
|
|
('g++ -DNAME="Bob" test.cpp', ['g++', '-DNAME=Bob', 'test.cpp'], False),
|
|
|
|
("g++ '-DNAME=\"Bob\"' test.cpp", ['g++', '-DNAME="Bob"', 'test.cpp'], True),
|
|
|
|
('g++ -DNAME="Bob, Alice" test.cpp', ['g++', '-DNAME=Bob, Alice', 'test.cpp'], False),
|
|
|
|
("g++ '-DNAME=\"Bob, Alice\"' test.cpp", ['g++', '-DNAME="Bob, Alice"', 'test.cpp'], True),
|
|
|
|
]
|
|
|
|
|
|
|
|
for (cmd, expected, roundtrip) in test_data:
|
|
|
|
self.assertEqual(split_args(cmd), expected)
|
|
|
|
if roundtrip:
|
|
|
|
self.assertEqual(join_args(expected), cmd)
|
|
|
|
|
|
|
|
def test_quote_arg(self):
|
|
|
|
split_args = mesonbuild.mesonlib.split_args
|
|
|
|
quote_arg = mesonbuild.mesonlib.quote_arg
|
|
|
|
if is_windows():
|
|
|
|
test_data = [
|
|
|
|
('', '""'),
|
|
|
|
('arg1', 'arg1'),
|
|
|
|
('/option1', '/option1'),
|
|
|
|
('/Ovalue', '/Ovalue'),
|
|
|
|
('/OBob&Alice', '/OBob&Alice'),
|
|
|
|
('/Ovalue with spaces', r'"/Ovalue with spaces"'),
|
|
|
|
(r'/O"value with spaces"', r'"/O\"value with spaces\""'),
|
|
|
|
(r'/OC:\path with spaces\test.exe', r'"/OC:\path with spaces\test.exe"'),
|
|
|
|
('/LIBPATH:C:\\path with spaces\\ends\\with\\backslashes\\', r'"/LIBPATH:C:\path with spaces\ends\with\backslashes\\"'),
|
|
|
|
('/LIBPATH:"C:\\path with spaces\\ends\\with\\backslashes\\\\"', r'"/LIBPATH:\"C:\path with spaces\ends\with\backslashes\\\\\""'),
|
|
|
|
(r'/DMSG="Alice said: \"Let\'s go\""', r'"/DMSG=\"Alice said: \\\"Let\'s go\\\"\""'),
|
|
|
|
]
|
|
|
|
else:
|
|
|
|
test_data = [
|
|
|
|
('arg1', 'arg1'),
|
|
|
|
('--option1', '--option1'),
|
|
|
|
('-O=value', '-O=value'),
|
|
|
|
('-O=Bob&Alice', "'-O=Bob&Alice'"),
|
|
|
|
('-O=value with spaces', "'-O=value with spaces'"),
|
|
|
|
('-O="value with spaces"', '\'-O=\"value with spaces\"\''),
|
|
|
|
('-O=/path with spaces/test', '\'-O=/path with spaces/test\''),
|
|
|
|
('-DMSG="Alice said: \\"Let\'s go\\""', "'-DMSG=\"Alice said: \\\"Let'\"'\"'s go\\\"\"'"),
|
|
|
|
]
|
|
|
|
|
|
|
|
for (arg, expected) in test_data:
|
|
|
|
self.assertEqual(quote_arg(arg), expected)
|
|
|
|
self.assertEqual(split_args(expected)[0], arg)
|
|
|
|
|
|
|
|
def test_depfile(self):
|
|
|
|
for (f, target, expdeps) in [
|
|
|
|
# empty, unknown target
|
|
|
|
([''], 'unknown', set()),
|
|
|
|
# simple target & deps
|
|
|
|
(['meson/foo.o : foo.c foo.h'], 'meson/foo.o', set({'foo.c', 'foo.h'})),
|
|
|
|
(['meson/foo.o: foo.c foo.h'], 'foo.c', set()),
|
|
|
|
# get all deps
|
|
|
|
(['meson/foo.o: foo.c foo.h',
|
|
|
|
'foo.c: gen.py'], 'meson/foo.o', set({'foo.c', 'foo.h', 'gen.py'})),
|
|
|
|
(['meson/foo.o: foo.c foo.h',
|
|
|
|
'foo.c: gen.py'], 'foo.c', set({'gen.py'})),
|
|
|
|
# linue continuation, multiple targets
|
|
|
|
(['foo.o \\', 'foo.h: bar'], 'foo.h', set({'bar'})),
|
|
|
|
(['foo.o \\', 'foo.h: bar'], 'foo.o', set({'bar'})),
|
|
|
|
# \\ handling
|
|
|
|
(['foo: Program\\ F\\iles\\\\X'], 'foo', set({'Program Files\\X'})),
|
|
|
|
# $ handling
|
|
|
|
(['f$o.o: c/b'], 'f$o.o', set({'c/b'})),
|
|
|
|
(['f$$o.o: c/b'], 'f$o.o', set({'c/b'})),
|
|
|
|
# cycles
|
|
|
|
(['a: b', 'b: a'], 'a', set({'a', 'b'})),
|
|
|
|
(['a: b', 'b: a'], 'b', set({'a', 'b'})),
|
|
|
|
]:
|
|
|
|
d = mesonbuild.depfile.DepFile(f)
|
|
|
|
deps = d.get_all_dependencies(target)
|
|
|
|
self.assertEqual(deps, expdeps)
|
|
|
|
|
|
|
|
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')
|
|
|
|
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'))
|
|
|
|
actual = f.getvalue().strip()
|
|
|
|
self.assertEqual(actual.count('foo'), 1, actual)
|
|
|
|
|
|
|
|
mesonbuild.mlog.log_once('foo')
|
|
|
|
actual = f.getvalue().strip()
|
|
|
|
self.assertEqual(actual.count('foo'), 1, actual)
|
|
|
|
|
|
|
|
f.truncate()
|
|
|
|
|
|
|
|
mesonbuild.mlog.warning('bar', once=True)
|
|
|
|
mesonbuild.mlog.warning('bar', once=True)
|
|
|
|
actual = f.getvalue().strip()
|
|
|
|
self.assertEqual(actual.count('bar'), 1, actual)
|
|
|
|
|
|
|
|
def test_sort_libpaths(self):
|
|
|
|
sort_libpaths = mesonbuild.dependencies.base.sort_libpaths
|
|
|
|
self.assertEqual(sort_libpaths(
|
|
|
|
['/home/mesonuser/.local/lib', '/usr/local/lib', '/usr/lib'],
|
|
|
|
['/home/mesonuser/.local/lib/pkgconfig', '/usr/local/lib/pkgconfig']),
|
|
|
|
['/home/mesonuser/.local/lib', '/usr/local/lib', '/usr/lib'])
|
|
|
|
self.assertEqual(sort_libpaths(
|
|
|
|
['/usr/local/lib', '/home/mesonuser/.local/lib', '/usr/lib'],
|
|
|
|
['/home/mesonuser/.local/lib/pkgconfig', '/usr/local/lib/pkgconfig']),
|
|
|
|
['/home/mesonuser/.local/lib', '/usr/local/lib', '/usr/lib'])
|
|
|
|
self.assertEqual(sort_libpaths(
|
|
|
|
['/usr/lib', '/usr/local/lib', '/home/mesonuser/.local/lib'],
|
|
|
|
['/home/mesonuser/.local/lib/pkgconfig', '/usr/local/lib/pkgconfig']),
|
|
|
|
['/home/mesonuser/.local/lib', '/usr/local/lib', '/usr/lib'])
|
|
|
|
self.assertEqual(sort_libpaths(
|
|
|
|
['/usr/lib', '/usr/local/lib', '/home/mesonuser/.local/lib'],
|
|
|
|
['/home/mesonuser/.local/lib/pkgconfig', '/usr/local/libdata/pkgconfig']),
|
|
|
|
['/home/mesonuser/.local/lib', '/usr/local/lib', '/usr/lib'])
|
|
|
|
|
|
|
|
|
|
|
|
@unittest.skipIf(is_tarball(), 'Skipping because this is a tarball release')
|
|
|
|
class DataTests(unittest.TestCase):
|
|
|
|
|
|
|
|
def test_snippets(self):
|
|
|
|
hashcounter = re.compile('^ *(#)+')
|
|
|
|
snippet_dir = Path('docs/markdown/snippets')
|
|
|
|
self.assertTrue(snippet_dir.is_dir())
|
|
|
|
for f in snippet_dir.glob('*'):
|
|
|
|
self.assertTrue(f.is_file())
|
|
|
|
if f.parts[-1].endswith('~'):
|
|
|
|
continue
|
|
|
|
if f.suffix == '.md':
|
|
|
|
in_code_block = False
|
|
|
|
with f.open() as snippet:
|
|
|
|
for line in snippet:
|
|
|
|
if line.startswith(' '):
|
|
|
|
continue
|
|
|
|
if line.startswith('```'):
|
|
|
|
in_code_block = not in_code_block
|
|
|
|
if in_code_block:
|
|
|
|
continue
|
|
|
|
m = re.match(hashcounter, line)
|
|
|
|
if m:
|
|
|
|
self.assertEqual(len(m.group(0)), 2, 'All headings in snippets must have two hash symbols: ' + f.name)
|
|
|
|
self.assertFalse(in_code_block, 'Unclosed code block.')
|
|
|
|
else:
|
|
|
|
if f.name != 'add_release_note_snippets_here':
|
|
|
|
self.assertTrue(False, 'A file without .md suffix in snippets dir: ' + f.name)
|
|
|
|
|
|
|
|
def test_compiler_options_documented(self):
|
|
|
|
'''
|
|
|
|
Test that C and C++ compiler options and base options are documented in
|
|
|
|
Builtin-Options.md. Only tests the default compiler for the current
|
|
|
|
platform on the CI.
|
|
|
|
'''
|
|
|
|
md = None
|
|
|
|
with open('docs/markdown/Builtin-options.md') as f:
|
|
|
|
md = f.read()
|
|
|
|
self.assertIsNotNone(md)
|
|
|
|
env = get_fake_env()
|
|
|
|
# FIXME: Support other compilers
|
|
|
|
cc = env.detect_c_compiler(MachineChoice.HOST)
|
|
|
|
cpp = env.detect_cpp_compiler(MachineChoice.HOST)
|
|
|
|
for comp in (cc, cpp):
|
|
|
|
for opt in comp.get_options().keys():
|
|
|
|
self.assertIn(opt, md)
|
|
|
|
for opt in comp.base_options:
|
|
|
|
self.assertIn(opt, md)
|
|
|
|
self.assertNotIn('b_unknown', md)
|
|
|
|
|
|
|
|
def test_builtin_options_documented(self):
|
|
|
|
'''
|
|
|
|
Test that universal options and base options are documented in
|
|
|
|
Builtin-Options.md.
|
|
|
|
'''
|
|
|
|
md = None
|
|
|
|
with open('docs/markdown/Builtin-options.md') as f:
|
|
|
|
md = f.read()
|
|
|
|
self.assertIsNotNone(md)
|
|
|
|
|
|
|
|
found_entries = set()
|
|
|
|
sections = list(re.finditer(r"^## (.+)$", md, re.MULTILINE)) + [None]
|
|
|
|
|
|
|
|
for s1, s2 in zip(sections[:], sections[1:]):
|
|
|
|
if s1.group(1) == "Universal options":
|
|
|
|
# Extract the content for this section
|
|
|
|
end = s2.start() if s2 is not None else len(md)
|
|
|
|
content = md[s1.end():end]
|
|
|
|
subsections = list(re.finditer(r"^### (.+)$", content, re.MULTILINE)) + [None]
|
|
|
|
|
|
|
|
for sub1, sub2 in zip(subsections[:], subsections[1:]):
|
|
|
|
if sub1.group(1) == "Directories" or sub1.group(1) == "Core options":
|
|
|
|
# Extract the content for this subsection
|
|
|
|
sub_end = sub2.start() if sub2 is not None else len(content)
|
|
|
|
subcontent = content[sub1.end():sub_end]
|
|
|
|
# Find the list entries
|
|
|
|
arches = [m.group(1) for m in re.finditer(r"^\| (\w+) .* \|", subcontent, re.MULTILINE)]
|
|
|
|
# Drop the header
|
|
|
|
arches = set(arches[1:])
|
|
|
|
|
|
|
|
self.assertEqual(len(found_entries & arches), 0)
|
|
|
|
found_entries |= arches
|
|
|
|
break
|
|
|
|
|
|
|
|
self.assertEqual(found_entries, set([
|
|
|
|
*mesonbuild.coredata.builtin_options.keys(),
|
|
|
|
*mesonbuild.coredata.builtin_options_per_machine.keys()
|
|
|
|
]))
|
|
|
|
|
|
|
|
def test_cpu_families_documented(self):
|
|
|
|
with open("docs/markdown/Reference-tables.md") as f:
|
|
|
|
md = f.read()
|
|
|
|
self.assertIsNotNone(md)
|
|
|
|
|
|
|
|
sections = list(re.finditer(r"^## (.+)$", md, re.MULTILINE))
|
|
|
|
for s1, s2 in zip(sections[::2], sections[1::2]):
|
|
|
|
if s1.group(1) == "CPU families":
|
|
|
|
# Extract the content for this section
|
|
|
|
content = md[s1.end():s2.start()]
|
|
|
|
# Find the list entries
|
|
|
|
arches = [m.group(1) for m in re.finditer(r"^\| (\w+) +\|", content, re.MULTILINE)]
|
|
|
|
# Drop the header
|
|
|
|
arches = set(arches[1:])
|
|
|
|
self.assertEqual(arches, set(mesonbuild.environment.known_cpu_families))
|
|
|
|
|
|
|
|
def test_markdown_files_in_sitemap(self):
|
|
|
|
'''
|
|
|
|
Test that each markdown files in docs/markdown is referenced in sitemap.txt
|
|
|
|
'''
|
|
|
|
with open("docs/sitemap.txt") as f:
|
|
|
|
md = f.read()
|
|
|
|
self.assertIsNotNone(md)
|
|
|
|
toc = list(m.group(1) for m in re.finditer(r"^\s*(\w.*)$", md, re.MULTILINE))
|
|
|
|
markdownfiles = [f.name for f in Path("docs/markdown").iterdir() if f.is_file() and f.suffix == '.md']
|
|
|
|
exceptions = ['_Sidebar.md']
|
|
|
|
for f in markdownfiles:
|
|
|
|
if f not in exceptions:
|
|
|
|
self.assertIn(f, toc)
|
|
|
|
|
|
|
|
def test_vim_syntax_highlighting(self):
|
|
|
|
'''
|
|
|
|
Ensure that vim syntax highlighting files were updated for new
|
|
|
|
functions in the global namespace in build files.
|
|
|
|
'''
|
|
|
|
env = get_fake_env()
|
|
|
|
interp = Interpreter(FakeBuild(env), mock=True)
|
|
|
|
with open('data/syntax-highlighting/vim/syntax/meson.vim') as f:
|
|
|
|
res = re.search(r'syn keyword mesonBuiltin(\s+\\\s\w+)+', f.read(), re.MULTILINE)
|
|
|
|
defined = set([a.strip() for a in res.group().split('\\')][1:])
|
|
|
|
self.assertEqual(defined, set(chain(interp.funcs.keys(), interp.builtin.keys())))
|
|
|
|
|
|
|
|
@unittest.skipIf(is_pull(), 'Skipping because this is a pull request')
|
|
|
|
def test_json_grammar_syntax_highlighting(self):
|
|
|
|
'''
|
|
|
|
Ensure that syntax highlighting JSON grammar written by TingPing was
|
|
|
|
updated for new functions in the global namespace in build files.
|
|
|
|
https://github.com/TingPing/language-meson/
|
|
|
|
'''
|
|
|
|
env = get_fake_env()
|
|
|
|
interp = Interpreter(FakeBuild(env), mock=True)
|
|
|
|
url = 'https://raw.githubusercontent.com/TingPing/language-meson/master/grammars/meson.json'
|
|
|
|
try:
|
|
|
|
# Use a timeout to avoid blocking forever in case the network is
|
|
|
|
# slow or unavailable in a weird way
|
|
|
|
r = urllib.request.urlopen(url, timeout=URLOPEN_TIMEOUT)
|
|
|
|
except urllib.error.URLError as e:
|
|
|
|
# Skip test when network is not available, such as during packaging
|
|
|
|
# by a distro or Flatpak
|
|
|
|
if not isinstance(e, urllib.error.HTTPError):
|
|
|
|
raise unittest.SkipTest('Network unavailable')
|
|
|
|
# Don't fail the test if github is down, but do fail if 4xx
|
|
|
|
if e.code >= 500:
|
|
|
|
raise unittest.SkipTest('Server error ' + str(e.code))
|
|
|
|
raise e
|
|
|
|
# On Python 3.5, we must decode bytes to string. Newer versions don't require that.
|
|
|
|
grammar = json.loads(r.read().decode('utf-8', 'surrogatepass'))
|
|
|
|
for each in grammar['patterns']:
|
|
|
|
if 'name' in each and each['name'] == 'support.function.builtin.meson':
|
|
|
|
# The string is of the form: (?x)\\b(func1|func2|...\n)\\b\\s*(?=\\() and
|
|
|
|
# we convert that to [func1, func2, ...] without using regex to parse regex
|
|
|
|
funcs = set(each['match'].split('\\b(')[1].split('\n')[0].split('|'))
|
|
|
|
if 'name' in each and each['name'] == 'support.variable.meson':
|
|
|
|
# \\b(builtin1|builtin2...)\\b
|
|
|
|
builtin = set(each['match'].split('\\b(')[1].split(')\\b')[0].split('|'))
|
|
|
|
self.assertEqual(builtin, set(interp.builtin.keys()))
|
|
|
|
self.assertEqual(funcs, set(interp.funcs.keys()))
|
|
|
|
|
|
|
|
def test_all_functions_defined_in_ast_interpreter(self):
|
|
|
|
'''
|
|
|
|
Ensure that the all functions defined in the Interpreter are also defined
|
|
|
|
in the AstInterpreter (and vice versa).
|
|
|
|
'''
|
|
|
|
env = get_fake_env()
|
|
|
|
interp = Interpreter(FakeBuild(env), mock=True)
|
|
|
|
astint = AstInterpreter('.', '')
|
|
|
|
self.assertEqual(set(interp.funcs.keys()), set(astint.funcs.keys()))
|
|
|
|
|
|
|
|
|
|
|
|
class BasePlatformTests(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
|
|
super().setUp()
|
|
|
|
src_root = os.path.dirname(__file__)
|
|
|
|
src_root = os.path.join(os.getcwd(), src_root)
|
|
|
|
self.src_root = src_root
|
|
|
|
self.prefix = '/usr'
|
|
|
|
self.libdir = 'lib'
|
|
|
|
# Get the backend
|
|
|
|
# FIXME: Extract this from argv?
|
|
|
|
self.backend = getattr(Backend, os.environ.get('MESON_UNIT_TEST_BACKEND', 'ninja'))
|
|
|
|
self.meson_args = ['--backend=' + self.backend.name]
|
|
|
|
self.meson_cross_file = None
|
Set the meson command to use when we know what it is
Instead of using fragile guessing to figure out how to invoke meson,
set the value when meson is run. Also rework how we pass of
meson_script_launcher to regenchecker.py -- it wasn't even being used
With this change, we only need to guess the meson path when running
the tests, and in that case:
1. If MESON_EXE is set in the env, we know how to run meson
for project tests.
2. MESON_EXE is not set, which means we run the configure in-process
for project tests and need to guess what meson to run, so either
- meson.py is found next to run_tests.py, or
- meson, meson.py, or meson.exe is in PATH
Otherwise, you can invoke meson in the following ways:
1. meson is installed, and mesonbuild is available in PYTHONPATH:
- meson, meson.py, meson.exe from PATH
- python3 -m mesonbuild.mesonmain
- python3 /path/to/meson.py
- meson is a shell wrapper to meson.real
2. meson is not installed, and is run from git:
- Absolute path to meson.py
- Relative path to meson.py
- Symlink to meson.py
All these are tested in test_meson_commands.py, except meson.exe since
that involves building the meson msi and installing it.
7 years ago
|
|
|
self.meson_command = python_command + [get_meson_script()]
|
|
|
|
self.setup_command = self.meson_command + self.meson_args
|
|
|
|
self.mconf_command = self.meson_command + ['configure']
|
|
|
|
self.mintro_command = self.meson_command + ['introspect']
|
|
|
|
self.wrap_command = self.meson_command + ['wrap']
|
|
|
|
self.rewrite_command = self.meson_command + ['rewrite']
|
|
|
|
# Backend-specific build commands
|
|
|
|
self.build_command, self.clean_command, self.test_command, self.install_command, \
|
|
|
|
self.uninstall_command = get_backend_commands(self.backend)
|
|
|
|
# Test directories
|
|
|
|
self.common_test_dir = os.path.join(src_root, 'test cases/common')
|
|
|
|
self.vala_test_dir = os.path.join(src_root, 'test cases/vala')
|
|
|
|
self.framework_test_dir = os.path.join(src_root, 'test cases/frameworks')
|
|
|
|
self.unit_test_dir = os.path.join(src_root, 'test cases/unit')
|
|
|
|
self.rewrite_test_dir = os.path.join(src_root, 'test cases/rewrite')
|
|
|
|
# Misc stuff
|
|
|
|
self.orig_env = os.environ.copy()
|
|
|
|
if self.backend is Backend.ninja:
|
|
|
|
self.no_rebuild_stdout = ['ninja: no work to do.', 'samu: nothing to do']
|
|
|
|
else:
|
|
|
|
# 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 = ['UNKNOWN BACKEND {!r}'.format(self.backend.name)]
|
|
|
|
|
|
|
|
self.builddirs = []
|
|
|
|
self.new_builddir()
|
|
|
|
|
|
|
|
def change_builddir(self, newdir):
|
|
|
|
self.builddir = newdir
|
|
|
|
self.privatedir = os.path.join(self.builddir, 'meson-private')
|
|
|
|
self.logdir = os.path.join(self.builddir, 'meson-logs')
|
|
|
|
self.installdir = os.path.join(self.builddir, 'install')
|
|
|
|
self.distdir = os.path.join(self.builddir, 'meson-dist')
|
Set the meson command to use when we know what it is
Instead of using fragile guessing to figure out how to invoke meson,
set the value when meson is run. Also rework how we pass of
meson_script_launcher to regenchecker.py -- it wasn't even being used
With this change, we only need to guess the meson path when running
the tests, and in that case:
1. If MESON_EXE is set in the env, we know how to run meson
for project tests.
2. MESON_EXE is not set, which means we run the configure in-process
for project tests and need to guess what meson to run, so either
- meson.py is found next to run_tests.py, or
- meson, meson.py, or meson.exe is in PATH
Otherwise, you can invoke meson in the following ways:
1. meson is installed, and mesonbuild is available in PYTHONPATH:
- meson, meson.py, meson.exe from PATH
- python3 -m mesonbuild.mesonmain
- python3 /path/to/meson.py
- meson is a shell wrapper to meson.real
2. meson is not installed, and is run from git:
- Absolute path to meson.py
- Relative path to meson.py
- Symlink to meson.py
All these are tested in test_meson_commands.py, except meson.exe since
that involves building the meson msi and installing it.
7 years ago
|
|
|
self.mtest_command = self.meson_command + ['test', '-C', self.builddir]
|
|
|
|
self.builddirs.append(self.builddir)
|
|
|
|
|
|
|
|
def new_builddir(self):
|
|
|
|
if not is_cygwin():
|
|
|
|
# Keep builddirs inside the source tree so that virus scanners
|
|
|
|
# don't complain
|
|
|
|
newdir = tempfile.mkdtemp(dir=os.getcwd())
|
|
|
|
else:
|
|
|
|
# But not on Cygwin because that breaks the umask tests. See:
|
|
|
|
# https://github.com/mesonbuild/meson/pull/5546#issuecomment-509666523
|
|
|
|
newdir = tempfile.mkdtemp()
|
|
|
|
# In case the directory is inside a symlinked directory, find the real
|
|
|
|
# path otherwise we might not find the srcdir from inside the builddir.
|
|
|
|
newdir = os.path.realpath(newdir)
|
|
|
|
self.change_builddir(newdir)
|
|
|
|
|
|
|
|
def _print_meson_log(self):
|
|
|
|
log = os.path.join(self.logdir, 'meson-log.txt')
|
|
|
|
if not os.path.isfile(log):
|
|
|
|
print("{!r} doesn't exist".format(log))
|
|
|
|
return
|
|
|
|
with open(log, 'r', encoding='utf-8') as f:
|
|
|
|
print(f.read())
|
|
|
|
|
|
|
|
def tearDown(self):
|
|
|
|
for path in self.builddirs:
|
|
|
|
try:
|
|
|
|
windows_proof_rmtree(path)
|
|
|
|
except FileNotFoundError:
|
|
|
|
pass
|
|
|
|
os.environ.clear()
|
|
|
|
os.environ.update(self.orig_env)
|
|
|
|
super().tearDown()
|
|
|
|
|
|
|
|
def _run(self, command, *, workdir=None, override_envvars=None):
|
|
|
|
'''
|
|
|
|
Run a command while printing the stdout and stderr to stdout,
|
|
|
|
and also return a copy of it
|
|
|
|
'''
|
|
|
|
# If this call hangs CI will just abort. It is very hard to distinguish
|
|
|
|
# between CI issue and test bug in that case. Set timeout and fail loud
|
|
|
|
# instead.
|
|
|
|
if override_envvars is None:
|
|
|
|
env = None
|
|
|
|
else:
|
|
|
|
env = os.environ.copy()
|
|
|
|
env.update(override_envvars)
|
|
|
|
|
|
|
|
p = subprocess.run(command, stdout=subprocess.PIPE,
|
|
|
|
stderr=subprocess.STDOUT, env=env,
|
|
|
|
universal_newlines=True, cwd=workdir, timeout=60 * 5)
|
|
|
|
print(p.stdout)
|
|
|
|
if p.returncode != 0:
|
|
|
|
if 'MESON_SKIP_TEST' in p.stdout:
|
|
|
|
raise unittest.SkipTest('Project requested skipping.')
|
|
|
|
raise subprocess.CalledProcessError(p.returncode, command, output=p.stdout)
|
|
|
|
return p.stdout
|
|
|
|
|
|
|
|
def init(self, srcdir, *,
|
|
|
|
extra_args=None,
|
|
|
|
default_args=True,
|
|
|
|
inprocess=False,
|
|
|
|
override_envvars=None):
|
|
|
|
self.assertPathExists(srcdir)
|
|
|
|
if extra_args is None:
|
|
|
|
extra_args = []
|
|
|
|
if not isinstance(extra_args, list):
|
|
|
|
extra_args = [extra_args]
|
|
|
|
args = [srcdir, self.builddir]
|
|
|
|
if default_args:
|
|
|
|
args += ['--prefix', self.prefix,
|
|
|
|
'--libdir', self.libdir]
|
|
|
|
if self.meson_cross_file:
|
|
|
|
args += ['--cross-file', self.meson_cross_file]
|
|
|
|
self.privatedir = os.path.join(self.builddir, 'meson-private')
|
|
|
|
if inprocess:
|
|
|
|
try:
|
|
|
|
if override_envvars is not None:
|
|
|
|
old_envvars = os.environ.copy()
|
|
|
|
os.environ.update(override_envvars)
|
Set the meson command to use when we know what it is
Instead of using fragile guessing to figure out how to invoke meson,
set the value when meson is run. Also rework how we pass of
meson_script_launcher to regenchecker.py -- it wasn't even being used
With this change, we only need to guess the meson path when running
the tests, and in that case:
1. If MESON_EXE is set in the env, we know how to run meson
for project tests.
2. MESON_EXE is not set, which means we run the configure in-process
for project tests and need to guess what meson to run, so either
- meson.py is found next to run_tests.py, or
- meson, meson.py, or meson.exe is in PATH
Otherwise, you can invoke meson in the following ways:
1. meson is installed, and mesonbuild is available in PYTHONPATH:
- meson, meson.py, meson.exe from PATH
- python3 -m mesonbuild.mesonmain
- python3 /path/to/meson.py
- meson is a shell wrapper to meson.real
2. meson is not installed, and is run from git:
- Absolute path to meson.py
- Relative path to meson.py
- Symlink to meson.py
All these are tested in test_meson_commands.py, except meson.exe since
that involves building the meson msi and installing it.
7 years ago
|
|
|
(returncode, out, err) = run_configure_inprocess(self.meson_args + args + extra_args)
|
|
|
|
if override_envvars is not None:
|
|
|
|
os.environ.clear()
|
|
|
|
os.environ.update(old_envvars)
|
|
|
|
if 'MESON_SKIP_TEST' in out:
|
|
|
|
raise unittest.SkipTest('Project requested skipping.')
|
|
|
|
if returncode != 0:
|
|
|
|
self._print_meson_log()
|
|
|
|
print('Stdout:\n')
|
|
|
|
print(out)
|
|
|
|
print('Stderr:\n')
|
|
|
|
print(err)
|
|
|
|
raise RuntimeError('Configure failed')
|
|
|
|
except Exception:
|
|
|
|
self._print_meson_log()
|
|
|
|
raise
|
|
|
|
finally:
|
|
|
|
# Close log file to satisfy Windows file locking
|
|
|
|
mesonbuild.mlog.shutdown()
|
|
|
|
mesonbuild.mlog.log_dir = None
|
|
|
|
mesonbuild.mlog.log_file = None
|
|
|
|
else:
|
|
|
|
try:
|
|
|
|
out = self._run(self.setup_command + args + extra_args, override_envvars=override_envvars)
|
|
|
|
except unittest.SkipTest:
|
|
|
|
raise unittest.SkipTest('Project requested skipping: ' + srcdir)
|
|
|
|
except Exception:
|
|
|
|
self._print_meson_log()
|
|
|
|
raise
|
|
|
|
return out
|
|
|
|
|
|
|
|
def build(self, target=None, *, extra_args=None, override_envvars=None):
|
|
|
|
if extra_args is None:
|
|
|
|
extra_args = []
|
|
|
|
# Add arguments for building the target (if specified),
|
|
|
|
# and using the build dir (if required, with VS)
|
|
|
|
args = get_builddir_target_args(self.backend, self.builddir, target)
|
|
|
|
return self._run(self.build_command + args + extra_args, workdir=self.builddir, override_envvars=override_envvars)
|
|
|
|
|
|
|
|
def clean(self, *, override_envvars=None):
|
|
|
|
dir_args = get_builddir_target_args(self.backend, self.builddir, None)
|
|
|
|
self._run(self.clean_command + dir_args, workdir=self.builddir, override_envvars=override_envvars)
|
|
|
|
|
|
|
|
def run_tests(self, *, inprocess=False, override_envvars=None):
|
|
|
|
if not inprocess:
|
|
|
|
self._run(self.test_command, workdir=self.builddir, override_envvars=override_envvars)
|
|
|
|
else:
|
|
|
|
if override_envvars is not None:
|
|
|
|
old_envvars = os.environ.copy()
|
|
|
|
os.environ.update(override_envvars)
|
|
|
|
try:
|
|
|
|
run_mtest_inprocess(['-C', self.builddir])
|
|
|
|
finally:
|
|
|
|
if override_envvars is not None:
|
|
|
|
os.environ.clear()
|
|
|
|
os.environ.update(old_envvars)
|
|
|
|
|
|
|
|
def install(self, *, use_destdir=True, override_envvars=None):
|
|
|
|
if self.backend is not Backend.ninja:
|
|
|
|
raise unittest.SkipTest('{!r} backend can\'t install files'.format(self.backend.name))
|
|
|
|
if use_destdir:
|
|
|
|
destdir = {'DESTDIR': self.installdir}
|
|
|
|
if override_envvars is None:
|
|
|
|
override_envvars = destdir
|
|
|
|
else:
|
|
|
|
override_envvars.update(destdir)
|
|
|
|
self._run(self.install_command, workdir=self.builddir, override_envvars=override_envvars)
|
|
|
|
|
|
|
|
def uninstall(self, *, override_envvars=None):
|
|
|
|
self._run(self.uninstall_command, workdir=self.builddir, override_envvars=override_envvars)
|
|
|
|
|
|
|
|
def run_target(self, target, *, override_envvars=None):
|
|
|
|
'''
|
|
|
|
Run a Ninja target while printing the stdout and stderr to stdout,
|
|
|
|
and also return a copy of it
|
|
|
|
'''
|
|
|
|
return self.build(target=target, override_envvars=override_envvars)
|
|
|
|
|
|
|
|
def setconf(self, arg, will_build=True):
|
|
|
|
if not isinstance(arg, list):
|
|
|
|
arg = [arg]
|
|
|
|
if will_build:
|
|
|
|
ensure_backend_detects_changes(self.backend)
|
|
|
|
self._run(self.mconf_command + arg + [self.builddir])
|
|
|
|
|
|
|
|
def wipe(self):
|
|
|
|
windows_proof_rmtree(self.builddir)
|
|
|
|
|
|
|
|
def utime(self, f):
|
|
|
|
ensure_backend_detects_changes(self.backend)
|
|
|
|
os.utime(f)
|
|
|
|
|
|
|
|
def get_compdb(self):
|
|
|
|
if self.backend is not Backend.ninja:
|
|
|
|
raise unittest.SkipTest('Compiler db not available with {} backend'.format(self.backend.name))
|
|
|
|
try:
|
|
|
|
with open(os.path.join(self.builddir, 'compile_commands.json')) as ifile:
|
|
|
|
contents = json.load(ifile)
|
|
|
|
except FileNotFoundError:
|
|
|
|
raise unittest.SkipTest('Compiler db not found')
|
|
|
|
# If Ninja is using .rsp files, generate them, read their contents, and
|
|
|
|
# replace it as the command for all compile commands in the parsed json.
|
|
|
|
if len(contents) > 0 and contents[0]['command'].endswith('.rsp'):
|
|
|
|
# Pretend to build so that the rsp files are generated
|
|
|
|
self.build(extra_args=['-d', 'keeprsp', '-n'])
|
|
|
|
for each in contents:
|
|
|
|
# Extract the actual command from the rsp file
|
|
|
|
compiler, rsp = each['command'].split(' @')
|
|
|
|
rsp = os.path.join(self.builddir, rsp)
|
|
|
|
# Replace the command with its contents
|
|
|
|
with open(rsp, 'r', encoding='utf-8') as f:
|
|
|
|
each['command'] = compiler + ' ' + f.read()
|
|
|
|
return contents
|
|
|
|
|
|
|
|
def get_meson_log(self):
|
|
|
|
with open(os.path.join(self.builddir, 'meson-logs', 'meson-log.txt')) as f:
|
|
|
|
return f.readlines()
|
|
|
|
|
|
|
|
def get_meson_log_compiler_checks(self):
|
|
|
|
'''
|
|
|
|
Fetch a list command-lines run by meson for compiler checks.
|
|
|
|
Each command-line is returned as a list of arguments.
|
|
|
|
'''
|
|
|
|
log = self.get_meson_log()
|
|
|
|
prefix = 'Command line:'
|
|
|
|
cmds = [l[len(prefix):].split() for l in log if l.startswith(prefix)]
|
|
|
|
return cmds
|
|
|
|
|
|
|
|
def introspect(self, args):
|
|
|
|
if isinstance(args, str):
|
|
|
|
args = [args]
|
|
|
|
out = subprocess.check_output(self.mintro_command + args + [self.builddir],
|
|
|
|
universal_newlines=True)
|
|
|
|
return json.loads(out)
|
|
|
|
|
|
|
|
def introspect_directory(self, directory, args):
|
|
|
|
if isinstance(args, str):
|
|
|
|
args = [args]
|
|
|
|
out = subprocess.check_output(self.mintro_command + args + [directory],
|
|
|
|
universal_newlines=True)
|
|
|
|
try:
|
|
|
|
obj = json.loads(out)
|
|
|
|
except Exception as e:
|
|
|
|
print(out)
|
|
|
|
raise e
|
|
|
|
return obj
|
|
|
|
|
|
|
|
def assertPathEqual(self, path1, path2):
|
|
|
|
'''
|
|
|
|
Handles a lot of platform-specific quirks related to paths such as
|
|
|
|
separator, case-sensitivity, etc.
|
|
|
|
'''
|
|
|
|
self.assertEqual(PurePath(path1), PurePath(path2))
|
|
|
|
|
|
|
|
def assertPathListEqual(self, pathlist1, pathlist2):
|
|
|
|
self.assertEqual(len(pathlist1), len(pathlist2))
|
|
|
|
worklist = list(zip(pathlist1, pathlist2))
|
|
|
|
for i in worklist:
|
|
|
|
if i[0] is None:
|
|
|
|
self.assertEqual(i[0], i[1])
|
|
|
|
else:
|
|
|
|
self.assertPathEqual(i[0], i[1])
|
|
|
|
|
|
|
|
def assertPathBasenameEqual(self, path, basename):
|
|
|
|
msg = '{!r} does not end with {!r}'.format(path, basename)
|
|
|
|
# We cannot use os.path.basename because it returns '' when the path
|
|
|
|
# ends with '/' for some silly reason. This is not how the UNIX utility
|
|
|
|
# `basename` works.
|
|
|
|
path_basename = PurePath(path).parts[-1]
|
|
|
|
self.assertEqual(PurePath(path_basename), PurePath(basename), msg)
|
|
|
|
|
|
|
|
def assertBuildIsNoop(self):
|
|
|
|
ret = self.build()
|
|
|
|
if self.backend is Backend.ninja:
|
|
|
|
self.assertIn(ret.split('\n')[-2], self.no_rebuild_stdout)
|
|
|
|
elif self.backend is Backend.vs:
|
|
|
|
# Ensure that some target said that no rebuild was done
|
|
|
|
self.assertIn('CustomBuild:\n All outputs are up-to-date.', ret)
|
|
|
|
self.assertIn('ClCompile:\n All outputs are up-to-date.', ret)
|
|
|
|
self.assertIn('Link:\n All outputs are up-to-date.', ret)
|
|
|
|
# Ensure that no targets were built
|
|
|
|
clre = re.compile('ClCompile:\n [^\n]*cl', flags=re.IGNORECASE)
|
|
|
|
linkre = re.compile('Link:\n [^\n]*link', flags=re.IGNORECASE)
|
|
|
|
self.assertNotRegex(ret, clre)
|
|
|
|
self.assertNotRegex(ret, linkre)
|
|
|
|
elif self.backend is Backend.xcode:
|
|
|
|
raise unittest.SkipTest('Please help us fix this test on the xcode backend')
|
|
|
|
else:
|
|
|
|
raise RuntimeError('Invalid backend: {!r}'.format(self.backend.name))
|
|
|
|
|
|
|
|
def assertRebuiltTarget(self, target):
|
|
|
|
ret = self.build()
|
|
|
|
if self.backend is Backend.ninja:
|
|
|
|
self.assertIn('Linking target {}'.format(target), ret)
|
|
|
|
elif self.backend is Backend.vs:
|
|
|
|
# Ensure that this target was rebuilt
|
|
|
|
linkre = re.compile('Link:\n [^\n]*link[^\n]*' + target, flags=re.IGNORECASE)
|
|
|
|
self.assertRegex(ret, linkre)
|
|
|
|
elif self.backend is Backend.xcode:
|
|
|
|
raise unittest.SkipTest('Please help us fix this test on the xcode backend')
|
|
|
|
else:
|
|
|
|
raise RuntimeError('Invalid backend: {!r}'.format(self.backend.name))
|
|
|
|
|
|
|
|
def assertPathExists(self, path):
|
|
|
|
m = 'Path {!r} should exist'.format(path)
|
|
|
|
self.assertTrue(os.path.exists(path), msg=m)
|
|
|
|
|
|
|
|
def assertPathDoesNotExist(self, path):
|
|
|
|
m = 'Path {!r} should not exist'.format(path)
|
|
|
|
self.assertFalse(os.path.exists(path), msg=m)
|
|
|
|
|
|
|
|
|
|
|
|
class AllPlatformTests(BasePlatformTests):
|
|
|
|
'''
|
|
|
|
Tests that should run on all platforms
|
|
|
|
'''
|
|
|
|
|
|
|
|
def test_default_options_prefix(self):
|
|
|
|
'''
|
|
|
|
Tests that setting a prefix in default_options in project() works.
|
|
|
|
Can't be an ordinary test because we pass --prefix to meson there.
|
|
|
|
https://github.com/mesonbuild/meson/issues/1349
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.common_test_dir, '90 default options')
|
|
|
|
self.init(testdir, default_args=False)
|
|
|
|
opts = self.introspect('--buildoptions')
|
|
|
|
for opt in opts:
|
|
|
|
if opt['name'] == 'prefix':
|
|
|
|
prefix = opt['value']
|
|
|
|
self.assertEqual(prefix, '/absoluteprefix')
|
|
|
|
|
|
|
|
def test_do_conf_file_preserve_newlines(self):
|
|
|
|
|
|
|
|
def conf_file(in_data, confdata):
|
|
|
|
with temp_filename() as fin:
|
|
|
|
with open(fin, 'wb') as fobj:
|
|
|
|
fobj.write(in_data.encode('utf-8'))
|
|
|
|
with temp_filename() as fout:
|
|
|
|
mesonbuild.mesonlib.do_conf_file(fin, fout, confdata, 'meson')
|
|
|
|
with open(fout, 'rb') as fobj:
|
|
|
|
return fobj.read().decode('utf-8')
|
|
|
|
|
|
|
|
confdata = {'VAR': ('foo', 'bar')}
|
|
|
|
self.assertEqual(conf_file('@VAR@\n@VAR@\n', confdata), 'foo\nfoo\n')
|
|
|
|
self.assertEqual(conf_file('@VAR@\r\n@VAR@\r\n', confdata), 'foo\r\nfoo\r\n')
|
|
|
|
|
|
|
|
def test_absolute_prefix_libdir(self):
|
|
|
|
'''
|
|
|
|
Tests that setting absolute paths for --prefix and --libdir work. Can't
|
|
|
|
be an ordinary test because these are set via the command-line.
|
|
|
|
https://github.com/mesonbuild/meson/issues/1341
|
|
|
|
https://github.com/mesonbuild/meson/issues/1345
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.common_test_dir, '90 default options')
|
|
|
|
prefix = '/someabs'
|
|
|
|
libdir = 'libdir'
|
|
|
|
extra_args = ['--prefix=' + prefix,
|
|
|
|
# This can just be a relative path, but we want to test
|
|
|
|
# that passing this as an absolute path also works
|
|
|
|
'--libdir=' + prefix + '/' + libdir]
|
|
|
|
self.init(testdir, extra_args=extra_args, default_args=False)
|
|
|
|
opts = self.introspect('--buildoptions')
|
|
|
|
for opt in opts:
|
|
|
|
if opt['name'] == 'prefix':
|
|
|
|
self.assertEqual(prefix, opt['value'])
|
|
|
|
elif opt['name'] == 'libdir':
|
|
|
|
self.assertEqual(libdir, opt['value'])
|
|
|
|
|
|
|
|
def test_libdir_must_be_inside_prefix(self):
|
|
|
|
'''
|
|
|
|
Tests that libdir is forced to be inside prefix no matter how it is set.
|
|
|
|
Must be a unit test for obvious reasons.
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.common_test_dir, '1 trivial')
|
|
|
|
# libdir being inside prefix is ok
|
|
|
|
args = ['--prefix', '/opt', '--libdir', '/opt/lib32']
|
|
|
|
self.init(testdir, extra_args=args)
|
|
|
|
self.wipe()
|
|
|
|
# libdir not being inside prefix is not ok
|
|
|
|
args = ['--prefix', '/usr', '--libdir', '/opt/lib32']
|
|
|
|
self.assertRaises(subprocess.CalledProcessError, self.init, testdir, extra_args=args)
|
|
|
|
self.wipe()
|
|
|
|
# libdir must be inside prefix even when set via mesonconf
|
|
|
|
self.init(testdir)
|
|
|
|
self.assertRaises(subprocess.CalledProcessError, self.setconf, '-Dlibdir=/opt', False)
|
|
|
|
|
|
|
|
def test_prefix_dependent_defaults(self):
|
|
|
|
'''
|
|
|
|
Tests that configured directory paths are set to prefix dependent
|
|
|
|
defaults.
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.common_test_dir, '1 trivial')
|
|
|
|
expected = {
|
|
|
|
'/opt': {'prefix': '/opt',
|
|
|
|
'bindir': 'bin', 'datadir': 'share', 'includedir': 'include',
|
|
|
|
'infodir': 'share/info',
|
|
|
|
'libexecdir': 'libexec', 'localedir': 'share/locale',
|
|
|
|
'localstatedir': 'var', 'mandir': 'share/man',
|
|
|
|
'sbindir': 'sbin', 'sharedstatedir': 'com',
|
|
|
|
'sysconfdir': 'etc'},
|
|
|
|
'/usr': {'prefix': '/usr',
|
|
|
|
'bindir': 'bin', 'datadir': 'share', 'includedir': 'include',
|
|
|
|
'infodir': 'share/info',
|
|
|
|
'libexecdir': 'libexec', 'localedir': 'share/locale',
|
|
|
|
'localstatedir': '/var', 'mandir': 'share/man',
|
|
|
|
'sbindir': 'sbin', 'sharedstatedir': '/var/lib',
|
|
|
|
'sysconfdir': '/etc'},
|
|
|
|
'/usr/local': {'prefix': '/usr/local',
|
|
|
|
'bindir': 'bin', 'datadir': 'share',
|
|
|
|
'includedir': 'include', 'infodir': 'share/info',
|
|
|
|
'libexecdir': 'libexec',
|
|
|
|
'localedir': 'share/locale',
|
|
|
|
'localstatedir': '/var/local', 'mandir': 'share/man',
|
|
|
|
'sbindir': 'sbin', 'sharedstatedir': '/var/local/lib',
|
|
|
|
'sysconfdir': 'etc'},
|
|
|
|
# N.B. We don't check 'libdir' as it's platform dependent, see
|
|
|
|
# default_libdir():
|
|
|
|
}
|
|
|
|
for prefix in expected:
|
|
|
|
args = ['--prefix', prefix]
|
|
|
|
self.init(testdir, extra_args=args, default_args=False)
|
|
|
|
opts = self.introspect('--buildoptions')
|
|
|
|
for opt in opts:
|
|
|
|
name = opt['name']
|
|
|
|
value = opt['value']
|
|
|
|
if name in expected[prefix]:
|
|
|
|
self.assertEqual(value, expected[prefix][name])
|
|
|
|
self.wipe()
|
|
|
|
|
|
|
|
def test_default_options_prefix_dependent_defaults(self):
|
|
|
|
'''
|
|
|
|
Tests that setting a prefix in default_options in project() sets prefix
|
|
|
|
dependent defaults for other options, and that those defaults can
|
|
|
|
be overridden in default_options or by the command line.
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.common_test_dir, '168 default options prefix dependent defaults')
|
|
|
|
expected = {
|
|
|
|
'':
|
|
|
|
{'prefix': '/usr',
|
|
|
|
'sysconfdir': '/etc',
|
|
|
|
'localstatedir': '/var',
|
|
|
|
'sharedstatedir': '/sharedstate'},
|
|
|
|
'--prefix=/usr':
|
|
|
|
{'prefix': '/usr',
|
|
|
|
'sysconfdir': '/etc',
|
|
|
|
'localstatedir': '/var',
|
|
|
|
'sharedstatedir': '/sharedstate'},
|
|
|
|
'--sharedstatedir=/var/state':
|
|
|
|
{'prefix': '/usr',
|
|
|
|
'sysconfdir': '/etc',
|
|
|
|
'localstatedir': '/var',
|
|
|
|
'sharedstatedir': '/var/state'},
|
|
|
|
'--sharedstatedir=/var/state --prefix=/usr --sysconfdir=sysconf':
|
|
|
|
{'prefix': '/usr',
|
|
|
|
'sysconfdir': 'sysconf',
|
|
|
|
'localstatedir': '/var',
|
|
|
|
'sharedstatedir': '/var/state'},
|
|
|
|
}
|
|
|
|
for args in expected:
|
|
|
|
self.init(testdir, extra_args=args.split(), default_args=False)
|
|
|
|
opts = self.introspect('--buildoptions')
|
|
|
|
for opt in opts:
|
|
|
|
name = opt['name']
|
|
|
|
value = opt['value']
|
|
|
|
if name in expected[args]:
|
|
|
|
self.assertEqual(value, expected[args][name])
|
|
|
|
self.wipe()
|
|
|
|
|
|
|
|
def test_clike_get_library_dirs(self):
|
|
|
|
env = get_fake_env()
|
|
|
|
cc = env.detect_c_compiler(MachineChoice.HOST)
|
|
|
|
for d in cc.get_library_dirs(env):
|
|
|
|
self.assertTrue(os.path.exists(d))
|
|
|
|
self.assertTrue(os.path.isdir(d))
|
|
|
|
self.assertTrue(os.path.isabs(d))
|
|
|
|
|
|
|
|
def test_static_library_overwrite(self):
|
|
|
|
'''
|
|
|
|
Tests that static libraries are never appended to, always overwritten.
|
|
|
|
Has to be a unit test because this involves building a project,
|
|
|
|
reconfiguring, and building it again so that `ar` is run twice on the
|
|
|
|
same static library.
|
|
|
|
https://github.com/mesonbuild/meson/issues/1355
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.common_test_dir, '3 static')
|
|
|
|
env = get_fake_env(testdir, self.builddir, self.prefix)
|
|
|
|
cc = env.detect_c_compiler(MachineChoice.HOST)
|
|
|
|
static_linker = env.detect_static_linker(cc)
|
|
|
|
if is_windows():
|
|
|
|
raise unittest.SkipTest('https://github.com/mesonbuild/meson/issues/1526')
|
|
|
|
if not isinstance(static_linker, mesonbuild.linkers.ArLinker):
|
|
|
|
raise unittest.SkipTest('static linker is not `ar`')
|
|
|
|
# Configure
|
|
|
|
self.init(testdir)
|
|
|
|
# Get name of static library
|
|
|
|
targets = self.introspect('--targets')
|
|
|
|
self.assertEqual(len(targets), 1)
|
|
|
|
libname = targets[0]['filename'][0]
|
|
|
|
# Build and get contents of static library
|
|
|
|
self.build()
|
|
|
|
before = self._run(['ar', 't', os.path.join(self.builddir, libname)]).split()
|
|
|
|
# Filter out non-object-file contents
|
|
|
|
before = [f for f in before if f.endswith(('.o', '.obj'))]
|
|
|
|
# Static library should contain only one object
|
|
|
|
self.assertEqual(len(before), 1, msg=before)
|
|
|
|
# Change the source to be built into the static library
|
|
|
|
self.setconf('-Dsource=libfile2.c')
|
|
|
|
self.build()
|
|
|
|
after = self._run(['ar', 't', os.path.join(self.builddir, libname)]).split()
|
|
|
|
# Filter out non-object-file contents
|
|
|
|
after = [f for f in after if f.endswith(('.o', '.obj'))]
|
|
|
|
# Static library should contain only one object
|
|
|
|
self.assertEqual(len(after), 1, msg=after)
|
|
|
|
# and the object must have changed
|
|
|
|
self.assertNotEqual(before, after)
|
|
|
|
|
|
|
|
def test_static_compile_order(self):
|
|
|
|
'''
|
|
|
|
Test that the order of files in a compiler command-line while compiling
|
|
|
|
and linking statically is deterministic. This can't be an ordinary test
|
|
|
|
case because we need to inspect the compiler database.
|
|
|
|
https://github.com/mesonbuild/meson/pull/951
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.common_test_dir, '5 linkstatic')
|
|
|
|
self.init(testdir)
|
|
|
|
compdb = self.get_compdb()
|
|
|
|
# Rules will get written out in this order
|
|
|
|
self.assertTrue(compdb[0]['file'].endswith("libfile.c"))
|
|
|
|
self.assertTrue(compdb[1]['file'].endswith("libfile2.c"))
|
|
|
|
self.assertTrue(compdb[2]['file'].endswith("libfile3.c"))
|
|
|
|
self.assertTrue(compdb[3]['file'].endswith("libfile4.c"))
|
|
|
|
# FIXME: We don't have access to the linker command
|
|
|
|
|
|
|
|
def test_run_target_files_path(self):
|
|
|
|
'''
|
|
|
|
Test that run_targets are run from the correct directory
|
|
|
|
https://github.com/mesonbuild/meson/issues/957
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.common_test_dir, '54 run target')
|
|
|
|
self.init(testdir)
|
|
|
|
self.run_target('check_exists')
|
|
|
|
|
|
|
|
def test_install_introspection(self):
|
|
|
|
'''
|
|
|
|
Tests that the Meson introspection API exposes install filenames correctly
|
|
|
|
https://github.com/mesonbuild/meson/issues/829
|
|
|
|
'''
|
|
|
|
if self.backend is not Backend.ninja:
|
|
|
|
raise unittest.SkipTest('{!r} backend can\'t install files'.format(self.backend.name))
|
|
|
|
testdir = os.path.join(self.common_test_dir, '8 install')
|
|
|
|
self.init(testdir)
|
|
|
|
intro = self.introspect('--targets')
|
|
|
|
if intro[0]['type'] == 'executable':
|
|
|
|
intro = intro[::-1]
|
|
|
|
self.assertPathListEqual(intro[0]['install_filename'], ['/usr/lib/libstat.a'])
|
|
|
|
self.assertPathListEqual(intro[1]['install_filename'], ['/usr/bin/prog' + exe_suffix])
|
|
|
|
|
|
|
|
def test_install_subdir_introspection(self):
|
|
|
|
'''
|
|
|
|
Test that the Meson introspection API also contains subdir install information
|
|
|
|
https://github.com/mesonbuild/meson/issues/5556
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.common_test_dir, '62 install subdir')
|
|
|
|
self.init(testdir)
|
|
|
|
intro = self.introspect('--installed')
|
|
|
|
expected = {
|
|
|
|
'sub2': 'share/sub2',
|
|
|
|
'subdir/sub1': 'share/sub1',
|
|
|
|
'subdir/sub_elided': 'share',
|
|
|
|
'sub1': 'share/sub1',
|
|
|
|
'sub/sub1': 'share/sub1',
|
|
|
|
'sub_elided': 'share',
|
|
|
|
'nested_elided/sub': 'share',
|
|
|
|
}
|
|
|
|
|
|
|
|
self.assertEqual(len(intro), len(expected))
|
|
|
|
|
|
|
|
# Convert expected to PurePath
|
|
|
|
expected_converted = {PurePath(os.path.join(testdir, key)): PurePath(os.path.join(self.prefix, val)) for key, val in expected.items()}
|
|
|
|
intro_converted = {PurePath(key): PurePath(val) for key, val in intro.items()}
|
|
|
|
|
|
|
|
for src, dst in expected_converted.items():
|
|
|
|
self.assertIn(src, intro_converted)
|
|
|
|
self.assertEqual(dst, intro_converted[src])
|
|
|
|
|
|
|
|
def test_install_introspection_multiple_outputs(self):
|
|
|
|
'''
|
|
|
|
Tests that the Meson introspection API exposes multiple install filenames correctly without crashing
|
|
|
|
https://github.com/mesonbuild/meson/pull/4555
|
|
|
|
|
|
|
|
Reverted to the first file only because of https://github.com/mesonbuild/meson/pull/4547#discussion_r244173438
|
|
|
|
TODO Change the format to a list officially in a followup PR
|
|
|
|
'''
|
|
|
|
if self.backend is not Backend.ninja:
|
|
|
|
raise unittest.SkipTest('{!r} backend can\'t install files'.format(self.backend.name))
|
|
|
|
testdir = os.path.join(self.common_test_dir, '144 custom target multiple outputs')
|
|
|
|
self.init(testdir)
|
|
|
|
intro = self.introspect('--targets')
|
|
|
|
if intro[0]['type'] == 'executable':
|
|
|
|
intro = intro[::-1]
|
|
|
|
self.assertPathListEqual(intro[0]['install_filename'], ['/usr/include/diff.h', '/usr/bin/diff.sh'])
|
|
|
|
self.assertPathListEqual(intro[1]['install_filename'], ['/opt/same.h', '/opt/same.sh'])
|
|
|
|
self.assertPathListEqual(intro[2]['install_filename'], ['/usr/include/first.h', None])
|
|
|
|
self.assertPathListEqual(intro[3]['install_filename'], [None, '/usr/bin/second.sh'])
|
|
|
|
|
|
|
|
def test_install_log_content(self):
|
|
|
|
'''
|
|
|
|
Tests that the install-log.txt is consistent with the installed files and directories.
|
|
|
|
Specifically checks that the log file only contains one entry per file/directory.
|
|
|
|
https://github.com/mesonbuild/meson/issues/4499
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.common_test_dir, '62 install subdir')
|
|
|
|
self.init(testdir)
|
|
|
|
self.install()
|
|
|
|
installpath = Path(self.installdir)
|
|
|
|
# Find installed files and directories
|
|
|
|
expected = {installpath: 0}
|
|
|
|
for name in installpath.rglob('*'):
|
|
|
|
expected[name] = 0
|
|
|
|
# Find logged files and directories
|
|
|
|
with Path(self.builddir, 'meson-logs', 'install-log.txt').open() as f:
|
|
|
|
logged = list(map(lambda l: Path(l.strip()),
|
|
|
|
filter(lambda l: not l.startswith('#'),
|
|
|
|
f.readlines())))
|
|
|
|
for name in logged:
|
|
|
|
self.assertTrue(name in expected, 'Log contains extra entry {}'.format(name))
|
|
|
|
expected[name] += 1
|
|
|
|
|
|
|
|
for name, count in expected.items():
|
|
|
|
self.assertGreater(count, 0, 'Log is missing entry for {}'.format(name))
|
|
|
|
self.assertLess(count, 2, 'Log has multiple entries for {}'.format(name))
|
|
|
|
|
|
|
|
def test_uninstall(self):
|
|
|
|
exename = os.path.join(self.installdir, 'usr/bin/prog' + exe_suffix)
|
|
|
|
testdir = os.path.join(self.common_test_dir, '8 install')
|
|
|
|
self.init(testdir)
|
|
|
|
self.assertPathDoesNotExist(exename)
|
|
|
|
self.install()
|
|
|
|
self.assertPathExists(exename)
|
|
|
|
self.uninstall()
|
|
|
|
self.assertPathDoesNotExist(exename)
|
|
|
|
|
|
|
|
def test_forcefallback(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '31 forcefallback')
|
|
|
|
self.init(testdir, extra_args=['--wrap-mode=forcefallback'])
|
|
|
|
self.build()
|
|
|
|
self.run_tests()
|
|
|
|
|
|
|
|
def test_env_ops_dont_stack(self):
|
|
|
|
'''
|
|
|
|
Test that env ops prepend/append do not stack, and that this usage issues a warning
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '63 test env does not stack')
|
|
|
|
out = self.init(testdir)
|
|
|
|
self.assertRegex(out, r'WARNING: Overriding.*TEST_VAR_APPEND')
|
|
|
|
self.assertRegex(out, r'WARNING: Overriding.*TEST_VAR_PREPEND')
|
|
|
|
self.assertNotRegex(out, r'WARNING: Overriding.*TEST_VAR_SET')
|
|
|
|
self.run_tests()
|
|
|
|
|
|
|
|
def test_testsetups(self):
|
|
|
|
if not shutil.which('valgrind'):
|
|
|
|
raise unittest.SkipTest('Valgrind not installed.')
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '2 testsetups')
|
|
|
|
self.init(testdir)
|
|
|
|
self.build()
|
|
|
|
# Run tests without setup
|
|
|
|
self.run_tests()
|
|
|
|
with open(os.path.join(self.logdir, 'testlog.txt')) as f:
|
|
|
|
basic_log = f.read()
|
|
|
|
# Run buggy test with setup that has env that will make it fail
|
|
|
|
self.assertRaises(subprocess.CalledProcessError,
|
|
|
|
self._run, self.mtest_command + ['--setup=valgrind'])
|
|
|
|
with open(os.path.join(self.logdir, 'testlog-valgrind.txt')) as f:
|
|
|
|
vg_log = f.read()
|
|
|
|
self.assertFalse('TEST_ENV is set' in basic_log)
|
|
|
|
self.assertFalse('Memcheck' in basic_log)
|
|
|
|
self.assertTrue('TEST_ENV is set' in vg_log)
|
|
|
|
self.assertTrue('Memcheck' in vg_log)
|
|
|
|
# Run buggy test with setup without env that will pass
|
|
|
|
self._run(self.mtest_command + ['--setup=wrapper'])
|
|
|
|
# Setup with no properties works
|
|
|
|
self._run(self.mtest_command + ['--setup=empty'])
|
|
|
|
# Setup with only env works
|
|
|
|
self._run(self.mtest_command + ['--setup=onlyenv'])
|
|
|
|
self._run(self.mtest_command + ['--setup=onlyenv2'])
|
|
|
|
self._run(self.mtest_command + ['--setup=onlyenv3'])
|
|
|
|
# Setup with only a timeout works
|
|
|
|
self._run(self.mtest_command + ['--setup=timeout'])
|
|
|
|
|
|
|
|
def test_testsetup_selection(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '14 testsetup selection')
|
|
|
|
self.init(testdir)
|
|
|
|
self.build()
|
|
|
|
|
|
|
|
# Run tests without setup
|
|
|
|
self.run_tests()
|
|
|
|
|
|
|
|
self.assertRaises(subprocess.CalledProcessError, self._run, self.mtest_command + ['--setup=missingfromfoo'])
|
|
|
|
self._run(self.mtest_command + ['--setup=missingfromfoo', '--no-suite=foo:'])
|
|
|
|
|
|
|
|
self._run(self.mtest_command + ['--setup=worksforall'])
|
|
|
|
self._run(self.mtest_command + ['--setup=main:worksforall'])
|
|
|
|
|
|
|
|
self.assertRaises(subprocess.CalledProcessError, self._run,
|
|
|
|
self.mtest_command + ['--setup=onlyinbar'])
|
|
|
|
self.assertRaises(subprocess.CalledProcessError, self._run,
|
|
|
|
self.mtest_command + ['--setup=onlyinbar', '--no-suite=main:'])
|
|
|
|
self._run(self.mtest_command + ['--setup=onlyinbar', '--no-suite=main:', '--no-suite=foo:'])
|
|
|
|
self._run(self.mtest_command + ['--setup=bar:onlyinbar'])
|
|
|
|
self.assertRaises(subprocess.CalledProcessError, self._run,
|
|
|
|
self.mtest_command + ['--setup=foo:onlyinbar'])
|
|
|
|
self.assertRaises(subprocess.CalledProcessError, self._run,
|
|
|
|
self.mtest_command + ['--setup=main:onlyinbar'])
|
|
|
|
|
|
|
|
def test_testsetup_default(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '49 testsetup default')
|
|
|
|
self.init(testdir)
|
|
|
|
self.build()
|
|
|
|
|
|
|
|
# Run tests without --setup will cause the default setup to be used
|
|
|
|
self.run_tests()
|
|
|
|
with open(os.path.join(self.logdir, 'testlog.txt')) as f:
|
|
|
|
default_log = f.read()
|
|
|
|
|
|
|
|
# Run tests with explicitly using the same setup that is set as default
|
|
|
|
self._run(self.mtest_command + ['--setup=mydefault'])
|
|
|
|
with open(os.path.join(self.logdir, 'testlog-mydefault.txt')) as f:
|
|
|
|
mydefault_log = f.read()
|
|
|
|
|
|
|
|
# Run tests with another setup
|
|
|
|
self._run(self.mtest_command + ['--setup=other'])
|
|
|
|
with open(os.path.join(self.logdir, 'testlog-other.txt')) as f:
|
|
|
|
other_log = f.read()
|
|
|
|
|
|
|
|
self.assertTrue('ENV_A is 1' in default_log)
|
|
|
|
self.assertTrue('ENV_B is 2' in default_log)
|
|
|
|
self.assertTrue('ENV_C is 2' in default_log)
|
|
|
|
|
|
|
|
self.assertTrue('ENV_A is 1' in mydefault_log)
|
|
|
|
self.assertTrue('ENV_B is 2' in mydefault_log)
|
|
|
|
self.assertTrue('ENV_C is 2' in mydefault_log)
|
|
|
|
|
|
|
|
self.assertTrue('ENV_A is 1' in other_log)
|
|
|
|
self.assertTrue('ENV_B is 3' in other_log)
|
|
|
|
self.assertTrue('ENV_C is 2' in other_log)
|
|
|
|
|
|
|
|
def assertFailedTestCount(self, failure_count, command):
|
|
|
|
try:
|
|
|
|
self._run(command)
|
|
|
|
self.assertEqual(0, failure_count, 'Expected %d tests to fail.' % failure_count)
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
|
|
self.assertEqual(e.returncode, failure_count)
|
|
|
|
|
|
|
|
def test_suite_selection(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '4 suite selection')
|
|
|
|
self.init(testdir)
|
|
|
|
self.build()
|
|
|
|
|
mtest: check for an empty suite list
For consistency, it can be useful to have an explicit empty test suite list
for a test:
test('test-name', binary, suite: [])
This currently passes meson but fails when running meson tests:
Traceback (most recent call last):
File "/usr/lib/python3.7/site-packages/mesonbuild/mesonmain.py", line 122, in run
return options.run_func(options)
File "/usr/lib/python3.7/site-packages/mesonbuild/mtest.py", line 1005, in run
return th.doit()
File "/usr/lib/python3.7/site-packages/mesonbuild/mtest.py", line 756, in doit
self.run_tests(tests)
File "/usr/lib/python3.7/site-packages/mesonbuild/mtest.py", line 896, in run_tests
visible_name = self.get_pretty_suite(test)
File "/usr/lib/python3.7/site-packages/mesonbuild/mtest.py", line 875, in get_pretty_suite
rv = TestHarness.split_suite_string(test.suite[0])[0]
IndexError: list index out of range
Fix it by simply checking for the test suite to be a valid list we can pass on
Fixes #5340
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
6 years ago
|
|
|
self.assertFailedTestCount(4, self.mtest_command)
|
|
|
|
|
|
|
|
self.assertFailedTestCount(0, self.mtest_command + ['--suite', ':success'])
|
|
|
|
self.assertFailedTestCount(3, self.mtest_command + ['--suite', ':fail'])
|
mtest: check for an empty suite list
For consistency, it can be useful to have an explicit empty test suite list
for a test:
test('test-name', binary, suite: [])
This currently passes meson but fails when running meson tests:
Traceback (most recent call last):
File "/usr/lib/python3.7/site-packages/mesonbuild/mesonmain.py", line 122, in run
return options.run_func(options)
File "/usr/lib/python3.7/site-packages/mesonbuild/mtest.py", line 1005, in run
return th.doit()
File "/usr/lib/python3.7/site-packages/mesonbuild/mtest.py", line 756, in doit
self.run_tests(tests)
File "/usr/lib/python3.7/site-packages/mesonbuild/mtest.py", line 896, in run_tests
visible_name = self.get_pretty_suite(test)
File "/usr/lib/python3.7/site-packages/mesonbuild/mtest.py", line 875, in get_pretty_suite
rv = TestHarness.split_suite_string(test.suite[0])[0]
IndexError: list index out of range
Fix it by simply checking for the test suite to be a valid list we can pass on
Fixes #5340
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
6 years ago
|
|
|
self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', ':success'])
|
|
|
|
self.assertFailedTestCount(1, self.mtest_command + ['--no-suite', ':fail'])
|
|
|
|
|
|
|
|
self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'mainprj'])
|
|
|
|
self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc'])
|
|
|
|
self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail'])
|
|
|
|
self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjmix'])
|
mtest: check for an empty suite list
For consistency, it can be useful to have an explicit empty test suite list
for a test:
test('test-name', binary, suite: [])
This currently passes meson but fails when running meson tests:
Traceback (most recent call last):
File "/usr/lib/python3.7/site-packages/mesonbuild/mesonmain.py", line 122, in run
return options.run_func(options)
File "/usr/lib/python3.7/site-packages/mesonbuild/mtest.py", line 1005, in run
return th.doit()
File "/usr/lib/python3.7/site-packages/mesonbuild/mtest.py", line 756, in doit
self.run_tests(tests)
File "/usr/lib/python3.7/site-packages/mesonbuild/mtest.py", line 896, in run_tests
visible_name = self.get_pretty_suite(test)
File "/usr/lib/python3.7/site-packages/mesonbuild/mtest.py", line 875, in get_pretty_suite
rv = TestHarness.split_suite_string(test.suite[0])[0]
IndexError: list index out of range
Fix it by simply checking for the test suite to be a valid list we can pass on
Fixes #5340
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
6 years ago
|
|
|
self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'mainprj'])
|
|
|
|
self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'subprjsucc'])
|
|
|
|
self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjfail'])
|
|
|
|
self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjmix'])
|
|
|
|
|
|
|
|
self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'mainprj:fail'])
|
|
|
|
self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'mainprj:success'])
|
mtest: check for an empty suite list
For consistency, it can be useful to have an explicit empty test suite list
for a test:
test('test-name', binary, suite: [])
This currently passes meson but fails when running meson tests:
Traceback (most recent call last):
File "/usr/lib/python3.7/site-packages/mesonbuild/mesonmain.py", line 122, in run
return options.run_func(options)
File "/usr/lib/python3.7/site-packages/mesonbuild/mtest.py", line 1005, in run
return th.doit()
File "/usr/lib/python3.7/site-packages/mesonbuild/mtest.py", line 756, in doit
self.run_tests(tests)
File "/usr/lib/python3.7/site-packages/mesonbuild/mtest.py", line 896, in run_tests
visible_name = self.get_pretty_suite(test)
File "/usr/lib/python3.7/site-packages/mesonbuild/mtest.py", line 875, in get_pretty_suite
rv = TestHarness.split_suite_string(test.suite[0])[0]
IndexError: list index out of range
Fix it by simply checking for the test suite to be a valid list we can pass on
Fixes #5340
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
6 years ago
|
|
|
self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'mainprj:fail'])
|
|
|
|
self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'mainprj:success'])
|
|
|
|
|
|
|
|
self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail:fail'])
|
|
|
|
self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjfail:success'])
|
mtest: check for an empty suite list
For consistency, it can be useful to have an explicit empty test suite list
for a test:
test('test-name', binary, suite: [])
This currently passes meson but fails when running meson tests:
Traceback (most recent call last):
File "/usr/lib/python3.7/site-packages/mesonbuild/mesonmain.py", line 122, in run
return options.run_func(options)
File "/usr/lib/python3.7/site-packages/mesonbuild/mtest.py", line 1005, in run
return th.doit()
File "/usr/lib/python3.7/site-packages/mesonbuild/mtest.py", line 756, in doit
self.run_tests(tests)
File "/usr/lib/python3.7/site-packages/mesonbuild/mtest.py", line 896, in run_tests
visible_name = self.get_pretty_suite(test)
File "/usr/lib/python3.7/site-packages/mesonbuild/mtest.py", line 875, in get_pretty_suite
rv = TestHarness.split_suite_string(test.suite[0])[0]
IndexError: list index out of range
Fix it by simply checking for the test suite to be a valid list we can pass on
Fixes #5340
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
6 years ago
|
|
|
self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjfail:fail'])
|
|
|
|
self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'subprjfail:success'])
|
|
|
|
|
|
|
|
self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc:fail'])
|
|
|
|
self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc:success'])
|
mtest: check for an empty suite list
For consistency, it can be useful to have an explicit empty test suite list
for a test:
test('test-name', binary, suite: [])
This currently passes meson but fails when running meson tests:
Traceback (most recent call last):
File "/usr/lib/python3.7/site-packages/mesonbuild/mesonmain.py", line 122, in run
return options.run_func(options)
File "/usr/lib/python3.7/site-packages/mesonbuild/mtest.py", line 1005, in run
return th.doit()
File "/usr/lib/python3.7/site-packages/mesonbuild/mtest.py", line 756, in doit
self.run_tests(tests)
File "/usr/lib/python3.7/site-packages/mesonbuild/mtest.py", line 896, in run_tests
visible_name = self.get_pretty_suite(test)
File "/usr/lib/python3.7/site-packages/mesonbuild/mtest.py", line 875, in get_pretty_suite
rv = TestHarness.split_suite_string(test.suite[0])[0]
IndexError: list index out of range
Fix it by simply checking for the test suite to be a valid list we can pass on
Fixes #5340
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
6 years ago
|
|
|
self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'subprjsucc:fail'])
|
|
|
|
self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'subprjsucc:success'])
|
|
|
|
|
|
|
|
self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjmix:fail'])
|
|
|
|
self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjmix:success'])
|
mtest: check for an empty suite list
For consistency, it can be useful to have an explicit empty test suite list
for a test:
test('test-name', binary, suite: [])
This currently passes meson but fails when running meson tests:
Traceback (most recent call last):
File "/usr/lib/python3.7/site-packages/mesonbuild/mesonmain.py", line 122, in run
return options.run_func(options)
File "/usr/lib/python3.7/site-packages/mesonbuild/mtest.py", line 1005, in run
return th.doit()
File "/usr/lib/python3.7/site-packages/mesonbuild/mtest.py", line 756, in doit
self.run_tests(tests)
File "/usr/lib/python3.7/site-packages/mesonbuild/mtest.py", line 896, in run_tests
visible_name = self.get_pretty_suite(test)
File "/usr/lib/python3.7/site-packages/mesonbuild/mtest.py", line 875, in get_pretty_suite
rv = TestHarness.split_suite_string(test.suite[0])[0]
IndexError: list index out of range
Fix it by simply checking for the test suite to be a valid list we can pass on
Fixes #5340
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
6 years ago
|
|
|
self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjmix:fail'])
|
|
|
|
self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'subprjmix:success'])
|
|
|
|
|
|
|
|
self.assertFailedTestCount(2, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix:fail'])
|
|
|
|
self.assertFailedTestCount(3, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj'])
|
|
|
|
self.assertFailedTestCount(2, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj', '--no-suite', 'subprjmix:fail'])
|
|
|
|
self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj', '--no-suite', 'subprjmix:fail', 'mainprj-failing_test'])
|
|
|
|
|
mtest: check for an empty suite list
For consistency, it can be useful to have an explicit empty test suite list
for a test:
test('test-name', binary, suite: [])
This currently passes meson but fails when running meson tests:
Traceback (most recent call last):
File "/usr/lib/python3.7/site-packages/mesonbuild/mesonmain.py", line 122, in run
return options.run_func(options)
File "/usr/lib/python3.7/site-packages/mesonbuild/mtest.py", line 1005, in run
return th.doit()
File "/usr/lib/python3.7/site-packages/mesonbuild/mtest.py", line 756, in doit
self.run_tests(tests)
File "/usr/lib/python3.7/site-packages/mesonbuild/mtest.py", line 896, in run_tests
visible_name = self.get_pretty_suite(test)
File "/usr/lib/python3.7/site-packages/mesonbuild/mtest.py", line 875, in get_pretty_suite
rv = TestHarness.split_suite_string(test.suite[0])[0]
IndexError: list index out of range
Fix it by simply checking for the test suite to be a valid list we can pass on
Fixes #5340
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
6 years ago
|
|
|
self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjfail:fail', '--no-suite', 'subprjmix:fail'])
|
|
|
|
|
|
|
|
def test_build_by_default(self):
|
|
|
|
testdir = os.path.join(self.common_test_dir, '133 build by default')
|
|
|
|
self.init(testdir)
|
|
|
|
self.build()
|
|
|
|
genfile1 = os.path.join(self.builddir, 'generated1.dat')
|
|
|
|
genfile2 = os.path.join(self.builddir, 'generated2.dat')
|
|
|
|
exe1 = os.path.join(self.builddir, 'fooprog' + exe_suffix)
|
|
|
|
exe2 = os.path.join(self.builddir, 'barprog' + exe_suffix)
|
|
|
|
self.assertPathExists(genfile1)
|
|
|
|
self.assertPathExists(genfile2)
|
|
|
|
self.assertPathDoesNotExist(exe1)
|
|
|
|
self.assertPathDoesNotExist(exe2)
|
|
|
|
self.build(target=('fooprog' + exe_suffix))
|
|
|
|
self.assertPathExists(exe1)
|
|
|
|
self.build(target=('barprog' + exe_suffix))
|
|
|
|
self.assertPathExists(exe2)
|
|
|
|
|
|
|
|
def test_internal_include_order(self):
|
|
|
|
testdir = os.path.join(self.common_test_dir, '134 include order')
|
|
|
|
self.init(testdir)
|
|
|
|
execmd = fxecmd = None
|
|
|
|
for cmd in self.get_compdb():
|
|
|
|
if 'someexe' in cmd['command']:
|
|
|
|
execmd = cmd['command']
|
|
|
|
continue
|
|
|
|
if 'somefxe' in cmd['command']:
|
|
|
|
fxecmd = cmd['command']
|
|
|
|
continue
|
|
|
|
if not execmd or not fxecmd:
|
|
|
|
raise Exception('Could not find someexe and somfxe commands')
|
|
|
|
# Check include order for 'someexe'
|
|
|
|
incs = [a for a in split_args(execmd) if a.startswith("-I")]
|
|
|
|
self.assertEqual(len(incs), 9)
|
|
|
|
# target private dir
|
|
|
|
someexe_id = Target.construct_id_from_path("sub4", "someexe", "@exe")
|
|
|
|
self.assertPathEqual(incs[0], "-I" + os.path.join("sub4", someexe_id))
|
|
|
|
# target build subdir
|
|
|
|
self.assertPathEqual(incs[1], "-Isub4")
|
|
|
|
# target source subdir
|
|
|
|
self.assertPathBasenameEqual(incs[2], 'sub4')
|
|
|
|
# include paths added via per-target c_args: ['-I'...]
|
|
|
|
self.assertPathBasenameEqual(incs[3], 'sub3')
|
|
|
|
# target include_directories: build dir
|
|
|
|
self.assertPathEqual(incs[4], "-Isub2")
|
|
|
|
# target include_directories: source dir
|
|
|
|
self.assertPathBasenameEqual(incs[5], 'sub2')
|
|
|
|
# target internal dependency include_directories: build dir
|
|
|
|
self.assertPathEqual(incs[6], "-Isub1")
|
|
|
|
# target internal dependency include_directories: source dir
|
|
|
|
self.assertPathBasenameEqual(incs[7], 'sub1')
|
|
|
|
# custom target include dir
|
|
|
|
self.assertPathEqual(incs[8], '-Ictsub')
|
|
|
|
# Check include order for 'somefxe'
|
|
|
|
incs = [a for a in split_args(fxecmd) if a.startswith('-I')]
|
|
|
|
self.assertEqual(len(incs), 9)
|
|
|
|
# target private dir
|
|
|
|
self.assertPathEqual(incs[0], '-Isomefxe@exe')
|
|
|
|
# target build dir
|
|
|
|
self.assertPathEqual(incs[1], '-I.')
|
|
|
|
# target source dir
|
|
|
|
self.assertPathBasenameEqual(incs[2], os.path.basename(testdir))
|
|
|
|
# target internal dependency correct include_directories: build dir
|
|
|
|
self.assertPathEqual(incs[3], "-Isub4")
|
|
|
|
# target internal dependency correct include_directories: source dir
|
|
|
|
self.assertPathBasenameEqual(incs[4], 'sub4')
|
|
|
|
# target internal dependency dep include_directories: build dir
|
|
|
|
self.assertPathEqual(incs[5], "-Isub1")
|
|
|
|
# target internal dependency dep include_directories: source dir
|
|
|
|
self.assertPathBasenameEqual(incs[6], 'sub1')
|
|
|
|
# target internal dependency wrong include_directories: build dir
|
|
|
|
self.assertPathEqual(incs[7], "-Isub2")
|
|
|
|
# target internal dependency wrong include_directories: source dir
|
|
|
|
self.assertPathBasenameEqual(incs[8], 'sub2')
|
|
|
|
|
|
|
|
def test_compiler_detection(self):
|
|
|
|
'''
|
|
|
|
Test that automatic compiler detection and setting from the environment
|
|
|
|
both work just fine. This is needed because while running project tests
|
|
|
|
and other unit tests, we always read CC/CXX/etc from the environment.
|
|
|
|
'''
|
|
|
|
gnu = mesonbuild.compilers.GnuCompiler
|
|
|
|
clang = mesonbuild.compilers.ClangCompiler
|
|
|
|
intel = mesonbuild.compilers.IntelGnuLikeCompiler
|
|
|
|
msvc = (mesonbuild.compilers.VisualStudioCCompiler, mesonbuild.compilers.VisualStudioCPPCompiler)
|
|
|
|
clangcl = (mesonbuild.compilers.ClangClCCompiler, mesonbuild.compilers.ClangClCPPCompiler)
|
|
|
|
ar = mesonbuild.linkers.ArLinker
|
|
|
|
lib = mesonbuild.linkers.VisualStudioLinker
|
|
|
|
langs = [('c', 'CC'), ('cpp', 'CXX')]
|
|
|
|
if not is_windows():
|
|
|
|
langs += [('objc', 'OBJC'), ('objcpp', 'OBJCXX')]
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '5 compiler detection')
|
|
|
|
env = get_fake_env(testdir, self.builddir, self.prefix)
|
|
|
|
for lang, evar in langs:
|
|
|
|
# Detect with evar and do sanity checks on that
|
|
|
|
if evar in os.environ:
|
|
|
|
ecc = getattr(env, 'detect_{}_compiler'.format(lang))(MachineChoice.HOST)
|
|
|
|
self.assertTrue(ecc.version)
|
|
|
|
elinker = env.detect_static_linker(ecc)
|
|
|
|
# Pop it so we don't use it for the next detection
|
|
|
|
evalue = os.environ.pop(evar)
|
|
|
|
# Very rough/strict heuristics. Would never work for actual
|
|
|
|
# compiler detection, but should be ok for the tests.
|
|
|
|
ebase = os.path.basename(evalue)
|
|
|
|
if ebase.startswith('g') or ebase.endswith(('-gcc', '-g++')):
|
|
|
|
self.assertIsInstance(ecc, gnu)
|
|
|
|
self.assertIsInstance(elinker, ar)
|
|
|
|
elif 'clang-cl' in ebase:
|
|
|
|
self.assertIsInstance(ecc, clangcl)
|
|
|
|
self.assertIsInstance(elinker, lib)
|
|
|
|
elif 'clang' in ebase:
|
|
|
|
self.assertIsInstance(ecc, clang)
|
|
|
|
self.assertIsInstance(elinker, ar)
|
|
|
|
elif ebase.startswith('ic'):
|
|
|
|
self.assertIsInstance(ecc, intel)
|
|
|
|
self.assertIsInstance(elinker, ar)
|
|
|
|
elif ebase.startswith('cl'):
|
|
|
|
self.assertIsInstance(ecc, msvc)
|
|
|
|
self.assertIsInstance(elinker, lib)
|
|
|
|
else:
|
|
|
|
raise AssertionError('Unknown compiler {!r}'.format(evalue))
|
|
|
|
# Check that we actually used the evalue correctly as the compiler
|
|
|
|
self.assertEqual(ecc.get_exelist(), split_args(evalue))
|
|
|
|
# Do auto-detection of compiler based on platform, PATH, etc.
|
|
|
|
cc = getattr(env, 'detect_{}_compiler'.format(lang))(MachineChoice.HOST)
|
|
|
|
self.assertTrue(cc.version)
|
|
|
|
linker = env.detect_static_linker(cc)
|
|
|
|
# Check compiler type
|
|
|
|
if isinstance(cc, gnu):
|
|
|
|
self.assertIsInstance(linker, ar)
|
|
|
|
if is_osx():
|
|
|
|
self.assertIsInstance(cc.linker, mesonbuild.linkers.AppleDynamicLinker)
|
|
|
|
else:
|
|
|
|
self.assertIsInstance(cc.linker, mesonbuild.linkers.GnuLikeDynamicLinkerMixin)
|
|
|
|
if isinstance(cc, clangcl):
|
|
|
|
self.assertIsInstance(linker, lib)
|
|
|
|
self.assertIsInstance(cc.linker, mesonbuild.linkers.ClangClDynamicLinker)
|
|
|
|
if isinstance(cc, clang):
|
|
|
|
self.assertIsInstance(linker, ar)
|
|
|
|
if is_osx():
|
|
|
|
self.assertIsInstance(cc.linker, mesonbuild.linkers.AppleDynamicLinker)
|
|
|
|
elif is_windows():
|
|
|
|
# This is clang, not clang-cl
|
|
|
|
self.assertIsInstance(cc.linker, mesonbuild.linkers.MSVCDynamicLinker)
|
|
|
|
else:
|
|
|
|
self.assertIsInstance(cc.linker, mesonbuild.linkers.GnuLikeDynamicLinkerMixin)
|
|
|
|
if isinstance(cc, intel):
|
|
|
|
self.assertIsInstance(linker, ar)
|
|
|
|
if is_osx():
|
|
|
|
self.assertIsInstance(cc.linker, mesonbuild.linkers.AppleDynamicLinker)
|
|
|
|
elif is_windows():
|
|
|
|
self.assertIsInstance(cc.linker, mesonbuild.linkers.XilinkDynamicLinker)
|
|
|
|
else:
|
|
|
|
self.assertIsInstance(cc.linker, mesonbuild.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)
|
|
|
|
# If we're on Windows CI, we know what the compiler will be
|
|
|
|
if 'arch' in os.environ:
|
|
|
|
if os.environ['arch'] == 'x64':
|
|
|
|
self.assertTrue(cc.is_64)
|
|
|
|
else:
|
|
|
|
self.assertFalse(cc.is_64)
|
|
|
|
# Set evar ourselves to a wrapper script that just calls the same
|
|
|
|
# exelist + some argument. This is meant to test that setting
|
|
|
|
# something like `ccache gcc -pipe` or `distcc ccache gcc` works.
|
|
|
|
wrapper = os.path.join(testdir, 'compiler wrapper.py')
|
|
|
|
wrappercc = python_command + [wrapper] + cc.get_exelist() + ['-DSOME_ARG']
|
|
|
|
wrappercc_s = ''
|
|
|
|
for w in wrappercc:
|
|
|
|
wrappercc_s += quote_arg(w) + ' '
|
|
|
|
os.environ[evar] = wrappercc_s
|
|
|
|
wcc = getattr(env, 'detect_{}_compiler'.format(lang))(MachineChoice.HOST)
|
|
|
|
# Check static linker too
|
|
|
|
wrapperlinker = python_command + [wrapper] + linker.get_exelist() + linker.get_always_args()
|
|
|
|
wrapperlinker_s = ''
|
|
|
|
for w in wrapperlinker:
|
|
|
|
wrapperlinker_s += quote_arg(w) + ' '
|
|
|
|
os.environ['AR'] = wrapperlinker_s
|
|
|
|
wlinker = env.detect_static_linker(wcc)
|
|
|
|
# Pop it so we don't use it for the next detection
|
|
|
|
evalue = os.environ.pop('AR')
|
|
|
|
# Must be the same type since it's a wrapper around the same exelist
|
|
|
|
self.assertIs(type(cc), type(wcc))
|
|
|
|
self.assertIs(type(linker), type(wlinker))
|
|
|
|
# Ensure that the exelist is correct
|
|
|
|
self.assertEqual(wcc.get_exelist(), wrappercc)
|
|
|
|
self.assertEqual(wlinker.get_exelist(), wrapperlinker)
|
|
|
|
# Ensure that the version detection worked correctly
|
|
|
|
self.assertEqual(cc.version, wcc.version)
|
|
|
|
if hasattr(cc, 'is_64'):
|
|
|
|
self.assertEqual(cc.is_64, wcc.is_64)
|
|
|
|
|
|
|
|
def test_always_prefer_c_compiler_for_asm(self):
|
|
|
|
testdir = os.path.join(self.common_test_dir, '137 c cpp and asm')
|
|
|
|
# Skip if building with MSVC
|
|
|
|
env = get_fake_env(testdir, self.builddir, self.prefix)
|
|
|
|
if env.detect_c_compiler(MachineChoice.HOST).get_id() == 'msvc':
|
|
|
|
raise unittest.SkipTest('MSVC can\'t compile assembly')
|
|
|
|
self.init(testdir)
|
|
|
|
commands = {'c-asm': {}, 'cpp-asm': {}, 'cpp-c-asm': {}, 'c-cpp-asm': {}}
|
|
|
|
for cmd in self.get_compdb():
|
|
|
|
# Get compiler
|
|
|
|
split = split_args(cmd['command'])
|
|
|
|
if split[0] == 'ccache':
|
|
|
|
compiler = split[1]
|
|
|
|
else:
|
|
|
|
compiler = split[0]
|
|
|
|
# Classify commands
|
|
|
|
if 'Ic-asm' in cmd['command']:
|
|
|
|
if cmd['file'].endswith('.S'):
|
|
|
|
commands['c-asm']['asm'] = compiler
|
|
|
|
elif cmd['file'].endswith('.c'):
|
|
|
|
commands['c-asm']['c'] = compiler
|
|
|
|
else:
|
|
|
|
raise AssertionError('{!r} found in cpp-asm?'.format(cmd['command']))
|
|
|
|
elif 'Icpp-asm' in cmd['command']:
|
|
|
|
if cmd['file'].endswith('.S'):
|
|
|
|
commands['cpp-asm']['asm'] = compiler
|
|
|
|
elif cmd['file'].endswith('.cpp'):
|
|
|
|
commands['cpp-asm']['cpp'] = compiler
|
|
|
|
else:
|
|
|
|
raise AssertionError('{!r} found in cpp-asm?'.format(cmd['command']))
|
|
|
|
elif 'Ic-cpp-asm' in cmd['command']:
|
|
|
|
if cmd['file'].endswith('.S'):
|
|
|
|
commands['c-cpp-asm']['asm'] = compiler
|
|
|
|
elif cmd['file'].endswith('.c'):
|
|
|
|
commands['c-cpp-asm']['c'] = compiler
|
|
|
|
elif cmd['file'].endswith('.cpp'):
|
|
|
|
commands['c-cpp-asm']['cpp'] = compiler
|
|
|
|
else:
|
|
|
|
raise AssertionError('{!r} found in c-cpp-asm?'.format(cmd['command']))
|
|
|
|
elif 'Icpp-c-asm' in cmd['command']:
|
|
|
|
if cmd['file'].endswith('.S'):
|
|
|
|
commands['cpp-c-asm']['asm'] = compiler
|
|
|
|
elif cmd['file'].endswith('.c'):
|
|
|
|
commands['cpp-c-asm']['c'] = compiler
|
|
|
|
elif cmd['file'].endswith('.cpp'):
|
|
|
|
commands['cpp-c-asm']['cpp'] = compiler
|
|
|
|
else:
|
|
|
|
raise AssertionError('{!r} found in cpp-c-asm?'.format(cmd['command']))
|
|
|
|
else:
|
|
|
|
raise AssertionError('Unknown command {!r} found'.format(cmd['command']))
|
|
|
|
# Check that .S files are always built with the C compiler
|
|
|
|
self.assertEqual(commands['c-asm']['asm'], commands['c-asm']['c'])
|
|
|
|
self.assertEqual(commands['c-asm']['asm'], commands['cpp-asm']['asm'])
|
|
|
|
self.assertEqual(commands['cpp-asm']['asm'], commands['c-cpp-asm']['c'])
|
|
|
|
self.assertEqual(commands['c-cpp-asm']['asm'], commands['c-cpp-asm']['c'])
|
|
|
|
self.assertEqual(commands['cpp-c-asm']['asm'], commands['cpp-c-asm']['c'])
|
|
|
|
self.assertNotEqual(commands['cpp-asm']['asm'], commands['cpp-asm']['cpp'])
|
|
|
|
self.assertNotEqual(commands['c-cpp-asm']['c'], commands['c-cpp-asm']['cpp'])
|
|
|
|
self.assertNotEqual(commands['cpp-c-asm']['c'], commands['cpp-c-asm']['cpp'])
|
|
|
|
# Check that the c-asm target is always linked with the C linker
|
|
|
|
build_ninja = os.path.join(self.builddir, 'build.ninja')
|
|
|
|
with open(build_ninja, 'r', encoding='utf-8') as f:
|
|
|
|
contents = f.read()
|
|
|
|
m = re.search('build c-asm.*: c_LINKER', contents)
|
|
|
|
self.assertIsNotNone(m, msg=contents)
|
|
|
|
|
|
|
|
def test_preprocessor_checks_CPPFLAGS(self):
|
|
|
|
'''
|
|
|
|
Test that preprocessor compiler checks read CPPFLAGS and also CFLAGS but
|
|
|
|
not LDFLAGS.
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.common_test_dir, '136 get define')
|
|
|
|
define = 'MESON_TEST_DEFINE_VALUE'
|
|
|
|
# NOTE: this list can't have \n, ' or "
|
|
|
|
# \n is never substituted by the GNU pre-processor via a -D define
|
|
|
|
# ' and " confuse split_args() even when they are escaped
|
|
|
|
# % and # confuse the MSVC preprocessor
|
|
|
|
# !, ^, *, and < confuse lcc preprocessor
|
|
|
|
value = 'spaces and fun@$&()-=_+{}[]:;>?,./~`'
|
|
|
|
for env_var in ['CPPFLAGS', 'CFLAGS']:
|
|
|
|
env = {}
|
|
|
|
env[env_var] = '-D{}="{}"'.format(define, value)
|
|
|
|
env['LDFLAGS'] = '-DMESON_FAIL_VALUE=cflags-read'.format(define)
|
|
|
|
self.init(testdir, extra_args=['-D{}={}'.format(define, value)], override_envvars=env)
|
|
|
|
|
|
|
|
def test_custom_target_exe_data_deterministic(self):
|
|
|
|
testdir = os.path.join(self.common_test_dir, '113 custom target capture')
|
|
|
|
self.init(testdir)
|
|
|
|
meson_exe_dat1 = glob(os.path.join(self.privatedir, 'meson_exe*.dat'))
|
|
|
|
self.wipe()
|
|
|
|
self.init(testdir)
|
|
|
|
meson_exe_dat2 = glob(os.path.join(self.privatedir, 'meson_exe*.dat'))
|
|
|
|
self.assertListEqual(meson_exe_dat1, meson_exe_dat2)
|
|
|
|
|
|
|
|
def test_source_changes_cause_rebuild(self):
|
|
|
|
'''
|
|
|
|
Test that changes to sources and headers cause rebuilds, but not
|
|
|
|
changes to unused files (as determined by the dependency file) in the
|
|
|
|
input files list.
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.common_test_dir, '20 header in file list')
|
|
|
|
self.init(testdir)
|
|
|
|
self.build()
|
|
|
|
# Immediately rebuilding should not do anything
|
|
|
|
self.assertBuildIsNoop()
|
|
|
|
# Changing mtime of header.h should rebuild everything
|
|
|
|
self.utime(os.path.join(testdir, 'header.h'))
|
|
|
|
self.assertRebuiltTarget('prog')
|
|
|
|
|
|
|
|
def test_custom_target_changes_cause_rebuild(self):
|
|
|
|
'''
|
|
|
|
Test that in a custom target, changes to the input files, the
|
|
|
|
ExternalProgram, and any File objects on the command-line cause
|
|
|
|
a rebuild.
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.common_test_dir, '60 custom header generator')
|
|
|
|
self.init(testdir)
|
|
|
|
self.build()
|
|
|
|
# Immediately rebuilding should not do anything
|
|
|
|
self.assertBuildIsNoop()
|
|
|
|
# Changing mtime of these should rebuild everything
|
|
|
|
for f in ('input.def', 'makeheader.py', 'somefile.txt'):
|
|
|
|
self.utime(os.path.join(testdir, f))
|
|
|
|
self.assertRebuiltTarget('prog')
|
|
|
|
|
|
|
|
def test_source_generator_program_cause_rebuild(self):
|
|
|
|
'''
|
|
|
|
Test that changes to generator programs in the source tree cause
|
|
|
|
a rebuild.
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.common_test_dir, '94 gen extra')
|
|
|
|
self.init(testdir)
|
|
|
|
self.build()
|
|
|
|
# Immediately rebuilding should not do anything
|
|
|
|
self.assertBuildIsNoop()
|
|
|
|
# Changing mtime of generator should rebuild the executable
|
|
|
|
self.utime(os.path.join(testdir, 'srcgen.py'))
|
|
|
|
self.assertRebuiltTarget('basic')
|
|
|
|
|
|
|
|
def test_static_library_lto(self):
|
|
|
|
'''
|
|
|
|
Test that static libraries can be built with LTO and linked to
|
|
|
|
executables. On Linux, this requires the use of gcc-ar.
|
|
|
|
https://github.com/mesonbuild/meson/issues/1646
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.common_test_dir, '5 linkstatic')
|
|
|
|
|
|
|
|
env = get_fake_env(testdir, self.builddir, self.prefix)
|
|
|
|
if env.detect_c_compiler(MachineChoice.HOST).get_id() == 'clang' and is_windows():
|
|
|
|
raise unittest.SkipTest('LTO not (yet) supported by windows clang')
|
|
|
|
|
|
|
|
self.init(testdir, extra_args='-Db_lto=true')
|
|
|
|
self.build()
|
|
|
|
self.run_tests()
|
|
|
|
|
|
|
|
def test_dist_git(self):
|
|
|
|
if not shutil.which('git'):
|
|
|
|
raise unittest.SkipTest('Git not found')
|
|
|
|
if self.backend is not Backend.ninja:
|
|
|
|
raise unittest.SkipTest('Dist is only supported with Ninja')
|
|
|
|
|
|
|
|
try:
|
|
|
|
self.dist_impl(_git_init)
|
|
|
|
except PermissionError:
|
|
|
|
# When run under Windows CI, something (virus scanner?)
|
|
|
|
# holds on to the git files so cleaning up the dir
|
|
|
|
# fails sometimes.
|
|
|
|
pass
|
|
|
|
|
|
|
|
def test_dist_hg(self):
|
|
|
|
if not shutil.which('hg'):
|
|
|
|
raise unittest.SkipTest('Mercurial not found')
|
|
|
|
if self.backend is not Backend.ninja:
|
|
|
|
raise unittest.SkipTest('Dist is only supported with Ninja')
|
|
|
|
|
|
|
|
def hg_init(project_dir):
|
|
|
|
subprocess.check_call(['hg', 'init'], cwd=project_dir)
|
|
|
|
with open(os.path.join(project_dir, '.hg', 'hgrc'), 'w') as f:
|
|
|
|
print('[ui]', file=f)
|
|
|
|
print('username=Author Person <teh_coderz@example.com>', file=f)
|
|
|
|
subprocess.check_call(['hg', 'add', 'meson.build', 'distexe.c'], cwd=project_dir)
|
|
|
|
subprocess.check_call(['hg', 'commit', '-m', 'I am a project'], cwd=project_dir)
|
|
|
|
|
|
|
|
try:
|
|
|
|
self.dist_impl(hg_init, include_subprojects=False)
|
|
|
|
except PermissionError:
|
|
|
|
# When run under Windows CI, something (virus scanner?)
|
|
|
|
# holds on to the hg files so cleaning up the dir
|
|
|
|
# fails sometimes.
|
|
|
|
pass
|
|
|
|
|
|
|
|
def test_dist_git_script(self):
|
|
|
|
if not shutil.which('git'):
|
|
|
|
raise unittest.SkipTest('Git not found')
|
|
|
|
if self.backend is not Backend.ninja:
|
|
|
|
raise unittest.SkipTest('Dist is only supported with Ninja')
|
|
|
|
|
|
|
|
try:
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
|
|
project_dir = os.path.join(tmpdir, 'a')
|
|
|
|
shutil.copytree(os.path.join(self.unit_test_dir, '35 dist script'),
|
|
|
|
project_dir)
|
|
|
|
_git_init(project_dir)
|
|
|
|
self.init(project_dir)
|
|
|
|
self.build('dist')
|
|
|
|
except PermissionError:
|
|
|
|
# When run under Windows CI, something (virus scanner?)
|
|
|
|
# holds on to the git files so cleaning up the dir
|
|
|
|
# fails sometimes.
|
|
|
|
pass
|
|
|
|
|
|
|
|
def create_dummy_subproject(self, project_dir, name):
|
|
|
|
path = os.path.join(project_dir, 'subprojects', name)
|
|
|
|
os.makedirs(path)
|
|
|
|
with open(os.path.join(path, 'meson.build'), 'w') as ofile:
|
|
|
|
ofile.write("project('{}')".format(name))
|
|
|
|
return path
|
|
|
|
|
|
|
|
def dist_impl(self, vcs_init, include_subprojects=True):
|
|
|
|
# Create this on the fly because having rogue .git directories inside
|
|
|
|
# the source tree leads to all kinds of trouble.
|
|
|
|
with tempfile.TemporaryDirectory() as project_dir:
|
|
|
|
with open(os.path.join(project_dir, 'meson.build'), 'w') as ofile:
|
|
|
|
ofile.write('''project('disttest', 'c', version : '1.4.3')
|
|
|
|
e = executable('distexe', 'distexe.c')
|
|
|
|
test('dist test', e)
|
|
|
|
subproject('vcssub', required : false)
|
|
|
|
subproject('tarballsub', required : false)
|
|
|
|
''')
|
|
|
|
with open(os.path.join(project_dir, 'distexe.c'), 'w') as ofile:
|
|
|
|
ofile.write('''#include<stdio.h>
|
|
|
|
|
|
|
|
int main(int argc, char **argv) {
|
|
|
|
printf("I am a distribution test.\\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
''')
|
|
|
|
xz_distfile = os.path.join(self.distdir, 'disttest-1.4.3.tar.xz')
|
|
|
|
xz_checksumfile = xz_distfile + '.sha256sum'
|
|
|
|
zip_distfile = os.path.join(self.distdir, 'disttest-1.4.3.zip')
|
|
|
|
zip_checksumfile = zip_distfile + '.sha256sum'
|
|
|
|
vcs_init(project_dir)
|
|
|
|
if include_subprojects:
|
|
|
|
vcs_init(self.create_dummy_subproject(project_dir, 'vcssub'))
|
|
|
|
self.create_dummy_subproject(project_dir, 'tarballsub')
|
|
|
|
self.create_dummy_subproject(project_dir, 'unusedsub')
|
|
|
|
self.init(project_dir)
|
|
|
|
self.build('dist')
|
|
|
|
self.assertPathExists(xz_distfile)
|
|
|
|
self.assertPathExists(xz_checksumfile)
|
|
|
|
self.assertPathDoesNotExist(zip_distfile)
|
|
|
|
self.assertPathDoesNotExist(zip_checksumfile)
|
|
|
|
self._run(self.meson_command + ['dist', '--formats', 'zip'],
|
|
|
|
workdir=self.builddir)
|
|
|
|
self.assertPathExists(zip_distfile)
|
|
|
|
self.assertPathExists(zip_checksumfile)
|
|
|
|
|
|
|
|
if include_subprojects:
|
|
|
|
z = zipfile.ZipFile(zip_distfile)
|
|
|
|
self.assertEqual(sorted(['disttest-1.4.3/',
|
|
|
|
'disttest-1.4.3/meson.build',
|
|
|
|
'disttest-1.4.3/distexe.c']),
|
|
|
|
sorted(z.namelist()))
|
|
|
|
|
|
|
|
self._run(self.meson_command + ['dist', '--formats', 'zip', '--include-subprojects'],
|
|
|
|
workdir=self.builddir)
|
|
|
|
z = zipfile.ZipFile(zip_distfile)
|
|
|
|
self.assertEqual(sorted(['disttest-1.4.3/',
|
|
|
|
'disttest-1.4.3/subprojects/',
|
|
|
|
'disttest-1.4.3/meson.build',
|
|
|
|
'disttest-1.4.3/distexe.c',
|
|
|
|
'disttest-1.4.3/subprojects/tarballsub/',
|
|
|
|
'disttest-1.4.3/subprojects/vcssub/',
|
|
|
|
'disttest-1.4.3/subprojects/tarballsub/meson.build',
|
|
|
|
'disttest-1.4.3/subprojects/vcssub/meson.build']),
|
|
|
|
sorted(z.namelist()))
|
|
|
|
|
|
|
|
def test_rpath_uses_ORIGIN(self):
|
|
|
|
'''
|
|
|
|
Test that built targets use $ORIGIN in rpath, which ensures that they
|
|
|
|
are relocatable and ensures that builds are reproducible since the
|
|
|
|
build directory won't get embedded into the built binaries.
|
|
|
|
'''
|
|
|
|
if is_windows() or is_cygwin():
|
|
|
|
raise unittest.SkipTest('Windows PE/COFF binaries do not use RPATH')
|
|
|
|
testdir = os.path.join(self.common_test_dir, '42 library chain')
|
|
|
|
self.init(testdir)
|
|
|
|
self.build()
|
|
|
|
for each in ('prog', 'subdir/liblib1.so', ):
|
|
|
|
rpath = get_rpath(os.path.join(self.builddir, each))
|
|
|
|
self.assertTrue(rpath, 'Rpath could not be determined for {}.'.format(each))
|
|
|
|
if is_dragonflybsd():
|
|
|
|
# DragonflyBSD will prepend /usr/lib/gccVERSION to the rpath,
|
|
|
|
# so ignore that.
|
|
|
|
self.assertTrue(rpath.startswith('/usr/lib/gcc'))
|
|
|
|
rpaths = rpath.split(':')[1:]
|
|
|
|
else:
|
|
|
|
rpaths = rpath.split(':')
|
|
|
|
for path in rpaths:
|
|
|
|
self.assertTrue(path.startswith('$ORIGIN'), msg=(each, path))
|
|
|
|
# These two don't link to anything else, so they do not need an rpath entry.
|
|
|
|
for each in ('subdir/subdir2/liblib2.so', 'subdir/subdir3/liblib3.so'):
|
|
|
|
rpath = get_rpath(os.path.join(self.builddir, each))
|
|
|
|
if is_dragonflybsd():
|
|
|
|
# The rpath should be equal to /usr/lib/gccVERSION
|
|
|
|
self.assertTrue(rpath.startswith('/usr/lib/gcc'))
|
|
|
|
self.assertEqual(len(rpath.split(':')), 1)
|
|
|
|
else:
|
|
|
|
self.assertTrue(rpath is None)
|
|
|
|
|
|
|
|
def test_dash_d_dedup(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '9 d dedup')
|
|
|
|
self.init(testdir)
|
|
|
|
cmd = self.get_compdb()[0]['command']
|
|
|
|
self.assertTrue('-D FOO -D BAR' in cmd or
|
|
|
|
'"-D" "FOO" "-D" "BAR"' in cmd or
|
|
|
|
'/D FOO /D BAR' in cmd or
|
|
|
|
'"/D" "FOO" "/D" "BAR"' in cmd)
|
|
|
|
|
|
|
|
def test_all_forbidden_targets_tested(self):
|
|
|
|
'''
|
|
|
|
Test that all forbidden targets are tested in the '154 reserved targets'
|
|
|
|
test. Needs to be a unit test because it accesses Meson internals.
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.common_test_dir, '154 reserved targets')
|
|
|
|
targets = mesonbuild.coredata.forbidden_target_names
|
|
|
|
# We don't actually define a target with this name
|
|
|
|
targets.pop('build.ninja')
|
|
|
|
# Remove this to avoid multiple entries with the same name
|
|
|
|
# but different case.
|
|
|
|
targets.pop('PHONY')
|
|
|
|
for i in targets:
|
|
|
|
self.assertPathExists(os.path.join(testdir, i))
|
|
|
|
|
|
|
|
def detect_prebuild_env(self):
|
|
|
|
env = get_fake_env()
|
|
|
|
cc = env.detect_c_compiler(MachineChoice.HOST)
|
|
|
|
stlinker = env.detect_static_linker(cc)
|
|
|
|
if mesonbuild.mesonlib.is_windows():
|
|
|
|
object_suffix = 'obj'
|
|
|
|
shared_suffix = 'dll'
|
|
|
|
elif mesonbuild.mesonlib.is_cygwin():
|
|
|
|
object_suffix = 'o'
|
|
|
|
shared_suffix = 'dll'
|
|
|
|
elif mesonbuild.mesonlib.is_osx():
|
|
|
|
object_suffix = 'o'
|
|
|
|
shared_suffix = 'dylib'
|
|
|
|
else:
|
|
|
|
object_suffix = 'o'
|
|
|
|
shared_suffix = 'so'
|
|
|
|
return (cc, stlinker, object_suffix, shared_suffix)
|
|
|
|
|
|
|
|
def pbcompile(self, compiler, source, objectfile, extra_args=None):
|
|
|
|
cmd = compiler.get_exelist()
|
|
|
|
extra_args = extra_args or []
|
|
|
|
if compiler.get_argument_syntax() == 'msvc':
|
|
|
|
cmd += ['/nologo', '/Fo' + objectfile, '/c', source] + extra_args
|
|
|
|
else:
|
|
|
|
cmd += ['-c', source, '-o', objectfile] + extra_args
|
|
|
|
subprocess.check_call(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
|
|
|
|
|
|
def test_prebuilt_object(self):
|
|
|
|
(compiler, _, object_suffix, _) = self.detect_prebuild_env()
|
|
|
|
tdir = os.path.join(self.unit_test_dir, '15 prebuilt object')
|
|
|
|
source = os.path.join(tdir, 'source.c')
|
|
|
|
objectfile = os.path.join(tdir, 'prebuilt.' + object_suffix)
|
|
|
|
self.pbcompile(compiler, source, objectfile)
|
|
|
|
try:
|
|
|
|
self.init(tdir)
|
|
|
|
self.build()
|
|
|
|
self.run_tests()
|
|
|
|
finally:
|
|
|
|
os.unlink(objectfile)
|
|
|
|
|
|
|
|
def build_static_lib(self, compiler, linker, source, objectfile, outfile, extra_args=None):
|
|
|
|
if extra_args is None:
|
|
|
|
extra_args = []
|
|
|
|
if compiler.get_argument_syntax() == 'msvc':
|
|
|
|
link_cmd = ['lib', '/NOLOGO', '/OUT:' + outfile, objectfile]
|
|
|
|
else:
|
|
|
|
link_cmd = ['ar', 'csr', outfile, objectfile]
|
|
|
|
link_cmd = linker.get_exelist()
|
|
|
|
link_cmd += linker.get_always_args()
|
|
|
|
link_cmd += linker.get_std_link_args()
|
|
|
|
link_cmd += linker.get_output_args(outfile)
|
|
|
|
link_cmd += [objectfile]
|
|
|
|
self.pbcompile(compiler, source, objectfile, extra_args=extra_args)
|
|
|
|
try:
|
|
|
|
subprocess.check_call(link_cmd)
|
|
|
|
finally:
|
|
|
|
os.unlink(objectfile)
|
|
|
|
|
|
|
|
def test_prebuilt_static_lib(self):
|
|
|
|
(cc, stlinker, object_suffix, _) = self.detect_prebuild_env()
|
|
|
|
tdir = os.path.join(self.unit_test_dir, '16 prebuilt static')
|
|
|
|
source = os.path.join(tdir, 'libdir/best.c')
|
|
|
|
objectfile = os.path.join(tdir, 'libdir/best.' + object_suffix)
|
|
|
|
stlibfile = os.path.join(tdir, 'libdir/libbest.a')
|
|
|
|
self.build_static_lib(cc, stlinker, source, objectfile, stlibfile)
|
|
|
|
# Run the test
|
|
|
|
try:
|
|
|
|
self.init(tdir)
|
|
|
|
self.build()
|
|
|
|
self.run_tests()
|
|
|
|
finally:
|
|
|
|
os.unlink(stlibfile)
|
|
|
|
|
|
|
|
def build_shared_lib(self, compiler, source, objectfile, outfile, impfile, extra_args=None):
|
|
|
|
if extra_args is None:
|
|
|
|
extra_args = []
|
|
|
|
if compiler.get_argument_syntax() == 'msvc':
|
|
|
|
link_cmd = compiler.get_linker_exelist() + [
|
|
|
|
'/NOLOGO', '/DLL', '/DEBUG', '/IMPLIB:' + impfile,
|
|
|
|
'/OUT:' + outfile, objectfile]
|
|
|
|
else:
|
|
|
|
if not (compiler.info.is_windows() or compiler.info.is_cygwin() or compiler.info.is_darwin()):
|
|
|
|
extra_args += ['-fPIC']
|
|
|
|
link_cmd = compiler.get_exelist() + ['-shared', '-o', outfile, objectfile]
|
|
|
|
if not mesonbuild.mesonlib.is_osx():
|
|
|
|
link_cmd += ['-Wl,-soname=' + os.path.basename(outfile)]
|
|
|
|
self.pbcompile(compiler, source, objectfile, extra_args=extra_args)
|
|
|
|
try:
|
|
|
|
subprocess.check_call(link_cmd)
|
|
|
|
finally:
|
|
|
|
os.unlink(objectfile)
|
|
|
|
|
|
|
|
def test_prebuilt_shared_lib(self):
|
|
|
|
(cc, _, object_suffix, shared_suffix) = self.detect_prebuild_env()
|
|
|
|
tdir = os.path.join(self.unit_test_dir, '17 prebuilt shared')
|
|
|
|
source = os.path.join(tdir, 'alexandria.c')
|
|
|
|
objectfile = os.path.join(tdir, 'alexandria.' + object_suffix)
|
|
|
|
impfile = os.path.join(tdir, 'alexandria.lib')
|
|
|
|
if cc.get_argument_syntax() == 'msvc':
|
|
|
|
shlibfile = os.path.join(tdir, 'alexandria.' + shared_suffix)
|
|
|
|
elif is_cygwin():
|
|
|
|
shlibfile = os.path.join(tdir, 'cygalexandria.' + shared_suffix)
|
|
|
|
else:
|
|
|
|
shlibfile = os.path.join(tdir, 'libalexandria.' + shared_suffix)
|
|
|
|
self.build_shared_lib(cc, source, objectfile, shlibfile, impfile)
|
|
|
|
# Run the test
|
|
|
|
try:
|
|
|
|
self.init(tdir)
|
|
|
|
self.build()
|
|
|
|
self.run_tests()
|
|
|
|
finally:
|
|
|
|
os.unlink(shlibfile)
|
|
|
|
if mesonbuild.mesonlib.is_windows():
|
|
|
|
# Clean up all the garbage MSVC writes in the
|
|
|
|
# source tree.
|
|
|
|
for fname in glob(os.path.join(tdir, 'alexandria.*')):
|
|
|
|
if os.path.splitext(fname)[1] not in ['.c', '.h']:
|
|
|
|
os.unlink(fname)
|
|
|
|
|
|
|
|
@skipIfNoPkgconfig
|
|
|
|
def test_pkgconfig_static(self):
|
|
|
|
'''
|
|
|
|
Test that the we prefer static libraries when `static: true` is
|
|
|
|
passed to dependency() with pkg-config. Can't be an ordinary test
|
|
|
|
because we need to build libs and try to find them from meson.build
|
|
|
|
|
|
|
|
Also test that it's not a hard error to have unsatisfiable library deps
|
|
|
|
since system libraries -lm will never be found statically.
|
|
|
|
https://github.com/mesonbuild/meson/issues/2785
|
|
|
|
'''
|
|
|
|
(cc, stlinker, objext, shext) = self.detect_prebuild_env()
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '18 pkgconfig static')
|
|
|
|
source = os.path.join(testdir, 'foo.c')
|
|
|
|
objectfile = os.path.join(testdir, 'foo.' + objext)
|
|
|
|
stlibfile = os.path.join(testdir, 'libfoo.a')
|
|
|
|
impfile = os.path.join(testdir, 'foo.lib')
|
|
|
|
if cc.get_argument_syntax() == 'msvc':
|
|
|
|
shlibfile = os.path.join(testdir, 'foo.' + shext)
|
|
|
|
elif is_cygwin():
|
|
|
|
shlibfile = os.path.join(testdir, 'cygfoo.' + shext)
|
|
|
|
else:
|
|
|
|
shlibfile = os.path.join(testdir, 'libfoo.' + shext)
|
|
|
|
# Build libs
|
|
|
|
self.build_static_lib(cc, stlinker, source, objectfile, stlibfile, extra_args=['-DFOO_STATIC'])
|
|
|
|
self.build_shared_lib(cc, source, objectfile, shlibfile, impfile)
|
|
|
|
# Run test
|
|
|
|
try:
|
|
|
|
self.init(testdir, override_envvars={'PKG_CONFIG_LIBDIR': self.builddir})
|
|
|
|
self.build()
|
|
|
|
self.run_tests()
|
|
|
|
finally:
|
|
|
|
os.unlink(stlibfile)
|
|
|
|
os.unlink(shlibfile)
|
|
|
|
if mesonbuild.mesonlib.is_windows():
|
|
|
|
# Clean up all the garbage MSVC writes in the
|
|
|
|
# source tree.
|
|
|
|
for fname in glob(os.path.join(testdir, 'foo.*')):
|
|
|
|
if os.path.splitext(fname)[1] not in ['.c', '.h', '.in']:
|
|
|
|
os.unlink(fname)
|
|
|
|
|
|
|
|
@skipIfNoPkgconfig
|
|
|
|
def test_pkgconfig_gen_escaping(self):
|
|
|
|
testdir = os.path.join(self.common_test_dir, '47 pkgconfig-gen')
|
|
|
|
prefix = '/usr/with spaces'
|
|
|
|
libdir = 'lib'
|
|
|
|
self.init(testdir, extra_args=['--prefix=' + prefix,
|
|
|
|
'--libdir=' + libdir])
|
|
|
|
# Find foo dependency
|
|
|
|
os.environ['PKG_CONFIG_LIBDIR'] = self.privatedir
|
|
|
|
env = get_fake_env(testdir, self.builddir, self.prefix)
|
|
|
|
kwargs = {'required': True, 'silent': True}
|
|
|
|
foo_dep = PkgConfigDependency('libfoo', env, kwargs)
|
|
|
|
# Ensure link_args are properly quoted
|
|
|
|
libdir = PurePath(prefix) / PurePath(libdir)
|
|
|
|
link_args = ['-L' + libdir.as_posix(), '-lfoo']
|
|
|
|
self.assertEqual(foo_dep.get_link_args(), link_args)
|
|
|
|
# Ensure include args are properly quoted
|
|
|
|
incdir = PurePath(prefix) / PurePath('include')
|
|
|
|
cargs = ['-I' + incdir.as_posix()]
|
|
|
|
self.assertEqual(foo_dep.get_compile_args(), cargs)
|
|
|
|
|
|
|
|
def test_array_option_change(self):
|
|
|
|
def get_opt():
|
|
|
|
opts = self.introspect('--buildoptions')
|
|
|
|
for x in opts:
|
|
|
|
if x.get('name') == 'list':
|
|
|
|
return x
|
|
|
|
raise Exception(opts)
|
|
|
|
|
|
|
|
expected = {
|
|
|
|
'name': 'list',
|
|
|
|
'description': 'list',
|
|
|
|
'section': 'user',
|
|
|
|
'type': 'array',
|
|
|
|
'value': ['foo', 'bar'],
|
|
|
|
'machine': 'any',
|
|
|
|
}
|
|
|
|
tdir = os.path.join(self.unit_test_dir, '19 array option')
|
|
|
|
self.init(tdir)
|
|
|
|
original = get_opt()
|
|
|
|
self.assertDictEqual(original, expected)
|
|
|
|
|
|
|
|
expected['value'] = ['oink', 'boink']
|
|
|
|
self.setconf('-Dlist=oink,boink')
|
|
|
|
changed = get_opt()
|
|
|
|
self.assertEqual(changed, expected)
|
|
|
|
|
|
|
|
def test_array_option_bad_change(self):
|
|
|
|
def get_opt():
|
|
|
|
opts = self.introspect('--buildoptions')
|
|
|
|
for x in opts:
|
|
|
|
if x.get('name') == 'list':
|
|
|
|
return x
|
|
|
|
raise Exception(opts)
|
|
|
|
|
|
|
|
expected = {
|
|
|
|
'name': 'list',
|
|
|
|
'description': 'list',
|
|
|
|
'section': 'user',
|
|
|
|
'type': 'array',
|
|
|
|
'value': ['foo', 'bar'],
|
|
|
|
'machine': 'any',
|
|
|
|
}
|
|
|
|
tdir = os.path.join(self.unit_test_dir, '19 array option')
|
|
|
|
self.init(tdir)
|
|
|
|
original = get_opt()
|
|
|
|
self.assertDictEqual(original, expected)
|
|
|
|
with self.assertRaises(subprocess.CalledProcessError):
|
|
|
|
self.setconf('-Dlist=bad')
|
|
|
|
changed = get_opt()
|
|
|
|
self.assertDictEqual(changed, expected)
|
|
|
|
|
|
|
|
def test_array_option_empty_equivalents(self):
|
|
|
|
"""Array options treat -Dopt=[] and -Dopt= as equivalent."""
|
|
|
|
def get_opt():
|
|
|
|
opts = self.introspect('--buildoptions')
|
|
|
|
for x in opts:
|
|
|
|
if x.get('name') == 'list':
|
|
|
|
return x
|
|
|
|
raise Exception(opts)
|
|
|
|
|
|
|
|
expected = {
|
|
|
|
'name': 'list',
|
|
|
|
'description': 'list',
|
|
|
|
'section': 'user',
|
|
|
|
'type': 'array',
|
|
|
|
'value': [],
|
|
|
|
'machine': 'any',
|
|
|
|
}
|
|
|
|
tdir = os.path.join(self.unit_test_dir, '19 array option')
|
|
|
|
self.init(tdir, extra_args='-Dlist=')
|
|
|
|
original = get_opt()
|
|
|
|
self.assertDictEqual(original, expected)
|
|
|
|
|
|
|
|
def opt_has(self, name, value):
|
|
|
|
res = self.introspect('--buildoptions')
|
|
|
|
found = False
|
|
|
|
for i in res:
|
|
|
|
if i['name'] == name:
|
|
|
|
self.assertEqual(i['value'], value)
|
|
|
|
found = True
|
|
|
|
break
|
|
|
|
self.assertTrue(found, "Array option not found in introspect data.")
|
|
|
|
|
|
|
|
def test_free_stringarray_setting(self):
|
|
|
|
testdir = os.path.join(self.common_test_dir, '43 options')
|
|
|
|
self.init(testdir)
|
|
|
|
self.opt_has('free_array_opt', [])
|
|
|
|
self.setconf('-Dfree_array_opt=foo,bar', will_build=False)
|
|
|
|
self.opt_has('free_array_opt', ['foo', 'bar'])
|
|
|
|
self.setconf("-Dfree_array_opt=['a,b', 'c,d']", will_build=False)
|
|
|
|
self.opt_has('free_array_opt', ['a,b', 'c,d'])
|
|
|
|
|
|
|
|
def test_subproject_promotion(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '12 promote')
|
|
|
|
workdir = os.path.join(self.builddir, 'work')
|
|
|
|
shutil.copytree(testdir, workdir)
|
|
|
|
spdir = os.path.join(workdir, 'subprojects')
|
|
|
|
s3dir = os.path.join(spdir, 's3')
|
|
|
|
scommondir = os.path.join(spdir, 'scommon')
|
|
|
|
self.assertFalse(os.path.isdir(s3dir))
|
|
|
|
subprocess.check_call(self.wrap_command + ['promote', 's3'], cwd=workdir)
|
|
|
|
self.assertTrue(os.path.isdir(s3dir))
|
|
|
|
self.assertFalse(os.path.isdir(scommondir))
|
|
|
|
self.assertNotEqual(subprocess.call(self.wrap_command + ['promote', 'scommon'],
|
|
|
|
cwd=workdir,
|
|
|
|
stdout=subprocess.DEVNULL), 0)
|
|
|
|
self.assertNotEqual(subprocess.call(self.wrap_command + ['promote', 'invalid/path/to/scommon'],
|
|
|
|
cwd=workdir,
|
|
|
|
stderr=subprocess.DEVNULL), 0)
|
|
|
|
self.assertFalse(os.path.isdir(scommondir))
|
|
|
|
subprocess.check_call(self.wrap_command + ['promote', 'subprojects/s2/subprojects/scommon'], cwd=workdir)
|
|
|
|
self.assertTrue(os.path.isdir(scommondir))
|
|
|
|
promoted_wrap = os.path.join(spdir, 'athing.wrap')
|
|
|
|
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.init(workdir)
|
|
|
|
self.build()
|
|
|
|
|
|
|
|
def test_subproject_promotion_wrap(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '44 promote wrap')
|
|
|
|
workdir = os.path.join(self.builddir, 'work')
|
|
|
|
shutil.copytree(testdir, workdir)
|
|
|
|
spdir = os.path.join(workdir, 'subprojects')
|
|
|
|
|
|
|
|
ambiguous_wrap = os.path.join(spdir, 'ambiguous.wrap')
|
|
|
|
self.assertNotEqual(subprocess.call(self.wrap_command + ['promote', 'ambiguous'],
|
|
|
|
cwd=workdir,
|
|
|
|
stdout=subprocess.DEVNULL), 0)
|
|
|
|
self.assertFalse(os.path.isfile(ambiguous_wrap))
|
|
|
|
subprocess.check_call(self.wrap_command + ['promote', 'subprojects/s2/subprojects/ambiguous.wrap'], cwd=workdir)
|
|
|
|
self.assertTrue(os.path.isfile(ambiguous_wrap))
|
|
|
|
|
|
|
|
def test_warning_location(self):
|
|
|
|
tdir = os.path.join(self.unit_test_dir, '22 warning location')
|
|
|
|
out = self.init(tdir)
|
|
|
|
for expected in [
|
|
|
|
r'meson.build:4: WARNING: Keyword argument "link_with" defined multiple times.',
|
|
|
|
r'sub' + os.path.sep + r'meson.build:3: WARNING: Keyword argument "link_with" defined multiple times.',
|
|
|
|
r'meson.build:6: WARNING: a warning of some sort',
|
|
|
|
r'sub' + os.path.sep + r'meson.build:4: WARNING: subdir warning',
|
|
|
|
r'meson.build:7: WARNING: Module unstable-simd has no backwards or forwards compatibility and might not exist in future releases.',
|
|
|
|
r"meson.build:11: WARNING: The variable(s) 'MISSING' in the input file 'conf.in' are not present in the given configuration data.",
|
|
|
|
r'meson.build:1: WARNING: Passed invalid keyword argument "invalid".',
|
|
|
|
]:
|
|
|
|
self.assertRegex(out, re.escape(expected))
|
|
|
|
|
|
|
|
def test_permitted_method_kwargs(self):
|
|
|
|
tdir = os.path.join(self.unit_test_dir, '25 non-permitted kwargs')
|
|
|
|
out = self.init(tdir)
|
|
|
|
for expected in [
|
|
|
|
r'WARNING: Passed invalid keyword argument "prefixxx".',
|
|
|
|
r'WARNING: Passed invalid keyword argument "argsxx".',
|
|
|
|
r'WARNING: Passed invalid keyword argument "invalidxx".',
|
|
|
|
]:
|
|
|
|
self.assertRegex(out, re.escape(expected))
|
|
|
|
|
|
|
|
def test_templates(self):
|
|
|
|
ninja = detect_ninja()
|
|
|
|
if ninja is None:
|
|
|
|
raise unittest.SkipTest('This test currently requires ninja. Fix this once "meson build" works.')
|
|
|
|
langs = ['c']
|
|
|
|
env = get_fake_env()
|
|
|
|
try:
|
|
|
|
env.detect_cpp_compiler(MachineChoice.HOST)
|
|
|
|
langs.append('cpp')
|
|
|
|
except EnvironmentException:
|
|
|
|
pass
|
|
|
|
try:
|
|
|
|
env.detect_d_compiler(MachineChoice.HOST)
|
|
|
|
langs.append('d')
|
|
|
|
except EnvironmentException:
|
|
|
|
pass
|
|
|
|
try:
|
|
|
|
env.detect_fortran_compiler(MachineChoice.HOST)
|
|
|
|
langs.append('fortran')
|
|
|
|
except EnvironmentException:
|
|
|
|
pass
|
|
|
|
try:
|
|
|
|
env.detect_objc_compiler(MachineChoice.HOST)
|
|
|
|
langs.append('objc')
|
|
|
|
except EnvironmentException:
|
|
|
|
pass
|
|
|
|
# FIXME: omitting rust as Windows AppVeyor CI finds Rust but doesn't link correctly
|
|
|
|
|
|
|
|
for lang in langs:
|
|
|
|
for target_type in ('executable', 'library'):
|
|
|
|
# test empty directory
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
|
|
self._run(self.meson_command + ['init', '--language', lang, '--type', target_type],
|
|
|
|
workdir=tmpdir)
|
Set the meson command to use when we know what it is
Instead of using fragile guessing to figure out how to invoke meson,
set the value when meson is run. Also rework how we pass of
meson_script_launcher to regenchecker.py -- it wasn't even being used
With this change, we only need to guess the meson path when running
the tests, and in that case:
1. If MESON_EXE is set in the env, we know how to run meson
for project tests.
2. MESON_EXE is not set, which means we run the configure in-process
for project tests and need to guess what meson to run, so either
- meson.py is found next to run_tests.py, or
- meson, meson.py, or meson.exe is in PATH
Otherwise, you can invoke meson in the following ways:
1. meson is installed, and mesonbuild is available in PYTHONPATH:
- meson, meson.py, meson.exe from PATH
- python3 -m mesonbuild.mesonmain
- python3 /path/to/meson.py
- meson is a shell wrapper to meson.real
2. meson is not installed, and is run from git:
- Absolute path to meson.py
- Relative path to meson.py
- Symlink to meson.py
All these are tested in test_meson_commands.py, except meson.exe since
that involves building the meson msi and installing it.
7 years ago
|
|
|
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'):
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
|
|
with open(os.path.join(tmpdir, 'foo.' + lang), 'w') as f:
|
|
|
|
f.write('int main(void) {}')
|
|
|
|
self._run(self.meson_command + ['init', '-b'], workdir=tmpdir)
|
|
|
|
|
|
|
|
# The test uses mocking and thus requires that
|
|
|
|
# the current process is the one to run the Meson steps.
|
|
|
|
# If we are using an external test executable (most commonly
|
|
|
|
# in Debian autopkgtests) then the mocking won't work.
|
|
|
|
@unittest.skipIf('MESON_EXE' in os.environ, 'MESON_EXE is defined, can not use mocking.')
|
|
|
|
def test_cross_file_system_paths(self):
|
|
|
|
if is_windows():
|
|
|
|
raise unittest.SkipTest('system crossfile paths not defined for Windows (yet)')
|
|
|
|
if is_sunos():
|
|
|
|
cc = 'gcc'
|
|
|
|
else:
|
|
|
|
cc = 'cc'
|
|
|
|
|
|
|
|
testdir = os.path.join(self.common_test_dir, '1 trivial')
|
|
|
|
cross_content = textwrap.dedent("""\
|
|
|
|
[binaries]
|
|
|
|
c = '/usr/bin/{}'
|
|
|
|
ar = '/usr/bin/ar'
|
|
|
|
strip = '/usr/bin/ar'
|
|
|
|
|
|
|
|
[properties]
|
|
|
|
|
|
|
|
[host_machine]
|
|
|
|
system = 'linux'
|
|
|
|
cpu_family = 'x86'
|
|
|
|
cpu = 'i686'
|
|
|
|
endian = 'little'
|
|
|
|
""".format(cc))
|
|
|
|
|
|
|
|
with tempfile.TemporaryDirectory() as d:
|
|
|
|
dir_ = os.path.join(d, 'meson', 'cross')
|
|
|
|
os.makedirs(dir_)
|
|
|
|
with tempfile.NamedTemporaryFile('w', dir=dir_, delete=False) as f:
|
|
|
|
f.write(cross_content)
|
|
|
|
name = os.path.basename(f.name)
|
|
|
|
|
|
|
|
with mock.patch.dict(os.environ, {'XDG_DATA_HOME': d}):
|
|
|
|
self.init(testdir, extra_args=['--cross-file=' + name], inprocess=True)
|
|
|
|
self.wipe()
|
|
|
|
|
|
|
|
with mock.patch.dict(os.environ, {'XDG_DATA_DIRS': d}):
|
|
|
|
os.environ.pop('XDG_DATA_HOME', None)
|
|
|
|
self.init(testdir, extra_args=['--cross-file=' + name], inprocess=True)
|
|
|
|
self.wipe()
|
|
|
|
|
|
|
|
with tempfile.TemporaryDirectory() as d:
|
|
|
|
dir_ = os.path.join(d, '.local', 'share', 'meson', 'cross')
|
|
|
|
os.makedirs(dir_)
|
|
|
|
with tempfile.NamedTemporaryFile('w', dir=dir_, delete=False) as f:
|
|
|
|
f.write(cross_content)
|
|
|
|
name = os.path.basename(f.name)
|
|
|
|
|
|
|
|
# If XDG_DATA_HOME is set in the environment running the
|
|
|
|
# tests this test will fail, os mock the environment, pop
|
|
|
|
# it, then test
|
|
|
|
with mock.patch.dict(os.environ):
|
|
|
|
os.environ.pop('XDG_DATA_HOME', None)
|
|
|
|
with mock.patch('mesonbuild.coredata.os.path.expanduser', lambda x: x.replace('~', d)):
|
|
|
|
self.init(testdir, extra_args=['--cross-file=' + name], inprocess=True)
|
|
|
|
self.wipe()
|
|
|
|
|
|
|
|
def test_compiler_run_command(self):
|
|
|
|
'''
|
|
|
|
The test checks that the compiler object can be passed to
|
|
|
|
run_command().
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '24 compiler run_command')
|
|
|
|
self.init(testdir)
|
|
|
|
|
|
|
|
def test_identical_target_name_in_subproject_flat_layout(self):
|
|
|
|
'''
|
|
|
|
Test that identical targets in different subprojects do not collide
|
|
|
|
if layout is flat.
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.common_test_dir, '177 identical target name in subproject flat layout')
|
|
|
|
self.init(testdir, extra_args=['--layout=flat'])
|
|
|
|
self.build()
|
|
|
|
|
|
|
|
def test_identical_target_name_in_subdir_flat_layout(self):
|
|
|
|
'''
|
|
|
|
Test that identical targets in different subdirs do not collide
|
|
|
|
if layout is flat.
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.common_test_dir, '186 same target name flat layout')
|
|
|
|
self.init(testdir, extra_args=['--layout=flat'])
|
|
|
|
self.build()
|
|
|
|
|
|
|
|
def test_flock(self):
|
|
|
|
exception_raised = False
|
|
|
|
with tempfile.TemporaryDirectory() as tdir:
|
|
|
|
os.mkdir(os.path.join(tdir, 'meson-private'))
|
|
|
|
with BuildDirLock(tdir):
|
|
|
|
try:
|
|
|
|
with BuildDirLock(tdir):
|
|
|
|
pass
|
|
|
|
except MesonException:
|
|
|
|
exception_raised = True
|
|
|
|
self.assertTrue(exception_raised, 'Double locking did not raise exception.')
|
|
|
|
|
|
|
|
@unittest.skipIf(is_osx(), 'Test not applicable to OSX')
|
|
|
|
def test_check_module_linking(self):
|
|
|
|
"""
|
|
|
|
Test that link_with: a shared module issues a warning
|
|
|
|
https://github.com/mesonbuild/meson/issues/2865
|
|
|
|
(That an error is raised on OSX is exercised by test failing/78)
|
|
|
|
"""
|
|
|
|
tdir = os.path.join(self.unit_test_dir, '30 shared_mod linking')
|
|
|
|
out = self.init(tdir)
|
|
|
|
msg = ('''WARNING: target links against shared modules. This is not
|
|
|
|
recommended as it is not supported on some platforms''')
|
|
|
|
self.assertIn(msg, out)
|
|
|
|
|
|
|
|
def test_ndebug_if_release_disabled(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '28 ndebug if-release')
|
|
|
|
self.init(testdir, extra_args=['--buildtype=release', '-Db_ndebug=if-release'])
|
|
|
|
self.build()
|
|
|
|
exe = os.path.join(self.builddir, 'main')
|
|
|
|
self.assertEqual(b'NDEBUG=1', subprocess.check_output(exe).strip())
|
|
|
|
|
|
|
|
def test_ndebug_if_release_enabled(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '28 ndebug if-release')
|
|
|
|
self.init(testdir, extra_args=['--buildtype=debugoptimized', '-Db_ndebug=if-release'])
|
|
|
|
self.build()
|
|
|
|
exe = os.path.join(self.builddir, 'main')
|
|
|
|
self.assertEqual(b'NDEBUG=0', subprocess.check_output(exe).strip())
|
|
|
|
|
|
|
|
def test_guessed_linker_dependencies(self):
|
|
|
|
'''
|
|
|
|
Test that meson adds dependencies for libraries based on the final
|
|
|
|
linker command line.
|
|
|
|
'''
|
|
|
|
testdirbase = os.path.join(self.unit_test_dir, '29 guessed linker dependencies')
|
|
|
|
testdirlib = os.path.join(testdirbase, 'lib')
|
|
|
|
|
|
|
|
extra_args = None
|
|
|
|
libdir_flags = ['-L']
|
|
|
|
env = get_fake_env(testdirlib, self.builddir, self.prefix)
|
|
|
|
if env.detect_c_compiler(MachineChoice.HOST).get_id() in {'msvc', 'clang-cl', 'intel-cl'}:
|
|
|
|
# msvc-like compiler, also test it with msvc-specific flags
|
|
|
|
libdir_flags += ['/LIBPATH:', '-LIBPATH:']
|
|
|
|
else:
|
|
|
|
# static libraries are not linkable with -l with msvc because meson installs them
|
|
|
|
# as .a files which unix_args_to_native will not know as it expects libraries to use
|
|
|
|
# .lib as extension. For a DLL the import library is installed as .lib. Thus for msvc
|
|
|
|
# this tests needs to use shared libraries to test the path resolving logic in the
|
|
|
|
# dependency generation code path.
|
|
|
|
extra_args = ['--default-library', 'static']
|
|
|
|
|
|
|
|
initial_builddir = self.builddir
|
|
|
|
initial_installdir = self.installdir
|
|
|
|
|
|
|
|
for libdir_flag in libdir_flags:
|
|
|
|
# build library
|
|
|
|
self.new_builddir()
|
|
|
|
self.init(testdirlib, extra_args=extra_args)
|
|
|
|
self.build()
|
|
|
|
self.install()
|
|
|
|
libbuilddir = self.builddir
|
|
|
|
installdir = self.installdir
|
|
|
|
libdir = os.path.join(self.installdir, self.prefix.lstrip('/').lstrip('\\'), 'lib')
|
|
|
|
|
|
|
|
# build user of library
|
|
|
|
self.new_builddir()
|
|
|
|
# replace is needed because meson mangles platform paths passed via LDFLAGS
|
|
|
|
self.init(os.path.join(testdirbase, 'exe'),
|
|
|
|
override_envvars={"LDFLAGS": '{}{}'.format(libdir_flag, libdir.replace('\\', '/'))})
|
|
|
|
self.build()
|
|
|
|
self.assertBuildIsNoop()
|
|
|
|
|
|
|
|
# rebuild library
|
|
|
|
exebuilddir = self.builddir
|
|
|
|
self.installdir = installdir
|
|
|
|
self.builddir = libbuilddir
|
|
|
|
# Microsoft's compiler is quite smart about touching import libs on changes,
|
|
|
|
# so ensure that there is actually a change in symbols.
|
|
|
|
self.setconf('-Dmore_exports=true')
|
|
|
|
self.build()
|
|
|
|
self.install()
|
|
|
|
# no ensure_backend_detects_changes needed because self.setconf did that already
|
|
|
|
|
|
|
|
# assert user of library will be rebuild
|
|
|
|
self.builddir = exebuilddir
|
|
|
|
self.assertRebuiltTarget('app')
|
|
|
|
|
|
|
|
# restore dirs for the next test case
|
|
|
|
self.installdir = initial_builddir
|
|
|
|
self.builddir = initial_installdir
|
|
|
|
|
|
|
|
def test_conflicting_d_dash_option(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '37 mixed command line args')
|
|
|
|
with self.assertRaises(subprocess.CalledProcessError) as e:
|
|
|
|
self.init(testdir, extra_args=['-Dbindir=foo', '--bindir=bar'])
|
|
|
|
# Just to ensure that we caught the correct error
|
|
|
|
self.assertIn('passed as both', e.stderr)
|
|
|
|
|
|
|
|
def _test_same_option_twice(self, arg, args):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '37 mixed command line args')
|
|
|
|
self.init(testdir, extra_args=args)
|
|
|
|
opts = self.introspect('--buildoptions')
|
|
|
|
for item in opts:
|
|
|
|
if item['name'] == arg:
|
|
|
|
self.assertEqual(item['value'], 'bar')
|
|
|
|
return
|
|
|
|
raise Exception('Missing {} value?'.format(arg))
|
|
|
|
|
|
|
|
def test_same_dash_option_twice(self):
|
|
|
|
self._test_same_option_twice('bindir', ['--bindir=foo', '--bindir=bar'])
|
|
|
|
|
|
|
|
def test_same_d_option_twice(self):
|
|
|
|
self._test_same_option_twice('bindir', ['-Dbindir=foo', '-Dbindir=bar'])
|
|
|
|
|
|
|
|
def test_same_project_d_option_twice(self):
|
|
|
|
self._test_same_option_twice('one', ['-Done=foo', '-Done=bar'])
|
|
|
|
|
|
|
|
def _test_same_option_twice_configure(self, arg, args):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '37 mixed command line args')
|
|
|
|
self.init(testdir)
|
|
|
|
self.setconf(args)
|
|
|
|
opts = self.introspect('--buildoptions')
|
|
|
|
for item in opts:
|
|
|
|
if item['name'] == arg:
|
|
|
|
self.assertEqual(item['value'], 'bar')
|
|
|
|
return
|
|
|
|
raise Exception('Missing {} value?'.format(arg))
|
|
|
|
|
|
|
|
def test_same_dash_option_twice_configure(self):
|
|
|
|
self._test_same_option_twice_configure(
|
|
|
|
'bindir', ['--bindir=foo', '--bindir=bar'])
|
|
|
|
|
|
|
|
def test_same_d_option_twice_configure(self):
|
|
|
|
self._test_same_option_twice_configure(
|
|
|
|
'bindir', ['-Dbindir=foo', '-Dbindir=bar'])
|
|
|
|
|
|
|
|
def test_same_project_d_option_twice_configure(self):
|
|
|
|
self._test_same_option_twice_configure(
|
|
|
|
'one', ['-Done=foo', '-Done=bar'])
|
|
|
|
|
|
|
|
def test_command_line(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '34 command line')
|
|
|
|
|
|
|
|
# Verify default values when passing no args
|
|
|
|
self.init(testdir)
|
|
|
|
obj = mesonbuild.coredata.load(self.builddir)
|
|
|
|
self.assertEqual(obj.builtins['default_library'].value, 'static')
|
|
|
|
self.assertEqual(obj.builtins['warning_level'].value, '1')
|
|
|
|
self.assertEqual(obj.user_options['set_sub_opt'].value, True)
|
|
|
|
self.assertEqual(obj.user_options['subp:subp_opt'].value, 'default3')
|
|
|
|
self.wipe()
|
|
|
|
|
|
|
|
# warning_level is special, it's --warnlevel instead of --warning-level
|
|
|
|
# for historical reasons
|
|
|
|
self.init(testdir, extra_args=['--warnlevel=2'])
|
|
|
|
obj = mesonbuild.coredata.load(self.builddir)
|
|
|
|
self.assertEqual(obj.builtins['warning_level'].value, '2')
|
|
|
|
self.setconf('--warnlevel=3')
|
|
|
|
obj = mesonbuild.coredata.load(self.builddir)
|
|
|
|
self.assertEqual(obj.builtins['warning_level'].value, '3')
|
|
|
|
self.wipe()
|
|
|
|
|
|
|
|
# But when using -D syntax, it should be 'warning_level'
|
|
|
|
self.init(testdir, extra_args=['-Dwarning_level=2'])
|
|
|
|
obj = mesonbuild.coredata.load(self.builddir)
|
|
|
|
self.assertEqual(obj.builtins['warning_level'].value, '2')
|
|
|
|
self.setconf('-Dwarning_level=3')
|
|
|
|
obj = mesonbuild.coredata.load(self.builddir)
|
|
|
|
self.assertEqual(obj.builtins['warning_level'].value, '3')
|
|
|
|
self.wipe()
|
|
|
|
|
|
|
|
# Mixing --option and -Doption is forbidden
|
|
|
|
with self.assertRaises(subprocess.CalledProcessError) as cm:
|
|
|
|
self.init(testdir, extra_args=['--warnlevel=1', '-Dwarning_level=3'])
|
|
|
|
self.assertNotEqual(0, cm.exception.returncode)
|
|
|
|
self.assertIn('as both', cm.exception.output)
|
|
|
|
self.init(testdir)
|
|
|
|
with self.assertRaises(subprocess.CalledProcessError) as cm:
|
|
|
|
self.setconf(['--warnlevel=1', '-Dwarning_level=3'])
|
|
|
|
self.assertNotEqual(0, cm.exception.returncode)
|
|
|
|
self.assertIn('as both', cm.exception.output)
|
|
|
|
self.wipe()
|
|
|
|
|
|
|
|
# --default-library should override default value from project()
|
|
|
|
self.init(testdir, extra_args=['--default-library=both'])
|
|
|
|
obj = mesonbuild.coredata.load(self.builddir)
|
|
|
|
self.assertEqual(obj.builtins['default_library'].value, 'both')
|
|
|
|
self.setconf('--default-library=shared')
|
|
|
|
obj = mesonbuild.coredata.load(self.builddir)
|
|
|
|
self.assertEqual(obj.builtins['default_library'].value, 'shared')
|
|
|
|
if self.backend is Backend.ninja:
|
|
|
|
# reconfigure target works only with ninja backend
|
|
|
|
self.build('reconfigure')
|
|
|
|
obj = mesonbuild.coredata.load(self.builddir)
|
|
|
|
self.assertEqual(obj.builtins['default_library'].value, 'shared')
|
|
|
|
self.wipe()
|
|
|
|
|
|
|
|
# Should warn on unknown options
|
|
|
|
out = self.init(testdir, extra_args=['-Dbad=1', '-Dfoo=2', '-Dwrong_link_args=foo'])
|
|
|
|
self.assertIn('Unknown options: "bad, foo, wrong_link_args"', out)
|
|
|
|
self.wipe()
|
|
|
|
|
|
|
|
# Should fail on malformed option
|
|
|
|
with self.assertRaises(subprocess.CalledProcessError) as cm:
|
|
|
|
self.init(testdir, extra_args=['-Dfoo'])
|
|
|
|
self.assertNotEqual(0, cm.exception.returncode)
|
|
|
|
self.assertIn('Option \'foo\' must have a value separated by equals sign.', cm.exception.output)
|
|
|
|
self.init(testdir)
|
|
|
|
with self.assertRaises(subprocess.CalledProcessError) as cm:
|
|
|
|
self.setconf('-Dfoo')
|
|
|
|
self.assertNotEqual(0, cm.exception.returncode)
|
|
|
|
self.assertIn('Option \'foo\' must have a value separated by equals sign.', cm.exception.output)
|
|
|
|
self.wipe()
|
|
|
|
|
|
|
|
# It is not an error to set wrong option for unknown subprojects or
|
|
|
|
# language because we don't have control on which one will be selected.
|
|
|
|
self.init(testdir, extra_args=['-Dc_wrong=1', '-Dwrong:bad=1', '-Db_wrong=1'])
|
|
|
|
self.wipe()
|
|
|
|
|
|
|
|
# Test we can set subproject option
|
|
|
|
self.init(testdir, extra_args=['-Dsubp:subp_opt=foo'])
|
|
|
|
obj = mesonbuild.coredata.load(self.builddir)
|
|
|
|
self.assertEqual(obj.user_options['subp:subp_opt'].value, 'foo')
|
|
|
|
self.wipe()
|
|
|
|
|
|
|
|
# c_args value should be parsed with split_args
|
|
|
|
self.init(testdir, extra_args=['-Dc_args=-Dfoo -Dbar "-Dthird=one two"'])
|
|
|
|
obj = mesonbuild.coredata.load(self.builddir)
|
|
|
|
self.assertEqual(obj.compiler_options.host['c_args'].value, ['-Dfoo', '-Dbar', '-Dthird=one two'])
|
|
|
|
|
|
|
|
self.setconf('-Dc_args="foo bar" one two')
|
|
|
|
obj = mesonbuild.coredata.load(self.builddir)
|
|
|
|
self.assertEqual(obj.compiler_options.host['c_args'].value, ['foo bar', 'one', 'two'])
|
|
|
|
self.wipe()
|
|
|
|
|
|
|
|
self.init(testdir, extra_args=['-Dset_percent_opt=myoption%'])
|
|
|
|
obj = mesonbuild.coredata.load(self.builddir)
|
|
|
|
self.assertEqual(obj.user_options['set_percent_opt'].value, 'myoption%')
|
|
|
|
self.wipe()
|
|
|
|
|
|
|
|
# Setting a 2nd time the same option should override the first value
|
|
|
|
try:
|
|
|
|
self.init(testdir, extra_args=['--bindir=foo', '--bindir=bar',
|
|
|
|
'-Dbuildtype=plain', '-Dbuildtype=release',
|
|
|
|
'-Db_sanitize=address', '-Db_sanitize=thread',
|
|
|
|
'-Dc_args=-Dfoo', '-Dc_args=-Dbar'])
|
|
|
|
obj = mesonbuild.coredata.load(self.builddir)
|
|
|
|
self.assertEqual(obj.builtins['bindir'].value, 'bar')
|
|
|
|
self.assertEqual(obj.builtins['buildtype'].value, 'release')
|
|
|
|
self.assertEqual(obj.base_options['b_sanitize'].value, 'thread')
|
|
|
|
self.assertEqual(obj.compiler_options.host['c_args'].value, ['-Dbar'])
|
|
|
|
self.setconf(['--bindir=bar', '--bindir=foo',
|
|
|
|
'-Dbuildtype=release', '-Dbuildtype=plain',
|
|
|
|
'-Db_sanitize=thread', '-Db_sanitize=address',
|
|
|
|
'-Dc_args=-Dbar', '-Dc_args=-Dfoo'])
|
|
|
|
obj = mesonbuild.coredata.load(self.builddir)
|
|
|
|
self.assertEqual(obj.builtins['bindir'].value, 'foo')
|
|
|
|
self.assertEqual(obj.builtins['buildtype'].value, 'plain')
|
|
|
|
self.assertEqual(obj.base_options['b_sanitize'].value, 'address')
|
|
|
|
self.assertEqual(obj.compiler_options.host['c_args'].value, ['-Dfoo'])
|
|
|
|
self.wipe()
|
|
|
|
except KeyError:
|
|
|
|
# Ignore KeyError, it happens on CI for compilers that does not
|
|
|
|
# support b_sanitize. We have to test with a base option because
|
|
|
|
# they used to fail this test with Meson 0.46 an earlier versions.
|
|
|
|
pass
|
|
|
|
|
|
|
|
def test_warning_level_0(self):
|
|
|
|
testdir = os.path.join(self.common_test_dir, '214 warning level 0')
|
|
|
|
|
|
|
|
# Verify default values when passing no args
|
|
|
|
self.init(testdir)
|
|
|
|
obj = mesonbuild.coredata.load(self.builddir)
|
|
|
|
self.assertEqual(obj.builtins['warning_level'].value, '0')
|
|
|
|
self.wipe()
|
|
|
|
|
|
|
|
# verify we can override w/ --warnlevel
|
|
|
|
self.init(testdir, extra_args=['--warnlevel=1'])
|
|
|
|
obj = mesonbuild.coredata.load(self.builddir)
|
|
|
|
self.assertEqual(obj.builtins['warning_level'].value, '1')
|
|
|
|
self.setconf('--warnlevel=0')
|
|
|
|
obj = mesonbuild.coredata.load(self.builddir)
|
|
|
|
self.assertEqual(obj.builtins['warning_level'].value, '0')
|
|
|
|
self.wipe()
|
|
|
|
|
|
|
|
# verify we can override w/ -Dwarning_level
|
|
|
|
self.init(testdir, extra_args=['-Dwarning_level=1'])
|
|
|
|
obj = mesonbuild.coredata.load(self.builddir)
|
|
|
|
self.assertEqual(obj.builtins['warning_level'].value, '1')
|
|
|
|
self.setconf('-Dwarning_level=0')
|
|
|
|
obj = mesonbuild.coredata.load(self.builddir)
|
|
|
|
self.assertEqual(obj.builtins['warning_level'].value, '0')
|
|
|
|
self.wipe()
|
|
|
|
|
|
|
|
def test_feature_check_usage_subprojects(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '41 featurenew subprojects')
|
|
|
|
out = self.init(testdir)
|
|
|
|
# Parent project warns correctly
|
|
|
|
self.assertRegex(out, "WARNING: Project targeting '>=0.45'.*'0.47.0': dict")
|
|
|
|
# Subprojects warn correctly
|
|
|
|
self.assertRegex(out, r"\|WARNING: Project targeting '>=0.40'.*'0.44.0': disabler")
|
|
|
|
self.assertRegex(out, r"\|WARNING: Project targeting '!=0.40'.*'0.44.0': disabler")
|
|
|
|
# Subproject has a new-enough meson_version, no warning
|
|
|
|
self.assertNotRegex(out, "WARNING: Project targeting.*Python")
|
|
|
|
# Ensure a summary is printed in the subproject and the outer project
|
|
|
|
self.assertRegex(out, r"\|WARNING: Project specifies a minimum meson_version '>=0.40'")
|
|
|
|
self.assertRegex(out, r"\| \* 0.44.0: {'disabler'}")
|
|
|
|
self.assertRegex(out, "WARNING: Project specifies a minimum meson_version '>=0.45'")
|
|
|
|
self.assertRegex(out, " * 0.47.0: {'dict'}")
|
|
|
|
|
|
|
|
def test_configure_file_warnings(self):
|
|
|
|
testdir = os.path.join(self.common_test_dir, "14 configure file")
|
|
|
|
out = self.init(testdir)
|
|
|
|
self.assertRegex(out, "WARNING:.*'empty'.*config.h.in.*not present.*")
|
|
|
|
self.assertRegex(out, "WARNING:.*'FOO_BAR'.*nosubst-nocopy2.txt.in.*not present.*")
|
|
|
|
self.assertRegex(out, "WARNING:.*'empty'.*config.h.in.*not present.*")
|
|
|
|
self.assertRegex(out, "WARNING:.*empty configuration_data.*test.py.in")
|
|
|
|
# Warnings for configuration files that are overwritten.
|
|
|
|
self.assertRegex(out, "WARNING:.*\"double_output.txt\".*overwrites")
|
|
|
|
self.assertRegex(out, "WARNING:.*\"subdir.double_output2.txt\".*overwrites")
|
|
|
|
self.assertNotRegex(out, "WARNING:.*no_write_conflict.txt.*overwrites")
|
|
|
|
self.assertNotRegex(out, "WARNING:.*@BASENAME@.*overwrites")
|
|
|
|
self.assertRegex(out, "WARNING:.*\"sameafterbasename\".*overwrites")
|
|
|
|
# No warnings about empty configuration data objects passed to files with substitutions
|
|
|
|
self.assertNotRegex(out, "WARNING:.*empty configuration_data.*nosubst-nocopy1.txt.in")
|
|
|
|
self.assertNotRegex(out, "WARNING:.*empty configuration_data.*nosubst-nocopy2.txt.in")
|
|
|
|
with open(os.path.join(self.builddir, 'nosubst-nocopy1.txt'), 'rb') as f:
|
|
|
|
self.assertEqual(f.read().strip(), b'/* #undef FOO_BAR */')
|
|
|
|
with open(os.path.join(self.builddir, 'nosubst-nocopy2.txt'), 'rb') as f:
|
|
|
|
self.assertEqual(f.read().strip(), b'')
|
|
|
|
self.assertRegex(out, r"DEPRECATION:.*\['array'\] is invalid.*dict")
|
|
|
|
|
|
|
|
def test_dirs(self):
|
|
|
|
with tempfile.TemporaryDirectory() as containing:
|
|
|
|
with tempfile.TemporaryDirectory(dir=containing) as srcdir:
|
|
|
|
mfile = os.path.join(srcdir, 'meson.build')
|
|
|
|
of = open(mfile, 'w')
|
|
|
|
of.write("project('foobar', 'c')\n")
|
|
|
|
of.close()
|
|
|
|
pc = subprocess.run(self.setup_command,
|
|
|
|
cwd=srcdir,
|
|
|
|
stdout=subprocess.PIPE,
|
|
|
|
stderr=subprocess.DEVNULL)
|
|
|
|
self.assertIn(b'Must specify at least one directory name', pc.stdout)
|
|
|
|
with tempfile.TemporaryDirectory(dir=srcdir) as builddir:
|
|
|
|
subprocess.run(self.setup_command,
|
|
|
|
check=True,
|
|
|
|
cwd=builddir,
|
|
|
|
stdout=subprocess.DEVNULL,
|
|
|
|
stderr=subprocess.DEVNULL)
|
|
|
|
|
|
|
|
def get_opts_as_dict(self):
|
|
|
|
result = {}
|
|
|
|
for i in self.introspect('--buildoptions'):
|
|
|
|
result[i['name']] = i['value']
|
|
|
|
return result
|
|
|
|
|
|
|
|
def test_buildtype_setting(self):
|
|
|
|
testdir = os.path.join(self.common_test_dir, '1 trivial')
|
|
|
|
self.init(testdir)
|
|
|
|
opts = self.get_opts_as_dict()
|
|
|
|
self.assertEqual(opts['buildtype'], 'debug')
|
|
|
|
self.assertEqual(opts['debug'], True)
|
|
|
|
self.setconf('-Ddebug=false')
|
|
|
|
opts = self.get_opts_as_dict()
|
|
|
|
self.assertEqual(opts['debug'], False)
|
|
|
|
self.assertEqual(opts['buildtype'], 'plain')
|
|
|
|
self.assertEqual(opts['optimization'], '0')
|
|
|
|
|
|
|
|
# Setting optimizations to 3 should cause buildtype
|
|
|
|
# to go to release mode.
|
|
|
|
self.setconf('-Doptimization=3')
|
|
|
|
opts = self.get_opts_as_dict()
|
|
|
|
self.assertEqual(opts['buildtype'], 'release')
|
|
|
|
self.assertEqual(opts['debug'], False)
|
|
|
|
self.assertEqual(opts['optimization'], '3')
|
|
|
|
|
|
|
|
# Going to debug build type should reset debugging
|
|
|
|
# and optimization
|
|
|
|
self.setconf('-Dbuildtype=debug')
|
|
|
|
opts = self.get_opts_as_dict()
|
|
|
|
self.assertEqual(opts['buildtype'], 'debug')
|
|
|
|
self.assertEqual(opts['debug'], True)
|
|
|
|
self.assertEqual(opts['optimization'], '0')
|
|
|
|
|
Don't use host pkg-config for native dependencies
When trying to cross-compile mesa on an aarch64 system, I noticed some
strange behavior. Meson would only ever find the wayland-scanner binary
in my host machine's sysroot (/mnt/amethyst):
Native dependency wayland-scanner found: YES 1.16.0
Program /mnt/amethyst/usr/bin/wayland-scanner found: YES (/mnt/amethyst/usr/bin/wayland-scanner)
It should be finding /usr/bin/wayland-scanner instead, since the
wayland-scanner dependency is created as native. On closer inspection,
it turned out that meson was ignoring the native argument passed to
dependency(), and wuld always use the pkgconfig binary specified in my
toolchain instead of the native one (/usr/bin/pkg-config):
Native dependency wayland-scanner found: YES 1.16.0
Called `/home/lyudess/Projects/panfrost/scripts/amethyst-pkg-config
--variable=wayland_scanner wayland-scanner` -> 0
Turns out that if we create a dependency() object with native:false, we
end up caching the pkg-config path for the host machine in
PkgConfigDependency.class_pkgbin, instead of the build machine's
pkg-config path. This results causing in all pkg-config invocations for
dependency() objects to use the host machine's pkg-config binary,
regardless of whether or not 'native: true' was specified when the
dependency() object was instantiated.
So, fix this by never setting PkgConfigDependency.class_pkgbin for cross
dependency() objects. Also, add some test cases for this. Since
triggering this bug can be avoided by creating a dependency() objects
with native:true before creating any with native:false, we make sure
that our test has two modes: one where it starts with a native
dependency first, and another where it starts with a cross dependency
first.
As a final note here: We currently skip this test on windows, because
windows doesn't support directly executing python scripts as
executables: something that we need in order to point pkgconfig to a
wrapper script that sets the PKG_CONFIG_LIBDIR env appropriately before
calling pkg-config.
Signed-off-by: Lyude Paul <thatslyude@gmail.com>
6 years ago
|
|
|
@skipIfNoPkgconfig
|
|
|
|
@unittest.skipIf(is_windows(), 'Help needed with fixing this test on windows')
|
|
|
|
def test_native_dep_pkgconfig(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir,
|
|
|
|
'46 native dep pkgconfig var')
|
|
|
|
with tempfile.NamedTemporaryFile(mode='w', delete=False) as crossfile:
|
|
|
|
crossfile.write(textwrap.dedent(
|
|
|
|
'''[binaries]
|
|
|
|
pkgconfig = r'{0}'
|
|
|
|
|
|
|
|
[properties]
|
|
|
|
|
|
|
|
[host_machine]
|
|
|
|
system = 'linux'
|
|
|
|
cpu_family = 'arm'
|
|
|
|
cpu = 'armv7'
|
|
|
|
endian = 'little'
|
|
|
|
'''.format(os.path.join(testdir, 'cross_pkgconfig.py'))))
|
|
|
|
crossfile.flush()
|
|
|
|
self.meson_cross_file = crossfile.name
|
|
|
|
|
|
|
|
env = {'PKG_CONFIG_LIBDIR': os.path.join(testdir,
|
|
|
|
'native_pkgconfig')}
|
|
|
|
self.init(testdir, extra_args=['-Dstart_native=false'], override_envvars=env)
|
Don't use host pkg-config for native dependencies
When trying to cross-compile mesa on an aarch64 system, I noticed some
strange behavior. Meson would only ever find the wayland-scanner binary
in my host machine's sysroot (/mnt/amethyst):
Native dependency wayland-scanner found: YES 1.16.0
Program /mnt/amethyst/usr/bin/wayland-scanner found: YES (/mnt/amethyst/usr/bin/wayland-scanner)
It should be finding /usr/bin/wayland-scanner instead, since the
wayland-scanner dependency is created as native. On closer inspection,
it turned out that meson was ignoring the native argument passed to
dependency(), and wuld always use the pkgconfig binary specified in my
toolchain instead of the native one (/usr/bin/pkg-config):
Native dependency wayland-scanner found: YES 1.16.0
Called `/home/lyudess/Projects/panfrost/scripts/amethyst-pkg-config
--variable=wayland_scanner wayland-scanner` -> 0
Turns out that if we create a dependency() object with native:false, we
end up caching the pkg-config path for the host machine in
PkgConfigDependency.class_pkgbin, instead of the build machine's
pkg-config path. This results causing in all pkg-config invocations for
dependency() objects to use the host machine's pkg-config binary,
regardless of whether or not 'native: true' was specified when the
dependency() object was instantiated.
So, fix this by never setting PkgConfigDependency.class_pkgbin for cross
dependency() objects. Also, add some test cases for this. Since
triggering this bug can be avoided by creating a dependency() objects
with native:true before creating any with native:false, we make sure
that our test has two modes: one where it starts with a native
dependency first, and another where it starts with a cross dependency
first.
As a final note here: We currently skip this test on windows, because
windows doesn't support directly executing python scripts as
executables: something that we need in order to point pkgconfig to a
wrapper script that sets the PKG_CONFIG_LIBDIR env appropriately before
calling pkg-config.
Signed-off-by: Lyude Paul <thatslyude@gmail.com>
6 years ago
|
|
|
self.wipe()
|
|
|
|
self.init(testdir, extra_args=['-Dstart_native=true'], override_envvars=env)
|
|
|
|
|
|
|
|
def __reconfigure(self, change_minor=False):
|
|
|
|
# Set an older version to force a reconfigure from scratch
|
|
|
|
filename = os.path.join(self.privatedir, 'coredata.dat')
|
|
|
|
with open(filename, 'rb') as f:
|
|
|
|
obj = pickle.load(f)
|
|
|
|
if change_minor:
|
|
|
|
v = mesonbuild.coredata.version.split('.')
|
|
|
|
obj.version = '.'.join(v[0:2] + [str(int(v[2]) + 1)])
|
|
|
|
else:
|
|
|
|
obj.version = '0.47.0'
|
|
|
|
with open(filename, 'wb') as f:
|
|
|
|
pickle.dump(obj, f)
|
|
|
|
|
|
|
|
def test_reconfigure(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '48 reconfigure')
|
|
|
|
self.init(testdir, extra_args=['-Dopt1=val1'])
|
|
|
|
self.setconf('-Dopt2=val2')
|
|
|
|
|
|
|
|
self.__reconfigure()
|
|
|
|
|
|
|
|
out = self.init(testdir, extra_args=['--reconfigure', '-Dopt3=val3'])
|
|
|
|
self.assertRegex(out, 'WARNING:.*Regenerating configuration from scratch')
|
|
|
|
self.assertRegex(out, 'opt1 val1')
|
|
|
|
self.assertRegex(out, 'opt2 val2')
|
|
|
|
self.assertRegex(out, 'opt3 val3')
|
|
|
|
self.assertRegex(out, 'opt4 default4')
|
|
|
|
self.build()
|
|
|
|
self.run_tests()
|
|
|
|
|
|
|
|
# Create a file in builddir and verify wipe command removes it
|
|
|
|
filename = os.path.join(self.builddir, 'something')
|
|
|
|
open(filename, 'w').close()
|
|
|
|
self.assertTrue(os.path.exists(filename))
|
|
|
|
out = self.init(testdir, extra_args=['--wipe', '-Dopt4=val4'])
|
|
|
|
self.assertFalse(os.path.exists(filename))
|
|
|
|
self.assertRegex(out, 'opt1 val1')
|
|
|
|
self.assertRegex(out, 'opt2 val2')
|
|
|
|
self.assertRegex(out, 'opt3 val3')
|
|
|
|
self.assertRegex(out, 'opt4 val4')
|
|
|
|
self.build()
|
|
|
|
self.run_tests()
|
|
|
|
|
|
|
|
def test_wipe_from_builddir(self):
|
|
|
|
testdir = os.path.join(self.common_test_dir, '161 custom target subdir depend files')
|
|
|
|
self.init(testdir)
|
|
|
|
self.__reconfigure()
|
|
|
|
|
|
|
|
with Path(self.builddir):
|
|
|
|
self.init(testdir, extra_args=['--wipe'])
|
|
|
|
|
|
|
|
def test_minor_version_does_not_reconfigure_wipe(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '48 reconfigure')
|
|
|
|
self.init(testdir, extra_args=['-Dopt1=val1'])
|
|
|
|
self.setconf('-Dopt2=val2')
|
|
|
|
|
|
|
|
self.__reconfigure(change_minor=True)
|
|
|
|
|
|
|
|
out = self.init(testdir, extra_args=['--reconfigure', '-Dopt3=val3'])
|
|
|
|
self.assertNotRegex(out, 'WARNING:.*Regenerating configuration from scratch')
|
|
|
|
self.assertRegex(out, 'opt1 val1')
|
|
|
|
self.assertRegex(out, 'opt2 val2')
|
|
|
|
self.assertRegex(out, 'opt3 val3')
|
|
|
|
self.assertRegex(out, 'opt4 default4')
|
|
|
|
self.build()
|
|
|
|
self.run_tests()
|
|
|
|
|
|
|
|
def test_target_construct_id_from_path(self):
|
|
|
|
# This id is stable but not guessable.
|
|
|
|
# The test is supposed to prevent unintentional
|
|
|
|
# changes of target ID generation.
|
|
|
|
target_id = Target.construct_id_from_path('some/obscure/subdir',
|
|
|
|
'target-id', '@suffix')
|
|
|
|
self.assertEqual('5e002d3@@target-id@suffix', target_id)
|
|
|
|
target_id = Target.construct_id_from_path('subproject/foo/subdir/bar',
|
|
|
|
'target2-id', '@other')
|
|
|
|
self.assertEqual('81d46d1@@target2-id@other', target_id)
|
|
|
|
|
|
|
|
def test_introspect_projectinfo_without_configured_build(self):
|
|
|
|
testfile = os.path.join(self.common_test_dir, '35 run program', 'meson.build')
|
|
|
|
res = self.introspect_directory(testfile, '--projectinfo')
|
|
|
|
self.assertEqual(set(res['buildsystem_files']), set(['meson.build']))
|
|
|
|
self.assertEqual(res['version'], 'undefined')
|
|
|
|
self.assertEqual(res['descriptive_name'], 'run command')
|
|
|
|
self.assertEqual(res['subprojects'], [])
|
|
|
|
|
|
|
|
testfile = os.path.join(self.common_test_dir, '43 options', 'meson.build')
|
|
|
|
res = self.introspect_directory(testfile, '--projectinfo')
|
|
|
|
self.assertEqual(set(res['buildsystem_files']), set(['meson_options.txt', 'meson.build']))
|
|
|
|
self.assertEqual(res['version'], 'undefined')
|
|
|
|
self.assertEqual(res['descriptive_name'], 'options')
|
|
|
|
self.assertEqual(res['subprojects'], [])
|
|
|
|
|
|
|
|
testfile = os.path.join(self.common_test_dir, '46 subproject options', 'meson.build')
|
|
|
|
res = self.introspect_directory(testfile, '--projectinfo')
|
|
|
|
self.assertEqual(set(res['buildsystem_files']), set(['meson_options.txt', 'meson.build']))
|
|
|
|
self.assertEqual(res['version'], 'undefined')
|
|
|
|
self.assertEqual(res['descriptive_name'], 'suboptions')
|
|
|
|
self.assertEqual(len(res['subprojects']), 1)
|
|
|
|
subproject_files = set(f.replace('\\', '/') for f in res['subprojects'][0]['buildsystem_files'])
|
|
|
|
self.assertEqual(subproject_files, set(['subprojects/subproject/meson_options.txt', 'subprojects/subproject/meson.build']))
|
|
|
|
self.assertEqual(res['subprojects'][0]['name'], 'subproject')
|
|
|
|
self.assertEqual(res['subprojects'][0]['version'], 'undefined')
|
|
|
|
self.assertEqual(res['subprojects'][0]['descriptive_name'], 'subproject')
|
|
|
|
|
|
|
|
def test_introspect_projectinfo_subprojects(self):
|
|
|
|
testdir = os.path.join(self.common_test_dir, '102 subproject subdir')
|
|
|
|
self.init(testdir)
|
|
|
|
res = self.introspect('--projectinfo')
|
|
|
|
expected = {
|
|
|
|
'descriptive_name': 'proj',
|
|
|
|
'version': 'undefined',
|
|
|
|
'subproject_dir': 'subprojects',
|
|
|
|
'subprojects': [
|
|
|
|
{
|
|
|
|
'descriptive_name': 'sub',
|
|
|
|
'name': 'sub',
|
|
|
|
'version': 'undefined'
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
self.assertDictEqual(res, expected)
|
|
|
|
|
|
|
|
def test_introspection_target_subproject(self):
|
|
|
|
testdir = os.path.join(self.common_test_dir, '45 subproject')
|
|
|
|
self.init(testdir)
|
|
|
|
res = self.introspect('--targets')
|
|
|
|
|
|
|
|
expected = {
|
|
|
|
'sublib': 'sublib',
|
|
|
|
'simpletest': 'sublib',
|
|
|
|
'user': None
|
|
|
|
}
|
|
|
|
|
|
|
|
for entry in res:
|
|
|
|
name = entry['name']
|
|
|
|
self.assertEqual(entry['subproject'], expected[name])
|
|
|
|
|
|
|
|
def test_introspect_projectinfo_subproject_dir(self):
|
|
|
|
testdir = os.path.join(self.common_test_dir, '78 custom subproject dir')
|
|
|
|
self.init(testdir)
|
|
|
|
res = self.introspect('--projectinfo')
|
|
|
|
|
|
|
|
self.assertEqual(res['subproject_dir'], 'custom_subproject_dir')
|
|
|
|
|
|
|
|
def test_introspect_projectinfo_subproject_dir_from_source(self):
|
|
|
|
testfile = os.path.join(self.common_test_dir, '78 custom subproject dir', 'meson.build')
|
|
|
|
res = self.introspect_directory(testfile, '--projectinfo')
|
|
|
|
|
|
|
|
self.assertEqual(res['subproject_dir'], 'custom_subproject_dir')
|
|
|
|
|
|
|
|
@skipIfNoExecutable('clang-format')
|
|
|
|
def test_clang_format(self):
|
|
|
|
if self.backend is not Backend.ninja:
|
|
|
|
raise unittest.SkipTest('Clang-format is for now only supported on Ninja, not {}'.format(self.backend.name))
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '54 clang-format')
|
|
|
|
testfile = os.path.join(testdir, 'prog.c')
|
|
|
|
badfile = os.path.join(testdir, 'prog_orig_c')
|
|
|
|
goodfile = os.path.join(testdir, 'prog_expected_c')
|
|
|
|
testheader = os.path.join(testdir, 'header.h')
|
|
|
|
badheader = os.path.join(testdir, 'header_orig_h')
|
|
|
|
goodheader = os.path.join(testdir, 'header_expected_h')
|
|
|
|
try:
|
|
|
|
shutil.copyfile(badfile, testfile)
|
|
|
|
shutil.copyfile(badheader, testheader)
|
|
|
|
self.init(testdir)
|
|
|
|
self.assertNotEqual(Path(testfile).read_text(),
|
|
|
|
Path(goodfile).read_text())
|
|
|
|
self.assertNotEqual(Path(testheader).read_text(),
|
|
|
|
Path(goodheader).read_text())
|
|
|
|
self.run_target('clang-format')
|
|
|
|
self.assertEqual(Path(testheader).read_text(),
|
|
|
|
Path(goodheader).read_text())
|
|
|
|
finally:
|
|
|
|
if os.path.exists(testfile):
|
|
|
|
os.unlink(testfile)
|
|
|
|
if os.path.exists(testheader):
|
|
|
|
os.unlink(testheader)
|
|
|
|
|
|
|
|
@skipIfNoExecutable('clang-tidy')
|
|
|
|
def test_clang_tidy(self):
|
|
|
|
if self.backend is not Backend.ninja:
|
|
|
|
raise unittest.SkipTest('Clang-tidy is for now only supported on Ninja, not {}'.format(self.backend.name))
|
|
|
|
if shutil.which('c++') is None:
|
|
|
|
raise unittest.SkipTest('Clang-tidy breaks when ccache is used and "c++" not in path.')
|
|
|
|
if is_osx():
|
|
|
|
raise unittest.SkipTest('Apple ships a broken clang-tidy that chokes on -pipe.')
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '70 clang-tidy')
|
|
|
|
self.init(testdir, override_envvars={'CXX': 'c++'})
|
|
|
|
out = self.run_target('clang-tidy')
|
|
|
|
self.assertIn('cttest.cpp:4:20', out)
|
|
|
|
|
|
|
|
def test_identity_cross(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '71 cross')
|
|
|
|
# Do a build to generate a cross file where the host is this target
|
|
|
|
self.init(testdir, extra_args=['-Dgenerate=true'])
|
|
|
|
self.meson_cross_file = os.path.join(self.builddir, "crossfile")
|
|
|
|
self.assertTrue(os.path.exists(self.meson_cross_file))
|
|
|
|
# Now verify that this is detected as cross
|
|
|
|
self.new_builddir()
|
|
|
|
self.init(testdir)
|
|
|
|
|
|
|
|
def test_introspect_buildoptions_without_configured_build(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '59 introspect buildoptions')
|
|
|
|
testfile = os.path.join(testdir, 'meson.build')
|
|
|
|
res_nb = self.introspect_directory(testfile, ['--buildoptions'] + self.meson_args)
|
|
|
|
self.init(testdir, default_args=False)
|
|
|
|
res_wb = self.introspect('--buildoptions')
|
|
|
|
self.maxDiff = None
|
|
|
|
self.assertListEqual(res_nb, res_wb)
|
|
|
|
|
|
|
|
def test_meson_configure_from_source_does_not_crash(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '59 introspect buildoptions')
|
|
|
|
self._run(self.mconf_command + [testdir])
|
|
|
|
|
|
|
|
def test_introspect_json_dump(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '57 introspection')
|
|
|
|
self.init(testdir)
|
|
|
|
infodir = os.path.join(self.builddir, 'meson-info')
|
|
|
|
self.assertPathExists(infodir)
|
|
|
|
|
|
|
|
def assertKeyTypes(key_type_list, obj):
|
|
|
|
for i in key_type_list:
|
|
|
|
self.assertIn(i[0], obj)
|
|
|
|
self.assertIsInstance(obj[i[0]], i[1])
|
|
|
|
|
|
|
|
root_keylist = [
|
|
|
|
('benchmarks', list),
|
|
|
|
('buildoptions', list),
|
|
|
|
('buildsystem_files', list),
|
|
|
|
('dependencies', list),
|
|
|
|
('installed', dict),
|
|
|
|
('projectinfo', dict),
|
|
|
|
('targets', list),
|
|
|
|
('tests', list),
|
|
|
|
]
|
|
|
|
|
|
|
|
test_keylist = [
|
|
|
|
('cmd', list),
|
|
|
|
('env', dict),
|
|
|
|
('name', str),
|
|
|
|
('timeout', int),
|
|
|
|
('suite', list),
|
|
|
|
('is_parallel', bool),
|
|
|
|
('protocol', str),
|
|
|
|
]
|
|
|
|
|
|
|
|
buildoptions_keylist = [
|
|
|
|
('name', str),
|
|
|
|
('section', str),
|
|
|
|
('type', str),
|
|
|
|
('description', str),
|
|
|
|
('machine', str),
|
|
|
|
]
|
|
|
|
|
|
|
|
buildoptions_typelist = [
|
|
|
|
('combo', str, [('choices', list)]),
|
|
|
|
('string', str, []),
|
|
|
|
('boolean', bool, []),
|
|
|
|
('integer', int, []),
|
|
|
|
('array', list, []),
|
|
|
|
]
|
|
|
|
|
|
|
|
buildoptions_sections = ['core', 'backend', 'base', 'compiler', 'directory', 'user', 'test']
|
|
|
|
buildoptions_machines = ['any', 'build', 'host']
|
|
|
|
|
|
|
|
dependencies_typelist = [
|
|
|
|
('name', str),
|
|
|
|
('version', str),
|
|
|
|
('compile_args', list),
|
|
|
|
('link_args', list),
|
|
|
|
]
|
|
|
|
|
|
|
|
targets_typelist = [
|
|
|
|
('name', str),
|
|
|
|
('id', str),
|
|
|
|
('type', str),
|
|
|
|
('defined_in', str),
|
|
|
|
('filename', list),
|
|
|
|
('build_by_default', bool),
|
|
|
|
('target_sources', list),
|
|
|
|
('installed', bool),
|
|
|
|
]
|
|
|
|
|
|
|
|
targets_sources_typelist = [
|
|
|
|
('language', str),
|
|
|
|
('compiler', list),
|
|
|
|
('parameters', list),
|
|
|
|
('sources', list),
|
|
|
|
('generated_sources', list),
|
|
|
|
]
|
|
|
|
|
|
|
|
# First load all files
|
|
|
|
res = {}
|
|
|
|
for i in root_keylist:
|
|
|
|
curr = os.path.join(infodir, 'intro-{}.json'.format(i[0]))
|
|
|
|
self.assertPathExists(curr)
|
|
|
|
with open(curr, 'r') as fp:
|
|
|
|
res[i[0]] = json.load(fp)
|
|
|
|
|
|
|
|
assertKeyTypes(root_keylist, res)
|
|
|
|
|
|
|
|
# Check Tests and benchmarks
|
|
|
|
tests_to_find = ['test case 1', 'test case 2', 'benchmark 1']
|
|
|
|
for i in res['benchmarks'] + res['tests']:
|
|
|
|
assertKeyTypes(test_keylist, i)
|
|
|
|
if i['name'] in tests_to_find:
|
|
|
|
tests_to_find.remove(i['name'])
|
|
|
|
self.assertListEqual(tests_to_find, [])
|
|
|
|
|
|
|
|
# Check buildoptions
|
|
|
|
buildopts_to_find = {'cpp_std': 'c++11'}
|
|
|
|
for i in res['buildoptions']:
|
|
|
|
assertKeyTypes(buildoptions_keylist, i)
|
|
|
|
valid_type = False
|
|
|
|
for j in buildoptions_typelist:
|
|
|
|
if i['type'] == j[0]:
|
|
|
|
self.assertIsInstance(i['value'], j[1])
|
|
|
|
assertKeyTypes(j[2], i)
|
|
|
|
valid_type = True
|
|
|
|
break
|
|
|
|
|
|
|
|
self.assertIn(i['section'], buildoptions_sections)
|
|
|
|
self.assertIn(i['machine'], buildoptions_machines)
|
|
|
|
self.assertTrue(valid_type)
|
|
|
|
if i['name'] in buildopts_to_find:
|
|
|
|
self.assertEqual(i['value'], buildopts_to_find[i['name']])
|
|
|
|
buildopts_to_find.pop(i['name'], None)
|
|
|
|
self.assertDictEqual(buildopts_to_find, {})
|
|
|
|
|
|
|
|
# Check buildsystem_files
|
|
|
|
bs_files = ['meson.build', 'meson_options.txt', 'sharedlib/meson.build', 'staticlib/meson.build']
|
|
|
|
bs_files = [os.path.join(testdir, x) for x in bs_files]
|
|
|
|
self.assertPathListEqual(list(sorted(res['buildsystem_files'])), list(sorted(bs_files)))
|
|
|
|
|
|
|
|
# Check dependencies
|
|
|
|
dependencies_to_find = ['threads']
|
|
|
|
for i in res['dependencies']:
|
|
|
|
assertKeyTypes(dependencies_typelist, i)
|
|
|
|
if i['name'] in dependencies_to_find:
|
|
|
|
dependencies_to_find.remove(i['name'])
|
|
|
|
self.assertListEqual(dependencies_to_find, [])
|
|
|
|
|
|
|
|
# Check projectinfo
|
|
|
|
self.assertDictEqual(res['projectinfo'], {'version': '1.2.3', 'descriptive_name': 'introspection', 'subproject_dir': 'subprojects', 'subprojects': []})
|
|
|
|
|
|
|
|
# Check targets
|
|
|
|
targets_to_find = {
|
|
|
|
'sharedTestLib': ('shared library', True, False, 'sharedlib/meson.build'),
|
|
|
|
'staticTestLib': ('static library', True, False, 'staticlib/meson.build'),
|
|
|
|
'test1': ('executable', True, True, 'meson.build'),
|
|
|
|
'test2': ('executable', True, False, 'meson.build'),
|
|
|
|
'test3': ('executable', True, False, 'meson.build'),
|
|
|
|
}
|
|
|
|
for i in res['targets']:
|
|
|
|
assertKeyTypes(targets_typelist, i)
|
|
|
|
if i['name'] in targets_to_find:
|
|
|
|
tgt = targets_to_find[i['name']]
|
|
|
|
self.assertEqual(i['type'], tgt[0])
|
|
|
|
self.assertEqual(i['build_by_default'], tgt[1])
|
|
|
|
self.assertEqual(i['installed'], tgt[2])
|
|
|
|
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.assertDictEqual(targets_to_find, {})
|
|
|
|
|
|
|
|
def test_introspect_file_dump_equals_all(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '57 introspection')
|
|
|
|
self.init(testdir)
|
|
|
|
res_all = self.introspect('--all')
|
|
|
|
res_file = {}
|
|
|
|
|
|
|
|
root_keylist = [
|
|
|
|
'benchmarks',
|
|
|
|
'buildoptions',
|
|
|
|
'buildsystem_files',
|
|
|
|
'dependencies',
|
|
|
|
'installed',
|
|
|
|
'projectinfo',
|
|
|
|
'targets',
|
|
|
|
'tests',
|
|
|
|
]
|
|
|
|
|
|
|
|
infodir = os.path.join(self.builddir, 'meson-info')
|
|
|
|
self.assertPathExists(infodir)
|
|
|
|
for i in root_keylist:
|
|
|
|
curr = os.path.join(infodir, 'intro-{}.json'.format(i))
|
|
|
|
self.assertPathExists(curr)
|
|
|
|
with open(curr, 'r') as fp:
|
|
|
|
res_file[i] = json.load(fp)
|
|
|
|
|
|
|
|
self.assertEqual(res_all, res_file)
|
|
|
|
|
|
|
|
def test_introspect_meson_info(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '57 introspection')
|
|
|
|
introfile = os.path.join(self.builddir, 'meson-info', 'meson-info.json')
|
|
|
|
self.init(testdir)
|
|
|
|
self.assertPathExists(introfile)
|
|
|
|
with open(introfile, 'r') as fp:
|
|
|
|
res1 = json.load(fp)
|
|
|
|
|
|
|
|
for i in ['meson_version', 'directories', 'introspection', 'build_files_updated', 'error']:
|
|
|
|
self.assertIn(i, res1)
|
|
|
|
|
|
|
|
self.assertEqual(res1['error'], False)
|
|
|
|
self.assertEqual(res1['build_files_updated'], True)
|
|
|
|
|
|
|
|
def test_introspect_config_update(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '57 introspection')
|
|
|
|
introfile = os.path.join(self.builddir, 'meson-info', 'intro-buildoptions.json')
|
|
|
|
self.init(testdir)
|
|
|
|
self.assertPathExists(introfile)
|
|
|
|
with open(introfile, 'r') as fp:
|
|
|
|
res1 = json.load(fp)
|
|
|
|
|
|
|
|
self.setconf('-Dcpp_std=c++14')
|
|
|
|
self.setconf('-Dbuildtype=release')
|
|
|
|
|
|
|
|
for idx, i in enumerate(res1):
|
|
|
|
if i['name'] == 'cpp_std':
|
|
|
|
res1[idx]['value'] = 'c++14'
|
|
|
|
if i['name'] == 'build.cpp_std':
|
|
|
|
res1[idx]['value'] = 'c++14'
|
|
|
|
if i['name'] == 'buildtype':
|
|
|
|
res1[idx]['value'] = 'release'
|
|
|
|
if i['name'] == 'optimization':
|
|
|
|
res1[idx]['value'] = '3'
|
|
|
|
if i['name'] == 'debug':
|
|
|
|
res1[idx]['value'] = False
|
|
|
|
|
|
|
|
with open(introfile, 'r') as fp:
|
|
|
|
res2 = json.load(fp)
|
|
|
|
|
|
|
|
self.assertListEqual(res1, res2)
|
|
|
|
|
|
|
|
def test_introspect_targets_from_source(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '57 introspection')
|
|
|
|
testfile = os.path.join(testdir, 'meson.build')
|
|
|
|
introfile = os.path.join(self.builddir, 'meson-info', 'intro-targets.json')
|
|
|
|
self.init(testdir)
|
|
|
|
self.assertPathExists(introfile)
|
|
|
|
with open(introfile, 'r') as fp:
|
|
|
|
res_wb = json.load(fp)
|
|
|
|
|
|
|
|
res_nb = self.introspect_directory(testfile, ['--targets'] + self.meson_args)
|
|
|
|
|
|
|
|
# Account for differences in output
|
|
|
|
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']
|
|
|
|
|
|
|
|
sources = []
|
|
|
|
for j in i['target_sources']:
|
|
|
|
sources += j['sources']
|
|
|
|
i['target_sources'] = [{
|
|
|
|
'language': 'unknown',
|
|
|
|
'compiler': [],
|
|
|
|
'parameters': [],
|
|
|
|
'sources': sources,
|
|
|
|
'generated_sources': []
|
|
|
|
}]
|
|
|
|
|
|
|
|
self.maxDiff = None
|
|
|
|
self.assertListEqual(res_nb, res_wb)
|
|
|
|
|
|
|
|
def test_introspect_dependencies_from_source(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '57 introspection')
|
|
|
|
testfile = os.path.join(testdir, 'meson.build')
|
|
|
|
res_nb = self.introspect_directory(testfile, ['--scan-dependencies'] + self.meson_args)
|
|
|
|
expected = [
|
|
|
|
{
|
|
|
|
'name': 'threads',
|
|
|
|
'required': True,
|
|
|
|
'version': [],
|
|
|
|
'has_fallback': False,
|
|
|
|
'conditional': False
|
|
|
|
},
|
|
|
|
{
|
|
|
|
'name': 'zlib',
|
|
|
|
'required': False,
|
|
|
|
'version': [],
|
|
|
|
'has_fallback': False,
|
|
|
|
'conditional': False
|
|
|
|
},
|
|
|
|
{
|
|
|
|
'name': 'bugDep1',
|
|
|
|
'required': True,
|
|
|
|
'version': [],
|
|
|
|
'has_fallback': False,
|
|
|
|
'conditional': False
|
|
|
|
},
|
|
|
|
{
|
|
|
|
'name': 'somethingthatdoesnotexist',
|
|
|
|
'required': True,
|
|
|
|
'version': ['>=1.2.3'],
|
|
|
|
'has_fallback': False,
|
|
|
|
'conditional': True
|
|
|
|
},
|
|
|
|
{
|
|
|
|
'name': 'look_i_have_a_fallback',
|
|
|
|
'required': True,
|
|
|
|
'version': ['>=1.0.0', '<=99.9.9'],
|
|
|
|
'has_fallback': True,
|
|
|
|
'conditional': True
|
|
|
|
}
|
|
|
|
]
|
|
|
|
self.maxDiff = None
|
|
|
|
self.assertListEqual(res_nb, expected)
|
|
|
|
|
|
|
|
def test_unstable_coredata(self):
|
|
|
|
testdir = os.path.join(self.common_test_dir, '1 trivial')
|
|
|
|
self.init(testdir)
|
|
|
|
# just test that the command does not fail (e.g. because it throws an exception)
|
|
|
|
self._run([*self.meson_command, 'unstable-coredata', self.builddir])
|
|
|
|
|
|
|
|
@skip_if_no_cmake
|
|
|
|
def test_cmake_prefix_path(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '64 cmake_prefix_path')
|
|
|
|
self.init(testdir, extra_args=['-Dcmake_prefix_path=' + os.path.join(testdir, 'prefix')])
|
|
|
|
|
|
|
|
@skip_if_no_cmake
|
|
|
|
def test_cmake_parser(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '65 cmake parser')
|
|
|
|
self.init(testdir, extra_args=['-Dcmake_prefix_path=' + os.path.join(testdir, 'prefix')])
|
|
|
|
|
|
|
|
def test_alias_target(self):
|
|
|
|
if self.backend is Backend.vs:
|
|
|
|
# FIXME: This unit test is broken with vs backend, needs investigation
|
|
|
|
raise unittest.SkipTest('Skipping alias_target test with {} backend'.format(self.backend.name))
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '66 alias target')
|
|
|
|
self.init(testdir)
|
|
|
|
self.build()
|
|
|
|
self.assertPathDoesNotExist(os.path.join(self.builddir, 'prog' + exe_suffix))
|
|
|
|
self.assertPathDoesNotExist(os.path.join(self.builddir, 'hello.txt'))
|
|
|
|
self.run_target('build-all')
|
|
|
|
self.assertPathExists(os.path.join(self.builddir, 'prog' + exe_suffix))
|
|
|
|
self.assertPathExists(os.path.join(self.builddir, 'hello.txt'))
|
|
|
|
|
|
|
|
def test_configure(self):
|
|
|
|
testdir = os.path.join(self.common_test_dir, '2 cpp')
|
|
|
|
self.init(testdir)
|
|
|
|
self._run(self.mconf_command + [self.builddir])
|
|
|
|
|
|
|
|
# FIXME: The test is failing on Windows CI even if the print looks good.
|
|
|
|
# Maybe encoding issue?
|
|
|
|
@unittest.skipIf(is_windows(), 'This test fails on Windows CI')
|
|
|
|
def test_summary(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '72 summary')
|
|
|
|
out = self.init(testdir)
|
|
|
|
expected = textwrap.dedent(r'''
|
|
|
|
Some Subproject 2.0
|
|
|
|
|
|
|
|
string: bar
|
|
|
|
integer: 1
|
|
|
|
boolean: True
|
|
|
|
|
|
|
|
My Project 1.0
|
|
|
|
|
|
|
|
Configuration
|
|
|
|
Some boolean: False
|
|
|
|
Another boolean: True
|
|
|
|
Some string: Hello World
|
|
|
|
A list: string
|
|
|
|
1
|
|
|
|
True
|
|
|
|
A number: 1
|
|
|
|
yes: YES
|
|
|
|
no: NO
|
|
|
|
''')
|
|
|
|
# Dict ordering is not guaranteed and an exact string comparison randomly
|
|
|
|
# fails on the CI because lines are reordered.
|
|
|
|
expected_lines = expected.split('\n')[1:]
|
|
|
|
out_start = out.find(expected_lines[0])
|
|
|
|
out_lines = out[out_start:].split('\n')[:len(expected_lines)]
|
|
|
|
self.assertEqual(sorted(expected_lines), sorted(out_lines))
|
|
|
|
|
|
|
|
|
|
|
|
class FailureTests(BasePlatformTests):
|
|
|
|
'''
|
|
|
|
Tests that test failure conditions. Build files here should be dynamically
|
|
|
|
generated and static tests should go into `test cases/failing*`.
|
|
|
|
This is useful because there can be many ways in which a particular
|
|
|
|
function can fail, and creating failing tests for all of them is tedious
|
|
|
|
and slows down testing.
|
|
|
|
'''
|
|
|
|
dnf = "[Dd]ependency.*not found(:.*)?"
|
|
|
|
nopkg = '[Pp]kg-config.*not found'
|
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
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')
|
|
|
|
|
|
|
|
def tearDown(self):
|
|
|
|
super().tearDown()
|
|
|
|
windows_proof_rmtree(self.srcdir)
|
|
|
|
|
|
|
|
def assertMesonRaises(self, contents, match, *,
|
|
|
|
extra_args=None,
|
|
|
|
langs=None,
|
|
|
|
meson_version=None,
|
|
|
|
options=None,
|
|
|
|
override_envvars=None):
|
|
|
|
'''
|
|
|
|
Assert that running meson configure on the specified @contents raises
|
|
|
|
a error message matching regex @match.
|
|
|
|
'''
|
|
|
|
if langs is None:
|
|
|
|
langs = []
|
|
|
|
with open(self.mbuild, 'w') as f:
|
|
|
|
f.write("project('failure test', 'c', 'cpp'")
|
|
|
|
if meson_version:
|
|
|
|
f.write(", meson_version: '{}'".format(meson_version))
|
|
|
|
f.write(")\n")
|
|
|
|
for lang in langs:
|
|
|
|
f.write("add_languages('{}', required : false)\n".format(lang))
|
|
|
|
f.write(contents)
|
|
|
|
if options is not None:
|
|
|
|
with open(self.moptions, 'w') as f:
|
|
|
|
f.write(options)
|
|
|
|
o = {'MESON_FORCE_BACKTRACE': '1'}
|
|
|
|
if override_envvars is None:
|
|
|
|
override_envvars = o
|
|
|
|
else:
|
|
|
|
override_envvars.update(o)
|
|
|
|
# Force tracebacks so we can detect them properly
|
|
|
|
with self.assertRaisesRegex(MesonException, match, msg=contents):
|
|
|
|
# Must run in-process or we'll get a generic CalledProcessError
|
|
|
|
self.init(self.srcdir, extra_args=extra_args,
|
|
|
|
inprocess=True,
|
|
|
|
override_envvars = override_envvars)
|
|
|
|
|
|
|
|
def obtainMesonOutput(self, contents, match, extra_args, langs, meson_version=None):
|
|
|
|
if langs is None:
|
|
|
|
langs = []
|
|
|
|
with open(self.mbuild, 'w') as f:
|
|
|
|
f.write("project('output test', 'c', 'cpp'")
|
|
|
|
if meson_version:
|
|
|
|
f.write(", meson_version: '{}'".format(meson_version))
|
|
|
|
f.write(")\n")
|
|
|
|
for lang in langs:
|
|
|
|
f.write("add_languages('{}', required : false)\n".format(lang))
|
|
|
|
f.write(contents)
|
|
|
|
# Run in-process for speed and consistency with assertMesonRaises
|
|
|
|
return self.init(self.srcdir, extra_args=extra_args, inprocess=True)
|
|
|
|
|
|
|
|
def assertMesonOutputs(self, contents, match, extra_args=None, langs=None, meson_version=None):
|
|
|
|
'''
|
|
|
|
Assert that running meson configure on the specified @contents outputs
|
|
|
|
something that matches regex @match.
|
|
|
|
'''
|
|
|
|
out = self.obtainMesonOutput(contents, match, extra_args, langs, meson_version)
|
|
|
|
self.assertRegex(out, match)
|
|
|
|
|
|
|
|
def assertMesonDoesNotOutput(self, contents, match, extra_args=None, langs=None, meson_version=None):
|
|
|
|
'''
|
|
|
|
Assert that running meson configure on the specified @contents does not output
|
|
|
|
something that matches regex @match.
|
|
|
|
'''
|
|
|
|
out = self.obtainMesonOutput(contents, match, extra_args, langs, meson_version)
|
|
|
|
self.assertNotRegex(out, match)
|
|
|
|
|
|
|
|
@skipIfNoPkgconfig
|
|
|
|
def test_dependency(self):
|
|
|
|
if subprocess.call(['pkg-config', '--exists', 'zlib']) != 0:
|
|
|
|
raise unittest.SkipTest('zlib not found with pkg-config')
|
|
|
|
a = (("dependency('zlib', method : 'fail')", "'fail' is invalid"),
|
|
|
|
("dependency('zlib', static : '1')", "[Ss]tatic.*boolean"),
|
|
|
|
("dependency('zlib', version : 1)", "[Vv]ersion.*string or list"),
|
|
|
|
("dependency('zlib', required : 1)", "[Rr]equired.*boolean"),
|
|
|
|
("dependency('zlib', method : 1)", "[Mm]ethod.*string"),
|
|
|
|
("dependency('zlibfail')", self.dnf),)
|
|
|
|
for contents, match in a:
|
|
|
|
self.assertMesonRaises(contents, match)
|
|
|
|
|
|
|
|
def test_apple_frameworks_dependency(self):
|
|
|
|
if not is_osx():
|
|
|
|
raise unittest.SkipTest('only run on macOS')
|
|
|
|
self.assertMesonRaises("dependency('appleframeworks')",
|
|
|
|
"requires at least one module")
|
|
|
|
|
|
|
|
def test_extraframework_dependency_method(self):
|
|
|
|
code = "dependency('python', method : 'extraframework')"
|
|
|
|
if not is_osx():
|
|
|
|
self.assertMesonRaises(code, self.dnf)
|
|
|
|
else:
|
|
|
|
# Python2 framework is always available on macOS
|
|
|
|
self.assertMesonOutputs(code, '[Dd]ependency.*python.*found.*YES')
|
|
|
|
|
|
|
|
def test_sdl2_notfound_dependency(self):
|
|
|
|
# Want to test failure, so skip if available
|
|
|
|
if shutil.which('sdl2-config'):
|
|
|
|
raise unittest.SkipTest('sdl2-config found')
|
|
|
|
self.assertMesonRaises("dependency('sdl2', method : 'sdlconfig')", self.dnf)
|
|
|
|
if shutil.which('pkg-config'):
|
|
|
|
self.assertMesonRaises("dependency('sdl2', method : 'pkg-config')", self.dnf)
|
|
|
|
with no_pkgconfig():
|
|
|
|
# Look for pkg-config, cache it, then
|
|
|
|
# Use cached pkg-config without erroring out, then
|
|
|
|
# Use cached pkg-config to error out
|
|
|
|
code = "dependency('foobarrr', method : 'pkg-config', required : false)\n" \
|
|
|
|
"dependency('foobarrr2', method : 'pkg-config', required : false)\n" \
|
|
|
|
"dependency('sdl2', method : 'pkg-config')"
|
|
|
|
self.assertMesonRaises(code, self.nopkg)
|
|
|
|
|
|
|
|
def test_gnustep_notfound_dependency(self):
|
|
|
|
# Want to test failure, so skip if available
|
|
|
|
if shutil.which('gnustep-config'):
|
|
|
|
raise unittest.SkipTest('gnustep-config found')
|
|
|
|
self.assertMesonRaises("dependency('gnustep')",
|
|
|
|
"(requires a Objc compiler|{})".format(self.dnf),
|
|
|
|
langs = ['objc'])
|
|
|
|
|
|
|
|
def test_wx_notfound_dependency(self):
|
|
|
|
# Want to test failure, so skip if available
|
|
|
|
if shutil.which('wx-config-3.0') or shutil.which('wx-config') or shutil.which('wx-config-gtk3'):
|
|
|
|
raise unittest.SkipTest('wx-config, wx-config-3.0 or wx-config-gtk3 found')
|
|
|
|
self.assertMesonRaises("dependency('wxwidgets')", self.dnf)
|
|
|
|
self.assertMesonOutputs("dependency('wxwidgets', required : false)",
|
|
|
|
"Run-time dependency .*WxWidgets.* found: .*NO.*")
|
|
|
|
|
|
|
|
def test_wx_dependency(self):
|
|
|
|
if not shutil.which('wx-config-3.0') and not shutil.which('wx-config') and not shutil.which('wx-config-gtk3'):
|
|
|
|
raise unittest.SkipTest('Neither wx-config, wx-config-3.0 nor wx-config-gtk3 found')
|
|
|
|
self.assertMesonRaises("dependency('wxwidgets', modules : 1)",
|
|
|
|
"module argument is not a string")
|
|
|
|
|
|
|
|
def test_llvm_dependency(self):
|
|
|
|
self.assertMesonRaises("dependency('llvm', modules : 'fail')",
|
|
|
|
"(required.*fail|{})".format(self.dnf))
|
|
|
|
|
|
|
|
def test_boost_notfound_dependency(self):
|
|
|
|
# Can be run even if Boost is found or not
|
|
|
|
self.assertMesonRaises("dependency('boost', modules : 1)",
|
|
|
|
"module.*not a string")
|
|
|
|
self.assertMesonRaises("dependency('boost', modules : 'fail')",
|
|
|
|
"(fail.*not found|{})".format(self.dnf))
|
|
|
|
|
|
|
|
def test_boost_BOOST_ROOT_dependency(self):
|
|
|
|
# Test BOOST_ROOT; can be run even if Boost is found or not
|
|
|
|
self.assertMesonRaises("dependency('boost')",
|
|
|
|
"(BOOST_ROOT.*absolute|{})".format(self.dnf),
|
|
|
|
override_envvars = {'BOOST_ROOT': 'relative/path'})
|
|
|
|
|
|
|
|
def test_dependency_invalid_method(self):
|
|
|
|
code = '''zlib_dep = dependency('zlib', required : false)
|
|
|
|
zlib_dep.get_configtool_variable('foo')
|
|
|
|
'''
|
|
|
|
self.assertMesonRaises(code, ".* is not a config-tool dependency")
|
|
|
|
code = '''zlib_dep = dependency('zlib', required : false)
|
|
|
|
dep = declare_dependency(dependencies : zlib_dep)
|
|
|
|
dep.get_pkgconfig_variable('foo')
|
|
|
|
'''
|
|
|
|
self.assertMesonRaises(code, "Method.*pkgconfig.*is invalid.*internal")
|
|
|
|
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")
|
|
|
|
|
|
|
|
def test_objc_cpp_detection(self):
|
|
|
|
'''
|
|
|
|
Test that when we can't detect objc or objcpp, we fail gracefully.
|
|
|
|
'''
|
|
|
|
env = get_fake_env()
|
|
|
|
try:
|
|
|
|
env.detect_objc_compiler(MachineChoice.HOST)
|
|
|
|
env.detect_objcpp_compiler(MachineChoice.HOST)
|
|
|
|
except EnvironmentException:
|
|
|
|
code = "add_languages('objc')\nadd_languages('objcpp')"
|
|
|
|
self.assertMesonRaises(code, "Unknown compiler")
|
|
|
|
return
|
|
|
|
raise unittest.SkipTest("objc and objcpp found, can't test detection failure")
|
|
|
|
|
|
|
|
def test_subproject_variables(self):
|
|
|
|
'''
|
|
|
|
Test that:
|
|
|
|
1. The correct message is outputted when a not-required dep is not
|
|
|
|
found and the fallback subproject is also not found.
|
|
|
|
2. A not-required fallback dependency is not found because the
|
|
|
|
subproject failed to parse.
|
|
|
|
3. A not-found not-required dep with a fallback subproject outputs the
|
|
|
|
correct message when the fallback subproject is found but the
|
|
|
|
variable inside it is not.
|
|
|
|
4. A fallback dependency is found from the subproject parsed in (3)
|
|
|
|
5. The correct message is outputted when the .wrap file is missing for
|
|
|
|
a sub-subproject.
|
|
|
|
'''
|
|
|
|
tdir = os.path.join(self.unit_test_dir, '20 subproj dep variables')
|
|
|
|
out = self.init(tdir, inprocess=True)
|
|
|
|
self.assertRegex(out, r"Subproject directory not found and .*nosubproj.wrap.* file not found")
|
|
|
|
self.assertRegex(out, r'Function does not take positional arguments.')
|
|
|
|
self.assertRegex(out, r'WARNING:.* Dependency .*subsubproject.* not found but it is available in a sub-subproject.')
|
|
|
|
self.assertRegex(out, r'Subproject directory not found and .*subsubproject.wrap.* file not found')
|
|
|
|
self.assertRegex(out, r'Dependency .*zlibproxy.* from subproject .*subprojects.*somesubproj.* found: .*YES.*')
|
|
|
|
|
|
|
|
def test_exception_exit_status(self):
|
|
|
|
'''
|
|
|
|
Test exit status on python exception
|
|
|
|
'''
|
|
|
|
tdir = os.path.join(self.unit_test_dir, '21 exit status')
|
|
|
|
with self.assertRaises(subprocess.CalledProcessError) as cm:
|
|
|
|
self.init(tdir, inprocess=False, override_envvars = {'MESON_UNIT_TEST': '1'})
|
|
|
|
self.assertEqual(cm.exception.returncode, 2)
|
|
|
|
self.wipe()
|
|
|
|
|
|
|
|
def test_dict_requires_key_value_pairs(self):
|
|
|
|
self.assertMesonRaises("dict = {3, 'foo': 'bar'}",
|
|
|
|
'Only key:value pairs are valid in dict construction.')
|
|
|
|
self.assertMesonRaises("{'foo': 'bar', 3}",
|
|
|
|
'Only key:value pairs are valid in dict construction.')
|
|
|
|
|
|
|
|
def test_dict_forbids_duplicate_keys(self):
|
|
|
|
self.assertMesonRaises("dict = {'a': 41, 'a': 42}",
|
|
|
|
'Duplicate dictionary key: a.*')
|
|
|
|
|
|
|
|
def test_dict_forbids_integer_key(self):
|
|
|
|
self.assertMesonRaises("dict = {3: 'foo'}",
|
|
|
|
'Key must be a string.*')
|
|
|
|
|
|
|
|
def test_using_too_recent_feature(self):
|
|
|
|
# Here we use a dict, which was introduced in 0.47.0
|
|
|
|
self.assertMesonOutputs("dict = {}",
|
|
|
|
".*WARNING.*Project targeting.*but.*",
|
|
|
|
meson_version='>= 0.46.0')
|
|
|
|
|
|
|
|
def test_using_recent_feature(self):
|
|
|
|
# Same as above, except the meson version is now appropriate
|
|
|
|
self.assertMesonDoesNotOutput("dict = {}",
|
|
|
|
".*WARNING.*Project targeting.*but.*",
|
|
|
|
meson_version='>= 0.47')
|
|
|
|
|
|
|
|
def test_using_too_recent_feature_dependency(self):
|
|
|
|
self.assertMesonOutputs("dependency('pcap', required: false)",
|
|
|
|
".*WARNING.*Project targeting.*but.*",
|
|
|
|
meson_version='>= 0.41.0')
|
|
|
|
|
|
|
|
def test_vcs_tag_featurenew_build_always_stale(self):
|
|
|
|
'https://github.com/mesonbuild/meson/issues/3904'
|
|
|
|
vcs_tag = '''version_data = configuration_data()
|
|
|
|
version_data.set('PROJVER', '@VCS_TAG@')
|
|
|
|
vf = configure_file(output : 'version.h.in', configuration: version_data)
|
|
|
|
f = vcs_tag(input : vf, output : 'version.h')
|
|
|
|
'''
|
|
|
|
msg = '.*WARNING:.*feature.*build_always_stale.*custom_target.*'
|
|
|
|
self.assertMesonDoesNotOutput(vcs_tag, msg, meson_version='>=0.43')
|
|
|
|
|
|
|
|
def test_missing_subproject_not_required_and_required(self):
|
|
|
|
self.assertMesonRaises("sub1 = subproject('not-found-subproject', required: false)\n" +
|
|
|
|
"sub2 = subproject('not-found-subproject', required: true)",
|
|
|
|
""".*Subproject "subprojects/not-found-subproject" required but not found.*""")
|
|
|
|
|
|
|
|
def test_get_variable_on_not_found_project(self):
|
|
|
|
self.assertMesonRaises("sub1 = subproject('not-found-subproject', required: false)\n" +
|
|
|
|
"sub1.get_variable('naaa')",
|
|
|
|
"""Subproject "subprojects/not-found-subproject" disabled can't get_variable on it.""")
|
|
|
|
|
|
|
|
def test_version_checked_before_parsing_options(self):
|
|
|
|
'''
|
|
|
|
https://github.com/mesonbuild/meson/issues/5281
|
|
|
|
'''
|
|
|
|
options = "option('some-option', type: 'foo', value: '')"
|
|
|
|
match = 'Meson version is.*but project requires >=2000'
|
|
|
|
self.assertMesonRaises("", match, meson_version='>=2000', options=options)
|
|
|
|
|
|
|
|
def test_assert_default_message(self):
|
|
|
|
self.assertMesonRaises("k1 = 'a'\n" +
|
|
|
|
"assert({\n" +
|
|
|
|
" k1: 1,\n" +
|
|
|
|
"}['a'] == 2)\n",
|
|
|
|
r"Assert failed: {k1 : 1}\['a'\] == 2")
|
|
|
|
|
|
|
|
@unittest.skipUnless(is_windows() or is_cygwin(), "requires Windows (or Windows via Cygwin)")
|
|
|
|
class WindowsTests(BasePlatformTests):
|
|
|
|
'''
|
|
|
|
Tests that should run on Cygwin, MinGW, and MSVC
|
|
|
|
'''
|
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
super().setUp()
|
|
|
|
self.platform_test_dir = os.path.join(self.src_root, 'test cases/windows')
|
|
|
|
|
|
|
|
@unittest.skipIf(is_cygwin(), 'Test only applicable to Windows')
|
|
|
|
def test_find_program(self):
|
|
|
|
'''
|
|
|
|
Test that Windows-specific edge-cases in find_program are functioning
|
|
|
|
correctly. Cannot be an ordinary test because it involves manipulating
|
|
|
|
PATH to point to a directory with Python scripts.
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.platform_test_dir, '8 find program')
|
|
|
|
# Find `cmd` and `cmd.exe`
|
|
|
|
prog1 = ExternalProgram('cmd')
|
|
|
|
self.assertTrue(prog1.found(), msg='cmd not found')
|
|
|
|
prog2 = ExternalProgram('cmd.exe')
|
|
|
|
self.assertTrue(prog2.found(), msg='cmd.exe not found')
|
|
|
|
self.assertPathEqual(prog1.get_path(), prog2.get_path())
|
|
|
|
# Find cmd with an absolute path that's missing the extension
|
|
|
|
cmd_path = prog2.get_path()[:-4]
|
|
|
|
prog = ExternalProgram(cmd_path)
|
|
|
|
self.assertTrue(prog.found(), msg='{!r} not found'.format(cmd_path))
|
|
|
|
# Finding a script with no extension inside a directory works
|
|
|
|
prog = ExternalProgram(os.path.join(testdir, 'test-script'))
|
|
|
|
self.assertTrue(prog.found(), msg='test-script not found')
|
|
|
|
# Finding a script with an extension inside a directory works
|
|
|
|
prog = ExternalProgram(os.path.join(testdir, 'test-script-ext.py'))
|
|
|
|
self.assertTrue(prog.found(), msg='test-script-ext.py not found')
|
|
|
|
# Finding a script in PATH
|
|
|
|
os.environ['PATH'] += os.pathsep + testdir
|
|
|
|
# Finding a script in PATH w/o extension works and adds the interpreter
|
|
|
|
# (check only if `.PY` is in PATHEXT)
|
|
|
|
if '.PY' in [ext.upper() for ext in os.environ['PATHEXT'].split(';')]:
|
|
|
|
prog = ExternalProgram('test-script-ext')
|
|
|
|
self.assertTrue(prog.found(), msg='test-script-ext not found in PATH')
|
|
|
|
self.assertPathEqual(prog.get_command()[0], python_command[0])
|
|
|
|
self.assertPathBasenameEqual(prog.get_path(), 'test-script-ext.py')
|
|
|
|
# Finding a script in PATH with extension works and adds the interpreter
|
|
|
|
prog = ExternalProgram('test-script-ext.py')
|
|
|
|
self.assertTrue(prog.found(), msg='test-script-ext.py not found in PATH')
|
|
|
|
self.assertPathEqual(prog.get_command()[0], python_command[0])
|
|
|
|
self.assertPathBasenameEqual(prog.get_path(), 'test-script-ext.py')
|
|
|
|
# Ensure that WindowsApps gets removed from PATH
|
|
|
|
path = os.environ['PATH']
|
|
|
|
if 'WindowsApps' not in path:
|
|
|
|
username = os.environ['USERNAME']
|
|
|
|
appstore_dir = r'C:\Users\{}\AppData\Local\Microsoft\WindowsApps'.format(username)
|
|
|
|
path = os.pathsep + appstore_dir
|
|
|
|
path = ExternalProgram._windows_sanitize_path(path)
|
|
|
|
self.assertNotIn('WindowsApps', path)
|
|
|
|
|
|
|
|
def test_ignore_libs(self):
|
|
|
|
'''
|
|
|
|
Test that find_library on libs that are to be ignored returns an empty
|
|
|
|
array of arguments. Must be a unit test because we cannot inspect
|
|
|
|
ExternalLibraryHolder from build files.
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.platform_test_dir, '1 basic')
|
|
|
|
env = get_fake_env(testdir, self.builddir, self.prefix)
|
|
|
|
cc = env.detect_c_compiler(MachineChoice.HOST)
|
|
|
|
if cc.get_argument_syntax() != 'msvc':
|
|
|
|
raise unittest.SkipTest('Not using MSVC')
|
|
|
|
# To force people to update this test, and also test
|
|
|
|
self.assertEqual(set(cc.ignore_libs), {'c', 'm', 'pthread', 'dl', 'rt', 'execinfo'})
|
|
|
|
for l in cc.ignore_libs:
|
|
|
|
self.assertEqual(cc.find_library(l, env, []), [])
|
|
|
|
|
|
|
|
def test_rc_depends_files(self):
|
|
|
|
testdir = os.path.join(self.platform_test_dir, '5 resources')
|
|
|
|
|
|
|
|
# resource compiler depfile generation is not yet implemented for msvc
|
|
|
|
env = get_fake_env(testdir, self.builddir, self.prefix)
|
|
|
|
depfile_works = env.detect_c_compiler(MachineChoice.HOST).get_id() not in {'msvc', 'clang-cl', 'intel-cl'}
|
|
|
|
|
|
|
|
self.init(testdir)
|
|
|
|
self.build()
|
|
|
|
# Immediately rebuilding should not do anything
|
|
|
|
self.assertBuildIsNoop()
|
|
|
|
# Test compile_resources(depend_file:)
|
|
|
|
# Changing mtime of sample.ico should rebuild prog
|
|
|
|
self.utime(os.path.join(testdir, 'res', 'sample.ico'))
|
|
|
|
self.assertRebuiltTarget('prog')
|
|
|
|
# Test depfile generation by compile_resources
|
|
|
|
# Changing mtime of resource.h should rebuild myres.rc and then prog
|
|
|
|
if depfile_works:
|
|
|
|
self.utime(os.path.join(testdir, 'inc', 'resource', 'resource.h'))
|
|
|
|
self.assertRebuiltTarget('prog')
|
|
|
|
self.wipe()
|
|
|
|
|
|
|
|
if depfile_works:
|
|
|
|
testdir = os.path.join(self.platform_test_dir, '12 resources with custom targets')
|
|
|
|
self.init(testdir)
|
|
|
|
self.build()
|
|
|
|
# Immediately rebuilding should not do anything
|
|
|
|
self.assertBuildIsNoop()
|
|
|
|
# Changing mtime of resource.h should rebuild myres_1.rc and then prog_1
|
|
|
|
self.utime(os.path.join(testdir, 'res', 'resource.h'))
|
|
|
|
self.assertRebuiltTarget('prog_1')
|
|
|
|
|
|
|
|
def test_msvc_cpp17(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '45 vscpp17')
|
|
|
|
|
|
|
|
env = get_fake_env(testdir, self.builddir, self.prefix)
|
|
|
|
cc = env.detect_c_compiler(MachineChoice.HOST)
|
|
|
|
if cc.get_argument_syntax() != 'msvc':
|
|
|
|
raise unittest.SkipTest('Test only applies to MSVC-like compilers')
|
|
|
|
|
|
|
|
try:
|
|
|
|
self.init(testdir)
|
|
|
|
except subprocess.CalledProcessError:
|
|
|
|
# According to Python docs, output is only stored when
|
|
|
|
# using check_output. We don't use it, so we can't check
|
|
|
|
# that the output is correct (i.e. that it failed due
|
|
|
|
# to the right reason).
|
|
|
|
return
|
|
|
|
self.build()
|
|
|
|
|
|
|
|
def test_install_pdb_introspection(self):
|
|
|
|
testdir = os.path.join(self.platform_test_dir, '1 basic')
|
|
|
|
|
|
|
|
env = get_fake_env(testdir, self.builddir, self.prefix)
|
|
|
|
cc = env.detect_c_compiler(MachineChoice.HOST)
|
|
|
|
if cc.get_argument_syntax() != 'msvc':
|
|
|
|
raise unittest.SkipTest('Test only applies to MSVC-like compilers')
|
|
|
|
|
|
|
|
self.init(testdir)
|
|
|
|
installed = self.introspect('--installed')
|
|
|
|
files = [os.path.basename(path) for path in installed.values()]
|
|
|
|
|
|
|
|
self.assertTrue('prog.pdb' in files)
|
|
|
|
|
|
|
|
def _check_ld(self, name: str, lang: str, expected: str) -> None:
|
|
|
|
if not shutil.which(name):
|
|
|
|
raise unittest.SkipTest('Could not find {}.'.format(name))
|
|
|
|
with mock.patch.dict(os.environ, {'LD': name}):
|
|
|
|
env = get_fake_env()
|
|
|
|
try:
|
|
|
|
comp = getattr(env, 'detect_{}_compiler'.format(lang))(MachineChoice.HOST)
|
|
|
|
except EnvironmentException:
|
|
|
|
raise unittest.SkipTest('Could not find a compiler for {}'.format(lang))
|
|
|
|
self.assertEqual(comp.linker.id, expected)
|
|
|
|
|
|
|
|
def test_link_environment_variable_lld_link(self):
|
|
|
|
self._check_ld('lld-link', 'c', 'lld-link')
|
|
|
|
|
|
|
|
def test_link_environment_variable_link(self):
|
|
|
|
self._check_ld('link', 'c', 'link')
|
|
|
|
|
|
|
|
def test_link_environment_variable_optlink(self):
|
|
|
|
self._check_ld('optlink', 'c', 'optlink')
|
|
|
|
|
|
|
|
def test_link_environment_variable_rust(self):
|
|
|
|
self._check_ld('link', 'rust', 'link')
|
|
|
|
|
|
|
|
@unittest.skipUnless(is_osx(), "requires Darwin")
|
|
|
|
class DarwinTests(BasePlatformTests):
|
|
|
|
'''
|
|
|
|
Tests that should run on macOS
|
|
|
|
'''
|
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
super().setUp()
|
|
|
|
self.platform_test_dir = os.path.join(self.src_root, 'test cases/osx')
|
|
|
|
|
|
|
|
def test_apple_bitcode(self):
|
|
|
|
'''
|
|
|
|
Test that -fembed-bitcode is correctly added while compiling and
|
|
|
|
-bitcode_bundle is added while linking when b_bitcode is true and not
|
|
|
|
when it is false. This can't be an ordinary test case because we need
|
|
|
|
to inspect the compiler database.
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.platform_test_dir, '7 bitcode')
|
|
|
|
env = get_fake_env(testdir, self.builddir, self.prefix)
|
|
|
|
cc = env.detect_c_compiler(MachineChoice.HOST)
|
|
|
|
if cc.id != 'clang':
|
|
|
|
raise unittest.SkipTest('Not using Clang on OSX')
|
|
|
|
# Try with bitcode enabled
|
|
|
|
out = self.init(testdir, extra_args='-Db_bitcode=true')
|
|
|
|
# Warning was printed
|
|
|
|
self.assertRegex(out, 'WARNING:.*b_bitcode')
|
|
|
|
# Compiler options were added
|
|
|
|
for compdb in self.get_compdb():
|
|
|
|
if 'module' in compdb['file']:
|
|
|
|
self.assertNotIn('-fembed-bitcode', compdb['command'])
|
|
|
|
else:
|
|
|
|
self.assertIn('-fembed-bitcode', compdb['command'])
|
|
|
|
build_ninja = os.path.join(self.builddir, 'build.ninja')
|
|
|
|
# Linker options were added
|
|
|
|
with open(build_ninja, 'r', encoding='utf-8') as f:
|
|
|
|
contents = f.read()
|
|
|
|
m = re.search('LINK_ARGS =.*-bitcode_bundle', contents)
|
|
|
|
self.assertIsNotNone(m, msg=contents)
|
|
|
|
# Try with bitcode disabled
|
|
|
|
self.setconf('-Db_bitcode=false')
|
|
|
|
# Regenerate build
|
|
|
|
self.build()
|
|
|
|
for compdb in self.get_compdb():
|
|
|
|
self.assertNotIn('-fembed-bitcode', compdb['command'])
|
|
|
|
build_ninja = os.path.join(self.builddir, 'build.ninja')
|
|
|
|
with open(build_ninja, 'r', encoding='utf-8') as f:
|
|
|
|
contents = f.read()
|
|
|
|
m = re.search('LINK_ARGS =.*-bitcode_bundle', contents)
|
|
|
|
self.assertIsNone(m, msg=contents)
|
|
|
|
|
|
|
|
def test_apple_bitcode_modules(self):
|
|
|
|
'''
|
|
|
|
Same as above, just for shared_module()
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.common_test_dir, '152 shared module resolving symbol in executable')
|
|
|
|
# Ensure that it builds even with bitcode enabled
|
|
|
|
self.init(testdir, extra_args='-Db_bitcode=true')
|
|
|
|
self.build()
|
|
|
|
self.run_tests()
|
|
|
|
|
|
|
|
def _get_darwin_versions(self, fname):
|
|
|
|
fname = os.path.join(self.builddir, fname)
|
|
|
|
out = subprocess.check_output(['otool', '-L', fname], universal_newlines=True)
|
|
|
|
m = re.match(r'.*version (.*), current version (.*)\)', out.split('\n')[1])
|
|
|
|
self.assertIsNotNone(m, msg=out)
|
|
|
|
return m.groups()
|
|
|
|
|
|
|
|
@skipIfNoPkgconfig
|
|
|
|
def test_library_versioning(self):
|
|
|
|
'''
|
|
|
|
Ensure that compatibility_version and current_version are set correctly
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.platform_test_dir, '2 library versions')
|
|
|
|
self.init(testdir)
|
|
|
|
self.build()
|
|
|
|
targets = {}
|
|
|
|
for t in self.introspect('--targets'):
|
|
|
|
targets[t['name']] = t['filename'][0] if isinstance(t['filename'], list) else t['filename']
|
|
|
|
self.assertEqual(self._get_darwin_versions(targets['some']), ('7.0.0', '7.0.0'))
|
|
|
|
self.assertEqual(self._get_darwin_versions(targets['noversion']), ('0.0.0', '0.0.0'))
|
|
|
|
self.assertEqual(self._get_darwin_versions(targets['onlyversion']), ('1.0.0', '1.0.0'))
|
|
|
|
self.assertEqual(self._get_darwin_versions(targets['onlysoversion']), ('5.0.0', '5.0.0'))
|
|
|
|
self.assertEqual(self._get_darwin_versions(targets['intver']), ('2.0.0', '2.0.0'))
|
|
|
|
self.assertEqual(self._get_darwin_versions(targets['stringver']), ('2.3.0', '2.3.0'))
|
|
|
|
self.assertEqual(self._get_darwin_versions(targets['stringlistver']), ('2.4.0', '2.4.0'))
|
|
|
|
self.assertEqual(self._get_darwin_versions(targets['intstringver']), ('1111.0.0', '2.5.0'))
|
|
|
|
self.assertEqual(self._get_darwin_versions(targets['stringlistvers']), ('2.6.0', '2.6.1'))
|
|
|
|
|
|
|
|
def test_duplicate_rpath(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '10 build_rpath')
|
|
|
|
# We purposely pass a duplicate rpath to Meson, in order
|
|
|
|
# to ascertain that Meson does not call install_name_tool
|
|
|
|
# with duplicate -delete_rpath arguments, which would
|
|
|
|
# lead to erroring out on installation
|
|
|
|
env = {"LDFLAGS": "-Wl,-rpath,/foo/bar"}
|
|
|
|
self.init(testdir, override_envvars=env)
|
|
|
|
self.build()
|
|
|
|
self.install()
|
|
|
|
|
|
|
|
|
|
|
|
@unittest.skipUnless(not is_windows(), "requires something Unix-like")
|
|
|
|
class LinuxlikeTests(BasePlatformTests):
|
|
|
|
'''
|
|
|
|
Tests that should run on Linux, macOS, and *BSD
|
|
|
|
'''
|
|
|
|
|
|
|
|
def test_basic_soname(self):
|
|
|
|
'''
|
|
|
|
Test that the soname is set correctly for shared libraries. This can't
|
|
|
|
be an ordinary test case because we need to run `readelf` and actually
|
|
|
|
check the soname.
|
|
|
|
https://github.com/mesonbuild/meson/issues/785
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.common_test_dir, '4 shared')
|
|
|
|
self.init(testdir)
|
|
|
|
self.build()
|
|
|
|
lib1 = os.path.join(self.builddir, 'libmylib.so')
|
|
|
|
soname = get_soname(lib1)
|
|
|
|
self.assertEqual(soname, 'libmylib.so')
|
|
|
|
|
|
|
|
def test_custom_soname(self):
|
|
|
|
'''
|
|
|
|
Test that the soname is set correctly for shared libraries when
|
|
|
|
a custom prefix and/or suffix is used. This can't be an ordinary test
|
|
|
|
case because we need to run `readelf` and actually check the soname.
|
|
|
|
https://github.com/mesonbuild/meson/issues/785
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.common_test_dir, '25 library versions')
|
|
|
|
self.init(testdir)
|
|
|
|
self.build()
|
|
|
|
lib1 = os.path.join(self.builddir, 'prefixsomelib.suffix')
|
|
|
|
soname = get_soname(lib1)
|
|
|
|
self.assertEqual(soname, 'prefixsomelib.suffix')
|
|
|
|
|
|
|
|
def test_pic(self):
|
|
|
|
'''
|
|
|
|
Test that -fPIC is correctly added to static libraries when b_staticpic
|
|
|
|
is true and not when it is false. This can't be an ordinary test case
|
|
|
|
because we need to inspect the compiler database.
|
|
|
|
'''
|
|
|
|
if is_windows() or is_cygwin() or is_osx():
|
|
|
|
raise unittest.SkipTest('PIC not relevant')
|
|
|
|
|
|
|
|
testdir = os.path.join(self.common_test_dir, '3 static')
|
|
|
|
self.init(testdir)
|
|
|
|
compdb = self.get_compdb()
|
|
|
|
self.assertIn('-fPIC', compdb[0]['command'])
|
|
|
|
self.setconf('-Db_staticpic=false')
|
|
|
|
# Regenerate build
|
|
|
|
self.build()
|
|
|
|
compdb = self.get_compdb()
|
|
|
|
self.assertNotIn('-fPIC', compdb[0]['command'])
|
|
|
|
|
|
|
|
def test_pkgconfig_gen(self):
|
|
|
|
'''
|
|
|
|
Test that generated pkg-config files can be found and have the correct
|
|
|
|
version and link args. This can't be an ordinary test case because we
|
|
|
|
need to run pkg-config outside of a Meson build file.
|
|
|
|
https://github.com/mesonbuild/meson/issues/889
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.common_test_dir, '47 pkgconfig-gen')
|
|
|
|
self.init(testdir)
|
|
|
|
env = get_fake_env(testdir, self.builddir, self.prefix)
|
|
|
|
kwargs = {'required': True, 'silent': True}
|
|
|
|
os.environ['PKG_CONFIG_LIBDIR'] = self.privatedir
|
|
|
|
foo_dep = PkgConfigDependency('libfoo', env, kwargs)
|
|
|
|
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', {}), 'bar')
|
|
|
|
self.assertPathEqual(foo_dep.get_pkgconfig_variable('datadir', {}), '/usr/data')
|
|
|
|
|
|
|
|
def test_pkgconfig_gen_deps(self):
|
|
|
|
'''
|
|
|
|
Test that generated pkg-config files correctly handle dependencies
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.common_test_dir, '47 pkgconfig-gen')
|
|
|
|
self.init(testdir)
|
|
|
|
privatedir1 = self.privatedir
|
|
|
|
|
|
|
|
self.new_builddir()
|
|
|
|
testdir = os.path.join(self.common_test_dir, '47 pkgconfig-gen', 'dependencies')
|
|
|
|
self.init(testdir, override_envvars={'PKG_CONFIG_LIBDIR': privatedir1})
|
|
|
|
privatedir2 = self.privatedir
|
|
|
|
|
|
|
|
os.environ
|
|
|
|
env = {'PKG_CONFIG_LIBDIR': os.pathsep.join([privatedir1, privatedir2])}
|
|
|
|
self._run(['pkg-config', 'dependency-test', '--validate'], override_envvars=env)
|
|
|
|
|
|
|
|
# pkg-config strips some duplicated flags so we have to parse the
|
|
|
|
# generated file ourself.
|
|
|
|
expected = {
|
|
|
|
'Requires': 'libexposed',
|
|
|
|
'Requires.private': 'libfoo >= 1.0',
|
|
|
|
'Libs': '-L${libdir} -llibmain -pthread -lcustom',
|
|
|
|
'Libs.private': '-lcustom2 -L${libdir} -llibinternal',
|
|
|
|
'Cflags': '-I${includedir} -pthread -DCUSTOM',
|
|
|
|
}
|
|
|
|
if is_osx() or is_haiku():
|
|
|
|
expected['Cflags'] = expected['Cflags'].replace('-pthread ', '')
|
|
|
|
with open(os.path.join(privatedir2, 'dependency-test.pc')) as f:
|
|
|
|
matched_lines = 0
|
|
|
|
for line in f:
|
|
|
|
parts = line.split(':', 1)
|
|
|
|
if parts[0] in expected:
|
|
|
|
key = parts[0]
|
|
|
|
val = parts[1].strip()
|
|
|
|
expected_val = expected[key]
|
|
|
|
self.assertEqual(expected_val, val)
|
|
|
|
matched_lines += 1
|
|
|
|
self.assertEqual(len(expected), matched_lines)
|
|
|
|
|
|
|
|
cmd = ['pkg-config', 'requires-test']
|
|
|
|
out = self._run(cmd + ['--print-requires'], override_envvars=env).strip().split('\n')
|
|
|
|
if not is_openbsd():
|
|
|
|
self.assertEqual(sorted(out), sorted(['libexposed', 'libfoo >= 1.0', 'libhello']))
|
|
|
|
else:
|
|
|
|
self.assertEqual(sorted(out), sorted(['libexposed', 'libfoo>=1.0', 'libhello']))
|
|
|
|
|
|
|
|
cmd = ['pkg-config', 'requires-private-test']
|
|
|
|
out = self._run(cmd + ['--print-requires-private'], override_envvars=env).strip().split('\n')
|
|
|
|
if not is_openbsd():
|
|
|
|
self.assertEqual(sorted(out), sorted(['libexposed', 'libfoo >= 1.0', 'libhello']))
|
|
|
|
else:
|
|
|
|
self.assertEqual(sorted(out), sorted(['libexposed', 'libfoo>=1.0', 'libhello']))
|
|
|
|
|
|
|
|
cmd = ['pkg-config', 'pub-lib-order']
|
|
|
|
out = self._run(cmd + ['--libs'], override_envvars=env).strip().split()
|
|
|
|
self.assertEqual(out, ['-llibmain2', '-llibinternal'])
|
|
|
|
|
|
|
|
def test_pkg_unfound(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '23 unfound pkgconfig')
|
|
|
|
self.init(testdir)
|
|
|
|
with open(os.path.join(self.privatedir, 'somename.pc')) as f:
|
|
|
|
pcfile = f.read()
|
|
|
|
self.assertFalse('blub_blob_blib' in pcfile)
|
|
|
|
|
|
|
|
def test_vala_c_warnings(self):
|
|
|
|
'''
|
|
|
|
Test that no warnings are emitted for C code generated by Vala. This
|
|
|
|
can't be an ordinary test case because we need to inspect the compiler
|
|
|
|
database.
|
|
|
|
https://github.com/mesonbuild/meson/issues/864
|
|
|
|
'''
|
|
|
|
if not shutil.which('valac'):
|
|
|
|
raise unittest.SkipTest('valac not installed.')
|
|
|
|
testdir = os.path.join(self.vala_test_dir, '5 target glib')
|
|
|
|
self.init(testdir)
|
|
|
|
compdb = self.get_compdb()
|
|
|
|
vala_command = None
|
|
|
|
c_command = None
|
|
|
|
for each in compdb:
|
|
|
|
if each['file'].endswith('GLib.Thread.c'):
|
|
|
|
vala_command = each['command']
|
|
|
|
elif each['file'].endswith('GLib.Thread.vala'):
|
|
|
|
continue
|
|
|
|
elif each['file'].endswith('retcode.c'):
|
|
|
|
c_command = each['command']
|
|
|
|
else:
|
|
|
|
m = 'Unknown file {!r} in vala_c_warnings test'.format(each['file'])
|
|
|
|
raise AssertionError(m)
|
|
|
|
self.assertIsNotNone(vala_command)
|
|
|
|
self.assertIsNotNone(c_command)
|
|
|
|
# -w suppresses all warnings, should be there in Vala but not in C
|
|
|
|
self.assertIn(" -w ", vala_command)
|
|
|
|
self.assertNotIn(" -w ", c_command)
|
|
|
|
# -Wall enables all warnings, should be there in C but not in Vala
|
|
|
|
self.assertNotIn(" -Wall ", vala_command)
|
|
|
|
self.assertIn(" -Wall ", c_command)
|
|
|
|
# -Werror converts warnings to errors, should always be there since it's
|
|
|
|
# injected by an unrelated piece of code and the project has werror=true
|
|
|
|
self.assertIn(" -Werror ", vala_command)
|
|
|
|
self.assertIn(" -Werror ", c_command)
|
|
|
|
|
|
|
|
@skipIfNoPkgconfig
|
|
|
|
def test_qtdependency_pkgconfig_detection(self):
|
|
|
|
'''
|
|
|
|
Test that qt4 and qt5 detection with pkgconfig works.
|
|
|
|
'''
|
|
|
|
# Verify Qt4 or Qt5 can be found with pkg-config
|
|
|
|
qt4 = subprocess.call(['pkg-config', '--exists', 'QtCore'])
|
|
|
|
qt5 = subprocess.call(['pkg-config', '--exists', 'Qt5Core'])
|
|
|
|
testdir = os.path.join(self.framework_test_dir, '4 qt')
|
|
|
|
self.init(testdir, extra_args=['-Dmethod=pkg-config'])
|
|
|
|
# Confirm that the dependency was found with pkg-config
|
|
|
|
mesonlog = self.get_meson_log()
|
|
|
|
if qt4 == 0:
|
|
|
|
self.assertRegex('\n'.join(mesonlog),
|
|
|
|
r'Run-time dependency qt4 \(modules: Core\) found: YES 4.* \(pkg-config\)\n')
|
|
|
|
if qt5 == 0:
|
|
|
|
self.assertRegex('\n'.join(mesonlog),
|
|
|
|
r'Run-time dependency qt5 \(modules: Core\) found: YES 5.* \(pkg-config\)\n')
|
|
|
|
|
|
|
|
@skip_if_not_base_option('b_sanitize')
|
|
|
|
def test_generate_gir_with_address_sanitizer(self):
|
|
|
|
if is_cygwin():
|
|
|
|
raise unittest.SkipTest('asan not available on Cygwin')
|
|
|
|
if is_openbsd():
|
|
|
|
raise unittest.SkipTest('-fsanitize=address is not supported on OpenBSD')
|
|
|
|
|
|
|
|
testdir = os.path.join(self.framework_test_dir, '7 gnome')
|
|
|
|
self.init(testdir, extra_args=['-Db_sanitize=address', '-Db_lundef=false'])
|
|
|
|
self.build()
|
|
|
|
|
|
|
|
def test_qt5dependency_qmake_detection(self):
|
|
|
|
'''
|
|
|
|
Test that qt5 detection with qmake works. This can't be an ordinary
|
|
|
|
test case because it involves setting the environment.
|
|
|
|
'''
|
|
|
|
# Verify that qmake is for Qt5
|
|
|
|
if not shutil.which('qmake-qt5'):
|
|
|
|
if not shutil.which('qmake'):
|
|
|
|
raise unittest.SkipTest('QMake not found')
|
|
|
|
output = subprocess.getoutput('qmake --version')
|
|
|
|
if 'Qt version 5' not in output:
|
|
|
|
raise unittest.SkipTest('Qmake found, but it is not for Qt 5.')
|
|
|
|
# Disable pkg-config codepath and force searching with qmake/qmake-qt5
|
|
|
|
testdir = os.path.join(self.framework_test_dir, '4 qt')
|
|
|
|
self.init(testdir, extra_args=['-Dmethod=qmake'])
|
|
|
|
# Confirm that the dependency was found with qmake
|
|
|
|
mesonlog = self.get_meson_log()
|
|
|
|
self.assertRegex('\n'.join(mesonlog),
|
|
|
|
r'Run-time dependency qt5 \(modules: Core\) found: YES .* \((qmake|qmake-qt5)\)\n')
|
|
|
|
|
|
|
|
def _test_soname_impl(self, libpath, install):
|
|
|
|
if is_cygwin() or is_osx():
|
|
|
|
raise unittest.SkipTest('Test only applicable to ELF and linuxlike sonames')
|
|
|
|
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '1 soname')
|
|
|
|
self.init(testdir)
|
|
|
|
self.build()
|
|
|
|
if install:
|
|
|
|
self.install()
|
|
|
|
|
|
|
|
# File without aliases set.
|
|
|
|
nover = os.path.join(libpath, 'libnover.so')
|
|
|
|
self.assertPathExists(nover)
|
|
|
|
self.assertFalse(os.path.islink(nover))
|
|
|
|
self.assertEqual(get_soname(nover), 'libnover.so')
|
|
|
|
self.assertEqual(len(glob(nover[:-3] + '*')), 1)
|
|
|
|
|
|
|
|
# File with version set
|
|
|
|
verset = os.path.join(libpath, 'libverset.so')
|
|
|
|
self.assertPathExists(verset + '.4.5.6')
|
|
|
|
self.assertEqual(os.readlink(verset), 'libverset.so.4')
|
|
|
|
self.assertEqual(get_soname(verset), 'libverset.so.4')
|
|
|
|
self.assertEqual(len(glob(verset[:-3] + '*')), 3)
|
|
|
|
|
|
|
|
# File with soversion set
|
|
|
|
soverset = os.path.join(libpath, 'libsoverset.so')
|
|
|
|
self.assertPathExists(soverset + '.1.2.3')
|
|
|
|
self.assertEqual(os.readlink(soverset), 'libsoverset.so.1.2.3')
|
|
|
|
self.assertEqual(get_soname(soverset), 'libsoverset.so.1.2.3')
|
|
|
|
self.assertEqual(len(glob(soverset[:-3] + '*')), 2)
|
|
|
|
|
|
|
|
# File with version and soversion set to same values
|
|
|
|
settosame = os.path.join(libpath, 'libsettosame.so')
|
|
|
|
self.assertPathExists(settosame + '.7.8.9')
|
|
|
|
self.assertEqual(os.readlink(settosame), 'libsettosame.so.7.8.9')
|
|
|
|
self.assertEqual(get_soname(settosame), 'libsettosame.so.7.8.9')
|
|
|
|
self.assertEqual(len(glob(settosame[:-3] + '*')), 2)
|
|
|
|
|
|
|
|
# File with version and soversion set to different values
|
|
|
|
bothset = os.path.join(libpath, 'libbothset.so')
|
|
|
|
self.assertPathExists(bothset + '.1.2.3')
|
|
|
|
self.assertEqual(os.readlink(bothset), 'libbothset.so.1.2.3')
|
|
|
|
self.assertEqual(os.readlink(bothset + '.1.2.3'), 'libbothset.so.4.5.6')
|
|
|
|
self.assertEqual(get_soname(bothset), 'libbothset.so.1.2.3')
|
|
|
|
self.assertEqual(len(glob(bothset[:-3] + '*')), 3)
|
|
|
|
|
|
|
|
def test_soname(self):
|
|
|
|
self._test_soname_impl(self.builddir, False)
|
|
|
|
|
|
|
|
def test_installed_soname(self):
|
macos: Rewrite install_name for dependent built libraries on install
On macOS, we set the install_name for built libraries to
@rpath/libfoo.dylib, and when linking to the library, we set the RPATH
to its path in the build directory. This allows all built binaries to
be run as-is from the build directory (uninstalled).
However, on install, we have to strip all the RPATHs because they
point to the build directory, and we change the install_name of all
built libraries to the absolute path to the library. This causes the
install name in binaries to be out of date.
We now change that install name to point to the absolute path to each
built library after installation.
Fixes https://github.com/mesonbuild/meson/issues/3038
Fixes https://github.com/mesonbuild/meson/issues/3077
With this, the default workflow on macOS matches what everyone seems
to do, including Autotools and CMake. The next step is providing a way
for build files to override the install_name that is used after
installation for use with, f.ex., private libraries when combined with
the install_rpath: kwarg on targets.
7 years ago
|
|
|
libdir = self.installdir + os.path.join(self.prefix, self.libdir)
|
|
|
|
self._test_soname_impl(libdir, True)
|
|
|
|
|
|
|
|
def test_compiler_check_flags_order(self):
|
|
|
|
'''
|
|
|
|
Test that compiler check flags override all other flags. This can't be
|
|
|
|
an ordinary test case because it needs the environment to be set.
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.common_test_dir, '39 has function')
|
|
|
|
env = get_fake_env(testdir, self.builddir, self.prefix)
|
|
|
|
cpp = env.detect_cpp_compiler(MachineChoice.HOST)
|
|
|
|
Oflag = '-O3'
|
|
|
|
OflagCPP = Oflag
|
|
|
|
if cpp.get_id() in ('clang', 'gcc'):
|
|
|
|
# prevent developers from adding "int main(int argc, char **argv)"
|
|
|
|
# to small Meson checks unless these parameters are actually used
|
|
|
|
OflagCPP += ' -Werror=unused-parameter'
|
|
|
|
env = {'CFLAGS': Oflag,
|
|
|
|
'CXXFLAGS': OflagCPP}
|
|
|
|
self.init(testdir, override_envvars=env)
|
|
|
|
cmds = self.get_meson_log_compiler_checks()
|
|
|
|
for cmd in cmds:
|
|
|
|
if cmd[0] == 'ccache':
|
|
|
|
cmd = cmd[1:]
|
|
|
|
# Verify that -I flags from the `args` kwarg are first
|
|
|
|
# This is set in the '39 has function' test case
|
|
|
|
self.assertEqual(cmd[1], '-I/tmp')
|
|
|
|
# Verify that -O3 set via the environment is overridden by -O0
|
|
|
|
Oargs = [arg for arg in cmd if arg.startswith('-O')]
|
|
|
|
self.assertEqual(Oargs, [Oflag, '-O0'])
|
|
|
|
|
|
|
|
def _test_stds_impl(self, testdir, compiler, p: str):
|
|
|
|
lang_std = p + '_std'
|
|
|
|
|
|
|
|
has_cpp17 = (compiler.get_id() not in {'clang', 'gcc'} or
|
|
|
|
compiler.get_id() == 'clang' and _clang_at_least(compiler, '>=5.0.0', '>=9.1') or
|
|
|
|
compiler.get_id() == 'gcc' and version_compare(compiler.version, '>=5.0.0'))
|
|
|
|
has_cpp2a_c17 = (compiler.get_id() not in {'clang', 'gcc'} or
|
|
|
|
compiler.get_id() == 'clang' and _clang_at_least(compiler, '>=6.0.0', '>=10.0') or
|
|
|
|
compiler.get_id() == 'gcc' and version_compare(compiler.version, '>=8.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'))
|
|
|
|
# Check that all the listed -std=xxx options for this compiler work just fine when used
|
|
|
|
# https://en.wikipedia.org/wiki/Xcode#Latest_versions
|
|
|
|
# https://www.gnu.org/software/gcc/projects/cxx-status.html
|
|
|
|
for v in compiler.get_options()[lang_std].choices:
|
|
|
|
# we do it like this to handle gnu++17,c++17 and gnu17,c17 cleanly
|
|
|
|
# thus, C++ first
|
|
|
|
if '++17' in v and not has_cpp17:
|
|
|
|
continue
|
|
|
|
elif '++2a' in v and not has_cpp2a_c17: # https://en.cppreference.com/w/cpp/compiler_support
|
|
|
|
continue
|
|
|
|
# now C
|
|
|
|
elif '17' in v and not has_cpp2a_c17:
|
|
|
|
continue
|
|
|
|
elif '18' in v and not has_c18:
|
|
|
|
continue
|
|
|
|
std_opt = '{}={}'.format(lang_std, v)
|
|
|
|
self.init(testdir, extra_args=['-D' + std_opt])
|
|
|
|
cmd = self.get_compdb()[0]['command']
|
|
|
|
# c++03 and gnu++03 are not understood by ICC, don't try to look for them
|
|
|
|
skiplist = frozenset([
|
|
|
|
('intel', 'c++03'),
|
|
|
|
('intel', 'gnu++03')])
|
|
|
|
if v != 'none' and not (compiler.get_id(), v) in skiplist:
|
|
|
|
cmd_std = " -std={} ".format(v)
|
|
|
|
self.assertIn(cmd_std, cmd)
|
|
|
|
try:
|
|
|
|
self.build()
|
|
|
|
except Exception:
|
|
|
|
print('{} was {!r}'.format(lang_std, v))
|
|
|
|
raise
|
|
|
|
self.wipe()
|
|
|
|
# Check that an invalid std option in CFLAGS/CPPFLAGS fails
|
|
|
|
# Needed because by default ICC ignores invalid options
|
|
|
|
cmd_std = '-std=FAIL'
|
|
|
|
if p == 'c':
|
|
|
|
env_flag_name = 'CFLAGS'
|
|
|
|
elif p == 'cpp':
|
|
|
|
env_flag_name = 'CXXFLAGS'
|
|
|
|
else:
|
|
|
|
raise NotImplementedError('Language {} not defined.'.format(p))
|
|
|
|
env = {}
|
|
|
|
env[env_flag_name] = cmd_std
|
|
|
|
with self.assertRaises((subprocess.CalledProcessError, mesonbuild.mesonlib.EnvironmentException),
|
|
|
|
msg='C compiler should have failed with -std=FAIL'):
|
|
|
|
self.init(testdir, override_envvars = env)
|
|
|
|
# ICC won't fail in the above because additional flags are needed to
|
|
|
|
# make unknown -std=... options errors.
|
|
|
|
self.build()
|
|
|
|
|
|
|
|
def test_compiler_c_stds(self):
|
|
|
|
'''
|
|
|
|
Test that C stds specified for this compiler can all be used. Can't be
|
|
|
|
an ordinary test because it requires passing options to meson.
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.common_test_dir, '1 trivial')
|
|
|
|
env = get_fake_env(testdir, self.builddir, self.prefix)
|
|
|
|
cc = env.detect_c_compiler(MachineChoice.HOST)
|
|
|
|
self._test_stds_impl(testdir, cc, 'c')
|
|
|
|
|
|
|
|
def test_compiler_cpp_stds(self):
|
|
|
|
'''
|
|
|
|
Test that C++ stds specified for this compiler can all be used. Can't
|
|
|
|
be an ordinary test because it requires passing options to meson.
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.common_test_dir, '2 cpp')
|
|
|
|
env = get_fake_env(testdir, self.builddir, self.prefix)
|
|
|
|
cpp = env.detect_cpp_compiler(MachineChoice.HOST)
|
|
|
|
self._test_stds_impl(testdir, cpp, 'cpp')
|
|
|
|
|
|
|
|
def test_unity_subproj(self):
|
|
|
|
testdir = os.path.join(self.common_test_dir, '45 subproject')
|
|
|
|
self.init(testdir, extra_args='--unity=subprojects')
|
|
|
|
simpletest_id = Target.construct_id_from_path('subprojects/sublib', 'simpletest', '@exe')
|
|
|
|
self.assertPathExists(os.path.join(self.builddir, 'subprojects/sublib', simpletest_id, 'simpletest-unity.c'))
|
|
|
|
sublib_id = Target.construct_id_from_path('subprojects/sublib', 'sublib', '@sha')
|
|
|
|
self.assertPathExists(os.path.join(self.builddir, 'subprojects/sublib', sublib_id, 'sublib-unity.c'))
|
|
|
|
self.assertPathDoesNotExist(os.path.join(self.builddir, 'user@exe/user-unity.c'))
|
|
|
|
self.build()
|
|
|
|
|
|
|
|
def test_installed_modes(self):
|
|
|
|
'''
|
|
|
|
Test that files installed by these tests have the correct permissions.
|
|
|
|
Can't be an ordinary test because our installed_files.txt is very basic.
|
|
|
|
'''
|
|
|
|
# Test file modes
|
|
|
|
testdir = os.path.join(self.common_test_dir, '12 data')
|
|
|
|
self.init(testdir)
|
|
|
|
self.install()
|
|
|
|
|
|
|
|
f = os.path.join(self.installdir, 'etc', 'etcfile.dat')
|
|
|
|
found_mode = stat.filemode(os.stat(f).st_mode)
|
|
|
|
want_mode = 'rw------T'
|
|
|
|
self.assertEqual(want_mode, found_mode[1:])
|
|
|
|
|
|
|
|
f = os.path.join(self.installdir, 'usr', 'bin', 'runscript.sh')
|
|
|
|
statf = os.stat(f)
|
|
|
|
found_mode = stat.filemode(statf.st_mode)
|
|
|
|
want_mode = 'rwxr-sr-x'
|
|
|
|
self.assertEqual(want_mode, found_mode[1:])
|
|
|
|
if os.getuid() == 0:
|
|
|
|
# The chown failed nonfatally if we're not root
|
|
|
|
self.assertEqual(0, statf.st_uid)
|
|
|
|
self.assertEqual(0, statf.st_gid)
|
|
|
|
|
|
|
|
f = os.path.join(self.installdir, 'usr', 'share', 'progname',
|
|
|
|
'fileobject_datafile.dat')
|
|
|
|
orig = os.path.join(testdir, 'fileobject_datafile.dat')
|
|
|
|
statf = os.stat(f)
|
|
|
|
statorig = os.stat(orig)
|
|
|
|
found_mode = stat.filemode(statf.st_mode)
|
|
|
|
orig_mode = stat.filemode(statorig.st_mode)
|
|
|
|
self.assertEqual(orig_mode[1:], found_mode[1:])
|
|
|
|
self.assertEqual(os.getuid(), statf.st_uid)
|
|
|
|
if os.getuid() == 0:
|
|
|
|
# The chown failed nonfatally if we're not root
|
|
|
|
self.assertEqual(0, statf.st_gid)
|
|
|
|
|
|
|
|
self.wipe()
|
|
|
|
# Test directory modes
|
|
|
|
testdir = os.path.join(self.common_test_dir, '62 install subdir')
|
|
|
|
self.init(testdir)
|
|
|
|
self.install()
|
|
|
|
|
|
|
|
f = os.path.join(self.installdir, 'usr', 'share', 'sub1', 'second.dat')
|
|
|
|
statf = os.stat(f)
|
|
|
|
found_mode = stat.filemode(statf.st_mode)
|
|
|
|
want_mode = 'rwxr-x--t'
|
|
|
|
self.assertEqual(want_mode, found_mode[1:])
|
|
|
|
if os.getuid() == 0:
|
|
|
|
# The chown failed nonfatally if we're not root
|
|
|
|
self.assertEqual(0, statf.st_uid)
|
|
|
|
|
|
|
|
def test_installed_modes_extended(self):
|
|
|
|
'''
|
|
|
|
Test that files are installed with correct permissions using install_mode.
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.common_test_dir, '195 install_mode')
|
|
|
|
self.init(testdir)
|
|
|
|
self.build()
|
|
|
|
self.install()
|
|
|
|
|
|
|
|
for fsobj, want_mode in [
|
|
|
|
('bin', 'drwxr-x---'),
|
|
|
|
('bin/runscript.sh', '-rwxr-sr-x'),
|
|
|
|
('bin/trivialprog', '-rwxr-sr-x'),
|
|
|
|
('include', 'drwxr-x---'),
|
|
|
|
('include/config.h', '-rw-rwSr--'),
|
|
|
|
('include/rootdir.h', '-r--r--r-T'),
|
|
|
|
('lib', 'drwxr-x---'),
|
|
|
|
('lib/libstat.a', '-rw---Sr--'),
|
|
|
|
('share', 'drwxr-x---'),
|
|
|
|
('share/man', 'drwxr-x---'),
|
|
|
|
('share/man/man1', 'drwxr-x---'),
|
|
|
|
('share/man/man1/foo.1', '-r--r--r-T'),
|
|
|
|
('share/sub1', 'drwxr-x---'),
|
|
|
|
('share/sub1/second.dat', '-rwxr-x--t'),
|
|
|
|
('subdir', 'drwxr-x---'),
|
|
|
|
('subdir/data.dat', '-rw-rwSr--'),
|
|
|
|
]:
|
|
|
|
f = os.path.join(self.installdir, 'usr', *fsobj.split('/'))
|
|
|
|
found_mode = stat.filemode(os.stat(f).st_mode)
|
|
|
|
self.assertEqual(want_mode, found_mode,
|
|
|
|
msg=('Expected file %s to have mode %s but found %s instead.' %
|
|
|
|
(fsobj, want_mode, found_mode)))
|
|
|
|
# Ensure that introspect --installed works on all types of files
|
|
|
|
# FIXME: also verify the files list
|
|
|
|
self.introspect('--installed')
|
|
|
|
|
|
|
|
def test_install_umask(self):
|
|
|
|
'''
|
|
|
|
Test that files are installed with correct permissions using default
|
|
|
|
install umask of 022, regardless of the umask at time the worktree
|
|
|
|
was checked out or the build was executed.
|
|
|
|
'''
|
|
|
|
# Copy source tree to a temporary directory and change permissions
|
|
|
|
# there to simulate a checkout with umask 002.
|
|
|
|
orig_testdir = os.path.join(self.unit_test_dir, '26 install umask')
|
|
|
|
# Create a new testdir under tmpdir.
|
|
|
|
tmpdir = os.path.realpath(tempfile.mkdtemp())
|
|
|
|
self.addCleanup(windows_proof_rmtree, tmpdir)
|
|
|
|
testdir = os.path.join(tmpdir, '26 install umask')
|
|
|
|
# Copy the tree using shutil.copyfile, which will use the current umask
|
|
|
|
# instead of preserving permissions of the old tree.
|
|
|
|
save_umask = os.umask(0o002)
|
|
|
|
self.addCleanup(os.umask, save_umask)
|
|
|
|
shutil.copytree(orig_testdir, testdir, copy_function=shutil.copyfile)
|
|
|
|
# Preserve the executable status of subdir/sayhello though.
|
|
|
|
os.chmod(os.path.join(testdir, 'subdir', 'sayhello'), 0o775)
|
|
|
|
self.init(testdir)
|
|
|
|
# Run the build under a 027 umask now.
|
|
|
|
os.umask(0o027)
|
|
|
|
self.build()
|
|
|
|
# And keep umask 027 for the install step too.
|
|
|
|
self.install()
|
|
|
|
|
|
|
|
for executable in [
|
|
|
|
'bin/prog',
|
|
|
|
'share/subdir/sayhello',
|
|
|
|
]:
|
|
|
|
f = os.path.join(self.installdir, 'usr', *executable.split('/'))
|
|
|
|
found_mode = stat.filemode(os.stat(f).st_mode)
|
|
|
|
want_mode = '-rwxr-xr-x'
|
|
|
|
self.assertEqual(want_mode, found_mode,
|
|
|
|
msg=('Expected file %s to have mode %s but found %s instead.' %
|
|
|
|
(executable, want_mode, found_mode)))
|
|
|
|
|
|
|
|
for directory in [
|
|
|
|
'usr',
|
|
|
|
'usr/bin',
|
|
|
|
'usr/include',
|
|
|
|
'usr/share',
|
|
|
|
'usr/share/man',
|
|
|
|
'usr/share/man/man1',
|
|
|
|
'usr/share/subdir',
|
|
|
|
]:
|
|
|
|
f = os.path.join(self.installdir, *directory.split('/'))
|
|
|
|
found_mode = stat.filemode(os.stat(f).st_mode)
|
|
|
|
want_mode = 'drwxr-xr-x'
|
|
|
|
self.assertEqual(want_mode, found_mode,
|
|
|
|
msg=('Expected directory %s to have mode %s but found %s instead.' %
|
|
|
|
(directory, want_mode, found_mode)))
|
|
|
|
|
|
|
|
for datafile in [
|
|
|
|
'include/sample.h',
|
|
|
|
'share/datafile.cat',
|
|
|
|
'share/file.dat',
|
|
|
|
'share/man/man1/prog.1',
|
|
|
|
'share/subdir/datafile.dog',
|
|
|
|
]:
|
|
|
|
f = os.path.join(self.installdir, 'usr', *datafile.split('/'))
|
|
|
|
found_mode = stat.filemode(os.stat(f).st_mode)
|
|
|
|
want_mode = '-rw-r--r--'
|
|
|
|
self.assertEqual(want_mode, found_mode,
|
|
|
|
msg=('Expected file %s to have mode %s but found %s instead.' %
|
|
|
|
(datafile, want_mode, found_mode)))
|
|
|
|
|
|
|
|
def test_cpp_std_override(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '6 std override')
|
|
|
|
self.init(testdir)
|
|
|
|
compdb = self.get_compdb()
|
|
|
|
# Don't try to use -std=c++03 as a check for the
|
|
|
|
# presence of a compiler flag, as ICC does not
|
|
|
|
# support it.
|
|
|
|
for i in compdb:
|
|
|
|
if 'prog98' in i['file']:
|
|
|
|
c98_comp = i['command']
|
|
|
|
if 'prog11' in i['file']:
|
|
|
|
c11_comp = i['command']
|
|
|
|
if 'progp' in i['file']:
|
|
|
|
plain_comp = i['command']
|
|
|
|
self.assertNotEqual(len(plain_comp), 0)
|
|
|
|
self.assertIn('-std=c++98', c98_comp)
|
|
|
|
self.assertNotIn('-std=c++11', c98_comp)
|
|
|
|
self.assertIn('-std=c++11', c11_comp)
|
|
|
|
self.assertNotIn('-std=c++98', c11_comp)
|
|
|
|
self.assertNotIn('-std=c++98', plain_comp)
|
|
|
|
self.assertNotIn('-std=c++11', plain_comp)
|
|
|
|
# Now werror
|
|
|
|
self.assertIn('-Werror', plain_comp)
|
|
|
|
self.assertNotIn('-Werror', c98_comp)
|
|
|
|
|
|
|
|
def test_run_installed(self):
|
|
|
|
if is_cygwin() or is_osx():
|
|
|
|
raise unittest.SkipTest('LD_LIBRARY_PATH and RPATH not applicable')
|
|
|
|
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '7 run installed')
|
|
|
|
self.init(testdir)
|
|
|
|
self.build()
|
|
|
|
self.install()
|
|
|
|
installed_exe = os.path.join(self.installdir, 'usr/bin/prog')
|
|
|
|
installed_libdir = os.path.join(self.installdir, 'usr/foo')
|
|
|
|
installed_lib = os.path.join(installed_libdir, 'libfoo.so')
|
|
|
|
self.assertTrue(os.path.isfile(installed_exe))
|
|
|
|
self.assertTrue(os.path.isdir(installed_libdir))
|
|
|
|
self.assertTrue(os.path.isfile(installed_lib))
|
|
|
|
# Must fail when run without LD_LIBRARY_PATH to ensure that
|
|
|
|
# rpath has been properly stripped rather than pointing to the builddir.
|
|
|
|
self.assertNotEqual(subprocess.call(installed_exe, stderr=subprocess.DEVNULL), 0)
|
|
|
|
# When LD_LIBRARY_PATH is set it should start working.
|
|
|
|
# For some reason setting LD_LIBRARY_PATH in os.environ fails
|
|
|
|
# when all tests are run (but works when only this test is run),
|
|
|
|
# but doing this explicitly works.
|
|
|
|
env = os.environ.copy()
|
|
|
|
env['LD_LIBRARY_PATH'] = ':'.join([installed_libdir, env.get('LD_LIBRARY_PATH', '')])
|
|
|
|
self.assertEqual(subprocess.call(installed_exe, env=env), 0)
|
meson introspect: Fix --installed argument
Traceback (most recent call last):
File "meson.py", line 29, in <module>
sys.exit(mesonmain.main())
File "mesonbuild/mesonmain.py", line 411, in main
return run(sys.argv[1:], launcher)
File "mesonbuild/mesonmain.py", line 320, in run
return mintro.run(remaining_args)
File "mesonbuild/mintro.py", line 234, in run
list_installed(installdata)
File "mesonbuild/mintro.py", line 72, in list_installed
for path, installdir, aliases, unknown1, unknown2 in installdata.targets:
ValueError: too many values to unpack (expected 5)
7 years ago
|
|
|
# Ensure that introspect --installed works
|
|
|
|
installed = self.introspect('--installed')
|
|
|
|
for v in installed.values():
|
|
|
|
self.assertTrue('prog' in v or 'foo' in v)
|
|
|
|
|
|
|
|
@skipIfNoPkgconfig
|
|
|
|
def test_order_of_l_arguments(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '8 -L -l order')
|
|
|
|
self.init(testdir, override_envvars={'PKG_CONFIG_PATH': testdir})
|
|
|
|
# NOTE: .pc file has -Lfoo -lfoo -Lbar -lbar but pkg-config reorders
|
|
|
|
# the flags before returning them to -Lfoo -Lbar -lfoo -lbar
|
|
|
|
# but pkgconf seems to not do that. Sigh. Support both.
|
|
|
|
expected_order = [('-L/me/first', '-lfoo1'),
|
|
|
|
('-L/me/second', '-lfoo2'),
|
|
|
|
('-L/me/first', '-L/me/second'),
|
|
|
|
('-lfoo1', '-lfoo2'),
|
|
|
|
('-L/me/second', '-L/me/third'),
|
|
|
|
('-L/me/third', '-L/me/fourth',),
|
|
|
|
('-L/me/third', '-lfoo3'),
|
|
|
|
('-L/me/fourth', '-lfoo4'),
|
|
|
|
('-lfoo3', '-lfoo4'),
|
|
|
|
]
|
|
|
|
with open(os.path.join(self.builddir, 'build.ninja')) as ifile:
|
|
|
|
for line in ifile:
|
|
|
|
if expected_order[0][0] in line:
|
|
|
|
for first, second in expected_order:
|
|
|
|
self.assertLess(line.index(first), line.index(second))
|
|
|
|
return
|
|
|
|
raise RuntimeError('Linker entries not found in the Ninja file.')
|
|
|
|
|
|
|
|
def test_introspect_dependencies(self):
|
|
|
|
'''
|
|
|
|
Tests that mesonintrospect --dependencies returns expected output.
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.framework_test_dir, '7 gnome')
|
|
|
|
self.init(testdir)
|
|
|
|
glib_found = False
|
|
|
|
gobject_found = False
|
|
|
|
deps = self.introspect('--dependencies')
|
|
|
|
self.assertIsInstance(deps, list)
|
|
|
|
for dep in deps:
|
|
|
|
self.assertIsInstance(dep, dict)
|
|
|
|
self.assertIn('name', dep)
|
|
|
|
self.assertIn('compile_args', dep)
|
|
|
|
self.assertIn('link_args', dep)
|
|
|
|
if dep['name'] == 'glib-2.0':
|
|
|
|
glib_found = True
|
|
|
|
elif dep['name'] == 'gobject-2.0':
|
|
|
|
gobject_found = True
|
|
|
|
self.assertTrue(glib_found)
|
|
|
|
self.assertTrue(gobject_found)
|
|
|
|
if subprocess.call(['pkg-config', '--exists', 'glib-2.0 >= 2.56.2']) != 0:
|
|
|
|
raise unittest.SkipTest('glib >= 2.56.2 needed for the rest')
|
|
|
|
targets = self.introspect('--targets')
|
|
|
|
docbook_target = None
|
|
|
|
for t in targets:
|
|
|
|
if t['name'] == 'generated-gdbus-docbook':
|
|
|
|
docbook_target = t
|
|
|
|
break
|
|
|
|
self.assertIsInstance(docbook_target, dict)
|
|
|
|
self.assertEqual(os.path.basename(t['filename'][0]), 'generated-gdbus-doc-' + os.path.basename(t['target_sources'][0]['sources'][0]))
|
|
|
|
|
|
|
|
def test_build_rpath(self):
|
|
|
|
if is_cygwin():
|
|
|
|
raise unittest.SkipTest('Windows PE/COFF binaries do not use RPATH')
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '10 build_rpath')
|
|
|
|
self.init(testdir)
|
|
|
|
self.build()
|
|
|
|
# C program RPATH
|
|
|
|
build_rpath = get_rpath(os.path.join(self.builddir, 'prog'))
|
|
|
|
self.assertEqual(build_rpath, '$ORIGIN/sub:/foo/bar')
|
|
|
|
self.install()
|
|
|
|
install_rpath = get_rpath(os.path.join(self.installdir, 'usr/bin/prog'))
|
|
|
|
self.assertEqual(install_rpath, '/baz')
|
|
|
|
# C++ program RPATH
|
|
|
|
build_rpath = get_rpath(os.path.join(self.builddir, 'progcxx'))
|
|
|
|
self.assertEqual(build_rpath, '$ORIGIN/sub:/foo/bar')
|
|
|
|
self.install()
|
|
|
|
install_rpath = get_rpath(os.path.join(self.installdir, 'usr/bin/progcxx'))
|
|
|
|
self.assertEqual(install_rpath, 'baz')
|
|
|
|
|
|
|
|
@skip_if_not_base_option('b_sanitize')
|
|
|
|
def test_pch_with_address_sanitizer(self):
|
|
|
|
if is_cygwin():
|
|
|
|
raise unittest.SkipTest('asan not available on Cygwin')
|
|
|
|
if is_openbsd():
|
|
|
|
raise unittest.SkipTest('-fsanitize=address is not supported on OpenBSD')
|
|
|
|
|
|
|
|
testdir = os.path.join(self.common_test_dir, '13 pch')
|
|
|
|
self.init(testdir, extra_args=['-Db_sanitize=address', '-Db_lundef=false'])
|
|
|
|
self.build()
|
|
|
|
compdb = self.get_compdb()
|
|
|
|
for i in compdb:
|
|
|
|
self.assertIn("-fsanitize=address", i["command"])
|
|
|
|
|
|
|
|
def test_coverage(self):
|
|
|
|
gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr()
|
|
|
|
if not gcovr_exe:
|
|
|
|
raise unittest.SkipTest('gcovr not found')
|
|
|
|
if not shutil.which('genhtml') and not gcovr_new_rootdir:
|
|
|
|
raise unittest.SkipTest('genhtml not found and gcovr is too old')
|
|
|
|
if 'clang' in os.environ.get('CC', ''):
|
|
|
|
# We need to use llvm-cov instead of gcovr with clang
|
|
|
|
raise unittest.SkipTest('Coverage does not work with clang right now, help wanted!')
|
|
|
|
testdir = os.path.join(self.common_test_dir, '1 trivial')
|
|
|
|
self.init(testdir, extra_args=['-Db_coverage=true'])
|
|
|
|
self.build()
|
|
|
|
self.run_tests()
|
|
|
|
self.run_target('coverage-html')
|
|
|
|
|
|
|
|
def test_cross_find_program(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '11 cross prog')
|
|
|
|
crossfile = tempfile.NamedTemporaryFile(mode='w')
|
|
|
|
print(os.path.join(testdir, 'some_cross_tool.py'))
|
|
|
|
crossfile.write(textwrap.dedent('''\
|
|
|
|
[binaries]
|
|
|
|
c = '/usr/bin/{1}'
|
|
|
|
ar = '/usr/bin/ar'
|
|
|
|
strip = '/usr/bin/ar'
|
|
|
|
sometool.py = ['{0}']
|
|
|
|
someothertool.py = '{0}'
|
|
|
|
|
|
|
|
[properties]
|
|
|
|
|
|
|
|
[host_machine]
|
|
|
|
system = 'linux'
|
|
|
|
cpu_family = 'arm'
|
|
|
|
cpu = 'armv7' # Not sure if correct.
|
|
|
|
endian = 'little'
|
|
|
|
''').format(os.path.join(testdir, 'some_cross_tool.py'),
|
|
|
|
'gcc' if is_sunos() else 'cc'))
|
|
|
|
crossfile.flush()
|
|
|
|
self.meson_cross_file = crossfile.name
|
|
|
|
self.init(testdir)
|
|
|
|
|
|
|
|
def test_reconfigure(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '13 reconfigure')
|
|
|
|
self.init(testdir, extra_args=['-Db_coverage=true'], default_args=False)
|
|
|
|
self.build('reconfigure')
|
|
|
|
|
|
|
|
def test_vala_generated_source_buildir_inside_source_tree(self):
|
|
|
|
'''
|
|
|
|
Test that valac outputs generated C files in the expected location when
|
|
|
|
the builddir is a subdir of the source tree.
|
|
|
|
'''
|
|
|
|
if not shutil.which('valac'):
|
|
|
|
raise unittest.SkipTest('valac not installed.')
|
|
|
|
|
|
|
|
testdir = os.path.join(self.vala_test_dir, '8 generated sources')
|
|
|
|
newdir = os.path.join(self.builddir, 'srctree')
|
|
|
|
shutil.copytree(testdir, newdir)
|
|
|
|
testdir = newdir
|
|
|
|
# New builddir
|
|
|
|
builddir = os.path.join(testdir, 'subdir/_build')
|
|
|
|
os.makedirs(builddir, exist_ok=True)
|
|
|
|
self.change_builddir(builddir)
|
|
|
|
self.init(testdir)
|
|
|
|
self.build()
|
|
|
|
|
|
|
|
def test_old_gnome_module_codepaths(self):
|
|
|
|
'''
|
|
|
|
A lot of code in the GNOME module is conditional on the version of the
|
|
|
|
glib tools that are installed, and breakages in the old code can slip
|
|
|
|
by once the CI has a newer glib version. So we force the GNOME module
|
|
|
|
to pretend that it's running on an ancient glib so the fallback code is
|
|
|
|
also tested.
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.framework_test_dir, '7 gnome')
|
|
|
|
mesonbuild.modules.gnome.native_glib_version = '2.20'
|
|
|
|
env = {'MESON_UNIT_TEST_PRETEND_GLIB_OLD': "1"}
|
|
|
|
try:
|
|
|
|
self.init(testdir,
|
|
|
|
inprocess=True,
|
|
|
|
override_envvars=env)
|
|
|
|
self.build(override_envvars=env)
|
|
|
|
finally:
|
|
|
|
mesonbuild.modules.gnome.native_glib_version = None
|
|
|
|
|
|
|
|
@skipIfNoPkgconfig
|
|
|
|
def test_pkgconfig_usage(self):
|
|
|
|
testdir1 = os.path.join(self.unit_test_dir, '27 pkgconfig usage/dependency')
|
|
|
|
testdir2 = os.path.join(self.unit_test_dir, '27 pkgconfig usage/dependee')
|
|
|
|
if subprocess.call(['pkg-config', '--cflags', 'glib-2.0'],
|
|
|
|
stdout=subprocess.DEVNULL,
|
|
|
|
stderr=subprocess.DEVNULL) != 0:
|
|
|
|
raise unittest.SkipTest('Glib 2.0 dependency not available.')
|
|
|
|
with tempfile.TemporaryDirectory() as tempdirname:
|
|
|
|
self.init(testdir1, extra_args=['--prefix=' + tempdirname, '--libdir=lib'], default_args=False)
|
|
|
|
self.install(use_destdir=False)
|
|
|
|
shutil.rmtree(self.builddir)
|
|
|
|
os.mkdir(self.builddir)
|
|
|
|
pkg_dir = os.path.join(tempdirname, 'lib/pkgconfig')
|
|
|
|
self.assertTrue(os.path.exists(os.path.join(pkg_dir, 'libpkgdep.pc')))
|
|
|
|
lib_dir = os.path.join(tempdirname, 'lib')
|
|
|
|
myenv = os.environ.copy()
|
|
|
|
myenv['PKG_CONFIG_PATH'] = pkg_dir
|
|
|
|
# Private internal libraries must not leak out.
|
|
|
|
pkg_out = subprocess.check_output(['pkg-config', '--static', '--libs', 'libpkgdep'], env=myenv)
|
|
|
|
self.assertFalse(b'libpkgdep-int' in pkg_out, 'Internal library leaked out.')
|
|
|
|
# Dependencies must not leak to cflags when building only a shared library.
|
|
|
|
pkg_out = subprocess.check_output(['pkg-config', '--cflags', 'libpkgdep'], env=myenv)
|
|
|
|
self.assertFalse(b'glib' in pkg_out, 'Internal dependency leaked to headers.')
|
|
|
|
# Test that the result is usable.
|
|
|
|
self.init(testdir2, override_envvars=myenv)
|
|
|
|
self.build(override_envvars=myenv)
|
|
|
|
myenv = os.environ.copy()
|
|
|
|
myenv['LD_LIBRARY_PATH'] = ':'.join([lib_dir, myenv.get('LD_LIBRARY_PATH', '')])
|
|
|
|
if is_cygwin():
|
|
|
|
bin_dir = os.path.join(tempdirname, 'bin')
|
|
|
|
myenv['PATH'] = bin_dir + os.pathsep + myenv['PATH']
|
|
|
|
self.assertTrue(os.path.isdir(lib_dir))
|
|
|
|
test_exe = os.path.join(self.builddir, 'pkguser')
|
|
|
|
self.assertTrue(os.path.isfile(test_exe))
|
|
|
|
subprocess.check_call(test_exe, env=myenv)
|
|
|
|
|
|
|
|
@skipIfNoPkgconfig
|
|
|
|
def test_pkgconfig_relative_paths(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '62 pkgconfig relative paths')
|
|
|
|
pkg_dir = os.path.join(testdir, 'pkgconfig')
|
|
|
|
self.assertTrue(os.path.exists(os.path.join(pkg_dir, 'librelativepath.pc')))
|
|
|
|
|
|
|
|
env = get_fake_env(testdir, self.builddir, self.prefix)
|
|
|
|
env.coredata.set_options({'pkg_config_path': pkg_dir}, subproject='')
|
|
|
|
kwargs = {'required': True, 'silent': True}
|
|
|
|
relative_path_dep = PkgConfigDependency('librelativepath', env, kwargs)
|
|
|
|
self.assertTrue(relative_path_dep.found())
|
|
|
|
|
|
|
|
# Ensure link_args are properly quoted
|
|
|
|
libpath = Path(self.builddir) / '../relativepath/lib'
|
|
|
|
link_args = ['-L' + libpath.as_posix(), '-lrelativepath']
|
|
|
|
self.assertEqual(relative_path_dep.get_link_args(), link_args)
|
|
|
|
|
|
|
|
@skipIfNoPkgconfig
|
|
|
|
def test_pkgconfig_internal_libraries(self):
|
|
|
|
'''
|
|
|
|
'''
|
|
|
|
with tempfile.TemporaryDirectory() as tempdirname:
|
|
|
|
# build library
|
|
|
|
testdirbase = os.path.join(self.unit_test_dir, '32 pkgconfig use libraries')
|
|
|
|
testdirlib = os.path.join(testdirbase, 'lib')
|
|
|
|
self.init(testdirlib, extra_args=['--prefix=' + tempdirname,
|
|
|
|
'--libdir=lib',
|
|
|
|
'--default-library=static'], default_args=False)
|
|
|
|
self.build()
|
|
|
|
self.install(use_destdir=False)
|
|
|
|
|
|
|
|
# build user of library
|
|
|
|
pkg_dir = os.path.join(tempdirname, 'lib/pkgconfig')
|
|
|
|
self.new_builddir()
|
|
|
|
self.init(os.path.join(testdirbase, 'app'),
|
|
|
|
override_envvars={'PKG_CONFIG_PATH': pkg_dir})
|
|
|
|
self.build()
|
|
|
|
|
|
|
|
@skipIfNoPkgconfig
|
|
|
|
def test_static_archive_stripping(self):
|
|
|
|
'''
|
|
|
|
Check that Meson produces valid static archives with --strip enabled
|
|
|
|
'''
|
|
|
|
with tempfile.TemporaryDirectory() as tempdirname:
|
|
|
|
testdirbase = os.path.join(self.unit_test_dir, '67 static archive stripping')
|
|
|
|
|
|
|
|
# build lib
|
|
|
|
self.new_builddir()
|
|
|
|
testdirlib = os.path.join(testdirbase, 'lib')
|
|
|
|
testlibprefix = os.path.join(tempdirname, 'libprefix')
|
|
|
|
self.init(testdirlib, extra_args=['--prefix=' + testlibprefix,
|
|
|
|
'--libdir=lib',
|
|
|
|
'--default-library=static',
|
|
|
|
'--buildtype=debug',
|
|
|
|
'--strip'], default_args=False)
|
|
|
|
self.build()
|
|
|
|
self.install(use_destdir=False)
|
|
|
|
|
|
|
|
# build executable (uses lib, fails if static archive has been stripped incorrectly)
|
|
|
|
pkg_dir = os.path.join(testlibprefix, 'lib/pkgconfig')
|
|
|
|
self.new_builddir()
|
|
|
|
self.init(os.path.join(testdirbase, 'app'),
|
|
|
|
override_envvars={'PKG_CONFIG_PATH': pkg_dir})
|
|
|
|
self.build()
|
|
|
|
|
|
|
|
@skipIfNoPkgconfig
|
|
|
|
def test_pkgconfig_formatting(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '38 pkgconfig format')
|
|
|
|
self.init(testdir)
|
|
|
|
myenv = os.environ.copy()
|
|
|
|
myenv['PKG_CONFIG_PATH'] = self.privatedir
|
|
|
|
stdo = subprocess.check_output(['pkg-config', '--libs-only-l', 'libsomething'], env=myenv)
|
|
|
|
deps = [b'-lgobject-2.0', b'-lgio-2.0', b'-lglib-2.0', b'-lsomething']
|
|
|
|
if is_windows() or is_cygwin() or is_osx() or is_openbsd():
|
|
|
|
# On Windows, libintl is a separate library
|
|
|
|
deps.append(b'-lintl')
|
|
|
|
self.assertEqual(set(deps), set(stdo.split()))
|
|
|
|
|
|
|
|
@skipIfNoPkgconfig
|
|
|
|
@skip_if_not_language('cs')
|
|
|
|
def test_pkgconfig_csharp_library(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '50 pkgconfig csharp library')
|
|
|
|
self.init(testdir)
|
|
|
|
myenv = os.environ.copy()
|
|
|
|
myenv['PKG_CONFIG_PATH'] = self.privatedir
|
|
|
|
stdo = subprocess.check_output(['pkg-config', '--libs', 'libsomething'], env=myenv)
|
|
|
|
|
|
|
|
self.assertEqual("-r/usr/lib/libsomething.dll", str(stdo.decode('ascii')).strip())
|
|
|
|
|
|
|
|
@skipIfNoPkgconfig
|
|
|
|
def test_pkgconfig_link_order(self):
|
|
|
|
'''
|
|
|
|
Test that libraries are listed before their dependencies.
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '53 pkgconfig static link order')
|
|
|
|
self.init(testdir)
|
|
|
|
myenv = os.environ.copy()
|
|
|
|
myenv['PKG_CONFIG_PATH'] = self.privatedir
|
|
|
|
stdo = subprocess.check_output(['pkg-config', '--libs', 'libsomething'], env=myenv)
|
|
|
|
deps = stdo.split()
|
|
|
|
self.assertTrue(deps.index(b'-lsomething') < deps.index(b'-ldependency'))
|
|
|
|
|
|
|
|
def test_deterministic_dep_order(self):
|
|
|
|
'''
|
|
|
|
Test that the dependencies are always listed in a deterministic order.
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '43 dep order')
|
|
|
|
self.init(testdir)
|
|
|
|
with open(os.path.join(self.builddir, 'build.ninja')) as bfile:
|
|
|
|
for line in bfile:
|
|
|
|
if 'build myexe:' in line or 'build myexe.exe:' in line:
|
|
|
|
self.assertIn('liblib1.a liblib2.a', line)
|
|
|
|
return
|
|
|
|
raise RuntimeError('Could not find the build rule')
|
|
|
|
|
|
|
|
def test_deterministic_rpath_order(self):
|
|
|
|
'''
|
|
|
|
Test that the rpaths are always listed in a deterministic order.
|
|
|
|
'''
|
|
|
|
if is_cygwin():
|
|
|
|
raise unittest.SkipTest('rpath are not used on Cygwin')
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '42 rpath order')
|
|
|
|
self.init(testdir)
|
|
|
|
if is_osx():
|
|
|
|
rpathre = re.compile(r'-rpath,.*/subprojects/sub1.*-rpath,.*/subprojects/sub2')
|
|
|
|
else:
|
|
|
|
rpathre = re.compile(r'-rpath,\$\$ORIGIN/subprojects/sub1:\$\$ORIGIN/subprojects/sub2')
|
|
|
|
with open(os.path.join(self.builddir, 'build.ninja')) as bfile:
|
|
|
|
for line in bfile:
|
|
|
|
if '-rpath' in line:
|
|
|
|
self.assertRegex(line, rpathre)
|
|
|
|
return
|
|
|
|
raise RuntimeError('Could not find the rpath')
|
|
|
|
|
|
|
|
def test_override_with_exe_dep(self):
|
|
|
|
'''
|
|
|
|
Test that we produce the correct dependencies when a program is overridden with an executable.
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.common_test_dir, '201 override with exe')
|
|
|
|
self.init(testdir)
|
|
|
|
with open(os.path.join(self.builddir, 'build.ninja')) as bfile:
|
|
|
|
for line in bfile:
|
|
|
|
if 'main1.c:' in line or 'main2.c:' in line:
|
|
|
|
self.assertIn('| subprojects/sub/foobar', line)
|
|
|
|
|
|
|
|
@skipIfNoPkgconfig
|
macos: Rewrite install_name for dependent built libraries on install
On macOS, we set the install_name for built libraries to
@rpath/libfoo.dylib, and when linking to the library, we set the RPATH
to its path in the build directory. This allows all built binaries to
be run as-is from the build directory (uninstalled).
However, on install, we have to strip all the RPATHs because they
point to the build directory, and we change the install_name of all
built libraries to the absolute path to the library. This causes the
install name in binaries to be out of date.
We now change that install name to point to the absolute path to each
built library after installation.
Fixes https://github.com/mesonbuild/meson/issues/3038
Fixes https://github.com/mesonbuild/meson/issues/3077
With this, the default workflow on macOS matches what everyone seems
to do, including Autotools and CMake. The next step is providing a way
for build files to override the install_name that is used after
installation for use with, f.ex., private libraries when combined with
the install_rpath: kwarg on targets.
7 years ago
|
|
|
def test_usage_external_library(self):
|
|
|
|
'''
|
|
|
|
Test that uninstalled usage of an external library (from the system or
|
macos: Rewrite install_name for dependent built libraries on install
On macOS, we set the install_name for built libraries to
@rpath/libfoo.dylib, and when linking to the library, we set the RPATH
to its path in the build directory. This allows all built binaries to
be run as-is from the build directory (uninstalled).
However, on install, we have to strip all the RPATHs because they
point to the build directory, and we change the install_name of all
built libraries to the absolute path to the library. This causes the
install name in binaries to be out of date.
We now change that install name to point to the absolute path to each
built library after installation.
Fixes https://github.com/mesonbuild/meson/issues/3038
Fixes https://github.com/mesonbuild/meson/issues/3077
With this, the default workflow on macOS matches what everyone seems
to do, including Autotools and CMake. The next step is providing a way
for build files to override the install_name that is used after
installation for use with, f.ex., private libraries when combined with
the install_rpath: kwarg on targets.
7 years ago
|
|
|
PkgConfigDependency) works. On macOS, this workflow works out of the
|
|
|
|
box. On Linux, BSDs, Windows, etc, you need to set extra arguments such
|
|
|
|
as LD_LIBRARY_PATH, etc, so this test is skipped.
|
|
|
|
|
|
|
|
The system library is found with cc.find_library() and pkg-config deps.
|
|
|
|
'''
|
|
|
|
oldprefix = self.prefix
|
|
|
|
# Install external library so we can find it
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '40 external, internal library rpath', 'external library')
|
macos: Rewrite install_name for dependent built libraries on install
On macOS, we set the install_name for built libraries to
@rpath/libfoo.dylib, and when linking to the library, we set the RPATH
to its path in the build directory. This allows all built binaries to
be run as-is from the build directory (uninstalled).
However, on install, we have to strip all the RPATHs because they
point to the build directory, and we change the install_name of all
built libraries to the absolute path to the library. This causes the
install name in binaries to be out of date.
We now change that install name to point to the absolute path to each
built library after installation.
Fixes https://github.com/mesonbuild/meson/issues/3038
Fixes https://github.com/mesonbuild/meson/issues/3077
With this, the default workflow on macOS matches what everyone seems
to do, including Autotools and CMake. The next step is providing a way
for build files to override the install_name that is used after
installation for use with, f.ex., private libraries when combined with
the install_rpath: kwarg on targets.
7 years ago
|
|
|
# install into installdir without using DESTDIR
|
|
|
|
installdir = self.installdir
|
|
|
|
self.prefix = installdir
|
|
|
|
self.init(testdir)
|
macos: Rewrite install_name for dependent built libraries on install
On macOS, we set the install_name for built libraries to
@rpath/libfoo.dylib, and when linking to the library, we set the RPATH
to its path in the build directory. This allows all built binaries to
be run as-is from the build directory (uninstalled).
However, on install, we have to strip all the RPATHs because they
point to the build directory, and we change the install_name of all
built libraries to the absolute path to the library. This causes the
install name in binaries to be out of date.
We now change that install name to point to the absolute path to each
built library after installation.
Fixes https://github.com/mesonbuild/meson/issues/3038
Fixes https://github.com/mesonbuild/meson/issues/3077
With this, the default workflow on macOS matches what everyone seems
to do, including Autotools and CMake. The next step is providing a way
for build files to override the install_name that is used after
installation for use with, f.ex., private libraries when combined with
the install_rpath: kwarg on targets.
7 years ago
|
|
|
self.prefix = oldprefix
|
|
|
|
self.build()
|
|
|
|
self.install(use_destdir=False)
|
|
|
|
## New builddir for the consumer
|
|
|
|
self.new_builddir()
|
|
|
|
env = {'LIBRARY_PATH': os.path.join(installdir, self.libdir),
|
|
|
|
'PKG_CONFIG_PATH': os.path.join(installdir, self.libdir, 'pkgconfig')}
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '40 external, internal library rpath', 'built library')
|
macos: Rewrite install_name for dependent built libraries on install
On macOS, we set the install_name for built libraries to
@rpath/libfoo.dylib, and when linking to the library, we set the RPATH
to its path in the build directory. This allows all built binaries to
be run as-is from the build directory (uninstalled).
However, on install, we have to strip all the RPATHs because they
point to the build directory, and we change the install_name of all
built libraries to the absolute path to the library. This causes the
install name in binaries to be out of date.
We now change that install name to point to the absolute path to each
built library after installation.
Fixes https://github.com/mesonbuild/meson/issues/3038
Fixes https://github.com/mesonbuild/meson/issues/3077
With this, the default workflow on macOS matches what everyone seems
to do, including Autotools and CMake. The next step is providing a way
for build files to override the install_name that is used after
installation for use with, f.ex., private libraries when combined with
the install_rpath: kwarg on targets.
7 years ago
|
|
|
# install into installdir without using DESTDIR
|
|
|
|
self.prefix = self.installdir
|
|
|
|
self.init(testdir, override_envvars=env)
|
macos: Rewrite install_name for dependent built libraries on install
On macOS, we set the install_name for built libraries to
@rpath/libfoo.dylib, and when linking to the library, we set the RPATH
to its path in the build directory. This allows all built binaries to
be run as-is from the build directory (uninstalled).
However, on install, we have to strip all the RPATHs because they
point to the build directory, and we change the install_name of all
built libraries to the absolute path to the library. This causes the
install name in binaries to be out of date.
We now change that install name to point to the absolute path to each
built library after installation.
Fixes https://github.com/mesonbuild/meson/issues/3038
Fixes https://github.com/mesonbuild/meson/issues/3077
With this, the default workflow on macOS matches what everyone seems
to do, including Autotools and CMake. The next step is providing a way
for build files to override the install_name that is used after
installation for use with, f.ex., private libraries when combined with
the install_rpath: kwarg on targets.
7 years ago
|
|
|
self.prefix = oldprefix
|
|
|
|
self.build(override_envvars=env)
|
macos: Rewrite install_name for dependent built libraries on install
On macOS, we set the install_name for built libraries to
@rpath/libfoo.dylib, and when linking to the library, we set the RPATH
to its path in the build directory. This allows all built binaries to
be run as-is from the build directory (uninstalled).
However, on install, we have to strip all the RPATHs because they
point to the build directory, and we change the install_name of all
built libraries to the absolute path to the library. This causes the
install name in binaries to be out of date.
We now change that install name to point to the absolute path to each
built library after installation.
Fixes https://github.com/mesonbuild/meson/issues/3038
Fixes https://github.com/mesonbuild/meson/issues/3077
With this, the default workflow on macOS matches what everyone seems
to do, including Autotools and CMake. The next step is providing a way
for build files to override the install_name that is used after
installation for use with, f.ex., private libraries when combined with
the install_rpath: kwarg on targets.
7 years ago
|
|
|
# test uninstalled
|
|
|
|
self.run_tests(override_envvars=env)
|
|
|
|
if not is_osx():
|
|
|
|
# Rest of the workflow only works on macOS
|
|
|
|
return
|
macos: Rewrite install_name for dependent built libraries on install
On macOS, we set the install_name for built libraries to
@rpath/libfoo.dylib, and when linking to the library, we set the RPATH
to its path in the build directory. This allows all built binaries to
be run as-is from the build directory (uninstalled).
However, on install, we have to strip all the RPATHs because they
point to the build directory, and we change the install_name of all
built libraries to the absolute path to the library. This causes the
install name in binaries to be out of date.
We now change that install name to point to the absolute path to each
built library after installation.
Fixes https://github.com/mesonbuild/meson/issues/3038
Fixes https://github.com/mesonbuild/meson/issues/3077
With this, the default workflow on macOS matches what everyone seems
to do, including Autotools and CMake. The next step is providing a way
for build files to override the install_name that is used after
installation for use with, f.ex., private libraries when combined with
the install_rpath: kwarg on targets.
7 years ago
|
|
|
# test running after installation
|
|
|
|
self.install(use_destdir=False)
|
|
|
|
prog = os.path.join(self.installdir, 'bin', 'prog')
|
|
|
|
self._run([prog])
|
|
|
|
out = self._run(['otool', '-L', prog])
|
|
|
|
self.assertNotIn('@rpath', out)
|
|
|
|
## New builddir for testing that DESTDIR is not added to install_name
|
|
|
|
self.new_builddir()
|
|
|
|
# install into installdir with DESTDIR
|
|
|
|
self.init(testdir, override_envvars=env)
|
|
|
|
self.build(override_envvars=env)
|
|
|
|
# test running after installation
|
|
|
|
self.install(override_envvars=env)
|
|
|
|
prog = self.installdir + os.path.join(self.prefix, 'bin', 'prog')
|
|
|
|
lib = self.installdir + os.path.join(self.prefix, 'lib', 'libbar_built.dylib')
|
|
|
|
for f in prog, lib:
|
|
|
|
out = self._run(['otool', '-L', f])
|
|
|
|
# Ensure that the otool output does not contain self.installdir
|
|
|
|
self.assertNotRegex(out, self.installdir + '.*dylib ')
|
|
|
|
|
|
|
|
def install_subdir_invalid_symlinks(self, testdir, subdir_path):
|
|
|
|
'''
|
|
|
|
Test that installation of broken symlinks works fine.
|
|
|
|
https://github.com/mesonbuild/meson/issues/3914
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.common_test_dir, testdir)
|
|
|
|
subdir = os.path.join(testdir, subdir_path)
|
|
|
|
curdir = os.getcwd()
|
|
|
|
os.chdir(subdir)
|
|
|
|
# Can't distribute broken symlinks in the source tree because it breaks
|
|
|
|
# the creation of zipapps. Create it dynamically and run the test by
|
|
|
|
# hand.
|
|
|
|
src = '../../nonexistent.txt'
|
|
|
|
os.symlink(src, 'invalid-symlink.txt')
|
|
|
|
try:
|
|
|
|
self.init(testdir)
|
|
|
|
self.build()
|
|
|
|
self.install()
|
|
|
|
install_path = subdir_path.split(os.path.sep)[-1]
|
|
|
|
link = os.path.join(self.installdir, 'usr', 'share', install_path, 'invalid-symlink.txt')
|
|
|
|
self.assertTrue(os.path.islink(link), msg=link)
|
|
|
|
self.assertEqual(src, os.readlink(link))
|
|
|
|
self.assertFalse(os.path.isfile(link), msg=link)
|
|
|
|
finally:
|
|
|
|
os.remove(os.path.join(subdir, 'invalid-symlink.txt'))
|
|
|
|
os.chdir(curdir)
|
|
|
|
|
|
|
|
def test_install_subdir_symlinks(self):
|
|
|
|
self.install_subdir_invalid_symlinks('62 install subdir', os.path.join('sub', 'sub1'))
|
|
|
|
|
|
|
|
def test_install_subdir_symlinks_with_default_umask(self):
|
|
|
|
self.install_subdir_invalid_symlinks('195 install_mode', 'sub2')
|
|
|
|
|
|
|
|
def test_install_subdir_symlinks_with_default_umask_and_mode(self):
|
|
|
|
self.install_subdir_invalid_symlinks('195 install_mode', 'sub1')
|
|
|
|
|
|
|
|
@skipIfNoPkgconfigDep('gmodule-2.0')
|
|
|
|
def test_ldflag_dedup(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '52 ldflagdedup')
|
|
|
|
if is_cygwin() or is_osx():
|
|
|
|
raise unittest.SkipTest('Not applicable on Cygwin or OSX.')
|
|
|
|
self.init(testdir)
|
|
|
|
build_ninja = os.path.join(self.builddir, 'build.ninja')
|
|
|
|
max_count = 0
|
|
|
|
search_term = '-Wl,--export-dynamic'
|
|
|
|
with open(build_ninja, 'r', encoding='utf-8') as f:
|
|
|
|
for line in f:
|
|
|
|
max_count = max(max_count, line.count(search_term))
|
|
|
|
self.assertEqual(max_count, 1, 'Export dynamic incorrectly deduplicated.')
|
|
|
|
|
|
|
|
def test_compiler_libs_static_dedup(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '56 dedup compiler libs')
|
|
|
|
self.init(testdir)
|
|
|
|
build_ninja = os.path.join(self.builddir, 'build.ninja')
|
|
|
|
with open(build_ninja, 'r', encoding='utf-8') as f:
|
|
|
|
lines = f.readlines()
|
|
|
|
for lib in ('-ldl', '-lm', '-lc', '-lrt'):
|
|
|
|
for line in lines:
|
|
|
|
if lib not in line:
|
|
|
|
continue
|
|
|
|
# Assert that
|
|
|
|
self.assertEqual(len(line.split(lib)), 2, msg=(lib, line))
|
|
|
|
|
|
|
|
@skipIfNoPkgconfig
|
|
|
|
def test_noncross_options(self):
|
|
|
|
# C_std defined in project options must be in effect also when native compiling.
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '51 noncross options')
|
|
|
|
self.init(testdir, extra_args=['-Dpkg_config_path=' + testdir])
|
|
|
|
compdb = self.get_compdb()
|
|
|
|
self.assertEqual(len(compdb), 2)
|
|
|
|
self.assertRegex(compdb[0]['command'], '-std=c99')
|
|
|
|
self.assertRegex(compdb[1]['command'], '-std=c99')
|
|
|
|
self.build()
|
|
|
|
|
|
|
|
def test_identity_cross(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '61 identity cross')
|
|
|
|
crossfile = tempfile.NamedTemporaryFile(mode='w')
|
|
|
|
env = {'CC': '"' + os.path.join(testdir, 'build_wrapper.py') + '"'}
|
|
|
|
crossfile.write('''[binaries]
|
|
|
|
c = ['{0}']
|
|
|
|
'''.format(os.path.join(testdir, 'host_wrapper.py')))
|
|
|
|
crossfile.flush()
|
|
|
|
self.meson_cross_file = crossfile.name
|
|
|
|
# TODO should someday be explicit about build platform only here
|
|
|
|
self.init(testdir, override_envvars=env)
|
|
|
|
|
|
|
|
@skipIfNoPkgconfig
|
|
|
|
def test_static_link(self):
|
|
|
|
if is_cygwin():
|
|
|
|
raise unittest.SkipTest("Cygwin doesn't support LD_LIBRARY_PATH.")
|
|
|
|
|
|
|
|
# Build some libraries and install them
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '68 static link/lib')
|
|
|
|
libdir = os.path.join(self.installdir, self.libdir)
|
|
|
|
oldprefix = self.prefix
|
|
|
|
self.prefix = self.installdir
|
|
|
|
self.init(testdir)
|
|
|
|
self.install(use_destdir=False)
|
|
|
|
|
|
|
|
# Test that installed libraries works
|
|
|
|
self.new_builddir()
|
|
|
|
self.prefix = oldprefix
|
|
|
|
meson_args = ['-Dc_link_args=-L{}'.format(libdir),
|
|
|
|
'--fatal-meson-warnings']
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '68 static link')
|
|
|
|
env = {'PKG_CONFIG_LIBDIR': os.path.join(libdir, 'pkgconfig')}
|
|
|
|
self.init(testdir, extra_args=meson_args, override_envvars=env)
|
|
|
|
self.build()
|
|
|
|
self.run_tests()
|
|
|
|
|
|
|
|
def _check_ld(self, check: str, name: str, lang: str, expected: str) -> None:
|
|
|
|
if is_sunos():
|
|
|
|
raise unittest.SkipTest('Solaris currently cannot override the linker.')
|
|
|
|
if not shutil.which(check):
|
|
|
|
raise unittest.SkipTest('Could not find {}.'.format(check))
|
|
|
|
with mock.patch.dict(os.environ, {'LD': name}):
|
|
|
|
env = get_fake_env()
|
|
|
|
comp = getattr(env, 'detect_{}_compiler'.format(lang))(MachineChoice.HOST)
|
|
|
|
if lang != 'rust' and comp.use_linker_args('foo') == []:
|
|
|
|
raise unittest.SkipTest(
|
|
|
|
'Compiler {} does not support using alternative linkers'.format(comp.id))
|
|
|
|
self.assertEqual(comp.linker.id, expected)
|
|
|
|
|
|
|
|
def test_ld_environment_variable_bfd(self):
|
|
|
|
self._check_ld('ld.bfd', 'bfd', 'c', 'GNU ld.bfd')
|
|
|
|
|
|
|
|
def test_ld_environment_variable_gold(self):
|
|
|
|
self._check_ld('ld.gold', 'gold', 'c', 'GNU ld.gold')
|
|
|
|
|
|
|
|
def test_ld_environment_variable_lld(self):
|
|
|
|
self._check_ld('ld.lld', 'lld', 'c', 'lld')
|
|
|
|
|
|
|
|
def test_ld_environment_variable_rust(self):
|
|
|
|
self._check_ld('ld.gold', 'gold', 'rust', 'GNU ld.gold')
|
|
|
|
|
|
|
|
def test_ld_environment_variable_cpp(self):
|
|
|
|
self._check_ld('ld.gold', 'gold', 'cpp', 'GNU ld.gold')
|
|
|
|
|
|
|
|
def test_ld_environment_variable_objc(self):
|
|
|
|
self._check_ld('ld.gold', 'gold', 'objc', 'GNU ld.gold')
|
|
|
|
|
|
|
|
def test_ld_environment_variable_objcpp(self):
|
|
|
|
self._check_ld('ld.gold', 'gold', 'objcpp', 'GNU ld.gold')
|
|
|
|
|
|
|
|
def test_ld_environment_variable_fortran(self):
|
|
|
|
self._check_ld('ld.gold', 'gold', 'fortran', 'GNU ld.gold')
|
|
|
|
|
|
|
|
|
|
|
|
def should_run_cross_arm_tests():
|
|
|
|
return shutil.which('arm-linux-gnueabihf-gcc') and not platform.machine().lower().startswith('arm')
|
|
|
|
|
|
|
|
@unittest.skipUnless(not is_windows() and should_run_cross_arm_tests(), "requires ability to cross compile to ARM")
|
|
|
|
class LinuxCrossArmTests(BasePlatformTests):
|
|
|
|
'''
|
|
|
|
Tests that cross-compilation to Linux/ARM works
|
|
|
|
'''
|
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
super().setUp()
|
|
|
|
src_root = os.path.dirname(__file__)
|
|
|
|
self.meson_cross_file = os.path.join(src_root, 'cross', 'ubuntu-armhf.txt')
|
|
|
|
|
|
|
|
def test_cflags_cross_environment_pollution(self):
|
|
|
|
'''
|
|
|
|
Test that the CFLAGS environment variable does not pollute the cross
|
|
|
|
environment. This can't be an ordinary test case because we need to
|
|
|
|
inspect the compiler database.
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.common_test_dir, '3 static')
|
|
|
|
self.init(testdir, override_envvars={'CFLAGS': '-DBUILD_ENVIRONMENT_ONLY'})
|
|
|
|
compdb = self.get_compdb()
|
|
|
|
self.assertNotIn('-DBUILD_ENVIRONMENT_ONLY', compdb[0]['command'])
|
|
|
|
|
|
|
|
def test_cross_file_overrides_always_args(self):
|
|
|
|
'''
|
|
|
|
Test that $lang_args in cross files always override get_always_args().
|
|
|
|
Needed for overriding the default -D_FILE_OFFSET_BITS=64 on some
|
|
|
|
architectures such as some Android versions and Raspbian.
|
|
|
|
https://github.com/mesonbuild/meson/issues/3049
|
|
|
|
https://github.com/mesonbuild/meson/issues/3089
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '33 cross file overrides always args')
|
|
|
|
self.meson_cross_file = os.path.join(testdir, 'ubuntu-armhf-overrides.txt')
|
|
|
|
self.init(testdir)
|
|
|
|
compdb = self.get_compdb()
|
|
|
|
self.assertRegex(compdb[0]['command'], '-D_FILE_OFFSET_BITS=64.*-U_FILE_OFFSET_BITS')
|
|
|
|
self.build()
|
|
|
|
|
|
|
|
def test_cross_libdir(self):
|
|
|
|
# When cross compiling "libdir" should default to "lib"
|
|
|
|
# rather than "lib/x86_64-linux-gnu" or something like that.
|
|
|
|
testdir = os.path.join(self.common_test_dir, '1 trivial')
|
|
|
|
self.init(testdir)
|
|
|
|
for i in self.introspect('--buildoptions'):
|
|
|
|
if i['name'] == 'libdir':
|
|
|
|
self.assertEqual(i['value'], 'lib')
|
|
|
|
return
|
|
|
|
self.assertTrue(False, 'Option libdir not in introspect data.')
|
|
|
|
|
|
|
|
def test_std_remains(self):
|
|
|
|
# C_std defined in project options must be in effect also when cross compiling.
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '51 noncross options')
|
|
|
|
self.init(testdir)
|
|
|
|
compdb = self.get_compdb()
|
|
|
|
self.assertRegex(compdb[0]['command'], '-std=c99')
|
|
|
|
self.build()
|
|
|
|
|
|
|
|
@skipIfNoPkgconfig
|
|
|
|
def test_pkg_config_option(self):
|
|
|
|
if not shutil.which('arm-linux-gnueabihf-pkg-config'):
|
|
|
|
raise unittest.SkipTest('Cross-pkgconfig not found.')
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '58 pkg_config_path option')
|
|
|
|
self.init(testdir, extra_args=[
|
|
|
|
'-Dbuild.pkg_config_path=' + os.path.join(testdir, 'build_extra_path'),
|
|
|
|
'-Dpkg_config_path=' + os.path.join(testdir, 'host_extra_path'),
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
def should_run_cross_mingw_tests():
|
|
|
|
return shutil.which('x86_64-w64-mingw32-gcc') and not (is_windows() or is_cygwin())
|
|
|
|
|
|
|
|
@unittest.skipUnless(not is_windows() and should_run_cross_mingw_tests(), "requires ability to cross compile with MinGW")
|
|
|
|
class LinuxCrossMingwTests(BasePlatformTests):
|
|
|
|
'''
|
|
|
|
Tests that cross-compilation to Windows/MinGW works
|
|
|
|
'''
|
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
super().setUp()
|
|
|
|
src_root = os.path.dirname(__file__)
|
|
|
|
self.meson_cross_file = os.path.join(src_root, 'cross', 'linux-mingw-w64-64bit.txt')
|
|
|
|
|
|
|
|
def test_exe_wrapper_behaviour(self):
|
|
|
|
'''
|
|
|
|
Test that an exe wrapper that isn't found doesn't cause compiler sanity
|
|
|
|
checks and compiler checks to fail, but causes configure to fail if it
|
|
|
|
requires running a cross-built executable (custom_target or run_target)
|
|
|
|
and causes the tests to be skipped if they are run.
|
|
|
|
'''
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '36 exe_wrapper behaviour')
|
|
|
|
# Configures, builds, and tests fine by default
|
|
|
|
self.init(testdir)
|
|
|
|
self.build()
|
|
|
|
self.run_tests()
|
|
|
|
self.wipe()
|
|
|
|
os.mkdir(self.builddir)
|
|
|
|
# Change cross file to use a non-existing exe_wrapper and it should fail
|
|
|
|
self.meson_cross_file = os.path.join(testdir, 'broken-cross.txt')
|
|
|
|
# Force tracebacks so we can detect them properly
|
|
|
|
env = {'MESON_FORCE_BACKTRACE': '1'}
|
|
|
|
with self.assertRaisesRegex(MesonException, 'exe_wrapper.*target.*use-exe-wrapper'):
|
|
|
|
# Must run in-process or we'll get a generic CalledProcessError
|
|
|
|
self.init(testdir, extra_args='-Drun-target=false',
|
|
|
|
inprocess=True,
|
|
|
|
override_envvars=env)
|
|
|
|
with self.assertRaisesRegex(MesonException, 'exe_wrapper.*run target.*run-prog'):
|
|
|
|
# Must run in-process or we'll get a generic CalledProcessError
|
|
|
|
self.init(testdir, extra_args='-Dcustom-target=false',
|
|
|
|
inprocess=True,
|
|
|
|
override_envvars=env)
|
|
|
|
self.init(testdir, extra_args=['-Dcustom-target=false', '-Drun-target=false'],
|
|
|
|
override_envvars=env)
|
|
|
|
self.build()
|
|
|
|
with self.assertRaisesRegex(MesonException, 'exe_wrapper.*PATH'):
|
|
|
|
# Must run in-process or we'll get a generic CalledProcessError
|
|
|
|
self.run_tests(inprocess=True, override_envvars=env)
|
|
|
|
|
|
|
|
@skipIfNoPkgconfig
|
|
|
|
def test_cross_pkg_config_option(self):
|
|
|
|
testdir = os.path.join(self.unit_test_dir, '58 pkg_config_path option')
|
|
|
|
self.init(testdir, extra_args=[
|
|
|
|
'-Dbuild.pkg_config_path=' + os.path.join(testdir, 'build_extra_path'),
|
|
|
|
'-Dpkg_config_path=' + os.path.join(testdir, 'host_extra_path'),
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
class PythonTests(BasePlatformTests):
|
|
|
|
'''
|
|
|
|
Tests that verify compilation of python extension modules
|
|
|
|
'''
|
|
|
|
|
|
|
|
def test_versions(self):
|
|
|
|
if self.backend is not Backend.ninja:
|
|
|
|
raise unittest.SkipTest('Skipping python tests with {} backend'.format(self.backend.name))
|
|
|
|
|
|
|
|
testdir = os.path.join(self.src_root, 'test cases', 'unit', '39 python extmodule')
|
|
|
|
|
|
|
|
# No python version specified, this will use meson's python
|
|
|
|
self.init(testdir)
|
|
|
|
self.build()
|
|
|
|
self.run_tests()
|
|
|
|
self.wipe()
|
|
|
|
|
|
|
|
# When specifying a known name, (python2 / python3) the module
|
|
|
|
# will also try 'python' as a fallback and use it if the major
|
|
|
|
# version matches
|
|
|
|
try:
|
|
|
|
self.init(testdir, extra_args=['-Dpython=python2'])
|
|
|
|
self.build()
|
|
|
|
self.run_tests()
|
|
|
|
except unittest.SkipTest:
|
|
|
|
# python2 is not necessarily installed on the test machine,
|
|
|
|
# if it is not, or the python headers can't be found, the test
|
|
|
|
# will raise MESON_SKIP_TEST, we could check beforehand what version
|
|
|
|
# of python is available, but it's a bit of a chicken and egg situation,
|
|
|
|
# as that is the job of the module, so we just ask for forgiveness rather
|
|
|
|
# than permission.
|
|
|
|
pass
|
|
|
|
|
|
|
|
self.wipe()
|
|
|
|
|
|
|
|
for py in ('pypy', 'pypy3'):
|
|
|
|
try:
|
|
|
|
self.init(testdir, extra_args=['-Dpython=%s' % py])
|
|
|
|
except unittest.SkipTest:
|
|
|
|
# Same as above, pypy2 and pypy3 are not expected to be present
|
|
|
|
# on the test system, the test project only raises in these cases
|
|
|
|
continue
|
|
|
|
|
|
|
|
# We have a pypy, this is expected to work
|
|
|
|
self.build()
|
|
|
|
self.run_tests()
|
|
|
|
self.wipe()
|
|
|
|
|
|
|
|
# The test is configured to error out with MESON_SKIP_TEST
|
|
|
|
# in case it could not find python
|
|
|
|
with self.assertRaises(unittest.SkipTest):
|
|
|
|
self.init(testdir, extra_args=['-Dpython=not-python'])
|
|
|
|
self.wipe()
|
|
|
|
|
|
|
|
# While dir is an external command on both Windows and Linux,
|
|
|
|
# it certainly isn't python
|
|
|
|
with self.assertRaises(unittest.SkipTest):
|
|
|
|
self.init(testdir, extra_args=['-Dpython=dir'])
|
|
|
|
self.wipe()
|
|
|
|
|
|
|
|
|
|
|
|
class RewriterTests(BasePlatformTests):
|
|
|
|
def setUp(self):
|
|
|
|
super().setUp()
|
|
|
|
self.maxDiff = None
|
|
|
|
|
|
|
|
def prime(self, dirname):
|
|
|
|
copy_tree(os.path.join(self.rewrite_test_dir, dirname), self.builddir)
|
|
|
|
|
|
|
|
def rewrite_raw(self, directory, args):
|
|
|
|
if isinstance(args, str):
|
|
|
|
args = [args]
|
|
|
|
command = self.rewrite_command + ['--verbose', '--skip', '--sourcedir', directory] + args
|
|
|
|
p = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
|
|
|
universal_newlines=True, timeout=60)
|
|
|
|
print('STDOUT:')
|
|
|
|
print(p.stdout)
|
|
|
|
print('STDERR:')
|
|
|
|
print(p.stderr)
|
|
|
|
if p.returncode != 0:
|
|
|
|
if 'MESON_SKIP_TEST' in p.stdout:
|
|
|
|
raise unittest.SkipTest('Project requested skipping.')
|
|
|
|
raise subprocess.CalledProcessError(p.returncode, command, output=p.stdout)
|
|
|
|
if not p.stderr:
|
|
|
|
return {}
|
|
|
|
return json.loads(p.stderr)
|
|
|
|
|
|
|
|
def rewrite(self, directory, args):
|
|
|
|
if isinstance(args, str):
|
|
|
|
args = [args]
|
|
|
|
return self.rewrite_raw(directory, ['command'] + args)
|
|
|
|
|
|
|
|
def test_target_source_list(self):
|
|
|
|
self.prime('1 basic')
|
|
|
|
out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json'))
|
|
|
|
expected = {
|
|
|
|
'target': {
|
|
|
|
'trivialprog0@exe': {'name': 'trivialprog0', 'sources': ['main.cpp', 'fileA.cpp', 'fileB.cpp', 'fileC.cpp']},
|
|
|
|
'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['main.cpp', 'fileA.cpp']},
|
|
|
|
'trivialprog2@exe': {'name': 'trivialprog2', 'sources': ['fileB.cpp', 'fileC.cpp']},
|
|
|
|
'trivialprog3@exe': {'name': 'trivialprog3', 'sources': ['main.cpp', 'fileA.cpp']},
|
|
|
|
'trivialprog4@exe': {'name': 'trivialprog4', 'sources': ['main.cpp', 'fileA.cpp']},
|
|
|
|
'trivialprog5@exe': {'name': 'trivialprog5', 'sources': ['main.cpp', 'fileB.cpp', 'fileC.cpp']},
|
|
|
|
'trivialprog6@exe': {'name': 'trivialprog6', 'sources': ['main.cpp', 'fileA.cpp']},
|
|
|
|
'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['fileB.cpp', 'fileC.cpp', 'main.cpp', 'fileA.cpp']},
|
|
|
|
'trivialprog8@exe': {'name': 'trivialprog8', 'sources': ['main.cpp', 'fileA.cpp']},
|
|
|
|
'trivialprog9@exe': {'name': 'trivialprog9', 'sources': ['main.cpp', 'fileA.cpp']},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.assertDictEqual(out, expected)
|
|
|
|
|
|
|
|
def test_target_add_sources(self):
|
|
|
|
self.prime('1 basic')
|
|
|
|
out = self.rewrite(self.builddir, os.path.join(self.builddir, 'addSrc.json'))
|
|
|
|
expected = {
|
|
|
|
'target': {
|
|
|
|
'trivialprog0@exe': {'name': 'trivialprog0', 'sources': ['a1.cpp', 'a2.cpp', 'a6.cpp', 'fileA.cpp', 'main.cpp', 'a7.cpp', 'fileB.cpp', 'fileC.cpp']},
|
|
|
|
'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['a1.cpp', 'a2.cpp', 'a6.cpp', 'fileA.cpp', 'main.cpp']},
|
|
|
|
'trivialprog2@exe': {'name': 'trivialprog2', 'sources': ['a7.cpp', 'fileB.cpp', 'fileC.cpp']},
|
|
|
|
'trivialprog3@exe': {'name': 'trivialprog3', 'sources': ['a5.cpp', 'fileA.cpp', 'main.cpp']},
|
|
|
|
'trivialprog4@exe': {'name': 'trivialprog4', 'sources': ['a5.cpp', 'main.cpp', 'fileA.cpp']},
|
|
|
|
'trivialprog5@exe': {'name': 'trivialprog5', 'sources': ['a3.cpp', 'main.cpp', 'a7.cpp', 'fileB.cpp', 'fileC.cpp']},
|
|
|
|
'trivialprog6@exe': {'name': 'trivialprog6', 'sources': ['main.cpp', 'fileA.cpp', 'a4.cpp']},
|
|
|
|
'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['fileB.cpp', 'fileC.cpp', 'a1.cpp', 'a2.cpp', 'a6.cpp', 'fileA.cpp', 'main.cpp']},
|
|
|
|
'trivialprog8@exe': {'name': 'trivialprog8', 'sources': ['a1.cpp', 'a2.cpp', 'a6.cpp', 'fileA.cpp', 'main.cpp']},
|
|
|
|
'trivialprog9@exe': {'name': 'trivialprog9', 'sources': ['a1.cpp', 'a2.cpp', 'a6.cpp', 'fileA.cpp', 'main.cpp']},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.assertDictEqual(out, expected)
|
|
|
|
|
|
|
|
# Check the written file
|
|
|
|
out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json'))
|
|
|
|
self.assertDictEqual(out, expected)
|
|
|
|
|
|
|
|
def test_target_add_sources_abs(self):
|
|
|
|
self.prime('1 basic')
|
|
|
|
abs_src = [os.path.join(self.builddir, x) for x in ['a1.cpp', 'a2.cpp', 'a6.cpp']]
|
|
|
|
add = json.dumps([{"type": "target", "target": "trivialprog1", "operation": "src_add", "sources": abs_src}])
|
|
|
|
inf = json.dumps([{"type": "target", "target": "trivialprog1", "operation": "info"}])
|
|
|
|
self.rewrite(self.builddir, add)
|
|
|
|
out = self.rewrite(self.builddir, inf)
|
|
|
|
expected = {'target': {'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['a1.cpp', 'a2.cpp', 'a6.cpp', 'fileA.cpp', 'main.cpp']}}}
|
|
|
|
self.assertDictEqual(out, expected)
|
|
|
|
|
|
|
|
def test_target_remove_sources(self):
|
|
|
|
self.prime('1 basic')
|
|
|
|
out = self.rewrite(self.builddir, os.path.join(self.builddir, 'rmSrc.json'))
|
|
|
|
expected = {
|
|
|
|
'target': {
|
|
|
|
'trivialprog0@exe': {'name': 'trivialprog0', 'sources': ['main.cpp', 'fileC.cpp']},
|
|
|
|
'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['main.cpp']},
|
|
|
|
'trivialprog2@exe': {'name': 'trivialprog2', 'sources': ['fileC.cpp']},
|
|
|
|
'trivialprog3@exe': {'name': 'trivialprog3', 'sources': ['main.cpp']},
|
|
|
|
'trivialprog4@exe': {'name': 'trivialprog4', 'sources': ['main.cpp']},
|
|
|
|
'trivialprog5@exe': {'name': 'trivialprog5', 'sources': ['main.cpp', 'fileC.cpp']},
|
|
|
|
'trivialprog6@exe': {'name': 'trivialprog6', 'sources': ['main.cpp']},
|
|
|
|
'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['fileC.cpp', 'main.cpp']},
|
|
|
|
'trivialprog8@exe': {'name': 'trivialprog8', 'sources': ['main.cpp']},
|
|
|
|
'trivialprog9@exe': {'name': 'trivialprog9', 'sources': ['main.cpp']},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.assertDictEqual(out, expected)
|
|
|
|
|
|
|
|
# Check the written file
|
|
|
|
out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json'))
|
|
|
|
self.assertDictEqual(out, expected)
|
|
|
|
|
|
|
|
def test_target_subdir(self):
|
|
|
|
self.prime('2 subdirs')
|
|
|
|
out = self.rewrite(self.builddir, os.path.join(self.builddir, 'addSrc.json'))
|
|
|
|
expected = {'name': 'something', 'sources': ['first.c', 'second.c', 'third.c']}
|
|
|
|
self.assertDictEqual(list(out['target'].values())[0], expected)
|
|
|
|
|
|
|
|
# Check the written file
|
|
|
|
out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json'))
|
|
|
|
self.assertDictEqual(list(out['target'].values())[0], expected)
|
|
|
|
|
|
|
|
def test_target_remove(self):
|
|
|
|
self.prime('1 basic')
|
|
|
|
self.rewrite(self.builddir, os.path.join(self.builddir, 'rmTgt.json'))
|
|
|
|
out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json'))
|
|
|
|
|
|
|
|
expected = {
|
|
|
|
'target': {
|
|
|
|
'trivialprog2@exe': {'name': 'trivialprog2', 'sources': ['fileB.cpp', 'fileC.cpp']},
|
|
|
|
'trivialprog3@exe': {'name': 'trivialprog3', 'sources': ['main.cpp', 'fileA.cpp']},
|
|
|
|
'trivialprog4@exe': {'name': 'trivialprog4', 'sources': ['main.cpp', 'fileA.cpp']},
|
|
|
|
'trivialprog5@exe': {'name': 'trivialprog5', 'sources': ['main.cpp', 'fileB.cpp', 'fileC.cpp']},
|
|
|
|
'trivialprog6@exe': {'name': 'trivialprog6', 'sources': ['main.cpp', 'fileA.cpp']},
|
|
|
|
'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['fileB.cpp', 'fileC.cpp', 'main.cpp', 'fileA.cpp']},
|
|
|
|
'trivialprog8@exe': {'name': 'trivialprog8', 'sources': ['main.cpp', 'fileA.cpp']},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.assertDictEqual(out, expected)
|
|
|
|
|
|
|
|
def test_tatrget_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'))
|
|
|
|
|
|
|
|
expected = {
|
|
|
|
'target': {
|
|
|
|
'trivialprog0@exe': {'name': 'trivialprog0', 'sources': ['main.cpp', 'fileA.cpp', 'fileB.cpp', 'fileC.cpp']},
|
|
|
|
'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['main.cpp', 'fileA.cpp']},
|
|
|
|
'trivialprog2@exe': {'name': 'trivialprog2', 'sources': ['fileB.cpp', 'fileC.cpp']},
|
|
|
|
'trivialprog3@exe': {'name': 'trivialprog3', 'sources': ['main.cpp', 'fileA.cpp']},
|
|
|
|
'trivialprog4@exe': {'name': 'trivialprog4', 'sources': ['main.cpp', 'fileA.cpp']},
|
|
|
|
'trivialprog5@exe': {'name': 'trivialprog5', 'sources': ['main.cpp', 'fileB.cpp', 'fileC.cpp']},
|
|
|
|
'trivialprog6@exe': {'name': 'trivialprog6', 'sources': ['main.cpp', 'fileA.cpp']},
|
|
|
|
'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['fileB.cpp', 'fileC.cpp', 'main.cpp', 'fileA.cpp']},
|
|
|
|
'trivialprog8@exe': {'name': 'trivialprog8', 'sources': ['main.cpp', 'fileA.cpp']},
|
|
|
|
'trivialprog9@exe': {'name': 'trivialprog9', 'sources': ['main.cpp', 'fileA.cpp']},
|
|
|
|
'trivialprog10@sha': {'name': 'trivialprog10', 'sources': ['new1.cpp', 'new2.cpp']},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.assertDictEqual(out, expected)
|
|
|
|
|
|
|
|
def test_target_remove_subdir(self):
|
|
|
|
self.prime('2 subdirs')
|
|
|
|
self.rewrite(self.builddir, os.path.join(self.builddir, 'rmTgt.json'))
|
|
|
|
out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json'))
|
|
|
|
self.assertDictEqual(out, {})
|
|
|
|
|
|
|
|
def test_target_add_subdir(self):
|
|
|
|
self.prime('2 subdirs')
|
|
|
|
self.rewrite(self.builddir, os.path.join(self.builddir, 'addTgt.json'))
|
|
|
|
out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json'))
|
|
|
|
expected = {'name': 'something', 'sources': ['first.c', 'second.c']}
|
|
|
|
self.assertDictEqual(out['target']['94b671c@@something@exe'], expected)
|
|
|
|
|
|
|
|
def test_target_source_sorting(self):
|
|
|
|
self.prime('5 sorting')
|
|
|
|
add_json = json.dumps([{'type': 'target', 'target': 'exe1', 'operation': 'src_add', 'sources': ['a666.c']}])
|
|
|
|
inf_json = json.dumps([{'type': 'target', 'target': 'exe1', 'operation': 'info'}])
|
|
|
|
out = self.rewrite(self.builddir, add_json)
|
|
|
|
out = self.rewrite(self.builddir, inf_json)
|
|
|
|
expected = {
|
|
|
|
'target': {
|
|
|
|
'exe1@exe': {
|
|
|
|
'name': 'exe1',
|
|
|
|
'sources': [
|
|
|
|
'aaa/a/a1.c',
|
|
|
|
'aaa/b/b1.c',
|
|
|
|
'aaa/b/b2.c',
|
|
|
|
'aaa/f1.c',
|
|
|
|
'aaa/f2.c',
|
|
|
|
'aaa/f3.c',
|
|
|
|
'bbb/a/b1.c',
|
|
|
|
'bbb/b/b2.c',
|
|
|
|
'bbb/c1/b5.c',
|
|
|
|
'bbb/c2/b7.c',
|
|
|
|
'bbb/c10/b6.c',
|
|
|
|
'bbb/a4.c',
|
|
|
|
'bbb/b3.c',
|
|
|
|
'bbb/b4.c',
|
|
|
|
'bbb/b5.c',
|
|
|
|
'a1.c',
|
|
|
|
'a2.c',
|
|
|
|
'a3.c',
|
|
|
|
'a10.c',
|
|
|
|
'a20.c',
|
|
|
|
'a30.c',
|
|
|
|
'a100.c',
|
|
|
|
'a101.c',
|
|
|
|
'a110.c',
|
|
|
|
'a210.c',
|
|
|
|
'a666.c',
|
|
|
|
'b1.c',
|
|
|
|
'c2.c'
|
|
|
|
]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.assertDictEqual(out, expected)
|
|
|
|
|
|
|
|
def test_target_same_name_skip(self):
|
|
|
|
self.prime('4 same name targets')
|
|
|
|
out = self.rewrite(self.builddir, os.path.join(self.builddir, 'addSrc.json'))
|
|
|
|
out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json'))
|
|
|
|
expected = {'name': 'myExe', 'sources': ['main.cpp']}
|
|
|
|
self.assertEqual(len(out['target']), 2)
|
|
|
|
for val in out['target'].values():
|
|
|
|
self.assertDictEqual(expected, val)
|
|
|
|
|
|
|
|
def test_kwargs_info(self):
|
|
|
|
self.prime('3 kwargs')
|
|
|
|
out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json'))
|
|
|
|
expected = {
|
|
|
|
'kwargs': {
|
|
|
|
'project#/': {'version': '0.0.1'},
|
|
|
|
'target#tgt1': {'build_by_default': True},
|
|
|
|
'dependency#dep1': {'required': False}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.assertDictEqual(out, expected)
|
|
|
|
|
|
|
|
def test_kwargs_set(self):
|
|
|
|
self.prime('3 kwargs')
|
|
|
|
self.rewrite(self.builddir, os.path.join(self.builddir, 'set.json'))
|
|
|
|
out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json'))
|
|
|
|
expected = {
|
|
|
|
'kwargs': {
|
|
|
|
'project#/': {'version': '0.0.2', 'meson_version': '0.50.0', 'license': ['GPL', 'MIT']},
|
|
|
|
'target#tgt1': {'build_by_default': False, 'build_rpath': '/usr/local', 'dependencies': 'dep1'},
|
|
|
|
'dependency#dep1': {'required': True, 'method': 'cmake'}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.assertDictEqual(out, expected)
|
|
|
|
|
|
|
|
def test_kwargs_add(self):
|
|
|
|
self.prime('3 kwargs')
|
|
|
|
self.rewrite(self.builddir, os.path.join(self.builddir, 'add.json'))
|
|
|
|
out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json'))
|
|
|
|
expected = {
|
|
|
|
'kwargs': {
|
|
|
|
'project#/': {'version': '0.0.1', 'license': ['GPL', 'MIT', 'BSD']},
|
|
|
|
'target#tgt1': {'build_by_default': True},
|
|
|
|
'dependency#dep1': {'required': False}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.assertDictEqual(out, expected)
|
|
|
|
|
|
|
|
def test_kwargs_remove(self):
|
|
|
|
self.prime('3 kwargs')
|
|
|
|
self.rewrite(self.builddir, os.path.join(self.builddir, 'remove.json'))
|
|
|
|
out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json'))
|
|
|
|
expected = {
|
|
|
|
'kwargs': {
|
|
|
|
'project#/': {'version': '0.0.1', 'license': 'GPL'},
|
|
|
|
'target#tgt1': {'build_by_default': True},
|
|
|
|
'dependency#dep1': {'required': False}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.assertDictEqual(out, expected)
|
|
|
|
|
|
|
|
def test_kwargs_remove_regex(self):
|
|
|
|
self.prime('3 kwargs')
|
|
|
|
self.rewrite(self.builddir, os.path.join(self.builddir, 'remove_regex.json'))
|
|
|
|
out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json'))
|
|
|
|
expected = {
|
|
|
|
'kwargs': {
|
|
|
|
'project#/': {'version': '0.0.1', 'default_options': ['buildtype=release', 'debug=true']},
|
|
|
|
'target#tgt1': {'build_by_default': True},
|
|
|
|
'dependency#dep1': {'required': False}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.assertDictEqual(out, expected)
|
|
|
|
|
|
|
|
def test_kwargs_delete(self):
|
|
|
|
self.prime('3 kwargs')
|
|
|
|
self.rewrite(self.builddir, os.path.join(self.builddir, 'delete.json'))
|
|
|
|
out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json'))
|
|
|
|
expected = {
|
|
|
|
'kwargs': {
|
|
|
|
'project#/': {},
|
|
|
|
'target#tgt1': {},
|
|
|
|
'dependency#dep1': {'required': False}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.assertDictEqual(out, expected)
|
|
|
|
|
|
|
|
def test_default_options_set(self):
|
|
|
|
self.prime('3 kwargs')
|
|
|
|
self.rewrite(self.builddir, os.path.join(self.builddir, 'defopts_set.json'))
|
|
|
|
out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json'))
|
|
|
|
expected = {
|
|
|
|
'kwargs': {
|
|
|
|
'project#/': {'version': '0.0.1', 'default_options': ['buildtype=release', 'debug=True', 'cpp_std=c++11']},
|
|
|
|
'target#tgt1': {'build_by_default': True},
|
|
|
|
'dependency#dep1': {'required': False}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.assertDictEqual(out, expected)
|
|
|
|
|
|
|
|
def test_default_options_delete(self):
|
|
|
|
self.prime('3 kwargs')
|
|
|
|
self.rewrite(self.builddir, os.path.join(self.builddir, 'defopts_delete.json'))
|
|
|
|
out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json'))
|
|
|
|
expected = {
|
|
|
|
'kwargs': {
|
|
|
|
'project#/': {'version': '0.0.1', 'default_options': ['cpp_std=c++14', 'debug=true']},
|
|
|
|
'target#tgt1': {'build_by_default': True},
|
|
|
|
'dependency#dep1': {'required': False}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.assertDictEqual(out, expected)
|
|
|
|
|
|
|
|
class NativeFileTests(BasePlatformTests):
|
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
super().setUp()
|
|
|
|
self.testcase = os.path.join(self.unit_test_dir, '47 native file binary')
|
|
|
|
self.current_config = 0
|
|
|
|
self.current_wrapper = 0
|
|
|
|
|
|
|
|
def helper_create_native_file(self, values):
|
|
|
|
"""Create a config file as a temporary file.
|
|
|
|
|
|
|
|
values should be a nested dictionary structure of {section: {key:
|
|
|
|
value}}
|
|
|
|
"""
|
|
|
|
filename = os.path.join(self.builddir, 'generated{}.config'.format(self.current_config))
|
|
|
|
self.current_config += 1
|
|
|
|
with open(filename, 'wt') as f:
|
|
|
|
for section, entries in values.items():
|
|
|
|
f.write('[{}]\n'.format(section))
|
|
|
|
for k, v in entries.items():
|
|
|
|
f.write("{}='{}'\n".format(k, v))
|
|
|
|
return filename
|
|
|
|
|
|
|
|
def helper_create_binary_wrapper(self, binary, dir_=None, extra_args=None, **kwargs):
|
|
|
|
"""Creates a wrapper around a binary that overrides specific values."""
|
|
|
|
filename = os.path.join(dir_ or self.builddir, 'binary_wrapper{}.py'.format(self.current_wrapper))
|
|
|
|
extra_args = extra_args or {}
|
|
|
|
self.current_wrapper += 1
|
|
|
|
if is_haiku():
|
|
|
|
chbang = '#!/bin/env python3'
|
|
|
|
else:
|
|
|
|
chbang = '#!/usr/bin/env python3'
|
|
|
|
|
|
|
|
with open(filename, 'wt') as f:
|
|
|
|
f.write(textwrap.dedent('''\
|
|
|
|
{}
|
|
|
|
import argparse
|
|
|
|
import subprocess
|
|
|
|
import sys
|
|
|
|
|
|
|
|
def main():
|
|
|
|
parser = argparse.ArgumentParser()
|
|
|
|
'''.format(chbang)))
|
|
|
|
for name in chain(extra_args, kwargs):
|
|
|
|
f.write(' parser.add_argument("-{0}", "--{0}", action="store_true")\n'.format(name))
|
|
|
|
f.write(' args, extra_args = parser.parse_known_args()\n')
|
|
|
|
for name, value in chain(extra_args.items(), kwargs.items()):
|
|
|
|
f.write(' if args.{}:\n'.format(name))
|
|
|
|
f.write(' print("{}", file=sys.{})\n'.format(value, kwargs.get('outfile', 'stdout')))
|
|
|
|
f.write(' sys.exit(0)\n')
|
|
|
|
f.write(textwrap.dedent('''
|
|
|
|
ret = subprocess.run(
|
|
|
|
["{}"] + extra_args,
|
|
|
|
stdout=subprocess.PIPE,
|
|
|
|
stderr=subprocess.PIPE)
|
|
|
|
print(ret.stdout.decode('utf-8'))
|
|
|
|
print(ret.stderr.decode('utf-8'), file=sys.stderr)
|
|
|
|
sys.exit(ret.returncode)
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|
|
|
|
'''.format(binary)))
|
|
|
|
|
|
|
|
if not is_windows():
|
|
|
|
os.chmod(filename, 0o755)
|
|
|
|
return filename
|
|
|
|
|
|
|
|
# On windows we need yet another level of indirection, as cmd cannot
|
|
|
|
# invoke python files itself, so instead we generate a .bat file, which
|
|
|
|
# invokes our python wrapper
|
|
|
|
batfile = os.path.join(self.builddir, 'binary_wrapper{}.bat'.format(self.current_wrapper))
|
|
|
|
with open(batfile, 'wt') as f:
|
|
|
|
f.write(r'@{} {} %*'.format(sys.executable, filename))
|
|
|
|
return batfile
|
|
|
|
|
|
|
|
def helper_for_compiler(self, lang, cb, for_machine = MachineChoice.HOST):
|
|
|
|
"""Helper for generating tests for overriding compilers for langaugages
|
|
|
|
with more than one implementation, such as C, C++, ObjC, ObjC++, and D.
|
|
|
|
"""
|
|
|
|
env = get_fake_env()
|
|
|
|
getter = getattr(env, 'detect_{}_compiler'.format(lang))
|
|
|
|
getter = functools.partial(getter, for_machine)
|
|
|
|
cc = getter()
|
|
|
|
binary, newid = cb(cc)
|
|
|
|
env.binaries[for_machine].binaries[lang] = binary
|
|
|
|
compiler = getter()
|
|
|
|
self.assertEqual(compiler.id, newid)
|
|
|
|
|
|
|
|
def test_multiple_native_files_override(self):
|
|
|
|
wrapper = self.helper_create_binary_wrapper('bash', version='foo')
|
|
|
|
config = self.helper_create_native_file({'binaries': {'bash': wrapper}})
|
|
|
|
wrapper = self.helper_create_binary_wrapper('bash', version='12345')
|
|
|
|
config2 = self.helper_create_native_file({'binaries': {'bash': wrapper}})
|
|
|
|
self.init(self.testcase, extra_args=[
|
|
|
|
'--native-file', config, '--native-file', config2,
|
|
|
|
'-Dcase=find_program'])
|
|
|
|
|
|
|
|
# This test hangs on cygwin.
|
|
|
|
@unittest.skipIf(os.name != 'posix' or is_cygwin(), 'Uses fifos, which are not available on non Unix OSes.')
|
|
|
|
def test_native_file_is_pipe(self):
|
|
|
|
fifo = os.path.join(self.builddir, 'native.file')
|
|
|
|
os.mkfifo(fifo)
|
|
|
|
with tempfile.TemporaryDirectory() as d:
|
|
|
|
wrapper = self.helper_create_binary_wrapper('bash', d, version='12345')
|
|
|
|
|
|
|
|
def filler():
|
|
|
|
with open(fifo, 'w') as f:
|
|
|
|
f.write('[binaries]\n')
|
|
|
|
f.write("bash = '{}'\n".format(wrapper))
|
|
|
|
|
|
|
|
thread = threading.Thread(target=filler)
|
|
|
|
thread.start()
|
|
|
|
|
|
|
|
self.init(self.testcase, extra_args=['--native-file', fifo, '-Dcase=find_program'])
|
|
|
|
|
|
|
|
thread.join()
|
|
|
|
os.unlink(fifo)
|
|
|
|
|
|
|
|
self.init(self.testcase, extra_args=['--wipe'])
|
|
|
|
|
|
|
|
def test_multiple_native_files(self):
|
|
|
|
wrapper = self.helper_create_binary_wrapper('bash', version='12345')
|
|
|
|
config = self.helper_create_native_file({'binaries': {'bash': wrapper}})
|
|
|
|
wrapper = self.helper_create_binary_wrapper('python')
|
|
|
|
config2 = self.helper_create_native_file({'binaries': {'python': wrapper}})
|
|
|
|
self.init(self.testcase, extra_args=[
|
|
|
|
'--native-file', config, '--native-file', config2,
|
|
|
|
'-Dcase=find_program'])
|
|
|
|
|
|
|
|
def _simple_test(self, case, binary):
|
|
|
|
wrapper = self.helper_create_binary_wrapper(binary, version='12345')
|
|
|
|
config = self.helper_create_native_file({'binaries': {binary: wrapper}})
|
|
|
|
self.init(self.testcase, extra_args=['--native-file', config, '-Dcase={}'.format(case)])
|
|
|
|
|
|
|
|
def test_find_program(self):
|
|
|
|
self._simple_test('find_program', 'bash')
|
|
|
|
|
|
|
|
def test_config_tool_dep(self):
|
|
|
|
# Do the skip at this level to avoid screwing up the cache
|
|
|
|
if mesonbuild.environment.detect_msys2_arch():
|
|
|
|
raise unittest.SkipTest('Skipped due to problems with LLVM on MSYS2')
|
|
|
|
if not shutil.which('llvm-config'):
|
|
|
|
raise unittest.SkipTest('No llvm-installed, cannot test')
|
|
|
|
self._simple_test('config_dep', 'llvm-config')
|
|
|
|
|
|
|
|
def test_python3_module(self):
|
|
|
|
self._simple_test('python3', 'python3')
|
|
|
|
|
|
|
|
def test_python_module(self):
|
|
|
|
if is_windows():
|
|
|
|
# Bat adds extra crap to stdout, so the version check logic in the
|
|
|
|
# python module breaks. This is fine on other OSes because they
|
|
|
|
# don't need the extra indirection.
|
|
|
|
raise unittest.SkipTest('bat indirection breaks internal sanity checks.')
|
|
|
|
if os.path.exists('/etc/debian_version'):
|
|
|
|
rc = subprocess.call(['pkg-config', '--cflags', 'python2'],
|
|
|
|
stdout=subprocess.DEVNULL,
|
|
|
|
stderr=subprocess.DEVNULL)
|
|
|
|
if rc != 0:
|
|
|
|
# Python 2 will be removed in Debian Bullseye, thus we must
|
|
|
|
# remove the build dependency on python2-dev. Keep the tests
|
|
|
|
# but only run them if dev packages are available.
|
|
|
|
raise unittest.SkipTest('Not running Python 2 tests because dev packages not installed.')
|
|
|
|
self._simple_test('python', 'python')
|
|
|
|
|
|
|
|
@unittest.skipIf(is_windows(), 'Setting up multiple compilers on windows is hard')
|
|
|
|
@skip_if_env_set('CC')
|
|
|
|
def test_c_compiler(self):
|
|
|
|
def cb(comp):
|
|
|
|
if comp.id == 'gcc':
|
|
|
|
if not shutil.which('clang'):
|
|
|
|
raise unittest.SkipTest('Only one compiler found, cannot test.')
|
|
|
|
return 'clang', 'clang'
|
|
|
|
if not is_real_gnu_compiler(shutil.which('gcc')):
|
|
|
|
raise unittest.SkipTest('Only one compiler found, cannot test.')
|
|
|
|
return 'gcc', 'gcc'
|
|
|
|
self.helper_for_compiler('c', cb)
|
|
|
|
|
|
|
|
@unittest.skipIf(is_windows(), 'Setting up multiple compilers on windows is hard')
|
|
|
|
@skip_if_env_set('CXX')
|
|
|
|
def test_cpp_compiler(self):
|
|
|
|
def cb(comp):
|
|
|
|
if comp.id == 'gcc':
|
|
|
|
if not shutil.which('clang++'):
|
|
|
|
raise unittest.SkipTest('Only one compiler found, cannot test.')
|
|
|
|
return 'clang++', 'clang'
|
|
|
|
if not is_real_gnu_compiler(shutil.which('g++')):
|
|
|
|
raise unittest.SkipTest('Only one compiler found, cannot test.')
|
|
|
|
return 'g++', 'gcc'
|
|
|
|
self.helper_for_compiler('cpp', cb)
|
|
|
|
|
|
|
|
@skip_if_not_language('objc')
|
|
|
|
@skip_if_env_set('OBJC')
|
|
|
|
def test_objc_compiler(self):
|
|
|
|
def cb(comp):
|
|
|
|
if comp.id == 'gcc':
|
|
|
|
if not shutil.which('clang'):
|
|
|
|
raise unittest.SkipTest('Only one compiler found, cannot test.')
|
|
|
|
return 'clang', 'clang'
|
|
|
|
if not is_real_gnu_compiler(shutil.which('gcc')):
|
|
|
|
raise unittest.SkipTest('Only one compiler found, cannot test.')
|
|
|
|
return 'gcc', 'gcc'
|
|
|
|
self.helper_for_compiler('objc', cb)
|
|
|
|
|
|
|
|
@skip_if_not_language('objcpp')
|
|
|
|
@skip_if_env_set('OBJCXX')
|
|
|
|
def test_objcpp_compiler(self):
|
|
|
|
def cb(comp):
|
|
|
|
if comp.id == 'gcc':
|
|
|
|
if not shutil.which('clang++'):
|
|
|
|
raise unittest.SkipTest('Only one compiler found, cannot test.')
|
|
|
|
return 'clang++', 'clang'
|
|
|
|
if not is_real_gnu_compiler(shutil.which('g++')):
|
|
|
|
raise unittest.SkipTest('Only one compiler found, cannot test.')
|
|
|
|
return 'g++', 'gcc'
|
|
|
|
self.helper_for_compiler('objcpp', cb)
|
|
|
|
|
|
|
|
@skip_if_not_language('d')
|
|
|
|
@skip_if_env_set('DC')
|
|
|
|
def test_d_compiler(self):
|
|
|
|
def cb(comp):
|
|
|
|
if comp.id == 'dmd':
|
|
|
|
if shutil.which('ldc'):
|
|
|
|
return 'ldc', 'ldc'
|
|
|
|
elif shutil.which('gdc'):
|
|
|
|
return 'gdc', 'gdc'
|
|
|
|
else:
|
|
|
|
raise unittest.SkipTest('No alternative dlang compiler found.')
|
|
|
|
if shutil.which('dmd'):
|
|
|
|
return 'dmd', 'dmd'
|
|
|
|
raise unittest.SkipTest('No alternative dlang compiler found.')
|
|
|
|
self.helper_for_compiler('d', cb)
|
|
|
|
|
|
|
|
@skip_if_not_language('cs')
|
|
|
|
@skip_if_env_set('CSC')
|
|
|
|
def test_cs_compiler(self):
|
|
|
|
def cb(comp):
|
|
|
|
if comp.id == 'csc':
|
|
|
|
if not shutil.which('mcs'):
|
|
|
|
raise unittest.SkipTest('No alternate C# implementation.')
|
|
|
|
return 'mcs', 'mcs'
|
|
|
|
if not shutil.which('csc'):
|
|
|
|
raise unittest.SkipTest('No alternate C# implementation.')
|
|
|
|
return 'csc', 'csc'
|
|
|
|
self.helper_for_compiler('cs', cb)
|
|
|
|
|
|
|
|
@skip_if_not_language('fortran')
|
|
|
|
@skip_if_env_set('FC')
|
|
|
|
def test_fortran_compiler(self):
|
|
|
|
def cb(comp):
|
|
|
|
if comp.id == 'lcc':
|
|
|
|
if shutil.which('lfortran'):
|
|
|
|
return 'lfortran', 'lcc'
|
|
|
|
raise unittest.SkipTest('No alternate Fortran implementation.')
|
|
|
|
elif comp.id == 'gcc':
|
|
|
|
if shutil.which('ifort'):
|
|
|
|
# There is an ICC for windows (windows build, linux host),
|
|
|
|
# but we don't support that ATM so lets not worry about it.
|
|
|
|
if is_windows():
|
|
|
|
return 'ifort', 'intel-cl'
|
|
|
|
return 'ifort', 'intel'
|
|
|
|
elif shutil.which('flang'):
|
|
|
|
return 'flang', 'flang'
|
|
|
|
elif shutil.which('pgfortran'):
|
|
|
|
return 'pgfortran', 'pgi'
|
|
|
|
# XXX: there are several other fortran compilers meson
|
|
|
|
# supports, but I don't have any of them to test with
|
|
|
|
raise unittest.SkipTest('No alternate Fortran implementation.')
|
|
|
|
if not shutil.which('gfortran'):
|
|
|
|
raise unittest.SkipTest('No alternate Fortran implementation.')
|
|
|
|
return 'gfortran', 'gcc'
|
|
|
|
self.helper_for_compiler('fortran', cb)
|
|
|
|
|
|
|
|
def _single_implementation_compiler(self, lang, binary, version_str, version):
|
|
|
|
"""Helper for languages with a single (supported) implementation.
|
|
|
|
|
|
|
|
Builds a wrapper around the compiler to override the version.
|
|
|
|
"""
|
|
|
|
wrapper = self.helper_create_binary_wrapper(binary, version=version_str)
|
|
|
|
env = get_fake_env()
|
|
|
|
getter = getattr(env, 'detect_{}_compiler'.format(lang))
|
|
|
|
getter = functools.partial(getter, MachineChoice.HOST)
|
|
|
|
env.binaries.host.binaries[lang] = wrapper
|
|
|
|
compiler = getter()
|
|
|
|
self.assertEqual(compiler.version, version)
|
|
|
|
|
|
|
|
@skip_if_not_language('vala')
|
|
|
|
@skip_if_env_set('VALAC')
|
|
|
|
def test_vala_compiler(self):
|
|
|
|
self._single_implementation_compiler(
|
|
|
|
'vala', 'valac', 'Vala 1.2345', '1.2345')
|
|
|
|
|
|
|
|
@skip_if_not_language('rust')
|
|
|
|
@skip_if_env_set('RUSTC')
|
|
|
|
def test_rust_compiler(self):
|
|
|
|
self._single_implementation_compiler(
|
|
|
|
'rust', 'rustc', 'rustc 1.2345', '1.2345')
|
|
|
|
|
|
|
|
@skip_if_not_language('java')
|
|
|
|
def test_java_compiler(self):
|
|
|
|
self._single_implementation_compiler(
|
|
|
|
'java', 'javac', 'javac 9.99.77', '9.99.77')
|
|
|
|
|
|
|
|
@skip_if_not_language('swift')
|
|
|
|
def test_swift_compiler(self):
|
|
|
|
wrapper = self.helper_create_binary_wrapper(
|
|
|
|
'swiftc', version='Swift 1.2345', outfile='stderr',
|
|
|
|
extra_args={'Xlinker': 'macosx_version. PROJECT:ld - 1.2.3'})
|
|
|
|
env = get_fake_env()
|
|
|
|
env.binaries.host.binaries['swift'] = wrapper
|
|
|
|
compiler = env.detect_swift_compiler(MachineChoice.HOST)
|
|
|
|
self.assertEqual(compiler.version, '1.2345')
|
|
|
|
|
|
|
|
def test_native_file_dirs(self):
|
|
|
|
testcase = os.path.join(self.unit_test_dir, '60 native file override')
|
|
|
|
self.init(testcase, default_args=False,
|
|
|
|
extra_args=['--native-file', os.path.join(testcase, 'nativefile')])
|
|
|
|
|
|
|
|
def test_native_file_dirs_overriden(self):
|
|
|
|
testcase = os.path.join(self.unit_test_dir, '60 native file override')
|
|
|
|
self.init(testcase, default_args=False,
|
|
|
|
extra_args=['--native-file', os.path.join(testcase, 'nativefile'),
|
|
|
|
'-Ddef_libdir=liblib', '-Dlibdir=liblib'])
|
|
|
|
|
|
|
|
def test_compile_sys_path(self):
|
|
|
|
"""Compiling with a native file stored in a system path works.
|
|
|
|
|
|
|
|
There was a bug which caused the paths to be stored incorrectly and
|
|
|
|
would result in ninja invoking meson in an infinite loop. This tests
|
|
|
|
for that by actually invoking ninja.
|
|
|
|
"""
|
|
|
|
testcase = os.path.join(self.common_test_dir, '1 trivial')
|
|
|
|
|
|
|
|
# It really doesn't matter what's in the native file, just that it exists
|
|
|
|
config = self.helper_create_native_file({'binaries': {'bash': 'false'}})
|
|
|
|
|
|
|
|
self.init(testcase, extra_args=['--native-file', config])
|
|
|
|
self.build()
|
|
|
|
|
|
|
|
|
|
|
|
class CrossFileTests(BasePlatformTests):
|
|
|
|
|
|
|
|
"""Tests for cross file functioality not directly related to
|
|
|
|
cross compiling.
|
|
|
|
|
|
|
|
This is mainly aimed to testing overrides from cross files.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def test_cross_file_dirs(self):
|
|
|
|
testcase = os.path.join(self.unit_test_dir, '60 native file override')
|
|
|
|
self.init(testcase, default_args=False,
|
|
|
|
extra_args=['--native-file', os.path.join(testcase, 'nativefile'),
|
|
|
|
'--cross-file', os.path.join(testcase, 'crossfile'),
|
|
|
|
'-Ddef_bindir=binbar',
|
|
|
|
'-Ddef_datadir=databar',
|
|
|
|
'-Ddef_includedir=includebar',
|
|
|
|
'-Ddef_infodir=infobar',
|
|
|
|
'-Ddef_libdir=libbar',
|
|
|
|
'-Ddef_libexecdir=libexecbar',
|
|
|
|
'-Ddef_localedir=localebar',
|
|
|
|
'-Ddef_localstatedir=localstatebar',
|
|
|
|
'-Ddef_mandir=manbar',
|
|
|
|
'-Ddef_sbindir=sbinbar',
|
|
|
|
'-Ddef_sharedstatedir=sharedstatebar',
|
|
|
|
'-Ddef_sysconfdir=sysconfbar'])
|
|
|
|
|
|
|
|
def test_cross_file_dirs_overriden(self):
|
|
|
|
testcase = os.path.join(self.unit_test_dir, '60 native file override')
|
|
|
|
self.init(testcase, default_args=False,
|
|
|
|
extra_args=['--native-file', os.path.join(testcase, 'nativefile'),
|
|
|
|
'--cross-file', os.path.join(testcase, 'crossfile'),
|
|
|
|
'-Ddef_libdir=liblib', '-Dlibdir=liblib',
|
|
|
|
'-Ddef_bindir=binbar',
|
|
|
|
'-Ddef_datadir=databar',
|
|
|
|
'-Ddef_includedir=includebar',
|
|
|
|
'-Ddef_infodir=infobar',
|
|
|
|
'-Ddef_libexecdir=libexecbar',
|
|
|
|
'-Ddef_localedir=localebar',
|
|
|
|
'-Ddef_localstatedir=localstatebar',
|
|
|
|
'-Ddef_mandir=manbar',
|
|
|
|
'-Ddef_sbindir=sbinbar',
|
|
|
|
'-Ddef_sharedstatedir=sharedstatebar',
|
|
|
|
'-Ddef_sysconfdir=sysconfbar'])
|
|
|
|
|
|
|
|
def test_cross_file_dirs_chain(self):
|
|
|
|
# crossfile2 overrides crossfile overrides nativefile
|
|
|
|
testcase = os.path.join(self.unit_test_dir, '60 native file override')
|
|
|
|
self.init(testcase, default_args=False,
|
|
|
|
extra_args=['--native-file', os.path.join(testcase, 'nativefile'),
|
|
|
|
'--cross-file', os.path.join(testcase, 'crossfile'),
|
|
|
|
'--cross-file', os.path.join(testcase, 'crossfile2'),
|
|
|
|
'-Ddef_bindir=binbar2',
|
|
|
|
'-Ddef_datadir=databar',
|
|
|
|
'-Ddef_includedir=includebar',
|
|
|
|
'-Ddef_infodir=infobar',
|
|
|
|
'-Ddef_libdir=libbar',
|
|
|
|
'-Ddef_libexecdir=libexecbar',
|
|
|
|
'-Ddef_localedir=localebar',
|
|
|
|
'-Ddef_localstatedir=localstatebar',
|
|
|
|
'-Ddef_mandir=manbar',
|
|
|
|
'-Ddef_sbindir=sbinbar',
|
|
|
|
'-Ddef_sharedstatedir=sharedstatebar',
|
|
|
|
'-Ddef_sysconfdir=sysconfbar'])
|
|
|
|
|
|
|
|
class TAPParserTests(unittest.TestCase):
|
|
|
|
def assert_test(self, events, **kwargs):
|
|
|
|
if 'explanation' not in kwargs:
|
|
|
|
kwargs['explanation'] = None
|
|
|
|
self.assertEqual(next(events), TAPParser.Test(**kwargs))
|
|
|
|
|
|
|
|
def assert_plan(self, events, **kwargs):
|
|
|
|
if 'skipped' not in kwargs:
|
|
|
|
kwargs['skipped'] = False
|
|
|
|
if 'explanation' not in kwargs:
|
|
|
|
kwargs['explanation'] = None
|
|
|
|
self.assertEqual(next(events), TAPParser.Plan(**kwargs))
|
|
|
|
|
|
|
|
def assert_version(self, events, **kwargs):
|
|
|
|
self.assertEqual(next(events), TAPParser.Version(**kwargs))
|
|
|
|
|
|
|
|
def assert_error(self, events):
|
|
|
|
self.assertEqual(type(next(events)), TAPParser.Error)
|
|
|
|
|
|
|
|
def assert_bailout(self, events, **kwargs):
|
|
|
|
self.assertEqual(next(events), TAPParser.Bailout(**kwargs))
|
|
|
|
|
|
|
|
def assert_last(self, events):
|
|
|
|
with self.assertRaises(StopIteration):
|
|
|
|
next(events)
|
|
|
|
|
|
|
|
def parse_tap(self, s):
|
|
|
|
parser = TAPParser(io.StringIO(s))
|
|
|
|
return iter(parser.parse())
|
|
|
|
|
|
|
|
def parse_tap_v13(self, s):
|
|
|
|
events = self.parse_tap('TAP version 13\n' + s)
|
|
|
|
self.assert_version(events, version=13)
|
|
|
|
return events
|
|
|
|
|
|
|
|
def test_empty(self):
|
|
|
|
events = self.parse_tap('')
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
def test_empty_plan(self):
|
|
|
|
events = self.parse_tap('1..0')
|
|
|
|
self.assert_plan(events, count=0, late=False, skipped=True)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
def test_plan_directive(self):
|
|
|
|
events = self.parse_tap('1..0 # skipped for some reason')
|
|
|
|
self.assert_plan(events, count=0, late=False, skipped=True,
|
|
|
|
explanation='for some reason')
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
events = self.parse_tap('1..1 # skipped for some reason\nok 1')
|
|
|
|
self.assert_error(events)
|
|
|
|
self.assert_plan(events, count=1, late=False, skipped=True,
|
|
|
|
explanation='for some reason')
|
|
|
|
self.assert_test(events, number=1, name='', result=TestResult.OK)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
events = self.parse_tap('1..1 # todo not supported here\nok 1')
|
|
|
|
self.assert_error(events)
|
|
|
|
self.assert_plan(events, count=1, late=False, skipped=False,
|
|
|
|
explanation='not supported here')
|
|
|
|
self.assert_test(events, number=1, name='', result=TestResult.OK)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
def test_one_test_ok(self):
|
|
|
|
events = self.parse_tap('ok')
|
|
|
|
self.assert_test(events, number=1, name='', result=TestResult.OK)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
def test_one_test_with_number(self):
|
|
|
|
events = self.parse_tap('ok 1')
|
|
|
|
self.assert_test(events, number=1, name='', result=TestResult.OK)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
def test_one_test_with_name(self):
|
|
|
|
events = self.parse_tap('ok 1 abc')
|
|
|
|
self.assert_test(events, number=1, name='abc', result=TestResult.OK)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
def test_one_test_not_ok(self):
|
|
|
|
events = self.parse_tap('not ok')
|
|
|
|
self.assert_test(events, number=1, name='', result=TestResult.FAIL)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
def test_one_test_todo(self):
|
|
|
|
events = self.parse_tap('not ok 1 abc # TODO')
|
|
|
|
self.assert_test(events, number=1, name='abc', result=TestResult.EXPECTEDFAIL)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
events = self.parse_tap('ok 1 abc # TODO')
|
|
|
|
self.assert_test(events, number=1, name='abc', result=TestResult.UNEXPECTEDPASS)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
def test_one_test_skip(self):
|
|
|
|
events = self.parse_tap('ok 1 abc # SKIP')
|
|
|
|
self.assert_test(events, number=1, name='abc', result=TestResult.SKIP)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
def test_one_test_skip_failure(self):
|
|
|
|
events = self.parse_tap('not ok 1 abc # SKIP')
|
|
|
|
self.assert_test(events, number=1, name='abc', result=TestResult.FAIL)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
def test_many_early_plan(self):
|
|
|
|
events = self.parse_tap('1..4\nok 1\nnot ok 2\nok 3\nnot ok 4')
|
|
|
|
self.assert_plan(events, count=4, late=False)
|
|
|
|
self.assert_test(events, number=1, name='', result=TestResult.OK)
|
|
|
|
self.assert_test(events, number=2, name='', result=TestResult.FAIL)
|
|
|
|
self.assert_test(events, number=3, name='', result=TestResult.OK)
|
|
|
|
self.assert_test(events, number=4, name='', result=TestResult.FAIL)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
def test_many_late_plan(self):
|
|
|
|
events = self.parse_tap('ok 1\nnot ok 2\nok 3\nnot ok 4\n1..4')
|
|
|
|
self.assert_test(events, number=1, name='', result=TestResult.OK)
|
|
|
|
self.assert_test(events, number=2, name='', result=TestResult.FAIL)
|
|
|
|
self.assert_test(events, number=3, name='', result=TestResult.OK)
|
|
|
|
self.assert_test(events, number=4, name='', result=TestResult.FAIL)
|
|
|
|
self.assert_plan(events, count=4, late=True)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
def test_directive_case(self):
|
|
|
|
events = self.parse_tap('ok 1 abc # skip')
|
|
|
|
self.assert_test(events, number=1, name='abc', result=TestResult.SKIP)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
events = self.parse_tap('ok 1 abc # ToDo')
|
|
|
|
self.assert_test(events, number=1, name='abc', result=TestResult.UNEXPECTEDPASS)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
def test_directive_explanation(self):
|
|
|
|
events = self.parse_tap('ok 1 abc # skip why')
|
|
|
|
self.assert_test(events, number=1, name='abc', result=TestResult.SKIP,
|
|
|
|
explanation='why')
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
events = self.parse_tap('ok 1 abc # ToDo Because')
|
|
|
|
self.assert_test(events, number=1, name='abc', result=TestResult.UNEXPECTEDPASS,
|
|
|
|
explanation='Because')
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
def test_one_test_early_plan(self):
|
|
|
|
events = self.parse_tap('1..1\nok')
|
|
|
|
self.assert_plan(events, count=1, late=False)
|
|
|
|
self.assert_test(events, number=1, name='', result=TestResult.OK)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
def test_one_test_late_plan(self):
|
|
|
|
events = self.parse_tap('ok\n1..1')
|
|
|
|
self.assert_test(events, number=1, name='', result=TestResult.OK)
|
|
|
|
self.assert_plan(events, count=1, late=True)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
def test_out_of_order(self):
|
|
|
|
events = self.parse_tap('ok 2')
|
|
|
|
self.assert_error(events)
|
|
|
|
self.assert_test(events, number=2, name='', result=TestResult.OK)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
def test_middle_plan(self):
|
|
|
|
events = self.parse_tap('ok 1\n1..2\nok 2')
|
|
|
|
self.assert_test(events, number=1, name='', result=TestResult.OK)
|
|
|
|
self.assert_plan(events, count=2, late=True)
|
|
|
|
self.assert_error(events)
|
|
|
|
self.assert_test(events, number=2, name='', result=TestResult.OK)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
def test_too_many_plans(self):
|
|
|
|
events = self.parse_tap('1..1\n1..2\nok 1')
|
|
|
|
self.assert_plan(events, count=1, late=False)
|
|
|
|
self.assert_error(events)
|
|
|
|
self.assert_test(events, number=1, name='', result=TestResult.OK)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
def test_too_many(self):
|
|
|
|
events = self.parse_tap('ok 1\nnot ok 2\n1..1')
|
|
|
|
self.assert_test(events, number=1, name='', result=TestResult.OK)
|
|
|
|
self.assert_test(events, number=2, name='', result=TestResult.FAIL)
|
|
|
|
self.assert_plan(events, count=1, late=True)
|
|
|
|
self.assert_error(events)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
events = self.parse_tap('1..1\nok 1\nnot ok 2')
|
|
|
|
self.assert_plan(events, count=1, late=False)
|
|
|
|
self.assert_test(events, number=1, name='', result=TestResult.OK)
|
|
|
|
self.assert_test(events, number=2, name='', result=TestResult.FAIL)
|
|
|
|
self.assert_error(events)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
def test_too_few(self):
|
|
|
|
events = self.parse_tap('ok 1\nnot ok 2\n1..3')
|
|
|
|
self.assert_test(events, number=1, name='', result=TestResult.OK)
|
|
|
|
self.assert_test(events, number=2, name='', result=TestResult.FAIL)
|
|
|
|
self.assert_plan(events, count=3, late=True)
|
|
|
|
self.assert_error(events)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
events = self.parse_tap('1..3\nok 1\nnot ok 2')
|
|
|
|
self.assert_plan(events, count=3, late=False)
|
|
|
|
self.assert_test(events, number=1, name='', result=TestResult.OK)
|
|
|
|
self.assert_test(events, number=2, name='', result=TestResult.FAIL)
|
|
|
|
self.assert_error(events)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
def test_too_few_bailout(self):
|
|
|
|
events = self.parse_tap('1..3\nok 1\nnot ok 2\nBail out! no third test')
|
|
|
|
self.assert_plan(events, count=3, late=False)
|
|
|
|
self.assert_test(events, number=1, name='', result=TestResult.OK)
|
|
|
|
self.assert_test(events, number=2, name='', result=TestResult.FAIL)
|
|
|
|
self.assert_bailout(events, message='no third test')
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
def test_diagnostics(self):
|
|
|
|
events = self.parse_tap('1..1\n# ignored\nok 1')
|
|
|
|
self.assert_plan(events, count=1, late=False)
|
|
|
|
self.assert_test(events, number=1, name='', result=TestResult.OK)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
events = self.parse_tap('# ignored\n1..1\nok 1\n# ignored too')
|
|
|
|
self.assert_plan(events, count=1, late=False)
|
|
|
|
self.assert_test(events, number=1, name='', result=TestResult.OK)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
events = self.parse_tap('# ignored\nok 1\n1..1\n# ignored too')
|
|
|
|
self.assert_test(events, number=1, name='', result=TestResult.OK)
|
|
|
|
self.assert_plan(events, count=1, late=True)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
def test_empty_line(self):
|
|
|
|
events = self.parse_tap('1..1\n\nok 1')
|
|
|
|
self.assert_plan(events, count=1, late=False)
|
|
|
|
self.assert_test(events, number=1, name='', result=TestResult.OK)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
def test_unexpected(self):
|
|
|
|
events = self.parse_tap('1..1\ninvalid\nok 1')
|
|
|
|
self.assert_plan(events, count=1, late=False)
|
|
|
|
self.assert_error(events)
|
|
|
|
self.assert_test(events, number=1, name='', result=TestResult.OK)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
def test_version(self):
|
|
|
|
events = self.parse_tap('TAP version 13\n')
|
|
|
|
self.assert_version(events, version=13)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
events = self.parse_tap('TAP version 12\n')
|
|
|
|
self.assert_error(events)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
events = self.parse_tap('1..0\nTAP version 13\n')
|
|
|
|
self.assert_plan(events, count=0, late=False, skipped=True)
|
|
|
|
self.assert_error(events)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
def test_yaml(self):
|
|
|
|
events = self.parse_tap_v13('ok\n ---\n foo: abc\n bar: def\n ...\nok 2')
|
|
|
|
self.assert_test(events, number=1, name='', result=TestResult.OK)
|
|
|
|
self.assert_test(events, number=2, name='', result=TestResult.OK)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
events = self.parse_tap_v13('ok\n ---\n foo: abc\n bar: def')
|
|
|
|
self.assert_test(events, number=1, name='', result=TestResult.OK)
|
|
|
|
self.assert_error(events)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
events = self.parse_tap_v13('ok 1\n ---\n foo: abc\n bar: def\nnot ok 2')
|
|
|
|
self.assert_test(events, number=1, name='', result=TestResult.OK)
|
|
|
|
self.assert_error(events)
|
|
|
|
self.assert_test(events, number=2, name='', result=TestResult.FAIL)
|
|
|
|
self.assert_last(events)
|
|
|
|
|
|
|
|
|
|
|
|
def _clang_at_least(compiler, minver: str, apple_minver: str) -> bool:
|
|
|
|
"""
|
|
|
|
check that Clang compiler is at least a specified version, whether AppleClang or regular Clang
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
compiler:
|
|
|
|
Meson compiler object
|
|
|
|
minver: str
|
|
|
|
Clang minimum version
|
|
|
|
apple_minver: str
|
|
|
|
AppleCLang minimum version
|
|
|
|
|
|
|
|
Returns
|
|
|
|
-------
|
|
|
|
at_least: bool
|
|
|
|
Clang is at least the specified version
|
|
|
|
"""
|
|
|
|
if isinstance(compiler, (mesonbuild.compilers.AppleClangCCompiler,
|
|
|
|
mesonbuild.compilers.AppleClangCPPCompiler)):
|
|
|
|
return version_compare(compiler.version, apple_minver)
|
|
|
|
return version_compare(compiler.version, minver)
|
|
|
|
|
|
|
|
|
|
|
|
def unset_envs():
|
|
|
|
# For unit tests we must fully control all command lines
|
|
|
|
# so that there are no unexpected changes coming from the
|
|
|
|
# environment, for example when doing a package build.
|
|
|
|
varnames = ['CPPFLAGS', 'LDFLAGS'] + list(mesonbuild.compilers.compilers.cflags_mapping.values())
|
|
|
|
for v in varnames:
|
|
|
|
if v in os.environ:
|
|
|
|
del os.environ[v]
|
|
|
|
|
|
|
|
def convert_args(argv):
|
|
|
|
# If we got passed a list of tests, pass it on
|
|
|
|
pytest_args = ['-v'] if '-v' in argv else []
|
|
|
|
test_list = []
|
|
|
|
for arg in argv:
|
|
|
|
if arg.startswith('-'):
|
|
|
|
continue
|
|
|
|
# ClassName.test_name => 'ClassName and test_name'
|
|
|
|
if '.' in arg:
|
|
|
|
arg = ' and '.join(arg.split('.'))
|
|
|
|
test_list.append(arg)
|
|
|
|
if test_list:
|
|
|
|
pytest_args += ['-k', ' or '.join(test_list)]
|
|
|
|
return pytest_args
|
|
|
|
|
|
|
|
def main():
|
|
|
|
unset_envs()
|
|
|
|
try:
|
|
|
|
import pytest # noqa: F401
|
|
|
|
# Need pytest-xdist for `-n` arg
|
|
|
|
import xdist # noqa: F401
|
|
|
|
if sys.version_info.major <= 3 and sys.version_info.minor <= 5:
|
|
|
|
raise ImportError('pytest with python <= 3.5 is causing issues on the CI')
|
|
|
|
pytest_args = ['-n', 'auto', './run_unittests.py']
|
|
|
|
pytest_args += convert_args(sys.argv[1:])
|
|
|
|
return subprocess.run(python_command + ['-m', 'pytest'] + pytest_args).returncode
|
|
|
|
except ImportError:
|
|
|
|
print('pytest-xdist not found, using unittest instead')
|
|
|
|
pass
|
|
|
|
# All attempts at locating pytest failed, fall back to plain unittest.
|
|
|
|
cases = ['InternalTests', 'DataTests', 'AllPlatformTests', 'FailureTests',
|
|
|
|
'PythonTests', 'NativeFileTests', 'RewriterTests', 'CrossFileTests',
|
|
|
|
'TAPParserTests',
|
|
|
|
|
|
|
|
'LinuxlikeTests', 'LinuxCrossArmTests', 'LinuxCrossMingwTests',
|
|
|
|
'WindowsTests', 'DarwinTests']
|
|
|
|
|
|
|
|
return unittest.main(defaultTest=cases, buffer=True)
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
raise SystemExit(main())
|