diff options
109 files changed, 1611 insertions, 423 deletions
diff --git a/.appveyor.yml b/.appveyor.yml index 289758d..c79e250 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -20,13 +20,27 @@ environment: compiler: msvc2015 backend: vs2015 + - arch: x86 + compiler: msys2-mingw + backend: ninja + - arch: x64 - compiler: msvc2015 + compiler: msvc2017 backend: ninja + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - arch: x64 - compiler: msvc2015 - backend: vs2015 + compiler: msvc2017 + backend: vs2017 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + + - arch: x64 + compiler: msys2-mingw + backend: ninja + + - arch: x64 + compiler: cygwin + backend: ninja platform: - x64 @@ -43,15 +57,24 @@ 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% ) + - cmd: if %compiler%==msys2-mingw (if %arch%==x86 (set "PATH=C:\msys64\mingw32\bin;%PATH%") else (set "PATH=C:\msys64\mingw64\bin;%PATH%")) + - cmd: if %compiler%==cygwin ( call ci\appveyor-install.bat ) build_script: - cmd: echo No build step. - - cmd: if %backend%==ninja ( ninja.exe --version ) else ( MSBuild /version & echo. ) + - cmd: if not %compiler%==cygwin if %backend%==ninja ( ninja.exe --version ) else ( MSBuild /version & echo. ) test_script: - cmd: echo Running tests for %arch% and %compiler% with the %backend% backend - - cmd: PATH=%cd%;%MESON_PYTHON_PATH%;%PATH%; && python run_tests.py --backend=%backend% + - cmd: set "ORIG_PATH=%PATH%" + - cmd: if %compiler%==cygwin ( set "PATH=%CYGWIN_ROOT%\bin;%SYSTEMROOT%\system32" && bash -lc "cd $APPVEYOR_BUILD_FOLDER && ci/appveyor-test.sh" ) + - cmd: if not %compiler%==cygwin ( set "PATH=%cd%;%MESON_PYTHON_PATH%;%PATH%;" && python run_tests.py --backend=%backend% ) on_finish: + - set "PATH=%ORIG_PATH%" - appveyor PushArtifact meson-test-run.txt -DeploymentName "Text test logs" - appveyor PushArtifact meson-test-run.xml -DeploymentName "XML test logs" + +cache: + - C:\cache @@ -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/authors.txt b/authors.txt index c82b288..50c032b 100644 --- a/authors.txt +++ b/authors.txt @@ -68,3 +68,12 @@ Rodrigo Lourenço Sebastian Stang Marc Becker Michal Sojka +Aaron Small +Joe Baldino +Peter Harris +Roger Boerdijk +melak47 +Philipp Ittershagen +Dylan Baker +Aaron Plattner +Jon Turney diff --git a/ci/appveyor-install.bat b/ci/appveyor-install.bat new file mode 100644 index 0000000..0c1ce44 --- /dev/null +++ b/ci/appveyor-install.bat @@ -0,0 +1,11 @@ +set CACHE=C:\cache +set CYGWIN_MIRROR="http://cygwin.mirror.constant.com" + +if _%arch%_ == _x64_ set SETUP=setup-x86_64.exe && set CYGWIN_ROOT=C:\cygwin64 +if _%arch%_ == _x86_ set SETUP=setup-x86.exe && set CYGWIN_ROOT=C:\cygwin + +if not exist %CACHE% mkdir %CACHE% + +echo Updating Cygwin and installing ninja and test prerequisites +%CYGWIN_ROOT%\%SETUP% -qnNdO -R "%CYGWIN_ROOT%" -s "%CYGWIN_MIRROR%" -l "%CACHE%" -g -P "ninja,gcc-objc,gcc-objc++,libglib2.0-devel,zlib-devel" +echo Install done diff --git a/ci/appveyor-test.sh b/ci/appveyor-test.sh new file mode 100755 index 0000000..2f29630 --- /dev/null +++ b/ci/appveyor-test.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +echo ninja $(ninja --version) +python3 --version -V + +python3 run_tests.py --backend=${backend} diff --git a/manual tests/4 standalone binaries/meson.build b/manual tests/4 standalone binaries/meson.build index baae796..ad6645f 100644 --- a/manual tests/4 standalone binaries/meson.build +++ b/manual tests/4 standalone binaries/meson.build @@ -16,12 +16,12 @@ if host_machine.system() == 'darwin' install_data('Info.plist', install_dir : 'Contents') - meson.set_install_script('osx_bundler.sh') + meson.add_install_script('osx_bundler.sh') endif if host_machine.system() == 'linux' install_data('myapp.sh', install_dir : '.') - meson.set_install_script('linux_bundler.sh') + meson.add_install_script('linux_bundler.sh') endif extra_link_args = [] diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 9749eb4..efc5bff 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -78,6 +78,24 @@ class TestSerialisation: self.workdir = workdir self.extra_paths = extra_paths +class OptionProxy: + def __init__(self, name, value): + self.name = name + self.value = value + +class OptionOverrideProxy: + '''Mimic an option list but transparently override + selected option values.''' + def __init__(self, overrides, options): + self.overrides = overrides + self.options = options + + def __getitem__(self, option_name): + base_opt = self.options[option_name] + if option_name in self.overrides: + return OptionProxy(base_opt.name, base_opt.validate_value(self.overrides[option_name])) + return base_opt + # This class contains the basic functionality that is needed by all backends. # Feel free to move stuff in and out of it as you see fit. class Backend: @@ -105,6 +123,12 @@ class Backend: def get_target_filename_abs(self, target): return os.path.join(self.environment.get_build_dir(), self.get_target_filename(target)) + def get_option_for_target(self, option_name, target): + if option_name in target.option_overrides: + override = target.option_overrides[option_name] + return self.environment.coredata.validate_option_value(option_name, override) + return self.environment.coredata.get_builtin_option(option_name) + def get_target_filename_for_linking(self, target): # On some platforms (msvc for instance), the file that is used for # dynamic linking is not the same as the dynamic library itself. This @@ -154,8 +178,10 @@ class Backend: compsrcs = classify_unity_sources(target.compilers.values(), unity_src) def init_language_file(suffix): - outfilename = os.path.join(self.get_target_private_dir_abs(target), - self.get_unity_source_filename(target, suffix)) + unity_src_name = self.get_unity_source_filename(target, suffix) + unity_src_subdir = self.get_target_private_dir_abs(target) + outfilename = os.path.join(unity_src_subdir, + unity_src_name) outfileabs = os.path.join(self.environment.get_build_dir(), outfilename) outfileabs_tmp = outfileabs + '.tmp' @@ -163,7 +189,7 @@ class Backend: outfileabs_tmp_dir = os.path.dirname(outfileabs_tmp) if not os.path.exists(outfileabs_tmp_dir): os.makedirs(outfileabs_tmp_dir) - result.append(outfilename) + result.append(mesonlib.File(True, unity_src_subdir, unity_src_name)) return open(outfileabs_tmp, 'w') # For each language, generate a unity source file and return the list @@ -188,7 +214,7 @@ class Backend: elif isinstance(obj, mesonlib.File): obj_list.append(obj.rel_to_builddir(self.build_to_src)) elif isinstance(obj, build.ExtractedObjects): - obj_list += self.determine_ext_objs(obj, proj_dir_to_build_root) + obj_list += self.determine_ext_objs(target, obj, proj_dir_to_build_root) else: raise MesonException('Unknown data type in object list.') return obj_list @@ -228,7 +254,7 @@ class Backend: exe_wrapper = self.environment.cross_info.config['binaries'].get('exe_wrapper', None) else: exe_wrapper = None - if mesonlib.is_windows(): + if mesonlib.is_windows() or mesonlib.is_cygwin(): extra_paths = self.determine_windows_extra_paths(exe) else: extra_paths = [] @@ -263,32 +289,34 @@ class Backend: raise MesonException(m.format(target.name)) return l - def object_filename_from_source(self, target, source): + def object_filename_from_source(self, target, source, is_unity): if isinstance(source, mesonlib.File): source = source.fname # foo.vala files compile down to foo.c and then foo.c.o, not foo.vala.o if source.endswith('.vala'): + if is_unity: + return source[:-5] + '.c.' + self.environment.get_object_suffix() source = os.path.join(self.get_target_private_dir(target), source[:-5] + '.c') return source.replace('/', '_').replace('\\', '_') + '.' + self.environment.get_object_suffix() - def determine_ext_objs(self, extobj, proj_dir_to_build_root): + def determine_ext_objs(self, target, extobj, proj_dir_to_build_root): result = [] targetdir = self.get_target_private_dir(extobj.target) # With unity builds, there's just one object that contains all the # sources, and we only support extracting all the objects in this mode, # so just return that. - if self.environment.coredata.get_builtin_option('unity'): + if self.get_option_for_target('unity', target): comp = get_compiler_for_source(extobj.target.compilers.values(), extobj.srclist[0]) - # The unity object name uses the full absolute path of the source file - osrc = os.path.join(self.get_target_private_dir_abs(extobj.target), - self.get_unity_source_filename(extobj.target, - comp.get_default_suffix())) - objname = self.object_filename_from_source(extobj.target, osrc) + # There is a potential conflict here, but it is unlikely that + # anyone both enables unity builds and has a file called foo-unity.cpp. + osrc = self.get_unity_source_filename(extobj.target, + comp.get_default_suffix()) + objname = self.object_filename_from_source(extobj.target, osrc, True) objpath = os.path.join(proj_dir_to_build_root, targetdir, objname) return [objpath] for osrc in extobj.srclist: - objname = self.object_filename_from_source(extobj.target, osrc) + objname = self.object_filename_from_source(extobj.target, osrc, False) objpath = os.path.join(proj_dir_to_build_root, targetdir, objname) result.append(objpath) return result @@ -340,6 +368,8 @@ class Backend: # various sources in the order in which they must override each other # starting from hard-coded defaults followed by build options and so on. commands = CompilerArgs(compiler) + + copt_proxy = OptionOverrideProxy(target.option_overrides, self.environment.coredata.compiler_options) # First, the trivial ones that are impossible to override. # # Add -nostdinc/-nostdinc++ if needed; can't be overriden @@ -350,19 +380,19 @@ class Backend: # we weren't explicitly asked to not emit warnings (for Vala, f.ex) if no_warn_args: commands += compiler.get_no_warn_args() - elif self.environment.coredata.get_builtin_option('buildtype') != 'plain': - commands += compiler.get_warn_args(self.environment.coredata.get_builtin_option('warning_level')) + elif self.get_option_for_target('buildtype', target) != 'plain': + commands += compiler.get_warn_args(self.get_option_for_target('warning_level', target)) # Add -Werror if werror=true is set in the build options set on the # command-line or default_options inside project(). This only sets the # action to be done for warnings if/when they are emitted, so it's ok # to set it after get_no_warn_args() or get_warn_args(). - if self.environment.coredata.get_builtin_option('werror'): + if self.get_option_for_target('werror', target): commands += compiler.get_werror_args() # Add compile args for c_* or cpp_* build options set on the # command-line or default_options inside project(). - commands += compiler.get_option_compile_args(self.environment.coredata.compiler_options) + commands += compiler.get_option_compile_args(copt_proxy) # Add buildtype args: optimization level, debugging, etc. - commands += compiler.get_buildtype_args(self.environment.coredata.get_builtin_option('buildtype')) + commands += compiler.get_buildtype_args(self.get_option_for_target('buildtype', target)) # Add compile args added using add_project_arguments() commands += self.build.get_project_args(compiler, target.subproject) # Add compile args added using add_global_arguments() @@ -377,9 +407,11 @@ class Backend: # Set -fPIC for static libraries by default unless explicitly disabled if isinstance(target, build.StaticLibrary) and target.pic: commands += compiler.get_pic_args() - # Add compile args needed to find external dependencies - # Link args are added while generating the link command - for dep in target.get_external_deps(): + # Add compile args needed to find external dependencies. Link args are + # added while generating the link command. + # NOTE: We must preserve the order in which external deps are + # specified, so we reverse the list before iterating over it. + for dep in reversed(target.get_external_deps()): commands += dep.get_compile_args() # Qt needs -fPIC for executables # XXX: We should move to -fPIC for all executables @@ -451,7 +483,7 @@ class Backend: exe_wrapper = self.environment.cross_info.config['binaries'].get('exe_wrapper', None) else: exe_wrapper = None - if mesonlib.is_windows(): + if mesonlib.is_windows() or mesonlib.is_cygwin(): extra_paths = self.determine_windows_extra_paths(exe) else: extra_paths = [] @@ -601,8 +633,13 @@ class Backend: def eval_custom_target_command(self, target, absolute_outputs=False): # We want the outputs to be absolute only when using the VS backend + # XXX: Maybe allow the vs backend to use relative paths too? + source_root = self.build_to_src + build_root = '.' outdir = self.get_target_dir(target) if absolute_outputs: + source_root = self.environment.get_source_dir() + build_root = self.environment.get_source_dir() outdir = os.path.join(self.environment.get_build_dir(), outdir) outputs = [] for i in target.output: @@ -628,6 +665,10 @@ class Backend: elif not isinstance(i, str): err_msg = 'Argument {0} is of unknown type {1}' raise RuntimeError(err_msg.format(str(i), str(type(i)))) + elif '@SOURCE_ROOT@' in i: + i = i.replace('@SOURCE_ROOT@', source_root) + elif '@BUILD_ROOT@' in i: + i = i.replace('@BUILD_ROOT@', build_root) elif '@DEPFILE@' in i: if target.depfile is None: msg = 'Custom target {!r} has @DEPFILE@ but no depfile ' \ diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index a26222f..5a9462f 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -147,6 +147,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 = {} @@ -336,7 +337,7 @@ int dummy; outname = self.get_target_filename(target) obj_list = [] use_pch = self.environment.coredata.base_options.get('b_pch', False) - is_unity = self.environment.coredata.get_builtin_option('unity') + is_unity = self.get_option_for_target('unity', target) if use_pch and target.has_pch(): pch_objects = self.generate_pch(target, outfile) else: @@ -433,7 +434,7 @@ int dummy; obj_list += self.flatten_object_list(target) if is_unity: for src in self.generate_unity_files(target, unity_src): - obj_list.append(self.generate_single_compile(target, outfile, RawFilename(src), True, unity_deps + header_deps)) + obj_list.append(self.generate_single_compile(target, outfile, src, True, unity_deps + header_deps)) linker = self.determine_linker(target) elem = self.generate_link(target, outfile, outname, obj_list, linker, pch_objects) self.generate_shlib_aliases(target, self.get_target_dir(target)) @@ -492,7 +493,7 @@ int dummy; # the project, we need to set PATH so the DLLs are found. We use # a serialized executable wrapper for that and check if the # CustomTarget command needs extra paths first. - if target.capture or (mesonlib.is_windows() and + if target.capture or ((mesonlib.is_windows() or mesonlib.is_cygwin()) and self.determine_windows_extra_paths(target.command[0])): exe_data = self.serialise_executable(target.command[0], cmd[1:], # All targets are built from the build dir @@ -631,9 +632,9 @@ int dummy; pickle.dump(d, ofile) def generate_target_install(self, d): - should_strip = self.environment.coredata.get_builtin_option('strip') for t in self.build.get_targets().values(): if t.should_install(): + should_strip = self.get_option_for_target('strip', t) # Find the installation directory. FIXME: Currently only one # installation directory is supported for each target outdir = t.get_custom_install_dir() @@ -845,7 +846,7 @@ int dummy; return args, deps def generate_cs_target(self, target, outfile): - buildtype = self.environment.coredata.get_builtin_option('buildtype') + buildtype = self.get_option_for_target('buildtype', target) fname = target.get_filename() outname_rel = os.path.join(self.get_target_dir(target), fname) src_list = target.get_sources() @@ -879,7 +880,7 @@ int dummy; def generate_single_java_compile(self, src, target, compiler, outfile): args = [] - args += compiler.get_buildtype_args(self.environment.coredata.get_builtin_option('buildtype')) + args += compiler.get_buildtype_args(self.get_option_for_target('buildtype', target)) args += self.build.get_global_args(compiler) args += self.build.get_project_args(compiler, target.subproject) args += target.get_java_args() @@ -1012,7 +1013,7 @@ int dummy; args = [] args += self.build.get_global_args(valac) args += self.build.get_project_args(valac, target.subproject) - args += valac.get_buildtype_args(self.environment.coredata.get_builtin_option('buildtype')) + args += valac.get_buildtype_args(self.get_option_for_target('buildtype', target)) # Tell Valac to output everything in our private directory. Sadly this # means it will also preserve the directory components of Vala sources # found inside the build tree (generated sources). @@ -1035,7 +1036,7 @@ int dummy; girname = os.path.join(self.get_target_dir(target), target.vala_gir) args += ['--gir', os.path.join('..', target.vala_gir)] valac_outputs.append(girname) - if self.environment.coredata.get_builtin_option('werror'): + if self.get_option_for_target('werror', target): args += valac.get_werror_args() for d in target.get_external_deps(): if isinstance(d, dependencies.PkgConfigDependency): @@ -1090,10 +1091,10 @@ int dummy; else: raise InvalidArguments('Unknown target type for rustc.') args.append(cratetype) - args += rustc.get_buildtype_args(self.environment.coredata.get_builtin_option('buildtype')) - depfile = target.name + '.d' - args += ['--out-dir', target.subdir] - args += ['--emit', 'dep-info', '--emit', 'link'] + args += rustc.get_buildtype_args(self.get_option_for_target('buildtype', target)) + depfile = os.path.join(target.subdir, target.name + '.d') + args += ['--emit', 'dep-info={}'.format(depfile), '--emit', 'link'] + args += ['-o', os.path.join(target.subdir, target.get_filename())] orderdeps = [os.path.join(t.subdir, t.get_filename()) for t in target.link_targets] linkdirs = OrderedDict() for d in target.link_targets: @@ -1250,7 +1251,11 @@ int dummy; else: command_template = ' command = {executable} $LINK_ARGS {output_args} $in\n' cmdlist = [] - if isinstance(static_linker, compilers.ArLinker): + # FIXME: Must normalize file names with pathlib.Path before writing + # them out to fix this properly on Windows. See: + # https://github.com/mesonbuild/meson/issues/1517 + # https://github.com/mesonbuild/meson/issues/1526 + if isinstance(static_linker, compilers.ArLinker) and not mesonlib.is_windows(): # `ar` has no options to overwrite archives. It always appends, # which is never what we want. Delete an existing library first if # it exists. https://github.com/mesonbuild/meson/issues/1355 @@ -1600,6 +1605,8 @@ rule FORTRAN_DEP_HACK relout = self.get_target_private_dir(target) args = [x.replace("@SOURCE_DIR@", self.build_to_src).replace("@BUILD_DIR@", relout) for x in args] + args = [x.replace("@SOURCE_ROOT@", self.build_to_src).replace("@BUILD_ROOT@", '.') + for x in args] cmdlist = exe_arr + self.replace_extra_args(args, genlist) elem = NinjaBuildElement(self.all_outputs, outfiles, rulename, infilename) if generator.depfile is not None: @@ -1805,24 +1812,16 @@ 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): + base_proxy = backends.OptionOverrideProxy(target.option_overrides, + self.environment.coredata.base_options) # 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) # Add compiler args for compiling this target derived from 'base' build # options passed on the command-line, in default_options, etc. # These have the lowest priority. - commands += compilers.get_base_compile_args(self.environment.coredata.base_options, + commands += compilers.get_base_compile_args(base_proxy, compiler) # The code generated by valac is usually crap and has tons of unused # variables and such, so disable warnings for Vala C sources. @@ -1834,10 +1833,12 @@ rule FORTRAN_DEP_HACK # and from `include_directories:` of internal deps of the target. # # Target include dirs should override internal deps include dirs. + # This is handled in BuildTarget.process_kwargs() # # Include dirs from internal deps should override include dirs from - # external deps. - for i in target.get_include_dirs(): + # external deps and must maintain the order in which they are specified. + # Hence, we must reverse the list so that the order is preserved. + for i in reversed(target.get_include_dirs()): basedir = i.get_curdir() for d in i.get_incdirs(): # Avoid superfluous '/.' at the end of paths when d is '.' @@ -1882,6 +1883,29 @@ 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)) + + 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. @@ -1891,6 +1915,10 @@ rule FORTRAN_DEP_HACK abs_src = src.fname else: abs_src = os.path.join(self.environment.get_build_dir(), src.fname) + elif isinstance(src, mesonlib.File): + rel_src = src.rel_to_builddir(self.build_to_src) + abs_src = src.absolute_path(self.environment.get_source_dir(), + self.environment.get_build_dir()) elif is_generated: raise AssertionError('BUG: broken generated source file handling for {!r}'.format(src)) else: @@ -1967,7 +1995,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() @@ -2132,7 +2159,7 @@ rule FORTRAN_DEP_HACK # Add things like /NOLOGO; usually can't be overriden commands += linker.get_linker_always_args() # Add buildtype linker args: optimization level, etc. - commands += linker.get_buildtype_linker_args(self.environment.coredata.get_builtin_option('buildtype')) + commands += linker.get_buildtype_linker_args(self.get_option_for_target('buildtype', target)) # Add /DEBUG and the pdb filename when using MSVC commands += self.get_link_debugfile_args(linker, target, outname) # Add link args specific to this BuildTarget type, such as soname args, diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index 5ec49ad..139360c 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -27,6 +27,29 @@ from ..compilers import CompilerArgs from ..mesonlib import MesonException, File, get_meson_script 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,8 +85,9 @@ 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): + def object_filename_from_source(self, target, source, is_unity=False): basename = os.path.basename(source.fname) filename_without_extension = '.'.join(basename.split('.')[:-1]) if basename in self.sources_conflicts[target.get_id()]: @@ -127,7 +151,11 @@ class Vs2010Backend(backends.Backend): args = [x.replace("@INPUT@", infilename).replace('@OUTPUT@', sole_output) for x in base_args] args = self.replace_outputs(args, target_private_dir, outfiles_rel) - args = [x.replace("@SOURCE_DIR@", self.environment.get_source_dir()).replace("@BUILD_DIR@", target_private_dir) + args = [x.replace("@SOURCE_DIR@", self.environment.get_source_dir()) + .replace("@BUILD_DIR@", target_private_dir) + for x in args] + args = [x.replace("@SOURCE_ROOT@", self.environment.get_source_dir()) + .replace("@BUILD_ROOT@", self.environment.get_build_dir()) for x in args] cmd = exe_arr + self.replace_extra_args(args, genlist) cbs = ET.SubElement(idgroup, 'CustomBuild', Include=infilename) @@ -354,6 +382,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') @@ -573,6 +603,8 @@ class Vs2010Backend(backends.Backend): # Prefix to use to access the source tree's subdir from the vcxproj dir proj_to_src_dir = os.path.join(proj_to_src_root, target.subdir) (sources, headers, objects, languages) = self.split_sources(target.sources) + if self.get_option_for_target('unity', target): + sources = self.generate_unity_files(target, sources) compiler = self._get_cl_compiler(target) buildtype_args = compiler.get_buildtype_args(self.buildtype) buildtype_link_args = compiler.get_buildtype_linker_args(self.buildtype) @@ -600,6 +632,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') @@ -660,7 +694,7 @@ class Vs2010Backend(backends.Backend): elif '/Od' in o_flags: ET.SubElement(type_config, 'Optimization').text = 'Disabled' # Warning level - warning_level = self.environment.coredata.get_builtin_option('warning_level') + warning_level = self.get_option_for_target('warning_level', target) ET.SubElement(type_config, 'WarningLevel').text = 'Level' + warning_level # End configuration ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.props') @@ -725,12 +759,15 @@ class Vs2010Backend(backends.Backend): # and from `include_directories:` of internal deps of the target. # # Target include dirs should override internal deps include dirs. + # This is handled in BuildTarget.process_kwargs() # # Include dirs from internal deps should override include dirs from - # external deps. + # external deps and must maintain the order in which they are + # specified. Hence, we must reverse so that the order is preserved. + # # These are per-target, but we still add them as per-file because we # need them to be looked in first. - for d in target.get_include_dirs(): + for d in reversed(target.get_include_dirs()): for i in d.get_incdirs(): curdir = os.path.join(d.get_curdir(), i) args.append('-I' + self.relpath(curdir, target.subdir)) # build dir @@ -780,7 +817,7 @@ class Vs2010Backend(backends.Backend): # Split compile args needed to find external dependencies # Link args are added while generating the link command - for d in target.get_external_deps(): + for d in reversed(target.get_external_deps()): # Cflags required by external deps might have UNIX-specific flags, # so filter them out if needed d_compile_args = compiler.unix_args_to_native(d.get_compile_args()) @@ -811,7 +848,7 @@ class Vs2010Backend(backends.Backend): ET.SubElement(clconf, 'MinimalRebuild').text = 'true' ET.SubElement(clconf, 'FunctionLevelLinking').text = 'true' pch_node = ET.SubElement(clconf, 'PrecompiledHeader') - if self.environment.coredata.get_builtin_option('werror'): + if self.get_option_for_target('werror', target): ET.SubElement(clconf, 'TreatWarningAsError').text = 'true' # Note: SuppressStartupBanner is /NOLOGO and is 'true' by default pch_sources = {} @@ -889,7 +926,7 @@ class Vs2010Backend(backends.Backend): assert(isinstance(o, str)) additional_objects.append(o) for o in custom_objs: - additional_objects.append(self.relpath(o, self.get_target_dir(target))) + additional_objects.append(o) if len(additional_links) > 0: additional_links.append('%(AdditionalDependencies)') ET.SubElement(link, 'AdditionalDependencies').text = ';'.join(additional_links) @@ -1017,6 +1054,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" @@ -1096,6 +1135,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..35d56f3 --- /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).rstrip('\\') diff --git a/mesonbuild/build.py b/mesonbuild/build.py index c7e8f8e..537c91b 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -12,15 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. +import copy, os, re +from collections import OrderedDict + from . import environment from . import dependencies from . import mlog -import copy, os, re from .mesonlib import File, MesonException from .mesonlib import flatten, stringlistify, classify_unity_sources from .mesonlib import get_filenames_templates_dict, substitute_values -from .environment import for_windows, for_darwin -from .compilers import is_object, clike_langs, lang_suffixes +from .environment import for_windows, for_darwin, for_cygwin +from .compilers import is_object, clike_langs, sort_clike, lang_suffixes known_basic_kwargs = {'install': True, 'c_pch': True, @@ -47,6 +49,7 @@ known_basic_kwargs = {'install': True, 'objects': True, 'native': True, 'build_by_default': True, + 'override_options': True, } # These contain kwargs supported by both static and shared libraries. These are @@ -270,6 +273,7 @@ class Target: self.build_by_default = build_by_default self.install = False self.build_always = False + self.option_overrides = {} def get_basename(self): return self.name @@ -282,6 +286,20 @@ class Target: self.build_by_default = kwargs['build_by_default'] if not isinstance(self.build_by_default, bool): raise InvalidArguments('build_by_default must be a boolean value.') + self.option_overrides = self.parse_overrides(kwargs) + + def parse_overrides(self, kwargs): + result = {} + overrides = stringlistify(kwargs.get('override_options', [])) + for o in overrides: + if '=' not in o: + raise InvalidArguments('Overrides must be of form "key=value"') + k, v = o.split('=', 1) + k = k.strip() + v = v.strip() + result[k] = v + return result + class BuildTarget(Target): def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs): @@ -291,7 +309,7 @@ class BuildTarget(Target): self.is_unity = environment.coredata.get_builtin_option('unity') self.environment = environment self.sources = [] - self.compilers = {} + self.compilers = OrderedDict() self.objects = [] self.external_deps = [] self.include_dirs = [] @@ -391,13 +409,6 @@ class BuildTarget(Target): raise InvalidArguments(msg) @staticmethod - def can_compile_sources(compiler, sources): - for s in sources: - if compiler.can_compile(s): - return True - return False - - @staticmethod def can_compile_remove_sources(compiler, sources): removed = False for s in sources[:]: @@ -442,13 +453,18 @@ class BuildTarget(Target): if not s.endswith(lang_suffixes['vala']): sources.append(s) if sources: - # Add compilers based on the above sources - for lang, compiler in compilers.items(): - # We try to be conservative because sometimes people add files - # in the list of sources that we can't determine the type based - # just on the suffix. - if self.can_compile_sources(compiler, sources): - self.compilers[lang] = compiler + # For each source, try to add one compiler that can compile it. + # It's ok if no compilers can do so, because users are expected to + # be able to add arbitrary non-source files to the sources list. + for s in sources: + for lang, compiler in compilers.items(): + if compiler.can_compile(s): + if lang not in self.compilers: + self.compilers[lang] = compiler + break + # Re-sort according to clike_langs + self.compilers = OrderedDict(sorted(self.compilers.items(), + key=lambda t: sort_clike(t[0]))) else: # No source files, target consists of only object files of unknown # origin. Just add the first clike compiler that we have and hope @@ -479,14 +495,6 @@ class BuildTarget(Target): # CSharp and Java targets can't contain any other file types assert(len(self.compilers) == 1) return - if 'rust' in self.compilers: - firstname = self.sources[0] - if isinstance(firstname, File): - firstname = firstname.fname - first = os.path.split(firstname)[1] - (base, suffix) = os.path.splitext(first) - if suffix != '.rs' or self.name != base: - raise InvalidArguments('In Rust targets, the first source file must be named projectname.rs.') def get_original_kwargs(self): return self.kwargs @@ -598,16 +606,17 @@ class BuildTarget(Target): for i in self.link_depends: if not isinstance(i, str): raise InvalidArguments('Link_depends arguments must be strings.') - deplist = kwargs.get('dependencies', []) - if not isinstance(deplist, list): - deplist = [deplist] - self.add_deps(deplist) - # Target-specific include dirs must be added after include dirs from - # internal deps (added inside self.add_deps()) to override correctly. + # Target-specific include dirs must be added BEFORE include dirs from + # internal deps (added inside self.add_deps()) to override them. inclist = kwargs.get('include_directories', []) if not isinstance(inclist, list): inclist = [inclist] self.add_include_dirs(inclist) + # Add dependencies (which also have include_directories) + deplist = kwargs.get('dependencies', []) + if not isinstance(deplist, list): + deplist = [deplist] + self.add_deps(deplist) self.custom_install_dir = kwargs.get('install_dir', None) if self.custom_install_dir is not None: if not isinstance(self.custom_install_dir, str): @@ -988,7 +997,8 @@ class Executable(BuildTarget): self.prefix = '' if not hasattr(self, 'suffix'): # Executable for Windows or C#/Mono - if for_windows(is_cross, environment) or 'cs' in self.compilers: + if (for_windows(is_cross, environment) or + for_cygwin(is_cross, environment) or 'cs' in self.compilers): self.suffix = 'exe' else: self.suffix = '' @@ -1111,6 +1121,18 @@ class SharedLibrary(BuildTarget): self.filename_tpl = '{0.prefix}{0.name}-{0.soversion}.{0.suffix}' else: self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' + elif for_cygwin(is_cross, env): + suffix = 'dll' + self.gcc_import_filename = 'lib{0}.dll.a'.format(self.name) + # Shared library is of the form cygfoo.dll + # (ld --dll-search-prefix=cyg is the default) + prefix = 'cyg' + # Import library is called libfoo.dll.a + self.import_filename = self.gcc_import_filename + if self.soversion: + self.filename_tpl = '{0.prefix}{0.name}-{0.soversion}.{0.suffix}' + else: + self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' elif for_darwin(is_cross, env): prefix = 'lib' suffix = 'dylib' @@ -1244,6 +1266,7 @@ class CustomTarget(Target): 'depend_files': True, 'depfile': True, 'build_by_default': True, + 'override_options': True, } def __init__(self, name, subdir, kwargs, absolute_paths=False): diff --git a/mesonbuild/compilers.py b/mesonbuild/compilers.py index 519a7d5..e6be8b1 100644 --- a/mesonbuild/compilers.py +++ b/mesonbuild/compilers.py @@ -57,6 +57,17 @@ clike_suffixes += ('h', 'll', 's') # All these are only for C-like languages; see `clike_langs` above. +def sort_clike(lang): + ''' + Sorting function to sort the list of languages according to + reversed(compilers.clike_langs) and append the unknown langs in the end. + The purpose is to prefer C over C++ for files that can be compiled by + both such as assembly, C, etc. Also applies to ObjC, ObjC++, etc. + ''' + if lang not in clike_langs: + return 1 + return -clike_langs.index(lang) + def is_header(fname): if hasattr(fname, 'fname'): fname = fname.fname @@ -369,7 +380,7 @@ class CompilerArgs(list): dedup2_args = () # Arg prefixes and args that must be de-duped by returning 1 dedup1_prefixes = () - dedup1_args = ('-c', '-S', '-E', '-pipe') + dedup1_args = ('-c', '-S', '-E', '-pipe', '-pthread') compiler = None def _check_args(self, args): @@ -413,7 +424,7 @@ class CompilerArgs(list): can safely remove the previous occurance and add a new one. The same is true for include paths and library paths with -I and -L. For these we return `2`. See `dedup2_prefixes` and `dedup2_args`. - b) Arguments that once specifie cannot be undone, such as `-c` or + b) Arguments that once specified cannot be undone, such as `-c` or `-pipe`. New instances of these can be completely skipped. For these we return `1`. See `dedup1_prefixes` and `dedup1_args`. c) Whether it matters where or how many times on the command-line @@ -514,6 +525,11 @@ class Compiler: self.version = version self.base_options = [] + def __repr__(self): + repr_str = "<{0}: v{1} `{2}`>" + return repr_str.format(self.__class__.__name__, self.version, + ' '.join(self.exelist)) + def can_compile(self, src): if hasattr(src, 'fname'): src = src.fname @@ -534,11 +550,11 @@ class Compiler: def get_exelist(self): return self.exelist[:] - def get_define(self, *args, **kwargs): - raise EnvironmentException('%s does not support get_define.' % self.id) + def get_builtin_define(self, *args, **kwargs): + raise EnvironmentException('%s does not support get_builtin_define.' % self.id) - def has_define(self, *args, **kwargs): - raise EnvironmentException('%s does not support has_define.' % self.id) + def has_builtin_define(self, *args, **kwargs): + raise EnvironmentException('%s does not support has_builtin_define.' % self.id) def get_always_args(self): return [] @@ -890,8 +906,6 @@ class CCompiler(Compiler): return self.sanity_check_impl(work_dir, environment, 'sanitycheckc.c', code) def has_header(self, hname, prefix, env, extra_args=None, dependencies=None): - if extra_args is None: - extra_args = [] fargs = {'prefix': prefix, 'header': hname} code = '''{prefix} #ifdef __has_include @@ -905,8 +919,6 @@ class CCompiler(Compiler): dependencies, 'preprocess') def has_header_symbol(self, hname, symbol, prefix, env, extra_args=None, dependencies=None): - if extra_args is None: - extra_args = [] fargs = {'prefix': prefix, 'header': hname, 'symbol': symbol} t = '''{prefix} #include <{header}> @@ -918,7 +930,7 @@ class CCompiler(Compiler): }}''' return self.compiles(t.format(**fargs), env, extra_args, dependencies) - def compiles(self, code, env, extra_args=None, dependencies=None, mode='compile'): + def _get_compiler_check_args(self, env, extra_args, dependencies, mode='compile'): if extra_args is None: extra_args = [] elif isinstance(extra_args, str): @@ -927,49 +939,43 @@ class CCompiler(Compiler): dependencies = [] elif not isinstance(dependencies, list): dependencies = [dependencies] - # Add compile flags needed by dependencies + # Collect compiler arguments args = CompilerArgs(self) for d in dependencies: + # Add compile flags needed by dependencies args += d.get_compile_args() + if mode == 'link': + # Add link flags needed to find dependencies + args += d.get_link_args() + # Select a CRT if needed since we're linking + if mode == 'link': + args += self.get_linker_debug_crt_args() # Read c_args/cpp_args/etc from the cross-info file (if needed) - args += self.get_cross_extra_flags(env, compile=True, link=False) - # Add CFLAGS/CXXFLAGS/OBJCFLAGS/OBJCXXFLAGS from the env - # We assume that the user has ensured these are compiler-specific - args += env.coredata.external_args[self.language] + args += self.get_cross_extra_flags(env, compile=(mode != 'preprocess'), + link=(mode == 'link')) + if mode == 'preprocess': + # Add CPPFLAGS from the env. + args += env.coredata.external_preprocess_args[self.language] + elif mode == 'compile': + # Add CFLAGS/CXXFLAGS/OBJCFLAGS/OBJCXXFLAGS from the env + args += env.coredata.external_args[self.language] + elif mode == 'link': + # Add LDFLAGS from the env + args += env.coredata.external_link_args[self.language] args += self.get_compiler_check_args() # extra_args must override all other arguments, so we add them last args += extra_args + return args + + def compiles(self, code, env, extra_args=None, dependencies=None, mode='compile'): + args = self._get_compiler_check_args(env, extra_args, dependencies, mode) # We only want to compile; not link with self.compile(code, args.to_native(), mode) as p: return p.returncode == 0 def _links_wrapper(self, code, env, extra_args, dependencies): "Shares common code between self.links and self.run" - if extra_args is None: - extra_args = [] - elif isinstance(extra_args, str): - extra_args = [extra_args] - if dependencies is None: - dependencies = [] - elif not isinstance(dependencies, list): - dependencies = [dependencies] - # Add compile and link flags needed by dependencies - args = CompilerArgs(self) - for d in dependencies: - args += d.get_compile_args() - args += d.get_link_args() - # Select a CRT if needed since we're linking - args += self.get_linker_debug_crt_args() - # Read c_args/c_link_args/cpp_args/cpp_link_args/etc from the - # cross-info file (if needed) - args += self.get_cross_extra_flags(env, compile=True, link=True) - # Add LDFLAGS from the env. We assume that the user has ensured these - # are compiler-specific - args += env.coredata.external_link_args[self.language] - # Add compiler check args such that they override - args += self.get_compiler_check_args() - # extra_args must override all other arguments, so we add them last - args += extra_args + args = self._get_compiler_check_args(env, extra_args, dependencies, mode='link') return self.compile(code, args.to_native()) def links(self, code, env, extra_args=None, dependencies=None): @@ -1125,6 +1131,24 @@ class CCompiler(Compiler): raise EnvironmentException('Could not determine alignment of %s. Sorry. You might want to file a bug.' % typename) return align + def get_define(self, dname, prefix, env, extra_args, dependencies): + delim = '"MESON_GET_DEFINE_DELIMITER"' + fargs = {'prefix': prefix, 'define': dname, 'delim': delim} + code = ''' + #ifndef {define} + # define {define} + #endif + {prefix} + {delim}\n{define}''' + args = self._get_compiler_check_args(env, extra_args, dependencies, + mode='preprocess').to_native() + with self.compile(code.format(**fargs), args, 'preprocess') as p: + if p.returncode != 0: + raise EnvironmentException('Could not get define {!r}'.format(dname)) + # Get the preprocessed value after the delimiter, + # minus the extra newline at the end + return p.stdo.split(delim + '\n')[-1][:-1] + @staticmethod def _no_prototype_templ(): """ @@ -1790,7 +1814,7 @@ class SwiftCompiler(Compiler): source_name = os.path.join(work_dir, src) output_name = os.path.join(work_dir, 'swifttest') with open(source_name, 'w') as ofile: - ofile.write('''1 + 2 + ofile.write('''print("Swift compilation is working.") ''') extra_flags = self.get_cross_extra_flags(environment, compile=True, link=True) pc = subprocess.Popen(self.exelist + extra_flags + ['-emit-executable', '-o', output_name, src], cwd=work_dir) @@ -2285,6 +2309,7 @@ class VisualStudioCPPCompiler(VisualStudioCCompiler, CPPCompiler): GCC_STANDARD = 0 GCC_OSX = 1 GCC_MINGW = 2 +GCC_CYGWIN = 3 CLANG_STANDARD = 0 CLANG_OSX = 1 @@ -2300,7 +2325,7 @@ def get_gcc_soname_args(gcc_type, prefix, shlib_name, suffix, path, soversion, i sostr = '' else: sostr = '.' + soversion - if gcc_type == GCC_STANDARD or gcc_type == GCC_MINGW: + if gcc_type in (GCC_STANDARD, GCC_MINGW, GCC_CYGWIN): # Might not be correct for mingw but seems to work. return ['-Wl,-soname,%s%s.%s%s' % (prefix, shlib_name, suffix, sostr)] elif gcc_type == GCC_OSX: @@ -2366,15 +2391,15 @@ class GnuCompiler: args[args.index('-Wpedantic')] = '-pedantic' return args - def has_define(self, define): + def has_builtin_define(self, define): return define in self.defines - def get_define(self, define): + def get_builtin_define(self, define): if define in self.defines: return self.defines[define] def get_pic_args(self): - if self.gcc_type in (GCC_MINGW, GCC_OSX): + if self.gcc_type in (GCC_CYGWIN, GCC_MINGW, GCC_OSX): return [] # On Window and OS X, pic is always on. return ['-fPIC'] @@ -2772,7 +2797,7 @@ class FortranCompiler(Compiler): return ' '.join(self.exelist) def get_pic_args(self): - if self.gcc_type in (GCC_MINGW, GCC_OSX): + if self.gcc_type in (GCC_CYGWIN, GCC_MINGW, GCC_OSX): return [] # On Window and OS X, pic is always on. return ['-fPIC'] @@ -2880,10 +2905,10 @@ class GnuFortranCompiler(FortranCompiler): self.defines = defines or {} self.id = 'gcc' - def has_define(self, define): + def has_builtin_define(self, define): return define in self.defines - def get_define(self, define): + def get_builtin_define(self, define): if define in self.defines: return self.defines[define] @@ -3055,9 +3080,8 @@ class VisualStudioLinker(StaticLinker): return VisualStudioCCompiler.unix_args_to_native(args) def get_link_debugfile_args(self, targetfile): - pdbarr = targetfile.split('.')[:-1] - pdbarr += ['pdb'] - return ['/DEBUG', '/PDB:' + '.'.join(pdbarr)] + # Static libraries do not have PDB files + return [] class ArLinker(StaticLinker): diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index e88cdc8..27f1dd7 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -14,11 +14,13 @@ import pickle, os, uuid from pathlib import PurePath +from collections import OrderedDict from .mesonlib import MesonException, commonpath from .mesonlib import default_libdir, default_libexecdir, default_prefix +import ast 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): @@ -30,6 +32,12 @@ class UserOption: def parse_string(self, valuestring): return valuestring + # Check that the input is a valid value and return the + # "cleaned" or "native" version. For example the Boolean + # option could take the string "true" and return True. + def validate_value(self, value): + 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) @@ -43,6 +51,10 @@ class UserStringOption(UserOption): self.validate(newvalue) self.value = newvalue + def validate_value(self, value): + self.validate(value) + return value + class UserBooleanOption(UserOption): def __init__(self, name, description, value): super().__init__(name, description, [True, False]) @@ -70,6 +82,9 @@ class UserBooleanOption(UserOption): def __bool__(self): return self.value + def validate_value(self, value): + return self.tobool(value) + class UserComboOption(UserOption): def __init__(self, name, description, choices, value): super().__init__(name, description, choices) @@ -86,22 +101,36 @@ class UserComboOption(UserOption): raise MesonException('Value "%s" for combo option "%s" is not one of the choices. Possible choices are: %s.' % (newvalue, self.name, optionsstring)) self.value = newvalue + def validate_value(self, value): + if value not in self.choices: + raise MesonException('Value %s not one of accepted values.' % value) + return value + class UserStringArrayOption(UserOption): def __init__(self, name, description, value, **kwargs): super().__init__(name, description, kwargs.get('choices', [])) self.set_value(value) - def set_value(self, newvalue): - if isinstance(newvalue, str): - if not newvalue.startswith('['): - raise MesonException('Valuestring does not define an array: ' + newvalue) - newvalue = eval(newvalue, {}, {}) # Yes, it is unsafe. + def validate(self, value): + if isinstance(value, str): + if not value.startswith('['): + raise MesonException('Valuestring does not define an array: ' + value) + newvalue = ast.literal_eval(value) + else: + newvalue = value if not isinstance(newvalue, list): raise MesonException('"{0}" should be a string array, but it is not'.format(str(newvalue))) for i in newvalue: if not isinstance(i, str): raise MesonException('String array element "{0}" is not a string.'.format(str(newvalue))) - self.value = newvalue + return newvalue + + def set_value(self, newvalue): + self.value = self.validate(newvalue) + + def validate_value(self, value): + self.validate(value) + return value # This class contains all data that must persist over multiple # invocations of Meson. It is roughly the same thing as @@ -119,17 +148,18 @@ class CoreData: self.user_options = {} self.compiler_options = {} self.base_options = {} - # These two, external_*args, are set via env vars CFLAGS, LDFLAGS, etc + # These external_*args, are set via env vars CFLAGS, LDFLAGS, etc # but only when not cross-compiling. - self.external_args = {} - self.external_link_args = {} + self.external_preprocess_args = {} # CPPFLAGS only + self.external_args = {} # CPPFLAGS + CFLAGS + self.external_link_args = {} # CFLAGS + LDFLAGS (with MSVC: only LDFLAGS) if options.cross_file is not None: self.cross_file = os.path.join(os.getcwd(), options.cross_file) else: self.cross_file = None - - self.compilers = {} - self.cross_compilers = {} + self.wrap_mode = options.wrap_mode + self.compilers = OrderedDict() + self.cross_compilers = OrderedDict() self.deps = {} self.modules = {} # Only to print a warning if it changes between Meson invocations. @@ -203,6 +233,13 @@ class CoreData: raise RuntimeError('Tried to set unknown builtin option %s.' % optname) self.builtins[optname].set_value(value) + def validate_option_value(self, option_name, override_value): + for opts in (self.builtins, self.base_options, self.compiler_options, self.user_options): + if option_name in opts: + opt = opts[option_name] + return opt.validate_value(override_value) + raise MesonException('Tried to validate unknown option %s.' % option_name) + def load(filename): load_fail_msg = 'Coredata file {!r} is corrupted. Try with a fresh build tree.'.format(filename) try: diff --git a/mesonbuild/dependencies.py b/mesonbuild/dependencies.py index e4317f1..7f22ae6 100644 --- a/mesonbuild/dependencies.py +++ b/mesonbuild/dependencies.py @@ -112,6 +112,7 @@ class PkgConfigDependency(Dependency): else: self.want_cross = environment.is_cross_build() self.name = name + self.modversion = 'none' # When finding dependencies for cross-compiling, we don't care about # the 'native' pkg-config @@ -154,7 +155,6 @@ class PkgConfigDependency(Dependency): if self.required: raise DependencyException('{} dependency {!r} not found' ''.format(self.type_string, name)) - self.modversion = 'none' return found_msg = [self.type_string + ' dependency', mlog.bold(name), 'found:'] self.version_reqs = kwargs.get('version', None) @@ -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): @@ -1010,7 +1010,7 @@ class QtBaseDependency(Dependency): corekwargs = {'required': 'false', 'silent': 'true'} core = PkgConfigDependency(self.qtpkgname + 'Core', env, corekwargs) # Used by self.compilers_detect() - self.bindir = core.get_pkgconfig_variable('host_bins') + self.bindir = self.get_pkgconfig_host_bins(core) if not self.bindir: # If exec_prefix is not defined, the pkg-config file is broken prefix = core.get_pkgconfig_variable('exec_prefix') @@ -1119,10 +1119,25 @@ class Qt5Dependency(QtBaseDependency): def __init__(self, env, kwargs): QtBaseDependency.__init__(self, 'qt5', env, kwargs) + def get_pkgconfig_host_bins(self, core): + return core.get_pkgconfig_variable('host_bins') + class Qt4Dependency(QtBaseDependency): def __init__(self, env, kwargs): QtBaseDependency.__init__(self, 'qt4', env, kwargs) + def get_pkgconfig_host_bins(self, core): + # Only return one bins dir, because the tools are generally all in one + # directory for Qt4, in Qt5, they must all be in one directory. Return + # the first one found among the bin variables, in case one tool is not + # configured to be built. + applications = ['moc', 'uic', 'rcc', 'lupdate', 'lrelease'] + for application in applications: + try: + return os.path.dirname(core.get_pkgconfig_variable('%s_location' % application)) + except MesonException: + pass + class GnuStepDependency(Dependency): def __init__(self, environment, kwargs): Dependency.__init__(self, 'gnustep') @@ -1468,6 +1483,14 @@ class Python3Dependency(Dependency): def get_version(self): return self.version +class ValgrindDependency(PkgConfigDependency): + + def __init__(self, environment, kwargs): + PkgConfigDependency.__init__(self, 'valgrind', environment, kwargs) + + def get_link_args(self): + return [] + def get_dep_identifier(name, kwargs): elements = [name] modlist = kwargs.get('modules', []) @@ -1529,4 +1552,5 @@ packages = {'boost': BoostDependency, 'gl': GLDependency, 'threads': ThreadDependency, 'python3': Python3Dependency, + 'valgrind': ValgrindDependency, } diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index 5217626..7861612 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -102,7 +102,7 @@ def detect_windows_arch(compilers): platform = os.environ.get('Platform', 'x86').lower() if platform == 'x86': return platform - if compiler.id == 'gcc' and compiler.has_define('__i386__'): + if compiler.id == 'gcc' and compiler.has_builtin_define('__i386__'): return 'x86' return os_arch @@ -129,10 +129,10 @@ def detect_cpu_family(compilers): # to know is to check the compiler defines. for c in compilers.values(): try: - if c.has_define('__i386__'): + if c.has_builtin_define('__i386__'): return 'x86' except mesonlib.MesonException: - # Ignore compilers that do not support has_define. + # Ignore compilers that do not support has_builtin_define. pass return 'x86_64' # Add fixes here as bugs are reported. @@ -149,7 +149,7 @@ def detect_cpu(compilers): # Same check as above for cpu_family for c in compilers.values(): try: - if c.has_define('__i386__'): + if c.has_builtin_define('__i386__'): return 'i686' # All 64 bit cpus have at least this level of x86 support. except mesonlib.MesonException: pass @@ -158,7 +158,10 @@ def detect_cpu(compilers): return trial def detect_system(): - return platform.system().lower() + system = platform.system().lower() + if system.startswith('cygwin'): + return 'cygwin' + return system def for_windows(is_cross, env): @@ -173,6 +176,20 @@ def for_windows(is_cross, env): return env.cross_info.config['host_machine']['system'] == 'windows' return False + +def for_cygwin(is_cross, env): + """ + Host machine is cygwin? + + Note: 'host' is the machine on which compiled binaries will run + """ + if not is_cross: + return mesonlib.is_cygwin() + elif env.cross_info.has_host(): + return env.cross_info.config['host_machine']['system'] == 'cygwin' + return False + + def for_darwin(is_cross, env): """ Host machine is Darwin (iOS/OS X)? @@ -257,6 +274,11 @@ class Environment: self.exe_suffix = 'exe' self.object_suffix = 'obj' self.win_libdir_layout = True + elif (not cross and mesonlib.is_cygwin()) \ + or (cross and self.cross_info.has_host() and self.cross_info.config['host_machine']['system'] == 'cygwin'): + self.exe_suffix = 'exe' + self.object_suffix = 'o' + self.win_libdir_layout = True else: self.exe_suffix = '' self.object_suffix = 'o' @@ -368,7 +390,8 @@ class Environment: return GCC_OSX elif '__MINGW32__' in defines or '__MINGW64__' in defines: return GCC_MINGW - # We ignore Cygwin for now, and treat it as a standard GCC + elif '__CYGWIN__' in defines: + return GCC_CYGWIN return GCC_STANDARD def _get_compilers(self, lang, evar, want_cross): @@ -377,16 +400,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: @@ -756,7 +793,7 @@ def get_args_from_envvars(compiler): compiler_is_linker = (compiler.get_exelist() == compiler.get_linker_exelist()) if lang not in ('c', 'cpp', 'objc', 'objcpp', 'fortran', 'd'): - return [], [] + return [], [], [] # Compile flags cflags_mapping = {'c': 'CFLAGS', @@ -767,12 +804,12 @@ def get_args_from_envvars(compiler): 'd': 'DFLAGS'} compile_flags = os.environ.get(cflags_mapping[lang], '') log_var(cflags_mapping[lang], compile_flags) - compile_flags = compile_flags.split() + compile_flags = shlex.split(compile_flags) # Link flags (same for all languages) link_flags = os.environ.get('LDFLAGS', '') log_var('LDFLAGS', link_flags) - link_flags = link_flags.split() + link_flags = shlex.split(link_flags) if compiler_is_linker: # When the compiler is used as a wrapper around the linker (such as # with GCC and Clang), the compile flags can be needed while linking @@ -780,14 +817,15 @@ def get_args_from_envvars(compiler): # this when the linker is stand-alone such as with MSVC C/C++, etc. link_flags = compile_flags + link_flags - # Pre-processof rlags (not for fortran) + # Pre-processor flags (not for fortran or D) preproc_flags = '' if lang in ('c', 'cpp', 'objc', 'objcpp'): preproc_flags = os.environ.get('CPPFLAGS', '') log_var('CPPFLAGS', preproc_flags) - compile_flags += preproc_flags.split() + preproc_flags = shlex.split(preproc_flags) + compile_flags += preproc_flags - return compile_flags, link_flags + return preproc_flags, compile_flags, link_flags class CrossBuildInfo: def __init__(self, filename): diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index d6f76e9..8c8000c 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, get_meson_script from .dependencies import InternalDependency, Dependency @@ -635,6 +635,7 @@ class CompilerHolder(InterpreterObject): 'get_id': self.get_id_method, 'compute_int': self.compute_int_method, 'sizeof': self.sizeof_method, + 'get_define': self.get_define_method, 'has_header': self.has_header_method, 'has_header_symbol': self.has_header_symbol_method, 'run': self.run_method, @@ -866,6 +867,20 @@ class CompilerHolder(InterpreterObject): mlog.log('Checking for size of "%s": %d' % (element, esize)) return esize + def get_define_method(self, args, kwargs): + if len(args) != 1: + raise InterpreterException('get_define() takes exactly one argument.') + check_stringlist(args) + element = args[0] + prefix = kwargs.get('prefix', '') + if not isinstance(prefix, str): + raise InterpreterException('Prefix argument of get_define() must be a string.') + extra_args = self.determine_args(kwargs) + deps = self.determine_dependencies(kwargs) + value = self.compiler.get_define(element, prefix, self.environment, extra_args, deps) + mlog.log('Checking for value of define "%s": %s' % (element, value)) + return value + def compiles_method(self, args, kwargs): if len(args) != 1: raise InterpreterException('compiles method takes exactly one argument.') @@ -1225,7 +1240,8 @@ class Interpreter(InterpreterBase): self.builtin.update({'meson': MesonMain(build, self)}) self.generators = [] self.visited_subdirs = {} - self.args_frozen = False + self.project_args_frozen = False + self.global_args_frozen = False # implies self.project_args_frozen self.subprojects = {} self.subproject_stack = [] self.default_project_options = default_project_options[:] # Passed from the outside, only used in subprojects. @@ -1408,7 +1424,7 @@ class Interpreter(InterpreterBase): if not isinstance(sources, list): sources = [sources] sources = self.source_strings_to_files(self.flatten(sources)) - deps = kwargs.get('dependencies', []) + deps = self.flatten(kwargs.get('dependencies', [])) if not isinstance(deps, list): deps = [deps] compile_args = mesonlib.stringlistify(kwargs.get('compile_args', [])) @@ -1499,14 +1515,16 @@ 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 + self.global_args_frozen = True mlog.log('\nExecuting subproject ', mlog.bold(dirname), '.\n', sep='') subi = Interpreter(self.build, self.backend, dirname, subdir, self.subproject_dir, mesonlib.stringlistify(kwargs.get('default_options', []))) @@ -1612,27 +1630,31 @@ class Interpreter(InterpreterBase): @stringArgs def func_project(self, node, args, kwargs): + if len(args) < 1: + raise InvalidArguments('Not enough arguments to project(). Needs at least the project name.') + proj_name = args[0] + proj_langs = args[1:] + if ':' in proj_name: + raise InvalidArguments("Project name {!r} must not contain ':'".format(proj_name)) default_options = kwargs.get('default_options', []) if self.environment.first_invocation and (len(default_options) > 0 or len(self.default_project_options) > 0): self.parse_default_options(default_options) if not self.is_subproject(): - self.build.project_name = args[0] + self.build.project_name = proj_name if os.path.exists(self.option_file): oi = optinterpreter.OptionInterpreter(self.subproject, self.build.environment.cmd_line_options.projectoptions, ) oi.process(self.option_file) self.build.environment.merge_options(oi.options) - if len(args) < 2: - raise InvalidArguments('Not enough arguments to project(). Needs at least the project name and one language') - self.active_projectname = args[0] + self.active_projectname = proj_name self.project_version = kwargs.get('version', 'undefined') if self.build.project_version is None: self.build.project_version = self.project_version proj_license = mesonlib.stringlistify(kwargs.get('license', 'unknown')) - self.build.dep_manifest[args[0]] = {'version': self.project_version, - 'license': proj_license} + self.build.dep_manifest[proj_name] = {'version': self.project_version, + 'license': proj_license} if self.subproject in self.build.projects: raise InvalidCode('Second call to project().') if not self.is_subproject() and 'subproject_dir' in kwargs: @@ -1643,9 +1665,9 @@ class Interpreter(InterpreterBase): pv = kwargs['meson_version'] if not mesonlib.version_compare(cv, pv): raise InterpreterException('Meson version is %s but project requires %s.' % (cv, pv)) - self.build.projects[self.subproject] = args[0] - mlog.log('Project name: ', mlog.bold(args[0]), sep='') - self.add_languages(args[1:], True) + self.build.projects[self.subproject] = proj_name + mlog.log('Project name: ', mlog.bold(proj_name), sep='') + self.add_languages(proj_langs, True) langs = self.coredata.compilers.keys() if 'vala' in langs: if 'c' not in langs: @@ -1754,7 +1776,7 @@ class Interpreter(InterpreterBase): def add_languages(self, args, required): success = True need_cross_compiler = self.environment.is_cross_build() and self.environment.cross_info.need_cross_compiler() - for lang in args: + for lang in sorted(args, key=compilers.sort_clike): lang = lang.lower() if lang in self.coredata.compilers: comp = self.coredata.compilers[lang] @@ -1771,9 +1793,10 @@ class Interpreter(InterpreterBase): raise mlog.log('Native %s compiler: ' % lang, mlog.bold(' '.join(comp.get_exelist())), ' (%s %s)' % (comp.id, comp.version), sep='') if not comp.get_language() in self.coredata.external_args: - (ext_compile_args, ext_link_args) = environment.get_args_from_envvars(comp) - self.coredata.external_args[comp.get_language()] = ext_compile_args - self.coredata.external_link_args[comp.get_language()] = ext_link_args + (preproc_args, compile_args, link_args) = environment.get_args_from_envvars(comp) + self.coredata.external_preprocess_args[comp.get_language()] = preproc_args + self.coredata.external_args[comp.get_language()] = compile_args + self.coredata.external_link_args[comp.get_language()] = link_args self.build.add_compiler(comp) if need_cross_compiler: mlog.log('Cross %s compiler: ' % lang, mlog.bold(' '.join(cross_comp.get_exelist())), ' (%s %s)' % (cross_comp.id, cross_comp.version), sep='') @@ -1834,8 +1857,8 @@ class Interpreter(InterpreterBase): self.validate_arguments(args, 1, [str]) name = args[0] if '<' in name or '>' in name or '=' in name: - raise InvalidArguments('''Characters <, > and = are forbidden in target names. To specify version -requirements use the version keyword argument instead.''') + raise InvalidArguments('Characters <, > and = are forbidden in dependency names. To specify' + 'version\n requirements use the \'version\' keyword argument instead.') identifier = dependencies.get_dep_identifier(name, kwargs) # Check if we want this as a cross-dep or a native-dep # FIXME: Not all dependencies support such a distinction right now, @@ -1910,6 +1933,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: @@ -1932,8 +1960,14 @@ requirements use the version keyword argument instead.''') try: dep = self.subprojects[dirname].get_variable_method([varname], {}) except KeyError: - raise InvalidCode('Fallback variable {!r} in the subproject ' - '{!r} does not exist'.format(varname, dirname)) + if kwargs.get('required', True): + m = 'Fallback variable {!r} in the subproject {!r} does not exist' + raise DependencyException(m.format(varname, dirname)) + # If the dependency is not required, don't raise an exception + mlog.log('Also couldn\'t find the dependency', mlog.bold(name), + 'in the fallback subproject', + mlog.bold(os.path.join(self.subproject_dir, dirname))) + return None if not isinstance(dep, DependencyHolder): raise InvalidCode('Fallback variable {!r} in the subproject {!r} is ' 'not a dependency object.'.format(varname, dirname)) @@ -2268,6 +2302,7 @@ requirements use the version keyword argument instead.''') 'they are mutually exclusive.') # Validate input inputfile = None + ifile_abs = None if 'input' in kwargs: inputfile = kwargs['input'] if isinstance(inputfile, list): @@ -2277,9 +2312,13 @@ requirements use the version keyword argument instead.''') inputfile = inputfile[0] if not isinstance(inputfile, (str, mesonlib.File)): raise InterpreterException('Input must be a string or a file') - ifile_abs = os.path.join(self.environment.source_dir, self.subdir, inputfile) - elif 'command' in kwargs: - raise InterpreterException('Required keyword argument \'input\' missing') + if isinstance(inputfile, str): + inputfile = os.path.join(self.subdir, inputfile) + else: + inputfile = inputfile.relative_name() + ifile_abs = os.path.join(self.environment.source_dir, inputfile) + elif 'command' in kwargs and '@INPUT@' in kwargs['command']: + raise InterpreterException('@INPUT@ used as command argument, but no input file specified.') # Validate output output = kwargs['output'] if not isinstance(output, str): @@ -2296,7 +2335,7 @@ requirements use the version keyword argument instead.''') if inputfile is not None: # Normalize the path of the conffile to avoid duplicates # This is especially important to convert '/' to '\' on Windows - conffile = os.path.normpath(os.path.join(self.subdir, inputfile)) + conffile = os.path.normpath(inputfile) if conffile not in self.build_def_files: self.build_def_files.append(conffile) os.makedirs(os.path.join(self.environment.build_dir, self.subdir), exist_ok=True) @@ -2308,7 +2347,10 @@ requirements use the version keyword argument instead.''') # We use absolute paths for input and output here because the cwd # that the command is run from is 'unspecified', so it could change. # Currently it's builddir/subdir for in_builddir else srcdir/subdir. - values = mesonlib.get_filenames_templates_dict([ifile_abs], [ofile_abs]) + if ifile_abs: + values = mesonlib.get_filenames_templates_dict([ifile_abs], [ofile_abs]) + else: + values = mesonlib.get_filenames_templates_dict(None, [ofile_abs]) # Substitute @INPUT@, @OUTPUT@, etc here. cmd = mesonlib.substitute_values(kwargs['command'], values) mlog.log('Configuring', mlog.bold(output), 'with command') @@ -2399,83 +2441,51 @@ different subdirectory. @stringArgs def func_add_global_arguments(self, node, args, kwargs): - if self.subproject != '': - msg = 'Global arguments can not be set in subprojects because ' \ - 'there is no way to make that reliable.\nPlease only call ' \ - 'this if is_subproject() returns false. Alternatively, ' \ - 'define a variable that\ncontains your language-specific ' \ - 'arguments and add it to the appropriate *_args kwarg ' \ - 'in each target.' - raise InvalidCode(msg) - if self.args_frozen: - msg = 'Tried to set global arguments after a build target has ' \ - 'been declared.\nThis is not permitted. Please declare all ' \ - 'global arguments before your targets.' - raise InvalidCode(msg) - if 'language' not in kwargs: - raise InvalidCode('Missing language definition in add_global_arguments') - lang = kwargs['language'].lower() - if lang in self.build.global_args: - self.build.global_args[lang] += args - else: - self.build.global_args[lang] = args + self.add_global_arguments(node, self.build.global_args, args, kwargs) @stringArgs def func_add_global_link_arguments(self, node, args, kwargs): + self.add_global_arguments(node, self.build.global_link_args, args, kwargs) + + @stringArgs + def func_add_project_arguments(self, node, args, kwargs): + self.add_project_arguments(node, self.build.projects_args, args, kwargs) + + @stringArgs + def func_add_project_link_arguments(self, node, args, kwargs): + self.add_project_arguments(node, self.build.projects_link_args, args, kwargs) + + def add_global_arguments(self, node, argsdict, args, kwargs): if self.subproject != '': - msg = 'Global link arguments can not be set in subprojects because ' \ + msg = 'Function \'{}\' cannot be used in subprojects because ' \ 'there is no way to make that reliable.\nPlease only call ' \ 'this if is_subproject() returns false. Alternatively, ' \ 'define a variable that\ncontains your language-specific ' \ 'arguments and add it to the appropriate *_args kwarg ' \ - 'in each target.' + 'in each target.'.format(node.func_name) raise InvalidCode(msg) - if self.args_frozen: - msg = 'Tried to set global link arguments after a build target has ' \ - 'been declared.\nThis is not permitted. Please declare all ' \ - 'global arguments before your targets.' + frozen = self.project_args_frozen or self.global_args_frozen + self.add_arguments(node, argsdict, frozen, args, kwargs) + + def add_project_arguments(self, node, argsdict, args, kwargs): + if self.subproject not in argsdict: + argsdict[self.subproject] = {} + self.add_arguments(node, argsdict[self.subproject], + self.project_args_frozen, args, kwargs) + + def add_arguments(self, node, argsdict, args_frozen, args, kwargs): + if args_frozen: + msg = 'Tried to use \'{}\' after a build target has been declared.\n' \ + 'This is not permitted. Please declare all ' \ + 'arguments before your targets.'.format(node.func_name) raise InvalidCode(msg) - if 'language' not in kwargs: - raise InvalidCode('Missing language definition in add_global_link_arguments') - lang = kwargs['language'].lower() - if lang in self.build.global_link_args: - self.build.global_link_args[lang] += args - else: - self.build.global_link_args[lang] = args - @stringArgs - def func_add_project_link_arguments(self, node, args, kwargs): - if self.args_frozen: - msg = 'Tried to set project link arguments after a build target has ' \ - 'been declared.\nThis is not permitted. Please declare all ' \ - 'project link arguments before your targets.' - raise InvalidCode(msg) if 'language' not in kwargs: - raise InvalidCode('Missing language definition in add_project_link_arguments') - lang = kwargs['language'].lower() - if self.subproject not in self.build.projects_link_args: - self.build.projects_link_args[self.subproject] = {} - - args = self.build.projects_link_args[self.subproject].get(lang, []) + args - self.build.projects_link_args[self.subproject][lang] = args + raise InvalidCode('Missing language definition in {}'.format(node.func_name)) - @stringArgs - def func_add_project_arguments(self, node, args, kwargs): - if self.args_frozen: - msg = 'Tried to set project arguments after a build target has ' \ - 'been declared.\nThis is not permitted. Please declare all ' \ - 'project arguments before your targets.' - raise InvalidCode(msg) - - if 'language' not in kwargs: - raise InvalidCode('Missing language definition in add_project_arguments') - - if self.subproject not in self.build.projects_args: - self.build.projects_args[self.subproject] = {} - - lang = kwargs['language'].lower() - args = self.build.projects_args[self.subproject].get(lang, []) + args - self.build.projects_args[self.subproject][lang] = args + for lang in mesonlib.stringlistify(kwargs['language']): + lang = lang.lower() + argsdict[lang] = argsdict.get(lang, []) + args def func_environment(self, node, args, kwargs): return EnvironmentVariablesHolder() @@ -2560,7 +2570,7 @@ different subdirectory. self.add_cross_stdlib_info(target) l = targetholder(target, self) self.add_target(name, l.held_object) - self.args_frozen = True + self.project_args_frozen = True return l def get_used_languages(self, target): diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index 345a184..a2ab484 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -121,16 +121,18 @@ class File: self.is_built = is_built self.subdir = subdir self.fname = fname + assert(isinstance(self.subdir, str)) + assert(isinstance(self.fname, str)) def __str__(self): - return os.path.join(self.subdir, self.fname) + return self.relative_name() def __repr__(self): ret = '<File: {0}' if not self.is_built: ret += ' (not built)' ret += '>' - return ret.format(os.path.join(self.subdir, self.fname)) + return ret.format(self.relative_name()) @staticmethod def from_source_file(source_root, subdir, fname): @@ -148,15 +150,15 @@ class File: def rel_to_builddir(self, build_to_src): if self.is_built: - return os.path.join(self.subdir, self.fname) + return self.relative_name() else: return os.path.join(build_to_src, self.subdir, self.fname) def absolute_path(self, srcdir, builddir): + absdir = srcdir if self.is_built: - return os.path.join(builddir, self.subdir, self.fname) - else: - return os.path.join(srcdir, self.subdir, self.fname) + absdir = builddir + return os.path.join(absdir, self.relative_name()) def endswith(self, ending): return self.fname.endswith(ending) @@ -219,6 +221,10 @@ def is_windows(): platname = platform.system().lower() return platname == 'windows' or 'mingw' in platname +def is_cygwin(): + platname = platform.system().lower() + return platname.startswith('cygwin') + def is_debianlike(): return os.path.isfile('/etc/debian_version') 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/modules/__init__.py b/mesonbuild/modules/__init__.py index c7f24d4..fde3b91 100644 --- a/mesonbuild/modules/__init__.py +++ b/mesonbuild/modules/__init__.py @@ -24,7 +24,11 @@ def find_program(program_name, target_name): return program -def get_include_args(environment, include_dirs, prefix='-I'): +def get_include_args(include_dirs, prefix='-I'): + ''' + Expand include arguments to refer to the source and build dirs + by using @SOURCE_ROOT@ and @BUILD_ROOT@ for later substitution + ''' if not include_dirs: return [] @@ -43,8 +47,8 @@ def get_include_args(environment, include_dirs, prefix='-I'): basedir = dirs.get_curdir() for d in dirs.get_incdirs(): expdir = os.path.join(basedir, d) - srctreedir = os.path.join(environment.get_source_dir(), expdir) - buildtreedir = os.path.join(environment.get_build_dir(), expdir) + srctreedir = os.path.join('@SOURCE_ROOT@', expdir) + buildtreedir = os.path.join('@BUILD_ROOT@', expdir) dirs_str += ['%s%s' % (prefix, buildtreedir), '%s%s' % (prefix, srctreedir)] for d in dirs.get_extra_build_dirs(): diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index 423031d..4b366bf 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -309,7 +309,7 @@ class GnomeModule(ExtensionModule): if hasattr(dep, 'held_object'): dep = dep.held_object if isinstance(dep, InternalDependency): - cflags.update(get_include_args(state.environment, dep.include_directories)) + cflags.update(get_include_args(dep.include_directories)) for lib in dep.libraries: ldflags.update(self._get_link_args(state, lib.held_object, depends, include_rpath)) libdepflags = self._get_dependencies_flags(lib.held_object.get_external_deps(), state, depends, include_rpath, @@ -398,7 +398,7 @@ class GnomeModule(ExtensionModule): scan_command += extra_args scan_command += ['-I' + os.path.join(state.environment.get_source_dir(), state.subdir), '-I' + os.path.join(state.environment.get_build_dir(), state.subdir)] - scan_command += get_include_args(state.environment, girtarget.get_include_dirs()) + scan_command += get_include_args(girtarget.get_include_dirs()) if 'link_with' in kwargs: link_with = kwargs.pop('link_with') @@ -525,9 +525,8 @@ class GnomeModule(ExtensionModule): if not isinstance(incd.held_object, (str, build.IncludeDirs)): raise MesonException( 'Gir include dirs should be include_directories().') - scan_command += get_include_args(state.environment, inc_dirs) - scan_command += get_include_args(state.environment, gir_inc_dirs + inc_dirs, - prefix='--add-include-path=') + scan_command += get_include_args(inc_dirs) + scan_command += get_include_args(gir_inc_dirs + inc_dirs, prefix='--add-include-path=') if isinstance(girtarget, build.Executable): scan_command += ['--program', girtarget] @@ -546,8 +545,7 @@ class GnomeModule(ExtensionModule): typelib_output = '%s-%s.typelib' % (ns, nsversion) typelib_cmd = [gicompiler, scan_target, '--output', '@OUTPUT@'] - typelib_cmd += get_include_args(state.environment, gir_inc_dirs, - prefix='--includedir=') + typelib_cmd += get_include_args(gir_inc_dirs, prefix='--includedir=') for incdir in typelib_includes: typelib_cmd += ["--includedir=" + incdir] @@ -716,7 +714,7 @@ class GnomeModule(ExtensionModule): if not isinstance(incd.held_object, (str, build.IncludeDirs)): raise MesonException( 'Gir include dirs should be include_directories().') - cflags.update(get_include_args(state.environment, inc_dirs)) + cflags.update(get_include_args(inc_dirs)) if cflags: args += ['--cflags=%s' % ' '.join(cflags)] if ldflags: diff --git a/mesonbuild/modules/python3.py b/mesonbuild/modules/python3.py index 53e28c4..9f01043 100644 --- a/mesonbuild/modules/python3.py +++ b/mesonbuild/modules/python3.py @@ -13,11 +13,13 @@ # limitations under the License. import sys +import sysconfig from .. import mesonlib, dependencies from . import ExtensionModule from mesonbuild.modules import ModuleReturnValue + class Python3Module(ExtensionModule): def __init__(self): super().__init__() @@ -45,5 +47,25 @@ class Python3Module(ExtensionModule): py3 = dependencies.ExternalProgram('python3', sys.executable, silent=True) return ModuleReturnValue(py3, [py3]) + def language_version(self, state, args, kwargs): + if args or kwargs: + raise mesonlib.MesonException('language_version() takes no arguments.') + return ModuleReturnValue(sysconfig.get_python_version(), []) + + def sysconfig_path(self, state, args, kwargs): + if len(args) != 1: + raise mesonlib.MesonException('sysconfig_path() requires passing the name of path to get.') + if kwargs: + raise mesonlib.MesonException('sysconfig_path() does not accept keywords.') + path_name = args[0] + valid_names = sysconfig.get_path_names() + if path_name not in valid_names: + raise mesonlib.MesonException('{} is not a valid path name {}.'.format(path_name, valid_names)) + + # Get a relative path without a prefix, e.g. lib/python3.6/site-packages + path = sysconfig.get_path(path_name, vars={'base': ''})[1:] + return ModuleReturnValue(path, []) + + def initialize(): return Python3Module() diff --git a/mesonbuild/modules/windows.py b/mesonbuild/modules/windows.py index 8203789..3fb0107 100644 --- a/mesonbuild/modules/windows.py +++ b/mesonbuild/modules/windows.py @@ -14,6 +14,7 @@ import os +from .. import mlog from .. import mesonlib, dependencies, build from ..mesonlib import MesonException from . import get_include_args @@ -38,16 +39,29 @@ class WindowsModule(ExtensionModule): for incd in inc_dirs: if not isinstance(incd.held_object, (str, build.IncludeDirs)): raise MesonException('Resource include dirs should be include_directories().') - extra_args += get_include_args(state.environment, inc_dirs) + extra_args += get_include_args(inc_dirs) if comp.id == 'msvc': rescomp = dependencies.ExternalProgram('rc', silent=True) res_args = extra_args + ['/nologo', '/fo@OUTPUT@', '@INPUT@'] suffix = 'res' else: - # Pick-up env var WINDRES if set. This is often used for specifying - # an arch-specific windres. - rescomp_name = os.environ.get('WINDRES', 'windres') + m = 'Argument {!r} has a space which may not work with windres due to ' \ + 'a MinGW bug: https://sourceware.org/bugzilla/show_bug.cgi?id=4933' + for arg in extra_args: + if ' ' in arg: + mlog.warning(m.format(arg)) + rescomp_name = None + # FIXME: Does not handle `native: true` executables, see + # https://github.com/mesonbuild/meson/issues/1531 + if state.environment.is_cross_build(): + # If cross compiling see if windres has been specified in the + # cross file before trying to find it another way. + rescomp_name = state.environment.cross_info.config['binaries'].get('windres') + if rescomp_name is None: + # Pick-up env var WINDRES if set. This is often used for + # specifying an arch-specific windres. + rescomp_name = os.environ.get('WINDRES', 'windres') rescomp = dependencies.ExternalProgram(rescomp_name, silent=True) res_args = extra_args + ['@INPUT@', '@OUTPUT@'] suffix = 'o' diff --git a/mesonbuild/mparser.py b/mesonbuild/mparser.py index fe5ccc5..a9f25b1 100644 --- a/mesonbuild/mparser.py +++ b/mesonbuild/mparser.py @@ -16,15 +16,41 @@ import re from .mesonlib import MesonException class ParseException(MesonException): - def __init__(self, text, lineno, colno): - super().__init__(text) + def __init__(self, text, line, lineno, colno): + # Format as error message, followed by the line with the error, followed by a caret to show the error column. + super().__init__("%s\n%s\n%s" % (text, line, '%s^' % (' ' * colno))) + self.lineno = lineno + self.colno = colno + +class BlockParseException(MesonException): + def __init__(self, text, line, lineno, colno, start_line, start_lineno, start_colno): + # This can be formatted in two ways - one if the block start and end are on the same line, and a different way if they are on different lines. + + if lineno == start_lineno: + # If block start and end are on the same line, it is formatted as: + # Error message + # Followed by the line with the error + # Followed by a caret to show the block start + # Followed by underscores + # Followed by a caret to show the block end. + super().__init__("%s\n%s\n%s" % (text, line, '%s^%s^' % (' ' * start_colno, '_' * (colno - start_colno - 1)))) + else: + # If block start and end are on different lines, it is formatted as: + # Error message + # Followed by the line with the error + # Followed by a caret to show the error column. + # Followed by a message saying where the block started. + # Followed by the line of the block start. + # Followed by a caret for the block start. + super().__init__("%s\n%s\n%s\nFor a block that started at %d,%d\n%s\n%s" % (text, line, '%s^' % (' ' * colno), start_lineno, start_colno, start_line, "%s^" % (' ' * start_colno))) self.lineno = lineno self.colno = colno class Token: - def __init__(self, tid, subdir, lineno, colno, bytespan, value): + def __init__(self, tid, subdir, line_start, lineno, colno, bytespan, value): self.tid = tid self.subdir = subdir + self.line_start = line_start self.lineno = lineno self.colno = colno self.bytespan = bytespan @@ -36,7 +62,8 @@ class Token: return self.tid == other.tid class Lexer: - def __init__(self): + def __init__(self, code): + self.code = code self.keywords = {'true', 'false', 'if', 'else', 'elif', 'endif', 'and', 'or', 'not', 'foreach', 'endforeach'} self.token_specification = [ @@ -73,20 +100,24 @@ class Lexer: ('questionmark', re.compile(r'\?')), ] - def lex(self, code, subdir): - lineno = 1 + def getline(self, line_start): + return self.code[line_start:self.code.find('\n', line_start)] + + def lex(self, subdir): line_start = 0 + lineno = 1 loc = 0 par_count = 0 bracket_count = 0 col = 0 - while loc < len(code): + while loc < len(self.code): matched = False value = None for (tid, reg) in self.token_specification: - mo = reg.match(code, loc) + mo = reg.match(self.code, loc) if mo: curline = lineno + curline_start = line_start col = mo.start() - line_start matched = True span_start = loc @@ -105,7 +136,7 @@ class Lexer: elif tid == 'rbracket': bracket_count -= 1 elif tid == 'dblquote': - raise ParseException('Double quotes are not supported. Use single quotes.', lineno, col) + raise ParseException('Double quotes are not supported. Use single quotes.', self.getline(line_start), lineno, col) elif tid == 'string': value = match_text[1:-1]\ .replace(r"\'", "'")\ @@ -130,10 +161,10 @@ class Lexer: tid = match_text else: value = match_text - yield Token(tid, subdir, curline, col, bytespan, value) + yield Token(tid, subdir, curline_start, curline, col, bytespan, value) break if not matched: - raise ParseException('lexer', lineno, col) + raise ParseException('lexer', self.getline(line_start), lineno, col) class ElementaryNode: def __init__(self, token): @@ -178,10 +209,10 @@ class ArrayNode: self.args = args class EmptyNode: - def __init__(self): + def __init__(self, lineno, colno): self.subdir = '' - self.lineno = 0 - self.colno = 0 + self.lineno = lineno + self.colno = colno self.value = None class OrNode: @@ -288,7 +319,7 @@ class IfClauseNode: self.lineno = lineno self.colno = colno self.ifs = [] - self.elseblock = EmptyNode() + self.elseblock = EmptyNode(lineno, colno) class UMinusNode: def __init__(self, current_location, value): @@ -374,7 +405,9 @@ comparison_map = {'equal': '==', class Parser: def __init__(self, code, subdir): - self.stream = Lexer().lex(code, subdir) + self.lexer = Lexer(code) + self.stream = self.lexer.lex(subdir) + self.current = Token('eof', '', 0, 0, 0, (0, 0), None) self.getsym() self.in_ternary = False @@ -382,7 +415,7 @@ class Parser: try: self.current = next(self.stream) except StopIteration: - self.current = Token('eof', '', 0, 0, (0, 0), None) + self.current = Token('eof', '', self.current.line_start, self.current.lineno, self.current.colno + self.current.bytespan[1] - self.current.bytespan[0], (0, 0), None) def accept(self, s): if self.current.tid == s: @@ -393,7 +426,12 @@ class Parser: def expect(self, s): if self.accept(s): return True - raise ParseException('Expecting %s got %s.' % (s, self.current.tid), self.current.lineno, self.current.colno) + raise ParseException('Expecting %s got %s.' % (s, self.current.tid), self.lexer.getline(self.current.line_start), self.current.lineno, self.current.colno) + + def block_expect(self, s, block_start): + if self.accept(s): + return True + raise BlockParseException('Expecting %s got %s.' % (s, self.current.tid), self.lexer.getline(self.current.line_start), self.current.lineno, self.current.colno, self.lexer.getline(block_start.line_start), block_start.lineno, block_start.colno) def parse(self): block = self.codeblock() @@ -489,9 +527,10 @@ class Parser: def e7(self): left = self.e8() + block_start = self.current if self.accept('lparen'): args = self.args() - self.expect('rparen') + self.block_expect('rparen', block_start) if not isinstance(left, IdNode): raise ParseException('Function call must be applied to plain id', left.lineno, left.colno) @@ -508,13 +547,14 @@ class Parser: return left def e8(self): + block_start = self.current if self.accept('lparen'): e = self.statement() - self.expect('rparen') + self.block_expect('rparen', block_start) return e elif self.accept('lbracket'): args = self.args() - self.expect('rbracket') + self.block_expect('rbracket', block_start) return ArrayNode(args) else: return self.e9() @@ -531,7 +571,7 @@ class Parser: return NumberNode(t) if self.accept('string'): return StringNode(t) - return EmptyNode() + return EmptyNode(self.current.lineno, self.current.colno) def args(self): s = self.statement() @@ -606,15 +646,16 @@ class Parser: return self.codeblock() def line(self): + block_start = self.current if self.current == 'eol': - return EmptyNode() + return EmptyNode(self.current.lineno, self.current.colno) if self.accept('if'): block = self.ifblock() - self.expect('endif') + self.block_expect('endif', block_start) return block if self.accept('foreach'): block = self.foreachblock() - self.expect('endforeach') + self.block_expect('endforeach', block_start) return block return self.statement() diff --git a/mesonbuild/optinterpreter.py b/mesonbuild/optinterpreter.py index 10b8fab..f9e7f26 100644 --- a/mesonbuild/optinterpreter.py +++ b/mesonbuild/optinterpreter.py @@ -75,15 +75,16 @@ class OptionInterpreter: self.cmd_line_options = {} for o in command_line_options: if self.subproject != '': # Strip the beginning. + # Ignore options that aren't for this subproject if not o.startswith(self.sbprefix): continue - else: - if ':' in o: - continue try: (key, value) = o.split('=', 1) except ValueError: raise OptionException('Option {!r} must have a value separated by equals sign.'.format(o)) + # Ignore subproject options if not fetching subproject options + if self.subproject == '' and ':' in key: + continue self.cmd_line_options[key] = value def process(self, option_file): diff --git a/mesonbuild/scripts/meson_exe.py b/mesonbuild/scripts/meson_exe.py index 5c5c317..643e1af 100644 --- a/mesonbuild/scripts/meson_exe.py +++ b/mesonbuild/scripts/meson_exe.py @@ -29,8 +29,12 @@ def is_windows(): platname = platform.system().lower() return platname == 'windows' or 'mingw' in platname +def is_cygwin(): + platname = platform.system().lower() + return 'cygwin' in platname + def run_with_mono(fname): - if fname.endswith('.exe') and not is_windows(): + if fname.endswith('.exe') and not (is_windows() or is_cygwin()): return True return False diff --git a/mesonbuild/scripts/meson_install.py b/mesonbuild/scripts/meson_install.py index 2544fbc..2205f1a 100644 --- a/mesonbuild/scripts/meson_install.py +++ b/mesonbuild/scripts/meson_install.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import sys, pickle, os, shutil, subprocess, gzip, platform +import sys, pickle, os, shutil, subprocess, gzip, platform, errno from glob import glob from . import depfixer from . import destdir_join @@ -34,6 +34,15 @@ def set_mode(path, mode): except PermissionError as e: msg = '{!r}: Unable to set owner {!r} and group {!r}: {}, ignoring...' print(msg.format(path, mode.owner, mode.group, e.strerror)) + except LookupError as e: + msg = '{!r}: Non-existent owner {!r} or group {!r}: ignoring...' + print(msg.format(path, mode.owner, mode.group)) + except OSError as e: + if e.errno == errno.EINVAL: + msg = '{!r}: Non-existent numeric owner {!r} or group {!r}: ignoring...' + print(msg.format(path, mode.owner, mode.group)) + else: + raise # Must set permissions *after* setting owner/group otherwise the # setuid/setgid bits will get wiped by chmod # NOTE: On Windows you can set read/write perms; the rest are ignored @@ -193,7 +202,7 @@ def run_install_script(d): def is_elf_platform(): platname = platform.system().lower() - if platname == 'darwin' or platname == 'windows': + if platname == 'darwin' or platname == 'windows' or platname == 'cygwin': return False return True 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..67e4700 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, stderr=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/mesontest.py b/mesontest.py index a1708e3..c4d1178 100755 --- a/mesontest.py +++ b/mesontest.py @@ -36,6 +36,10 @@ def is_windows(): platname = platform.system().lower() return platname == 'windows' or 'mingw' in platname +def is_cygwin(): + platname = platform.system().lower() + return 'cygwin' in platname + def determine_worker_count(): varname = 'MESON_TESTTHREADS' if varname in os.environ: @@ -150,7 +154,7 @@ def write_json_log(jsonlogfile, test_name, result): jsonlogfile.write(json.dumps(jresult) + '\n') def run_with_mono(fname): - if fname.endswith('.exe') and not is_windows(): + if fname.endswith('.exe') and not (is_windows() or is_cygwin()): return True return False @@ -202,7 +206,7 @@ class TestHarness: child_env.update(test.env) if len(test.extra_paths) > 0: - child_env['PATH'] += ';'.join([''] + test.extra_paths) + child_env['PATH'] += os.pathsep.join([''] + test.extra_paths) # If MALLOC_PERTURB_ is not set, or if it is set to an empty value, # (i.e., the test or the environment don't explicitly set it), set diff --git a/run_project_tests.py b/run_project_tests.py index 3684de5..dc05524 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -31,9 +31,11 @@ import xml.etree.ElementTree as ET import time import multiprocessing import concurrent.futures as conc +import re from mesonbuild.coredata import backendlist + class BuildStep(Enum): configure = 1 build = 2 @@ -41,6 +43,7 @@ class BuildStep(Enum): install = 4 clean = 5 + class TestResult: def __init__(self, msg, step, stdo, stde, mlog, conftime=0, buildtime=0, testtime=0): self.msg = msg @@ -52,6 +55,54 @@ class TestResult: self.buildtime = buildtime self.testtime = testtime +class DummyFuture(conc.Future): + ''' + Dummy Future implementation that executes the provided function when you + ask for the result. Used on platforms where sem_open() is not available: + MSYS2, OpenBSD, etc: https://bugs.python.org/issue3770 + ''' + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def set_function(self, fn, *args, **kwargs): + self.fn = fn + self.fn_args = args + self.fn_kwargs = kwargs + + def result(self, **kwargs): + try: + result = self.fn(*self.fn_args, **self.fn_kwargs) + except BaseException as e: + self.set_exception(e) + else: + self.set_result(result) + return super().result(**kwargs) + + +class DummyExecutor(conc.Executor): + ''' + Dummy single-thread 'concurrent' executor for use on platforms where + sem_open is not available: https://bugs.python.org/issue3770 + ''' + + def __init__(self): + from threading import Lock + self._shutdown = False + self._shutdownLock = Lock() + + def submit(self, fn, *args, **kwargs): + with self._shutdownLock: + if self._shutdown: + raise RuntimeError('Cannot schedule new futures after shutdown') + f = DummyFuture() + f.set_function(fn, *args, **kwargs) + return f + + def shutdown(self, wait=True): + with self._shutdownLock: + self._shutdown = True + + class AutoDeletedDir: def __init__(self, d): self.dir = d @@ -158,12 +209,18 @@ def get_relative_files_list_from_dir(fromdir): paths.append(path) return paths -def platform_fix_exe_name(fname): - if not fname.endswith('?exe'): - return fname - fname = fname[:-4] - if mesonlib.is_windows(): - return fname + '.exe' +def platform_fix_name(fname): + if '?lib' in fname: + if mesonlib.is_cygwin(): + fname = re.sub(r'\?lib(.*)\.dll$', r'cyg\1.dll', fname) + else: + fname = re.sub(r'\?lib', 'lib', fname) + + if fname.endswith('?exe'): + fname = fname[:-4] + if mesonlib.is_windows() or mesonlib.is_cygwin(): + return fname + '.exe' + return fname def validate_install(srcdir, installdir): @@ -180,13 +237,16 @@ def validate_install(srcdir, installdir): elif os.path.exists(info_file): with open(info_file) as f: for line in f: - expected[platform_fix_exe_name(line.strip())] = False + expected[platform_fix_name(line.strip())] = False # Check if expected files were found for fname in expected: if os.path.exists(os.path.join(installdir, fname)): expected[fname] = True for (fname, found) in expected.items(): if not found: + # Ignore missing PDB files if we aren't using cl + if fname.endswith('.pdb') and compiler != 'cl': + continue ret_msg += 'Expected file {0} missing.\n'.format(fname) # Check if there are any unexpected files found = get_relative_files_list_from_dir(installdir) @@ -194,7 +254,7 @@ def validate_install(srcdir, installdir): # Windows-specific tests check for the existence of installed PDB # files, but common tests do not, for obvious reasons. Ignore any # extra PDB files found. - if fname not in expected and not fname.endswith('.pdb'): + if fname not in expected and not fname.endswith('.pdb') and compiler == 'cl': ret_msg += 'Extra file {0} found.\n'.format(fname) return ret_msg @@ -387,9 +447,9 @@ def detect_tests_to_run(): all_tests.append(('prebuilt', gather_tests('test cases/prebuilt'), False)) all_tests.append(('platform-osx', gather_tests('test cases/osx'), False if mesonlib.is_osx() else True)) - all_tests.append(('platform-windows', gather_tests('test cases/windows'), False if mesonlib.is_windows() else True)) + all_tests.append(('platform-windows', gather_tests('test cases/windows'), False if mesonlib.is_windows() or mesonlib.is_cygwin() else True)) all_tests.append(('platform-linux', gather_tests('test cases/linuxlike'), False if not (mesonlib.is_osx() or mesonlib.is_windows()) else True)) - all_tests.append(('framework', gather_tests('test cases/frameworks'), False if not mesonlib.is_osx() and not mesonlib.is_windows() else True)) + all_tests.append(('framework', gather_tests('test cases/frameworks'), False if not mesonlib.is_osx() and not mesonlib.is_windows() and not mesonlib.is_cygwin() else True)) all_tests.append(('java', gather_tests('test cases/java'), False if using_backend('ninja') and not mesonlib.is_osx() and have_java() else True)) all_tests.append(('C#', gather_tests('test cases/csharp'), False if using_backend('ninja') and shutil.which('mcs') else True)) all_tests.append(('vala', gather_tests('test cases/vala'), False if using_backend('ninja') and shutil.which('valac') else True)) @@ -421,7 +481,11 @@ def run_tests(all_tests, log_name_base, extra_args): print('Could not determine number of CPUs due to the following reason:' + str(e)) print('Defaulting to using only one process') num_workers = 1 - executor = conc.ProcessPoolExecutor(max_workers=num_workers) + try: + executor = conc.ProcessPoolExecutor(max_workers=num_workers) + except ImportError: + print('Platform doesn\'t ProcessPoolExecutor, falling back to single-threaded testing\n') + executor = DummyExecutor() for name, test_cases, skipped in all_tests: current_suite = ET.SubElement(junit_root, 'testsuite', {'name': name, 'tests': str(len(test_cases))}) @@ -441,6 +505,7 @@ def run_tests(all_tests, log_name_base, extra_args): result = executor.submit(run_test, skipped, t, extra_args, unity_flags + backend_flags, compile_commands, should_fail) futures.append((testname, t, result)) for (testname, t, result) in futures: + sys.stdout.flush() result = result.result() if result is None or 'MESON_SKIP_TEST' in result.stdo: print('Skipping:', t) @@ -532,6 +597,7 @@ def generate_pb_static(compiler, object_suffix, static_suffix): return stlibfile def generate_prebuilt(): + global compiler static_suffix = 'a' if shutil.which('cl'): compiler = 'cl' diff --git a/run_tests.py b/run_tests.py index 5025057..02aa701 100755 --- a/run_tests.py +++ b/run_tests.py @@ -21,6 +21,12 @@ import subprocess import platform from mesonbuild import mesonlib +def using_vs_backend(): + for arg in sys.argv[1:]: + if arg.startswith('--backend=vs'): + return True + return False + if __name__ == '__main__': returncode = 0 # Running on a developer machine? Be nice! @@ -32,7 +38,10 @@ if __name__ == '__main__': units += ['LinuxlikeTests'] elif mesonlib.is_windows(): units += ['WindowsTests'] - returncode += subprocess.call([sys.executable, 'run_unittests.py', '-v'] + units) + # Unit tests always use the Ninja backend, so just skip them if we're + # testing the VS backend + if not using_vs_backend(): + returncode += subprocess.call([sys.executable, 'run_unittests.py', '-v'] + units) # Ubuntu packages do not have a binary without -6 suffix. if shutil.which('arm-linux-gnueabihf-gcc-6') and not platform.machine().startswith('arm'): print('Running cross compilation tests.\n') diff --git a/run_unittests.py b/run_unittests.py index 9945057..1b24d08 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -24,11 +24,11 @@ from pathlib import PurePath import mesonbuild.compilers import mesonbuild.environment import mesonbuild.mesonlib -from mesonbuild.mesonlib import is_windows, is_osx +from mesonbuild.mesonlib import is_windows, is_osx, is_cygwin from mesonbuild.environment import detect_ninja, Environment from mesonbuild.dependencies import PkgConfigDependency, ExternalProgram -if is_windows(): +if is_windows() or is_cygwin(): exe_suffix = '.exe' else: exe_suffix = '' @@ -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 @@ -432,7 +433,20 @@ class BasePlatformTests(unittest.TestCase): def get_compdb(self): with open(os.path.join(self.builddir, 'compile_commands.json')) as ifile: - return json.load(ifile) + contents = json.load(ifile) + # If Ninja is using .rsp files, generate them, read their contents, and + # replace it as the command for all compile commands in the parsed json. + if len(contents) > 0 and contents[0]['command'].endswith('.rsp'): + # Pretend to build so that the rsp files are generated + self.build(['-d', 'keeprsp', '-n']) + for each in contents: + # Extract the actual command from the rsp file + compiler, rsp = each['command'].split(' @') + rsp = os.path.join(self.builddir, rsp) + # Replace the command with its contents + with open(rsp, 'r', encoding='utf-8') as f: + each['command'] = compiler + ' ' + f.read() + return contents def get_meson_log(self): with open(os.path.join(self.builddir, 'meson-logs', 'meson-log.txt')) as f: @@ -540,8 +554,10 @@ class AllPlatformTests(BasePlatformTests): get_fake_options(self.prefix), []) cc = env.detect_c_compiler(False) static_linker = env.detect_static_linker(cc) + if is_windows(): + raise unittest.SkipTest('https://github.com/mesonbuild/meson/issues/1526') if not isinstance(static_linker, mesonbuild.compilers.ArLinker): - raise unittest.SkipTest('static linker is not `ar`') + raise unittest.SkipTest('static linker is not `ar`') # Configure self.init(testdir) # Get name of static library @@ -712,20 +728,18 @@ class AllPlatformTests(BasePlatformTests): def test_internal_include_order(self): testdir = os.path.join(self.common_test_dir, '138 include order') self.init(testdir) + execmd = fxecmd = None for cmd in self.get_compdb(): - if cmd['file'].endswith('/main.c'): - cmd = cmd['command'] - break - else: - raise Exception('Could not find main.c command') - if cmd.endswith('.rsp'): - # Pretend to build so that the rsp files are generated - self.build(['-d', 'keeprsp', '-n']) - # Extract the actual command from the rsp file - rsp = os.path.join(self.builddir, cmd.split('cl @')[1]) - with open(rsp, 'r', encoding='utf-8') as f: - cmd = f.read() - incs = [a for a in shlex.split(cmd) if a.startswith("-I")] + if 'someexe' in cmd['command']: + execmd = cmd['command'] + continue + if 'somefxe' in cmd['command']: + fxecmd = cmd['command'] + continue + if not execmd or not fxecmd: + raise Exception('Could not find someexe and somfxe commands') + # Check include order for 'someexe' + incs = [a for a in shlex.split(execmd) if a.startswith("-I")] self.assertEqual(len(incs), 8) # target private dir self.assertPathEqual(incs[0], "-Isub4/someexe@exe") @@ -743,6 +757,27 @@ class AllPlatformTests(BasePlatformTests): self.assertPathEqual(incs[6], "-Isub1") # target internal dependency include_directories: source dir self.assertPathBasenameEqual(incs[7], 'sub1') + # Check include order for 'somefxe' + incs = [a for a in shlex.split(fxecmd) if a.startswith('-I')] + self.assertEqual(len(incs), 9) + # target private dir + self.assertPathEqual(incs[0], '-Isomefxe@exe') + # target build dir + self.assertPathEqual(incs[1], '-I.') + # target source dir + self.assertPathBasenameEqual(incs[2], os.path.basename(testdir)) + # target internal dependency correct include_directories: build dir + self.assertPathEqual(incs[3], "-Isub4") + # target internal dependency correct include_directories: source dir + self.assertPathBasenameEqual(incs[4], 'sub4') + # target internal dependency dep include_directories: build dir + self.assertPathEqual(incs[5], "-Isub1") + # target internal dependency dep include_directories: source dir + self.assertPathBasenameEqual(incs[6], 'sub1') + # target internal dependency wrong include_directories: build dir + self.assertPathEqual(incs[7], "-Isub2") + # target internal dependency wrong include_directories: source dir + self.assertPathBasenameEqual(incs[8], 'sub2') def test_compiler_detection(self): ''' @@ -798,6 +833,8 @@ class AllPlatformTests(BasePlatformTests): self.assertEqual(cc.gcc_type, mesonbuild.compilers.GCC_OSX) elif is_windows(): self.assertEqual(cc.gcc_type, mesonbuild.compilers.GCC_MINGW) + elif is_cygwin(): + self.assertEqual(cc.gcc_type, mesonbuild.compilers.GCC_CYGWIN) else: self.assertEqual(cc.gcc_type, mesonbuild.compilers.GCC_STANDARD) if isinstance(cc, clang): @@ -845,6 +882,88 @@ class AllPlatformTests(BasePlatformTests): self.assertEqual(wcc.get_exelist(), wrappercc) self.assertEqual(wlinker.get_exelist(), wrapperlinker) + def test_always_prefer_c_compiler_for_asm(self): + testdir = os.path.join(self.common_test_dir, '141 c cpp and asm') + # Skip if building with MSVC + env = Environment(testdir, self.builddir, self.meson_command, + get_fake_options(self.prefix), []) + if env.detect_c_compiler(False).get_id() == 'msvc': + raise unittest.SkipTest('MSVC can\'t compile assembly') + self.init(testdir) + commands = {'c-asm': {}, 'cpp-asm': {}, 'cpp-c-asm': {}, 'c-cpp-asm': {}} + for cmd in self.get_compdb(): + # Get compiler + split = shlex.split(cmd['command']) + if split[0] == 'ccache': + compiler = split[1] + else: + compiler = split[0] + # Classify commands + if 'Ic-asm' in cmd['command']: + if cmd['file'].endswith('.S'): + commands['c-asm']['asm'] = compiler + elif cmd['file'].endswith('.c'): + commands['c-asm']['c'] = compiler + else: + raise AssertionError('{!r} found in cpp-asm?'.format(cmd['command'])) + elif 'Icpp-asm' in cmd['command']: + if cmd['file'].endswith('.S'): + commands['cpp-asm']['asm'] = compiler + elif cmd['file'].endswith('.cpp'): + commands['cpp-asm']['cpp'] = compiler + else: + raise AssertionError('{!r} found in cpp-asm?'.format(cmd['command'])) + elif 'Ic-cpp-asm' in cmd['command']: + if cmd['file'].endswith('.S'): + commands['c-cpp-asm']['asm'] = compiler + elif cmd['file'].endswith('.c'): + commands['c-cpp-asm']['c'] = compiler + elif cmd['file'].endswith('.cpp'): + commands['c-cpp-asm']['cpp'] = compiler + else: + raise AssertionError('{!r} found in c-cpp-asm?'.format(cmd['command'])) + elif 'Icpp-c-asm' in cmd['command']: + if cmd['file'].endswith('.S'): + commands['cpp-c-asm']['asm'] = compiler + elif cmd['file'].endswith('.c'): + commands['cpp-c-asm']['c'] = compiler + elif cmd['file'].endswith('.cpp'): + commands['cpp-c-asm']['cpp'] = compiler + else: + raise AssertionError('{!r} found in cpp-c-asm?'.format(cmd['command'])) + else: + raise AssertionError('Unknown command {!r} found'.format(cmd['command'])) + # Check that .S files are always built with the C compiler + self.assertEqual(commands['c-asm']['asm'], commands['c-asm']['c']) + self.assertEqual(commands['c-asm']['asm'], commands['cpp-asm']['asm']) + self.assertEqual(commands['cpp-asm']['asm'], commands['c-cpp-asm']['c']) + self.assertEqual(commands['c-cpp-asm']['asm'], commands['c-cpp-asm']['c']) + self.assertEqual(commands['cpp-c-asm']['asm'], commands['cpp-c-asm']['c']) + self.assertNotEqual(commands['cpp-asm']['asm'], commands['cpp-asm']['cpp']) + self.assertNotEqual(commands['c-cpp-asm']['c'], commands['c-cpp-asm']['cpp']) + self.assertNotEqual(commands['cpp-c-asm']['c'], commands['cpp-c-asm']['cpp']) + # Check that the c-asm target is always linked with the C linker + build_ninja = os.path.join(self.builddir, 'build.ninja') + with open(build_ninja, 'r', encoding='utf-8') as f: + contents = f.read() + m = re.search('build c-asm.*: c_LINKER', contents) + self.assertIsNotNone(m, msg=contents) + + def test_preprocessor_checks_CPPFLAGS(self): + ''' + Test that preprocessor compiler checks read CPPFLAGS but not CFLAGS + ''' + testdir = os.path.join(self.common_test_dir, '140 get define') + define = 'MESON_TEST_DEFINE_VALUE' + # NOTE: this list can't have \n, ' or " + # \n is never substituted by the GNU pre-processor via a -D define + # ' and " confuse shlex.split() even when they are escaped + # % and # confuse the MSVC preprocessor + value = 'spaces and fun!@$^&*()-=_+{}[]:;<>?,./~`' + os.environ['CPPFLAGS'] = '-D{}="{}"'.format(define, value) + os.environ['CFLAGS'] = '-DMESON_FAIL_VALUE=cflags-read'.format(define) + self.init(testdir, ['-D{}={}'.format(define, value)]) + class WindowsTests(BasePlatformTests): ''' @@ -990,6 +1109,25 @@ class LinuxlikeTests(BasePlatformTests): self.assertIn("'-Werror'", vala_command) self.assertIn("'-Werror'", c_command) + def test_qt5dependency_pkgconfig_detection(self): + ''' + Test that qt4 and qt5 detection with pkgconfig works. + ''' + # Verify Qt4 or Qt5 can be found with pkg-config + if not shutil.which('pkg-config'): + raise unittest.SkipTest('pkg-config not found') + qt4 = subprocess.call(['pkg-config', '--exists', 'QtCore']) + qt5 = subprocess.call(['pkg-config', '--exists', 'Qt5Core']) + if qt4 != 0 or qt5 != 0: + raise unittest.SkipTest('Qt not found with pkg-config') + testdir = os.path.join(self.framework_test_dir, '4 qt') + self.init(testdir) + # Confirm that the dependency was found with qmake + msg = 'Qt4 native `pkg-config` dependency (modules: Core, Gui) found: YES\n' + msg2 = 'Qt5 native `pkg-config` dependency (modules: Core, Gui) found: YES\n' + mesonlog = self.get_meson_log() + self.assertTrue(msg in mesonlog or msg2 in mesonlog) + def test_qt5dependency_qmake_detection(self): ''' Test that qt5 detection with qmake works. This can't be an ordinary @@ -1207,6 +1345,27 @@ class LinuxlikeTests(BasePlatformTests): # The chown failed nonfatally if we're not root self.assertEqual(0, statf.st_uid) + def test_cpp_std_override(self): + testdir = os.path.join(self.unit_test_dir, '6 std override') + self.init(testdir) + compdb = self.get_compdb() + for i in compdb: + if 'prog03' in i['file']: + c03_comp = i['command'] + if 'prog11' in i['file']: + c11_comp = i['command'] + if 'progp' in i['file']: + plain_comp = i['command'] + self.assertNotEqual(len(plain_comp), 0) + self.assertIn('-std=c++03', c03_comp) + self.assertNotIn('-std=c++11', c03_comp) + self.assertIn('-std=c++11', c11_comp) + self.assertNotIn('-std=c++03', c11_comp) + self.assertNotIn('-std=c++03', plain_comp) + self.assertNotIn('-std=c++11', plain_comp) + # Now werror + self.assertIn('-Werror', plain_comp) + self.assertNotIn('-Werror', c03_comp) class RewriterTests(unittest.TestCase): diff --git a/test cases/common/123 subproject project arguments/exe.c b/test cases/common/123 subproject project arguments/exe.c index b04344a..d6440f0 100644 --- a/test cases/common/123 subproject project arguments/exe.c +++ b/test cases/common/123 subproject project arguments/exe.c @@ -18,6 +18,10 @@ #error #endif +#ifndef PROJECT_OPTION_C_CPP +#error +#endif + int main(int argc, char **argv) { return 0; } diff --git a/test cases/common/123 subproject project arguments/exe.cpp b/test cases/common/123 subproject project arguments/exe.cpp index 7ffe098..8471c6f 100644 --- a/test cases/common/123 subproject project arguments/exe.cpp +++ b/test cases/common/123 subproject project arguments/exe.cpp @@ -18,6 +18,10 @@ #error #endif +#ifndef PROJECT_OPTION_C_CPP +#error +#endif + int main(int argc, char **argv) { return 0; } diff --git a/test cases/common/123 subproject project arguments/meson.build b/test cases/common/123 subproject project arguments/meson.build index aee803c..90d4c05 100644 --- a/test cases/common/123 subproject project arguments/meson.build +++ b/test cases/common/123 subproject project arguments/meson.build @@ -4,10 +4,13 @@ project('project options tester', 'c', 'cpp', add_global_arguments('-DGLOBAL_ARGUMENT', language: 'c') add_project_arguments('-DPROJECT_OPTION', language: 'c') -add_project_arguments('-DPROJECT_OPTION_1', language: 'c') add_project_arguments('-DPROJECT_OPTION_CPP', language: 'cpp') +add_project_arguments('-DPROJECT_OPTION_C_CPP', language: ['c', 'cpp']) sub = subproject('subexe', version : '1.0.0') + +add_project_arguments('-DPROJECT_OPTION_1', language: 'c') + e = executable('exe', 'exe.c') e = executable('execpp', 'exe.cpp') test('exetest', e) diff --git a/test cases/common/123 subproject project arguments/subprojects/subexe/subexe.c b/test cases/common/123 subproject project arguments/subprojects/subexe/subexe.c index 6ebd752..f748afc 100644 --- a/test cases/common/123 subproject project arguments/subprojects/subexe/subexe.c +++ b/test cases/common/123 subproject project arguments/subprojects/subexe/subexe.c @@ -6,6 +6,10 @@ #error #endif +#ifdef PROJECT_OPTION_C_CPP +#error +#endif + #ifndef GLOBAL_ARGUMENT #error #endif diff --git a/test cases/common/125 shared module/meson.build b/test cases/common/125 shared module/meson.build index 7c15bcc..d96d8fc 100644 --- a/test cases/common/125 shared module/meson.build +++ b/test cases/common/125 shared module/meson.build @@ -5,7 +5,8 @@ l = shared_library('runtime', 'runtime.c') # Do NOT link the module with the runtime library. This # is a common approach for plugins that are only used # with dlopen. Any symbols are resolved dynamically -# at runtime +# at runtime. This requires extra help on Windows, so +# should be avoided unless really neccessary. m = shared_module('mymodule', 'module.c') e = executable('prog', 'prog.c', link_with : l, dependencies : dl) test('import test', e, args : m) diff --git a/test cases/common/125 shared module/module.c b/test cases/common/125 shared module/module.c index 56078c5..181b760 100644 --- a/test cases/common/125 shared module/module.c +++ b/test cases/common/125 shared module/module.c @@ -9,14 +9,24 @@ #endif #endif -#ifdef _WIN32 +#if defined(_WIN32) || defined(__CYGWIN__) #include <stdio.h> -#include <windows.h> -#include <tlhelp32.h> typedef int (*fptr) (void); +#ifdef __CYGWIN__ + +#include <dlfcn.h> + +fptr find_any_f (const char *name) { + return (fptr) dlsym(RTLD_DEFAULT, name); +} +#else /* _WIN32 */ + +#include <windows.h> +#include <tlhelp32.h> + /* Unlike Linux and OS X, when a library is loaded, all the symbols aren't * loaded into a single namespace. You must fetch the symbol by iterating over * all loaded modules. Code for finding the function from any of the loaded @@ -45,6 +55,7 @@ fptr find_any_f (const char *name) { CloseHandle (snapshot); return f; } +#endif int DLL_PUBLIC func() { fptr f; diff --git a/test cases/common/126 llvm ir and assembly/square-x86_64.S b/test cases/common/126 llvm ir and assembly/square-x86_64.S index 4adc31e..1452f47 100644 --- a/test cases/common/126 llvm ir and assembly/square-x86_64.S +++ b/test cases/common/126 llvm ir and assembly/square-x86_64.S @@ -19,12 +19,12 @@ END .text .globl SYMBOL_NAME(square_unsigned) -# ifdef _WIN32 /* MinGW */ +# if defined(_WIN32) || defined(__CYGWIN__) /* msabi */ SYMBOL_NAME(square_unsigned): imull %ecx, %ecx movl %ecx, %eax retq -# else /* Linux and OS X */ +# else /* sysvabi */ SYMBOL_NAME(square_unsigned): imull %edi, %edi movl %edi, %eax diff --git a/test cases/common/127 cpp and asm/meson.build b/test cases/common/127 cpp and asm/meson.build index ac7adb3..9160775 100644 --- a/test cases/common/127 cpp and asm/meson.build +++ b/test cases/common/127 cpp and asm/meson.build @@ -1,5 +1,6 @@ project('c++ and assembly test', 'cpp') +cpp = meson.get_compiler('cpp') cpu = host_machine.cpu_family() supported_cpus = ['arm', 'x86', 'x86_64'] @@ -8,6 +9,10 @@ if not supported_cpus.contains(cpu) error('MESON_SKIP_TEST unsupported cpu:' + cpu) endif +if cpp.symbols_have_underscore_prefix() + add_project_arguments('-DMESON_TEST__UNDERSCORE_SYMBOL', language : 'cpp') +endif + sources = ['trivial.cc'] # If the compiler cannot compile assembly, don't use it if meson.get_compiler('cpp').get_id() != 'msvc' diff --git a/test cases/common/127 cpp and asm/symbol-underscore.h b/test cases/common/127 cpp and asm/symbol-underscore.h index 508cf50..d0f3ef9 100644 --- a/test cases/common/127 cpp and asm/symbol-underscore.h +++ b/test cases/common/127 cpp and asm/symbol-underscore.h @@ -1,4 +1,4 @@ -#if defined(__WIN32__) || defined(__APPLE__) +#if defined(MESON_TEST__UNDERSCORE_SYMBOL) # define SYMBOL_NAME(name) _##name #else # define SYMBOL_NAME(name) name diff --git a/test cases/common/135 generated assembly/main.c b/test cases/common/135 generated assembly/main.c index 97fe723..b669cba 100644 --- a/test cases/common/135 generated assembly/main.c +++ b/test cases/common/135 generated assembly/main.c @@ -1,5 +1,8 @@ #include <stdio.h> +#if defined(_WIN32) || defined(__CYGWIN__) + __declspec(dllimport) +#endif unsigned square_unsigned (unsigned a); int diff --git a/test cases/common/135 generated assembly/square-arm.S.in b/test cases/common/135 generated assembly/square-arm.S.in index b13c8a0..168c980 100644 --- a/test cases/common/135 generated assembly/square-arm.S.in +++ b/test cases/common/135 generated assembly/square-arm.S.in @@ -2,7 +2,8 @@ .text .globl SYMBOL_NAME(square_unsigned) -#ifndef __APPLE__ +/* Only supported on Linux with GAS */ +# ifdef __linux__ .type square_unsigned,%function #endif diff --git a/test cases/common/135 generated assembly/square-x86.S.in b/test cases/common/135 generated assembly/square-x86.S.in index 31688b1..19dd9f5 100644 --- a/test cases/common/135 generated assembly/square-x86.S.in +++ b/test cases/common/135 generated assembly/square-x86.S.in @@ -21,7 +21,8 @@ END .text .globl SYMBOL_NAME(square_unsigned) -# ifndef __APPLE__ +/* Only supported on Linux with GAS */ +# ifdef __linux__ .type square_unsigned,@function # endif diff --git a/test cases/common/135 generated assembly/square-x86_64.S.in b/test cases/common/135 generated assembly/square-x86_64.S.in index 5aedd81..0834f16 100644 --- a/test cases/common/135 generated assembly/square-x86_64.S.in +++ b/test cases/common/135 generated assembly/square-x86_64.S.in @@ -1,7 +1,5 @@ #include "symbol-underscore.h" -#include "symbol-underscore.h" - #ifdef _MSC_VER /* MSVC on Windows */ PUBLIC SYMBOL_NAME(square_unsigned) @@ -20,16 +18,17 @@ END .text .globl SYMBOL_NAME(square_unsigned) -# ifndef __APPLE__ +/* Only supported on Linux with GAS */ +# ifdef __linux__ .type square_unsigned,@function # endif -# ifdef _WIN32 /* MinGW */ +# if defined(_WIN32) || defined(__CYGWIN__) /* msabi */ SYMBOL_NAME(square_unsigned): imull %ecx, %ecx movl %ecx, %eax retq -# else /* Linux and OS X */ +# else /* sysvabi */ SYMBOL_NAME(square_unsigned): imull %edi, %edi movl %edi, %eax diff --git a/test cases/common/138 include order/meson.build b/test cases/common/138 include order/meson.build index f744ae7..c79cb0a 100644 --- a/test cases/common/138 include order/meson.build +++ b/test cases/common/138 include order/meson.build @@ -19,4 +19,12 @@ subdir('sub3') # The directory where the target resides subdir('sub4') +# Test that the order in which internal dependencies are specified is +# preserved. This is needed especially when subprojects get involved and +# multiple build-root config.h files exist, and we must be sure that the +# correct one is found: https://github.com/mesonbuild/meson/issues/1495 +f = executable('somefxe', 'sub4/main.c', + dependencies : [correctinc, dep, wronginc]) + test('eh', e) +test('oh', f) diff --git a/test cases/common/138 include order/sub2/meson.build b/test cases/common/138 include order/sub2/meson.build index 7b49d6a..b1e6190 100644 --- a/test cases/common/138 include order/sub2/meson.build +++ b/test cases/common/138 include order/sub2/meson.build @@ -1 +1,2 @@ j = include_directories('.') +wronginc = declare_dependency(include_directories : j) diff --git a/test cases/common/138 include order/sub4/meson.build b/test cases/common/138 include order/sub4/meson.build index 538899a..ab4c455 100644 --- a/test cases/common/138 include order/sub4/meson.build +++ b/test cases/common/138 include order/sub4/meson.build @@ -2,3 +2,5 @@ e = executable('someexe', 'main.c', c_args : ['-I' + sub3], include_directories : j, dependencies : dep) + +correctinc = declare_dependency(include_directories : include_directories('.')) diff --git a/test cases/common/139 override options/four.c b/test cases/common/139 override options/four.c new file mode 100644 index 0000000..54f8491 --- /dev/null +++ b/test cases/common/139 override options/four.c @@ -0,0 +1,9 @@ +int func(); + +static int duplicate_func() { + return -4; +} + +int main(int argc, char **argv) { + return duplicate_func() + func(); +} diff --git a/test cases/common/139 override options/meson.build b/test cases/common/139 override options/meson.build new file mode 100644 index 0000000..0db0513 --- /dev/null +++ b/test cases/common/139 override options/meson.build @@ -0,0 +1,8 @@ +project('option override', 'c', + default_options : 'unity=true') + +executable('mustunity', 'one.c', 'two.c') +executable('notunity', 'three.c', 'four.c', + override_options : ['unity=false']) + + diff --git a/test cases/common/139 override options/one.c b/test cases/common/139 override options/one.c new file mode 100644 index 0000000..14fe9d6 --- /dev/null +++ b/test cases/common/139 override options/one.c @@ -0,0 +1,3 @@ +static int hidden_func() { + return 0; +} diff --git a/test cases/common/139 override options/three.c b/test cases/common/139 override options/three.c new file mode 100644 index 0000000..305a575 --- /dev/null +++ b/test cases/common/139 override options/three.c @@ -0,0 +1,7 @@ +static int duplicate_func() { + return 4; +} + +int func() { + return duplicate_func(); +} diff --git a/test cases/common/139 override options/two.c b/test cases/common/139 override options/two.c new file mode 100644 index 0000000..04b1d3f --- /dev/null +++ b/test cases/common/139 override options/two.c @@ -0,0 +1,6 @@ +/* + * Requires a Unity build. Otherwise hidden_func is not specified. + */ +int main(int argc, char **argv) { + return hidden_func(); +} diff --git a/test cases/common/140 get define/meson.build b/test cases/common/140 get define/meson.build new file mode 100644 index 0000000..5ce4b36 --- /dev/null +++ b/test cases/common/140 get define/meson.build @@ -0,0 +1,31 @@ +project('get define', 'c', 'cpp') + +host_system = host_machine.system() + +foreach lang : ['c', 'cpp'] + cc = meson.get_compiler(lang) + if host_system == 'linux' + d = cc.get_define('__linux__') + assert(d == '1', '__linux__ value is @0@ instead of 1'.format(d)) + elif host_system == 'darwin' + d = cc.get_define('__APPLE__') + assert(d == '1', '__APPLE__ value is @0@ instead of 1'.format(d)) + elif host_system == 'windows' + d = cc.get_define('_WIN32') + assert(d == '1', '_WIN32 value is @0@ instead of 1'.format(d)) + elif host_system == 'cygwin' + d = cc.get_define('__CYGWIN__') + assert(d == '1', '__CYGWIN__ value is @0@ instead of 1'.format(d)) + else + error('Please report a bug and help us improve support for this platform') + endif + + # Check that an undefined value is empty. + have = cc.get_define('MESON_FAIL_VALUE') + assert(have == '', 'MESON_FAIL_VALUE value is "@0@" instead of ""'.format(have)) + + # This is used in the test_preprocessor_checks_CPPFLAGS() unit test. + have = cc.get_define('MESON_TEST_DEFINE_VALUE') + expect = get_option('MESON_TEST_DEFINE_VALUE') + assert(have == expect, 'MESON_TEST_DEFINE_VALUE value is "@0@" instead of "@1@"'.format(have, expect)) +endforeach diff --git a/test cases/common/140 get define/meson_options.txt b/test cases/common/140 get define/meson_options.txt new file mode 100644 index 0000000..a88cecd --- /dev/null +++ b/test cases/common/140 get define/meson_options.txt @@ -0,0 +1 @@ +option('MESON_TEST_DEFINE_VALUE', type : 'string', default : '') diff --git a/test cases/common/141 c cpp and asm/main.c b/test cases/common/141 c cpp and asm/main.c new file mode 100644 index 0000000..8976723 --- /dev/null +++ b/test cases/common/141 c cpp and asm/main.c @@ -0,0 +1,8 @@ +#include <stdio.h> + +int get_retval(void); + +int main(int argc, char **argv) { + printf("C seems to be working.\n"); + return get_retval(); +} diff --git a/test cases/common/141 c cpp and asm/main.cpp b/test cases/common/141 c cpp and asm/main.cpp new file mode 100644 index 0000000..c089870 --- /dev/null +++ b/test cases/common/141 c cpp and asm/main.cpp @@ -0,0 +1,11 @@ +#include <iostream> + +extern "C" { + int get_retval(void); + int get_cval(void); +} + +int main(int argc, char **argv) { + std::cout << "C++ seems to be working." << std::endl; + return get_retval(); +} diff --git a/test cases/common/141 c cpp and asm/meson.build b/test cases/common/141 c cpp and asm/meson.build new file mode 100644 index 0000000..2c3610e --- /dev/null +++ b/test cases/common/141 c cpp and asm/meson.build @@ -0,0 +1,23 @@ +project('c cpp and asm', 'c', 'cpp') + +cpu = host_machine.cpu_family() +cc = meson.get_compiler('c') + +supported_cpus = ['arm', 'x86', 'x86_64'] + +if not supported_cpus.contains(cpu) + error('MESON_SKIP_TEST unsupported cpu:' + cpu) +endif + +if meson.get_compiler('c').get_id() == 'msvc' + error('MESON_SKIP_TEST MSVC can\'t compile assembly') +endif + +if cc.symbols_have_underscore_prefix() + add_project_arguments('-DMESON_TEST__UNDERSCORE_SYMBOL', language: 'c') +endif + +test('test-c-asm', executable('c-asm', ['main.c', 'retval-' + cpu + '.S'])) +test('test-cpp-asm', executable('cpp-asm', ['main.cpp', 'retval-' + cpu + '.S'])) +test('test-c-cpp-asm', executable('c-cpp-asm', ['somelib.c', 'main.cpp', 'retval-' + cpu + '.S'])) +test('test-cpp-c-asm', executable('cpp-c-asm', ['main.cpp', 'somelib.c', 'retval-' + cpu + '.S'])) diff --git a/test cases/common/141 c cpp and asm/retval-arm.S b/test cases/common/141 c cpp and asm/retval-arm.S new file mode 100644 index 0000000..8b37197 --- /dev/null +++ b/test cases/common/141 c cpp and asm/retval-arm.S @@ -0,0 +1,8 @@ +#include "symbol-underscore.h" + +.text +.globl SYMBOL_NAME(get_retval) + +SYMBOL_NAME(get_retval): + mov r0, #0 + mov pc, lr diff --git a/test cases/common/141 c cpp and asm/retval-x86.S b/test cases/common/141 c cpp and asm/retval-x86.S new file mode 100644 index 0000000..06bd75c --- /dev/null +++ b/test cases/common/141 c cpp and asm/retval-x86.S @@ -0,0 +1,8 @@ +#include "symbol-underscore.h" + +.text +.globl SYMBOL_NAME(get_retval) + +SYMBOL_NAME(get_retval): + xorl %eax, %eax + retl diff --git a/test cases/common/141 c cpp and asm/retval-x86_64.S b/test cases/common/141 c cpp and asm/retval-x86_64.S new file mode 100644 index 0000000..638921e --- /dev/null +++ b/test cases/common/141 c cpp and asm/retval-x86_64.S @@ -0,0 +1,8 @@ +#include "symbol-underscore.h" + +.text +.globl SYMBOL_NAME(get_retval) + +SYMBOL_NAME(get_retval): + xorl %eax, %eax + retq diff --git a/test cases/common/141 c cpp and asm/somelib.c b/test cases/common/141 c cpp and asm/somelib.c new file mode 100644 index 0000000..e585b8e --- /dev/null +++ b/test cases/common/141 c cpp and asm/somelib.c @@ -0,0 +1,3 @@ +int get_cval (void) { + return 0; +} diff --git a/test cases/common/141 c cpp and asm/symbol-underscore.h b/test cases/common/141 c cpp and asm/symbol-underscore.h new file mode 100644 index 0000000..d0f3ef9 --- /dev/null +++ b/test cases/common/141 c cpp and asm/symbol-underscore.h @@ -0,0 +1,5 @@ +#if defined(MESON_TEST__UNDERSCORE_SYMBOL) +# define SYMBOL_NAME(name) _##name +#else +# define SYMBOL_NAME(name) name +#endif diff --git a/test cases/common/139 compute int/config.h.in b/test cases/common/142 compute int/config.h.in index ad8d077..ad8d077 100644 --- a/test cases/common/139 compute int/config.h.in +++ b/test cases/common/142 compute int/config.h.in diff --git a/test cases/common/139 compute int/foobar.h b/test cases/common/142 compute int/foobar.h index fd3cb5e..fd3cb5e 100644 --- a/test cases/common/139 compute int/foobar.h +++ b/test cases/common/142 compute int/foobar.h diff --git a/test cases/common/139 compute int/meson.build b/test cases/common/142 compute int/meson.build index 43553fe..43553fe 100644 --- a/test cases/common/139 compute int/meson.build +++ b/test cases/common/142 compute int/meson.build diff --git a/test cases/common/139 compute int/prog.c.in b/test cases/common/142 compute int/prog.c.in index 3ff1463..3ff1463 100644 --- a/test cases/common/139 compute int/prog.c.in +++ b/test cases/common/142 compute int/prog.c.in diff --git a/test cases/common/143 custom target object output/meson.build b/test cases/common/143 custom target object output/meson.build new file mode 100644 index 0000000..ede165b --- /dev/null +++ b/test cases/common/143 custom target object output/meson.build @@ -0,0 +1,16 @@ +project('custom target object output', 'c') + +comp = find_program('obj_generator.py') + +if host_machine.system() == 'windows' + outputname = '@BASENAME@.obj' +else + outputname = '@BASENAME@.o' +endif + +cc = meson.get_compiler('c').cmd_array().get(-1) + +subdir('objdir') +subdir('progdir') + +test('objgen', e) diff --git a/test cases/common/143 custom target object output/obj_generator.py b/test cases/common/143 custom target object output/obj_generator.py new file mode 100644 index 0000000..a33872a --- /dev/null +++ b/test cases/common/143 custom target object output/obj_generator.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 + +# Mimic a binary that generates an object file (e.g. windres). + +import sys, subprocess + +if __name__ == '__main__': + if len(sys.argv) != 4: + print(sys.argv[0], 'compiler input_file output_file') + sys.exit(1) + compiler = sys.argv[1] + ifile = sys.argv[2] + ofile = sys.argv[3] + if compiler.endswith('cl'): + cmd = [compiler, '/nologo', '/MDd', '/Fo' + ofile, '/c', ifile] + else: + cmd = [compiler, '-c', ifile, '-o', ofile] + sys.exit(subprocess.call(cmd)) diff --git a/test cases/common/143 custom target object output/objdir/meson.build b/test cases/common/143 custom target object output/objdir/meson.build new file mode 100644 index 0000000..0d7f6c2 --- /dev/null +++ b/test cases/common/143 custom target object output/objdir/meson.build @@ -0,0 +1,5 @@ +# Generate an object file manually. +object = custom_target('object', + input : 'source.c', + output : outputname, + command : [comp, cc, '@INPUT@', '@OUTPUT@']) diff --git a/test cases/common/143 custom target object output/objdir/source.c b/test cases/common/143 custom target object output/objdir/source.c new file mode 100644 index 0000000..7779b33 --- /dev/null +++ b/test cases/common/143 custom target object output/objdir/source.c @@ -0,0 +1,3 @@ +int func1_in_obj() { + return 0; +} diff --git a/test cases/common/143 custom target object output/progdir/meson.build b/test cases/common/143 custom target object output/progdir/meson.build new file mode 100644 index 0000000..4216c24 --- /dev/null +++ b/test cases/common/143 custom target object output/progdir/meson.build @@ -0,0 +1 @@ +e = executable('prog', 'prog.c', object) diff --git a/test cases/common/143 custom target object output/progdir/prog.c b/test cases/common/143 custom target object output/progdir/prog.c new file mode 100644 index 0000000..c1ece33 --- /dev/null +++ b/test cases/common/143 custom target object output/progdir/prog.c @@ -0,0 +1,5 @@ +int func1_in_obj(); + +int main(int argc, char **argv) { + return func1_in_obj(); +} diff --git a/test cases/common/144 empty build file/meson.build b/test cases/common/144 empty build file/meson.build new file mode 100644 index 0000000..73d0397 --- /dev/null +++ b/test cases/common/144 empty build file/meson.build @@ -0,0 +1,2 @@ +project('subdir with empty meson.build test', 'c') +subdir('subdir') diff --git a/test cases/common/144 empty build file/subdir/meson.build b/test cases/common/144 empty build file/subdir/meson.build new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test cases/common/144 empty build file/subdir/meson.build diff --git a/test cases/common/16 configure file/generator-without-input-file.py b/test cases/common/16 configure file/generator-without-input-file.py new file mode 100755 index 0000000..2ee059e --- /dev/null +++ b/test cases/common/16 configure file/generator-without-input-file.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 + +import sys, os +from pathlib import Path + +if len(sys.argv) != 2: + print("Wrong amount of parameters.") + +build_dir = Path(os.environ['MESON_BUILD_ROOT']) +subdir = Path(os.environ['MESON_SUBDIR']) +outputf = Path(sys.argv[1]) + +with outputf.open('w') as ofile: + ofile.write("#define ZERO_RESULT 0\n") diff --git a/test cases/common/16 configure file/installed_files.txt b/test cases/common/16 configure file/installed_files.txt index d9fee12..542516e 100644 --- a/test cases/common/16 configure file/installed_files.txt +++ b/test cases/common/16 configure file/installed_files.txt @@ -1,3 +1,4 @@ usr/share/appdir/config2.h +usr/share/appdir/config2b.h usr/share/appdireh/config2-1.h usr/share/appdirok/config2-2.h diff --git a/test cases/common/16 configure file/meson.build b/test cases/common/16 configure file/meson.build index bff041b..8ffc28c 100644 --- a/test cases/common/16 configure file/meson.build +++ b/test cases/common/16 configure file/meson.build @@ -22,6 +22,11 @@ e = executable('inctest', 'prog.c', cfile) test('inctest', e) +# Test if we can also pass files() as input +configure_file(input : files('config.h.in'), + output : 'config2.h', + configuration : conf) + # Now generate a header file with an external script. genprog = import('python3').find_python() scriptfile = '@0@/generator.py'.format(meson.current_source_dir()) @@ -36,6 +41,16 @@ configure_file(input : 'dummy.dat', install_dir : 'share/appdir') run_command(check_file, join_paths(meson.current_build_dir(), 'config2.h')) +# Same again as before, but an input file should not be required in +# this case where we use a command/script to generate the output file. +genscript2b = '@0@/generator-without-input-file.py'.format(meson.current_source_dir()) +ofile2b = '@0@/config2b.h'.format(meson.current_build_dir()) +configure_file( + output : 'config2b.h', + command : [genprog, genscript2b, ofile2b], + install_dir : 'share/appdir') +run_command(check_file, join_paths(meson.current_build_dir(), 'config2b.h')) + found_script = find_program('generator.py') # More configure_file tests in here subdir('subdir') diff --git a/test cases/common/23 global arg/meson.build b/test cases/common/23 global arg/meson.build index aec5c2d..d7fd428 100644 --- a/test cases/common/23 global arg/meson.build +++ b/test cases/common/23 global arg/meson.build @@ -3,6 +3,8 @@ project('global arg test', 'cpp', 'c') add_global_arguments('-DMYTHING', language : 'c') add_global_arguments('-DMYCPPTHING', language : 'cpp') +add_global_arguments('-DMYCANDCPPTHING', language: ['c', 'cpp']) + exe1 = executable('prog', 'prog.c') exe2 = executable('prog2', 'prog.cc') diff --git a/test cases/common/23 global arg/prog.c b/test cases/common/23 global arg/prog.c index df91777..ace5a0a 100644 --- a/test cases/common/23 global arg/prog.c +++ b/test cases/common/23 global arg/prog.c @@ -6,6 +6,10 @@ #error "Wrong global argument set" #endif +#ifndef MYCANDCPPTHING +#error "Global argument not set" +#endif + int main(int argc, char **argv) { return 0; } diff --git a/test cases/common/23 global arg/prog.cc b/test cases/common/23 global arg/prog.cc index 342fdd0..0ffd85e 100644 --- a/test cases/common/23 global arg/prog.cc +++ b/test cases/common/23 global arg/prog.cc @@ -6,6 +6,10 @@ #error "Global argument not set" #endif +#ifndef MYCANDCPPTHING +#error "Global argument not set" +#endif + int main(int argc, char **argv) { return 0; } diff --git a/test cases/common/37 has header/meson.build b/test cases/common/37 has header/meson.build index 4299ce5..b53849c 100644 --- a/test cases/common/37 has header/meson.build +++ b/test cases/common/37 has header/meson.build @@ -11,19 +11,19 @@ configure_file(input : non_existant_header, # Test that the fallback to __has_include also works on all compilers if host_system != 'darwin' - args = [[], ['-U__has_include']] + fallbacks = ['', '\n#undef __has_include'] else # On Darwin's clang you can't redefine builtin macros so the above doesn't work - args = [[]] + fallbacks = [''] endif -foreach arg : args +foreach fallback : fallbacks foreach comp : [meson.get_compiler('c'), meson.get_compiler('cpp')] - assert(comp.has_header('stdio.h', args : arg), 'Stdio missing.') + assert(comp.has_header('stdio.h', prefix : fallback), 'Stdio missing.') # stdio.h doesn't actually need stdlib.h, but just test that setting the # prefix does not result in an error. - assert(comp.has_header('stdio.h', prefix : '#include <stdlib.h>', args : arg), + assert(comp.has_header('stdio.h', prefix : '#include <stdlib.h>' + fallback), 'Stdio missing.') # XInput.h should not require type definitions from windows.h, but it does @@ -32,9 +32,9 @@ foreach arg : args # We only do this check on MSVC because MinGW often defines its own wrappers # that pre-include windows.h if comp.get_id() == 'msvc' - assert(comp.has_header('XInput.h', prefix : '#include <windows.h>', args : arg), + assert(comp.has_header('XInput.h', prefix : '#include <windows.h>' + fallback), 'XInput.h should not be missing on Windows') - assert(comp.has_header('XInput.h', prefix : '#define _X86_', args : arg), + assert(comp.has_header('XInput.h', prefix : '#define _X86_' + fallback), 'XInput.h should not need windows.h') endif @@ -42,13 +42,13 @@ foreach arg : args # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80005 # https://github.com/mesonbuild/meson/issues/1458 if host_system == 'linux' - assert(comp.has_header('linux/if.h', args : arg), + assert(comp.has_header('linux/if.h', prefix : fallback), 'Could not find <linux/if.h>') endif # This header exists in the source and the builddir, but we still must not # find it since we are looking in the system directories. - assert(not comp.has_header(non_existant_header, args : arg), + assert(not comp.has_header(non_existant_header, prefix : fallback), 'Found non-existant header.') endforeach endforeach diff --git a/test cases/common/87 declare dep/meson.build b/test cases/common/87 declare dep/meson.build index 74333a2..e427def 100644 --- a/test cases/common/87 declare dep/meson.build +++ b/test cases/common/87 declare dep/meson.build @@ -5,3 +5,20 @@ subdir('entity') exe = executable('dep_user', 'main.c', dependencies : entity_dep) test('dep', exe) + +# just to make sure [] works as a no-op dep here +executable('dummy', 'main.c', + dependencies : [entity_dep, []]) + +# simple case +declare_dependency(dependencies : entity_dep) + +# nested deps should be flattened +declare_dependency(dependencies : [entity_dep]) +declare_dependency(dependencies : [[entity_dep]]) + +# check that [] properly works as a no-op dep in declare_dependency() too +declare_dependency(dependencies : []) +declare_dependency(dependencies : [[]]) +declare_dependency(dependencies : [entity_dep, []]) +declare_dependency(dependencies : [[], entity_dep]) diff --git a/test cases/common/9 header install/meson.build b/test cases/common/9 header install/meson.build index 7f3ce51..7dfbddb 100644 --- a/test cases/common/9 header install/meson.build +++ b/test cases/common/9 header install/meson.build @@ -1,4 +1,4 @@ -project('header install', 'c') +project('header install') as_array = ['subdir.h'] diff --git a/test cases/common/95 dep fallback/meson.build b/test cases/common/95 dep fallback/meson.build index 9358d29..a96520e 100644 --- a/test cases/common/95 dep fallback/meson.build +++ b/test cases/common/95 dep fallback/meson.build @@ -5,7 +5,11 @@ bob = dependency('boblib', fallback : ['boblib', 'bob_dep'], required: false, if not bob.found() error('Bob is actually needed') endif +# boblib subproject exists, but sita_dep doesn't exist +sita = dependency('sitalib', fallback : ['boblib', 'sita_dep'], required: false) +# jimmylib subproject doesn't exist jimmy = dependency('jimmylib', fallback : ['jimmylib', 'jimmy_dep'], required: false) +# dummylib subproject fails to configure dummy = dependency('dummylib', fallback : ['dummylib', 'dummy_dep'], required: false) gensrc_py = find_program('gensrc.py') diff --git a/test cases/failing/14 invalid option name/meson_options.txt b/test cases/failing/14 invalid option name/meson_options.txt index c656402..aab6ae8 100644 --- a/test cases/failing/14 invalid option name/meson_options.txt +++ b/test cases/failing/14 invalid option name/meson_options.txt @@ -1 +1 @@ -option('invalid/name', type : 'boolean', value : false)
\ No newline at end of file +option('invalid:name', type : 'boolean', value : false) diff --git a/test cases/failing/19 target clash/meson.build b/test cases/failing/19 target clash/meson.build index 070631b..fbc757c 100644 --- a/test cases/failing/19 target clash/meson.build +++ b/test cases/failing/19 target clash/meson.build @@ -7,7 +7,7 @@ project('clash', 'c') # This test might fail to work on different backends or when # output location is redirected. -if host_machine.system() == 'windows' +if host_machine.system() == 'windows' or host_machine.system() == 'cygwin' error('This is expected.') endif diff --git a/test cases/failing/43 project name colon/meson.build b/test cases/failing/43 project name colon/meson.build new file mode 100644 index 0000000..53e947e --- /dev/null +++ b/test cases/failing/43 project name colon/meson.build @@ -0,0 +1 @@ +project('name with :') diff --git a/test cases/linuxlike/10 large file support/meson.build b/test cases/linuxlike/10 large file support/meson.build index aa4eccf..6683345 100644 --- a/test cases/linuxlike/10 large file support/meson.build +++ b/test cases/linuxlike/10 large file support/meson.build @@ -1,5 +1,9 @@ project('trivial test', 'c') +if host_machine.system() == 'cygwin' + error('MESON_SKIP_TEST _FILE_OFFSET_BITS not yet supported on Cygwin.') +endif + cc = meson.get_compiler('c') size = cc.sizeof('off_t') diff --git a/test cases/linuxlike/7 library versions/meson.build b/test cases/linuxlike/7 library versions/meson.build index 48b75ad..451e42e 100644 --- a/test cases/linuxlike/7 library versions/meson.build +++ b/test cases/linuxlike/7 library versions/meson.build @@ -1,5 +1,9 @@ project('library versions', 'c') +if host_machine.system() == 'cygwin' + error('MESON_SKIP_TEST linuxlike soversions not supported on Cygwin.') +endif + some = shared_library('some', 'lib.c', version : '1.2.3', soversion : '0', diff --git a/test cases/linuxlike/8 subproject library install/meson.build b/test cases/linuxlike/8 subproject library install/meson.build index 63e57cf..ff55799 100644 --- a/test cases/linuxlike/8 subproject library install/meson.build +++ b/test cases/linuxlike/8 subproject library install/meson.build @@ -2,5 +2,9 @@ project('subproj lib install', 'c', version : '2.3.4', license : 'mylicense') +if host_machine.system() == 'cygwin' + error('MESON_SKIP_TEST linuxlike soversions not supported on Cygwin.') +endif + # Test that the subproject library gets installed subproject('sublib', version : '1.0.0') diff --git a/test cases/objc/2 nsstring/meson.build b/test cases/objc/2 nsstring/meson.build index ec496a2..a877d74 100644 --- a/test cases/objc/2 nsstring/meson.build +++ b/test cases/objc/2 nsstring/meson.build @@ -2,6 +2,8 @@ project('nsstring', 'objc') if host_machine.system() == 'darwin' dep = dependency('appleframeworks', modules : 'foundation') +elif host_machine.system() == 'cygwin' + error('MESON_SKIP_TEST GNUstep is not packaged for Cygwin.') else dep = dependency('gnustep') if host_machine.system() == 'linux' and meson.get_compiler('objc').get_id() == 'clang' diff --git a/test cases/python3/1 basic/meson.build b/test cases/python3/1 basic/meson.build index 9d5f874..111b717 100644 --- a/test cases/python3/1 basic/meson.build +++ b/test cases/python3/1 basic/meson.build @@ -3,6 +3,16 @@ project('python sample', 'c') py3_mod = import('python3') py3 = py3_mod.find_python() +py3_version = py3_mod.language_version() +if py3_version.version_compare('< 3.2') + error('Invalid python version!?') +endif + +py3_purelib = py3_mod.sysconfig_path('purelib') +if not py3_purelib.endswith('site-packages') + error('Python3 purelib path seems invalid?') +endif + main = files('prog.py') test('toplevel', py3, args : main) diff --git a/test cases/rust/1 basic/installed_files.txt b/test cases/rust/1 basic/installed_files.txt index c7dab9f..9dea55f 100644 --- a/test cases/rust/1 basic/installed_files.txt +++ b/test cases/rust/1 basic/installed_files.txt @@ -1 +1,2 @@ -usr/bin/prog?exe +usr/bin/program?exe +usr/bin/program2?exe diff --git a/test cases/rust/1 basic/meson.build b/test cases/rust/1 basic/meson.build index 7cd84b6..076d86b 100644 --- a/test cases/rust/1 basic/meson.build +++ b/test cases/rust/1 basic/meson.build @@ -1,4 +1,6 @@ project('rustprog', 'rust') -e = executable('prog', 'prog.rs', install : true) +e = executable('program', 'prog.rs', install : true) test('rusttest', e) + +subdir('subdir') diff --git a/test cases/rust/1 basic/subdir/meson.build b/test cases/rust/1 basic/subdir/meson.build new file mode 100644 index 0000000..51b385b --- /dev/null +++ b/test cases/rust/1 basic/subdir/meson.build @@ -0,0 +1,2 @@ +e = executable('program2', 'prog.rs', install : true) +test('rusttest2', e) diff --git a/test cases/rust/1 basic/subdir/prog.rs b/test cases/rust/1 basic/subdir/prog.rs new file mode 100644 index 0000000..b171a80 --- /dev/null +++ b/test cases/rust/1 basic/subdir/prog.rs @@ -0,0 +1,3 @@ +fn main() { + println!("rust compiler is working"); +} diff --git a/test cases/unit/5 compiler detection/meson.build b/test cases/unit/5 compiler detection/meson.build index 5491c64..8b47bd4 100644 --- a/test cases/unit/5 compiler detection/meson.build +++ b/test cases/unit/5 compiler detection/meson.build @@ -3,6 +3,6 @@ project('trivial test', meson_version : '>=0.27.0') executable('trivialc', 'trivial.c') -executable('trivialcpp', 'trivial.cpp') +executable('trivialcpp', 'trivial.cc') executable('trivialobjc', 'trivial.m') executable('trivialobjcpp', 'trivial.mm') diff --git a/test cases/unit/6 std override/meson.build b/test cases/unit/6 std override/meson.build new file mode 100644 index 0000000..ef2baac --- /dev/null +++ b/test cases/unit/6 std override/meson.build @@ -0,0 +1,10 @@ +project('cpp std override', 'cpp', + default_options : ['cpp_std=c++03', + 'werror=true']) + +executable('plain', 'progp.cpp', + override_options : 'cpp_std=none') +executable('v03', 'prog03.cpp', + override_options : 'werror=false') +executable('v11', 'prog11.cpp', + override_options : 'cpp_std=c++11') diff --git a/test cases/unit/6 std override/prog03.cpp b/test cases/unit/6 std override/prog03.cpp new file mode 100644 index 0000000..d30abc9 --- /dev/null +++ b/test cases/unit/6 std override/prog03.cpp @@ -0,0 +1,6 @@ +#include<iostream> + +int main(int argc, char **argv) { + std::cout << "I am a c++03 test program.\n"; + return 0; +} diff --git a/test cases/unit/6 std override/prog11.cpp b/test cases/unit/6 std override/prog11.cpp new file mode 100644 index 0000000..dde1fc0 --- /dev/null +++ b/test cases/unit/6 std override/prog11.cpp @@ -0,0 +1,6 @@ +#include<iostream> + +int main(int argc, char **argv) { + std::cout << "I am a C++11 test program.\n"; + return 0; +} diff --git a/test cases/unit/6 std override/progp.cpp b/test cases/unit/6 std override/progp.cpp new file mode 100644 index 0000000..b9bd97f --- /dev/null +++ b/test cases/unit/6 std override/progp.cpp @@ -0,0 +1,6 @@ +#include<iostream> + +int main(int argc, char **argv) { + std::cout << "I am a test program of undefined C++ standard.\n"; + return 0; +} diff --git a/test cases/windows/5 resources/inc/meson.build b/test cases/windows/5 resources/inc/meson.build new file mode 100644 index 0000000..b8b511a --- /dev/null +++ b/test cases/windows/5 resources/inc/meson.build @@ -0,0 +1 @@ +inc = include_directories('resource') diff --git a/test cases/windows/5 resources/inc/resource.h b/test cases/windows/5 resources/inc/resource/resource.h index dbdd509..dbdd509 100644 --- a/test cases/windows/5 resources/inc/resource.h +++ b/test cases/windows/5 resources/inc/resource/resource.h diff --git a/test cases/windows/5 resources/meson.build b/test cases/windows/5 resources/meson.build index 3c13634..ddb7d6e 100644 --- a/test cases/windows/5 resources/meson.build +++ b/test cases/windows/5 resources/meson.build @@ -1,9 +1,66 @@ project('winmain', 'c') -win = import('windows') -res = win.compile_resources('myres.rc', - include_directories : include_directories('inc') -) +# MinGW windres has a bug due to which it doesn't parse args with space properly: +# https://github.com/mesonbuild/meson/pull/1346 +# https://sourceware.org/bugzilla/show_bug.cgi?id=4933 +if meson.get_compiler('c').get_id() == 'gcc' and host_machine.system() == 'windows' + # Construct build_to_src and skip this test if it has spaces + # because then the -I flag to windres will also have spaces + # and we know the test will fail + src_parts = meson.source_root().split('/') + build_parts = meson.build_root().split('/') + + # Get the common path (which might just be '/' or 'C:/') + common = [] + done = false + count = 0 + if src_parts.length() > build_parts.length() + parts = build_parts + other = src_parts + else + parts = src_parts + other = build_parts + endif + foreach part : parts + if not done and part == other.get(count) + common += [part] + else + done = true + endif + count += 1 + endforeach + + # Create path components to go down from the build root to the common path + count = 0 + rel = build_parts + foreach build : build_parts + if count < build_parts.length() - common.length() + rel += ['..'] + endif + count += 1 + endforeach + + # Create path components to go up from the common path to the build root + count = 0 + foreach src : src_parts + if count >= common.length() + rel += [src] + endif + count += 1 + endforeach + + build_to_src = '/'.join(rel) + + if build_to_src.contains(' ') + message('build_to_src is: ' + build_to_src) + error('MESON_SKIP_TEST build_to_src has spaces') + endif + # Welcome to the end of this conditional. + # We hope you never have to implement something like this. +endif + +subdir('inc') +subdir('res') exe = executable('prog', 'prog.c', res, diff --git a/test cases/windows/5 resources/res/meson.build b/test cases/windows/5 resources/res/meson.build new file mode 100644 index 0000000..184854e --- /dev/null +++ b/test cases/windows/5 resources/res/meson.build @@ -0,0 +1,4 @@ +win = import('windows') + +res = win.compile_resources('myres.rc', + include_directories : inc) diff --git a/test cases/windows/5 resources/myres.rc b/test cases/windows/5 resources/res/myres.rc index 802bc7b..802bc7b 100644 --- a/test cases/windows/5 resources/myres.rc +++ b/test cases/windows/5 resources/res/myres.rc diff --git a/test cases/windows/5 resources/sample.ico b/test cases/windows/5 resources/res/sample.ico Binary files differindex 24bd3d9..24bd3d9 100644 --- a/test cases/windows/5 resources/sample.ico +++ b/test cases/windows/5 resources/res/sample.ico diff --git a/test cases/windows/7 mingw dll versioning/installed_files.txt b/test cases/windows/7 mingw dll versioning/installed_files.txt index ebad9e4..661005c 100644 --- a/test cases/windows/7 mingw dll versioning/installed_files.txt +++ b/test cases/windows/7 mingw dll versioning/installed_files.txt @@ -1,8 +1,8 @@ -usr/bin/libsome-0.dll +usr/bin/?libsome-0.dll usr/lib/libsome.dll.a -usr/bin/libnoversion.dll +usr/bin/?libnoversion.dll usr/lib/libnoversion.dll.a -usr/bin/libonlyversion-1.dll +usr/bin/?libonlyversion-1.dll usr/lib/libonlyversion.dll.a -usr/bin/libonlysoversion-5.dll +usr/bin/?libonlysoversion-5.dll usr/lib/libonlysoversion.dll.a |