diff options
99 files changed, 1454 insertions, 883 deletions
diff --git a/.appveyor.yml b/.appveyor.yml index 38ebe56..adc13b8 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -2,23 +2,56 @@ version: 1.0.{build} os: Visual Studio 2015 +environment: + matrix: + - arch: x86 + compiler: msvc2010 + backend: ninja + + - arch: x86 + compiler: msvc2010 + backend: vs2010 + + - arch: x86 + compiler: msvc2015 + backend: ninja + + - arch: x86 + compiler: msvc2015 + backend: vs2015 + + - arch: x64 + compiler: msvc2015 + backend: ninja + + - arch: x64 + compiler: msvc2015 + backend: vs2015 + platform: - - x86 + - x64 branches: only: - master install: - - ps: (new-object net.webclient).DownloadFile('https://dl.dropboxusercontent.com/u/37517477/ninja.exe', 'c:\python34\ninja.exe') - - cmd: copy c:\python34\python.exe c:\python34\python3.exe - - '"C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\SetEnv.cmd" /x86' + # Use the x86 python only when building for x86 for the cpython tests. + # For all other archs (including, say, arm), use the x64 python. + - ps: (new-object net.webclient).DownloadFile('https://dl.dropboxusercontent.com/u/37517477/ninja.exe', 'C:\projects\meson\ninja.exe') + - cmd: if %arch%==x86 (set MESON_PYTHON_PATH=C:\python34) else (set MESON_PYTHON_PATH=C:\python34-x64) + - cmd: echo Using Python at %MESON_PYTHON_PATH% + - cmd: copy %MESON_PYTHON_PATH%\python.exe %MESON_PYTHON_PATH%\python3.exe + - 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% ) build_script: - cmd: echo No build step. + - cmd: if %backend%==ninja ( ninja.exe --version ) else ( MSBuild /version & echo. ) test_script: - - cmd: PATH c:\python34;%PATH%; && python3 run_tests.py --backend=ninja + - cmd: echo Running tests for %arch% and %compiler% with the %backend% backend + - cmd: PATH=%cd%;%MESON_PYTHON_PATH%;%PATH%; && python3 run_tests.py --backend=%backend% on_finish: - appveyor PushArtifact meson-test-run.txt -DeploymentName "Text test logs" diff --git a/.travis.yml b/.travis.yml index cf3a5a6..8648572 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,29 @@ -sudo: required +sudo: false os: - linux - osx +compiler: + - gcc + - clang + +env: + - MESON_ARGS="" + - MESON_ARGS="--unity" + language: - cpp services: - docker +matrix: + exclude: + # On OS X gcc is just a wrapper around clang, so don't waste time testing that + - os: osx + compiler: gcc + before_install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install ninja python3; fi @@ -22,5 +36,5 @@ script: - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo FROM jpakkane/mesonci:yakkety > Dockerfile; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo ADD . /root >> Dockerfile; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker build -t withgit .; fi - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker run withgit /bin/sh -c "cd /root && TRAVIS=true ./run_tests.py"; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then SDKROOT=$(xcodebuild -version -sdk macosx Path) ./run_tests.py --backend=ninja ; fi + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker run withgit /bin/sh -c "cd /root && TRAVIS=true CC=$CC CXX=$CXX ./run_tests.py -- $MESON_ARGS"; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then SDKROOT=$(xcodebuild -version -sdk macosx Path) ./run_tests.py --backend=ninja -- $MESON_ARGS ; fi diff --git a/authors.txt b/authors.txt index f591d13..9bf3e33 100644 --- a/authors.txt +++ b/authors.txt @@ -54,3 +54,5 @@ Alexandre Foley Jouni Kosonen Aurelien Jarno Mark Schulte +Paulo Antonio Alvarez +Olexa Bilaniuk diff --git a/contributing.txt b/contributing.txt index 7553ab0..cb71ca0 100644 --- a/contributing.txt +++ b/contributing.txt @@ -32,7 +32,7 @@ those are simple. External dependencies The goal of Meson is to be as easily usable as possible. The user -experience thould be "get Python3 and Ninja, run", even on +experience should be "get Python3 and Ninja, run", even on Windows. Unfortunately this means that we can't have dependencies on projects outside of Python's standard library. This applies only to core functionality, though. For additional helper programs etc the use diff --git a/manual tests/6 qt4/main.cpp b/manual tests/6 qt4/main.cpp deleted file mode 100644 index 30fcd4b..0000000 --- a/manual tests/6 qt4/main.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include <QApplication> -#include "mainWindow.h" - -int main(int argc, char **argv) { - QApplication app(argc, argv); - MainWindow *win = new MainWindow(); - QImage qi(":/thing.png"); - if(qi.width() != 640) { - return 1; - } - QImage qi2(":/thing2.png"); - if(qi2.width() != 640) { - return 1; - } - win->setWindowTitle("Meson Qt4 build test"); - - win->show(); - return app.exec(); - return 0; -} diff --git a/manual tests/6 qt4/meson.build b/manual tests/6 qt4/meson.build deleted file mode 100644 index 94f78ab..0000000 --- a/manual tests/6 qt4/meson.build +++ /dev/null @@ -1,41 +0,0 @@ -project('qt4 build test', 'cpp') - -# This is a manual test rather than an automatic one -# because during Debian package builds only Qt4 or Qt5 -# can be active. - -qt4 = import('qt4') -qt4dep = dependency('qt4', modules : 'Gui') - -prep = qt4.preprocess(moc_headers : ['mainWindow.h'], # These need to be fed through the moc tool before use. - ui_files : 'mainWindow.ui', # XML files that need to be compiled with the uic tol. - qresources : 'stuff.qrc', # Resource file for rcc compiler. -) - -q5exe = executable('qt4app', -sources : ['main.cpp', 'mainWindow.cpp', # Sources that don't need preprocessing. -prep], -dependencies : qt4dep) - -# We need a console test application because some test environments -# do not have an X server. - -qt4core = dependency('qt4', modules : 'Core') - -qt4coreapp = executable('q4core', 'q4core.cpp', -dependencies : qt4core) - -test('qt4test', qt4coreapp) - -# The build system needs to include the cpp files from -# headers but the user must manually include moc -# files from sources. -manpreprocessed = qt4.preprocess( - moc_sources : 'manualinclude.cpp', - moc_headers : 'manualinclude.h') - -q4maninclude = executable('q4maninclude', -sources : ['manualinclude.cpp', manpreprocessed], -dependencies : qt4core) - -test('q4maninclude', q4maninclude) diff --git a/manual tests/6 qt4/q4core.cpp b/manual tests/6 qt4/q4core.cpp deleted file mode 100644 index 706e4dc..0000000 --- a/manual tests/6 qt4/q4core.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include <QCoreApplication> - -int main(int argc, char **argv) { - QCoreApplication app(argc, argv); - - // Don't actually start the main loop so this - // can be run as a unit test. - //return app.exec(); - return 0; -} diff --git a/manual tests/6 qt4/stuff.qrc b/manual tests/6 qt4/stuff.qrc deleted file mode 100644 index 9152500..0000000 --- a/manual tests/6 qt4/stuff.qrc +++ /dev/null @@ -1,7 +0,0 @@ -<!DOCTYPE RCC> -<RCC version="1.0"> - <qresource> - <file>thing.png</file> - <file>thing2.png</file> - </qresource> -</RCC> diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index e91b44b..b82227f 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -81,8 +81,8 @@ class Backend(): def get_target_filename(self, t): if isinstance(t, build.CustomTarget): if len(t.get_outputs()) != 1: - mlog.log(mlog.red('WARNING'), 'custom_target {!r} has more ' \ - 'than one output! Using the first one.'.format(t.name)) + mlog.warning('custom_target {!r} has more than one output! ' \ + 'Using the first one.'.format(t.name)) filename = t.get_outputs()[0] else: assert(isinstance(t, build.BuildTarget)) @@ -236,19 +236,23 @@ class Backend(): def determine_linker(self, target, src): if isinstance(target, build.StaticLibrary): - if self.build.static_cross_linker is not None: + if target.is_cross: return self.build.static_cross_linker else: return self.build.static_linker - if len(self.build.compilers) == 1: - return self.build.compilers[0] + if target.is_cross: + compilers = self.build.cross_compilers + else: + compilers = self.build.compilers + if len(compilers) == 1: + return compilers[0] # Currently a bit naive. C++ must # be linked with a C++ compiler, but # otherwise we don't care. This will # become trickier if and when Fortran # and the like become supported. cpp = None - for c in self.build.compilers: + for c in compilers: if c.get_language() == 'cpp': cpp = c break @@ -256,7 +260,7 @@ class Backend(): for s in src: if c.can_compile(s): return cpp - for c in self.build.compilers: + for c in compilers: if c.get_language() == 'vala': continue for s in src: @@ -267,6 +271,9 @@ class Backend(): def object_filename_from_source(self, target, source): 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'): + 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): @@ -347,6 +354,7 @@ class Backend(): commands += compiler.get_warn_args(self.environment.coredata.get_builtin_option('warning_level')) commands += compiler.get_option_compile_args(self.environment.coredata.compiler_options) commands += self.build.get_global_args(compiler) + commands += self.build.get_project_args(compiler, target.subproject) commands += self.environment.coredata.external_args[compiler.get_language()] commands += self.escape_extra_args(compiler, target.get_extra_args(compiler.get_language())) commands += compiler.get_buildtype_args(self.environment.coredata.get_builtin_option('buildtype')) @@ -568,32 +576,52 @@ class Backend(): i = i.replace('@INPUT%d@' % j, src) for (j, res) in enumerate(ofilenames): i = i.replace('@OUTPUT%d@' % j, res) - if i == '@INPUT@': - cmd += srcs - elif i == '@OUTPUT@': - cmd += ofilenames - else: - if '@OUTDIR@' in i: - i = i.replace('@OUTDIR@', outdir) - elif '@DEPFILE@' in i: - if target.depfile is None: - raise MesonException('Custom target %s has @DEPFILE@ but no depfile keyword argument.' % target.name) - if absolute_paths: - dfilename = os.path.join(self.get_target_private_dir_abs(target), target.depfile) - else: - dfilename = os.path.join(self.get_target_private_dir(target), target.depfile) - i = i.replace('@DEPFILE@', dfilename) - elif '@PRIVATE_OUTDIR_' in i: - match = re.search('@PRIVATE_OUTDIR_(ABS_)?([-a-zA-Z0-9.@:]*)@', i) - source = match.group(0) - if match.group(1) is None and not absolute_paths: - lead_dir = '' - else: - lead_dir = self.environment.get_build_dir() - i = i.replace(source, - os.path.join(lead_dir, - outdir)) - cmd.append(i) + if '@INPUT@' in i: + msg = 'Custom target {} has @INPUT@ in the command, but'.format(target.name) + if len(srcs) == 0: + raise MesonException(msg + ' no input files') + if i == '@INPUT@': + cmd += srcs + continue + else: + if len(srcs) > 1: + raise MesonException(msg + ' more than one input file') + i = i.replace('@INPUT@', srcs[0]) + elif '@OUTPUT@' in i: + msg = 'Custom target {} has @OUTPUT@ in the command, but'.format(target.name) + if len(ofilenames) == 0: + raise MesonException(msg + ' no output files') + if i == '@OUTPUT@': + cmd += ofilenames + continue + else: + if len(ofilenames) > 1: + raise MesonException(msg + ' more than one output file') + i = i.replace('@OUTPUT@', ofilenames[0]) + elif '@OUTDIR@' in i: + i = i.replace('@OUTDIR@', outdir) + elif '@DEPFILE@' in i: + if target.depfile is None: + msg = 'Custom target {!r} has @DEPFILE@ but no depfile ' \ + 'keyword argument.'.format(target.name) + raise MesonException(msg) + dfilename = os.path.join(outdir, target.depfile) + i = i.replace('@DEPFILE@', dfilename) + elif '@PRIVATE_OUTDIR_' in i: + match = re.search('@PRIVATE_OUTDIR_(ABS_)?([^\/\s*]*)@', i) + if not match: + msg = 'Custom target {!r} has an invalid argument {!r}' \ + ''.format(target.name, i) + raise MesonException(msg) + source = match.group(0) + if match.group(1) is None and not absolute_paths: + lead_dir = '' + else: + lead_dir = self.environment.get_build_dir() + i = i.replace(source, + os.path.join(lead_dir, + outdir)) + cmd.append(i) # This should not be necessary but removing it breaks # building GStreamer on Windows. The underlying issue # is problems with quoting backslashes on Windows diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index add7dcc..fb1280b 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -215,7 +215,7 @@ int dummy; with open(os.path.join(builddir, 'compile_commands.json'), 'wb') as f: f.write(jsondb) except Exception: - mlog.log(mlog.red('Warning:', 'Could not create compilation database.')) + mlog.warning('Could not create compilation database.') # Get all generated headers. Any source file might need them so # we need to add an order dependency to them. @@ -494,8 +494,8 @@ int dummy; cmd_type = 'custom' if target.depfile is not None: - rel_dfile = os.path.join(self.get_target_private_dir(target), target.depfile) - abs_pdir = os.path.join(self.environment.get_build_dir(), self.get_target_private_dir(target)) + rel_dfile = os.path.join(self.get_target_dir(target), target.depfile) + abs_pdir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target)) os.makedirs(abs_pdir, exist_ok=True) elem.add_item('DEPFILE', rel_dfile) elem.add_item('COMMAND', cmd) @@ -515,10 +515,10 @@ int dummy; arg_strings.append(os.path.join(self.environment.get_build_dir(), relfname)) deps.append(relfname) elif isinstance(i, mesonlib.File): - arg_strings.append(i.rel_to_builddir(self.build_to_src)) + relfname = i.rel_to_builddir(self.build_to_src) + arg_strings.append(os.path.join(self.environment.get_build_dir(), relfname)) else: - mlog.debug(str(i)) - raise MesonException('Unreachable code in generate_run_target.') + raise AssertionError('Unreachable code in generate_run_target: ' + str(i)) elem = NinjaBuildElement(self.all_outputs, target.name, 'CUSTOM_COMMAND', []) cmd = runnerscript + [self.environment.get_source_dir(), self.environment.get_build_dir(), target.subdir] texe = target.command @@ -581,7 +581,7 @@ int dummy; elem.add_item('DESC', 'Generating HTML coverage report.') elem.write(outfile) if not added_rule: - mlog.log(mlog.red('Warning:'), 'coverage requested but neither gcovr nor lcov/genhtml found.') + mlog.warning('coverage requested but neither gcovr nor lcov/genhtml found.') def generate_install(self, outfile): install_data_file = os.path.join(self.environment.get_scratch_dir(), 'install.dat') @@ -1018,6 +1018,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')) # Tell Valac to output everything in our private directory. Sadly this # means it will also preserve the directory components of Vala sources @@ -1045,6 +1046,8 @@ int dummy; and d.version_requirement.startswith(('>=', '==')): args += ['--target-glib', d.version_requirement[2:]] args += ['--pkg', d.name] + elif isinstance(d, dependencies.ExternalLibrary): + args += d.get_lang_args('vala') extra_args = [] for a in target.extra_args.get('vala', []): @@ -1694,9 +1697,9 @@ rule FORTRAN_DEP_HACK if target.has_pch(): tfilename = self.get_target_filename_abs(target) - return compiler.get_compile_debugfile_args(tfilename) + return compiler.get_compile_debugfile_args(tfilename, pch=True) else: - return compiler.get_compile_debugfile_args(objfile) + return compiler.get_compile_debugfile_args(objfile, pch=False) def get_link_debugfile_args(self, linker, target, outname): return linker.get_link_debugfile_args(outname) @@ -1720,10 +1723,11 @@ rule FORTRAN_DEP_HACK # Add the root source and build directories as include dirs curdir = target.get_subdir() tmppath = os.path.normpath(os.path.join(self.build_to_src, curdir)) - commands += compiler.get_include_args(tmppath, False) + src_inc = compiler.get_include_args(tmppath, False) if curdir == '': curdir = '.' - commands += compiler.get_include_args(curdir, False) + build_inc = compiler.get_include_args(curdir, False) + commands += build_inc + src_inc # -I args work differently than other ones. In them the first found # directory is used whereas for other flags (such as -ffoo -fno-foo) the # latest one is used. Therefore put the internal include directories @@ -1942,6 +1946,7 @@ rule FORTRAN_DEP_HACK abspath = os.path.join(self.environment.get_build_dir(), target.subdir) commands = [] if not isinstance(target, build.StaticLibrary): + commands += self.build.get_project_link_args(linker, target.subproject) commands += self.build.get_global_link_args(linker) commands += self.get_cross_stdlib_link_args(target, linker) commands += linker.get_linker_always_args() @@ -2084,7 +2089,7 @@ rule FORTRAN_DEP_HACK ninja_command = environment.detect_ninja() if ninja_command is None: - raise MesonException('Could not detect Ninja v1.6 or newer)') + raise MesonException('Could not detect Ninja v1.6 or newer') elem = NinjaBuildElement(self.all_outputs, 'clean', 'CUSTOM_COMMAND', 'PHONY') elem.add_item('COMMAND', [ninja_command, '-t', 'clean']) elem.add_item('description', 'Cleaning') @@ -2100,5 +2105,9 @@ rule FORTRAN_DEP_HACK elem.add_item('pool', 'console') elem.write(outfile) + elem = NinjaBuildElement(self.all_outputs, 'reconfigure', 'REGENERATE_BUILD', 'PHONY') + elem.add_item('pool', 'console') + elem.write(outfile) + elem = NinjaBuildElement(self.all_outputs, deps, 'phony', '') elem.write(outfile) diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index f1d949a..15bebba 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -284,20 +284,13 @@ class Vs2010Backend(backends.Backend): def generate_projects(self): projlist = [] - comp = None - for l, c in self.environment.coredata.compilers.items(): - if l == 'c' or l == 'cpp': - comp = c - break - if comp is None: - raise RuntimeError('C and C++ compilers missing.') for name, target in self.build.targets.items(): outdir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target)) fname = name + '.vcxproj' relname = os.path.join(target.subdir, fname) projfile = os.path.join(outdir, fname) uuid = self.environment.coredata.target_guids[name] - self.gen_vcxproj(target, projfile, uuid, comp) + self.gen_vcxproj(target, projfile, uuid) projlist.append((name, relname, uuid)) return projlist @@ -430,12 +423,26 @@ class Vs2010Backend(backends.Backend): pch_out = ET.SubElement(inc_cl, 'PrecompiledHeaderOutputFile') pch_out.text = '$(IntDir)$(TargetName)-%s.pch' % lang - def add_additional_options(self, source_file, parent_node, extra_args, has_additional_options_set): - if has_additional_options_set: + def add_additional_options(self, lang, parent_node, file_args): + if len(file_args[lang]) == 0: # We only need per file options if they were not set per project. return - lang = Vs2010Backend.lang_from_source_file(source_file) - ET.SubElement(parent_node, "AdditionalOptions").text = ' '.join(extra_args[lang]) + ' %(AdditionalOptions)' + args = file_args[lang] + ['%(AdditionalOptions)'] + ET.SubElement(parent_node, "AdditionalOptions").text = ' '.join(args) + + def add_preprocessor_defines(self, lang, parent_node, file_defines): + if len(file_defines[lang]) == 0: + # We only need per file options if they were not set per project. + return + defines = file_defines[lang] + ['%(PreprocessorDefinitions)'] + ET.SubElement(parent_node, "PreprocessorDefinitions").text = ';'.join(defines) + + def add_include_dirs(self, lang, parent_node, file_inc_dirs): + if len(file_inc_dirs[lang]) == 0: + # We only need per file options if they were not set per project. + return + dirs = file_inc_dirs[lang] + ['%(AdditionalIncludeDirectories)'] + ET.SubElement(parent_node, "AdditionalIncludeDirectories").text = ';'.join(dirs) @staticmethod def has_objects(objects, additional_objects, generated_objects): @@ -505,7 +512,19 @@ class Vs2010Backend(backends.Backend): other.append(arg) return (lpaths, libs, other) - def gen_vcxproj(self, target, ofname, guid, compiler): + def _get_cl_compiler(self, target): + for lang, c in target.compilers.items(): + if lang in ('c', 'cpp'): + return c + # No source files, only objects, but we still need a compiler, so + # return a found compiler + if len(target.objects) > 0: + for lang, c in self.environment.coredata.compilers.items(): + if lang in ('c', 'cpp'): + return c + raise MesonException('Could not find a C or C++ compiler. MSVC can only build C/C++ projects.') + + def gen_vcxproj(self, target, ofname, guid): mlog.debug('Generating vcxproj %s.' % target.name) entrypoint = 'WinMainCRTStartup' subsystem = 'Windows' @@ -532,6 +551,7 @@ 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) + compiler = self._get_cl_compiler(target) buildtype_args = compiler.get_buildtype_args(self.buildtype) buildtype_link_args = compiler.get_buildtype_linker_args(self.buildtype) project_name = target.name @@ -643,83 +663,89 @@ class Vs2010Backend(backends.Backend): # Build information compiles = ET.SubElement(root, 'ItemDefinitionGroup') clconf = ET.SubElement(compiles, 'ClCompile') - inc_dirs = ['.', self.relpath(self.get_target_private_dir(target), self.get_target_dir(target)), - proj_to_src_dir] + generated_files_include_dirs - - extra_args = {'c': [], 'cpp': []} + # Arguments, include dirs, defines for all files in the current target + target_args = [] + target_defines = [] + target_inc_dirs = ['.', self.relpath(self.get_target_private_dir(target), + self.get_target_dir(target)), + proj_to_src_dir] + generated_files_include_dirs + # Arguments, include dirs, defines passed to individual files in + # a target; perhaps because the args are language-specific + file_args = dict((lang, []) for lang in target.compilers) + file_defines = dict((lang, []) for lang in target.compilers) + file_inc_dirs = dict((lang, []) for lang in target.compilers) for l, args in self.environment.coredata.external_args.items(): - if l in extra_args: - extra_args[l] += args + if l in file_args: + file_args[l] += args for l, args in self.build.global_args.items(): - if l in extra_args: - extra_args[l] += args + if l in file_args: + file_args[l] += args + for l, args in self.build.projects_args.get(target.subproject, {}).items(): + if l in file_args: + file_args[l] += args for l, args in target.extra_args.items(): - if l in extra_args: - extra_args[l] += compiler.unix_compile_flags_to_native(args) - # FIXME all the internal flags of VS (optimization etc) are represented - # by their own XML elements. In theory we should split all flags to those - # that have an XML element and those that don't and serialise them - # properly. This is a crapton of work for no real gain, so just dump them - # here. - general_args = compiler.get_option_compile_args(self.environment.coredata.compiler_options) + if l in file_args: + file_args[l] += compiler.unix_compile_flags_to_native(args) + for l, comp in target.compilers.items(): + if l in file_args: + file_args[l] += comp.get_option_compile_args(self.environment.coredata.compiler_options) for d in 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_compile_flags_to_native(d.get_compile_args()) for arg in d_compile_args: - if arg.startswith('-I') or arg.startswith('/I'): + if arg.startswith(('-D', '/D')): + define = arg[2:] + # De-dup + if define not in target_defines: + target_defines.append(define) + elif arg.startswith(('-I', '/I')): inc_dir = arg[2:] # De-dup - if inc_dir not in inc_dirs: - inc_dirs.append(inc_dir) + if inc_dir not in target_inc_dirs: + target_inc_dirs.append(inc_dir) else: - general_args.append(arg) + # De-dup + if arg not in target_args: + target_args.append(arg) - defines = [] # Split preprocessor defines and include directories out of the list of # all extra arguments. The rest go into %(AdditionalOptions). - for l, args in extra_args.items(): - extra_args[l] = [] + for l, args in file_args.items(): + file_args[l] = [] for arg in args: - if arg.startswith('-D') or arg.startswith('/D'): + if arg.startswith(('-D', '/D')): define = self.escape_preprocessor_define(arg[2:]) # De-dup - if define not in defines: - defines.append(define) - elif arg.startswith('-I') or arg.startswith('/I'): + if define not in file_defines[l]: + file_defines[l].append(define) + elif arg.startswith(('-I', '/I')): inc_dir = arg[2:] # De-dup - if inc_dir not in inc_dirs: - inc_dirs.append(inc_dir) + if inc_dir not in file_inc_dirs[l]: + file_inc_dirs[l].append(inc_dir) else: - extra_args[l].append(self.escape_additional_option(arg)) + file_args[l].append(self.escape_additional_option(arg)) languages += gen_langs - has_language_specific_args = any(l != extra_args['c'] for l in extra_args.values()) - additional_options_set = False - if not has_language_specific_args or len(languages) == 1: - if len(languages) == 0: - extra_args = [] - else: - extra_args = extra_args[languages[0]] - extra_args = general_args + extra_args - if len(extra_args) > 0: - extra_args.append('%(AdditionalOptions)') - ET.SubElement(clconf, "AdditionalOptions").text = ' '.join(extra_args) - additional_options_set = True + if len(target_args) > 0: + target_args.append('%(AdditionalOptions)') + ET.SubElement(clconf, "AdditionalOptions").text = ' '.join(target_args) + additional_options_set = True for d in target.include_dirs: for i in d.incdirs: curdir = os.path.join(d.curdir, i) - inc_dirs.append(self.relpath(curdir, target.subdir)) # build dir - inc_dirs.append(os.path.join(proj_to_src_root, curdir)) # src dir + target_inc_dirs.append(self.relpath(curdir, target.subdir)) # build dir + target_inc_dirs.append(os.path.join(proj_to_src_root, curdir)) # src dir for i in d.get_extra_build_dirs(): curdir = os.path.join(d.curdir, i) - inc_dirs.append(self.relpath(curdir, target.subdir)) # build dir + target_inc_dirs.append(self.relpath(curdir, target.subdir)) # build dir - inc_dirs.append('%(AdditionalIncludeDirectories)') - ET.SubElement(clconf, 'AdditionalIncludeDirectories').text = ';'.join(inc_dirs) - ET.SubElement(clconf, 'PreprocessorDefinitions').text = ';'.join(defines) + target_inc_dirs.append('%(AdditionalIncludeDirectories)') + ET.SubElement(clconf, 'AdditionalIncludeDirectories').text = ';'.join(target_inc_dirs) + target_defines.append('%(PreprocessorDefinitions)') + ET.SubElement(clconf, 'PreprocessorDefinitions').text = ';'.join(target_defines) rebuild = ET.SubElement(clconf, 'MinimalRebuild') rebuild.text = 'true' funclink = ET.SubElement(clconf, 'FunctionLevelLinking') @@ -834,19 +860,26 @@ class Vs2010Backend(backends.Backend): for s in sources: relpath = os.path.join(down, s.rel_to_builddir(self.build_to_src)) inc_cl = ET.SubElement(inc_src, 'CLCompile', Include=relpath) + lang = Vs2010Backend.lang_from_source_file(s) self.add_pch(inc_cl, proj_to_src_dir, pch_sources, s) - self.add_additional_options(s, inc_cl, extra_args, additional_options_set) + self.add_additional_options(lang, inc_cl, file_args) + self.add_preprocessor_defines(lang, inc_cl, file_defines) + self.add_include_dirs(lang, inc_cl, file_inc_dirs) basename = os.path.basename(s.fname) if basename in self.sources_conflicts[target.get_id()]: ET.SubElement(inc_cl, 'ObjectFileName').text = "$(IntDir)" + self.object_filename_from_source(target, s) for s in gen_src: inc_cl = ET.SubElement(inc_src, 'CLCompile', Include=s) + lang = Vs2010Backend.lang_from_source_file(s) self.add_pch(inc_cl, proj_to_src_dir, pch_sources, s) - self.add_additional_options(s, inc_cl, extra_args, additional_options_set) + self.add_additional_options(lang, inc_cl, file_args) + self.add_preprocessor_defines(lang, inc_cl, file_defines) + self.add_include_dirs(lang, inc_cl, file_inc_dirs) for lang in pch_sources: header, impl, suffix = pch_sources[lang] relpath = os.path.join(proj_to_src_dir, impl) inc_cl = ET.SubElement(inc_src, 'CLCompile', Include=relpath) + lang = Vs2010Backend.lang_from_source_file(s) pch = ET.SubElement(inc_cl, 'PrecompiledHeader') pch.text = 'Create' pch_out = ET.SubElement(inc_cl, 'PrecompiledHeaderOutputFile') @@ -855,7 +888,9 @@ class Vs2010Backend(backends.Backend): # MSBuild searches for the header relative from the implementation, so we have to use # just the file name instead of the relative path to the file. pch_file.text = os.path.split(header)[1] - self.add_additional_options(impl, inc_cl, extra_args, additional_options_set) + self.add_additional_options(lang, inc_cl, file_args) + self.add_preprocessor_defines(lang, inc_cl, file_defines) + self.add_include_dirs(lang, inc_cl, file_inc_dirs) if self.has_objects(objects, additional_objects, gen_objs): inc_objs = ET.SubElement(root, 'ItemGroup') diff --git a/mesonbuild/build.py b/mesonbuild/build.py index c3867e0..d1746f1 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -89,7 +89,9 @@ class Build: self.compilers = [] self.cross_compilers = [] self.global_args = {} + self.projects_args = {} self.global_link_args = {} + self.projects_link_args = {} self.tests = [] self.benchmarks = [] self.headers = [] @@ -153,9 +155,22 @@ class Build: def get_global_args(self, compiler): return self.global_args.get(compiler.get_language(), []) + def get_project_args(self, compiler, project): + args = self.projects_args.get(project) + if not args: + return [] + return args.get(compiler.get_language(), []) + def get_global_link_args(self, compiler): return self.global_link_args.get(compiler.get_language(), []) + def get_project_link_args(self, compiler, project): + link_args = self.projects_link_args.get(project) + if not link_args: + return [] + + return link_args.get(compiler.get_language(), []) + class IncludeDirs(): def __init__(self, curdir, dirs, is_system, extra_build_dirs=None): self.curdir = curdir @@ -309,8 +324,8 @@ class BuildTarget(): if not k in known_kwargs: unknowns.append(k) if len(unknowns) > 0: - mlog.log(mlog.bold('Warning:'), 'Unknown keyword argument(s) in target %s: %s.' % - (self.name, ', '.join(unknowns))) + mlog.warning('Unknown keyword argument(s) in target %s: %s.' % + (self.name, ', '.join(unknowns))) def process_objectlist(self, objects): assert(isinstance(objects, list)) @@ -583,7 +598,7 @@ class BuildTarget(): if for_darwin(self.is_cross, self.environment) or for_windows(self.is_cross, self.environment): self.pic = True elif '-fPIC' in clist + cpplist: - mlog.log(mlog.red('WARNING:'), "Use the 'pic' kwarg instead of passing -fPIC manually to static library {!r}".format(self.name)) + mlog.warning("Use the 'pic' kwarg instead of passing -fPIC manually to static library {!r}".format(self.name)) self.pic = True else: self.pic = kwargs.get('pic', False) @@ -1139,8 +1154,8 @@ class CustomTarget: if k not in CustomTarget.known_kwargs: unknowns.append(k) if len(unknowns) > 0: - mlog.log(mlog.bold('Warning:'), 'Unknown keyword arguments in target %s: %s' % - (self.name, ', '.join(unknowns))) + mlog.warning('Unknown keyword arguments in target %s: %s' % + (self.name, ', '.join(unknowns))) def __repr__(self): repr_str = "<{0} {1}: {2}>" diff --git a/mesonbuild/compilers.py b/mesonbuild/compilers.py index 94e8a54..1505807 100644 --- a/mesonbuild/compilers.py +++ b/mesonbuild/compilers.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import shutil import contextlib import subprocess, os.path import tempfile @@ -429,12 +430,57 @@ class Compiler(): extra_flags += environment.cross_info.config['properties'].get(lang_link_args_key, []) return extra_flags + @contextlib.contextmanager + def compile(self, code, extra_args=None): + if extra_args is None: + extra_args = [] + + try: + with tempfile.TemporaryDirectory() as tmpdirname: + if isinstance(code, str): + srcname = os.path.join(tmpdirname, + 'testfile.' + self.default_suffix) + with open(srcname, 'w') as ofile: + ofile.write(code) + elif isinstance(code, mesonlib.File): + srcname = code.fname + + # Extension only matters if running results; '.exe' is + # guaranteed to be executable on every platform. + output = os.path.join(tmpdirname, 'output.exe') + + commands = self.get_exelist() + commands.append(srcname) + commands += extra_args + commands += self.get_output_args(output) + mlog.debug('Running compile:') + mlog.debug('Working directory: ', tmpdirname) + mlog.debug('Command line: ', ' '.join(commands), '\n') + mlog.debug('Code:\n', code) + p = subprocess.Popen(commands, cwd=tmpdirname, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + (stde, stdo) = p.communicate() + stde = stde.decode() + stdo = stdo.decode() + mlog.debug('Compiler stdout:\n', stdo) + mlog.debug('Compiler stderr:\n', stde) + + p.input_name = srcname + p.output_name = output + yield p + except (PermissionError, OSError): + # On Windows antivirus programs and the like hold on to files so + # they can't be deleted. There's not much to do in this case. Also, + # catch OSError because the directory is then no longer empty. + pass + def get_colorout_args(self, colortype): return [] # Some compilers (msvc) write debug info to a separate file. # These args specify where it should be written. - def get_compile_debugfile_args(self, rel_obj): + def get_compile_debugfile_args(self, rel_obj, **kwargs): return [] def get_link_debugfile_args(self, rel_obj): @@ -512,6 +558,13 @@ class CCompiler(Compiler): def get_no_optimization_args(self): return ['-O0'] + def get_compiler_check_args(self): + ''' + Get arguments useful for compiler checks such as being permissive in + the code quality and not doing any optimization. + ''' + return self.get_no_optimization_args() + def get_output_args(self, target): return ['-o', target] @@ -630,69 +683,27 @@ class CCompiler(Compiler): code = 'int main(int argc, char **argv) { int class=0; return class; }\n' return self.sanity_check_impl(work_dir, environment, 'sanitycheckc.c', code) - def has_header(self, hname, env, extra_args=None, dependencies=None): + def has_header(self, hname, prefix, env, extra_args=None, dependencies=None): if extra_args is None: extra_args = [] - templ = '''#include<%s> -int someSymbolHereJustForFun; -''' - return self.compiles(templ % hname, env, extra_args, dependencies) + code = '{}\n#include<{}>\nint someUselessSymbol;'.format(prefix, hname) + return self.compiles(code, env, extra_args, dependencies) def has_header_symbol(self, hname, symbol, prefix, env, extra_args=None, dependencies=None): if extra_args is None: extra_args = [] templ = '''{2} #include <{0}> -int main () {{ {1}; }}''' - # Pass -O0 to ensure that the symbol isn't optimized away - args = extra_args + self.get_no_optimization_args() +int main () {{ + /* If it's not defined as a macro, try to use as a symbol */ + #ifndef {1} + {1}; + #endif + return 0; +}}''' + args = extra_args + self.get_compiler_check_args() return self.compiles(templ.format(hname, symbol, prefix), env, args, dependencies) - @contextlib.contextmanager - def compile(self, code, extra_args=None): - if extra_args is None: - extra_args = [] - - try: - with tempfile.TemporaryDirectory() as tmpdirname: - if isinstance(code, str): - srcname = os.path.join(tmpdirname, - 'testfile.' + self.default_suffix) - with open(srcname, 'w') as ofile: - ofile.write(code) - elif isinstance(code, mesonlib.File): - srcname = code.fname - - # Extension only matters if running results; '.exe' is - # guaranteed to be executable on every platform. - output = os.path.join(tmpdirname, 'output.exe') - - commands = self.get_exelist() - commands.append(srcname) - commands += extra_args - commands += self.get_output_args(output) - mlog.debug('Running compile:') - mlog.debug('Working directory: ', tmpdirname) - mlog.debug('Command line: ', ' '.join(commands), '\n') - mlog.debug('Code:\n', code) - p = subprocess.Popen(commands, cwd=tmpdirname, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - (stde, stdo) = p.communicate() - stde = stde.decode() - stdo = stdo.decode() - mlog.debug('Compiler stdout:\n', stdo) - mlog.debug('Compiler stderr:\n', stde) - - p.input_name = srcname - p.output_name = output - yield p - except (PermissionError, OSError): - # On Windows antivirus programs and the like hold on to files so - # they can't be deleted. There's not much to do in this case. Also, - # catch OSError because the directory is then no longer empty. - pass - def compiles(self, code, env, extra_args=None, dependencies=None): if extra_args is None: extra_args = [] @@ -784,7 +795,7 @@ int main(int argc, char **argv) {{ %s int temparray[%d-sizeof(%s)]; ''' - args = extra_args + self.get_no_optimization_args() + args = extra_args + self.get_compiler_check_args() if not self.compiles(element_exists_templ.format(prefix, element), env, args, dependencies): return -1 for i in range(1, 1024): @@ -833,7 +844,7 @@ struct tmp { int testarray[%d-offsetof(struct tmp, target)]; ''' - args = extra_args + self.get_no_optimization_args() + args = extra_args + self.get_compiler_check_args() if not self.compiles(type_exists_templ.format(typename), env, args, dependencies): return -1 for i in range(1, 1024): @@ -918,7 +929,7 @@ int main(int argc, char **argv) { head = '#include <limits.h>\n{0}\n' # We don't know what the function takes or returns, so try to use it as # a function pointer - main = '\nint main() {{ int a = (int) &{1}; }}' + main = '\nint main() {{ void *a = (void*) &{1}; }}' return head, main def has_function(self, funcname, prefix, env, extra_args=None, dependencies=None): @@ -967,9 +978,8 @@ int main(int argc, char **argv) { head, main = self._no_prototype_templ() templ = head + stubs_fail + main - # Add -O0 to ensure that the symbol isn't optimized away by the compiler - args = extra_args + self.get_no_optimization_args() - if self.links(templ.format(prefix, funcname), env, extra_args, dependencies): + args = extra_args + self.get_compiler_check_args() + if self.links(templ.format(prefix, funcname), env, args, dependencies): return True # Some functions like alloca() are defined as compiler built-ins which # are inlined by the compiler, so test for that instead. Built-ins are @@ -1048,6 +1058,20 @@ class CPPCompiler(CCompiler): code = 'class breakCCompiler;int main(int argc, char **argv) { return 0; }\n' return self.sanity_check_impl(work_dir, environment, 'sanitycheckcpp.cc', code) + def has_header_symbol(self, hname, symbol, prefix, env, extra_args=None, dependencies=None): + # Check if it's a C-like symbol + if super().has_header_symbol(hname, symbol, prefix, env, extra_args, dependencies): + return True + # Check if it's a class or a template + if extra_args is None: + extra_args = [] + templ = '''{2} +#include <{0}> +using {1}; +int main () {{ return 0; }}''' + args = extra_args + self.get_compiler_check_args() + return self.compiles(templ.format(hname, symbol, prefix), env, args, dependencies) + class ObjCCompiler(CCompiler): def __init__(self, exelist, version, is_cross, exe_wrap): self.language = 'objc' @@ -1274,11 +1298,20 @@ class JavaCompiler(Compiler): pc.wait() if pc.returncode != 0: raise EnvironmentException('Java compiler %s can not compile programs.' % self.name_string()) - cmdlist = [self.javarunner, obj] - pe = subprocess.Popen(cmdlist, cwd=work_dir) - pe.wait() - if pe.returncode != 0: - raise EnvironmentException('Executables created by Java compiler %s are not runnable.' % self.name_string()) + runner = shutil.which(self.javarunner) + if runner: + cmdlist = [runner, obj] + pe = subprocess.Popen(cmdlist, cwd=work_dir) + pe.wait() + if pe.returncode != 0: + raise EnvironmentException('Executables created by Java compiler %s are not runnable.' % self.name_string()) + else: + m = "Java Virtual Machine wasn't found, but it's needed by Meson. " \ + "Please install a JRE.\nIf you have specific needs where this " \ + "requirement doesn't make sense, please open a bug at " \ + "https://github.com/mesonbuild/meson/issues/new and tell us " \ + "all about it." + raise EnvironmentException(m) def needs_static_linker(self): return False @@ -1297,27 +1330,48 @@ class ValaCompiler(Compiler): def needs_static_linker(self): return False # Because compiles into C. + def get_output_args(self, target): + return ['-o', target] + def get_werror_args(self): return ['--fatal-warnings'] def sanity_check(self, work_dir, environment): - src = 'valatest.vala' - source_name = os.path.join(work_dir, src) - with open(source_name, 'w') as ofile: - ofile.write('''class SanityCheck : Object { -} -''') - extra_flags = self.get_cross_extra_flags(environment, compile=True, link=False) - pc = subprocess.Popen(self.exelist + extra_flags + ['-C', '-c', src], cwd=work_dir) - pc.wait() - if pc.returncode != 0: - raise EnvironmentException('Vala compiler %s can not compile programs.' % self.name_string()) + code = 'class MesonSanityCheck : Object { }' + args = self.get_cross_extra_flags(environment, compile=True, link=False) + args += ['-C'] + with self.compile(code, args) as p: + if p.returncode != 0: + msg = 'Vala compiler {!r} can not compile programs' \ + ''.format(self.name_string()) + raise EnvironmentException(msg) def get_buildtype_args(self, buildtype): if buildtype == 'debug' or buildtype == 'debugoptimized' or buildtype == 'minsize': return ['--debug'] return [] + def find_library(self, libname, env, extra_dirs): + if extra_dirs and isinstance(extra_dirs, str): + extra_dirs = [extra_dirs] + # Valac always looks in the default vapi dir, so only search there if + # no extra dirs are specified. + if len(extra_dirs) == 0: + code = 'class MesonFindLibrary : Object { }' + vapi_args = ['--pkg', libname] + args = self.get_cross_extra_flags(env, compile=True, link=False) + args += ['-C'] + vapi_args + with self.compile(code, args) as p: + if p.returncode == 0: + return vapi_args + # Not found? Try to find the vapi file itself. + for d in extra_dirs: + vapi = os.path.join(d, libname + '.vapi') + if os.path.isfile(vapi): + return vapi + mlog.debug('Searched {!r} and {!r} wasn\'t found'.format(extra_dirs, libname)) + return None + class RustCompiler(Compiler): def __init__(self, exelist, version): self.language = 'rust' @@ -1819,6 +1873,9 @@ class VisualStudioCCompiler(CCompiler): result.append(i) return result + def get_werror_args(self): + return ['/WX'] + def get_include_args(self, path, is_system): if path == '': path = '.' @@ -1848,19 +1905,29 @@ class VisualStudioCCompiler(CCompiler): raise MesonException('Compiling test app failed.') return not(warning_text in stde or warning_text in stdo) - def get_compile_debugfile_args(self, rel_obj): + def get_compile_debugfile_args(self, rel_obj, pch=False): pdbarr = rel_obj.split('.')[:-1] pdbarr += ['pdb'] - return ['/Fd' + '.'.join(pdbarr)] + args = ['/Fd' + '.'.join(pdbarr)] + # When generating a PDB file with PCH, all compile commands write + # to the same PDB file. Hence, we need to serialize the PDB + # writes using /FS since we do parallel builds. This slows down the + # build obviously, which is why we only do this when PCH is on. + # This was added in Visual Studio 2013 (MSVC 18.0). Before that it was + # always on: https://msdn.microsoft.com/en-us/library/dn502518.aspx + if pch and mesonlib.version_compare(self.version, '>=18.0'): + args = ['/FS'] + args + return args def get_link_debugfile_args(self, targetfile): pdbarr = targetfile.split('.')[:-1] pdbarr += ['pdb'] return ['/DEBUG', '/PDB:' + '.'.join(pdbarr)] -class VisualStudioCPPCompiler(VisualStudioCCompiler): +class VisualStudioCPPCompiler(VisualStudioCCompiler, CPPCompiler): def __init__(self, exelist, version, is_cross, exe_wrap): self.language = 'cpp' + CPPCompiler.__init__(self, exelist, version, is_cross, exe_wrap) VisualStudioCCompiler.__init__(self, exelist, version, is_cross, exe_wrap) self.base_options = ['b_pch'] # FIXME add lto, pgo and the like @@ -2035,6 +2102,12 @@ class GnuCPPCompiler(GnuCompiler, CPPCompiler): return options['cpp_winlibs'].value return [] + def get_compiler_check_args(self): + # -fpermissive allows non-conforming code to compile which is necessary + # for many C++ checks. Particularly, the has_header_symbol check is + # too strict without this and always fails. + return self.get_no_optimization_args() + ['-fpermissive'] + class GnuObjCCompiler(GnuCompiler,ObjCCompiler): def __init__(self, exelist, version, is_cross, exe_wrapper=None, defines=None): @@ -2057,6 +2130,12 @@ class GnuObjCPPCompiler(GnuCompiler, ObjCPPCompiler): '2': ['-Wall', '-Wextra', '-Winvalid-pch', '-Wnon-virtual-dtor'], '3' : ['-Wall', '-Wpedantic', '-Wextra', '-Winvalid-pch', '-Wnon-virtual-dtor']} + def get_compiler_check_args(self): + # -fpermissive allows non-conforming code to compile which is necessary + # for many ObjC++ checks. Particularly, the has_header_symbol check is + # too strict without this and always fails. + return self.get_no_optimization_args() + ['-fpermissive'] + class ClangCompiler(): def __init__(self, clang_type): self.id = 'clang' diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index c8ee13f..fad39e6 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -247,4 +247,5 @@ forbidden_target_names = {'clean': None, 'install': None, 'build.ninja': None, 'scan-build': None, + 'reconfigure': None, } diff --git a/mesonbuild/dependencies.py b/mesonbuild/dependencies.py index 74738ae..d336c21 100644 --- a/mesonbuild/dependencies.py +++ b/mesonbuild/dependencies.py @@ -22,6 +22,7 @@ import re import os, stat, glob, subprocess, shutil import sysconfig +from collections import OrderedDict from . mesonlib import MesonException from . import mlog from . import mesonlib @@ -454,29 +455,37 @@ class ExternalProgram(): return self.name class ExternalLibrary(Dependency): - def __init__(self, name, link_args=None, silent=False): + # TODO: Add `lang` to the parent Dependency object so that dependencies can + # be expressed for languages other than C-like + def __init__(self, name, link_args=None, language=None, silent=False): super().__init__('external') self.name = name - # Rename fullpath to link_args once standalone find_library() gets removed. - if link_args is not None: - if isinstance(link_args, list): - self.link_args = link_args + self.is_found = False + self.link_args = [] + self.lang_args = [] + if link_args: + self.is_found = True + if not isinstance(link_args, list): + link_args = [link_args] + if language: + self.lang_args = {language: link_args} else: - self.link_args = [link_args] - else: - self.link_args = link_args + self.link_args = link_args if not silent: - if self.found(): + if self.is_found: mlog.log('Library', mlog.bold(name), 'found:', mlog.green('YES')) else: mlog.log('Library', mlog.bold(name), 'found:', mlog.red('NO')) def found(self): - return self.link_args is not None + return self.is_found def get_link_args(self): - if self.found(): - return self.link_args + return self.link_args + + def get_lang_args(self, lang): + if lang in self.lang_args: + return self.lang_args[lang] return [] class BoostDependency(Dependency): @@ -806,11 +815,20 @@ class GMockDependency(Dependency): def found(self): return self.is_found -class Qt5Dependency(Dependency): - def __init__(self, environment, kwargs): - Dependency.__init__(self, 'qt5') - self.name = 'qt5' +class QtBaseDependency(Dependency): + def __init__(self, name, env, kwargs): + Dependency.__init__(self, name) + self.name = name + self.qtname = name.capitalize() + self.qtver = name[-1] self.root = '/usr' + self.bindir = None + self.silent = kwargs.get('silent', False) + # We store the value of required here instead of passing it on to + # PkgConfigDependency etc because we want to try the qmake-based + # fallback as well. + self.required = kwargs.pop('required', True) + kwargs['required'] = False mods = kwargs.get('modules', []) self.cargs = [] self.largs = [] @@ -818,69 +836,130 @@ class Qt5Dependency(Dependency): if isinstance(mods, str): mods = [mods] if len(mods) == 0: - raise DependencyException('No Qt5 modules specified.') - type_text = 'native' - if environment.is_cross_build() and kwargs.get('native', False): - type_text = 'cross' - self.pkgconfig_detect(mods, environment, kwargs) - elif not environment.is_cross_build() and shutil.which('pkg-config') is not None: - self.pkgconfig_detect(mods, environment, kwargs) - elif shutil.which('qmake') is not None: - self.qmake_detect(mods, kwargs) - else: - self.version = 'none' + raise DependencyException('No ' + self.qtname + ' modules specified.') + type_text = 'cross' if env.is_cross_build() else 'native' + found_msg = '{} {} `{{}}` dependency found:'.format(self.qtname, type_text) + from_text = 'pkg-config' + # Prefer pkg-config, then fallback to `qmake -query` + self._pkgconfig_detect(mods, env, kwargs) if not self.is_found: - mlog.log('Qt5 %s dependency found: ' % type_text, mlog.red('NO')) + from_text = self._qmake_detect(mods, env, kwargs) + if not self.is_found: + from_text = '(checked pkg-config, qmake-{}, and qmake)' \ + ''.format(self.name) + self.version = 'none' + if self.required: + err_msg = '{} {} dependency not found {}' \ + ''.format(self.qtname, type_text, from_text) + raise DependencyException(err_msg) + if not self.silent: + mlog.log(found_msg.format(from_text), mlog.red('NO')) + return + if not self.silent: + mlog.log(found_msg.format(from_text), mlog.green('YES')) + + def compilers_detect(self): + "Detect Qt (4 or 5) moc, uic, rcc in the specified bindir or in PATH" + if self.bindir: + moc = ExternalProgram(os.path.join(self.bindir, 'moc'), silent=True) + uic = ExternalProgram(os.path.join(self.bindir, 'uic'), silent=True) + rcc = ExternalProgram(os.path.join(self.bindir, 'rcc'), silent=True) else: - mlog.log('Qt5 %s dependency found: ' % type_text, mlog.green('YES')) - - def pkgconfig_detect(self, mods, environment, kwargs): - modules = [] + # We don't accept unsuffixed 'moc', 'uic', and 'rcc' because they + # are sometimes older, or newer versions. + moc = ExternalProgram('moc-' + self.name, silent=True) + uic = ExternalProgram('uic-' + self.name, silent=True) + rcc = ExternalProgram('rcc-' + self.name, silent=True) + return moc, uic, rcc + + def _pkgconfig_detect(self, mods, env, kwargs): + if self.qtver == "4": + qtpkgname = 'Qt' + else: + qtpkgname = self.qtname + modules = OrderedDict() for module in mods: - modules.append(PkgConfigDependency('Qt5' + module, environment, kwargs)) - for m in modules: + modules[module] = PkgConfigDependency(qtpkgname + module, env, kwargs) + self.is_found = True + for m in modules.values(): + if not m.found(): + self.is_found = False self.cargs += m.get_compile_args() self.largs += m.get_link_args() - self.is_found = True - self.version = modules[0].modversion - - def qmake_detect(self, mods, kwargs): - pc = subprocess.Popen(['qmake', '-v'], stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - (stdo, _) = pc.communicate() - if pc.returncode != 0: - return - stdo = stdo.decode() - if not 'version 5' in stdo: - mlog.log('QMake is not for Qt5.') + self.version = m.modversion + # Try to detect moc, uic, rcc + if 'Core' in modules: + core = modules['Core'] + else: + corekwargs = {'required': 'false', 'silent': 'true'} + core = PkgConfigDependency(qtpkgname + 'Core', env, corekwargs) + # Used by self.compilers_detect() + self.bindir = core.get_pkgconfig_variable('host_bins') + if not self.bindir: + # If exec_prefix is not defined, the pkg-config file is broken + prefix = core.get_pkgconfig_variable('exec_prefix') + if prefix: + self.bindir = os.path.join(prefix, 'bin') + + def _find_qmake(self, qmake, env): + # Even when cross-compiling, if we don't get a cross-info qmake, we + # fallback to using the qmake in PATH because that's what we used to do + if env.is_cross_build(): + qmake = env.cross_info.config['binaries'].get('qmake', qmake) + return ExternalProgram(qmake, silent=True) + + def _qmake_detect(self, mods, env, kwargs): + for qmake in ('qmake-' + self.name, 'qmake'): + self.qmake = self._find_qmake(qmake, env) + if not self.qmake.found(): + continue + # Check that the qmake is for qt5 + pc = subprocess.Popen(self.qmake.fullpath + ['-v'], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + universal_newlines=True) + stdo = pc.communicate()[0] + if pc.returncode != 0: + continue + if not 'Qt version ' + self.qtver in stdo: + mlog.log('QMake is not for ' + self.qtname) + continue + # Found qmake for Qt5! + break + else: + # Didn't find qmake :( return - self.version = re.search('5(\.\d+)+', stdo).group(0) - (stdo, _) = subprocess.Popen(['qmake', '-query'], stdout=subprocess.PIPE).communicate() + self.version = re.search(self.qtver + '(\.\d+)+', stdo).group(0) + # Query library path, header path, and binary path + stdo = subprocess.Popen(self.qmake.fullpath + ['-query'], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + universal_newlines=True).communicate()[0] qvars = {} - for line in stdo.decode().split('\n'): + for line in stdo.split('\n'): line = line.strip() if line == '': continue (k, v) = tuple(line.split(':', 1)) qvars[k] = v if mesonlib.is_osx(): - return self.framework_detect(qvars, mods, kwargs) + return self._framework_detect(qvars, mods, kwargs) incdir = qvars['QT_INSTALL_HEADERS'] self.cargs.append('-I' + incdir) libdir = qvars['QT_INSTALL_LIBS'] - bindir = qvars['QT_INSTALL_BINS'] + # Used by self.compilers_detect() + self.bindir = qvars['QT_INSTALL_BINS'] #self.largs.append('-L' + libdir) for module in mods: mincdir = os.path.join(incdir, 'Qt' + module) self.cargs.append('-I' + mincdir) - libfile = os.path.join(libdir, 'Qt5' + module + '.lib') + libfile = os.path.join(libdir, self.qtname + module + '.lib') if not os.path.isfile(libfile): # MinGW links directly to .dll, not to .lib. - libfile = os.path.join(bindir, 'Qt5' + module + '.dll') + libfile = os.path.join(self.bindir, self.qtname + module + '.dll') self.largs.append(libfile) self.is_found = True + return qmake - def framework_detect(self, qvars, modules, kwargs): + def _framework_detect(self, qvars, modules, kwargs): libdir = qvars['QT_INSTALL_LIBS'] for m in modules: fname = 'Qt' + m @@ -890,7 +969,8 @@ class Qt5Dependency(Dependency): self.is_found = True self.cargs += fwdep.get_compile_args() self.largs += fwdep.get_link_args() - + # Used by self.compilers_detect() + self.bindir = qvars['QT_INSTALL_BINS'] def get_version(self): return self.version @@ -917,43 +997,13 @@ class Qt5Dependency(Dependency): # Fix this to be more portable, especially to MSVC. return ['-fPIC'] -class Qt4Dependency(Dependency): - def __init__(self, environment, kwargs): - Dependency.__init__(self, 'qt4') - self.name = 'qt4' - self.root = '/usr' - self.modules = [] - mods = kwargs.get('modules', []) - if isinstance(mods, str): - mods = [mods] - for module in mods: - self.modules.append(PkgConfigDependency('Qt' + module, environment, kwargs)) - if len(self.modules) == 0: - raise DependencyException('No Qt4 modules specified.') - - def get_version(self): - return self.modules[0].get_version() - - def get_compile_args(self): - args = [] - for m in self.modules: - args += m.get_compile_args() - return args - - def get_sources(self): - return [] +class Qt5Dependency(QtBaseDependency): + def __init__(self, env, kwargs): + QtBaseDependency.__init__(self, 'qt5', env, kwargs) - def get_link_args(self): - args = [] - for module in self.modules: - args += module.get_link_args() - return args - - def found(self): - for i in self.modules: - if not i.found(): - return False - return True +class Qt4Dependency(QtBaseDependency): + def __init__(self, env, kwargs): + QtBaseDependency.__init__(self, 'qt4', env, kwargs) class GnuStepDependency(Dependency): def __init__(self, environment, kwargs): diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index f7045f4..405685c 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -331,6 +331,13 @@ class Environment(): if len(rest) == 2: defines[rest[0]] = rest[1] return defines + @staticmethod + def get_gnu_version_from_defines(defines): + dot = '.' + major = defines.get('__GNUC__', '0') + minor = defines.get('__GNUC_MINOR__', '0') + patch = defines.get('__GNUC_PATCHLEVEL__', '0') + return dot.join((major, minor, patch)) @staticmethod def get_gnu_compiler_type(defines): @@ -385,6 +392,7 @@ class Environment(): popen_exceptions[compiler] = 'no pre-processor defines' continue gtype = self.get_gnu_compiler_type(defines) + version = self.get_gnu_version_from_defines(defines) return GnuCCompiler(ccache + [compiler], version, gtype, is_cross, exe_wrap, defines) if 'clang' in out: if 'Apple' in out: @@ -443,6 +451,7 @@ class Environment(): popen_exceptions[compiler] = 'no pre-processor defines' continue gtype = self.get_gnu_compiler_type(defines) + version = self.get_gnu_version_from_defines(defines) return GnuFortranCompiler([compiler], version, gtype, is_cross, exe_wrap, defines) if 'G95' in out: @@ -524,6 +533,7 @@ class Environment(): popen_exceptions[compiler] = 'no pre-processor defines' continue gtype = self.get_gnu_compiler_type(defines) + version = self.get_gnu_version_from_defines(defines) return GnuCPPCompiler(ccache + [compiler], version, gtype, is_cross, exe_wrap, defines) if 'clang' in out: if 'Apple' in out: @@ -563,6 +573,7 @@ class Environment(): version = search_version(out) if 'Free Software Foundation' in out: defines = self.get_gnu_compiler_defines(exelist) + version = self.get_gnu_version_from_defines(defines) return GnuObjCCompiler(exelist, version, is_cross, exe_wrap, defines) if out.startswith('Apple LLVM'): return ClangObjCCompiler(exelist, version, CLANG_OSX, is_cross, exe_wrap) diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 3044d51..9780626 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -92,6 +92,10 @@ class InterpreterObject(): return self.methods[method_name](args, kwargs) raise InvalidCode('Unknown method "%s" in object.' % method_name) +class MutableInterpreterObject(InterpreterObject): + def __init__(self): + super().__init__() + class TryRunResultHolder(InterpreterObject): def __init__(self, res): super().__init__() @@ -182,14 +186,13 @@ class ConfigureFileHolder(InterpreterObject): self.held_object = build.ConfigureFile(subdir, sourcename, targetname, configuration_data) -class EnvironmentVariablesHolder(InterpreterObject): +class EnvironmentVariablesHolder(MutableInterpreterObject): def __init__(self): super().__init__() self.held_object = build.EnvironmentVariables() self.methods.update({'set': self.set_method, 'append': self.append_method, 'prepend' : self.prepend_method, - 'copy' : self.copy_method, }) @stringArgs @@ -212,11 +215,8 @@ class EnvironmentVariablesHolder(InterpreterObject): def prepend_method(self, args, kwargs): self.add_var(self.held_object.prepend, args, kwargs) - def copy_method(self, args, kwargs): - return copy.deepcopy(self) - -class ConfigurationDataHolder(InterpreterObject): +class ConfigurationDataHolder(MutableInterpreterObject): def __init__(self): super().__init__() self.used = False # These objects become immutable after use in configure_file. @@ -896,15 +896,18 @@ class CompilerHolder(InterpreterObject): if len(args) != 1: raise InterpreterException('has_header method takes exactly one argument.') check_stringlist(args) - string = args[0] + hname = args[0] + prefix = kwargs.get('prefix', '') + if not isinstance(prefix, str): + raise InterpreterException('Prefix argument of has_header must be a string.') extra_args = self.determine_args(kwargs) deps = self.determine_dependencies(kwargs, allowed_dep_types=(dependencies.Dependency,)) - haz = self.compiler.has_header(string, self.environment, extra_args, deps) + haz = self.compiler.has_header(hname, prefix, self.environment, extra_args, deps) if haz: h = mlog.green('YES') else: h = mlog.red('NO') - mlog.log('Has header "%s":' % string, h) + mlog.log('Has header "%s":' % hname, h) return haz def has_header_symbol_method(self, args, kwargs): @@ -915,7 +918,7 @@ class CompilerHolder(InterpreterObject): symbol = args[1] prefix = kwargs.get('prefix', '') if not isinstance(prefix, str): - raise InterpreterException('Prefix argument of has_function must be a string.') + raise InterpreterException('Prefix argument of has_header_symbol must be a string.') extra_args = self.determine_args(kwargs) deps = self.determine_dependencies(kwargs, allowed_dep_types=(dependencies.Dependency,)) haz = self.compiler.has_header_symbol(hname, symbol, prefix, self.environment, extra_args, deps) @@ -941,9 +944,17 @@ class CompilerHolder(InterpreterObject): if not os.path.isabs(i): raise InvalidCode('Search directory %s is not an absolute path.' % i) linkargs = self.compiler.find_library(libname, self.environment, search_dirs) - if required and linkargs is None: - raise InterpreterException('Library {} not found'.format(libname)) - lib = dependencies.ExternalLibrary(libname, linkargs) + if required and not linkargs: + l = self.compiler.language.capitalize() + raise InterpreterException('{} library {!r} not found'.format(l, libname)) + # If this is set to None, the library and link arguments are for + # a C-like compiler. Otherwise, it's for some other language that has + # a find_library implementation. We do this because it's easier than + # maintaining a list of languages that can consume C libraries. + lang = None + if self.compiler.language == 'vala': + lang = 'vala' + lib = dependencies.ExternalLibrary(libname, linkargs, language=lang) return ExternalLibraryHolder(lib) def has_argument_method(self, args, kwargs): @@ -981,6 +992,8 @@ class ModuleHolder(InterpreterObject): fn = getattr(self.held_object, method_name) except AttributeError: raise InvalidArguments('Module %s does not have method %s.' % (self.modname, method_name)) + if method_name.startswith('_'): + raise InvalidArguments('Function {!r} in module {!r} is private.'.format(method_name, self.modname)) state = ModuleState() state.build_to_src = os.path.relpath(self.interpreter.environment.get_source_dir(), self.interpreter.environment.get_build_dir()) @@ -994,6 +1007,7 @@ class ModuleHolder(InterpreterObject): state.headers = self.interpreter.build.get_headers() state.man = self.interpreter.build.get_man() state.global_args = self.interpreter.build.global_args + state.project_args = self.interpreter.build.projects_args.get(self.interpreter.subproject, {}) value = fn(state, args, kwargs) return self.interpreter.module_method_callback(value) @@ -1169,7 +1183,7 @@ class Interpreter(): self.builtin = {'meson': MesonMain(build, self)} self.generators = [] self.visited_subdirs = {} - self.global_args_frozen = False + self.args_frozen = False self.subprojects = {} self.subproject_stack = [] self.build_func_dict() @@ -1214,7 +1228,9 @@ class Interpreter(): 'configure_file' : self.func_configure_file, 'include_directories' : self.func_include_directories, 'add_global_arguments' : self.func_add_global_arguments, + 'add_project_arguments' : self.func_add_project_arguments, 'add_global_link_arguments' : self.func_add_global_link_arguments, + 'add_project_link_arguments' : self.func_add_project_link_arguments, 'add_languages' : self.func_add_languages, 'find_program' : self.func_find_program, 'find_library' : self.func_find_library, @@ -1555,7 +1571,7 @@ class Interpreter(): if dirname in self.subproject_stack: fullstack = self.subproject_stack + [dirname] incpath = ' => '.join(fullstack) - raise InterpreterException('Recursive include of subprojects: %s.' % incpath) + 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)) @@ -1565,7 +1581,7 @@ class Interpreter(): raise InterpreterException(msg.format(os.path.join(self.subproject_dir, dirname))) subdir = os.path.join(self.subproject_dir, resolved) os.makedirs(os.path.join(self.build.environment.get_build_dir(), subdir), exist_ok=True) - self.global_args_frozen = True + self.args_frozen = True mlog.log('\nExecuting subproject ', mlog.bold(dirname), '.\n', sep='') subi = Interpreter(self.build, self.backend, dirname, subdir, self.subproject_dir) subi.subprojects = self.subprojects @@ -1856,12 +1872,10 @@ requirements use the version keyword argument instead.''') # Cached dep has the wrong version. Check if an external # dependency or a fallback dependency provides it. cached_dep = None - # Don't re-use cached dep if it wasn't required but this one is, # so we properly go into fallback/error code paths - if 'required' in kwargs and cached_dep is not None: - if not cached_dep.required and kwargs.get('required', True): - cached_dep = None + if kwargs.get('required', True) and not getattr(cached_dep, 'required', False): + cached_dep = None if cached_dep: dep = cached_dep @@ -1908,8 +1922,14 @@ requirements use the version keyword argument instead.''') def dependency_fallback(self, name, kwargs): dirname, varname = self.get_subproject_infos(kwargs) + # Try to execute the subproject try: self.do_subproject(dirname, {}) + # Invalid code is always an error + except InvalidCode: + raise + # If the subproject execution failed in a non-fatal way, don't raise an + # exception; let the caller handle things. except: mlog.log('Also couldn\'t find a fallback subproject in', mlog.bold(os.path.join(self.subproject_dir, dirname)), @@ -1918,13 +1938,11 @@ requirements use the version keyword argument instead.''') try: dep = self.subprojects[dirname].get_variable_method([varname], {}) except KeyError: - mlog.log('Fallback variable', mlog.bold(varname), - 'in the subproject', mlog.bold(dirname), 'does not exist') - return None + raise InvalidCode('Fallback variable {!r} in the subproject ' + '{!r} does not exist'.format(varname, dirname)) if not isinstance(dep, DependencyHolder): - mlog.log('Fallback variable', mlog.bold(varname), - 'in the subproject', mlog.bold(dirname), - 'is not a dependency object.') + raise InvalidCode('Fallback variable {!r} in the subproject {!r} is ' + 'not a dependency object.'.format(varname, dirname)) return None # Check if the version of the declared dependency matches what we want if 'version' in kwargs: @@ -2173,10 +2191,7 @@ requirements use the version keyword argument instead.''') % subdir) self.visited_subdirs[subdir] = True self.subdir = subdir - try: - os.makedirs(os.path.join(self.environment.build_dir, subdir)) - except FileExistsError: - pass + os.makedirs(os.path.join(self.environment.build_dir, subdir), exist_ok=True) buildfilename = os.path.join(self.subdir, environment.build_filename) self.build_def_files.append(buildfilename) absname = os.path.join(self.environment.get_source_dir(), buildfilename) @@ -2279,7 +2294,7 @@ requirements use the version keyword argument instead.''') 'arguments and add it to the appropriate *_args kwarg ' \ 'in each target.' raise InvalidCode(msg) - if self.global_args_frozen: + 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.' @@ -2302,7 +2317,7 @@ requirements use the version keyword argument instead.''') 'arguments and add it to the appropriate *_args kwarg ' \ 'in each target.' raise InvalidCode(msg) - if self.global_args_frozen: + 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.' @@ -2315,6 +2330,39 @@ requirements use the version keyword argument instead.''') 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 not 'language' 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 + + @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 not 'language' 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 def func_environment(self, node, args, kwargs): return EnvironmentVariablesHolder() @@ -2413,7 +2461,7 @@ requirements use the version keyword argument instead.''') self.add_cross_stdlib_info(target) l = targetholder(target, self) self.add_target(name, l.held_object) - self.global_args_frozen = True + self.args_frozen = True return l def get_used_languages(self, target): @@ -2460,6 +2508,9 @@ requirements use the version keyword argument instead.''') value = self.to_native(value) if not self.is_assignable(value): raise InvalidCode('Tried to assign an invalid value to variable.') + # For mutable objects we need to make a copy on assignment + if isinstance(value, MutableInterpreterObject): + value = copy.deepcopy(value) self.set_variable(var_name, value) return value diff --git a/mesonbuild/mesonmain.py b/mesonbuild/mesonmain.py index 943c087..88826f8 100644 --- a/mesonbuild/mesonmain.py +++ b/mesonbuild/mesonmain.py @@ -89,6 +89,10 @@ class MesonApp(): def validate_core_dirs(self, dir1, dir2): ndir1 = os.path.abspath(dir1) ndir2 = os.path.abspath(dir2) + if not os.path.exists(ndir1): + os.makedirs(ndir1) + if not os.path.exists(ndir2): + os.makedirs(ndir2) if not stat.S_ISDIR(os.stat(ndir1).st_mode): raise RuntimeError('%s is not a directory' % dir1) if not stat.S_ISDIR(os.stat(ndir2).st_mode): @@ -121,8 +125,8 @@ itself as required.''' def check_pkgconfig_envvar(self, env): curvar = os.environ.get('PKG_CONFIG_PATH', '') if curvar != env.coredata.pkgconf_envvar: - mlog.log(mlog.red("WARNING:"), 'PKG_CONFIG_PATH has changed between invocations from "%s" to "%s".' % - (env.coredata.pkgconf_envvar, curvar)) + mlog.warning('PKG_CONFIG_PATH has changed between invocations from "%s" to "%s".' % + (env.coredata.pkgconf_envvar, curvar)) env.coredata.pkgconf_envvar = curvar def generate(self): diff --git a/mesonbuild/mlog.py b/mesonbuild/mlog.py index dab51bd..cded2b0 100644 --- a/mesonbuild/mlog.py +++ b/mesonbuild/mlog.py @@ -53,6 +53,9 @@ def red(text): def green(text): return AnsiDecorator(text, "\033[1;32m") +def yellow(text): + return AnsiDecorator(text, "\033[1;33m") + def cyan(text): return AnsiDecorator(text, "\033[1;36m") @@ -81,3 +84,6 @@ def log(*args, **kwargs): if colorize_console: arr = process_markup(args, True) print(*arr, **kwargs) + +def warning(*args, **kwargs): + log(yellow('WARNING:'), *args, **kwargs) diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index e9a4a8a..faca857 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -18,6 +18,7 @@ functionality such as gobject-introspection and gresources.''' from .. import build import os import sys +import copy import subprocess from ..mesonlib import MesonException from .. import dependencies @@ -31,7 +32,8 @@ gresource_warning_printed = False class GnomeModule: - def get_native_glib_version(self, state): + @staticmethod + def _get_native_glib_version(state): global native_glib_version if native_glib_version is None: glib_dep = dependencies.PkgConfigDependency( @@ -42,11 +44,11 @@ class GnomeModule: def __print_gresources_warning(self, state): global gresource_warning_printed if not gresource_warning_printed: - if mesonlib.version_compare(self.get_native_glib_version(state), '< 2.50.0'): - mlog.log('Warning, GLib compiled dependencies do not work fully ' - 'with versions of GLib older than 2.50.0.\n' - 'See the following upstream issue:', - mlog.bold('https://bugzilla.gnome.org/show_bug.cgi?id=745754')) + if mesonlib.version_compare(self._get_native_glib_version(state), '< 2.50.2'): + mlog.warning('GLib compiled dependencies do not work fully ' + 'with versions of GLib older than 2.50.2.\n' + 'See the following upstream issue:', + mlog.bold('https://bugzilla.gnome.org/show_bug.cgi?id=745754')) gresource_warning_printed = True return [] @@ -59,9 +61,6 @@ class GnomeModule: if not isinstance(source_dirs, list): source_dirs = [source_dirs] - # Always include current directory, but after paths set by user - source_dirs.append(os.path.join(state.environment.get_source_dir(), state.subdir)) - if len(args) < 2: raise MesonException('Not enough arguments; The name of the resource and the path to the XML file are required') @@ -69,8 +68,8 @@ class GnomeModule: if not isinstance(dependencies, list): dependencies = [dependencies] - if mesonlib.version_compare(self.get_native_glib_version(state), - '< 2.48.2'): + glib_version = self._get_native_glib_version(state) + if mesonlib.version_compare(glib_version, '< 2.48.2'): if len(dependencies) > 0: raise MesonException( 'The "dependencies" argument of gnome.compile_resources() ' @@ -86,18 +85,18 @@ class GnomeModule: else: raise RuntimeError('Unreachable code.') - kwargs['depend_files'] = self.get_gresource_dependencies( + depend_files, depends, subdirs = self._get_gresource_dependencies( state, ifile, source_dirs, dependencies) - for source_dir in source_dirs: - sourcedir = os.path.join(state.build_to_src, state.subdir, source_dir) - cmd += ['--sourcedir', sourcedir] + # Make source dirs relative to build dir now + source_dirs = [os.path.join(state.build_to_src, state.subdir, d) for d in source_dirs] + # Always include current directory, but after paths set by user + source_dirs.append(os.path.join(state.build_to_src, state.subdir)) + # Ensure build directories of generated deps are included + source_dirs += subdirs - if len(dependencies) > 0: - # Add the build variant of each sourcedir if we have any - # generated dependencies. - sourcedir = os.path.join(state.subdir, source_dir) - cmd += ['--sourcedir', sourcedir] + for source_dir in set(source_dirs): + cmd += ['--sourcedir', source_dir] if 'c_name' in kwargs: cmd += ['--c-name', kwargs.pop('c_name')] @@ -105,17 +104,30 @@ class GnomeModule: cmd += mesonlib.stringlistify(kwargs.pop('extra_args', [])) - kwargs['command'] = cmd kwargs['input'] = args[1] kwargs['output'] = args[0] + '.c' + kwargs['depends'] = depends + if mesonlib.version_compare(glib_version, '< 2.50.2'): + # This will eventually go out of sync if dependencies are added + kwargs['depend_files'] = depend_files + kwargs['command'] = cmd + else: + depfile = kwargs['output'] + '.d' + kwargs['depfile'] = depfile + kwargs['command'] = copy.copy(cmd) + ['--dependency-file', '@DEPFILE@'] target_c = build.CustomTarget(args[0] + '_c', state.subdir, kwargs) - kwargs['output'] = args[0] + '.h' - target_h = build.CustomTarget(args[0] + '_h', state.subdir, kwargs) - return [target_c, target_h] - def get_gresource_dependencies(self, state, input_file, source_dirs, dependencies): - self.__print_gresources_warning(state) + h_kwargs = { + 'command': cmd, + 'input': args[1], + 'output': args[0] + '.h', + # The header doesn't actually care about the files yet it errors if missing + 'depends': depends + } + target_h = build.CustomTarget(args[0] + '_h', state.subdir, h_kwargs) + return [target_c, target_h] + def _get_gresource_dependencies(self, state, input_file, source_dirs, dependencies): for dep in dependencies: if not isinstance(dep, interpreter.CustomTargetHolder) and not \ isinstance(dep, mesonlib.File): @@ -130,12 +142,13 @@ class GnomeModule: for source_dir in source_dirs: cmd += ['--sourcedir', os.path.join(state.subdir, source_dir)] + cmd += ['--sourcedir', state.subdir] # Current dir pc = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True, cwd=state.environment.get_source_dir()) (stdout, _) = pc.communicate() if pc.returncode != 0: - mlog.log(mlog.bold('Warning:'), 'glib-compile-resources has failed to get the dependencies for {}'.format(cmd[1])) + mlog.warning('glib-compile-resources has failed to get the dependencies for {}'.format(cmd[1])) raise subprocess.CalledProcessError(pc.returncode, cmd) dep_files = stdout.split('\n')[:-1] @@ -153,6 +166,8 @@ class GnomeModule: return os.path.exists(os.path.join(state.environment.get_source_dir(), f)) missing_dep_files = [f for f in dep_files if not exists_in_srcdir(f)] + depends = [] + subdirs = [] for missing in missing_dep_files: found = False missing_basename = os.path.basename(missing) @@ -163,6 +178,7 @@ class GnomeModule: found = True dep_files.remove(missing) dep_files.append(dep) + subdirs.append(dep.subdir) break elif isinstance(dep, interpreter.CustomTargetHolder): if dep.held_object.get_basename() == missing_basename: @@ -173,6 +189,8 @@ class GnomeModule: is_built=True, subdir=dep.held_object.get_subdir(), fname=dep.held_object.get_basename())) + depends.append(dep.held_object) + subdirs.append(dep.held_object.get_subdir()) break if not found: @@ -182,9 +200,10 @@ class GnomeModule: 'gnome.compile_resources() using the "dependencies" ' 'keyword argument.' % (missing, input_file)) - return dep_files + return dep_files, depends, subdirs - def get_link_args(self, state, lib, depends=None): + @staticmethod + def _get_link_args(state, lib, depends=None): link_command = ['-l%s' % lib.name] if isinstance(lib, build.SharedLibrary): link_command += ['-L%s' % @@ -194,7 +213,8 @@ class GnomeModule: depends.append(lib) return link_command - def get_include_args(self, state, include_dirs, prefix='-I'): + @staticmethod + def _get_include_args(state, include_dirs, prefix='-I'): if not include_dirs: return [] @@ -222,7 +242,7 @@ class GnomeModule: return dirs_str - def get_dependencies_flags(self, deps, state, depends=None): + def _get_dependencies_flags(self, deps, state, depends=None): cflags = set() ldflags = set() gi_includes = set() @@ -233,14 +253,14 @@ class GnomeModule: if hasattr(dep, 'held_object'): dep = dep.held_object if isinstance(dep, dependencies.InternalDependency): - cflags.update(self.get_include_args( state, dep.include_directories)) + cflags.update(self._get_include_args(state, dep.include_directories)) for lib in dep.libraries: - ldflags.update(self.get_link_args(state, lib.held_object, depends)) - libdepflags = self.get_dependencies_flags(lib.held_object.get_external_deps(), state, depends) + ldflags.update(self._get_link_args(state, lib.held_object, depends)) + libdepflags = self._get_dependencies_flags(lib.held_object.get_external_deps(), state, depends) cflags.update(libdepflags[0]) ldflags.update(libdepflags[1]) gi_includes.update(libdepflags[2]) - extdepflags = self.get_dependencies_flags(dep.ext_deps, state, depends) + extdepflags = self._get_dependencies_flags(dep.ext_deps, state, depends) cflags.update(extdepflags[0]) ldflags.update(extdepflags[1]) gi_includes.update(extdepflags[2]) @@ -294,7 +314,7 @@ class GnomeModule: except Exception: global girwarning_printed if not girwarning_printed: - mlog.log(mlog.bold('Warning:'), 'gobject-introspection dependency was not found, disabling gir generation.') + mlog.warning('gobject-introspection dependency was not found, disabling gir generation.') girwarning_printed = True return [] pkgargs = pkgstr.decode().strip().split() @@ -314,14 +334,14 @@ class GnomeModule: 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 += self.get_include_args(state, girtarget.get_include_dirs()) + scan_command += self._get_include_args(state, girtarget.get_include_dirs()) if 'link_with' in kwargs: link_with = kwargs.pop('link_with') if not isinstance(link_with, list): link_with = [link_with] for link in link_with: - scan_command += self.get_link_args(state, link.held_object, depends) + scan_command += self._get_link_args(state, link.held_object, depends) if 'includes' in kwargs: includes = kwargs.pop('includes') @@ -348,6 +368,8 @@ class GnomeModule: cflags = [] if state.global_args.get('c'): cflags += state.global_args['c'] + if state.project_args.get('c'): + cflags += state.project_args['c'] for compiler in state.compilers: if compiler.get_language() == 'c': sanitize = compiler.get_options().get('b_sanitize') @@ -384,7 +406,7 @@ class GnomeModule: # ldflags will be misinterpreted by gir scanner (showing # spurious dependencies) but building GStreamer fails if they # are not used here. - cflags, ldflags, gi_includes = self.get_dependencies_flags(deps, state, depends) + cflags, ldflags, gi_includes = self._get_dependencies_flags(deps, state, depends) scan_command += list(cflags) # need to put our output directory first as we need to use the # generated libraries instead of any possibly installed system/prefix @@ -402,9 +424,9 @@ class GnomeModule: if not isinstance(incd.held_object, (str, build.IncludeDirs)): raise MesonException( 'Gir include dirs should be include_directories().') - scan_command += self.get_include_args(state, inc_dirs) - scan_command += self.get_include_args(state, gir_inc_dirs + inc_dirs, - prefix='--add-include-path=') + scan_command += self._get_include_args(state, inc_dirs) + scan_command += self._get_include_args(state, gir_inc_dirs + inc_dirs, + prefix='--add-include-path=') if isinstance(girtarget, build.Executable): scan_command += ['--program', girtarget] @@ -424,8 +446,8 @@ class GnomeModule: typelib_output = '%s-%s.typelib' % (ns, nsversion) typelib_cmd = ['g-ir-compiler', scan_target, '--output', '@OUTPUT@'] - typelib_cmd += self.get_include_args(state, gir_inc_dirs, - prefix='--includedir=') + typelib_cmd += self._get_include_args(state, gir_inc_dirs, + prefix='--includedir=') for dep in deps: if hasattr(dep, 'held_object'): dep = dep.held_object @@ -566,24 +588,24 @@ class GnomeModule: '--headerdir=' + header_dir, '--mainfile=' + main_file, '--modulename=' + modulename] - args += self.unpack_args('--htmlargs=', 'html_args', kwargs) - args += self.unpack_args('--scanargs=', 'scan_args', kwargs) - args += self.unpack_args('--scanobjsargs=', 'scanobjs_args', kwargs) - args += self.unpack_args('--gobjects-types-file=', 'gobject_typesfile', kwargs, state) - args += self.unpack_args('--fixxrefargs=', 'fixxref_args', kwargs) - args += self.unpack_args('--html-assets=', 'html_assets', kwargs, state) - args += self.unpack_args('--content-files=', 'content_files', kwargs, state) - args += self.unpack_args('--ignore-headers=', 'ignore_headers', kwargs) - args += self.unpack_args('--installdir=', 'install_dir', kwargs, state) - args += self.get_build_args(kwargs, state) + args += self._unpack_args('--htmlargs=', 'html_args', kwargs) + args += self._unpack_args('--scanargs=', 'scan_args', kwargs) + args += self._unpack_args('--scanobjsargs=', 'scanobjs_args', kwargs) + args += self._unpack_args('--gobjects-types-file=', 'gobject_typesfile', kwargs, state) + args += self._unpack_args('--fixxrefargs=', 'fixxref_args', kwargs) + args += self._unpack_args('--html-assets=', 'html_assets', kwargs, state) + args += self._unpack_args('--content-files=', 'content_files', kwargs, state) + args += self._unpack_args('--ignore-headers=', 'ignore_headers', kwargs) + args += self._unpack_args('--installdir=', 'install_dir', kwargs, state) + args += self._get_build_args(kwargs, state) res = [build.RunTarget(targetname, command[0], command[1:] + args, [], state.subdir)] if kwargs.get('install', True): res.append(build.InstallScript(command + args)) return res - def get_build_args(self, kwargs, state): + def _get_build_args(self, kwargs, state): args = [] - cflags, ldflags, gi_includes = self.get_dependencies_flags(kwargs.get('dependencies', []), state) + cflags, ldflags, gi_includes = self._get_dependencies_flags(kwargs.get('dependencies', []), state) inc_dirs = kwargs.get('include_directories', []) if not isinstance(inc_dirs, list): inc_dirs = [inc_dirs] @@ -591,7 +613,7 @@ class GnomeModule: if not isinstance(incd.held_object, (str, build.IncludeDirs)): raise MesonException( 'Gir include dirs should be include_directories().') - cflags.update(self.get_include_args(state, inc_dirs)) + cflags.update(self._get_include_args(state, inc_dirs)) if cflags: args += ['--cflags=%s' % ' '.join(cflags)] if ldflags: @@ -611,8 +633,8 @@ class GnomeModule: raise MesonException('Argument must be a string') return os.path.join('share/gtkdoc/html', modulename) - - def unpack_args(self, arg, kwarg_name, kwargs, expend_file_state=None): + @staticmethod + def _unpack_args(arg, kwarg_name, kwargs, expend_file_state=None): if kwarg_name not in kwargs: return [] @@ -704,9 +726,9 @@ class GnomeModule: if 'install_dir' not in custom_kwargs: custom_kwargs['install_dir'] = \ state.environment.coredata.get_builtin_option('includedir') - h_target = self.make_mkenum_custom_target(state, h_sources, - h_output, h_cmd, - custom_kwargs) + h_target = self._make_mkenum_custom_target(state, h_sources, + h_output, h_cmd, + custom_kwargs) targets.append(h_target) if c_template is not None: @@ -722,9 +744,9 @@ class GnomeModule: custom_kwargs['depends'] += [h_target] else: custom_kwargs['depends'] = h_target - c_target = self.make_mkenum_custom_target(state, c_sources, - c_output, c_cmd, - custom_kwargs) + c_target = self._make_mkenum_custom_target(state, c_sources, + c_output, c_cmd, + custom_kwargs) targets.insert(0, c_target) if c_template is None and h_template is None: @@ -733,15 +755,16 @@ class GnomeModule: if 'install_dir' not in custom_kwargs: custom_kwargs['install_dir'] = \ state.environment.coredata.get_builtin_option('includedir') - target = self.make_mkenum_custom_target(state, sources, basename, - generic_cmd, custom_kwargs) + target = self._make_mkenum_custom_target(state, sources, basename, + generic_cmd, custom_kwargs) return target elif len(targets) == 1: return targets[0] else: return targets - def make_mkenum_custom_target(self, state, sources, output, cmd, kwargs): + @staticmethod + def _make_mkenum_custom_target(state, sources, output, cmd, kwargs): custom_kwargs = { 'input': sources, 'output': output, diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py index 7556375..3ecb40d 100644 --- a/mesonbuild/modules/pkgconfig.py +++ b/mesonbuild/modules/pkgconfig.py @@ -34,7 +34,7 @@ class PkgConfigModule: return l.name # In other cases, we can't guarantee that the compiler will be able to # find the library via '-lfoo', so tell the user that. - mlog.log(mlog.red('WARNING:'), msg.format(l.name, 'name_prefix', l.name, pcfile)) + mlog.warning(msg.format(l.name, 'name_prefix', l.name, pcfile)) return l.name def generate_pkgconfig_file(self, state, libraries, subdirs, name, description, @@ -79,7 +79,7 @@ class PkgConfigModule: # If using a custom suffix, the compiler may not be able to # find the library if l.name_suffix_set: - mlog.log(mlog.red('WARNING:'), msg.format(l.name, 'name_suffix', lname, pcfile)) + mlog.warning(msg.format(l.name, 'name_suffix', lname, pcfile)) yield '-l%s' % lname if len(libraries) > 0: ofile.write('Libs: {}\n'.format(' '.join(generate_libs_flags(libraries)))) diff --git a/mesonbuild/modules/qt4.py b/mesonbuild/modules/qt4.py index 81a70fc..5108baa 100644 --- a/mesonbuild/modules/qt4.py +++ b/mesonbuild/modules/qt4.py @@ -12,26 +12,27 @@ # See the License for the specific language governing permissions and # limitations under the License. -from .. import dependencies, mlog import os, subprocess +from .. import mlog from .. import build from ..mesonlib import MesonException +from ..dependencies import Qt4Dependency import xml.etree.ElementTree as ET class Qt4Module(): - def __init__(self): - mlog.log('Detecting Qt tools.') - # The binaries have different names on different - # distros. Joy. - self.moc = dependencies.ExternalProgram('moc-qt4', silent=True) - if not self.moc.found(): - self.moc = dependencies.ExternalProgram('moc', silent=True) - self.uic = dependencies.ExternalProgram('uic-qt4', silent=True) - if not self.uic.found(): - self.uic = dependencies.ExternalProgram('uic', silent=True) - self.rcc = dependencies.ExternalProgram('rcc-qt4', silent=True) - if not self.rcc.found(): - self.rcc = dependencies.ExternalProgram('rcc', silent=True) + tools_detected = False + + def _detect_tools(self, env): + if self.tools_detected: + return + mlog.log('Detecting Qt4 tools') + # FIXME: We currently require Qt4 to exist while importing the module. + # We should make it gracefully degrade and not create any targets if + # the import is marked as 'optional' (not implemented yet) + kwargs = {'required': 'true', 'modules': 'Core', 'silent': 'true'} + qt4 = Qt4Dependency(env, kwargs) + # Get all tools and then make sure that they are the right version + self.moc, self.uic, self.rcc = qt4.compilers_detect() # Moc, uic and rcc write their version strings to stderr. # Moc and rcc return a non-zero result when doing so. # What kind of an idiot thought that was a good idea? @@ -80,6 +81,7 @@ class Qt4Module(): % (' '.join(self.rcc.fullpath), rcc_ver.split()[-1])) else: mlog.log(' rcc:', mlog.red('NO')) + self.tools_detected = True def parse_qrc(self, state, fname): abspath = os.path.join(state.environment.source_dir, state.subdir, fname) @@ -90,7 +92,7 @@ class Qt4Module(): result = [] for child in root[0]: if child.tag != 'file': - mlog.log("Warning, malformed rcc file: ", os.path.join(state.subdir, fname)) + mlog.warning("malformed rcc file: ", os.path.join(state.subdir, fname)) break else: result.append(os.path.join(state.subdir, relative_part, child.text)) @@ -115,18 +117,29 @@ class Qt4Module(): if not isinstance(srctmp, list): srctmp = [srctmp] sources = args[1:] + srctmp + self._detect_tools(state.environment) + err_msg = "{0} sources specified and couldn't find {1}, " \ + "please check your qt4 installation" + if len(moc_headers) + len(moc_sources) > 0 and not self.moc.found(): + raise MesonException(err_msg.format('MOC', 'moc-qt4')) if len(rcc_files) > 0: - rcc_kwargs = {'output' : '@BASENAME@.cpp', - 'arguments' : ['@INPUT@', '-o', '@OUTPUT@']} - rcc_gen = build.Generator([self.rcc], rcc_kwargs) - rcc_output = build.GeneratedList(rcc_gen) + if not self.rcc.found(): + raise MesonException(err_msg.format('RCC', 'rcc-qt4')) qrc_deps = [] for i in rcc_files: qrc_deps += self.parse_qrc(state, i) - rcc_output.extra_depends = qrc_deps - [rcc_output.add_file(os.path.join(state.subdir, a)) for a in rcc_files] - sources.append(rcc_output) + basename = os.path.split(rcc_files[0])[1] + name = 'qt4-' + basename.replace('.', '_') + rcc_kwargs = {'input' : rcc_files, + 'output' : name + '.cpp', + 'command' : [self.rcc, '-o', '@OUTPUT@', '@INPUT@'], + 'depend_files' : qrc_deps, + } + res_target = build.CustomTarget(name, state.subdir, rcc_kwargs) + sources.append(res_target) if len(ui_files) > 0: + if not self.uic.found(): + raise MesonException(err_msg.format('UIC', 'uic-qt4')) ui_kwargs = {'output' : 'ui_@BASENAME@.h', 'arguments' : ['-o', '@OUTPUT@', '@INPUT@']} ui_gen = build.Generator([self.uic], ui_kwargs) @@ -150,6 +163,6 @@ class Qt4Module(): return sources def initialize(): - mlog.log('Warning, rcc dependencies will not work properly until this upstream issue is fixed:', - mlog.bold('https://bugreports.qt.io/browse/QTBUG-45460')) + mlog.warning('rcc dependencies will not work properly until this upstream issue is fixed:', + mlog.bold('https://bugreports.qt.io/browse/QTBUG-45460')) return Qt4Module() diff --git a/mesonbuild/modules/qt5.py b/mesonbuild/modules/qt5.py index 4f19b78..52ad155 100644 --- a/mesonbuild/modules/qt5.py +++ b/mesonbuild/modules/qt5.py @@ -12,27 +12,27 @@ # See the License for the specific language governing permissions and # limitations under the License. -from .. import dependencies, mlog import os, subprocess +from .. import mlog from .. import build from ..mesonlib import MesonException +from ..dependencies import Qt5Dependency import xml.etree.ElementTree as ET class Qt5Module(): + tools_detected = False - def __init__(self): - mlog.log('Detecting Qt tools.') - # The binaries have different names on different - # distros. Joy. - self.moc = dependencies.ExternalProgram('moc-qt5', silent=True) - if not self.moc.found(): - self.moc = dependencies.ExternalProgram('moc', silent=True) - self.uic = dependencies.ExternalProgram('uic-qt5', silent=True) - if not self.uic.found(): - self.uic = dependencies.ExternalProgram('uic', silent=True) - self.rcc = dependencies.ExternalProgram('rcc-qt5', silent=True) - if not self.rcc.found(): - self.rcc = dependencies.ExternalProgram('rcc', silent=True) + def _detect_tools(self, env): + if self.tools_detected: + return + mlog.log('Detecting Qt5 tools') + # FIXME: We currently require Qt5 to exist while importing the module. + # We should make it gracefully degrade and not create any targets if + # the import is marked as 'optional' (not implemented yet) + kwargs = {'required': 'true', 'modules': 'Core', 'silent': 'true'} + qt5 = Qt5Dependency(env, kwargs) + # Get all tools and then make sure that they are the right version + self.moc, self.uic, self.rcc = qt5.compilers_detect() # Moc, uic and rcc write their version strings to stderr. # Moc and rcc return a non-zero result when doing so. # What kind of an idiot thought that was a good idea? @@ -87,6 +87,7 @@ class Qt5Module(): % (' '.join(self.rcc.fullpath), rcc_ver.split()[-1])) else: mlog.log(' rcc:', mlog.red('NO')) + self.tools_detected = True def parse_qrc(self, state, fname): abspath = os.path.join(state.environment.source_dir, state.subdir, fname) @@ -97,7 +98,7 @@ class Qt5Module(): result = [] for child in root[0]: if child.tag != 'file': - mlog.log("Warning, malformed rcc file: ", os.path.join(state.subdir, fname)) + mlog.warning("malformed rcc file: ", os.path.join(state.subdir, fname)) break else: result.append(os.path.join(state.subdir, relative_part, child.text)) @@ -122,7 +123,14 @@ class Qt5Module(): if not isinstance(srctmp, list): srctmp = [srctmp] sources = args[1:] + srctmp + self._detect_tools(state.environment) + err_msg = "{0} sources specified and couldn't find {1}, " \ + "please check your qt5 installation" + if len(moc_headers) + len(moc_sources) > 0 and not self.moc.found(): + raise MesonException(err_msg.format('MOC', 'moc-qt5')) if len(rcc_files) > 0: + if not self.rcc.found(): + raise MesonException(err_msg.format('RCC', 'rcc-qt5')) qrc_deps = [] for i in rcc_files: qrc_deps += self.parse_qrc(state, i) @@ -132,11 +140,12 @@ class Qt5Module(): 'command' : [self.rcc, '-o', '@OUTPUT@', '@INPUT@'], 'depend_files' : qrc_deps, } - res_target = build.CustomTarget(basename.replace('.', '_'), - state.subdir, - rcc_kwargs) + name = 'qt5-' + basename.replace('.', '_') + res_target = build.CustomTarget(name, state.subdir, rcc_kwargs) sources.append(res_target) if len(ui_files) > 0: + if not self.uic.found(): + raise MesonException(err_msg.format('UIC', 'uic-qt5')) ui_kwargs = {'output' : 'ui_@BASENAME@.h', 'arguments' : ['-o', '@OUTPUT@', '@INPUT@']} ui_gen = build.Generator([self.uic], ui_kwargs) @@ -160,6 +169,6 @@ class Qt5Module(): return sources def initialize(): - mlog.log('Warning, rcc dependencies will not work reliably until this upstream issue is fixed:', - mlog.bold('https://bugreports.qt.io/browse/QTBUG-45460')) + mlog.warning('rcc dependencies will not work reliably until this upstream issue is fixed:', + mlog.bold('https://bugreports.qt.io/browse/QTBUG-45460')) return Qt5Module() diff --git a/mesonbuild/modules/rpm.py b/mesonbuild/modules/rpm.py index 13aa20b..2c9ed57 100644 --- a/mesonbuild/modules/rpm.py +++ b/mesonbuild/modules/rpm.py @@ -63,8 +63,8 @@ class RPMModule: so_installed = True elif isinstance(target, build.StaticLibrary) and target.need_install: to_delete.add('%%{buildroot}%%{_libdir}/%s' % target.get_filename()) - mlog.log('Warning, removing', mlog.bold(target.get_filename()), - 'from package because packaging static libs not recommended') + mlog.warning('removing', mlog.bold(target.get_filename()), + 'from package because packaging static libs not recommended') elif isinstance(target, gnome.GirTarget) and target.should_install(): files_devel.add('%%{_datadir}/gir-1.0/%s' % target.get_filename()[0]) elif isinstance(target, gnome.TypelibTarget) and target.should_install(): @@ -94,14 +94,13 @@ class RPMModule: for compiler in compiler_deps: fn.write('BuildRequires: %s\n' % compiler) for dep in state.environment.coredata.deps: - fn.write('BuildRequires: pkgconfig(%s)\n' % dep) + fn.write('BuildRequires: pkgconfig(%s)\n' % dep[0]) for lib in state.environment.coredata.ext_libs.values(): fn.write('BuildRequires: %s # FIXME\n' % lib.fullpath) - mlog.log('Warning, replace', mlog.bold(lib.fullpath), - 'with real package.', - 'You can use following command to find package which ' - 'contains this lib:', - mlog.bold('dnf provides %s' % lib.fullpath)) + mlog.warning('replace', mlog.bold(lib.fullpath), 'with real package.', + 'You can use following command to find package which ' + 'contains this lib:', + mlog.bold('dnf provides %s' % lib.fullpath)) for prog in state.environment.coredata.ext_progs.values(): if not prog.found(): fn.write('BuildRequires: %{_bindir}/%s # FIXME\n' % diff --git a/mesonbuild/scripts/gtkdochelper.py b/mesonbuild/scripts/gtkdochelper.py index 220801d..e34b541 100755 --- a/mesonbuild/scripts/gtkdochelper.py +++ b/mesonbuild/scripts/gtkdochelper.py @@ -90,10 +90,11 @@ def build_gtkdoc(source_root, build_root, doc_subdir, src_subdir, f_abs = os.path.join(doc_src, f) shutil.copyfile(f_abs, os.path.join(htmldir, os.path.basename(f_abs))) - scan_cmd = ['gtkdoc-scan', - '--module=' + module, - '--source-dir=' + abs_src, - '--ignore-headers=' + ignore_headers] + scan_args + scan_cmd = ['gtkdoc-scan', '--module=' + module, '--source-dir=' + abs_src] + if ignore_headers: + scan_cmd.append('--ignore-headers=' + ' '.join(ignore_headers)) + # Add user-specified arguments + scan_cmd += scan_args gtkdoc_run_check(scan_cmd, abs_out) if gobject_typesfile: diff --git a/mesonbuild/scripts/regen_checker.py b/mesonbuild/scripts/regen_checker.py index ddf4943..e8e1077 100755 --- a/mesonbuild/scripts/regen_checker.py +++ b/mesonbuild/scripts/regen_checker.py @@ -52,7 +52,7 @@ def run(args): regeninfo = pickle.load(f) with open(coredata, 'rb') as f: coredata = pickle.load(f) - mesonscript = coredata.meson_script_file + mesonscript = coredata.meson_script_launcher backend = coredata.get_builtin_option('backend') regen_timestamp = os.stat(dumpfile).st_mtime if need_regen(regeninfo, regen_timestamp): diff --git a/mesonbuild/scripts/scanbuild.py b/mesonbuild/scripts/scanbuild.py index f13a1a4..dd74ce8 100644 --- a/mesonbuild/scripts/scanbuild.py +++ b/mesonbuild/scripts/scanbuild.py @@ -16,10 +16,10 @@ import subprocess import shutil import tempfile -def scanbuild(srcdir, blddir, privdir, logdir, args): +def scanbuild(exename, srcdir, blddir, privdir, logdir, args): with tempfile.TemporaryDirectory(dir=privdir) as scandir: - meson_cmd = ['scan-build'] + args - build_cmd = ['scan-build', '-o', logdir, 'ninja'] + meson_cmd = [exename] + args + build_cmd = [exename, '-o', logdir, 'ninja'] rc = subprocess.call(meson_cmd + [srcdir, scandir]) if rc != 0: return rc @@ -32,7 +32,8 @@ def run(args): privdir = os.path.join(blddir, 'meson-private') logdir = os.path.join(blddir, 'meson-logs/scanbuild') shutil.rmtree(logdir, ignore_errors=True) - if not shutil.which('scan-build'): - print('Scan-build not installed') + exename = os.environ.get('SCANBUILD', 'scan-build') + if not shutil.which(exename): + print('Scan-build not installed.') return 1 - return scanbuild(srcdir, blddir, privdir, logdir, meson_cmd) + return scanbuild(exename, srcdir, blddir, privdir, logdir, meson_cmd) diff --git a/mesonbuild/scripts/yelphelper.py b/mesonbuild/scripts/yelphelper.py index 00d713a..524ef45 100644 --- a/mesonbuild/scripts/yelphelper.py +++ b/mesonbuild/scripts/yelphelper.py @@ -34,7 +34,7 @@ def build_pot(srcdir, project_id, sources): # Must be relative paths sources = [os.path.join('C', source) for source in sources] outfile = os.path.join(srcdir, project_id + '.pot') - subprocess.call(['itstool', '-o', outfile, *sources]) + subprocess.call(['itstool', '-o', outfile]+sources) def update_po(srcdir, project_id, langs): potfile = os.path.join(srcdir, project_id + '.pot') @@ -55,9 +55,8 @@ def merge_translations(blddir, sources, langs): for lang in langs: subprocess.call([ 'itstool', '-m', os.path.join(blddir, lang, lang + '.gmo'), - '-o', os.path.join(blddir, lang), - *sources, - ]) + '-o', os.path.join(blddir, lang) + ]+sources) def install_help(srcdir, blddir, sources, media, langs, install_dir, destdir, project_id, symlinks): c_install_dir = os.path.join(install_dir, 'C', project_id) @@ -75,7 +74,7 @@ def install_help(srcdir, blddir, sources, media, langs, install_dir, destdir, pr outfile = os.path.join(indir, m) if not os.path.exists(infile): if lang == 'C': - mlog.log(mlog.bold('Warning:'), 'Media file "%s" did not exist in C directory' %m) + mlog.warning('Media file "%s" did not exist in C directory' %m) elif symlinks: srcfile = os.path.join(c_install_dir, m) mlog.log('Symlinking %s to %s.' %(outfile, srcfile)) diff --git a/run_project_tests.py b/run_project_tests.py index 22e92b8..15d656b 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -326,6 +326,28 @@ def have_d_compiler(): return True return False +def have_java(): + if shutil.which('javac') and shutil.which('java'): + return True + return False + +def using_backend(backends): + if isinstance(backends, str): + backends = (backends,) + for backend in backends: + if backend == 'ninja': + if not backend_flags: + return True + elif backend == 'xcode': + if backend_flags == '--backend=xcode': + return True + elif backend == 'vs': + if backend_flags.startswith('--backend=vs'): + return True + else: + raise AssertionError('Unknown backend type: ' + backend) + return False + def detect_tests_to_run(): all_tests = [] all_tests.append(('common', gather_tests('test cases/common'), False)) @@ -338,15 +360,15 @@ def detect_tests_to_run(): all_tests.append(('platform-windows', gather_tests('test cases/windows'), False if mesonlib.is_windows() 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(('java', gather_tests('test cases/java'), False if not mesonlib.is_osx() and shutil.which('javac') else True)) - all_tests.append(('C#', gather_tests('test cases/csharp'), False if shutil.which('mcs') else True)) - all_tests.append(('vala', gather_tests('test cases/vala'), False if shutil.which('valac') else True)) - all_tests.append(('rust', gather_tests('test cases/rust'), False if shutil.which('rustc') else True)) - all_tests.append(('d', gather_tests('test cases/d'), False if have_d_compiler() else True)) - all_tests.append(('objective c', gather_tests('test cases/objc'), False if not mesonlib.is_windows() else True)) - all_tests.append(('fortran', gather_tests('test cases/fortran'), False if shutil.which('gfortran') else True)) - all_tests.append(('swift', gather_tests('test cases/swift'), False if shutil.which('swiftc') else True)) - all_tests.append(('python3', gather_tests('test cases/python3'), False if shutil.which('python3') 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)) + all_tests.append(('rust', gather_tests('test cases/rust'), False if using_backend('ninja') and shutil.which('rustc') else True)) + all_tests.append(('d', gather_tests('test cases/d'), False if using_backend('ninja') and have_d_compiler() else True)) + all_tests.append(('objective c', gather_tests('test cases/objc'), False if using_backend(('ninja', 'xcode')) and not mesonlib.is_windows() else True)) + all_tests.append(('fortran', gather_tests('test cases/fortran'), False if using_backend('ninja') and shutil.which('gfortran') else True)) + all_tests.append(('swift', gather_tests('test cases/swift'), False if using_backend(('ninja', 'xcode')) and shutil.which('swiftc') else True)) + all_tests.append(('python3', gather_tests('test cases/python3'), False if using_backend('ninja') and shutil.which('python3') else True)) return all_tests def run_tests(extra_args): @@ -379,7 +401,7 @@ def run_tests(extra_args): futures.append((testname, t, result)) for (testname, t, result) in futures: result = result.result() - if result is None: + if result is None or 'MESON_SKIP_TEST' in result.stdo: print('Skipping:', t) current_test = ET.SubElement(current_suite, 'testcase', {'name' : testname, 'classname' : name}) @@ -490,6 +512,21 @@ if __name__ == '__main__': options = parser.parse_args() setup_commands(options.backend) + # Appveyor sets the `platform` environment variable which completely messes + # up building with the vs2010 and vs2015 backends. + # + # Specifically, MSBuild reads the `platform` environment variable to set + # the configured value for the platform (Win32/x64/arm), which breaks x86 + # builds. + # + # Appveyor setting this also breaks our 'native build arch' detection for + # Windows in environment.py:detect_windows_arch() by overwriting the value + # of `platform` set by vcvarsall.bat. + # + # While building for x86, `platform` should be unset. + if 'APPVEYOR' in os.environ and os.environ['arch'] == 'x86': + os.environ.pop('platform') + script_dir = os.path.split(__file__)[0] if script_dir != '': os.chdir(script_dir) diff --git a/run_unittests.py b/run_unittests.py index cf30276..44e190e 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -19,7 +19,7 @@ import re, json import tempfile import mesonbuild.environment from mesonbuild.environment import detect_ninja -from mesonbuild.dependencies import PkgConfigDependency +from mesonbuild.dependencies import PkgConfigDependency, Qt5Dependency def get_soname(fname): # HACK, fix to not use shell. @@ -59,6 +59,7 @@ class LinuxlikeTests(unittest.TestCase): self.ninja_command = [detect_ninja(), '-C', self.builddir] self.common_test_dir = os.path.join(src_root, 'test cases/common') self.vala_test_dir = os.path.join(src_root, 'test cases/vala') + self.framework_test_dir = os.path.join(src_root, 'test cases/frameworks') self.output = b'' self.orig_env = os.environ.copy() @@ -67,19 +68,29 @@ class LinuxlikeTests(unittest.TestCase): os.environ = self.orig_env super().tearDown() + def _run(self, command): + self.output += subprocess.check_output(command, env=os.environ.copy()) + def init(self, srcdir): - self.output += subprocess.check_output(self.meson_command + [srcdir, self.builddir]) + self._run(self.meson_command + [srcdir, self.builddir]) def build(self): - self.output += subprocess.check_output(self.ninja_command) + self._run(self.ninja_command) + + def run_target(self, target): + self.output += subprocess.check_output(self.ninja_command + [target]) def setconf(self, arg): - self.output += subprocess.check_output(self.mconf_command + [arg, self.builddir]) + self._run(self.mconf_command + [arg, self.builddir]) def get_compdb(self): with open(os.path.join(self.builddir, 'compile_commands.json')) as ifile: return json.load(ifile) + def get_meson_log(self): + with open(os.path.join(self.builddir, 'meson-logs', 'meson-log.txt')) as f: + return f.readlines() + def introspect(self, arg): out = subprocess.check_output(self.mintro_command + [arg, self.builddir]) return json.loads(out.decode('utf-8')) @@ -173,5 +184,24 @@ class LinuxlikeTests(unittest.TestCase): self.assertEqual(intro[0]['install_filename'], '/usr/local/libtest/libstat.a') self.assertEqual(intro[1]['install_filename'], '/usr/local/bin/prog') + def test_run_target_files_path(self): + testdir = os.path.join(self.common_test_dir, '58 run target') + self.init(testdir) + self.run_target('check_exists') + + def test_qt5dependency_qmake_detection(self): + # Can't be sure that `qmake` is Qt5, so just try qmake-qt5. + if not shutil.which('qmake-qt5'): + raise unittest.SkipTest('qt5 not found') + # Disable pkg-config codepath and force searching with qmake/qmake-qt5 + os.environ['PKG_CONFIG_LIBDIR'] = self.builddir + os.environ['PKG_CONFIG_PATH'] = self.builddir + testdir = os.path.join(self.framework_test_dir, '4 qt') + self.init(testdir) + # Confirm that the dependency was found with qmake + msg = 'Qt5 native `qmake-qt5` dependency found: YES\n' + mesonlog = self.get_meson_log() + self.assertTrue(msg in mesonlog) + if __name__ == '__main__': unittest.main() diff --git a/test cases/common/103 manygen/subdir/manygen.py b/test cases/common/103 manygen/subdir/manygen.py index 4fd2f25..8449dc3 100755 --- a/test cases/common/103 manygen/subdir/manygen.py +++ b/test cases/common/103 manygen/subdir/manygen.py @@ -9,6 +9,7 @@ import shutil, subprocess with open(sys.argv[1]) as f: funcname = f.readline().strip() outdir = sys.argv[2] +buildtype_args = sys.argv[3] if not os.path.isdir(outdir): print('Outdir does not exist.') @@ -67,7 +68,7 @@ with open(tmpc, 'w') as f: ''' % funcname) if is_vs: - subprocess.check_call([compiler, '/nologo', '/c', '/Fo' + outo, tmpc]) + subprocess.check_call([compiler, '/nologo', '/c', buildtype_args, '/Fo' + outo, tmpc]) else: subprocess.check_call([compiler, '-c', '-o', outo, tmpc]) diff --git a/test cases/common/103 manygen/subdir/meson.build b/test cases/common/103 manygen/subdir/meson.build index 5c5d763..3036899 100644 --- a/test cases/common/103 manygen/subdir/meson.build +++ b/test cases/common/103 manygen/subdir/meson.build @@ -1,6 +1,17 @@ gen = find_program('manygen.py') +buildtype = get_option('buildtype') +buildtype_args = '-Dfooxxx' # a useless compiler argument if meson.get_compiler('c').get_id() == 'msvc' + # We need our manually generated code to use the same CRT as the executable. + # Taken from compilers.py since build files do not have access to this. + if buildtype == 'debug' + buildtype_args = '/MDd' + elif buildtype == 'debugoptimized' + buildtype_args = '/MDd' + elif buildtype == 'release' + buildtype_args = '/MD' + endif outfiles = ['gen_func.lib', 'gen_func.c', 'gen_func.h', 'gen_func.o'] else outfiles = ['gen_func.a', 'gen_func.c', 'gen_func.h', 'gen_func.o'] @@ -9,5 +20,5 @@ endif generated = custom_target('manygen', output : outfiles, input : ['funcinfo.def'], - command : [gen, '@INPUT@', '@OUTDIR@'], + command : [gen, '@INPUT@', '@OUTDIR@', buildtype_args], ) diff --git a/test cases/common/111 has header symbol/meson.build b/test cases/common/111 has header symbol/meson.build index e0afb42..b5c865f 100644 --- a/test cases/common/111 has header symbol/meson.build +++ b/test cases/common/111 has header symbol/meson.build @@ -1,18 +1,32 @@ -project('has header symbol', 'c') +project('has header symbol', 'c', 'cpp') cc = meson.get_compiler('c') +cpp = meson.get_compiler('cpp') -assert (cc.has_header_symbol('stdio.h', 'int'), 'base types should always be available') -assert (cc.has_header_symbol('stdio.h', 'printf'), 'printf function not found') -assert (cc.has_header_symbol('stdio.h', 'FILE'), 'FILE structure not found') -assert (cc.has_header_symbol('limits.h', 'INT_MAX'), 'INT_MAX define not found') -assert (not cc.has_header_symbol('limits.h', 'guint64'), 'guint64 is not defined in limits.h') -assert (not cc.has_header_symbol('stdlib.h', 'FILE'), 'FILE structure is defined in stdio.h, not stdlib.h') -assert (not cc.has_header_symbol('stdlol.h', 'printf'), 'stdlol.h shouldn\'t exist') -assert (not cc.has_header_symbol('stdlol.h', 'int'), 'shouldn\'t be able to find "int" with invalid header') +foreach comp : [cc, cpp] + assert (comp.has_header_symbol('stdio.h', 'int'), 'base types should always be available') + assert (comp.has_header_symbol('stdio.h', 'printf'), 'printf function not found') + assert (comp.has_header_symbol('stdio.h', 'FILE'), 'FILE structure not found') + assert (comp.has_header_symbol('limits.h', 'INT_MAX'), 'INT_MAX define not found') + assert (not comp.has_header_symbol('limits.h', 'guint64'), 'guint64 is not defined in limits.h') + assert (not comp.has_header_symbol('stdlib.h', 'FILE'), 'FILE structure is defined in stdio.h, not stdlib.h') + assert (not comp.has_header_symbol('stdlol.h', 'printf'), 'stdlol.h shouldn\'t exist') + assert (not comp.has_header_symbol('stdlol.h', 'int'), 'shouldn\'t be able to find "int" with invalid header') +endforeach # This is likely only available on Glibc, so just test for it if cc.has_function('ppoll') assert (not cc.has_header_symbol('poll.h', 'ppoll'), 'ppoll should not be accessible without _GNU_SOURCE') assert (cc.has_header_symbol('poll.h', 'ppoll', prefix : '#define _GNU_SOURCE'), 'ppoll should be accessible with _GNU_SOURCE') endif + +assert (cpp.has_header_symbol('iostream', 'std::iostream'), 'iostream not found in iostream.h') +assert (cpp.has_header_symbol('vector', 'std::vector'), 'vector not found in vector.h') +assert (not cpp.has_header_symbol('limits.h', 'std::iostream'), 'iostream should not be defined in limits.h') + +boost = dependency('boost', required : false) +if boost.found() + assert (cpp.has_header_symbol('boost/math/quaternion.hpp', 'boost::math::quaternion', dependencies : boost), 'quaternion not found') +else + assert (not cpp.has_header_symbol('boost/math/quaternion.hpp', 'boost::math::quaternion', dependencies : boost), 'quaternion found?!') +endif diff --git a/test cases/common/113 generatorcustom/meson.build b/test cases/common/113 generatorcustom/meson.build index 1f4cc88..472d565 100644 --- a/test cases/common/113 generatorcustom/meson.build +++ b/test cases/common/113 generatorcustom/meson.build @@ -1,17 +1,21 @@ project('generatorcustom', 'c') -creator = find_program('gen.py') -catter = find_program('catter.py') +if meson.get_compiler('c').get_id() != 'msvc' + creator = find_program('gen.py') + catter = find_program('catter.py') -gen = generator(creator, - output: '@BASENAME@.h', - arguments : ['@INPUT@', '@OUTPUT@']) + gen = generator(creator, + output: '@BASENAME@.h', + arguments : ['@INPUT@', '@OUTPUT@']) -hs = gen.process('res1.txt', 'res2.txt') + hs = gen.process('res1.txt', 'res2.txt') -allinone = custom_target('alltogether', - input : hs, - output : 'alltogether.h', - command : [catter, '@INPUT@', '@OUTPUT@']) + allinone = custom_target('alltogether', + input : hs, + output : 'alltogether.h', + command : [catter, '@INPUT@', '@OUTPUT@']) -executable('proggie', 'main.c', allinone) + executable('proggie', 'main.c', allinone) +else + error('MESON_SKIP_TEST: Skipping test on VS backend; see: https://github.com/mesonbuild/meson/issues/1004') +endif diff --git a/test cases/common/121 interpreter copy mutable var on assignment/meson.build b/test cases/common/121 interpreter copy mutable var on assignment/meson.build new file mode 100644 index 0000000..8b15357 --- /dev/null +++ b/test cases/common/121 interpreter copy mutable var on assignment/meson.build @@ -0,0 +1,20 @@ +project('foo', 'c') + +a = configuration_data() +a.set('HELLO', 1) + +b = a + +assert(a.has('HELLO'), 'Original config data should be set on a') +assert(b.has('HELLO'), 'Original config data should be set on copy') + +configure_file(output : 'b.h', configuration : b) + +# This should still work, as we didn't use the original above but a copy! +a.set('WORLD', 1) + +assert(a.has('WORLD'), 'New config data should have been set') +assert(not b.has('WORLD'), 'New config data set should not affect var copied earlier') + +configure_file(output : 'a.h', configuration : a) + diff --git a/test cases/common/121 skip/meson.build b/test cases/common/121 skip/meson.build new file mode 100644 index 0000000..1adedb6 --- /dev/null +++ b/test cases/common/121 skip/meson.build @@ -0,0 +1,4 @@ +project('skip', 'c') + +error('MESON_SKIP_TEST this test is always skipped.') + diff --git a/test cases/common/122 subproject project arguments/exe.c b/test cases/common/122 subproject project arguments/exe.c new file mode 100644 index 0000000..b04344a --- /dev/null +++ b/test cases/common/122 subproject project arguments/exe.c @@ -0,0 +1,23 @@ +#ifndef PROJECT_OPTION +#error +#endif + +#ifndef PROJECT_OPTION_1 +#error +#endif + +#ifndef GLOBAL_ARGUMENT +#error +#endif + +#ifdef SUBPROJECT_OPTION +#error +#endif + +#ifdef OPTION_CPP +#error +#endif + +int main(int argc, char **argv) { + return 0; +} diff --git a/test cases/common/122 subproject project arguments/exe.cpp b/test cases/common/122 subproject project arguments/exe.cpp new file mode 100644 index 0000000..7ffe098 --- /dev/null +++ b/test cases/common/122 subproject project arguments/exe.cpp @@ -0,0 +1,24 @@ +#ifdef PROJECT_OPTION +#error +#endif + +#ifdef PROJECT_OPTION_1 +#error +#endif + +#ifdef GLOBAL_ARGUMENT +#error +#endif + +#ifdef SUBPROJECT_OPTION +#error +#endif + +#ifndef PROJECT_OPTION_CPP +#error +#endif + +int main(int argc, char **argv) { + return 0; +} + diff --git a/test cases/common/122 subproject project arguments/meson.build b/test cases/common/122 subproject project arguments/meson.build new file mode 100644 index 0000000..aee803c --- /dev/null +++ b/test cases/common/122 subproject project arguments/meson.build @@ -0,0 +1,14 @@ +project('project options tester', 'c', 'cpp', + version : '2.3.4', + license : 'mylicense') + +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') + +sub = subproject('subexe', version : '1.0.0') +e = executable('exe', 'exe.c') +e = executable('execpp', 'exe.cpp') +test('exetest', e) +test('execpptest', e) diff --git a/test cases/common/122 subproject project arguments/subprojects/subexe/meson.build b/test cases/common/122 subproject project arguments/subprojects/subexe/meson.build new file mode 100644 index 0000000..ef141dc --- /dev/null +++ b/test cases/common/122 subproject project arguments/subprojects/subexe/meson.build @@ -0,0 +1,13 @@ +project('subproject', 'c', + version : '1.0.0', + license : ['sublicense1', 'sublicense2']) + +if not meson.is_subproject() + error('Claimed to be master project even though we are a subproject.') +endif + +assert(meson.project_name() == 'subproject', 'Incorrect subproject name') + +add_project_arguments('-DSUBPROJECT_OPTION', language: 'c') +e = executable('subexe', 'subexe.c') +test('subexetest', e) diff --git a/test cases/common/122 subproject project arguments/subprojects/subexe/subexe.c b/test cases/common/122 subproject project arguments/subprojects/subexe/subexe.c new file mode 100644 index 0000000..6ebd752 --- /dev/null +++ b/test cases/common/122 subproject project arguments/subprojects/subexe/subexe.c @@ -0,0 +1,23 @@ +#ifdef PROJECT_OPTION +#error +#endif + +#ifdef PROJECT_OPTION_1 +#error +#endif + +#ifndef GLOBAL_ARGUMENT +#error +#endif + +#ifndef SUBPROJECT_OPTION +#error +#endif + +#ifdef OPTION_CPP +#error +#endif + +int main(int argc, char **argv) { + return 0; +} diff --git a/test cases/common/16 configure file/config.h b/test cases/common/16 configure file/config.h new file mode 100644 index 0000000..e85b634 --- /dev/null +++ b/test cases/common/16 configure file/config.h @@ -0,0 +1 @@ +#error "This file should not be included. Build dir must become before source dir in search order" diff --git a/test cases/common/16 configure file/prog.c b/test cases/common/16 configure file/prog.c index 718a402..89a718e 100644 --- a/test cases/common/16 configure file/prog.c +++ b/test cases/common/16 configure file/prog.c @@ -1,5 +1,8 @@ #include <string.h> -#include "config.h" +/* config.h must not be in quotes: + * https://gcc.gnu.org/onlinedocs/cpp/Search-Path.html + */ +#include <config.h> #ifdef SHOULD_BE_UNDEF #error "FAIL!" diff --git a/test cases/common/16 configure file/prog2.c b/test cases/common/16 configure file/prog2.c index be85033..a88c70f 100644 --- a/test cases/common/16 configure file/prog2.c +++ b/test cases/common/16 configure file/prog2.c @@ -1,4 +1,4 @@ -#include"config2.h" +#include<config2.h> int main(int argc, char **argv) { return ZERO_RESULT; diff --git a/test cases/common/33 try compile/meson.build b/test cases/common/33 try compile/meson.build index bca82ce..09ca395 100644 --- a/test cases/common/33 try compile/meson.build +++ b/test cases/common/33 try compile/meson.build @@ -1,4 +1,4 @@ -project('try compile', 'c') +project('try compile', 'c', 'cpp') code = '''#include<stdio.h> void func() { printf("Something.\n"); } @@ -8,19 +8,20 @@ breakcode = '''#include<nonexisting.h> void func() { printf("This won't work.\n"); } ''' -compiler = meson.get_compiler('c') -if compiler.compiles(code, name : 'should succeed') == false - error('Compiler is fail.') -endif +foreach compiler : [meson.get_compiler('c'), meson.get_compiler('cpp')] + if compiler.compiles(code, name : 'should succeed') == false + error('Compiler ' + compiler.get_id() + ' is fail.') + endif -if compiler.compiles(files('valid.c'), name : 'should succeed') == false - error('Compiler is fail.') -endif + if compiler.compiles(files('valid.c'), name : 'should succeed') == false + error('Compiler ' + compiler.get_id() + ' is fail.') + endif -if compiler.compiles(breakcode, name : 'should fail') - error('Compiler returned true on broken code.') -endif + if compiler.compiles(breakcode, name : 'should fail') + error('Compiler ' + compiler.get_id() + ' returned true on broken code.') + endif -if compiler.compiles(files('invalid.c'), name : 'should fail') - error('Compiler returned true on broken code.') -endif + if compiler.compiles(files('invalid.c'), name : 'should fail') + error('Compiler ' + compiler.get_id() + ' returned true on broken code.') + endif +endforeach diff --git a/test cases/common/35 sizeof/meson.build b/test cases/common/35 sizeof/meson.build index 4a0398b..9de5b78 100644 --- a/test cases/common/35 sizeof/meson.build +++ b/test cases/common/35 sizeof/meson.build @@ -1,13 +1,33 @@ -project('sizeof', 'c') +project('sizeof', 'c', 'cpp') +# Test with C cc = meson.get_compiler('c') + intsize = cc.sizeof('int') wcharsize = cc.sizeof('wchar_t', prefix : '#include<wchar.h>') cd = configuration_data() cd.set('INTSIZE', intsize) cd.set('WCHARSIZE', wcharsize) +cd.set('CONFIG', 'config.h') configure_file(input : 'config.h.in', output : 'config.h', configuration : cd) +s = configure_file(input : 'prog.c.in', output : 'prog.c', configuration : cd) -e = executable('prog', 'prog.c') +e = executable('prog', s) test('sizeof test', e) + +# Test with C++ +cpp = meson.get_compiler('cpp') + +intsize = cpp.sizeof('int') +wcharsize = cpp.sizeof('wchar_t', prefix : '#include<wchar.h>') + +cdpp = configuration_data() +cdpp.set('INTSIZE', intsize) +cdpp.set('WCHARSIZE', wcharsize) +cdpp.set('CONFIG', 'config.hpp') +configure_file(input : 'config.h.in', output : 'config.hpp', configuration : cdpp) +spp = configure_file(input : 'prog.c.in', output : 'prog.cc', configuration : cdpp) + +epp = executable('progpp', spp) +test('sizeof test c++', epp) diff --git a/test cases/common/35 sizeof/prog.c b/test cases/common/35 sizeof/prog.c.in index 9164c18..85b1229 100644 --- a/test cases/common/35 sizeof/prog.c +++ b/test cases/common/35 sizeof/prog.c.in @@ -1,6 +1,6 @@ -#include"config.h" -#include<stdio.h> -#include<wchar.h> +#include "@CONFIG@" +#include <stdio.h> +#include <wchar.h> int main(int argc, char **argv) { if(INTSIZE != sizeof(int)) { diff --git a/test cases/common/37 has header/meson.build b/test cases/common/37 has header/meson.build index bbfce6d..4f9b94f 100644 --- a/test cases/common/37 has header/meson.build +++ b/test cases/common/37 has header/meson.build @@ -1,11 +1,29 @@ -project('has header', 'c') +project('has header', 'c', 'cpp') -cc = meson.get_compiler('c') +foreach comp : [meson.get_compiler('c'), meson.get_compiler('cpp')] + if not comp.has_header('stdio.h') + error('Stdio missing.') + endif -if cc.has_header('stdio.h') == false - error('Stdio missing.') -endif + # stdio.h doesn't actually need stdlib.h, but I don't know any headers on + # UNIX/Linux that need other headers defined beforehand + if not comp.has_header('stdio.h', prefix : '#include <stdlib.h>') + error('Stdio missing.') + endif -if cc.has_header('ouagadougou.h') - error('Found non-existant header.') -endif + # XInput.h needs windows.h included beforehand. We only do this check on MSVC + # because MinGW often defines its own wrappers that pre-include windows.h + if comp.get_id() == 'msvc' + if not comp.has_header('XInput.h', prefix : '#include <windows.h>') + error('XInput.h is missing on Windows') + endif + if comp.has_header('XInput.h') + error('XInput.h needs windows.h') + endif + endif + + + if comp.has_header('ouagadougou.h') + error('Found non-existant header.') + endif +endforeach diff --git a/test cases/common/39 tryrun/meson.build b/test cases/common/39 tryrun/meson.build index f5d07ab..c64446f 100644 --- a/test cases/common/39 tryrun/meson.build +++ b/test cases/common/39 tryrun/meson.build @@ -1,14 +1,14 @@ -project('tryrun', 'c') +project('tryrun', 'c', 'cpp') # Complex to exercise all code paths. if meson.is_cross_build() if meson.has_exe_wrapper() - cc = meson.get_compiler('c', native : false) + compilers = [meson.get_compiler('c', native : false), meson.get_compiler('cpp', native : false)] else - cc = meson.get_compiler('c', native : true) + compilers = [meson.get_compiler('c', native : true), meson.get_compiler('cpp', native : true)] endif else - cc = meson.get_compiler('c') + compilers = [meson.get_compiler('c'), meson.get_compiler('cpp')] endif ok_code = '''#include<stdio.h> @@ -32,45 +32,47 @@ INPUTS = [ ['File', files('ok.c'), files('error.c'), files('no_compile.c')], ] -foreach input : INPUTS - type = input[0] - ok = cc.run(input[1], name : type + ' should succeed') - err = cc.run(input[2], name : type + ' should fail') - noc = cc.run(input[3], name : type + ' does not compile') +foreach cc : compilers + foreach input : INPUTS + type = input[0] + ok = cc.run(input[1], name : type + ' should succeed') + err = cc.run(input[2], name : type + ' should fail') + noc = cc.run(input[3], name : type + ' does not compile') - if noc.compiled() - error(type + ' compilation fail test failed.') - else - message(type + ' fail detected properly.') - endif + if noc.compiled() + error(type + ' compilation fail test failed.') + else + message(type + ' fail detected properly.') + endif - if ok.compiled() - message(type + ' compilation worked.') - else - error(type + ' compilation did not work.') - endif + if ok.compiled() + message(type + ' compilation worked.') + else + error(type + ' compilation did not work.') + endif - if ok.returncode() == 0 - message(type + ' return code ok.') - else - error(type + ' return code fail') - endif + if ok.returncode() == 0 + message(type + ' return code ok.') + else + error(type + ' return code fail') + endif - if err.returncode() == 1 - message(type + ' bad return code ok.') - else - error(type + ' bad return code fail.') - endif + if err.returncode() == 1 + message(type + ' bad return code ok.') + else + error(type + ' bad return code fail.') + endif - if ok.stdout().strip() == 'stdout' - message(type + ' stdout ok.') - else - message(type + ' bad stdout.') - endif + if ok.stdout().strip() == 'stdout' + message(type + ' stdout ok.') + else + message(type + ' bad stdout.') + endif - if ok.stderr().strip() == 'stderr' - message(type + ' stderr ok.') - else - message(type + ' bad stderr.') - endif + if ok.stderr().strip() == 'stderr' + message(type + ' stderr ok.') + else + message(type + ' bad stderr.') + endif + endforeach endforeach diff --git a/test cases/common/43 has function/meson.build b/test cases/common/43 has function/meson.build index 00ca640..61f96e1 100644 --- a/test cases/common/43 has function/meson.build +++ b/test cases/common/43 has function/meson.build @@ -1,41 +1,43 @@ -project('has function', 'c') +project('has function', 'c', 'cpp') -cc = meson.get_compiler('c') +compilers = [meson.get_compiler('c'), meson.get_compiler('cpp')] -if not cc.has_function('printf', prefix : '#include<stdio.h>') - error('"printf" function not found (should always exist).') -endif +foreach cc : compilers + if not cc.has_function('printf', prefix : '#include<stdio.h>') + error('"printf" function not found (should always exist).') + endif -# Should also be able to detect it without specifying the header -# We check for a different function here to make sure the result is -# not taken from a cache (ie. the check above) -# On MSVC fprintf is defined as an inline function in the header, so it cannot -# be found without the include. -if cc.get_id() != 'msvc' - assert(cc.has_function('fprintf'), '"fprintf" function not found without include (on !msvc).') -else - assert(cc.has_function('fprintf', prefix : '#include <stdio.h>'), '"fprintf" function not found with include (on msvc).') -endif + # Should also be able to detect it without specifying the header + # We check for a different function here to make sure the result is + # not taken from a cache (ie. the check above) + # On MSVC fprintf is defined as an inline function in the header, so it cannot + # be found without the include. + if cc.get_id() != 'msvc' + assert(cc.has_function('fprintf'), '"fprintf" function not found without include (on !msvc).') + else + assert(cc.has_function('fprintf', prefix : '#include <stdio.h>'), '"fprintf" function not found with include (on msvc).') + endif -if cc.has_function('hfkerhisadf', prefix : '#include<stdio.h>') - error('Found non-existent function "hfkerhisadf".') -endif + if cc.has_function('hfkerhisadf', prefix : '#include<stdio.h>') + error('Found non-existent function "hfkerhisadf".') + endif -# With glibc on Linux lchmod is a stub that will always return an error, -# we want to detect that and declare that the function is not available. -# We can't check for the C library used here of course, but if it's not -# implemented in glibc it's probably not implemented in any other 'slimmer' -# C library variants either, so the check should be safe either way hopefully. -if host_machine.system() == 'linux' and cc.get_id() == 'gcc' - assert (cc.has_function('poll', prefix : '#include <poll.h>'), 'couldn\'t detect "poll" when defined by a header') - assert (not cc.has_function('lchmod', prefix : '''#include <sys/stat.h> - #include <unistd.h>'''), '"lchmod" check should have failed') -endif + # With glibc on Linux lchmod is a stub that will always return an error, + # we want to detect that and declare that the function is not available. + # We can't check for the C library used here of course, but if it's not + # implemented in glibc it's probably not implemented in any other 'slimmer' + # C library variants either, so the check should be safe either way hopefully. + if host_machine.system() == 'linux' and cc.get_id() == 'gcc' + assert (cc.has_function('poll', prefix : '#include <poll.h>'), 'couldn\'t detect "poll" when defined by a header') + assert (not cc.has_function('lchmod', prefix : '''#include <sys/stat.h> + #include <unistd.h>'''), '"lchmod" check should have failed') + endif -# For some functions one needs to define _GNU_SOURCE before including the -# right headers to get them picked up. Make sure we can detect these functions -# as well without any prefix -if cc.has_header_symbol('sys/socket.h', 'recvmmsg', prefix : '#define _GNU_SOURCE') - # We assume that if recvmmsg exists sendmmsg does too - assert (cc.has_function('sendmmsg'), 'Failed to detect function "sendmmsg" (should always exist).') -endif + # For some functions one needs to define _GNU_SOURCE before including the + # right headers to get them picked up. Make sure we can detect these functions + # as well without any prefix + if cc.has_header_symbol('sys/socket.h', 'recvmmsg', prefix : '#define _GNU_SOURCE') + # We assume that if recvmmsg exists sendmmsg does too + assert (cc.has_function('sendmmsg'), 'Failed to detect function "sendmmsg" (should always exist).') + endif +endforeach diff --git a/test cases/common/44 has member/meson.build b/test cases/common/44 has member/meson.build index e60aeb3..4e61956 100644 --- a/test cases/common/44 has member/meson.build +++ b/test cases/common/44 has member/meson.build @@ -1,19 +1,21 @@ -project('has member', 'c') +project('has member', 'c', 'cpp') -cc = meson.get_compiler('c') +compilers = [meson.get_compiler('c'), meson.get_compiler('cpp')] -if not cc.has_member('struct tm', 'tm_sec', prefix : '#include<time.h>') - error('Did not detect member of "struct tm" that exists: "tm_sec"') -endif +foreach cc : compilers + if not cc.has_member('struct tm', 'tm_sec', prefix : '#include<time.h>') + error('Did not detect member of "struct tm" that exists: "tm_sec"') + endif -if cc.has_member('struct tm', 'tm_nonexistent', prefix : '#include<time.h>') - error('Not existing member "tm_nonexistent" found.') -endif + if cc.has_member('struct tm', 'tm_nonexistent', prefix : '#include<time.h>') + error('Not existing member "tm_nonexistent" found.') + endif -if not cc.has_members('struct tm', 'tm_sec', 'tm_min', prefix : '#include<time.h>') - error('Did not detect members of "struct tm" that exist: "tm_sec" "tm_min"') -endif + if not cc.has_members('struct tm', 'tm_sec', 'tm_min', prefix : '#include<time.h>') + error('Did not detect members of "struct tm" that exist: "tm_sec" "tm_min"') + endif -if cc.has_members('struct tm', 'tm_sec', 'tm_nonexistent2', prefix : '#include<time.h>') - error('Not existing member "tm_nonexistent2" found.') -endif + if cc.has_members('struct tm', 'tm_sec', 'tm_nonexistent2', prefix : '#include<time.h>') + error('Not existing member "tm_nonexistent2" found.') + endif +endforeach diff --git a/test cases/common/45 alignment/meson.build b/test cases/common/45 alignment/meson.build index 2ec3f89..a9bd65b 100644 --- a/test cases/common/45 alignment/meson.build +++ b/test cases/common/45 alignment/meson.build @@ -1,29 +1,31 @@ -project('alignment', 'c') +project('alignment', 'c', 'cpp') -cc = meson.get_compiler('c') +compilers = [meson.get_compiler('c'), meson.get_compiler('cpp')] -# These tests should return the same value on all -# platforms. If (and when) they don't, fix 'em up. -if cc.alignment('char') != 1 - error('Alignment of char misdetected.') -endif +foreach cc : compilers + # These tests should return the same value on all + # platforms. If (and when) they don't, fix 'em up. + if cc.alignment('char') != 1 + error('Alignment of char misdetected.') + endif -ptr_size = cc.sizeof('void*') -dbl_alignment = cc.alignment('double') + ptr_size = cc.sizeof('void*') + dbl_alignment = cc.alignment('double') -# These tests are not thorough. Doing this properly -# would take a lot of work because it is strongly -# platform and compiler dependent. So just check -# that they produce something fairly sane. + # These tests are not thorough. Doing this properly + # would take a lot of work because it is strongly + # platform and compiler dependent. So just check + # that they produce something fairly sane. -if ptr_size == 8 or ptr_size == 4 - message('Size of ptr ok.') -else - error('Size of ptr misdetected.') -endif + if ptr_size == 8 or ptr_size == 4 + message('Size of ptr ok.') + else + error('Size of ptr misdetected.') + endif -if dbl_alignment == 8 or dbl_alignment == 4 - message('Alignment of double ok.') -else - error('Alignment of double misdetected.') -endif + if dbl_alignment == 8 or dbl_alignment == 4 + message('Alignment of double ok.') + else + error('Alignment of double misdetected.') + endif +endforeach diff --git a/test cases/common/48 test args/env2vars.c b/test cases/common/48 test args/env2vars.c new file mode 100644 index 0000000..19250a8 --- /dev/null +++ b/test cases/common/48 test args/env2vars.c @@ -0,0 +1,23 @@ +#include<stdio.h> +#include<string.h> +#include<stdlib.h> + +int main(int argc, char **argv) { + if(strcmp(getenv("first"), "something-else") != 0) { + fprintf(stderr, "First envvar is wrong. %s\n", getenv("first")); + return 1; + } + if(strcmp(getenv("second"), "val2") != 0) { + fprintf(stderr, "Second envvar is wrong.\n"); + return 1; + } + if(strcmp(getenv("third"), "val3:and_more") != 0) { + fprintf(stderr, "Third envvar is wrong.\n"); + return 1; + } + if(strstr(getenv("PATH"), "fakepath:") != NULL) { + fprintf(stderr, "Third envvar is wrong.\n"); + return 1; + } + return 0; +} diff --git a/test cases/common/48 test args/meson.build b/test cases/common/48 test args/meson.build index 6400198..f599bf7 100644 --- a/test cases/common/48 test args/meson.build +++ b/test cases/common/48 test args/meson.build @@ -2,6 +2,7 @@ project('test features', 'c') e1 = executable('cmd_args', 'cmd_args.c') e2 = executable('envvars', 'envvars.c') +e3 = executable('env2vars', 'env2vars.c') env = environment() env.set('first', 'val1') @@ -9,6 +10,12 @@ env.set('second', 'val2') env.set('third', 'val3', 'and_more', separator: ':') env.append('PATH', 'fakepath', separator: ':') +# Make sure environment objects are copied on assignment and we can +# change the copy without affecting the original environment object. +env2 = env +env2.set('first', 'something-else') + test('command line arguments', e1, args : ['first', 'second']) test('environment variables', e2, env : env) +test('environment variables 2', e3, env : env2) test('file arg', find_program('tester.py'), args : files('testfile.txt')) diff --git a/test cases/common/51 pkgconfig-gen/meson.build b/test cases/common/51 pkgconfig-gen/meson.build index fa9439c..0933238 100644 --- a/test cases/common/51 pkgconfig-gen/meson.build +++ b/test cases/common/51 pkgconfig-gen/meson.build @@ -19,7 +19,7 @@ pkgg.generate( ) pkgconfig = find_program('pkg-config', required: false) -if pkgconfig.found() +if pkgconfig.found() and build_machine.system() != 'windows' test('pkgconfig-validation', pkgconfig, args: ['--validate', 'simple'], env: ['PKG_CONFIG_PATH=' + meson.current_build_dir() + '/meson-private' ], diff --git a/test cases/common/56 custom target/meson.build b/test cases/common/56 custom target/meson.build index e216bae..feaa762 100644 --- a/test cases/common/56 custom target/meson.build +++ b/test cases/common/56 custom target/meson.build @@ -9,7 +9,7 @@ comp = '@0@/@1@'.format(meson.current_source_dir(), 'my_compiler.py') mytarget = custom_target('bindat', output : 'data.dat', input : 'data_source.txt', -command : [python, comp, '@INPUT@', '@OUTPUT@'], +command : [python, comp, '--input=@INPUT@', '--output=@OUTPUT@'], install : true, install_dir : 'subdir' ) diff --git a/test cases/common/56 custom target/my_compiler.py b/test cases/common/56 custom target/my_compiler.py index d99029b..4ba2da6 100755 --- a/test cases/common/56 custom target/my_compiler.py +++ b/test cases/common/56 custom target/my_compiler.py @@ -3,13 +3,14 @@ import sys if __name__ == '__main__': - if len(sys.argv) != 3: - print(sys.argv[0], 'input_file output_file') + if len(sys.argv) != 3 or not sys.argv[1].startswith('--input') or \ + not sys.argv[2].startswith('--output'): + print(sys.argv[0], '--input=input_file --output=output_file') sys.exit(1) - with open(sys.argv[1]) as f: + with open(sys.argv[1].split('=')[1]) as f: ifile = f.read() if ifile != 'This is a text only input file.\n': print('Malformed input') sys.exit(1) - with open(sys.argv[2], 'w') as ofile: + with open(sys.argv[2].split('=')[1], 'w') as ofile: ofile.write('This is a binary output file.\n') diff --git a/test cases/common/58 run target/check_exists.py b/test cases/common/58 run target/check_exists.py new file mode 100755 index 0000000..62cbe23 --- /dev/null +++ b/test cases/common/58 run target/check_exists.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python + +import os +import sys + +if not os.path.isfile(sys.argv[1]): + raise Exception("Couldn't find {!r}".format(sys.argv[1])) diff --git a/test cases/common/58 run target/meson.build b/test cases/common/58 run target/meson.build index 5824c74..0540b80 100644 --- a/test cases/common/58 run target/meson.build +++ b/test cases/common/58 run target/meson.build @@ -35,8 +35,8 @@ python3 = find_program('python3') run_target('py3hi', command : [python3, '-c', 'print("I am Python3.")']) -run_target('ct_in_arg', - command : ['echo', hex, files('helloprinter.c')]) +run_target('check_exists', + command : [find_program('check_exists.py'), files('helloprinter.c')]) # What if the output of a custom_target is the command to # execute. Obviously this will not work as hex is not an diff --git a/test cases/common/62 exe static shared/stat.c b/test cases/common/62 exe static shared/stat.c index 4eceb30..680ed92 100644 --- a/test cases/common/62 exe static shared/stat.c +++ b/test cases/common/62 exe static shared/stat.c @@ -1,5 +1,7 @@ +#include "subdir/exports.h" + int shlibfunc(); -int statlibfunc() { +int DLL_PUBLIC statlibfunc() { return shlibfunc(); } diff --git a/test cases/common/83 has type/meson.build b/test cases/common/83 has type/meson.build index 002f150..de8dbc8 100644 --- a/test cases/common/83 has type/meson.build +++ b/test cases/common/83 has type/meson.build @@ -1,11 +1,13 @@ -project('has type', 'c') +project('has type', 'c', 'cpp') -cc = meson.get_compiler('c') +compilers = [meson.get_compiler('c'), meson.get_compiler('cpp')] -if not cc.has_type('time_t', prefix : '#include<time.h>') - error('Did not detect type that exists.') -endif +foreach cc : compilers + if not cc.has_type('time_t', prefix : '#include<time.h>') + error('Did not detect type that exists.') + endif -if cc.has_type('no_time_t', prefix : '#include<time.h>') - error('Not existing type found.') -endif + if cc.has_type('no_time_t', prefix : '#include<time.h>') + error('Not existing type found.') + endif +endforeach diff --git a/test cases/common/95 dep fallback/meson.build b/test cases/common/95 dep fallback/meson.build index 13541f2..212c64f 100644 --- a/test cases/common/95 dep fallback/meson.build +++ b/test cases/common/95 dep fallback/meson.build @@ -5,6 +5,7 @@ if not bob.found() error('Bob is actually needed') endif jimmy = dependency('jimmylib', fallback : ['jimmylib', 'jimmy_dep'], required: false) +dummy = dependency('dummylib', fallback : ['dummylib', 'dummy_dep'], required: false) gensrc_py = find_program('gensrc.py') gensrc = custom_target('gensrc.c', diff --git a/test cases/common/95 dep fallback/subprojects/dummylib/meson.build b/test cases/common/95 dep fallback/subprojects/dummylib/meson.build new file mode 100644 index 0000000..3ad33e7 --- /dev/null +++ b/test cases/common/95 dep fallback/subprojects/dummylib/meson.build @@ -0,0 +1,4 @@ +project('dummylib', 'c') + +dummy_dep = declare_dependency() +error('this subproject fails to configure') diff --git a/test cases/failing/34 dependency not-required then required/meson.build b/test cases/failing/34 dependency not-required then required/meson.build index f33c41c..1796699 100644 --- a/test cases/failing/34 dependency not-required then required/meson.build +++ b/test cases/failing/34 dependency not-required then required/meson.build @@ -1,4 +1,4 @@ project('dep-test', 'c', version : '1.0') foo_dep = dependency('foo-bar-xyz-12.3', required : false) -bar_dep = dependency('foo-bar-xyz-12.3', required : true) +bar_dep = dependency('foo-bar-xyz-12.3') diff --git a/test cases/failing/34 non-root subproject/meson.build b/test cases/failing/34 non-root subproject/meson.build new file mode 100644 index 0000000..c84dce7 --- /dev/null +++ b/test cases/failing/34 non-root subproject/meson.build @@ -0,0 +1,3 @@ +project('non-root subproject', 'c') + +subdir('some') diff --git a/test cases/failing/34 non-root subproject/some/meson.build b/test cases/failing/34 non-root subproject/some/meson.build new file mode 100644 index 0000000..d82f451 --- /dev/null +++ b/test cases/failing/34 non-root subproject/some/meson.build @@ -0,0 +1 @@ +dependency('definitely-doesnt-exist', fallback : ['someproj', 'some_dep']) diff --git a/test cases/failing/35 project argument after target/exe.c b/test cases/failing/35 project argument after target/exe.c new file mode 100644 index 0000000..11b7fad --- /dev/null +++ b/test cases/failing/35 project argument after target/exe.c @@ -0,0 +1,3 @@ +int main(int argc, char **argv) { + return 0; +} diff --git a/test cases/failing/35 project argument after target/meson.build b/test cases/failing/35 project argument after target/meson.build new file mode 100644 index 0000000..5402c67 --- /dev/null +++ b/test cases/failing/35 project argument after target/meson.build @@ -0,0 +1,7 @@ +project('project argument after target failing', 'c', + version : '2.3.4', + license : 'mylicense') + +add_project_arguments('-DPROJECT_OPTION', language: 'c') +e = executable('exe', 'exe.c') +add_project_arguments('-DPROJECT_OPTION1', language: 'c') diff --git a/test cases/frameworks/10 gtk-doc/meson.build b/test cases/frameworks/10 gtk-doc/meson.build index c6881ab..95eeefa 100644 --- a/test cases/frameworks/10 gtk-doc/meson.build +++ b/test cases/frameworks/10 gtk-doc/meson.build @@ -8,4 +8,6 @@ inc = include_directories('include') # We have to disable this test until this bug fix has landed to # distros https://bugzilla.gnome.org/show_bug.cgi?id=753145 -# subdir('doc') +error('MESON_SKIP_TEST can not enable gtk-doc test until upstream fixes have landed.') + +subdir('doc') diff --git a/test cases/frameworks/4 qt5/main.cpp b/test cases/frameworks/4 qt/main.cpp index 4c257a4..4c257a4 100644 --- a/test cases/frameworks/4 qt5/main.cpp +++ b/test cases/frameworks/4 qt/main.cpp diff --git a/manual tests/6 qt4/mainWindow.cpp b/test cases/frameworks/4 qt/mainWindow.cpp index cc82c4f..cc82c4f 100644 --- a/manual tests/6 qt4/mainWindow.cpp +++ b/test cases/frameworks/4 qt/mainWindow.cpp diff --git a/manual tests/6 qt4/mainWindow.h b/test cases/frameworks/4 qt/mainWindow.h index 7f6d906..7f6d906 100644 --- a/manual tests/6 qt4/mainWindow.h +++ b/test cases/frameworks/4 qt/mainWindow.h diff --git a/manual tests/6 qt4/mainWindow.ui b/test cases/frameworks/4 qt/mainWindow.ui index 2eb226a..2eb226a 100644 --- a/manual tests/6 qt4/mainWindow.ui +++ b/test cases/frameworks/4 qt/mainWindow.ui diff --git a/manual tests/6 qt4/manualinclude.cpp b/test cases/frameworks/4 qt/manualinclude.cpp index 0602882..0602882 100644 --- a/manual tests/6 qt4/manualinclude.cpp +++ b/test cases/frameworks/4 qt/manualinclude.cpp diff --git a/manual tests/6 qt4/manualinclude.h b/test cases/frameworks/4 qt/manualinclude.h index 4a00b6c..4a00b6c 100644 --- a/manual tests/6 qt4/manualinclude.h +++ b/test cases/frameworks/4 qt/manualinclude.h diff --git a/test cases/frameworks/4 qt/meson.build b/test cases/frameworks/4 qt/meson.build new file mode 100644 index 0000000..1096c78 --- /dev/null +++ b/test cases/frameworks/4 qt/meson.build @@ -0,0 +1,45 @@ +project('qt4 and 5 build test', 'cpp') + +foreach qt : ['qt4', 'qt5'] + qtdep = dependency(qt, modules : ['Core', 'Gui', 'Widgets'], required : qt == 'qt5') + if qtdep.found() + qtmodule = import(qt) + + # The following has two resource files because having two in one target + # requires you to do it properly or you get linker symbol clashes. + + prep = qtmodule.preprocess( + moc_headers : ['mainWindow.h'], # These need to be fed through the moc tool before use. + ui_files : 'mainWindow.ui', # XML files that need to be compiled with the uic tol. + qresources : ['stuff.qrc', 'stuff2.qrc'], # Resource file for rcc compiler. + ) + + qexe = executable(qt + 'app', + sources : ['main.cpp', 'mainWindow.cpp', # Sources that don't need preprocessing. + prep], + dependencies : qtdep) + + # We need a console test application because some test environments + # do not have an X server. + + qtcore = dependency(qt, modules : 'Core') + + qtcoreapp = executable(qt + 'core', 'q5core.cpp', + dependencies : qtcore) + + test(qt + 'test', qtcoreapp) + + # The build system needs to include the cpp files from + # headers but the user must manually include moc + # files from sources. + manpreprocessed = qtmodule.preprocess( + moc_sources : 'manualinclude.cpp', + moc_headers : 'manualinclude.h') + + qtmaninclude = executable(qt + 'maninclude', + sources : ['manualinclude.cpp', manpreprocessed], + dependencies : qtcore) + + test(qt + 'maninclude', qtmaninclude) + endif +endforeach diff --git a/test cases/frameworks/4 qt5/q5core.cpp b/test cases/frameworks/4 qt/q5core.cpp index 706e4dc..706e4dc 100644 --- a/test cases/frameworks/4 qt5/q5core.cpp +++ b/test cases/frameworks/4 qt/q5core.cpp diff --git a/test cases/frameworks/4 qt5/stuff.qrc b/test cases/frameworks/4 qt/stuff.qrc index fdfb58e..fdfb58e 100644 --- a/test cases/frameworks/4 qt5/stuff.qrc +++ b/test cases/frameworks/4 qt/stuff.qrc diff --git a/test cases/frameworks/4 qt5/stuff2.qrc b/test cases/frameworks/4 qt/stuff2.qrc index 910e2fb..910e2fb 100644 --- a/test cases/frameworks/4 qt5/stuff2.qrc +++ b/test cases/frameworks/4 qt/stuff2.qrc diff --git a/manual tests/6 qt4/thing.png b/test cases/frameworks/4 qt/thing.png Binary files differindex 4b001bd..4b001bd 100644 --- a/manual tests/6 qt4/thing.png +++ b/test cases/frameworks/4 qt/thing.png diff --git a/manual tests/6 qt4/thing2.png b/test cases/frameworks/4 qt/thing2.png Binary files differindex 4b001bd..4b001bd 100644 --- a/manual tests/6 qt4/thing2.png +++ b/test cases/frameworks/4 qt/thing2.png diff --git a/test cases/frameworks/4 qt5/mainWindow.cpp b/test cases/frameworks/4 qt5/mainWindow.cpp deleted file mode 100644 index cc82c4f..0000000 --- a/test cases/frameworks/4 qt5/mainWindow.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "mainWindow.h" - -MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { - setupUi(this); -} - -MainWindow::~MainWindow() { -} diff --git a/test cases/frameworks/4 qt5/mainWindow.h b/test cases/frameworks/4 qt5/mainWindow.h deleted file mode 100644 index 7f6d906..0000000 --- a/test cases/frameworks/4 qt5/mainWindow.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef MES_MAINWINDOW -#define MES_MAINWINDOW - -#include <QObject> -#include <QMainWindow> -#include "ui_mainWindow.h" - -class NotificationModel; - -class MainWindow : public QMainWindow, private Ui_MainWindow { - Q_OBJECT - -public: - explicit MainWindow(QWidget *parent=0); - ~MainWindow(); - -private: -}; - -#endif diff --git a/test cases/frameworks/4 qt5/mainWindow.ui b/test cases/frameworks/4 qt5/mainWindow.ui deleted file mode 100644 index 2eb226a..0000000 --- a/test cases/frameworks/4 qt5/mainWindow.ui +++ /dev/null @@ -1,34 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>MainWindow</class> - <widget class="QMainWindow" name="MainWindow"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>270</width> - <height>115</height> - </rect> - </property> - <property name="windowTitle"> - <string>MainWindow</string> - </property> - <widget class="QWidget" name="centralwidget"> - <widget class="QPushButton" name="pushButton"> - <property name="geometry"> - <rect> - <x>10</x> - <y>10</y> - <width>241</width> - <height>91</height> - </rect> - </property> - <property name="text"> - <string>I am a button</string> - </property> - </widget> - </widget> - </widget> - <resources/> - <connections/> -</ui> diff --git a/test cases/frameworks/4 qt5/manualinclude.cpp b/test cases/frameworks/4 qt5/manualinclude.cpp deleted file mode 100644 index 0602882..0000000 --- a/test cases/frameworks/4 qt5/manualinclude.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include"manualinclude.h" -#include<QCoreApplication> - -#include<QObject> - -ManualInclude::ManualInclude() { -} - -class MocClass : public QObject { - Q_OBJECT -}; - -int main(int argc, char **argv) { - ManualInclude mi; - MocClass mc; - return 0; -} - -#include"manualinclude.moc" - diff --git a/test cases/frameworks/4 qt5/manualinclude.h b/test cases/frameworks/4 qt5/manualinclude.h deleted file mode 100644 index 4a00b6c..0000000 --- a/test cases/frameworks/4 qt5/manualinclude.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef MANUALINCLUDE_H_ -#define MANUALINCLUDE_H_ - -#include<QObject> - -class ManualInclude : public QObject { - Q_OBJECT - -public: - ManualInclude(); - -signals: - int mysignal(); -}; - -#endif diff --git a/test cases/frameworks/4 qt5/meson.build b/test cases/frameworks/4 qt5/meson.build deleted file mode 100644 index 5672071..0000000 --- a/test cases/frameworks/4 qt5/meson.build +++ /dev/null @@ -1,41 +0,0 @@ -project('qt5 build test', 'cpp') - -qt5 = import('qt5') -qt5dep = dependency('qt5', modules : ['Core', 'Gui', 'Widgets']) - -# The following has two resource files because having two in one target -# requires you to do it properly or you get linker symbol clashes. - -prep = qt5.preprocess( - moc_headers : ['mainWindow.h'], # These need to be fed through the moc tool before use. - ui_files : 'mainWindow.ui', # XML files that need to be compiled with the uic tol. - qresources : ['stuff.qrc', 'stuff2.qrc'], # Resource file for rcc compiler. -) - -q5exe = executable('qt5app', - sources : ['main.cpp', 'mainWindow.cpp', # Sources that don't need preprocessing. - prep], - dependencies : qt5dep) - -# We need a console test application because some test environments -# do not have an X server. - -qt5core = dependency('qt5', modules : 'Core') - -qt5coreapp = executable('q5core', 'q5core.cpp', - dependencies : qt5core) - -test('qt5test', qt5coreapp) - -# The build system needs to include the cpp files from -# headers but the user must manually include moc -# files from sources. -manpreprocessed = qt5.preprocess( - moc_sources : 'manualinclude.cpp', - moc_headers : 'manualinclude.h') - -q5maninclude = executable('q5maninclude', - sources : ['manualinclude.cpp', manpreprocessed], - dependencies : qt5core) - -test('q5maninclude', q5maninclude) diff --git a/test cases/frameworks/4 qt5/thing.png b/test cases/frameworks/4 qt5/thing.png Binary files differdeleted file mode 100644 index 4b001bd..0000000 --- a/test cases/frameworks/4 qt5/thing.png +++ /dev/null diff --git a/test cases/frameworks/4 qt5/thing2.png b/test cases/frameworks/4 qt5/thing2.png Binary files differdeleted file mode 100644 index 4b001bd..0000000 --- a/test cases/frameworks/4 qt5/thing2.png +++ /dev/null diff --git a/test cases/frameworks/7 gnome/gir/meson.build b/test cases/frameworks/7 gnome/gir/meson.build index a513062..51bbbab 100644 --- a/test cases/frameworks/7 gnome/gir/meson.build +++ b/test cases/frameworks/7 gnome/gir/meson.build @@ -1,7 +1,7 @@ libsources = ['meson-sample.c', 'meson-sample.h'] girlib = shared_library( - 'girlib', + 'gir_lib', sources : libsources, dependencies : gobj, install : true diff --git a/test cases/frameworks/7 gnome/installed_files.txt b/test cases/frameworks/7 gnome/installed_files.txt index c922f8b..a18e445 100644 --- a/test cases/frameworks/7 gnome/installed_files.txt +++ b/test cases/frameworks/7 gnome/installed_files.txt @@ -3,6 +3,6 @@ usr/include/enums2.h usr/include/enums3.h usr/include/marshaller.h usr/lib/girepository-1.0/Meson-1.0.typelib -usr/lib/libgirlib.so +usr/lib/libgir_lib.so usr/share/gir-1.0/Meson-1.0.gir usr/share/glib-2.0/schemas/com.github.meson.gschema.xml diff --git a/test cases/python3/3 cython/meson.build b/test cases/python3/3 cython/meson.build index cd245c7..8729640 100644 --- a/test cases/python3/3 cython/meson.build +++ b/test cases/python3/3 cython/meson.build @@ -13,5 +13,5 @@ if cython.found() env : ['PYTHONPATH=' + pydir] ) else - message('Cython not found, skipping test.') + error('MESON_SKIP_TEST: Cython not found, skipping test.') endif diff --git a/test cases/vala/13 find library/meson.build b/test cases/vala/13 find library/meson.build new file mode 100644 index 0000000..03054d2 --- /dev/null +++ b/test cases/vala/13 find library/meson.build @@ -0,0 +1,9 @@ +project('find vala library', 'vala', 'c') + +valac = meson.get_compiler('vala') + +gobject = dependency('gobject-2.0') +zlib = valac.find_library('zlib') + +e = executable('zlibtest', 'test.vala', dependencies : [gobject, zlib]) +test('testzlib', e) diff --git a/test cases/vala/13 find library/test.vala b/test cases/vala/13 find library/test.vala new file mode 100644 index 0000000..b087cfb --- /dev/null +++ b/test cases/vala/13 find library/test.vala @@ -0,0 +1,6 @@ +using ZLib; + +public static int main(string[] args) { + stdout.printf("ZLIB_VERSION is: %s\n", ZLib.VERSION.STRING); + return 0; +} diff --git a/test cases/vala/4 config/meson-something-else.vapi b/test cases/vala/4 config/meson-something-else.vapi new file mode 100644 index 0000000..ce2b83a --- /dev/null +++ b/test cases/vala/4 config/meson-something-else.vapi @@ -0,0 +1 @@ +public const string SOMETHING_ELSE; diff --git a/test cases/vala/4 config/meson.build b/test cases/vala/4 config/meson.build index 9436e41..7cb93cf 100644 --- a/test cases/vala/4 config/meson.build +++ b/test cases/vala/4 config/meson.build @@ -1,11 +1,15 @@ project('valatest', 'vala', 'c') -valadeps = [dependency('glib-2.0'), dependency('gobject-2.0')] +valac = meson.get_compiler('vala') +# Try to find our library +valadeps = [valac.find_library('meson-something-else', dirs : meson.current_source_dir())] +valadeps += [dependency('glib-2.0'), dependency('gobject-2.0')] e = executable( 'valaprog', sources : ['config.vapi', 'prog.vala'], dependencies : valadeps, -c_args : '-DDATA_DIRECTORY="@0@"'.format(meson.current_source_dir()) +c_args : ['-DDATA_DIRECTORY="@0@"'.format(meson.current_source_dir()), + '-DSOMETHING_ELSE="Out of this world!"'] ) test('valatest', e) diff --git a/test cases/vala/4 config/prog.vala b/test cases/vala/4 config/prog.vala index 7ab600c..2b08dad 100644 --- a/test cases/vala/4 config/prog.vala +++ b/test cases/vala/4 config/prog.vala @@ -2,6 +2,7 @@ class MainProg : GLib.Object { public static int main(string[] args) { stdout.printf("DATA_DIRECTORY is: %s.\n", DATA_DIRECTORY); + stdout.printf("SOMETHING_ELSE is: %s.\n", SOMETHING_ELSE); return 0; } } diff --git a/test cases/vala/6 static library/installed_files.txt b/test cases/vala/6 static library/installed_files.txt new file mode 100644 index 0000000..f464bc0 --- /dev/null +++ b/test cases/vala/6 static library/installed_files.txt @@ -0,0 +1 @@ +usr/lib/libextractedlib.a diff --git a/test cases/vala/6 static library/meson.build b/test cases/vala/6 static library/meson.build index ae8eeb9..c74bdf8 100644 --- a/test cases/vala/6 static library/meson.build +++ b/test cases/vala/6 static library/meson.build @@ -3,6 +3,12 @@ project('valastatic', 'vala', 'c') valadeps = [dependency('glib-2.0'), dependency('gobject-2.0')] l = static_library('valalib', 'mylib.vala', dependencies : valadeps) +# NOTE: This static library is not usable from Vala because it does not carry +# forward the .vapi and .h files generated by Valac to the next BuildTarget. +# Will have to be fixed with https://github.com/mesonbuild/meson/issues/891 +m = static_library('extractedlib', + objects : l.extract_all_objects(), + install : true) e = executable('valaprog', 'prog.vala', link_with : l, |