|  | # Copyright 2016-2021 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 subprocess | 
|  | import re | 
|  | import os | 
|  | import shutil | 
|  | from unittest import mock, SkipTest, skipUnless, skipIf | 
|  | from glob import glob | 
|  |  | 
|  | import mesonbuild.mlog | 
|  | import mesonbuild.depfile | 
|  | import mesonbuild.dependencies.factory | 
|  | import mesonbuild.envconfig | 
|  | import mesonbuild.environment | 
|  | import mesonbuild.coredata | 
|  | import mesonbuild.modules.gnome | 
|  | from mesonbuild.mesonlib import ( | 
|  | MachineChoice, is_windows, is_cygwin, python_command, version_compare, | 
|  | EnvironmentException, OptionKey | 
|  | ) | 
|  | from mesonbuild.compilers import ( | 
|  | detect_c_compiler, detect_d_compiler, compiler_from_language, | 
|  | GnuLikeCompiler | 
|  | ) | 
|  | from mesonbuild.programs import ExternalProgram | 
|  | import mesonbuild.dependencies.base | 
|  | import mesonbuild.modules.pkgconfig | 
|  |  | 
|  |  | 
|  | from run_tests import ( | 
|  | Backend, get_fake_env | 
|  | ) | 
|  |  | 
|  | from .baseplatformtests import BasePlatformTests | 
|  | from .helpers import * | 
|  |  | 
|  | @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') | 
|  |  | 
|  | @skipIf(is_cygwin(), 'Test only applicable to Windows') | 
|  | @mock.patch.dict(os.environ) | 
|  | 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.exe with args without searching | 
|  | prog = ExternalProgram('cmd', command=['cmd', '/C']) | 
|  | self.assertTrue(prog.found(), msg='cmd not found with args') | 
|  | self.assertPathEqual(prog.get_command()[0], 'cmd') | 
|  | # 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=f'{cmd_path!r} not found') | 
|  | # 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 | 
|  | # If `.PY` is in PATHEXT, scripts can be found as programs | 
|  | if '.PY' in [ext.upper() for ext in os.environ['PATHEXT'].split(';')]: | 
|  | # Finding a script in PATH w/o extension works and adds the interpreter | 
|  | 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') | 
|  | # Using a script with an extension directly via command= works and adds the interpreter | 
|  | prog = ExternalProgram('test-script-ext.py', command=[os.path.join(testdir, 'test-script-ext.py'), '--help']) | 
|  | self.assertTrue(prog.found(), msg='test-script-ext.py with full path not picked up via command=') | 
|  | self.assertPathEqual(prog.get_command()[0], python_command[0]) | 
|  | self.assertPathEqual(prog.get_command()[2], '--help') | 
|  | self.assertPathBasenameEqual(prog.get_path(), 'test-script-ext.py') | 
|  | # Using a script without an extension directly via command= works and adds the interpreter | 
|  | prog = ExternalProgram('test-script', command=[os.path.join(testdir, 'test-script'), '--help']) | 
|  | self.assertTrue(prog.found(), msg='test-script with full path not picked up via command=') | 
|  | self.assertPathEqual(prog.get_command()[0], python_command[0]) | 
|  | self.assertPathEqual(prog.get_command()[2], '--help') | 
|  | self.assertPathBasenameEqual(prog.get_path(), 'test-script') | 
|  | # Ensure that WindowsApps gets removed from PATH | 
|  | path = os.environ['PATH'] | 
|  | if 'WindowsApps' not in path: | 
|  | username = os.environ['USERNAME'] | 
|  | appstore_dir = fr'C:\Users\{username}\AppData\Local\Microsoft\WindowsApps' | 
|  | 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 = detect_c_compiler(env, MachineChoice.HOST) | 
|  | if cc.get_argument_syntax() != 'msvc': | 
|  | raise 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 = detect_c_compiler(env, 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 = detect_c_compiler(env, MachineChoice.HOST) | 
|  | if cc.get_argument_syntax() != 'msvc': | 
|  | raise 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 = detect_c_compiler(env, MachineChoice.HOST) | 
|  | if cc.get_argument_syntax() != 'msvc': | 
|  | raise 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.assertIn('prog.pdb', files) | 
|  |  | 
|  | def _check_ld(self, name: str, lang: str, expected: str) -> None: | 
|  | if not shutil.which(name): | 
|  | raise SkipTest(f'Could not find {name}.') | 
|  | envvars = [mesonbuild.envconfig.ENV_VAR_PROG_MAP[f'{lang}_ld']] | 
|  |  | 
|  | # Also test a deprecated variable if there is one. | 
|  | if f'{lang}_ld' in mesonbuild.envconfig.DEPRECATED_ENV_PROG_MAP: | 
|  | envvars.append( | 
|  | mesonbuild.envconfig.DEPRECATED_ENV_PROG_MAP[f'{lang}_ld']) | 
|  |  | 
|  | for envvar in envvars: | 
|  | with mock.patch.dict(os.environ, {envvar: name}): | 
|  | env = get_fake_env() | 
|  | try: | 
|  | comp = compiler_from_language(env, lang, MachineChoice.HOST) | 
|  | except EnvironmentException: | 
|  | raise SkipTest(f'Could not find a compiler for {lang}') | 
|  | self.assertEqual(comp.linker.id, expected) | 
|  |  | 
|  | def test_link_environment_variable_lld_link(self): | 
|  | env = get_fake_env() | 
|  | comp = detect_c_compiler(env, MachineChoice.HOST) | 
|  | if isinstance(comp, GnuLikeCompiler): | 
|  | raise SkipTest('GCC cannot be used with link compatible linkers.') | 
|  | self._check_ld('lld-link', 'c', 'lld-link') | 
|  |  | 
|  | def test_link_environment_variable_link(self): | 
|  | env = get_fake_env() | 
|  | comp = detect_c_compiler(env, MachineChoice.HOST) | 
|  | if isinstance(comp, GnuLikeCompiler): | 
|  | raise SkipTest('GCC cannot be used with link compatible linkers.') | 
|  | self._check_ld('link', 'c', 'link') | 
|  |  | 
|  | def test_link_environment_variable_optlink(self): | 
|  | env = get_fake_env() | 
|  | comp = detect_c_compiler(env, MachineChoice.HOST) | 
|  | if isinstance(comp, GnuLikeCompiler): | 
|  | raise SkipTest('GCC cannot be used with link compatible linkers.') | 
|  | self._check_ld('optlink', 'c', 'optlink') | 
|  |  | 
|  | @skip_if_not_language('rust') | 
|  | def test_link_environment_variable_rust(self): | 
|  | self._check_ld('link', 'rust', 'link') | 
|  |  | 
|  | @skip_if_not_language('d') | 
|  | def test_link_environment_variable_d(self): | 
|  | env = get_fake_env() | 
|  | comp = detect_d_compiler(env, MachineChoice.HOST) | 
|  | if comp.id == 'dmd': | 
|  | raise SkipTest('meson cannot reliably make DMD use a different linker.') | 
|  | self._check_ld('lld-link', 'd', 'lld-link') | 
|  |  | 
|  | def test_pefile_checksum(self): | 
|  | try: | 
|  | import pefile | 
|  | except ImportError: | 
|  | if is_ci(): | 
|  | raise | 
|  | raise SkipTest('pefile module not found') | 
|  | testdir = os.path.join(self.common_test_dir, '6 linkshared') | 
|  | self.init(testdir, extra_args=['--buildtype=release']) | 
|  | self.build() | 
|  | # Test that binaries have a non-zero checksum | 
|  | env = get_fake_env() | 
|  | cc = detect_c_compiler(env, MachineChoice.HOST) | 
|  | cc_id = cc.get_id() | 
|  | ld_id = cc.get_linker_id() | 
|  | dll = glob(os.path.join(self.builddir, '*mycpplib.dll'))[0] | 
|  | exe = os.path.join(self.builddir, 'cppprog.exe') | 
|  | for f in (dll, exe): | 
|  | pe = pefile.PE(f) | 
|  | msg = f'PE file: {f!r}, compiler: {cc_id!r}, linker: {ld_id!r}' | 
|  | if cc_id == 'clang-cl': | 
|  | # Latest clang-cl tested (7.0) does not write checksums out | 
|  | self.assertFalse(pe.verify_checksum(), msg=msg) | 
|  | else: | 
|  | # Verify that a valid checksum was written by all other compilers | 
|  | self.assertTrue(pe.verify_checksum(), msg=msg) | 
|  |  | 
|  | def test_qt5dependency_vscrt(self): | 
|  | ''' | 
|  | Test that qt5 dependencies use the debug module suffix when b_vscrt is | 
|  | set to 'mdd' | 
|  | ''' | 
|  | # Verify that the `b_vscrt` option is available | 
|  | env = get_fake_env() | 
|  | cc = detect_c_compiler(env, MachineChoice.HOST) | 
|  | if OptionKey('b_vscrt') not in cc.base_options: | 
|  | raise SkipTest('Compiler does not support setting the VS CRT') | 
|  | # Verify that qmake is for Qt5 | 
|  | if not shutil.which('qmake-qt5'): | 
|  | if not shutil.which('qmake') and not is_ci(): | 
|  | raise SkipTest('QMake not found') | 
|  | output = subprocess.getoutput('qmake --version') | 
|  | if 'Qt version 5' not in output and not is_ci(): | 
|  | raise SkipTest('Qmake found, but it is not for Qt 5.') | 
|  | # Setup with /MDd | 
|  | testdir = os.path.join(self.framework_test_dir, '4 qt') | 
|  | self.init(testdir, extra_args=['-Db_vscrt=mdd']) | 
|  | # Verify that we're linking to the debug versions of Qt DLLs | 
|  | build_ninja = os.path.join(self.builddir, 'build.ninja') | 
|  | with open(build_ninja, encoding='utf-8') as f: | 
|  | contents = f.read() | 
|  | m = re.search('build qt5core.exe: cpp_LINKER.*Qt5Cored.lib', contents) | 
|  | self.assertIsNotNone(m, msg=contents) | 
|  |  | 
|  | def test_compiler_checks_vscrt(self): | 
|  | ''' | 
|  | Test that the correct VS CRT is used when running compiler checks | 
|  | ''' | 
|  | # Verify that the `b_vscrt` option is available | 
|  | env = get_fake_env() | 
|  | cc = detect_c_compiler(env, MachineChoice.HOST) | 
|  | if OptionKey('b_vscrt') not in cc.base_options: | 
|  | raise SkipTest('Compiler does not support setting the VS CRT') | 
|  |  | 
|  | def sanitycheck_vscrt(vscrt): | 
|  | checks = self.get_meson_log_sanitychecks() | 
|  | self.assertGreater(len(checks), 0) | 
|  | for check in checks: | 
|  | self.assertIn(vscrt, check) | 
|  |  | 
|  | testdir = os.path.join(self.common_test_dir, '1 trivial') | 
|  | self.init(testdir) | 
|  | sanitycheck_vscrt('/MDd') | 
|  |  | 
|  | self.new_builddir() | 
|  | self.init(testdir, extra_args=['-Dbuildtype=debugoptimized']) | 
|  | sanitycheck_vscrt('/MD') | 
|  |  | 
|  | self.new_builddir() | 
|  | self.init(testdir, extra_args=['-Dbuildtype=release']) | 
|  | sanitycheck_vscrt('/MD') | 
|  |  | 
|  | self.new_builddir() | 
|  | self.init(testdir, extra_args=['-Db_vscrt=md']) | 
|  | sanitycheck_vscrt('/MD') | 
|  |  | 
|  | self.new_builddir() | 
|  | self.init(testdir, extra_args=['-Db_vscrt=mdd']) | 
|  | sanitycheck_vscrt('/MDd') | 
|  |  | 
|  | self.new_builddir() | 
|  | self.init(testdir, extra_args=['-Db_vscrt=mt']) | 
|  | sanitycheck_vscrt('/MT') | 
|  |  | 
|  | self.new_builddir() | 
|  | self.init(testdir, extra_args=['-Db_vscrt=mtd']) | 
|  | sanitycheck_vscrt('/MTd') | 
|  |  | 
|  | def test_modules(self): | 
|  | if self.backend is not Backend.ninja: | 
|  | raise SkipTest(f'C++ modules only work with the Ninja backend (not {self.backend.name}).') | 
|  | if 'VSCMD_VER' not in os.environ: | 
|  | raise SkipTest('C++ modules is only supported with Visual Studio.') | 
|  | if version_compare(os.environ['VSCMD_VER'], '<16.10.0'): | 
|  | raise SkipTest('C++ modules are only supported with VS 2019 Preview or newer.') | 
|  | self.init(os.path.join(self.unit_test_dir, '86 cpp modules')) | 
|  | self.build() | 
|  |  | 
|  | def test_non_utf8_fails(self): | 
|  | # FIXME: VS backend does not use flags from compiler.get_always_args() | 
|  | # and thus it's missing /utf-8 argument. Was that intentional? This needs | 
|  | # to be revisited. | 
|  | if self.backend is not Backend.ninja: | 
|  | raise SkipTest(f'This test only pass with ninja backend (not {self.backend.name}).') | 
|  | testdir = os.path.join(self.platform_test_dir, '18 msvc charset') | 
|  | env = get_fake_env(testdir, self.builddir, self.prefix) | 
|  | cc = detect_c_compiler(env, MachineChoice.HOST) | 
|  | if cc.get_argument_syntax() != 'msvc': | 
|  | raise SkipTest('Not using MSVC') | 
|  | self.init(testdir, extra_args=['-Dtest-failure=true']) | 
|  | self.assertRaises(subprocess.CalledProcessError, self.build) |