diff options
-rw-r--r-- | docs/markdown/Reference-manual.md | 22 | ||||
-rw-r--r-- | docs/markdown/snippets/configure_file_output_format.md | 14 | ||||
-rwxr-xr-x | meson.py | 17 | ||||
-rw-r--r-- | mesonbuild/environment.py | 6 | ||||
-rw-r--r-- | mesonbuild/interpreter.py | 14 | ||||
-rw-r--r-- | mesonbuild/mesonlib.py | 84 | ||||
-rw-r--r-- | mesonbuild/mesonmain.py | 34 | ||||
-rw-r--r-- | mesonbuild/scripts/regen_checker.py | 6 | ||||
-rwxr-xr-x | run_meson_command_tests.py | 186 | ||||
-rwxr-xr-x | run_project_tests.py | 21 | ||||
-rwxr-xr-x | run_tests.py | 43 | ||||
-rwxr-xr-x | run_unittests.py | 64 | ||||
-rw-r--r-- | test cases/nasm/1 configure file/hello.asm | 27 | ||||
-rw-r--r-- | test cases/nasm/1 configure file/meson.build | 44 |
14 files changed, 434 insertions, 148 deletions
diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index 3f1bb50..0c54de0 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -208,6 +208,10 @@ the `@variable@` syntax. `@PLAINNAME@` or `@BASENAME@` substitutions). In configuration mode, the permissions of the input file (if it is specified) are copied to the output file. +- `output_format` *(added 0.47.0)* the format of the output to generate when no input + was specified. It defaults to `c`, in which case preprocessor directives + will be prefixed with `#`, you can also use `nasm`, in which case the + prefix will be `%`. ### custom_target() @@ -1423,7 +1427,7 @@ the following methods. the string to an array if needed. - `override_find_program(progname, program)` [*(Added - 0.46.0)*](Release-notes-for-0-46-0.html#Can-override-find_program) + 0.46.0)*](Release-notes-for-0-46-0.html#can-override-find_program) specifies that whenever `find_program` is used to find a program named `progname`, Meson should not not look it up on the system but instead return `program`, which may either be the result of @@ -1444,17 +1448,15 @@ doing the actual compilation. See [Cross-compilation](Cross-compilation.md). It has the following methods: -- `cpu_family()` returns the CPU family name. Guaranteed to return - `x86` for 32-bit userland on x86 CPUs, `x86_64` for 64-bit userland - on x86 CPUs, `arm` for 32-bit userland on all ARM CPUs, etc. +- `cpu_family()` returns the CPU family name. [This table](Reference-tables.md#cpu-families) + contains all known CPU families. These are guaranteed to continue working. - `cpu()` returns a more specific CPU name, such as `i686`, `amd64`, etc. -- `system()` returns the operating system name, such as `windows` (all - versions of Windows), `linux` (all Linux distros), `darwin` (all - versions of OS X/macOS), `cygwin` (for Cygwin), and `bsd` (all *BSD - OSes). +- `system()` returns the operating system name. + [This table](Reference-tables.html#operating-system-names) Lists all of the + currently known Operating System names, these are guaranteed to continue working. - `endian()` returns `big` on big-endian systems and `little` on little-endian systems. @@ -1464,7 +1466,7 @@ Currently, these values are populated using and [`platform.machine()`](https://docs.python.org/3.4/library/platform.html#platform.machine). If you think the returned values for any of these are incorrect for your -system or CPU, or if your OS is not in the above list, please file [a +system or CPU, or if your OS is not in the linked table, please file [a bug report](https://github.com/mesonbuild/meson/issues/new) with details and we'll look into it. @@ -1544,7 +1546,7 @@ the following methods: value as a string or empty string if it is not defined. - `get_id()` returns a string identifying the compiler. For example, - `gcc`, `msvc`, [and more](Compiler-properties.md#compiler-id). + `gcc`, `msvc`, [and more](Reference-tables.html#compiler-ids). - `get_supported_arguments(list_of_string)` *(added 0.43.0)* returns an array containing only the arguments supported by the compiler, diff --git a/docs/markdown/snippets/configure_file_output_format.md b/docs/markdown/snippets/configure_file_output_format.md new file mode 100644 index 0000000..e522885 --- /dev/null +++ b/docs/markdown/snippets/configure_file_output_format.md @@ -0,0 +1,14 @@ +## New keyword argument `output_format` for configure_file() + +When called without an input file, `configure_file` generates a +C header file by default. A keyword argument was added to allow +specifying the output format, for example for use with nasm or yasm: + +``` +conf = configuration_data() +conf.set('FOO', 1) + +configure_file('config.asm', + configuration: conf, + output_format: 'nasm') +``` @@ -14,13 +14,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -from mesonbuild import mesonmain -import sys, os +import sys +from pathlib import Path + +# If we're run uninstalled, add the script directory to sys.path to ensure that +# we always import the correct mesonbuild modules even if PYTHONPATH is mangled +meson_exe = Path(sys.argv[0]).resolve() +if (meson_exe.parent / 'mesonbuild').is_dir(): + sys.path.insert(0, meson_exe.parent) -def main(): - # Always resolve the command path so Ninja can find it for regen, tests, etc. - launcher = os.path.realpath(sys.argv[0]) - return mesonmain.run(sys.argv[1:], launcher) +from mesonbuild import mesonmain if __name__ == '__main__': - sys.exit(main()) + sys.exit(mesonmain.main()) diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index fc837d6..4bcffbd 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -262,10 +262,9 @@ class Environment: private_dir = 'meson-private' log_dir = 'meson-logs' - def __init__(self, source_dir, build_dir, main_script_launcher, options, original_cmd_line_args): + def __init__(self, source_dir, build_dir, options, original_cmd_line_args): self.source_dir = source_dir self.build_dir = build_dir - self.meson_script_launcher = main_script_launcher self.scratch_dir = os.path.join(build_dir, Environment.private_dir) self.log_dir = os.path.join(build_dir, Environment.log_dir) os.makedirs(self.scratch_dir, exist_ok=True) @@ -278,7 +277,8 @@ class Environment: # re-initialized with project options by the interpreter during # build file parsing. self.coredata = coredata.CoreData(options) - self.coredata.meson_script_launcher = self.meson_script_launcher + # Used by the regenchecker script, which runs meson + self.coredata.meson_command = mesonlib.meson_command self.first_invocation = True if self.coredata.cross_file: self.cross_info = CrossBuildInfo(self.coredata.cross_file) diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 79b66c1..93fd237 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -1716,7 +1716,7 @@ permitted_kwargs = {'add_global_arguments': {'language'}, 'add_test_setup': {'exe_wrapper', 'gdb', 'timeout_multiplier', 'env'}, 'benchmark': {'args', 'env', 'should_fail', 'timeout', 'workdir', 'suite'}, 'build_target': known_build_target_kwargs, - 'configure_file': {'input', 'output', 'configuration', 'command', 'copy', 'install_dir', 'capture', 'install', 'format'}, + 'configure_file': {'input', 'output', 'configuration', 'command', 'copy', 'install_dir', 'capture', 'install', 'format', 'output_format'}, 'custom_target': {'input', 'output', 'command', 'install', 'install_dir', 'build_always', 'capture', 'depends', 'depend_files', 'depfile', 'build_by_default'}, 'dependency': {'default_options', 'fallback', 'language', 'main', 'method', 'modules', 'optional_modules', 'native', 'required', 'static', 'version', 'private_headers'}, 'declare_dependency': {'include_directories', 'link_with', 'sources', 'dependencies', 'compile_args', 'link_args', 'link_whole', 'version'}, @@ -3239,6 +3239,16 @@ root and issuing %s. if fmt not in ('meson', 'cmake', 'cmake@'): raise InterpreterException('"format" possible values are "meson", "cmake" or "cmake@".') + if 'output_format' in kwargs: + output_format = kwargs['output_format'] + if not isinstance(output_format, str): + raise InterpreterException('"output_format" keyword must be a string.') + else: + output_format = 'c' + + if output_format not in ('c', 'nasm'): + raise InterpreterException('"format" possible values are "c" or "nasm".') + # Validate input inputfile = None ifile_abs = None @@ -3301,7 +3311,7 @@ root and issuing %s. "present in the given configuration data." % ( var_list, inputfile), location=node) else: - mesonlib.dump_conf_header(ofile_abs, conf.held_object) + mesonlib.dump_conf_header(ofile_abs, conf.held_object, output_format) conf.mark_used() elif 'command' in kwargs: # We use absolute paths for input and output here because the cwd diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index fde7ed5..eaa5359 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -40,58 +40,12 @@ except Exception: from glob import glob -def detect_meson_py_location(): - c = sys.argv[0] - c_dir, c_fname = os.path.split(c) - - # get the absolute path to the <mesontool> folder - m_dir = None - if os.path.isabs(c): - # $ /foo/<mesontool>.py <args> - m_dir = c_dir - elif c_dir == '': - # $ <mesontool> <args> (gets run from /usr/bin/<mesontool>) - in_path_exe = shutil.which(c_fname) - if in_path_exe: - if not os.path.isabs(in_path_exe): - m_dir = os.getcwd() - c_fname = in_path_exe - else: - m_dir, c_fname = os.path.split(in_path_exe) - else: - m_dir = os.path.abspath(c_dir) - - # find meson in m_dir - if m_dir is not None: - for fname in ['meson', 'meson.py']: - m_path = os.path.join(m_dir, fname) - if os.path.exists(m_path): - return m_path - - # No meson found, which means that either: - # a) meson is not installed - # b) meson is installed to a non-standard location - # c) the script that invoked mesonlib is not the one of meson tools (e.g. run_unittests.py) - fname = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', 'meson.py')) - if os.path.exists(fname): - return fname - # If meson is still not found, we might be imported by out-of-source tests - # https://github.com/mesonbuild/meson/issues/3015 - exe = shutil.which('meson') - if exe is None: - exe = shutil.which('meson.py') - if exe is not None: - return exe - # Give up. - raise RuntimeError('Could not determine how to run Meson. Please file a bug with details.') - if os.path.basename(sys.executable) == 'meson.exe': # In Windows and using the MSI installed executable. - meson_command = [sys.executable] python_command = [sys.executable, 'runpython'] else: python_command = [sys.executable] - meson_command = python_command + [detect_meson_py_location()] +meson_command = None def is_ascii_string(astring): try: @@ -704,28 +658,46 @@ def do_conf_file(src, dst, confdata, format): replace_if_different(dst, dst_tmp) return missing_variables -def dump_conf_header(ofilename, cdata): - ofilename_tmp = ofilename + '~' - with open(ofilename_tmp, 'w', encoding='utf-8') as ofile: - ofile.write('''/* +CONF_C_PRELUDE = '''/* * Autogenerated by the Meson build system. * Do not edit, your changes will be lost. */ #pragma once -''') +''' + +CONF_NASM_PRELUDE = '''; Autogenerated by the Meson build system. +; Do not edit, your changes will be lost. + +''' + +def dump_conf_header(ofilename, cdata, output_format): + if output_format == 'c': + prelude = CONF_C_PRELUDE + prefix = '#' + elif output_format == 'nasm': + prelude = CONF_NASM_PRELUDE + prefix = '%' + + ofilename_tmp = ofilename + '~' + with open(ofilename_tmp, 'w', encoding='utf-8') as ofile: + ofile.write(prelude) for k in sorted(cdata.keys()): (v, desc) = cdata.get(k) if desc: - ofile.write('/* %s */\n' % desc) + if output_format == 'c': + ofile.write('/* %s */\n' % desc) + elif output_format == 'nasm': + for line in desc.split('\n'): + ofile.write('; %s\n' % line) if isinstance(v, bool): if v: - ofile.write('#define %s\n\n' % k) + ofile.write('%sdefine %s\n\n' % (prefix, k)) else: - ofile.write('#undef %s\n\n' % k) + ofile.write('%sundef %s\n\n' % (prefix, k)) elif isinstance(v, (int, str)): - ofile.write('#define %s %s\n\n' % (k, v)) + ofile.write('%sdefine %s %s\n\n' % (prefix, k, v)) else: raise MesonException('Unknown data type in configuration file entry: ' + k) replace_if_different(ofilename, ofilename_tmp) diff --git a/mesonbuild/mesonmain.py b/mesonbuild/mesonmain.py index 4a977a1..c03ef9b 100644 --- a/mesonbuild/mesonmain.py +++ b/mesonbuild/mesonmain.py @@ -73,9 +73,8 @@ def filter_builtin_options(args, original_args): class MesonApp: - def __init__(self, dir1, dir2, script_launcher, handshake, options, original_cmd_line_args): + def __init__(self, dir1, dir2, handshake, options, original_cmd_line_args): (self.source_dir, self.build_dir) = self.validate_dirs(dir1, dir2, handshake) - self.meson_script_launcher = script_launcher self.options = options self.original_cmd_line_args = original_cmd_line_args @@ -129,7 +128,7 @@ class MesonApp: env.coredata.pkgconf_envvar = curvar def generate(self): - env = environment.Environment(self.source_dir, self.build_dir, self.meson_script_launcher, self.options, self.original_cmd_line_args) + env = environment.Environment(self.source_dir, self.build_dir, self.options, self.original_cmd_line_args) mlog.initialize(env.get_log_dir()) with mesonlib.BuildDirLock(self.build_dir): self._generate(env) @@ -269,12 +268,27 @@ def run_script_command(args): raise MesonException('Unknown internal command {}.'.format(cmdname)) return cmdfunc(cmdargs) -def run(original_args, mainfile=None): +def set_meson_command(mainfile): + if mainfile.endswith('.exe'): + mesonlib.meson_command = [mainfile] + elif os.path.isabs(mainfile) and mainfile.endswith('mesonmain.py'): + # Can't actually run meson with an absolute path to mesonmain.py, it must be run as -m mesonbuild.mesonmain + mesonlib.meson_command = mesonlib.python_command + ['-m', 'mesonbuild.mesonmain'] + else: + mesonlib.meson_command = mesonlib.python_command + [mainfile] + # This won't go into the log file because it's not initialized yet, and we + # need this value for unit tests. + if 'MESON_COMMAND_TESTS' in os.environ: + mlog.log('meson_command is {!r}'.format(mesonlib.meson_command)) + +def run(original_args, mainfile): if sys.version_info < (3, 5): print('Meson works correctly only with python 3.5+.') print('You have python %s.' % sys.version) print('Please update your environment') return 1 + # Set the meson command that will be used to run scripts and so on + set_meson_command(mainfile) args = original_args[:] if len(args) > 0: # First check if we want to run a subcommand. @@ -352,9 +366,7 @@ def run(original_args, mainfile=None): else: dir2 = '.' try: - if mainfile is None: - raise AssertionError('I iz broken. Sorry.') - app = MesonApp(dir1, dir2, mainfile, handshake, options, original_args) + app = MesonApp(dir1, dir2, handshake, options, original_args) except Exception as e: # Log directory does not exist, so just print # to stdout. @@ -382,3 +394,11 @@ def run(original_args, mainfile=None): mlog.shutdown() return 0 + +def main(): + # Always resolve the command path so Ninja can find it for regen, tests, etc. + launcher = os.path.realpath(sys.argv[0]) + return run(sys.argv[1:], launcher) + +if __name__ == '__main__': + sys.exit(main()) diff --git a/mesonbuild/scripts/regen_checker.py b/mesonbuild/scripts/regen_checker.py index a9b00c7..80d9242 100644 --- a/mesonbuild/scripts/regen_checker.py +++ b/mesonbuild/scripts/regen_checker.py @@ -14,7 +14,6 @@ import sys, os import pickle, subprocess -from mesonbuild.mesonlib import meson_command # This could also be used for XCode. @@ -32,7 +31,7 @@ def need_regen(regeninfo, regen_timestamp): Vs2010Backend.touch_regen_timestamp(regeninfo.build_dir) return False -def regen(regeninfo, mesonscript, backend): +def regen(regeninfo, meson_command, backend): cmd = meson_command + ['--internal', 'regenerate', regeninfo.build_dir, @@ -48,11 +47,10 @@ def run(args): regeninfo = pickle.load(f) with open(coredata, 'rb') as f: coredata = pickle.load(f) - mesonscript = coredata.meson_script_launcher backend = coredata.get_builtin_option('backend') regen_timestamp = os.stat(dumpfile).st_mtime if need_regen(regeninfo, regen_timestamp): - regen(regeninfo, mesonscript, backend) + regen(regeninfo, coredata.meson_command, backend) sys.exit(0) if __name__ == '__main__': diff --git a/run_meson_command_tests.py b/run_meson_command_tests.py new file mode 100755 index 0000000..6efd911 --- /dev/null +++ b/run_meson_command_tests.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python3 + +# Copyright 2018 The Meson development team +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# 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 os +import tempfile +import unittest +import subprocess +from pathlib import Path + +from mesonbuild.mesonlib import windows_proof_rmtree, python_command, is_windows + +# Find the meson.py adjacent to us +meson_py = Path(__file__).resolve().parent / 'meson.py' +if not meson_py.is_file(): + raise RuntimeError("meson.py not found: test must only run from git") + +def get_pypath(): + import sysconfig + pypath = sysconfig.get_path('purelib', vars={'base': ''}) + # Ensure that / is the path separator and not \, then strip / + return Path(pypath).as_posix().strip('/') + +def get_pybindir(): + import sysconfig + # 'Scripts' on Windows and 'bin' on other platforms including MSYS + return sysconfig.get_path('scripts', vars={'base': ''}).strip('\\/') + +class CommandTests(unittest.TestCase): + ''' + Test that running meson in various ways works as expected by checking the + value of mesonlib.meson_command that was set during configuration. + ''' + + def setUp(self): + super().setUp() + self.orig_env = os.environ.copy() + self.orig_dir = os.getcwd() + os.environ['MESON_COMMAND_TESTS'] = '1' + self.tmpdir = Path(tempfile.mkdtemp()).resolve() + self.src_root = Path(__file__).resolve().parent + self.testdir = str(self.src_root / 'test cases/common/1 trivial') + self.meson_args = ['--backend=ninja'] + + def tearDown(self): + try: + windows_proof_rmtree(str(self.tmpdir)) + except FileNotFoundError: + pass + os.environ.clear() + os.environ.update(self.orig_env) + os.chdir(str(self.orig_dir)) + super().tearDown() + + def _run(self, command, workdir=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. + p = subprocess.run(command, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, env=os.environ.copy(), + universal_newlines=True, cwd=workdir, timeout=60 * 5) + print(p.stdout) + if p.returncode != 0: + raise subprocess.CalledProcessError(p.returncode, command) + return p.stdout + + def assertMesonCommandIs(self, line, cmd): + self.assertTrue(line.startswith('meson_command '), msg=line) + self.assertEqual(line, 'meson_command is {!r}'.format(cmd)) + + def test_meson_uninstalled(self): + # This is what the meson command must be for all these cases + resolved_meson_command = python_command + [str(self.src_root / 'meson.py')] + # Absolute path to meson.py + os.chdir('/') + builddir = str(self.tmpdir / 'build1') + meson_py = str(self.src_root / 'meson.py') + meson_setup = [meson_py, 'setup'] + meson_command = python_command + meson_setup + self.meson_args + stdo = self._run(meson_command + [self.testdir, builddir]) + self.assertMesonCommandIs(stdo.split('\n')[0], resolved_meson_command) + # ./meson.py + os.chdir(str(self.src_root)) + builddir = str(self.tmpdir / 'build2') + meson_py = './meson.py' + meson_setup = [meson_py, 'setup'] + meson_command = python_command + meson_setup + self.meson_args + stdo = self._run(meson_command + [self.testdir, builddir]) + self.assertMesonCommandIs(stdo.split('\n')[0], resolved_meson_command) + # Symlink to meson.py + if is_windows(): + # Symlinks require admin perms + return + os.chdir(str(self.src_root)) + builddir = str(self.tmpdir / 'build3') + # Create a symlink to meson.py in bindir, and add it to PATH + bindir = (self.tmpdir / 'bin') + bindir.mkdir() + (bindir / 'meson').symlink_to(self.src_root / 'meson.py') + os.environ['PATH'] = str(bindir) + os.pathsep + os.environ['PATH'] + # See if it works! + meson_py = 'meson' + meson_setup = [meson_py, 'setup'] + meson_command = meson_setup + self.meson_args + stdo = self._run(meson_command + [self.testdir, builddir]) + self.assertMesonCommandIs(stdo.split('\n')[0], resolved_meson_command) + + def test_meson_installed(self): + # Install meson + prefix = self.tmpdir / 'prefix' + pylibdir = prefix / get_pypath() + bindir = prefix / get_pybindir() + pylibdir.mkdir(parents=True) + os.environ['PYTHONPATH'] = str(pylibdir) + os.environ['PATH'] = str(bindir) + os.pathsep + os.environ['PATH'] + self._run(python_command + ['setup.py', 'install', '--prefix', str(prefix)]) + self.assertTrue(pylibdir.is_dir()) + self.assertTrue(bindir.is_dir()) + # Run `meson` + os.chdir('/') + if is_windows(): + resolved_meson_command = python_command + [str(bindir / 'meson.py')] + else: + resolved_meson_command = python_command + [str(bindir / 'meson')] + # The python configuration on appveyor does not register .py as + # a valid extension, so we cannot run `meson` on Windows. + builddir = str(self.tmpdir / 'build1') + meson_setup = ['meson', 'setup'] + meson_command = meson_setup + self.meson_args + stdo = self._run(meson_command + [self.testdir, builddir]) + self.assertMesonCommandIs(stdo.split('\n')[0], resolved_meson_command) + # Run `/path/to/meson` + builddir = str(self.tmpdir / 'build2') + if is_windows(): + # Cannot run .py directly because of the appveyor configuration, + # and the script is named meson.py, not meson + meson_setup = python_command + [str(bindir / 'meson.py'), 'setup'] + else: + meson_setup = [str(bindir / 'meson'), 'setup'] + meson_command = meson_setup + self.meson_args + stdo = self._run(meson_command + [self.testdir, builddir]) + self.assertMesonCommandIs(stdo.split('\n')[0], resolved_meson_command) + # Run `python3 -m mesonbuild.mesonmain` + resolved_meson_command = python_command + ['-m', 'mesonbuild.mesonmain'] + builddir = str(self.tmpdir / 'build3') + meson_setup = ['-m', 'mesonbuild.mesonmain', 'setup'] + meson_command = python_command + meson_setup + self.meson_args + stdo = self._run(meson_command + [self.testdir, builddir]) + self.assertMesonCommandIs(stdo.split('\n')[0], resolved_meson_command) + if is_windows(): + # Next part requires a shell + return + # `meson` is a wrapper to `meson.real` + resolved_meson_command = python_command + [str(bindir / 'meson.real')] + builddir = str(self.tmpdir / 'build4') + (bindir / 'meson').rename(bindir / 'meson.real') + wrapper = (bindir / 'meson') + with open(wrapper, 'w') as f: + f.write('#!/bin/sh\n\nmeson.real "$@"') + wrapper.chmod(0o755) + meson_setup = [str(wrapper), 'setup'] + meson_command = meson_setup + self.meson_args + stdo = self._run(meson_command + [self.testdir, builddir]) + self.assertMesonCommandIs(stdo.split('\n')[0], resolved_meson_command) + + def test_meson_exe_windows(self): + raise unittest.SkipTest('NOT IMPLEMENTED') + +if __name__ == '__main__': + unittest.main(buffer=True) diff --git a/run_project_tests.py b/run_project_tests.py index 3801432..0b77a37 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -38,8 +38,7 @@ import time import multiprocessing from concurrent.futures import ProcessPoolExecutor import re -from run_unittests import get_fake_options, run_configure - +from run_tests import get_fake_options, run_configure, get_meson_script from run_tests import get_backend_commands, get_backend_args_for_dir, Backend from run_tests import ensure_backend_detects_changes @@ -88,12 +87,6 @@ no_meson_log_msg = 'No meson-log.txt found.' system_compiler = None -meson_command = os.path.join(os.getcwd(), 'meson') -if not os.path.exists(meson_command): - meson_command += '.py' - if not os.path.exists(meson_command): - raise RuntimeError('Could not find main Meson script to run.') - class StopException(Exception): def __init__(self): super().__init__('Stopped by user') @@ -324,7 +317,7 @@ def _run_test(testdir, test_build_dir, install_dir, extra_args, compiler, backen if pass_libdir_to_test(testdir): gen_args += ['--libdir', 'lib'] gen_args += [testdir, test_build_dir] + flags + test_args + extra_args - (returncode, stdo, stde) = run_configure(meson_command, gen_args) + (returncode, stdo, stde) = run_configure(gen_args) try: logfile = Path(test_build_dir, 'meson-logs', 'meson-log.txt') mesonlog = logfile.open(errors='ignore', encoding='utf-8').read() @@ -417,7 +410,7 @@ def have_d_compiler(): def have_objc_compiler(): with AutoDeletedDir(tempfile.mkdtemp(prefix='b ', dir='.')) as build_dir: - env = environment.Environment(None, build_dir, None, get_fake_options('/'), []) + env = environment.Environment(None, build_dir, get_fake_options('/'), []) try: objc_comp = env.detect_objc_compiler(False) except mesonlib.MesonException: @@ -432,7 +425,7 @@ def have_objc_compiler(): def have_objcpp_compiler(): with AutoDeletedDir(tempfile.mkdtemp(prefix='b ', dir='.')) as build_dir: - env = environment.Environment(None, build_dir, None, get_fake_options('/'), []) + env = environment.Environment(None, build_dir, get_fake_options('/'), []) try: objcpp_comp = env.detect_objcpp_compiler(False) except mesonlib.MesonException: @@ -523,6 +516,7 @@ def detect_tests_to_run(): ('python3', 'python3', backend is not Backend.ninja), ('fpga', 'fpga', shutil.which('yosys') is None), ('frameworks', 'frameworks', False), + ('nasm', 'nasm', False), ] gathered_tests = [(name, gather_tests(Path('test cases', subdir)), skip) for name, subdir, skip in all_tests] return gathered_tests @@ -647,11 +641,12 @@ def check_format(): check_file(fullname) def check_meson_commands_work(): - global backend, meson_command, compile_commands, test_commands, install_commands + global backend, compile_commands, test_commands, install_commands testdir = PurePath('test cases', 'common', '1 trivial').as_posix() + meson_commands = mesonlib.python_command + [get_meson_script()] with AutoDeletedDir(tempfile.mkdtemp(prefix='b ', dir='.')) as build_dir: print('Checking that configuring works...') - gen_cmd = mesonlib.meson_command + [testdir, build_dir] + backend_flags + gen_cmd = meson_commands + [testdir, build_dir] + backend_flags pc, o, e = Popen_safe(gen_cmd) if pc.returncode != 0: raise RuntimeError('Failed to configure {!r}:\n{}\n{}'.format(testdir, e, o)) diff --git a/run_tests.py b/run_tests.py index 648e6ce..736cdc0 100755 --- a/run_tests.py +++ b/run_tests.py @@ -21,13 +21,16 @@ import shutil import subprocess import tempfile import platform +from io import StringIO +from enum import Enum +from glob import glob +from pathlib import Path + +import mesonbuild from mesonbuild import mesonlib from mesonbuild import mesonmain from mesonbuild import mlog from mesonbuild.environment import detect_ninja -from io import StringIO -from enum import Enum -from glob import glob Backend = Enum('Backend', 'ninja vs xcode') @@ -42,6 +45,28 @@ if mesonlib.is_windows() or mesonlib.is_cygwin(): else: exe_suffix = '' +def get_meson_script(): + ''' + Guess the meson that corresponds to the `mesonbuild` that has been imported + so we can run configure and other commands in-process, since mesonmain.run + needs to know the meson_command to use. + + Also used by run_unittests.py to determine what meson to run when not + running in-process (which is the default). + ''' + # Is there a meson.py next to the mesonbuild currently in use? + mesonbuild_dir = Path(mesonbuild.__file__).resolve().parent.parent + meson_script = mesonbuild_dir / 'meson.py' + if meson_script.is_file(): + return str(meson_script) + # Then if mesonbuild is in PYTHONPATH, meson must be in PATH + mlog.warning('Could not find meson.py next to the mesonbuild module. ' + 'Trying system meson...') + meson_cmd = shutil.which('meson') + if meson_cmd: + return meson_cmd + raise RuntimeError('Could not find {!r} or a meson in PATH'.format(meson_script)) + def get_backend_args_for_dir(backend, builddir): ''' Visual Studio backend needs to be given the solution to build @@ -133,13 +158,13 @@ def get_fake_options(prefix): def should_run_linux_cross_tests(): return shutil.which('arm-linux-gnueabihf-gcc') and not platform.machine().lower().startswith('arm') -def run_configure_inprocess(meson_command, commandlist): +def run_configure_inprocess(commandlist): old_stdout = sys.stdout sys.stdout = mystdout = StringIO() old_stderr = sys.stderr sys.stderr = mystderr = StringIO() try: - returncode = mesonmain.run(commandlist, meson_command) + returncode = mesonmain.run(commandlist, get_meson_script()) finally: sys.stdout = old_stdout sys.stderr = old_stderr @@ -149,11 +174,11 @@ def run_configure_external(full_command): pc, o, e = mesonlib.Popen_safe(full_command) return pc.returncode, o, e -def run_configure(meson_command, commandlist): +def run_configure(commandlist): global meson_exe if meson_exe: return run_configure_external(meson_exe + commandlist) - return run_configure_inprocess(meson_command, commandlist) + return run_configure_inprocess(commandlist) def print_system_info(): print(mlog.bold('System information.').get_text(mlog.colorize_console)) @@ -214,6 +239,9 @@ if __name__ == '__main__': 'coverage.process_startup()\n') env['COVERAGE_PROCESS_START'] = '.coveragerc' env['PYTHONPATH'] = os.pathsep.join([td] + env.get('PYTHONPATH', [])) + # Meson command tests + returncode += subprocess.call(mesonlib.python_command + ['run_meson_command_tests.py', '-v'], env=env) + # Unit tests returncode += subprocess.call(mesonlib.python_command + ['run_unittests.py', '-v'], env=env) # Ubuntu packages do not have a binary without -6 suffix. if should_run_linux_cross_tests(): @@ -221,5 +249,6 @@ if __name__ == '__main__': print() returncode += subprocess.call(mesonlib.python_command + ['run_cross_test.py', 'cross/ubuntu-armhf.txt'], env=env) + # Project tests returncode += subprocess.call(mesonlib.python_command + ['run_project_tests.py'] + sys.argv[1:], env=env) sys.exit(returncode) diff --git a/run_unittests.py b/run_unittests.py index 552769b..d6b2619 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -36,7 +36,7 @@ import mesonbuild.modules.gnome from mesonbuild.interpreter import ObjectHolder from mesonbuild.mesonlib import ( is_windows, is_osx, is_cygwin, is_dragonflybsd, - windows_proof_rmtree, python_command, meson_command, version_compare, + windows_proof_rmtree, python_command, version_compare, BuildDirLock ) from mesonbuild.environment import Environment, detect_ninja @@ -44,9 +44,9 @@ from mesonbuild.mesonlib import MesonException, EnvironmentException from mesonbuild.dependencies import PkgConfigDependency, ExternalProgram import mesonbuild.modules.pkgconfig -from run_tests import exe_suffix, get_fake_options +from run_tests import exe_suffix, get_fake_options, get_meson_script from run_tests import get_builddir_target_args, get_backend_commands, Backend -from run_tests import ensure_backend_detects_changes, run_configure, meson_exe +from run_tests import ensure_backend_detects_changes, run_configure_inprocess from run_tests import should_run_linux_cross_tests @@ -488,13 +488,13 @@ class BasePlatformTests(unittest.TestCase): # Get the backend # FIXME: Extract this from argv? self.backend = getattr(Backend, os.environ.get('MESON_UNIT_TEST_BACKEND', 'ninja')) - self.meson_mainfile = os.path.join(src_root, 'meson.py') self.meson_args = ['--backend=' + self.backend.name] self.meson_cross_file = None - self.meson_command = meson_command + self.meson_args - self.mconf_command = meson_command + ['configure'] - self.mintro_command = meson_command + ['introspect'] - self.wrap_command = meson_command + ['wrap'] + 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'] # Backend-specific build commands self.build_command, self.clean_command, self.test_command, self.install_command, \ self.uninstall_command = get_backend_commands(self.backend) @@ -521,7 +521,7 @@ class BasePlatformTests(unittest.TestCase): 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') - self.mtest_command = meson_command + ['test', '-C', self.builddir] + self.mtest_command = self.meson_command + ['test', '-C', self.builddir] self.builddirs.append(self.builddir) def new_builddir(self): @@ -581,7 +581,7 @@ class BasePlatformTests(unittest.TestCase): self.privatedir = os.path.join(self.builddir, 'meson-private') if inprocess: try: - (returncode, out, err) = run_configure(self.meson_mainfile, self.meson_args + args + extra_args) + (returncode, out, err) = run_configure_inprocess(self.meson_args + args + extra_args) if 'MESON_SKIP_TEST' in out: raise unittest.SkipTest('Project requested skipping.') if returncode != 0: @@ -601,7 +601,7 @@ class BasePlatformTests(unittest.TestCase): mesonbuild.mlog.log_file = None else: try: - out = self._run(self.meson_command + args + extra_args) + out = self._run(self.setup_command + args + extra_args) except unittest.SkipTest: raise unittest.SkipTest('Project requested skipping: ' + srcdir) except: @@ -902,8 +902,7 @@ class AllPlatformTests(BasePlatformTests): https://github.com/mesonbuild/meson/issues/1355 ''' testdir = os.path.join(self.common_test_dir, '3 static') - env = Environment(testdir, self.builddir, self.meson_command, - get_fake_options(self.prefix), []) + env = Environment(testdir, self.builddir, get_fake_options(self.prefix), []) cc = env.detect_c_compiler(False) static_linker = env.detect_static_linker(cc) if is_windows(): @@ -1184,8 +1183,7 @@ class AllPlatformTests(BasePlatformTests): if not is_windows(): langs += [('objc', 'OBJC'), ('objcpp', 'OBJCXX')] testdir = os.path.join(self.unit_test_dir, '5 compiler detection') - env = Environment(testdir, self.builddir, self.meson_command, - get_fake_options(self.prefix), []) + env = Environment(testdir, self.builddir, get_fake_options(self.prefix), []) for lang, evar in langs: # Detect with evar and do sanity checks on that if evar in os.environ: @@ -1287,8 +1285,7 @@ class AllPlatformTests(BasePlatformTests): def test_always_prefer_c_compiler_for_asm(self): testdir = os.path.join(self.common_test_dir, '141 c cpp and asm') # Skip if building with MSVC - env = Environment(testdir, self.builddir, self.meson_command, - get_fake_options(self.prefix), []) + env = Environment(testdir, self.builddir, get_fake_options(self.prefix), []) if env.detect_c_compiler(False).get_id() == 'msvc': raise unittest.SkipTest('MSVC can\'t compile assembly') self.init(testdir) @@ -1546,8 +1543,7 @@ int main(int argc, char **argv) { self.assertPathExists(os.path.join(testdir, i)) def detect_prebuild_env(self): - env = Environment('', self.builddir, self.meson_command, - get_fake_options(self.prefix), []) + env = Environment('', self.builddir, get_fake_options(self.prefix), []) cc = env.detect_c_compiler(False) stlinker = env.detect_static_linker(cc) if mesonbuild.mesonlib.is_windows(): @@ -1715,8 +1711,7 @@ int main(int argc, char **argv) { '--libdir=' + libdir]) # Find foo dependency os.environ['PKG_CONFIG_LIBDIR'] = self.privatedir - env = Environment(testdir, self.builddir, self.meson_command, - get_fake_options(self.prefix), []) + env = Environment(testdir, self.builddir, get_fake_options(self.prefix), []) kwargs = {'required': True, 'silent': True} foo_dep = PkgConfigDependency('libfoo', env, kwargs) # Ensure link_args are properly quoted @@ -1849,16 +1844,16 @@ int main(int argc, char **argv) { for lang in ('c', 'cpp'): for type in ('executable', 'library'): with tempfile.TemporaryDirectory() as tmpdir: - self._run(meson_command + ['init', '--language', lang, '--type', type], + self._run(self.meson_command + ['init', '--language', lang, '--type', type], workdir=tmpdir) - self._run(self.meson_command + ['--backend=ninja', 'builddir'], + self._run(self.setup_command + ['--backend=ninja', 'builddir'], workdir=tmpdir) self._run(ninja, workdir=os.path.join(tmpdir, 'builddir')) with tempfile.TemporaryDirectory() as tmpdir: with open(os.path.join(tmpdir, 'foo.' + lang), 'w') as f: f.write('int main() {}') - self._run(meson_command + ['init', '-b'], workdir=tmpdir) + 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. @@ -1983,8 +1978,7 @@ recommended as it can lead to undefined behaviour on some platforms''') testdirbase = os.path.join(self.unit_test_dir, '26 guessed linker dependencies') testdirlib = os.path.join(testdirbase, 'lib') extra_args = None - env = Environment(testdirlib, self.builddir, self.meson_command, - get_fake_options(self.prefix), []) + env = Environment(testdirlib, self.builddir, get_fake_options(self.prefix), []) if env.detect_c_compiler(False).get_id() != 'msvc': # 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 @@ -2146,9 +2140,6 @@ class FailureTests(BasePlatformTests): Assert that running meson configure on the specified @contents raises a error message matching regex @match. ''' - if meson_exe is not None: - # Because the exception happens in a different process. - raise unittest.SkipTest('Can not test assert raise tests with an external Meson command.') if langs is None: langs = [] with open(self.mbuild, 'w') as f: @@ -2283,8 +2274,7 @@ class FailureTests(BasePlatformTests): ''' Test that when we can't detect objc or objcpp, we fail gracefully. ''' - env = Environment('', self.builddir, self.meson_command, - get_fake_options(self.prefix), []) + env = Environment('', self.builddir, get_fake_options(self.prefix), []) try: env.detect_objc_compiler(False) env.detect_objcpp_compiler(False) @@ -2393,8 +2383,7 @@ class WindowsTests(BasePlatformTests): ExternalLibraryHolder from build files. ''' testdir = os.path.join(self.platform_test_dir, '1 basic') - env = Environment(testdir, self.builddir, self.meson_command, - get_fake_options(self.prefix), []) + env = Environment(testdir, self.builddir, get_fake_options(self.prefix), []) cc = env.detect_c_compiler(False) if cc.id != 'msvc': raise unittest.SkipTest('Not using MSVC') @@ -2464,8 +2453,7 @@ class LinuxlikeTests(BasePlatformTests): ''' testdir = os.path.join(self.common_test_dir, '51 pkgconfig-gen') self.init(testdir) - env = Environment(testdir, self.builddir, self.meson_command, - get_fake_options(self.prefix), []) + env = Environment(testdir, self.builddir, get_fake_options(self.prefix), []) kwargs = {'required': True, 'silent': True} os.environ['PKG_CONFIG_LIBDIR'] = self.privatedir foo_dep = PkgConfigDependency('libfoo', env, kwargs) @@ -2715,8 +2703,7 @@ class LinuxlikeTests(BasePlatformTests): an ordinary test because it requires passing options to meson. ''' testdir = os.path.join(self.common_test_dir, '1 trivial') - env = Environment(testdir, self.builddir, self.meson_command, - get_fake_options(self.prefix), []) + env = Environment(testdir, self.builddir, get_fake_options(self.prefix), []) cc = env.detect_c_compiler(False) self._test_stds_impl(testdir, cc, 'c') @@ -2726,8 +2713,7 @@ class LinuxlikeTests(BasePlatformTests): be an ordinary test because it requires passing options to meson. ''' testdir = os.path.join(self.common_test_dir, '2 cpp') - env = Environment(testdir, self.builddir, self.meson_command, - get_fake_options(self.prefix), []) + env = Environment(testdir, self.builddir, get_fake_options(self.prefix), []) cpp = env.detect_cpp_compiler(False) self._test_stds_impl(testdir, cpp, 'cpp') diff --git a/test cases/nasm/1 configure file/hello.asm b/test cases/nasm/1 configure file/hello.asm new file mode 100644 index 0000000..4188b8d --- /dev/null +++ b/test cases/nasm/1 configure file/hello.asm @@ -0,0 +1,27 @@ +; hello.asm a first program for nasm for Linux, Intel, gcc +; +; assemble: nasm -f elf -l hello.lst hello.asm +; link: gcc -o hello hello.o +; run: hello +; output is: Hello World + +%include "config.asm" + + SECTION .data ; data section +msg: db "Hello World",10 ; the string to print, 10=cr +len: equ $-msg ; "$" means "here" + ; len is a value, not an address + + SECTION .text ; code section + global main ; make label available to linker +main: ; standard gcc entry point + + mov edx,len ; arg3, length of string to print + mov ecx,msg ; arg2, pointer to string + mov ebx,1 ; arg1, where to write, screen + mov eax,4 ; write sysout command to int 80 hex + int 0x80 ; interrupt 80 hex, call kernel + + mov ebx,HELLO ; exit code, 0=normal + mov eax,1 ; exit command to kernel + int 0x80 ; interrupt 80 hex, call kernel diff --git a/test cases/nasm/1 configure file/meson.build b/test cases/nasm/1 configure file/meson.build new file mode 100644 index 0000000..213e114 --- /dev/null +++ b/test cases/nasm/1 configure file/meson.build @@ -0,0 +1,44 @@ +project('nasm config file', 'c') + +if host_machine.cpu_family() == 'x86' and host_machine.system() == 'windows' + asm_format = 'win32' +elif host_machine.cpu_family() == 'x86_64' and host_machine.system() == 'windows' + asm_format = 'win64' +elif host_machine.cpu_family() == 'x86' and host_machine.system() == 'linux' + asm_format = 'elf32' +elif host_machine.cpu_family() == 'x86_64' and host_machine.system() == 'linux' + asm_format = 'elf64' +else + error('MESON_SKIP_TEST: skipping test on this platform') +endif + +nasm = find_program('nasm', required: false) + +if not nasm.found() + error('MESON_SKIP_TEST: nasm not available') +endif + +conf = configuration_data() + +conf.set('HELLO', 0) + +asm_gen = generator(nasm, + output : '@BASENAME@.o', + arguments : [ + '-f', asm_format, + '-i', meson.current_source_dir() + '/', + '-i', join_paths(meson.current_source_dir(), ''), + '-P', join_paths(meson.current_build_dir(), 'config.asm'), + '@INPUT@', + '-o', '@OUTPUT@']) + + +config_file = configure_file( + output: 'config.asm', + configuration: conf, + output_format: 'nasm', +) + +exe = executable('hello', asm_gen.process('hello.asm')) + +test('test-nasm-configure-file', exe) |