diff options
Diffstat (limited to 'mesonbuild')
33 files changed, 558 insertions, 274 deletions
diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 5a5db22..292b027 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -322,7 +322,7 @@ class Backend: continue if os.path.splitext(libpath)[1] not in ['.dll', '.lib', '.so']: continue - absdir = os.path.split(libpath)[0] + absdir = os.path.dirname(libpath) rel_to_src = absdir[len(self.environment.get_source_dir()) + 1:] assert(not os.path.isabs(rel_to_src)) paths.append(os.path.join(self.build_to_src, rel_to_src)) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 77c7d50..6ab67fb 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -38,8 +38,12 @@ else: execute_wrapper = '' rmfile_prefix = 'rm -f {} &&' -def ninja_quote(text): - for char in ('$', ' ', ':'): +def ninja_quote(text, is_build_line=False): + if is_build_line: + qcs = ('$', ' ', ':') + else: + qcs = ('$', ' ') + for char in qcs: text = text.replace(char, '$' + char) if '\n' in text: errmsg = '''Ninja does not support newlines in rules. The content was: @@ -87,13 +91,13 @@ class NinjaBuildElement: def write(self, outfile): self.check_outputs() - line = 'build %s: %s %s' % (' '.join([ninja_quote(i) for i in self.outfilenames]), + line = 'build %s: %s %s' % (' '.join([ninja_quote(i, True) for i in self.outfilenames]), self.rule, - ' '.join([ninja_quote(i) for i in self.infilenames])) + ' '.join([ninja_quote(i, True) for i in self.infilenames])) if len(self.deps) > 0: - line += ' | ' + ' '.join([ninja_quote(x) for x in self.deps]) + line += ' | ' + ' '.join([ninja_quote(x, True) for x in self.deps]) if len(self.orderdeps) > 0: - line += ' || ' + ' '.join([ninja_quote(x) for x in self.orderdeps]) + line += ' || ' + ' '.join([ninja_quote(x, True) for x in self.orderdeps]) line += '\n' # This is the only way I could find to make this work on all # platforms including Windows command shell. Slash is a dir separator @@ -823,7 +827,7 @@ int dummy; if subdir is None: subdir = os.path.join(manroot, 'man' + num) srcabs = f.absolute_path(self.environment.get_source_dir(), self.environment.get_build_dir()) - dstabs = os.path.join(subdir, os.path.split(f.fname)[1] + '.gz') + dstabs = os.path.join(subdir, os.path.basename(f.fname) + '.gz') i = [srcabs, dstabs] d.man.append(i) @@ -836,24 +840,22 @@ int dummy; subdir = de.install_dir for f in de.sources: assert(isinstance(f, mesonlib.File)) - plain_f = os.path.split(f.fname)[1] + plain_f = os.path.basename(f.fname) dstabs = os.path.join(subdir, plain_f) i = [f.absolute_path(srcdir, builddir), dstabs, de.install_mode] d.data.append(i) def generate_subdir_install(self, d): for sd in self.build.get_install_subdirs(): - inst_subdir = sd.installable_subdir.rstrip('/') - idir_parts = inst_subdir.split('/') - if len(idir_parts) > 1: - subdir = os.path.join(sd.source_subdir, '/'.join(idir_parts[:-1])) - inst_dir = idir_parts[-1] - else: - subdir = sd.source_subdir - inst_dir = sd.installable_subdir - src_dir = os.path.join(self.environment.get_source_dir(), subdir) - dst_dir = os.path.join(self.environment.get_prefix(), sd.install_dir) - d.install_subdirs.append([src_dir, inst_dir, dst_dir, sd.install_mode, sd.exclude]) + src_dir = os.path.join(self.environment.get_source_dir(), + sd.source_subdir, + sd.installable_subdir).rstrip('/') + dst_dir = os.path.join(self.environment.get_prefix(), + sd.install_dir) + if not sd.strip_directory: + dst_dir = os.path.join(dst_dir, os.path.basename(src_dir)) + d.install_subdirs.append([src_dir, dst_dir, sd.install_mode, + sd.exclude]) def generate_tests(self, outfile): self.serialize_tests() @@ -1278,7 +1280,7 @@ int dummy; # Target names really should not have slashes in them, but # unfortunately we did not check for that and some downstream projects # now have them. Once slashes are forbidden, remove this bit. - target_slashname_workaround_dir = os.path.join(os.path.split(target.name)[0], + target_slashname_workaround_dir = os.path.join(os.path.dirname(target.name), self.get_target_dir(target)) else: target_slashname_workaround_dir = self.get_target_dir(target) @@ -1291,7 +1293,11 @@ int dummy; # installations for rpath_arg in rpath_args: args += ['-C', 'link-arg=' + rpath_arg + ':' + os.path.join(rustc.get_sysroot(), 'lib')] - element = NinjaBuildElement(self.all_outputs, target_name, 'rust_COMPILER', relsrc) + crstr = '' + if target.is_cross: + crstr = '_CROSS' + compiler_name = 'rust%s_COMPILER' % crstr + element = NinjaBuildElement(self.all_outputs, target_name, compiler_name, relsrc) if len(orderdeps) > 0: element.add_orderdep(orderdeps) element.add_item('ARGS', args) @@ -1401,7 +1407,7 @@ int dummy; objects = [] # Relative to swift invocation dir rel_objects = [] # Relative to build.ninja for i in abssrc + abs_generated: - base = os.path.split(i)[1] + base = os.path.basename(i) oname = os.path.splitext(base)[0] + '.o' objects.append(oname) rel_objects.append(os.path.join(self.get_target_private_dir(target), oname)) @@ -1579,8 +1585,11 @@ int dummy; outfile.write(restat) outfile.write('\n') - def generate_rust_compile_rules(self, compiler, outfile): - rule = 'rule %s_COMPILER\n' % compiler.get_language() + def generate_rust_compile_rules(self, compiler, outfile, is_cross): + crstr = '' + if is_cross: + crstr = '_CROSS' + rule = 'rule %s%s_COMPILER\n' % (compiler.get_language(), crstr) invoc = ' '.join([ninja_quote(i) for i in compiler.get_exelist()]) command = ' command = %s $ARGS $in\n' % invoc description = ' description = Compiling Rust source $in.\n' @@ -1671,8 +1680,7 @@ rule FORTRAN_DEP_HACK self.generate_vala_compile_rules(compiler, outfile) return if langname == 'rust': - if not is_cross: - self.generate_rust_compile_rules(compiler, outfile) + self.generate_rust_compile_rules(compiler, outfile, is_cross) return if langname == 'swift': if not is_cross: @@ -1928,7 +1936,7 @@ rule FORTRAN_DEP_HACK # Check if a source uses a module it exports itself. # Potential bug if multiple targets have a file with # the same name. - if mod_source_file.fname == os.path.split(src)[1]: + if mod_source_file.fname == os.path.basename(src): continue mod_name = compiler.module_name_to_filename( usematch.group(1)) @@ -2271,7 +2279,7 @@ rule FORTRAN_DEP_HACK commands = [] commands += self.generate_basic_compiler_args(target, compiler) - just_name = os.path.split(header)[1] + just_name = os.path.basename(header) (objname, pch_args) = compiler.gen_pch_args(just_name, source, dst) commands += pch_args commands += self.get_compile_debugfile_args(compiler, target, objname) @@ -2281,7 +2289,7 @@ rule FORTRAN_DEP_HACK def generate_gcc_pch_command(self, target, compiler, pch): commands = self._generate_single_compile(target, compiler) dst = os.path.join(self.get_target_private_dir(target), - os.path.split(pch)[-1] + '.' + compiler.get_pch_suffix()) + os.path.basename(pch) + '.' + compiler.get_pch_suffix()) dep = dst + '.' + compiler.get_depfile_suffix() return commands, dep, dst, [] # Gcc does not create an object file during pch generation. @@ -2476,7 +2484,7 @@ rule FORTRAN_DEP_HACK # unfortunately we did not check for that and some downstream projects # now have them. Once slashes are forbidden, remove this bit. target_slashname_workaround_dir = os.path.join( - os.path.split(target.name)[0], + os.path.dirname(target.name), self.get_target_dir(target)) else: target_slashname_workaround_dir = self.get_target_dir(target) diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index 1722db7..057e7c9 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -1034,7 +1034,7 @@ class Vs2010Backend(backends.Backend): pch_file = ET.SubElement(inc_cl, 'PrecompiledHeaderFile') # MSBuild searches for the header relative from the implementation, so we have to use # just the file name instead of the relative path to the file. - pch_file.text = os.path.split(header)[1] + pch_file.text = os.path.basename(header) self.add_additional_options(lang, inc_cl, file_args) self.add_preprocessor_defines(lang, inc_cl, file_defines) self.add_include_dirs(lang, inc_cl, file_inc_dirs) diff --git a/mesonbuild/backend/xcodebackend.py b/mesonbuild/backend/xcodebackend.py index aca3aea..3ae31e4 100644 --- a/mesonbuild/backend/xcodebackend.py +++ b/mesonbuild/backend/xcodebackend.py @@ -311,7 +311,7 @@ class XCodeBackend(backends.Backend): for fname, idval in self.filemap.items(): fullpath = os.path.join(self.environment.get_source_dir(), fname) xcodetype = self.get_xcodetype(fname) - name = os.path.split(fname)[-1] + name = os.path.basename(fname) path = fname self.ofile.write(src_templ % (idval, fullpath, xcodetype, name, path)) target_templ = '%s /* %s */ = { isa = PBXFileReference; explicitFileType = "%s"; path = %s; refType = %d; sourceTree = BUILT_PRODUCTS_DIR; };\n' diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 5eab794..400b9e5 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -22,7 +22,7 @@ from . import mlog from .mesonlib import File, MesonException, listify, extract_as_list from .mesonlib import typeslistify, stringlistify, classify_unity_sources from .mesonlib import get_filenames_templates_dict, substitute_values -from .mesonlib import for_windows, for_darwin, for_cygwin +from .mesonlib import for_windows, for_darwin, for_cygwin, for_android from .compilers import is_object, clike_langs, sort_clike, lang_suffixes known_basic_kwargs = {'install': True, @@ -1065,7 +1065,7 @@ class Generator: depfile = kwargs['depfile'] if not isinstance(depfile, str): raise InvalidArguments('Depfile must be a string.') - if os.path.split(depfile)[1] != depfile: + if os.path.basename(depfile) != depfile: raise InvalidArguments('Depfile must be a plain filename without a subdirectory.') self.depfile = depfile if 'capture' in kwargs: @@ -1075,7 +1075,7 @@ class Generator: self.capture = capture def get_base_outnames(self, inname): - plainname = os.path.split(inname)[1] + plainname = os.path.basename(inname) basename = os.path.splitext(plainname)[0] bases = [x.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname) for x in self.outputs] return bases @@ -1083,12 +1083,12 @@ class Generator: def get_dep_outname(self, inname): if self.depfile is None: raise InvalidArguments('Tried to get dep name for rule that does not have dependency file defined.') - plainname = os.path.split(inname)[1] + plainname = os.path.basename(inname) basename = os.path.splitext(plainname)[0] return self.depfile.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname) def get_arglist(self, inname): - plainname = os.path.split(inname)[1] + plainname = os.path.basename(inname) basename = os.path.splitext(plainname)[0] return [x.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname) for x in self.arglist] @@ -1130,7 +1130,7 @@ class GeneratedList: in_abs = infile.absolute_path(state.environment.source_dir, state.environment.build_dir) assert(os.path.isabs(self.preserve_path_from)) rel = os.path.relpath(in_abs, self.preserve_path_from) - path_segment = os.path.split(rel)[0] + path_segment = os.path.dirname(rel) for of in outfiles: result.append(os.path.join(path_segment, of)) return result @@ -1393,6 +1393,11 @@ class SharedLibrary(BuildTarget): else: # libfoo.dylib self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' + elif for_android(is_cross, env): + prefix = 'lib' + suffix = 'so' + # Android doesn't support shared_library versioning + self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' else: prefix = 'lib' suffix = 'so' @@ -1414,25 +1419,32 @@ class SharedLibrary(BuildTarget): def process_kwargs(self, kwargs, environment): super().process_kwargs(kwargs, environment) - # Shared library version - if 'version' in kwargs: - self.ltversion = kwargs['version'] - if not isinstance(self.ltversion, str): - raise InvalidArguments('Shared library version needs to be a string, not ' + type(self.ltversion).__name__) - if not re.fullmatch(r'[0-9]+(\.[0-9]+){0,2}', self.ltversion): - raise InvalidArguments('Invalid Shared library version "{0}". Must be of the form X.Y.Z where all three are numbers. Y and Z are optional.'.format(self.ltversion)) - # Try to extract/deduce the soversion - if 'soversion' in kwargs: - self.soversion = kwargs['soversion'] - if isinstance(self.soversion, int): - self.soversion = str(self.soversion) - if not isinstance(self.soversion, str): - raise InvalidArguments('Shared library soversion is not a string or integer.') - elif self.ltversion: - # library version is defined, get the soversion from that - # We replicate what Autotools does here and take the first - # number of the version by default. - self.soversion = self.ltversion.split('.')[0] + + if not for_android(self.is_cross, self.environment): + supports_versioning = True + else: + supports_versioning = False + + if supports_versioning: + # Shared library version + if 'version' in kwargs: + self.ltversion = kwargs['version'] + if not isinstance(self.ltversion, str): + raise InvalidArguments('Shared library version needs to be a string, not ' + type(self.ltversion).__name__) + if not re.fullmatch(r'[0-9]+(\.[0-9]+){0,2}', self.ltversion): + raise InvalidArguments('Invalid Shared library version "{0}". Must be of the form X.Y.Z where all three are numbers. Y and Z are optional.'.format(self.ltversion)) + # Try to extract/deduce the soversion + if 'soversion' in kwargs: + self.soversion = kwargs['soversion'] + if isinstance(self.soversion, int): + self.soversion = str(self.soversion) + if not isinstance(self.soversion, str): + raise InvalidArguments('Shared library soversion is not a string or integer.') + elif self.ltversion: + # library version is defined, get the soversion from that + # We replicate what Autotools does here and take the first + # number of the version by default. + self.soversion = self.ltversion.split('.')[0] # Visual Studio module-definitions file if 'vs_module_defs' in kwargs: path = kwargs['vs_module_defs'] @@ -1637,6 +1649,10 @@ class CustomTarget(Target): for i in self.outputs: if not(isinstance(i, str)): raise InvalidArguments('Output argument not a string.') + if i == '': + raise InvalidArguments('Output must not be empty.') + if i.strip() == '': + raise InvalidArguments('Output must not consist only of whitespace.') if '/' in i: raise InvalidArguments('Output must not contain a path segment.') if '@INPUT@' in i or '@INPUT0@' in i: @@ -1659,7 +1675,7 @@ class CustomTarget(Target): depfile = kwargs['depfile'] if not isinstance(depfile, str): raise InvalidArguments('Depfile must be a string.') - if os.path.split(depfile)[1] != depfile: + if os.path.basename(depfile) != depfile: raise InvalidArguments('Depfile must be a plain filename without a subdirectory.') self.depfile = depfile self.command = self.flatten_command(kwargs['command']) diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index 4c6e3a2..a59b7d3 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -172,10 +172,10 @@ class CCompiler(Compiler): return ' '.join(self.exelist) def get_pch_use_args(self, pch_dir, header): - return ['-include', os.path.split(header)[-1]] + return ['-include', os.path.basename(header)] def get_pch_name(self, header_name): - return os.path.split(header_name)[-1] + '.' + self.get_pch_suffix() + return os.path.basename(header_name) + '.' + self.get_pch_suffix() def get_linker_search_args(self, dirname): return ['-L' + dirname] @@ -184,7 +184,7 @@ class CCompiler(Compiler): return [] def gen_export_dynamic_link_args(self, env): - if for_windows(env.is_cross_build(), env): + if for_windows(env.is_cross_build(), env) or for_cygwin(env.is_cross_build(), env): return ['-Wl,--export-all-symbols'] elif for_darwin(env.is_cross_build(), env): return [] @@ -804,6 +804,13 @@ class CCompiler(Compiler): return ['-pthread'] def has_multi_arguments(self, args, env): + for arg in args: + if arg.startswith('-Wl,'): + mlog.warning('''{} looks like a linker argument, but has_argument +and other similar methods only support checking compiler arguments. +Using them to check linker arguments are never supported, and results +are likely to be wrong regardless of the compiler you are using. +'''.format(arg)) return self.compiles('int i;\n', env, extra_args=args) @@ -875,7 +882,7 @@ class GnuCCompiler(GnuCompiler, CCompiler): return ['-shared'] def get_pch_use_args(self, pch_dir, header): - return ['-fpch-preprocess', '-include', os.path.split(header)[-1]] + return ['-fpch-preprocess', '-include', os.path.basename(header)] class IntelCCompiler(IntelCompiler, CCompiler): @@ -954,13 +961,13 @@ class VisualStudioCCompiler(CCompiler): return 'pch' def get_pch_name(self, header): - chopped = os.path.split(header)[-1].split('.')[:-1] + chopped = os.path.basename(header).split('.')[:-1] chopped.append(self.get_pch_suffix()) pchname = '.'.join(chopped) return pchname def get_pch_use_args(self, pch_dir, header): - base = os.path.split(header)[-1] + base = os.path.basename(header) pchname = self.get_pch_name(header) return ['/FI' + base, '/Yu' + base, '/Fp' + os.path.join(pch_dir, pchname)] @@ -1087,7 +1094,7 @@ class VisualStudioCCompiler(CCompiler): mlog.debug('Running VS compile:') mlog.debug('Command line: ', ' '.join(commands)) mlog.debug('Code:\n', code) - p, stdo, stde = Popen_safe(commands, cwd=os.path.split(srcname)[0]) + p, stdo, stde = Popen_safe(commands, cwd=os.path.dirname(srcname)) if p.returncode != 0: return False return not(warning_text in stde or warning_text in stdo) diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 2602d14..034fef4 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -137,8 +137,11 @@ gnulike_buildtype_linker_args = {'plain': [], msvc_buildtype_linker_args = {'plain': [], 'debug': [], 'debugoptimized': [], - 'release': [], - 'minsize': ['/INCREMENTAL:NO'], + # The otherwise implicit REF and ICF linker + # optimisations are disabled by /DEBUG. + # REF implies ICF. + 'release': ['/OPT:REF'], + 'minsize': ['/INCREMENTAL:NO', '/OPT:REF'], } java_buildtype_args = {'plain': [], @@ -1040,7 +1043,7 @@ class GnuCompiler: return 'gch' def split_shlib_to_parts(self, fname): - return os.path.split(fname)[0], fname + return os.path.dirname(fname), fname def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, is_shared_module): return get_gcc_soname_args(self.gcc_type, prefix, shlib_name, suffix, path, soversion, is_shared_module) @@ -1188,10 +1191,10 @@ class IntelCompiler: self.lang_header, '-include', header, '-x', 'none'] def get_pch_name(self, header_name): - return os.path.split(header_name)[-1] + '.' + self.get_pch_suffix() + return os.path.basename(header_name) + '.' + self.get_pch_suffix() def split_shlib_to_parts(self, fname): - return os.path.split(fname)[0], fname + return os.path.dirname(fname), fname def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, is_shared_module): if self.icc_type == ICC_STANDARD: diff --git a/mesonbuild/compilers/cpp.py b/mesonbuild/compilers/cpp.py index 5e32ace..c10f38e 100644 --- a/mesonbuild/compilers/cpp.py +++ b/mesonbuild/compilers/cpp.py @@ -14,6 +14,7 @@ import os.path +from .. import mlog from .. import coredata from ..mesonlib import version_compare @@ -129,7 +130,7 @@ class GnuCPPCompiler(GnuCompiler, CPPCompiler): return [] def get_pch_use_args(self, pch_dir, header): - return ['-fpch-preprocess', '-include', os.path.split(header)[-1]] + return ['-fpch-preprocess', '-include', os.path.basename(header)] class IntelCPPCompiler(IntelCompiler, CPPCompiler): @@ -174,6 +175,13 @@ class IntelCPPCompiler(IntelCompiler, CPPCompiler): return [] def has_multi_arguments(self, args, env): + for arg in args: + if arg.startswith('-Wl,'): + mlog.warning('''{} looks like a linker argument, but has_argument +and other similar methods only support checking compiler arguments. +Using them to check linker arguments are never supported, and results +are likely to be wrong regardless of the compiler you are using. +'''.format(arg)) return super().has_multi_arguments(args + ['-diag-error', '10006'], env) diff --git a/mesonbuild/compilers/fortran.py b/mesonbuild/compilers/fortran.py index 1b42bfa..f9fcc1c 100644 --- a/mesonbuild/compilers/fortran.py +++ b/mesonbuild/compilers/fortran.py @@ -91,7 +91,7 @@ end program prog return gnulike_buildtype_linker_args[buildtype] def split_shlib_to_parts(self, fname): - return os.path.split(fname)[0], fname + return os.path.dirname(fname), fname def get_soname_args(self, prefix, shlib_name, suffix, path, soversion, is_shared_module): return get_gcc_soname_args(self.gcc_type, prefix, shlib_name, suffix, path, soversion, is_shared_module) diff --git a/mesonbuild/compilers/rust.py b/mesonbuild/compilers/rust.py index b93289f..d1a05ed 100644 --- a/mesonbuild/compilers/rust.py +++ b/mesonbuild/compilers/rust.py @@ -19,9 +19,11 @@ from ..mesonlib import EnvironmentException, Popen_safe from .compilers import Compiler, rust_buildtype_args class RustCompiler(Compiler): - def __init__(self, exelist, version): + def __init__(self, exelist, version, is_cross, exe_wrapper=None): self.language = 'rust' super().__init__(exelist, version) + self.is_cross = is_cross + self.exe_wrapper = exe_wrapper self.id = 'rustc' def needs_static_linker(self): @@ -41,7 +43,16 @@ class RustCompiler(Compiler): pc.wait() if pc.returncode != 0: raise EnvironmentException('Rust compiler %s can not compile programs.' % self.name_string()) - if subprocess.call(output_name) != 0: + if self.is_cross: + if self.exe_wrapper is None: + # Can't check if the binaries run so we have to assume they do + return + cmdlist = self.exe_wrapper + [output_name] + else: + cmdlist = [output_name] + pe = subprocess.Popen(cmdlist, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + pe.wait() + if pe.returncode != 0: raise EnvironmentException('Executables created by Rust compiler %s are not runnable.' % self.name_string()) def get_dependency_gen_args(self, outfile): diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 0fdac8b..f87e62c 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -1,5 +1,4 @@ - -# Copyright 2012-2017 The Meson development team +# Copyright 2012-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. @@ -25,12 +24,19 @@ import ast version = '0.45.0.dev1' backendlist = ['ninja', 'vs', 'vs2010', 'vs2015', 'vs2017', 'xcode'] +default_yielding = False + class UserOption: - def __init__(self, name, description, choices): + def __init__(self, name, description, choices, yielding): super().__init__() self.name = name self.choices = choices self.description = description + if yielding is None: + yielding = default_yielding + if not isinstance(yielding, bool): + raise MesonException('Value of "yielding" must be a boolean.') + self.yielding = yielding # Check that the input is a valid value and return the # "cleaned" or "native" version. For example the Boolean @@ -39,8 +45,8 @@ class UserOption: raise RuntimeError('Derived option class did not override validate_value.') class UserStringOption(UserOption): - def __init__(self, name, description, value, choices=None): - super().__init__(name, description, choices) + def __init__(self, name, description, value, choices=None, yielding=None): + super().__init__(name, description, choices, yielding) self.set_value(value) def validate(self, value): @@ -56,8 +62,8 @@ class UserStringOption(UserOption): return value class UserBooleanOption(UserOption): - def __init__(self, name, description, value): - super().__init__(name, description, [True, False]) + def __init__(self, name, description, value, yielding=None): + super().__init__(name, description, [True, False], yielding) self.set_value(value) def tobool(self, thing): @@ -79,11 +85,17 @@ class UserBooleanOption(UserOption): return self.tobool(value) class UserIntegerOption(UserOption): - def __init__(self, name, description, min_value, max_value, value): - super().__init__(name, description, [True, False]) + def __init__(self, name, description, min_value, max_value, value, yielding=None): + super().__init__(name, description, [True, False], yielding) self.min_value = min_value self.max_value = max_value self.set_value(value) + c = [] + if min_value is not None: + c.append('>=' + str(min_value)) + if max_value is not None: + c.append('<=' + str(max_value)) + self.choices = ', '.join(c) def set_value(self, newvalue): if isinstance(newvalue, str): @@ -106,8 +118,8 @@ class UserIntegerOption(UserOption): return self.toint(value) class UserComboOption(UserOption): - def __init__(self, name, description, choices, value): - super().__init__(name, description, choices) + def __init__(self, name, description, choices, value, yielding=None): + super().__init__(name, description, choices, yielding) if not isinstance(self.choices, list): raise MesonException('Combo choices must be an array.') for i in self.choices: @@ -128,7 +140,7 @@ class UserComboOption(UserOption): class UserArrayOption(UserOption): def __init__(self, name, description, value, **kwargs): - super().__init__(name, description, kwargs.get('choices', [])) + super().__init__(name, description, kwargs.get('choices', []), yielding=kwargs.get('yielding', None)) self.set_value(value, user_input=False) def validate(self, value, user_input): diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index 04ca706..66bc3b4 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -16,7 +16,7 @@ # Custom logic for several other packages are in separate files. import os -import sys +import re import stat import shlex import shutil @@ -214,10 +214,12 @@ class ConfigToolDependency(ExternalDependency): tools = None tool_name = None + __strip_version = re.compile(r'^[0-9.]*') def __init__(self, name, environment, language, kwargs): super().__init__('config-tool', environment, language, kwargs) self.name = name + self.native = kwargs.get('native', False) self.tools = listify(kwargs.get('tools', self.tools)) req_version = kwargs.get('version', None) @@ -229,6 +231,15 @@ class ConfigToolDependency(ExternalDependency): return self.version = version + def _sanitize_version(self, version): + """Remove any non-numeric, non-point version suffixes.""" + m = self.__strip_version.match(version) + if m: + # Ensure that there isn't a trailing '.', such as an input like + # `1.2.3.git-1234` + return m.group(0).rstrip('.') + return version + @classmethod def factory(cls, name, environment, language, kwargs, tools, tool_name): """Constructor for use in dependencies that can be found multiple ways. @@ -260,8 +271,20 @@ class ConfigToolDependency(ExternalDependency): if not isinstance(versions, list) and versions is not None: versions = listify(versions) + if self.env.is_cross_build() and not self.native: + cross_file = self.env.cross_info.config['binaries'] + try: + tools = [cross_file[self.tool_name]] + except KeyError: + mlog.warning('No entry for {0} specified in your cross file. ' + 'Falling back to searching PATH. This may find a ' + 'native version of {0}!'.format(self.tool_name)) + tools = self.tools + else: + tools = self.tools + best_match = (None, None) - for tool in self.tools: + for tool in tools: try: p, out = Popen_safe([tool, '--version'])[:2] except (FileNotFoundError, PermissionError): @@ -269,10 +292,10 @@ class ConfigToolDependency(ExternalDependency): if p.returncode != 0: continue - out = out.strip() + out = self._sanitize_version(out.strip()) # Some tools, like pcap-config don't supply a version, but also - # dont fail with --version, in that case just assume that there is - # only one verison and return it. + # don't fail with --version, in that case just assume that there is + # only one version and return it. if not out: return (tool, 'none') if versions: @@ -543,6 +566,17 @@ class PkgConfigDependency(ExternalDependency): (self.type_string, self.name)) else: variable = out.strip() + + # pkg-config doesn't distinguish between empty and non-existent variables + # use the variable list to check for variable existence + if not variable: + ret, out = self._call_pkgbin(['--print-variables', self.name]) + if not re.search(r'^' + variable_name + r'$', out, re.MULTILINE): + if 'default' in kwargs: + variable = kwargs['default'] + else: + mlog.warning("pkgconfig variable '%s' not defined for dependency %s." % (variable_name, self.name)) + mlog.debug('Got pkgconfig variable %s : %s' % (variable_name, variable)) return variable diff --git a/mesonbuild/dependencies/dev.py b/mesonbuild/dependencies/dev.py index 25316df..c254947 100644 --- a/mesonbuild/dependencies/dev.py +++ b/mesonbuild/dependencies/dev.py @@ -146,16 +146,6 @@ class LLVMDependency(ConfigToolDependency): return self.static = kwargs.get('static', False) - # Currently meson doesn't really attempt to handle pre-release versions, - # so strip the 'svn' off the end, since it will probably cuase problems - # for users who want the patch version. - # - # If LLVM is built from svn then "svn" will be appended to the version - # string, if it's built from a git mirror then "git-<very short sha>" - # will be appended instead. - self.version = self.version.rstrip('svn') - self.version = self.version.split('git')[0] - self.provided_modules = self.get_config_value(['--components'], 'modules') modules = stringlistify(extract_as_list(kwargs, 'modules')) self.check_components(modules) diff --git a/mesonbuild/dependencies/misc.py b/mesonbuild/dependencies/misc.py index 0570e88..af80160 100644 --- a/mesonbuild/dependencies/misc.py +++ b/mesonbuild/dependencies/misc.py @@ -63,9 +63,12 @@ from .base import ( # **On Unix**, official packaged versions of boost libraries follow the following schemes: # -# Linux / Debian: libboost_<module>.so.1.66.0 -> libboost_<module>.so -# Linux / Red Hat: libboost_<module>.so.1.66.0 -> libboost_<module>.so -# Linux / OpenSuse: libboost_<module>.so.1.66.0 -> libboost_<module>.so +# Linux / Debian: libboost_<module>.so -> libboost_<module>.so.1.66.0 +# Linux / Red Hat: libboost_<module>.so -> libboost_<module>.so.1.66.0 +# Linux / OpenSuse: libboost_<module>.so -> libboost_<module>.so.1.66.0 +# Win / Cygwin: libboost_<module>.dll.a (location = /usr/lib) +# libboost_<module>.a +# cygboost_<module>_1_64.dll (location = /usr/bin) # Mac / homebrew: libboost_<module>.dylib + libboost_<module>-mt.dylib (location = /usr/local/lib) # Mac / macports: libboost_<module>.dylib + libboost_<module>-mt.dylib (location = /opt/local/lib) # @@ -147,6 +150,28 @@ class BoostDependency(ExternalDependency): self.log_fail() return + if self.check_invalid_modules(): + return + + mlog.debug('Boost library root dir is', mlog.bold(self.boost_root)) + mlog.debug('Boost include directory is', mlog.bold(self.incdir)) + + self.lib_modules = {} + self.detect_version() + if self.is_found: + self.detect_lib_modules() + mlog.debug('Boost library directory is', mlog.bold(self.libdir)) + for m in self.requested_modules: + if 'boost_' + m not in self.lib_modules: + mlog.debug('Requested Boost library {!r} not found'.format(m)) + self.log_fail() + self.is_found = False + return + self.log_success() + else: + self.log_fail() + + def check_invalid_modules(self): invalid_modules = [c for c in self.requested_modules if 'boost_' + c not in BOOST_LIBS] # previous versions of meson allowed include dirs as modules @@ -163,21 +188,9 @@ class BoostDependency(ExternalDependency): if invalid_modules: mlog.log(mlog.red('ERROR:'), 'Invalid Boost modules: ' + ', '.join(invalid_modules)) self.log_fail() - return - - mlog.debug('Boost library root dir is', mlog.bold(self.boost_root)) - mlog.debug('Boost include directory is', mlog.bold(self.incdir)) - - self.lib_modules = {} - self.detect_version() - if self.is_found: - self.detect_lib_modules() - mlog.debug('Boost library directory is', mlog.bold(self.libdir)) - self.validate_requested() - self.log_success() + return True else: - self.log_fail() - + return False def log_fail(self): module_str = ', '.join(self.requested_modules) @@ -262,12 +275,6 @@ class BoostDependency(ExternalDependency): raise DependencyException('Boost module argument is not a string.') return candidates - def validate_requested(self): - for m in self.requested_modules: - if 'boost_' + m not in self.lib_modules: - msg = 'Requested Boost library {!r} not found' - raise DependencyException(msg.format(m)) - def detect_version(self): try: version = self.compiler.get_define('BOOST_LIB_VERSION', '#include <boost/version.hpp>', self.env, self.get_compile_args(), []) @@ -360,15 +367,23 @@ class BoostDependency(ExternalDependency): fname = os.path.basename(entry) self.lib_modules[self.modname_from_filename(fname)] = [fname] + # - Linux leaves off -mt but libraries are multithreading-aware. + # - Cygwin leaves off -mt but libraries are multithreading-aware. + # - Mac requires -mt for multithreading, so should not fall back + # to non-mt libraries. + def abi_tag(self): + if mesonlib.for_windows(self.want_cross, self.env): + return None + if self.is_multithreading and mesonlib.for_darwin(self.want_cross, self.env): + return '-mt' + else: + return '' + def detect_lib_modules_nix(self): all_found = True for module in self.requested_modules: - args = None - libname = 'boost_' + module - if self.is_multithreading and mesonlib.for_darwin(self.want_cross, self.env): - # - Linux leaves off -mt but libraries are multithreading-aware. - # - Mac requires -mt for multithreading, so should not fall back to non-mt libraries. - libname = libname + '-mt' + libname = 'boost_' + module + self.abi_tag() + args = self.compiler.find_library(libname, self.env, self.extra_lib_dirs()) if args is None: mlog.debug('Couldn\'t find library "{}" for boost module "{}"'.format(module, libname)) @@ -417,29 +432,17 @@ class BoostDependency(ExternalDependency): if modname not in self.lib_modules: self.lib_modules[modname] = [entry] - def get_win_link_args(self): - args = [] - # TODO: should this check self.libdir? - if self.libdir: - args.append('-L' + self.libdir) - for lib in self.requested_modules: - args += self.lib_modules['boost_' + lib] - return args - def extra_lib_dirs(self): - dirs = [] - if self.boost_root: - dirs = [os.path.join(self.boost_root, 'lib')] - elif self.libdir: - dirs = [self.libdir] - return dirs + if self.libdir: + return [self.libdir] + elif self.boost_root: + return [os.path.join(self.boost_root, 'lib')] + return [] def get_link_args(self): - if mesonlib.is_windows(): - return self.get_win_link_args() args = [] for dir in self.extra_lib_dirs(): - args += ['-L' + dir] + args += self.compiler.get_linker_search_args(self.libdir) for lib in self.requested_modules: args += self.lib_modules['boost_' + lib] return args diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index e5aa43e..52c670a 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -278,6 +278,7 @@ class Environment: self.default_objc = ['cc'] self.default_objcpp = ['c++'] self.default_fortran = ['gfortran', 'g95', 'f95', 'f90', 'f77', 'ifort'] + self.default_rust = ['rustc'] self.default_static_linker = ['ar'] self.vs_static_linker = ['lib'] self.gcc_static_linker = ['gcc-ar'] @@ -594,7 +595,7 @@ class Environment: return self.scratch_dir def get_depfixer(self): - path = os.path.split(__file__)[0] + path = os.path.dirname(__file__) return os.path.join(path, 'depfixer.py') def detect_objc_compiler(self, want_cross): @@ -688,16 +689,24 @@ class Environment: return ValaCompiler(exelist, version) raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"') - def detect_rust_compiler(self): - exelist = ['rustc'] - try: - p, out = Popen_safe(exelist + ['--version'])[0:2] - except OSError: - raise EnvironmentException('Could not execute Rust compiler "%s"' % ' '.join(exelist)) - version = search_version(out) - if 'rustc' in out: - return RustCompiler(exelist, version) - raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"') + def detect_rust_compiler(self, want_cross): + popen_exceptions = {} + compilers, ccache, is_cross, exe_wrap = self._get_compilers('rust', 'RUSTC', want_cross) + for compiler in compilers: + if isinstance(compiler, str): + compiler = [compiler] + try: + p, out = Popen_safe(compiler + ['--version'])[0:2] + except OSError as e: + popen_exceptions[compiler] = e + continue + + version = search_version(out) + + if 'rustc' in out: + return RustCompiler(compiler, version, is_cross, exe_wrap) + + self._handle_exceptions(popen_exceptions, compilers) def detect_d_compiler(self, want_cross): is_cross = False @@ -968,7 +977,7 @@ class CrossBuildInfo: def get_properties(self): return self.config['properties'] - # Wehn compiling a cross compiler we use the native compiler for everything. + # When compiling a cross compiler we use the native compiler for everything. # But not when cross compiling a cross compiler. def need_cross_compiler(self): return 'host_machine' in self.config diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index c759892..31d7616 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -1,4 +1,4 @@ -# Copyright 2012-2017 The Meson development team +# Copyright 2012-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 @@ -36,8 +36,6 @@ from collections import namedtuple import importlib -run_depr_printed = False - def stringifyUserArguments(args): if isinstance(args, list): return '[%s]' % ', '.join([stringifyUserArguments(x) for x in args]) @@ -509,13 +507,14 @@ class DataHolder(InterpreterObject, ObjectHolder): return self.held_object.install_dir class InstallDir(InterpreterObject): - def __init__(self, src_subdir, inst_subdir, install_dir, install_mode, exclude): + def __init__(self, src_subdir, inst_subdir, install_dir, install_mode, exclude, strip_directory): InterpreterObject.__init__(self) self.source_subdir = src_subdir self.installable_subdir = inst_subdir self.install_dir = install_dir self.install_mode = install_mode self.exclude = exclude + self.strip_directory = strip_directory class Man(InterpreterObject): @@ -1170,6 +1169,7 @@ class MesonMain(InterpreterObject): 'add_postconf_script': self.add_postconf_script_method, 'install_dependency_manifest': self.install_dependency_manifest_method, 'project_version': self.project_version_method, + 'project_license': self.project_license_method, 'version': self.version_method, 'project_name': self.project_name_method, 'get_cross_property': self.get_cross_property_method, @@ -1283,6 +1283,9 @@ class MesonMain(InterpreterObject): def project_version_method(self, args, kwargs): return self.build.dep_manifest[self.interpreter.active_projectname]['version'] + def project_license_method(self, args, kwargs): + return self.build.dep_manifest[self.interpreter.active_projectname]['license'] + def version_method(self, args, kwargs): return coredata.version @@ -1379,7 +1382,7 @@ permitted_kwargs = {'add_global_arguments': {'language'}, 'build_target': build_target_kwargs, 'configure_file': {'input', 'output', 'configuration', 'command', 'install_dir', 'capture', 'install'}, 'custom_target': {'input', 'output', 'command', 'install', 'install_dir', 'build_always', 'capture', 'depends', 'depend_files', 'depfile', 'build_by_default'}, - 'dependency': {'default_options', 'fallback', 'language', 'method', 'modules', 'optional_modules', 'native', 'required', 'static', 'version'}, + 'dependency': {'default_options', 'fallback', 'language', 'main', 'method', 'modules', 'optional_modules', 'native', 'required', 'static', 'version'}, 'declare_dependency': {'include_directories', 'link_with', 'sources', 'dependencies', 'compile_args', 'link_args', 'version'}, 'executable': exe_kwargs, 'find_program': {'required', 'native'}, @@ -1388,7 +1391,7 @@ permitted_kwargs = {'add_global_arguments': {'language'}, 'install_data': {'install_dir', 'install_mode', 'sources'}, 'install_headers': {'install_dir', 'subdir'}, 'install_man': {'install_dir'}, - 'install_subdir': {'exclude_files', 'exclude_directories', 'install_dir', 'install_mode'}, + 'install_subdir': {'exclude_files', 'exclude_directories', 'install_dir', 'install_mode', 'strip_directory'}, 'jar': jar_kwargs, 'project': {'version', 'meson_version', 'default_options', 'license', 'subproject_dir'}, 'run_target': {'command', 'depends'}, @@ -1585,7 +1588,7 @@ class Interpreter(InterpreterBase): modname = args[0] if modname.startswith('unstable-'): plainname = modname.split('-', 1)[1] - mlog.warning('Module %s has no backwards or forwards compatibility and might not exist in future releases' % modname, location=node) + mlog.warning('Module %s has no backwards or forwards compatibility and might not exist in future releases.' % modname, location=node) modname = 'unstable_' + plainname if modname not in self.environment.coredata.modules: try: @@ -1765,7 +1768,7 @@ external dependencies (including libraries) must go to "dependencies".''') def func_get_option(self, nodes, args, kwargs): if len(args) != 1: raise InterpreterException('Argument required for get_option.') - optname = args[0] + undecorated_optname = optname = args[0] if ':' in optname: raise InterpreterException('''Having a colon in option name is forbidden, projects are not allowed to directly access options of other subprojects.''') @@ -1784,7 +1787,11 @@ to directly access options of other subprojects.''') if not coredata.is_builtin_option(optname) and self.is_subproject(): optname = self.subproject + ':' + optname try: - return self.environment.coredata.user_options[optname].value + opt = self.environment.coredata.user_options[optname] + if opt.yielding and ':' in optname: + # If option not present in superproject, keep the original. + opt = self.environment.coredata.user_options.get(undecorated_optname, opt) + return opt.value except KeyError: pass if optname.endswith('_link_args'): @@ -1992,9 +1999,9 @@ to directly access options of other subprojects.''') if need_cross_compiler: cross_comp = self.environment.detect_d_compiler(True) elif lang == 'rust': - comp = self.environment.detect_rust_compiler() + comp = self.environment.detect_rust_compiler(False) if need_cross_compiler: - cross_comp = comp # FIXME, not correct. + cross_comp = self.environment.detect_rust_compiler(True) elif lang == 'fortran': comp = self.environment.detect_fortran_compiler(False) if need_cross_compiler: @@ -2138,7 +2145,7 @@ to directly access options of other subprojects.''') return progobj def func_find_library(self, node, args, kwargs): - mlog.log(mlog.red('DEPRECATION:'), 'find_library() is removed, use the corresponding method in compiler object instead.') + raise InvalidCode('find_library() is removed, use the corresponding method in a compiler object instead.') def _find_cached_dep(self, name, kwargs): # Check if we want this as a cross-dep or a native-dep @@ -2447,15 +2454,8 @@ root and issuing %s. @permittedKwargs(permitted_kwargs['run_target']) def func_run_target(self, node, args, kwargs): - global run_depr_printed if len(args) > 1: - if not run_depr_printed: - mlog.log(mlog.red('DEPRECATION'), 'positional version of run_target is deprecated, use the keyword version instead.') - run_depr_printed = True - if 'command' in kwargs: - raise InterpreterException('Can not have command both in positional and keyword arguments.') - all_args = args[1:] - deps = [] + raise InvalidCode('Run_target takes only one positional argument: the target name.') elif len(args) == 1: if 'command' not in kwargs: raise InterpreterException('Missing "command" keyword argument') @@ -2679,6 +2679,12 @@ root and issuing %s. install_dir = kwargs['install_dir'] if not isinstance(install_dir, str): raise InvalidArguments('Keyword argument install_dir not a string.') + if 'strip_directory' in kwargs: + if not isinstance(kwargs['strip_directory'], bool): + raise InterpreterException('"strip_directory" keyword must be a boolean.') + strip_directory = kwargs['strip_directory'] + else: + strip_directory = False if 'exclude_files' in kwargs: exclude = extract_as_list(kwargs, 'exclude_files') for f in exclude: @@ -2686,7 +2692,7 @@ root and issuing %s. raise InvalidArguments('Exclude argument not a string.') elif os.path.isabs(f): raise InvalidArguments('Exclude argument cannot be absolute.') - exclude_files = {os.path.join(subdir, f) for f in exclude} + exclude_files = set(exclude) else: exclude_files = set() if 'exclude_directories' in kwargs: @@ -2696,12 +2702,12 @@ root and issuing %s. raise InvalidArguments('Exclude argument not a string.') elif os.path.isabs(d): raise InvalidArguments('Exclude argument cannot be absolute.') - exclude_directories = {os.path.join(subdir, f) for f in exclude} + exclude_directories = set(exclude) else: exclude_directories = set() exclude = (exclude_files, exclude_directories) install_mode = self._get_kwarg_install_mode(kwargs) - idir = InstallDir(self.subdir, subdir, install_dir, install_mode, exclude) + idir = InstallDir(self.subdir, subdir, install_dir, install_mode, exclude, strip_directory) self.build.install_dirs.append(idir) return idir @@ -2748,7 +2754,7 @@ root and issuing %s. values = mesonlib.get_filenames_templates_dict([ifile_abs], None) outputs = mesonlib.substitute_values([output], values) output = outputs[0] - if os.path.split(output)[0] != '': + if os.path.dirname(output) != '': raise InterpreterException('Output file name must not contain a subdirectory.') (ofile_path, ofile_fname) = os.path.split(os.path.join(self.subdir, output)) ofile_abs = os.path.join(self.environment.build_dir, ofile_path, ofile_fname) @@ -2765,7 +2771,7 @@ root and issuing %s. var_list = ", ".join(map(repr, sorted(missing_variables))) mlog.warning( "The variable(s) %s in the input file %s are not " - "present in the given configuration data" % ( + "present in the given configuration data." % ( var_list, inputfile), location=node) else: mesonlib.dump_conf_header(ofile_abs, conf.held_object) @@ -2986,7 +2992,7 @@ different subdirectory. norm = os.path.relpath(norm, self.environment.source_dir) assert(not os.path.isabs(norm)) (num_sps, sproj_name) = self.evaluate_subproject_info(norm, self.subproject_dir) - plain_filename = os.path.split(norm)[-1] + plain_filename = os.path.basename(norm) if num_sps == 0: if self.subproject == '': return @@ -2999,6 +3005,8 @@ different subdirectory. def source_strings_to_files(self, sources): results = [] mesonlib.check_direntry_issues(sources) + if not isinstance(sources, list): + sources = [sources] for s in sources: if isinstance(s, (mesonlib.File, GeneratedListHolder, CustomTargetHolder, CustomTargetIndexHolder)): @@ -3015,6 +3023,8 @@ different subdirectory. def add_target(self, name, tobj): if name == '': raise InterpreterException('Target name must not be empty.') + if name.strip() == '': + raise InterpreterException('Target name must not consist only of whitespace.') if name.startswith('meson-'): raise InvalidArguments("Target names starting with 'meson-' are reserved " "for Meson's internal use. Please rename.") diff --git a/mesonbuild/interpreterbase.py b/mesonbuild/interpreterbase.py index 9dc6b0f..6618dc8 100644 --- a/mesonbuild/interpreterbase.py +++ b/mesonbuild/interpreterbase.py @@ -74,7 +74,7 @@ class permittedKwargs: loc = None for k in kwargs: if k not in self.permitted: - mlog.warning('''Passed invalid keyword argument "{}"'''.format(k), location=loc) + mlog.warning('''Passed invalid keyword argument "{}".'''.format(k), location=loc) mlog.warning('This will become a hard error in the future.') return f(s, node_or_state, args, kwargs) return wrapped @@ -265,6 +265,12 @@ class InterpreterBase: if not isinstance(node.elseblock, mparser.EmptyNode): self.evaluate_codeblock(node.elseblock) + def validate_comparison_types(self, val1, val2): + if type(val1) != type(val2): + mlog.warning('''Trying to compare values of different types ({}, {}). +The result of this is undefined and will become a hard error +in a future Meson release.'''.format(type(val1).__name__, type(val2).__name__)) + def evaluate_comparison(self, node): val1 = self.evaluate_statement(node.left) if is_disabler(val1): @@ -272,15 +278,11 @@ class InterpreterBase: val2 = self.evaluate_statement(node.right) if is_disabler(val2): return val2 + self.validate_comparison_types(val1, val2) if node.ctype == '==': return val1 == val2 elif node.ctype == '!=': return val1 != val2 - elif not isinstance(val1, type(val2)): - raise InterpreterException( - 'Values of different types ({}, {}) cannot be compared using {}.'.format(type(val1).__name__, - type(val2).__name__, - node.ctype)) elif not self.is_elementary_type(val1): raise InterpreterException('{} can only be compared for equality.'.format(node.left.value)) elif not self.is_elementary_type(val2): diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index 4871bf7..65b689f 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -282,6 +282,9 @@ def is_osx(): def is_linux(): return platform.system().lower() == 'linux' +def is_android(): + return platform.system().lower() == 'android' + def is_haiku(): return platform.system().lower() == 'haiku' @@ -350,6 +353,18 @@ def for_darwin(is_cross, env): return env.cross_info.config['host_machine']['system'] == 'darwin' return False +def for_android(is_cross, env): + """ + Host machine is Android? + + Note: 'host' is the machine on which compiled binaries will run + """ + if not is_cross: + return is_android() + elif env.cross_info.has_host(): + return env.cross_info.config['host_machine']['system'] == 'android' + return False + def for_haiku(is_cross, env): """ Host machine is Haiku? @@ -576,7 +591,8 @@ def do_conf_file(src, dst, confdata): return missing_variables def dump_conf_header(ofilename, cdata): - with open(ofilename, 'w', encoding='utf-8') as ofile: + ofilename_tmp = ofilename + '~' + with open(ofilename_tmp, 'w', encoding='utf-8') as ofile: ofile.write('''/* * Autogenerated by the Meson build system. * Do not edit, your changes will be lost. @@ -598,6 +614,7 @@ def dump_conf_header(ofilename, cdata): ofile.write('#define %s %s\n\n' % (k, v)) else: raise MesonException('Unknown data type in configuration file entry: ' + k) + replace_if_different(ofilename, ofilename_tmp) def replace_if_different(dst, dst_tmp): # If contents are identical, don't touch the file to prevent @@ -855,7 +872,7 @@ def get_filenames_templates_dict(inputs, outputs): values['@INPUT{}@'.format(ii)] = vv if len(inputs) == 1: # Just one value, substitute @PLAINNAME@ and @BASENAME@ - values['@PLAINNAME@'] = plain = os.path.split(inputs[0])[1] + values['@PLAINNAME@'] = plain = os.path.basename(inputs[0]) values['@BASENAME@'] = os.path.splitext(plain)[0] if outputs: # Gather values derived from the outputs, similar to above. @@ -863,7 +880,7 @@ def get_filenames_templates_dict(inputs, outputs): for (ii, vv) in enumerate(outputs): values['@OUTPUT{}@'.format(ii)] = vv # Outdir should be the same for all outputs - values['@OUTDIR@'] = os.path.split(outputs[0])[0] + values['@OUTDIR@'] = os.path.dirname(outputs[0]) # Many external programs fail on empty arguments. if values['@OUTDIR@'] == '': values['@OUTDIR@'] = '.' @@ -893,7 +910,7 @@ def detect_subprojects(spdir_name, current_dir='', result=None): if not os.path.exists(spdir): return result for trial in glob(os.path.join(spdir, '*')): - basename = os.path.split(trial)[1] + basename = os.path.basename(trial) if trial == 'packagecache': continue append_this = True diff --git a/mesonbuild/mesonmain.py b/mesonbuild/mesonmain.py index e48122f..619aa39 100644 --- a/mesonbuild/mesonmain.py +++ b/mesonbuild/mesonmain.py @@ -296,8 +296,6 @@ def run(original_args, mainfile=None): # FALLTHROUGH like it's 1972. elif cmd_name == 'introspect': return mintro.run(remaining_args) - elif cmd_name == 'test': - return mtest.run(remaining_args) elif cmd_name == 'rewrite': return rewriter.run(remaining_args) elif cmd_name == 'configure': @@ -368,10 +366,11 @@ def run(original_args, mainfile=None): app.generate() except Exception as e: if isinstance(e, MesonException): + mlog.log() if hasattr(e, 'file') and hasattr(e, 'lineno') and hasattr(e, 'colno'): - mlog.log(mlog.red('\nMeson encountered an error in file %s, line %d, column %d:' % (e.file, e.lineno, e.colno))) + mlog.log('%s:%d:%d:' % (e.file, e.lineno, e.colno), mlog.red('ERROR: '), end='') else: - mlog.log(mlog.red('\nMeson encountered an error:')) + mlog.log(mlog.red('ERROR: '), end='') # Error message mlog.log(e) # Path to log file diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py index b23869f..8cf66af 100644 --- a/mesonbuild/mintro.py +++ b/mesonbuild/mintro.py @@ -49,14 +49,14 @@ parser.add_argument('builddir', nargs='?', help='The build directory') def determine_installed_path(target, installdata): install_target = None for i in installdata.targets: - if os.path.split(i[0])[1] == target.get_filename(): # FIXME, might clash due to subprojects. + if os.path.basename(i[0]) == target.get_filename(): # FIXME, might clash due to subprojects. install_target = i break if install_target is None: raise RuntimeError('Something weird happened. File a bug.') fname = i[0] outdir = i[1] - outname = os.path.join(installdata.prefix, outdir, os.path.split(fname)[-1]) + outname = os.path.join(installdata.prefix, outdir, os.path.basename(fname)) # Normalize the path by using os.path.sep consistently, etc. # Does not change the effective path. return str(pathlib.PurePath(outname)) @@ -139,6 +139,8 @@ def add_keys(optlist, options): elif isinstance(opt, coredata.UserComboOption): optdict['choices'] = opt.choices typestr = 'combo' + elif isinstance(opt, coredata.UserIntegerOption): + typestr = 'integer' elif isinstance(opt, coredata.UserArrayOption): typestr = 'array' else: diff --git a/mesonbuild/mlog.py b/mesonbuild/mlog.py index aa2ac20..273552d 100644 --- a/mesonbuild/mlog.py +++ b/mesonbuild/mlog.py @@ -105,12 +105,15 @@ def log(*args, **kwargs): def warning(*args, **kwargs): from . import environment + args = (yellow('WARNING:'),) + args + if kwargs.get('location'): location = kwargs['location'] del kwargs['location'] - args += ('in file {}, line {}.'.format(os.path.join(location.subdir, environment.build_filename), location.lineno),) + location = '{}:{}:'.format(os.path.join(location.subdir, environment.build_filename), location.lineno) + args = (location,) + args - log(yellow('WARNING:'), *args, **kwargs) + log(*args, **kwargs) # Format a list for logging purposes as a string. It separates # all but the last item with commas, and the last with 'and'. diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index db85420..218e3b3 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -362,7 +362,7 @@ class GnomeModule(ExtensionModule): ldflags.update([lib]) if isinstance(dep, PkgConfigDependency): - girdir = dep.get_pkgconfig_variable("girdir", {}) + girdir = dep.get_pkgconfig_variable("girdir", {'default': ''}) if girdir: gi_includes.update([girdir]) elif isinstance(dep, (build.StaticLibrary, build.SharedLibrary)): @@ -553,7 +553,7 @@ class GnomeModule(ExtensionModule): if subdir not in typelib_includes: typelib_includes.append(subdir) elif isinstance(dep, PkgConfigDependency): - girdir = dep.get_pkgconfig_variable("girdir", {}) + girdir = dep.get_pkgconfig_variable("girdir", {'default': ''}) if girdir and girdir not in typelib_includes: typelib_includes.append(girdir) # ldflags will be misinterpreted by gir scanner (showing diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py index 54c2126..5573a2e 100644 --- a/mesonbuild/modules/pkgconfig.py +++ b/mesonbuild/modules/pkgconfig.py @@ -66,13 +66,15 @@ class DependenciesHelper: elif hasattr(obj, 'generated_pc'): processed_reqs.append(obj.generated_pc) elif isinstance(obj, dependencies.PkgConfigDependency): - processed_reqs.append(obj.name) + if obj.found(): + processed_reqs.append(obj.name) elif isinstance(obj, dependencies.ThreadDependency): processed_libs += obj.get_compiler().thread_link_flags(obj.env) processed_cflags += obj.get_compiler().thread_flags(obj.env) elif isinstance(obj, dependencies.Dependency): - processed_libs += obj.get_link_args() - processed_cflags += obj.get_compile_args() + if obj.found(): + processed_libs += obj.get_link_args() + processed_cflags += obj.get_compile_args() elif isinstance(obj, (build.SharedLibrary, build.StaticLibrary)): processed_libs.append(obj) if public: diff --git a/mesonbuild/modules/qt.py b/mesonbuild/modules/qt.py index 54e2c73..f5ce1ed 100644 --- a/mesonbuild/modules/qt.py +++ b/mesonbuild/modules/qt.py @@ -73,7 +73,7 @@ class QtBaseModule: def parse_qrc(self, state, fname): abspath = os.path.join(state.environment.source_dir, state.subdir, fname) - relative_part = os.path.split(fname)[0] + relative_part = os.path.dirname(fname) try: tree = ET.parse(abspath) root = tree.getroot() @@ -83,7 +83,7 @@ class QtBaseModule: mlog.warning("malformed rcc file: ", os.path.join(state.subdir, fname)) break else: - result.append(os.path.join(state.subdir, relative_part, child.text)) + result.append(os.path.join(relative_part, child.text)) return result except Exception: return [] @@ -116,7 +116,7 @@ class QtBaseModule: sources.append(res_target) else: for rcc_file in rcc_files: - basename = os.path.split(rcc_file)[1] + basename = os.path.basename(rcc_file) name = 'qt' + str(self.qt_version) + '-' + basename.replace('.', '_') rcc_kwargs = {'input': rcc_file, 'output': name + '.cpp', diff --git a/mesonbuild/modules/unstable_icestorm.py b/mesonbuild/modules/unstable_icestorm.py new file mode 100644 index 0000000..0b7b339 --- /dev/null +++ b/mesonbuild/modules/unstable_icestorm.py @@ -0,0 +1,85 @@ +# Copyright 2017 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .. import mesonlib, compilers, mlog + +from . import ExtensionModule + +class IceStormModule(ExtensionModule): + + def __init__(self): + super().__init__() + self.snippets.add('project') + self.yosys_bin = None + + def detect_binaries(self, interpreter): + self.yosys_bin = interpreter.func_find_program(None, ['yosys'], {}) + self.arachne_bin = interpreter.func_find_program(None, ['arachne-pnr'], {}) + self.icepack_bin = interpreter.func_find_program(None, ['icepack'], {}) + self.iceprog_bin = interpreter.func_find_program(None, ['iceprog'], {}) + self.icetime_bin = interpreter.func_find_program(None, ['icetime'], {}) + + def project(self, interpreter, state, args, kwargs): + if not self.yosys_bin: + self.detect_binaries(interpreter) + result = [] + if not len(args): + raise mesonlib.MesonException('Project requires at least one argument, which is the project name.') + proj_name = args[0] + arg_sources = args[1:] + if not isinstance(proj_name, str): + raise mesonlib.MesonException('Argument must be a string.') + kwarg_sources = kwargs.get('sources', []) + if not isinstance(kwarg_sources, list): + kwarg_sources = [kwarg_sources] + all_sources = interpreter.source_strings_to_files(interpreter.flatten(arg_sources + kwarg_sources)) + if 'constraint_file' not in kwargs: + raise mesonlib.MesonException('Constraint file not specified.') + + constraint_file = interpreter.source_strings_to_files(kwargs['constraint_file']) + if len(constraint_file) != 1: + raise mesonlib.MesonException('Constraint file must contain one and only one entry.') + blif_name = proj_name + '_blif' + blif_fname = proj_name + '.blif' + asc_name = proj_name + '_asc' + asc_fname = proj_name + '.asc' + bin_name = proj_name + '_bin' + bin_fname = proj_name + '.bin' + time_name = proj_name + '-time' + upload_name = proj_name + '-upload' + + blif_target = interpreter.func_custom_target(None, [blif_name], { + 'input': all_sources, + 'output': blif_fname, + 'command': [self.yosys_bin, '-q', '-p', 'synth_ice40 -blif @OUTPUT@', '@INPUT@']}) + + asc_target = interpreter.func_custom_target(None, [asc_name], { + 'input': blif_target, + 'output': asc_fname, + 'command': [self.arachne_bin, '-q', '-d', '1k', '-p', constraint_file, '@INPUT@', '-o', '@OUTPUT@']}) + + bin_target = interpreter.func_custom_target(None, [bin_name], { + 'input': asc_target, + 'output': bin_fname, + 'command': [self.icepack_bin, '@INPUT@', '@OUTPUT@'], + 'build_by_default' : True}) + + up_target = interpreter.func_run_target(None, [upload_name], { + 'command': [self.iceprog_bin, bin_target]}) + + time_target = interpreter.func_run_target(None, [time_name], { + 'command' : [self.icetime_bin, bin_target]}) + +def initialize(): + return IceStormModule() diff --git a/mesonbuild/mparser.py b/mesonbuild/mparser.py index eb03393..94d56e5 100644 --- a/mesonbuild/mparser.py +++ b/mesonbuild/mparser.py @@ -71,6 +71,7 @@ class Lexer: # Need to be sorted longest to shortest. ('ignore', re.compile(r'[ \t]')), ('id', re.compile('[_a-zA-Z][_0-9a-zA-Z]*')), + ('hexnumber', re.compile('0[xX][0-9a-fA-F]+')), ('number', re.compile(r'\d+')), ('eol_cont', re.compile(r'\\\n')), ('eol', re.compile(r'\n')), @@ -152,6 +153,9 @@ class Lexer: line_start = mo.end() - len(lines[-1]) elif tid == 'number': value = int(match_text) + elif tid == 'hexnumber': + tid = 'number' + value = int(match_text, base=16) elif tid == 'eol' or tid == 'eol_cont': lineno += 1 line_start = loc @@ -368,7 +372,7 @@ class ArgumentNode: def set_kwarg(self, name, value): if name in self.kwargs: - mlog.warning('Keyword argument "{}" defined multiple times'.format(name), location=self) + mlog.warning('Keyword argument "{}" defined multiple times.'.format(name), location=self) mlog.warning('This will be an error in future Meson releases.') self.kwargs[name] = value diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py index b39f5af..95e532c 100644 --- a/mesonbuild/mtest.py +++ b/mesonbuild/mtest.py @@ -241,8 +241,8 @@ class TestHarness: stdout = subprocess.PIPE stderr = subprocess.PIPE if self.options and self.options.split else subprocess.STDOUT - if not is_windows(): - setsid = os.setsid + if not is_windows(): + setsid = os.setsid p = subprocess.Popen(cmd, stdout=stdout, @@ -251,6 +251,7 @@ class TestHarness: cwd=test.workdir, preexec_fn=setsid) timed_out = False + kill_test = False if test.timeout is None: timeout = None else: @@ -261,6 +262,11 @@ class TestHarness: if self.options.verbose: print("%s time out (After %d seconds)" % (test.name, timeout)) timed_out = True + except KeyboardInterrupt: + mlog.warning("CTRL-C detected while running %s" % (test.name)) + kill_test = True + + if kill_test or timed_out: # Python does not provide multiplatform support for # killing a process and all its children so we need # to roll our own. @@ -438,7 +444,7 @@ TIMEOUT: %4d logfile_base = os.path.join(self.options.wd, 'meson-logs', self.options.logbase) if self.options.wrapper: - namebase = os.path.split(self.get_wrapper()[0])[1] + namebase = os.path.basename(self.get_wrapper()[0]) elif self.options.setup: namebase = self.options.setup diff --git a/mesonbuild/optinterpreter.py b/mesonbuild/optinterpreter.py index df945ab..d4ea06a 100644 --- a/mesonbuild/optinterpreter.py +++ b/mesonbuild/optinterpreter.py @@ -64,16 +64,21 @@ def permitted_kwargs(permitted): optname_regex = re.compile('[^a-zA-Z0-9_-]') -@permitted_kwargs({'value'}) +@permitted_kwargs({'value', 'yield'}) def StringParser(name, description, kwargs): - return coredata.UserStringOption(name, description, - kwargs.get('value', ''), kwargs.get('choices', [])) + return coredata.UserStringOption(name, + description, + kwargs.get('value', ''), + kwargs.get('choices', []), + kwargs.get('yield', coredata.default_yielding)) -@permitted_kwargs({'value'}) +@permitted_kwargs({'value', 'yield'}) def BooleanParser(name, description, kwargs): - return coredata.UserBooleanOption(name, description, kwargs.get('value', True)) + return coredata.UserBooleanOption(name, description, + kwargs.get('value', True), + kwargs.get('yield', coredata.default_yielding)) -@permitted_kwargs({'value', 'choices'}) +@permitted_kwargs({'value', 'yiel', 'choices'}) def ComboParser(name, description, kwargs): if 'choices' not in kwargs: raise OptionException('Combo option missing "choices" keyword.') @@ -83,9 +88,25 @@ def ComboParser(name, description, kwargs): for i in choices: if not isinstance(i, str): raise OptionException('Combo choice elements must be strings.') - return coredata.UserComboOption(name, description, choices, kwargs.get('value', choices[0])) - -@permitted_kwargs({'value', 'choices'}) + return coredata.UserComboOption(name, + description, + choices, + kwargs.get('value', choices[0]), + kwargs.get('yield', coredata.default_yielding),) + + +@permitted_kwargs({'value', 'min', 'max', 'yield'}) +def IntegerParser(name, description, kwargs): + if 'value' not in kwargs: + raise OptionException('Integer option must contain value argument.') + return coredata.UserIntegerOption(name, + description, + kwargs.get('min', None), + kwargs.get('max', None), + kwargs['value'], + kwargs.get('yield', coredata.default_yielding)) + +@permitted_kwargs({'value', 'yield', 'choices'}) def string_array_parser(name, description, kwargs): if 'choices' in kwargs: choices = kwargs['choices'] @@ -100,11 +121,16 @@ def string_array_parser(name, description, kwargs): value = kwargs.get('value', []) if not isinstance(value, list): raise OptionException('Array choices must be passed as an array.') - return coredata.UserArrayOption(name, description, value, choices=choices) + return coredata.UserArrayOption(name, + description, + value, + choices=choices, + yielding=kwargs.get('yield', coredata.default_yielding)) option_types = {'string': StringParser, 'boolean': BooleanParser, 'combo': ComboParser, + 'integer': IntegerParser, 'array': string_array_parser, } diff --git a/mesonbuild/scripts/coverage.py b/mesonbuild/scripts/coverage.py index 25451d4..47f4cda 100644 --- a/mesonbuild/scripts/coverage.py +++ b/mesonbuild/scripts/coverage.py @@ -14,12 +14,7 @@ from mesonbuild import environment -import sys, os, subprocess - -def remove_dir_from_trace(lcov_command, covfile, dirname): - tmpfile = covfile + '.tmp' - subprocess.check_call([lcov_command, '--remove', covfile, dirname, '-o', tmpfile]) - os.replace(tmpfile, covfile) +import sys, os, subprocess, pathlib def coverage(source_root, build_root, log_dir): (gcovr_exe, lcov_exe, genhtml_exe) = environment.find_coverage_tools() @@ -38,6 +33,7 @@ def coverage(source_root, build_root, log_dir): covinfo = os.path.join(log_dir, 'coverage.info') initial_tracefile = covinfo + '.initial' run_tracefile = covinfo + '.run' + raw_tracefile = covinfo + '.raw' subprocess.check_call([lcov_exe, '--directory', build_root, '--capture', @@ -55,11 +51,12 @@ def coverage(source_root, build_root, log_dir): subprocess.check_call([lcov_exe, '-a', initial_tracefile, '-a', run_tracefile, - '-o', covinfo]) - remove_dir_from_trace(lcov_exe, covinfo, '/usr/include/*') - remove_dir_from_trace(lcov_exe, covinfo, '/usr/local/include/*') - remove_dir_from_trace(lcov_exe, covinfo, '/usr/src/*') - remove_dir_from_trace(lcov_exe, covinfo, '/usr/lib/llvm-*/include/*') + '-o', raw_tracefile]) + # Remove all directories outside the source_root from the covinfo + subprocess.check_call([lcov_exe, + '--extract', raw_tracefile, + os.path.join(source_root, '*'), + '--output-file', covinfo]) subprocess.check_call([genhtml_exe, '--prefix', build_root, '--output-directory', htmloutdir, @@ -68,6 +65,15 @@ def coverage(source_root, build_root, log_dir): '--show-details', '--branch-coverage', covinfo]) + if gcovr_exe: + print('') + print('XML coverage report can be found at', + pathlib.Path(log_dir, 'coverage.xml').as_uri()) + print('Text coverage report can be found at', + pathlib.Path(log_dir, 'coverage.txt').as_uri()) + if lcov_exe and genhtml_exe: + print('Html coverage report can be found at', + pathlib.Path(htmloutdir, 'index.html').as_uri()) return 0 def run(args): diff --git a/mesonbuild/scripts/gettext.py b/mesonbuild/scripts/gettext.py index 30ac54c..f308c5a 100644 --- a/mesonbuild/scripts/gettext.py +++ b/mesonbuild/scripts/gettext.py @@ -81,7 +81,7 @@ def do_install(src_sub, bld_sub, dest, pkgname, langs): srcfile = os.path.join(bld_sub, l + '.gmo') outfile = os.path.join(dest, l, 'LC_MESSAGES', pkgname + '.mo') - os.makedirs(os.path.split(outfile)[0], exist_ok=True) + os.makedirs(os.path.dirname(outfile), exist_ok=True) shutil.copyfile(srcfile, outfile) shutil.copystat(srcfile, outfile) print('Installing %s to %s' % (srcfile, outfile)) diff --git a/mesonbuild/scripts/meson_install.py b/mesonbuild/scripts/meson_install.py index fe1de1f..cbc782d 100644 --- a/mesonbuild/scripts/meson_install.py +++ b/mesonbuild/scripts/meson_install.py @@ -128,20 +128,42 @@ def do_copyfile(from_file, to_file): selinux_updates.append(to_file) append_to_log(to_file) -def do_copydir(data, src_prefix, src_dir, dst_dir, exclude): +def do_copydir(data, src_dir, dst_dir, exclude): ''' - Copies the directory @src_prefix (full path) into @dst_dir - - @src_dir is simply the parent directory of @src_prefix + Copies the contents of directory @src_dir into @dst_dir. + + For directory + /foo/ + bar/ + excluded + foobar + file + do_copydir(..., '/foo', '/dst/dir', {'bar/excluded'}) creates + /dst/ + dir/ + bar/ + foobar + file + + Args: + src_dir: str, absolute path to the source directory + dst_dir: str, absolute path to the destination directory + exclude: (set(str), set(str)), tuple of (exclude_files, exclude_dirs), + each element of the set is a path relative to src_dir. ''' + if not os.path.isabs(src_dir): + raise ValueError('src_dir must be absolute, got %s' % src_dir) + if not os.path.isabs(dst_dir): + raise ValueError('dst_dir must be absolute, got %s' % dst_dir) if exclude is not None: exclude_files, exclude_dirs = exclude else: exclude_files = exclude_dirs = set() - for root, dirs, files in os.walk(src_prefix): + for root, dirs, files in os.walk(src_dir): + assert os.path.isabs(root) for d in dirs[:]: - abs_src = os.path.join(src_dir, root, d) - filepart = abs_src[len(src_dir) + 1:] + abs_src = os.path.join(root, d) + filepart = os.path.relpath(abs_src, start=src_dir) abs_dst = os.path.join(dst_dir, filepart) # Remove these so they aren't visited by os.walk at all. if filepart in exclude_dirs: @@ -155,8 +177,8 @@ def do_copydir(data, src_prefix, src_dir, dst_dir, exclude): data.dirmaker.makedirs(abs_dst) shutil.copystat(abs_src, abs_dst) for f in files: - abs_src = os.path.join(src_dir, root, f) - filepart = abs_src[len(src_dir) + 1:] + abs_src = os.path.join(root, f) + filepart = os.path.relpath(abs_src, start=src_dir) if filepart in exclude_files: continue abs_dst = os.path.join(dst_dir, filepart) @@ -164,10 +186,10 @@ def do_copydir(data, src_prefix, src_dir, dst_dir, exclude): print('Tried to copy file %s but a directory of that name already exists.' % abs_dst) if os.path.exists(abs_dst): os.unlink(abs_dst) - parent_dir = os.path.split(abs_dst)[0] + parent_dir = os.path.dirname(abs_dst) if not os.path.isdir(parent_dir): os.mkdir(parent_dir) - shutil.copystat(os.path.split(abs_src)[0], parent_dir) + shutil.copystat(os.path.dirname(abs_src), parent_dir) shutil.copy2(abs_src, abs_dst, follow_symlinks=False) append_to_log(abs_dst) @@ -195,23 +217,19 @@ def do_install(datafilename): run_install_script(d) def install_subdirs(d): - for (src_dir, inst_dir, dst_dir, mode, exclude) in d.install_subdirs: - if src_dir.endswith('/') or src_dir.endswith('\\'): - src_dir = src_dir[:-1] - src_prefix = os.path.join(src_dir, inst_dir) - print('Installing subdir %s to %s' % (src_prefix, dst_dir)) - dst_dir = get_destdir_path(d, dst_dir) - d.dirmaker.makedirs(dst_dir, exist_ok=True) - do_copydir(d, src_prefix, src_dir, dst_dir, exclude) - dst_prefix = os.path.join(dst_dir, inst_dir) - set_mode(dst_prefix, mode) + for (src_dir, dst_dir, mode, exclude) in d.install_subdirs: + full_dst_dir = get_destdir_path(d, dst_dir) + print('Installing subdir %s to %s' % (src_dir, full_dst_dir)) + d.dirmaker.makedirs(full_dst_dir, exist_ok=True) + do_copydir(d, src_dir, full_dst_dir, exclude) + set_mode(full_dst_dir, mode) def install_data(d): for i in d.data: fullfilename = i[0] outfilename = get_destdir_path(d, i[1]) mode = i[2] - outdir = os.path.split(outfilename)[0] + outdir = os.path.dirname(outfilename) d.dirmaker.makedirs(outdir, exist_ok=True) print('Installing %s to %s' % (fullfilename, outdir)) do_copyfile(fullfilename, outfilename) @@ -221,7 +239,7 @@ def install_man(d): for m in d.man: full_source_filename = m[0] outfilename = get_destdir_path(d, m[1]) - outdir = os.path.split(outfilename)[0] + outdir = os.path.dirname(outfilename) d.dirmaker.makedirs(outdir, exist_ok=True) print('Installing %s to %s' % (full_source_filename, outdir)) if outfilename.endswith('.gz') and not full_source_filename.endswith('.gz'): @@ -238,7 +256,7 @@ def install_man(d): def install_headers(d): for t in d.headers: fullfilename = t[0] - fname = os.path.split(fullfilename)[1] + fname = os.path.basename(fullfilename) outdir = get_destdir_path(d, t[1]) outfilename = os.path.join(outdir, fname) print('Installing %s to %s' % (fname, outdir)) @@ -304,7 +322,7 @@ def install_targets(d): for t in d.targets: fname = check_for_stampfile(t[0]) outdir = get_destdir_path(d, t[1]) - outname = os.path.join(outdir, os.path.split(fname)[-1]) + outname = os.path.join(outdir, os.path.basename(fname)) aliases = t[2] should_strip = t[3] install_rpath = t[4] @@ -316,7 +334,7 @@ def install_targets(d): do_copyfile(fname, outname) if should_strip and d.strip_bin is not None: if fname.endswith('.jar'): - print('Not stripping jar target:', os.path.split(fname)[1]) + print('Not stripping jar target:', os.path.basename(fname)) continue print('Stripping target {!r}'.format(fname)) ps, stdo, stde = Popen_safe(d.strip_bin + [outname]) @@ -332,7 +350,7 @@ def install_targets(d): do_copyfile(pdb_filename, pdb_outname) elif os.path.isdir(fname): fname = os.path.join(d.build_dir, fname.rstrip('/')) - do_copydir(d, fname, os.path.dirname(fname), outdir, None) + do_copydir(d, fname, os.path.join(outdir, os.path.basename(fname)), None) else: raise RuntimeError('Unknown file type for {!r}'.format(fname)) printed_symlink_error = False @@ -366,7 +384,7 @@ def run(args): print('Installer script for Meson. Do not run on your own, mmm\'kay?') print('meson_install.py [install info file]') datafilename = args[0] - private_dir = os.path.split(datafilename)[0] + private_dir = os.path.dirname(datafilename) log_dir = os.path.join(private_dir, '../meson-logs') with open(os.path.join(log_dir, 'install-log.txt'), 'w') as lf: install_log_file = lf diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py index 26a3489..bd440a1 100644 --- a/mesonbuild/wrap/wrap.py +++ b/mesonbuild/wrap/wrap.py @@ -164,17 +164,20 @@ class Resolver: if not ret: return False # Submodule has not been added, add it - if out.startswith(b'-'): + if out.startswith(b'+'): + mlog.warning('submodule {} might be out of date'.format(dirname)) + return True + elif out.startswith(b'U'): + raise RuntimeError('submodule {} has merge conflicts'.format(dirname)) + elif out.startswith(b'-'): if subprocess.call(['git', '-C', self.subdir_root, 'submodule', 'update', '--init', 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 + m = 'Unknown git submodule output: {!r}' + raise RuntimeError(m.format(out)) def get_git(self, p): checkoutdir = os.path.join(self.subdir_root, p.get('directory')) diff --git a/mesonbuild/wrap/wraptool.py b/mesonbuild/wrap/wraptool.py index 0bdc417..09a0289 100644 --- a/mesonbuild/wrap/wraptool.py +++ b/mesonbuild/wrap/wraptool.py @@ -150,7 +150,7 @@ def do_promotion(from_path, spdir_name): assert(from_path.endswith('.wrap')) shutil.copy(from_path, spdir_name) elif os.path.isdir(from_path): - sproj_name = os.path.split(from_path)[1] + sproj_name = os.path.basename(from_path) outputdir = os.path.join(spdir_name, sproj_name) if os.path.exists(outputdir): sys.exit('Output dir %s already exists. Will not overwrite.' % outputdir) @@ -178,7 +178,7 @@ def promote(argument): def status(): print('Subproject status') for w in glob('subprojects/*.wrap'): - name = os.path.split(w)[1][:-5] + name = os.path.basename(w)[:-5] try: (latest_branch, latest_revision) = get_latest_version(name) except Exception: |