diff options
23 files changed, 426 insertions, 87 deletions
diff --git a/.appveyor.yml b/.appveyor.yml index 289758d..8f215c9 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -20,6 +20,16 @@ environment: compiler: msvc2015 backend: vs2015 + - arch: x86 + compiler: msvc2017 + backend: ninja + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + + - arch: x86 + compiler: msvc2017 + backend: vs2017 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + - arch: x64 compiler: msvc2015 backend: ninja @@ -28,6 +38,16 @@ environment: compiler: msvc2015 backend: vs2015 + - arch: x64 + compiler: msvc2017 + backend: ninja + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + + - arch: x64 + compiler: msvc2017 + backend: vs2017 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + platform: - x64 @@ -43,6 +63,7 @@ install: - cmd: echo Using Python at %MESON_PYTHON_PATH% - cmd: if %compiler%==msvc2010 ( call "C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\vcvarsall.bat" %arch% ) - cmd: if %compiler%==msvc2015 ( call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" %arch% ) + - cmd: if %compiler%==msvc2017 ( call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\Tools\VsDevCmd.bat" -arch=%arch% ) build_script: - cmd: echo No build step. @@ -10,12 +10,12 @@ build system. [](https://travis-ci.org/mesonbuild/meson) [](https://ci.appveyor.com/project/jpakkane/meson) -####Dependencies +#### Dependencies - [Python](http://python.org) (version 3.4 or newer) - [Ninja](https://ninja-build.org) (version 1.5 or newer) -####Installing from source +#### Installing from source You can run Meson directly from a revision control checkout or an extracted tarball. If you wish you can install it locally with the @@ -44,7 +44,7 @@ This will zip all files inside the source checkout into the script which includes hundreds of tests, so you might want to temporarily remove those before running it. -####Running +#### Running Meson requires that you have a source directory and a build directory and that these two are different. In your source root must exist a file @@ -77,18 +77,18 @@ Install is the same but it can take an extra argument: you may need to run this command with sudo. -####Contributing +#### Contributing We love code contributions. See the contributing.txt file for details. -####IRC +#### IRC The irc channel for Meson is `#mesonbuild` over at Freenode. -####Further info +#### Further info More information about the Meson build system can be found at the [project's home page](http://mesonbuild.com). diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index e0106b2..a167c46 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -146,6 +146,7 @@ class NinjaBackend(backends.Backend): super().__init__(build) self.name = 'ninja' self.ninja_filename = 'build.ninja' + self.target_arg_cache = {} self.fortran_deps = {} self.all_outputs = {} @@ -1802,17 +1803,7 @@ rule FORTRAN_DEP_HACK incs += compiler.get_include_args(i, False) return incs - def generate_single_compile(self, target, outfile, src, is_generated=False, header_deps=[], order_deps=[]): - """ - Compiles C/C++, ObjC/ObjC++, Fortran, and D sources - """ - if isinstance(src, str) and src.endswith('.h'): - raise AssertionError('BUG: sources should not contain headers {!r}'.format(src)) - if isinstance(src, RawFilename) and src.fname.endswith('.h'): - raise AssertionError('BUG: sources should not contain headers {!r}'.format(src.fname)) - extra_orderdeps = [] - compiler = get_compiler_for_source(target.compilers.values(), src) - + def _generate_single_compile(self, target, compiler, is_generated=False): # Create an empty commands list, and start adding arguments from # various sources in the order in which they must override each other commands = CompilerArgs(compiler) @@ -1881,6 +1872,24 @@ rule FORTRAN_DEP_HACK # Finally add the private dir for the target to the include path. This # must override everything else and must be the final path added. commands += compiler.get_include_args(self.get_target_private_dir(target), False) + return commands + + def generate_single_compile(self, target, outfile, src, is_generated=False, header_deps=[], order_deps=[]): + """ + Compiles C/C++, ObjC/ObjC++, Fortran, and D sources + """ + if isinstance(src, str) and src.endswith('.h'): + raise AssertionError('BUG: sources should not contain headers {!r}'.format(src)) + if isinstance(src, RawFilename) and src.fname.endswith('.h'): + raise AssertionError('BUG: sources should not contain headers {!r}'.format(src.fname)) + compiler = get_compiler_for_source(target.compilers.values(), src) + key = (target, compiler, is_generated) + if key in self.target_arg_cache: + commands = self.target_arg_cache[key] + else: + commands = self._generate_single_compile(target, compiler, is_generated) + self.target_arg_cache[key] = commands + commands = CompilerArgs(commands.compiler, commands) # FIXME: This file handling is atrocious and broken. We need to # replace it with File objects used consistently everywhere. @@ -1966,7 +1975,6 @@ rule FORTRAN_DEP_HACK d = os.path.join(self.get_target_private_dir(target), d) element.add_orderdep(d) element.add_orderdep(pch_dep) - element.add_orderdep(extra_orderdeps) # Convert from GCC-style link argument naming to the naming used by the # current compiler. commands = commands.to_native() diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index e512f0e..929fa93 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -27,6 +27,29 @@ from ..compilers import CompilerArgs from ..mesonlib import MesonException, File from ..environment import Environment +def autodetect_vs_version(build): + vs_version = os.getenv('VisualStudioVersion', None) + if vs_version: + if vs_version == '14.0': + from mesonbuild.backend.vs2015backend import Vs2015Backend + return Vs2015Backend(build) + if vs_version == '15.0': + from mesonbuild.backend.vs2017backend import Vs2017Backend + return Vs2017Backend(build) + raise MesonException('Could not detect Visual Studio (unknown Visual Studio version: "{}")!\n' + 'Please specify the exact backend to use.'.format(vs_version)) + + vs_install_dir = os.getenv('VSINSTALLDIR', None) + if not vs_install_dir: + raise MesonException('Could not detect Visual Studio (neither VisualStudioVersion nor VSINSTALLDIR set in ' + 'environment)!\nPlease specify the exact backend to use.') + + if 'Visual Studio 10.0' in vs_install_dir: + return Vs2010Backend(build) + + raise MesonException('Could not detect Visual Studio (unknown VSINSTALLDIR: "{}")!\n' + 'Please specify the exact backend to use.'.format(vs_install_dir)) + def split_o_flags_args(args): """ Splits any /O args and returns them. Does not take care of flags overriding @@ -62,6 +85,7 @@ class Vs2010Backend(backends.Backend): self.sources_conflicts = {} self.platform_toolset = None self.vs_version = '2010' + self.windows_target_platform_version = None def object_filename_from_source(self, target, source): basename = os.path.basename(source.fname) @@ -354,6 +378,8 @@ class Vs2010Backend(backends.Backend): p.text = self.platform pname = ET.SubElement(globalgroup, 'ProjectName') pname.text = project_name + if self.windows_target_platform_version: + ET.SubElement(globalgroup, 'WindowsTargetPlatformVersion').text = self.windows_target_platform_version ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.Default.props') type_config = ET.SubElement(root, 'PropertyGroup', Label='Configuration') ET.SubElement(type_config, 'ConfigurationType') @@ -403,7 +429,16 @@ class Vs2010Backend(backends.Backend): # from the target dir, not the build root. target.absolute_paths = True (srcs, ofilenames, cmd) = self.eval_custom_target_command(target, True) - ET.SubElement(customstep, 'Command').text = ' '.join(self.quote_arguments(cmd)) + # Always use a wrapper because MSBuild eats random characters when + # there are many arguments. + tdir_abs = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target)) + exe_data = self.serialise_executable(target.command[0], cmd[1:], + # All targets run from the target dir + tdir_abs, + capture=ofilenames[0] if target.capture else None) + wrapper_cmd = [sys.executable, self.environment.get_build_command(), + '--internal', 'exe', exe_data] + ET.SubElement(customstep, 'Command').text = ' '.join(self.quote_arguments(wrapper_cmd)) ET.SubElement(customstep, 'Outputs').text = ';'.join(ofilenames) ET.SubElement(customstep, 'Inputs').text = ';'.join(srcs) ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.targets') @@ -589,6 +624,8 @@ class Vs2010Backend(backends.Backend): p.text = self.platform pname = ET.SubElement(globalgroup, 'ProjectName') pname.text = project_name + if self.windows_target_platform_version: + ET.SubElement(globalgroup, 'WindowsTargetPlatformVersion').text = self.windows_target_platform_version ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.Default.props') # Start configuration type_config = ET.SubElement(root, 'PropertyGroup', Label='Configuration') @@ -1009,6 +1046,8 @@ class Vs2010Backend(backends.Backend): p.text = self.platform pname = ET.SubElement(globalgroup, 'ProjectName') pname.text = project_name + if self.windows_target_platform_version: + ET.SubElement(globalgroup, 'WindowsTargetPlatformVersion').text = self.windows_target_platform_version ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.Default.props') type_config = ET.SubElement(root, 'PropertyGroup', Label='Configuration') ET.SubElement(type_config, 'ConfigurationType').text = "Utility" @@ -1088,6 +1127,8 @@ if %%errorlevel%% neq 0 goto :VCEnd''' p.text = self.platform pname = ET.SubElement(globalgroup, 'ProjectName') pname.text = project_name + if self.windows_target_platform_version: + ET.SubElement(globalgroup, 'WindowsTargetPlatformVersion').text = self.windows_target_platform_version ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.Default.props') type_config = ET.SubElement(root, 'PropertyGroup', Label='Configuration') ET.SubElement(type_config, 'ConfigurationType') diff --git a/mesonbuild/backend/vs2017backend.py b/mesonbuild/backend/vs2017backend.py new file mode 100644 index 0000000..8301790 --- /dev/null +++ b/mesonbuild/backend/vs2017backend.py @@ -0,0 +1,27 @@ +# Copyright 2014-2016 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +from .vs2010backend import Vs2010Backend + + +class Vs2017Backend(Vs2010Backend): + def __init__(self, build): + super().__init__(build) + self.name = 'vs2017' + self.platform_toolset = 'v141' + self.vs_version = '2017' + # WindowsSDKVersion should be set by command prompt. + self.windows_target_platform_version = os.getenv('WindowsSDKVersion', None) diff --git a/mesonbuild/compilers.py b/mesonbuild/compilers.py index e814693..85b56a6 100644 --- a/mesonbuild/compilers.py +++ b/mesonbuild/compilers.py @@ -1001,44 +1001,75 @@ class CCompiler(Compiler): mlog.debug(se) return RunResult(True, pe.returncode, so, se) - def _bisect_compiles(self, t, fargs, env, extra_args, dependencies): - # FIXME: Does not actually do bisection right now - for i in range(1, 1024): - fargs['size'] = i - if self.compiles(t.format(**fargs), env, extra_args, dependencies): - if self.id == 'msvc': - # MSVC refuses to construct an array of zero size, so - # the test only succeeds when i is sizeof(element) + 1 - return i - 1 - return i + def _compile_int(self, expression, prefix, env, extra_args, dependencies): + fargs = {'prefix': prefix, 'expression': expression} + t = '''#include <stdio.h> + {prefix} + int main() {{ static int a[1-2*!({expression})]; a[0]=0; return 0; }}''' + return self.compiles(t.format(**fargs), env, extra_args, dependencies) + + def cross_compute_int(self, expression, l, h, guess, prefix, env, extra_args, dependencies): + if isinstance(guess, int): + if self._compile_int('%s == %d' % (expression, guess), prefix, env, extra_args, dependencies): + return guess + + cur = l + while l < h: + cur = int((l + h) / 2) + if cur == l: + break + + if self._compile_int('%s >= %d' % (expression, cur), prefix, env, extra_args, dependencies): + l = cur + else: + h = cur + + if self._compile_int('%s == %d' % (expression, cur), prefix, env, extra_args, dependencies): + return cur raise EnvironmentException('Cross-compile check overflowed') - def cross_sizeof(self, element, prefix, env, extra_args=None, dependencies=None): + def compute_int(self, expression, l, h, guess, prefix, env, extra_args=None, dependencies=None): + if extra_args is None: + extra_args = [] + if self.is_cross: + return self.cross_compute_int(expression, l, h, guess, prefix, env, extra_args, dependencies) + fargs = {'prefix': prefix, 'expression': expression} + t = '''#include<stdio.h> + {prefix} + int main(int argc, char **argv) {{ + printf("%ld\\n", (long)({expression})); + return 0; + }};''' + res = self.run(t.format(**fargs), env, extra_args, dependencies) + if not res.compiled: + return -1 + if res.returncode != 0: + raise EnvironmentException('Could not run compute_int test binary.') + return int(res.stdout) + + def cross_sizeof(self, typename, prefix, env, extra_args=None, dependencies=None): if extra_args is None: extra_args = [] - fargs = {'prefix': prefix, 'name': element} + fargs = {'prefix': prefix, 'type': typename} t = '''#include <stdio.h> {prefix} int main(int argc, char **argv) {{ - {name} something; + {type} something; }}''' if not self.compiles(t.format(**fargs), env, extra_args, dependencies): return -1 - t = '''#include <stdio.h> - {prefix} - int temparray[{size}-sizeof({name})];''' - return self._bisect_compiles(t, fargs, env, extra_args, dependencies) + return self.cross_compute_int('sizeof(%s)' % typename, 1, 128, None, prefix, env, extra_args, dependencies) - def sizeof(self, element, prefix, env, extra_args=None, dependencies=None): + def sizeof(self, typename, prefix, env, extra_args=None, dependencies=None): if extra_args is None: extra_args = [] - fargs = {'prefix': prefix, 'name': element} + fargs = {'prefix': prefix, 'type': typename} if self.is_cross: - return self.cross_sizeof(element, prefix, env, extra_args, dependencies) + return self.cross_sizeof(typename, prefix, env, extra_args, dependencies) t = '''#include<stdio.h> {prefix} int main(int argc, char **argv) {{ - printf("%ld\\n", (long)(sizeof({name}))); + printf("%ld\\n", (long)(sizeof({type}))); return 0; }};''' res = self.run(t.format(**fargs), env, extra_args, dependencies) @@ -1048,32 +1079,34 @@ class CCompiler(Compiler): raise EnvironmentException('Could not run sizeof test binary.') return int(res.stdout) - def cross_alignment(self, typename, env, extra_args=None, dependencies=None): + def cross_alignment(self, typename, prefix, env, extra_args=None, dependencies=None): if extra_args is None: extra_args = [] - fargs = {'type': typename} + fargs = {'prefix': prefix, 'type': typename} t = '''#include <stdio.h> + {prefix} int main(int argc, char **argv) {{ {type} something; }}''' if not self.compiles(t.format(**fargs), env, extra_args, dependencies): return -1 t = '''#include <stddef.h> + {prefix} struct tmp {{ char c; {type} target; - }}; - int testarray[{size}-offsetof(struct tmp, target)];''' - return self._bisect_compiles(t, fargs, env, extra_args, dependencies) + }};''' + return self.cross_compute_int('offsetof(struct tmp, target)', 1, 1024, None, t.format(**fargs), env, extra_args, dependencies) - def alignment(self, typename, env, extra_args=None, dependencies=None): + def alignment(self, typename, prefix, env, extra_args=None, dependencies=None): if extra_args is None: extra_args = [] if self.is_cross: - return self.cross_alignment(typename, env, extra_args, dependencies) - fargs = {'type': typename} + return self.cross_alignment(typename, prefix, env, extra_args, dependencies) + fargs = {'prefix': prefix, 'type': typename} t = '''#include <stdio.h> #include <stddef.h> + {prefix} struct tmp {{ char c; {type} target; diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index e88cdc8..9562211 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -18,7 +18,7 @@ from .mesonlib import MesonException, commonpath from .mesonlib import default_libdir, default_libexecdir, default_prefix version = '0.40.0.dev1' -backendlist = ['ninja', 'vs2010', 'vs2015', 'xcode'] +backendlist = ['ninja', 'vs', 'vs2010', 'vs2015', 'vs2017', 'xcode'] class UserOption: def __init__(self, name, description, choices): @@ -127,7 +127,7 @@ class CoreData: self.cross_file = os.path.join(os.getcwd(), options.cross_file) else: self.cross_file = None - + self.wrap_mode = options.wrap_mode self.compilers = {} self.cross_compilers = {} self.deps = {} diff --git a/mesonbuild/dependencies.py b/mesonbuild/dependencies.py index e4317f1..031e106 100644 --- a/mesonbuild/dependencies.py +++ b/mesonbuild/dependencies.py @@ -456,7 +456,7 @@ class ExternalProgram: if suffix in self.windows_exts: return True elif os.access(path, os.X_OK): - return True + return not os.path.isdir(path) return False def _search_dir(self, name, search_dir): diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index 5217626..92040c4 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -377,16 +377,30 @@ class Environment: C, C++, ObjC, ObjC++, Fortran so consolidate it here. ''' if self.is_cross_build() and want_cross: - compilers = [mesonlib.stringlistify(self.cross_info.config['binaries'][lang])] - ccache = [] + compilers = mesonlib.stringlistify(self.cross_info.config['binaries'][lang]) + # Ensure ccache exists and remove it if it doesn't + if compilers[0] == 'ccache': + compilers = compilers[1:] + ccache = self.detect_ccache() + else: + ccache = [] + # Return value has to be a list of compiler 'choices' + compilers = [compilers] is_cross = True if self.cross_info.need_exe_wrapper(): exe_wrap = self.cross_info.config['binaries'].get('exe_wrapper', None) else: exe_wrap = [] elif evar in os.environ: - compilers = [shlex.split(os.environ[evar])] - ccache = [] + compilers = shlex.split(os.environ[evar]) + # Ensure ccache exists and remove it if it doesn't + if compilers[0] == 'ccache': + compilers = compilers[1:] + ccache = self.detect_ccache() + else: + ccache = [] + # Return value has to be a list of compiler 'choices' + compilers = [compilers] is_cross = False exe_wrap = None else: diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index c16b668..6e8cf1a 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -20,7 +20,7 @@ from . import mlog from . import build from . import optinterpreter from . import compilers -from .wrap import wrap +from .wrap import wrap, WrapMode from . import mesonlib from .mesonlib import FileMode, Popen_safe from .dependencies import InternalDependency, Dependency @@ -632,6 +632,7 @@ class CompilerHolder(InterpreterObject): self.methods.update({'compiles': self.compiles_method, 'links': self.links_method, 'get_id': self.get_id_method, + 'compute_int': self.compute_int_method, 'sizeof': self.sizeof_method, 'has_header': self.has_header_method, 'has_header_symbol': self.has_header_symbol_method, @@ -700,8 +701,12 @@ class CompilerHolder(InterpreterObject): raise InterpreterException('Alignment method takes exactly one positional argument.') check_stringlist(args) typename = args[0] + prefix = kwargs.get('prefix', '') + if not isinstance(prefix, str): + raise InterpreterException('Prefix argument of sizeof must be a string.') extra_args = mesonlib.stringlistify(kwargs.get('args', [])) - result = self.compiler.alignment(typename, self.environment, extra_args) + deps = self.determine_dependencies(kwargs) + result = self.compiler.alignment(typename, prefix, self.environment, extra_args, deps) mlog.log('Checking for alignment of "', mlog.bold(typename), '": ', result, sep='') return result @@ -823,6 +828,29 @@ class CompilerHolder(InterpreterObject): mlog.log('Checking for type "', mlog.bold(typename), '": ', hadtxt, sep='') return had + def compute_int_method(self, args, kwargs): + if len(args) != 1: + raise InterpreterException('Compute_int takes exactly one argument.') + check_stringlist(args) + expression = args[0] + prefix = kwargs.get('prefix', '') + l = kwargs.get('low', -1024) + h = kwargs.get('high', 1024) + guess = kwargs.get('guess', None) + if not isinstance(prefix, str): + raise InterpreterException('Prefix argument of compute_int must be a string.') + if not isinstance(l, int): + raise InterpreterException('Low argument of compute_int must be an int.') + if not isinstance(h, int): + raise InterpreterException('High argument of compute_int must be an int.') + if guess is not None and not isinstance(guess, int): + raise InterpreterException('Guess argument of compute_int must be an int.') + extra_args = self.determine_args(kwargs) + deps = self.determine_dependencies(kwargs) + res = self.compiler.compute_int(expression, l, h, guess, prefix, self.environment, extra_args, deps) + mlog.log('Computing int of "%s": %d' % (expression, res)) + return res + def sizeof_method(self, args, kwargs): if len(args) != 1: raise InterpreterException('Sizeof takes exactly one argument.') @@ -1470,11 +1498,13 @@ class Interpreter(InterpreterBase): raise InvalidCode('Recursive include of subprojects: %s.' % incpath) if dirname in self.subprojects: return self.subprojects[dirname] - r = wrap.Resolver(os.path.join(self.build.environment.get_source_dir(), self.subproject_dir)) - resolved = r.resolve(dirname) - if resolved is None: - msg = 'Subproject directory {!r} does not exist and cannot be downloaded.' - raise InterpreterException(msg.format(os.path.join(self.subproject_dir, dirname))) + subproject_dir_abs = os.path.join(self.environment.get_source_dir(), self.subproject_dir) + r = wrap.Resolver(subproject_dir_abs, self.coredata.wrap_mode) + try: + resolved = r.resolve(dirname) + except RuntimeError as e: + msg = 'Subproject directory {!r} does not exist and cannot be downloaded:\n{}' + raise InterpreterException(msg.format(os.path.join(self.subproject_dir, dirname), e)) subdir = os.path.join(self.subproject_dir, resolved) os.makedirs(os.path.join(self.build.environment.get_build_dir(), subdir), exist_ok=True) self.args_frozen = True @@ -1881,6 +1911,11 @@ requirements use the version keyword argument instead.''') return fbinfo def dependency_fallback(self, name, kwargs): + if self.coredata.wrap_mode in (WrapMode.nofallback, WrapMode.nodownload): + mlog.log('Not looking for a fallback subproject for the dependency', + mlog.bold(name), 'because:\nAutomatic wrap-based fallback ' + 'dependency downloading is disabled.') + return None dirname, varname = self.get_subproject_infos(kwargs) # Try to execute the subproject try: diff --git a/mesonbuild/mesonmain.py b/mesonbuild/mesonmain.py index 031486c..51041cc 100644 --- a/mesonbuild/mesonmain.py +++ b/mesonbuild/mesonmain.py @@ -20,6 +20,7 @@ from . import build import platform from . import mlog, coredata from .mesonlib import MesonException +from .wrap import WrapMode parser = argparse.ArgumentParser() @@ -67,6 +68,10 @@ parser.add_argument('-D', action='append', dest='projectoptions', default=[], help='Set project options.') parser.add_argument('-v', '--version', action='version', version=coredata.version) + # See the mesonlib.WrapMode enum for documentation +parser.add_argument('--wrap-mode', default=WrapMode.default, + type=lambda t: getattr(WrapMode, t), choices=WrapMode, + help='Special wrap mode to use') parser.add_argument('directories', nargs='*') class MesonApp: @@ -145,12 +150,19 @@ If you want to change option values, use the mesonconf tool instead.''' if self.options.backend == 'ninja': from .backend import ninjabackend g = ninjabackend.NinjaBackend(b) + elif self.options.backend == 'vs': + from .backend import vs2010backend + g = vs2010backend.autodetect_vs_version(b) + mlog.log('Auto detected Visual Studio backend:', mlog.bold(g.name)) elif self.options.backend == 'vs2010': from .backend import vs2010backend g = vs2010backend.Vs2010Backend(b) elif self.options.backend == 'vs2015': from .backend import vs2015backend g = vs2015backend.Vs2015Backend(b) + elif self.options.backend == 'vs2017': + from .backend import vs2017backend + g = vs2017backend.Vs2017Backend(b) elif self.options.backend == 'xcode': from .backend import xcodebackend g = xcodebackend.XCodeBackend(b) diff --git a/mesonbuild/mparser.py b/mesonbuild/mparser.py index 6e1e398..fe5ccc5 100644 --- a/mesonbuild/mparser.py +++ b/mesonbuild/mparser.py @@ -193,9 +193,10 @@ class OrNode: self.right = right class AndNode: - def __init__(self, lineno, colno, left, right): - self.lineno = lineno - self.colno = colno + def __init__(self, left, right): + self.subdir = left.subdir + self.lineno = left.lineno + self.colno = left.colno self.left = left self.right = right @@ -436,7 +437,7 @@ class Parser: def e3(self): left = self.e4() while self.accept('and'): - left = AndNode(left.lineno, left.colno, left, self.e4()) + left = AndNode(left, self.e4()) return left def e4(self): diff --git a/mesonbuild/wrap/__init__.py b/mesonbuild/wrap/__init__.py index e69de29..019634c 100644 --- a/mesonbuild/wrap/__init__.py +++ b/mesonbuild/wrap/__init__.py @@ -0,0 +1,31 @@ +from enum import Enum + +# Used for the --wrap-mode command-line argument +# +# Special wrap modes: +# nofallback: Don't download wraps for dependency() fallbacks +# nodownload: Don't download wraps for all subproject() calls +# +# subprojects are used for two purposes: +# 1. To download and build dependencies by using .wrap +# files if they are not provided by the system. This is +# usually expressed via dependency(..., fallback: ...). +# 2. To download and build 'copylibs' which are meant to be +# used by copying into your project. This is always done +# with an explicit subproject() call. +# +# --wrap-mode=nofallback will never do (1) +# --wrap-mode=nodownload will do neither (1) nor (2) +# +# If you are building from a release tarball, you should be +# able to safely use 'nodownload' since upstream is +# expected to ship all required sources with the tarball. +# +# If you are building from a git repository, you will want +# to use 'nofallback' so that any 'copylib' wraps will be +# download as subprojects. +# +# Note that these options do not affect subprojects that +# are git submodules since those are only usable in git +# repositories, and you almost always want to download them. +WrapMode = Enum('WrapMode', 'default nofallback nodownload') diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py index b1efc13..fcacc16 100644 --- a/mesonbuild/wrap/wrap.py +++ b/mesonbuild/wrap/wrap.py @@ -17,6 +17,8 @@ import contextlib import urllib.request, os, hashlib, shutil import subprocess import sys +from pathlib import Path +from . import WrapMode try: import ssl @@ -36,6 +38,13 @@ def build_ssl_context(): ctx.load_default_certs() return ctx +def quiet_git(cmd): + pc = subprocess.Popen(['git'] + cmd, stdout=subprocess.PIPE) + out, err = pc.communicate() + if pc.returncode != 0: + return False, err + return True, out + def open_wrapdburl(urlstring): global ssl_warning_printed if has_ssl: @@ -86,29 +95,45 @@ class PackageDefinition: return 'patch_url' in self.values class Resolver: - def __init__(self, subdir_root): + def __init__(self, subdir_root, wrap_mode=WrapMode(1)): + self.wrap_mode = wrap_mode self.subdir_root = subdir_root self.cachedir = os.path.join(self.subdir_root, 'packagecache') def resolve(self, packagename): - fname = os.path.join(self.subdir_root, packagename + '.wrap') - dirname = os.path.join(self.subdir_root, packagename) - try: - if os.listdir(dirname): - # The directory is there and not empty? Great, use it. + # Check if the directory is already resolved + dirname = Path(os.path.join(self.subdir_root, packagename)) + subprojdir = os.path.join(*dirname.parts[-2:]) + if dirname.is_dir(): + if (dirname / 'meson.build').is_file(): + # The directory is there and has meson.build? Great, use it. return packagename - else: - mlog.warning('Subproject directory %s is empty, possibly because of an unfinished' - 'checkout, removing to reclone' % dirname) - os.rmdir(dirname) - except NotADirectoryError: - raise RuntimeError('%s is not a directory, can not use as subproject.' % dirname) - except FileNotFoundError: - pass + # Is the dir not empty and also not a git submodule dir that is + # not checkout properly? Can't do anything, exception! + elif next(dirname.iterdir(), None) and not (dirname / '.git').is_file(): + m = '{!r} is not empty and has no meson.build files' + raise RuntimeError(m.format(subprojdir)) + elif dirname.exists(): + m = '{!r} already exists and is not a dir; cannot use as subproject' + raise RuntimeError(m.format(subprojdir)) + dirname = str(dirname) + # Check if the subproject is a git submodule + if self.resolve_git_submodule(dirname): + return packagename + + # Don't download subproject data based on wrap file if requested. + # Git submodules are ok (see above)! + if self.wrap_mode is WrapMode.nodownload: + m = 'Automatic wrap-based subproject downloading is disabled' + raise RuntimeError(m) + + # Check if there's a .wrap file for this subproject + fname = os.path.join(self.subdir_root, packagename + '.wrap') if not os.path.isfile(fname): # No wrap file with this name? Give up. - return None + m = 'No {}.wrap found for {!r}' + raise RuntimeError(m.format(packagename, subprojdir)) p = PackageDefinition(fname) if p.type == 'file': if not os.path.isdir(self.cachedir): @@ -120,9 +145,31 @@ class Resolver: elif p.type == "hg": self.get_hg(p) else: - raise RuntimeError('Unreachable code.') + raise AssertionError('Unreachable code.') return p.get('directory') + def resolve_git_submodule(self, dirname): + # Are we in a git repository? + ret, out = quiet_git(['rev-parse']) + if not ret: + return False + # Is `dirname` a submodule? + ret, out = quiet_git(['submodule', 'status', dirname]) + if not ret: + return False + # Submodule has not been added, add it + if out.startswith(b'-'): + if subprocess.call(['git', 'submodule', 'update', dirname]) != 0: + return False + # Submodule was added already, but it wasn't populated. Do a checkout. + elif out.startswith(b' '): + if subprocess.call(['git', 'checkout', '.'], cwd=dirname): + return True + else: + m = 'Unknown git submodule output: {!r}' + raise AssertionError(m.format(out)) + return True + def get_git(self, p): checkoutdir = os.path.join(self.subdir_root, p.get('directory')) revno = p.get('revision') diff --git a/run_unittests.py b/run_unittests.py index bba5bb8..a11e3a5 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -48,6 +48,7 @@ def get_fake_options(prefix): import argparse opts = argparse.Namespace() opts.cross_file = None + opts.wrap_mode = None opts.prefix = prefix return opts diff --git a/test cases/common/117 custom target capture/meson.build b/test cases/common/117 custom target capture/meson.build index 16b6ac9..58a69ca 100644 --- a/test cases/common/117 custom target capture/meson.build +++ b/test cases/common/117 custom target capture/meson.build @@ -2,10 +2,6 @@ project('custom target', 'c') python3 = import('python3').find_python() -if meson.backend().startswith('vs') - error('MESON_SKIP_TEST: capturing of custom target output is broken with the VS backends') -endif - # Note that this will not add a dependency to the compiler executable. # Code will not be rebuilt if it changes. comp = '@0@/@1@'.format(meson.current_source_dir(), 'my_compiler.py') diff --git a/test cases/common/139 compute int/config.h.in b/test cases/common/139 compute int/config.h.in new file mode 100644 index 0000000..ad8d077 --- /dev/null +++ b/test cases/common/139 compute int/config.h.in @@ -0,0 +1,2 @@ +#define INTSIZE @INTSIZE@ +#define FOOBAR_IN_CONFIG_H @FOOBAR@ diff --git a/test cases/common/139 compute int/foobar.h b/test cases/common/139 compute int/foobar.h new file mode 100644 index 0000000..fd3cb5e --- /dev/null +++ b/test cases/common/139 compute int/foobar.h @@ -0,0 +1,6 @@ +#ifndef __FOOBAR_H__ +#define __FOOBAR_H__ + +#define FOOBAR_IN_FOOBAR_H 10 + +#endif /*__FOOBAR_H__*/ diff --git a/test cases/common/139 compute int/meson.build b/test cases/common/139 compute int/meson.build new file mode 100644 index 0000000..43553fe --- /dev/null +++ b/test cases/common/139 compute int/meson.build @@ -0,0 +1,35 @@ +project('compute int', 'c', 'cpp') + +inc = include_directories('.') + +# Test with C +cc = meson.get_compiler('c') + +intsize = cc.compute_int('sizeof(int)', low : 1, high : 16, guess : 4) +foobar = cc.compute_int('FOOBAR_IN_FOOBAR_H', prefix : '#include "foobar.h"', include_directories : inc) + +cd = configuration_data() +cd.set('INTSIZE', intsize) +cd.set('FOOBAR', foobar) +cd.set('CONFIG', 'config.h') +configure_file(input : 'config.h.in', output : 'config.h', configuration : cd) +s = configure_file(input : 'prog.c.in', output : 'prog.c', configuration : cd) + +e = executable('prog', s) +test('compute int test', e) + +# Test with C++ +cpp = meson.get_compiler('cpp') + +intsize = cpp.compute_int('sizeof(int)') +foobar = cpp.compute_int('FOOBAR_IN_FOOBAR_H', prefix : '#include "foobar.h"', include_directories : inc) + +cdpp = configuration_data() +cdpp.set('INTSIZE', intsize) +cdpp.set('FOOBAR', foobar) +cdpp.set('CONFIG', 'config.hpp') +configure_file(input : 'config.h.in', output : 'config.hpp', configuration : cdpp) +spp = configure_file(input : 'prog.c.in', output : 'prog.cc', configuration : cdpp) + +epp = executable('progpp', spp) +test('compute int test c++', epp) diff --git a/test cases/common/139 compute int/prog.c.in b/test cases/common/139 compute int/prog.c.in new file mode 100644 index 0000000..3ff1463 --- /dev/null +++ b/test cases/common/139 compute int/prog.c.in @@ -0,0 +1,16 @@ +#include "@CONFIG@" +#include <stdio.h> +#include <wchar.h> +#include "foobar.h" + +int main(int argc, char **argv) { + if(INTSIZE != sizeof(int)) { + fprintf(stderr, "Mismatch: computed int size %d, actual size %d.\n", INTSIZE, (int)sizeof(int)); + return 1; + } + if(FOOBAR_IN_CONFIG_H != FOOBAR_IN_FOOBAR_H) { + fprintf(stderr, "Mismatch: computed int %d, should be %d.\n", FOOBAR_IN_CONFIG_H, FOOBAR_IN_FOOBAR_H); + return 1; + } + return 0; +} diff --git a/test cases/common/40 logic ops/meson.build b/test cases/common/40 logic ops/meson.build index e975c7e..897054e 100644 --- a/test cases/common/40 logic ops/meson.build +++ b/test cases/common/40 logic ops/meson.build @@ -87,3 +87,9 @@ if t and t and t and t and t and t and t and t and f else message('Ok.') endif + +if t and t or t + message('Ok.') +else + error('Combination of and-or failed.') +endif diff --git a/test cases/windows/8 msvc dll versioning/copyfile.py b/test cases/windows/8 msvc dll versioning/copyfile.py new file mode 100644 index 0000000..ff42ac3 --- /dev/null +++ b/test cases/windows/8 msvc dll versioning/copyfile.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 + +import sys +import shutil + +shutil.copyfile(sys.argv[1], sys.argv[2]) diff --git a/test cases/windows/8 msvc dll versioning/meson.build b/test cases/windows/8 msvc dll versioning/meson.build index 3f15e76..b72c5ec 100644 --- a/test cases/windows/8 msvc dll versioning/meson.build +++ b/test cases/windows/8 msvc dll versioning/meson.build @@ -28,11 +28,12 @@ onlysoversion = shared_library('onlysoversion', 'lib.c', # Hack to make the executables below depend on the shared libraries above # without actually adding them as `link_with` dependencies since we want to try # linking to them with -lfoo linker arguments. +cp = find_program('copyfile.py') out = custom_target('library-dependency-hack', input : 'exe.orig.c', output : 'exe.c', depends : [some, noversion, onlyversion, onlysoversion], - command : ['cp', '@INPUT@', '@OUTPUT@']) + command : [cp, '@INPUT@', '@OUTPUT@']) # Manually test if the linker can find the above libraries # i.e., whether they were generated with the right naming scheme |