diff options
author | Jussi Pakkanen <jpakkane@gmail.com> | 2016-01-15 21:43:15 +0200 |
---|---|---|
committer | Jussi Pakkanen <jpakkane@gmail.com> | 2016-01-15 21:43:15 +0200 |
commit | a5508d3fd362ea33633d9706a6257ef0f0c2bbc0 (patch) | |
tree | d1b35f622aa5feae62dee35eaef33df19b2b9d40 /scripts | |
parent | 8b1039fa30a405e2d07ac70eb0284ee4654c619a (diff) | |
download | meson-a5508d3fd362ea33633d9706a6257ef0f0c2bbc0.zip meson-a5508d3fd362ea33633d9706a6257ef0f0c2bbc0.tar.gz meson-a5508d3fd362ea33633d9706a6257ef0f0c2bbc0.tar.bz2 |
Can run most of test suite (with hacks).
Diffstat (limited to 'scripts')
-rwxr-xr-x | scripts/commandrunner.py | 55 | ||||
-rwxr-xr-x | scripts/delwithsuffix.py | 32 | ||||
-rwxr-xr-x | scripts/depfixer.py | 299 | ||||
-rwxr-xr-x | scripts/dirchanger.py | 26 | ||||
-rwxr-xr-x | scripts/gtkdochelper.py | 118 | ||||
-rwxr-xr-x | scripts/meson_benchmark.py | 97 | ||||
-rwxr-xr-x | scripts/meson_install.py | 212 | ||||
-rwxr-xr-x | scripts/meson_test.py | 233 | ||||
-rwxr-xr-x | scripts/mesonconf.py | 205 | ||||
-rwxr-xr-x | scripts/mesongui.py | 561 | ||||
-rwxr-xr-x | scripts/mesonintrospect.py | 208 | ||||
-rwxr-xr-x | scripts/regen_checker.py | 42 | ||||
-rwxr-xr-x | scripts/symbolextractor.py | 102 | ||||
-rwxr-xr-x | scripts/vcstagger.py | 33 | ||||
-rwxr-xr-x | scripts/wraptool.py | 206 |
15 files changed, 2429 insertions, 0 deletions
diff --git a/scripts/commandrunner.py b/scripts/commandrunner.py new file mode 100755 index 0000000..0dad585 --- /dev/null +++ b/scripts/commandrunner.py @@ -0,0 +1,55 @@ +#!/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) + +if __name__ == '__main__': + if len(sys.argv) < 5: + print(sys.argv[0], '<source dir> <build dir> <subdir> <command> [arguments]') + 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() + sys.exit(pc.returncode) diff --git a/scripts/delwithsuffix.py b/scripts/delwithsuffix.py new file mode 100755 index 0000000..4b8a60d --- /dev/null +++ b/scripts/delwithsuffix.py @@ -0,0 +1,32 @@ +#!/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 + +if len(sys.argv) != 3: + print('%s <root of subdir to process> <suffix to delete>' % sys.argv[0]) + sys.exit(1) + +topdir = sys.argv[1] +suffix = sys.argv[2] +if suffix[0] != '.': + suffix = '.' + suffix + +for (root, dirs, files) in os.walk(topdir): + for f in files: + if f.endswith(suffix): + fullname = os.path.join(root, f) + os.unlink(fullname) diff --git a/scripts/depfixer.py b/scripts/depfixer.py new file mode 100755 index 0000000..4f7ce3d --- /dev/null +++ b/scripts/depfixer.py @@ -0,0 +1,299 @@ +#!/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 + +if __name__ == '__main__': + if len(sys.argv) < 2 or len(sys.argv) > 3: + 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(sys.argv[1]) + if len(sys.argv) == 2: + e.print_rpath() + else: + new_rpath = sys.argv[2] + e.fix_rpath(new_rpath.encode('utf8')) + #e.fix_deps(prefix.encode()) diff --git a/scripts/dirchanger.py b/scripts/dirchanger.py new file mode 100755 index 0000000..fd3dc23 --- /dev/null +++ b/scripts/dirchanger.py @@ -0,0 +1,26 @@ +#!/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 + +dirname = sys.argv[1] +command = sys.argv[2:] + +os.chdir(dirname) +sys.exit(subprocess.call(command)) diff --git a/scripts/gtkdochelper.py b/scripts/gtkdochelper.py new file mode 100755 index 0000000..7e476b8 --- /dev/null +++ b/scripts/gtkdochelper.py @@ -0,0 +1,118 @@ +#!/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 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) + +if __name__ == '__main__': + options = parser.parse_args(sys.argv[1:]) + 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) diff --git a/scripts/meson_benchmark.py b/scripts/meson_benchmark.py new file mode 100755 index 0000000..26f1f95 --- /dev/null +++ b/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/scripts/meson_install.py b/scripts/meson_install.py new file mode 100755 index 0000000..e0a5eb2 --- /dev/null +++ b/scripts/meson_install.py @@ -0,0 +1,212 @@ +#!/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) + +if __name__ == '__main__': + if len(sys.argv) != 2: + print('Installer script for Meson. Do not run on your own, mmm\'kay?') + print('%s [install info file]' % sys.argv[0]) + datafilename = sys.argv[1] + do_install(datafilename) + diff --git a/scripts/meson_test.py b/scripts/meson_test.py new file mode 100755 index 0000000..43b1cdb --- /dev/null +++ b/scripts/meson_test.py @@ -0,0 +1,233 @@ +#!/usr/bin/env python3 + +# Copyright 2013-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 meson +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/scripts/mesonconf.py b/scripts/mesonconf.py new file mode 100755 index 0000000..e53875f --- /dev/null +++ b/scripts/mesonconf.py @@ -0,0 +1,205 @@ +#!/usr/bin/env python3 + +# Copyright 2014-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 sys, os +import pickle +import argparse +import coredata, mesonlib +from coredata import build_types, layouts, warning_levels, libtypelist + +parser = argparse.ArgumentParser() + +parser.add_argument('-D', action='append', default=[], dest='sets', + help='Set an option to the given value.') +parser.add_argument('directory', nargs='*') + +class ConfException(coredata.MesonException): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + +class Conf: + def __init__(self, build_dir): + self.build_dir = build_dir + self.coredata_file = os.path.join(build_dir, 'meson-private/coredata.dat') + self.build_file = os.path.join(build_dir, 'meson-private/build.dat') + if not os.path.isfile(self.coredata_file) or not os.path.isfile(self.build_file): + raise ConfException('Directory %s does not seem to be a Meson build directory.' % build_dir) + self.coredata = pickle.load(open(self.coredata_file, 'rb')) + self.build = pickle.load(open(self.build_file, 'rb')) + if self.coredata.version != coredata.version: + raise ConfException('Version mismatch (%s vs %s)' % + (coredata.version, self.coredata.version)) + + def save(self): + # Only called if something has changed so overwrite unconditionally. + pickle.dump(self.coredata, open(self.coredata_file, 'wb')) + # We don't write the build file because any changes to it + # are erased when Meson is executed the nex time, i.e. the next + # time Ninja is run. + + def print_aligned(self, arr): + if len(arr) == 0: + return + titles = ['Option', 'Description', 'Current Value', ''] + longest_name = len(titles[0]) + longest_descr = len(titles[1]) + longest_value = len(titles[2]) + longest_possible_value = len(titles[3]) + for x in arr: + longest_name = max(longest_name, len(x[0])) + longest_descr = max(longest_descr, len(x[1])) + longest_value = max(longest_value, len(str(x[2]))) + longest_possible_value = max(longest_possible_value, len(x[3])) + + if longest_possible_value > 0: + titles[3] = 'Possible Values' + print(' %s%s %s%s %s%s %s' % (titles[0], ' '*(longest_name - len(titles[0])), titles[1], ' '*(longest_descr - len(titles[1])), titles[2], ' '*(longest_value - len(titles[2])), titles[3])) + print(' %s%s %s%s %s%s %s' % ('-'*len(titles[0]), ' '*(longest_name - len(titles[0])), '-'*len(titles[1]), ' '*(longest_descr - len(titles[1])), '-'*len(titles[2]), ' '*(longest_value - len(titles[2])), '-'*len(titles[3]))) + for i in arr: + name = i[0] + descr = i[1] + value = i[2] + if isinstance(value, bool): + value = 'true' if value else 'false' + possible_values = i[3] + namepad = ' '*(longest_name - len(name)) + descrpad = ' '*(longest_descr - len(descr)) + valuepad = ' '*(longest_value - len(str(value))) + f = ' %s%s %s%s %s%s %s' % (name, namepad, descr, descrpad, value, valuepad, possible_values) + print(f) + + def set_options(self, options): + for o in options: + if '=' not in o: + raise ConfException('Value "%s" not of type "a=b".' % o) + (k, v) = o.split('=', 1) + if self.coredata.is_builtin_option(k): + self.coredata.set_builtin_option(k, v) + elif k in self.coredata.user_options: + tgt = self.coredata.user_options[k] + tgt.set_value(v) + elif k in self.coredata.compiler_options: + tgt = self.coredata.compiler_options[k] + tgt.set_value(v) + elif k.endswith('linkargs'): + lang = k[:-8] + if not lang in self.coredata.external_link_args: + raise ConfException('Unknown language %s in linkargs.' % lang) + # TODO, currently split on spaces, make it so that user + # can pass in an array string. + newvalue = v.split() + self.coredata.external_link_args[lang] = newvalue + elif k.endswith('args'): + lang = k[:-4] + if not lang in self.coredata.external_args: + raise ConfException('Unknown language %s in compile args' % lang) + # TODO same fix as above + newvalue = v.split() + self.coredata.external_args[lang] = newvalue + else: + raise ConfException('Unknown option %s.' % k) + + + def print_conf(self): + print('Core properties:') + print(' Source dir', self.build.environment.source_dir) + print(' Build dir ', self.build.environment.build_dir) + print('') + print('Core options:') + carr = [] + booleans = '[true, false]' + carr.append(['buildtype', 'Build type', self.coredata.get_builtin_option('buildtype'), build_types]) + carr.append(['warning_level', 'Warning level', self.coredata.get_builtin_option('warning_level'), warning_levels]) + carr.append(['strip', 'Strip on install', self.coredata.get_builtin_option('strip'), booleans]) + carr.append(['coverage', 'Coverage report', self.coredata.get_builtin_option('coverage'), booleans]) + carr.append(['use_pch', 'Precompiled headers', self.coredata.get_builtin_option('use_pch'), booleans]) + carr.append(['unity', 'Unity build', self.coredata.get_builtin_option('unity'), booleans]) + carr.append(['default_library', 'Default library type', self.coredata.get_builtin_option('default_library'), libtypelist]) + self.print_aligned(carr) + print('') + print('Compiler arguments:') + for (lang, args) in self.coredata.external_args.items(): + print(' ' + lang + 'args', str(args)) + print('') + print('Linker args:') + for (lang, args) in self.coredata.external_link_args.items(): + print(' ' + lang + 'linkargs', str(args)) + print('') + print('Compiler options:') + okeys = sorted(self.coredata.compiler_options.keys()) + if len(okeys) == 0: + print(' No compiler options\n') + else: + coarr = [] + for k in okeys: + o = self.coredata.compiler_options[k] + coarr.append([k, o.description, o.value, '']) + self.print_aligned(coarr) + print('') + print('Directories:') + parr = [] + parr.append(['prefix', 'Install prefix', self.coredata.get_builtin_option('prefix'), '']) + parr.append(['libdir', 'Library directory', self.coredata.get_builtin_option('libdir'), '']) + parr.append(['bindir', 'Binary directory', self.coredata.get_builtin_option('bindir'), '']) + parr.append(['includedir', 'Header directory', self.coredata.get_builtin_option('includedir'), '']) + parr.append(['datadir', 'Data directory', self.coredata.get_builtin_option('datadir'), '']) + parr.append(['mandir', 'Man page directory', self.coredata.get_builtin_option('mandir'), '']) + parr.append(['localedir', 'Locale file directory', self.coredata.get_builtin_option('localedir'), '']) + self.print_aligned(parr) + print('') + print('Project options:') + if len(self.coredata.user_options) == 0: + print(' This project does not have any options') + else: + options = self.coredata.user_options + keys = list(options.keys()) + keys.sort() + optarr = [] + for key in keys: + opt = options[key] + if (opt.choices is None) or (len(opt.choices) == 0): + # Zero length list or string + choices = ''; + else: + # A non zero length list or string, convert to string + choices = str(opt.choices); + optarr.append([key, opt.description, opt.value, choices]) + self.print_aligned(optarr) + +if __name__ == '__main__': + args = mesonlib.expand_arguments(sys.argv[:]) + if not args: + sys.exit(1) + options = parser.parse_args(args[1:]) + if len(options.directory) > 1: + print('%s <build directory>' % sys.argv[0]) + print('If you omit the build directory, the current directory is substituted.') + sys.exit(1) + if len(options.directory) == 0: + builddir = os.getcwd() + else: + builddir = options.directory[0] + try: + c = Conf(builddir) + if len(options.sets) > 0: + c.set_options(options.sets) + c.save() + else: + c.print_conf() + except ConfException as e: + print('Meson configurator encountered an error:\n') + print(e) + diff --git a/scripts/mesongui.py b/scripts/mesongui.py new file mode 100755 index 0000000..bdd44bb --- /dev/null +++ b/scripts/mesongui.py @@ -0,0 +1,561 @@ +#!/usr/bin/env python3 + +# Copyright 2013-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 sys, os, pickle, time, shutil +import build, coredata, environment, mesonlib +from PyQt5 import uic +from PyQt5.QtWidgets import QApplication, QMainWindow, QHeaderView +from PyQt5.QtWidgets import QComboBox, QCheckBox +from PyQt5.QtCore import QAbstractItemModel, QModelIndex, QVariant, QTimer +import PyQt5.QtCore +import PyQt5.QtWidgets + +priv_dir = os.path.split(os.path.abspath(os.path.realpath(__file__)))[0] + +class PathModel(QAbstractItemModel): + def __init__(self, coredata): + super().__init__() + self.coredata = coredata + self.names = ['Prefix', 'Library dir', 'Binary dir', 'Include dir', 'Data dir',\ + 'Man dir', 'Locale dir'] + self.attr_name = ['prefix', 'libdir', 'bindir', 'includedir', 'datadir', \ + 'mandir', 'localedir'] + + def args(self, index): + if index.column() == 1: + editable = PyQt5.QtCore.Qt.ItemIsEditable + else: + editable= 0 + return PyQt5.QtCore.Qt.ItemIsSelectable | PyQt5.QtCore.Qt.ItemIsEnabled | editable + + def rowCount(self, index): + if index.isValid(): + return 0 + return len(self.names) + + def columnCount(self, index): + return 2 + + def headerData(self, section, orientation, role): + if role != PyQt5.QtCore.Qt.DisplayRole: + return QVariant() + if section == 1: + return QVariant('Path') + return QVariant('Type') + + def index(self, row, column, parent): + return self.createIndex(row, column) + + def data(self, index, role): + if role != PyQt5.QtCore.Qt.DisplayRole: + return QVariant() + row = index.row() + column = index.column() + if column == 0: + return self.names[row] + return getattr(self.coredata, self.attr_name[row]) + + def parent(self, index): + return QModelIndex() + + def setData(self, index, value, role): + if role != PyQt5.QtCore.Qt.EditRole: + return False + row = index.row() + column = index.column() + s = str(value) + setattr(self.coredata, self.attr_name[row], s) + self.dataChanged.emit(self.createIndex(row, column), self.createIndex(row, column)) + return True + +class TargetModel(QAbstractItemModel): + def __init__(self, builddata): + super().__init__() + self.targets = [] + for target in builddata.get_targets().values(): + name = target.get_basename() + num_sources = len(target.get_sources()) + len(target.get_generated_sources()) + if isinstance(target, build.Executable): + typename = 'executable' + elif isinstance(target, build.SharedLibrary): + typename = 'shared library' + elif isinstance(target, build.StaticLibrary): + typename = 'static library' + elif isinstance(target, build.CustomTarget): + typename = 'custom' + else: + typename = 'unknown' + if target.should_install(): + installed = 'Yes' + else: + installed = 'No' + self.targets.append((name, typename, installed, num_sources)) + + def args(self, index): + return PyQt5.QtCore.Qt.ItemIsSelectable | PyQt5.QtCore.Qt.ItemIsEnabled + + def rowCount(self, index): + if index.isValid(): + return 0 + return len(self.targets) + + def columnCount(self, index): + return 4 + + def headerData(self, section, orientation, role): + if role != PyQt5.QtCore.Qt.DisplayRole: + return QVariant() + if section == 3: + return QVariant('Source files') + if section == 2: + return QVariant('Installed') + if section == 1: + return QVariant('Type') + return QVariant('Name') + + def data(self, index, role): + if role != PyQt5.QtCore.Qt.DisplayRole: + return QVariant() + row = index.row() + column = index.column() + return self.targets[row][column] + + def index(self, row, column, parent): + return self.createIndex(row, column) + + def parent(self, index): + return QModelIndex() + +class DependencyModel(QAbstractItemModel): + def __init__(self, coredata): + super().__init__() + self.deps = [] + for k in coredata.deps.keys(): + bd = coredata.deps[k] + name = k + found = bd.found() + if found: + cflags = str(bd.get_compile_args()) + libs = str(bd.get_link_args()) + found = 'yes' + else: + cflags = '' + libs = '' + found = 'no' + self.deps.append((name, found, cflags, libs)) + + def args(self, index): + return PyQt5.QtCore.Qt.ItemIsSelectable | PyQt5.QtCore.Qt.ItemIsEnabled + + def rowCount(self, index): + if index.isValid(): + return 0 + return len(self.deps) + + def columnCount(self, index): + return 4 + + def headerData(self, section, orientation, role): + if role != PyQt5.QtCore.Qt.DisplayRole: + return QVariant() + if section == 3: + return QVariant('Link args') + if section == 2: + return QVariant('Compile args') + if section == 1: + return QVariant('Found') + return QVariant('Name') + + def data(self, index, role): + if role != PyQt5.QtCore.Qt.DisplayRole: + return QVariant() + row = index.row() + column = index.column() + return self.deps[row][column] + + def index(self, row, column, parent): + return self.createIndex(row, column) + + def parent(self, index): + return QModelIndex() + +class CoreModel(QAbstractItemModel): + def __init__(self, core_data): + super().__init__() + self.elems = [] + for langname, comp in core_data.compilers.items(): + self.elems.append((langname + ' compiler', str(comp.get_exelist()))) + for langname, comp in core_data.cross_compilers.items(): + self.elems.append((langname + ' cross compiler', str(comp.get_exelist()))) + + def args(self, index): + return PyQt5.QtCore.Qt.ItemIsSelectable | PyQt5.QtCore.Qt.ItemIsEnabled + + def rowCount(self, index): + if index.isValid(): + return 0 + return len(self.elems) + + def columnCount(self, index): + return 2 + + def headerData(self, section, orientation, role): + if role != PyQt5.QtCore.Qt.DisplayRole: + return QVariant() + if section == 1: + return QVariant('Value') + return QVariant('Name') + + def data(self, index, role): + if role != PyQt5.QtCore.Qt.DisplayRole: + return QVariant() + row = index.row() + column = index.column() + return self.elems[row][column] + + def index(self, row, column, parent): + return self.createIndex(row, column) + + def parent(self, index): + return QModelIndex() + +class OptionForm: + def __init__(self, coredata, form): + self.coredata = coredata + self.form = form + form.addRow(PyQt5.QtWidgets.QLabel("Meson options")) + combo = QComboBox() + combo.addItem('plain') + combo.addItem('debug') + combo.addItem('debugoptimized') + combo.addItem('release') + combo.setCurrentText(self.coredata.buildtype) + combo.currentTextChanged.connect(self.build_type_changed) + self.form.addRow('Build type', combo) + strip = QCheckBox("") + strip.setChecked(self.coredata.strip) + strip.stateChanged.connect(self.strip_changed) + self.form.addRow('Strip on install', strip) + coverage = QCheckBox("") + coverage.setChecked(self.coredata.coverage) + coverage.stateChanged.connect(self.coverage_changed) + self.form.addRow('Enable coverage', coverage) + pch = QCheckBox("") + pch.setChecked(self.coredata.use_pch) + pch.stateChanged.connect(self.pch_changed) + self.form.addRow('Enable pch', pch) + unity = QCheckBox("") + unity.setChecked(self.coredata.unity) + unity.stateChanged.connect(self.unity_changed) + self.form.addRow('Unity build', unity) + form.addRow(PyQt5.QtWidgets.QLabel("Project options")) + self.set_user_options() + + def set_user_options(self): + options = self.coredata.user_options + keys = list(options.keys()) + keys.sort() + self.opt_keys = keys + self.opt_widgets = [] + for key in keys: + opt = options[key] + if isinstance(opt, mesonlib.UserStringOption): + w = PyQt5.QtWidgets.QLineEdit(opt.value) + w.textChanged.connect(self.user_option_changed) + elif isinstance(opt, mesonlib.UserBooleanOption): + w = QCheckBox('') + w.setChecked(opt.value) + w.stateChanged.connect(self.user_option_changed) + elif isinstance(opt, mesonlib.UserComboOption): + w = QComboBox() + for i in opt.choices: + w.addItem(i) + w.setCurrentText(opt.value) + w.currentTextChanged.connect(self.user_option_changed) + else: + raise RuntimeError("Unknown option type") + self.opt_widgets.append(w) + self.form.addRow(opt.description, w) + + def user_option_changed(self, dummy=None): + for i in range(len(self.opt_keys)): + key = self.opt_keys[i] + w = self.opt_widgets[i] + if isinstance(w, PyQt5.QtWidgets.QLineEdit): + newval = w.text() + elif isinstance(w, QComboBox): + newval = w.currentText() + elif isinstance(w, QCheckBox): + if w.checkState() == 0: + newval = False + else: + newval = True + else: + raise RuntimeError('Unknown widget type') + self.coredata.user_options[key].set_value(newval) + + def build_type_changed(self, newtype): + self.coredata.buildtype = newtype + + def strip_changed(self, newState): + if newState == 0: + ns = False + else: + ns = True + self.coredata.strip = ns + + def coverage_changed(self, newState): + if newState == 0: + ns = False + else: + ns = True + self.coredata.coverage = ns + + def pch_changed(self, newState): + if newState == 0: + ns = False + else: + ns = True + self.coredata.use_pch = ns + + def unity_changed(self, newState): + if newState == 0: + ns = False + else: + ns = True + self.coredata.unity = ns + +class ProcessRunner(): + def __init__(self, rundir, cmdlist): + self.cmdlist = cmdlist + self.ui = uic.loadUi(os.path.join(priv_dir, 'mesonrunner.ui')) + self.timer = QTimer(self.ui) + self.timer.setInterval(1000) + self.timer.timeout.connect(self.timeout) + self.process = PyQt5.QtCore.QProcess() + self.process.setProcessChannelMode(PyQt5.QtCore.QProcess.MergedChannels) + self.process.setWorkingDirectory(rundir) + self.process.readyRead.connect(self.read_data) + self.process.finished.connect(self.finished) + self.ui.termbutton.clicked.connect(self.terminated) + self.return_value = 100 + + def run(self): + self.process.start(self.cmdlist[0], self.cmdlist[1:]) + self.timer.start() + self.start_time = time.time() + return self.ui.exec() + + def read_data(self): + while(self.process.canReadLine()): + txt = bytes(self.process.readLine()).decode('utf8') + self.ui.console.append(txt) + + def finished(self): + self.read_data() + self.ui.termbutton.setText('Done') + self.timer.stop() + self.return_value = self.process.exitCode() + + def terminated(self, foo): + self.process.kill() + self.timer.stop() + self.ui.done(self.return_value) + + def timeout(self): + now = time.time() + duration = int(now - self.start_time) + msg = 'Elapsed time: %d:%d' % (duration // 60, duration % 60) + self.ui.timelabel.setText(msg) + +class MesonGui(): + def __init__(self, respawner, build_dir): + self.respawner = respawner + uifile = os.path.join(priv_dir, 'mesonmain.ui') + self.ui = uic.loadUi(uifile) + self.coredata_file = os.path.join(build_dir, 'meson-private/coredata.dat') + self.build_file = os.path.join(build_dir, 'meson-private/build.dat') + if not os.path.exists(self.coredata_file): + print("Argument is not build directory.") + sys.exit(1) + self.coredata = pickle.load(open(self.coredata_file, 'rb')) + self.build = pickle.load(open(self.build_file, 'rb')) + self.build_dir = self.build.environment.build_dir + self.src_dir = self.build.environment.source_dir + self.build_models() + self.options = OptionForm(self.coredata, self.ui.option_form) + self.ui.show() + + def hide(self): + self.ui.hide() + + def geometry(self): + return self.ui.geometry() + + def move(self, x, y): + return self.ui.move(x, y) + + def size(self): + return self.ui.size() + + def resize(self, s): + return self.ui.resize(s) + + def build_models(self): + self.path_model = PathModel(self.coredata) + self.target_model = TargetModel(self.build) + self.dep_model = DependencyModel(self.coredata) + self.core_model = CoreModel(self.coredata) + self.fill_data() + self.ui.core_view.setModel(self.core_model) + hv = QHeaderView(1) + hv.setModel(self.core_model) + self.ui.core_view.setHeader(hv) + self.ui.path_view.setModel(self.path_model) + hv = QHeaderView(1) + hv.setModel(self.path_model) + self.ui.path_view.setHeader(hv) + self.ui.target_view.setModel(self.target_model) + hv = QHeaderView(1) + hv.setModel(self.target_model) + self.ui.target_view.setHeader(hv) + self.ui.dep_view.setModel(self.dep_model) + hv = QHeaderView(1) + hv.setModel(self.dep_model) + self.ui.dep_view.setHeader(hv) + self.ui.compile_button.clicked.connect(self.compile) + self.ui.test_button.clicked.connect(self.run_tests) + self.ui.install_button.clicked.connect(self.install) + self.ui.clean_button.clicked.connect(self.clean) + self.ui.save_button.clicked.connect(self.save) + + def fill_data(self): + self.ui.project_label.setText(self.build.projects['']) + self.ui.srcdir_label.setText(self.src_dir) + self.ui.builddir_label.setText(self.build_dir) + if self.coredata.cross_file is None: + btype = 'Native build' + else: + btype = 'Cross build' + self.ui.buildtype_label.setText(btype) + + def run_process(self, cmdlist): + cmdlist = [shutil.which(environment.detect_ninja())] + cmdlist + dialog = ProcessRunner(self.build.environment.build_dir, cmdlist) + dialog.run() + # All processes (at the moment) may change cache state + # so reload. + self.respawner.respawn() + + def compile(self, foo): + self.run_process([]) + + def run_tests(self, foo): + self.run_process(['test']) + + def install(self, foo): + self.run_process(['install']) + + def clean(self, foo): + self.run_process(['clean']) + + def save(self, foo): + pickle.dump(self.coredata, open(self.coredata_file, 'wb')) + +class Starter(): + def __init__(self, sdir): + uifile = os.path.join(priv_dir, 'mesonstart.ui') + self.ui = uic.loadUi(uifile) + self.ui.source_entry.setText(sdir) + self.dialog = PyQt5.QtWidgets.QFileDialog() + if len(sdir) == 0: + self.dialog.setDirectory(os.getcwd()) + else: + self.dialog.setDirectory(sdir) + self.ui.source_browse_button.clicked.connect(self.src_browse_clicked) + self.ui.build_browse_button.clicked.connect(self.build_browse_clicked) + self.ui.cross_browse_button.clicked.connect(self.cross_browse_clicked) + self.ui.source_entry.textChanged.connect(self.update_button) + self.ui.build_entry.textChanged.connect(self.update_button) + self.ui.generate_button.clicked.connect(self.generate) + self.update_button() + self.ui.show() + + def generate(self): + srcdir = self.ui.source_entry.text() + builddir = self.ui.build_entry.text() + cross = self.ui.cross_entry.text() + cmdlist = [os.path.join(os.path.split(__file__)[0], 'meson.py'), srcdir, builddir] + if cross != '': + cmdlist += ['--cross', cross] + pr = ProcessRunner(os.getcwd(), cmdlist) + rvalue = pr.run() + if rvalue == 0: + os.execl(__file__, 'dummy', builddir) + + def update_button(self): + if self.ui.source_entry.text() == '' or self.ui.build_entry.text() == '': + self.ui.generate_button.setEnabled(False) + else: + self.ui.generate_button.setEnabled(True) + + def src_browse_clicked(self): + self.dialog.setFileMode(2) + if self.dialog.exec(): + self.ui.source_entry.setText(self.dialog.selectedFiles()[0]) + + def build_browse_clicked(self): + self.dialog.setFileMode(2) + if self.dialog.exec(): + self.ui.build_entry.setText(self.dialog.selectedFiles()[0]) + + def cross_browse_clicked(self): + self.dialog.setFileMode(1) + if self.dialog.exec(): + self.ui.cross_entry.setText(self.dialog.selectedFiles()[0]) + +# Rather than rewrite all classes and arrays to be +# updateable, just rebuild the entire GUI from +# scratch whenever data on disk changes. + +class MesonGuiRespawner(): + def __init__(self, arg): + self.arg = arg + self.gui = MesonGui(self, self.arg) + + def respawn(self): + geo = self.gui.geometry() + s = self.gui.size() + self.gui.hide() + self.gui = MesonGui(self, self.arg) + self.gui.move(geo.x(), geo.y()) + self.gui.resize(s) + # Garbage collection takes care of the old gui widget + +if __name__ == '__main__': + app = QApplication(sys.argv) + if len(sys.argv) == 1: + arg = "" + elif len(sys.argv) == 2: + arg = sys.argv[1] + else: + print(sys.argv[0], "<build or source dir>") + sys.exit(1) + if os.path.exists(os.path.join(arg, 'meson-private/coredata.dat')): + guirespawner = MesonGuiRespawner(arg) + else: + runner = Starter(arg) + sys.exit(app.exec_()) diff --git a/scripts/mesonintrospect.py b/scripts/mesonintrospect.py new file mode 100755 index 0000000..9fcd4db --- /dev/null +++ b/scripts/mesonintrospect.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python3 + +# Copyright 2014-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. + +"""This is a helper script for IDE developers. It allows you to +extract information such as list of targets, files, compiler flags, +tests and so on. All output is in JSON for simple parsing. + +Currently only works for the Ninja backend. Others use generated +project files and don't need this info.""" + +import json, pickle +import coredata, build, mesonlib +import argparse +import sys, os + +parser = argparse.ArgumentParser() +parser.add_argument('--targets', action='store_true', dest='list_targets', default=False, + help='List top level targets.') +parser.add_argument('--target-files', action='store', dest='target_files', default=None, + help='List source files for a given target.') +parser.add_argument('--buildsystem-files', action='store_true', dest='buildsystem_files', default=False, + help='List files that make up the build system.') +parser.add_argument('--buildoptions', action='store_true', dest='buildoptions', default=False, + help='List all build options.') +parser.add_argument('--tests', action='store_true', dest='tests', default=False, + help='List all unit tests.') +parser.add_argument('--benchmarks', action='store_true', dest='benchmarks', default=False, + help='List all benchmarks.') +parser.add_argument('--dependencies', action='store_true', dest='dependencies', default=False, + help='list external dependencies.') +parser.add_argument('args', nargs='+') + +def list_targets(coredata, builddata): + tlist = [] + for (idname, target) in builddata.get_targets().items(): + t = {} + t['name'] = target.get_basename() + t['id'] = idname + fname = target.get_filename() + if isinstance(fname, list): + fname = [os.path.join(target.subdir, x) for x in fname] + else: + fname = os.path.join(target.subdir, fname) + t['filename'] = fname + if isinstance(target, build.Executable): + typename = 'executable' + elif isinstance(target, build.SharedLibrary): + typename = 'shared library' + elif isinstance(target, build.StaticLibrary): + typename = 'static library' + elif isinstance(target, build.CustomTarget): + typename = 'custom' + elif isinstance(target, build.RunTarget): + typename = 'run' + else: + typename = 'unknown' + t['type'] = typename + if target.should_install(): + t['installed'] = True + else: + t['installed'] = False + tlist.append(t) + print(json.dumps(tlist)) + +def list_target_files(target_name, coredata, builddata): + try: + t = builddata.targets[target_name] + sources = t.sources + t.extra_files + subdir = t.subdir + except KeyError: + print("Unknown target %s." % target_name) + sys.exit(1) + sources = [os.path.join(i.subdir, i.fname) for i in sources] + print(json.dumps(sources)) + +def list_buildoptions(coredata, builddata): + buildtype= {'choices': ['plain', 'debug', 'debugoptimized', 'release'], + 'type' : 'combo', + 'value' : coredata.buildtype, + 'description' : 'Build type', + 'name' : 'type'} + strip = {'value' : coredata.strip, + 'type' : 'boolean', + 'description' : 'Strip on install', + 'name' : 'strip'} + coverage = {'value': coredata.coverage, + 'type' : 'boolean', + 'description' : 'Enable coverage', + 'name' : 'coverage'} + pch = {'value' : coredata.use_pch, + 'type' : 'boolean', + 'description' : 'Use precompiled headers', + 'name' : 'pch'} + unity = {'value' : coredata.unity, + 'type' : 'boolean', + 'description' : 'Unity build', + 'name' : 'unity'} + optlist = [buildtype, strip, coverage, pch, unity] + add_keys(optlist, coredata.user_options) + add_keys(optlist, coredata.compiler_options) + print(json.dumps(optlist)) + +def add_keys(optlist, options): + keys = list(options.keys()) + keys.sort() + for key in keys: + opt = options[key] + optdict = {} + optdict['name'] = key + optdict['value'] = opt.value + if isinstance(opt, mesonlib.UserStringOption): + typestr = 'string' + elif isinstance(opt, mesonlib.UserBooleanOption): + typestr = 'boolean' + elif isinstance(opt, mesonlib.UserComboOption): + optdict['choices'] = opt.choices + typestr = 'combo' + elif isinstance(opt, mesonlib.UserStringArrayOption): + typestr = 'stringarray' + else: + raise RuntimeError("Unknown option type") + optdict['type'] = typestr + optdict['description'] = opt.description + optlist.append(optdict) + +def list_buildsystem_files(coredata, builddata): + src_dir = builddata.environment.get_source_dir() + # I feel dirty about this. But only slightly. + filelist = [] + for root, _, files in os.walk(src_dir): + for f in files: + if f == 'meson.build' or f == 'meson_options.txt': + filelist.append(os.path.relpath(os.path.join(root, f), src_dir)) + print(json.dumps(filelist)) + +def list_deps(coredata): + result = {} + for d in coredata.deps.values(): + if d.found(): + args = {'compile_args': d.get_compile_args(), + 'link_args': d.get_link_args()} + result[d.name] = args + print(json.dumps(result)) + +def list_tests(testdata): + result = [] + for t in testdata: + to = {} + if isinstance(t.fname, str): + fname = [t.fname] + else: + fname = t.fname + to['cmd'] = fname + t.cmd_args + to['env'] = t.env + to['name'] = t.name + to['workdir'] = t.workdir + to['timeout'] = t.timeout + to['suite'] = t.suite + result.append(to) + print(json.dumps(result)) + +if __name__ == '__main__': + options = parser.parse_args() + if len(options.args) > 1: + print('Too many arguments') + sys.exit(1) + elif len(options.args) == 1: + bdir = options.args[0] + else: + bdir = '' + corefile = os.path.join(bdir, 'meson-private/coredata.dat') + buildfile = os.path.join(bdir, 'meson-private/build.dat') + testfile = os.path.join(bdir, 'meson-private/meson_test_setup.dat') + benchmarkfile = os.path.join(bdir, 'meson-private/meson_benchmark_setup.dat') + coredata = pickle.load(open(corefile, 'rb')) + builddata = pickle.load(open(buildfile, 'rb')) + testdata = pickle.load(open(testfile, 'rb')) + benchmarkdata = pickle.load(open(benchmarkfile, 'rb')) + if options.list_targets: + list_targets(coredata, builddata) + elif options.target_files is not None: + list_target_files(options.target_files, coredata, builddata) + elif options.buildsystem_files: + list_buildsystem_files(coredata, builddata) + elif options.buildoptions: + list_buildoptions(coredata, builddata) + elif options.tests: + list_tests(testdata) + elif options.benchmarks: + list_tests(benchmarkdata) + elif options.dependencies: + list_deps(coredata) + else: + print('No command specified') + sys.exit(1) diff --git a/scripts/regen_checker.py b/scripts/regen_checker.py new file mode 100755 index 0000000..a0fe028 --- /dev/null +++ b/scripts/regen_checker.py @@ -0,0 +1,42 @@ +#!/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 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) + +if __name__ == '__main__': + regeninfo = pickle.load(open(os.path.join(sys.argv[1], 'regeninfo.dump'), 'rb')) + if need_regen(regeninfo): + regen(regeninfo) + sys.exit(0) diff --git a/scripts/symbolextractor.py b/scripts/symbolextractor.py new file mode 100755 index 0000000..f2c709d --- /dev/null +++ b/scripts/symbolextractor.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 + +# Copyright 2013-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. + +# 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 +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) + +if __name__ == '__main__': + options = parser.parse_args() + if len(options.args) != 2: + print(sys.argv[0], '<shared library file> <output file>') + sys.exit(1) + libfile = options.args[0] + outfile = options.args[1] + gen_symbols(libfile, outfile, options.cross_host) diff --git a/scripts/vcstagger.py b/scripts/vcstagger.py new file mode 100755 index 0000000..ccc584e --- /dev/null +++ b/scripts/vcstagger.py @@ -0,0 +1,33 @@ +#!/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 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) + +if __name__ == '__main__': + infile, outfile, fallback, source_dir, replace_string, regex_selector = sys.argv[1:7] + command = sys.argv[7:] + config_vcs_tag(infile, outfile, fallback, source_dir, replace_string, regex_selector, command) diff --git a/scripts/wraptool.py b/scripts/wraptool.py new file mode 100755 index 0000000..46860aa --- /dev/null +++ b/scripts/wraptool.py @@ -0,0 +1,206 @@ +#!/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 json +import sys, os +import configparser +import shutil + + +ssl_warning_printed = False + +from glob import glob + +help_templ = '''This program allows you to manage your Wrap dependencies +using the online wrap database http://wrapdb.mesonbuild.com. + +Run this command in your top level source directory. + +Usage: + +%s <command> [options] + +Commands: + + list - show all available projects + search - search the db by name + install - install the specified project + update - update the project to its newest available release + info - show available versions of a project + status - show installed and available versions of your projects + +''' + + +def print_help(): + print(help_templ % sys.argv[0]) + +def build_ssl_context(): + ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23) + ctx.options |= ssl.OP_NO_SSLv2 + ctx.options |= ssl.OP_NO_SSLv3 + ctx.verify_mode = ssl.CERT_REQUIRED + ctx.load_default_certs() + return ctx + +def get_result(urlstring): + u = open_wrapdburl(urlstring) + data = u.read().decode('utf-8') + jd = json.loads(data) + if jd['output'] != 'ok': + print('Got bad output from server.') + print(data) + sys.exit(1) + return jd + +def get_projectlist(): + jd = get_result(API_ROOT + 'projects') + projects = jd['projects'] + return projects + +def list_projects(): + projects = get_projectlist() + for p in projects: + print(p) + +def search(name): + jd = get_result(API_ROOT + 'query/byname/' + name) + for p in jd['projects']: + print(p) + +def get_latest_version(name): + jd = get_result(API_ROOT + 'query/get_latest/' + name) + branch = jd['branch'] + revision = jd['revision'] + return (branch, revision) + +def install(name): + if not os.path.isdir('subprojects'): + print('Subprojects dir not found. Run this script in your source root directory.') + sys.exit(1) + if os.path.isdir(os.path.join('subprojects', name)): + print('Subproject directory for this project already exists.') + sys.exit(1) + wrapfile = os.path.join('subprojects', name + '.wrap') + if os.path.exists(wrapfile): + print('Wrap file already exists.') + sys.exit(1) + (branch, revision) = get_latest_version(name) + u = open_wrapdburl(API_ROOT + 'projects/%s/%s/%s/get_wrap' % (name, branch, revision)) + data = u.read() + open(wrapfile, 'wb').write(data) + print('Installed', name, 'branch', branch, 'revision', revision) + +def get_current_version(wrapfile): + cp = configparser.ConfigParser() + cp.read(wrapfile) + cp = cp['wrap-file'] + patch_url = cp['patch_url'] + arr = patch_url.split('/') + branch = arr[-3] + revision = int(arr[-2]) + return (branch, revision, cp['directory'], cp['source_filename'], cp['patch_filename']) + +def update(name): + if not os.path.isdir('subprojects'): + print('Subprojects dir not found. Run this command in your source root directory.') + sys.exit(1) + wrapfile = os.path.join('subprojects', name + '.wrap') + if not os.path.exists(wrapfile): + print('Project', name, 'is not in use.') + sys.exit(1) + (branch, revision, subdir, src_file, patch_file) = get_current_version(wrapfile) + (new_branch, new_revision) = get_latest_version(name) + if new_branch == branch and new_revision == revision: + print('Project', name, 'is already up to date.') + sys.exit(0) + u = open_wrapdburl(API_ROOT + 'projects/%s/%s/%d/get_wrap' % (name, new_branch, new_revision)) + data = u.read() + shutil.rmtree(os.path.join('subprojects', subdir), ignore_errors=True) + try: + os.unlink(os.path.join('subprojects/packagecache', src_file)) + except FileNotFoundError: + pass + try: + os.unlink(os.path.join('subprojects/packagecache', patch_file)) + except FileNotFoundError: + pass + open(wrapfile, 'wb').write(data) + print('Updated', name, 'to branch', new_branch, 'revision', new_revision) + +def info(name): + jd = get_result(API_ROOT + 'projects/' + name) + versions = jd['versions'] + if len(versions) == 0: + print('No available versions of', name) + sys.exit(0) + print('Available versions of %s:' % name) + for v in versions: + print(' ', v['branch'], v['revision']) + +def status(): + print('Subproject status') + for w in glob('subprojects/*.wrap'): + name = os.path.split(w)[1][:-5] + try: + (latest_branch, latest_revision) = get_latest_version(name) + except Exception: + print('', name, 'not available in wrapdb.') + continue + try: + (current_branch, current_revision, _, _, _) = get_current_version(w) + except Exception: + print('Wrap file not from wrapdb.') + continue + if current_branch == latest_branch and current_revision == latest_revision: + print('', name, 'up to date. Branch %s, revision %d.' % (current_branch, current_revision)) + else: + print('', name, 'not up to date. Have %s %d, but %s %d is available.' % (current_branch, current_revision, latest_branch, latest_revision)) + +if __name__ == '__main__': + if len(sys.argv) < 2 or sys.argv[1] == '-h' or sys.argv[1] == '--help': + print_help() + sys.exit(0) + command = sys.argv[1] + args = sys.argv[2:] + if command == 'list': + list_projects() + elif command == 'search': + if len(args) != 1: + print('Search requires exactly one argument.') + sys.exit(1) + search(args[0]) + elif command == 'install': + if len(args) != 1: + print('Install requires exactly one argument.') + sys.exit(1) + install(args[0]) + elif command == 'update': + if len(args) != 1: + print('update requires exactly one argument.') + sys.exit(1) + update(args[0]) + elif command == 'info': + if len(args) != 1: + print('info requires exactly one argument.') + sys.exit(1) + info(args[0]) + elif command == 'status': + status() + else: + print('Unknown command', command) + sys.exit(1) + |