aboutsummaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
authorJussi Pakkanen <jpakkane@gmail.com>2016-01-15 21:43:15 +0200
committerJussi Pakkanen <jpakkane@gmail.com>2016-01-15 21:43:15 +0200
commita5508d3fd362ea33633d9706a6257ef0f0c2bbc0 (patch)
treed1b35f622aa5feae62dee35eaef33df19b2b9d40 /scripts
parent8b1039fa30a405e2d07ac70eb0284ee4654c619a (diff)
downloadmeson-a5508d3fd362ea33633d9706a6257ef0f0c2bbc0.zip
meson-a5508d3fd362ea33633d9706a6257ef0f0c2bbc0.tar.gz
meson-a5508d3fd362ea33633d9706a6257ef0f0c2bbc0.tar.bz2
Can run most of test suite (with hacks).
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/commandrunner.py55
-rwxr-xr-xscripts/delwithsuffix.py32
-rwxr-xr-xscripts/depfixer.py299
-rwxr-xr-xscripts/dirchanger.py26
-rwxr-xr-xscripts/gtkdochelper.py118
-rwxr-xr-xscripts/meson_benchmark.py97
-rwxr-xr-xscripts/meson_install.py212
-rwxr-xr-xscripts/meson_test.py233
-rwxr-xr-xscripts/mesonconf.py205
-rwxr-xr-xscripts/mesongui.py561
-rwxr-xr-xscripts/mesonintrospect.py208
-rwxr-xr-xscripts/regen_checker.py42
-rwxr-xr-xscripts/symbolextractor.py102
-rwxr-xr-xscripts/vcstagger.py33
-rwxr-xr-xscripts/wraptool.py206
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)
+