diff options
author | Jussi Pakkanen <jpakkane@gmail.com> | 2016-01-16 17:35:29 +0200 |
---|---|---|
committer | Jussi Pakkanen <jpakkane@gmail.com> | 2016-01-16 17:35:29 +0200 |
commit | 23b98cd6e66c6ae0f070e28e0f8b1566c0b5e585 (patch) | |
tree | e349597556abe3d22578cfb1f9529f4626ceb5aa /mesonbuild/scripts | |
parent | 1510522b1b9970376a1e1cc5f39e00d8749ec19a (diff) | |
download | meson-23b98cd6e66c6ae0f070e28e0f8b1566c0b5e585.zip meson-23b98cd6e66c6ae0f070e28e0f8b1566c0b5e585.tar.gz meson-23b98cd6e66c6ae0f070e28e0f8b1566c0b5e585.tar.bz2 |
Renamed meson package to mesonbuild so that we can have a script named meson in the same toplevel dir.
Diffstat (limited to 'mesonbuild/scripts')
-rw-r--r-- | mesonbuild/scripts/commandrunner.py | 59 | ||||
-rw-r--r-- | mesonbuild/scripts/delwithsuffix.py | 37 | ||||
-rw-r--r-- | mesonbuild/scripts/depfixer.py | 302 | ||||
-rw-r--r-- | mesonbuild/scripts/dirchanger.py | 30 | ||||
-rw-r--r-- | mesonbuild/scripts/gtkdochelper.py | 122 | ||||
-rw-r--r-- | mesonbuild/scripts/meson_benchmark.py | 97 | ||||
-rw-r--r-- | mesonbuild/scripts/meson_install.py | 215 | ||||
-rw-r--r-- | mesonbuild/scripts/meson_test.py | 233 | ||||
-rw-r--r-- | mesonbuild/scripts/regen_checker.py | 45 | ||||
-rw-r--r-- | mesonbuild/scripts/symbolextractor.py | 106 | ||||
-rw-r--r-- | mesonbuild/scripts/vcstagger.py | 36 |
11 files changed, 1282 insertions, 0 deletions
diff --git a/mesonbuild/scripts/commandrunner.py b/mesonbuild/scripts/commandrunner.py new file mode 100644 index 0000000..f5a2fff --- /dev/null +++ b/mesonbuild/scripts/commandrunner.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 + +# Copyright 2014 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. + +"""This program is a wrapper to run external commands. It determines +what to run, sets up the environment and executes the command.""" + +import sys, os, subprocess, shutil + +def run_command(source_dir, build_dir, subdir, command, arguments): + env = {'MESON_SOURCE_ROOT' : source_dir, + 'MESON_BUILD_ROOT' : build_dir, + 'MESON_SUBDIR' : subdir + } + cwd = os.path.join(source_dir, subdir) + child_env = os.environ.copy() + child_env.update(env) + + # Is the command an executable in path? + exe = shutil.which(command) + if exe is not None: + command_array = [exe] + arguments + return subprocess.Popen(command_array, env=child_env, cwd=cwd) + # No? Maybe it is a script in the source tree. + fullpath = os.path.join(source_dir, subdir, command) + command_array = [fullpath] + arguments + try: + return subprocess.Popen(command_array,env=child_env, cwd=cwd) + except FileNotFoundError: + print('Could not execute command "%s".' % command) + sys.exit(1) + +def run(args): + if len(sys.argv) < 4: + print('commandrunner.py <source dir> <build dir> <subdir> <command> [arguments]') + sys.exit(1) + src_dir = sys.argv[1] + build_dir = sys.argv[2] + subdir = sys.argv[3] + command = sys.argv[4] + arguments = sys.argv[5:] + pc = run_command(src_dir, build_dir, subdir, command, arguments) + pc.wait() + return pc.returncode + +if __name__ == '__main__': + sys.exit(run(sys.argv[1:])) diff --git a/mesonbuild/scripts/delwithsuffix.py b/mesonbuild/scripts/delwithsuffix.py new file mode 100644 index 0000000..38ab406 --- /dev/null +++ b/mesonbuild/scripts/delwithsuffix.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 + +# Copyright 2013 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, sys + +def run(args): + if len(sys.argv) != 2: + print('delwithsuffix.py <root of subdir to process> <suffix to delete>') + sys.exit(1) + + topdir = sys.argv[1] + suffix = sys.argv[2] + if suffix[0] != '.': + suffix = '.' + suffix + + for (root, _, files) in os.walk(topdir): + for f in files: + if f.endswith(suffix): + fullname = os.path.join(root, f) + os.unlink(fullname) + return 0 + +if __name__ == '__main__': + run(sys.argv[1:]) diff --git a/mesonbuild/scripts/depfixer.py b/mesonbuild/scripts/depfixer.py new file mode 100644 index 0000000..1ab83b6 --- /dev/null +++ b/mesonbuild/scripts/depfixer.py @@ -0,0 +1,302 @@ +#!/usr/bin/env python3 + +# Copyright 2013-2014 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, struct + +SHT_STRTAB = 3 +DT_NEEDED = 1 +DT_RPATH = 15 +DT_STRTAB = 5 +DT_SONAME = 14 + +class DataSizes(): + def __init__(self, ptrsize, is_le): + if is_le: + p = '<' + else: + p = '>' + self.Half = p+'h' + self.HalfSize = 2 + self.Word = p+'I' + self.WordSize = 4 + self.Sword = p+'i' + self.SwordSize = 4 + if ptrsize == 64: + self.Addr = p+'Q' + self.AddrSize = 8 + self.Off = p+'Q' + self.OffSize = 8 + self.XWord = p+'Q' + self.XWordSize = 8 + self.Sxword = p+'q' + self.SxwordSize = 8 + else: + self.Addr = p+'I' + self.AddrSize = 4 + self.Off = p+'I' + self.OffSize = 4 + +class DynamicEntry(DataSizes): + def __init__(self, ifile, ptrsize, is_le): + super().__init__(ptrsize, is_le) + self.ptrsize = ptrsize + if ptrsize == 64: + self.d_tag = struct.unpack(self.Sxword, ifile.read(self.SxwordSize))[0]; + self.val = struct.unpack(self.XWord, ifile.read(self.XWordSize))[0]; + else: + self.d_tag = struct.unpack(self.Sword, ifile.read(self.SwordSize))[0] + self.val = struct.unpack(self.Word, ifile.read(self.WordSize))[0] + + def write(self, ofile): + if self.ptrsize == 64: + ofile.write(struct.pack(self.Sxword, self.d_tag)) + ofile.write(struct.pack(self.XWord, self.val)) + else: + ofile.write(struct.pack(self.Sword, self.d_tag)) + ofile.write(struct.pack(self.Word, self.val)) + +class SectionHeader(DataSizes): + def __init__(self, ifile, ptrsize, is_le): + super().__init__(ptrsize, is_le) + if ptrsize == 64: + is_64 = True + else: + is_64 = False +#Elf64_Word + self.sh_name = struct.unpack(self.Word, ifile.read(self.WordSize))[0]; +#Elf64_Word + self.sh_type = struct.unpack(self.Word, ifile.read(self.WordSize))[0] +#Elf64_Xword + if is_64: + self.sh_flags = struct.unpack(self.XWord, ifile.read(self.XWordSize))[0] + else: + self.sh_flags = struct.unpack(self.Word, ifile.read(self.WordSize))[0] +#Elf64_Addr + self.sh_addr = struct.unpack(self.Addr, ifile.read(self.AddrSize))[0]; +#Elf64_Off + self.sh_offset = struct.unpack(self.Off, ifile.read(self.OffSize))[0] +#Elf64_Xword + if is_64: + self.sh_size = struct.unpack(self.XWord, ifile.read(self.XWordSize))[0] + else: + self.sh_size = struct.unpack(self.Word, ifile.read(self.WordSize))[0] +#Elf64_Word + self.sh_link = struct.unpack(self.Word, ifile.read(self.WordSize))[0]; +#Elf64_Word + self.sh_info = struct.unpack(self.Word, ifile.read(self.WordSize))[0]; +#Elf64_Xword + if is_64: + self.sh_addralign = struct.unpack(self.XWord, ifile.read(self.XWordSize))[0] + else: + self.sh_addralign = struct.unpack(self.Word, ifile.read(self.WordSize))[0] +#Elf64_Xword + if is_64: + self.sh_entsize = struct.unpack(self.XWord, ifile.read(self.XWordSize))[0] + else: + self.sh_entsize = struct.unpack(self.Word, ifile.read(self.WordSize))[0] + +class Elf(DataSizes): + def __init__(self, bfile): + self.bfile = bfile + self.bf = open(bfile, 'r+b') + (self.ptrsize, self.is_le) = self.detect_elf_type() + super().__init__(self.ptrsize, self.is_le) + self.parse_header() + self.parse_sections() + self.parse_dynamic() + + def detect_elf_type(self): + data = self.bf.read(6) + if data[1:4] != b'ELF': + # This script gets called to non-elf targets too + # so just ignore them. + print('File "%s" is not an ELF file.' % self.bfile) + sys.exit(0) + if data[4] == 1: + ptrsize = 32 + elif data[4] == 2: + ptrsize = 64 + else: + print('File "%s" has unknown ELF class.' % self.bfile) + sys.exit(1) + if data[5] == 1: + is_le = True + elif data[5] == 2: + is_le = False + else: + print('File "%s" has unknown ELF endianness.' % self.bfile) + sys.exit(1) + return (ptrsize, is_le) + + def parse_header(self): + self.bf.seek(0) + self.e_ident = struct.unpack('16s', self.bf.read(16))[0] + self.e_type = struct.unpack(self.Half, self.bf.read(self.HalfSize))[0] + self.e_machine = struct.unpack(self.Half, self.bf.read(self.HalfSize))[0] + self.e_version = struct.unpack(self.Word, self.bf.read(self.WordSize))[0] + self.e_entry = struct.unpack(self.Addr, self.bf.read(self.AddrSize))[0] + self.e_phoff = struct.unpack(self.Off, self.bf.read(self.OffSize))[0] + self.e_shoff = struct.unpack(self.Off, self.bf.read(self.OffSize))[0] + self.e_flags = struct.unpack(self.Word, self.bf.read(self.WordSize))[0] + self.e_ehsize = struct.unpack(self.Half, self.bf.read(self.HalfSize))[0] + self.e_phentsize = struct.unpack(self.Half, self.bf.read(self.HalfSize))[0] + self.e_phnum = struct.unpack(self.Half, self.bf.read(self.HalfSize))[0] + self.e_shentsize = struct.unpack(self.Half, self.bf.read(self.HalfSize))[0] + self.e_shnum = struct.unpack(self.Half, self.bf.read(self.HalfSize))[0] + self.e_shstrndx = struct.unpack(self.Half, self.bf.read(self.HalfSize))[0] + + def parse_sections(self): + self.bf.seek(self.e_shoff) + self.sections = [] + for i in range(self.e_shnum): + self.sections.append(SectionHeader(self.bf, self.ptrsize, self.is_le)) + + def read_str(self): + arr = [] + x = self.bf.read(1) + while x != b'\0': + arr.append(x) + x = self.bf.read(1) + if x == b'': + raise RuntimeError('Tried to read past the end of the file') + return b''.join(arr) + + def find_section(self, target_name): + section_names = self.sections[self.e_shstrndx] + for i in self.sections: + self.bf.seek(section_names.sh_offset + i.sh_name) + name = self.read_str() + if name == target_name: + return i + + def parse_dynamic(self): + sec = self.find_section(b'.dynamic') + self.dynamic = [] + self.bf.seek(sec.sh_offset) + while True: + e = DynamicEntry(self.bf, self.ptrsize, self.is_le) + self.dynamic.append(e) + if e.d_tag == 0: + break + + def print_section_names(self): + section_names = self.sections[self.e_shstrndx] + for i in self.sections: + self.bf.seek(section_names.sh_offset + i.sh_name) + name = self.read_str() + print(name.decode()) + + def print_soname(self): + soname = None + strtab = None + for i in self.dynamic: + if i.d_tag == DT_SONAME: + soname = i + if i.d_tag == DT_STRTAB: + strtab = i + self.bf.seek(strtab.val + soname.val) + print(self.read_str()) + + def get_rpath_offset(self): + sec = self.find_section(b'.dynstr') + for i in self.dynamic: + if i.d_tag == DT_RPATH: + return sec.sh_offset + i.val + return None + + def print_rpath(self): + offset = self.get_rpath_offset() + if offset is None: + print("This file does not have an rpath.") + else: + self.bf.seek(offset) + print(self.read_str()) + + def print_deps(self): + sec = self.find_section(b'.dynstr') + deps = [] + for i in self.dynamic: + if i.d_tag == DT_NEEDED: + deps.append(i) + for i in deps: + offset = sec.sh_offset + i.val + self.bf.seek(offset) + name = self.read_str() + print(name) + + def fix_deps(self, prefix): + sec = self.find_section(b'.dynstr') + deps = [] + for i in self.dynamic: + if i.d_tag == DT_NEEDED: + deps.append(i) + for i in deps: + offset = sec.sh_offset + i.val + self.bf.seek(offset) + name = self.read_str() + if name.startswith(prefix): + basename = name.split(b'/')[-1] + padding = b'\0'*(len(name) - len(basename)) + newname = basename + padding + assert(len(newname) == len(name)) + self.bf.seek(offset) + self.bf.write(newname) + + def fix_rpath(self, new_rpath): + rp_off = self.get_rpath_offset() + if rp_off is None: + print('File does not have rpath. It should be a fully static executable.') + return + self.bf.seek(rp_off) + old_rpath = self.read_str() + if len(old_rpath) < len(new_rpath): + print("New rpath must not be longer than the old one.") + self.bf.seek(rp_off) + self.bf.write(new_rpath) + self.bf.write(b'\0'*(len(old_rpath) - len(new_rpath) + 1)) + if len(new_rpath) == 0: + self.remove_rpath_entry() + + def remove_rpath_entry(self): + sec = self.find_section(b'.dynamic') + for (i, entry) in enumerate(self.dynamic): + if entry.d_tag == DT_RPATH: + rpentry = self.dynamic[i] + rpentry.d_tag = 0 + self.dynamic = self.dynamic[:i] + self.dynamic[i+1:] + [rpentry] + break; + self.bf.seek(sec.sh_offset) + for entry in self.dynamic: + entry.write(self.bf) + return None + +def run(args): + if len(args) < 1 or len(args) > 2: + print('This application resets target rpath.') + print('Don\'t run this unless you know what you are doing.') + print('%s: <binary file> <prefix>' % sys.argv[0]) + exit(1) + e = Elf(args[0]) + if len(args) == 1: + e.print_rpath() + else: + new_rpath = args[1] + e.fix_rpath(new_rpath.encode('utf8')) + return 0 + +if __name__ == '__main__': + run(sys.argv[1:]) diff --git a/mesonbuild/scripts/dirchanger.py b/mesonbuild/scripts/dirchanger.py new file mode 100644 index 0000000..93a901d --- /dev/null +++ b/mesonbuild/scripts/dirchanger.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 + +# Copyright 2015-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. + +'''CD into dir given as first argument and execute +the command given in the rest of the arguments.''' + +import os, subprocess, sys + +def run(args): + dirname = args[0] + command = args[1:] + + os.chdir(dirname) + return subprocess.call(command) + +if __name__ == '__main__': + sys.exit(run(sys.argv[1:])) diff --git a/mesonbuild/scripts/gtkdochelper.py b/mesonbuild/scripts/gtkdochelper.py new file mode 100644 index 0000000..68be8f2 --- /dev/null +++ b/mesonbuild/scripts/gtkdochelper.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +# Copyright 2015-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 argparse + +parser = argparse.ArgumentParser() + +parser.add_argument('--sourcedir', dest='sourcedir') +parser.add_argument('--builddir', dest='builddir') +parser.add_argument('--subdir', dest='subdir') +parser.add_argument('--headerdir', dest='headerdir') +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='') + +def build_gtkdoc(source_root, build_root, doc_subdir, src_subdir, + main_file, module, html_args, scan_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') + scan_cmd = ['gtkdoc-scan', + '--module=' + module, + '--source-dir=' + abs_src] + scan_args +# print(scan_cmd) +# sys.exit(1) + subprocess.check_call(scan_cmd, + cwd=abs_out) + if main_file.endswith('sgml'): + modeflag = '--sgml-mode' + else: + modeflag = '--xml-mode' + mkdb_cmd = ['gtkdoc-mkdb', + '--module=' + module, + '--output-format=xml', + modeflag, + '--source-dir=' + abs_src] + main_abs = os.path.join(source_root, doc_subdir, main_file) + if len(main_file) > 0: + # Yes, this is the flag even if the file is in xml. + mkdb_cmd.append('--main-sgml-file=' + main_file) +# print(mkdb_cmd) +# sys.exit(1) + subprocess.check_call(mkdb_cmd, cwd=abs_out) + shutil.rmtree(htmldir, ignore_errors=True) + try: + os.mkdir(htmldir) + except Exception: + pass + mkhtml_cmd = ['gtkdoc-mkhtml', + '--path=' + abs_src, + module, + ] + html_args + if len(main_file) > 0: + mkhtml_cmd.append('../' + main_file) + else: + mkhtml_cmd.append('%s-docs.xml' % module) + # html gen must be run in the HTML dir +# print(mkhtml_cmd) +# sys.exit(1) + subprocess.check_call(mkhtml_cmd, cwd=os.path.join(abs_out, 'html'), shell=False) + fixref_cmd = ['gtkdoc-fixxref', + '--module=' + module, + '--module-dir=html'] +# print(fixref_cmd) +# sys.exit(1) + subprocess.check_call(fixref_cmd, cwd=abs_out) + +def install_gtkdoc(build_root, doc_subdir, install_prefix, datadir, module): + source = os.path.join(build_root, doc_subdir, 'html') + final_destination = os.path.join(install_prefix, datadir, module) + shutil.rmtree(final_destination, ignore_errors=True) + shutil.copytree(source, final_destination) + +def run(args): + options = parser.parse_args(args) + if len(options.htmlargs) > 0: + htmlargs = options.htmlargs.split('@@') + else: + htmlargs = [] + if len(options.scanargs) > 0: + scanargs = options.scanargs.split('@@') + else: + scanargs = [] + build_gtkdoc(options.sourcedir, + options.builddir, + options.subdir, + options.headerdir, + options.mainfile, + options.modulename, + htmlargs, + scanargs) + + if 'MESON_INSTALL_PREFIX' in os.environ: + if 'DESTDIR' in os.environ: + installdir = os.environ['DESTDIR'] + os.environ['MESON_INSTALL_PREFIX'] + else: + installdir = os.environ['MESON_INSTALL_PREFIX'] + install_gtkdoc(options.builddir, + options.subdir, + installdir, + 'share/gtk-doc/html', + options.modulename) + return 0 + +if __name__ == '__main__': + sys.exit(run(sys.argv[1:])) diff --git a/mesonbuild/scripts/meson_benchmark.py b/mesonbuild/scripts/meson_benchmark.py new file mode 100644 index 0000000..26f1f95 --- /dev/null +++ b/mesonbuild/scripts/meson_benchmark.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 + +# Copyright 2015 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 subprocess, sys, os, argparse +import pickle, statistics, json +from . import meson_test + +parser = argparse.ArgumentParser() +parser.add_argument('--wd', default=None, dest='wd', + help='directory to cd into before running') +parser.add_argument('args', nargs='+') + +def print_stats(numlen, num_tests, name, res, i, duration, stdev): + startpad = ' '*(numlen - len('%d' % (i+1))) + num = '%s%d/%d' % (startpad, i+1, num_tests) + padding1 = ' '*(38-len(name)) + padding2 = ' '*(8-len(res)) + result_str = '%s %s %s%s%s%5.5f s +- %5.5f s' % \ + (num, name, padding1, res, padding2, duration, stdev) + print(result_str) +# write_json_log(jsonlogfile, name, result) + +def print_json_log(jsonlogfile, rawruns, test_name, i): + jsonobj = {'name' : test_name} + runs = [] + for r in rawruns: + runobj = {'duration': r.duration, + 'stdout': r.stdo, + 'stderr': r.stde, + 'returncode' : r.returncode, + 'duration' : r.duration} + runs.append(runobj) + jsonobj['runs'] = runs + jsonlogfile.write(json.dumps(jsonobj) + '\n') + jsonlogfile.flush() + +def run_benchmarks(options, datafile): + failed_tests = 0 + logfile_base = 'meson-logs/benchmarklog' + jsonlogfilename = logfile_base+ '.json' + jsonlogfile = open(jsonlogfilename, 'w') + tests = pickle.load(open(datafile, 'rb')) + num_tests = len(tests) + if num_tests == 0: + print('No benchmarks defined.') + return 0 + iteration_count = 5 + wrap = [] # Benchmarks on cross builds are pointless so don't support them. + for i, test in enumerate(tests): + runs = [] + durations = [] + failed = False + for _ in range(iteration_count): + res = meson_test.run_single_test(wrap, test) + runs.append(res) + durations.append(res.duration) + if res.returncode != 0: + failed = True + mean = statistics.mean(durations) + stddev = statistics.stdev(durations) + if failed: + resultstr = 'FAIL' + failed_tests += 1 + else: + resultstr = 'OK' + print_stats(3, num_tests, test.name, resultstr, i, mean, stddev) + print_json_log(jsonlogfile, runs, test.name, i) + print('\nFull log written to meson-logs/benchmarklog.json.') + return failed_tests + +def run(args): + global failed_tests + options = parser.parse_args(args) + if len(options.args) != 1: + print('Benchmark runner for Meson. Do not run on your own, mmm\'kay?') + print('%s [data file]' % sys.argv[0]) + if options.wd is not None: + os.chdir(options.wd) + datafile = options.args[0] + returncode = run_benchmarks(options, datafile) + return returncode + +if __name__ == '__main__': + sys.exit(run(sys.argv[1:])) diff --git a/mesonbuild/scripts/meson_install.py b/mesonbuild/scripts/meson_install.py new file mode 100644 index 0000000..1ede757 --- /dev/null +++ b/mesonbuild/scripts/meson_install.py @@ -0,0 +1,215 @@ +#!/usr/bin/env python3 + +# Copyright 2013-2014 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, pickle, os, shutil, subprocess, gzip, platform +from glob import glob + +def do_install(datafilename): + ifile = open(datafilename, 'rb') + d = pickle.load(ifile) + destdir_var = 'DESTDIR' + if destdir_var in os.environ: + d.destdir = os.environ[destdir_var] + else: + d.destdir = '' + d.fullprefix = d.destdir + d.prefix + + install_subdirs(d) # Must be first, because it needs to delete the old subtree. + install_targets(d) + install_headers(d) + install_man(d) + install_data(d) + install_po(d) + run_install_script(d) + +def install_subdirs(d): + for (src_dir, dst_dir) in d.install_subdirs: + if os.path.isabs(dst_dir): + dst_dir = d.destdir + dst_dir + else: + dst_dir = d.fullprefix + dst_dir + # Python's copytree works in strange ways. + last_level = os.path.split(src_dir)[-1] + final_dst = os.path.join(dst_dir, last_level) +# Don't do rmtree because final_dst might point to e.g. /var/www +# We might need to revert to walking the directory tree by hand. +# shutil.rmtree(final_dst, ignore_errors=True) + 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] + outfilename = i[1] + if os.path.isabs(outfilename): + outdir = d.destdir + os.path.split(outfilename)[0] + outfilename = d.destdir + outfilename + else: + outdir = os.path.join(d.fullprefix, os.path.split(outfilename)[0]) + outfilename = os.path.join(outdir, os.path.split(outfilename)[1]) + os.makedirs(outdir, exist_ok=True) + print('Installing %s to %s.' % (fullfilename, outdir)) + shutil.copyfile(fullfilename, outfilename) + shutil.copystat(fullfilename, outfilename) + +def install_man(d): + for m in d.man: + outfileroot = m[1] + outfilename = os.path.join(d.fullprefix, outfileroot) + full_source_filename = m[0] + outdir = os.path.split(outfilename)[0] + os.makedirs(outdir, exist_ok=True) + print('Installing %s to %s.' % (full_source_filename, outdir)) + if outfilename.endswith('.gz') and not full_source_filename.endswith('.gz'): + open(outfilename, 'wb').write(gzip.compress(open(full_source_filename, 'rb').read())) + else: + shutil.copyfile(full_source_filename, outfilename) + shutil.copystat(full_source_filename, outfilename) + +def install_headers(d): + for t in d.headers: + fullfilename = t[0] + outdir = os.path.join(d.fullprefix, t[1]) + fname = os.path.split(fullfilename)[1] + outfilename = os.path.join(outdir, fname) + print('Installing %s to %s' % (fname, outdir)) + os.makedirs(outdir, exist_ok=True) + shutil.copyfile(fullfilename, outfilename) + shutil.copystat(fullfilename, outfilename) + +def run_install_script(d): + env = {'MESON_SOURCE_ROOT' : d.source_dir, + 'MESON_BUILD_ROOT' : d.build_dir, + 'MESON_INSTALL_PREFIX' : d.prefix + } + child_env = os.environ.copy() + child_env.update(env) + + for i in d.install_scripts: + script = i.cmd_arr[0] + print('Running custom install script %s' % script) + suffix = os.path.splitext(script)[1].lower() + if platform.system().lower() == 'windows' and suffix != '.bat': + first_line = open(script).readline().strip() + if first_line.startswith('#!'): + commands = first_line[2:].split('#')[0].strip().split() + commands[0] = shutil.which(commands[0].split('/')[-1]) + if commands[0] is None: + raise RuntimeError("Don't know how to run script %s." % script) + final_command = commands + [script] + i.cmd_arr[1:] + else: + final_command = i.cmd_arr + subprocess.check_call(final_command, env=child_env) + +def is_elf_platform(): + platname = platform.system().lower() + if platname == 'darwin' or platname == 'windows': + return False + return True + +def check_for_stampfile(fname): + '''Some languages e.g. Rust have output files + whose names are not known at configure time. + Check if this is the case and return the real + file instead.''' + if fname.endswith('.so') or fname.endswith('.dll'): + if os.stat(fname).st_size == 0: + (base, suffix) = os.path.splitext(fname) + files = glob(base + '-*' + suffix) + if len(files) > 1: + print("Stale dynamic library files in build dir. Can't install.") + sys.exit(1) + if len(files) == 1: + return files[0] + elif fname.endswith('.a') or fname.endswith('.lib'): + if os.stat(fname).st_size == 0: + (base, suffix) = os.path.splitext(fname) + files = glob(base + '-*' + '.rlib') + if len(files) > 1: + print("Stale static library files in build dir. Can't install.") + sys.exit(1) + if len(files) == 1: + return files[0] + return fname + +def install_targets(d): + for t in d.targets: + fname = check_for_stampfile(t[0]) + outdir = os.path.join(d.fullprefix, t[1]) + aliases = t[2] + outname = os.path.join(outdir, os.path.split(fname)[-1]) + should_strip = t[3] + install_rpath = t[4] + print('Installing %s to %s' % (fname, outname)) + os.makedirs(outdir, exist_ok=True) + shutil.copyfile(fname, outname) + shutil.copystat(fname, outname) + if should_strip: + print('Stripping target') + ps = subprocess.Popen(['strip', outname], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (stdo, stde) = ps.communicate() + if ps.returncode != 0: + print('Could not strip file.\n') + print('Stdout:\n%s\n' % stdo.decode()) + print('Stderr:\n%s\n' % stde.decode()) + sys.exit(1) + printed_symlink_error = False + for alias in aliases: + try: + symlinkfilename = os.path.join(outdir, alias) + try: + os.unlink(symlinkfilename) + except FileNotFoundError: + pass + os.symlink(os.path.split(fname)[-1], symlinkfilename) + except NotImplementedError: + if not printed_symlink_error: + print("Symlink creation does not work on this platform.") + printed_symlink_error = True + if is_elf_platform(): + p = subprocess.Popen(d.depfixer + [outname, install_rpath], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + (stdo, stde) = p.communicate() + if p.returncode != 0: + print('Could not fix dependency info.\n') + print('Stdout:\n%s\n' % stdo.decode()) + print('Stderr:\n%s\n' % stde.decode()) + sys.exit(1) + +def run(args): + if len(args) != 1: + print('Installer script for Meson. Do not run on your own, mmm\'kay?') + print('meson_install.py [install info file]') + datafilename = args[0] + do_install(datafilename) + return 0 + +if __name__ == '__main__': + sys.exit(run(sys.argv[1:])) diff --git a/mesonbuild/scripts/meson_test.py b/mesonbuild/scripts/meson_test.py new file mode 100644 index 0000000..03fd073 --- /dev/null +++ b/mesonbuild/scripts/meson_test.py @@ -0,0 +1,233 @@ +#!/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 mesonbuild +import sys, os, subprocess, time, datetime, pickle, multiprocessing, json +import concurrent.futures as conc +import argparse +import platform + +def is_windows(): + platname = platform.system().lower() + return platname == 'windows' or 'mingw' in platname + +tests_failed = [] + +parser = argparse.ArgumentParser() +parser.add_argument('--wrapper', default=None, dest='wrapper', + help='wrapper to run tests with (e.g. valgrind)') +parser.add_argument('--wd', default=None, dest='wd', + help='directory to cd into before running') +parser.add_argument('--suite', default=None, dest='suite', + help='Only run tests belonging to this suite.') +parser.add_argument('args', nargs='+') + + +class TestRun(): + def __init__(self, res, returncode, duration, stdo, stde, cmd): + self.res = res + self.returncode = returncode + self.duration = duration + self.stdo = stdo + self.stde = stde + self.cmd = cmd + +def decode(stream): + try: + return stream.decode('utf-8') + except UnicodeDecodeError: + return stream.decode('iso-8859-1', errors='ignore') + +def write_log(logfile, test_name, result_str, result): + logfile.write(result_str + '\n\n') + logfile.write('--- command ---\n') + if result.cmd is None: + logfile.write('NONE') + else: + logfile.write(' '.join(result.cmd)) + logfile.write('\n--- "%s" stdout ---\n' % test_name) + logfile.write(result.stdo) + logfile.write('\n--- "%s" stderr ---\n' % test_name) + logfile.write(result.stde) + logfile.write('\n-------\n\n') + +def write_json_log(jsonlogfile, test_name, result): + result = {'name' : test_name, + 'stdout' : result.stdo, + 'stderr' : result.stde, + 'result' : result.res, + 'duration' : result.duration, + 'returncode' : result.returncode, + 'command' : result.cmd} + jsonlogfile.write(json.dumps(result) + '\n') + +def run_with_mono(fname): + if fname.endswith('.exe') and not is_windows(): + return True + return False + +def run_single_test(wrap, test): + global tests_failed + if test.fname[0].endswith('.jar'): + cmd = ['java', '-jar'] + test.fname + elif not test.is_cross and run_with_mono(test.fname[0]): + cmd = ['mono'] + test.fname + else: + if test.is_cross: + if test.exe_runner is None: + # Can not run test on cross compiled executable + # because there is no execute wrapper. + cmd = None + else: + cmd = [test.exe_runner] + test.fname + else: + cmd = test.fname + if len(wrap) > 0 and 'valgrind' in wrap[0]: + wrap += test.valgrind_args + if cmd is None: + res = 'SKIP' + duration = 0.0 + stdo = 'Not run because can not execute cross compiled binaries.' + stde = '' + returncode = -1 + else: + cmd = wrap + cmd + test.cmd_args + starttime = time.time() + child_env = os.environ.copy() + child_env.update(test.env) + if len(test.extra_paths) > 0: + child_env['PATH'] = child_env['PATH'] + ';'.join([''] + test.extra_paths) + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + env=child_env, cwd=test.workdir) + timed_out = False + try: + (stdo, stde) = p.communicate(timeout=test.timeout) + except subprocess.TimeoutExpired: + timed_out = True + p.kill() + (stdo, stde) = p.communicate() + endtime = time.time() + duration = endtime - starttime + stdo = decode(stdo) + stde = decode(stde) + if timed_out: + res = 'TIMEOUT' + tests_failed.append((test.name, stdo, stde)) + elif (not test.should_fail and p.returncode == 0) or \ + (test.should_fail and p.returncode != 0): + res = 'OK' + else: + res = 'FAIL' + tests_failed.append((test.name, stdo, stde)) + returncode = p.returncode + return TestRun(res, returncode, duration, stdo, stde, cmd) + +def print_stats(numlen, tests, name, result, i, logfile, jsonlogfile): + startpad = ' '*(numlen - len('%d' % (i+1))) + num = '%s%d/%d' % (startpad, i+1, len(tests)) + padding1 = ' '*(38-len(name)) + padding2 = ' '*(8-len(result.res)) + result_str = '%s %s %s%s%s%5.2f s' % \ + (num, name, padding1, result.res, padding2, result.duration) + print(result_str) + write_log(logfile, name, result_str, result) + write_json_log(jsonlogfile, name, result) + +def drain_futures(futures): + for i in futures: + (result, numlen, tests, name, i, logfile, jsonlogfile) = i + print_stats(numlen, tests, name, result.result(), i, logfile, jsonlogfile) + +def filter_tests(suite, tests): + if suite is None: + return tests + return [x for x in tests if suite in x.suite] + +def run_tests(options, datafilename): + logfile_base = 'meson-logs/testlog' + if options.wrapper is None: + wrap = [] + logfilename = logfile_base + '.txt' + jsonlogfilename = logfile_base+ '.json' + else: + wrap = [options.wrapper] + logfilename = logfile_base + '-' + options.wrapper.replace(' ', '_') + '.txt' + jsonlogfilename = logfile_base + '-' + options.wrapper.replace(' ', '_') + '.json' + logfile = open(logfilename, 'w') + jsonlogfile = open(jsonlogfilename, 'w') + logfile.write('Log of Meson test suite run on %s.\n\n' % datetime.datetime.now().isoformat()) + tests = pickle.load(open(datafilename, 'rb')) + if len(tests) == 0: + print('No tests defined.') + return + numlen = len('%d' % len(tests)) + varname = 'MESON_TESTTHREADS' + if varname in os.environ: + try: + num_workers = int(os.environ[varname]) + except ValueError: + print('Invalid value in %s, using 1 thread.' % varname) + num_workers = 1 + else: + num_workers = multiprocessing.cpu_count() + executor = conc.ThreadPoolExecutor(max_workers=num_workers) + futures = [] + filtered_tests = filter_tests(options.suite, tests) + for i, test in enumerate(filtered_tests): + if test.suite[0] == '': + visible_name = test.name + else: + if options.suite is not None: + visible_name = options.suite + ' / ' + test.name + else: + visible_name = test.suite[0] + ' / ' + test.name + + if not test.is_parallel: + drain_futures(futures) + futures = [] + res = run_single_test(wrap, test) + print_stats(numlen, filtered_tests, visible_name, res, i, logfile, jsonlogfile) + else: + f = executor.submit(run_single_test, wrap, test) + futures.append((f, numlen, filtered_tests, visible_name, i, logfile, jsonlogfile)) + drain_futures(futures) + return logfilename + +def run(args): + global tests_failed + options = parser.parse_args(args) + if len(options.args) != 1: + print('Test runner for Meson. Do not run on your own, mmm\'kay?') + print('%s [data file]' % sys.argv[0]) + if options.wd is not None: + os.chdir(options.wd) + datafile = options.args[0] + logfilename = run_tests(options, datafile) + returncode = 0 + if len(tests_failed) > 0: + print('\nOutput of failed tests (max 10):') + for (name, stdo, stde) in tests_failed[:10]: + print("{} stdout:\n".format(name)) + print(stdo) + print('\n{} stderr:\n'.format(name)) + print(stde) + print('\n') + returncode = 1 + print('\nFull log written to %s.' % logfilename) + return returncode + +if __name__ == '__main__': + sys.exit(run(sys.argv[1:])) diff --git a/mesonbuild/scripts/regen_checker.py b/mesonbuild/scripts/regen_checker.py new file mode 100644 index 0000000..f360a7c --- /dev/null +++ b/mesonbuild/scripts/regen_checker.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 + +# Copyright 2015-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 pickle, subprocess + +# This could also be used for XCode. + +def need_regen(regeninfo): + sln_time = os.stat(os.path.join(regeninfo.build_dir, regeninfo.solutionfile)).st_mtime + for i in regeninfo.depfiles: + curfile = os.path.join(regeninfo.build_dir, i) + curtime = os.stat(curfile).st_mtime + if curtime > sln_time: + return True + return False + +def regen(regeninfo): + scriptdir = os.path.split(__file__)[0] + mesonscript = os.path.join(scriptdir, 'meson.py') + cmd = [sys.executable, mesonscript, regeninfo.build_dir, regeninfo.source_dir, + '--backend=vs2010', 'secret-handshake'] + subprocess.check_call(cmd) + +def run(args): + regeninfo = pickle.load(open(os.path.join(args[0], 'regeninfo.dump'), 'rb')) + if need_regen(regeninfo): + regen(regeninfo) + sys.exit(0) + +if __name__ == '__main__': + run(sys.argv[1:]) diff --git a/mesonbuild/scripts/symbolextractor.py b/mesonbuild/scripts/symbolextractor.py new file mode 100644 index 0000000..79c1264 --- /dev/null +++ b/mesonbuild/scripts/symbolextractor.py @@ -0,0 +1,106 @@ +#!/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. + +# This script extracts the symbols of a given shared library +# into a file. If the symbols have not changed, the file is not +# touched. This information is used to skip link steps if the +# ABI has not changed. + +# This file is basically a reimplementation of +# http://cgit.freedesktop.org/libreoffice/core/commit/?id=3213cd54b76bc80a6f0516aac75a48ff3b2ad67c + +import sys, subprocess +from mesonbuild import mesonlib +import argparse + +parser = argparse.ArgumentParser() + +parser.add_argument('--cross-host', default=None, dest='cross_host', + help='cross compilation host platform') +parser.add_argument('args', nargs='+') + +def dummy_syms(outfilename): + """Just touch it so relinking happens always.""" + open(outfilename, 'w').close() + +def write_if_changed(text, outfilename): + try: + oldtext = open(outfilename, 'r').read() + if text == oldtext: + return + except FileNotFoundError: + pass + open(outfilename, 'w').write(text) + +def linux_syms(libfilename, outfilename): + pe = subprocess.Popen(['readelf', '-d', libfilename], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + output = pe.communicate()[0].decode() + if pe.returncode != 0: + raise RuntimeError('Readelf does not work') + result = [x for x in output.split('\n') if 'SONAME' in x] + assert(len(result) <= 1) + pnm = subprocess.Popen(['nm', '--dynamic', '--extern-only', '--defined-only', '--format=posix', libfilename], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + output = pnm.communicate()[0].decode() + if pnm.returncode != 0: + raise RuntimeError('nm does not work.') + result += [' '.join(x.split()[0:2]) for x in output.split('\n') if len(x) > 0] + write_if_changed('\n'.join(result) + '\n', outfilename) + +def osx_syms(libfilename, outfilename): + pe = subprocess.Popen(['otool', '-l', libfilename], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + output = pe.communicate()[0].decode() + if pe.returncode != 0: + raise RuntimeError('Otool does not work.') + arr = output.split('\n') + for (i, val) in enumerate(arr): + if 'LC_ID_DYLIB' in val: + match = i + break + result = [arr[match+2], arr[match+5]] # Libreoffice stores all 5 lines but the others seem irrelevant. + pnm = subprocess.Popen(['nm', '-g', '-P', libfilename], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + output = pnm.communicate()[0].decode() + if pnm.returncode != 0: + raise RuntimeError('nm does not work.') + result += [' '.join(x.split()[0:2]) for x in output.split('\n') if len(x) > 0 and not x.endswith('U')] + write_if_changed('\n'.join(result) + '\n', outfilename) + +def gen_symbols(libfilename, outfilename, cross_host): + if cross_host is not None: + # In case of cross builds just always relink. + # In theory we could determine the correct + # toolset but there are more important things + # to do. + dummy_syms(outfilename) + elif mesonlib.is_linux(): + linux_syms(libfilename, outfilename) + elif mesonlib.is_osx(): + osx_syms(libfilename, outfilename) + else: + dummy_syms(outfilename) + +def run(args): + options = parser.parse_args(args) + if len(options.args) != 2: + print('symbolextractor.py <shared library file> <output file>') + sys.exit(1) + libfile = options.args[0] + outfile = options.args[1] + gen_symbols(libfile, outfile, options.cross_host) + return 0 + +if __name__ == '__main__': + sys.exit(run(sys.argv[1:])) diff --git a/mesonbuild/scripts/vcstagger.py b/mesonbuild/scripts/vcstagger.py new file mode 100644 index 0000000..390e37a --- /dev/null +++ b/mesonbuild/scripts/vcstagger.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 + +# Copyright 2015-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, subprocess, re + +def config_vcs_tag(infile, outfile, fallback, source_dir, replace_string, regex_selector, cmd): + try: + output = subprocess.check_output(cmd, cwd=source_dir) + new_string = re.search(regex_selector, output.decode()).group(1).strip() + except Exception: + new_string = fallback + + new_data = open(infile).read().replace(replace_string, new_string) + if (not os.path.exists(outfile)) or (open(outfile).read() != new_data): + open(outfile, 'w').write(new_data) + +def run(args): + infile, outfile, fallback, source_dir, replace_string, regex_selector = args[0:6] + command = args[6:] + config_vcs_tag(infile, outfile, fallback, source_dir, replace_string, regex_selector, command) + +if __name__ == '__main__': + sys.exit(run(sys.argv[1:])) |