diff options
30 files changed, 681 insertions, 283 deletions
diff --git a/authors.txt b/authors.txt index d8b0abc..d628368 100644 --- a/authors.txt +++ b/authors.txt @@ -33,3 +33,4 @@ Nicolas Schneider Luke Adams Rogiel Sulzbach Tim-Philipp Müller +Emmanuele Bassi @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 + +# Copyright 2016 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import urllib.request, json, sys, os, shutil, subprocess +import configparser, hashlib + +private_repos = {'meson', 'wrapweb', 'meson-ci'} + +def gh_get(url): + r = urllib.request.urlopen(url) + jd = json.loads(r.read().decode('utf-8')) + return jd + +def list_projects(): + jd = gh_get('https://api.github.com/orgs/mesonbuild/repos') + entries = [entry['name'] for entry in jd] + entries = [e for e in entries if e not in private_repos] + entries.sort() + for i in entries: + print(i) + return 0 + +def unpack(sproj, branch, outdir): + subprocess.check_call(['git', 'clone', '-b', branch, 'https://github.com/mesonbuild/%s.git' % sproj, outdir]) + usfile = os.path.join(outdir, 'upstream.wrap') + assert(os.path.isfile(usfile)) + config = configparser.ConfigParser() + config.read(usfile) + us_url = config['wrap-file']['source_url'] + us = urllib.request.urlopen(us_url).read() + h = hashlib.sha256() + h.update(us) + dig = h.hexdigest() + should = config['wrap-file']['source_hash'] + if dig != should: + print('Incorrect hash on download.') + print(' expected:', dig) + print(' obtained:', should) + return 1 + spdir = os.path.split(outdir)[0] + ofilename = os.path.join(spdir, config['wrap-file']['source_filename']) + ofile = open(ofilename, 'wb') + ofile.write(us) + if 'lead_directory_missing' in config['wrap-file']: + os.mkdir(outdir) + shutil.unpack_archive(ofilename, outdir) + else: + shutil.unpack_archive(ofilename, spdir) + extdir = os.path.join(spdir, config['wrap-file']['directory']) + assert(os.path.isdir(extdir)) + shutil.move(os.path.join(outdir, '.git'), extdir) + subprocess.check_call(['git', 'reset', '--hard'], cwd=extdir) + shutil.rmtree(outdir) + shutil.move(extdir, outdir) + shutil.rmtree(os.path.join(outdir, '.git')) + os.unlink(ofilename) + +def install(sproj): + sproj_dir = os.path.join('subprojects', sproj) + if not os.path.isdir('subprojects'): + print('Run this in your source root and make sure there is a subprojects directory in it.') + return 1 + if os.path.isdir(sproj_dir): + print('Subproject is already there. To update, nuke the dir and reinstall.') + return 1 + blist = gh_get('https://api.github.com/repos/mesonbuild/%s/branches' % sproj) + blist = [b['name'] for b in blist] + blist = [b for b in blist if b != 'master'] + blist.sort() + branch = blist[-1] + print('Using branch', branch) + return unpack(sproj, branch, sproj_dir) + +def run(args): + if len(args) == 0 or args[0] == '-h' or args[0] == '--help': + print(sys.argv[0], 'list/install', 'package_name') + return 1 + command = args[0] + args = args[1:] + if command == 'list': + list_projects() + return 0 + elif command == 'install': + if len(args) != 1: + print('Install requires exactly one argument.') + return 1 + return install(args[0]) + else: + print('Unknown command') + return 1 + +if __name__ == '__main__': + print('This is an emergency wrap downloader. Use only when wrapdb is down.') + sys.exit(run(sys.argv[1:])) diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 29966b9..d4a0f99 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -34,6 +34,18 @@ class InstallData(): self.install_scripts = [] self.install_subdirs = [] +class ExecutableSerialisation(): + def __init__(self, name, fname, cmd_args, env, is_cross, exe_wrapper, + workdir, extra_paths): + self.name = name + self.fname = fname + self.cmd_args = cmd_args + self.env = env + self.is_cross = is_cross + self.exe_runner = exe_wrapper + self.workdir = workdir + self.extra_paths = extra_paths + class TestSerialisation: def __init__(self, name, suite, fname, is_cross, exe_wrapper, is_parallel, cmd_args, env, should_fail, valgrind_args, timeout, workdir, extra_paths): @@ -154,6 +166,35 @@ class Backend(): raise MesonException('Unknown data type in object list.') return obj_list + def serialise_executable(self, exe, cmd_args, workdir, env={}): + import uuid + # Can't just use exe.name here; it will likely be run more than once + scratch_file = 'meson_exe_{0}_{1}.dat'.format(exe.name, + str(uuid.uuid4())[:8]) + exe_data = os.path.join(self.environment.get_scratch_dir(), scratch_file) + with open(exe_data, 'wb') as f: + if isinstance(exe, dependencies.ExternalProgram): + exe_fullpath = exe.fullpath + else: + exe_fullpath = [os.path.join(self.environment.get_build_dir(), + self.get_target_filename(exe))] + is_cross = self.environment.is_cross_build() and \ + self.environment.cross_info.need_cross_compiler() and \ + self.environment.cross_info.need_exe_wrapper() + if is_cross: + exe_wrapper = self.environment.cross_info.config['binaries'].get('exe_wrapper', None) + else: + exe_wrapper = None + if mesonlib.is_windows(): + extra_paths = self.determine_windows_extra_paths(exe) + else: + extra_paths = [] + es = ExecutableSerialisation(exe.name, exe_fullpath, cmd_args, env, + is_cross, exe_wrapper, workdir, + extra_paths) + pickle.dump(es, f) + return exe_data + def serialise_tests(self): test_data = os.path.join(self.environment.get_scratch_dir(), 'meson_test_setup.dat') datafile = open(test_data, 'wb') @@ -163,6 +204,7 @@ class Backend(): datafile = open(benchmark_data, 'wb') self.write_benchmark_file(datafile) datafile.close() + return (test_data, benchmark_data) def has_source_suffix(self, target, suffix): for s in target.get_sources(): @@ -316,7 +358,9 @@ class Backend(): fname = exe.fullpath else: fname = [os.path.join(self.environment.get_build_dir(), self.get_target_filename(t.get_exe()))] - is_cross = self.environment.is_cross_build() and self.environment.cross_info.need_cross_compiler() + is_cross = self.environment.is_cross_build() and \ + self.environment.cross_info.need_cross_compiler() and \ + self.environment.cross_info.need_exe_wrapper() if is_cross: exe_wrapper = self.environment.cross_info.config['binaries'].get('exe_wrapper', None) else: @@ -367,8 +411,9 @@ class Backend(): def exe_object_to_cmd_array(self, exe): if self.environment.is_cross_build() and \ - isinstance(exe, build.BuildTarget) and exe.is_cross: - if 'exe_wrapper' not in self.environment.cross_info: + self.environment.cross_info.need_exe_wrapper() and \ + isinstance(exe, build.BuildTarget) and exe.is_cross: + if 'exe_wrapper' not in self.environment.cross_info.config: s = 'Can not use target %s as a generator because it is cross-built\n' s += 'and no exe wrapper is defined. You might want to set it to native instead.' s = s % exe.name @@ -388,6 +433,16 @@ class Backend(): final_args.append(a) return final_args + def get_custom_target_provided_libraries(self, target): + libs = [] + for t in target.get_generated_sources(): + if not isinstance(t, build.CustomTarget): + continue + for f in t.output: + if self.environment.is_library(f): + libs.append(os.path.join(self.get_target_dir(t), f)) + return libs + def eval_custom_target_command(self, target, absolute_paths=False): if not absolute_paths: ofilenames = [os.path.join(self.get_target_dir(target), i) for i in target.output] diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index ec07395..4b85565 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -176,9 +176,6 @@ int dummy; self.generate_phony(outfile) outfile.write('# Build rules for targets\n\n') [self.generate_target(t, outfile) for t in self.build.get_targets().values()] - if len(self.build.pot) > 0: - outfile.write('# Build rules for localisation.\n\n') - self.generate_po(outfile) outfile.write('# Test rules\n\n') self.generate_tests(outfile) outfile.write('# Install rules\n\n') @@ -188,6 +185,7 @@ int dummy; outfile.write('# Coverage rules\n\n') self.generate_coverage_rules(outfile) outfile.write('# Suffix\n\n') + self.generate_utils(outfile) self.generate_ending(outfile) # Only ovewrite the old build file after the new one has been # fully created. @@ -199,7 +197,10 @@ int dummy; def generate_compdb(self): ninja_exe = environment.detect_ninja() builddir = self.environment.get_build_dir() - jsondb = subprocess.check_output([ninja_exe, '-t', 'compdb', 'c_COMPILER', 'cpp_COMPILER'], cwd=builddir) + try: + jsondb = subprocess.check_output([ninja_exe, '-t', 'compdb', 'c_COMPILER', 'cpp_COMPILER'], cwd=builddir) + except Exception: + raise MesonException('Could not create compilation database.') open(os.path.join(builddir, 'compile_commands.json'), 'wb').write(jsondb) # Get all generated headers. Any source file might need them so @@ -341,6 +342,7 @@ int dummy; def generate_custom_target(self, target, outfile): (srcs, ofilenames, cmd) = self.eval_custom_target_command(target) deps = [] + desc = 'Generating {0} with a {1} command.' for i in target.get_dependencies(): # FIXME, should not grab element at zero but rather expand all. if isinstance(i, list): @@ -364,9 +366,23 @@ int dummy; tmp = [tmp] for fname in tmp: elem.add_dep(os.path.join(self.get_target_dir(d), fname)) + # Windows doesn't have -rpath, so for EXEs that need DLLs built within + # the project, we need to set PATH so the DLLs are found. We use + # a serialized executable wrapper for that and check if the + # CustomTarget command needs extra paths first. + if mesonlib.is_windows() and \ + self.determine_windows_extra_paths(target.command[0]): + exe_data = self.serialise_executable(target.command[0], cmd[1:], + # All targets are built from the build dir + self.environment.get_build_dir()) + cmd = [sys.executable, self.environment.get_build_command(), + '--internal', 'exe', exe_data] + cmd_type = 'meson_exe.py custom' + else: + cmd_type = 'custom' elem.add_item('COMMAND', cmd) - elem.add_item('description', 'Generating %s with a custom command.' % target.name) + elem.add_item('description', desc.format(target.name, cmd_type)) elem.write(outfile) self.processed_targets[target.name + target.type_suffix()] = True @@ -394,9 +410,11 @@ int dummy; if isinstance(texe, build.Executable): abs_exe = os.path.join(self.environment.get_build_dir(), self.get_target_filename(texe)) deps.append(self.get_target_filename(texe)) - if self.environment.is_cross_build() \ - and self.environment.cross_info.config['binaries'].get('exe_wrapper', None) is not None: - cmd += [self.environment.cross_info.config['binaries']['exe_wrapper']] + if self.environment.is_cross_build() and \ + self.environment.cross_info.need_exe_wrapper(): + exe_wrap = self.environment.cross_info.config['binaries'].get('exe_wrapper', None) + if exe_wrap is not None: + cmd += [exe_wrap] cmd.append(abs_exe) else: cmd.append(target.command) @@ -407,51 +425,33 @@ int dummy; elem.write(outfile) self.processed_targets[target.name + target.type_suffix()] = True - def generate_po(self, outfile): - for p in self.build.pot: - (packagename, languages, subdir) = p - input_file = os.path.join(subdir, 'POTFILES') - elem = NinjaBuildElement(self.all_outputs, 'pot', 'GEN_POT', []) - elem.add_item('PACKAGENAME', packagename) - elem.add_item('OUTFILE', packagename + '.pot') - elem.add_item('FILELIST', os.path.join(self.environment.get_source_dir(), input_file)) - elem.add_item('OUTDIR', os.path.join(self.environment.get_source_dir(), subdir)) - elem.write(outfile) - for l in languages: - infile = os.path.join(self.environment.get_source_dir(), subdir, l + '.po') - outfilename = os.path.join(subdir, l + '.gmo') - lelem = NinjaBuildElement(self.all_outputs, outfilename, 'GEN_GMO', infile) - lelem.add_item('INFILE', infile) - lelem.add_item('OUTFILE', outfilename) - lelem.write(outfile) - def generate_coverage_rules(self, outfile): (gcovr_exe, lcov_exe, genhtml_exe) = environment.find_coverage_tools() added_rule = False if gcovr_exe: added_rule = True elem = NinjaBuildElement(self.all_outputs, 'coverage-xml', 'CUSTOM_COMMAND', '') - elem.add_item('COMMAND', [gcovr_exe, '-x', '-r', self.environment.get_build_dir(),\ + elem.add_item('COMMAND', [gcovr_exe, '-x', '-r', self.environment.get_source_dir(),\ '-o', os.path.join(self.environment.get_log_dir(), 'coverage.xml')]) elem.add_item('DESC', 'Generating XML coverage report.') elem.write(outfile) elem = NinjaBuildElement(self.all_outputs, 'coverage-text', 'CUSTOM_COMMAND', '') - elem.add_item('COMMAND', [gcovr_exe, '-r', self.environment.get_build_dir(),\ + elem.add_item('COMMAND', [gcovr_exe, '-r', self.environment.get_source_dir(),\ '-o', os.path.join(self.environment.get_log_dir(), 'coverage.txt')]) elem.add_item('DESC', 'Generating text coverage report.') elem.write(outfile) if lcov_exe and genhtml_exe: added_rule = True - phony_elem = NinjaBuildElement(self.all_outputs, 'coverage-html', 'phony', 'coveragereport/index.html') - phony_elem.write(outfile) - - elem = NinjaBuildElement(self.all_outputs, 'coveragereport/index.html', 'CUSTOM_COMMAND', '') htmloutdir = os.path.join(self.environment.get_log_dir(), 'coveragereport') + covinfo = os.path.join(self.environment.get_log_dir(), 'coverage.info') + phony_elem = NinjaBuildElement(self.all_outputs, 'coverage-html', 'phony', os.path.join(htmloutdir, 'index.html')) + phony_elem.write(outfile) + elem = NinjaBuildElement(self.all_outputs, os.path.join(htmloutdir, 'index.html'), 'CUSTOM_COMMAND', '') command = [lcov_exe, '--directory', self.environment.get_build_dir(),\ - '--capture', '--output-file', 'coverage.info', '--no-checksum',\ + '--capture', '--output-file', covinfo, '--no-checksum',\ '&&', genhtml_exe, '--prefix', self.environment.get_build_dir(),\ '--output-directory', htmloutdir, '--title', 'Code coverage',\ - '--legend', '--show-details', 'coverage.info'] + '--legend', '--show-details', covinfo] elem.add_item('COMMAND', command) elem.add_item('DESC', 'Generating HTML coverage report.') elem.write(outfile) @@ -475,7 +475,6 @@ int dummy; self.generate_header_install(d) self.generate_man_install(d) self.generate_data_install(d) - self.generate_po_install(d, elem) self.generate_custom_install_script(d) self.generate_subdir_install(d) elem.write(outfile) @@ -483,17 +482,6 @@ int dummy; ofile = open(install_data_file, 'wb') pickle.dump(d, ofile) - def generate_po_install(self, d, elem): - for p in self.build.pot: - (package_name, languages, subdir) = p - # FIXME: assumes only one po package per source - d.po_package_name = package_name - for lang in languages: - rel_src = os.path.join(subdir, lang + '.gmo') - src_file = os.path.join(self.environment.get_build_dir(), rel_src) - d.po.append((src_file, self.environment.coredata.get_builtin_option('localedir'), lang)) - elem.add_dep(rel_src) - def generate_target_install(self, d): libdir = self.environment.get_libdir() bindir = self.environment.get_bindir() @@ -582,10 +570,9 @@ int dummy; elem.write(outfile) def generate_tests(self, outfile): - self.serialise_tests() + (test_data, benchmark_data) = self.serialise_tests() valgrind = environment.find_valgrind() script_root = self.environment.get_script_dir() - test_data = os.path.join(self.environment.get_scratch_dir(), 'meson_test_setup.dat') cmd = [ sys.executable, self.environment.get_build_command(), '--internal', 'test' ] if not self.environment.coredata.get_builtin_option('stdsplit'): cmd += ['--no-stdsplit'] @@ -608,7 +595,6 @@ int dummy; # And then benchmarks. benchmark_script = os.path.join(script_root, 'meson_benchmark.py') - benchmark_data = os.path.join(self.environment.get_scratch_dir(), 'meson_benchmark_setup.dat') cmd = [sys.executable, self.environment.get_build_command(), '--internal', 'benchmark', benchmark_data] elem = NinjaBuildElement(self.all_outputs, 'benchmark', 'CUSTOM_COMMAND', ['all', 'PHONY']) elem.add_item('COMMAND', cmd) @@ -639,25 +625,6 @@ int dummy; outfile.write(" command = %s %s %s %s %s %s --backend ninja\n" % c) outfile.write(' description = Regenerating build files\n') outfile.write(' generator = 1\n\n') - if len(self.build.pot) > 0: - self.generate_gettext_rules(outfile) - outfile.write('\n') - - def generate_gettext_rules(self, outfile): - rule = 'rule GEN_POT\n' - command = " command = xgettext --package-name=$PACKAGENAME -p $OUTDIR -f $FILELIST -D '%s' -k_ -o $OUTFILE\n" % \ - self.environment.get_source_dir() - desc = " description = Creating pot file for package $PACKAGENAME.\n" - outfile.write(rule) - outfile.write(command) - outfile.write(desc) - outfile.write('\n') - rule = 'rule GEN_GMO\n' - command = ' command = msgfmt $INFILE -o $OUTFILE\n' - desc = ' description = Generating gmo file $OUTFILE\n' - outfile.write(rule) - outfile.write(command) - outfile.write(desc) outfile.write('\n') def generate_phony(self, outfile): @@ -1719,16 +1686,6 @@ rule FORTRAN_DEP_HACK elem.add_item('LINK_ARGS', commands) return elem - def get_custom_target_provided_libraries(self, target): - libs = [] - for t in target.get_generated_sources(): - if not isinstance(t, build.CustomTarget): - continue - for f in t.output: - if self.environment.is_library(f): - libs.append(os.path.join(self.get_target_dir(t), f)) - return libs - def determine_rpath_dirs(self, target): link_deps = target.get_all_link_deps() result = [] @@ -1804,6 +1761,16 @@ rule FORTRAN_DEP_HACK other_deps.append(outfilename) return (src_deps, other_deps) + # For things like scan-build and other helper tools we might have. + def generate_utils(self, outfile): + cmd = [sys.executable, self.environment.get_build_command(), + '--internal', 'scanbuild', self.environment.source_dir, self.environment.build_dir, + sys.executable, self.environment.get_build_command()] + elem = NinjaBuildElement(self.all_outputs, 'scan-build', 'CUSTOM_COMMAND', 'PHONY') + elem.add_item('COMMAND', cmd) + elem.add_item('pool', 'console') + elem.write(outfile) + def generate_ending(self, outfile): targetlist = [self.get_target_filename(t) for t in self.build.get_targets().values()\ if not isinstance(t, build.RunTarget)] diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index 725b8ed..82d0dc9 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -69,13 +69,18 @@ class Vs2010Backend(backends.Backend): if len(src_conflicts) > 1} def generate_custom_generator_commands(self, target, parent_node): - all_output_files = [] + generator_output_files = [] commands = [] inputs = [] outputs = [] + custom_target_include_dirs = [] + custom_target_output_files = [] for genlist in target.get_generated_sources(): if isinstance(genlist, build.CustomTarget): - all_output_files += [os.path.join(self.get_target_dir(genlist), i) for i in genlist.output] + custom_target_output_files += [os.path.join(self.get_target_dir(genlist), i) for i in genlist.output] + idir = self.relpath(self.get_target_dir(genlist), self.get_target_dir(target)) + if idir not in custom_target_include_dirs: + custom_target_include_dirs.append(idir) else: generator = genlist.get_generator() exe = generator.get_exe() @@ -93,7 +98,7 @@ class Vs2010Backend(backends.Backend): infilename = os.path.join(self.environment.get_source_dir(), curfile) outfiles = genlist.get_outputs_for(curfile) outfiles = [os.path.join(target_private_dir, of) for of in outfiles] - all_output_files += outfiles + generator_output_files += outfiles args = [x.replace("@INPUT@", infilename).replace('@OUTPUT@', sole_output)\ for x in base_args] args = [x.replace("@SOURCE_DIR@", self.environment.get_source_dir()).replace("@BUILD_DIR@", target_private_dir) @@ -111,7 +116,7 @@ class Vs2010Backend(backends.Backend): ET.SubElement(cbs, 'Message').text = 'Generating custom sources.' pg = ET.SubElement(parent_node, 'PropertyGroup') ET.SubElement(pg, 'CustomBuildBeforeTargets').text = 'ClCompile' - return all_output_files + return generator_output_files, custom_target_output_files, custom_target_include_dirs def generate(self, interp): self.resolve_source_conflicts() @@ -260,6 +265,8 @@ class Vs2010Backend(backends.Backend): lang = self.lang_from_source_file(i) if lang not in languages: languages.append(lang) + elif self.environment.is_library(i): + pass else: # Everything that is not an object or source file is considered a header. headers.append(i) @@ -436,8 +443,12 @@ class Vs2010Backend(backends.Backend): ET.SubElement(type_config, 'WholeProgramOptimization').text = 'false' ET.SubElement(type_config, 'UseDebugLibraries').text = 'true' ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.props') - generated_files = self.generate_custom_generator_commands(target, root) + generated_files, custom_target_output_files, generated_files_include_dirs = self.generate_custom_generator_commands(target, root) (gen_src, gen_hdrs, gen_objs, gen_langs) = self.split_sources(generated_files) + (custom_src, custom_hdrs, custom_objs, custom_langs) = self.split_sources(custom_target_output_files) + gen_src += custom_src + gen_hdrs += custom_hdrs + gen_langs += custom_langs direlem = ET.SubElement(root, 'PropertyGroup') fver = ET.SubElement(direlem, '_ProjectFileVersion') fver.text = self.project_file_version @@ -455,7 +466,7 @@ class Vs2010Backend(backends.Backend): opt = ET.SubElement(clconf, 'Optimization') opt.text = 'disabled' inc_dirs = ['.', self.relpath(self.get_target_private_dir(target), self.get_target_dir(target)), - proj_to_src_dir] + proj_to_src_dir] + generated_files_include_dirs extra_args = {'c': [], 'cpp': []} for l, args in self.environment.coredata.external_args.items(): @@ -561,10 +572,14 @@ class Vs2010Backend(backends.Backend): rel_path = self.relpath(lobj.subdir, target.subdir) linkname = os.path.join(rel_path, lobj.get_import_filename()) additional_links.append(linkname) + for lib in self.get_custom_target_provided_libraries(target): + additional_links.append(self.relpath(lib, self.get_target_dir(target))) additional_objects = [] for o in self.flatten_object_list(target, down): assert(isinstance(o, str)) additional_objects.append(o) + for o in custom_objs: + additional_objects.append(self.relpath(o, self.get_target_dir(target))) if len(additional_links) > 0: additional_links.append('%(AdditionalDependencies)') ET.SubElement(link, 'AdditionalDependencies').text = ';'.join(additional_links) @@ -773,7 +788,6 @@ if %%errorlevel%% neq 0 goto :VCEnd''' ET.SubElement(midl, 'ProxyFileName').text = '%(Filename)_p.c' postbuild = ET.SubElement(action, 'PostBuildEvent') ET.SubElement(postbuild, 'Message') - test_data = os.path.join(self.environment.get_scratch_dir(), 'meson_test_setup.dat') test_command = [sys.executable, self.environment.get_build_command(), '--internal', @@ -787,14 +801,12 @@ endlocal & call :cmErrorLevel %%errorlevel%% & goto :cmDone exit /b %%1 :cmDone if %%errorlevel%% neq 0 goto :VCEnd''' + test_data = self.serialise_tests()[0] ET.SubElement(postbuild, 'Command').text =\ cmd_templ % ('" "'.join(test_command), test_data) ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.targets') tree = ET.ElementTree(root) tree.write(ofname, encoding='utf-8', xml_declaration=True) - datafile = open(test_data, 'wb') - self.serialise_tests() - datafile.close() # ElementTree can not do prettyprinting so do it manually #doc = xml.dom.minidom.parse(ofname) #open(ofname, 'w').write(doc.toprettyxml()) diff --git a/mesonbuild/backend/xcodebackend.py b/mesonbuild/backend/xcodebackend.py index 6bda826..0ce90ce 100644 --- a/mesonbuild/backend/xcodebackend.py +++ b/mesonbuild/backend/xcodebackend.py @@ -64,7 +64,7 @@ class XCodeBackend(backends.Backend): def generate(self, interp): self.interpreter = interp - self.serialise_tests() + test_data = self.serialise_tests()[0] self.generate_filemap() self.generate_buildmap() self.generate_buildstylemap() @@ -92,7 +92,7 @@ class XCodeBackend(backends.Backend): self.generate_pbx_group() self.generate_pbx_native_target() self.generate_pbx_project() - self.generate_pbx_shell_build_phase() + self.generate_pbx_shell_build_phase(test_data) self.generate_pbx_sources_build_phase() self.generate_pbx_target_dependency() self.generate_xc_build_configuration() @@ -480,7 +480,7 @@ class XCodeBackend(backends.Backend): self.write_line('};') self.ofile.write('/* End PBXProject section */\n') - def generate_pbx_shell_build_phase(self): + def generate_pbx_shell_build_phase(self, test_data): self.ofile.write('\n/* Begin PBXShellScriptBuildPhase section */\n') self.write_line('%s = {' % self.test_command_id) self.indent_level += 1 @@ -496,7 +496,6 @@ class XCodeBackend(backends.Backend): self.write_line('shellPath = /bin/sh;') script_root = self.environment.get_script_dir() test_script = os.path.join(script_root, 'meson_test.py') - test_data = os.path.join(self.environment.get_scratch_dir(), 'meson_test_setup.dat') cmd = [sys.executable, test_script, test_data, '--wd', self.environment.get_build_dir()] cmdstr = ' '.join(["'%s'" % i for i in cmd]) self.write_line('shellScript = "%s";' % cmdstr) diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 60b5ec0..9de556c 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -95,7 +95,6 @@ class Build: self.data = [] self.static_linker = None self.static_cross_linker = None - self.pot = [] self.subprojects = {} self.install_scripts = [] self.postconf_scripts = [] @@ -872,11 +871,12 @@ class CustomTarget: self.install = kwargs['install'] if not isinstance(self.install, bool): raise InvalidArguments('"install" must be boolean.') - if 'install_dir' not in kwargs: - raise InvalidArguments('"install_dir" not specified.') - self.install_dir = kwargs['install_dir'] - if not(isinstance(self.install_dir, str)): - raise InvalidArguments('"install_dir" must be a string.') + if self.install: + if 'install_dir' not in kwargs: + raise InvalidArguments('"install_dir" not specified.') + self.install_dir = kwargs['install_dir'] + if not(isinstance(self.install_dir, str)): + raise InvalidArguments('"install_dir" must be a string.') else: self.install = False self.build_always = kwargs.get('build_always', False) diff --git a/mesonbuild/compilers.py b/mesonbuild/compilers.py index c4e8f0d..241192e 100644 --- a/mesonbuild/compilers.py +++ b/mesonbuild/compilers.py @@ -420,29 +420,31 @@ class CCompiler(Compiler): def get_linker_search_args(self, dirname): return ['-L'+dirname] - def sanity_check(self, work_dir): - mlog.debug('Sanity testing C compiler:', ' '.join(self.exelist)) + def sanity_check_impl(self, work_dir, sname, code): + mlog.debug('Sanity testing ' + self.language + ' compiler:', ' '.join(self.exelist)) mlog.debug('Is cross compiler: %s.' % str(self.is_cross)) - source_name = os.path.join(work_dir, 'sanitycheckc.c') + extra_flags = [] + source_name = os.path.join(work_dir, sname) + binname = sname.rsplit('.', 1)[0] if self.is_cross: - binname = 'sanitycheckc_cross' - else: - binname = 'sanitycheckc' + binname += '_cross' + if self.exe_wrapper is None: + # Linking cross built apps is painful. You can't really + # tell if you should use -nostdlib or not and for example + # on OSX the compiler binary is the same but you need + # a ton of compiler flags to differentiate between + # arm and x86_64. So just compile. + extra_flags = self.get_compile_only_args() + # Is a valid executable output for all toolchains and platforms + binname += '.exe' + # Write binary check source binary_name = os.path.join(work_dir, binname) ofile = open(source_name, 'w') - ofile.write('int main(int argc, char **argv) { int class=0; return class; }\n') + ofile.write(code) ofile.close() - if self.is_cross and self.exe_wrapper is None: - # Linking cross built apps is painful. You can't really - # tell if you should use -nostdlib or not and for example - # on OSX the compiler binary is the same but you need - # a ton of compiler flags to differentiate between - # arm and x86_64. So just compile. - extra_flags = ['-c'] - else: - extra_flags = [] - cmdlist = self.exelist + extra_flags + [source_name, '-o', binary_name] + # Compile sanity check + cmdlist = self.exelist + extra_flags + [source_name] + self.get_output_args(binary_name) pc = subprocess.Popen(cmdlist, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (stdo, stde) = pc.communicate() stdo = stdo.decode() @@ -454,7 +456,8 @@ class CCompiler(Compiler): mlog.debug(stde) mlog.debug('-----') if pc.returncode != 0: - raise EnvironmentException('Compiler %s can not compile programs.' % self.name_string()) + raise EnvironmentException('Compiler {0} can not compile programs.'.format(self.name_string())) + # Run sanity check if self.is_cross: if self.exe_wrapper is None: # Can't check if the binaries run so we have to assume they do @@ -466,7 +469,11 @@ class CCompiler(Compiler): pe = subprocess.Popen(cmdlist) pe.wait() if pe.returncode != 0: - raise EnvironmentException('Executables created by C compiler %s are not runnable.' % self.name_string()) + raise EnvironmentException('Executables created by {0} compiler {1} are not runnable.'.format(self.language, self.name_string())) + + def sanity_check(self, work_dir): + code = 'int main(int argc, char **argv) { int class=0; return class; }\n' + return self.sanity_check_impl(work_dir, 'sanitycheckc.c', code) def has_header(self, hname, extra_args=[]): templ = '''#include<%s> @@ -526,7 +533,8 @@ int main () {{ {1}; }}''' ofile = open(srcname, 'w') ofile.write(code) ofile.close() - extra_args = extra_args + self.get_output_args(dstname) + extra_args = self.unix_link_flags_to_native(extra_args) + \ + self.get_output_args(dstname) p = self.compile(code, srcname, extra_args) try: os.remove(dstname) @@ -545,7 +553,7 @@ int main () {{ {1}; }}''' ofile.close() exename = srcname + '.exe' # Is guaranteed to be executable on every platform. commands = self.get_exelist() - commands += extra_args + commands += self.unix_link_flags_to_native(extra_args) commands.append(srcname) commands += self.get_output_args(exename) p = subprocess.Popen(commands, cwd=os.path.split(srcname)[0], stdout=subprocess.PIPE, stderr=subprocess.PIPE) @@ -585,7 +593,7 @@ int main () {{ {1}; }}''' element_exists_templ = '''#include <stdio.h> {0} int main(int argc, char **argv) {{ - {1}; + {1} something; }} ''' templ = '''#include <stdio.h> @@ -602,6 +610,10 @@ int temparray[%d-sizeof(%s)]; for i in range(1, 1024): code = templ % (prefix, i, element) if self.compiles(code, extra_args): + if self.id == 'msvc': + # MSVC refuses to construct an array of zero size, so + # the test only succeeds when i is sizeof(element) + 1 + return i - 1 return i raise EnvironmentException('Cross checking sizeof overflowed.') @@ -624,6 +636,11 @@ int main(int argc, char **argv) { return int(res.stdout) def cross_alignment(self, typename, env, extra_args=[]): + type_exists_templ = '''#include <stdio.h> +int main(int argc, char **argv) {{ + {0} something; +}} +''' templ = '''#include<stddef.h> struct tmp { char c; @@ -636,9 +653,16 @@ int testarray[%d-offsetof(struct tmp, target)]; extra_args += env.cross_info.config['properties'][self.language + '_args'] except KeyError: pass + extra_args += self.get_no_optimization_args() + if not self.compiles(type_exists_templ.format(typename)): + return -1 for i in range(1, 1024): code = templ % (typename, i) if self.compiles(code, extra_args): + if self.id == 'msvc': + # MSVC refuses to construct an array of zero size, so + # the test only succeeds when i is sizeof(element) + 1 + return i - 1 return i raise EnvironmentException('Cross checking offsetof overflowed.') @@ -669,11 +693,19 @@ int main(int argc, char **argv) { return align def has_function(self, funcname, prefix, env, extra_args=[]): + """ + First, this function looks for the symbol in the default libraries + provided by the compiler (stdlib + a few others usually). If that + fails, it checks if any of the headers specified in the prefix provide + an implementation of the function, and if that fails, it checks if it's + implemented as a compiler-builtin. + """ # Define the symbol to something else in case it is defined by the # includes or defines listed by the user `{0}` or by the compiler. # Then, undef the symbol to get rid of it completely. templ = ''' #define {1} meson_disable_define_of_{1} + #include <limits.h> {0} #undef {1} ''' @@ -691,11 +723,14 @@ int main(int argc, char **argv) { # glibc defines functions that are not available on Linux as stubs that # fail with ENOSYS (such as e.g. lchmod). In this case we want to fail # instead of detecting the stub as a valid symbol. - templ += ''' + # We always include limits.h above to ensure that these are defined for + # stub functions. + stubs_fail = ''' #if defined __stub_{1} || defined __stub___{1} fail fail fail this function is not going to work #endif ''' + templ += stubs_fail # And finally the actual function call templ += ''' @@ -714,12 +749,18 @@ int main(int argc, char **argv) { raise EnvironmentException('Cross variable {0} is not a boolean.'.format(varname)) if self.links(templ.format(prefix, funcname), extra_args): return True + # Add -O0 to ensure that the symbol isn't optimized away by the compiler + extra_args += self.get_no_optimization_args() + # Sometimes the implementation is provided by the header, or the header + # redefines the symbol to be something else. In that case, we want to + # still detect the function. We still want to fail if __stub_foo or + # _stub_foo are defined, of course. + if self.links('{0}\n' + stubs_fail + '\nint main() {{ {1}; }}'.format(prefix, funcname), extra_args): + 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 # special functions that ignore all includes and defines, so we just # directly try to link via main(). - # Add -O0 to ensure that the symbol isn't optimized away by the compiler - extra_args += self.get_no_optimization_args() return self.links('int main() {{ {0}; }}'.format('__builtin_' + funcname), extra_args) def has_member(self, typename, membername, prefix, extra_args=[]): @@ -783,42 +824,8 @@ class CPPCompiler(CCompiler): return False def sanity_check(self, work_dir): - source_name = os.path.join(work_dir, 'sanitycheckcpp.cc') - binary_name = os.path.join(work_dir, 'sanitycheckcpp') - ofile = open(source_name, 'w') - ofile.write('class breakCCompiler;int main(int argc, char **argv) { return 0; }\n') - ofile.close() - if self.is_cross and self.exe_wrapper is None: - # Skipping link because of the same reason as for C. - # The comment in CCompiler explains why this is done. - extra_flags = ['-c'] - else: - extra_flags = [] - cmdlist = self.exelist + extra_flags + [source_name, '-o', binary_name] - pc = subprocess.Popen(cmdlist, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - (stdo, stde) = pc.communicate() - stdo = stdo.decode() - stde = stde.decode() - mlog.debug('Sanity check compiler command line:', ' '.join(cmdlist)) - mlog.debug('Sanity check compile stdout:') - mlog.debug(stdo) - mlog.debug('-----\nSanity check compile stderr:') - mlog.debug(stde) - mlog.debug('-----') - pc.wait() - if pc.returncode != 0: - raise EnvironmentException('Compiler %s can not compile programs.' % self.name_string()) - if self.is_cross: - if self.exe_wrapper is None: - # Can't check if the binaries run so we have to assume they do - return - cmdlist = self.exe_wrapper + [binary_name] - else: - cmdlist = [binary_name] - pe = subprocess.Popen(cmdlist) - pe.wait() - if pe.returncode != 0: - raise EnvironmentException('Executables created by C++ compiler %s are not runnable.' % self.name_string()) + code = 'class breakCCompiler;int main(int argc, char **argv) { return 0; }\n' + return self.sanity_check_impl(work_dir, 'sanitycheckcpp.cc', code) class ObjCCompiler(CCompiler): def __init__(self, exelist, version, is_cross, exe_wrap): @@ -1361,40 +1368,9 @@ class VisualStudioCCompiler(CCompiler): objname = os.path.splitext(pchname)[0] + '.obj' return (objname, ['/Yc' + header, '/Fp' + pchname, '/Fo' + objname ]) - def sanity_check(self, work_dir): - source_name = 'sanitycheckc.c' - binary_name = 'sanitycheckc' - ofile = open(os.path.join(work_dir, source_name), 'w') - ofile.write('int main(int argc, char **argv) { return 0; }\n') - ofile.close() - pc = subprocess.Popen(self.exelist + [source_name, '/Fe' + binary_name], - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - cwd=work_dir) - pc.wait() - if pc.returncode != 0: - raise EnvironmentException('Compiler %s can not compile programs.' % self.name_string()) - pe = subprocess.Popen(os.path.join(work_dir, binary_name)) - pe.wait() - if pe.returncode != 0: - raise EnvironmentException('Executables created by C++ compiler %s are not runnable.' % self.name_string()) - def build_rpath_args(self, build_dir, rpath_paths, install_rpath): return [] - def find_library(self, libname, extra_dirs): - code = '''int main(int argc, char **argv) { - return 0; -} - ''' - args = [] - for i in extra_dirs: - args += self.get_linker_search_args(i) - args.append(libname + '.lib') - if self.links(code, extra_args=args): - return args - return None - # FIXME, no idea what these should be. def thread_flags(self): return [] @@ -1456,24 +1432,6 @@ class VisualStudioCPPCompiler(VisualStudioCCompiler): return True return False - def sanity_check(self, work_dir): - source_name = 'sanitycheckcpp.cpp' - binary_name = 'sanitycheckcpp' - ofile = open(os.path.join(work_dir, source_name), 'w') - ofile.write('class BreakPlainC;int main(int argc, char **argv) { return 0; }\n') - ofile.close() - pc = subprocess.Popen(self.exelist + [source_name, '/Fe' + binary_name], - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - cwd=work_dir) - pc.wait() - if pc.returncode != 0: - raise EnvironmentException('Compiler %s can not compile programs.' % self.name_string()) - pe = subprocess.Popen(os.path.join(work_dir, binary_name)) - pe.wait() - if pe.returncode != 0: - raise EnvironmentException('Executables created by C++ compiler %s are not runnable.' % self.name_string()) - def get_options(self): return {'cpp_eh' : coredata.UserComboOption('cpp_eh', 'C++ exception handling type.', @@ -1730,7 +1688,10 @@ class GnuCPPCompiler(CPPCompiler): def get_options(self): opts = {'cpp_std' : coredata.UserComboOption('cpp_std', 'C++ language standard to use', ['none', 'c++03', 'c++11', 'c++14'], - 'none')} + 'none'), + 'cpp_debugstl': coredata.UserBooleanOption('cpp_debugstl', + 'STL debug mode', + False)} if self.gcc_type == GCC_MINGW: opts.update({ 'cpp_winlibs': coredata.UserStringArrayOption('c_winlibs', 'Standard Win libraries to link against', @@ -1743,6 +1704,8 @@ class GnuCPPCompiler(CPPCompiler): std = options['cpp_std'] if std.value != 'none': args.append('-std=' + std.value) + if options['cpp_debugstl'].value: + args.append('-D_GLIBCXX_DEBUG=1') return args def get_option_link_args(self, options): diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 39e0da6..8227340 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -229,4 +229,5 @@ forbidden_target_names = {'clean': None, 'benchmark': None, 'install': None, 'build.ninja': None, + 'scan-build': None, } diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index 19594c8..5096320 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os, re, subprocess +import os, re, subprocess, platform from . import coredata, mesonlib from .compilers import * import configparser @@ -52,6 +52,33 @@ def detect_ninja(): if p.returncode == 0: return n +def detect_cpu_family(): + """ + Python is inconsistent in its platform module. + It returns different values for the same cpu. + For x86 it might return 'x86', 'i686' or somesuch. + Do some canonicalization. + """ + trial = platform.machine().lower() + if trial.startswith('i') and trial.endswith('86'): + return 'x86' + if trial.startswith('arm'): + return 'arm' + if trial == 'amd64': + return 'x86_64' + # Add fixes here as bugs are reported. + return trial + +def detect_cpu(): + trial = platform.machine().lower() + if trial == 'amd64': + return 'x86_64' + # Add fixes here as bugs are reported. + return trial + +def detect_system(): + return platform.system().lower() + class Environment(): private_dir = 'meson-private' @@ -131,7 +158,8 @@ class Environment(): coredata.save(self.coredata, cdf) def get_script_dir(self): - return os.path.join(os.path.dirname(self.meson_script_file), '../scripts') + import mesonbuild.scripts + return os.path.dirname(mesonbuild.scripts.__file__) def get_log_dir(self): return self.log_dir @@ -181,7 +209,10 @@ class Environment(): compilers = [self.cross_info.config['binaries']['c']] ccache = [] is_cross = True - exe_wrap = self.cross_info.config['binaries'].get('exe_wrapper', None) + if self.cross_info.need_exe_wrapper(): + exe_wrap = self.cross_info.config['binaries'].get('exe_wrapper', None) + else: + exe_wrap = [] elif evar in os.environ: compilers = os.environ[evar].split() ccache = [] @@ -246,7 +277,10 @@ class Environment(): if self.is_cross_build() and want_cross: compilers = [self.cross_info['fortran']] is_cross = True - exe_wrap = self.cross_info.get('exe_wrapper', None) + if self.cross_info.need_exe_wrapper(): + exe_wrap = self.cross_info.get('exe_wrapper', None) + else: + exe_wrap = [] elif evar in os.environ: compilers = os.environ[evar].split() is_cross = False @@ -321,7 +355,10 @@ class Environment(): compilers = [self.cross_info.config['binaries']['cpp']] ccache = [] is_cross = True - exe_wrap = self.cross_info.config['binaries'].get('exe_wrapper', None) + if self.cross_info.need_exe_wrapper(): + exe_wrap = self.cross_info.config['binaries'].get('exe_wrapper', None) + else: + exe_wrap = [] elif evar in os.environ: compilers = os.environ[evar].split() ccache = [] @@ -384,7 +421,10 @@ class Environment(): if self.is_cross_build() and want_cross: exelist = [self.cross_info['objc']] is_cross = True - exe_wrap = self.cross_info.get('exe_wrapper', None) + if self.cross_info.need_exe_wrapper(): + exe_wrap = self.cross_info.get('exe_wrapper', None) + else: + exe_wrap = [] else: exelist = self.get_objc_compiler_exelist() is_cross = False @@ -414,7 +454,10 @@ class Environment(): if self.is_cross_build() and want_cross: exelist = [self.cross_info['objcpp']] is_cross = True - exe_wrap = self.cross_info.get('exe_wrapper', None) + if self.cross_info.need_exe_wrapper(): + exe_wrap = self.cross_info.get('exe_wrapper', None) + else: + exe_wrap = [] else: exelist = self.get_objcpp_compiler_exelist() is_cross = False @@ -726,3 +769,12 @@ class CrossBuildInfo(): # But not when cross compiling a cross compiler. def need_cross_compiler(self): return 'host_machine' in self.config + + def need_exe_wrapper(self): + if self.has_host() and detect_cpu_family() == 'x86_64' and \ + self.config['host_machine']['cpu_family'] == 'x86' and \ + self.config['host_machine']['system'] == detect_system(): + # Can almost always run 32-bit binaries on 64-bit natively if the + # host and build systems are the same + return False + return True diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index b83f4a3..f269c2f 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -322,24 +322,14 @@ class BuildMachine(InterpreterObject): 'endian' : self.endian_method, }) - # Python is inconsistent in its platform module. - # It returns different values for the same cpu. - # For x86 it might return 'x86', 'i686' or somesuch. - # Do some canonicalization. def cpu_family_method(self, args, kwargs): - trial = platform.machine().lower() - if trial.startswith('i') and trial.endswith('86'): - return 'x86' - if trial.startswith('arm'): - return 'arm' - # Add fixes here as bugs are reported. - return trial + return environment.detect_cpu_family() def cpu_method(self, args, kwargs): - return platform.machine().lower() + return environment.detect_cpu() def system_method(self, args, kwargs): - return platform.system().lower() + return environment.detect_system() def endian_method(self, args, kwargs): return sys.byteorder @@ -883,9 +873,16 @@ class MesonMain(InterpreterObject): return self.interpreter.environment.build_dir def has_exe_wrapper_method(self, args, kwargs): - if self.is_cross_build_method(None, None) and 'binaries' in self.build.environment.cross_info.config: - return 'exe_wrap' in self.build.environment.cross_info.config['binaries'] - return True # This is semantically confusing. + if self.is_cross_build_method(None, None) and \ + 'binaries' in self.build.environment.cross_info.config and \ + self.build.environment.cross_info.need_exe_wrapper(): + exe_wrap = self.build.environment.cross_info.config['binaries'].get('exe_wrapper', None) + if exe_wrap is None: + return False + # We return True when exe_wrap is defined, when it's not needed, and + # when we're compiling natively. The last two are semantically confusing. + # Need to revisit this. + return True def is_cross_build_method(self, args, kwargs): return self.build.environment.is_cross_build() @@ -1055,6 +1052,7 @@ class Interpreter(): raise InterpreterException('Tried to create target %s which already exists.' % v.name) self.build.targets[v.name] = v elif isinstance(v, build.InstallScript): + print('x') self.build.install_scripts.append(v) elif isinstance(v, build.Data): self.build.data.append(v) @@ -1304,15 +1302,7 @@ class Interpreter(): @stringArgs def func_gettext(self, nodes, args, kwargs): - if len(args) != 1: - raise InterpreterException('Gettext requires one positional argument (package name).') - packagename = args[0] - languages = kwargs.get('languages', None) - check_stringlist(languages, 'Argument languages must be a list of strings.') - # TODO: check that elements are strings - if len(self.build.pot) > 0: - raise InterpreterException('More than one gettext definition currently not supported.') - self.build.pot.append((packagename, languages, self.subdir)) + raise InterpreterException('Gettext() function has been moved to module i18n. Import it and use i18n.gettext() instead') def func_option(self, nodes, args, kwargs): raise InterpreterException('Tried to call option() in build description file. All options must be in the option file.') @@ -1788,7 +1778,9 @@ class Interpreter(): if self.is_subproject(): newsuite = [] for s in suite: - newsuite.append(self.subproject.replace(' ', '_').replace('.', '_') + '.' + s) + if len(s) > 0: + s = '.' + s + newsuite.append(self.subproject.replace(' ', '_').replace('.', '_') + s) suite = newsuite t = Test(args[0], suite, args[1].held_object, par, cmd_args, env, should_fail, valgrind_args, timeout, workdir) if is_base_test: @@ -2145,6 +2137,12 @@ class Interpreter(): return int(obj) except Exception: raise InterpreterException('String can not be converted to int: ' + obj) + elif method_name == 'join': + if len(posargs) != 1: + raise InterpreterException('Join() takes exactly one argument.') + strlist = posargs[0] + check_stringlist(strlist) + return obj.join(strlist) raise InterpreterException('Unknown method "%s" for a string.' % method_name) def to_native(self, arg): diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index 2ac0932..fe831bd 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -252,7 +252,9 @@ def do_mesondefine(line, confdata): def do_conf_file(src, dst, confdata): data = open(src).readlines() - regex = re.compile('@(.*?)@') + # Only allow (a-z, A-Z, 0-9, _, -) as valid characters for a define + # Also allow escaping '@' with '\@' + regex = re.compile(r'[^\\]?@([-a-zA-Z0-9_]+)@') result = [] for line in data: if line.startswith('#mesondefine'): diff --git a/mesonbuild/mesonmain.py b/mesonbuild/mesonmain.py index 3b05afb..4f8314c 100644 --- a/mesonbuild/mesonmain.py +++ b/mesonbuild/mesonmain.py @@ -163,7 +163,10 @@ itself as required.''' def run_script_command(args): cmdname = args[0] cmdargs = args[1:] - if cmdname == 'test': + if cmdname == 'exe': + import mesonbuild.scripts.meson_exe as abc + cmdfunc = abc.run + elif cmdname == 'test': import mesonbuild.scripts.meson_test as abc cmdfunc = abc.run elif cmdname == 'benchmark': @@ -193,9 +196,15 @@ def run_script_command(args): elif cmdname == 'symbolextractor': import mesonbuild.scripts.symbolextractor as abc cmdfunc = abc.run + elif cmdname == 'scanbuild': + import mesonbuild.scripts.scanbuild as abc + cmdfunc = abc.run elif cmdname == 'vcstagger': import mesonbuild.scripts.vcstagger as abc cmdfunc = abc.run + elif cmdname == 'gettext': + import mesonbuild.scripts.gettext as abc + cmdfunc = abc.run else: raise MesonException('Unknown internal command {}.'.format(cmdname)) return cmdfunc(cmdargs) diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py index 0881f69..955d12b 100644 --- a/mesonbuild/mintro.py +++ b/mesonbuild/mintro.py @@ -89,14 +89,14 @@ def list_target_files(target_name, coredata, builddata): def list_buildoptions(coredata, builddata): buildtype= {'choices': ['plain', 'debug', 'debugoptimized', 'release'], 'type' : 'combo', - 'value' : coredata.builtin_options['buildtype'].value, + 'value' : coredata.get_builtin_option('buildtype'), 'description' : 'Build type', 'name' : 'type'} - strip = {'value' : coredata.builtin_options['strip'].value, + strip = {'value' : coredata.get_builtin_option('strip'), 'type' : 'boolean', 'description' : 'Strip on install', 'name' : 'strip'} - unity = {'value' : coredata.builtin_options['unity'].value, + unity = {'value' : coredata.get_builtin_option('unity'), 'type' : 'boolean', 'description' : 'Unity build', 'name' : 'unity'} diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index 39a6ff7..2c37655 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -289,11 +289,21 @@ class GnomeModule: '--modulename=' + modulename] args += self.unpack_args('--htmlargs=', 'html_args', kwargs) args += self.unpack_args('--scanargs=', 'scan_args', kwargs) + args += self.unpack_args('--fixxrefargs=', 'fixxref_args', kwargs) 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 gtkdoc_html_dir(self, state, args, kwarga): + if len(args) != 1: + raise MesonException('Must have exactly one argument.') + modulename = args[0] + if not isinstance(modulename, str): + raise MesonException('Argument must be a string') + return os.path.join('share/gtkdoc/html', modulename) + + def unpack_args(self, arg, kwarg_name, kwargs): try: new_args = kwargs[kwarg_name] diff --git a/mesonbuild/modules/i18n.py b/mesonbuild/modules/i18n.py new file mode 100644 index 0000000..51668cb --- /dev/null +++ b/mesonbuild/modules/i18n.py @@ -0,0 +1,44 @@ +# Copyright 2016 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .. import coredata, mesonlib, build +import sys + +class I18nModule: + + def gettext(self, state, args, kwargs): + if len(args) != 1: + raise coredata.MesonException('Gettext requires one positional argument (package name).') + packagename = args[0] + languages = mesonlib.stringlistify(kwargs.get('languages', [])) + if len(languages) == 0: + raise coredata.MesonException('List of languages empty.') + potargs = [state.environment.get_build_command(), '--internal', 'gettext', 'pot', packagename] + pottarget = build.RunTarget(packagename + '-pot', sys.executable, potargs, state.subdir) + gmoargs = [state.environment.get_build_command(), '--internal', 'gettext', 'gen_gmo'] + languages + gmotarget = build.RunTarget(packagename + '-gmo', sys.executable, gmoargs, state.subdir) + installcmd = [sys.executable, + state.environment.get_build_command(), + '--internal', + 'gettext', + 'install', + state.subdir, + packagename, + state.environment.coredata.get_builtin_option('localedir'), + ] + languages + iscript = build.InstallScript(installcmd) + return [pottarget, gmotarget, iscript] + +def initialize(): + return I18nModule() diff --git a/mesonbuild/scripts/delwithsuffix.py b/mesonbuild/scripts/delwithsuffix.py index 38ab406..e112101 100644 --- a/mesonbuild/scripts/delwithsuffix.py +++ b/mesonbuild/scripts/delwithsuffix.py @@ -17,7 +17,7 @@ import os, sys def run(args): - if len(sys.argv) != 2: + if len(sys.argv) != 3: print('delwithsuffix.py <root of subdir to process> <suffix to delete>') sys.exit(1) diff --git a/mesonbuild/scripts/gettext.py b/mesonbuild/scripts/gettext.py new file mode 100644 index 0000000..adc4483 --- /dev/null +++ b/mesonbuild/scripts/gettext.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 + +# Copyright 2016 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os, subprocess, shutil + +def run_potgen(src_sub, pkgname, langs): + listfile = os.path.join(src_sub, 'POTFILES') + ofile = os.path.join(src_sub, pkgname + '.pot') + return subprocess.call(['xgettext', '--package-name=' + pkgname, '-p', src_sub, listfile, + '-D', os.environ['MESON_SOURCE_ROOT'], '-k_', '-o', ofile]) + +def gen_gmo(src_sub, bld_sub, langs): + for l in langs: + subprocess.check_call(['msgfmt', os.path.join(src_sub, l + '.po'), + '-o', os.path.join(bld_sub, l + '.gmo')]) + return 0 + +def do_install(src_sub, bld_sub, dest, pkgname, langs): + for l in langs: + srcfile = os.path.join(bld_sub, l + '.gmo') + outfile = os.path.join(dest, l, 'LC_MESSAGES', + pkgname + '.mo') + os.makedirs(os.path.split(outfile)[0], exist_ok=True) + shutil.copyfile(srcfile, outfile) + shutil.copystat(srcfile, outfile) + print('Installing %s to %s.' % (srcfile, outfile)) + return 0 + +def run(args): + subcmd = args[0] + if subcmd == 'pot': + src_sub = os.path.join(os.environ['MESON_SOURCE_ROOT'], os.environ['MESON_SUBDIR']) + bld_sub = os.path.join(os.environ['MESON_BUILD_ROOT'], os.environ['MESON_SUBDIR']) + return run_potgen(src_sub, args[1], args[2:]) + elif subcmd == 'gen_gmo': + src_sub = os.path.join(os.environ['MESON_SOURCE_ROOT'], os.environ['MESON_SUBDIR']) + bld_sub = os.path.join(os.environ['MESON_BUILD_ROOT'], os.environ['MESON_SUBDIR']) + return gen_gmo(src_sub, bld_sub, args[1:]) + elif subcmd == 'install': + subdir = args[1] + pkgname = args[2] + instsubdir = args[3] + langs = args[4:] + src_sub = os.path.join(os.environ['MESON_SOURCE_ROOT'], subdir) + bld_sub = os.path.join(os.environ['MESON_BUILD_ROOT'], subdir) + dest = os.environ.get('DESTDIR') + os.path.join(os.environ['MESON_INSTALL_PREFIX'], instsubdir) + if gen_gmo(src_sub, bld_sub, langs) != 0: + return 1 + do_install(src_sub, bld_sub, dest, pkgname, langs) + else: + print('Unknown subcommand.') + return 1 diff --git a/mesonbuild/scripts/gtkdochelper.py b/mesonbuild/scripts/gtkdochelper.py index 68be8f2..d920b61 100644 --- a/mesonbuild/scripts/gtkdochelper.py +++ b/mesonbuild/scripts/gtkdochelper.py @@ -28,9 +28,10 @@ parser.add_argument('--mainfile', dest='mainfile') parser.add_argument('--modulename', dest='modulename') parser.add_argument('--htmlargs', dest='htmlargs', default='') parser.add_argument('--scanargs', dest='scanargs', default='') +parser.add_argument('--fixxrefargs', dest='fixxrefargs', default='') def build_gtkdoc(source_root, build_root, doc_subdir, src_subdir, - main_file, module, html_args, scan_args): + main_file, module, html_args, scan_args, fixxref_args): abs_src = os.path.join(source_root, src_subdir) abs_out = os.path.join(build_root, doc_subdir) htmldir = os.path.join(abs_out, 'html') @@ -76,7 +77,7 @@ def build_gtkdoc(source_root, build_root, doc_subdir, src_subdir, subprocess.check_call(mkhtml_cmd, cwd=os.path.join(abs_out, 'html'), shell=False) fixref_cmd = ['gtkdoc-fixxref', '--module=' + module, - '--module-dir=html'] + '--module-dir=html'] + fixxref_args # print(fixref_cmd) # sys.exit(1) subprocess.check_call(fixref_cmd, cwd=abs_out) @@ -97,6 +98,10 @@ def run(args): scanargs = options.scanargs.split('@@') else: scanargs = [] + if len(options.fixxrefargs) > 0: + fixxrefargs = options.fixxrefargs.split('@@') + else: + fixxrefargs = [] build_gtkdoc(options.sourcedir, options.builddir, options.subdir, @@ -104,7 +109,8 @@ def run(args): options.mainfile, options.modulename, htmlargs, - scanargs) + scanargs, + fixxrefargs) if 'MESON_INSTALL_PREFIX' in os.environ: if 'DESTDIR' in os.environ: diff --git a/mesonbuild/scripts/meson_exe.py b/mesonbuild/scripts/meson_exe.py new file mode 100644 index 0000000..f075fa0 --- /dev/null +++ b/mesonbuild/scripts/meson_exe.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 + +# Copyright 2013-2016 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import sys +import argparse +import pickle +import platform +import subprocess + +import mesonbuild + +options = None + +parser = argparse.ArgumentParser() +parser.add_argument('args', nargs='+') + +def is_windows(): + platname = platform.system().lower() + return platname == 'windows' or 'mingw' in platname + +def run_with_mono(fname): + if fname.endswith('.exe') and not is_windows(): + return True + return False + +def run_exe(exe): + if exe.fname[0].endswith('.jar'): + cmd = ['java', '-jar'] + exe.fname + elif not exe.is_cross and run_with_mono(exe.fname[0]): + cmd = ['mono'] + exe.fname + else: + if exe.is_cross: + if exe.exe_runner is None: + raise Exception('BUG: Trying to run cross-compiled exes with no wrapper') + else: + cmd = [exe.exe_runner] + exe.fname + else: + cmd = exe.fname + child_env = os.environ.copy() + child_env.update(exe.env) + if len(exe.extra_paths) > 0: + child_env['PATH'] = ';'.join(exe.extra_paths + ['']) + child_env['PATH'] + p = subprocess.Popen(cmd + exe.cmd_args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=child_env, + cwd=exe.workdir) + +def run(args): + global options + options = parser.parse_args(args) + if len(options.args) != 1: + print('Test runner for Meson. Do not run on your own, mmm\'kay?') + print(sys.argv[0] + ' [data file]') + exe_data_file = options.args[0] + exe = pickle.load(open(exe_data_file, 'rb')) + run_exe(exe) + +if __name__ == '__main__': + sys.exit(run(sys.argv[1:])) diff --git a/mesonbuild/scripts/meson_install.py b/mesonbuild/scripts/meson_install.py index cc86b62..20a50f7 100644 --- a/mesonbuild/scripts/meson_install.py +++ b/mesonbuild/scripts/meson_install.py @@ -33,7 +33,6 @@ def do_install(datafilename): install_headers(d) install_man(d) install_data(d) - install_po(d) run_install_script(d) def install_subdirs(d): @@ -51,19 +50,6 @@ def install_subdirs(d): shutil.copytree(src_dir, final_dst, symlinks=True) print('Installing subdir %s to %s.' % (src_dir, dst_dir)) -def install_po(d): - packagename = d.po_package_name - for f in d.po: - srcfile = f[0] - localedir = f[1] - languagename = f[2] - outfile = os.path.join(d.fullprefix, localedir, languagename, 'LC_MESSAGES', - packagename + '.mo') - os.makedirs(os.path.split(outfile)[0], exist_ok=True) - shutil.copyfile(srcfile, outfile) - shutil.copystat(srcfile, outfile) - print('Installing %s to %s.' % (srcfile, outfile)) - def install_data(d): for i in d.data: fullfilename = i[0] diff --git a/mesonbuild/scripts/scanbuild.py b/mesonbuild/scripts/scanbuild.py new file mode 100644 index 0000000..f90c3c7 --- /dev/null +++ b/mesonbuild/scripts/scanbuild.py @@ -0,0 +1,39 @@ +# Copyright 2016 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys, os +import subprocess +import shutil +import tempfile + +def scanbuild(srcdir, blddir, privdir, logdir, args): + with tempfile.TemporaryDirectory(dir=privdir) as scandir: + meson_cmd = ['scan-build'] + args + build_cmd = ['scan-build', '-o', logdir, 'ninja'] + rc = subprocess.call(meson_cmd + [srcdir, scandir]) + if rc != 0: + return rc + return subprocess.call(build_cmd) + +def run(args): + srcdir = args[0] + blddir = args[1] + meson_cmd = args[2:] + 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') + return 1 + return scanbuild(srcdir, blddir, privdir, logdir, meson_cmd) diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py index ad8f106..6e3383c 100644 --- a/mesonbuild/wrap/wrap.py +++ b/mesonbuild/wrap/wrap.py @@ -90,10 +90,11 @@ class Resolver: def resolve(self, packagename): fname = os.path.join(self.subdir_root, packagename + '.wrap') dirname = os.path.join(self.subdir_root, packagename) + if os.path.isdir(dirname): + # The directory is there? Great, use it. + return packagename if not os.path.isfile(fname): - if os.path.isdir(dirname): - # No wrap file but dir exists -> user put it there manually. - return packagename + # No wrap file with this name? Give up. return None p = PackageDefinition(fname) if p.type == 'file': diff --git a/test cases/common/42 string formatting/meson.build b/test cases/common/42 string formatting/meson.build index c2ee151..0d17448 100644 --- a/test cases/common/42 string formatting/meson.build +++ b/test cases/common/42 string formatting/meson.build @@ -51,3 +51,7 @@ assert(false.to_string() == 'false', 'bool string conversion failed') assert(true.to_string('yes', 'no') == 'yes', 'bool string conversion with args failed') assert(false.to_string('yes', 'no') == 'no', 'bool string conversion with args failed') assert('@0@'.format(true) == 'true', 'bool string formatting failed') + +assert(' '.join(['a', 'b', 'c']) == 'a b c', 'join() array broken') +assert(''.join(['a', 'b', 'c']) == 'abc', 'empty join() broken') +assert(' '.join(['a']) == 'a', 'single join broken') diff --git a/test cases/common/43 has function/meson.build b/test cases/common/43 has function/meson.build index 3736a3d..c7fe353 100644 --- a/test cases/common/43 has function/meson.build +++ b/test cases/common/43 has function/meson.build @@ -3,16 +3,16 @@ project('has function', 'c') cc = meson.get_compiler('c') if not cc.has_function('printf', prefix : '#include<stdio.h>') - error('Existing function not found.') + 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) -assert(cc.has_function('fprintf'), 'Existing function not found without include') +assert(cc.has_function('fprintf'), '"fprintf" function not found without include (should always exist).') if cc.has_function('hfkerhisadf', prefix : '#include<stdio.h>') - error('Found non-existant function.') + error('Found non-existent function "hfkerhisadf".') endif # With glibc on Linux lchmod is a stub that will always return an error, @@ -21,9 +21,9 @@ endif # 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 (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') + #include <unistd.h>'''), '"lchmod" check should have failed') endif # For some functions one needs to define _GNU_SOURCE before including the @@ -31,5 +31,5 @@ endif # 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 existing function') + assert (cc.has_function('sendmmsg'), 'Failed to detect function "sendmmsg" (should always exist).') endif diff --git a/test cases/frameworks/10 gtk-doc/doc/meson.build b/test cases/frameworks/10 gtk-doc/doc/meson.build index 3172b42..5f08e89 100644 --- a/test cases/frameworks/10 gtk-doc/doc/meson.build +++ b/test cases/frameworks/10 gtk-doc/doc/meson.build @@ -1,5 +1,3 @@ -gnome = import('gnome') - cdata = configuration_data() cdata.set('VERSION', '1.0') configure_file(input : 'version.xml.in', diff --git a/test cases/frameworks/10 gtk-doc/meson.build b/test cases/frameworks/10 gtk-doc/meson.build index 9712f21..c6881ab 100644 --- a/test cases/frameworks/10 gtk-doc/meson.build +++ b/test cases/frameworks/10 gtk-doc/meson.build @@ -1,5 +1,9 @@ project('gtkdoctest', 'c') +gnome = import('gnome') + +assert(gnome.gtkdoc_html_dir('foobar') == 'share/gtkdoc/html/foobar', 'Gtkdoc install dir is incorrect.') + inc = include_directories('include') # We have to disable this test until this bug fix has landed to diff --git a/test cases/frameworks/6 gettext/meson.build b/test cases/frameworks/6 gettext/meson.build index 4384978..6bba7e0 100644 --- a/test cases/frameworks/6 gettext/meson.build +++ b/test cases/frameworks/6 gettext/meson.build @@ -1,4 +1,6 @@ project('gettext example', 'c') +i18n = import('i18n') + subdir('po') subdir('src') diff --git a/test cases/frameworks/6 gettext/po/intltest.pot b/test cases/frameworks/6 gettext/po/intltest.pot index 05da67d..d65e2c1 100644 --- a/test cases/frameworks/6 gettext/po/intltest.pot +++ b/test cases/frameworks/6 gettext/po/intltest.pot @@ -1,6 +1,6 @@ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. +# This file is distributed under the same license as the intltest package. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # #, fuzzy @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: intltest\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2013-09-12 19:04+0300\n" +"POT-Creation-Date: 2016-03-28 19:59+0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" diff --git a/test cases/frameworks/6 gettext/po/meson.build b/test cases/frameworks/6 gettext/po/meson.build index 3376c84..8ea2c11 100644 --- a/test cases/frameworks/6 gettext/po/meson.build +++ b/test cases/frameworks/6 gettext/po/meson.build @@ -1,4 +1,3 @@ langs = ['fi', 'de'] -gettext('intltest', -languages : langs) +i18n.gettext('intltest', languages : langs) |