diff options
author | Jussi Pakkanen <jpakkane@gmail.com> | 2016-01-16 17:35:29 +0200 |
---|---|---|
committer | Jussi Pakkanen <jpakkane@gmail.com> | 2016-01-16 17:35:29 +0200 |
commit | 23b98cd6e66c6ae0f070e28e0f8b1566c0b5e585 (patch) | |
tree | e349597556abe3d22578cfb1f9529f4626ceb5aa /mesonbuild | |
parent | 1510522b1b9970376a1e1cc5f39e00d8749ec19a (diff) | |
download | meson-23b98cd6e66c6ae0f070e28e0f8b1566c0b5e585.zip meson-23b98cd6e66c6ae0f070e28e0f8b1566c0b5e585.tar.gz meson-23b98cd6e66c6ae0f070e28e0f8b1566c0b5e585.tar.bz2 |
Renamed meson package to mesonbuild so that we can have a script named meson in the same toplevel dir.
Diffstat (limited to 'mesonbuild')
42 files changed, 16136 insertions, 0 deletions
diff --git a/mesonbuild/__init__.py b/mesonbuild/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/mesonbuild/__init__.py diff --git a/mesonbuild/backends.py b/mesonbuild/backends.py new file mode 100644 index 0000000..c583a7b --- /dev/null +++ b/mesonbuild/backends.py @@ -0,0 +1,423 @@ +# Copyright 2012-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 os, pickle, re +from . import build +from . import dependencies +from . import mesonlib +import json +from .coredata import MesonException + +class InstallData(): + def __init__(self, source_dir, build_dir, prefix, depfixer): + self.source_dir = source_dir + self.build_dir= build_dir + self.prefix = prefix + self.targets = [] + self.depfixer = depfixer + self.headers = [] + self.man = [] + self.data = [] + self.po_package_name = '' + self.po = [] + self.install_scripts = [] + self.install_subdirs = [] + +class TestSerialisation: + def __init__(self, name, suite, fname, is_cross, exe_wrapper, is_parallel, cmd_args, env, + should_fail, valgrind_args, timeout, workdir, extra_paths): + self.name = name + self.suite = suite + self.fname = fname + self.is_cross = is_cross + self.exe_runner = exe_wrapper + self.is_parallel = is_parallel + self.cmd_args = cmd_args + self.env = env + self.should_fail = should_fail + self.valgrind_args = valgrind_args + self.timeout = timeout + self.workdir = workdir + self.extra_paths = extra_paths + +# This class contains the basic functionality that is needed by all backends. +# Feel free to move stuff in and out of it as you see fit. +class Backend(): + def __init__(self, build): + self.build = build + self.environment = build.environment + self.processed_targets = {} + self.dep_rules = {} + self.build_to_src = os.path.relpath(self.environment.get_source_dir(), + self.environment.get_build_dir()) + for t in self.build.targets: + priv_dirname = self.get_target_private_dir_abs(t) + os.makedirs(priv_dirname, exist_ok=True) + + def get_compiler_for_lang(self, lang): + for i in self.build.compilers: + if i.language == lang: + return i + raise RuntimeError('No compiler for language ' + lang) + + def get_compiler_for_source(self, src): + for i in self.build.compilers: + if i.can_compile(src): + return i + if isinstance(src, mesonlib.File): + src = src.fname + raise RuntimeError('No specified compiler can handle file ' + src) + + def get_target_filename(self, target): + targetdir = self.get_target_dir(target) + fname = target.get_filename() + if isinstance(fname, list): + fname = fname[0] # HORROR, HORROR! Fix this. + filename = os.path.join(targetdir, fname) + return filename + + def get_target_dir(self, target): + if self.environment.coredata.get_builtin_option('layout') == 'mirror': + dirname = target.get_subdir() + else: + dirname = 'meson-out' + return dirname + + def get_target_private_dir(self, target): + dirname = os.path.join(self.get_target_dir(target), target.get_basename() + target.type_suffix()) + return dirname + + def get_target_private_dir_abs(self, target): + dirname = os.path.join(self.environment.get_build_dir(), self.get_target_private_dir(target)) + return dirname + + def generate_unity_files(self, target, unity_src): + langlist = {} + abs_files = [] + result = [] + for src in unity_src: + comp = self.get_compiler_for_source(src) + language = comp.get_language() + suffix = '.' + comp.get_default_suffix() + if language not in langlist: + outfilename = os.path.join(self.get_target_private_dir_abs(target), target.name + '-unity' + suffix) + outfileabs = os.path.join(self.environment.get_build_dir(), outfilename) + outfileabs_tmp = outfileabs + '.tmp' + abs_files.append(outfileabs) + outfile = open(outfileabs_tmp, 'w') + langlist[language] = outfile + result.append(outfilename) + ofile = langlist[language] + ofile.write('#include<%s>\n' % src) + [x.close() for x in langlist.values()] + [mesonlib.replace_if_different(x, x + '.tmp') for x in abs_files] + return result + + def relpath(self, todir, fromdir): + return os.path.relpath(os.path.join('dummyprefixdir', todir),\ + os.path.join('dummyprefixdir', fromdir)) + + def flatten_object_list(self, target, proj_dir_to_build_root=''): + obj_list = [] + for obj in target.get_objects(): + if isinstance(obj, str): + o = os.path.join(proj_dir_to_build_root, + self.build_to_src, target.get_subdir(), obj) + obj_list.append(o) + elif isinstance(obj, build.ExtractedObjects): + obj_list += self.determine_ext_objs(obj, proj_dir_to_build_root) + else: + raise MesonException('Unknown data type in object list.') + return obj_list + + def serialise_tests(self): + test_data = os.path.join(self.environment.get_scratch_dir(), 'meson_test_setup.dat') + datafile = open(test_data, 'wb') + self.write_test_file(datafile) + datafile.close() + benchmark_data = os.path.join(self.environment.get_scratch_dir(), 'meson_benchmark_setup.dat') + datafile = open(benchmark_data, 'wb') + self.write_benchmark_file(datafile) + datafile.close() + + def has_source_suffix(self, target, suffix): + for s in target.get_sources(): + if s.endswith(suffix): + return True + return False + + def has_vala(self, target): + return self.has_source_suffix(target, '.vala') + + def has_rust(self, target): + return self.has_source_suffix(target, '.rs') + + def has_cs(self, target): + return self.has_source_suffix(target, '.cs') + + def has_swift(self, target): + return self.has_source_suffix(target, '.swift') + + def determine_linker(self, target, src): + if isinstance(target, build.StaticLibrary): + return self.build.static_linker + if len(self.build.compilers) == 1: + return self.build.compilers[0] + # Currently a bit naive. C++ must + # be linked with a C++ compiler, but + # otherwise we don't care. This will + # become trickier if and when Fortran + # and the like become supported. + cpp = None + for c in self.build.compilers: + if c.get_language() == 'cpp': + cpp = c + break + if cpp is not None: + for s in src: + if c.can_compile(s): + return cpp + for c in self.build.compilers: + if c.get_language() != 'vala': + return c + raise RuntimeError('Unreachable code') + + def determine_ext_objs(self, extobj, proj_dir_to_build_root=''): + result = [] + targetdir = self.get_target_private_dir(extobj.target) + suffix = '.' + self.environment.get_object_suffix() + for osrc in extobj.srclist: + osrc_base = osrc.fname + if not self.source_suffix_in_objs: + osrc_base = '.'.join(osrc.split('.')[:-1]) + # If extracting in a subproject, the subproject + # name gets duplicated in the file name. + pathsegs = osrc.subdir.split(os.sep) + if pathsegs[0] == 'subprojects': + pathsegs = pathsegs[2:] + fixedpath = os.sep.join(pathsegs) + objbase = osrc.fname.replace('/', '_').replace('\\', '_') + objname = os.path.join(proj_dir_to_build_root, + targetdir, os.path.basename(objbase) + suffix) + result.append(objname) + return result + + def get_pch_include_args(self, compiler, target): + args = [] + pchpath = self.get_target_private_dir(target) + includeargs = compiler.get_include_args(pchpath, False) + for lang in ['c', 'cpp']: + p = target.get_pch(lang) + if len(p) == 0: + continue + if compiler.can_compile(p[-1]): + header = p[0] + args += compiler.get_pch_use_args(pchpath, header) + if len(args) > 0: + args = includeargs + args + return args + + def generate_basic_compiler_args(self, target, compiler): + commands = [] + commands += compiler.get_always_args() + if self.environment.coredata.get_builtin_option('buildtype') != 'plain': + commands += compiler.get_warn_args(self.environment.coredata.get_builtin_option('warning_level')) + commands += compiler.get_option_compile_args(self.environment.coredata.compiler_options) + commands += self.build.get_global_args(compiler) + commands += self.environment.coredata.external_args[compiler.get_language()] + commands += target.get_extra_args(compiler.get_language()) + commands += compiler.get_buildtype_args(self.environment.coredata.get_builtin_option('buildtype')) + if self.environment.coredata.get_builtin_option('coverage'): + commands += compiler.get_coverage_args() + if self.environment.coredata.get_builtin_option('werror'): + commands += compiler.get_werror_args() + if isinstance(target, build.SharedLibrary): + commands += compiler.get_pic_args() + for dep in target.get_external_deps(): + commands += dep.get_compile_args() + if isinstance(target, build.Executable): + commands += dep.get_exe_args() + + # Fortran requires extra include directives. + if compiler.language == 'fortran': + for lt in target.link_targets: + priv_dir = os.path.join(self.get_target_dir(lt), lt.get_basename() + lt.type_suffix()) + incflag = compiler.get_include_args(priv_dir, False) + commands += incflag + return commands + + def build_target_link_arguments(self, compiler, deps): + args = [] + for d in deps: + if not isinstance(d, build.StaticLibrary) and\ + not isinstance(d, build.SharedLibrary): + raise RuntimeError('Tried to link with a non-library target "%s".' % d.get_basename()) + fname = self.get_target_filename(d) + if compiler.id == 'msvc': + if fname.endswith('dll'): + fname = fname[:-3] + 'lib' + args.append(fname) + # If you have executable e that links to shared lib s1 that links to shared library s2 + # you have to specify s2 as well as s1 when linking e even if e does not directly use + # s2. Gcc handles this case fine but Clang does not for some reason. Thus we need to + # explictly specify all libraries every time. + args += self.build_target_link_arguments(compiler, d.get_dependencies()) + return args + + def determine_windows_extra_paths(self, target): + '''On Windows there is no such thing as an rpath. + We must determine all locations of DLLs that this exe + links to and return them so they can be used in unit + tests.''' + if not isinstance(target, build.Executable): + return [] + prospectives = target.get_transitive_link_deps() + result = [] + for ld in prospectives: + if ld == '' or ld == '.': + continue + dirseg = os.path.join(self.environment.get_build_dir(), self.get_target_dir(ld)) + if dirseg not in result: + result.append(dirseg) + return result + + def write_benchmark_file(self, datafile): + self.write_test_serialisation(self.build.get_benchmarks(), datafile) + + def write_test_file(self, datafile): + self.write_test_serialisation(self.build.get_tests(), datafile) + + def write_test_serialisation(self, tests, datafile): + arr = [] + for t in tests: + exe = t.get_exe() + if isinstance(exe, dependencies.ExternalProgram): + fname = exe.fullpath + else: + fname = [os.path.join(self.environment.get_build_dir(), self.get_target_filename(t.get_exe()))] + is_cross = self.environment.is_cross_build() and self.environment.cross_info.need_cross_compiler() + if is_cross: + exe_wrapper = self.environment.cross_info.config['binaries'].get('exe_wrapper', None) + else: + exe_wrapper = None + if mesonlib.is_windows(): + extra_paths = self.determine_windows_extra_paths(exe) + else: + extra_paths = [] + cmd_args = [] + for a in t.cmd_args: + if isinstance(a, mesonlib.File): + a = os.path.join(self.environment.get_build_dir(), a.rel_to_builddir(self.build_to_src)) + cmd_args.append(a) + ts = TestSerialisation(t.get_name(), t.suite, fname, is_cross, exe_wrapper, + t.is_parallel, cmd_args, t.env, t.should_fail, t.valgrind_args, + t.timeout, t.workdir, extra_paths) + arr.append(ts) + pickle.dump(arr, datafile) + + + def generate_depmf_install(self, d): + if self.build.dep_manifest_name is None: + return + ifilename = os.path.join(self.environment.get_build_dir(), 'depmf.json') + ofilename = os.path.join(self.environment.get_prefix(), self.build.dep_manifest_name) + mfobj = {'type': 'dependency manifest', + 'version': '1.0'} + mfobj['projects'] = self.build.dep_manifest + open(ifilename, 'w').write(json.dumps(mfobj)) + d.data.append([ifilename, ofilename]) + + def get_regen_filelist(self): + '''List of all files whose alteration means that the build + definition needs to be regenerated.''' + deps = [os.path.join(self.build_to_src, df) \ + for df in self.interpreter.get_build_def_files()] + if self.environment.is_cross_build(): + deps.append(os.path.join(self.build_to_src, + self.environment.coredata.cross_file)) + deps.append('meson-private/coredata.dat') + if os.path.exists(os.path.join(self.environment.get_source_dir(), 'meson_options.txt')): + deps.append(os.path.join(self.build_to_src, 'meson_options.txt')) + for sp in self.build.subprojects.keys(): + fname = os.path.join(self.environment.get_source_dir(), sp, 'meson_options.txt') + if os.path.isfile(fname): + deps.append(os.path.join(self.build_to_src, sp, 'meson_options.txt')) + return deps + + def exe_object_to_cmd_array(self, exe): + if self.environment.is_cross_build() and \ + isinstance(exe, build.BuildTarget) and exe.is_cross: + if 'exe_wrapper' not in self.environment.cross_info: + s = 'Can not use target %s as a generator because it is cross-built\n' + s += 'and no exe wrapper is defined. You might want to set it to native instead.' + s = s % exe.name + raise MesonException(s) + if isinstance(exe, build.BuildTarget): + exe_arr = [os.path.join(self.environment.get_build_dir(), self.get_target_filename(exe))] + else: + exe_arr = exe.get_command() + return exe_arr + + def eval_custom_target_command(self, target, absolute_paths=False): + ofilenames = [os.path.join(self.get_target_dir(target), i) for i in target.output] + srcs = [] + outdir = self.get_target_dir(target) + # Many external programs fail on empty arguments. + if outdir == '': + outdir = '.' + if absolute_paths: + outdir = os.path.join(self.environment.get_build_dir(), outdir) + for i in target.sources: + if isinstance(i, str): + fname = os.path.join(self.build_to_src, target.subdir, i) + else: + fname = i.rel_to_builddir(self.build_to_src) + if absolute_paths: + fname = os.path.join(self.environment.get_build_dir(), fname) + srcs.append(fname) + cmd = [] + for i in target.command: + if isinstance(i, build.Executable): + cmd += self.exe_object_to_cmd_array(i) + continue + if isinstance(i, build.CustomTarget): + # GIR scanner will attempt to execute this binary but + # it assumes that it is in path, so always give it a full path. + tmp = i.get_filename()[0] + i = os.path.join(self.get_target_dir(i), tmp) + for (j, src) in enumerate(srcs): + i = i.replace('@INPUT%d@' % j, src) + for (j, res) in enumerate(ofilenames): + i = i.replace('@OUTPUT%d@' % j, res) + if i == '@INPUT@': + cmd += srcs + elif i == '@OUTPUT@': + cmd += ofilenames + else: + if '@OUTDIR@' in i: + i = i.replace('@OUTDIR@', outdir) + elif '@PRIVATE_OUTDIR_' in i: + match = re.search('@PRIVATE_OUTDIR_(ABS_)?([-a-zA-Z0-9.@:]*)@', i) + source = match.group(0) + if match.group(1) is None and not absolute_paths: + lead_dir = '' + else: + lead_dir = self.environment.get_build_dir() + target_id = match.group(2) + i = i.replace(source, + os.path.join(lead_dir, + outdir)) + cmd.append(i) + cmd = [i.replace('\\', '/') for i in cmd] + return (srcs, ofilenames, cmd) diff --git a/mesonbuild/build.py b/mesonbuild/build.py new file mode 100644 index 0000000..c0ba895 --- /dev/null +++ b/mesonbuild/build.py @@ -0,0 +1,969 @@ +# Copyright 2012-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. + +from . import coredata +from . import environment +from . import dependencies +from . import mlog +import copy, os +from .mesonlib import File, flatten + +known_basic_kwargs = {'install' : True, + 'c_pch' : True, + 'cpp_pch' : True, + 'c_args' : True, + 'cpp_args' : True, + 'cs_args' : True, + 'vala_args' : True, + 'link_args' : True, + 'link_depends': True, + 'link_with' : True, + 'include_directories': True, + 'dependencies' : True, + 'install_dir' : True, + 'main_class' : True, + 'gui_app' : True, + 'extra_files' : True, + 'install_rpath' : True, + 'resources' : True, + 'sources' : True, + 'objects' : True, + 'native' : True, + } + +known_shlib_kwargs = known_basic_kwargs.copy() +known_shlib_kwargs.update({'version' : True, + 'soversion' : True}) + +backslash_explanation = \ +'''Compiler arguments have a backslash "\\" character. This is unfortunately not +permitted. The reason for this is that backslash is a shell quoting character +that behaves differently across different systems. Because of this is it not +possible to make it work reliably across all the platforms Meson needs to +support. + +There are several different ways of working around this issue. Most of the time +you are using this to provide a -D define to your compiler. Try instead to +create a config.h file and put all of your definitions in it using +configure_file(). + +Another approach is to move the backslashes into the source and have the other +bits in the def. So you would have an arg -DPLAIN_TEXT="foo" and then in your +C sources something like this: + +const char *fulltext = "\\\\" PLAIN_TEXT; + +We are fully aware that these are not really usable or pleasant ways to do +this but it's the best we can do given the way shell quoting works. +''' + +class InvalidArguments(coredata.MesonException): + pass + +class Build: + """A class that holds the status of one build including + all dependencies and so on. + """ + + def __init__(self, environment): + self.project_name = 'name of master project' + self.project_version = None + self.environment = environment + self.projects = {} + self.targets = {} + self.compilers = [] + self.cross_compilers = [] + self.global_args = {} + self.tests = [] + self.benchmarks = [] + self.headers = [] + self.man = [] + self.data = [] + self.static_linker = None + self.static_cross_linker = None + self.pot = [] + self.subprojects = {} + self.install_scripts = [] + self.install_dirs = [] + self.dep_manifest_name = None + self.dep_manifest = {} + + def has_language(self, language): + for i in self.compilers: + if i.get_language() == language: + return True + return False + + def add_compiler(self, compiler): + if self.static_linker is None and compiler.needs_static_linker(): + self.static_linker = self.environment.detect_static_linker(compiler) + if self.has_language(compiler.get_language()): + return + self.compilers.append(compiler) + + def add_cross_compiler(self, compiler): + if len(self.cross_compilers) == 0: + self.static_cross_linker = self.environment.detect_static_linker(compiler) + for i in self.cross_compilers: + if i.get_language() == compiler.get_language(): + return + self.cross_compilers.append(compiler) + + def get_project(self): + return self.projects[''] + + def get_targets(self): + return self.targets + + def get_tests(self): + return self.tests + + def get_benchmarks(self): + return self.benchmarks + + def get_headers(self): + return self.headers + + def get_man(self): + return self.man + + def get_data(self): + return self.data + + def get_install_subdirs(self): + return self.install_dirs + + def get_global_args(self, compiler): + return self.global_args.get(compiler.get_language(), []) + +class IncludeDirs(): + def __init__(self, curdir, dirs, is_system, extra_build_dirs=None): + self.curdir = curdir + self.incdirs = dirs + self.is_system = is_system + # Interpreter has validated that all given directories + # actually exist. + if extra_build_dirs is None: + self.extra_build_dirs = [] + else: + self.extra_build_dirs = extra_build_dirs + + def get_curdir(self): + return self.curdir + + def get_incdirs(self): + return self.incdirs + + def get_extra_build_dirs(self): + return self.extra_build_dirs + +class ExtractedObjects(): + def __init__(self, target, srclist): + self.target = target + self.srclist = srclist + +class BuildTarget(): + def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs): + self.name = name + self.subdir = subdir + self.subproject = subproject # Can not be calculated from subdir as subproject dirname can be changed per project. + self.is_cross = is_cross + self.sources = [] + self.objects = [] + self.external_deps = [] + self.include_dirs = [] + self.link_targets = [] + self.link_depends = [] + self.filename = 'no_name' + self.need_install = False + self.pch = {} + self.extra_args = {} + self.generated = [] + self.extra_files = [] + self.process_sourcelist(sources) + self.process_objectlist(objects) + self.process_kwargs(kwargs, environment) + self.check_unknown_kwargs(kwargs) + if len(self.sources) == 0 and \ + len(self.generated) == 0 and \ + len(self.objects) == 0: + raise InvalidArguments('Build target %s has no sources.' % name) + self.validate_sources() + + def get_id(self): + # This ID must also be a valid file name on all OSs. + # It should also avoid shell metacharacters for obvious + # reasons. + base = self.name + self.type_suffix() + if self.subproject == '': + return base + return self.subproject + '@@' + base + + def check_unknown_kwargs(self, kwargs): + # Override this method in derived classes that have more + # keywords. + self.check_unknown_kwargs_int(kwargs, known_basic_kwargs) + + def check_unknown_kwargs_int(self, kwargs, known_kwargs): + unknowns = [] + for k in kwargs: + if not k in known_kwargs: + unknowns.append(k) + if len(unknowns) > 0: + mlog.log(mlog.bold('Warning:'), 'Unknown keyword argument(s) in target %s: %s.' % + (self.name, ', '.join(unknowns))) + + def process_objectlist(self, objects): + assert(isinstance(objects, list)) + for s in objects: + if hasattr(s, 'held_object'): + s = s.held_object + if isinstance(s, str): + self.objects.append(s) + elif isinstance(s, ExtractedObjects): + self.objects.append(s) + else: + raise InvalidArguments('Bad object in target %s.' % self.name) + + def process_sourcelist(self, sources): + if not isinstance(sources, list): + sources = [sources] + added_sources = {} # If the same source is defined multiple times, use it only once. + for s in sources: + # Holder unpacking. Ugly. + if hasattr(s, 'held_object'): + s = s.held_object + if isinstance(s, File): + if not s in added_sources: + self.sources.append(s) + added_sources[s] = True + elif isinstance(s, GeneratedList) or isinstance(s, CustomTarget): + self.generated.append(s) + else: + raise InvalidArguments('Bad source in target %s.' % self.name) + + def validate_sources(self): + if len(self.sources) > 0: + firstname = self.sources[0] + if isinstance(firstname, File): + firstname = firstname.fname + first = os.path.split(firstname)[1] + (base, suffix) = os.path.splitext(first) + if suffix == '.rs': + if self.name != base: + raise InvalidArguments('In Rust targets, the first source file must be named projectname.rs.') + + def get_original_kwargs(self): + return self.kwargs + + def unpack_holder(self, d): + if not isinstance(d, list): + d = [d] + newd = [] + for i in d: + if hasattr(i, 'held_object'): + newd.append(i.held_object) + else: + newd.append(i) + return newd + + def copy_kwargs(self, kwargs): + self.kwargs = copy.copy(kwargs) + # This sucks quite badly. Arguments + # are holders but they can't be pickled + # so unpack those known. + if 'dependencies' in self.kwargs: + self.kwargs['dependencies'] = self.unpack_holder(self.kwargs['dependencies']) + if 'link_with' in self.kwargs: + self.kwargs['link_with'] = self.unpack_holder(self.kwargs['link_with']) + + def extract_objects(self, srcargs): + obj_src = [] + for srclist in srcargs: + if not isinstance(srclist, list): + srclist = [srclist] + for src in srclist: + if not isinstance(src, str): + raise coredata.MesonException('Extraction arguments must be strings.') + src = File(False, self.subdir, src) + if src not in self.sources: + raise coredata.MesonException('Tried to extract unknown source %s.' % src) + obj_src.append(src) + return ExtractedObjects(self, obj_src) + + def extract_all_objects(self): + return ExtractedObjects(self, self.sources) + + def get_all_link_deps(self): + return self.get_transitive_link_deps() + + def get_transitive_link_deps(self): + result = [] + for i in self.link_targets: + result += i.get_all_link_deps() + return result + + def get_custom_install_dir(self): + return self.custom_install_dir + + def process_kwargs(self, kwargs, environment): + self.copy_kwargs(kwargs) + kwargs.get('modules', []) + self.need_install = kwargs.get('install', self.need_install) + llist = kwargs.get('link_with', []) + if not isinstance(llist, list): + llist = [llist] + for linktarget in llist: + # Sorry for this hack. Keyword targets are kept in holders + # in kwargs. Unpack here without looking at the exact type. + if hasattr(linktarget, "held_object"): + linktarget = linktarget.held_object + self.link(linktarget) + c_pchlist = kwargs.get('c_pch', []) + if not isinstance(c_pchlist, list): + c_pchlist = [c_pchlist] + self.add_pch('c', c_pchlist) + cpp_pchlist = kwargs.get('cpp_pch', []) + if not isinstance(cpp_pchlist, list): + cpp_pchlist = [cpp_pchlist] + self.add_pch('cpp', cpp_pchlist) + clist = kwargs.get('c_args', []) + if not isinstance(clist, list): + clist = [clist] + self.add_compiler_args('c', clist) + cpplist = kwargs.get('cpp_args', []) + if not isinstance(cpplist, list): + cpplist = [cpplist] + self.add_compiler_args('cpp', cpplist) + cslist = kwargs.get('cs_args', []) + if not isinstance(cslist, list): + cslist = [cslist] + self.add_compiler_args('cs', cslist) + valalist = kwargs.get('vala_args', []) + if not isinstance(valalist, list): + valalist = [valalist] + self.add_compiler_args('vala', valalist) + self.link_args = kwargs.get('link_args', []) + if not isinstance(self.link_args, list): + self.link_args = [self.link_args] + for i in self.link_args: + if not isinstance(i, str): + raise InvalidArguments('Link_args arguments must be strings.') + self.link_depends = kwargs.get('link_depends', []) + if not isinstance(self.link_depends, list): + self.link_depends = [self.link_depends] + for i in self.link_depends: + if not isinstance(i, str): + raise InvalidArguments('Link_depends arguments must be strings.') + inclist = kwargs.get('include_directories', []) + if not isinstance(inclist, list): + inclist = [inclist] + self.add_include_dirs(inclist) + deplist = kwargs.get('dependencies', []) + if not isinstance(deplist, list): + deplist = [deplist] + self.add_external_deps(deplist) + self.custom_install_dir = kwargs.get('install_dir', None) + if self.custom_install_dir is not None: + if not isinstance(self.custom_install_dir, str): + raise InvalidArguments('Custom_install_dir must be a string') + main_class = kwargs.get('main_class', '') + if not isinstance(main_class, str): + raise InvalidArguments('Main class must be a string') + self.main_class = main_class + if isinstance(self, Executable): + self.gui_app = kwargs.get('gui_app', False) + if not isinstance(self.gui_app, bool): + raise InvalidArguments('Argument gui_app must be boolean.') + elif 'gui_app' in kwargs: + raise InvalidArguments('Argument gui_app can only be used on executables.') + extra_files = kwargs.get('extra_files', []) + if isinstance(extra_files, str): + extra_files = [extra_files] + for i in extra_files: + if not isinstance(i, str): + raise InvalidArguments('Arguments to extra_files must be strings.') + trial = os.path.join(environment.get_source_dir(), self.subdir, i) + if not(os.path.isfile(trial)): + raise InvalidArguments('Tried to add non-existing extra file %s.' % i) + self.extra_files = extra_files + self.install_rpath = kwargs.get('install_rpath', '') + if not isinstance(self.install_rpath, str): + raise InvalidArguments('Install_rpath is not a string.') + resources = kwargs.get('resources', []) + if not isinstance(resources, list): + resources = [resources] + for r in resources: + if not isinstance(r, str): + raise InvalidArguments('Resource argument is not a string.') + trial = os.path.join(environment.get_source_dir(), self.subdir, r) + if not os.path.isfile(trial): + raise InvalidArguments('Tried to add non-existing resource %s.' % r) + self.resources = resources + + def get_subdir(self): + return self.subdir + + def get_filename(self): + return self.filename + + def get_extra_args(self, language): + return self.extra_args.get(language, []) + + def get_dependencies(self): + transitive_deps = [] + for t in self.link_targets: + transitive_deps.append(t) + if isinstance(t, StaticLibrary): + transitive_deps += t.get_dependencies() + return transitive_deps + + def get_basename(self): + return self.name + + def get_source_subdir(self): + return self.subdir + + def get_sources(self): + return self.sources + + def get_objects(self): + return self.objects + + def get_generated_sources(self): + return self.generated + + def should_install(self): + return self.need_install + + def has_pch(self): + return len(self.pch) > 0 + + def get_pch(self, language): + try: + return self.pch[language] + except KeyError: + return[] + + def get_include_dirs(self): + return self.include_dirs + + def add_external_deps(self, deps): + if not isinstance(deps, list): + deps = [deps] + for dep in deps: + if hasattr(dep, 'held_object'): + dep = dep.held_object + if isinstance(dep, dependencies.InternalDependency): + self.process_sourcelist(dep.sources) + self.add_include_dirs(dep.include_directories) + for l in dep.libraries: + self.link(l) + self.add_external_deps(dep.ext_deps) + elif isinstance(dep, dependencies.Dependency): + self.external_deps.append(dep) + self.process_sourcelist(dep.get_sources()) + else: + raise InvalidArguments('Argument is not an external dependency') + + def get_external_deps(self): + return self.external_deps + + def link(self, target): + if not isinstance(target, list): + target = [target] + for t in target: + if hasattr(t, 'held_object'): + t = t.held_object + if not isinstance(t, StaticLibrary) and \ + not isinstance(t, SharedLibrary): + raise InvalidArguments('Link target is not library.') + if self.is_cross != t.is_cross: + raise InvalidArguments('Tried to mix cross built and native libraries in target %s.' % self.name) + self.link_targets.append(t) + + def set_generated(self, genlist): + for g in genlist: + if not(isinstance(g, GeneratedList)): + raise InvalidArguments('Generated source argument is not the output of a generator.') + self.generated.append(g) + + def add_pch(self, language, pchlist): + if len(pchlist) == 0: + return + elif len(pchlist) == 1: + if not environment.is_header(pchlist[0]): + raise InvalidArguments('Pch argument %s is not a header.' % pchlist[0]) + elif len(pchlist) == 2: + if environment.is_header(pchlist[0]): + if not environment.is_source(pchlist[1]): + raise InvalidArguments('PCH definition must contain one header and at most one source.') + elif environment.is_source(pchlist[0]): + if not environment.is_header(pchlist[1]): + raise InvalidArguments('PCH definition must contain one header and at most one source.') + pchlist = [pchlist[1], pchlist[0]] + else: + raise InvalidArguments('PCH argument %s is of unknown type.' % pchlist[0]) + elif len(pchlist) > 2: + raise InvalidArguments('PCH definition may have a maximum of 2 files.') + self.pch[language] = pchlist + + def add_include_dirs(self, args): + ids = [] + for a in args: + # FIXME same hack, forcibly unpack from holder. + if hasattr(a, 'held_object'): + a = a.held_object + if not isinstance(a, IncludeDirs): + raise InvalidArguments('Include directory to be added is not an include directory object.') + ids.append(a) + self.include_dirs += ids + + def add_compiler_args(self, language, args): + args = flatten(args) + for a in args: + if not isinstance(a, (str, File)): + raise InvalidArguments('A non-string passed to compiler args.') + if isinstance(a, str) and '\\' in a: + raise InvalidArguments(backslash_explanation) + if language in self.extra_args: + self.extra_args[language] += args + else: + self.extra_args[language] = args + + def get_aliaslist(self): + return [] + + +class Generator(): + def __init__(self, args, kwargs): + if len(args) != 1: + raise InvalidArguments('Generator requires one and only one positional argument') + + exe = args[0] + if hasattr(exe, 'held_object'): + exe = exe.held_object + if not isinstance(exe, Executable) and not isinstance(exe, dependencies.ExternalProgram): + raise InvalidArguments('First generator argument must be an executable.') + self.exe = exe + self.process_kwargs(kwargs) + + def get_exe(self): + return self.exe + + def process_kwargs(self, kwargs): + if 'arguments' not in kwargs: + raise InvalidArguments('Generator must have "arguments" keyword argument.') + args = kwargs['arguments'] + if isinstance(args, str): + args = [args] + if not isinstance(args, list): + raise InvalidArguments('"Arguments" keyword argument must be a string or a list of strings.') + for a in args: + if not isinstance(a, str): + raise InvalidArguments('A non-string object in "arguments" keyword argument.') + self.arglist = args + + if 'output' not in kwargs: + raise InvalidArguments('Generator must have "output" keyword argument.') + outputs = kwargs['output'] + if not isinstance(outputs, list): + outputs = [outputs] + for rule in outputs: + if not isinstance(rule, str): + raise InvalidArguments('"output" may only contain strings.') + if not '@BASENAME@' in rule and not '@PLAINNAME@' in rule: + raise InvalidArguments('Every element of "output" must contain @BASENAME@ or @PLAINNAME@.') + if '/' in rule or '\\' in rule: + raise InvalidArguments('"outputs" must not contain a directory separator.') + if len(outputs) > 1: + for o in outputs: + if '@OUTPUT@' in o: + raise InvalidArguments('Tried to use @OUTPUT@ in a rule with more than one output.') + self.outputs = outputs + + def get_base_outnames(self, inname): + plainname = os.path.split(inname)[1] + basename = plainname.split('.')[0] + return [x.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname) for x in self.outputs] + + def get_arglist(self): + return self.arglist + +class GeneratedList(): + def __init__(self, generator, extra_args=[]): + if hasattr(generator, 'held_object'): + generator = generator.held_object + self.generator = generator + self.infilelist = [] + self.outfilelist = [] + self.outmap = {} + self.extra_depends = [] + self.extra_args = extra_args + + def add_file(self, newfile): + self.infilelist.append(newfile) + outfiles = self.generator.get_base_outnames(newfile) + self.outfilelist += outfiles + self.outmap[newfile] = outfiles + + def get_infilelist(self): + return self.infilelist + + def get_outfilelist(self): + return self.outfilelist + + def get_outputs_for(self, filename): + return self.outmap[filename] + + def get_generator(self): + return self.generator + + def get_extra_args(self): + return self.extra_args + +class Executable(BuildTarget): + def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs): + super().__init__(name, subdir, subproject, is_cross, sources, objects, environment, kwargs) + self.prefix = '' + self.suffix = environment.get_exe_suffix() + suffix = environment.get_exe_suffix() + if len(self.sources) > 0 and self.sources[0].endswith('.cs'): + suffix = 'exe' + if suffix != '': + self.filename = self.name + '.' + suffix + else: + self.filename = self.name + + def type_suffix(self): + return "@exe" + +class StaticLibrary(BuildTarget): + def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs): + super().__init__(name, subdir, subproject, is_cross, sources, objects, environment, kwargs) + if len(self.sources) > 0 and self.sources[0].endswith('.cs'): + raise InvalidArguments('Static libraries not supported for C#.') + self.prefix = environment.get_static_lib_prefix() + self.suffix = environment.get_static_lib_suffix() + if len(self.sources) > 0 and self.sources[0].endswith('.rs'): + self.suffix = 'rlib' + self.filename = self.prefix + self.name + '.' + self.suffix + + def get_import_filename(self): + return self.filename + + def get_osx_filename(self): + return self.get_filename() + + def type_suffix(self): + return "@sta" + +class SharedLibrary(BuildTarget): + def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs): + self.version = None + self.soversion = None + super().__init__(name, subdir, subproject, is_cross, sources, objects, environment, kwargs); + if len(self.sources) > 0 and self.sources[0].endswith('.cs'): + self.suffix = 'dll' + self.prefix = 'lib' + else: + self.prefix = environment.get_shared_lib_prefix() + self.suffix = environment.get_shared_lib_suffix() + if len(self.sources) > 0 and self.sources[0].endswith('.rs'): + self.suffix = 'rlib' + self.importsuffix = environment.get_import_lib_suffix() + self.filename = self.prefix + self.name + '.' + self.suffix + + def process_kwargs(self, kwargs, environment): + super().process_kwargs(kwargs, environment) + if 'version' in kwargs: + self.set_version(kwargs['version']) + if 'soversion' in kwargs: + self.set_soversion(kwargs['soversion']) + + def check_unknown_kwargs(self, kwargs): + self.check_unknown_kwargs_int(kwargs, known_shlib_kwargs) + + def get_shbase(self): + return self.prefix + self.name + '.' + self.suffix + + def get_import_filename(self): + return self.prefix + self.name + '.' + self.importsuffix + + def get_all_link_deps(self): + return [self] + self.get_transitive_link_deps() + + def get_filename(self): + '''Works on all platforms except OSX, which does its own thing.''' + fname = self.get_shbase() + if self.version is None: + return fname + else: + return fname + '.' + self.version + + def get_osx_filename(self): + if self.version is None: + return self.get_shbase() + return self.prefix + self.name + '.' + self.version + '.' + self.suffix + + def set_version(self, version): + if not isinstance(version, str): + raise InvalidArguments('Shared library version is not a string.') + self.version = version + + def set_soversion(self, version): + if isinstance(version, int): + version = str(version) + if not isinstance(version, str): + raise InvalidArguments('Shared library soversion is not a string or integer.') + self.soversion = version + + def get_aliaslist(self): + aliases = [] + if self.soversion is not None: + aliases.append(self.get_shbase() + '.' + self.soversion) + if self.version is not None: + aliases.append(self.get_shbase()) + return aliases + + def type_suffix(self): + return "@sha" + +class CustomTarget: + known_kwargs = {'input' : True, + 'output' : True, + 'command' : True, + 'install' : True, + 'install_dir' : True, + 'build_always' : True, + 'depends' : True, + 'depend_files' : True, + } + + def __init__(self, name, subdir, kwargs): + self.name = name + self.subdir = subdir + self.dependencies = [] + self.extra_depends = [] + self.depend_files = [] # Files that this target depends on but are not on the command line. + self.process_kwargs(kwargs) + self.extra_files = [] + self.install_rpath = '' + unknowns = [] + for k in kwargs: + if k not in CustomTarget.known_kwargs: + unknowns.append(k) + if len(unknowns) > 0: + mlog.log(mlog.bold('Warning:'), 'Unknown keyword arguments in target %s: %s' % + (self.name, ', '.join(unknowns))) + + def get_id(self): + return self.name + self.type_suffix() + + def process_kwargs(self, kwargs): + self.sources = kwargs.get('input', []) + if not isinstance(self.sources, list): + self.sources = [self.sources] + if 'output' not in kwargs: + raise InvalidArguments('Missing keyword argument "output".') + self.output = kwargs['output'] + if not isinstance(self.output, list): + self.output = [self.output] + for i in self.output: + if not(isinstance(i, str)): + raise InvalidArguments('Output argument not a string.') + if '/' in i: + raise InvalidArguments('Output must not contain a path segment.') + if 'command' not in kwargs: + raise InvalidArguments('Missing keyword argument "command".') + cmd = kwargs['command'] + if not(isinstance(cmd, list)): + cmd = [cmd] + final_cmd = [] + for i, c in enumerate(cmd): + if hasattr(c, 'held_object'): + c = c.held_object + if isinstance(c, str): + final_cmd.append(c) + elif isinstance(c, dependencies.ExternalProgram): + final_cmd += c.get_command() + elif isinstance(c, BuildTarget) or isinstance(c, CustomTarget): + self.dependencies.append(c) + final_cmd.append(c) + elif isinstance(c, list): + # Hackety hack, only supports one level of flattening. Should really + # work to arbtrary depth. + for s in c: + if not isinstance(s, str): + raise InvalidArguments('Array as argument %d contains a non-string.' % i) + final_cmd.append(s) + else: + raise InvalidArguments('Argument %s in "command" is invalid.' % i) + self.command = final_cmd + if 'install' in kwargs: + self.install = kwargs['install'] + if not isinstance(self.install, bool): + raise InvalidArguments('"install" must be boolean.') + if 'install_dir' not in kwargs: + raise InvalidArguments('"install_dir" not specified.') + self.install_dir = kwargs['install_dir'] + if not(isinstance(self.install_dir, str)): + raise InvalidArguments('"install_dir" must be a string.') + else: + self.install = False + self.build_always = kwargs.get('build_always', False) + if not isinstance(self.build_always, bool): + raise InvalidArguments('Argument build_always must be a boolean.') + extra_deps = kwargs.get('depends', []) + if not isinstance(extra_deps, list): + extra_deps = [extra_deps] + for ed in extra_deps: + while hasattr(ed, 'held_object'): + ed = ed.held_object + if not isinstance(ed, CustomTarget) and not isinstance(ed, BuildTarget): + raise InvalidArguments('Can only depend on toplevel targets.') + self.extra_depends.append(ed) + depend_files = kwargs.get('depend_files', []) + if not isinstance(depend_files, list): + depend_files = [depend_files] + for i in depend_files: + if isinstance(i, (File, str)): + self.depend_files.append(i) + else: + mlog.debug(i) + raise InvalidArguments('Unknown type in depend_files.') + + def get_basename(self): + return self.name + + def get_dependencies(self): + return self.dependencies + + def should_install(self): + return self.install + + def get_custom_install_dir(self): + return self.install_dir + + def get_subdir(self): + return self.subdir + + def get_filename(self): + return self.output + + def get_aliaslist(self): + return [] + + def get_sources(self): + return self.sources + + def get_generated_sources(self): + return [] + + def type_suffix(self): + return "@cus" + +class RunTarget: + def __init__(self, name, command, args, subdir): + self.name = name + self.command = command + self.args = args + self.subdir = subdir + + def get_id(self): + return self.name + self.type_suffix() + + def get_basename(self): + return self.name + + def get_dependencies(self): + return [] + + def get_generated_sources(self): + return [] + + def get_sources(self): + return [] + + def get_subdir(self): + return self.subdir + + def should_install(self): + return False + + def get_filename(self): + return self.name + + def type_suffix(self): + return "@run" + +class Jar(BuildTarget): + def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs): + super().__init__(name, subdir, subproject, is_cross, sources, objects, environment, kwargs); + for s in self.sources: + if not s.endswith('.java'): + raise InvalidArguments('Jar source %s is not a java file.' % s) + self.filename = self.name + '.jar' + incdirs = kwargs.get('include_directories', []) + + def get_main_class(self): + return self.main_class + + def type_suffix(self): + return "@jar" + +class ConfigureFile(): + + def __init__(self, subdir, sourcename, targetname, configuration_data): + self.subdir = subdir + self.sourcename = sourcename + self.targetname = targetname + self.configuration_data = configuration_data + + def get_configuration_data(self): + return self.configuration_data + + def get_subdir(self): + return self.subdir + + def get_source_name(self): + return self.sourcename + + def get_target_name(self): + return self.targetname + +class ConfigurationData(): + def __init__(self): + super().__init__() + self.values = {} + + def get(self, name): + return self.values[name] + + def keys(self): + return self.values.keys() + +# A bit poorly named, but this represents plain data files to copy +# during install. +class Data(): + def __init__(self, in_sourcetree, source_subdir, sources, install_dir): + self.in_sourcetree = in_sourcetree + self.source_subdir = source_subdir + self.sources = sources + self.install_dir = install_dir + +class InstallScript: + def __init__(self, cmd_arr): + assert(isinstance(cmd_arr, list)) + self.cmd_arr = cmd_arr diff --git a/mesonbuild/compilers.py b/mesonbuild/compilers.py new file mode 100644 index 0000000..ec0181e --- /dev/null +++ b/mesonbuild/compilers.py @@ -0,0 +1,1837 @@ +# Copyright 2012-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 subprocess, os.path +import tempfile +from .import mesonlib +from . import mlog +from .coredata import MesonException +from . import coredata + +"""This file contains the data files of all compilers Meson knows +about. To support a new compiler, add its information below. +Also add corresponding autodetection code in environment.py.""" + +header_suffixes = ['h', 'hh', 'hpp', 'hxx', 'H', 'moc', 'vapi'] +cpp_suffixes = ['cc', 'cpp', 'cxx', 'h', 'hh', 'hpp', 'hxx', 'c++'] +c_suffixes = ['c'] +clike_suffixes = c_suffixes + cpp_suffixes +obj_suffixes = ['o', 'obj', 'res'] +lib_suffixes = ['a', 'lib', 'dll', 'dylib', 'so'] + +def is_header(fname): + if hasattr(fname, 'fname'): + fname = fname.fname + suffix = fname.split('.')[-1] + return suffix in header_suffixes + +def is_source(fname): + if hasattr(fname, 'fname'): + fname = fname.fname + suffix = fname.split('.')[-1] + return suffix in clike_suffixes + +def is_object(fname): + if hasattr(fname, 'fname'): + fname = fname.fname + suffix = fname.split('.')[-1] + return suffix in obj_suffixes + +def is_library(fname): + if hasattr(fname, 'fname'): + fname = fname.fname + suffix = fname.split('.')[-1] + return suffix in lib_suffixes + +gnulike_buildtype_args = {'plain' : [], + 'debug' : ['-g'], + 'debugoptimized' : ['-O2', '-g'], + 'release' : ['-O3']} + +msvc_buildtype_args = {'plain' : [], + 'debug' : ["/MDd", "/ZI", "/Ob0", "/Od", "/RTC1"], + 'debugoptimized' : ["/MD", "/Zi", "/O2", "/Ob1", "/D"], + 'release' : ["/MD", "/O2", "/Ob2"]} + +gnulike_buildtype_linker_args = {} + +if mesonlib.is_osx(): + gnulike_buildtype_linker_args.update({'plain' : [], + 'debug' : [], + 'debugoptimized' : [], + 'release' : [], + }) +else: + gnulike_buildtype_linker_args.update({'plain' : [], + 'debug' : [], + 'debugoptimized' : [], + 'release' : ['-Wl,-O1'], + }) + +msvc_buildtype_linker_args = {'plain' : [], + 'debug' : [], + 'debugoptimized' : [], + 'release' : []} + +java_buildtype_args = {'plain' : [], + 'debug' : ['-g'], + 'debugoptimized' : ['-g'], + 'release' : []} + +rust_buildtype_args = {'plain' : [], + 'debug' : ['-g'], + 'debugoptimized' : ['-g', '--opt-level', '2'], + 'release' : ['--opt-level', '3']} + +mono_buildtype_args = {'plain' : [], + 'debug' : ['-debug'], + 'debugoptimized': ['-debug', '-optimize+'], + 'release' : ['-optimize+']} + +swift_buildtype_args = {'plain' : [], + 'debug' : ['-g'], + 'debugoptimized': ['-g', '-O'], + 'release' : ['-O']} + +gnu_winlibs = ['-lkernel32', '-luser32', '-lgdi32', '-lwinspool', '-lshell32', + '-lole32', '-loleaut32', '-luuid', '-lcomdlg32', '-ladvapi32'] + +msvc_winlibs = ['kernel32.lib', 'user32.lib', 'gdi32.lib', + 'winspool.lib', 'shell32.lib', 'ole32.lib', 'oleaut32.lib', + 'uuid.lib', 'comdlg32.lib', 'advapi32.lib'] + +def build_unix_rpath_args(build_dir, rpath_paths, install_rpath): + if len(rpath_paths) == 0 and len(install_rpath) == 0: + return [] + paths = ':'.join([os.path.join(build_dir, p) for p in rpath_paths]) + if len(paths) < len(install_rpath): + padding = 'X'*(len(install_rpath) - len(paths)) + if len(paths) == 0: + paths = padding + else: + paths = paths + ':' + padding + return ['-Wl,-rpath,' + paths] + +class EnvironmentException(MesonException): + def __init(self, *args, **kwargs): + Exception.__init__(self, *args, **kwargs) + +class CrossNoRunException(MesonException): + def __init(self, *args, **kwargs): + Exception.__init__(self, *args, **kwargs) + +class RunResult(): + def __init__(self, compiled, returncode=999, stdout='UNDEFINED', stderr='UNDEFINED'): + self.compiled = compiled + self.returncode = returncode + self.stdout = stdout + self.stderr = stderr + +class Compiler(): + def __init__(self, exelist, version): + if type(exelist) == type(''): + self.exelist = [exelist] + elif type(exelist) == type([]): + self.exelist = exelist + else: + raise TypeError('Unknown argument to Compiler') + self.version = version + + def get_always_args(self): + return [] + + def get_linker_always_args(self): + return [] + + def get_options(self): + return {} # build afresh every time + + def get_option_compile_args(self, options): + return [] + + def get_option_link_args(self, options): + return [] + + def has_header(self, *args, **kwargs): + raise EnvironmentException('Language %s does not support header checks.' % self.language) + + def compiles(self, *args, **kwargs): + raise EnvironmentException('Language %s does not support compile checks.' % self.language) + + def links(self, *args, **kwargs): + raise EnvironmentException('Language %s does not support link checks.' % self.language) + + def run(self, *args, **kwargs): + raise EnvironmentException('Language %s does not support run checks.' % self.language) + + def sizeof(self, *args, **kwargs): + raise EnvironmentException('Language %s does not support sizeof checks.' % self.language) + + def alignment(self, *args, **kwargs): + raise EnvironmentException('Language %s does not support alignment checks.' % self.language) + + def has_function(self, *args, **kwargs): + raise EnvironmentException('Language %s does not support function checks.' % self.language) + + def unixtype_flags_to_native(self, args): + return args + +class CCompiler(Compiler): + def __init__(self, exelist, version, is_cross, exe_wrapper=None): + super().__init__(exelist, version) + self.language = 'c' + self.default_suffix = 'c' + self.id = 'unknown' + self.is_cross = is_cross + if isinstance(exe_wrapper, str): + self.exe_wrapper = [exe_wrapper] + else: + self.exe_wrapper = exe_wrapper + + def needs_static_linker(self): + return True # When compiling static libraries, so yes. + + def get_always_args(self): + return [] + + def get_warn_args(self, level): + return self.warn_args[level] + + def get_soname_args(self, shlib_name, path, soversion): + return [] + + def split_shlib_to_parts(self, fname): + return (None, fname) + + # The default behaviour is this, override in + # OSX and MSVC. + def build_rpath_args(self, build_dir, rpath_paths, install_rpath): + return build_unix_rpath_args(build_dir, rpath_paths, install_rpath) + + def get_id(self): + return self.id + + def get_dependency_gen_args(self, outtarget, outfile): + return ['-MMD', '-MQ', outtarget, '-MF', outfile] + + def depfile_for_object(self, objfile): + return objfile + '.' + self.get_depfile_suffix() + + def get_depfile_suffix(self): + return 'd' + + def get_language(self): + return self.language + + def get_default_suffix(self): + return self.default_suffix + + def get_exelist(self): + return self.exelist[:] + + def get_linker_exelist(self): + return self.exelist[:] + + def get_compile_only_args(self): + return ['-c'] + + def get_output_args(self, target): + return ['-o', target] + + def get_linker_output_args(self, outputname): + return ['-o', outputname] + + def get_coverage_args(self): + return ['--coverage'] + + def get_coverage_link_args(self): + return ['-lgcov'] + + def get_werror_args(self): + return ['-Werror'] + + def get_std_exe_link_args(self): + return [] + + def get_include_args(self, path, is_system): + if path == '': + path = '.' + if is_system: + return ['-isystem', path] + return ['-I' + path] + + def get_std_shared_lib_link_args(self): + return ['-shared'] + + def can_compile(self, filename): + suffix = filename.split('.')[-1] + if suffix == 'c' or suffix == 'h': + return True + return False + + def get_pic_args(self): + return ['-fPIC'] + + def name_string(self): + return ' '.join(self.exelist) + + def get_pch_use_args(self, pch_dir, header): + return ['-include', os.path.split(header)[-1]] + + def get_pch_name(self, header_name): + return os.path.split(header_name)[-1] + '.' + self.get_pch_suffix() + + def sanity_check(self, work_dir): + mlog.debug('Sanity testing C compiler:', ' '.join(self.exelist)) + mlog.debug('Is cross compiler: %s.' % str(self.is_cross)) + + source_name = os.path.join(work_dir, 'sanitycheckc.c') + if self.is_cross: + binname = 'sanitycheckc_cross' + else: + binname = 'sanitycheckc' + binary_name = os.path.join(work_dir, binname) + ofile = open(source_name, 'w') + ofile.write('int main(int argc, char **argv) { int class=0; return class; }\n') + ofile.close() + if self.is_cross and self.exe_wrapper is None: + # Linking cross built apps is painful. You can't really + # tell if you should use -nostdlib or not and for example + # on OSX the compiler binary is the same but you need + # a ton of compiler flags to differentiate between + # arm and x86_64. So just compile. + extra_flags = ['-c'] + else: + extra_flags = [] + cmdlist = self.exelist + extra_flags + [source_name, '-o', binary_name] + pc = subprocess.Popen(cmdlist, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (stdo, stde) = pc.communicate() + stdo = stdo.decode() + stde = stde.decode() + mlog.debug('Sanity check compiler command line:', ' '.join(cmdlist)) + mlog.debug('Sanity check compile stdout:') + mlog.debug(stdo) + mlog.debug('-----\nSanity check compile stderr:') + mlog.debug(stde) + mlog.debug('-----') + if pc.returncode != 0: + raise EnvironmentException('Compiler %s can not compile programs.' % self.name_string()) + if self.is_cross: + if self.exe_wrapper is None: + # Can't check if the binaries run so we have to assume they do + return + cmdlist = self.exe_wrapper + [binary_name] + else: + cmdlist = [binary_name] + mlog.debug('Running test binary command: ' + ' '.join(cmdlist)) + pe = subprocess.Popen(cmdlist) + pe.wait() + if pe.returncode != 0: + raise EnvironmentException('Executables created by C compiler %s are not runnable.' % self.name_string()) + + def has_header(self, hname, extra_args=[]): + templ = '''#include<%s> +int someSymbolHereJustForFun; +''' + return self.compiles(templ % hname, extra_args) + + def compile(self, code, srcname, extra_args=[]): + commands = self.get_exelist() + commands.append(srcname) + commands += extra_args + mlog.debug('Running compile:') + mlog.debug('Command line: ', ' '.join(commands)) + mlog.debug('Code:\n', code) + p = subprocess.Popen(commands, cwd=os.path.split(srcname)[0], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (stde, stdo) = p.communicate() + stde = stde.decode() + stdo = stdo.decode() + mlog.debug('Compiler stdout:\n', stdo) + mlog.debug('Compiler stderr:\n', stde) + os.remove(srcname) + return p + + def compiles(self, code, extra_args = []): + suflen = len(self.default_suffix) + (fd, srcname) = tempfile.mkstemp(suffix='.'+self.default_suffix) + os.close(fd) + ofile = open(srcname, 'w') + ofile.write(code) + ofile.close() + extra_args = extra_args + self.get_compile_only_args() + p = self.compile(code, srcname, extra_args) + try: + trial = srcname[:-suflen] + 'o' + os.remove(trial) + except FileNotFoundError: + pass + try: + os.remove(srcname[:-suflen] + 'obj') + except FileNotFoundError: + pass + return p.returncode == 0 + + def links(self, code, extra_args = []): + suflen = len(self.default_suffix) + (fd, srcname) = tempfile.mkstemp(suffix='.'+self.default_suffix) + os.close(fd) + (fd, dstname) = tempfile.mkstemp() + os.close(fd) + ofile = open(srcname, 'w') + ofile.write(code) + ofile.close() + extra_args = extra_args + self.get_output_args(dstname) + p = self.compile(code, srcname, extra_args) + try: + os.remove(dstname) + except FileNotFoundError: + pass + return p.returncode == 0 + + def run(self, code, extra_args=[]): + mlog.debug('Running code:\n\n', code) + if self.is_cross and self.exe_wrapper is None: + raise CrossNoRunException('Can not run test applications in this cross environment.') + (fd, srcname) = tempfile.mkstemp(suffix='.'+self.default_suffix) + os.close(fd) + ofile = open(srcname, 'w') + ofile.write(code) + ofile.close() + exename = srcname + '.exe' # Is guaranteed to be executable on every platform. + commands = self.get_exelist() + commands += extra_args + commands.append(srcname) + commands += self.get_output_args(exename) + p = subprocess.Popen(commands, cwd=os.path.split(srcname)[0], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (stdo, stde) = p.communicate() + stde = stde.decode() + stdo = stdo.decode() + mlog.debug('Compiler stdout:\n', stdo) + mlog.debug('Compiler stderr:\n', stde) + os.remove(srcname) + if p.returncode != 0: + return RunResult(False) + if self.is_cross: + cmdlist = self.exe_wrapper + [exename] + else: + cmdlist = exename + try: + pe = subprocess.Popen(cmdlist, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except Exception as e: + mlog.debug('Could not run: %s (error: %s)\n' % (cmdlist, e)) + return RunResult(False) + + (so, se) = pe.communicate() + so = so.decode() + se = se.decode() + mlog.debug('Program stdout:\n', so) + mlog.debug('Program stderr:\n', se) + os.remove(exename) + return RunResult(True, pe.returncode, so, se) + + def cross_sizeof(self, element, prefix, env, extra_args=[]): + templ = '''%s +int temparray[%d-sizeof(%s)]; +''' + try: + extra_args += env.cross_info.config['properties'][self.language + '_args'] + except KeyError: + pass + for i in range(1, 1024): + code = templ % (prefix, i, element) + if self.compiles(code, extra_args): + return i + raise EnvironmentException('Cross checking sizeof overflowed.') + + def sizeof(self, element, prefix, env, extra_args=[]): + if self.is_cross: + return self.cross_sizeof(element, prefix, env, extra_args) + templ = '''#include<stdio.h> +%s + +int main(int argc, char **argv) { + printf("%%ld\\n", (long)(sizeof(%s))); + return 0; +}; +''' + res = self.run(templ % (prefix, element), extra_args) + if not res.compiled: + raise EnvironmentException('Could not compile sizeof test.') + if res.returncode != 0: + raise EnvironmentException('Could not run sizeof test binary.') + return int(res.stdout) + + def cross_alignment(self, typename, env, extra_args=[]): + templ = '''#include<stddef.h> +struct tmp { + char c; + %s target; +}; + +int testarray[%d-offsetof(struct tmp, target)]; +''' + try: + extra_args += env.cross_info.config['properties'][self.language + '_args'] + except KeyError: + pass + for i in range(1, 1024): + code = templ % (typename, i) + if self.compiles(code, extra_args): + return i + raise EnvironmentException('Cross checking offsetof overflowed.') + + def alignment(self, typename, env, extra_args=[]): + if self.is_cross: + return self.cross_alignment(typename, env, extra_args) + templ = '''#include<stdio.h> +#include<stddef.h> + +struct tmp { + char c; + %s target; +}; + +int main(int argc, char **argv) { + printf("%%d", (int)offsetof(struct tmp, target)); + return 0; +} +''' + res = self.run(templ % typename, extra_args) + if not res.compiled: + raise EnvironmentException('Could not compile alignment test.') + if res.returncode != 0: + raise EnvironmentException('Could not run alignment test binary.') + align = int(res.stdout) + if align == 0: + raise EnvironmentException('Could not determine alignment of %s. Sorry. You might want to file a bug.' % typename) + return align + + def has_function(self, funcname, prefix, env, extra_args=[]): + # This fails (returns true) if funcname is a ptr or a variable. + # The correct check is a lot more difficult. + # Fix this to do that eventually. + templ = '''%s +int main(int argc, char **argv) { + void *ptr = (void*)(%s); + return 0; +}; +''' + varname = 'has function ' + funcname + varname = varname.replace(' ', '_') + if self.is_cross: + val = env.cross_info.config['properties'].get(varname, None) + if val is not None: + if isinstance(val, bool): + return val + raise EnvironmentException('Cross variable {0} is not a boolean.'.format(varname)) + return self.compiles(templ % (prefix, funcname), extra_args) + + def has_member(self, typename, membername, prefix, extra_args=[]): + templ = '''%s +void bar() { + %s foo; + foo.%s; +}; +''' + return self.compiles(templ % (prefix, typename, membername), extra_args) + + def has_type(self, typename, prefix, extra_args): + templ = '''%s +void bar() { + sizeof(%s); +}; +''' + return self.compiles(templ % (prefix, typename), extra_args) + + def thread_flags(self): + return ['-pthread'] + + def thread_link_flags(self): + return ['-pthread'] + +class CPPCompiler(CCompiler): + def __init__(self, exelist, version, is_cross, exe_wrap): + CCompiler.__init__(self, exelist, version, is_cross, exe_wrap) + self.language = 'cpp' + self.default_suffix = 'cpp' + + def can_compile(self, filename): + suffix = filename.split('.')[-1] + if suffix in cpp_suffixes: + return True + return False + + def sanity_check(self, work_dir): + source_name = os.path.join(work_dir, 'sanitycheckcpp.cc') + binary_name = os.path.join(work_dir, 'sanitycheckcpp') + ofile = open(source_name, 'w') + ofile.write('class breakCCompiler;int main(int argc, char **argv) { return 0; }\n') + ofile.close() + if self.is_cross and self.exe_wrapper is None: + # Skipping link because of the same reason as for C. + # The comment in CCompiler explains why this is done. + extra_flags = ['-c'] + else: + extra_flags = [] + cmdlist = self.exelist + extra_flags + [source_name, '-o', binary_name] + pc = subprocess.Popen(cmdlist, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (stdo, stde) = pc.communicate() + stdo = stdo.decode() + stde = stde.decode() + mlog.debug('Sanity check compiler command line:', ' '.join(cmdlist)) + mlog.debug('Sanity check compile stdout:') + mlog.debug(stdo) + mlog.debug('-----\nSanity check compile stderr:') + mlog.debug(stde) + mlog.debug('-----') + pc.wait() + if pc.returncode != 0: + raise EnvironmentException('Compiler %s can not compile programs.' % self.name_string()) + if self.is_cross: + if self.exe_wrapper is None: + # Can't check if the binaries run so we have to assume they do + return + cmdlist = self.exe_wrapper + [binary_name] + else: + cmdlist = [binary_name] + pe = subprocess.Popen(cmdlist) + pe.wait() + if pe.returncode != 0: + raise EnvironmentException('Executables created by C++ compiler %s are not runnable.' % self.name_string()) + +class ObjCCompiler(CCompiler): + def __init__(self, exelist, version, is_cross, exe_wrap): + CCompiler.__init__(self, exelist, version, is_cross, exe_wrap) + self.language = 'objc' + self.default_suffix = 'm' + + def can_compile(self, filename): + suffix = filename.split('.')[-1] + if suffix == 'm' or suffix == 'h': + return True + return False + + def sanity_check(self, work_dir): + source_name = os.path.join(work_dir, 'sanitycheckobjc.m') + binary_name = os.path.join(work_dir, 'sanitycheckobjc') + ofile = open(source_name, 'w') + ofile.write('#import<stdio.h>\nint main(int argc, char **argv) { return 0; }\n') + ofile.close() + pc = subprocess.Popen(self.exelist + [source_name, '-o', binary_name]) + pc.wait() + if pc.returncode != 0: + raise EnvironmentException('ObjC compiler %s can not compile programs.' % self.name_string()) + pe = subprocess.Popen(binary_name) + pe.wait() + if pe.returncode != 0: + raise EnvironmentException('Executables created by ObjC compiler %s are not runnable.' % self.name_string()) + +class ObjCPPCompiler(CPPCompiler): + def __init__(self, exelist, version, is_cross, exe_wrap): + CPPCompiler.__init__(self, exelist, version, is_cross, exe_wrap) + self.language = 'objcpp' + self.default_suffix = 'mm' + + def can_compile(self, filename): + suffix = filename.split('.')[-1] + if suffix == 'mm' or suffix == 'h': + return True + return False + + def sanity_check(self, work_dir): + source_name = os.path.join(work_dir, 'sanitycheckobjcpp.mm') + binary_name = os.path.join(work_dir, 'sanitycheckobjcpp') + ofile = open(source_name, 'w') + ofile.write('#import<stdio.h>\nclass MyClass;int main(int argc, char **argv) { return 0; }\n') + ofile.close() + pc = subprocess.Popen(self.exelist + [source_name, '-o', binary_name]) + pc.wait() + if pc.returncode != 0: + raise EnvironmentException('ObjC++ compiler %s can not compile programs.' % self.name_string()) + pe = subprocess.Popen(binary_name) + pe.wait() + if pe.returncode != 0: + raise EnvironmentException('Executables created by ObjC++ compiler %s are not runnable.' % self.name_string()) + +class MonoCompiler(Compiler): + def __init__(self, exelist, version): + super().__init__(exelist, version) + self.language = 'cs' + self.default_suffix = 'cs' + self.id = 'mono' + self.monorunner = 'mono' + + def get_output_args(self, fname): + return ['-out:' + fname] + + def get_link_args(self, fname): + return ['-r:' + fname] + + def get_soname_args(self, shlib_name, path, soversion): + return [] + + def get_werror_args(self): + return ['-warnaserror'] + + def split_shlib_to_parts(self, fname): + return (None, fname) + + def build_rpath_args(self, build_dir, rpath_paths, install_rpath): + return [] + + def get_id(self): + return self.id + + def get_dependency_gen_args(self, outtarget, outfile): + return [] + + def get_language(self): + return self.language + + def get_default_suffix(self): + return self.default_suffix + + def get_exelist(self): + return self.exelist[:] + + def get_linker_exelist(self): + return self.exelist[:] + + def get_compile_only_args(self): + return [] + + def get_linker_output_args(self, outputname): + return [] + + def get_coverage_args(self): + return [] + + def get_coverage_link_args(self): + return [] + + def get_std_exe_link_args(self): + return [] + + def get_include_args(self, path): + return [] + + def get_std_shared_lib_link_args(self): + return [] + + def can_compile(self, filename): + suffix = filename.split('.')[-1] + if suffix == 'cs': + return True + return False + + def get_pic_args(self): + return [] + + def name_string(self): + return ' '.join(self.exelist) + + def get_pch_use_args(self, pch_dir, header): + return [] + + def get_pch_name(self, header_name): + return '' + + def sanity_check(self, work_dir): + src = 'sanity.cs' + obj = 'sanity.exe' + source_name = os.path.join(work_dir, src) + ofile = open(source_name, 'w') + ofile.write('''public class Sanity { + static public void Main () { + } +} +''') + ofile.close() + pc = subprocess.Popen(self.exelist + [src], cwd=work_dir) + pc.wait() + if pc.returncode != 0: + raise EnvironmentException('Mono compiler %s can not compile programs.' % self.name_string()) + cmdlist = [self.monorunner, obj] + pe = subprocess.Popen(cmdlist, cwd=work_dir) + pe.wait() + if pe.returncode != 0: + raise EnvironmentException('Executables created by Mono compiler %s are not runnable.' % self.name_string()) + + def needs_static_linker(self): + return False + + def get_buildtype_args(self, buildtype): + return mono_buildtype_args[buildtype] + +class JavaCompiler(Compiler): + def __init__(self, exelist, version): + super().__init__(exelist, version) + self.language = 'java' + self.default_suffix = 'java' + self.id = 'unknown' + self.javarunner = 'java' + + def get_soname_args(self, shlib_name, path, soversion): + return [] + + def get_werror_args(self): + return ['-Werror'] + + def split_shlib_to_parts(self, fname): + return (None, fname) + + def build_rpath_args(self, build_dir, rpath_paths, install_rpath): + return [] + + def get_id(self): + return self.id + + def get_dependency_gen_args(self, outtarget, outfile): + return [] + + def get_language(self): + return self.language + + def get_default_suffix(self): + return self.default_suffix + + def get_exelist(self): + return self.exelist[:] + + def get_linker_exelist(self): + return self.exelist[:] + + def get_compile_only_args(self): + return [] + + def get_output_args(self, subdir): + if subdir == '': + subdir = './' + return ['-d', subdir, '-s', subdir] + + def get_linker_output_args(self, outputname): + return [] + + def get_coverage_args(self): + return [] + + def get_coverage_link_args(self): + return [] + + def get_std_exe_link_args(self): + return [] + + def get_include_args(self, path): + return [] + + def get_std_shared_lib_link_args(self): + return [] + + def can_compile(self, filename): + suffix = filename.split('.')[-1] + if suffix == 'java': + return True + return False + + def get_pic_args(self): + return [] + + def name_string(self): + return ' '.join(self.exelist) + + def get_pch_use_args(self, pch_dir, header): + return [] + + def get_pch_name(self, header_name): + return '' + + def get_buildtype_args(self, buildtype): + return java_buildtype_args[buildtype] + + def sanity_check(self, work_dir): + src = 'SanityCheck.java' + obj = 'SanityCheck' + source_name = os.path.join(work_dir, src) + ofile = open(source_name, 'w') + ofile.write('''class SanityCheck { + public static void main(String[] args) { + int i; + } +} +''') + ofile.close() + pc = subprocess.Popen(self.exelist + [src], cwd=work_dir) + pc.wait() + if pc.returncode != 0: + raise EnvironmentException('Java compiler %s can not compile programs.' % self.name_string()) + cmdlist = [self.javarunner, obj] + pe = subprocess.Popen(cmdlist, cwd=work_dir) + pe.wait() + if pe.returncode != 0: + raise EnvironmentException('Executables created by Java compiler %s are not runnable.' % self.name_string()) + + def needs_static_linker(self): + return False + +class ValaCompiler(Compiler): + def __init__(self, exelist, version): + super().__init__(exelist, version) + self.version = version + self.id = 'unknown' + self.language = 'vala' + + def name_string(self): + return ' '.join(self.exelist) + + def needs_static_linker(self): + return False # Because compiles into C. + + def get_exelist(self): + return self.exelist + + def get_werror_args(self): + return ['--fatal-warnings'] + + def get_language(self): + return self.language + + def sanity_check(self, work_dir): + src = 'valatest.vala' + source_name = os.path.join(work_dir, src) + ofile = open(source_name, 'w') + ofile.write('''class SanityCheck : Object { +} +''') + ofile.close() + pc = subprocess.Popen(self.exelist + ['-C', '-c', src], cwd=work_dir) + pc.wait() + if pc.returncode != 0: + raise EnvironmentException('Vala compiler %s can not compile programs.' % self.name_string()) + + def can_compile(self, filename): + suffix = filename.split('.')[-1] + return suffix in ('vala', 'vapi') + +class RustCompiler(Compiler): + def __init__(self, exelist, version): + super().__init__(exelist, version) + self.id = 'unknown' + self.language = 'rust' + + def needs_static_linker(self): + return False + + def name_string(self): + return ' '.join(self.exelist) + + def get_exelist(self): + return self.exelist + + def get_id(self): + return self.id + + def get_language(self): + return self.language + + def sanity_check(self, work_dir): + source_name = os.path.join(work_dir, 'sanity.rs') + output_name = os.path.join(work_dir, 'rusttest') + ofile = open(source_name, 'w') + ofile.write('''fn main() { +} +''') + ofile.close() + pc = subprocess.Popen(self.exelist + ['-o', output_name, source_name], cwd=work_dir) + pc.wait() + if pc.returncode != 0: + raise EnvironmentException('Rust compiler %s can not compile programs.' % self.name_string()) + if subprocess.call(output_name) != 0: + raise EnvironmentException('Executables created by Rust compiler %s are not runnable.' % self.name_string()) + + def can_compile(self, fname): + return fname.endswith('.rs') + + def get_dependency_gen_args(self, outfile): + return ['--dep-info', outfile] + + def get_buildtype_args(self, buildtype): + return rust_buildtype_args[buildtype] + +class SwiftCompiler(Compiler): + def __init__(self, exelist, version): + super().__init__(exelist, version) + self.version = version + self.id = 'llvm' + self.language = 'swift' + self.is_cross = False + + def get_id(self): + return self.id + + def get_linker_exelist(self): + return self.exelist + + def name_string(self): + return ' '.join(self.exelist) + + def needs_static_linker(self): + return True + + def get_exelist(self): + return self.exelist + + def get_werror_args(self): + return ['--fatal-warnings'] + + def get_language(self): + return self.language + + def get_dependency_gen_args(self, outtarget, outfile): + return ['-emit-dependencies'] + + def depfile_for_object(self, objfile): + return os.path.splitext(objfile)[0] + '.' + self.get_depfile_suffix() + + def get_depfile_suffix(self): + return 'd' + + def get_output_args(self, target): + return ['-o', target] + + def get_linker_output_args(self, target): + return ['-o', target] + + def get_header_import_args(self, headername): + return ['-import-objc-header', headername] + + def get_warn_args(self, level): + return [] + + def get_buildtype_args(self, buildtype): + return swift_buildtype_args[buildtype] + + def get_buildtype_linker_args(self, buildtype): + return [] + + def get_std_exe_link_args(self): + return ['-emit-executable'] + + def get_module_args(self, modname): + return ['-module-name', modname] + + def get_mod_gen_args(self): + return ['-emit-module'] + + def build_rpath_args(self, *args): + return [] # FIXME + + def get_include_args(self, dirname): + return ['-I' + dirname] + + def get_compile_only_args(self): + return ['-c'] + + def sanity_check(self, work_dir): + src = 'swifttest.swift' + source_name = os.path.join(work_dir, src) + output_name = os.path.join(work_dir, 'swifttest') + ofile = open(source_name, 'w') + ofile.write('''1 + 2 +''') + ofile.close() + pc = subprocess.Popen(self.exelist + ['-emit-executable', '-o', output_name, src], cwd=work_dir) + pc.wait() + if pc.returncode != 0: + raise EnvironmentException('Swift compiler %s can not compile programs.' % self.name_string()) + if subprocess.call(output_name) != 0: + raise EnvironmentException('Executables created by Swift compiler %s are not runnable.' % self.name_string()) + + def can_compile(self, filename): + suffix = filename.split('.')[-1] + return suffix in ('swift') + +class VisualStudioCCompiler(CCompiler): + std_warn_args = ['/W3'] + std_opt_args= ['/O2'] + vs2010_always_args = ['/nologo', '/showIncludes'] + vs2013_always_args = ['/nologo', '/showIncludes', '/FS'] + + def __init__(self, exelist, version, is_cross, exe_wrap): + CCompiler.__init__(self, exelist, version, is_cross, exe_wrap) + self.id = 'msvc' + if int(version.split('.')[0]) > 17: + self.always_args = VisualStudioCCompiler.vs2013_always_args + else: + self.always_args = VisualStudioCCompiler.vs2010_always_args + self.warn_args = {'1': ['/W2'], + '2': ['/W3'], + '3': ['/w4']} + + def get_always_args(self): + return self.always_args + + def get_buildtype_args(self, buildtype): + return msvc_buildtype_args[buildtype] + + def get_buildtype_linker_args(self, buildtype): + return msvc_buildtype_linker_args[buildtype] + + def get_pch_suffix(self): + return 'pch' + + def get_pch_name(self, header): + chopped = os.path.split(header)[-1].split('.')[:-1] + chopped.append(self.get_pch_suffix()) + pchname = '.'.join(chopped) + return pchname + + def get_pch_use_args(self, pch_dir, header): + base = os.path.split(header)[-1] + pchname = self.get_pch_name(header) + return ['/FI' + base, '/Yu' + base, '/Fp' + os.path.join(pch_dir, pchname)] + + def get_compile_only_args(self): + return ['/c'] + + def get_output_args(self, target): + if target.endswith('.exe'): + return ['/Fe' + target] + return ['/Fo' + target] + + def get_dependency_gen_args(self, outtarget, outfile): + return [] + + def get_linker_exelist(self): + return ['link'] # FIXME, should have same path as compiler. + + def get_linker_always_args(self): + return ['/nologo'] + + def get_linker_output_args(self, outputname): + return ['/OUT:' + outputname] + + def get_pic_args(self): + return ['/LD'] + + def get_std_shared_lib_link_args(self): + return ['/DLL'] + + def gen_pch_args(self, header, source, pchname): + objname = os.path.splitext(pchname)[0] + '.obj' + return (objname, ['/Yc' + header, '/Fp' + pchname, '/Fo' + objname ]) + + def sanity_check(self, work_dir): + source_name = 'sanitycheckc.c' + binary_name = 'sanitycheckc' + ofile = open(os.path.join(work_dir, source_name), 'w') + ofile.write('int main(int argc, char **argv) { return 0; }\n') + ofile.close() + pc = subprocess.Popen(self.exelist + [source_name, '/Fe' + binary_name], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + cwd=work_dir) + pc.wait() + if pc.returncode != 0: + raise EnvironmentException('Compiler %s can not compile programs.' % self.name_string()) + pe = subprocess.Popen(os.path.join(work_dir, binary_name)) + pe.wait() + if pe.returncode != 0: + raise EnvironmentException('Executables created by C++ compiler %s are not runnable.' % self.name_string()) + + def build_rpath_args(self, build_dir, rpath_paths, install_rpath): + return [] + + # FIXME, no idea what these should be. + def thread_flags(self): + return [] + + def thread_link_flags(self): + return [] + + def get_options(self): + return {'c_winlibs' : coredata.UserStringArrayOption('c_winlibs', + 'Windows libs to link against.', + msvc_winlibs) + } + + def get_option_link_args(self, options): + return options['c_winlibs'].value + + def unixtype_flags_to_native(self, args): + result = [] + for i in args: + if i.startswith('-L'): + i = '/LIBPATH:' + i[2:] + result.append(i) + return result + + def get_include_args(self, path, is_system): + if path == '': + path = '.' + # msvc does not have a concept of system header dirs. + return ['-I' + path] + +class VisualStudioCPPCompiler(VisualStudioCCompiler): + def __init__(self, exelist, version, is_cross, exe_wrap): + VisualStudioCCompiler.__init__(self, exelist, version, is_cross, exe_wrap) + self.language = 'cpp' + self.default_suffix = 'cpp' + + def can_compile(self, filename): + suffix = filename.split('.')[-1] + if suffix in cpp_suffixes: + return True + return False + + def sanity_check(self, work_dir): + source_name = 'sanitycheckcpp.cpp' + binary_name = 'sanitycheckcpp' + ofile = open(os.path.join(work_dir, source_name), 'w') + ofile.write('class BreakPlainC;int main(int argc, char **argv) { return 0; }\n') + ofile.close() + pc = subprocess.Popen(self.exelist + [source_name, '/Fe' + binary_name], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + cwd=work_dir) + pc.wait() + if pc.returncode != 0: + raise EnvironmentException('Compiler %s can not compile programs.' % self.name_string()) + pe = subprocess.Popen(os.path.join(work_dir, binary_name)) + pe.wait() + if pe.returncode != 0: + raise EnvironmentException('Executables created by C++ compiler %s are not runnable.' % self.name_string()) + + def get_options(self): + return {'cpp_eh' : coredata.UserComboOption('cpp_eh', + 'C++ exception handling type.', + ['none', 'a', 's', 'sc'], + 'sc'), + 'cpp_winlibs' : coredata.UserStringArrayOption('cpp_winlibs', + 'Windows libs to link against.', + msvc_winlibs) + } + + def get_option_compile_args(self, options): + args = [] + std = options['cpp_eh'] + if std.value != 'none': + args.append('/EH' + std.value) + return args + + def get_option_link_args(self, options): + return options['cpp_winlibs'].value + +GCC_STANDARD = 0 +GCC_OSX = 1 +GCC_MINGW = 2 + +def get_gcc_soname_args(gcc_type, shlib_name, path, soversion): + if soversion is None: + sostr = '' + else: + sostr = '.' + soversion + if gcc_type == GCC_STANDARD or gcc_type == GCC_MINGW: + # Might not be correct for mingw but seems to work. + return ['-Wl,-soname,lib%s.so%s' % (shlib_name, sostr)] + elif gcc_type == GCC_OSX: + return ['-install_name', os.path.join(path, 'lib' + shlib_name + '.dylib')] + else: + raise RuntimeError('Not implemented yet.') + + +class GnuCCompiler(CCompiler): + def __init__(self, exelist, version, gcc_type, is_cross, exe_wrapper=None): + CCompiler.__init__(self, exelist, version, is_cross, exe_wrapper) + self.id = 'gcc' + self.gcc_type = gcc_type + self.warn_args = {'1': ['-Wall', '-Winvalid-pch'], + '2': ['-Wall', '-Wextra', '-Winvalid-pch'], + '3' : ['-Wall', '-Wpedantic', '-Wextra', '-Winvalid-pch']} + + def get_pic_args(self): + if self.gcc_type == GCC_MINGW: + return [] # On Window gcc defaults to fpic being always on. + return ['-fPIC'] + + def get_always_args(self): + return ['-pipe'] + + def get_buildtype_args(self, buildtype): + return gnulike_buildtype_args[buildtype] + + def get_buildtype_linker_args(self, buildtype): + return gnulike_buildtype_linker_args[buildtype] + + def get_pch_suffix(self): + return 'gch' + + def split_shlib_to_parts(self, fname): + return (os.path.split(fname)[0], fname) + + def get_soname_args(self, shlib_name, path, soversion): + return get_gcc_soname_args(self.gcc_type, shlib_name, path, soversion) + + def can_compile(self, filename): + return super().can_compile(filename) or filename.split('.')[-1].lower() == 's' # Gcc can do asm, too. + + def get_options(self): + opts = {'c_std' : coredata.UserComboOption('c_std', 'C language standard to use', + ['none', 'c89', 'c99', 'c11', 'gnu89', 'gnu99', 'gnu11'], + 'none')} + if self.gcc_type == GCC_MINGW: + opts.update({ + 'c_winlibs': coredata.UserStringArrayOption('c_winlibs', 'Standard Win libraries to link against', + gnu_winlibs), + }) + return opts + + def get_option_compile_args(self, options): + args = [] + std = options['c_std'] + if std.value != 'none': + args.append('-std=' + std.value) + return args + + def get_option_link_args(self, options): + if self.gcc_type == GCC_MINGW: + return options['c_winlibs'].value + return [] + +class GnuObjCCompiler(ObjCCompiler): + std_opt_args = ['-O2'] + + def __init__(self, exelist, version, is_cross, exe_wrapper=None): + ObjCCompiler.__init__(self, exelist, version, is_cross, exe_wrapper) + self.id = 'gcc' + # Not really correct, but GNU objc is only used on non-OSX non-win. File a bug + # if this breaks your use case. + self.gcc_type = GCC_STANDARD + self.warn_args = {'1': ['-Wall', '-Winvalid-pch'], + '2': ['-Wall', '-Wextra', '-Winvalid-pch'], + '3' : ['-Wall', '-Wpedantic', '-Wextra', '-Winvalid-pch']} + + def get_buildtype_args(self, buildtype): + return gnulike_buildtype_args[buildtype] + + def get_buildtype_linker_args(self, buildtype): + return gnulike_buildtype_linker_args[buildtype] + + def get_pch_suffix(self): + return 'gch' + + def get_soname_args(self, shlib_name, path, soversion): + return get_gcc_soname_args(self.gcc_type, shlib_name, path, soversion) + +class GnuObjCPPCompiler(ObjCPPCompiler): + std_opt_args = ['-O2'] + + def __init__(self, exelist, version, is_cross, exe_wrapper=None): + ObjCCompiler.__init__(self, exelist, version, is_cross, exe_wrapper) + self.id = 'gcc' + # Not really correct, but GNU objc is only used on non-OSX non-win. File a bug + # if this breaks your use case. + self.gcc_type = GCC_STANDARD + self.warn_args = {'1': ['-Wall', '-Winvalid-pch', '-Wnon-virtual-dtor'], + '2': ['-Wall', '-Wextra', '-Winvalid-pch', '-Wnon-virtual-dtor'], + '3' : ['-Wall', '-Wpedantic', '-Wextra', '-Winvalid-pch', '-Wnon-virtual-dtor']} + + def get_buildtype_args(self, buildtype): + return gnulike_buildtype_args[buildtype] + + def get_buildtype_linker_args(self, buildtype): + return gnulike_buildtype_linker_args[buildtype] + + def get_pch_suffix(self): + return 'gch' + + def get_soname_args(self, shlib_name, path, soversion): + return get_gcc_soname_args(self.gcc_type, shlib_name, path, soversion) + +class ClangObjCCompiler(GnuObjCCompiler): + def __init__(self, exelist, version, is_cross, exe_wrapper=None): + super().__init__(exelist, version, is_cross, exe_wrapper) + self.id = 'clang' + +class ClangObjCPPCompiler(GnuObjCPPCompiler): + def __init__(self, exelist, version, is_cross, exe_wrapper=None): + super().__init__(exelist, version, is_cross, exe_wrapper) + self.id = 'clang' + +class ClangCCompiler(CCompiler): + def __init__(self, exelist, version, is_cross, exe_wrapper=None): + CCompiler.__init__(self, exelist, version, is_cross, exe_wrapper) + self.id = 'clang' + self.warn_args = {'1': ['-Wall', '-Winvalid-pch'], + '2': ['-Wall', '-Wextra', '-Winvalid-pch'], + '3' : ['-Weverything']} + + def get_buildtype_args(self, buildtype): + return gnulike_buildtype_args[buildtype] + + def get_buildtype_linker_args(self, buildtype): + return gnulike_buildtype_linker_args[buildtype] + + def get_pch_suffix(self): + return 'pch' + + def can_compile(self, filename): + return super().can_compile(filename) or filename.split('.')[-1].lower() == 's' # Clang can do asm, too. + + def get_pch_use_args(self, pch_dir, header): + # Workaround for Clang bug http://llvm.org/bugs/show_bug.cgi?id=15136 + # This flag is internal to Clang (or at least not documented on the man page) + # so it might change semantics at any time. + return ['-include-pch', os.path.join (pch_dir, self.get_pch_name (header))] + + def get_options(self): + return {'c_std' : coredata.UserComboOption('c_std', 'C language standard to use', + ['none', 'c89', 'c99', 'c11'], + 'none')} + + def get_option_compile_args(self, options): + args = [] + std = options['c_std'] + if std.value != 'none': + args.append('-std=' + std.value) + return args + + def get_option_link_args(self, options): + return [] + +class GnuCPPCompiler(CPPCompiler): + # may need to separate the latter to extra_debug_args or something + std_debug_args = ['-g'] + + def __init__(self, exelist, version, gcc_type, is_cross, exe_wrap): + CPPCompiler.__init__(self, exelist, version, is_cross, exe_wrap) + self.id = 'gcc' + self.gcc_type = gcc_type + self.warn_args = {'1': ['-Wall', '-Winvalid-pch', '-Wnon-virtual-dtor'], + '2': ['-Wall', '-Wextra', '-Winvalid-pch', '-Wnon-virtual-dtor'], + '3': ['-Wall', '-Wpedantic', '-Wextra', '-Winvalid-pch', '-Wnon-virtual-dtor']} + + def get_always_args(self): + return ['-pipe'] + + def get_buildtype_args(self, buildtype): + return gnulike_buildtype_args[buildtype] + + def get_buildtype_linker_args(self, buildtype): + return gnulike_buildtype_linker_args[buildtype] + + def get_pch_suffix(self): + return 'gch' + + def get_soname_args(self, shlib_name, path, soversion): + return get_gcc_soname_args(self.gcc_type, shlib_name, path, soversion) + + def get_options(self): + opts = {'cpp_std' : coredata.UserComboOption('cpp_std', 'C++ language standard to use', + ['none', 'c++03', 'c++11', 'c++14'], + 'none')} + if self.gcc_type == GCC_MINGW: + opts.update({ + 'cpp_winlibs': coredata.UserStringArrayOption('c_winlibs', 'Standard Win libraries to link against', + gnu_winlibs), + }) + return opts + + def get_option_compile_args(self, options): + args = [] + std = options['cpp_std'] + if std.value != 'none': + args.append('-std=' + std.value) + return args + + def get_option_link_args(self, options): + if self.gcc_type == GCC_MINGW: + return options['cpp_winlibs'].value + return [] + +class ClangCPPCompiler(CPPCompiler): + def __init__(self, exelist, version, is_cross, exe_wrapper=None): + CPPCompiler.__init__(self, exelist, version, is_cross, exe_wrapper) + self.id = 'clang' + self.warn_args = {'1': ['-Wall', '-Winvalid-pch', '-Wnon-virtual-dtor'], + '2': ['-Wall', '-Wextra', '-Winvalid-pch', '-Wnon-virtual-dtor'], + '3': ['-Weverything']} + + def get_buildtype_args(self, buildtype): + return gnulike_buildtype_args[buildtype] + + def get_buildtype_linker_args(self, buildtype): + return gnulike_buildtype_linker_args[buildtype] + + def get_pch_suffix(self): + return 'pch' + + def get_pch_use_args(self, pch_dir, header): + # Workaround for Clang bug http://llvm.org/bugs/show_bug.cgi?id=15136 + # This flag is internal to Clang (or at least not documented on the man page) + # so it might change semantics at any time. + return ['-include-pch', os.path.join (pch_dir, self.get_pch_name (header))] + + def get_options(self): + return {'cpp_std' : coredata.UserComboOption('cpp_std', 'C++ language standard to use', + ['none', 'c++03', 'c++11', 'c++14'], + 'none')} + + def get_option_compile_args(self, options): + args = [] + std = options['cpp_std'] + if std.value != 'none': + args.append('-std=' + std.value) + return args + + def get_option_link_args(self, options): + return [] + +class FortranCompiler(Compiler): + def __init__(self, exelist, version, is_cross, exe_wrapper=None): + super().__init__(exelist, version) + self.is_cross = is_cross + self.exe_wrapper = exe_wrapper + self.language = 'fortran' + # Not really correct but I don't have Fortran compilers to test with. Sorry. + self.gcc_type = GCC_STANDARD + self.id = "IMPLEMENTATION CLASSES MUST SET THIS" + + def get_id(self): + return self.id + + def name_string(self): + return ' '.join(self.exelist) + + def get_exelist(self): + return self.exelist + + def get_language(self): + return self.language + + def get_pic_args(self): + if self.gcc_type == GCC_MINGW: + return [] # On Windows gcc defaults to fpic being always on. + return ['-fPIC'] + + def get_std_shared_lib_link_args(self): + return ['-shared'] + + def needs_static_linker(self): + return True + + def sanity_check(self, work_dir): + source_name = os.path.join(work_dir, 'sanitycheckf.f90') + binary_name = os.path.join(work_dir, 'sanitycheckf') + ofile = open(source_name, 'w') + ofile.write('''program prog + print *, "Fortran compilation is working." +end program prog +''') + ofile.close() + pc = subprocess.Popen(self.exelist + [source_name, '-o', binary_name]) + pc.wait() + if pc.returncode != 0: + raise EnvironmentException('Compiler %s can not compile programs.' % self.name_string()) + if self.is_cross: + if self.exe_wrapper is None: + # Can't check if the binaries run so we have to assume they do + return + cmdlist = self.exe_wrapper + [binary_name] + else: + cmdlist = [binary_name] + pe = subprocess.Popen(cmdlist, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + pe.wait() + if pe.returncode != 0: + raise EnvironmentException('Executables created by Fortran compiler %s are not runnable.' % self.name_string()) + + def get_std_warn_args(self, level): + return FortranCompiler.std_warn_args + + def get_buildtype_args(self, buildtype): + return gnulike_buildtype_args[buildtype] + + def get_buildtype_linker_args(self, buildtype): + return gnulike_buildtype_linker_args[buildtype] + + def split_shlib_to_parts(self, fname): + return (os.path.split(fname)[0], fname) + + def get_soname_args(self, shlib_name, path, soversion): + return get_gcc_soname_args(self.gcc_type, shlib_name, path, soversion) + + def get_dependency_gen_args(self, outtarget, outfile): + # Disabled until this is fixed: + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=62162 + #return ['-cpp', '-MMD', '-MQ', outtarget] + return [] + + def get_output_args(self, target): + return ['-o', target] + + def get_compile_only_args(self): + return ['-c'] + + def get_linker_exelist(self): + return self.exelist[:] + + def get_linker_output_args(self, outputname): + return ['-o', outputname] + + def can_compile(self, src): + if hasattr(src, 'fname'): + src = src.fname + suffix = os.path.splitext(src)[1].lower() + if suffix == '.f' or suffix == '.f95' or suffix == '.f90': + return True + return False + + def get_include_args(self, path, is_system): + return ['-I' + path] + + def get_module_outdir_args(self, path): + return ['-J' + path] + + def depfile_for_object(self, objfile): + return objfile + '.' + self.get_depfile_suffix() + + def get_depfile_suffix(self): + return 'd' + + def get_std_exe_link_args(self): + return [] + + def build_rpath_args(self, build_dir, rpath_paths, install_rpath): + return build_unix_rpath_args(build_dir, rpath_paths, install_rpath) + + def module_name_to_filename(self, module_name): + return module_name.lower() + '.mod' + + def get_warn_args(self, level): + return ['-Wall'] + + +class GnuFortranCompiler(FortranCompiler): + def __init__(self, exelist, version, gcc_type, is_cross, exe_wrapper=None): + super().__init__(exelist, version, is_cross, exe_wrapper=None) + self.gcc_type = gcc_type + self.id = 'gcc' + + def get_always_args(self): + return ['-pipe'] + +class G95FortranCompiler(FortranCompiler): + def __init__(self, exelist, version, is_cross, exe_wrapper=None): + super().__init__(exelist, version, is_cross, exe_wrapper=None) + self.id = 'g95' + + def get_module_outdir_args(self, path): + return ['-fmod='+path] + + def get_always_args(self): + return ['-pipe'] + +class SunFortranCompiler(FortranCompiler): + def __init__(self, exelist, version, is_cross, exe_wrapper=None): + super().__init__(exelist, version, is_cross, exe_wrapper=None) + self.id = 'sun' + + def get_dependency_gen_args(self, outtarget, outfile): + return ['-fpp'] + + def get_always_args(self): + return [] + + def get_warn_args(self): + return [] + + def get_module_outdir_args(self, path): + return ['-moddir='+path] + +class IntelFortranCompiler(FortranCompiler): + std_warn_args = ['-warn', 'all'] + + def __init__(self, exelist, version, is_cross, exe_wrapper=None): + super().__init__(exelist, version, is_cross, exe_wrapper=None) + self.id = 'intel' + + def get_module_outdir_args(self, path): + return ['-module', path] + + def can_compile(self, src): + suffix = os.path.splitext(src)[1].lower() + if suffix == '.f' or suffix == '.f90': + return True + return False + + def get_warn_args(self, level): + return IntelFortranCompiler.std_warn_args + +class PathScaleFortranCompiler(FortranCompiler): + std_warn_args = ['-fullwarn'] + + def __init__(self, exelist, version, is_cross, exe_wrapper=None): + super().__init__(exelist, version, is_cross, exe_wrapper=None) + self.id = 'pathscale' + + def get_module_outdir_args(self, path): + return ['-module', path] + + def can_compile(self, src): + suffix = os.path.splitext(src)[1].lower() + if suffix == '.f' or suffix == '.f90' or suffix == '.f95': + return True + return False + + def get_std_warn_args(self, level): + return PathScaleFortranCompiler.std_warn_args + +class PGIFortranCompiler(FortranCompiler): + std_warn_args = ['-Minform=inform'] + + def __init__(self, exelist, version, is_cross, exe_wrapper=None): + super().__init__(exelist, version, is_cross, exe_wrapper=None) + self.id = 'pgi' + + def get_module_outdir_args(self, path): + return ['-module', path] + + def can_compile(self, src): + suffix = os.path.splitext(src)[1].lower() + if suffix == '.f' or suffix == '.f90' or suffix == '.f95': + return True + return False + + def get_warn_args(self, level): + return PGIFortranCompiler.std_warn_args + + +class Open64FortranCompiler(FortranCompiler): + std_warn_args = ['-fullwarn'] + + def __init__(self, exelist, version, is_cross, exe_wrapper=None): + super().__init__(exelist, version, is_cross, exe_wrapper=None) + self.id = 'open64' + + def get_module_outdir_args(self, path): + return ['-module', path] + + def can_compile(self, src): + suffix = os.path.splitext(src)[1].lower() + if suffix == '.f' or suffix == '.f90' or suffix == '.f95': + return True + return False + + def get_warn_args(self, level): + return Open64FortranCompiler.std_warn_args + +class NAGFortranCompiler(FortranCompiler): + std_warn_args = [] + + def __init__(self, exelist, version, is_cross, exe_wrapper=None): + super().__init__(exelist, version, is_cross, exe_wrapper=None) + self.id = 'nagfor' + + def get_module_outdir_args(self, path): + return ['-mdir', path] + + def get_always_args(self): + return [] + + def can_compile(self, src): + suffix = os.path.splitext(src)[1].lower() + if suffix == '.f' or suffix == '.f90' or suffix == '.f95': + return True + return False + + def get_warn_args(self, level): + return NAGFortranCompiler.std_warn_args + + +class VisualStudioLinker(): + always_args = ['/NOLOGO'] + def __init__(self, exelist): + self.exelist = exelist + + def get_exelist(self): + return self.exelist + + def get_std_link_args(self): + return [] + + def get_buildtype_linker_args(self, buildtype): + return [] + + def get_output_args(self, target): + return ['/OUT:' + target] + + def get_coverage_link_args(self): + return [] + + def get_always_args(self): + return VisualStudioLinker.always_args + + def get_linker_always_args(self): + return VisualStudioLinker.always_args + + def build_rpath_args(self, build_dir, rpath_paths, install_rpath): + return [] + + def thread_link_flags(self): + return [] + + def get_option_link_args(self, options): + return [] + + def unixtype_flags_to_native(self, args): + return args + +class ArLinker(): + std_args = ['csr'] + + def __init__(self, exelist): + self.exelist = exelist + self.id = 'ar' + + def build_rpath_args(self, build_dir, rpath_paths, install_rpath): + return [] + + def get_exelist(self): + return self.exelist + + def get_std_link_args(self): + return self.std_args + + def get_output_args(self, target): + return [target] + + def get_buildtype_linker_args(self, buildtype): + return [] + + def get_linker_always_args(self): + return [] + + def get_coverage_link_args(self): + return [] + + def get_always_args(self): + return [] + + def thread_link_flags(self): + return [] + + def get_option_link_args(self, options): + return [] + + def unixtype_flags_to_native(self, args): + return args diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py new file mode 100644 index 0000000..5b1102c --- /dev/null +++ b/mesonbuild/coredata.py @@ -0,0 +1,222 @@ +# Copyright 2012-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 pickle, os, uuid + +version = '0.29.0-research' + +build_types = ['plain', 'debug', 'debugoptimized', 'release'] +layouts = ['mirror', 'flat'] +warning_levels = ['1', '2', '3'] +libtypelist = ['shared', 'static'] + +builtin_options = {'buildtype': True, + 'strip': True, + 'coverage': True, + 'pch': True, + 'unity': True, + 'prefix': True, + 'libdir' : True, + 'bindir' : True, + 'includedir' : True, + 'datadir' : True, + 'mandir' : True, + 'localedir' : True, + 'werror' : True, + 'warning_level': True, + 'layout' : True, + 'default_library': True, + } + +class MesonException(Exception): + def __init__(self, *args, **kwargs): + Exception.__init__(self, *args, **kwargs) + +class UserOption: + def __init__(self, name, description, choices): + super().__init__() + self.name = name + self.choices = choices + self.description = description + + def parse_string(self, valuestring): + return valuestring + +class UserStringOption(UserOption): + def __init__(self, name, description, value, choices=None): + super().__init__(name, description, choices) + self.set_value(value) + + def validate(self, value): + if not isinstance(value, str): + raise MesonException('Value "%s" for string option "%s" is not a string.' % (str(newvalue), self.name)) + if self.name == 'prefix' and not os.path.isabs(value): + raise MesonException('Prefix option must be an absolute path.') + if self.name in ('libdir', 'bindir', 'includedir', 'datadir', 'mandir', 'localedir') \ + and os.path.isabs(value): + raise MesonException('Option %s must not be an absolute path.' % self.name) + + def set_value(self, newvalue): + self.validate(newvalue) + self.value = newvalue + +class UserBooleanOption(UserOption): + def __init__(self, name, description, value): + super().__init__(name, description, '[true, false]') + self.set_value(value) + + def tobool(self, thing): + if isinstance(thing, bool): + return thing + if thing.lower() == 'true': + return True + if thing.lower() == 'false': + return False + raise MesonException('Value %s is not boolean (true or false).' % thing) + + def set_value(self, newvalue): + self.value = self.tobool(newvalue) + + def parse_string(self, valuestring): + if valuestring == 'false': + return False + if valuestring == 'true': + return True + raise MesonException('Value "%s" for boolean option "%s" is not a boolean.' % (valuestring, self.name)) + +class UserComboOption(UserOption): + def __init__(self, name, description, choices, value): + super().__init__(name, description, choices) + if not isinstance(self.choices, list): + raise MesonException('Combo choices must be an array.') + for i in self.choices: + if not isinstance(i, str): + raise MesonException('Combo choice elements must be strings.') + self.set_value(value) + + def set_value(self, newvalue): + if newvalue not in self.choices: + optionsstring = ', '.join(['"%s"' % (item,) for item in self.choices]) + raise MesonException('Value "%s" for combo option "%s" is not one of the choices. Possible choices are: %s.' % (newvalue, self.name, optionsstring)) + self.value = newvalue + +class UserStringArrayOption(UserOption): + def __init__(self, name, description, value, **kwargs): + super().__init__(name, description, kwargs.get('choices', [])) + self.set_value(value) + + def set_value(self, newvalue): + if isinstance(newvalue, str): + if not newvalue.startswith('['): + raise MesonException('Valuestring does not define an array: ' + newvalue) + newvalue = eval(newvalue, {}, {}) # Yes, it is unsafe. + if not isinstance(newvalue, list): + raise MesonException('String array value is not an array.') + for i in newvalue: + if not isinstance(i, str): + raise MesonException('String array element not a string.') + self.value = newvalue + +# This class contains all data that must persist over multiple +# invocations of Meson. It is roughly the same thing as +# cmakecache. + +class CoreData(): + + def __init__(self, options): + self.guid = str(uuid.uuid4()).upper() + self.test_guid = str(uuid.uuid4()).upper() + self.regen_guid = str(uuid.uuid4()).upper() + self.target_guids = {} + self.version = version + self.builtin_options = {} + self.init_builtins(options) + self.user_options = {} + self.compiler_options = {} + self.external_args = {} # These are set from "the outside" with e.g. mesonconf + self.external_link_args = {} + if options.cross_file is not None: + self.cross_file = os.path.join(os.getcwd(), options.cross_file) + else: + self.cross_file = None + + self.compilers = {} + self.cross_compilers = {} + self.deps = {} + self.ext_progs = {} + self.modules = {} + + def init_builtins(self, options): + self.builtin_options['prefix'] = UserStringOption('prefix', 'Installation prefix', options.prefix) + self.builtin_options['libdir'] = UserStringOption('libdir', 'Library dir', options.libdir) + self.builtin_options['bindir'] = UserStringOption('bindir', 'Executable dir', options.bindir) + self.builtin_options['includedir'] = UserStringOption('includedir', 'Include dir', options.includedir) + self.builtin_options['datadir'] = UserStringOption('datadir', 'Data directory', options.datadir) + self.builtin_options['mandir'] = UserStringOption('mandir', 'Man page dir', options.mandir) + self.builtin_options['localedir'] = UserStringOption('localedir', 'Locale dir', options.localedir) + self.builtin_options['backend'] = UserStringOption('backend', 'Backend to use', options.backend) + self.builtin_options['buildtype'] = UserComboOption('buildtype', 'Build type', build_types, options.buildtype) + self.builtin_options['strip'] = UserBooleanOption('strip', 'Strip on install', options.strip) + self.builtin_options['use_pch'] = UserBooleanOption('use_pch', 'Use precompiled headers', options.use_pch) + self.builtin_options['unity'] = UserBooleanOption('unity', 'Unity build', options.unity) + self.builtin_options['coverage'] = UserBooleanOption('coverage', 'Enable coverage', options.coverage) + self.builtin_options['warning_level'] = UserComboOption('warning_level', 'Warning level', warning_levels, options.warning_level) + self.builtin_options['werror'] = UserBooleanOption('werror', 'Warnings are errors', options.werror) + self.builtin_options['layout'] = UserComboOption('layout', 'Build dir layout', layouts, options.layout) + self.builtin_options['default_library'] = UserComboOption('default_library', 'Default_library type', libtypelist, options.default_library) + + def get_builtin_option(self, optname): + if optname in self.builtin_options: + return self.builtin_options[optname].value + raise RuntimeError('Tried to get unknown builtin option %s' % optname) + + def set_builtin_option(self, optname, value): + if optname in self.builtin_options: + self.builtin_options[optname].set_value(value) + else: + raise RuntimeError('Tried to set unknown builtin option %s' % optname) + + def is_builtin_option(self, optname): + return optname in self.builtin_options + +def load(filename): + obj = pickle.load(open(filename, 'rb')) + if not isinstance(obj, CoreData): + raise RuntimeError('Core data file is corrupted.') + if obj.version != version: + raise RuntimeError('Build tree has been generated with Meson version %s, which is incompatible with current version %s.'% + (obj.version, version)) + return obj + +def save(obj, filename): + if obj.version != version: + raise RuntimeError('Fatal version mismatch corruption.') + pickle.dump(obj, open(filename, 'wb')) + +forbidden_target_names = {'clean': None, + 'clean-gcno': None, + 'clean-gcda': None, + 'coverage-text': None, + 'coverage-xml': None, + 'coverage-html': None, + 'phony': None, + 'PHONY': None, + 'all': None, + 'test': None, + 'test-valgrind': None, + 'test-': None, + 'benchmark': None, + 'install': None, + 'build.ninja': None, + } diff --git a/mesonbuild/dependencies.py b/mesonbuild/dependencies.py new file mode 100644 index 0000000..974559f --- /dev/null +++ b/mesonbuild/dependencies.py @@ -0,0 +1,1120 @@ +# 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 file contains the detection logic for external +# dependencies. Mostly just uses pkg-config but also contains +# custom logic for packages that don't provide them. + +# Currently one file, should probably be split into a +# package before this gets too big. + +import re +import os, stat, glob, subprocess, shutil +from . coredata import MesonException +from . import mlog +from . import mesonlib + +class DependencyException(MesonException): + def __init__(self, *args, **kwargs): + MesonException.__init__(self, *args, **kwargs) + +class Dependency(): + def __init__(self): + self.name = "null" + self.is_found = False + + def get_compile_args(self): + return [] + + def get_link_args(self): + return [] + + def found(self): + return self.is_found + + def get_sources(self): + """Source files that need to be added to the target. + As an example, gtest-all.cc when using GTest.""" + return [] + + def get_name(self): + return self.name + + def get_exe_args(self): + return [] + + def need_threads(self): + return False + +class InternalDependency(): + def __init__(self, incdirs, libraries, sources, ext_deps): + super().__init__() + self.include_directories = incdirs + self.libraries = libraries + self.sources = sources + self.ext_deps = ext_deps + +class PkgConfigDependency(Dependency): + pkgconfig_found = None + + def __init__(self, name, environment, kwargs): + Dependency.__init__(self) + self.is_libtool = False + self.required = kwargs.get('required', True) + if 'native' in kwargs and environment.is_cross_build(): + want_cross = not kwargs['native'] + else: + want_cross = environment.is_cross_build() + self.name = name + if PkgConfigDependency.pkgconfig_found is None: + self.check_pkgconfig() + + self.is_found = False + if not PkgConfigDependency.pkgconfig_found: + if self.required: + raise DependencyException('Pkg-config not found.') + self.cargs = [] + self.libs = [] + return + if environment.is_cross_build() and want_cross: + if "pkgconfig" not in environment.cross_info.config["binaries"]: + raise DependencyException('Pkg-config binary missing from cross file.') + pkgbin = environment.cross_info.config["binaries"]['pkgconfig'] + self.type_string = 'Cross' + else: + pkgbin = 'pkg-config' + self.type_string = 'Native' + + mlog.debug('Determining dependency %s with pkg-config executable %s.' % (name, pkgbin)) + self.pkgbin = pkgbin + p = subprocess.Popen([pkgbin, '--modversion', name], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + out = p.communicate()[0] + if p.returncode != 0: + if self.required: + raise DependencyException('%s dependency %s not found.' % (self.type_string, name)) + self.modversion = 'none' + self.cargs = [] + self.libs = [] + else: + self.modversion = out.decode().strip() + mlog.log('%s dependency' % self.type_string, mlog.bold(name), 'found:', + mlog.green('YES'), self.modversion) + self.version_requirement = kwargs.get('version', None) + if self.version_requirement is None: + self.is_found = True + else: + if not isinstance(self.version_requirement, str): + raise DependencyException('Version argument must be string.') + self.is_found = mesonlib.version_compare(self.modversion, self.version_requirement) + if not self.is_found and self.required: + raise DependencyException( + 'Invalid version of a dependency, needed %s %s found %s.' % + (name, self.version_requirement, self.modversion)) + if not self.is_found: + return + p = subprocess.Popen([pkgbin, '--cflags', name], stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + out = p.communicate()[0] + if p.returncode != 0: + raise DependencyException('Could not generate cargs for %s:\n\n%s' % \ + (name, out.decode(errors='ignore'))) + self.cargs = out.decode().split() + + p = subprocess.Popen([pkgbin, '--libs', name], stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + out = p.communicate()[0] + if p.returncode != 0: + raise DependencyException('Could not generate libs for %s:\n\n%s' % \ + (name, out.decode(errors='ignore'))) + self.libs = [] + for lib in out.decode().split(): + if lib.endswith(".la"): + shared_libname = self.extract_libtool_shlib(lib) + shared_lib = os.path.join(os.path.dirname(lib), shared_libname) + if not os.path.exists(shared_lib): + shared_lib = os.path.join(os.path.dirname(lib), ".libs", shared_libname) + + if not os.path.exists(shared_lib): + raise DependencyException('Got a libtools specific "%s" dependencies' + 'but we could not compute the actual shared' + 'library path' % lib) + lib = shared_lib + self.is_libtool = True + + self.libs.append(lib) + + def get_variable(self, variable_name): + p = subprocess.Popen([self.pkgbin, '--variable=%s' % variable_name, self.name], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out = p.communicate()[0] + if p.returncode != 0: + if self.required: + raise DependencyException('%s dependency %s not found.' % + (self.type_string, self.name)) + else: + variable = out.decode().strip() + mlog.debug('return of subprocess : %s' % variable) + + return variable + + def get_modversion(self): + return self.modversion + + def get_compile_args(self): + return self.cargs + + def get_link_args(self): + return self.libs + + def check_pkgconfig(self): + try: + p = subprocess.Popen(['pkg-config', '--version'], stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + out = p.communicate()[0] + if p.returncode == 0: + mlog.log('Found pkg-config:', mlog.bold(shutil.which('pkg-config')), + '(%s)' % out.decode().strip()) + PkgConfigDependency.pkgconfig_found = True + return + except Exception: + pass + PkgConfigDependency.pkgconfig_found = False + mlog.log('Found Pkg-config:', mlog.red('NO')) + + def found(self): + return self.is_found + + def extract_field(self, la_file, fieldname): + for line in open(la_file): + arr = line.strip().split('=') + if arr[0] == fieldname: + return arr[1][1:-1] + return None + + def extract_dlname_field(self, la_file): + return self.extract_field(la_file, 'dlname') + + def extract_libdir_field(self, la_file): + return self.extract_field(la_file, 'libdir') + + def extract_libtool_shlib(self, la_file): + ''' + Returns the path to the shared library + corresponding to this .la file + ''' + dlname = self.extract_dlname_field(la_file) + if dlname is None: + return None + + # Darwin uses absolute paths where possible; since the libtool files never + # contain absolute paths, use the libdir field + if mesonlib.is_osx(): + dlbasename = os.path.basename(dlname) + libdir = self.extract_libdir_field(la_file) + if libdir is None: + return dlbasename + return os.path.join(libdir, dlbasename) + # From the comments in extract_libtool(), older libtools had + # a path rather than the raw dlname + return os.path.basename(dlname) + +class WxDependency(Dependency): + wx_found = None + + def __init__(self, environment, kwargs): + Dependency.__init__(self) + if WxDependency.wx_found is None: + self.check_wxconfig() + + if not WxDependency.wx_found: + raise DependencyException('Wx-config not found.') + self.is_found = False + p = subprocess.Popen([self.wxc, '--version'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + out = p.communicate()[0] + if p.returncode != 0: + mlog.log('Dependency wxwidgets found:', mlog.red('NO')) + self.cargs = [] + self.libs = [] + else: + self.modversion = out.decode().strip() + version_req = kwargs.get('version', None) + if version_req is not None: + if not mesonlib.version_compare(self.modversion, version_req): + mlog.log('Wxwidgets version %s does not fullfill requirement %s' %\ + (self.modversion, version_req)) + return + mlog.log('Dependency wxwidgets found:', mlog.green('YES')) + self.is_found = True + self.requested_modules = self.get_requested(kwargs) + # wx-config seems to have a cflags as well but since it requires C++, + # this should be good, at least for now. + p = subprocess.Popen([self.wxc, '--cxxflags'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + out = p.communicate()[0] + if p.returncode != 0: + raise DependencyException('Could not generate cargs for wxwidgets.') + self.cargs = out.decode().split() + + p = subprocess.Popen([self.wxc, '--libs'] + self.requested_modules, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out = p.communicate()[0] + if p.returncode != 0: + raise DependencyException('Could not generate libs for wxwidgets.') + self.libs = out.decode().split() + + def get_requested(self, kwargs): + modules = 'modules' + if not modules in kwargs: + return [] + candidates = kwargs[modules] + if isinstance(candidates, str): + return [candidates] + for c in candidates: + if not isinstance(c, str): + raise DependencyException('wxwidgets module argument is not a string.') + return candidates + + def get_modversion(self): + return self.modversion + + def get_compile_args(self): + return self.cargs + + def get_link_args(self): + return self.libs + + def check_wxconfig(self): + for wxc in ['wx-config-3.0', 'wx-config']: + try: + p = subprocess.Popen([wxc, '--version'], stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + out = p.communicate()[0] + if p.returncode == 0: + mlog.log('Found wx-config:', mlog.bold(shutil.which(wxc)), + '(%s)' % out.decode().strip()) + self.wxc = wxc + WxDependency.wx_found = True + return + except Exception: + pass + WxDependency.wxconfig_found = False + mlog.log('Found wx-config:', mlog.red('NO')) + + def found(self): + return self.is_found + +class ExternalProgram(): + def __init__(self, name, fullpath=None, silent=False, search_dir=None): + self.name = name + self.fullpath = None + if fullpath is not None: + if not isinstance(fullpath, list): + self.fullpath = [fullpath] + else: + self.fullpath = fullpath + else: + self.fullpath = [shutil.which(name)] + if self.fullpath[0] is None and search_dir is not None: + trial = os.path.join(search_dir, name) + suffix = os.path.splitext(trial)[-1].lower()[1:] + if mesonlib.is_windows() and (suffix == 'exe' or suffix == 'com'\ + or suffix == 'bat'): + self.fullpath = [trial] + elif not mesonlib.is_windows() and os.access(trial, os.X_OK): + self.fullpath = [trial] + else: + # Now getting desperate. Maybe it is a script file that is a) not chmodded + # executable or b) we are on windows so they can't be directly executed. + try: + first_line = open(trial).readline().strip() + if first_line.startswith('#!'): + commands = first_line[2:].split('#')[0].strip().split() + if mesonlib.is_windows(): + # Windows does not have /usr/bin. + commands[0] = commands[0].split('/')[-1] + if commands[0] == 'env': + commands = commands[1:] + self.fullpath = commands + [trial] + except Exception: + pass + if not silent: + if self.found(): + mlog.log('Program', mlog.bold(name), 'found:', mlog.green('YES'), + '(%s)' % ' '.join(self.fullpath)) + else: + mlog.log('Program', mlog.bold(name), 'found:', mlog.red('NO')) + + def found(self): + return self.fullpath[0] is not None + + def get_command(self): + return self.fullpath + + def get_name(self): + return self.name + +class ExternalLibrary(Dependency): + def __init__(self, name, fullpath=None, silent=False): + super().__init__() + self.name = name + self.fullpath = fullpath + if not silent: + if self.found(): + mlog.log('Library', mlog.bold(name), 'found:', mlog.green('YES'), + '(%s)' % self.fullpath) + else: + mlog.log('Library', mlog.bold(name), 'found:', mlog.red('NO')) + + def found(self): + return self.fullpath is not None + + def get_link_args(self): + if self.found(): + return [self.fullpath] + return [] + +class BoostDependency(Dependency): + # Some boost libraries have different names for + # their sources and libraries. This dict maps + # between the two. + name2lib = {'test' : 'unit_test_framework'} + + def __init__(self, environment, kwargs): + Dependency.__init__(self) + self.name = 'boost' + self.libdir = '' + try: + self.boost_root = os.environ['BOOST_ROOT'] + if not os.path.isabs(self.boost_root): + raise DependencyException('BOOST_ROOT must be an absolute path.') + except KeyError: + self.boost_root = None + if self.boost_root is None: + if mesonlib.is_windows(): + self.boost_root = self.detect_win_root() + self.incdir = self.boost_root + else: + self.incdir = '/usr/include' + else: + self.incdir = os.path.join(self.boost_root, 'include') + self.boost_inc_subdir = os.path.join(self.incdir, 'boost') + mlog.debug('Boost library root dir is', self.boost_root) + self.src_modules = {} + self.lib_modules = {} + self.lib_modules_mt = {} + self.detect_version() + self.requested_modules = self.get_requested(kwargs) + module_str = ', '.join(self.requested_modules) + if self.version is not None: + self.detect_src_modules() + self.detect_lib_modules() + self.validate_requested() + if self.boost_root is not None: + info = self.version + ', ' + self.boost_root + else: + info = self.version + mlog.log('Dependency Boost (%s) found:' % module_str, mlog.green('YES'), + '(' + info + ')') + else: + mlog.log("Dependency Boost (%s) found:" % module_str, mlog.red('NO')) + + def detect_win_root(self): + globtext = 'c:\\local\\boost_*' + files = glob.glob(globtext) + if len(files) > 0: + return files[0] + return 'C:\\' + + def get_compile_args(self): + args = [] + if self.boost_root is not None: + if mesonlib.is_windows(): + args.append('-I' + self.boost_root) + else: + args.append('-I' + os.path.join(self.boost_root, 'include')) + else: + args.append('-I' + self.incdir) + return args + + def get_requested(self, kwargs): + candidates = kwargs.get('modules', []) + if isinstance(candidates, str): + return [candidates] + for c in candidates: + if not isinstance(c, str): + raise DependencyException('Boost module argument is not a string.') + return candidates + + def validate_requested(self): + for m in self.requested_modules: + if m not in self.src_modules: + raise DependencyException('Requested Boost module "%s" not found.' % m) + + def found(self): + return self.version is not None + + def get_version(self): + return self.version + + def detect_version(self): + try: + ifile = open(os.path.join(self.boost_inc_subdir, 'version.hpp')) + except FileNotFoundError: + self.version = None + return + for line in ifile: + if line.startswith("#define") and 'BOOST_LIB_VERSION' in line: + ver = line.split()[-1] + ver = ver[1:-1] + self.version = ver.replace('_', '.') + return + self.version = None + + def detect_src_modules(self): + for entry in os.listdir(self.boost_inc_subdir): + entry = os.path.join(self.boost_inc_subdir, entry) + if stat.S_ISDIR(os.stat(entry).st_mode): + self.src_modules[os.path.split(entry)[-1]] = True + + def detect_lib_modules(self): + if mesonlib.is_windows(): + return self.detect_lib_modules_win() + return self.detect_lib_modules_nix() + + def detect_lib_modules_win(self): + if mesonlib.is_32bit(): + gl = 'lib32*' + else: + gl = 'lib64*' + libdir = glob.glob(os.path.join(self.boost_root, gl)) + if len(libdir) == 0: + return + libdir = libdir[0] + self.libdir = libdir + globber = 'boost_*-gd-*.lib' # FIXME + for entry in glob.glob(os.path.join(libdir, globber)): + (_, fname) = os.path.split(entry) + base = fname.split('_', 1)[1] + modname = base.split('-', 1)[0] + self.lib_modules_mt[modname] = fname + + def detect_lib_modules_nix(self): + libsuffix = None + if mesonlib.is_osx(): + libsuffix = 'dylib' + else: + libsuffix = 'so' + + globber = 'libboost_*.{}'.format(libsuffix) + if self.boost_root is None: + libdirs = mesonlib.get_library_dirs() + else: + libdirs = [os.path.join(self.boost_root, 'lib')] + for libdir in libdirs: + for entry in glob.glob(os.path.join(libdir, globber)): + lib = os.path.basename(entry) + name = lib.split('.')[0].split('_', 1)[-1] + # I'm not 100% sure what to do here. Some distros + # have modules such as thread only as -mt versions. + if entry.endswith('-mt.so'): + self.lib_modules_mt[name] = True + else: + self.lib_modules[name] = True + + def get_win_link_args(self): + args = [] + if self.boost_root: + args.append('-L' + self.libdir) + for module in self.requested_modules: + module = BoostDependency.name2lib.get(module, module) + if module in self.lib_modules_mt: + args.append(self.lib_modules_mt[module]) + return args + + def get_link_args(self): + if mesonlib.is_windows(): + return self.get_win_link_args() + args = [] + if self.boost_root: + args.append('-L' + os.path.join(self.boost_root, 'lib')) + for module in self.requested_modules: + module = BoostDependency.name2lib.get(module, module) + if module in self.lib_modules or module in self.lib_modules_mt: + linkcmd = '-lboost_' + module + args.append(linkcmd) + # FIXME a hack, but Boost's testing framework has a lot of + # different options and it's hard to determine what to do + # without feedback from actual users. Update this + # as we get more bug reports. + if module == 'unit_testing_framework': + args.append('-lboost_test_exec_monitor') + elif module + '-mt' in self.lib_modules_mt: + linkcmd = '-lboost_' + module + '-mt' + args.append(linkcmd) + if module == 'unit_testing_framework': + args.append('-lboost_test_exec_monitor-mt') + return args + + def get_sources(self): + return [] + + def need_threads(self): + return 'thread' in self.requested_modules + +class GTestDependency(Dependency): + def __init__(self, environment, kwargs): + Dependency.__init__(self) + self.main = kwargs.get('main', False) + self.name = 'gtest' + self.libname = 'libgtest.so' + self.libmain_name = 'libgtest_main.so' + self.include_dir = '/usr/include' + self.src_include_dir = '/usr/src/gtest' + self.src_dir = '/usr/src/gtest/src' + self.all_src = mesonlib.File.from_absolute_file( + os.path.join(self.src_dir, 'gtest-all.cc')) + self.main_src = mesonlib.File.from_absolute_file( + os.path.join(self.src_dir, 'gtest_main.cc')) + self.detect() + + def found(self): + return self.is_found + + def detect(self): + trial_dirs = mesonlib.get_library_dirs() + glib_found = False + gmain_found = False + for d in trial_dirs: + if os.path.isfile(os.path.join(d, self.libname)): + glib_found = True + if os.path.isfile(os.path.join(d, self.libmain_name)): + gmain_found = True + if glib_found and gmain_found: + self.is_found = True + self.compile_args = [] + self.link_args = ['-lgtest'] + if self.main: + self.link_args.append('-lgtest_main') + self.sources = [] + mlog.log('Dependency GTest found:', mlog.green('YES'), '(prebuilt)') + elif os.path.exists(self.src_dir): + self.is_found = True + self.compile_args = ['-I' + self.src_include_dir] + self.link_args = [] + if self.main: + self.sources = [self.all_src, self.main_src] + else: + self.sources = [self.all_src] + mlog.log('Dependency GTest found:', mlog.green('YES'), '(building self)') + else: + mlog.log('Dependency GTest found:', mlog.red('NO')) + self.is_found = False + return self.is_found + + def get_compile_args(self): + arr = [] + if self.include_dir != '/usr/include': + arr.append('-I' + self.include_dir) + arr.append('-I' + self.src_include_dir) + return arr + + def get_link_args(self): + return self.link_args + def get_version(self): + return '1.something_maybe' + def get_sources(self): + return self.sources + + def need_threads(self): + return True + +class GMockDependency(Dependency): + def __init__(self, environment, kwargs): + Dependency.__init__(self) + # GMock may be a library or just source. + # Work with both. + self.name = 'gmock' + self.libname = 'libgmock.so' + trial_dirs = mesonlib.get_library_dirs() + gmock_found = False + for d in trial_dirs: + if os.path.isfile(os.path.join(d, self.libname)): + gmock_found = True + if gmock_found: + self.is_found = True + self.compile_args = [] + self.link_args = ['-lgmock'] + self.sources = [] + mlog.log('Dependency GMock found:', mlog.green('YES'), '(prebuilt)') + return + + for d in ['/usr/src/gmock/src', '/usr/src/gmock']: + if os.path.exists(d): + self.is_found = True + # Yes, we need both because there are multiple + # versions of gmock that do different things. + self.compile_args = ['-I/usr/src/gmock', '-I/usr/src/gmock/src'] + self.link_args = [] + all_src = mesonlib.File.from_absolute_file(os.path.join(d, 'gmock-all.cc')) + main_src = mesonlib.File.from_absolute_file(os.path.join(d, 'gmock_main.cc')) + if kwargs.get('main', False): + self.sources = [all_src, main_src] + else: + self.sources = [all_src] + mlog.log('Dependency GMock found:', mlog.green('YES'), '(building self)') + return + + mlog.log('Dependency GMock found:', mlog.red('NO')) + self.is_found = False + + def get_version(self): + return '1.something_maybe' + + def get_compile_args(self): + return self.compile_args + + def get_sources(self): + return self.sources + + def get_link_args(self): + return self.link_args + + def found(self): + return self.is_found + +class Qt5Dependency(Dependency): + def __init__(self, environment, kwargs): + Dependency.__init__(self) + self.name = 'qt5' + self.root = '/usr' + mods = kwargs.get('modules', []) + self.cargs = [] + self.largs = [] + self.is_found = False + if isinstance(mods, str): + mods = [mods] + if len(mods) == 0: + raise DependencyException('No Qt5 modules specified.') + type_text = 'native' + if environment.is_cross_build() and kwargs.get('native', False): + type_text = 'cross' + self.pkgconfig_detect(mods, environment, kwargs) + elif not environment.is_cross_build() and shutil.which('pkg-config') is not None: + self.pkgconfig_detect(mods, environment, kwargs) + elif shutil.which('qmake') is not None: + self.qmake_detect(mods, kwargs) + else: + self.version = 'none' + if not self.is_found: + mlog.log('Qt5 %s dependency found: ' % type_text, mlog.red('NO')) + else: + mlog.log('Qt5 %s dependency found: ' % type_text, mlog.green('YES')) + + def pkgconfig_detect(self, mods, environment, kwargs): + modules = [] + for module in mods: + modules.append(PkgConfigDependency('Qt5' + module, environment, kwargs)) + for m in modules: + self.cargs += m.get_compile_args() + self.largs += m.get_link_args() + self.is_found = True + self.version = modules[0].modversion + + def qmake_detect(self, mods, kwargs): + pc = subprocess.Popen(['qmake', '-v'], stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + (stdo, _) = pc.communicate() + if pc.returncode != 0: + return + stdo = stdo.decode() + if not 'version 5' in stdo: + mlog.log('QMake is not for Qt5.') + return + self.version = re.search('5(\.\d+)+', stdo).group(0) + (stdo, _) = subprocess.Popen(['qmake', '-query'], stdout=subprocess.PIPE).communicate() + qvars = {} + for line in stdo.decode().split('\n'): + line = line.strip() + if line == '': + continue + (k, v) = tuple(line.split(':', 1)) + qvars[k] = v + if mesonlib.is_osx(): + return self.framework_detect(qvars, mods, kwargs) + incdir = qvars['QT_INSTALL_HEADERS'] + self.cargs.append('-I' + incdir) + libdir = qvars['QT_INSTALL_LIBS'] + bindir = qvars['QT_INSTALL_BINS'] + #self.largs.append('-L' + libdir) + for module in mods: + mincdir = os.path.join(incdir, 'Qt' + module) + self.cargs.append('-I' + mincdir) + libfile = os.path.join(libdir, 'Qt5' + module + '.lib') + if not os.path.isfile(libfile): + # MinGW links directly to .dll, not to .lib. + libfile = os.path.join(bindir, 'Qt5' + module + '.dll') + self.largs.append(libfile) + self.is_found = True + + def framework_detect(self, qvars, modules, kwargs): + libdir = qvars['QT_INSTALL_LIBS'] + for m in modules: + fname = 'Qt' + m + fwdep = ExtraFrameworkDependency(fname, kwargs.get('required', True), libdir) + self.cargs.append('-F' + libdir) + if fwdep.found(): + self.is_found = True + self.cargs += fwdep.get_compile_args() + self.largs += fwdep.get_link_args() + + + def get_version(self): + return self.version + + def get_compile_args(self): + return self.cargs + + def get_sources(self): + return [] + + def get_link_args(self): + return self.largs + + def found(self): + return self.is_found + + def get_exe_args(self): + # Originally this was -fPIE but nowadays the default + # for upstream and distros seems to be -reduce-relocations + # which requires -fPIC. This may cause a performance + # penalty when using self-built Qt or on platforms + # where -fPIC is not required. If this is an issue + # for you, patches are welcome. + # Fix this to be more portable, especially to MSVC. + return ['-fPIC'] + +class Qt4Dependency(Dependency): + def __init__(self, environment, kwargs): + Dependency.__init__(self) + self.name = 'qt4' + self.root = '/usr' + self.modules = [] + mods = kwargs.get('modules', []) + if isinstance(mods, str): + mods = [mods] + for module in mods: + self.modules.append(PkgConfigDependency('Qt' + module, environment, kwargs)) + if len(self.modules) == 0: + raise DependencyException('No Qt4 modules specified.') + + def get_version(self): + return self.modules[0].get_version() + + def get_compile_args(self): + args = [] + for m in self.modules: + args += m.get_compile_args() + return args + + def get_sources(self): + return [] + + def get_link_args(self): + args = [] + for module in self.modules: + args += module.get_link_args() + return args + + def found(self): + for i in self.modules: + if not i.found(): + return False + return True + +class GnuStepDependency(Dependency): + def __init__(self, environment, kwargs): + Dependency.__init__(self) + self.modules = kwargs.get('modules', []) + self.detect() + + def detect(self): + confprog = 'gnustep-config' + try: + gp = subprocess.Popen([confprog, '--help'], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + gp.communicate() + except FileNotFoundError: + self.args = None + mlog.log('Dependency GnuStep found:', mlog.red('NO'), '(no gnustep-config)') + return + if gp.returncode != 0: + self.args = None + mlog.log('Dependency GnuStep found:', mlog.red('NO')) + return + if 'gui' in self.modules: + arg = '--gui-libs' + else: + arg = '--base-libs' + fp = subprocess.Popen([confprog, '--objc-flags'], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (flagtxt, flagerr) = fp.communicate() + flagtxt = flagtxt.decode() + flagerr = flagerr.decode() + if fp.returncode != 0: + raise DependencyException('Error getting objc-args: %s %s' % (flagtxt, flagerr)) + args = flagtxt.split() + self.args = self.filter_arsg(args) + fp = subprocess.Popen([confprog, arg], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (libtxt, liberr) = fp.communicate() + libtxt = libtxt.decode() + liberr = liberr.decode() + if fp.returncode != 0: + raise DependencyException('Error getting objc-lib args: %s %s' % (libtxt, liberr)) + self.libs = self.weird_filter(libtxt.split()) + mlog.log('Dependency GnuStep found:', mlog.green('YES')) + + def weird_filter(self, elems): + """When building packages, the output of the enclosing Make +is sometimes mixed among the subprocess output. I have no idea +why. As a hack filter out everything that is not a flag.""" + return [e for e in elems if e.startswith('-')] + + + def filter_arsg(self, args): + """gnustep-config returns a bunch of garbage args such + as -O2 and so on. Drop everything that is not needed.""" + result = [] + for f in args: + if f.startswith('-D') or f.startswith('-f') or \ + f.startswith('-I') or f == '-pthread' or\ + (f.startswith('-W') and not f == '-Wall'): + result.append(f) + return result + + def found(self): + return self.args is not None + + def get_compile_args(self): + if self.args is None: + return [] + return self.args + + def get_link_args(self): + return self.libs + +class AppleFrameworks(Dependency): + def __init__(self, environment, kwargs): + Dependency.__init__(self) + modules = kwargs.get('modules', []) + if isinstance(modules, str): + modules = [modules] + if len(modules) == 0: + raise DependencyException("AppleFrameworks dependency requires at least one module.") + self.frameworks = modules + + def get_link_args(self): + args = [] + for f in self.frameworks: + args.append('-framework') + args.append(f) + return args + + def found(self): + return mesonlib.is_osx() + +class GLDependency(Dependency): + def __init__(self, environment, kwargs): + Dependency.__init__(self) + self.is_found = False + self.cargs = [] + self.linkargs = [] + try: + pcdep = PkgConfigDependency('gl', environment, kwargs) + if pcdep.found(): + self.is_found = True + self.cargs = pcdep.get_compile_args() + self.linkargs = pcdep.get_link_args() + return + except Exception: + pass + if mesonlib.is_osx(): + self.is_found = True + self.linkargs = ['-framework', 'OpenGL'] + return + if mesonlib.is_windows(): + self.is_found = True + self.linkargs = ['-lopengl32'] + return + + def get_link_args(self): + return self.linkargs + +# There are three different ways of depending on SDL2: +# sdl2-config, pkg-config and OSX framework +class SDL2Dependency(Dependency): + def __init__(self, environment, kwargs): + Dependency.__init__(self) + self.is_found = False + self.cargs = [] + self.linkargs = [] + sdlconf = shutil.which('sdl2-config') + if sdlconf: + pc = subprocess.Popen(['sdl2-config', '--cflags'], + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL) + (stdo, _) = pc.communicate() + self.cargs = stdo.decode().strip().split() + pc = subprocess.Popen(['sdl2-config', '--libs'], + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL) + (stdo, _) = pc.communicate() + self.linkargs = stdo.decode().strip().split() + self.is_found = True + mlog.log('Dependency', mlog.bold('sdl2'), 'found:', mlog.green('YES'), '(%s)' % sdlconf) + return + try: + pcdep = PkgConfigDependency('sdl2', kwargs) + if pcdep.found(): + self.is_found = True + self.cargs = pcdep.get_compile_args() + self.linkargs = pcdep.get_link_args() + return + except Exception: + pass + if mesonlib.is_osx(): + fwdep = ExtraFrameworkDependency('sdl2', kwargs.get('required', True)) + if fwdep.found(): + self.is_found = True + self.cargs = fwdep.get_compile_args() + self.linkargs = fwdep.get_link_args() + return + mlog.log('Dependency', mlog.bold('sdl2'), 'found:', mlog.red('NO')) + + def get_compile_args(self): + return self.cargs + + def get_link_args(self): + return self.linkargs + + def found(self): + return self.is_found + +class ExtraFrameworkDependency(Dependency): + def __init__(self, name, required, path=None): + Dependency.__init__(self) + self.name = None + self.detect(name, path) + if self.found(): + mlog.log('Dependency', mlog.bold(name), 'found:', mlog.green('YES'), + os.path.join(self.path, self.name)) + else: + mlog.log('Dependency', name, 'found:', mlog.red('NO')) + + def detect(self, name, path): + lname = name.lower() + if path is None: + paths = ['/Library/Frameworks'] + else: + paths = [path] + for p in paths: + for d in os.listdir(p): + fullpath = os.path.join(p, d) + if lname != d.split('.')[0].lower(): + continue + if not stat.S_ISDIR(os.stat(fullpath).st_mode): + continue + self.path = p + self.name = d + return + + def get_compile_args(self): + if self.found(): + return ['-I' + os.path.join(self.path, self.name, 'Headers')] + return [] + + def get_link_args(self): + if self.found(): + return ['-F' + self.path, '-framework', self.name.split('.')[0]] + return [] + + def found(self): + return self.name is not None + +class ThreadDependency(Dependency): + def __init__(self, environment, kwargs): + super().__init__() + self.name = 'threads' + self.is_found = True + mlog.log('Dependency', mlog.bold(self.name), 'found:', mlog.green('YES')) + + def need_threads(self): + return True + +def get_dep_identifier(name, kwargs): + elements = [name] + modlist = kwargs.get('modules', []) + if isinstance(modlist, str): + modlist = [modlist] + for module in modlist: + elements.append(module) + return '/'.join(elements) + '/main' + str(kwargs.get('main', False)) + +def find_external_dependency(name, environment, kwargs): + required = kwargs.get('required', True) + if not isinstance(required, bool): + raise DependencyException('Keyword "required" must be a boolean.') + lname = name.lower() + if lname in packages: + dep = packages[lname](environment, kwargs) + if required and not dep.found(): + raise DependencyException('Dependency "%s" not found' % name) + return dep + pkg_exc = None + pkgdep = None + try: + pkgdep = PkgConfigDependency(name, environment, kwargs) + if pkgdep.found(): + return pkgdep + except Exception as e: + pkg_exc = e + if mesonlib.is_osx(): + fwdep = ExtraFrameworkDependency(name, required) + if required and not fwdep.found(): + raise DependencyException('Dependency "%s" not found' % name) + return fwdep + if pkg_exc is not None: + raise pkg_exc + mlog.log('Dependency', mlog.bold(name), 'found:', mlog.red('NO')) + return pkgdep + +# This has to be at the end so the classes it references +# are defined. +packages = {'boost': BoostDependency, + 'gtest': GTestDependency, + 'gmock': GMockDependency, + 'qt5': Qt5Dependency, + 'qt4': Qt4Dependency, + 'gnustep': GnuStepDependency, + 'appleframeworks': AppleFrameworks, + 'wxwidgets' : WxDependency, + 'sdl2' : SDL2Dependency, + 'gl' : GLDependency, + 'threads' : ThreadDependency, + } diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py new file mode 100644 index 0000000..8df856c --- /dev/null +++ b/mesonbuild/environment.py @@ -0,0 +1,673 @@ +# Copyright 2012-2016 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os, re, subprocess +from . import coredata, mesonlib +from .compilers import * +import configparser + +build_filename = 'meson.build' + +class EnvironmentException(coredata.MesonException): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + +def find_coverage_tools(): + gcovr_exe = 'gcovr' + lcov_exe = 'lcov' + genhtml_exe = 'genhtml' + + if not mesonlib.exe_exists([gcovr_exe, '--version']): + gcovr_exe = None + if not mesonlib.exe_exists([lcov_exe, '--version']): + lcov_exe = None + if not mesonlib.exe_exists([genhtml_exe, '--version']): + genhtml_exe = None + return (gcovr_exe, lcov_exe, genhtml_exe) + +def find_valgrind(): + valgrind_exe = 'valgrind' + if not mesonlib.exe_exists([valgrind_exe, '--version']): + valgrind_exe = None + return valgrind_exe + +def detect_ninja(): + for n in ['ninja', 'ninja-build']: + try: + p = subprocess.Popen([n, '--version'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + except FileNotFoundError: + continue + p.communicate() + if p.returncode == 0: + return n + + +class Environment(): + private_dir = 'meson-private' + log_dir = 'meson-logs' + coredata_file = os.path.join(private_dir, 'coredata.dat') + version_regex = '\d+(\.\d+)+(-[a-zA-Z0-9]+)?' + def __init__(self, source_dir, build_dir, main_script_file, options): + assert(os.path.isabs(main_script_file)) + assert(not os.path.islink(main_script_file)) + self.source_dir = source_dir + self.build_dir = build_dir + self.meson_script_file = main_script_file + self.scratch_dir = os.path.join(build_dir, Environment.private_dir) + self.log_dir = os.path.join(build_dir, Environment.log_dir) + os.makedirs(self.scratch_dir, exist_ok=True) + os.makedirs(self.log_dir, exist_ok=True) + try: + cdf = os.path.join(self.get_build_dir(), Environment.coredata_file) + self.coredata = coredata.load(cdf) + self.first_invocation = False + except FileNotFoundError: + self.coredata = coredata.CoreData(options) + self.first_invocation = True + if self.coredata.cross_file: + self.cross_info = CrossBuildInfo(self.coredata.cross_file) + else: + self.cross_info = None + self.cmd_line_options = options + + # List of potential compilers. + if mesonlib.is_windows(): + self.default_c = ['cl', 'cc', 'gcc', 'clang'] + self.default_cpp = ['cl', 'c++', 'g++', 'clang++'] + else: + self.default_c = ['cc'] + self.default_cpp = ['c++'] + self.default_objc = ['cc'] + self.default_objcpp = ['c++'] + self.default_fortran = ['gfortran', 'g95', 'f95', 'f90', 'f77'] + self.default_static_linker = 'ar' + self.vs_static_linker = 'lib' + + cross = self.is_cross_build() + if (not cross and mesonlib.is_windows()) \ + or (cross and self.cross_info.has_host() and self.cross_info.config['host_machine']['system'] == 'windows'): + self.exe_suffix = 'exe' + self.import_lib_suffix = 'lib' + self.shared_lib_suffix = 'dll' + self.shared_lib_prefix = '' + self.static_lib_suffix = 'lib' + self.static_lib_prefix = '' + self.object_suffix = 'obj' + else: + self.exe_suffix = '' + if (not cross and mesonlib.is_osx()) or \ + (cross and self.cross_info.has_host() and self.cross_info.config['host_machine']['system'] == 'darwin'): + self.shared_lib_suffix = 'dylib' + else: + self.shared_lib_suffix = 'so' + self.shared_lib_prefix = 'lib' + self.static_lib_suffix = 'a' + self.static_lib_prefix = 'lib' + self.object_suffix = 'o' + self.import_lib_suffix = self.shared_lib_suffix + + def is_cross_build(self): + return self.cross_info is not None + + def generating_finished(self): + cdf = os.path.join(self.get_build_dir(), Environment.coredata_file) + coredata.save(self.coredata, cdf) + + def get_script_dir(self): + return os.path.join(os.path.dirname(self.meson_script_file), '../scripts') + + def get_log_dir(self): + return self.log_dir + + def get_coredata(self): + return self.coredata + + def get_build_command(self): + return self.meson_script_file + + def is_header(self, fname): + return is_header(fname) + + def is_source(self, fname): + return is_source(fname) + + def is_object(self, fname): + return is_object(fname) + + def is_library(self, fname): + return is_library(fname) + + def merge_options(self, options): + for (name, value) in options.items(): + if name not in self.coredata.user_options: + self.coredata.user_options[name] = value + else: + oldval = self.coredata.user_options[name] + if type(oldval) != type(value): + self.coredata.user_options[name] = value + + def detect_c_compiler(self, want_cross): + evar = 'CC' + if self.is_cross_build() and want_cross: + compilers = [self.cross_info.config['binaries']['c']] + ccache = [] + is_cross = True + exe_wrap = self.cross_info.config['binaries'].get('exe_wrapper', None) + elif evar in os.environ: + compilers = os.environ[evar].split() + ccache = [] + is_cross = False + exe_wrap = None + else: + compilers = self.default_c + ccache = self.detect_ccache() + is_cross = False + exe_wrap = None + for compiler in compilers: + try: + basename = os.path.basename(compiler).lower() + if basename == 'cl' or basename == 'cl.exe': + arg = '/?' + else: + arg = '--version' + p = subprocess.Popen([compiler] + [arg], stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + except OSError: + continue + (out, err) = p.communicate() + out = out.decode(errors='ignore') + err = err.decode(errors='ignore') + vmatch = re.search(Environment.version_regex, out) + if vmatch: + version = vmatch.group(0) + else: + version = 'unknown version' + if 'apple' in out and 'Free Software Foundation' in out: + return GnuCCompiler(ccache + [compiler], version, GCC_OSX, is_cross, exe_wrap) + if (out.startswith('cc') or 'gcc' in out) and \ + 'Free Software Foundation' in out: + lowerout = out.lower() + if 'mingw' in lowerout or 'msys' in lowerout or 'mingw' in compiler.lower(): + gtype = GCC_MINGW + else: + gtype = GCC_STANDARD + return GnuCCompiler(ccache + [compiler], version, gtype, is_cross, exe_wrap) + if 'clang' in out: + return ClangCCompiler(ccache + [compiler], version, is_cross, exe_wrap) + if 'Microsoft' in out or 'Microsoft' in err: + # Visual Studio prints version number to stderr but + # everything else to stdout. Why? Lord only knows. + version = re.search(Environment.version_regex, err).group() + return VisualStudioCCompiler([compiler], version, is_cross, exe_wrap) + raise EnvironmentException('Unknown compiler(s): "' + ', '.join(compilers) + '"') + + def detect_fortran_compiler(self, want_cross): + evar = 'FC' + if self.is_cross_build() and want_cross: + compilers = [self.cross_info['fortran']] + is_cross = True + exe_wrap = self.cross_info.get('exe_wrapper', None) + elif evar in os.environ: + compilers = os.environ[evar].split() + is_cross = False + exe_wrap = None + else: + compilers = self.default_fortran + is_cross = False + exe_wrap = None + for compiler in compilers: + for arg in ['--version', '-V']: + try: + p = subprocess.Popen([compiler] + [arg], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + except OSError: + continue + (out, err) = p.communicate() + out = out.decode(errors='ignore') + err = err.decode(errors='ignore') + + version = 'unknown version' + vmatch = re.search(Environment.version_regex, out) + if vmatch: + version = vmatch.group(0) + + if 'GNU Fortran' in out: + return GnuFortranCompiler([compiler], version, GCC_STANDARD, is_cross, exe_wrap) + + if 'G95' in out: + return G95FortranCompiler([compiler], version, is_cross, exe_wrap) + + if 'Sun Fortran' in err: + version = 'unknown version' + vmatch = re.search(Environment.version_regex, err) + if vmatch: + version = vmatch.group(0) + return SunFortranCompiler([compiler], version, is_cross, exe_wrap) + + if 'ifort (IFORT)' in out: + return IntelFortranCompiler([compiler], version, is_cross, exe_wrap) + + if 'PathScale EKOPath(tm)' in err: + return PathScaleFortranCompiler([compiler], version, is_cross, exe_wrap) + + if 'pgf90' in out: + return PGIFortranCompiler([compiler], version, is_cross, exe_wrap) + + if 'Open64 Compiler Suite' in err: + return Open64FortranCompiler([compiler], version, is_cross, exe_wrap) + + if 'NAG Fortran' in err: + return NAGFortranCompiler([compiler], version, is_cross, exe_wrap) + + raise EnvironmentException('Unknown compiler(s): "' + ', '.join(compilers) + '"') + + def get_scratch_dir(self): + return self.scratch_dir + + def get_depfixer(self): + path = os.path.split(__file__)[0] + return os.path.join(path, 'depfixer.py') + + def detect_cpp_compiler(self, want_cross): + evar = 'CXX' + if self.is_cross_build() and want_cross: + compilers = [self.cross_info.config['binaries']['cpp']] + ccache = [] + is_cross = True + exe_wrap = self.cross_info.config['binaries'].get('exe_wrapper', None) + elif evar in os.environ: + compilers = os.environ[evar].split() + ccache = [] + is_cross = False + exe_wrap = None + else: + compilers = self.default_cpp + ccache = self.detect_ccache() + is_cross = False + exe_wrap = None + for compiler in compilers: + basename = os.path.basename(compiler).lower() + if basename == 'cl' or basename == 'cl.exe': + arg = '/?' + else: + arg = '--version' + try: + p = subprocess.Popen([compiler, arg], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + except OSError: + continue + (out, err) = p.communicate() + out = out.decode(errors='ignore') + err = err.decode(errors='ignore') + vmatch = re.search(Environment.version_regex, out) + if vmatch: + version = vmatch.group(0) + else: + version = 'unknown version' + if 'apple' in out and 'Free Software Foundation' in out: + return GnuCPPCompiler(ccache + [compiler], version, GCC_OSX, is_cross, exe_wrap) + if (out.startswith('c++ ') or 'g++' in out or 'GCC' in out) and \ + 'Free Software Foundation' in out: + lowerout = out.lower() + if 'mingw' in lowerout or 'msys' in lowerout or 'mingw' in compiler.lower(): + gtype = GCC_MINGW + else: + gtype = GCC_STANDARD + return GnuCPPCompiler(ccache + [compiler], version, gtype, is_cross, exe_wrap) + if 'clang' in out: + return ClangCPPCompiler(ccache + [compiler], version, is_cross, exe_wrap) + if 'Microsoft' in out or 'Microsoft' in err: + version = re.search(Environment.version_regex, err).group() + return VisualStudioCPPCompiler([compiler], version, is_cross, exe_wrap) + raise EnvironmentException('Unknown compiler(s) "' + ', '.join(compilers) + '"') + + def detect_objc_compiler(self, want_cross): + if self.is_cross_build() and want_cross: + exelist = [self.cross_info['objc']] + is_cross = True + exe_wrap = self.cross_info.get('exe_wrapper', None) + else: + exelist = self.get_objc_compiler_exelist() + is_cross = False + exe_wrap = None + try: + p = subprocess.Popen(exelist + ['--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except OSError: + raise EnvironmentException('Could not execute ObjC compiler "%s"' % ' '.join(exelist)) + (out, err) = p.communicate() + out = out.decode(errors='ignore') + err = err.decode(errors='ignore') + vmatch = re.search(Environment.version_regex, out) + if vmatch: + version = vmatch.group(0) + else: + version = 'unknown version' + if (out.startswith('cc ') or 'gcc' in out) and \ + 'Free Software Foundation' in out: + return GnuObjCCompiler(exelist, version, is_cross, exe_wrap) + if out.startswith('Apple LLVM'): + return ClangObjCCompiler(exelist, version, is_cross, exe_wrap) + if 'apple' in out and 'Free Software Foundation' in out: + return GnuObjCCompiler(exelist, version, is_cross, exe_wrap) + raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"') + + def detect_objcpp_compiler(self, want_cross): + if self.is_cross_build() and want_cross: + exelist = [self.cross_info['objcpp']] + is_cross = True + exe_wrap = self.cross_info.get('exe_wrapper', None) + else: + exelist = self.get_objcpp_compiler_exelist() + is_cross = False + exe_wrap = None + try: + p = subprocess.Popen(exelist + ['--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except OSError: + raise EnvironmentException('Could not execute ObjC++ compiler "%s"' % ' '.join(exelist)) + (out, err) = p.communicate() + out = out.decode(errors='ignore') + err = err.decode(errors='ignore') + vmatch = re.search(Environment.version_regex, out) + if vmatch: + version = vmatch.group(0) + else: + version = 'unknown version' + if (out.startswith('c++ ') or out.startswith('g++')) and \ + 'Free Software Foundation' in out: + return GnuObjCPPCompiler(exelist, version, is_cross, exe_wrap) + if out.startswith('Apple LLVM'): + return ClangObjCPPCompiler(exelist, version, is_cross, exe_wrap) + if 'apple' in out and 'Free Software Foundation' in out: + return GnuObjCPPCompiler(exelist, version, is_cross, exe_wrap) + raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"') + + def detect_java_compiler(self): + exelist = ['javac'] + try: + p = subprocess.Popen(exelist + ['-version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except OSError: + raise EnvironmentException('Could not execute Java compiler "%s"' % ' '.join(exelist)) + (out, err) = p.communicate() + out = out.decode(errors='ignore') + err = err.decode(errors='ignore') + vmatch = re.search(Environment.version_regex, err) + if vmatch: + version = vmatch.group(0) + else: + version = 'unknown version' + if 'javac' in err: + return JavaCompiler(exelist, version) + raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"') + + def detect_cs_compiler(self): + exelist = ['mcs'] + try: + p = subprocess.Popen(exelist + ['--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except OSError: + raise EnvironmentException('Could not execute C# compiler "%s"' % ' '.join(exelist)) + (out, err) = p.communicate() + out = out.decode(errors='ignore') + err = err.decode(errors='ignore') + vmatch = re.search(Environment.version_regex, out) + if vmatch: + version = vmatch.group(0) + else: + version = 'unknown version' + if 'Mono' in out: + return MonoCompiler(exelist, version) + raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"') + + def detect_vala_compiler(self): + exelist = ['valac'] + try: + p = subprocess.Popen(exelist + ['--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except OSError: + raise EnvironmentException('Could not execute Vala compiler "%s"' % ' '.join(exelist)) + (out, _) = p.communicate() + out = out.decode(errors='ignore') + vmatch = re.search(Environment.version_regex, out) + if vmatch: + version = vmatch.group(0) + else: + version = 'unknown version' + if 'Vala' in out: + return ValaCompiler(exelist, version) + raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"') + + def detect_rust_compiler(self): + exelist = ['rustc'] + try: + p = subprocess.Popen(exelist + ['--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except OSError: + raise EnvironmentException('Could not execute Rust compiler "%s"' % ' '.join(exelist)) + (out, _) = p.communicate() + out = out.decode(errors='ignore') + vmatch = re.search(Environment.version_regex, out) + if vmatch: + version = vmatch.group(0) + else: + version = 'unknown version' + if 'rustc' in out: + return RustCompiler(exelist, version) + raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"') + + def detect_swift_compiler(self): + exelist = ['swiftc'] + try: + p = subprocess.Popen(exelist + ['-v'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except OSError: + raise EnvironmentException('Could not execute Swift compiler "%s"' % ' '.join(exelist)) + (_, err) = p.communicate() + err = err.decode(errors='ignore') + vmatch = re.search(Environment.version_regex, err) + if vmatch: + version = vmatch.group(0) + else: + version = 'unknown version' + if 'Swift' in err: + return SwiftCompiler(exelist, version) + raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"') + + def detect_static_linker(self, compiler): + if compiler.is_cross: + linker = self.cross_info.config['binaries']['ar'] + else: + evar = 'AR' + if evar in os.environ: + linker = os.environ[evar].strip() + if isinstance(compiler, VisualStudioCCompiler): + linker= self.vs_static_linker + else: + linker = self.default_static_linker + basename = os.path.basename(linker).lower() + if basename == 'lib' or basename == 'lib.exe': + arg = '/?' + else: + arg = '--version' + try: + p = subprocess.Popen([linker, arg], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except OSError: + raise EnvironmentException('Could not execute static linker "%s".' % linker) + (out, err) = p.communicate() + out = out.decode(errors='ignore') + err = err.decode(errors='ignore') + if '/OUT:' in out or '/OUT:' in err: + return VisualStudioLinker([linker]) + if p.returncode == 0: + return ArLinker([linker]) + if p.returncode == 1 and err.startswith('usage'): # OSX + return ArLinker([linker]) + raise EnvironmentException('Unknown static linker "%s"' % linker) + + def detect_ccache(self): + try: + has_ccache = subprocess.call(['ccache', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except OSError: + has_ccache = 1 + if has_ccache == 0: + cmdlist = ['ccache'] + else: + cmdlist = [] + return cmdlist + + def get_objc_compiler_exelist(self): + ccachelist = self.detect_ccache() + evar = 'OBJCC' + if evar in os.environ: + return os.environ[evar].split() + return ccachelist + self.default_objc + + def get_objcpp_compiler_exelist(self): + ccachelist = self.detect_ccache() + evar = 'OBJCXX' + if evar in os.environ: + return os.environ[evar].split() + return ccachelist + self.default_objcpp + + def get_source_dir(self): + return self.source_dir + + def get_build_dir(self): + return self.build_dir + + def get_exe_suffix(self): + return self.exe_suffix + + # On Windows the library has suffix dll + # but you link against a file that has suffix lib. + def get_import_lib_suffix(self): + return self.import_lib_suffix + + def get_shared_lib_prefix(self): + return self.shared_lib_prefix + + def get_shared_lib_suffix(self): + return self.shared_lib_suffix + + def get_static_lib_prefix(self): + return self.static_lib_prefix + + def get_static_lib_suffix(self): + return self.static_lib_suffix + + def get_object_suffix(self): + return self.object_suffix + + def get_prefix(self): + return self.coredata.get_builtin_option('prefix') + + def get_libdir(self): + return self.coredata.get_builtin_option('libdir') + + def get_bindir(self): + return self.coredata.get_builtin_option('bindir') + + def get_includedir(self): + return self.coredata.get_builtin_option('includedir') + + def get_mandir(self): + return self.coredata.get_builtin_option('mandir') + + def get_datadir(self): + return self.coredata.get_builtin_option('datadir') + + def find_library(self, libname, dirs): + if dirs is None: + dirs = mesonlib.get_library_dirs() + suffixes = [self.get_shared_lib_suffix(), self.get_static_lib_suffix()] + prefix = self.get_shared_lib_prefix() + for d in dirs: + for suffix in suffixes: + trial = os.path.join(d, prefix + libname + '.' + suffix) + if os.path.isfile(trial): + return trial + + +def get_args_from_envvars(lang): + if lang == 'c': + compile_args = os.environ.get('CFLAGS', '').split() + link_args = compile_args + os.environ.get('LDFLAGS', '').split() + compile_args += os.environ.get('CPPFLAGS', '').split() + elif lang == 'cpp': + compile_args = os.environ.get('CXXFLAGS', '').split() + link_args = compile_args + os.environ.get('LDFLAGS', '').split() + compile_args += os.environ.get('CPPFLAGS', '').split() + elif lang == 'objc': + compile_args = os.environ.get('OBJCFLAGS', '').split() + link_args = compile_args + os.environ.get('LDFLAGS', '').split() + compile_args += os.environ.get('CPPFLAGS', '').split() + elif lang == 'objcpp': + compile_args = os.environ.get('OBJCXXFLAGS', '').split() + link_args = compile_args + os.environ.get('LDFLAGS', '').split() + compile_args += os.environ.get('CPPFLAGS', '').split() + elif lang == 'fortran': + compile_args = os.environ.get('FFLAGS', '').split() + link_args = compile_args + os.environ.get('LDFLAGS', '').split() + else: + compile_args = [] + link_args = [] + return (compile_args, link_args) + +class CrossBuildInfo(): + def __init__(self, filename): + self.config = {} + self.parse_datafile(filename) + if 'target_machine' in self.config: + return + if not 'host_machine' in self.config: + raise coredata.MesonException('Cross info file must have either host or a target machine.') + if not 'properties' in self.config: + raise coredata.MesonException('Cross file is missing "properties".') + if not 'binaries' in self.config: + raise coredata.MesonException('Cross file is missing "binaries".') + + def ok_type(self, i): + return isinstance(i, str) or isinstance(i, int) or isinstance(i, bool) + + def parse_datafile(self, filename): + config = configparser.ConfigParser() + config.read(filename) + # This is a bit hackish at the moment. + for s in config.sections(): + self.config[s] = {} + for entry in config[s]: + value = config[s][entry] + if ' ' in entry or '\t' in entry or "'" in entry or '"' in entry: + raise EnvironmentException('Malformed variable name %s in cross file..' % varname) + try: + res = eval(value, {'true' : True, 'false' : False}) + except Exception: + raise EnvironmentException('Malformed value in cross file variable %s.' % varname) + if self.ok_type(res): + self.config[s][entry] = res + elif isinstance(res, list): + for i in res: + if not self.ok_type(i): + raise EnvironmentException('Malformed value in cross file variable %s.' % varname) + self.config[s][entry] = res + else: + raise EnvironmentException('Malformed value in cross file variable %s.' % varname) + + def has_host(self): + return 'host_machine' in self.config + + def has_target(self): + return 'target_machine' in self.config + + # Wehn compiling a cross compiler we use the native compiler for everything. + # But not when cross compiling a cross compiler. + def need_cross_compiler(self): + return 'host_machine' in self.config diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py new file mode 100644 index 0000000..4894ac7 --- /dev/null +++ b/mesonbuild/interpreter.py @@ -0,0 +1,2259 @@ +# Copyright 2012-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. + +from . import mparser +from . import environment +from . import coredata +from . import dependencies +from . import mlog +from . import build +from . import optinterpreter +from .wrap import wrap +from . import mesonlib + +import os, sys, platform, subprocess, shutil, uuid, re +from functools import wraps + +import importlib + +class InterpreterException(coredata.MesonException): + pass + +class InvalidCode(InterpreterException): + pass + +class InvalidArguments(InterpreterException): + pass + +# Decorators for method calls. + +def check_stringlist(a, msg='Arguments must be strings.'): + if not isinstance(a, list): + mlog.debug('Not a list:', str(a)) + raise InvalidArguments('Argument not a list.') + if not all(isinstance(s, str) for s in a): + mlog.debug('Element not a string:', str(a)) + raise InvalidArguments(msg) + +def noPosargs(f): + @wraps(f) + def wrapped(self, node, args, kwargs): + if len(args) != 0: + raise InvalidArguments('Function does not take positional arguments.') + return f(self, node, args, kwargs) + return wrapped + +def noKwargs(f): + @wraps(f) + def wrapped(self, node, args, kwargs): + if len(kwargs) != 0: + raise InvalidArguments('Function does not take keyword arguments.') + return f(self, node, args, kwargs) + return wrapped + +def stringArgs(f): + @wraps(f) + def wrapped(self, node, args, kwargs): + assert(isinstance(args, list)) + check_stringlist(args) + return f(self, node, args, kwargs) + return wrapped + +def stringifyUserArguments(args): + if isinstance(args, list): + return '[%s]' % ', '.join([stringifyUserArguments(x) for x in args]) + elif isinstance(args, int): + return str(args) + elif isinstance(args, str): + return "'%s'" % args + raise InvalidArguments('Function accepts only strings, integers, lists and lists thereof.') + +class InterpreterObject(): + def __init__(self): + self.methods = {} + + def method_call(self, method_name, args, kwargs): + if method_name in self.methods: + return self.methods[method_name](args, kwargs) + raise InvalidCode('Unknown method "%s" in object.' % method_name) + +class TryRunResultHolder(InterpreterObject): + def __init__(self, res): + super().__init__() + self.res = res + self.methods.update({'returncode' : self.returncode_method, + 'compiled' : self.compiled_method, + 'stdout' : self.stdout_method, + 'stderr' : self.stderr_method, + }) + + def returncode_method(self, args, kwargs): + return self.res.returncode + + def compiled_method(self, args, kwargs): + return self.res.compiled + + def stdout_method(self, args, kwargs): + return self.res.stdout + + def stderr_method(self, args, kwargs): + return self.res.stderr + +class RunProcess(InterpreterObject): + + def __init__(self, command_array, source_dir, build_dir, subdir, in_builddir=False): + super().__init__() + pc = self.run_command(command_array, source_dir, build_dir, subdir, in_builddir) + (stdout, stderr) = pc.communicate() + self.returncode = pc.returncode + self.stdout = stdout.decode().replace('\r\n', '\n') + self.stderr = stderr.decode().replace('\r\n', '\n') + self.methods.update({'returncode' : self.returncode_method, + 'stdout' : self.stdout_method, + 'stderr' : self.stderr_method, + }) + + def run_command(self, command_array, source_dir, build_dir, subdir, in_builddir): + cmd_name = command_array[0] + env = {'MESON_SOURCE_ROOT' : source_dir, + 'MESON_BUILD_ROOT' : build_dir, + 'MESON_SUBDIR' : subdir} + if in_builddir: + cwd = os.path.join(build_dir, subdir) + else: + cwd = os.path.join(source_dir, subdir) + child_env = os.environ.copy() + child_env.update(env) + try: + return subprocess.Popen(command_array, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + env=child_env, cwd=cwd) + except FileNotFoundError: + pass + # Was not a command, is a program in path? + exe = shutil.which(cmd_name) + if exe is not None: + command_array = [exe] + command_array[1:] + return subprocess.Popen(command_array, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + env=child_env, cwd=cwd) + # No? Maybe it is a script in the source tree. + fullpath = os.path.join(source_dir, subdir, cmd_name) + command_array = [fullpath] + command_array[1:] + try: + return subprocess.Popen(command_array, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + env=child_env, cwd=cwd) + except FileNotFoundError: + raise InterpreterException('Could not execute command "%s".' % cmd_name) + + def returncode_method(self, args, kwargs): + return self.returncode + + def stdout_method(self, args, kwargs): + return self.stdout + + def stderr_method(self, args, kwargs): + return self.stderr + +class ConfigureFileHolder(InterpreterObject): + + def __init__(self, subdir, sourcename, targetname, configuration_data): + InterpreterObject.__init__(self) + self.held_object = build.ConfigureFile(subdir, sourcename, targetname, configuration_data) + +class ConfigurationDataHolder(InterpreterObject): + def __init__(self): + super().__init__() + self.used = False # These objects become immutable after use in configure_file. + self.held_object = build.ConfigurationData() + self.methods.update({'set': self.set_method, + 'set10': self.set10_method, + 'has' : self.has_method, + }) + + def is_used(self): + return self.used + + def mark_used(self): + self.used = True + + def validate_args(self, args): + if len(args) != 2: + raise InterpreterException("Configuration set requires 2 arguments.") + if self.used: + raise InterpreterException("Can not set values on configuration object that has been used.") + name = args[0] + val = args[1] + if not isinstance(name, str): + raise InterpreterException("First argument to set must be a string.") + return (name, val) + + def set_method(self, args, kwargs): + (name, val) = self.validate_args(args) + self.held_object.values[name] = val + + def set10_method(self, args, kwargs): + (name, val) = self.validate_args(args) + if val: + self.held_object.values[name] = 1 + else: + self.held_object.values[name] = 0 + + def has_method(self, args, kwargs): + return args[0] in self.held_object.values + + def get(self, name): + return self.held_object.values[name] + + def keys(self): + return self.held_object.values.keys() + +# Interpreter objects can not be pickled so we must have +# these wrappers. + +class DependencyHolder(InterpreterObject): + def __init__(self, dep): + InterpreterObject.__init__(self) + self.held_object = dep + self.methods.update({'found' : self.found_method}) + + def found_method(self, args, kwargs): + return self.held_object.found() + +class InternalDependencyHolder(InterpreterObject): + def __init__(self, dep): + InterpreterObject.__init__(self) + self.held_object = dep + self.methods.update({'found' : self.found_method}) + + def found_method(self, args, kwargs): + return True + +class ExternalProgramHolder(InterpreterObject): + def __init__(self, ep): + InterpreterObject.__init__(self) + self.held_object = ep + self.methods.update({'found': self.found_method}) + + def found_method(self, args, kwargs): + return self.found() + + def found(self): + return self.held_object.found() + + def get_command(self): + return self.held_object.fullpath + + def get_name(self): + return self.held_object.name + +class ExternalLibraryHolder(InterpreterObject): + def __init__(self, el): + InterpreterObject.__init__(self) + self.held_object = el + self.methods.update({'found': self.found_method}) + + def found(self): + return self.held_object.found() + + def found_method(self, args, kwargs): + return self.found() + + def get_filename(self): + return self.held_object.fullpath + + def get_name(self): + return self.held_object.name + + def get_compile_args(self): + return self.held_object.get_compile_args() + + def get_link_args(self): + return self.held_object.get_link_args() + + def get_exe_args(self): + return self.held_object.get_exe_args() + +class GeneratorHolder(InterpreterObject): + def __init__(self, interpreter, args, kwargs): + super().__init__() + self.interpreter = interpreter + self.held_object = build.Generator(args, kwargs) + self.methods.update({'process' : self.process_method}) + + def process_method(self, args, kwargs): + check_stringlist(args) + extras = mesonlib.stringlistify(kwargs.get('extra_args', [])) + gl = GeneratedListHolder(self, extras) + [gl.add_file(os.path.join(self.interpreter.subdir, a)) for a in args] + return gl + +class GeneratedListHolder(InterpreterObject): + def __init__(self, arg1, extra_args=[]): + super().__init__() + if isinstance(arg1, GeneratorHolder): + self.held_object = build.GeneratedList(arg1.held_object, extra_args) + else: + self.held_object = arg1 + + def add_file(self, a): + self.held_object.add_file(a) + +class BuildMachine(InterpreterObject): + def __init__(self): + InterpreterObject.__init__(self) + self.methods.update({'system' : self.system_method, + 'cpu_family' : self.cpu_family_method, + 'cpu' : self.cpu_method, + 'endian' : self.endian_method, + }) + + # Python is inconsistent in its platform module. + # It returns different values for the same cpu. + # For x86 it might return 'x86', 'i686' or somesuch. + # Do some canonicalization. + def cpu_family_method(self, args, kwargs): + trial = platform.machine().lower() + if trial.startswith('i') and trial.endswith('86'): + return 'x86' + if trial.startswith('arm'): + return 'arm' + # Add fixes here as bugs are reported. + return trial + + def cpu_method(self, args, kwargs): + return platform.machine().lower() + + def system_method(self, args, kwargs): + return platform.system().lower() + + def endian_method(self, args, kwargs): + return sys.byteorder + +# This class will provide both host_machine and +# target_machine +class CrossMachineInfo(InterpreterObject): + def __init__(self, cross_info): + InterpreterObject.__init__(self) + minimum_cross_info = {'cpu', 'cpu_family', 'endian', 'system'} + if set(cross_info) < minimum_cross_info: + raise InterpreterException( + 'Machine info is currently {}\n'.format(cross_info) + + 'but is missing {}.'.format(minimum_cross_info - set(cross_info))) + self.info = cross_info + self.methods.update({'system' : self.system_method, + 'cpu' : self.cpu_method, + 'cpu_family' : self.cpu_family_method, + 'endian' : self.endian_method, + }) + + def system_method(self, args, kwargs): + return self.info['system'] + + def cpu_method(self, args, kwargs): + return self.info['cpu'] + + def cpu_family_method(self, args, kwargs): + return self.info['cpu_family'] + + def endian_method(self, args, kwargs): + return self.info['endian'] + +class IncludeDirsHolder(InterpreterObject): + def __init__(self, idobj): + super().__init__() + self.held_object = idobj + +class Headers(InterpreterObject): + + def __init__(self, src_subdir, sources, kwargs): + InterpreterObject.__init__(self) + self.sources = sources + self.source_subdir = src_subdir + self.install_subdir = kwargs.get('subdir', '') + self.custom_install_dir = kwargs.get('install_dir', None) + if self.custom_install_dir is not None: + if not isinstance(self.custom_install_dir, str): + raise InterpreterException('Custom_install_dir must be a string.') + + def set_install_subdir(self, subdir): + self.install_subdir = subdir + + def get_install_subdir(self): + return self.install_subdir + + def get_source_subdir(self): + return self.source_subdir + + def get_sources(self): + return self.sources + + def get_custom_install_dir(self): + return self.custom_install_dir + +class DataHolder(InterpreterObject): + def __init__(self, in_sourcetree, source_subdir, sources, kwargs): + super().__init__() + kwsource = mesonlib.stringlistify(kwargs.get('sources', [])) + sources += kwsource + check_stringlist(sources) + install_dir = kwargs.get('install_dir', None) + if not isinstance(install_dir, str): + raise InterpreterException('Custom_install_dir must be a string.') + self.held_object = build.Data(in_sourcetree, source_subdir, sources, install_dir) + + def get_source_subdir(self): + return self.held_object.source_subdir + + def get_sources(self): + return self.held_object.sources + + def get_install_dir(self): + return self.held_object.install_dir + +class InstallDir(InterpreterObject): + def __init__(self, source_subdir, installable_subdir, install_dir): + InterpreterObject.__init__(self) + self.source_subdir = source_subdir + self.installable_subdir = installable_subdir + self.install_dir = install_dir + +class Man(InterpreterObject): + + def __init__(self, source_subdir, sources, kwargs): + InterpreterObject.__init__(self) + self.source_subdir = source_subdir + self.sources = sources + self.validate_sources() + if len(kwargs) > 1: + raise InvalidArguments('Man function takes at most one keyword arguments.') + self.custom_install_dir = kwargs.get('install_dir', None) + if self.custom_install_dir is not None and not isinstance(self.custom_install_dir, str): + raise InterpreterException('Custom_install_dir must be a string.') + + def validate_sources(self): + for s in self.sources: + num = int(s.split('.')[-1]) + if num < 1 or num > 8: + raise InvalidArguments('Man file must have a file extension of a number between 1 and 8') + + def get_custom_install_dir(self): + return self.custom_install_dir + + def get_sources(self): + return self.sources + + def get_source_subdir(self): + return self.source_subdir + +class GeneratedObjectsHolder(InterpreterObject): + def __init__(self, held_object): + super().__init__() + self.held_object = held_object + +class BuildTargetHolder(InterpreterObject): + def __init__(self, target, interp): + super().__init__() + self.held_object = target + self.interpreter = interp + self.methods.update({'extract_objects' : self.extract_objects_method, + 'extract_all_objects' : self.extract_all_objects_method, + 'get_id': self.get_id_method, + 'outdir' : self.outdir_method, + 'private_dir_include' : self.private_dir_include_method, + }) + + def is_cross(self): + return self.held_object.is_cross() + + def private_dir_include_method(self, args, kwargs): + return IncludeDirsHolder(build.IncludeDirs('', [], False, + [self.interpreter.backend.get_target_private_dir(self.held_object)])) + + def outdir_method(self, args, kwargs): + return self.interpreter.backend.get_target_dir(self.held_object) + + def extract_objects_method(self, args, kwargs): + gobjs = self.held_object.extract_objects(args) + return GeneratedObjectsHolder(gobjs) + + def extract_all_objects_method(self, args, kwargs): + gobjs = self.held_object.extract_all_objects() + return GeneratedObjectsHolder(gobjs) + + def get_id_method(self, args, kwargs): + return self.held_object.get_id() + +class ExecutableHolder(BuildTargetHolder): + def __init__(self, target, interp): + super().__init__(target, interp) + +class StaticLibraryHolder(BuildTargetHolder): + def __init__(self, target, interp): + super().__init__(target, interp) + +class SharedLibraryHolder(BuildTargetHolder): + def __init__(self, target, interp): + super().__init__(target, interp) + +class JarHolder(BuildTargetHolder): + def __init__(self, target, interp): + super().__init__(target, interp) + +class CustomTargetHolder(InterpreterObject): + def __init__(self, object_to_hold): + self.held_object = object_to_hold + + def is_cross(self): + return self.held_object.is_cross() + + def extract_objects_method(self, args, kwargs): + gobjs = self.held_object.extract_objects(args) + return GeneratedObjectsHolder(gobjs) + +class RunTargetHolder(InterpreterObject): + def __init__(self, name, command, args, subdir): + self.held_object = build.RunTarget(name, command, args, subdir) + +class Test(InterpreterObject): + def __init__(self, name, suite, exe, is_parallel, cmd_args, env, should_fail, valgrind_args, timeout, workdir): + InterpreterObject.__init__(self) + self.name = name + self.suite = suite + self.exe = exe + self.is_parallel = is_parallel + self.cmd_args = cmd_args + self.env = env + self.should_fail = should_fail + self.valgrind_args = valgrind_args + self.timeout = timeout + self.workdir = workdir + + def get_exe(self): + return self.exe + + def get_name(self): + return self.name + +class SubprojectHolder(InterpreterObject): + + def __init__(self, subinterpreter): + super().__init__() + self.subinterpreter = subinterpreter + self.methods.update({'get_variable' : self.get_variable_method, + }) + + def get_variable_method(self, args, kwargs): + if len(args) != 1: + raise InterpreterException('Get_variable takes one argument.') + varname = args[0] + if not isinstance(varname, str): + raise InterpreterException('Get_variable takes a string argument.') + return self.subinterpreter.variables[varname] + +class CompilerHolder(InterpreterObject): + def __init__(self, compiler, env): + InterpreterObject.__init__(self) + self.compiler = compiler + self.environment = env + self.methods.update({'compiles': self.compiles_method, + 'links': self.links_method, + 'get_id': self.get_id_method, + 'sizeof': self.sizeof_method, + 'has_header': self.has_header_method, + 'run' : self.run_method, + 'has_function' : self.has_function_method, + 'has_member' : self.has_member_method, + 'has_type' : self.has_type_method, + 'alignment' : self.alignment_method, + 'version' : self.version_method, + 'cmd_array' : self.cmd_array_method, + }) + + def version_method(self, args, kwargs): + return self.compiler.version + + def cmd_array_method(self, args, kwargs): + return self.compiler.exelist + + def determine_args(self, kwargs): + nobuiltins = kwargs.get('no_builtin_args', False) + if not isinstance(nobuiltins, bool): + raise InterpreterException('Type of no_builtin_args not a boolean.') + args = [] + if not nobuiltins: + opts = self.environment.coredata.compiler_options + args += self.compiler.get_option_compile_args(opts) + args += self.compiler.get_option_link_args(opts) + args += mesonlib.stringlistify(kwargs.get('args', [])) + return args + + def alignment_method(self, args, kwargs): + if len(args) != 1: + raise InterpreterException('Alignment method takes exactly one positional argument.') + check_stringlist(args) + typename = args[0] + extra_args = mesonlib.stringlistify(kwargs.get('args', [])) + result = self.compiler.alignment(typename, self.environment, extra_args) + mlog.log('Checking for alignment of "', mlog.bold(typename), '": ', result, sep='') + return result + + def run_method(self, args, kwargs): + if len(args) != 1: + raise InterpreterException('Run method takes exactly one positional argument.') + check_stringlist(args) + code = args[0] + testname = kwargs.get('name', '') + if not isinstance(testname, str): + raise InterpreterException('Testname argument must be a string.') + extra_args = self.determine_args(kwargs) + result = self.compiler.run(code, extra_args) + if len(testname) > 0: + if not result.compiled: + h = mlog.red('DID NOT COMPILE') + elif result.returncode == 0: + h = mlog.green('YES') + else: + h = mlog.red('NO (%d)' % result.returncode) + mlog.log('Checking if "', mlog.bold(testname), '" runs : ', h, sep='') + return TryRunResultHolder(result) + + def get_id_method(self, args, kwargs): + return self.compiler.get_id() + + def has_member_method(self, args, kwargs): + if len(args) != 2: + raise InterpreterException('Has_member takes exactly two arguments.') + check_stringlist(args) + typename = args[0] + membername = args[1] + prefix = kwargs.get('prefix', '') + if not isinstance(prefix, str): + raise InterpreterException('Prefix argument of has_function must be a string.') + extra_args = self.determine_args(kwargs) + had = self.compiler.has_member(typename, membername, prefix, extra_args) + if had: + hadtxt = mlog.green('YES') + else: + hadtxt = mlog.red('NO') + mlog.log('Checking whether type "', mlog.bold(typename), + '" has member "', mlog.bold(membername), '": ', hadtxt, sep='') + return had + + def has_function_method(self, args, kwargs): + if len(args) != 1: + raise InterpreterException('Has_function takes exactly one argument.') + check_stringlist(args) + funcname = args[0] + prefix = kwargs.get('prefix', '') + if not isinstance(prefix, str): + raise InterpreterException('Prefix argument of has_function must be a string.') + extra_args = self.determine_args(kwargs) + had = self.compiler.has_function(funcname, prefix, self.environment, extra_args) + if had: + hadtxt = mlog.green('YES') + else: + hadtxt = mlog.red('NO') + mlog.log('Checking for function "', mlog.bold(funcname), '": ', hadtxt, sep='') + return had + + def has_type_method(self, args, kwargs): + if len(args) != 1: + raise InterpreterException('Has_type takes exactly one argument.') + check_stringlist(args) + typename = args[0] + prefix = kwargs.get('prefix', '') + if not isinstance(prefix, str): + raise InterpreterException('Prefix argument of has_type must be a string.') + extra_args = self.determine_args(kwargs) + had = self.compiler.has_type(typename, prefix, extra_args) + if had: + hadtxt = mlog.green('YES') + else: + hadtxt = mlog.red('NO') + mlog.log('Checking for type "', mlog.bold(typename), '": ', hadtxt, sep='') + return had + + def sizeof_method(self, args, kwargs): + if len(args) != 1: + raise InterpreterException('Sizeof takes exactly one argument.') + check_stringlist(args) + element = args[0] + prefix = kwargs.get('prefix', '') + if not isinstance(prefix, str): + raise InterpreterException('Prefix argument of sizeof must be a string.') + extra_args = self.determine_args(kwargs) + esize = self.compiler.sizeof(element, prefix, self.environment, extra_args) + mlog.log('Checking for size of "%s": %d' % (element, esize)) + return esize + + def compiles_method(self, args, kwargs): + if len(args) != 1: + raise InterpreterException('compiles method takes exactly one argument.') + check_stringlist(args) + code = args[0] + testname = kwargs.get('name', '') + if not isinstance(testname, str): + raise InterpreterException('Testname argument must be a string.') + extra_args = self.determine_args(kwargs) + result = self.compiler.compiles(code, extra_args) + if len(testname) > 0: + if result: + h = mlog.green('YES') + else: + h = mlog.red('NO') + mlog.log('Checking if "', mlog.bold(testname), '" compiles : ', h, sep='') + return result + + def links_method(self, args, kwargs): + if len(args) != 1: + raise InterpreterException('links method takes exactly one argument.') + check_stringlist(args) + code = args[0] + testname = kwargs.get('name', '') + if not isinstance(testname, str): + raise InterpreterException('Testname argument must be a string.') + extra_args = self.determine_args(kwargs) + result = self.compiler.links(code, extra_args) + if len(testname) > 0: + if result: + h = mlog.green('YES') + else: + h = mlog.red('NO') + mlog.log('Checking if "', mlog.bold(testname), '" links : ', h, sep='') + return result + + def has_header_method(self, args, kwargs): + if len(args) != 1: + raise InterpreterException('has_header method takes exactly one argument.') + check_stringlist(args) + string = args[0] + extra_args = self.determine_args(kwargs) + haz = self.compiler.has_header(string, extra_args) + if haz: + h = mlog.green('YES') + else: + h = mlog.red('NO') + mlog.log('Has header "%s":' % string, h) + return haz + +class ModuleState: + pass + +class ModuleHolder(InterpreterObject): + def __init__(self, modname, module, interpreter): + InterpreterObject.__init__(self) + self.modname = modname + self.held_object = module + self.interpreter = interpreter + + def method_call(self, method_name, args, kwargs): + try: + fn = getattr(self.held_object, method_name) + except AttributeError: + raise InvalidArguments('Module %s does not have method %s.' % (self.modname, method_name)) + state = ModuleState() + state.build_to_src = os.path.relpath(self.interpreter.environment.get_source_dir(), + self.interpreter.environment.get_build_dir()) + state.subdir = self.interpreter.subdir + state.environment = self.interpreter.environment + state.project_name = self.interpreter.build.project_name + state.project_version = self.interpreter.build.dep_manifest[self.interpreter.active_projectname] + state.compilers = self.interpreter.build.compilers + state.targets = self.interpreter.build.targets + state.headers = self.interpreter.build.get_headers() + state.man = self.interpreter.build.get_man() + state.global_args = self.interpreter.build.global_args + value = fn(state, args, kwargs) + return self.interpreter.module_method_callback(value) + +class MesonMain(InterpreterObject): + def __init__(self, build, interpreter): + InterpreterObject.__init__(self) + self.build = build + self.interpreter = interpreter + self.methods.update({'get_compiler': self.get_compiler_method, + 'is_cross_build' : self.is_cross_build_method, + 'has_exe_wrapper' : self.has_exe_wrapper_method, + 'is_unity' : self.is_unity_method, + 'is_subproject' : self.is_subproject_method, + 'current_source_dir' : self.current_source_dir_method, + 'current_build_dir' : self.current_build_dir_method, + 'source_root' : self.source_root_method, + 'build_root' : self.build_root_method, + 'add_install_script' : self.add_install_script_method, + 'install_dependency_manifest': self.install_dependency_manifest_method, + 'project_version': self.project_version_method, + }) + + def add_install_script_method(self, args, kwargs): + if len(args) != 1: + raise InterpreterException('Set_install_script takes exactly one argument.') + check_stringlist(args) + scriptbase = args[0] + scriptfile = os.path.join(self.interpreter.environment.source_dir, + self.interpreter.subdir, scriptbase) + if not os.path.isfile(scriptfile): + raise InterpreterException('Can not find install script %s.' % scriptbase) + self.build.install_scripts.append(build.InstallScript([scriptfile])) + + def current_source_dir_method(self, args, kwargs): + src = self.interpreter.environment.source_dir + sub = self.interpreter.subdir + if sub == '': + return src + return os.path.join(src, sub) + + def current_build_dir_method(self, args, kwargs): + src = self.interpreter.environment.build_dir + sub = self.interpreter.subdir + if sub == '': + return src + return os.path.join(src, sub) + + def source_root_method(self, args, kwargs): + return self.interpreter.environment.source_dir + + def build_root_method(self, args, kwargs): + return self.interpreter.environment.build_dir + + def has_exe_wrapper_method(self, args, kwargs): + if self.is_cross_build_method(None, None) and 'binaries' in self.build.environment.cross_info.config: + return 'exe_wrap' in self.build.environment.cross_info.config['binaries'] + return True # This is semantically confusing. + + def is_cross_build_method(self, args, kwargs): + return self.build.environment.is_cross_build() + + def get_compiler_method(self, args, kwargs): + if len(args) != 1: + raise InterpreterException('get_compiler_method must have one and only one argument.') + cname = args[0] + native = kwargs.get('native', None) + if native is None: + if self.build.environment.is_cross_build(): + native = False + else: + native = True + if not isinstance(native, bool): + raise InterpreterException('Type of "native" must be a boolean.') + if native: + clist = self.build.compilers + else: + clist = self.build.cross_compilers + for c in clist: + if c.get_language() == cname: + return CompilerHolder(c, self.build.environment) + raise InterpreterException('Tried to access compiler for unspecified language "%s".' % cname) + + def is_unity_method(self, args, kwargs): + return self.build.environment.coredata.get_builtin_option('unity') + + def is_subproject_method(self, args, kwargs): + return self.interpreter.is_subproject() + + def install_dependency_manifest_method(self, args, kwargs): + if len(args) != 1: + raise InterpreterException('Must specify manifest install file name') + if not isinstance(args[0], str): + raise InterpreterException('Argument must be a string.') + self.build.dep_manifest_name = args[0] + + def project_version_method(self, args, kwargs): + return self.build.dep_manifest[self.interpreter.active_projectname]['version'] + +class Interpreter(): + + def __init__(self, build, backend, subproject='', subdir='', subproject_dir='subprojects'): + self.build = build + self.backend = backend + self.subproject = subproject + self.subdir = subdir + self.source_root = build.environment.get_source_dir() + self.subproject_dir = subproject_dir + option_file = os.path.join(self.source_root, self.subdir, 'meson_options.txt') + if os.path.exists(option_file): + oi = optinterpreter.OptionInterpreter(self.subproject, \ + self.build.environment.cmd_line_options.projectoptions) + oi.process(option_file) + self.build.environment.merge_options(oi.options) + mesonfile = os.path.join(self.source_root, self.subdir, environment.build_filename) + if not os.path.isfile(mesonfile): + raise InvalidArguments('Missing Meson file in %s' % mesonfile) + code = open(mesonfile).read() + if len(code.strip()) == 0: + raise InvalidCode('Builder file is empty.') + assert(isinstance(code, str)) + try: + self.ast = mparser.Parser(code).parse() + except coredata.MesonException as me: + me.file = environment.build_filename + raise me + self.sanity_check_ast() + self.variables = {} + self.builtin = {} + self.builtin['build_machine'] = BuildMachine() + if not self.build.environment.is_cross_build(): + self.builtin['host_machine'] = self.builtin['build_machine'] + self.builtin['target_machine'] = self.builtin['build_machine'] + else: + cross_info = self.build.environment.cross_info + if cross_info.has_host(): + self.builtin['host_machine'] = CrossMachineInfo(cross_info.config['host_machine']) + else: + self.builtin['host_machine'] = self.builtin['build_machine'] + if cross_info.has_target(): + self.builtin['target_machine'] = CrossMachineInfo(cross_info.config['target_machine']) + else: + self.builtin['target_machine'] = self.builtin['host_machine'] + self.builtin['meson'] = MesonMain(build, self) + self.environment = build.environment + self.build_func_dict() + self.build_def_files = [os.path.join(self.subdir, environment.build_filename)] + self.coredata = self.environment.get_coredata() + self.generators = [] + self.visited_subdirs = {} + self.global_args_frozen = False + self.subprojects = {} + self.subproject_stack = [] + + def build_func_dict(self): + self.funcs = {'project' : self.func_project, + 'message' : self.func_message, + 'error' : self.func_error, + 'executable': self.func_executable, + 'dependency' : self.func_dependency, + 'static_library' : self.func_static_lib, + 'shared_library' : self.func_shared_lib, + 'library' : self.func_library, + 'jar' : self.func_jar, + 'build_target': self.func_build_target, + 'custom_target' : self.func_custom_target, + 'run_target' : self.func_run_target, + 'generator' : self.func_generator, + 'test' : self.func_test, + 'benchmark' : self.func_benchmark, + 'install_headers' : self.func_install_headers, + 'install_man' : self.func_install_man, + 'subdir' : self.func_subdir, + 'install_data' : self.func_install_data, + 'install_subdir' : self.func_install_subdir, + 'configure_file' : self.func_configure_file, + 'include_directories' : self.func_include_directories, + 'add_global_arguments' : self.func_add_global_arguments, + 'add_languages' : self.func_add_languages, + 'find_program' : self.func_find_program, + 'find_library' : self.func_find_library, + 'configuration_data' : self.func_configuration_data, + 'run_command' : self.func_run_command, + 'gettext' : self.func_gettext, + 'option' : self.func_option, + 'get_option' : self.func_get_option, + 'subproject' : self.func_subproject, + 'vcs_tag' : self.func_vcs_tag, + 'set_variable' : self.func_set_variable, + 'is_variable' : self.func_is_variable, + 'get_variable' : self.func_get_variable, + 'import' : self.func_import, + 'files' : self.func_files, + 'declare_dependency': self.func_declare_dependency, + 'assert': self.func_assert, + } + + def module_method_callback(self, invalues): + unwrap_single = False + if invalues is None: + return + if not isinstance(invalues, list): + unwrap_single = True + invalues = [invalues] + outvalues = [] + for v in invalues: + if isinstance(v, build.CustomTarget): + if v.name in self.build.targets: + raise InterpreterException('Tried to create target %s which already exists.' % v.name) + self.build.targets[v.name] = v + outvalues.append(CustomTargetHolder(v)) + elif isinstance(v, int) or isinstance(v, str): + outvalues.append(v) + elif isinstance(v, build.Executable): + if v.name in self.build.targets: + raise InterpreterException('Tried to create target %s which already exists.' % v.name) + self.build.targets[v.name] = v + outvalues.append(ExecutableHolder(v)) + elif isinstance(v, list): + outvalues.append(self.module_method_callback(v)) + elif isinstance(v, build.GeneratedList): + outvalues.append(GeneratedListHolder(v)) + elif isinstance(v, build.RunTarget): + if v.name in self.build.targets: + raise InterpreterException('Tried to create target %s which already exists.' % v.name) + self.build.targets[v.name] = v + elif isinstance(v, build.InstallScript): + self.build.install_scripts.append(v) + elif isinstance(v, build.Data): + self.build.data.append(v) + else: + print(v) + raise InterpreterException('Module returned a value of unknown type.') + if len(outvalues) == 1 and unwrap_single: + return outvalues[0] + return outvalues + + def get_build_def_files(self): + return self.build_def_files + + def get_variables(self): + return self.variables + + def sanity_check_ast(self): + if not isinstance(self.ast, mparser.CodeBlockNode): + raise InvalidCode('AST is of invalid type. Possibly a bug in the parser.') + if len(self.ast.lines) == 0: + raise InvalidCode('No statements in code.') + first = self.ast.lines[0] + if not isinstance(first, mparser.FunctionNode) or first.func_name != 'project': + raise InvalidCode('First statement must be a call to project') + + def run(self): + self.evaluate_codeblock(self.ast) + mlog.log('Build targets in project:', mlog.bold(str(len(self.build.targets)))) + + def evaluate_codeblock(self, node): + if node is None: + return + if not isinstance(node, mparser.CodeBlockNode): + e = InvalidCode('Tried to execute a non-codeblock. Possibly a bug in the parser.') + e.lineno = node.lineno + e.colno = node.colno + raise e + statements = node.lines + i = 0 + while i < len(statements): + cur = statements[i] + try: + self.evaluate_statement(cur) + except Exception as e: + if not(hasattr(e, 'lineno')): + e.lineno = cur.lineno + e.colno = cur.colno + e.file = os.path.join(self.subdir, 'meson.build') + raise e + i += 1 # In THE FUTURE jump over blocks and stuff. + + def get_variable(self, varname): + if varname in self.builtin: + return self.builtin[varname] + if varname in self.variables: + return self.variables[varname] + raise InvalidCode('Unknown variable "%s".' % varname) + + def func_set_variable(self, node, args, kwargs): + if len(args) != 2: + raise InvalidCode('Set_variable takes two arguments.') + varname = args[0] + value = self.to_native(args[1]) + self.set_variable(varname, value) + + @noKwargs + def func_get_variable(self, node, args, kwargs): + if len(args)<1 or len(args)>2: + raise InvalidCode('Get_variable takes one or two arguments.') + varname = args[0] + if not isinstance(varname, str): + raise InterpreterException('First argument must be a string.') + try: + return self.variables[varname] + except KeyError: + pass + if len(args) == 2: + return args[1] + raise InterpreterException('Tried to get unknown variable "%s".' % varname) + + @stringArgs + @noKwargs + def func_is_variable(self, node, args, kwargs): + if len(args) != 1: + raise InvalidCode('Is_variable takes two arguments.') + varname = args[0] + return varname in self.variables + + @stringArgs + @noKwargs + def func_import(self, node, args, kwargs): + if len(args) != 1: + raise InvalidCode('Import takes one argument.') + modname = args[0] + if not modname in self.environment.coredata.modules: + module = importlib.import_module('mesonbuild.modules.' + modname).initialize() + self.environment.coredata.modules[modname] = module + return ModuleHolder(modname, self.environment.coredata.modules[modname], self) + + @stringArgs + @noKwargs + def func_files(self, node, args, kwargs): + return [mesonlib.File.from_source_file(self.environment.source_dir, self.subdir, fname) for fname in args] + + @noPosargs + def func_declare_dependency(self, node, args, kwargs): + incs = kwargs.get('include_directories', []) + if not isinstance(incs, list): + incs = [incs] + libs = kwargs.get('link_with', []) + if not isinstance(libs, list): + libs = [libs] + sources = kwargs.get('sources', []) + if not isinstance(sources, list): + sources = [sources] + sources = self.source_strings_to_files(self.flatten(sources)) + deps = kwargs.get('dependencies', []) + if not isinstance(deps, list): + deps = [deps] + final_deps = [] + for d in deps: + try: + d = d.held_object + except Exception: + pass + if not isinstance(d, (dependencies.Dependency, dependencies.ExternalLibrary, dependencies.InternalDependency)): + raise InterpreterException('Dependencies must be external deps') + final_deps.append(d) + dep = dependencies.InternalDependency(incs, libs, sources, final_deps) + return InternalDependencyHolder(dep) + + @noKwargs + def func_assert(self, node, args, kwargs): + if len(args) != 2: + raise InterpreterException('Assert takes exactly two arguments') + value, message = args + if not isinstance(value, bool): + raise InterpreterException('Assert value not bool.') + if not isinstance(message, str): + raise InterpreterException('Assert message not a string.') + if not value: + raise InterpreterException('Assert failed: ' + message) + + def set_variable(self, varname, variable): + if variable is None: + raise InvalidCode('Can not assign None to variable.') + if not isinstance(varname, str): + raise InvalidCode('First argument to set_variable must be a string.') + if not self.is_assignable(variable): + raise InvalidCode('Assigned value not of assignable type.') + if re.match('[_a-zA-Z][_0-9a-zA-Z]*$', varname) is None: + raise InvalidCode('Invalid variable name: ' + varname) + if varname in self.builtin: + raise InvalidCode('Tried to overwrite internal variable "%s"' % varname) + self.variables[varname] = variable + + def evaluate_statement(self, cur): + if isinstance(cur, mparser.FunctionNode): + return self.function_call(cur) + elif isinstance(cur, mparser.AssignmentNode): + return self.assignment(cur) + elif isinstance(cur, mparser.MethodNode): + return self.method_call(cur) + elif isinstance(cur, mparser.StringNode): + return cur.value + elif isinstance(cur, mparser.BooleanNode): + return cur.value + elif isinstance(cur, mparser.IfClauseNode): + return self.evaluate_if(cur) + elif isinstance(cur, mparser.IdNode): + return self.get_variable(cur.value) + elif isinstance(cur, mparser.ComparisonNode): + return self.evaluate_comparison(cur) + elif isinstance(cur, mparser.ArrayNode): + return self.evaluate_arraystatement(cur) + elif isinstance(cur, mparser.NumberNode): + return cur.value + elif isinstance(cur, mparser.AndNode): + return self.evaluate_andstatement(cur) + elif isinstance(cur, mparser.OrNode): + return self.evaluate_orstatement(cur) + elif isinstance(cur, mparser.NotNode): + return self.evaluate_notstatement(cur) + elif isinstance(cur, mparser.UMinusNode): + return self.evaluate_uminusstatement(cur) + elif isinstance(cur, mparser.ArithmeticNode): + return self.evaluate_arithmeticstatement(cur) + elif isinstance(cur, mparser.ForeachClauseNode): + return self.evaluate_foreach(cur) + elif isinstance(cur, mparser.PlusAssignmentNode): + return self.evaluate_plusassign(cur) + elif isinstance(cur, mparser.IndexNode): + return self.evaluate_indexing(cur) + elif self.is_elementary_type(cur): + return cur + else: + raise InvalidCode("Unknown statement.") + + def validate_arguments(self, args, argcount, arg_types): + if argcount is not None: + if argcount != len(args): + raise InvalidArguments('Expected %d arguments, got %d.' % + (argcount, len(args))) + for i in range(min(len(args), len(arg_types))): + wanted = arg_types[i] + actual = args[i] + if wanted != None: + if not isinstance(actual, wanted): + raise InvalidArguments('Incorrect argument type.') + + def func_run_command(self, node, args, kwargs): + if len(args) < 1: + raise InterpreterException('Not enough arguments') + cmd = args[0] + cargs = args[1:] + if isinstance(cmd, ExternalProgramHolder): + cmd = cmd.get_command() + elif isinstance(cmd, str): + cmd = [cmd] + else: + raise InterpreterException('First argument is of incorrect type.') + check_stringlist(cargs, 'Run_command arguments must be strings.') + args = cmd + cargs + in_builddir = kwargs.get('in_builddir', False) + if not isinstance(in_builddir, bool): + raise InterpreterException('in_builddir must be boolean.') + return RunProcess(args, self.environment.source_dir, self.environment.build_dir, + self.subdir, in_builddir) + + @stringArgs + def func_gettext(self, nodes, args, kwargs): + if len(args) != 1: + raise InterpreterException('Gettext requires one positional argument (package name).') + packagename = args[0] + languages = kwargs.get('languages', None) + check_stringlist(languages, 'Argument languages must be a list of strings.') + # TODO: check that elements are strings + if len(self.build.pot) > 0: + raise InterpreterException('More than one gettext definition currently not supported.') + self.build.pot.append((packagename, languages, self.subdir)) + + def func_option(self, nodes, args, kwargs): + raise InterpreterException('Tried to call option() in build description file. All options must be in the option file.') + + @stringArgs + def func_subproject(self, nodes, args, kwargs): + if len(args) != 1: + raise InterpreterException('Subproject takes exactly one argument') + dirname = args[0] + return self.do_subproject(dirname, kwargs) + + def do_subproject(self, dirname, kwargs): + if self.subdir != '': + segs = os.path.split(self.subdir) + if len(segs) != 2 or segs[0] != self.subproject_dir: + raise InterpreterException('Subprojects must be defined at the root directory.') + if dirname in self.subproject_stack: + fullstack = self.subproject_stack + [dirname] + incpath = ' => '.join(fullstack) + raise InterpreterException('Recursive include of subprojects: %s.' % incpath) + if dirname in self.subprojects: + return self.subprojects[dirname] + r = wrap.Resolver(os.path.join(self.build.environment.get_source_dir(), self.subproject_dir)) + resolved = r.resolve(dirname) + if resolved is None: + raise InterpreterException('Subproject directory does not exist and can not be downloaded.') + subdir = os.path.join(self.subproject_dir, resolved) + os.makedirs(os.path.join(self.build.environment.get_build_dir(), subdir), exist_ok=True) + self.global_args_frozen = True + mlog.log('\nExecuting subproject ', mlog.bold(dirname), '.\n', sep='') + subi = Interpreter(self.build, self.backend, dirname, subdir, self.subproject_dir) + subi.subprojects = self.subprojects + + subi.subproject_stack = self.subproject_stack + [dirname] + current_active = self.active_projectname + subi.run() + if 'version' in kwargs: + pv = subi.project_version + wanted = kwargs['version'] + if not mesonlib.version_compare(pv, wanted): + raise InterpreterException('Subproject %s version is %s but %s required.' % (dirname, pv, wanted)) + self.active_projectname = current_active + mlog.log('\nSubproject', mlog.bold(dirname), 'finished.') + self.build.subprojects[dirname] = True + self.subprojects.update(subi.subprojects) + self.subprojects[dirname] = SubprojectHolder(subi) + self.build_def_files += subi.build_def_files + return self.subprojects[dirname] + + @stringArgs + @noKwargs + def func_get_option(self, nodes, args, kwargs): + if len(args) != 1: + raise InterpreterException('Argument required for get_option.') + optname = args[0] + try: + return self.environment.get_coredata().get_builtin_option(optname) + except RuntimeError: + pass + try: + return self.environment.coredata.compiler_options[optname].value + except KeyError: + pass + if optname not in coredata.builtin_options and self.is_subproject(): + optname = self.subproject + ':' + optname + try: + return self.environment.coredata.user_options[optname].value + except KeyError: + raise InterpreterException('Tried to access unknown option "%s".' % optname) + + @noKwargs + def func_configuration_data(self, node, args, kwargs): + if len(args) != 0: + raise InterpreterException('configuration_data takes no arguments') + return ConfigurationDataHolder() + + def parse_default_options(self, default_options): + if not isinstance(default_options, list): + default_options = [default_options] + for option in default_options: + if not isinstance(option, str): + mlog.debug(option) + raise InterpreterException('Default options must be strings') + if '=' not in option: + raise InterpreterException('All default options must be of type key=value.') + key, value = option.split('=', 1) + builtin_options = self.coredata.builtin_options + if key in builtin_options: + if not hasattr(self.environment.cmd_line_options, value): + self.coredata.set_builtin_option(key, value) + # If this was set on the command line, do not override. + else: + newoptions = [option] + self.environment.cmd_line_options.projectoptions + self.environment.cmd_line_options.projectoptions = newoptions + + @stringArgs + def func_project(self, node, args, kwargs): + if len(args) < 2: + raise InvalidArguments('Not enough arguments to project(). Needs at least the project name and one language') + + if not self.is_subproject(): + self.build.project_name = args[0] + if self.environment.first_invocation and 'default_options' in kwargs: + self.parse_default_options(kwargs['default_options']) + self.active_projectname = args[0] + self.project_version = kwargs.get('version', 'undefined') + proj_license = mesonlib.stringlistify(kwargs.get('license', 'unknown')) + self.build.dep_manifest[args[0]] = {'version': self.project_version, + 'license': proj_license} + if self.subproject in self.build.projects: + raise InvalidCode('Second call to project().') + if not self.is_subproject() and 'subproject_dir' in kwargs: + self.subproject_dir = kwargs['subproject_dir'] + + if 'meson_version' in kwargs: + cv = coredata.version + pv = kwargs['meson_version'] + if not mesonlib.version_compare(cv, pv): + raise InterpreterException('Meson version is %s but project requires %s.' % (cv, pv)) + self.build.projects[self.subproject] = args[0] + mlog.log('Project name: ', mlog.bold(args[0]), sep='') + self.add_languages(node, args[1:]) + langs = self.coredata.compilers.keys() + if 'vala' in langs: + if not 'c' in langs: + raise InterpreterException('Compiling Vala requires C. Add C to your project languages and rerun Meson.') + + @noKwargs + @stringArgs + def func_add_languages(self, node, args, kwargs): + self.add_languages(node, args) + + @noKwargs + def func_message(self, node, args, kwargs): + # reduce arguments again to avoid flattening posargs + (posargs, _) = self.reduce_arguments(node.args) + if len(posargs) != 1: + raise InvalidArguments('Expected 1 argument, got %d' % len(posargs)) + + arg = posargs[0] + if isinstance(arg, list): + argstr = stringifyUserArguments(arg) + elif isinstance(arg, str): + argstr = arg + elif isinstance(arg, int): + argstr = str(arg) + else: + raise InvalidArguments('Function accepts only strings, integers, lists and lists thereof.') + + mlog.log(mlog.bold('Message:'), argstr) + return + + + @noKwargs + def func_error(self, node, args, kwargs): + self.validate_arguments(args, 1, [str]) + raise InterpreterException('Error encountered: ' + args[0]) + + def add_languages(self, node, args): + need_cross_compiler = self.environment.is_cross_build() and self.environment.cross_info.need_cross_compiler() + for lang in args: + lang = lang.lower() + if lang in self.coredata.compilers: + comp = self.coredata.compilers[lang] + cross_comp = self.coredata.cross_compilers.get(lang, None) + else: + cross_comp = None + if lang == 'c': + comp = self.environment.detect_c_compiler(False) + if need_cross_compiler: + cross_comp = self.environment.detect_c_compiler(True) + elif lang == 'cpp': + comp = self.environment.detect_cpp_compiler(False) + if need_cross_compiler: + cross_comp = self.environment.detect_cpp_compiler(True) + elif lang == 'objc': + comp = self.environment.detect_objc_compiler(False) + if need_cross_compiler: + cross_comp = self.environment.detect_objc_compiler(True) + elif lang == 'objcpp': + comp = self.environment.detect_objcpp_compiler(False) + if need_cross_compiler: + cross_comp = self.environment.detect_objcpp_compiler(True) + elif lang == 'java': + comp = self.environment.detect_java_compiler() + if need_cross_compiler: + cross_comp = comp # Java is platform independent. + elif lang == 'cs': + comp = self.environment.detect_cs_compiler() + if need_cross_compiler: + cross_comp = comp # C# is platform independent. + elif lang == 'vala': + comp = self.environment.detect_vala_compiler() + if need_cross_compiler: + cross_comp = comp # Vala is too (I think). + elif lang == 'rust': + comp = self.environment.detect_rust_compiler() + if need_cross_compiler: + cross_comp = comp # FIXME, probably not correct. + elif lang == 'fortran': + comp = self.environment.detect_fortran_compiler(False) + if need_cross_compiler: + cross_comp = self.environment.detect_fortran_compiler(True) + elif lang == 'swift': + comp = self.environment.detect_swift_compiler() + if need_cross_compiler: + raise InterpreterException('Cross compilation with Swift is not working yet.') + #cross_comp = self.environment.detect_fortran_compiler(True) + else: + raise InvalidCode('Tried to use unknown language "%s".' % lang) + comp.sanity_check(self.environment.get_scratch_dir()) + self.coredata.compilers[lang] = comp + if cross_comp is not None: + cross_comp.sanity_check(self.environment.get_scratch_dir()) + self.coredata.cross_compilers[lang] = cross_comp + new_options = comp.get_options() + optprefix = lang + '_' + for i in new_options: + if not i.startswith(optprefix): + raise InterpreterException('Internal error, %s has incorrect prefix.' % i) + cmd_prefix = i + '=' + for cmd_arg in self.environment.cmd_line_options.projectoptions: + if cmd_arg.startswith(cmd_prefix): + value = cmd_arg.split('=', 1)[1] + new_options[i].set_value(value) + new_options.update(self.coredata.compiler_options) + self.coredata.compiler_options = new_options + mlog.log('Native %s compiler: ' % lang, mlog.bold(' '.join(comp.get_exelist())), ' (%s %s)' % (comp.id, comp.version), sep='') + if not comp.get_language() in self.coredata.external_args: + (ext_compile_args, ext_link_args) = environment.get_args_from_envvars(comp.get_language()) + self.coredata.external_args[comp.get_language()] = ext_compile_args + self.coredata.external_link_args[comp.get_language()] = ext_link_args + self.build.add_compiler(comp) + if need_cross_compiler: + mlog.log('Cross %s compiler: ' % lang, mlog.bold(' '.join(cross_comp.get_exelist())), ' (%s %s)' % (cross_comp.id, cross_comp.version), sep='') + self.build.add_cross_compiler(cross_comp) + if self.environment.is_cross_build() and not need_cross_compiler: + self.build.add_cross_compiler(comp) + + def func_find_program(self, node, args, kwargs): + self.validate_arguments(args, 1, [str]) + required = kwargs.get('required', True) + if not isinstance(required, bool): + raise InvalidArguments('"required" argument must be a boolean.') + exename = args[0] + if exename in self.coredata.ext_progs and\ + self.coredata.ext_progs[exename].found(): + return ExternalProgramHolder(self.coredata.ext_progs[exename]) + # Search for scripts relative to current subdir. + search_dir = os.path.join(self.environment.get_source_dir(), self.subdir) + extprog = dependencies.ExternalProgram(exename, search_dir=search_dir) + progobj = ExternalProgramHolder(extprog) + self.coredata.ext_progs[exename] = extprog + if required and not progobj.found(): + raise InvalidArguments('Program "%s" not found.' % exename) + return progobj + + def func_find_library(self, node, args, kwargs): + self.validate_arguments(args, 1, [str]) + required = kwargs.get('required', True) + if not isinstance(required, bool): + raise InvalidArguments('"required" argument must be a boolean.') + libname = args[0] + # We do not cache found libraries because they can come + # and go between invocations wildly. As an example we + # may find the 64 bit version but need instead the 32 bit + # one that is not installed. If we cache the found path + # then we will never found the new one if it get installed. + # This causes a bit of a slowdown as libraries are rechecked + # on every regen, but since it is a fast operation it should be + # ok. + if 'dirs' in kwargs: + search_dirs = kwargs['dirs'] + if not isinstance(search_dirs, list): + search_dirs = [search_dirs] + for i in search_dirs: + if not isinstance(i, str): + raise InvalidCode('Directory entry is not a string.') + if not os.path.isabs(i): + raise InvalidCode('Search directory %s is not an absolute path.' % i) + else: + search_dirs = None + result = self.environment.find_library(libname, search_dirs) + extlib = dependencies.ExternalLibrary(libname, result) + libobj = ExternalLibraryHolder(extlib) + if required and not libobj.found(): + raise InvalidArguments('External library "%s" not found.' % libname) + return libobj + + def func_dependency(self, node, args, kwargs): + self.validate_arguments(args, 1, [str]) + name = args[0] + identifier = dependencies.get_dep_identifier(name, kwargs) + if identifier in self.coredata.deps: + dep = self.coredata.deps[identifier] + else: + dep = dependencies.Dependency() # Returns always false for dep.found() + if not dep.found(): + try: + dep = dependencies.find_external_dependency(name, self.environment, kwargs) + except dependencies.DependencyException: + if 'fallback' in kwargs: + return self.dependency_fallback(kwargs) + raise + self.coredata.deps[identifier] = dep + return DependencyHolder(dep) + + def dependency_fallback(self, kwargs): + fbinfo = kwargs['fallback'] + check_stringlist(fbinfo) + if len(fbinfo) != 2: + raise InterpreterException('Fallback info must have exactly two items.') + dirname, varname = fbinfo + self.do_subproject(dirname, kwargs) + return self.subprojects[dirname].get_variable_method([varname], {}) + + def func_executable(self, node, args, kwargs): + return self.build_target(node, args, kwargs, ExecutableHolder) + + def func_static_lib(self, node, args, kwargs): + return self.build_target(node, args, kwargs, StaticLibraryHolder) + + def func_shared_lib(self, node, args, kwargs): + return self.build_target(node, args, kwargs, SharedLibraryHolder) + + def func_library(self, node, args, kwargs): + if self.coredata.get_builtin_option('default_library') == 'shared': + return self.func_shared_lib(node, args, kwargs) + return self.func_static_lib(node, args, kwargs) + + def func_jar(self, node, args, kwargs): + return self.build_target(node, args, kwargs, JarHolder) + + def func_build_target(self, node, args, kwargs): + if 'target_type' not in kwargs: + raise InterpreterException('Missing target_type keyword argument') + target_type = kwargs.pop('target_type') + if target_type == 'executable': + return self.func_executable(node, args, kwargs) + elif target_type == 'shared_library': + return self.func_shared_lib(node, args, kwargs) + elif target_type == 'static_library': + return self.func_static_lib(node, args, kwargs) + elif target_type == 'library': + return self.func_library(node, args, kwargs) + elif target_type == 'jar': + return self.func_jar(node, args, kwargs) + else: + raise InterpreterException('Unknown target_type.') + + def func_vcs_tag(self, node, args, kwargs): + fallback = kwargs.pop('fallback', None) + if not isinstance(fallback, str): + raise InterpreterException('Keyword argument fallback must exist and be a string.') + replace_string = kwargs.pop('replace_string', '@VCS_TAG@') + regex_selector = '(.*)' # default regex selector for custom command: use complete output + vcs_cmd = kwargs.get('command', None) + if vcs_cmd and not isinstance(vcs_cmd, list): + vcs_cmd = [vcs_cmd] + source_dir = os.path.normpath(os.path.join(self.environment.get_source_dir(), self.subdir)) + if vcs_cmd: + # Is the command an executable in path or maybe a script in the source tree? + vcs_cmd[0] = shutil.which(vcs_cmd[0]) or os.path.join(source_dir, vcs_cmd[0]) + else: + vcs = mesonlib.detect_vcs(source_dir) + if vcs: + mlog.log('Found %s repository at %s' % (vcs['name'], vcs['wc_dir'])) + vcs_cmd = vcs['get_rev'].split() + regex_selector = vcs['rev_regex'] + else: + vcs_cmd = [' '] # executing this cmd will fail in vcstagger.py and force to use the fallback string + # vcstagger.py parameters: infile, outfile, fallback, source_dir, replace_string, regex_selector, command... + kwargs['command'] = [sys.executable, + self.environment.get_build_command(), + '--internal', + 'vcstagger', + '@INPUT0@', + '@OUTPUT0@', + fallback, + source_dir, + replace_string, + regex_selector] + vcs_cmd + kwargs.setdefault('build_always', True) + return self.func_custom_target(node, [kwargs['output']], kwargs) + + @stringArgs + def func_custom_target(self, node, args, kwargs): + if len(args) != 1: + raise InterpreterException('Incorrect number of arguments') + name = args[0] + tg = CustomTargetHolder(build.CustomTarget(name, self.subdir, kwargs)) + self.add_target(name, tg.held_object) + return tg + + @noKwargs + def func_run_target(self, node, args, kwargs): + if len(args) < 2: + raise InterpreterException('Incorrect number of arguments') + cleaned_args = [] + for i in args: + try: + i = i.held_object + except AttributeError: + pass + if not isinstance(i, (str, build.BuildTarget, build.CustomTarget)): + mlog.debug('Wrong type:', str(i)) + raise InterpreterException('Invalid argument to run_target.') + cleaned_args.append(i) + name = cleaned_args[0] + command = cleaned_args[1] + cmd_args = cleaned_args[2:] + tg = RunTargetHolder(name, command, cmd_args, self.subdir) + self.add_target(name, tg.held_object) + return tg + + def func_generator(self, node, args, kwargs): + gen = GeneratorHolder(self, args, kwargs) + self.generators.append(gen) + return gen + + def func_benchmark(self, node, args, kwargs): + self.add_test(node, args, kwargs, False) + + def func_test(self, node, args, kwargs): + self.add_test(node, args, kwargs, True) + + def add_test(self, node, args, kwargs, is_base_test): + if len(args) != 2: + raise InterpreterException('Incorrect number of arguments') + if not isinstance(args[0], str): + raise InterpreterException('First argument of test must be a string.') + if not isinstance(args[1], (ExecutableHolder, JarHolder, ExternalProgramHolder)): + raise InterpreterException('Second argument must be executable.') + par = kwargs.get('is_parallel', True) + if not isinstance(par, bool): + raise InterpreterException('Keyword argument is_parallel must be a boolean.') + cmd_args = kwargs.get('args', []) + if not isinstance(cmd_args, list): + cmd_args = [cmd_args] + for i in cmd_args: + if not isinstance(i, (str, mesonlib.File)): + raise InterpreterException('Command line arguments must be strings') + envlist = kwargs.get('env', []) + if not isinstance(envlist, list): + envlist = [envlist] + env = {} + for e in envlist: + if '=' not in e: + raise InterpreterException('Env var definition must be of type key=val.') + (k, val) = e.split('=', 1) + k = k.strip() + val = val.strip() + if ' ' in k: + raise InterpreterException('Env var key must not have spaces in it.') + env[k] = val + valgrind_args = kwargs.get('valgrind_args', []) + if not isinstance(valgrind_args, list): + valgrind_args = [valgrind_args] + for a in valgrind_args: + if not isinstance(a, str): + raise InterpreterException('Valgrind_arg not a string.') + should_fail = kwargs.get('should_fail', False) + if not isinstance(should_fail, bool): + raise InterpreterException('Keyword argument should_fail must be a boolean.') + timeout = kwargs.get('timeout', 30) + if 'workdir' in kwargs: + workdir = kwargs['workdir'] + if not isinstance(workdir, str): + raise InterpreterException('Workdir keyword argument must be a string.') + if not os.path.isabs(workdir): + raise InterpreterException('Workdir keyword argument must be an absolute path.') + else: + workdir = None + if not isinstance(timeout, int): + raise InterpreterException('Timeout must be an integer.') + suite = mesonlib.stringlistify(kwargs.get('suite', '')) + if self.is_subproject(): + newsuite = [] + for s in suite: + newsuite.append(self.subproject.replace(' ', '_').replace('.', '_') + '.' + s) + suite = newsuite + t = Test(args[0], suite, args[1].held_object, par, cmd_args, env, should_fail, valgrind_args, timeout, workdir) + if is_base_test: + self.build.tests.append(t) + mlog.debug('Adding test "', mlog.bold(args[0]), '".', sep='') + else: + self.build.benchmarks.append(t) + mlog.debug('Adding benchmark "', mlog.bold(args[0]), '".', sep='') + + @stringArgs + def func_install_headers(self, node, args, kwargs): + h = Headers(self.subdir, args, kwargs) + self.build.headers.append(h) + return h + + @stringArgs + def func_install_man(self, node, args, kwargs): + m = Man(self.subdir, args, kwargs) + self.build.man.append(m) + return m + + @noKwargs + def func_subdir(self, node, args, kwargs): + self.validate_arguments(args, 1, [str]) + if '..' in args[0]: + raise InvalidArguments('Subdir contains ..') + if self.subdir == '' and args[0] == self.subproject_dir: + raise InvalidArguments('Must not go into subprojects dir with subdir(), use subproject() instead.') + prev_subdir = self.subdir + subdir = os.path.join(prev_subdir, args[0]) + if subdir in self.visited_subdirs: + raise InvalidArguments('Tried to enter directory "%s", which has already been visited.'\ + % subdir) + self.visited_subdirs[subdir] = True + self.subdir = subdir + try: + os.makedirs(os.path.join(self.environment.build_dir, subdir)) + except FileExistsError: + pass + buildfilename = os.path.join(self.subdir, environment.build_filename) + self.build_def_files.append(buildfilename) + absname = os.path.join(self.environment.get_source_dir(), buildfilename) + if not os.path.isfile(absname): + raise InterpreterException('Nonexistant build def file %s.' % buildfilename) + code = open(absname).read() + assert(isinstance(code, str)) + try: + codeblock = mparser.Parser(code).parse() + except coredata.MesonException as me: + me.file = buildfilename + raise me + self.evaluate_codeblock(codeblock) + self.subdir = prev_subdir + + @stringArgs + def func_install_data(self, node, args, kwargs): + data = DataHolder(True, self.subdir, args, kwargs) + self.build.data.append(data.held_object) + return data + + @stringArgs + def func_install_subdir(self, node, args, kwargs): + if len(args) != 1: + raise InvalidArguments('Install_subdir requires exactly one argument.') + if not 'install_dir' in kwargs: + raise InvalidArguments('Missing keyword argument install_dir') + install_dir = kwargs['install_dir'] + if not isinstance(install_dir, str): + raise InvalidArguments('Keyword argument install_dir not a string.') + idir = InstallDir(self.subdir, args[0], install_dir) + self.build.install_dirs.append(idir) + return idir + + def func_configure_file(self, node, args, kwargs): + if len(args) > 0: + raise InterpreterException("configure_file takes only keyword arguments.") + if not 'input' in kwargs: + raise InterpreterException('Required keyword argument "input" not defined.') + if not 'output' in kwargs: + raise InterpreterException('Required keyword argument "output" not defined.') + inputfile = kwargs['input'] + output = kwargs['output'] + if not isinstance(inputfile, str): + raise InterpreterException('Input must be a string.') + if not isinstance(output, str): + raise InterpreterException('Output must be a string.') + if 'configuration' in kwargs: + conf = kwargs['configuration'] + if not isinstance(conf, ConfigurationDataHolder): + raise InterpreterException('Argument "configuration" is not of type configuration_data') + + conffile = os.path.join(self.subdir, inputfile) + if conffile not in self.build_def_files: + self.build_def_files.append(conffile) + os.makedirs(os.path.join(self.environment.build_dir, self.subdir), exist_ok=True) + ifile_abs = os.path.join(self.environment.source_dir, self.subdir, inputfile) + ofile_abs = os.path.join(self.environment.build_dir, self.subdir, output) + mesonlib.do_conf_file(ifile_abs, ofile_abs, conf.held_object) + conf.mark_used() + elif 'command' in kwargs: + res = self.func_run_command(node, kwargs['command'], {}) + if res.returncode != 0: + raise InterpreterException('Running configure command failed.\n%s\n%s' % + (res.stdout, res.stderr)) + else: + raise InterpreterException('Configure_file must have either "configuration" or "command".') + if isinstance(kwargs.get('install_dir', None), str): + self.build.data.append(DataHolder(False, self.subdir, [output], kwargs).held_object) + return mesonlib.File.from_built_file(self.subdir, output) + + @stringArgs + def func_include_directories(self, node, args, kwargs): + absbase = os.path.join(self.environment.get_source_dir(), self.subdir) + for a in args: + absdir = os.path.join(absbase, a) + if not os.path.isdir(absdir): + raise InvalidArguments('Include dir %s does not exist.' % a) + is_system = kwargs.get('is_system', False) + if not isinstance(is_system, bool): + raise InvalidArguments('Is_system must be boolean.') + i = IncludeDirsHolder(build.IncludeDirs(self.subdir, args, is_system)) + return i + + @stringArgs + def func_add_global_arguments(self, node, args, kwargs): + if self.subproject != '': + raise InvalidCode('Global arguments can not be set in subprojects because there is no way to make that reliable.') + if self.global_args_frozen: + raise InvalidCode('Tried to set global arguments after a build target has been declared.\nThis is not permitted. Please declare all global arguments before your targets.') + if not 'language' in kwargs: + raise InvalidCode('Missing language definition in add_global_arguments') + lang = kwargs['language'].lower() + if lang in self.build.global_args: + self.build.global_args[lang] += args + else: + self.build.global_args[lang] = args + + def flatten(self, args): + if isinstance(args, mparser.StringNode): + return args.value + if isinstance(args, str): + return args + if isinstance(args, InterpreterObject): + return args + if isinstance(args, int): + return args + result = [] + for a in args: + if isinstance(a, list): + rest = self.flatten(a) + result = result + rest + elif isinstance(a, mparser.StringNode): + result.append(a.value) + else: + result.append(a) + return result + + def source_strings_to_files(self, sources): + results = [] + for s in sources: + if isinstance(s, mesonlib.File) or isinstance(s, GeneratedListHolder) or \ + isinstance(s, CustomTargetHolder): + pass + elif isinstance(s, str): + s = mesonlib.File.from_source_file(self.environment.source_dir, self.subdir, s) + else: + raise InterpreterException("Source item is not string or File-type object.") + results.append(s) + return results + + def add_target(self, name, tobj): + if name in coredata.forbidden_target_names: + raise InvalidArguments('Target name "%s" is reserved for Meson\'s internal use. Please rename.'\ + % name) + # To permit an executable and a shared library to have the + # same name, such as "foo.exe" and "libfoo.a". + idname = tobj.get_id() + if idname in self.build.targets: + raise InvalidCode('Tried to create target "%s", but a target of that name already exists.' % name) + self.build.targets[idname] = tobj + if idname not in self.coredata.target_guids: + self.coredata.target_guids[idname] = str(uuid.uuid4()).upper() + + def build_target(self, node, args, kwargs, targetholder): + name = args[0] + sources = args[1:] + if self.environment.is_cross_build(): + if kwargs.get('native', False): + is_cross = False + else: + is_cross = True + else: + is_cross = False + try: + kw_src = self.flatten(kwargs['sources']) + if not isinstance(kw_src, list): + kw_src = [kw_src] + except KeyError: + kw_src = [] + sources += kw_src + sources = self.source_strings_to_files(sources) + objs = self.flatten(kwargs.get('objects', [])) + kwargs['dependencies'] = self.flatten(kwargs.get('dependencies', [])) + if not isinstance(objs, list): + objs = [objs] + self.check_sources_exist(os.path.join(self.source_root, self.subdir), sources) + if targetholder is ExecutableHolder: + targetclass = build.Executable + elif targetholder is SharedLibraryHolder: + targetclass = build.SharedLibrary + elif targetholder is StaticLibraryHolder: + targetclass = build.StaticLibrary + elif targetholder is JarHolder: + targetclass = build.Jar + else: + mlog.debug('Unknown target type:', str(targetholder)) + raise RuntimeError('Unreachable code') + target = targetclass(name, self.subdir, self.subproject, is_cross, sources, objs, self.environment, kwargs) + l = targetholder(target, self) + self.add_target(name, l.held_object) + self.global_args_frozen = True + return l + + def check_sources_exist(self, subdir, sources): + for s in sources: + if not isinstance(s, str): + continue # This means a generated source and they always exist. + fname = os.path.join(subdir, s) + if not os.path.isfile(fname): + raise InterpreterException('Tried to add non-existing source %s.' % s) + + def function_call(self, node): + func_name = node.func_name + (posargs, kwargs) = self.reduce_arguments(node.args) + if func_name in self.funcs: + return self.funcs[func_name](node, self.flatten(posargs), kwargs) + else: + raise InvalidCode('Unknown function "%s".' % func_name) + + def is_assignable(self, value): + if isinstance(value, InterpreterObject) or \ + isinstance(value, dependencies.Dependency) or\ + isinstance(value, str) or\ + isinstance(value, int) or \ + isinstance(value, list) or \ + isinstance(value, mesonlib.File): + return True + return False + + def assignment(self, node): + assert(isinstance(node, mparser.AssignmentNode)) + var_name = node.var_name + if not isinstance(var_name, str): + raise InvalidArguments('Tried to assign value to a non-variable.') + value = self.evaluate_statement(node.value) + value = self.to_native(value) + if not self.is_assignable(value): + raise InvalidCode('Tried to assign an invalid value to variable.') + self.set_variable(var_name, value) + return value + + def reduce_arguments(self, args): + assert(isinstance(args, mparser.ArgumentNode)) + if args.incorrect_order(): + raise InvalidArguments('All keyword arguments must be after positional arguments.') + reduced_pos = [self.evaluate_statement(arg) for arg in args.arguments] + reduced_kw = {} + for key in args.kwargs.keys(): + if not isinstance(key, str): + raise InvalidArguments('Keyword argument name is not a string.') + a = args.kwargs[key] + reduced_kw[key] = self.evaluate_statement(a) + if not isinstance(reduced_pos, list): + reduced_pos = [reduced_pos] + return (reduced_pos, reduced_kw) + + def string_method_call(self, obj, method_name, args): + obj = self.to_native(obj) + (posargs, _) = self.reduce_arguments(args) + if method_name == 'strip': + return obj.strip() + elif method_name == 'format': + return self.format_string(obj, args) + elif method_name == 'split': + if len(posargs) > 1: + raise InterpreterException('Split() must have at most one argument.') + elif len(posargs) == 1: + s = posargs[0] + if not isinstance(s, str): + raise InterpreterException('Split() argument must be a string') + return obj.split(s) + else: + return obj.split() + elif method_name == 'startswith' or method_name == 'endswith': + s = posargs[0] + if not isinstance(s, str): + raise InterpreterException('Argument must be a string.') + if method_name == 'startswith': + return obj.startswith(s) + return obj.endswith(s) + raise InterpreterException('Unknown method "%s" for a string.' % method_name) + + def to_native(self, arg): + if isinstance(arg, mparser.StringNode) or \ + isinstance(arg, mparser.NumberNode) or \ + isinstance(arg, mparser.BooleanNode): + return arg.value + return arg + + def format_string(self, templ, args): + templ = self.to_native(templ) + if isinstance(args, mparser.ArgumentNode): + args = args.arguments + for (i, arg) in enumerate(args): + arg = self.to_native(self.evaluate_statement(arg)) + if isinstance(arg, bool): # Python boolean is upper case. + arg = str(arg).lower() + templ = templ.replace('@{}@'.format(i), str(arg)) + return templ + + def method_call(self, node): + invokable = node.source_object + if isinstance(invokable, mparser.IdNode): + object_name = invokable.value + obj = self.get_variable(object_name) + else: + obj = self.evaluate_statement(invokable) + method_name = node.name + if method_name == 'extract_objects' and self.environment.coredata.get_builtin_option('unity'): + raise InterpreterException('Single object files can not be extracted in Unity builds.') + args = node.args + if isinstance(obj, mparser.StringNode): + obj = obj.get_value() + if isinstance(obj, str): + return self.string_method_call(obj, method_name, args) + if isinstance(obj, list): + return self.array_method_call(obj, method_name, self.reduce_arguments(args)[0]) + if not isinstance(obj, InterpreterObject): + raise InvalidArguments('Variable "%s" is not callable.' % object_name) + (args, kwargs) = self.reduce_arguments(args) + if method_name == 'extract_objects': + self.validate_extraction(obj.held_object) + return obj.method_call(method_name, self.flatten(args), kwargs) + + # Only permit object extraction from the same subproject + def validate_extraction(self, buildtarget): + if not self.subdir.startswith(self.subproject_dir): + if buildtarget.subdir.startswith(self.subproject_dir): + raise InterpreterException('Tried to extract objects from a subproject target.') + else: + if not buildtarget.subdir.startswith(self.subproject_dir): + raise InterpreterException('Tried to extract objects from the main project from a subproject.') + if self.subdir.split('/')[1] != buildtarget.subdir.split('/')[1]: + raise InterpreterException('Tried to extract objects from a different subproject.') + + def array_method_call(self, obj, method_name, args): + if method_name == 'contains': + return self.check_contains(obj, args) + elif method_name == 'length': + return len(obj) + elif method_name == 'get': + index = args[0] + if not isinstance(index, int): + raise InvalidArguments('Array index must be a number.') + if index < -len(obj) or index >= len(obj): + raise InvalidArguments('Array index %s is out of bounds for array of size %d.' % (index, len(obj))) + return obj[index] + raise InterpreterException('Arrays do not have a method called "%s".' % method_name) + + def check_contains(self, obj, args): + if len(args) != 1: + raise InterpreterException('Contains method takes exactly one argument.') + item = args[0] + for element in obj: + if isinstance(element, list): + found = self.check_contains(element, args) + if found: + return True + try: + if element == item: + return True + except Exception: + pass + return False + + def evaluate_if(self, node): + assert(isinstance(node, mparser.IfClauseNode)) + for i in node.ifs: + result = self.evaluate_statement(i.condition) + if not(isinstance(result, bool)): + print(result) + raise InvalidCode('If clause does not evaluate to true or false.') + if result: + self.evaluate_codeblock(i.block) + return + if not isinstance(node.elseblock, mparser.EmptyNode): + self.evaluate_codeblock(node.elseblock) + + def evaluate_foreach(self, node): + assert(isinstance(node, mparser.ForeachClauseNode)) + varname = node.varname.value + items = self.evaluate_statement(node.items) + if not isinstance(items, list): + raise InvalidArguments('Items of foreach loop is not an array') + for item in items: + self.set_variable(varname, item) + self.evaluate_codeblock(node.block) + + def evaluate_plusassign(self, node): + assert(isinstance(node, mparser.PlusAssignmentNode)) + varname = node.var_name + addition = self.evaluate_statement(node.value) + # Remember that all variables are immutable. We must always create a + # full new variable and then assign it. + old_variable = self.get_variable(varname) + if not isinstance(old_variable, list): + raise InvalidArguments('The += operator currently only works with arrays.') + # Add other data types here. + else: + if isinstance(addition, list): + new_value = old_variable + addition + else: + new_value = old_variable + [addition] + self.set_variable(varname, new_value) + + def evaluate_indexing(self, node): + assert(isinstance(node, mparser.IndexNode)) + iobject = self.evaluate_statement(node.iobject) + if not isinstance(iobject, list): + raise InterpreterException('Tried to index a non-array object.') + index = self.evaluate_statement(node.index) + if not isinstance(index, int): + raise InterpreterException('Index value is not an integer.') + if index < -len(iobject) or index >= len(iobject): + raise InterpreterException('Index %d out of bounds of array of size %d.' % (index, len(iobject))) + return iobject[index] + + def is_elementary_type(self, v): + if isinstance(v, (int, float, str, bool, list)): + return True + return False + + def evaluate_comparison(self, node): + v1 = self.evaluate_statement(node.left) + v2 = self.evaluate_statement(node.right) + if self.is_elementary_type(v1): + val1 = v1 + else: + val1 = v1.value + if self.is_elementary_type(v2): + val2 = v2 + else: + val2 = v2.value + if node.ctype == '==': + return val1 == val2 + elif node.ctype == '!=': + return val1 != val2 + else: + raise InvalidCode('You broke me.') + + def evaluate_andstatement(self, cur): + l = self.evaluate_statement(cur.left) + if isinstance(l, mparser.BooleanNode): + l = l.value + if not isinstance(l, bool): + raise InterpreterException('First argument to "and" is not a boolean.') + if not l: + return False + r = self.evaluate_statement(cur.right) + if isinstance(r, mparser.BooleanNode): + r = r.value + if not isinstance(r, bool): + raise InterpreterException('Second argument to "and" is not a boolean.') + return r + + def evaluate_orstatement(self, cur): + l = self.evaluate_statement(cur.left) + if isinstance(l, mparser.BooleanNode): + l = l.get_value() + if not isinstance(l, bool): + raise InterpreterException('First argument to "or" is not a boolean.') + if l: + return True + r = self.evaluate_statement(cur.right) + if isinstance(r, mparser.BooleanNode): + r = r.get_value() + if not isinstance(r, bool): + raise InterpreterException('Second argument to "or" is not a boolean.') + return r + + def evaluate_notstatement(self, cur): + v = self.evaluate_statement(cur.value) + if isinstance(v, mparser.BooleanNode): + v = v.value + if not isinstance(v, bool): + raise InterpreterException('Argument to "not" is not a boolean.') + return not v + + def evaluate_uminusstatement(self, cur): + v = self.evaluate_statement(cur.value) + if isinstance(v, mparser.NumberNode): + v = v.value + if not isinstance(v, int): + raise InterpreterException('Argument to negation is not an integer.') + return -v + + def evaluate_arithmeticstatement(self, cur): + l = self.to_native(self.evaluate_statement(cur.left)) + r = self.to_native(self.evaluate_statement(cur.right)) + + if cur.operation == 'add': + try: + return l + r + except Exception as e: + raise InvalidCode('Invalid use of addition: ' + str(e)) + elif cur.operation == 'sub': + if not isinstance(l, int) or not isinstance(r, int): + raise InvalidCode('Subtraction works only with integers.') + return l - r + elif cur.operation == 'mul': + if not isinstance(l, int) or not isinstance(r, int): + raise InvalidCode('Multiplication works only with integers.') + return l * r + elif cur.operation == 'div': + if not isinstance(l, int) or not isinstance(r, int): + raise InvalidCode('Division works only with integers.') + return l // r + else: + raise InvalidCode('You broke me.') + + def evaluate_arraystatement(self, cur): + (arguments, kwargs) = self.reduce_arguments(cur.args) + if len(kwargs) > 0: + raise InvalidCode('Keyword arguments are invalid in array construction.') + return arguments + + def is_subproject(self): + return self.subproject != '' diff --git a/mesonbuild/mconf.py b/mesonbuild/mconf.py new file mode 100644 index 0000000..f174425 --- /dev/null +++ b/mesonbuild/mconf.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python3 + +# Copyright 2014-2016 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys, os +import pickle +import argparse +from . import coredata, mesonlib +from .coredata import build_types, 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) + +def run(args): + args = mesonlib.expand_arguments(args) + if not args: + sys.exit(1) + options = parser.parse_args(args) + if len(options.directory) > 1: + print('%s <build directory>' % args[0]) + print('If you omit the build directory, the current directory is substituted.') + return 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) + return(1) + return 0 + +if __name__ == '__main__': + sys.exit(run(sys.argv[1:])) diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py new file mode 100644 index 0000000..2ab5ce4 --- /dev/null +++ b/mesonbuild/mesonlib.py @@ -0,0 +1,284 @@ +# Copyright 2012-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. + +"""A library of random helper functionality.""" + +import platform, subprocess, operator, os, shutil, re, sys + +from glob import glob + +from .coredata import MesonException + +class File: + def __init__(self, is_built, subdir, fname): + self.is_built = is_built + self.subdir = subdir + self.fname = fname + + @staticmethod + def from_source_file(source_root, subdir, fname): + if not os.path.isfile(os.path.join(source_root, subdir, fname)): + raise MesonException('File %s does not exist.' % fname) + return File(False, subdir, fname) + + @staticmethod + def from_built_file(subdir, fname): + return File(True, subdir, fname) + + @staticmethod + def from_absolute_file(fname): + return File(False, '', fname) + + def rel_to_builddir(self, build_to_src): + if self.is_built: + return os.path.join(self.subdir, self.fname) + else: + return os.path.join(build_to_src, self.subdir, self.fname) + + def endswith(self, ending): + return self.fname.endswith(ending) + + def split(self, s): + return self.fname.split(s) + + def __eq__(self, other): + return (self.fname, self.subdir, self.is_built) == (other.fname, other.subdir, other.is_built) + + def __hash__(self): + return hash((self.fname, self.subdir, self.is_built)) + +def flatten(item): + if not isinstance(item, list): + return item + result = [] + for i in item: + if isinstance(i, list): + result += flatten(i) + else: + result.append(i) + return result + +def is_osx(): + return platform.system().lower() == 'darwin' + +def is_linux(): + return platform.system().lower() == 'linux' + +def is_windows(): + platname = platform.system().lower() + return platname == 'windows' or 'mingw' in platname + +def is_32bit(): + return not(sys.maxsize > 2**32) + +def is_debianlike(): + try: + open('/etc/debian_version', 'r') + return True + except FileNotFoundError: + return False + +def exe_exists(arglist): + try: + p = subprocess.Popen(arglist, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + p.communicate() + if p.returncode == 0: + return True + except FileNotFoundError: + pass + return False + +def detect_vcs(source_dir): + vcs_systems = [ + dict(name = 'git', cmd = 'git', repo_dir = '.git', get_rev = 'git describe --dirty=+', rev_regex = '(.*)', dep = '.git/logs/HEAD'), + dict(name = 'mercurial', cmd = 'hg', repo_dir = '.hg', get_rev = 'hg id -n', rev_regex = '(.*)', dep = '.hg/dirstate'), + dict(name = 'subversion', cmd = 'svn', repo_dir = '.svn', get_rev = 'svn info', rev_regex = 'Revision: (.*)', dep = '.svn/wc.db'), + dict(name = 'bazaar', cmd = 'bzr', repo_dir = '.bzr', get_rev = 'bzr revno', rev_regex = '(.*)', dep = '.bzr'), + ] + + segs = source_dir.replace('\\', '/').split('/') + for i in range(len(segs), -1, -1): + curdir = '/'.join(segs[:i]) + for vcs in vcs_systems: + if os.path.isdir(os.path.join(curdir, vcs['repo_dir'])) and shutil.which(vcs['cmd']): + vcs['wc_dir'] = curdir + return vcs + return None + +numpart = re.compile('[0-9.]+') + +def version_compare(vstr1, vstr2): + match = numpart.match(vstr1.strip()) + if match is None: + raise MesonException('Unconparable version string %s.' % vstr1) + vstr1 = match.group(0) + if vstr2.startswith('>='): + cmpop = operator.ge + vstr2 = vstr2[2:] + elif vstr2.startswith('<='): + cmpop = operator.le + vstr2 = vstr2[2:] + elif vstr2.startswith('!='): + cmpop = operator.ne + vstr2 = vstr2[2:] + elif vstr2.startswith('=='): + cmpop = operator.eq + vstr2 = vstr2[2:] + elif vstr2.startswith('='): + cmpop = operator.eq + vstr2 = vstr2[1:] + elif vstr2.startswith('>'): + cmpop = operator.gt + vstr2 = vstr2[1:] + elif vstr2.startswith('<'): + cmpop = operator.lt + vstr2 = vstr2[1:] + else: + cmpop = operator.eq + varr1 = [int(x) for x in vstr1.split('.')] + varr2 = [int(x) for x in vstr2.split('.')] + return cmpop(varr1, varr2) + +def default_libdir(): + try: + archpath = subprocess.check_output(['dpkg-architecture', '-qDEB_HOST_MULTIARCH']).decode().strip() + return 'lib/' + archpath + except: + pass + if os.path.isdir('/usr/lib64'): + return 'lib64' + return 'lib' + +def get_library_dirs(): + if is_windows(): + return ['C:/mingw/lib'] # Fixme + if is_osx(): + return ['/usr/lib'] # Fix me as well. + # The following is probably Debian/Ubuntu specific. + # /usr/local/lib is first because it contains stuff + # installed by the sysadmin and is probably more up-to-date + # than /usr/lib. If you feel that this search order is + # problematic, please raise the issue on the mailing list. + unixdirs = ['/usr/local/lib', '/usr/lib', '/lib'] + plat = subprocess.check_output(['uname', '-m']).decode().strip() + # This is a terrible hack. I admit it and I'm really sorry. + # I just don't know what the correct solution is. + if plat == 'i686': + plat = 'i386' + if plat.startswith('arm'): + plat = 'arm' + unixdirs += glob('/usr/lib/' + plat + '*') + if os.path.exists('/usr/lib64'): + unixdirs.append('/usr/lib64') + unixdirs += glob('/lib/' + plat + '*') + if os.path.exists('/lib64'): + unixdirs.append('/lib64') + unixdirs += glob('/lib/' + plat + '*') + return unixdirs + + +def do_replacement(regex, line, confdata): + match = re.search(regex, line) + while match: + varname = match.group(1) + if varname in confdata.keys(): + var = confdata.get(varname) + if isinstance(var, str): + pass + elif isinstance(var, int): + var = str(var) + else: + raise RuntimeError('Tried to replace a variable with something other than a string or int.') + else: + var = '' + line = line.replace('@' + varname + '@', var) + match = re.search(regex, line) + return line + +def do_mesondefine(line, confdata): + arr = line.split() + if len(arr) != 2: + raise MesonException('#mesondefine does not contain exactly two tokens: %s', line.strip()) + varname = arr[1] + try: + v = confdata.get(varname) + except KeyError: + return '/* undef %s */\n' % varname + if isinstance(v, bool): + if v: + return '#define %s\n' % varname + else: + return '#undef %s\n' % varname + elif isinstance(v, int): + return '#define %s %d\n' % (varname, v) + elif isinstance(v, str): + return '#define %s %s\n' % (varname, v) + else: + raise MesonException('#mesondefine argument "%s" is of unknown type.' % varname) + + +def do_conf_file(src, dst, confdata): + data = open(src).readlines() + regex = re.compile('@(.*?)@') + result = [] + for line in data: + if line.startswith('#mesondefine'): + line = do_mesondefine(line, confdata) + else: + line = do_replacement(regex, line, confdata) + result.append(line) + dst_tmp = dst + '~' + open(dst_tmp, 'w').writelines(result) + shutil.copymode(src, dst_tmp) + replace_if_different(dst, dst_tmp) + + +def replace_if_different(dst, dst_tmp): + # If contents are identical, don't touch the file to prevent + # unnecessary rebuilds. + try: + if open(dst, 'r').read() == open(dst_tmp, 'r').read(): + os.unlink(dst_tmp) + return + except FileNotFoundError: + pass + os.replace(dst_tmp, dst) + +def stringlistify(item): + if isinstance(item, str): + item = [item] + if not isinstance(item, list): + raise MesonException('Item is not an array') + for i in item: + if not isinstance(i, str): + raise MesonException('List item not a string.') + return item + +def expand_arguments(args): + expended_args = [] + for arg in args: + if not arg.startswith('@'): + expended_args.append(arg) + continue + + args_file = arg[1:] + try: + with open(args_file) as f: + extended_args = f.read().split() + expended_args += extended_args + except Exception as e: + print('Error expanding command line arguments, %s not found' % args_file) + print(e) + return None + return expended_args diff --git a/mesonbuild/mesonmain.py b/mesonbuild/mesonmain.py new file mode 100644 index 0000000..82f30fe --- /dev/null +++ b/mesonbuild/mesonmain.py @@ -0,0 +1,262 @@ +#!/usr/bin/env python3 + +# Copyright 2012-2016 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys, stat, traceback, pickle, argparse +import datetime +import os.path +from . import environment, interpreter, mesonlib +from . import build +import platform +from . import mlog, coredata + +from .coredata import MesonException, build_types, layouts, warning_levels, libtypelist + +backendlist = ['ninja', 'vs2010', 'xcode'] + +parser = argparse.ArgumentParser() + +default_warning = '1' + +if mesonlib.is_windows(): + def_prefix = 'c:/' +else: + def_prefix = '/usr/local' + +parser.add_argument('--prefix', default=def_prefix, dest='prefix', + help='the installation prefix (default: %(default)s)') +parser.add_argument('--libdir', default=mesonlib.default_libdir(), dest='libdir', + help='the installation subdir of libraries (default: %(default)s)') +parser.add_argument('--bindir', default='bin', dest='bindir', + help='the installation subdir of executables (default: %(default)s)') +parser.add_argument('--includedir', default='include', dest='includedir', + help='relative path of installed headers (default: %(default)s)') +parser.add_argument('--datadir', default='share', dest='datadir', + help='relative path to the top of data file subdirectory (default: %(default)s)') +parser.add_argument('--mandir', default='share/man', dest='mandir', + help='relative path of man files (default: %(default)s)') +parser.add_argument('--localedir', default='share/locale', dest='localedir', + help='relative path of locale data (default: %(default)s)') +parser.add_argument('--backend', default='ninja', dest='backend', choices=backendlist, + help='backend to use (default: %(default)s)') +parser.add_argument('--buildtype', default='debug', choices=build_types, dest='buildtype', + help='build type go use (default: %(default)s)') +parser.add_argument('--strip', action='store_true', dest='strip', default=False,\ + help='strip targets on install (default: %(default)s)') +parser.add_argument('--enable-gcov', action='store_true', dest='coverage', default=False,\ + help='measure test coverage') +parser.add_argument('--disable-pch', action='store_false', dest='use_pch', default=True,\ + help='do not use precompiled headers') +parser.add_argument('--unity', action='store_true', dest='unity', default=False,\ + help='unity build') +parser.add_argument('--werror', action='store_true', dest='werror', default=False,\ + help='Treat warnings as errors') +parser.add_argument('--layout', choices=layouts, dest='layout', default='mirror',\ + help='Build directory layout.') +parser.add_argument('--default-library', choices=libtypelist, dest='default_library', + default='shared', help='Default library type.') +parser.add_argument('--warnlevel', default=default_warning, dest='warning_level', choices=warning_levels,\ + help='Level of compiler warnings to use (larger is more, default is %(default)s)') +parser.add_argument('--cross-file', default=None, dest='cross_file', + help='file describing cross compilation environment') +parser.add_argument('-D', action='append', dest='projectoptions', default=[], + help='Set project options.') +parser.add_argument('-v', '--version', action='store_true', dest='print_version', default=False, + help='Print version.') +parser.add_argument('directories', nargs='*') + +class MesonApp(): + + def __init__(self, dir1, dir2, script_file, handshake, options): + (self.source_dir, self.build_dir) = self.validate_dirs(dir1, dir2, handshake) + if not os.path.isabs(options.prefix): + raise RuntimeError('--prefix must be an absolute path.') + self.meson_script_file = script_file + self.options = options + + def has_build_file(self, dirname): + fname = os.path.join(dirname, environment.build_filename) + return os.path.exists(fname) + + def validate_core_dirs(self, dir1, dir2): + ndir1 = os.path.abspath(dir1) + ndir2 = os.path.abspath(dir2) + if not stat.S_ISDIR(os.stat(ndir1).st_mode): + raise RuntimeError('%s is not a directory' % dir1) + if not stat.S_ISDIR(os.stat(ndir2).st_mode): + raise RuntimeError('%s is not a directory' % dir2) + if os.path.samefile(dir1, dir2): + raise RuntimeError('Source and build directories must not be the same. Create a pristine build directory.') + if self.has_build_file(ndir1): + if self.has_build_file(ndir2): + raise RuntimeError('Both directories contain a build file %s.' % environment.build_filename) + return (ndir1, ndir2) + if self.has_build_file(ndir2): + return (ndir2, ndir1) + raise RuntimeError('Neither directory contains a build file %s.' % environment.build_filename) + + def validate_dirs(self, dir1, dir2, handshake): + (src_dir, build_dir) = self.validate_core_dirs(dir1, dir2) + priv_dir = os.path.join(build_dir, 'meson-private/coredata.dat') + if os.path.exists(priv_dir): + if not handshake: + msg = '''Trying to run Meson on a build directory that has already been configured. +If you want to build it, just run your build command (e.g. ninja) inside the +build directory. Meson will autodetect any changes in your setup and regenerate +itself as required.''' + raise RuntimeError(msg) + else: + if handshake: + raise RuntimeError('Something went terribly wrong. Please file a bug.') + return (src_dir, build_dir) + + def generate(self): + env = environment.Environment(self.source_dir, self.build_dir, self.meson_script_file, self.options) + mlog.initialize(env.get_log_dir()) + mlog.debug('Build started at', datetime.datetime.now().isoformat()) + mlog.debug('Python binary:', sys.executable) + mlog.debug('Python system:', platform.system()) + mlog.log(mlog.bold('The Meson build system')) + mlog.log('Version:', coredata.version) + mlog.log('Source dir:', mlog.bold(self.source_dir)) + mlog.log('Build dir:', mlog.bold(self.build_dir)) + if env.is_cross_build(): + mlog.log('Build type:', mlog.bold('cross build')) + else: + mlog.log('Build type:', mlog.bold('native build')) + b = build.Build(env) + if self.options.backend == 'ninja': + from . import ninjabackend + g = ninjabackend.NinjaBackend(b) + elif self.options.backend == 'vs2010': + from . import vs2010backend + g = vs2010backend.Vs2010Backend(b) + elif self.options.backend == 'xcode': + from . import xcodebackend + g = xcodebackend.XCodeBackend(b) + else: + raise RuntimeError('Unknown backend "%s".' % self.options.backend) + + intr = interpreter.Interpreter(b, g) + if env.is_cross_build(): + mlog.log('Host machine cpu family:', mlog.bold(intr.builtin['host_machine'].cpu_family_method([], {}))) + mlog.log('Host machine cpu:', mlog.bold(intr.builtin['host_machine'].cpu_method([], {}))) + mlog.log('Target machine cpu family:', mlog.bold(intr.builtin['target_machine'].cpu_family_method([], {}))) + mlog.log('Target machine cpu:', mlog.bold(intr.builtin['target_machine'].cpu_method([], {}))) + mlog.log('Build machine cpu family:', mlog.bold(intr.builtin['build_machine'].cpu_family_method([], {}))) + mlog.log('Build machine cpu:', mlog.bold(intr.builtin['build_machine'].cpu_method([], {}))) + intr.run() + g.generate(intr) + env.generating_finished() + dumpfile = os.path.join(env.get_scratch_dir(), 'build.dat') + pickle.dump(b, open(dumpfile, 'wb')) + +def run_script_command(args): + cmdname = args[0] + cmdargs = args[1:] + if cmdname == 'test': + import mesonbuild.scripts.meson_test as abc + cmdfunc = abc.run + elif cmdname == 'benchmark': + import mesonbuild.scripts.meson_benchmark as abc + cmdfunc = abc.run + elif cmdname == 'install': + import mesonbuild.scripts.meson_install as abc + cmdfunc = abc.run + elif cmdname == 'commandrunner': + import mesonbuild.scripts.commandrunner as abc + cmdfunc = abc.run + elif cmdname == 'delsuffix': + import mesonbuild.scripts.delwithsuffix as abc + cmdfunc = abc.run + elif cmdname == 'depfixer': + import mesonbuild.scripts.depfixer as abc + cmdfunc = abc.run + elif cmdname == 'dirchanger': + import mesonbuild.scripts.dirchanger as abc + cmdfunc = abc.run + elif cmdname == 'gtkdoc': + import meson.scripts.gtkdochelper as abc + cmdfunc = abc.run + elif cmdname == 'regencheck': + import mesonbuild.scripts.regen_checker as abc + cmdfunc = abc.run + elif cmdname == 'symbolextractor': + import mesonbuild.scripts.symbolextractor as abc + cmdfunc = abc.run + elif cmdname == 'vcstagger': + import mesonbuild.scripts.vcstagger as abc + cmdfunc = abc.run + else: + raise MesonException('Unknown internal command {}.'.format(cmdname)) + return cmdfunc(cmdargs) + +def run(mainfile, args): + if sys.version_info < (3, 3): + print('Meson works correctly only with python 3.3+.') + print('You have python %s.' % sys.version) + print('Please update your environment') + return 1 + if args[0] == '--internal': + if args[1] != 'regenerate': + sys.exit(run_script_command(args[1:])) + args = args[2:] + handshake = True + else: + handshake = False + args = mesonlib.expand_arguments(args) + if not args: + return 1 + options = parser.parse_args(args) + if options.print_version: + print(coredata.version) + return 0 + args = options.directories + if len(args) == 0 or len(args) > 2: + print('%s <source directory> <build directory>' % sys.argv[0]) + print('If you omit either directory, the current directory is substituted.') + return 1 + dir1 = args[0] + if len(args) > 1: + dir2 = args[1] + else: + dir2 = '.' + while os.path.islink(mainfile): + resolved = os.readlink(mainfile) + if resolved[0] != '/': + mainfile = os.path.join(os.path.dirname(mainfile), resolved) + else: + mainfile = resolved + try: + app = MesonApp(dir1, dir2, mainfile, handshake, options) + except Exception as e: + # Log directory does not exist, so just print + # to stdout. + print('Error during basic setup:\n') + print(e) + return 1 + try: + app.generate() + except Exception as e: + if isinstance(e, MesonException): + if hasattr(e, 'file') and hasattr(e, 'lineno') and hasattr(e, 'colno'): + mlog.log(mlog.red('\nMeson encountered an error in file %s, line %d, column %d:' % (e.file, e.lineno, e.colno))) + else: + mlog.log(mlog.red('\nMeson encountered an error:')) + mlog.log(e) + else: + traceback.print_exc() + return 1 + return 0 diff --git a/mesonbuild/mesonmain.ui b/mesonbuild/mesonmain.ui new file mode 100644 index 0000000..209584b --- /dev/null +++ b/mesonbuild/mesonmain.ui @@ -0,0 +1,248 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MainWindow</class> + <widget class="QMainWindow" name="MainWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>740</width> + <height>613</height> + </rect> + </property> + <property name="windowTitle"> + <string>Meson</string> + </property> + <widget class="QWidget" name="centralwidget"> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Project</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="project_label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Source directory</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="srcdir_label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_5"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Build directory</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="builddir_label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string>Build type</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLabel" name="buildtype_label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Backend</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QLabel" name="backend_label"> + <property name="text"> + <string>Ninja</string> + </property> + </widget> + </item> + <item row="5" column="0" colspan="2"> + <widget class="QTabWidget" name="tabWidget"> + <property name="currentIndex"> + <number>2</number> + </property> + <widget class="QWidget" name="core_tab"> + <attribute name="title"> + <string>Core data</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_6"> + <item row="0" column="0"> + <widget class="QTreeView" name="core_view"/> + </item> + </layout> + </widget> + <widget class="QWidget" name="path_tab"> + <attribute name="title"> + <string>Paths</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="0" column="0"> + <widget class="QTreeView" name="path_view"/> + </item> + </layout> + </widget> + <widget class="QWidget" name="option_tab"> + <attribute name="title"> + <string>Options</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_4"> + <item row="0" column="0"> + <layout class="QFormLayout" name="option_form"/> + </item> + </layout> + </widget> + <widget class="QWidget" name="dependency_tab"> + <attribute name="title"> + <string>Dependencies</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_5"> + <item row="0" column="0"> + <widget class="QTreeView" name="dep_view"/> + </item> + </layout> + </widget> + <widget class="QWidget" name="target_tab"> + <attribute name="title"> + <string>Build targets</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="0"> + <widget class="QTreeView" name="target_view"/> + </item> + </layout> + </widget> + </widget> + </item> + <item row="6" column="0" colspan="2"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QPushButton" name="save_button"> + <property name="text"> + <string>Save</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="compile_button"> + <property name="text"> + <string>Compile</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="test_button"> + <property name="text"> + <string>Run tests</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="install_button"> + <property name="text"> + <string>Install</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="clean_button"> + <property name="text"> + <string>Clean</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <widget class="QMenuBar" name="menubar"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>740</width> + <height>25</height> + </rect> + </property> + <widget class="QMenu" name="menuFile"> + <property name="title"> + <string>File</string> + </property> + <addaction name="actionSave"/> + <addaction name="actionQuit"/> + </widget> + <addaction name="menuFile"/> + </widget> + <widget class="QStatusBar" name="statusbar"/> + <action name="actionSave"> + <property name="text"> + <string>&Save</string> + </property> + </action> + <action name="actionQuit"> + <property name="text"> + <string>&Quit</string> + </property> + </action> + </widget> + <resources/> + <connections/> +</ui> diff --git a/mesonbuild/mesonrunner.ui b/mesonbuild/mesonrunner.ui new file mode 100644 index 0000000..942c6bd --- /dev/null +++ b/mesonbuild/mesonrunner.ui @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>rundialog</class> + <widget class="QDialog" name="rundialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>581</width> + <height>368</height> + </rect> + </property> + <property name="windowTitle"> + <string>External process output</string> + </property> + <property name="modal"> + <bool>true</bool> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="1" column="0"> + <widget class="QLabel" name="timelabel"> + <property name="text"> + <string>Compile time: 0:0</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QPushButton" name="termbutton"> + <property name="text"> + <string>Terminate</string> + </property> + </widget> + </item> + <item row="0" column="0" colspan="2"> + <widget class="QTextEdit" name="console"> + <property name="readOnly"> + <bool>true</bool> + </property> + <property name="html"> + <string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html></string> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/mesonbuild/mesonstart.ui b/mesonbuild/mesonstart.ui new file mode 100644 index 0000000..c6c5f96 --- /dev/null +++ b/mesonbuild/mesonstart.ui @@ -0,0 +1,119 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MainWindow</class> + <widget class="QMainWindow" name="MainWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>644</width> + <height>192</height> + </rect> + </property> + <property name="windowTitle"> + <string>Meson</string> + </property> + <widget class="QWidget" name="centralwidget"> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Source directory</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLineEdit" name="source_entry"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QPushButton" name="source_browse_button"> + <property name="text"> + <string>Browse</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Build directory</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="build_entry"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QPushButton" name="build_browse_button"> + <property name="text"> + <string>Browse</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Cross file</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLineEdit" name="cross_entry"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QPushButton" name="cross_browse_button"> + <property name="text"> + <string>Browse</string> + </property> + </widget> + </item> + <item row="3" column="2"> + <widget class="QPushButton" name="generate_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Generate</string> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QMenuBar" name="menubar"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>644</width> + <height>25</height> + </rect> + </property> + </widget> + <widget class="QStatusBar" name="statusbar"/> + </widget> + <resources/> + <connections/> +</ui> diff --git a/mesonbuild/mgui.py b/mesonbuild/mgui.py new file mode 100644 index 0000000..6e57ce7 --- /dev/null +++ b/mesonbuild/mgui.py @@ -0,0 +1,565 @@ +#!/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 +from . 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.get_builtin_option('buildtype')) + combo.currentTextChanged.connect(self.build_type_changed) + self.form.addRow('Build type', combo) + strip = QCheckBox("") + strip.setChecked(self.coredata.get_builtin_option('strip')) + strip.stateChanged.connect(self.strip_changed) + self.form.addRow('Strip on install', strip) + coverage = QCheckBox("") + coverage.setChecked(self.coredata.get_builtin_option('coverage')) + coverage.stateChanged.connect(self.coverage_changed) + self.form.addRow('Enable coverage', coverage) + pch = QCheckBox("") + pch.setChecked(self.coredata.get_builtin_option('use_pch')) + pch.stateChanged.connect(self.pch_changed) + self.form.addRow('Enable pch', pch) + unity = QCheckBox("") + unity.setChecked(self.coredata.get_builtin_option('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 + + +def run(args): # SPECIAL, Qt wants all args, including command name. + app = QApplication(sys.argv) + if len(args) == 1: + arg = "" + elif len(args) == 2: + arg = sys.argv[1] + else: + print(sys.argv[0], "<build or source dir>") + return 1 + if os.path.exists(os.path.join(arg, 'meson-private/coredata.dat')): + guirespawner = MesonGuiRespawner(arg) + else: + runner = Starter(arg) + return app.exec_() + +if __name__ == '__main__': + sys.exit(run(sys.argv)) diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py new file mode 100644 index 0000000..b088117 --- /dev/null +++ b/mesonbuild/mintro.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python3 + +# Copyright 2014-2016 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This 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 +from . 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)) + +def run(args): + options = parser.parse_args(args) + if len(options.args) > 1: + print('Too many arguments') + return 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') + return 1 + return 0 + +if __name__ == '__main__': + sys.exit(run(sys.argv[1:])) diff --git a/mesonbuild/mlog.py b/mesonbuild/mlog.py new file mode 100644 index 0000000..2807c2b --- /dev/null +++ b/mesonbuild/mlog.py @@ -0,0 +1,81 @@ +# 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, os, platform + +"""This is (mostly) a standalone module used to write logging +information about Meson runs. Some output goes to screen, +some to logging dir and some goes to both.""" + +colorize_console = platform.system().lower() != 'windows' and os.isatty(sys.stdout.fileno()) +log_dir = None +log_file = None + +def initialize(logdir): + global log_dir, log_file + log_dir = logdir + log_file = open(os.path.join(logdir, 'meson-log.txt'), 'w') + +def shutdown(): + global log_file + if log_file is not None: + log_file.close() + +class AnsiDecorator(): + plain_code = "\033[0m" + + def __init__(self, text, code): + self.text = text + self.code = code + + def get_text(self, with_codes): + if with_codes: + return self.code + self.text + AnsiDecorator.plain_code + return self.text + +def bold(text): + return AnsiDecorator(text, "\033[1m") + +def red(text): + return AnsiDecorator(text, "\033[1;31m") + +def green(text): + return AnsiDecorator(text, "\033[1;32m") + +def cyan(text): + return AnsiDecorator(text, "\033[1;36m") + +def process_markup(args, keep): + arr = [] + for arg in args: + if isinstance(arg, str): + arr.append(arg) + elif isinstance(arg, AnsiDecorator): + arr.append(arg.get_text(keep)) + else: + arr.append(str(arg)) + return arr + +def debug(*args, **kwargs): + arr = process_markup(args, False) + if log_file is not None: + print(*arr, file=log_file, **kwargs) # Log file never gets ANSI codes. + +def log(*args, **kwargs): + arr = process_markup(args, False) + if log_file is not None: + print(*arr, file=log_file, **kwargs) # Log file never gets ANSI codes. + if colorize_console: + arr = process_markup(args, True) + print(*arr, **kwargs) diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py new file mode 100644 index 0000000..e552b84 --- /dev/null +++ b/mesonbuild/modules/gnome.py @@ -0,0 +1,330 @@ +# 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. + +'''This module provides helper functions for Gnome/GLib related +functionality such as gobject-introspection and gresources.''' + +from .. import build +import os, sys +import subprocess +from ..coredata import MesonException +from .. import mlog +import xml.etree.ElementTree as ET +from ..mesonlib import File + +girwarning_printed = False + +class GnomeModule: + + def compile_resources(self, state, args, kwargs): + cmd = ['glib-compile-resources', '@INPUT@', '--generate'] + if 'source_dir' in kwargs: + resource_loc = os.path.join(state.subdir, kwargs.pop('source_dir')) + d = os.path.join(state.build_to_src, resource_loc) + cmd += ['--sourcedir', d] + else: + resource_loc = state.subdir + if 'c_name' in kwargs: + cmd += ['--c-name', kwargs.pop('c_name')] + cmd += ['--target', '@OUTPUT@'] + kwargs['command'] = cmd + output_c = args[0] + '.c' + output_h = args[0] + '.h' + resfile = args[1] + kwargs['depend_files'] = self.parse_gresource_xml(state, resfile, resource_loc) + kwargs['input'] = resfile + kwargs['output'] = output_c + target_c = build.CustomTarget(args[0]+'_c', state.subdir, kwargs) + kwargs['output'] = output_h + target_h = build.CustomTarget(args[0] + '_h', state.subdir, kwargs) + return [target_c, target_h] + + def parse_gresource_xml(self, state, fobj, resource_loc): + if isinstance(fobj, File): + fname = fobj.fname + subdir = fobj.subdir + else: + fname = fobj + subdir = state.subdir + abspath = os.path.join(state.environment.source_dir, state.subdir, fname) + relative_part = os.path.split(fname)[0] + try: + tree = ET.parse(abspath) + root = tree.getroot() + result = [] + for child in root[0]: + if child.tag != 'file': + mlog.log("Warning, malformed rcc file: ", os.path.join(state.subdir, fname)) + break + else: + relfname = os.path.join(resource_loc, child.text) + absfname = os.path.join(state.environment.source_dir, relfname) + if os.path.isfile(absfname): + result.append(relfname) + else: + mlog.log('Warning, resource file points to nonexisting file %s.' % relfname) + return result + except Exception: + return [] + + def generate_gir(self, state, args, kwargs): + if len(args) != 1: + raise MesonException('Gir takes one argument') + girtarget = args[0] + while hasattr(girtarget, 'held_object'): + girtarget = girtarget.held_object + if not isinstance(girtarget, (build.Executable, build.SharedLibrary)): + raise MesonException('Gir target must be an executable or shared library') + try: + pkgstr = subprocess.check_output(['pkg-config', '--cflags', 'gobject-introspection-1.0']) + except Exception: + global girwarning_printed + if not girwarning_printed: + mlog.log(mlog.bold('Warning:'), 'gobject-introspection dependency was not found, disabling gir generation.') + girwarning_printed = True + return [] + pkgargs = pkgstr.decode().strip().split() + ns = kwargs.pop('namespace') + nsversion = kwargs.pop('nsversion') + libsources = kwargs.pop('sources') + girfile = '%s-%s.gir' % (ns, nsversion) + depends = [girtarget] + + scan_command = ['g-ir-scanner', '@INPUT@'] + scan_command += pkgargs + scan_command += ['--no-libtool', '--namespace='+ns, '--nsversion=' + nsversion, '--warn-all', + '--output', '@OUTPUT@'] + + extra_args = kwargs.pop('extra_args', []) + if not isinstance(extra_args, list): + extra_args = [extra_args] + scan_command += extra_args + + for incdirs in girtarget.include_dirs: + for incdir in incdirs.get_incdirs(): + scan_command += ['-I%s' % os.path.join(state.environment.get_source_dir(), incdir)] + + if 'link_with' in kwargs: + link_with = kwargs.pop('link_with') + if not isinstance(link_with, list): + link_with = [link_with] + for link in link_with: + lib = link.held_object + scan_command += ['-l%s' % lib.name] + if isinstance(lib, build.SharedLibrary): + scan_command += ['-L%s' % + os.path.join(state.environment.get_build_dir(), + lib.subdir)] + depends.append(lib) + + if 'includes' in kwargs: + includes = kwargs.pop('includes') + if isinstance(includes, str): + scan_command += ['--include=%s' % includes] + elif isinstance(includes, list): + scan_command += ['--include=%s' % inc for inc in includes] + else: + raise MesonException('Gir includes must be str or list') + if state.global_args.get('c'): + scan_command += ['--cflags-begin'] + scan_command += state.global_args['c'] + scan_command += ['--cflags-end'] + if kwargs.get('symbol_prefix'): + sym_prefix = kwargs.pop('symbol_prefix') + if not isinstance(sym_prefix, str): + raise MesonException('Gir symbol prefix must be str') + scan_command += ['--symbol-prefix=%s' % sym_prefix] + if kwargs.get('identifier_prefix'): + identifier_prefix = kwargs.pop('identifier_prefix') + if not isinstance(identifier_prefix, str): + raise MesonException('Gir identifier prefix must be str') + scan_command += ['--identifier-prefix=%s' % identifier_prefix] + if kwargs.get('export_packages'): + pkgs = kwargs.pop('export_packages') + if isinstance(pkgs, str): + scan_command += ['--pkg-export=%s' % pkgs] + elif isinstance(pkgs, list): + scan_command += ['--pkg-export=%s' % pkg for pkg in pkgs] + else: + raise MesonException('Gir export packages must be str or list') + + deps = None + if 'dependencies' in kwargs: + deps = kwargs.pop('dependencies') + if not isinstance (deps, list): + deps = [deps] + for dep in deps: + girdir = dep.held_object.get_variable ("girdir") + if girdir: + scan_command += ["--add-include-path=%s" % girdir] + for lib in dep.held_object.libs: + if os.path.isabs(lib) and dep.held_object.is_libtool: + scan_command += ["-L%s" % os.path.dirname(lib)] + libname = os.path.basename(lib) + if libname.startswith("lib"): + libname = libname[3:] + libname = libname.split(".so")[0] + lib = "-l%s" % libname + scan_command += [lib] + + inc_dirs = None + if kwargs.get('include_directories'): + inc_dirs = kwargs.pop('include_directories') + if not isinstance(inc_dirs, list): + inc_dirs = [inc_dirs] + for ind in inc_dirs: + if isinstance(ind.held_object, build.IncludeDirs): + scan_command += ['--add-include-path=%s' % inc for inc in ind.held_object.get_incdirs()] + else: + raise MesonException('Gir include dirs should be include_directories()') + if isinstance(girtarget, build.Executable): + scan_command += ['--program', girtarget] + elif isinstance(girtarget, build.SharedLibrary): + scan_command += ["-L@PRIVATE_OUTDIR_ABS_%s@" % girtarget.get_id()] + libname = girtarget.get_basename() + scan_command += ['--library', libname] + scankwargs = {'output' : girfile, + 'input' : libsources, + 'command' : scan_command, + 'depends' : depends, + } + if kwargs.get('install'): + scankwargs['install'] = kwargs['install'] + scankwargs['install_dir'] = os.path.join(state.environment.get_datadir(), 'gir-1.0') + scan_target = GirTarget(girfile, state.subdir, scankwargs) + + typelib_output = '%s-%s.typelib' % (ns, nsversion) + typelib_cmd = ['g-ir-compiler', scan_target, '--output', '@OUTPUT@'] + if inc_dirs: + for incd in inc_dirs: + typelib_cmd += ['--includedir=%s' % inc for inc in + incd.held_object.get_incdirs()] + if deps: + for dep in deps: + girdir = dep.held_object.get_variable ("girdir") + if girdir: + typelib_cmd += ["--includedir=%s" % girdir] + + kwargs['output'] = typelib_output + kwargs['command'] = typelib_cmd + # Note that this can't be libdir, because e.g. on Debian it points to + # lib/x86_64-linux-gnu but the girepo dir is always under lib. + kwargs['install_dir'] = 'lib/girepository-1.0' + typelib_target = TypelibTarget(typelib_output, state.subdir, kwargs) + return [scan_target, typelib_target] + + def compile_schemas(self, state, args, kwargs): + if len(args) != 0: + raise MesonException('Compile_schemas does not take positional arguments.') + srcdir = os.path.join(state.build_to_src, state.subdir) + outdir = state.subdir + cmd = ['glib-compile-schemas', '--targetdir', outdir, srcdir] + kwargs['command'] = cmd + kwargs['input'] = [] + kwargs['output'] = 'gschemas.compiled' + if state.subdir == '': + targetname = 'gsettings-compile' + else: + targetname = 'gsettings-compile-' + state.subdir + target_g = build.CustomTarget(targetname, state.subdir, kwargs) + return target_g + + def gtkdoc(self, state, args, kwargs): + if len(args) != 1: + raise MesonException('Gtkdoc must have one positional argument.') + modulename = args[0] + if not isinstance(modulename, str): + raise MesonException('Gtkdoc arg must be string.') + if not 'src_dir' in kwargs: + raise MesonException('Keyword argument src_dir missing.') + main_file = kwargs.get('main_sgml', '') + if not isinstance(main_file, str): + raise MesonException('Main sgml keyword argument must be a string.') + main_xml = kwargs.get('main_xml', '') + if not isinstance(main_xml, str): + raise MesonException('Main xml keyword argument must be a string.') + if main_xml != '': + if main_file != '': + raise MesonException('You can only specify main_xml or main_sgml, not both.') + main_file = main_xml + src_dir = kwargs['src_dir'] + targetname = modulename + '-doc' + command = os.path.normpath(os.path.join(os.path.split(__file__)[0], "../gtkdochelper.py")) + if hasattr(src_dir, 'held_object'): + src_dir= src_dir.held_object + if not isinstance(src_dir, build.IncludeDirs): + raise MesonException('Invalidt keyword argument for src_dir.') + incdirs = src_dir.get_incdirs() + if len(incdirs) != 1: + raise MesonException('Argument src_dir has more than one directory specified.') + header_dir = os.path.join(state.environment.get_source_dir(), src_dir.get_curdir(), incdirs[0]) + else: + header_dir = os.path.normpath(os.path.join(state.subdir, src_dir)) + args = ['--sourcedir=' + state.environment.get_source_dir(), + '--builddir=' + state.environment.get_build_dir(), + '--subdir=' + state.subdir, + '--headerdir=' + header_dir, + '--mainfile=' + main_file, + '--modulename=' + modulename] + args += self.unpack_args('--htmlargs=', 'html_args', kwargs) + args += self.unpack_args('--scanargs=', 'scan_args', kwargs) + res = [build.RunTarget(targetname, command, args, state.subdir)] + if kwargs.get('install', True): + res.append(build.InstallScript([command] + args)) + return res + + def unpack_args(self, arg, kwarg_name, kwargs): + try: + new_args = kwargs[kwarg_name] + if not isinstance(new_args, list): + new_args = [new_args] + for i in new_args: + if not isinstance(i, str): + raise MesonException('html_args values must be strings.') + except KeyError: + return[] + if len(new_args) > 0: + return [arg + '@@'.join(new_args)] + return [] + + def gdbus_codegen(self, state, args, kwargs): + if len(args) != 2: + raise MesonException('Gdbus_codegen takes two arguments, name and xml file.') + namebase = args[0] + xml_file = args[1] + cmd = ['gdbus-codegen'] + if 'interface_prefix' in kwargs: + cmd += ['--interface-prefix', kwargs.pop('interface_prefix')] + if 'namespace' in kwargs: + cmd += ['--c-namespace', kwargs.pop('namespace')] + cmd += ['--generate-c-code', '@OUTDIR@/' + namebase, '@INPUT@'] + outputs = [namebase + '.c', namebase + '.h'] + custom_kwargs = {'input' : xml_file, + 'output' : outputs, + 'command' : cmd + } + return build.CustomTarget(namebase + '-gdbus', state.subdir, custom_kwargs) + +def initialize(): + mlog.log('Warning, glib compiled dependencies will not work until this upstream issue is fixed:', + mlog.bold('https://bugzilla.gnome.org/show_bug.cgi?id=745754')) + return GnomeModule() + +class GirTarget(build.CustomTarget): + def __init__(self, name, subdir, kwargs): + super().__init__(name, subdir, kwargs) + +class TypelibTarget(build.CustomTarget): + def __init__(self, name, subdir, kwargs): + super().__init__(name, subdir, kwargs) diff --git a/mesonbuild/modules/modtest.py b/mesonbuild/modules/modtest.py new file mode 100644 index 0000000..c9247e6 --- /dev/null +++ b/mesonbuild/modules/modtest.py @@ -0,0 +1,21 @@ +# 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. + +class TestModule: + + def print_hello(self, state, args, kwargs): + print('Hello from a Meson module') + +def initialize(): + return TestModule() diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py new file mode 100644 index 0000000..f18decf --- /dev/null +++ b/mesonbuild/modules/pkgconfig.py @@ -0,0 +1,82 @@ +# 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. + +from .. import coredata, build +from .. import mesonlib +import os + +class PkgConfigModule: + + def print_hello(self, state, args, kwargs): + print('Hello from a Meson module') + + def generate_pkgconfig_file(self, state, libraries, subdirs, name, description, version, filebase): + outdir = state.environment.scratch_dir + fname = os.path.join(outdir, filebase + '.pc') + ofile = open(fname, 'w') + coredata = state.environment.get_coredata() + ofile.write('prefix=%s\n' % coredata.get_builtin_option('prefix')) + ofile.write('libdir=${prefix}/%s\n' % coredata.get_builtin_option('libdir')) + ofile.write('includedir=${prefix}/%s\n\n' % coredata.get_builtin_option('includedir')) + ofile.write('Name: %s\n' % name) + if len(description) > 0: + ofile.write('Description: %s\n' % description) + if len(version) > 0: + ofile.write('Version: %s\n' % version) + ofile.write('Libs: -L${libdir} ') + for l in libraries: + ofile.write('-l%s ' % l.name) + ofile.write('\n') + ofile.write('CFlags: ') + for h in subdirs: + if h == '.': + h = '' + ofile.write(os.path.join('-I${includedir}', h)) + ofile.write(' ') + ofile.write('\n') + + def generate(self, state, args, kwargs): + if len(args) > 0: + raise coredata.MesonException('Pkgconfig_gen takes no positional arguments.') + libs = kwargs.get('libraries', []) + if not isinstance(libs, list): + libs = [libs] + processed_libs = [] + for l in libs: + if hasattr(l, 'held_object'): + l = l.held_object + if not (isinstance(l, build.SharedLibrary) or isinstance(l, build.StaticLibrary)): + raise coredata.MesonException('Library argument not a library object.') + processed_libs.append(l) + libs = processed_libs + subdirs = mesonlib.stringlistify(kwargs.get('subdirs', ['.'])) + version = kwargs.get('version', '') + if not isinstance(version, str): + raise coredata.MesonException('Version must be a string.') + name = kwargs.get('name', None) + if not isinstance(name, str): + raise coredata.MesonException('Name not specified.') + filebase = kwargs.get('filebase', name) + if not isinstance(filebase, str): + raise coredata.MesonException('Filebase must be a string.') + description = kwargs.get('description', None) + if not isinstance(description, str): + raise coredata.MesonException('Description is not a string.') + pcfile = filebase + '.pc' + pkgroot = os.path.join(state.environment.coredata.get_builtin_option('libdir'), 'pkgconfig') + self.generate_pkgconfig_file(state, libs, subdirs, name, description, version, filebase) + return build.Data(False, state.environment.get_scratch_dir(), [pcfile], pkgroot) + +def initialize(): + return PkgConfigModule() diff --git a/mesonbuild/modules/qt4.py b/mesonbuild/modules/qt4.py new file mode 100644 index 0000000..162b553 --- /dev/null +++ b/mesonbuild/modules/qt4.py @@ -0,0 +1,155 @@ +# 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. + +from .. import dependencies, mlog +import os, subprocess +from .. import build +from ..coredata import MesonException +import xml.etree.ElementTree as ET + +class Qt4Module(): + def __init__(self): + mlog.log('Detecting Qt tools.') + # The binaries have different names on different + # distros. Joy. + self.moc = dependencies.ExternalProgram('moc-qt4', silent=True) + if not self.moc.found(): + self.moc = dependencies.ExternalProgram('moc', silent=True) + self.uic = dependencies.ExternalProgram('uic-qt4', silent=True) + if not self.uic.found(): + self.uic = dependencies.ExternalProgram('uic', silent=True) + self.rcc = dependencies.ExternalProgram('rcc-qt4', silent=True) + if not self.rcc.found(): + self.rcc = dependencies.ExternalProgram('rcc', silent=True) + # Moc, uic and rcc write their version strings to stderr. + # Moc and rcc return a non-zero result when doing so. + # What kind of an idiot thought that was a good idea? + if self.moc.found(): + mp = subprocess.Popen(self.moc.get_command() + ['-v'], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (stdout, stderr) = mp.communicate() + stdout = stdout.decode().strip() + stderr = stderr.decode().strip() + if 'Qt Meta' in stderr: + moc_ver = stderr + else: + raise MesonException('Moc preprocessor is not for Qt 4. Output:\n%s\n%s' % + (stdout, stderr)) + mlog.log(' moc:', mlog.green('YES'), '(%s, %s)' % \ + (' '.join(self.moc.fullpath), moc_ver.split()[-1])) + else: + mlog.log(' moc:', mlog.red('NO')) + if self.uic.found(): + up = subprocess.Popen(self.uic.get_command() + ['-v'], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (stdout, stderr) = up.communicate() + stdout = stdout.decode().strip() + stderr = stderr.decode().strip() + if 'version 4.' in stderr: + uic_ver = stderr + else: + raise MesonException('Uic compiler is not for Qt4. Output:\n%s\n%s' % + (stdout, stderr)) + mlog.log(' uic:', mlog.green('YES'), '(%s, %s)' % \ + (' '.join(self.uic.fullpath), uic_ver.split()[-1])) + else: + mlog.log(' uic:', mlog.red('NO')) + if self.rcc.found(): + rp = subprocess.Popen(self.rcc.get_command() + ['-v'], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (stdout, stderr) = rp.communicate() + stdout = stdout.decode().strip() + stderr = stderr.decode().strip() + if 'version 4.' in stderr: + rcc_ver = stderr + else: + raise MesonException('Rcc compiler is not for Qt 4. Output:\n%s\n%s' % + (stdout, stderr)) + mlog.log(' rcc:', mlog.green('YES'), '(%s, %s)'\ + % (' '.join(self.rcc.fullpath), rcc_ver.split()[-1])) + else: + mlog.log(' rcc:', mlog.red('NO')) + + def parse_qrc(self, state, fname): + abspath = os.path.join(state.environment.source_dir, state.subdir, fname) + relative_part = os.path.split(fname)[0] + try: + tree = ET.parse(abspath) + root = tree.getroot() + result = [] + for child in root[0]: + if child.tag != 'file': + mlog.log("Warning, malformed rcc file: ", os.path.join(state.subdir, fname)) + break + else: + result.append(os.path.join(state.subdir, relative_part, child.text)) + return result + except Exception: + return [] + + def preprocess(self, state, args, kwargs): + rcc_files = kwargs.pop('qresources', []) + if not isinstance(rcc_files, list): + rcc_files = [rcc_files] + ui_files = kwargs.pop('ui_files', []) + if not isinstance(ui_files, list): + ui_files = [ui_files] + moc_headers = kwargs.pop('moc_headers', []) + if not isinstance(moc_headers, list): + moc_headers = [moc_headers] + moc_sources = kwargs.pop('moc_sources', []) + if not isinstance(moc_sources, list): + moc_sources = [moc_sources] + srctmp = kwargs.pop('sources', []) + if not isinstance(srctmp, list): + srctmp = [srctmp] + sources = args[1:] + srctmp + if len(rcc_files) > 0: + rcc_kwargs = {'output' : '@BASENAME@.cpp', + 'arguments' : ['@INPUT@', '-o', '@OUTPUT@']} + rcc_gen = build.Generator([self.rcc], rcc_kwargs) + rcc_output = build.GeneratedList(rcc_gen) + qrc_deps = [] + for i in rcc_files: + qrc_deps += self.parse_qrc(state, i) + rcc_output.extra_depends = qrc_deps + [rcc_output.add_file(os.path.join(state.subdir, a)) for a in rcc_files] + sources.append(rcc_output) + if len(ui_files) > 0: + ui_kwargs = {'output' : 'ui_@BASENAME@.h', + 'arguments' : ['-o', '@OUTPUT@', '@INPUT@']} + ui_gen = build.Generator([self.uic], ui_kwargs) + ui_output = build.GeneratedList(ui_gen) + [ui_output.add_file(os.path.join(state.subdir, a)) for a in ui_files] + sources.append(ui_output) + if len(moc_headers) > 0: + moc_kwargs = {'output' : 'moc_@BASENAME@.cpp', + 'arguments' : ['@INPUT@', '-o', '@OUTPUT@']} + moc_gen = build.Generator([self.moc], moc_kwargs) + moc_output = build.GeneratedList(moc_gen) + [moc_output.add_file(os.path.join(state.subdir, a)) for a in moc_headers] + sources.append(moc_output) + if len(moc_sources) > 0: + moc_kwargs = {'output' : '@BASENAME@.moc', + 'arguments' : ['@INPUT@', '-o', '@OUTPUT@']} + moc_gen = build.Generator([self.moc], moc_kwargs) + moc_output = build.GeneratedList(moc_gen) + [moc_output.add_file(os.path.join(state.subdir, a)) for a in moc_sources] + sources.append(moc_output) + return sources + +def initialize(): + mlog.log('Warning, rcc dependencies will not work properly until this upstream issue is fixed:', + mlog.bold('https://bugreports.qt.io/browse/QTBUG-45460')) + return Qt4Module() diff --git a/mesonbuild/modules/qt5.py b/mesonbuild/modules/qt5.py new file mode 100644 index 0000000..81edc76 --- /dev/null +++ b/mesonbuild/modules/qt5.py @@ -0,0 +1,162 @@ +# 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. + +from .. import dependencies, mlog +import os, subprocess +from .. import build +from ..coredata import MesonException +import xml.etree.ElementTree as ET + +class Qt5Module(): + + def __init__(self): + mlog.log('Detecting Qt tools.') + # The binaries have different names on different + # distros. Joy. + self.moc = dependencies.ExternalProgram('moc-qt5', silent=True) + if not self.moc.found(): + self.moc = dependencies.ExternalProgram('moc', silent=True) + self.uic = dependencies.ExternalProgram('uic-qt5', silent=True) + if not self.uic.found(): + self.uic = dependencies.ExternalProgram('uic', silent=True) + self.rcc = dependencies.ExternalProgram('rcc-qt5', silent=True) + if not self.rcc.found(): + self.rcc = dependencies.ExternalProgram('rcc', silent=True) + # Moc, uic and rcc write their version strings to stderr. + # Moc and rcc return a non-zero result when doing so. + # What kind of an idiot thought that was a good idea? + if self.moc.found(): + mp = subprocess.Popen(self.moc.get_command() + ['-v'], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (stdout, stderr) = mp.communicate() + stdout = stdout.decode().strip() + stderr = stderr.decode().strip() + if 'Qt 5' in stderr: + moc_ver = stderr + elif '5.' in stdout: + moc_ver = stdout + else: + raise MesonException('Moc preprocessor is not for Qt 5. Output:\n%s\n%s' % + (stdout, stderr)) + mlog.log(' moc:', mlog.green('YES'), '(%s, %s)' % \ + (' '.join(self.moc.fullpath), moc_ver.split()[-1])) + else: + mlog.log(' moc:', mlog.red('NO')) + if self.uic.found(): + up = subprocess.Popen(self.uic.get_command() + ['-v'], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (stdout, stderr) = up.communicate() + stdout = stdout.decode().strip() + stderr = stderr.decode().strip() + if 'version 5.' in stderr: + uic_ver = stderr + elif '5.' in stdout: + uic_ver = stdout + else: + raise MesonException('Uic compiler is not for Qt 5. Output:\n%s\n%s' % + (stdout, stderr)) + mlog.log(' uic:', mlog.green('YES'), '(%s, %s)' % \ + (' '.join(self.uic.fullpath), uic_ver.split()[-1])) + else: + mlog.log(' uic:', mlog.red('NO')) + if self.rcc.found(): + rp = subprocess.Popen(self.rcc.get_command() + ['-v'], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (stdout, stderr) = rp.communicate() + stdout = stdout.decode().strip() + stderr = stderr.decode().strip() + if 'version 5.' in stderr: + rcc_ver = stderr + elif '5.' in stdout: + rcc_ver = stdout + else: + raise MesonException('Rcc compiler is not for Qt 5. Output:\n%s\n%s' % + (stdout, stderr)) + mlog.log(' rcc:', mlog.green('YES'), '(%s, %s)'\ + % (' '.join(self.rcc.fullpath), rcc_ver.split()[-1])) + else: + mlog.log(' rcc:', mlog.red('NO')) + + def parse_qrc(self, state, fname): + abspath = os.path.join(state.environment.source_dir, state.subdir, fname) + relative_part = os.path.split(fname)[0] + try: + tree = ET.parse(abspath) + root = tree.getroot() + result = [] + for child in root[0]: + if child.tag != 'file': + mlog.log("Warning, malformed rcc file: ", os.path.join(state.subdir, fname)) + break + else: + result.append(os.path.join(state.subdir, relative_part, child.text)) + return result + except Exception: + return [] + + def preprocess(self, state, args, kwargs): + rcc_files = kwargs.pop('qresources', []) + if not isinstance(rcc_files, list): + rcc_files = [rcc_files] + ui_files = kwargs.pop('ui_files', []) + if not isinstance(ui_files, list): + ui_files = [ui_files] + moc_headers = kwargs.pop('moc_headers', []) + if not isinstance(moc_headers, list): + moc_headers = [moc_headers] + moc_sources = kwargs.pop('moc_sources', []) + if not isinstance(moc_sources, list): + moc_sources = [moc_sources] + srctmp = kwargs.pop('sources', []) + if not isinstance(srctmp, list): + srctmp = [srctmp] + sources = args[1:] + srctmp + if len(rcc_files) > 0: + rcc_kwargs = {'output' : '@BASENAME@.cpp', + 'arguments' : ['@INPUT@', '-o', '@OUTPUT@']} + rcc_gen = build.Generator([self.rcc], rcc_kwargs) + rcc_output = build.GeneratedList(rcc_gen) + qrc_deps = [] + for i in rcc_files: + qrc_deps += self.parse_qrc(state, i) + rcc_output.extra_depends = qrc_deps + [rcc_output.add_file(os.path.join(state.subdir, a)) for a in rcc_files] + sources.append(rcc_output) + if len(ui_files) > 0: + ui_kwargs = {'output' : 'ui_@BASENAME@.h', + 'arguments' : ['-o', '@OUTPUT@', '@INPUT@']} + ui_gen = build.Generator([self.uic], ui_kwargs) + ui_output = build.GeneratedList(ui_gen) + [ui_output.add_file(os.path.join(state.subdir, a)) for a in ui_files] + sources.append(ui_output) + if len(moc_headers) > 0: + moc_kwargs = {'output' : 'moc_@BASENAME@.cpp', + 'arguments' : ['@INPUT@', '-o', '@OUTPUT@']} + moc_gen = build.Generator([self.moc], moc_kwargs) + moc_output = build.GeneratedList(moc_gen) + [moc_output.add_file(os.path.join(state.subdir, a)) for a in moc_headers] + sources.append(moc_output) + if len(moc_sources) > 0: + moc_kwargs = {'output' : '@BASENAME@.moc', + 'arguments' : ['@INPUT@', '-o', '@OUTPUT@']} + moc_gen = build.Generator([self.moc], moc_kwargs) + moc_output = build.GeneratedList(moc_gen) + [moc_output.add_file(os.path.join(state.subdir, a)) for a in moc_sources] + sources.append(moc_output) + return sources + +def initialize(): + mlog.log('Warning, rcc dependencies will not work properly until this upstream issue is fixed:', + mlog.bold('https://bugreports.qt.io/browse/QTBUG-45460')) + return Qt5Module() diff --git a/mesonbuild/modules/rpm.py b/mesonbuild/modules/rpm.py new file mode 100644 index 0000000..a2c0502 --- /dev/null +++ b/mesonbuild/modules/rpm.py @@ -0,0 +1,163 @@ +# 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. + +'''This module provides helper functions for RPM related +functionality such as generating template RPM spec file.''' + +from .. import build +from .. import compilers +from .. import datetime +from .. import mlog +from .. import modules.gnome +import os + +class RPMModule: + + def generate_spec_template(self, state, args, kwargs): + compiler_deps = set() + for compiler in state.compilers: + if isinstance(compiler, compilers.GnuCCompiler): + compiler_deps.add('gcc') + elif isinstance(compiler, compilers.GnuCPPCompiler): + compiler_deps.add('gcc-c++') + elif isinstance(compiler, compilers.ValaCompiler): + compiler_deps.add('vala') + elif isinstance(compiler, compilers.GnuFortranCompiler): + compiler_deps.add('gcc-gfortran') + elif isinstance(compiler, compilers.GnuObjCCompiler): + compiler_deps.add('gcc-objc') + elif compiler == compilers.GnuObjCPPCompiler: + compiler_deps.add('gcc-objc++') + else: + mlog.log('RPM spec file will not created, generating not allowed for:', + mlog.bold(compiler.get_id())) + return + proj = state.project_name.replace(' ', '_').replace('\t', '_') + so_installed = False + devel_subpkg = False + files = set() + files_devel = set() + to_delete = set() + for target in state.targets.values(): + if isinstance(target, build.Executable) and target.need_install: + files.add('%%{_bindir}/%s' % target.get_filename()) + elif isinstance(target, build.SharedLibrary) and target.need_install: + files.add('%%{_libdir}/%s' % target.get_filename()) + for alias in target.get_aliaslist(): + if alias.endswith('.so'): + files_devel.add('%%{_libdir}/%s' % alias) + else: + files.add('%%{_libdir}/%s' % alias) + so_installed = True + elif isinstance(target, build.StaticLibrary) and target.need_install: + to_delete.add('%%{buildroot}%%{_libdir}/%s' % target.get_filename()) + mlog.log('Warning, removing', mlog.bold(target.get_filename()), + 'from package because packaging static libs not recommended') + elif isinstance(target, modules.gnome.GirTarget) and target.should_install(): + files_devel.add('%%{_datadir}/gir-1.0/%s' % target.get_filename()[0]) + elif isinstance(target, modules.gnome.TypelibTarget) and target.should_install(): + files.add('%%{_libdir}/girepository-1.0/%s' % target.get_filename()[0]) + for header in state.headers: + if len(header.get_install_subdir()) > 0: + files_devel.add('%%{_includedir}/%s/' % header.get_install_subdir()) + else: + for hdr_src in header.get_sources(): + files_devel.add('%%{_includedir}/%s' % hdr_src) + for man in state.man: + for man_file in man.get_sources(): + files.add('%%{_mandir}/man%u/%s.*' % (int(man_file.split('.')[-1]), man_file)) + for pkgconfig in state.pkgconfig_gens: + files_devel.add('%%{_libdir}/pkgconfig/%s.pc' % pkgconfig.filebase) + if len(files_devel) > 0: + devel_subpkg = True + fn = open('%s.spec' % os.path.join(state.environment.get_build_dir(), proj), 'w+') + fn.write('Name: %s\n' % proj) + fn.write('Version: # FIXME\n') + fn.write('Release: 1%{?dist}\n') + fn.write('Summary: # FIXME\n') + fn.write('License: # FIXME\n') + fn.write('\n') + fn.write('Source0: %{name}-%{version}.tar.xz # FIXME\n') + fn.write('\n') + for compiler in compiler_deps: + fn.write('BuildRequires: %s\n' % compiler) + for dep in state.environment.coredata.deps: + fn.write('BuildRequires: pkgconfig(%s)\n' % dep) + for lib in state.environment.coredata.ext_libs.values(): + fn.write('BuildRequires: %s # FIXME\n' % lib.fullpath) + mlog.log('Warning, replace', mlog.bold(lib.fullpath), 'with real package.', + 'You can use following command to find package which contains this lib:', + mlog.bold('dnf provides %s' % lib.fullpath)) + for prog in state.environment.coredata.ext_progs.values(): + if not prog.found(): + fn.write('BuildRequires: /usr/bin/%s # FIXME\n' % prog.get_name()) + else: + fn.write('BuildRequires: %s\n' % ' '.join(prog.fullpath)) + fn.write('BuildRequires: meson\n') + fn.write('\n') + fn.write('%description\n') + fn.write('\n') + if devel_subpkg: + fn.write('%package devel\n') + fn.write('Summary: Development files for %{name}\n') + fn.write('Requires: %{name}%{?_isa} = %{version}-%{release}\n') + fn.write('\n') + fn.write('%description devel\n') + fn.write('Development files for %{name}.\n') + fn.write('\n') + fn.write('%prep\n') + fn.write('%autosetup\n') + fn.write('rm -rf rpmbuilddir && mkdir rpmbuilddir\n') + fn.write('\n') + fn.write('%build\n') + fn.write('pushd rpmbuilddir\n') + fn.write(' %meson ..\n') + fn.write(' ninja-build -v\n') + fn.write('popd\n') + fn.write('\n') + fn.write('%install\n') + fn.write('pushd rpmbuilddir\n') + fn.write(' DESTDIR=%{buildroot} ninja-build -v install\n') + fn.write('popd\n') + if len(to_delete) > 0: + fn.write('rm -rf %s\n' % ' '.join(to_delete)) + fn.write('\n') + fn.write('%check\n') + fn.write('pushd rpmbuilddir\n') + fn.write(' ninja-build -v test\n') + fn.write('popd\n') + fn.write('\n') + fn.write('%files\n') + for f in files: + fn.write('%s\n' % f) + fn.write('\n') + if devel_subpkg: + fn.write('%files devel\n') + for f in files_devel: + fn.write('%s\n' % f) + fn.write('\n') + if so_installed: + fn.write('%post -p /sbin/ldconfig\n') + fn.write('\n') + fn.write('%postun -p /sbin/ldconfig\n') + fn.write('\n') + fn.write('%changelog\n') + fn.write('* %s meson <meson@example.com> - \n' % datetime.date.today().strftime('%a %b %d %Y')) + fn.write('- \n') + fn.write('\n') + fn.close() + mlog.log('RPM spec template written to %s.spec.\n' % proj) + +def initialize(): + return RPMModule() diff --git a/mesonbuild/modules/windows.py b/mesonbuild/modules/windows.py new file mode 100644 index 0000000..a785250 --- /dev/null +++ b/mesonbuild/modules/windows.py @@ -0,0 +1,47 @@ +# 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. + +from .. import mesonlib, dependencies, build +from ..coredata import MesonException +import os + +class WindowsModule: + + def detect_compiler(self, compilers): + for c in compilers: + if c.language == 'c' or c.language == 'cpp': + return c + raise MesonException('Resource compilation requires a C or C++ compiler.') + + def compile_resources(self, state, args, kwargs): + comp = self.detect_compiler(state.compilers) + extra_args = mesonlib.stringlistify(kwargs.get('args', [])) + if comp.id == 'msvc': + rescomp = dependencies.ExternalProgram('rc', silent=True) + res_args = extra_args + ['/nologo', '/fo@OUTPUT@', '@INPUT@'] + suffix = 'res' + else: + rescomp = dependencies.ExternalProgram('windres', silent=True) + res_args = extra_args + ['@INPUT@', '@OUTPUT@'] + suffix = 'o' + res_files = mesonlib.stringlistify(args) + res_kwargs = {'output' : '@BASENAME@.' + suffix, + 'arguments': res_args} + res_gen = build.Generator([rescomp], res_kwargs) + res_output = build.GeneratedList(res_gen) + [res_output.add_file(os.path.join(state.subdir, a)) for a in res_files] + return res_output + +def initialize(): + return WindowsModule() diff --git a/mesonbuild/mparser.py b/mesonbuild/mparser.py new file mode 100644 index 0000000..1d569d5 --- /dev/null +++ b/mesonbuild/mparser.py @@ -0,0 +1,565 @@ +# 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 re +from .coredata import MesonException + +class ParseException(MesonException): + def __init__(self, text, lineno, colno): + super().__init__(text) + self.lineno = lineno + self.colno = colno + +class Token: + def __init__(self, tid, lineno, colno, value): + self.tid = tid + self.lineno = lineno + self.colno = colno + self.value = value + + def __eq__(self, other): + if isinstance(other, str): + return self.tid == other + return self.tid == other.tid + +class Lexer: + def __init__(self): + self.keywords = {'true', 'false', 'if', 'else', 'elif', + 'endif', 'and', 'or', 'not', 'foreach', 'endforeach'} + self.token_specification = [ + # Need to be sorted longest to shortest. + ('ignore', re.compile(r'[ \t]')), + ('id', re.compile('[_a-zA-Z][_0-9a-zA-Z]*')), + ('number', re.compile(r'\d+')), + ('eol_cont', re.compile(r'\\\n')), + ('eol', re.compile(r'\n')), + ('multiline_string', re.compile(r"'''(.|\n)*?'''", re.M)), + ('comment', re.compile(r'\#.*')), + ('lparen', re.compile(r'\(')), + ('rparen', re.compile(r'\)')), + ('lbracket', re.compile(r'\[')), + ('rbracket', re.compile(r'\]')), + ('dblquote', re.compile(r'"')), + ('string', re.compile(r"'([^'\\]|(\\.))*'")), + ('comma', re.compile(r',')), + ('plusassign', re.compile(r'\+=')), + ('dot', re.compile(r'\.')), + ('plus', re.compile(r'\+')), + ('dash', re.compile(r'-')), + ('star', re.compile(r'\*')), + ('fslash', re.compile(r'/')), + ('colon', re.compile(r':')), + ('equal', re.compile(r'==')), + ('nequal', re.compile(r'\!=')), + ('assign', re.compile(r'=')), + ] + + def lex(self, code): + lineno = 1 + line_start = 0 + loc = 0; + par_count = 0 + bracket_count = 0 + col = 0 + while(loc < len(code)): + matched = False + value = None + for (tid, reg) in self.token_specification: + mo = reg.match(code, loc) + if mo: + curline = lineno + col = mo.start()-line_start + matched = True + loc = mo.end() + match_text = mo.group() + if tid == 'ignore' or tid == 'comment': + break + elif tid == 'lparen': + par_count += 1 + elif tid == 'rparen': + par_count -= 1 + elif tid == 'lbracket': + bracket_count += 1 + elif tid == 'rbracket': + bracket_count -= 1 + elif tid == 'dblquote': + raise ParseException('Double quotes are not supported. Use single quotes.', lineno, col) + elif tid == 'string': + value = match_text[1:-1].replace(r"\'", "'").replace(r" \\ ".strip(), r" \ ".strip())\ + .replace("\\n", "\n") + elif tid == 'multiline_string': + tid = 'string' + value = match_text[3:-3] + lines = match_text.split('\n') + if len(lines) > 1: + lineno += len(lines) - 1 + line_start = mo.end() - len(lines[-1]) + elif tid == 'number': + value = int(match_text) + elif tid == 'eol' or tid == 'eol_cont': + lineno += 1 + line_start = loc + if par_count > 0 or bracket_count > 0: + break + elif tid == 'id': + if match_text in self.keywords: + tid = match_text + else: + value = match_text + yield Token(tid, curline, col, value) + break + if not matched: + raise ParseException('lexer', lineno, col) + +class BooleanNode: + def __init__(self, token, value): + self.lineno = token.lineno + self.colno = token.colno + self.value = value + assert(isinstance(self.value, bool)) + +class IdNode: + def __init__(self, token): + self.lineno = token.lineno + self.colno = token.colno + self.value = token.value + assert(isinstance(self.value, str)) + + def __str__(self): + return "Id node: '%s' (%d, %d)." % (self.value, self.lineno, self.colno) + +class NumberNode: + def __init__(self, token): + self.lineno = token.lineno + self.colno = token.colno + self.value = token.value + assert(isinstance(self.value, int)) + +class StringNode: + def __init__(self, token): + self.lineno = token.lineno + self.colno = token.colno + self.value = token.value + assert(isinstance(self.value, str)) + + def __str__(self): + return "String node: '%s' (%d, %d)." % (self.value, self.lineno, self.colno) + +class ArrayNode: + def __init__(self, args): + self.lineno = args.lineno + self.colno = args.colno + self.args = args + +class EmptyNode: + def __init__(self): + self.lineno = 0 + self.colno = 0 + self.value = None + +class OrNode: + def __init__(self, lineno, colno, left, right): + self.lineno = lineno + self.colno = colno + self.left = left + self.right = right + +class AndNode: + def __init__(self, lineno, colno, left, right): + self.lineno = lineno + self.colno = colno + self.left = left + self.right = right + +class ComparisonNode: + def __init__(self, lineno, colno, ctype, left, right): + self.lineno = lineno + self.colno = colno + self.left = left + self.right = right + self.ctype = ctype + +class ArithmeticNode: + def __init__(self, lineno, colno, operation, left, right): + self.lineno = lineno + self.colno = colno + self.left = left + self.right = right + self.operation = operation + +class NotNode: + def __init__(self, lineno, colno, value): + self.lineno = lineno + self.colno = colno + self.value = value + +class CodeBlockNode: + def __init__(self, lineno, colno): + self.lineno = lineno + self.colno = colno + self.lines = [] + +class IndexNode: + def __init__(self, iobject, index): + self.iobject = iobject + self.index = index + self.lineno = iobject.lineno + self.colno = iobject.colno + +class MethodNode: + def __init__(self, lineno, colno, source_object, name, args): + self.lineno = lineno + self.colno = colno + self.source_object = source_object + self.name = name + assert(isinstance(self.name, str)) + self.args = args + +class FunctionNode: + def __init__(self, lineno, colno, func_name, args): + self.lineno = lineno + self.colno = colno + self.func_name = func_name + assert(isinstance(func_name, str)) + self.args = args + +class AssignmentNode: + def __init__(self, lineno, colno, var_name, value): + self.lineno = lineno + self.colno = colno + self.var_name = var_name + assert(isinstance(var_name, str)) + self.value = value + +class PlusAssignmentNode: + def __init__(self, lineno, colno, var_name, value): + self.lineno = lineno + self.colno = colno + self.var_name = var_name + assert(isinstance(var_name, str)) + self.value = value + +class ForeachClauseNode(): + def __init__(self, lineno, colno, varname, items, block): + self.lineno = lineno + self.colno = colno + self.varname = varname + self.items = items + self.block = block + +class IfClauseNode(): + def __init__(self, lineno, colno): + self.lineno = lineno + self.colno = colno + self.ifs = [] + self.elseblock = EmptyNode() + +class UMinusNode(): + def __init__(self, lineno, colno, value): + self.lineno = lineno + self.colno = colno + self.value = value + +class IfNode(): + def __init__(self, lineno, colno, condition, block): + self.lineno = lineno + self.colno = colno + self.condition = condition + self.block = block + +class ArgumentNode(): + def __init__(self, token): + self.lineno = token.lineno + self.colno = token.colno + self.arguments = [] + self.kwargs = {} + self.order_error = False + + def prepend(self, statement): + if self.num_kwargs() > 0: + self.order_error = True + if not isinstance(statement, EmptyNode): + self.arguments = [statement] + self.arguments + + def append(self, statement): + if self.num_kwargs() > 0: + self.order_error = True + if not isinstance(statement, EmptyNode): + self.arguments = self.arguments + [statement] + + def set_kwarg(self, name, value): + self.kwargs[name] = value + + def num_args(self): + return len(self.arguments) + + def num_kwargs(self): + return len(self.kwargs) + + def incorrect_order(self): + return self.order_error + + def __len__(self): + return self.num_args() # Fixme + +# Recursive descent parser for Meson's definition language. +# Very basic apart from the fact that we have many precedence +# levels so there are not enough words to describe them all. +# Enter numbering: +# +# 1 assignment +# 2 or +# 3 and +# 4 comparison +# 5 arithmetic +# 6 negation +# 7 funcall, method call +# 8 parentheses +# 9 plain token + +class Parser: + def __init__(self, code): + self.stream = Lexer().lex(code) + self.getsym() + + def getsym(self): + try: + self.current = next(self.stream) + except StopIteration: + self.current = Token('eof', 0, 0, None) + + def accept(self, s): + if self.current.tid == s: + self.getsym() + return True + return False + + def expect(self, s): + if self.accept(s): + return True + raise ParseException('Expecting %s got %s.' % (s, self.current.tid), self.current.lineno, self.current.colno) + + def parse(self): + block = self.codeblock() + self.expect('eof') + return block + + def statement(self): + return self.e1() + + def e1(self): + left = self.e2() + if self.accept('plusassign'): + value = self.e1() + if not isinstance(left, IdNode): + raise ParseException('Plusassignment target must be an id.', left.lineno, left.colno) + return PlusAssignmentNode(left.lineno, left.colno, left.value, value) + elif self.accept('assign'): + value = self.e1() + if not isinstance(left, IdNode): + raise ParseException('Assignment target must be an id.', + left.lineno, left.colno) + return AssignmentNode(left.lineno, left.colno, left.value, value) + return left + + def e2(self): + left = self.e3() + while self.accept('or'): + left = OrNode(left.lineno, left.colno, left, self.e3()) + return left + + def e3(self): + left = self.e4() + while self.accept('and'): + left = AndNode(left.lineno, left.colno, left, self.e4()) + return left + + def e4(self): + left = self.e5() + if self.accept('equal'): + return ComparisonNode(left.lineno, left.colno, '==', left, self.e5()) + if self.accept('nequal'): + return ComparisonNode(left.lineno, left.colno, '!=', left, self.e5()) + return left + + def e5(self): + return self.e5add() + + def e5add(self): + left = self.e5sub() + if self.accept('plus'): + return ArithmeticNode(left.lineno, left.colno, 'add', left, self.e5add()) + return left + + def e5sub(self): + left = self.e5mul() + if self.accept('dash'): + return ArithmeticNode(left.lineno, left.colno, 'sub', left, self.e5sub()) + return left + + def e5mul(self): + left = self.e5div() + if self.accept('star'): + return ArithmeticNode(left.lineno, left.colno, 'mul', left, self.e5mul()) + return left + + def e5div(self): + left = self.e6() + if self.accept('fslash'): + return ArithmeticNode(left.lineno, left.colno, 'div', left, self.e5div()) + return left + + def e6(self): + if self.accept('not'): + return NotNode(self.current.lineno, self.current.colno, self.e7()) + if self.accept('dash'): + return UMinusNode(self.current.lineno, self.current.colno, self.e7()) + return self.e7() + + def e7(self): + left = self.e8() + if self.accept('lparen'): + args = self.args() + self.expect('rparen') + if not isinstance(left, IdNode): + raise ParseException('Function call must be applied to plain id', + left.lineno, left.colno) + left = FunctionNode(left.lineno, left.colno, left.value, args) + go_again = True + while go_again: + go_again = False + if self.accept('dot'): + go_again = True + left = self.method_call(left) + if self.accept('lbracket'): + go_again = True + left = self.index_call(left) + return left + + def e8(self): + if self.accept('lparen'): + e = self.statement() + self.expect('rparen') + return e + elif self.accept('lbracket'): + args = self.args() + self.expect('rbracket') + return ArrayNode(args) + else: + return self.e9() + + def e9(self): + t = self.current + if self.accept('true'): + return BooleanNode(t, True); + if self.accept('false'): + return BooleanNode(t, False) + if self.accept('id'): + return IdNode(t) + if self.accept('number'): + return NumberNode(t) + if self.accept('string'): + return StringNode(t) + return EmptyNode() + + def args(self): + s = self.statement() + a = ArgumentNode(s) + + while not isinstance(s, EmptyNode): + if self.accept('comma'): + a.append(s) + elif self.accept('colon'): + if not isinstance(s, IdNode): + raise ParseException('Keyword argument must be a plain identifier.', + s.lineno, s.colno) + a.set_kwarg(s.value, self.statement()) + if not self.accept('comma'): + return a + else: + a.append(s) + return a + s = self.statement() + return a + + def method_call(self, source_object): + methodname = self.e9() + if not(isinstance(methodname, IdNode)): + raise ParseException('Method name must be plain id', + self.current.lineno, self.current.colno) + self.expect('lparen') + args = self.args() + self.expect('rparen') + method = MethodNode(methodname.lineno, methodname.colno, source_object, methodname.value, args) + if self.accept('dot'): + return self.method_call(method) + return method + + def index_call(self, source_object): + index_statement = self.statement() + self.expect('rbracket') + return IndexNode(source_object, index_statement) + + def foreachblock(self): + t = self.current + self.expect('id') + varname = t + self.expect('colon') + items = self.statement() + block = self.codeblock() + return ForeachClauseNode(varname.lineno, varname.colno, varname, items, block) + + def ifblock(self): + condition = self.statement() + clause = IfClauseNode(condition.lineno, condition.colno) + block = self.codeblock() + clause.ifs.append(IfNode(clause.lineno, clause.colno, condition, block)) + self.elseifblock(clause) + clause.elseblock = self.elseblock() + return clause + + def elseifblock(self, clause): + while self.accept('elif'): + s = self.statement() + self.expect('eol') + b = self.codeblock() + clause.ifs.append(IfNode(s.lineno, s.colno, s, b)) + + def elseblock(self): + if self.accept('else'): + self.expect('eol') + return self.codeblock() + + def line(self): + if self.current == 'eol': + return EmptyNode() + if self.accept('if'): + block = self.ifblock() + self.expect('endif') + return block + if self.accept('foreach'): + block = self.foreachblock() + self.expect('endforeach') + return block + return self.statement() + + def codeblock(self): + block = CodeBlockNode(self.current.lineno, self.current.colno) + cond = True + while cond: + curline = self.line() + if not isinstance(curline, EmptyNode): + block.lines.append(curline) + cond = self.accept('eol') + return block diff --git a/mesonbuild/ninjabackend.py b/mesonbuild/ninjabackend.py new file mode 100644 index 0000000..36c5ce9 --- /dev/null +++ b/mesonbuild/ninjabackend.py @@ -0,0 +1,1819 @@ +# Copyright 2012-2016 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from . import backends +from . import environment, mesonlib +from . import build +from . import mlog +from . import dependencies +from .mesonlib import File +from .backends import InstallData +from .build import InvalidArguments +from .coredata import MesonException +import os, sys, pickle, re +import subprocess, shutil + +if mesonlib.is_windows(): + quote_char = '"' + execute_wrapper = 'cmd /c' +else: + quote_char = "'" + execute_wrapper = '' + +def ninja_quote(text): + return text.replace(' ', '$ ').replace(':', '$:') + +class RawFilename(): + def __init__(self, fname): + self.fname = fname + + def split(self, c): + return self.fname.split(c) + + def startswith(self, s): + return self.fname.startswith(s) + +class NinjaBuildElement(): + def __init__(self, outfilenames, rule, infilenames): + if isinstance(outfilenames, str): + self.outfilenames = [outfilenames] + else: + self.outfilenames = outfilenames + assert(isinstance(rule, str)) + self.rule = rule + if isinstance(infilenames, str): + self.infilenames = [infilenames] + else: + self.infilenames = infilenames + self.deps = [] + self.orderdeps = [] + self.elems = [] + + def add_dep(self, dep): + if isinstance(dep, list): + self.deps += dep + else: + self.deps.append(dep) + + def add_orderdep(self, dep): + if isinstance(dep, list): + self.orderdeps += dep + else: + self.orderdeps.append(dep) + + def add_item(self, name, elems): + if isinstance(elems, str): + elems = [elems] + self.elems.append((name, elems)) + + def write(self, outfile): + line = 'build %s: %s %s' % (' '.join([ninja_quote(i) for i in self.outfilenames]),\ + self.rule, + ' '.join([ninja_quote(i) for i in self.infilenames])) + if len(self.deps) > 0: + line += ' | ' + ' '.join([ninja_quote(x) for x in self.deps]) + if len(self.orderdeps) > 0: + line += ' || ' + ' '.join([ninja_quote(x) for x in self.orderdeps]) + line += '\n' + # This is the only way I could find to make this work on all + # platforms including Windows command shell. Slash is a dir separator + # on Windows, too, so all characters are unambiguous and, more importantly, + # do not require quoting. + line = line.replace('\\', '/') + outfile.write(line) + + for e in self.elems: + (name, elems) = e + should_quote = True + if name == 'DEPFILE' or name == 'DESC' or name == 'pool': + should_quote = False + line = ' %s = ' % name + q_templ = quote_char + "%s" + quote_char + noq_templ = "%s" + newelems = [] + for i in elems: + if not should_quote or i == '&&': # Hackety hack hack + templ = noq_templ + else: + templ = q_templ + i = i.replace('\\', '\\\\') + if quote_char == '"': + i = i.replace('"', '\\"') + newelems.append(templ % ninja_quote(i)) + line += ' '.join(newelems) + line += '\n' + outfile.write(line) + outfile.write('\n') + +class NinjaBackend(backends.Backend): + + def __init__(self, build): + super().__init__(build) + self.source_suffix_in_objs = True + self.ninja_filename = 'build.ninja' + self.fortran_deps = {} + self.all_outputs = {} + + def check_outputs(self, elem): + for n in elem.outfilenames: + if n in self.all_outputs: + raise MesonException('Multiple producers for Ninja target "%s". Please rename your targets.' % n) + self.all_outputs[n] = True + + def detect_vs_dep_prefix(self, outfile, tempfilename): + '''VS writes its dependency in a locale dependent format. + Detect the search prefix to use.''' + if shutil.which('cl') is None: + return outfile + outfile.close() + open(os.path.join(self.environment.get_scratch_dir(), 'incdetect.c'), + 'w').write('''#include<stdio.h> +int dummy; +''') + + pc = subprocess.Popen(['cl', '/showIncludes', '/c', 'incdetect.c'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=self.environment.get_scratch_dir()) + + (stdo, _) = pc.communicate() + + for line in stdo.split(b'\r\n'): + if line.endswith(b'stdio.h'): + matchstr = b':'.join(line.split(b':')[0:2]) + b':' + binfile = open(tempfilename, 'ab') + binfile.write(b'msvc_deps_prefix = ' + matchstr + b'\r\n') + binfile.close() + return open(tempfilename, 'a') + raise MesonException('Could not determine vs dep dependency prefix string.') + + def generate(self, interp): + self.interpreter = interp + outfilename = os.path.join(self.environment.get_build_dir(), self.ninja_filename) + tempfilename = outfilename + '~' + outfile = open(tempfilename, 'w') + outfile.write('# This is the build file for project "%s"\n' % self.build.get_project()) + outfile.write('# It is autogenerated by the Meson build system.\n') + outfile.write('# Do not edit by hand.\n\n') + outfile.write('ninja_required_version = 1.5.1\n\n') + outfile = self.detect_vs_dep_prefix(outfile, tempfilename) + self.generate_rules(outfile) + self.generate_phony(outfile) + outfile.write('# Build rules for targets\n\n') + [self.generate_target(t, outfile) for t in self.build.get_targets().values()] + if len(self.build.pot) > 0: + outfile.write('# Build rules for localisation.\n\n') + self.generate_po(outfile) + outfile.write('# Test rules\n\n') + self.generate_tests(outfile) + outfile.write('# Install rules\n\n') + self.generate_install(outfile) + if self.environment.coredata.get_builtin_option('coverage'): + outfile.write('# Coverage rules\n\n') + self.generate_coverage_rules(outfile) + outfile.write('# Suffix\n\n') + self.generate_ending(outfile) + # Only ovewrite the old build file after the new one has been + # fully created. + outfile.close() + os.replace(tempfilename, outfilename) + self.generate_compdb() + + # http://clang.llvm.org/docs/JSONCompilationDatabase.html + def generate_compdb(self): + ninja_exe = environment.detect_ninja() + builddir = self.environment.get_build_dir() + jsondb = subprocess.check_output([ninja_exe, '-t', 'compdb', 'c_COMPILER', 'cpp_COMPILER'], cwd=builddir) + open(os.path.join(builddir, 'compile_commands.json'), 'wb').write(jsondb) + + # Get all generated headers. Any source file might need them so + # we need to add an order dependency to them. + def get_generated_headers(self, target): + header_deps = [] + for gensource in target.get_generated_sources(): + if isinstance(gensource, build.CustomTarget): + continue + for src in gensource.get_outfilelist(): + if self.environment.is_header(src): + header_deps.append(os.path.join(self.get_target_private_dir(target), src)) + for dep in target.link_targets: + if isinstance(dep, (build.StaticLibrary, build.SharedLibrary)): + header_deps += self.get_generated_headers(dep) + return header_deps + + def generate_target(self, target, outfile): + if isinstance(target, build.CustomTarget): + self.generate_custom_target(target, outfile) + if isinstance(target, build.RunTarget): + self.generate_run_target(target, outfile) + name = target.get_id() + gen_src_deps = [] + if name in self.processed_targets: + return + if isinstance(target, build.Jar): + self.generate_jar_target(target, outfile) + return + if 'rust' in self.environment.coredata.compilers.keys() and self.has_rust(target): + self.generate_rust_target(target, outfile) + return + if 'cs' in self.environment.coredata.compilers.keys() and self.has_cs(target): + self.generate_cs_target(target, outfile) + return + if 'vala' in self.environment.coredata.compilers.keys() and self.has_vala(target): + gen_src_deps += self.generate_vala_compile(target, outfile) + if 'swift' in self.environment.coredata.compilers.keys() and self.has_swift(target): + self.generate_swift_target(target, outfile) + return + self.scan_fortran_module_outputs(target) + # The following deals with C/C++ compilation. + (gen_src, gen_other_deps) = self.process_dep_gens(outfile, target) + gen_src_deps += gen_src + self.process_target_dependencies(target, outfile) + self.generate_custom_generator_rules(target, outfile) + outname = self.get_target_filename(target) + obj_list = [] + use_pch = self.environment.coredata.get_builtin_option('use_pch') + is_unity = self.environment.coredata.get_builtin_option('unity') + if use_pch and target.has_pch(): + pch_objects = self.generate_pch(target, outfile) + else: + pch_objects = [] + header_deps = gen_other_deps + unity_src = [] + unity_deps = [] # Generated sources that must be built before compiling a Unity target. + header_deps += self.get_generated_headers(target) + for gensource in target.get_generated_sources(): + if isinstance(gensource, build.CustomTarget): + for src in gensource.output: + src = os.path.join(self.get_target_dir(gensource), src) + if self.environment.is_source(src) and not self.environment.is_header(src): + if is_unity: + unity_deps.append(os.path.join(self.environment.get_build_dir(), RawFilename(src))) + else: + obj_list.append(self.generate_single_compile(target, outfile, RawFilename(src), True, + header_deps)) + elif self.environment.is_object(src): + obj_list.append(src) + elif self.environment.is_library(src): + pass + else: + # Assume anything not specifically a source file is a header. This is because + # people generate files with weird suffixes (.inc, .fh) that they then include + # in their source files. + header_deps.append(RawFilename(src)) + else: + for src in gensource.get_outfilelist(): + if self.environment.is_object(src): + obj_list.append(os.path.join(self.get_target_private_dir(target), src)) + elif not self.environment.is_header(src): + if is_unity: + if self.has_dir_part(src): + rel_src = src + else: + rel_src = os.path.join(self.get_target_private_dir(target), src) + unity_deps.append(rel_src) + abs_src = os.path.join(self.environment.get_build_dir(), rel_src) + unity_src.append(abs_src) + else: + obj_list.append(self.generate_single_compile(target, outfile, src, True, + header_deps=header_deps)) + src_list = [] + for src in gen_src_deps: + src_list.append(src) + if is_unity: + unity_src.append(os.path.join(self.environment.get_build_dir(), src)) + header_deps.append(src) + else: + # Generated targets are ordered deps because the must exist + # before the sources compiling them are used. After the first + # compile we get precise dependency info from dep files. + # This should work in all cases. If it does not, then just + # move them from orderdeps to proper deps. + obj_list.append(self.generate_single_compile(target, outfile, src, True, [], header_deps)) + for src in target.get_sources(): + if src.endswith('.vala'): + continue + if not self.environment.is_header(src): + src_list.append(src) + if is_unity: + abs_src = os.path.join(self.environment.get_build_dir(), + src.rel_to_builddir(self.build_to_src)) + unity_src.append(abs_src) + else: + obj_list.append(self.generate_single_compile(target, outfile, src, False, [], header_deps)) + obj_list += self.flatten_object_list(target) + if is_unity: + for src in self.generate_unity_files(target, unity_src): + obj_list.append(self.generate_single_compile(target, outfile, src, True, unity_deps + header_deps)) + linker = self.determine_linker(target, src_list) + elem = self.generate_link(target, outfile, outname, obj_list, linker, pch_objects) + self.generate_shlib_aliases(target, self.get_target_dir(target)) + elem.write(outfile) + self.processed_targets[name] = True + + def process_target_dependencies(self, target, outfile): + for t in target.get_dependencies(): + tname = t.get_basename() + t.type_suffix() + if not tname in self.processed_targets: + self.generate_target(t, outfile) + + def generate_custom_target(self, target, outfile): + (srcs, ofilenames, cmd) = self.eval_custom_target_command(target) + deps = [] + for i in target.get_dependencies(): + # FIXME, should not grab element at zero but rather expand all. + if isinstance(i, list): + i = i[0] + fname = i.get_filename() + if isinstance(fname, list): + fname = fname[0] + deps.append(os.path.join(self.get_target_dir(i), fname)) + if target.build_always: + deps.append('PHONY') + elem = NinjaBuildElement(ofilenames, 'CUSTOM_COMMAND', srcs) + for i in target.depend_files: + if isinstance(i, mesonlib.File): + deps.append(i.rel_to_builddir(self.build_to_src)) + else: + deps.append(os.path.join(self.build_to_src, i)) + elem.add_dep(deps) + for d in target.extra_depends: + tmp = d.get_filename() + if not isinstance(tmp, list): + tmp = [tmp] + for fname in tmp: + elem.add_dep(os.path.join(self.get_target_dir(d), fname)) + + elem.add_item('COMMAND', cmd) + elem.add_item('description', 'Generating %s with a custom command.' % target.name) + elem.write(outfile) + self.check_outputs(elem) + self.processed_targets[target.name + target.type_suffix()] = True + + def generate_run_target(self, target, outfile): + runnerscript = os.path.join(self.environment.get_script_dir(), 'commandrunner.py') + deps = [] + arg_strings = [] + for i in target.args: + if isinstance(i, str): + arg_strings.append(i) + elif isinstance(i, (build.BuildTarget, build.CustomTarget)): + relfname = self.get_target_filename(i) + deps.append(relfname) + arg_strings.append(os.path.join(self.environment.get_build_dir(), relfname)) + else: + mlog.debug(str(i)) + raise MesonException('Unreachable code in generate_run_target.') + elem = NinjaBuildElement(target.name, 'CUSTOM_COMMAND', deps) + cmd = [sys.executable, runnerscript, self.environment.get_source_dir(), self.environment.get_build_dir(), target.subdir] + texe = target.command + try: + texe = texe.held_object + except AttributeError: + pass + if isinstance(texe, build.Executable): + abs_exe = os.path.join(self.environment.get_build_dir(), self.get_target_filename(texe)) + deps.append(self.get_target_filename(texe)) + if self.environment.is_cross_build() \ + and self.environment.cross_info.config['binaries'].get('exe_wrapper', None) is not None: + cmd += [self.environment.cross_info.config['binaries']['exe_wrapper']] + cmd.append(abs_exe) + else: + cmd.append(target.command) + cmd += arg_strings + elem.add_item('COMMAND', cmd) + elem.add_item('description', 'Running external command %s.' % target.name) + elem.add_item('pool', 'console') + elem.write(outfile) + self.check_outputs(elem) + self.processed_targets[target.name + target.type_suffix()] = True + + def generate_po(self, outfile): + for p in self.build.pot: + (packagename, languages, subdir) = p + input_file = os.path.join(subdir, 'POTFILES') + elem = NinjaBuildElement('pot', 'GEN_POT', []) + elem.add_item('PACKAGENAME', packagename) + elem.add_item('OUTFILE', packagename + '.pot') + elem.add_item('FILELIST', os.path.join(self.environment.get_source_dir(), input_file)) + elem.add_item('OUTDIR', os.path.join(self.environment.get_source_dir(), subdir)) + elem.write(outfile) + self.check_outputs(elem) + for l in languages: + infile = os.path.join(self.environment.get_source_dir(), subdir, l + '.po') + outfilename = os.path.join(subdir, l + '.gmo') + lelem = NinjaBuildElement(outfilename, 'GEN_GMO', infile) + lelem.add_item('INFILE', infile) + lelem.add_item('OUTFILE', outfilename) + lelem.write(outfile) + self.check_outputs(lelem) + + def generate_coverage_rules(self, outfile): + (gcovr_exe, lcov_exe, genhtml_exe) = environment.find_coverage_tools() + added_rule = False + if gcovr_exe: + added_rule = True + elem = NinjaBuildElement('coverage-xml', 'CUSTOM_COMMAND', '') + elem.add_item('COMMAND', [gcovr_exe, '-x', '-r', self.environment.get_build_dir(),\ + '-o', os.path.join(self.environment.get_log_dir(), 'coverage.xml')]) + elem.add_item('DESC', 'Generating XML coverage report.') + elem.write(outfile) + elem = NinjaBuildElement('coverage-text', 'CUSTOM_COMMAND', '') + elem.add_item('COMMAND', [gcovr_exe, '-r', self.environment.get_build_dir(),\ + '-o', os.path.join(self.environment.get_log_dir(), 'coverage.txt')]) + elem.add_item('DESC', 'Generating text coverage report.') + elem.write(outfile) + self.check_outputs(elem) + if lcov_exe and genhtml_exe: + added_rule = True + phony_elem = NinjaBuildElement('coverage-html', 'phony', 'coveragereport/index.html') + phony_elem.write(outfile) + + elem = NinjaBuildElement('coveragereport/index.html', 'CUSTOM_COMMAND', '') + command = [lcov_exe, '--directory', self.environment.get_build_dir(),\ + '--capture', '--output-file', 'coverage.info', '--no-checksum',\ + '&&', genhtml_exe, '--prefix', self.environment.get_build_dir(),\ + '--output-directory', self.environment.get_log_dir(), '--title', 'Code coverage',\ + '--legend', '--show-details', 'coverage.info'] + elem.add_item('COMMAND', command) + elem.add_item('DESC', 'Generating HTML coverage report.') + self.check_outputs(elem) + elem.write(outfile) + if not added_rule: + mlog.log(mlog.red('Warning:'), 'coverage requested but neither gcovr nor lcov/genhtml found.') + + def generate_install(self, outfile): + script_root = self.environment.get_script_dir() + install_script = os.path.join(script_root, 'meson_install.py') + install_data_file = os.path.join(self.environment.get_scratch_dir(), 'install.dat') + depfixer = [sys.executable, self.environment.get_build_command(), '--internal', 'depfixer'] + d = InstallData(self.environment.get_source_dir(), + self.environment.get_build_dir(), + self.environment.get_prefix(), depfixer) + elem = NinjaBuildElement('install', 'CUSTOM_COMMAND', 'PHONY') + elem.add_dep('all') + elem.add_item('DESC', 'Installing files.') + elem.add_item('COMMAND', [sys.executable, self.environment.get_build_command(), '--internal', 'install', install_data_file]) + elem.add_item('pool', 'console') + self.generate_depmf_install(d) + self.generate_target_install(d) + self.generate_header_install(d) + self.generate_man_install(d) + self.generate_data_install(d) + self.generate_po_install(d, elem) + self.generate_custom_install_script(d) + self.generate_subdir_install(d) + elem.write(outfile) + self.check_outputs(elem) + + ofile = open(install_data_file, 'wb') + pickle.dump(d, ofile) + + def generate_po_install(self, d, elem): + for p in self.build.pot: + (package_name, languages, subdir) = p + # FIXME: assumes only one po package per source + d.po_package_name = package_name + for lang in languages: + rel_src = os.path.join(subdir, lang + '.gmo') + src_file = os.path.join(self.environment.get_build_dir(), rel_src) + d.po.append((src_file, self.environment.coredata.get_builtin_option('localedir'), lang)) + elem.add_dep(rel_src) + + def generate_target_install(self, d): + libdir = self.environment.get_libdir() + bindir = self.environment.get_bindir() + + should_strip = self.environment.coredata.get_builtin_option('strip') + for t in self.build.get_targets().values(): + if t.should_install(): + outdir = t.get_custom_install_dir() + if outdir is None: + if isinstance(t, build.Executable): + outdir = bindir + else: + outdir = libdir + i = [self.get_target_filename(t), outdir, t.get_aliaslist(),\ + should_strip, t.install_rpath] + d.targets.append(i) + + def generate_custom_install_script(self, d): + d.install_scripts = self.build.install_scripts + + def generate_header_install(self, d): + incroot = self.environment.get_includedir() + headers = self.build.get_headers() + + for h in headers: + outdir = h.get_custom_install_dir() + if outdir is None: + outdir = os.path.join(incroot, h.get_install_subdir()) + for f in h.get_sources(): + abspath = os.path.join(self.environment.get_source_dir(), h.get_source_subdir(), f) + i = [abspath, outdir] + d.headers.append(i) + + def generate_man_install(self, d): + manroot = self.environment.get_mandir() + man = self.build.get_man() + for m in man: + for f in m.get_sources(): + num = f.split('.')[-1] + subdir = m.get_custom_install_dir() + if subdir is None: + subdir = os.path.join(manroot, 'man' + num) + srcabs = os.path.join(self.environment.get_source_dir(), m.get_source_subdir(), f) + dstabs = os.path.join(subdir, f + '.gz') + i = [srcabs, dstabs] + d.man.append(i) + + def generate_data_install(self, d): + data = self.build.get_data() + for de in data: + assert(isinstance(de, build.Data)) + subdir = de.install_dir + for f in de.sources: + if de.in_sourcetree: + srcprefix = self.environment.get_source_dir() + else: + srcprefix = self.environment.get_build_dir() + srcabs = os.path.join(srcprefix, de.source_subdir, f) + dstabs = os.path.join(subdir, f) + i = [srcabs, dstabs] + d.data.append(i) + + def generate_subdir_install(self, d): + for sd in self.build.get_install_subdirs(): + src_dir = os.path.join(self.environment.get_source_dir(), sd.source_subdir, sd.installable_subdir) + dst_dir = os.path.join(self.environment.get_prefix(), sd.install_dir) + d.install_subdirs.append([src_dir, dst_dir]) + + def write_test_suite_targets(self, cmd, outfile): + suites = {} + for t in self.build.get_tests(): + for s in t.suite: + suites[s] = True + suites = list(suites.keys()) + suites.sort() + for s in suites: + if s == '': + visible_name = 'for top level tests' + else: + visible_name = s + elem = NinjaBuildElement('test-' + s, 'CUSTOM_COMMAND', ['all', 'PHONY']) + elem.add_item('COMMAND', cmd + ['--suite=' + s]) + elem.add_item('DESC', 'Running test suite %s.' % visible_name) + elem.add_item('pool', 'console') + elem.write(outfile) + self.check_outputs(elem) + + def generate_tests(self, outfile): + self.serialise_tests() + valgrind = environment.find_valgrind() + script_root = self.environment.get_script_dir() + test_data = os.path.join(self.environment.get_scratch_dir(), 'meson_test_setup.dat') + cmd = [sys.executable, self.environment.get_build_command(), '--internal', 'test', test_data] + elem = NinjaBuildElement('test', 'CUSTOM_COMMAND', ['all', 'PHONY']) + elem.add_item('COMMAND', cmd) + elem.add_item('DESC', 'Running all tests.') + elem.add_item('pool', 'console') + elem.write(outfile) + self.check_outputs(elem) + self.write_test_suite_targets(cmd, outfile) + + if valgrind: + velem = NinjaBuildElement('test-valgrind', 'CUSTOM_COMMAND', ['all', 'PHONY']) + velem.add_item('COMMAND', cmd + ['--wrapper=' + valgrind]) + velem.add_item('DESC', 'Running test suite under Valgrind.') + velem.add_item('pool', 'console') + velem.write(outfile) + self.check_outputs(velem) + + # And then benchmarks. + benchmark_script = os.path.join(script_root, 'meson_benchmark.py') + benchmark_data = os.path.join(self.environment.get_scratch_dir(), 'meson_benchmark_setup.dat') + cmd = [sys.executable, self.environment.get_build_command(), '--internal', 'benchmark', benchmark_data] + elem = NinjaBuildElement('benchmark', 'CUSTOM_COMMAND', ['all', 'PHONY']) + elem.add_item('COMMAND', cmd) + elem.add_item('DESC', 'Running benchmark suite.') + elem.add_item('pool', 'console') + elem.write(outfile) + self.check_outputs(elem) + + def generate_rules(self, outfile): + outfile.write('# Rules for compiling.\n\n') + self.generate_compile_rules(outfile) + outfile.write('# Rules for linking.\n\n') + if self.environment.is_cross_build(): + self.generate_static_link_rules(True, outfile) + self.generate_static_link_rules(False, outfile) + self.generate_dynamic_link_rules(outfile) + outfile.write('# Other rules\n\n') + outfile.write('rule CUSTOM_COMMAND\n') + outfile.write(' command = $COMMAND\n') + outfile.write(' description = $DESC\n') + outfile.write(' restat = 1\n\n') + outfile.write('rule REGENERATE_BUILD\n') + c = (quote_char + ninja_quote(sys.executable) + quote_char, + quote_char + ninja_quote(self.environment.get_build_command()) + quote_char, + '--internal', + 'regenerate', + quote_char + ninja_quote(self.environment.get_source_dir()) + quote_char, + quote_char + ninja_quote(self.environment.get_build_dir()) + quote_char) + outfile.write(" command = %s %s %s %s %s %s --backend ninja\n" % c) + outfile.write(' description = Regenerating build files\n') + outfile.write(' generator = 1\n\n') + if len(self.build.pot) > 0: + self.generate_gettext_rules(outfile) + outfile.write('\n') + + def generate_gettext_rules(self, outfile): + rule = 'rule GEN_POT\n' + command = " command = xgettext --package-name=$PACKAGENAME -p $OUTDIR -f $FILELIST -D '%s' -k_ -o $OUTFILE\n" % \ + self.environment.get_source_dir() + desc = " description = Creating pot file for package $PACKAGENAME.\n" + outfile.write(rule) + outfile.write(command) + outfile.write(desc) + outfile.write('\n') + rule = 'rule GEN_GMO\n' + command = ' command = msgfmt $INFILE -o $OUTFILE\n' + desc = ' description = Generating gmo file $OUTFILE\n' + outfile.write(rule) + outfile.write(command) + outfile.write(desc) + outfile.write('\n') + + def generate_phony(self, outfile): + outfile.write('# Phony build target, always out of date\n') + outfile.write('build PHONY: phony\n') + outfile.write('\n') + + def generate_jar_target(self, target, outfile): + fname = target.get_filename() + subdir = target.get_subdir() + outname_rel = os.path.join(self.get_target_dir(target), fname) + src_list = target.get_sources() + class_list = [] + compiler = self.get_compiler_for_source(src_list[0]) + assert(compiler.get_language() == 'java') + c = 'c' + m = '' + e = '' + f = 'f' + main_class = target.get_main_class() + if main_class != '': + e = 'e' + for src in src_list: + plain_class_path = self.generate_single_java_compile(src, target, compiler, outfile) + class_list.append(plain_class_path) + class_dep_list = [os.path.join(self.get_target_private_dir(target), i) for i in class_list] + jar_rule = 'java_LINKER' + commands = [c+m+e+f] + if e != '': + commands.append(main_class) + commands.append(self.get_target_filename(target)) + for cls in class_list: + commands += ['-C', self.get_target_private_dir(target), cls] + elem = NinjaBuildElement(outname_rel, jar_rule, []) + elem.add_dep(class_dep_list) + elem.add_item('ARGS', commands) + elem.write(outfile) + self.check_outputs(elem) + + def generate_cs_resource_tasks(self, target, outfile): + args = [] + deps = [] + for r in target.resources: + rel_sourcefile = os.path.join(self.build_to_src, target.subdir, r) + if r.endswith('.resources'): + a = '-resource:' + rel_sourcefile + elif r.endswith('.txt') or r.endswith('.resx'): + ofilebase = os.path.splitext(os.path.basename(r))[0] + '.resources' + ofilename = os.path.join(self.get_target_private_dir(target), ofilebase) + elem = NinjaBuildElement(ofilename, "CUSTOM_COMMAND", rel_sourcefile) + elem.add_item('COMMAND', ['resgen', rel_sourcefile, ofilename]) + elem.add_item('DESC', 'Compiling resource %s.' % rel_sourcefile) + elem.write(outfile) + self.check_outputs(elem) + deps.append(ofilename) + a = '-resource:' + ofilename + else: + raise InvalidArguments('Unknown resource file %s.' % r) + args.append(a) + return (args, deps) + + def generate_cs_target(self, target, outfile): + buildtype = self.environment.coredata.get_builtin_option('buildtype') + fname = target.get_filename() + outname_rel = os.path.join(self.get_target_dir(target), fname) + src_list = target.get_sources() + compiler = self.get_compiler_for_source(src_list[0]) + assert(compiler.get_language() == 'cs') + rel_srcs = [s.rel_to_builddir(self.build_to_src) for s in src_list] + deps = [] + commands = target.extra_args.get('cs', []) + commands += compiler.get_buildtype_args(buildtype) + if isinstance(target, build.Executable): + commands.append('-target:exe') + elif isinstance(target, build.SharedLibrary): + commands.append('-target:library') + else: + raise MesonException('Unknown C# target type.') + (resource_args, resource_deps) = self.generate_cs_resource_tasks(target, outfile) + commands += resource_args + deps += resource_deps + commands += compiler.get_output_args(outname_rel) + for l in target.link_targets: + lname = os.path.join(self.get_target_dir(l), l.get_filename()) + commands += compiler.get_link_args(lname) + deps.append(lname) + if '-g' in commands: + outputs = [outname_rel, outname_rel + '.mdb'] + else: + outputs = [outname_rel] + elem = NinjaBuildElement(outputs, 'cs_COMPILER', rel_srcs) + elem.add_dep(deps) + elem.add_item('ARGS', commands) + self.check_outputs(elem) + elem.write(outfile) + + def generate_single_java_compile(self, src, target, compiler, outfile): + args = [] + args += compiler.get_buildtype_args(self.environment.coredata.get_builtin_option('buildtype')) + args += compiler.get_output_args(self.get_target_private_dir(target)) + for i in target.include_dirs: + for idir in i.get_incdirs(): + args += ['-sourcepath', os.path.join(self.build_to_src, i.curdir, idir)] + rel_src = src.rel_to_builddir(self.build_to_src) + plain_class_path = src.fname[:-4] + 'class' + rel_obj = os.path.join(self.get_target_private_dir(target), plain_class_path) + element = NinjaBuildElement(rel_obj, compiler.get_language() + '_COMPILER', rel_src) + element.add_item('ARGS', args) + element.write(outfile) + self.check_outputs(element) + return plain_class_path + + def generate_java_link(self, outfile): + rule = 'rule java_LINKER\n' + command = ' command = jar $ARGS\n' + description = ' description = Creating jar $out.\n' + outfile.write(rule) + outfile.write(command) + outfile.write(description) + outfile.write('\n') + + def generate_fastvapi_compile(self, target, valac, outfile): + fastvapis = {} + for s in target.get_sources(): + if not s.endswith('.vala'): + continue + vapibase = os.path.basename(s.fname)[:-4] + 'vapi' + rel_vapi = os.path.join(self.get_target_private_dir(target), vapibase) + args = ['--fast-vapi=' + rel_vapi] + rel_s = s.rel_to_builddir(self.build_to_src) + element = NinjaBuildElement(rel_vapi, valac.get_language() + '_COMPILER', rel_s) + element.add_item('ARGS', args) + element.write(outfile) + self.check_outputs(element) + fastvapis[s] = (vapibase, rel_vapi) + return fastvapis + + def split_vala_sources(self, sources): + src = [] + vapi_src = [] + for s in sources: + if s.endswith('.vapi'): + vapi_src.append(s) + else: + src.append(s) + return (src, vapi_src) + + def generate_vala_compile(self, target, outfile): + """Vala is compiled into C. Set up all necessary build steps here.""" + valac = self.environment.coredata.compilers['vala'] + fast_vapis = self.generate_fastvapi_compile(target, valac, outfile) + generated_c = [] + (src, vapi_src) = self.split_vala_sources(target.get_sources()) + vapi_src = [x.rel_to_builddir(self.build_to_src) for x in vapi_src] + extra_dep_files = [] + for s in src: + if not s.endswith('.vala'): + continue + args = ['-d', self.get_target_private_dir(target)] + sc = os.path.basename(s.fname)[:-4] + 'c' + args += ['-C'] + vapi_order_deps = [] + for (sourcefile, vapi_info) in fast_vapis.items(): + if sourcefile == s: + continue + (vapibase, rel_vapi) = vapi_info + args += ['--use-fast-vapi=' + rel_vapi] + vapi_order_deps.append(rel_vapi) + relsc = os.path.join(self.get_target_private_dir(target), sc) + rel_s = s.rel_to_builddir(self.build_to_src) + args += ['--deps', relsc + '.d'] + if self.environment.coredata.get_builtin_option('werror'): + args += valac.get_werror_args() + for d in target.external_deps: + if isinstance(d, dependencies.PkgConfigDependency): + if d.name == 'glib-2.0' and d.version_requirement is not None \ + and d.version_requirement.startswith(('>=', '==')): + args += ['--target-glib', d.version_requirement[2:]] + args += ['--pkg', d.name] + args += vapi_src + extra_args = [] + + for a in target.extra_args.get('vala', []): + if isinstance(a, File): + relname = a.rel_to_builddir(self.build_to_src) + extra_dep_files.append(relname) + extra_args.append(relname) + else: + extra_args.append(a) + args += extra_args + generated_c += [relsc] + element = NinjaBuildElement(relsc, valac.get_language() + '_COMPILER', rel_s) + element.add_item('ARGS', args) + element.add_orderdep(vapi_order_deps) + element.add_dep(extra_dep_files) + element.write(outfile) + self.check_outputs(element) + return generated_c + + def generate_rust_target(self, target, outfile): + rustc = self.environment.coredata.compilers['rust'] + relsrc = [] + for i in target.get_sources(): + if not rustc.can_compile(i): + raise InvalidArguments('Rust target %s contains a non-rust source file.' % target.get_basename()) + relsrc.append(i.rel_to_builddir(self.build_to_src)) + target_name = os.path.join(target.subdir, target.get_filename()) + args = ['--crate-type'] + if isinstance(target, build.Executable): + cratetype = 'bin' + elif isinstance(target, build.SharedLibrary): + cratetype = 'rlib' + elif isinstance(target, build.StaticLibrary): + cratetype = 'rlib' + else: + raise InvalidArguments('Unknown target type for rustc.') + args.append(cratetype) + args += rustc.get_buildtype_args(self.environment.coredata.get_builtin_option('buildtype')) + depfile = target.name + '.d' + args += ['--out-dir', target.subdir] + args += ['--emit', 'dep-info', '--emit', 'link'] + orderdeps = [os.path.join(t.subdir, t.get_filename()) for t in target.link_targets] + linkdirs = {} + for d in target.link_targets: + linkdirs[d.subdir] = True + for d in linkdirs.keys(): + if d == '': + d = '.' + args += ['-L', d] + element = NinjaBuildElement(target_name, 'rust_COMPILER', relsrc) + if len(orderdeps) > 0: + element.add_orderdep(orderdeps) + element.add_item('ARGS', args) + element.add_item('targetdep', depfile) + element.add_item('cratetype', cratetype) + element.write(outfile) + self.check_outputs(element) + + def swift_module_file_name(self, target): + return os.path.join(self.get_target_private_dir(target), + self.target_swift_modulename(target) + '.swiftmodule') + + def target_swift_modulename(self, target): + return target.name + + def is_swift_target(self, target): + for s in target.sources: + if s.endswith('swift'): + return True + return False + + def determine_swift_dep_modules(self, target): + result = [] + for l in target.link_targets: + if self.is_swift_target(l): + result.append(self.swift_module_file_name(l)) + return result + + def determine_swift_dep_dirs(self, target): + result = [] + for l in target.link_targets: + result.append(self.get_target_private_dir_abs(l)) + return result + + def get_swift_link_deps(self, target): + result = [] + for l in target.link_targets: + result.append(self.get_target_filename(l)) + return result + + def split_swift_generated_sources(self, target): + all_srcs = [] + for genlist in target.get_generated_sources(): + if isinstance(genlist, build.CustomTarget): + for ifile in genlist.get_filename(): + rel = os.path.join(self.get_target_dir(genlist), ifile) + all_srcs.append(rel) + else: + for ifile in genlist.get_outfilelist(): + rel = os.path.join(self.get_target_private_dir(target), ifile) + all_srcs.append(rel) + srcs = [] + others = [] + for i in all_srcs: + if i.endswith('.swift'): + srcs.append(i) + else: + others.append(i) + return (srcs, others) + + def generate_swift_target(self, target, outfile): + module_name = self.target_swift_modulename(target) + swiftc = self.environment.coredata.compilers['swift'] + abssrc = [] + abs_headers = [] + header_imports = [] + for i in target.get_sources(): + if swiftc.can_compile(i): + relsrc = i.rel_to_builddir(self.build_to_src) + abss = os.path.normpath(os.path.join(self.environment.get_build_dir(), relsrc)) + abssrc.append(abss) + elif self.environment.is_header(i): + relh = i.rel_to_builddir(self.build_to_src) + absh = os.path.normpath(os.path.join(self.environment.get_build_dir(), relh)) + abs_headers.append(absh) + header_imports += swiftc.get_header_import_args(absh) + else: + raise InvalidArguments('Swift target %s contains a non-swift source file.' % target.get_basename()) + os.makedirs(self.get_target_private_dir_abs(target), exist_ok=True) + compile_args = swiftc.get_compile_only_args() + compile_args += swiftc.get_module_args(module_name) + link_args = swiftc.get_output_args(os.path.join(self.environment.get_build_dir(), self.get_target_filename(target))) + rundir = self.get_target_private_dir(target) + out_module_name = self.swift_module_file_name(target) + in_module_files = self.determine_swift_dep_modules(target) + abs_module_dirs = self.determine_swift_dep_dirs(target) + module_includes = [] + for x in abs_module_dirs: + module_includes += swiftc.get_include_args(x) + link_deps = self.get_swift_link_deps(target) + abs_link_deps = [os.path.join(self.environment.get_build_dir(), x) for x in link_deps] + (rel_generated, _) = self.split_swift_generated_sources(target) + abs_generated = [os.path.join(self.environment.get_build_dir(), x) for x in rel_generated] + # We need absolute paths because swiftc needs to be invoked in a subdir + # and this is the easiest way about it. + objects = [] # Relative to swift invocation dir + rel_objects = [] # Relative to build.ninja + for i in abssrc + abs_generated: + base = os.path.split(i)[1] + oname = os.path.splitext(base)[0] + '.o' + objects.append(oname) + rel_objects.append(os.path.join(self.get_target_private_dir(target), oname)) + + # Swiftc does not seem to be able to emit objects and module files in one go. + elem = NinjaBuildElement(rel_objects, + 'swift_COMPILER', + abssrc) + elem.add_dep(in_module_files + rel_generated) + elem.add_dep(abs_headers) + elem.add_item('ARGS', compile_args + header_imports + abs_generated + module_includes) + elem.add_item('RUNDIR', rundir) + elem.write(outfile) + self.check_outputs(elem) + elem = NinjaBuildElement(out_module_name, + 'swift_COMPILER', + abssrc) + elem.add_dep(in_module_files + rel_generated) + elem.add_item('ARGS', compile_args + abs_generated + module_includes + swiftc.get_mod_gen_args()) + elem.add_item('RUNDIR', rundir) + elem.write(outfile) + self.check_outputs(elem) + if isinstance(target, build.StaticLibrary): + elem = self.generate_link(target, outfile, self.get_target_filename(target), + rel_objects, self.build.static_linker) + elem.write(outfile) + elif isinstance(target, build.Executable): + elem = NinjaBuildElement(self.get_target_filename(target), 'swift_COMPILER', []) + elem.add_dep(rel_objects) + elem.add_dep(link_deps) + elem.add_item('ARGS', link_args + swiftc.get_std_exe_link_args() + objects + abs_link_deps) + elem.add_item('RUNDIR', rundir) + elem.write(outfile) + self.check_outputs(elem) + else: + raise MesonException('Swift supports only executable and static library targets.') + + def generate_static_link_rules(self, is_cross, outfile): + if self.build.has_language('java'): + if not is_cross: + self.generate_java_link(outfile) + if is_cross: + if self.environment.cross_info.need_cross_compiler(): + static_linker = self.build.static_cross_linker + else: + static_linker = self.build.static_linker + crstr = '_CROSS' + else: + static_linker = self.build.static_linker + crstr = '' + if static_linker is None: + return + rule = 'rule STATIC%s_LINKER\n' % crstr + if mesonlib.is_windows(): + command_templ = ''' command = %s @$out.rsp + rspfile = $out.rsp + rspfile_content = $LINK_ARGS %s $in +''' + else: + command_templ = ' command = %s $LINK_ARGS %s $in\n' + command = command_templ %\ + (' '.join(static_linker.get_exelist()), + ' '.join(static_linker.get_output_args('$out'))) + description = ' description = Static linking library $out\n\n' + outfile.write(rule) + outfile.write(command) + outfile.write(description) + + def generate_dynamic_link_rules(self, outfile): + ctypes = [(self.build.compilers, False)] + if self.environment.is_cross_build(): + if self.environment.cross_info.need_cross_compiler(): + ctypes.append((self.build.cross_compilers, True)) + else: + # Native compiler masquerades as the cross compiler. + ctypes.append((self.build.compilers, True)) + else: + ctypes.append((self.build.cross_compilers, True)) + for (complist, is_cross) in ctypes: + for compiler in complist: + langname = compiler.get_language() + if langname == 'java' or langname == 'vala' or\ + langname == 'rust' or langname == 'cs': + continue + crstr = '' + cross_args = [] + if is_cross: + crstr = '_CROSS' + try: + cross_args = self.environment.cross_info.config['properties'][langname + '_link_args'] + except KeyError: + pass + rule = 'rule %s%s_LINKER\n' % (langname, crstr) + if mesonlib.is_windows(): + command_template = ''' command = %s @$out.rsp + rspfile = $out.rsp + rspfile_content = %s $ARGS %s $in $LINK_ARGS $aliasing +''' + else: + command_template = ' command = %s %s $ARGS %s $in $LINK_ARGS $aliasing\n' + command = command_template % \ + (' '.join(compiler.get_linker_exelist()),\ + ' '.join(cross_args),\ + ' '.join(compiler.get_linker_output_args('$out'))) + description = ' description = Linking target $out' + outfile.write(rule) + outfile.write(command) + outfile.write(description) + outfile.write('\n') + scriptdir = self.environment.get_script_dir() + outfile.write('\n') + symrule = 'rule SHSYM\n' + symcmd = ' command = "%s" "%s" %s %s %s %s $CROSS\n' % (ninja_quote(sys.executable), + self.environment.get_build_command(), + '--internal', + 'symbolextractor', + '$in', + '$out') + synstat = ' restat = 1\n' + syndesc = ' description = Generating symbol file $out.\n' + outfile.write(symrule) + outfile.write(symcmd) + outfile.write(synstat) + outfile.write(syndesc) + outfile.write('\n') + + def generate_java_compile_rule(self, compiler, outfile): + rule = 'rule %s_COMPILER\n' % compiler.get_language() + invoc = ' '.join([ninja_quote(i) for i in compiler.get_exelist()]) + command = ' command = %s $ARGS $in\n' % invoc + description = ' description = Compiling Java object $in.\n' + outfile.write(rule) + outfile.write(command) + outfile.write(description) + outfile.write('\n') + + def generate_cs_compile_rule(self, compiler, outfile): + rule = 'rule %s_COMPILER\n' % compiler.get_language() + invoc = ' '.join([ninja_quote(i) for i in compiler.get_exelist()]) + command = ' command = %s $ARGS $in\n' % invoc + description = ' description = Compiling cs target $out.\n' + outfile.write(rule) + outfile.write(command) + outfile.write(description) + outfile.write('\n') + + def generate_vala_compile_rules(self, compiler, outfile): + rule = 'rule %s_COMPILER\n' % compiler.get_language() + invoc = ' '.join([ninja_quote(i) for i in compiler.get_exelist()]) + command = ' command = %s $ARGS $in\n' % invoc + description = ' description = Compiling Vala source $in.\n' + restat = ' restat = 1\n' # ValaC does this always to take advantage of it. + depfile = ' depfile = $out.d\n' + depstyle = ' deps = gcc\n' + outfile.write(rule) + outfile.write(command) + outfile.write(description) + outfile.write(restat) + outfile.write(depfile) + outfile.write(depstyle) + outfile.write('\n') + + def generate_rust_compile_rules(self, compiler, outfile): + rule = 'rule %s_COMPILER\n' % compiler.get_language() + invoc = ' '.join([ninja_quote(i) for i in compiler.get_exelist()]) + command = ' command = %s $ARGS $in\n' % invoc + description = ' description = Compiling Rust source $in.\n' + depfile = ' depfile = $targetdep\n' + + depstyle = ' deps = gcc\n' + outfile.write(rule) + outfile.write(command) + outfile.write(description) + outfile.write(depfile) + outfile.write(depstyle) + outfile.write('\n') + + def generate_swift_compile_rules(self, compiler, outfile): + rule = 'rule %s_COMPILER\n' % compiler.get_language() + full_exe = [sys.executable, + os.path.join(self.environment.get_script_dir(), 'dirchanger.py'), + '$RUNDIR'] + compiler.get_exelist() + invoc = ' '.join([ninja_quote(i) for i in full_exe]) + command = ' command = %s $ARGS $in\n' % invoc + description = ' description = Compiling Swift source $in.\n' + outfile.write(rule) + outfile.write(command) + outfile.write(description) + outfile.write('\n') + + def generate_fortran_dep_hack(self, outfile): + if mesonlib.is_windows(): + cmd = 'cmd /C ""' + else: + cmd = 'true' + template = '''# Workaround for these issues: +# https://groups.google.com/forum/#!topic/ninja-build/j-2RfBIOd_8 +# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485 +rule FORTRAN_DEP_HACK + command = %s + description = Dep hack + restat = 1 + +''' + outfile.write(template % cmd) + + def generate_compile_rule_for(self, langname, compiler, qstr, is_cross, outfile): + if langname == 'java': + if not is_cross: + self.generate_java_compile_rule(compiler, outfile) + return + if langname == 'cs': + if not is_cross: + self.generate_cs_compile_rule(compiler, outfile) + return + if langname == 'vala': + if not is_cross: + self.generate_vala_compile_rules(compiler, outfile) + return + if langname == 'rust': + if not is_cross: + self.generate_rust_compile_rules(compiler, outfile) + return + if langname == 'swift': + if not is_cross: + self.generate_swift_compile_rules(compiler, outfile) + return + if langname == 'fortran': + self.generate_fortran_dep_hack(outfile) + if is_cross: + crstr = '_CROSS' + else: + crstr = '' + rule = 'rule %s%s_COMPILER\n' % (langname, crstr) + depargs = compiler.get_dependency_gen_args('$out', '$DEPFILE') + quoted_depargs = [] + for d in depargs: + if d != '$out' and d != '$in': + d = qstr % d + quoted_depargs.append(d) + cross_args = [] + if is_cross: + try: + cross_args = self.environment.cross_info.config['properties'][langname + '_args'] + except KeyError: + pass + if mesonlib.is_windows(): + command_template = ''' command = %s @$out.rsp + rspfile = $out.rsp + rspfile_content = %s $ARGS %s %s %s $in +''' + else: + command_template = ' command = %s %s $ARGS %s %s %s $in\n' + command = command_template % \ + (' '.join(compiler.get_exelist()),\ + ' '.join(cross_args), + ' '.join(quoted_depargs),\ + ' '.join(compiler.get_output_args('$out')),\ + ' '.join(compiler.get_compile_only_args())) + description = ' description = Compiling %s object $out\n' % langname + if compiler.get_id() == 'msvc': + deps = ' deps = msvc\n' + else: + deps = ' deps = gcc\n' + deps += ' depfile = $DEPFILE\n' + outfile.write(rule) + outfile.write(command) + outfile.write(deps) + outfile.write(description) + outfile.write('\n') + + def generate_pch_rule_for(self, langname, compiler, qstr, is_cross, outfile): + if langname != 'c' and langname != 'cpp': + return + if is_cross: + crstr = '_CROSS' + else: + crstr = '' + rule = 'rule %s%s_PCH\n' % (langname, crstr) + depargs = compiler.get_dependency_gen_args('$out', '$DEPFILE') + cross_args = [] + if is_cross: + try: + cross_args = self.environment.cross_info.config['properties'][langname + '_args'] + except KeyError: + pass + + quoted_depargs = [] + for d in depargs: + if d != '$out' and d != '$in': + d = qstr % d + quoted_depargs.append(d) + if compiler.get_id() == 'msvc': + output = '' + else: + output = ' '.join(compiler.get_output_args('$out')) + command = " command = %s %s $ARGS %s %s %s $in\n" % \ + (' '.join(compiler.get_exelist()),\ + ' '.join(cross_args),\ + ' '.join(quoted_depargs),\ + output,\ + ' '.join(compiler.get_compile_only_args())) + description = ' description = Precompiling header %s\n' % '$in' + if compiler.get_id() == 'msvc': + deps = ' deps = msvc\n' + else: + deps = ' deps = gcc\n' + deps += ' depfile = $DEPFILE\n' + outfile.write(rule) + outfile.write(command) + outfile.write(deps) + outfile.write(description) + outfile.write('\n') + + def generate_compile_rules(self, outfile): + qstr = quote_char + "%s" + quote_char + for compiler in self.build.compilers: + langname = compiler.get_language() + self.generate_compile_rule_for(langname, compiler, qstr, False, outfile) + self.generate_pch_rule_for(langname, compiler, qstr, False, outfile) + if self.environment.is_cross_build(): + # In case we are going a target-only build, make the native compilers + # masquerade as cross compilers. + if self.environment.cross_info.need_cross_compiler(): + cclist = self.build.cross_compilers + else: + cclist = self.build.compilers + for compiler in cclist: + langname = compiler.get_language() + self.generate_compile_rule_for(langname, compiler, qstr, True, outfile) + self.generate_pch_rule_for(langname, compiler, qstr, True, outfile) + outfile.write('\n') + + def replace_outputs(self, args, private_dir, output_list): + newargs = [] + regex = re.compile('@OUTPUT(\d+)@') + for arg in args: + m = regex.search(arg) + while m is not None: + index = int(m.group(1)) + src = '@OUTPUT%d@' % index + arg = arg.replace(src, os.path.join(private_dir, output_list[index])) + m = regex.search(arg) + newargs.append(arg) + return newargs + + def generate_custom_generator_rules(self, target, outfile): + for genlist in target.get_generated_sources(): + if isinstance(genlist, build.CustomTarget): + continue # Customtarget has already written its output rules + generator = genlist.get_generator() + exe = generator.get_exe() + exe_arr = self.exe_object_to_cmd_array(exe) + infilelist = genlist.get_infilelist() + outfilelist = genlist.get_outfilelist() + base_args = generator.get_arglist() + extra_dependencies = [os.path.join(self.build_to_src, i) for i in genlist.extra_depends] + for i in range(len(infilelist)): + if len(generator.outputs) == 1: + sole_output = os.path.join(self.get_target_private_dir(target), outfilelist[i]) + else: + sole_output = '' + curfile = infilelist[i] + infilename = os.path.join(self.build_to_src, curfile) + outfiles = genlist.get_outputs_for(curfile) + outfiles = [os.path.join(self.get_target_private_dir(target), of) for of in outfiles] + args = [x.replace("@INPUT@", infilename).replace('@OUTPUT@', sole_output)\ + for x in base_args] + args = self.replace_outputs(args, self.get_target_private_dir(target), outfilelist) + # We have consumed output files, so drop them from the list of remaining outputs. + if sole_output == '': + outfilelist = outfilelist[len(generator.outputs):] + relout = self.get_target_private_dir(target) + args = [x.replace("@SOURCE_DIR@", self.build_to_src).replace("@BUILD_DIR@", relout) + for x in args] + final_args = [] + for a in args: + if a == '@EXTRA_ARGS@': + final_args += genlist.get_extra_args() + else: + final_args.append(a) + cmdlist = exe_arr + final_args + elem = NinjaBuildElement(outfiles, 'CUSTOM_COMMAND', infilename) + if len(extra_dependencies) > 0: + elem.add_dep(extra_dependencies) + elem.add_item('DESC', 'Generating $out') + if isinstance(exe, build.BuildTarget): + elem.add_dep(self.get_target_filename(exe)) + elem.add_item('COMMAND', cmdlist) + elem.write(outfile) + self.check_outputs(elem) + + def scan_fortran_module_outputs(self, target): + compiler = None + for c in self.build.compilers: + if c.get_language() == 'fortran': + compiler = c + break + if compiler is None: + self.fortran_deps[target.get_basename()] = {} + return + modre = re.compile(r"\s*module\s+(\w+)", re.IGNORECASE) + module_files = {} + for s in target.get_sources(): + # FIXME, does not work for generated Fortran sources, + # but those are really rare. I hope. + if not compiler.can_compile(s): + continue + for line in open(os.path.join(self.environment.get_source_dir(), s.subdir, s.fname)): + modmatch = modre.match(line) + if modmatch is not None: + modname = modmatch.group(1) + if modname.lower() == 'procedure': # MODULE PROCEDURE construct + continue + if modname in module_files: + raise InvalidArguments('Namespace collision: module %s defined in two files %s and %s.' % + (modname, module_files[modname], s)) + module_files[modname] = s + self.fortran_deps[target.get_basename()] = module_files + + def get_fortran_deps(self, compiler, src, target): + mod_files = [] + usere = re.compile(r"\s*use\s+(\w+)", re.IGNORECASE) + dirname = self.get_target_private_dir(target) + tdeps= self.fortran_deps[target.get_basename()] + for line in open(src): + usematch = usere.match(line) + if usematch is not None: + usename = usematch.group(1) + if usename not in tdeps: + # The module is not provided by any source file. This is due to + # a) missing file/typo/etc + # b) using a module provided by the compiler, such as OpenMP + # There's no easy way to tell which is which (that I know of) + # so just ignore this and go on. Ideally we would print a + # warning message to the user but this is a common occurrance, + # which would lead to lots of distracting noise. + continue + mod_source_file = tdeps[usename] + # Check if a source uses a module it exports itself. + # Potential bug if multiple targets have a file with + # the same name. + if mod_source_file.fname == os.path.split(src)[1]: + continue + mod_name = compiler.module_name_to_filename(usematch.group(1)) + mod_files.append(os.path.join(dirname, mod_name)) + return mod_files + + def generate_single_compile(self, target, outfile, src, is_generated=False, header_deps=[], order_deps=[]): + if(isinstance(src, str) and src.endswith('.h')): + raise RuntimeError('Fug') + if isinstance(src, RawFilename) and src.fname.endswith('.h'): + raise RuntimeError('Fug') + extra_orderdeps = [] + compiler = self.get_compiler_for_source(src) + commands = self.generate_basic_compiler_args(target, compiler) + commands += compiler.get_include_args(self.get_target_private_dir(target), False) + curdir = target.get_subdir() + tmppath = os.path.normpath(os.path.join(self.build_to_src, curdir)) + commands += compiler.get_include_args(tmppath, False) + if curdir == '': + curdir = '.' + commands += compiler.get_include_args(curdir, False) + for d in target.external_deps: + if d.need_threads(): + commands += compiler.thread_flags() + break + if isinstance(src, RawFilename): + rel_src = src.fname + elif is_generated: + if self.has_dir_part(src): + rel_src = src + else: + rel_src = os.path.join(self.get_target_private_dir(target), src) + abs_src = os.path.join(self.environment.get_source_dir(), rel_src) + else: + if isinstance(src, File): + rel_src = src.rel_to_builddir(self.build_to_src) + else: + raise build.InvalidArguments('Invalid source type.') + abs_src = os.path.join(self.environment.get_build_dir(), rel_src) + if isinstance(src, RawFilename): + src_filename = src.fname + elif isinstance(src, File): + src_filename = src.fname + elif os.path.isabs(src): + src_filename = os.path.basename(src) + else: + src_filename = src + obj_basename = src_filename.replace('/', '_').replace('\\', '_') + rel_obj = os.path.join(self.get_target_private_dir(target), obj_basename) + rel_obj += '.' + self.environment.get_object_suffix() + dep_file = compiler.depfile_for_object(rel_obj) + if self.environment.coredata.get_builtin_option('use_pch'): + pchlist = target.get_pch(compiler.language) + else: + pchlist = [] + if len(pchlist) == 0: + pch_dep = [] + else: + arr = [] + i = os.path.join(self.get_target_private_dir(target), compiler.get_pch_name(pchlist[0])) + arr.append(i) + pch_dep = arr + for i in target.get_include_dirs(): + basedir = i.get_curdir() + for d in i.get_incdirs(): + expdir = os.path.join(basedir, d) + srctreedir = os.path.join(self.build_to_src, expdir) + bargs = compiler.get_include_args(expdir, i.is_system) + sargs = compiler.get_include_args(srctreedir, i.is_system) + commands += bargs + commands += sargs + for d in i.get_extra_build_dirs(): + commands += compiler.get_include_args(d, i.is_system) + custom_target_include_dirs = [] + for i in target.generated: + if isinstance(i, build.CustomTarget): + idir = self.get_target_dir(i) + if idir not in custom_target_include_dirs: + custom_target_include_dirs.append(idir) + for i in custom_target_include_dirs: + commands+= compiler.get_include_args(i, False) + if self.environment.coredata.get_builtin_option('use_pch'): + commands += self.get_pch_include_args(compiler, target) + crstr = '' + if target.is_cross: + crstr = '_CROSS' + compiler_name = '%s%s_COMPILER' % (compiler.get_language(), crstr) + extra_deps = [] + if compiler.get_language() == 'fortran': + extra_deps += self.get_fortran_deps(compiler, abs_src, target) + # Dependency hack. Remove once multiple outputs in Ninja is fixed: + # https://groups.google.com/forum/#!topic/ninja-build/j-2RfBIOd_8 + for modname, srcfile in self.fortran_deps[target.get_basename()].items(): + modfile = os.path.join(self.get_target_private_dir(target), + compiler.module_name_to_filename(modname)) + if srcfile == src: + depelem = NinjaBuildElement(modfile, 'FORTRAN_DEP_HACK', rel_obj) + depelem.write(outfile) + self.check_outputs(depelem) + commands += compiler.get_module_outdir_args(self.get_target_private_dir(target)) + + element = NinjaBuildElement(rel_obj, compiler_name, rel_src) + for d in header_deps: + if isinstance(d, RawFilename): + d = d.fname + elif not self.has_dir_part(d): + d = os.path.join(self.get_target_private_dir(target), d) + element.add_dep(d) + for d in extra_deps: + element.add_dep(d) + for d in order_deps: + if isinstance(d, RawFilename): + d = d.fname + elif not self.has_dir_part(d): + d = os.path.join(self.get_target_private_dir(target), d) + element.add_orderdep(d) + element.add_orderdep(pch_dep) + element.add_orderdep(extra_orderdeps) + for i in self.get_fortran_orderdeps(target, compiler): + element.add_orderdep(i) + element.add_item('DEPFILE', dep_file) + element.add_item('ARGS', commands) + element.write(outfile) + self.check_outputs(element) + return rel_obj + + def has_dir_part(self, fname): + return '/' in fname or '\\' in fname + + # Fortran is a bit weird (again). When you link against a library, just compiling a source file + # requires the mod files that are output when single files are built. To do this right we would need to + # scan all inputs and write out explicit deps for each file. That is stoo slow and too much effort so + # instead just have an ordered dependendy on the library. This ensures all required mod files are created. + # The real deps are then detected via dep file generation from the compiler. This breaks on compilers that + # produce incorrect dep files but such is life. + def get_fortran_orderdeps(self, target, compiler): + if compiler.language != 'fortran': + return [] + return [os.path.join(self.get_target_dir(lt), lt.get_filename()) for lt in target.link_targets] + + def generate_msvc_pch_command(self, target, compiler, pch): + if len(pch) != 2: + raise RuntimeError('MSVC requires one header and one source to produce precompiled headers.') + header = pch[0] + source = pch[1] + pchname = compiler.get_pch_name(header) + dst = os.path.join(self.get_target_private_dir(target), pchname) + + commands = [] + commands += self.generate_basic_compiler_args(target, compiler) + just_name = os.path.split(header)[1] + (objname, pch_args) = compiler.gen_pch_args(just_name, source, dst) + commands += pch_args + dep = dst + '.' + compiler.get_depfile_suffix() + return (commands, dep, dst, [objname]) + + def generate_gcc_pch_command(self, target, compiler, pch): + commands = [] + commands += self.generate_basic_compiler_args(target, compiler) + dst = os.path.join(self.get_target_private_dir(target), + os.path.split(pch)[-1] + '.' + compiler.get_pch_suffix()) + dep = dst + '.' + compiler.get_depfile_suffix() + return (commands, dep, dst, []) # Gcc does not create an object file during pch generation. + + def generate_pch(self, target, outfile): + cstr = '' + pch_objects = [] + if target.is_cross: + cstr = '_CROSS' + for lang in ['c', 'cpp']: + pch = target.get_pch(lang) + if len(pch) == 0: + continue + if '/' not in pch[0] or '/' not in pch[-1]: + raise build.InvalidArguments('Precompiled header of "%s" must not be in the same directory as source, please put it in a subdirectory.' % target.get_basename()) + compiler = self.get_compiler_for_lang(lang) + if compiler.id == 'msvc': + src = os.path.join(self.build_to_src, target.get_source_subdir(), pch[-1]) + (commands, dep, dst, objs) = self.generate_msvc_pch_command(target, compiler, pch) + extradep = os.path.join(self.build_to_src, target.get_source_subdir(), pch[0]) + else: + src = os.path.join(self.build_to_src, target.get_source_subdir(), pch[0]) + (commands, dep, dst, objs) = self.generate_gcc_pch_command(target, compiler, pch[0]) + extradep = None + pch_objects += objs + rulename = compiler.get_language() + cstr + '_PCH' + elem = NinjaBuildElement(dst, rulename, src) + if extradep is not None: + elem.add_dep(extradep) + elem.add_item('ARGS', commands) + elem.add_item('DEPFILE', dep) + elem.write(outfile) + self.check_outputs(elem) + return pch_objects + + def generate_shsym(self, outfile, target): + target_name = self.get_target_filename(target) + targetdir = self.get_target_private_dir(target) + symname = os.path.join(targetdir, target_name + '.symbols') + elem = NinjaBuildElement(symname, 'SHSYM', target_name) + if self.environment.is_cross_build() and self.environment.cross_info.need_cross_compiler(): + elem.add_item('CROSS', '--cross-host=' + self.environment.cross_info.config['host_machine']['system']) + elem.write(outfile) + self.check_outputs(elem) + + def generate_link(self, target, outfile, outname, obj_list, linker, extra_args=[]): + if isinstance(target, build.StaticLibrary): + linker_base = 'STATIC' + else: + linker_base = linker.get_language() # Fixme. + if isinstance(target, build.SharedLibrary): + self.generate_shsym(outfile, target) + crstr = '' + if target.is_cross: + crstr = '_CROSS' + linker_rule = linker_base + crstr + '_LINKER' + abspath = os.path.join(self.environment.get_build_dir(), target.subdir) + commands = [] + commands += linker.get_linker_always_args() + commands += linker.get_buildtype_linker_args(self.environment.coredata.get_builtin_option('buildtype')) + commands += linker.get_option_link_args(self.environment.coredata.compiler_options) + if not(isinstance(target, build.StaticLibrary)): + commands += self.environment.coredata.external_link_args[linker.get_language()] + if isinstance(target, build.Executable): + commands += linker.get_std_exe_link_args() + elif isinstance(target, build.SharedLibrary): + commands += linker.get_std_shared_lib_link_args() + commands += linker.get_pic_args() + if hasattr(target, 'soversion'): + soversion = target.soversion + else: + soversion = None + commands += linker.get_soname_args(target.name, abspath, soversion) + elif isinstance(target, build.StaticLibrary): + commands += linker.get_std_link_args() + else: + raise RuntimeError('Unknown build target type.') + # Link arguments of static libraries are not put in the command line of + # the library. They are instead appended to the command line where + # the static library is used. + if linker_base == 'STATIC': + dependencies = [] + else: + dependencies = target.get_dependencies() + commands += self.build_target_link_arguments(linker, dependencies) + for d in target.external_deps: + if d.need_threads(): + commands += linker.thread_link_flags() + if not isinstance(target, build.StaticLibrary): + commands += target.link_args + # External deps must be last because target link libraries may depend on them. + if not(isinstance(target, build.StaticLibrary)): + for dep in target.get_external_deps(): + commands += dep.get_link_args() + for d in target.get_dependencies(): + if isinstance(d, build.StaticLibrary): + for dep in d.get_external_deps(): + commands += dep.get_link_args() + commands += linker.build_rpath_args(self.environment.get_build_dir(),\ + self.determine_rpath_dirs(target), target.install_rpath) + if self.environment.coredata.get_builtin_option('coverage'): + commands += linker.get_coverage_link_args() + custom_target_libraries = self.get_custom_target_provided_libraries(target) + commands += extra_args + commands += custom_target_libraries + commands = linker.unixtype_flags_to_native(commands) + dep_targets = [self.get_dependency_filename(t) for t in dependencies] + dep_targets += [os.path.join(self.environment.source_dir, + target.subdir, t) for t in target.link_depends] + elem = NinjaBuildElement(outname, linker_rule, obj_list) + elem.add_dep(dep_targets + custom_target_libraries) + elem.add_item('LINK_ARGS', commands) + self.check_outputs(elem) + return elem + + def get_custom_target_provided_libraries(self, target): + libs = [] + for t in target.get_generated_sources(): + if not isinstance(t, build.CustomTarget): + continue + for f in t.output: + if self.environment.is_library(f): + libs.append(os.path.join(self.get_target_dir(t), f)) + return libs + + def determine_rpath_dirs(self, target): + link_deps = target.get_all_link_deps() + result = [] + for ld in link_deps: + prospective = self.get_target_dir(ld) + if not prospective in result: + result.append(prospective) + return result + + def get_dependency_filename(self, t): + if isinstance(t, build.SharedLibrary): + return os.path.join(self.get_target_private_dir(t), self.get_target_filename(t) + '.symbols') + return self.get_target_filename(t) + + def generate_shlib_aliases(self, target, outdir): + basename = target.get_filename() + aliases = target.get_aliaslist() + if not mesonlib.is_windows(): + for alias in aliases: + aliasfile = os.path.join(self.environment.get_build_dir(), outdir, alias) + try: + os.remove(aliasfile) + except Exception: + pass + os.symlink(basename, aliasfile) + else: + mlog.debug("Library versioning disabled because host does not support symlinks.") + + def generate_gcov_clean(self, outfile): + gcno_elem = NinjaBuildElement('clean-gcno', 'CUSTOM_COMMAND', 'PHONY') + script_root = self.environment.get_script_dir() + clean_script = os.path.join(script_root, 'delwithsuffix.py') + gcno_elem.add_item('COMMAND', [sys.executable, clean_script, '.', 'gcno']) + gcno_elem.add_item('description', 'Deleting gcno files') + gcno_elem.write(outfile) + self.check_outputs(gcno_elem) + + gcda_elem = NinjaBuildElement('clean-gcda', 'CUSTOM_COMMAND', 'PHONY') + script_root = self.environment.get_script_dir() + clean_script = os.path.join(script_root, 'delwithsuffix.py') + gcda_elem.add_item('COMMAND', [sys.executable, clean_script, '.', 'gcda']) + gcda_elem.add_item('description', 'Deleting gcda files') + gcda_elem.write(outfile) + self.check_outputs(gcda_elem) + + def is_compilable_file(self, filename): + if filename.endswith('.cpp') or\ + filename.endswith('.c') or\ + filename.endswith('.cxx') or\ + filename.endswith('.cc') or\ + filename.endswith('.C'): + return True + return False + + def process_dep_gens(self, outfile, target): + src_deps = [] + other_deps = [] + for rule in self.dep_rules.values(): + srcs = target.get_original_kwargs().get(rule.src_keyword, []) + if isinstance(srcs, str): + srcs = [srcs] + for src in srcs: + plainname = os.path.split(src)[1] + basename = plainname.split('.')[0] + outname = rule.name_templ.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname) + outfilename = os.path.join(self.get_target_private_dir(target), outname) + infilename = os.path.join(self.build_to_src, target.get_source_subdir(), src) + elem = NinjaBuildElement(outfilename, rule.name, infilename) + elem.write(outfile) + self.check_outputs(elem) + if self.is_compilable_file(outfilename): + src_deps.append(outfilename) + else: + other_deps.append(outfilename) + return (src_deps, other_deps) + + def generate_ending(self, outfile): + targetlist = [self.get_target_filename(t) for t in self.build.get_targets().values()\ + if not isinstance(t, build.RunTarget)] + + elem = NinjaBuildElement('all', 'phony', targetlist) + elem.write(outfile) + self.check_outputs(elem) + + default = 'default all\n\n' + outfile.write(default) + + ninja_command = environment.detect_ninja() + if ninja_command is None: + raise MesonException('Could not detect ninja command') + elem = NinjaBuildElement('clean', 'CUSTOM_COMMAND', 'PHONY') + elem.add_item('COMMAND', [ninja_command, '-t', 'clean']) + elem.add_item('description', 'Cleaning') + if self.environment.coredata.get_builtin_option('coverage'): + self.generate_gcov_clean(outfile) + elem.add_dep('clean-gcda') + elem.add_dep('clean-gcno') + elem.write(outfile) + self.check_outputs(elem) + + deps = self.get_regen_filelist() + elem = NinjaBuildElement('build.ninja', 'REGENERATE_BUILD', deps) + elem.add_item('pool', 'console') + elem.write(outfile) + + elem = NinjaBuildElement(deps, 'phony', '') + elem.write(outfile) + self.check_outputs(elem) diff --git a/mesonbuild/optinterpreter.py b/mesonbuild/optinterpreter.py new file mode 100644 index 0000000..f0c93ae --- /dev/null +++ b/mesonbuild/optinterpreter.py @@ -0,0 +1,148 @@ +# 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. + +from . import mparser +from . import coredata, mesonlib +import os, re + +forbidden_option_names = coredata.builtin_options +forbidden_prefixes = {'c_': True, + 'cpp_': True, + 'rust_': True, + 'fortran_': True, + 'objc_': True, + 'objcpp_': True, + 'vala_': True, + 'csharp_': True + } + +def is_invalid_name(name): + if name in forbidden_option_names: + return True + if name in forbidden_prefixes: + return True + return False + +class OptionException(coredata.MesonException): + pass + +optname_regex = re.compile('[^a-zA-Z0-9_-]') + +def StringParser(name, description, kwargs): + return coredata.UserStringOption(name, description, + kwargs.get('value', ''), kwargs.get('choices', [])) + +def BooleanParser(name, description, kwargs): + return coredata.UserBooleanOption(name, description, kwargs.get('value', True)) + +def ComboParser(name, description, kwargs): + if 'choices' not in kwargs: + raise OptionException('Combo option missing "choices" keyword.') + choices = kwargs['choices'] + if not isinstance(choices, list): + raise OptionException('Combo choices must be an array.') + for i in choices: + if not isinstance(i, str): + raise OptionException('Combo choice elements must be strings.') + return coredata.UserComboOption(name, description, choices, kwargs.get('value', choices[0])) + +option_types = {'string' : StringParser, + 'boolean' : BooleanParser, + 'combo' : ComboParser, + } + +class OptionInterpreter: + def __init__(self, subproject, command_line_options): + self.options = {} + self.subproject = subproject + self.cmd_line_options = {} + for o in command_line_options: + (key, value) = o.split('=', 1) + self.cmd_line_options[key] = value + + def process(self, option_file): + try: + ast = mparser.Parser(open(option_file, 'r').read()).parse() + except coredata.MesonException as me: + me.file = option_file + raise me + if not isinstance(ast, mparser.CodeBlockNode): + e = OptionException('Option file is malformed.') + e.lineno = ast.lineno() + raise e + for cur in ast.lines: + try: + self.evaluate_statement(cur) + except Exception as e: + e.lineno = cur.lineno + e.colno = cur.colno + e.file = os.path.join('meson_options.txt') + raise e + + def reduce_single(self, arg): + if isinstance(arg, str): + return arg + elif isinstance(arg, mparser.StringNode): + return arg.value + elif isinstance(arg, mparser.BooleanNode): + return arg.value + elif isinstance(arg, mparser.ArrayNode): + return [self.reduce_single(curarg) for curarg in arg.args.arguments] + elif isinstance(arg, mparser.NumberNode): + return arg.value + else: + raise OptionException('Arguments may only be string, int, bool, or array of those.') + + def reduce_arguments(self, args): + assert(isinstance(args, mparser.ArgumentNode)) + if args.incorrect_order(): + raise OptionException('All keyword arguments must be after positional arguments.') + reduced_pos = [self.reduce_single(arg) for arg in args.arguments] + reduced_kw = {} + for key in args.kwargs.keys(): + if not isinstance(key, str): + raise OptionException('Keyword argument name is not a string.') + a = args.kwargs[key] + reduced_kw[key] = self.reduce_single(a) + return (reduced_pos, reduced_kw) + + def evaluate_statement(self, node): + if not isinstance(node, mparser.FunctionNode): + raise OptionException('Option file may only contain option definitions') + func_name = node.func_name + if func_name != 'option': + raise OptionException('Only calls to option() are allowed in option files.') + (posargs, kwargs) = self.reduce_arguments(node.args) + if 'type' not in kwargs: + raise OptionException('Option call missing mandatory "type" keyword argument') + opt_type = kwargs['type'] + if not opt_type in option_types: + raise OptionException('Unknown type %s.' % opt_type) + if len(posargs) != 1: + raise OptionException('Option() must have one (and only one) positional argument') + opt_name = posargs[0] + if not isinstance(opt_name, str): + raise OptionException('Positional argument must be a string.') + if optname_regex.search(opt_name) is not None: + raise OptionException('Option names can only contain letters, numbers or dashes.') + if is_invalid_name(opt_name): + raise OptionException('Option name %s is reserved.' % opt_name) + if self.subproject != '': + opt_name = self.subproject + ':' + opt_name + opt = option_types[opt_type](opt_name, kwargs.get('description', ''), kwargs) + if opt.description == '': + opt.description = opt_name + if opt_name in self.cmd_line_options: + opt.set_value(opt.parse_string(self.cmd_line_options[opt_name])) + self.options[opt_name] = opt diff --git a/mesonbuild/scripts/commandrunner.py b/mesonbuild/scripts/commandrunner.py new file mode 100644 index 0000000..f5a2fff --- /dev/null +++ b/mesonbuild/scripts/commandrunner.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 + +# Copyright 2014 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This program is a wrapper to run external commands. It determines +what to run, sets up the environment and executes the command.""" + +import sys, os, subprocess, shutil + +def run_command(source_dir, build_dir, subdir, command, arguments): + env = {'MESON_SOURCE_ROOT' : source_dir, + 'MESON_BUILD_ROOT' : build_dir, + 'MESON_SUBDIR' : subdir + } + cwd = os.path.join(source_dir, subdir) + child_env = os.environ.copy() + child_env.update(env) + + # Is the command an executable in path? + exe = shutil.which(command) + if exe is not None: + command_array = [exe] + arguments + return subprocess.Popen(command_array, env=child_env, cwd=cwd) + # No? Maybe it is a script in the source tree. + fullpath = os.path.join(source_dir, subdir, command) + command_array = [fullpath] + arguments + try: + return subprocess.Popen(command_array,env=child_env, cwd=cwd) + except FileNotFoundError: + print('Could not execute command "%s".' % command) + sys.exit(1) + +def run(args): + if len(sys.argv) < 4: + print('commandrunner.py <source dir> <build dir> <subdir> <command> [arguments]') + sys.exit(1) + src_dir = sys.argv[1] + build_dir = sys.argv[2] + subdir = sys.argv[3] + command = sys.argv[4] + arguments = sys.argv[5:] + pc = run_command(src_dir, build_dir, subdir, command, arguments) + pc.wait() + return pc.returncode + +if __name__ == '__main__': + sys.exit(run(sys.argv[1:])) diff --git a/mesonbuild/scripts/delwithsuffix.py b/mesonbuild/scripts/delwithsuffix.py new file mode 100644 index 0000000..38ab406 --- /dev/null +++ b/mesonbuild/scripts/delwithsuffix.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 + +# Copyright 2013 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os, sys + +def run(args): + if len(sys.argv) != 2: + print('delwithsuffix.py <root of subdir to process> <suffix to delete>') + sys.exit(1) + + topdir = sys.argv[1] + suffix = sys.argv[2] + if suffix[0] != '.': + suffix = '.' + suffix + + for (root, _, files) in os.walk(topdir): + for f in files: + if f.endswith(suffix): + fullname = os.path.join(root, f) + os.unlink(fullname) + return 0 + +if __name__ == '__main__': + run(sys.argv[1:]) diff --git a/mesonbuild/scripts/depfixer.py b/mesonbuild/scripts/depfixer.py new file mode 100644 index 0000000..1ab83b6 --- /dev/null +++ b/mesonbuild/scripts/depfixer.py @@ -0,0 +1,302 @@ +#!/usr/bin/env python3 + +# Copyright 2013-2014 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import sys, struct + +SHT_STRTAB = 3 +DT_NEEDED = 1 +DT_RPATH = 15 +DT_STRTAB = 5 +DT_SONAME = 14 + +class DataSizes(): + def __init__(self, ptrsize, is_le): + if is_le: + p = '<' + else: + p = '>' + self.Half = p+'h' + self.HalfSize = 2 + self.Word = p+'I' + self.WordSize = 4 + self.Sword = p+'i' + self.SwordSize = 4 + if ptrsize == 64: + self.Addr = p+'Q' + self.AddrSize = 8 + self.Off = p+'Q' + self.OffSize = 8 + self.XWord = p+'Q' + self.XWordSize = 8 + self.Sxword = p+'q' + self.SxwordSize = 8 + else: + self.Addr = p+'I' + self.AddrSize = 4 + self.Off = p+'I' + self.OffSize = 4 + +class DynamicEntry(DataSizes): + def __init__(self, ifile, ptrsize, is_le): + super().__init__(ptrsize, is_le) + self.ptrsize = ptrsize + if ptrsize == 64: + self.d_tag = struct.unpack(self.Sxword, ifile.read(self.SxwordSize))[0]; + self.val = struct.unpack(self.XWord, ifile.read(self.XWordSize))[0]; + else: + self.d_tag = struct.unpack(self.Sword, ifile.read(self.SwordSize))[0] + self.val = struct.unpack(self.Word, ifile.read(self.WordSize))[0] + + def write(self, ofile): + if self.ptrsize == 64: + ofile.write(struct.pack(self.Sxword, self.d_tag)) + ofile.write(struct.pack(self.XWord, self.val)) + else: + ofile.write(struct.pack(self.Sword, self.d_tag)) + ofile.write(struct.pack(self.Word, self.val)) + +class SectionHeader(DataSizes): + def __init__(self, ifile, ptrsize, is_le): + super().__init__(ptrsize, is_le) + if ptrsize == 64: + is_64 = True + else: + is_64 = False +#Elf64_Word + self.sh_name = struct.unpack(self.Word, ifile.read(self.WordSize))[0]; +#Elf64_Word + self.sh_type = struct.unpack(self.Word, ifile.read(self.WordSize))[0] +#Elf64_Xword + if is_64: + self.sh_flags = struct.unpack(self.XWord, ifile.read(self.XWordSize))[0] + else: + self.sh_flags = struct.unpack(self.Word, ifile.read(self.WordSize))[0] +#Elf64_Addr + self.sh_addr = struct.unpack(self.Addr, ifile.read(self.AddrSize))[0]; +#Elf64_Off + self.sh_offset = struct.unpack(self.Off, ifile.read(self.OffSize))[0] +#Elf64_Xword + if is_64: + self.sh_size = struct.unpack(self.XWord, ifile.read(self.XWordSize))[0] + else: + self.sh_size = struct.unpack(self.Word, ifile.read(self.WordSize))[0] +#Elf64_Word + self.sh_link = struct.unpack(self.Word, ifile.read(self.WordSize))[0]; +#Elf64_Word + self.sh_info = struct.unpack(self.Word, ifile.read(self.WordSize))[0]; +#Elf64_Xword + if is_64: + self.sh_addralign = struct.unpack(self.XWord, ifile.read(self.XWordSize))[0] + else: + self.sh_addralign = struct.unpack(self.Word, ifile.read(self.WordSize))[0] +#Elf64_Xword + if is_64: + self.sh_entsize = struct.unpack(self.XWord, ifile.read(self.XWordSize))[0] + else: + self.sh_entsize = struct.unpack(self.Word, ifile.read(self.WordSize))[0] + +class Elf(DataSizes): + def __init__(self, bfile): + self.bfile = bfile + self.bf = open(bfile, 'r+b') + (self.ptrsize, self.is_le) = self.detect_elf_type() + super().__init__(self.ptrsize, self.is_le) + self.parse_header() + self.parse_sections() + self.parse_dynamic() + + def detect_elf_type(self): + data = self.bf.read(6) + if data[1:4] != b'ELF': + # This script gets called to non-elf targets too + # so just ignore them. + print('File "%s" is not an ELF file.' % self.bfile) + sys.exit(0) + if data[4] == 1: + ptrsize = 32 + elif data[4] == 2: + ptrsize = 64 + else: + print('File "%s" has unknown ELF class.' % self.bfile) + sys.exit(1) + if data[5] == 1: + is_le = True + elif data[5] == 2: + is_le = False + else: + print('File "%s" has unknown ELF endianness.' % self.bfile) + sys.exit(1) + return (ptrsize, is_le) + + def parse_header(self): + self.bf.seek(0) + self.e_ident = struct.unpack('16s', self.bf.read(16))[0] + self.e_type = struct.unpack(self.Half, self.bf.read(self.HalfSize))[0] + self.e_machine = struct.unpack(self.Half, self.bf.read(self.HalfSize))[0] + self.e_version = struct.unpack(self.Word, self.bf.read(self.WordSize))[0] + self.e_entry = struct.unpack(self.Addr, self.bf.read(self.AddrSize))[0] + self.e_phoff = struct.unpack(self.Off, self.bf.read(self.OffSize))[0] + self.e_shoff = struct.unpack(self.Off, self.bf.read(self.OffSize))[0] + self.e_flags = struct.unpack(self.Word, self.bf.read(self.WordSize))[0] + self.e_ehsize = struct.unpack(self.Half, self.bf.read(self.HalfSize))[0] + self.e_phentsize = struct.unpack(self.Half, self.bf.read(self.HalfSize))[0] + self.e_phnum = struct.unpack(self.Half, self.bf.read(self.HalfSize))[0] + self.e_shentsize = struct.unpack(self.Half, self.bf.read(self.HalfSize))[0] + self.e_shnum = struct.unpack(self.Half, self.bf.read(self.HalfSize))[0] + self.e_shstrndx = struct.unpack(self.Half, self.bf.read(self.HalfSize))[0] + + def parse_sections(self): + self.bf.seek(self.e_shoff) + self.sections = [] + for i in range(self.e_shnum): + self.sections.append(SectionHeader(self.bf, self.ptrsize, self.is_le)) + + def read_str(self): + arr = [] + x = self.bf.read(1) + while x != b'\0': + arr.append(x) + x = self.bf.read(1) + if x == b'': + raise RuntimeError('Tried to read past the end of the file') + return b''.join(arr) + + def find_section(self, target_name): + section_names = self.sections[self.e_shstrndx] + for i in self.sections: + self.bf.seek(section_names.sh_offset + i.sh_name) + name = self.read_str() + if name == target_name: + return i + + def parse_dynamic(self): + sec = self.find_section(b'.dynamic') + self.dynamic = [] + self.bf.seek(sec.sh_offset) + while True: + e = DynamicEntry(self.bf, self.ptrsize, self.is_le) + self.dynamic.append(e) + if e.d_tag == 0: + break + + def print_section_names(self): + section_names = self.sections[self.e_shstrndx] + for i in self.sections: + self.bf.seek(section_names.sh_offset + i.sh_name) + name = self.read_str() + print(name.decode()) + + def print_soname(self): + soname = None + strtab = None + for i in self.dynamic: + if i.d_tag == DT_SONAME: + soname = i + if i.d_tag == DT_STRTAB: + strtab = i + self.bf.seek(strtab.val + soname.val) + print(self.read_str()) + + def get_rpath_offset(self): + sec = self.find_section(b'.dynstr') + for i in self.dynamic: + if i.d_tag == DT_RPATH: + return sec.sh_offset + i.val + return None + + def print_rpath(self): + offset = self.get_rpath_offset() + if offset is None: + print("This file does not have an rpath.") + else: + self.bf.seek(offset) + print(self.read_str()) + + def print_deps(self): + sec = self.find_section(b'.dynstr') + deps = [] + for i in self.dynamic: + if i.d_tag == DT_NEEDED: + deps.append(i) + for i in deps: + offset = sec.sh_offset + i.val + self.bf.seek(offset) + name = self.read_str() + print(name) + + def fix_deps(self, prefix): + sec = self.find_section(b'.dynstr') + deps = [] + for i in self.dynamic: + if i.d_tag == DT_NEEDED: + deps.append(i) + for i in deps: + offset = sec.sh_offset + i.val + self.bf.seek(offset) + name = self.read_str() + if name.startswith(prefix): + basename = name.split(b'/')[-1] + padding = b'\0'*(len(name) - len(basename)) + newname = basename + padding + assert(len(newname) == len(name)) + self.bf.seek(offset) + self.bf.write(newname) + + def fix_rpath(self, new_rpath): + rp_off = self.get_rpath_offset() + if rp_off is None: + print('File does not have rpath. It should be a fully static executable.') + return + self.bf.seek(rp_off) + old_rpath = self.read_str() + if len(old_rpath) < len(new_rpath): + print("New rpath must not be longer than the old one.") + self.bf.seek(rp_off) + self.bf.write(new_rpath) + self.bf.write(b'\0'*(len(old_rpath) - len(new_rpath) + 1)) + if len(new_rpath) == 0: + self.remove_rpath_entry() + + def remove_rpath_entry(self): + sec = self.find_section(b'.dynamic') + for (i, entry) in enumerate(self.dynamic): + if entry.d_tag == DT_RPATH: + rpentry = self.dynamic[i] + rpentry.d_tag = 0 + self.dynamic = self.dynamic[:i] + self.dynamic[i+1:] + [rpentry] + break; + self.bf.seek(sec.sh_offset) + for entry in self.dynamic: + entry.write(self.bf) + return None + +def run(args): + if len(args) < 1 or len(args) > 2: + print('This application resets target rpath.') + print('Don\'t run this unless you know what you are doing.') + print('%s: <binary file> <prefix>' % sys.argv[0]) + exit(1) + e = Elf(args[0]) + if len(args) == 1: + e.print_rpath() + else: + new_rpath = args[1] + e.fix_rpath(new_rpath.encode('utf8')) + return 0 + +if __name__ == '__main__': + run(sys.argv[1:]) diff --git a/mesonbuild/scripts/dirchanger.py b/mesonbuild/scripts/dirchanger.py new file mode 100644 index 0000000..93a901d --- /dev/null +++ b/mesonbuild/scripts/dirchanger.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 + +# Copyright 2015-2016 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +'''CD into dir given as first argument and execute +the command given in the rest of the arguments.''' + +import os, subprocess, sys + +def run(args): + dirname = args[0] + command = args[1:] + + os.chdir(dirname) + return subprocess.call(command) + +if __name__ == '__main__': + sys.exit(run(sys.argv[1:])) diff --git a/mesonbuild/scripts/gtkdochelper.py b/mesonbuild/scripts/gtkdochelper.py new file mode 100644 index 0000000..68be8f2 --- /dev/null +++ b/mesonbuild/scripts/gtkdochelper.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +# Copyright 2015-2016 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys, os +import subprocess +import shutil +import argparse + +parser = argparse.ArgumentParser() + +parser.add_argument('--sourcedir', dest='sourcedir') +parser.add_argument('--builddir', dest='builddir') +parser.add_argument('--subdir', dest='subdir') +parser.add_argument('--headerdir', dest='headerdir') +parser.add_argument('--mainfile', dest='mainfile') +parser.add_argument('--modulename', dest='modulename') +parser.add_argument('--htmlargs', dest='htmlargs', default='') +parser.add_argument('--scanargs', dest='scanargs', default='') + +def build_gtkdoc(source_root, build_root, doc_subdir, src_subdir, + main_file, module, html_args, scan_args): + abs_src = os.path.join(source_root, src_subdir) + abs_out = os.path.join(build_root, doc_subdir) + htmldir = os.path.join(abs_out, 'html') + scan_cmd = ['gtkdoc-scan', + '--module=' + module, + '--source-dir=' + abs_src] + scan_args +# print(scan_cmd) +# sys.exit(1) + subprocess.check_call(scan_cmd, + cwd=abs_out) + if main_file.endswith('sgml'): + modeflag = '--sgml-mode' + else: + modeflag = '--xml-mode' + mkdb_cmd = ['gtkdoc-mkdb', + '--module=' + module, + '--output-format=xml', + modeflag, + '--source-dir=' + abs_src] + main_abs = os.path.join(source_root, doc_subdir, main_file) + if len(main_file) > 0: + # Yes, this is the flag even if the file is in xml. + mkdb_cmd.append('--main-sgml-file=' + main_file) +# print(mkdb_cmd) +# sys.exit(1) + subprocess.check_call(mkdb_cmd, cwd=abs_out) + shutil.rmtree(htmldir, ignore_errors=True) + try: + os.mkdir(htmldir) + except Exception: + pass + mkhtml_cmd = ['gtkdoc-mkhtml', + '--path=' + abs_src, + module, + ] + html_args + if len(main_file) > 0: + mkhtml_cmd.append('../' + main_file) + else: + mkhtml_cmd.append('%s-docs.xml' % module) + # html gen must be run in the HTML dir +# print(mkhtml_cmd) +# sys.exit(1) + subprocess.check_call(mkhtml_cmd, cwd=os.path.join(abs_out, 'html'), shell=False) + fixref_cmd = ['gtkdoc-fixxref', + '--module=' + module, + '--module-dir=html'] +# print(fixref_cmd) +# sys.exit(1) + subprocess.check_call(fixref_cmd, cwd=abs_out) + +def install_gtkdoc(build_root, doc_subdir, install_prefix, datadir, module): + source = os.path.join(build_root, doc_subdir, 'html') + final_destination = os.path.join(install_prefix, datadir, module) + shutil.rmtree(final_destination, ignore_errors=True) + shutil.copytree(source, final_destination) + +def run(args): + options = parser.parse_args(args) + if len(options.htmlargs) > 0: + htmlargs = options.htmlargs.split('@@') + else: + htmlargs = [] + if len(options.scanargs) > 0: + scanargs = options.scanargs.split('@@') + else: + scanargs = [] + build_gtkdoc(options.sourcedir, + options.builddir, + options.subdir, + options.headerdir, + options.mainfile, + options.modulename, + htmlargs, + scanargs) + + if 'MESON_INSTALL_PREFIX' in os.environ: + if 'DESTDIR' in os.environ: + installdir = os.environ['DESTDIR'] + os.environ['MESON_INSTALL_PREFIX'] + else: + installdir = os.environ['MESON_INSTALL_PREFIX'] + install_gtkdoc(options.builddir, + options.subdir, + installdir, + 'share/gtk-doc/html', + options.modulename) + return 0 + +if __name__ == '__main__': + sys.exit(run(sys.argv[1:])) diff --git a/mesonbuild/scripts/meson_benchmark.py b/mesonbuild/scripts/meson_benchmark.py new file mode 100644 index 0000000..26f1f95 --- /dev/null +++ b/mesonbuild/scripts/meson_benchmark.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 + +# Copyright 2015 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import subprocess, sys, os, argparse +import pickle, statistics, json +from . import meson_test + +parser = argparse.ArgumentParser() +parser.add_argument('--wd', default=None, dest='wd', + help='directory to cd into before running') +parser.add_argument('args', nargs='+') + +def print_stats(numlen, num_tests, name, res, i, duration, stdev): + startpad = ' '*(numlen - len('%d' % (i+1))) + num = '%s%d/%d' % (startpad, i+1, num_tests) + padding1 = ' '*(38-len(name)) + padding2 = ' '*(8-len(res)) + result_str = '%s %s %s%s%s%5.5f s +- %5.5f s' % \ + (num, name, padding1, res, padding2, duration, stdev) + print(result_str) +# write_json_log(jsonlogfile, name, result) + +def print_json_log(jsonlogfile, rawruns, test_name, i): + jsonobj = {'name' : test_name} + runs = [] + for r in rawruns: + runobj = {'duration': r.duration, + 'stdout': r.stdo, + 'stderr': r.stde, + 'returncode' : r.returncode, + 'duration' : r.duration} + runs.append(runobj) + jsonobj['runs'] = runs + jsonlogfile.write(json.dumps(jsonobj) + '\n') + jsonlogfile.flush() + +def run_benchmarks(options, datafile): + failed_tests = 0 + logfile_base = 'meson-logs/benchmarklog' + jsonlogfilename = logfile_base+ '.json' + jsonlogfile = open(jsonlogfilename, 'w') + tests = pickle.load(open(datafile, 'rb')) + num_tests = len(tests) + if num_tests == 0: + print('No benchmarks defined.') + return 0 + iteration_count = 5 + wrap = [] # Benchmarks on cross builds are pointless so don't support them. + for i, test in enumerate(tests): + runs = [] + durations = [] + failed = False + for _ in range(iteration_count): + res = meson_test.run_single_test(wrap, test) + runs.append(res) + durations.append(res.duration) + if res.returncode != 0: + failed = True + mean = statistics.mean(durations) + stddev = statistics.stdev(durations) + if failed: + resultstr = 'FAIL' + failed_tests += 1 + else: + resultstr = 'OK' + print_stats(3, num_tests, test.name, resultstr, i, mean, stddev) + print_json_log(jsonlogfile, runs, test.name, i) + print('\nFull log written to meson-logs/benchmarklog.json.') + return failed_tests + +def run(args): + global failed_tests + options = parser.parse_args(args) + if len(options.args) != 1: + print('Benchmark runner for Meson. Do not run on your own, mmm\'kay?') + print('%s [data file]' % sys.argv[0]) + if options.wd is not None: + os.chdir(options.wd) + datafile = options.args[0] + returncode = run_benchmarks(options, datafile) + return returncode + +if __name__ == '__main__': + sys.exit(run(sys.argv[1:])) diff --git a/mesonbuild/scripts/meson_install.py b/mesonbuild/scripts/meson_install.py new file mode 100644 index 0000000..1ede757 --- /dev/null +++ b/mesonbuild/scripts/meson_install.py @@ -0,0 +1,215 @@ +#!/usr/bin/env python3 + +# Copyright 2013-2014 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys, pickle, os, shutil, subprocess, gzip, platform +from glob import glob + +def do_install(datafilename): + ifile = open(datafilename, 'rb') + d = pickle.load(ifile) + destdir_var = 'DESTDIR' + if destdir_var in os.environ: + d.destdir = os.environ[destdir_var] + else: + d.destdir = '' + d.fullprefix = d.destdir + d.prefix + + install_subdirs(d) # Must be first, because it needs to delete the old subtree. + install_targets(d) + install_headers(d) + install_man(d) + install_data(d) + install_po(d) + run_install_script(d) + +def install_subdirs(d): + for (src_dir, dst_dir) in d.install_subdirs: + if os.path.isabs(dst_dir): + dst_dir = d.destdir + dst_dir + else: + dst_dir = d.fullprefix + dst_dir + # Python's copytree works in strange ways. + last_level = os.path.split(src_dir)[-1] + final_dst = os.path.join(dst_dir, last_level) +# Don't do rmtree because final_dst might point to e.g. /var/www +# We might need to revert to walking the directory tree by hand. +# shutil.rmtree(final_dst, ignore_errors=True) + shutil.copytree(src_dir, final_dst, symlinks=True) + print('Installing subdir %s to %s.' % (src_dir, dst_dir)) + +def install_po(d): + packagename = d.po_package_name + for f in d.po: + srcfile = f[0] + localedir = f[1] + languagename = f[2] + outfile = os.path.join(d.fullprefix, localedir, languagename, 'LC_MESSAGES', + packagename + '.mo') + os.makedirs(os.path.split(outfile)[0], exist_ok=True) + shutil.copyfile(srcfile, outfile) + shutil.copystat(srcfile, outfile) + print('Installing %s to %s.' % (srcfile, outfile)) + +def install_data(d): + for i in d.data: + fullfilename = i[0] + outfilename = i[1] + if os.path.isabs(outfilename): + outdir = d.destdir + os.path.split(outfilename)[0] + outfilename = d.destdir + outfilename + else: + outdir = os.path.join(d.fullprefix, os.path.split(outfilename)[0]) + outfilename = os.path.join(outdir, os.path.split(outfilename)[1]) + os.makedirs(outdir, exist_ok=True) + print('Installing %s to %s.' % (fullfilename, outdir)) + shutil.copyfile(fullfilename, outfilename) + shutil.copystat(fullfilename, outfilename) + +def install_man(d): + for m in d.man: + outfileroot = m[1] + outfilename = os.path.join(d.fullprefix, outfileroot) + full_source_filename = m[0] + outdir = os.path.split(outfilename)[0] + os.makedirs(outdir, exist_ok=True) + print('Installing %s to %s.' % (full_source_filename, outdir)) + if outfilename.endswith('.gz') and not full_source_filename.endswith('.gz'): + open(outfilename, 'wb').write(gzip.compress(open(full_source_filename, 'rb').read())) + else: + shutil.copyfile(full_source_filename, outfilename) + shutil.copystat(full_source_filename, outfilename) + +def install_headers(d): + for t in d.headers: + fullfilename = t[0] + outdir = os.path.join(d.fullprefix, t[1]) + fname = os.path.split(fullfilename)[1] + outfilename = os.path.join(outdir, fname) + print('Installing %s to %s' % (fname, outdir)) + os.makedirs(outdir, exist_ok=True) + shutil.copyfile(fullfilename, outfilename) + shutil.copystat(fullfilename, outfilename) + +def run_install_script(d): + env = {'MESON_SOURCE_ROOT' : d.source_dir, + 'MESON_BUILD_ROOT' : d.build_dir, + 'MESON_INSTALL_PREFIX' : d.prefix + } + child_env = os.environ.copy() + child_env.update(env) + + for i in d.install_scripts: + script = i.cmd_arr[0] + print('Running custom install script %s' % script) + suffix = os.path.splitext(script)[1].lower() + if platform.system().lower() == 'windows' and suffix != '.bat': + first_line = open(script).readline().strip() + if first_line.startswith('#!'): + commands = first_line[2:].split('#')[0].strip().split() + commands[0] = shutil.which(commands[0].split('/')[-1]) + if commands[0] is None: + raise RuntimeError("Don't know how to run script %s." % script) + final_command = commands + [script] + i.cmd_arr[1:] + else: + final_command = i.cmd_arr + subprocess.check_call(final_command, env=child_env) + +def is_elf_platform(): + platname = platform.system().lower() + if platname == 'darwin' or platname == 'windows': + return False + return True + +def check_for_stampfile(fname): + '''Some languages e.g. Rust have output files + whose names are not known at configure time. + Check if this is the case and return the real + file instead.''' + if fname.endswith('.so') or fname.endswith('.dll'): + if os.stat(fname).st_size == 0: + (base, suffix) = os.path.splitext(fname) + files = glob(base + '-*' + suffix) + if len(files) > 1: + print("Stale dynamic library files in build dir. Can't install.") + sys.exit(1) + if len(files) == 1: + return files[0] + elif fname.endswith('.a') or fname.endswith('.lib'): + if os.stat(fname).st_size == 0: + (base, suffix) = os.path.splitext(fname) + files = glob(base + '-*' + '.rlib') + if len(files) > 1: + print("Stale static library files in build dir. Can't install.") + sys.exit(1) + if len(files) == 1: + return files[0] + return fname + +def install_targets(d): + for t in d.targets: + fname = check_for_stampfile(t[0]) + outdir = os.path.join(d.fullprefix, t[1]) + aliases = t[2] + outname = os.path.join(outdir, os.path.split(fname)[-1]) + should_strip = t[3] + install_rpath = t[4] + print('Installing %s to %s' % (fname, outname)) + os.makedirs(outdir, exist_ok=True) + shutil.copyfile(fname, outname) + shutil.copystat(fname, outname) + if should_strip: + print('Stripping target') + ps = subprocess.Popen(['strip', outname], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (stdo, stde) = ps.communicate() + if ps.returncode != 0: + print('Could not strip file.\n') + print('Stdout:\n%s\n' % stdo.decode()) + print('Stderr:\n%s\n' % stde.decode()) + sys.exit(1) + printed_symlink_error = False + for alias in aliases: + try: + symlinkfilename = os.path.join(outdir, alias) + try: + os.unlink(symlinkfilename) + except FileNotFoundError: + pass + os.symlink(os.path.split(fname)[-1], symlinkfilename) + except NotImplementedError: + if not printed_symlink_error: + print("Symlink creation does not work on this platform.") + printed_symlink_error = True + if is_elf_platform(): + p = subprocess.Popen(d.depfixer + [outname, install_rpath], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + (stdo, stde) = p.communicate() + if p.returncode != 0: + print('Could not fix dependency info.\n') + print('Stdout:\n%s\n' % stdo.decode()) + print('Stderr:\n%s\n' % stde.decode()) + sys.exit(1) + +def run(args): + if len(args) != 1: + print('Installer script for Meson. Do not run on your own, mmm\'kay?') + print('meson_install.py [install info file]') + datafilename = args[0] + do_install(datafilename) + return 0 + +if __name__ == '__main__': + sys.exit(run(sys.argv[1:])) diff --git a/mesonbuild/scripts/meson_test.py b/mesonbuild/scripts/meson_test.py new file mode 100644 index 0000000..03fd073 --- /dev/null +++ b/mesonbuild/scripts/meson_test.py @@ -0,0 +1,233 @@ +#!/usr/bin/env python3 + +# Copyright 2013-2016 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import mesonbuild +import sys, os, subprocess, time, datetime, pickle, multiprocessing, json +import concurrent.futures as conc +import argparse +import platform + +def is_windows(): + platname = platform.system().lower() + return platname == 'windows' or 'mingw' in platname + +tests_failed = [] + +parser = argparse.ArgumentParser() +parser.add_argument('--wrapper', default=None, dest='wrapper', + help='wrapper to run tests with (e.g. valgrind)') +parser.add_argument('--wd', default=None, dest='wd', + help='directory to cd into before running') +parser.add_argument('--suite', default=None, dest='suite', + help='Only run tests belonging to this suite.') +parser.add_argument('args', nargs='+') + + +class TestRun(): + def __init__(self, res, returncode, duration, stdo, stde, cmd): + self.res = res + self.returncode = returncode + self.duration = duration + self.stdo = stdo + self.stde = stde + self.cmd = cmd + +def decode(stream): + try: + return stream.decode('utf-8') + except UnicodeDecodeError: + return stream.decode('iso-8859-1', errors='ignore') + +def write_log(logfile, test_name, result_str, result): + logfile.write(result_str + '\n\n') + logfile.write('--- command ---\n') + if result.cmd is None: + logfile.write('NONE') + else: + logfile.write(' '.join(result.cmd)) + logfile.write('\n--- "%s" stdout ---\n' % test_name) + logfile.write(result.stdo) + logfile.write('\n--- "%s" stderr ---\n' % test_name) + logfile.write(result.stde) + logfile.write('\n-------\n\n') + +def write_json_log(jsonlogfile, test_name, result): + result = {'name' : test_name, + 'stdout' : result.stdo, + 'stderr' : result.stde, + 'result' : result.res, + 'duration' : result.duration, + 'returncode' : result.returncode, + 'command' : result.cmd} + jsonlogfile.write(json.dumps(result) + '\n') + +def run_with_mono(fname): + if fname.endswith('.exe') and not is_windows(): + return True + return False + +def run_single_test(wrap, test): + global tests_failed + if test.fname[0].endswith('.jar'): + cmd = ['java', '-jar'] + test.fname + elif not test.is_cross and run_with_mono(test.fname[0]): + cmd = ['mono'] + test.fname + else: + if test.is_cross: + if test.exe_runner is None: + # Can not run test on cross compiled executable + # because there is no execute wrapper. + cmd = None + else: + cmd = [test.exe_runner] + test.fname + else: + cmd = test.fname + if len(wrap) > 0 and 'valgrind' in wrap[0]: + wrap += test.valgrind_args + if cmd is None: + res = 'SKIP' + duration = 0.0 + stdo = 'Not run because can not execute cross compiled binaries.' + stde = '' + returncode = -1 + else: + cmd = wrap + cmd + test.cmd_args + starttime = time.time() + child_env = os.environ.copy() + child_env.update(test.env) + if len(test.extra_paths) > 0: + child_env['PATH'] = child_env['PATH'] + ';'.join([''] + test.extra_paths) + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + env=child_env, cwd=test.workdir) + timed_out = False + try: + (stdo, stde) = p.communicate(timeout=test.timeout) + except subprocess.TimeoutExpired: + timed_out = True + p.kill() + (stdo, stde) = p.communicate() + endtime = time.time() + duration = endtime - starttime + stdo = decode(stdo) + stde = decode(stde) + if timed_out: + res = 'TIMEOUT' + tests_failed.append((test.name, stdo, stde)) + elif (not test.should_fail and p.returncode == 0) or \ + (test.should_fail and p.returncode != 0): + res = 'OK' + else: + res = 'FAIL' + tests_failed.append((test.name, stdo, stde)) + returncode = p.returncode + return TestRun(res, returncode, duration, stdo, stde, cmd) + +def print_stats(numlen, tests, name, result, i, logfile, jsonlogfile): + startpad = ' '*(numlen - len('%d' % (i+1))) + num = '%s%d/%d' % (startpad, i+1, len(tests)) + padding1 = ' '*(38-len(name)) + padding2 = ' '*(8-len(result.res)) + result_str = '%s %s %s%s%s%5.2f s' % \ + (num, name, padding1, result.res, padding2, result.duration) + print(result_str) + write_log(logfile, name, result_str, result) + write_json_log(jsonlogfile, name, result) + +def drain_futures(futures): + for i in futures: + (result, numlen, tests, name, i, logfile, jsonlogfile) = i + print_stats(numlen, tests, name, result.result(), i, logfile, jsonlogfile) + +def filter_tests(suite, tests): + if suite is None: + return tests + return [x for x in tests if suite in x.suite] + +def run_tests(options, datafilename): + logfile_base = 'meson-logs/testlog' + if options.wrapper is None: + wrap = [] + logfilename = logfile_base + '.txt' + jsonlogfilename = logfile_base+ '.json' + else: + wrap = [options.wrapper] + logfilename = logfile_base + '-' + options.wrapper.replace(' ', '_') + '.txt' + jsonlogfilename = logfile_base + '-' + options.wrapper.replace(' ', '_') + '.json' + logfile = open(logfilename, 'w') + jsonlogfile = open(jsonlogfilename, 'w') + logfile.write('Log of Meson test suite run on %s.\n\n' % datetime.datetime.now().isoformat()) + tests = pickle.load(open(datafilename, 'rb')) + if len(tests) == 0: + print('No tests defined.') + return + numlen = len('%d' % len(tests)) + varname = 'MESON_TESTTHREADS' + if varname in os.environ: + try: + num_workers = int(os.environ[varname]) + except ValueError: + print('Invalid value in %s, using 1 thread.' % varname) + num_workers = 1 + else: + num_workers = multiprocessing.cpu_count() + executor = conc.ThreadPoolExecutor(max_workers=num_workers) + futures = [] + filtered_tests = filter_tests(options.suite, tests) + for i, test in enumerate(filtered_tests): + if test.suite[0] == '': + visible_name = test.name + else: + if options.suite is not None: + visible_name = options.suite + ' / ' + test.name + else: + visible_name = test.suite[0] + ' / ' + test.name + + if not test.is_parallel: + drain_futures(futures) + futures = [] + res = run_single_test(wrap, test) + print_stats(numlen, filtered_tests, visible_name, res, i, logfile, jsonlogfile) + else: + f = executor.submit(run_single_test, wrap, test) + futures.append((f, numlen, filtered_tests, visible_name, i, logfile, jsonlogfile)) + drain_futures(futures) + return logfilename + +def run(args): + global tests_failed + options = parser.parse_args(args) + if len(options.args) != 1: + print('Test runner for Meson. Do not run on your own, mmm\'kay?') + print('%s [data file]' % sys.argv[0]) + if options.wd is not None: + os.chdir(options.wd) + datafile = options.args[0] + logfilename = run_tests(options, datafile) + returncode = 0 + if len(tests_failed) > 0: + print('\nOutput of failed tests (max 10):') + for (name, stdo, stde) in tests_failed[:10]: + print("{} stdout:\n".format(name)) + print(stdo) + print('\n{} stderr:\n'.format(name)) + print(stde) + print('\n') + returncode = 1 + print('\nFull log written to %s.' % logfilename) + return returncode + +if __name__ == '__main__': + sys.exit(run(sys.argv[1:])) diff --git a/mesonbuild/scripts/regen_checker.py b/mesonbuild/scripts/regen_checker.py new file mode 100644 index 0000000..f360a7c --- /dev/null +++ b/mesonbuild/scripts/regen_checker.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 + +# Copyright 2015-2016 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys, os +import pickle, subprocess + +# This could also be used for XCode. + +def need_regen(regeninfo): + sln_time = os.stat(os.path.join(regeninfo.build_dir, regeninfo.solutionfile)).st_mtime + for i in regeninfo.depfiles: + curfile = os.path.join(regeninfo.build_dir, i) + curtime = os.stat(curfile).st_mtime + if curtime > sln_time: + return True + return False + +def regen(regeninfo): + scriptdir = os.path.split(__file__)[0] + mesonscript = os.path.join(scriptdir, 'meson.py') + cmd = [sys.executable, mesonscript, regeninfo.build_dir, regeninfo.source_dir, + '--backend=vs2010', 'secret-handshake'] + subprocess.check_call(cmd) + +def run(args): + regeninfo = pickle.load(open(os.path.join(args[0], 'regeninfo.dump'), 'rb')) + if need_regen(regeninfo): + regen(regeninfo) + sys.exit(0) + +if __name__ == '__main__': + run(sys.argv[1:]) diff --git a/mesonbuild/scripts/symbolextractor.py b/mesonbuild/scripts/symbolextractor.py new file mode 100644 index 0000000..79c1264 --- /dev/null +++ b/mesonbuild/scripts/symbolextractor.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 + +# Copyright 2013-2016 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This script extracts the symbols of a given shared library +# into a file. If the symbols have not changed, the file is not +# touched. This information is used to skip link steps if the +# ABI has not changed. + +# This file is basically a reimplementation of +# http://cgit.freedesktop.org/libreoffice/core/commit/?id=3213cd54b76bc80a6f0516aac75a48ff3b2ad67c + +import sys, subprocess +from mesonbuild import mesonlib +import argparse + +parser = argparse.ArgumentParser() + +parser.add_argument('--cross-host', default=None, dest='cross_host', + help='cross compilation host platform') +parser.add_argument('args', nargs='+') + +def dummy_syms(outfilename): + """Just touch it so relinking happens always.""" + open(outfilename, 'w').close() + +def write_if_changed(text, outfilename): + try: + oldtext = open(outfilename, 'r').read() + if text == oldtext: + return + except FileNotFoundError: + pass + open(outfilename, 'w').write(text) + +def linux_syms(libfilename, outfilename): + pe = subprocess.Popen(['readelf', '-d', libfilename], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + output = pe.communicate()[0].decode() + if pe.returncode != 0: + raise RuntimeError('Readelf does not work') + result = [x for x in output.split('\n') if 'SONAME' in x] + assert(len(result) <= 1) + pnm = subprocess.Popen(['nm', '--dynamic', '--extern-only', '--defined-only', '--format=posix', libfilename], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + output = pnm.communicate()[0].decode() + if pnm.returncode != 0: + raise RuntimeError('nm does not work.') + result += [' '.join(x.split()[0:2]) for x in output.split('\n') if len(x) > 0] + write_if_changed('\n'.join(result) + '\n', outfilename) + +def osx_syms(libfilename, outfilename): + pe = subprocess.Popen(['otool', '-l', libfilename], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + output = pe.communicate()[0].decode() + if pe.returncode != 0: + raise RuntimeError('Otool does not work.') + arr = output.split('\n') + for (i, val) in enumerate(arr): + if 'LC_ID_DYLIB' in val: + match = i + break + result = [arr[match+2], arr[match+5]] # Libreoffice stores all 5 lines but the others seem irrelevant. + pnm = subprocess.Popen(['nm', '-g', '-P', libfilename], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + output = pnm.communicate()[0].decode() + if pnm.returncode != 0: + raise RuntimeError('nm does not work.') + result += [' '.join(x.split()[0:2]) for x in output.split('\n') if len(x) > 0 and not x.endswith('U')] + write_if_changed('\n'.join(result) + '\n', outfilename) + +def gen_symbols(libfilename, outfilename, cross_host): + if cross_host is not None: + # In case of cross builds just always relink. + # In theory we could determine the correct + # toolset but there are more important things + # to do. + dummy_syms(outfilename) + elif mesonlib.is_linux(): + linux_syms(libfilename, outfilename) + elif mesonlib.is_osx(): + osx_syms(libfilename, outfilename) + else: + dummy_syms(outfilename) + +def run(args): + options = parser.parse_args(args) + if len(options.args) != 2: + print('symbolextractor.py <shared library file> <output file>') + sys.exit(1) + libfile = options.args[0] + outfile = options.args[1] + gen_symbols(libfile, outfile, options.cross_host) + return 0 + +if __name__ == '__main__': + sys.exit(run(sys.argv[1:])) diff --git a/mesonbuild/scripts/vcstagger.py b/mesonbuild/scripts/vcstagger.py new file mode 100644 index 0000000..390e37a --- /dev/null +++ b/mesonbuild/scripts/vcstagger.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 + +# Copyright 2015-2016 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys, os, subprocess, re + +def config_vcs_tag(infile, outfile, fallback, source_dir, replace_string, regex_selector, cmd): + try: + output = subprocess.check_output(cmd, cwd=source_dir) + new_string = re.search(regex_selector, output.decode()).group(1).strip() + except Exception: + new_string = fallback + + new_data = open(infile).read().replace(replace_string, new_string) + if (not os.path.exists(outfile)) or (open(outfile).read() != new_data): + open(outfile, 'w').write(new_data) + +def run(args): + infile, outfile, fallback, source_dir, replace_string, regex_selector = args[0:6] + command = args[6:] + config_vcs_tag(infile, outfile, fallback, source_dir, replace_string, regex_selector, command) + +if __name__ == '__main__': + sys.exit(run(sys.argv[1:])) diff --git a/mesonbuild/vs2010backend.py b/mesonbuild/vs2010backend.py new file mode 100644 index 0000000..33e9646 --- /dev/null +++ b/mesonbuild/vs2010backend.py @@ -0,0 +1,640 @@ +# 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 os, sys +import pickle +from . import backends, build +from . import dependencies +from . import mlog +import xml.etree.ElementTree as ET +import xml.dom.minidom +from .coredata import MesonException + +class RegenInfo(): + def __init__(self, source_dir, build_dir, depfiles, solutionfile): + self.source_dir = source_dir + self.build_dir = build_dir + self.depfiles = depfiles + self.solutionfile = solutionfile + +class Vs2010Backend(backends.Backend): + def __init__(self, build): + super().__init__(build) + self.project_file_version = '10.0.30319.1' + # foo.c compiles to foo.obj, not foo.c.obj + self.source_suffix_in_obj = False + + def generate_custom_generator_commands(self, target, parent_node): + idgroup = ET.SubElement(parent_node, 'ItemDefinitionGroup') + all_output_files = [] + for genlist in target.get_generated_sources(): + if isinstance(genlist, build.CustomTarget): + all_output_files += [os.path.join(self.get_target_dir(genlist), i) for i in genlist.output] + else: + generator = genlist.get_generator() + exe = generator.get_exe() + infilelist = genlist.get_infilelist() + outfilelist = genlist.get_outfilelist() + if isinstance(exe, build.BuildTarget): + exe_file = os.path.join(self.environment.get_build_dir(), self.get_target_filename(exe)) + else: + exe_file = exe.get_command() + base_args = generator.get_arglist() + for i in range(len(infilelist)): + if len(infilelist) == len(outfilelist): + sole_output = os.path.join(self.get_target_private_dir(target), outfilelist[i]) + else: + sole_output = '' + curfile = infilelist[i] + infilename = os.path.join(self.environment.get_source_dir(), curfile) + outfiles = genlist.get_outputs_for(curfile) + outfiles = [os.path.join(self.get_target_private_dir(target), of) for of in outfiles] + all_output_files += outfiles + args = [x.replace("@INPUT@", infilename).replace('@OUTPUT@', sole_output)\ + for x in base_args] + args = [x.replace("@SOURCE_DIR@", self.environment.get_source_dir()).replace("@BUILD_DIR@", self.get_target_private_dir(target)) + for x in args] + fullcmd = [exe_file] + args + cbs = ET.SubElement(idgroup, 'CustomBuildStep') + ET.SubElement(cbs, 'Command').text = ' '.join(self.special_quote(fullcmd)) + ET.SubElement(cbs, 'Inputs').text = infilename + ET.SubElement(cbs, 'Outputs').text = ';'.join(outfiles) + ET.SubElement(cbs, 'Message').text = 'Generating sources from %s.' % infilename + pg = ET.SubElement(parent_node, 'PropertyGroup') + ET.SubElement(pg, 'CustomBuildBeforeTargets').text = 'ClCompile' + return all_output_files + + def generate(self, interp): + self.interpreter = interp + self.platform = 'Win32' + self.buildtype = self.environment.coredata.get_builtin_option('buildtype') + sln_filename = os.path.join(self.environment.get_build_dir(), self.build.project_name + '.sln') + projlist = self.generate_projects() + self.gen_testproj('RUN_TESTS', os.path.join(self.environment.get_build_dir(), 'RUN_TESTS.vcxproj')) + self.gen_regenproj('REGEN', os.path.join(self.environment.get_build_dir(), 'REGEN.vcxproj')) + self.generate_solution(sln_filename, projlist) + self.generate_regen_info(sln_filename) + open(os.path.join(self.environment.get_scratch_dir(), 'regen.stamp'), 'wb') + rulefile = os.path.join(self.environment.get_scratch_dir(), 'regen.rule') + if not os.path.exists(rulefile): + open(rulefile, 'w').write("# For some reason this needs to be here.") + + def generate_regen_info(self, sln_filename): + deps = self.get_regen_filelist() + regeninfo = RegenInfo(self.environment.get_source_dir(), + self.environment.get_build_dir(), + deps, + sln_filename) + pickle.dump(regeninfo, open(os.path.join(self.environment.get_scratch_dir(), 'regeninfo.dump'), 'wb')) + + def get_obj_target_deps(self, obj_list): + result = {} + for o in obj_list: + if isinstance(o, build.ExtractedObjects): + result[o.target.get_basename()] = True + return result.keys() + + def determine_deps(self, p): + all_deps = {} + target = self.build.targets[p[0]] + if isinstance(target, build.CustomTarget): + for d in target.dependencies: + all_deps[d.get_id()] = True + return all_deps + if isinstance(target, build.RunTarget): + for d in [target.command] + target.args: + if isinstance(d, build.BuildTarget): + all_deps[d.get_id()] = True + return all_deps + for ldep in target.link_targets: + all_deps[ldep.get_id()] = True + for objdep in self.get_obj_target_deps(target.objects): + all_deps[objdep] = True + for gendep in target.generated: + if isinstance(gendep, build.CustomTarget): + all_deps[gendep.get_id()] = True + else: + gen_exe = gendep.generator.get_exe() + if isinstance(gen_exe, build.Executable): + all_deps[gen_exe.get_id()] = True + return all_deps + + def generate_solution(self, sln_filename, projlist): + ofile = open(sln_filename, 'w') + ofile.write('Microsoft Visual Studio Solution File, Format Version 11.00\n') + ofile.write('# Visual Studio 2010\n') + prj_templ = prj_line = 'Project("{%s}") = "%s", "%s", "{%s}"\n' + for p in projlist: + prj_line = prj_templ % (self.environment.coredata.guid, p[0], p[1], p[2]) + ofile.write(prj_line) + all_deps = self.determine_deps(p) + ofile.write('\tProjectSection(ProjectDependencies) = postProject\n') + regen_guid = self.environment.coredata.regen_guid + ofile.write('\t\t{%s} = {%s}\n' % (regen_guid, regen_guid)) + for dep in all_deps.keys(): + guid = self.environment.coredata.target_guids[dep] + ofile.write('\t\t{%s} = {%s}\n' % (guid, guid)) + ofile.write('EndProjectSection\n') + ofile.write('EndProject\n') + test_line = prj_templ % (self.environment.coredata.guid, + 'RUN_TESTS', 'RUN_TESTS.vcxproj', self.environment.coredata.test_guid) + ofile.write(test_line) + ofile.write('EndProject\n') + regen_line = prj_templ % (self.environment.coredata.guid, + 'REGEN', 'REGEN.vcxproj', self.environment.coredata.regen_guid) + ofile.write(regen_line) + ofile.write('EndProject\n') + ofile.write('Global\n') + ofile.write('\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n') + ofile.write('\t\t%s|%s = %s|%s\n' % (self.buildtype, self.platform, self.buildtype, self.platform)) + ofile.write('\tEndGlobalSection\n') + ofile.write('\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n') + ofile.write('\t\t{%s}.%s|%s.ActiveCfg = %s|%s\n' % + (self.environment.coredata.regen_guid, self.buildtype, self.platform, + self.buildtype, self.platform)) + ofile.write('\t\t{%s}.%s|%s.Build.0 = %s|%s\n' % + (self.environment.coredata.regen_guid, self.buildtype, self.platform, + self.buildtype, self.platform)) + for p in projlist: + ofile.write('\t\t{%s}.%s|%s.ActiveCfg = %s|%s\n' % + (p[2], self.buildtype, self.platform, + self.buildtype, self.platform)) + if not isinstance(self.build.targets[p[0]], build.RunTarget): + ofile.write('\t\t{%s}.%s|%s.Build.0 = %s|%s\n' % + (p[2], self.buildtype, self.platform, + self.buildtype, self.platform)) + ofile.write('\t\t{%s}.%s|%s.ActiveCfg = %s|%s\n' % + (self.environment.coredata.test_guid, self.buildtype, self.platform, + self.buildtype, self.platform)) + ofile.write('\tEndGlobalSection\n') + ofile.write('\tGlobalSection(SolutionProperties) = preSolution\n') + ofile.write('\t\tHideSolutionNode = FALSE\n') + ofile.write('\tEndGlobalSection\n') + ofile.write('EndGlobal\n') + + def generate_projects(self): + projlist = [] + comp = None + for l, c in self.environment.coredata.compilers.items(): + if l == 'c' or l == 'cpp': + comp = c + break + if comp is None: + raise RuntimeError('C and C++ compilers missing.') + for name, target in self.build.targets.items(): + outdir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target)) + fname = name + '.vcxproj' + relname = os.path.join(target.subdir, fname) + projfile = os.path.join(outdir, fname) + uuid = self.environment.coredata.target_guids[name] + self.gen_vcxproj(target, projfile, uuid, comp) + projlist.append((name, relname, uuid)) + return projlist + + def split_sources(self, srclist): + sources = [] + headers = [] + for i in srclist: + if self.environment.is_header(i): + headers.append(i) + else: + sources.append(i) + return (sources, headers) + + def target_to_build_root(self, target): + if target.subdir == '': + return '' + + directories = os.path.split(target.subdir) + directories = list(filter(bool,directories)) #Filter out empty strings + + return '/'.join(['..']*len(directories)) + + def special_quote(self, arr): + return ['"%s"' % i for i in arr] + + def create_basic_crap(self, target): + project_name = target.name + root = ET.Element('Project', {'DefaultTargets' : "Build", + 'ToolsVersion' : '4.0', + 'xmlns' : 'http://schemas.microsoft.com/developer/msbuild/2003'}) + confitems = ET.SubElement(root, 'ItemGroup', {'Label' : 'ProjectConfigurations'}) + prjconf = ET.SubElement(confitems, 'ProjectConfiguration', + {'Include' : self.buildtype + '|' + self.platform}) + p = ET.SubElement(prjconf, 'Configuration') + p.text= self.buildtype + pl = ET.SubElement(prjconf, 'Platform') + pl.text = self.platform + globalgroup = ET.SubElement(root, 'PropertyGroup', Label='Globals') + guidelem = ET.SubElement(globalgroup, 'ProjectGuid') + guidelem.text = self.environment.coredata.test_guid + kw = ET.SubElement(globalgroup, 'Keyword') + kw.text = self.platform + 'Proj' + p = ET.SubElement(globalgroup, 'Platform') + p.text= self.platform + pname= ET.SubElement(globalgroup, 'ProjectName') + pname.text = project_name + ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.Default.props') + type_config = ET.SubElement(root, 'PropertyGroup', Label='Configuration') + ET.SubElement(type_config, 'ConfigurationType') + ET.SubElement(type_config, 'CharacterSet').text = 'MultiByte' + ET.SubElement(type_config, 'UseOfMfc').text = 'false' + ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.props') + direlem = ET.SubElement(root, 'PropertyGroup') + fver = ET.SubElement(direlem, '_ProjectFileVersion') + fver.text = self.project_file_version + outdir = ET.SubElement(direlem, 'OutDir') + outdir.text = '.\\' + intdir = ET.SubElement(direlem, 'IntDir') + intdir.text = 'test-temp\\' + tname = ET.SubElement(direlem, 'TargetName') + tname.text = target.name + return root + + def gen_run_target_vcxproj(self, target, ofname, guid): + root = self.create_basic_crap(target) + action = ET.SubElement(root, 'ItemDefinitionGroup') + customstep = ET.SubElement(action, 'PostBuildEvent') + cmd_raw = [target.command] + target.args + cmd = [sys.executable, os.path.join(self.environment.get_script_dir(), 'commandrunner.py'), + self.environment.get_build_dir(), self.environment.get_source_dir(), + self.get_target_dir(target)] + for i in cmd_raw: + if isinstance(i, build.BuildTarget): + cmd.append(os.path.join(self.environment.get_build_dir(), self.get_target_filename(i))) + elif isinstance(i, dependencies.ExternalProgram): + cmd += i.fullpath + else: + cmd.append(i) + cmd_templ = '''"%s" '''*len(cmd) + ET.SubElement(customstep, 'Command').text = cmd_templ % tuple(cmd) + ET.SubElement(customstep, 'Message').text = 'Running custom command.' + ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.targets') + tree = ET.ElementTree(root) + tree.write(ofname, encoding='utf-8', xml_declaration=True) + + def gen_custom_target_vcxproj(self, target, ofname, guid): + root = self.create_basic_crap(target) + action = ET.SubElement(root, 'ItemDefinitionGroup') + customstep = ET.SubElement(action, 'CustomBuildStep') + (srcs, ofilenames, cmd) = self.eval_custom_target_command(target, True) + cmd_templ = '''"%s" '''*len(cmd) + ET.SubElement(customstep, 'Command').text = cmd_templ % tuple(cmd) + ET.SubElement(customstep, 'Outputs').text = ';'.join([os.path.join(self.environment.get_build_dir(), i)\ + for i in ofilenames]) + ET.SubElement(customstep, 'Inputs').text = ';'.join([os.path.join(self.environment.get_build_dir(), i) \ + for i in srcs]) + ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.targets') + tree = ET.ElementTree(root) + tree.write(ofname, encoding='utf-8', xml_declaration=True) + + def gen_vcxproj(self, target, ofname, guid, compiler): + mlog.debug('Generating vcxproj %s.' % target.name) + entrypoint = 'WinMainCRTStartup' + subsystem = 'Windows' + if isinstance(target, build.Executable): + conftype = 'Application' + if not target.gui_app: + subsystem = 'Console' + entrypoint = 'mainCRTStartup' + elif isinstance(target, build.StaticLibrary): + conftype = 'StaticLibrary' + elif isinstance(target, build.SharedLibrary): + conftype = 'DynamicLibrary' + entrypoint = '_DllMainCrtStartup' + elif isinstance(target, build.CustomTarget): + return self.gen_custom_target_vcxproj(target, ofname, guid) + elif isinstance(target, build.RunTarget): + return self.gen_run_target_vcxproj(target, ofname, guid) + else: + raise MesonException('Unknown target type for %s' % target.get_basename()) + down = self.target_to_build_root(target) + proj_to_src_root = os.path.join(down, self.build_to_src) + proj_to_src_dir = os.path.join(proj_to_src_root, target.subdir) + (sources, headers) = self.split_sources(target.sources) + buildtype = self.buildtype + project_name = target.name + target_name = target.name + root = ET.Element('Project', {'DefaultTargets' : "Build", + 'ToolsVersion' : '4.0', + 'xmlns' : 'http://schemas.microsoft.com/developer/msbuild/2003'}) + confitems = ET.SubElement(root, 'ItemGroup', {'Label' : 'ProjectConfigurations'}) + prjconf = ET.SubElement(confitems, 'ProjectConfiguration', + {'Include' : self.buildtype + '|' + self.platform}) + p = ET.SubElement(prjconf, 'Configuration') + p.text= buildtype + pl = ET.SubElement(prjconf, 'Platform') + pl.text = self.platform + globalgroup = ET.SubElement(root, 'PropertyGroup', Label='Globals') + guidelem = ET.SubElement(globalgroup, 'ProjectGuid') + guidelem.text = guid + kw = ET.SubElement(globalgroup, 'Keyword') + kw.text = self.platform + 'Proj' + ns = ET.SubElement(globalgroup, 'RootNamespace') + ns.text = target_name + p = ET.SubElement(globalgroup, 'Platform') + p.text= self.platform + pname= ET.SubElement(globalgroup, 'ProjectName') + pname.text = project_name + ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.Default.props') + type_config = ET.SubElement(root, 'PropertyGroup', Label='Configuration') + ET.SubElement(type_config, 'ConfigurationType').text = conftype + ET.SubElement(type_config, 'CharacterSet').text = 'MultiByte' + ET.SubElement(type_config, 'WholeProgramOptimization').text = 'false' + ET.SubElement(type_config, 'UseDebugLibraries').text = 'true' + ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.props') + generated_files = self.generate_custom_generator_commands(target, root) + (gen_src, gen_hdrs) = self.split_sources(generated_files) + direlem = ET.SubElement(root, 'PropertyGroup') + fver = ET.SubElement(direlem, '_ProjectFileVersion') + fver.text = self.project_file_version + outdir = ET.SubElement(direlem, 'OutDir') + outdir.text = '.\\' + intdir = ET.SubElement(direlem, 'IntDir') + intdir.text = os.path.join(self.get_target_dir(target), target.get_basename() + '.dir') + '\\' + tname = ET.SubElement(direlem, 'TargetName') + tname.text = target_name + inclinc = ET.SubElement(direlem, 'LinkIncremental') + inclinc.text = 'true' + + compiles = ET.SubElement(root, 'ItemDefinitionGroup') + clconf = ET.SubElement(compiles, 'ClCompile') + opt = ET.SubElement(clconf, 'Optimization') + opt.text = 'disabled' + inc_dirs = [proj_to_src_dir, self.get_target_private_dir(target)] + cur_dir = target.subdir + if cur_dir == '': + cur_dir= '.' + inc_dirs.append(cur_dir) + extra_args = [] + # SUCKS, VS can not handle per-language type flags, so just use + # them all. + extra_args += compiler.get_buildtype_args(self.buildtype) + for l in self.environment.coredata.external_args.values(): + for a in l: + extra_args.append(a) + for l in self.build.global_args.values(): + for a in l: + extra_args.append(a) + for l in target.extra_args.values(): + for a in l: + extra_args.append(a) + # FIXME all the internal flags of VS (optimization etc) are represented + # by their own XML elements. In theory we should split all flags to those + # that have an XML element and those that don't and serialise them + # properly. This is a crapton of work for no real gain, so just dump them + # here. + extra_args = compiler.get_option_compile_args(self.environment.coredata.compiler_options) + if len(extra_args) > 0: + extra_args.append('%(AdditionalOptions)') + ET.SubElement(clconf, "AdditionalOptions").text = ' '.join(extra_args) + for d in target.include_dirs: + for i in d.incdirs: + curdir = os.path.join(d.curdir, i) + inc_dirs.append(self.relpath(curdir, target.subdir)) # build dir + inc_dirs.append(os.path.join(proj_to_src_root, curdir)) # src dir + inc_dirs.append('%(AdditionalIncludeDirectories)') + ET.SubElement(clconf, 'AdditionalIncludeDirectories').text = ';'.join(inc_dirs) + preproc = ET.SubElement(clconf, 'PreprocessorDefinitions') + rebuild = ET.SubElement(clconf, 'MinimalRebuild') + rebuild.text = 'true' + rtlib = ET.SubElement(clconf, 'RuntimeLibrary') + rtlib.text = 'MultiThreadedDebugDLL' + funclink = ET.SubElement(clconf, 'FunctionLevelLinking') + funclink.text = 'true' + pch = ET.SubElement(clconf, 'PrecompiledHeader') + warnings = ET.SubElement(clconf, 'WarningLevel') + warnings.text = 'Level3' + debinfo = ET.SubElement(clconf, 'DebugInformationFormat') + debinfo.text = 'EditAndContinue' + resourcecompile = ET.SubElement(compiles, 'ResourceCompile') + ET.SubElement(resourcecompile, 'PreprocessorDefinitions') + link = ET.SubElement(compiles, 'Link') + # Put all language args here, too. + extra_link_args = compiler.get_option_link_args(self.environment.coredata.compiler_options) + extra_link_args += compiler.get_buildtype_linker_args(self.buildtype) + for l in self.environment.coredata.external_link_args.values(): + for a in l: + extra_link_args.append(a) + for l in target.link_args: + for a in l: + extra_link_args.append(a) + if len(extra_args) > 0: + extra_args.append('%(AdditionalOptions)') + ET.SubElement(link, "AdditionalOptions").text = ' '.join(extra_args) + + additional_links = [] + for t in target.link_targets: + lobj = self.build.targets[t.get_id()] + rel_path = self.relpath(lobj.subdir, target.subdir) + linkname = os.path.join(rel_path, lobj.get_import_filename()) + additional_links.append(linkname) + for o in self.flatten_object_list(target, down): + assert(isinstance(o, str)) + additional_links.append(o) + if len(additional_links) > 0: + additional_links.append('%(AdditionalDependencies)') + ET.SubElement(link, 'AdditionalDependencies').text = ';'.join(additional_links) + ofile = ET.SubElement(link, 'OutputFile') + ofile.text = '$(OutDir)%s' % target.get_filename() + addlibdir = ET.SubElement(link, 'AdditionalLibraryDirectories') + addlibdir.text = '%(AdditionalLibraryDirectories)' + subsys = ET.SubElement(link, 'SubSystem') + subsys.text = subsystem + gendeb = ET.SubElement(link, 'GenerateDebugInformation') + gendeb.text = 'true' + if isinstance(target, build.SharedLibrary): + ET.SubElement(link, 'ImportLibrary').text = target.get_import_filename() + pdb = ET.SubElement(link, 'ProgramDataBaseFileName') + pdb.text = '$(OutDir}%s.pdb' % target_name + if isinstance(target, build.Executable): + ET.SubElement(link, 'EntryPointSymbol').text = entrypoint + targetmachine = ET.SubElement(link, 'TargetMachine') + targetmachine.text = 'MachineX86' + + if len(headers) + len(gen_hdrs) > 0: + inc_hdrs = ET.SubElement(root, 'ItemGroup') + for h in headers: + relpath = h.rel_to_builddir(proj_to_src_root) + ET.SubElement(inc_hdrs, 'CLInclude', Include=relpath) + for h in gen_hdrs: + if isinstance(h, str): + relpath = h + else: + relpath = h.rel_to_builddir(proj_to_src_root) + ET.SubElement(inc_hdrs, 'CLInclude', Include = relpath) + if len(sources) + len(gen_src) > 0: + inc_src = ET.SubElement(root, 'ItemGroup') + for s in sources: + relpath = s.rel_to_builddir(proj_to_src_root) + ET.SubElement(inc_src, 'CLCompile', Include=relpath) + for s in gen_src: + relpath = self.relpath(s, target.subdir) + ET.SubElement(inc_src, 'CLCompile', Include=relpath) + ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.targets') + # Reference the regen target. + ig = ET.SubElement(root, 'ItemGroup') + pref = ET.SubElement(ig, 'ProjectReference', Include=os.path.join(self.environment.get_build_dir(), 'REGEN.vcxproj')) + ET.SubElement(pref, 'Project').text = self.environment.coredata.regen_guid + tree = ET.ElementTree(root) + tree.write(ofname, encoding='utf-8', xml_declaration=True) + # ElementTree can not do prettyprinting so do it manually + doc = xml.dom.minidom.parse(ofname) + open(ofname, 'w').write(doc.toprettyxml()) + # World of horror! Python insists on not quoting quotes and + # fixing the escaped " into &quot; whereas MSVS + # requires quoted but not fixed elements. Enter horrible hack. + txt = open(ofname, 'r').read() + open(ofname, 'w').write(txt.replace('&quot;', '"')) + + def gen_regenproj(self, project_name, ofname): + root = ET.Element('Project', {'DefaultTargets': 'Build', + 'ToolsVersion' : '4.0', + 'xmlns' : 'http://schemas.microsoft.com/developer/msbuild/2003'}) + confitems = ET.SubElement(root, 'ItemGroup', {'Label' : 'ProjectConfigurations'}) + prjconf = ET.SubElement(confitems, 'ProjectConfiguration', + {'Include' : self.buildtype + '|' + self.platform}) + p = ET.SubElement(prjconf, 'Configuration') + p.text= self.buildtype + pl = ET.SubElement(prjconf, 'Platform') + pl.text = self.platform + globalgroup = ET.SubElement(root, 'PropertyGroup', Label='Globals') + guidelem = ET.SubElement(globalgroup, 'ProjectGuid') + guidelem.text = self.environment.coredata.test_guid + kw = ET.SubElement(globalgroup, 'Keyword') + kw.text = self.platform + 'Proj' + p = ET.SubElement(globalgroup, 'Platform') + p.text = self.platform + pname= ET.SubElement(globalgroup, 'ProjectName') + pname.text = project_name + ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.Default.props') + type_config = ET.SubElement(root, 'PropertyGroup', Label='Configuration') + ET.SubElement(type_config, 'ConfigurationType').text = "Utility" + ET.SubElement(type_config, 'CharacterSet').text = 'MultiByte' + ET.SubElement(type_config, 'UseOfMfc').text = 'false' + ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.props') + direlem = ET.SubElement(root, 'PropertyGroup') + fver = ET.SubElement(direlem, '_ProjectFileVersion') + fver.text = self.project_file_version + outdir = ET.SubElement(direlem, 'OutDir') + outdir.text = '.\\' + intdir = ET.SubElement(direlem, 'IntDir') + intdir.text = 'test-temp\\' + tname = ET.SubElement(direlem, 'TargetName') + tname.text = project_name + + action = ET.SubElement(root, 'ItemDefinitionGroup') + midl = ET.SubElement(action, 'Midl') + ET.SubElement(midl, "AdditionalIncludeDirectories").text = '%(AdditionalIncludeDirectories)' + ET.SubElement(midl, "OutputDirectory").text = '$(IntDir)' + ET.SubElement(midl, 'HeaderFileName').text = '%(Filename).h' + ET.SubElement(midl, 'TypeLibraryName').text = '%(Filename).tlb' + ET.SubElement(midl, 'InterfaceIdentifierFilename').text = '%(Filename)_i.c' + ET.SubElement(midl, 'ProxyFileName').text = '%(Filename)_p.c' + script_root = self.environment.get_script_dir() + regen_script = os.path.join(script_root, 'regen_checker.py') + private_dir = self.environment.get_scratch_dir() + cmd_templ = '''setlocal +"%s" "%s" "%s" +if %%errorlevel%% neq 0 goto :cmEnd +:cmEnd +endlocal & call :cmErrorLevel %%errorlevel%% & goto :cmDone +:cmErrorLevel +exit /b %%1 +:cmDone +if %%errorlevel%% neq 0 goto :VCEnd''' + igroup = ET.SubElement(root, 'ItemGroup') + custombuild = ET.SubElement(igroup, 'CustomBuild', Include='meson-private/regen.rule') + message = ET.SubElement(custombuild, 'Message') + message.text = 'Checking whether solution needs to be regenerated.' + ET.SubElement(custombuild, 'Command').text = cmd_templ % \ + (sys.executable, regen_script, private_dir) + ET.SubElement(custombuild, 'Outputs').text = os.path.join(self.environment.get_scratch_dir(), 'regen.stamp') + deps = self.get_regen_filelist() + depstr = ';'.join([os.path.join(self.environment.get_source_dir(), d) for d in deps]) + ET.SubElement(custombuild, 'AdditionalInputs').text = depstr + ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.targets') + ET.SubElement(root, 'ImportGroup', Label='ExtensionTargets') + tree = ET.ElementTree(root) + tree.write(ofname, encoding='utf-8', xml_declaration=True) + + def gen_testproj(self, target_name, ofname): + project_name = target_name + root = ET.Element('Project', {'DefaultTargets' : "Build", + 'ToolsVersion' : '4.0', + 'xmlns' : 'http://schemas.microsoft.com/developer/msbuild/2003'}) + confitems = ET.SubElement(root, 'ItemGroup', {'Label' : 'ProjectConfigurations'}) + prjconf = ET.SubElement(confitems, 'ProjectConfiguration', + {'Include' : self.buildtype + '|' + self.platform}) + p = ET.SubElement(prjconf, 'Configuration') + p.text= self.buildtype + pl = ET.SubElement(prjconf, 'Platform') + pl.text = self.platform + globalgroup = ET.SubElement(root, 'PropertyGroup', Label='Globals') + guidelem = ET.SubElement(globalgroup, 'ProjectGuid') + guidelem.text = self.environment.coredata.test_guid + kw = ET.SubElement(globalgroup, 'Keyword') + kw.text = self.platform + 'Proj' + p = ET.SubElement(globalgroup, 'Platform') + p.text= self.platform + pname= ET.SubElement(globalgroup, 'ProjectName') + pname.text = project_name + ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.Default.props') + type_config = ET.SubElement(root, 'PropertyGroup', Label='Configuration') + ET.SubElement(type_config, 'ConfigurationType') + ET.SubElement(type_config, 'CharacterSet').text = 'MultiByte' + ET.SubElement(type_config, 'UseOfMfc').text = 'false' + ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.props') + direlem = ET.SubElement(root, 'PropertyGroup') + fver = ET.SubElement(direlem, '_ProjectFileVersion') + fver.text = self.project_file_version + outdir = ET.SubElement(direlem, 'OutDir') + outdir.text = '.\\' + intdir = ET.SubElement(direlem, 'IntDir') + intdir.text = 'test-temp\\' + tname = ET.SubElement(direlem, 'TargetName') + tname.text = target_name + + action = ET.SubElement(root, 'ItemDefinitionGroup') + midl = ET.SubElement(action, 'Midl') + ET.SubElement(midl, "AdditionalIncludeDirectories").text = '%(AdditionalIncludeDirectories)' + ET.SubElement(midl, "OutputDirectory").text = '$(IntDir)' + ET.SubElement(midl, 'HeaderFileName').text = '%(Filename).h' + ET.SubElement(midl, 'TypeLibraryName').text = '%(Filename).tlb' + ET.SubElement(midl, 'InterfaceIdentifierFilename').text = '%(Filename)_i.c' + ET.SubElement(midl, 'ProxyFileName').text = '%(Filename)_p.c' + postbuild = ET.SubElement(action, 'PostBuildEvent') + ET.SubElement(postbuild, 'Message') + script_root = self.environment.get_script_dir() + test_script = os.path.join(script_root, 'meson_test.py') + test_data = os.path.join(self.environment.get_scratch_dir(), 'meson_test_setup.dat') + cmd_templ = '''setlocal +"%s" "%s" "%s" +if %%errorlevel%% neq 0 goto :cmEnd +:cmEnd +endlocal & call :cmErrorLevel %%errorlevel%% & goto :cmDone +:cmErrorLevel +exit /b %%1 +:cmDone +if %%errorlevel%% neq 0 goto :VCEnd''' + ET.SubElement(postbuild, 'Command').text = cmd_templ % (sys.executable, test_script, test_data) + ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.targets') + tree = ET.ElementTree(root) + tree.write(ofname, encoding='utf-8', xml_declaration=True) + datafile = open(test_data, 'wb') + self.serialise_tests() + datafile.close() + # ElementTree can not do prettyprinting so do it manually + #doc = xml.dom.minidom.parse(ofname) + #open(ofname, 'w').write(doc.toprettyxml()) diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py new file mode 100644 index 0000000..2818fa0 --- /dev/null +++ b/mesonbuild/wrap/wrap.py @@ -0,0 +1,212 @@ +# 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. + +from .. import mlog +import urllib.request, os, hashlib, shutil +import subprocess +import sys + +try: + import ssl + has_ssl = True + API_ROOT = 'https://wrapdb.mesonbuild.com/v1/' +except ImportError: + has_ssl = False + API_ROOT = 'http://wrapdb.mesonbuild.com/v1/' + +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 open_wrapdburl(urlstring): + global ssl_warning_printed + if has_ssl: + try: + return urllib.request.urlopen(urlstring)#, context=build_ssl_context()) + except urllib.error.URLError: + if not ssl_warning_printed: + print('SSL connection failed. Falling back to unencrypted connections.') + ssl_warning_printed = True + if not ssl_warning_printed: + print('Warning: SSL not available, traffic not authenticated.', + file=sys.stderr) + ssl_warning_printed = True + # Trying to open SSL connection to wrapdb fails because the + # certificate is not known. + if urlstring.startswith('https'): + urlstring = 'http' + urlstring[5:] + return urllib.request.urlopen(urlstring) + + +class PackageDefinition: + def __init__(self, fname): + self.values = {} + ifile = open(fname) + first = ifile.readline().strip() + + if first == '[wrap-file]': + self.type = 'file' + elif first == '[wrap-git]': + self.type = 'git' + else: + raise RuntimeError('Invalid format of package file') + for line in ifile: + line = line.strip() + if line == '': + continue + (k, v) = line.split('=', 1) + k = k.strip() + v = v.strip() + self.values[k] = v + + def get(self, key): + return self.values[key] + + def has_patch(self): + return 'patch_url' in self.values + +class Resolver: + def __init__(self, subdir_root): + self.subdir_root = subdir_root + self.cachedir = os.path.join(self.subdir_root, 'packagecache') + + def resolve(self, packagename): + fname = os.path.join(self.subdir_root, packagename + '.wrap') + dirname = os.path.join(self.subdir_root, packagename) + if not os.path.isfile(fname): + if os.path.isdir(dirname): + # No wrap file but dir exists -> user put it there manually. + return packagename + return None + p = PackageDefinition(fname) + if p.type == 'file': + if not os.path.isdir(self.cachedir): + os.mkdir(self.cachedir) + self.download(p, packagename) + self.extract_package(p) + elif p.type == 'git': + self.get_git(p) + else: + raise RuntimeError('Unreachable code.') + return p.get('directory') + + def get_git(self, p): + checkoutdir = os.path.join(self.subdir_root, p.get('directory')) + revno = p.get('revision') + is_there = os.path.isdir(checkoutdir) + if is_there: + if revno.lower() == 'head': + subprocess.check_call(['git', 'pull'], cwd=checkoutdir) + else: + if subprocess.call(['git', 'checkout', revno], cwd=checkoutdir) != 0: + subprocess.check_call(['git', 'fetch'], cwd=checkoutdir) + subprocess.check_call(['git', 'checkout', revno], + cwd=checkoutdir) + else: + subprocess.check_call(['git', 'clone', p.get('url'), + p.get('directory')], cwd=self.subdir_root) + if revno.lower() != 'head': + subprocess.check_call(['git', 'checkout', revno], + cwd=checkoutdir) + + + def get_data(self, url): + blocksize = 10*1024 + if url.startswith('https://wrapdb.mesonbuild.com'): + resp = open_wrapdburl(url) + else: + resp = urllib.request.urlopen(url) + dlsize = int(resp.info()['Content-Length']) + print('Download size:', dlsize) + print('Downloading: ', end='') + sys.stdout.flush() + printed_dots = 0 + blocks = [] + downloaded = 0 + while True: + block = resp.read(blocksize) + if block == b'': + break + downloaded += len(block) + blocks.append(block) + ratio = int(downloaded/dlsize * 10) + while printed_dots < ratio: + print('.', end='') + sys.stdout.flush() + printed_dots += 1 + print('') + resp.close() + return b''.join(blocks) + + def get_hash(self, data): + h = hashlib.sha256() + h.update(data) + hashvalue = h.hexdigest() + return hashvalue + + def download(self, p, packagename): + ofname = os.path.join(self.cachedir, p.get('source_filename')) + if os.path.exists(ofname): + mlog.log('Using', mlog.bold(packagename), 'from cache.') + return + srcurl = p.get('source_url') + mlog.log('Dowloading', mlog.bold(packagename), 'from', mlog.bold(srcurl)) + srcdata = self.get_data(srcurl) + dhash = self.get_hash(srcdata) + expected = p.get('source_hash') + if dhash != expected: + raise RuntimeError('Incorrect hash for source %s:\n %s expected\n %s actual.' % (packagename, expected, dhash)) + open(ofname, 'wb').write(srcdata) + if p.has_patch(): + purl = p.get('patch_url') + mlog.log('Downloading patch from', mlog.bold(purl)) + pdata = self.get_data(purl) + phash = self.get_hash(pdata) + expected = p.get('patch_hash') + if phash != expected: + raise RuntimeError('Incorrect hash for patch %s:\n %s expected\n %s actual' % (packagename, expected, phash)) + open(os.path.join(self.cachedir, p.get('patch_filename')), 'wb').write(pdata) + else: + mlog.log('Package does not require patch.') + + def extract_package(self, package): + if sys.version_info < (3, 5): + try: + import lzma + del lzma + try: + shutil.register_unpack_format('xztar', ['.tar.xz', '.txz'], shutil._unpack_tarfile, [], "xz'ed tar-file") + except shutil.RegistryError: + pass + except ImportError: + pass + target_dir = os.path.join(self.subdir_root, package.get('directory')) + if os.path.isdir(target_dir): + return + extract_dir = self.subdir_root + # Some upstreams ship packages that do not have a leading directory. + # Create one for them. + try: + package.get('lead_directory_missing') + os.mkdir(target_dir) + extract_dir = target_dir + except KeyError: + pass + shutil.unpack_archive(os.path.join(self.cachedir, package.get('source_filename')), extract_dir) + if package.has_patch(): + shutil.unpack_archive(os.path.join(self.cachedir, package.get('patch_filename')), self.subdir_root) diff --git a/mesonbuild/wrap/wraptool.py b/mesonbuild/wrap/wraptool.py new file mode 100755 index 0000000..d2f0a28 --- /dev/null +++ b/mesonbuild/wrap/wraptool.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python3 + +# Copyright 2015-2016 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import sys, os +import configparser +import shutil + +from glob import glob + +from .wrap import API_ROOT, open_wrapdburl + +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 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)) + +def run(args): + if len(sys.argv) < 1 or sys.argv[0] == '-h' or sys.argv[1] == '--help': + print_help() + return 0 + command = args[0] + args = args[1:] + if command == 'list': + list_projects() + elif command == 'search': + if len(args) != 1: + print('Search requires exactly one argument.') + return 1 + search(args[0]) + elif command == 'install': + if len(args) != 1: + print('Install requires exactly one argument.') + return 1 + install(args[0]) + elif command == 'update': + if len(args) != 1: + print('update requires exactly one argument.') + return 1 + update(args[0]) + elif command == 'info': + if len(args) != 1: + print('info requires exactly one argument.') + return 1 + info(args[0]) + elif command == 'status': + status() + else: + print('Unknown command', command) + return 1 + return 0 + +if __name__ == '__main__': + sys.exit(run(sys.argv[1:])) diff --git a/mesonbuild/xcodebackend.py b/mesonbuild/xcodebackend.py new file mode 100644 index 0000000..8ac3f67 --- /dev/null +++ b/mesonbuild/xcodebackend.py @@ -0,0 +1,775 @@ +# 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. + +from . import backends, build +from . import mesonlib +import uuid, os, sys + +from .coredata import MesonException + +class XCodeBackend(backends.Backend): + def __init__(self, build): + super().__init__(build) + self.project_uid = self.environment.coredata.guid.replace('-', '')[:24] + self.project_conflist = self.gen_id() + self.indent = ' ' + self.indent_level = 0 + self.xcodetypemap = {'c' : 'sourcecode.c.c', + 'a' : 'archive.ar', + 'cc': 'sourcecode.cpp.cpp', + 'cxx' : 'sourcecode.cpp.cpp', + 'cpp' : 'sourcecode.cpp.cpp', + 'c++' : 'sourcecode.cpp.cpp', + 'm' : 'sourcecode.c.objc', + 'mm' : 'sourcecode.cpp.objcpp', + 'h' : 'sourcecode.c.h', + 'hpp' : 'sourcecode.cpp.h', + 'hxx' : 'sourcecode.cpp.h', + 'hh' : 'sourcecode.cpp.hh', + 'inc' : 'sourcecode.c.h', + 'dylib' : 'compiled.mach-o.dylib', + 'o' : 'compiled.mach-o.objfile',} + self.maingroup_id = self.gen_id() + self.all_id = self.gen_id() + self.all_buildconf_id = self.gen_id() + self.buildtypes = ['debug'] + self.test_id = self.gen_id() + self.test_command_id = self.gen_id() + self.test_buildconf_id = self.gen_id() + + def gen_id(self): + return str(uuid.uuid4()).upper().replace('-', '')[:24] + + def get_target_dir(self, target): + dirname = os.path.join(target.get_subdir(), self.environment.coredata.get_builtin_option('buildtype')) + os.makedirs(os.path.join(self.environment.get_build_dir(), dirname), exist_ok=True) + return dirname + + def write_line(self, text): + self.ofile.write(self.indent*self.indent_level + text) + if not text.endswith('\n'): + self.ofile.write('\n') + + def generate(self, interp): + self.interpreter = interp + self.serialise_tests() + self.generate_filemap() + self.generate_buildmap() + self.generate_buildstylemap() + self.generate_build_phase_map() + self.generate_build_configuration_map() + self.generate_build_configurationlist_map() + self.generate_project_configurations_map() + self.generate_buildall_configurations_map() + self.generate_test_configurations_map() + self.generate_native_target_map() + self.generate_source_phase_map() + self.generate_target_dependency_map() + self.generate_pbxdep_map() + self.generate_containerproxy_map() + self.proj_dir = os.path.join(self.environment.get_build_dir(), self.build.project_name + '.xcodeproj') + os.makedirs(self.proj_dir, exist_ok=True) + self.proj_file = os.path.join(self.proj_dir, 'project.pbxproj') + self.ofile = open(self.proj_file, 'w') + self.generate_prefix() + self.generate_pbx_aggregate_target() + self.generate_pbx_build_file() + self.generate_pbx_build_style() + self.generate_pbx_container_item_proxy() + self.generate_pbx_file_reference() + self.generate_pbx_group() + self.generate_pbx_native_target() + self.generate_pbx_project() + self.generate_pbx_shell_build_phase() + self.generate_pbx_sources_build_phase() + self.generate_pbx_target_dependency() + self.generate_xc_build_configuration() + self.generate_xc_configurationList() + self.generate_suffix() + + def get_xcodetype(self, fname): + return self.xcodetypemap[fname.split('.')[-1]] + + def generate_filemap(self): + self.filemap = {} # Key is source file relative to src root. + self.target_filemap = {} + for name, t in self.build.targets.items(): + for s in t.sources: + if isinstance(s, mesonlib.File): + s = os.path.join(s.subdir, s.fname) + self.filemap[s] = self.gen_id() + for o in t.objects: + if isinstance(o, str): + o = os.path.join(t.subdir, o) + self.filemap[o] = self.gen_id() + self.target_filemap[name] = self.gen_id() + + def generate_buildmap(self): + self.buildmap = {} + for t in self.build.targets.values(): + for s in t.sources: + s = os.path.join(s.subdir, s.fname) + self.buildmap[s] = self.gen_id() + for o in t.objects: + o = os.path.join(t.subdir, o) + if isinstance(o, str): + self.buildmap[o] = self.gen_id() + + def generate_buildstylemap(self): + self.buildstylemap = {'debug' : self.gen_id()} + + def generate_build_phase_map(self): + self.buildphasemap = {} + for t in self.build.targets: + self.buildphasemap[t] = self.gen_id() + + def generate_build_configuration_map(self): + self.buildconfmap = {} + for t in self.build.targets: + bconfs = {'debug' : self.gen_id()} + self.buildconfmap[t] = bconfs + + def generate_project_configurations_map(self): + self.project_configurations = {'debug' : self.gen_id()} + + def generate_buildall_configurations_map(self): + self.buildall_configurations = {'debug' : self.gen_id()} + + def generate_test_configurations_map(self): + self.test_configurations = {'debug' : self.gen_id()} + + def generate_build_configurationlist_map(self): + self.buildconflistmap = {} + for t in self.build.targets: + self.buildconflistmap[t] = self.gen_id() + + def generate_native_target_map(self): + self.native_targets = {} + for t in self.build.targets: + self.native_targets[t] = self.gen_id() + + def generate_target_dependency_map(self): + self.target_dependency_map = {} + for tname, t in self.build.targets.items(): + for target in t.link_targets: + self.target_dependency_map[(tname, target.get_basename())] = self.gen_id() + + def generate_pbxdep_map(self): + self.pbx_dep_map = {} + for t in self.build.targets: + self.pbx_dep_map[t] = self.gen_id() + + def generate_containerproxy_map(self): + self.containerproxy_map = {} + for t in self.build.targets: + self.containerproxy_map[t] = self.gen_id() + + def generate_source_phase_map(self): + self.source_phase = {} + for t in self.build.targets: + self.source_phase[t] = self.gen_id() + + def generate_pbx_aggregate_target(self): + self.ofile.write('\n/* Begin PBXAggregateTarget section */\n') + self.write_line('%s /* ALL_BUILD */ = {' % self.all_id) + self.indent_level+=1 + self.write_line('isa = PBXAggregateTarget;') + self.write_line('buildConfigurationList = %s;' % self.all_buildconf_id) + self.write_line('buildPhases = (') + self.write_line(');') + self.write_line('dependencies = (') + self.indent_level+=1 + for t in self.build.targets: + self.write_line('%s /* PBXTargetDependency */,' % self.pbx_dep_map[t]) + self.indent_level-=1 + self.write_line(');') + self.write_line('name = ALL_BUILD;') + self.write_line('productName = ALL_BUILD;') + self.indent_level-=1 + self.write_line('};') + self.write_line('%s /* RUN_TESTS */ = {' % self.test_id) + self.indent_level +=1 + self.write_line('isa = PBXAggregateTarget;') + self.write_line('buildConfigurationList = %s;' % self.test_buildconf_id) + self.write_line('buildPhases = (') + self.indent_level+=1 + self.write_line('%s /* test run command */,' % self.test_command_id) + self.indent_level-=1 + self.write_line(');') + self.write_line('dependencies = (') + self.write_line(');') + self.write_line('name = RUN_TESTS;') + self.write_line('productName = RUN_TESTS;') + self.indent_level-=1 + self.write_line('};') + self.ofile.write('/* End PBXAggregateTarget section */\n') + + def generate_pbx_build_file(self): + self.ofile.write('\n/* Begin PBXBuildFile section */\n') + templ = '%s /* %s */ = { isa = PBXBuildFile; fileRef = %s /* %s */; settings = { COMPILER_FLAGS = "%s"; }; };\n' + otempl = '%s /* %s */ = { isa = PBXBuildFile; fileRef = %s /* %s */;};\n' + for t in self.build.targets.values(): + for s in t.sources: + if isinstance(s, str): + s = os.path.join(t.subdir, s) + idval = self.buildmap[s] + fullpath = os.path.join(self.environment.get_source_dir(), s) + fileref = self.filemap[s] + fullpath2 = fullpath + compiler_args = '' + self.ofile.write(templ % (idval, fullpath, fileref, fullpath2, compiler_args)) + for o in t.objects: + o = os.path.join(t.subdir, o) + idval = self.buildmap[o] + fileref = self.filemap[o] + fullpath = os.path.join(self.environment.get_source_dir(), o) + fullpath2 = fullpath + self.ofile.write(otempl % (idval, fullpath, fileref, fullpath2)) + self.ofile.write('/* End PBXBuildFile section */\n') + + def generate_pbx_build_style(self): + self.ofile.write('\n/* Begin PBXBuildStyle section */\n') + for name, idval in self.buildstylemap.items(): + self.write_line('%s /* %s */ = {\n' % (idval, name)) + self.indent_level += 1 + self.write_line('isa = PBXBuildStyle;\n') + self.write_line('buildSettings = {\n') + self.indent_level += 1 + self.write_line('COPY_PHASE_STRIP = NO;\n') + self.indent_level -= 1 + self.write_line('};\n') + self.write_line('name = %s;\n' % name) + self.indent_level -= 1 + self.write_line('};\n') + self.ofile.write('/* End PBXBuildStyle section */\n') + + def generate_pbx_container_item_proxy(self): + self.ofile.write('\n/* Begin PBXContainerItemProxy section */\n') + for t in self.build.targets: + self.write_line('%s /* PBXContainerItemProxy */ = {' % self.containerproxy_map[t]) + self.indent_level += 1 + self.write_line('isa = PBXContainerItemProxy;') + self.write_line('containerPortal = %s /* Project object */;' % self.project_uid) + self.write_line('proxyType = 1;') + self.write_line('remoteGlobalIDString = %s;' % self.native_targets[t]) + self.write_line('remoteInfo = %s;' % t) + self.indent_level-=1 + self.write_line('};') + self.ofile.write('/* End PBXContainerItemProxy section */\n') + + def generate_pbx_file_reference(self): + self.ofile.write('\n/* Begin PBXFileReference section */\n') + src_templ = '%s /* %s */ = { isa = PBXFileReference; explicitFileType = "%s"; fileEncoding = 4; name = "%s"; path = "%s"; sourceTree = SOURCE_ROOT; };\n' + for fname, idval in self.filemap.items(): + fullpath = os.path.join(self.environment.get_source_dir(), fname) + xcodetype = self.get_xcodetype(fname) + name = os.path.split(fname)[-1] + path = fname + self.ofile.write(src_templ % (idval, fullpath, xcodetype, name, path)) + target_templ = '%s /* %s */ = { isa = PBXFileReference; explicitFileType = "%s"; path = %s; refType = %d; sourceTree = BUILT_PRODUCTS_DIR; };\n' + for tname, idval in self.target_filemap.items(): + t = self.build.targets[tname] + fname = t.get_filename() + reftype = 0 + if isinstance(t, build.Executable): + typestr = 'compiled.mach-o.executable' + path = t.get_filename() + elif isinstance(t, build.SharedLibrary): + # OSX has a completely different shared library + # naming scheme so do this manually. + typestr = self.get_xcodetype('dummy.dylib') + path = t.get_osx_filename() + else: + typestr = self.get_xcodetype(fname) + path = '"%s"' % t.get_filename() + self.ofile.write(target_templ % (idval, tname, typestr, path, reftype)) + self.ofile.write('/* End PBXFileReference section */\n') + + def generate_pbx_group(self): + groupmap = {} + target_src_map = {} + for t in self.build.targets: + groupmap[t] = self.gen_id() + target_src_map[t] = self.gen_id() + self.ofile.write('\n/* Begin PBXGroup section */\n') + sources_id = self.gen_id() + resources_id = self.gen_id() + products_id = self.gen_id() + self.write_line('%s = {' % self.maingroup_id) + self.indent_level+=1 + self.write_line('isa = PBXGroup;') + self.write_line('children = (') + self.indent_level+=1 + self.write_line('%s /* Sources */,' % sources_id) + self.write_line('%s /* Resources */,' % resources_id) + self.write_line('%s /* Products */,' % products_id) + self.indent_level-=1 + self.write_line(');') + self.write_line('sourceTree = "<group>";') + self.indent_level -= 1 + self.write_line('};') + + # Sources + self.write_line('%s /* Sources */ = {' % sources_id) + self.indent_level+=1 + self.write_line('isa = PBXGroup;') + self.write_line('children = (') + self.indent_level+=1 + for t in self.build.targets: + self.write_line('%s /* %s */,' % (groupmap[t], t)) + self.indent_level-=1 + self.write_line(');') + self.write_line('name = Sources;') + self.write_line('sourcetree = "<group>";') + self.indent_level-=1 + self.write_line('};') + + self.write_line('%s /* Resources */ = {' % resources_id) + self.indent_level+=1 + self.write_line('isa = PBXGroup;') + self.write_line('children = (') + self.write_line(');') + self.write_line('name = Resources;') + self.write_line('sourceTree = "<group>";') + self.indent_level-=1 + self.write_line('};') + + # Targets + for t in self.build.targets: + self.write_line('%s /* %s */ = {' % (groupmap[t], t)) + self.indent_level+=1 + self.write_line('isa = PBXGroup;') + self.write_line('children = (') + self.indent_level+=1 + self.write_line('%s /* Source files */,' % target_src_map[t]) + self.indent_level-=1 + self.write_line(');') + self.write_line('name = %s;' % t) + self.write_line('sourceTree = "<group>";') + self.indent_level-=1 + self.write_line('};') + self.write_line('%s /* Source files */ = {' % target_src_map[t]) + self.indent_level+=1 + self.write_line('isa = PBXGroup;') + self.write_line('children = (') + self.indent_level+=1 + for s in self.build.targets[t].sources: + s = os.path.join(s.subdir, s.fname) + if isinstance(s, str): + self.write_line('%s /* %s */,' % (self.filemap[s], s)) + for o in self.build.targets[t].objects: + o = os.path.join(self.build.targets[t].subdir, o) + self.write_line('%s /* %s */,' % (self.filemap[o], o)) + self.indent_level-=1 + self.write_line(');') + self.write_line('name = "Source files";') + self.write_line('sourceTree = "<group>";') + self.indent_level-=1 + self.write_line('};') + + # And finally products + self.write_line('%s /* Products */ = {' % products_id) + self.indent_level+=1 + self.write_line('isa = PBXGroup;') + self.write_line('children = (') + self.indent_level+=1 + for t in self.build.targets: + self.write_line('%s /* %s */,' % (self.target_filemap[t], t)) + self.indent_level-=1 + self.write_line(');') + self.write_line('name = Products;') + self.write_line('sourceTree = "<group>";') + self.indent_level-=1 + self.write_line('};') + self.ofile.write('/* End PBXGroup section */\n') + + def generate_pbx_native_target(self): + self.ofile.write('\n/* Begin PBXNativeTarget section */\n') + for tname, idval in self.native_targets.items(): + t = self.build.targets[tname] + self.write_line('%s /* %s */ = {' % (idval, tname)) + self.indent_level+=1 + self.write_line('isa = PBXNativeTarget;') + self.write_line('buildConfigurationList = %s /* Build configuration list for PBXNativeTarget "%s" */;'\ + % (self.buildconflistmap[tname], tname)) + self.write_line('buildPhases = (') + self.indent_level+=1 + self.write_line('%s /* Sources */,' % self.buildphasemap[tname]) + self.indent_level-=1 + self.write_line(');') + self.write_line('buildRules = (') + self.write_line(');') + self.write_line('dependencies = (') + self.indent_level+=1 + for lt in self.build.targets[tname].link_targets: + # NOT DOCUMENTED, may need to make different links + # to same target have different targetdependency item. + idval = self.pbx_dep_map[lt.get_basename()] + self.write_line('%s /* PBXTargetDependency */,' % idval) + self.indent_level -=1 + self.write_line(");") + self.write_line('name = %s;' % tname) + self.write_line('productName = %s;' % tname) + self.write_line('productReference = %s /* %s */;' % (self.target_filemap[tname], tname)) + if isinstance(t, build.Executable): + typestr = 'com.apple.product-type.tool' + elif isinstance(t, build.StaticLibrary): + typestr = 'com.apple.product-type.library.static' + elif isinstance(t, build.SharedLibrary): + typestr = 'com.apple.product-type.library.dynamic' + else: + raise MesonException('Unknown target type for %s' % tname) + self.write_line('productType = "%s";' % typestr) + self.indent_level-=1 + self.write_line('};') + self.ofile.write('/* End PBXNativeTarget section */\n') + + def generate_pbx_project(self): + self.ofile.write('\n/* Begin PBXProject section */\n') + self.write_line('%s /* Project object */ = {' % self.project_uid) + self.indent_level += 1 + self.write_line('isa = PBXProject;') + self.write_line('attributes = {') + self.indent_level += 1 + self.write_line('BuildIndependentTargetsInParallel = YES;') + self.indent_level -= 1 + self.write_line('};') + conftempl = 'buildConfigurationList = %s /* build configuration list for PBXProject "%s"*/;' + self.write_line(conftempl % (self.project_conflist, self.build.project_name)) + self.write_line('buildSettings = {') + self.write_line('};') + self.write_line('buildStyles = (') + self.indent_level += 1 + for name, idval in self.buildstylemap.items(): + self.write_line('%s /* %s */,' % (idval, name)) + self.indent_level -= 1 + self.write_line(');') + self.write_line('compatibilityVersion = "Xcode 3.2";') + self.write_line('hasScannedForEncodings = 0;') + self.write_line('mainGroup = %s;' % self.maingroup_id) + self.write_line('projectDirPath = "%s";' % self.build_to_src) + self.write_line('projectRoot = "";') + self.write_line('targets = (') + self.indent_level += 1 + self.write_line('%s /* ALL_BUILD */,' % self.all_id) + self.write_line('%s /* RUN_TESTS */,' % self.test_id) + for t in self.build.targets: + self.write_line('%s /* %s */,' % (self.native_targets[t], t)) + self.indent_level -= 1 + self.write_line(');') + self.indent_level -= 1 + self.write_line('};') + self.ofile.write('/* End PBXProject section */\n') + + def generate_pbx_shell_build_phase(self): + self.ofile.write('\n/* Begin PBXShellScriptBuildPhase section */\n') + self.write_line('%s = {' % self.test_command_id) + self.indent_level += 1 + self.write_line('isa = PBXShellScriptBuildPhase;') + self.write_line('buildActionMask = 2147483647;') + self.write_line('files = (') + self.write_line(');') + self.write_line('inputPaths = (') + self.write_line(');') + self.write_line('outputPaths = (') + self.write_line(');') + self.write_line('runOnlyForDeploymentPostprocessing = 0;') + self.write_line('shellPath = /bin/sh;') + script_root = self.environment.get_script_dir() + test_script = os.path.join(script_root, 'meson_test.py') + test_data = os.path.join(self.environment.get_scratch_dir(), 'meson_test_setup.dat') + cmd = [sys.executable, test_script, test_data, '--wd', self.environment.get_build_dir()] + cmdstr = ' '.join(["'%s'" % i for i in cmd]) + self.write_line('shellScript = "%s";' % cmdstr) + self.write_line('showEnvVarsInLog = 0;') + self.indent_level-=1 + self.write_line('};') + self.ofile.write('/* End PBXShellScriptBuildPhase section */\n') + + def generate_pbx_sources_build_phase(self): + self.ofile.write('\n/* Begin PBXSourcesBuildPhase section */\n') + for name, phase_id in self.source_phase.items(): + self.write_line('%s /* Sources */ = {' % self.buildphasemap[name]) + self.indent_level+=1 + self.write_line('isa = PBXSourcesBuildPhase;') + self.write_line('buildActionMask = 2147483647;') + self.write_line('files = (') + self.indent_level+=1 + for s in self.build.targets[name].sources: + s = os.path.join(s.subdir, s.fname) + if not self.environment.is_header(s): + self.write_line('%s /* %s */,' % (self.buildmap[s], os.path.join(self.environment.get_source_dir(), s))) + self.indent_level-=1 + self.write_line(');') + self.write_line('runOnlyForDeploymentPostprocessing = 0;') + self.indent_level-=1 + self.write_line('};') + self.ofile.write('/* End PBXSourcesBuildPhase section */\n') + + def generate_pbx_target_dependency(self): + self.ofile.write('\n/* Begin PBXTargetDependency section */\n') + for t in self.build.targets: + idval = self.pbx_dep_map[t] # VERIFY: is this correct? + self.write_line('%s /* PBXTargetDependency */ = {' % idval) + self.indent_level += 1 + self.write_line('isa = PBXTargetDependency;') + self.write_line('target = %s /* %s */;' % (self.native_targets[t], t)) + self.write_line('targetProxy = %s /* PBXContainerItemProxy */;' % self.containerproxy_map[t]) + self.indent_level-=1 + self.write_line('};') + self.ofile.write('/* End PBXTargetDependency section */\n') + + def generate_xc_build_configuration(self): + self.ofile.write('\n/* Begin XCBuildConfiguration section */\n') + # First the setup for the toplevel project. + for buildtype in self.buildtypes: + self.write_line('%s /* %s */ = {' % (self.project_configurations[buildtype], buildtype)) + self.indent_level+=1 + self.write_line('isa = XCBuildConfiguration;') + self.write_line('buildSettings = {') + self.indent_level+=1 + self.write_line('ARCHS = "$(ARCHS_STANDARD_32_64_BIT)";') + self.write_line('ONLY_ACTIVE_ARCH = YES;') + self.write_line('SDKROOT = "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk";') + self.write_line('SYMROOT = "%s/build";' % self.environment.get_build_dir()) + self.indent_level-=1 + self.write_line('};') + self.write_line('name = %s;' % buildtype) + self.indent_level-=1 + self.write_line('};') + + # Then the all target. + for buildtype in self.buildtypes: + self.write_line('%s /* %s */ = {' % (self.buildall_configurations[buildtype], buildtype)) + self.indent_level+=1 + self.write_line('isa = XCBuildConfiguration;') + self.write_line('buildSettings = {') + self.indent_level += 1 + self.write_line('COMBINE_HIDPI_IMAGES = YES;') + self.write_line('GCC_GENERATE_DEBUGGING_SYMBOLS = NO;') + self.write_line('GCC_INLINES_ARE_PRIVATE_EXTERN = NO;') + self.write_line('GCC_OPTIMIZATION_LEVEL = 0;') + self.write_line('GCC_PREPROCESSOR_DEFINITIONS = ("");') + self.write_line('GCC_SYMBOLS_PRIVATE_EXTERN = NO;') + self.write_line('INSTALL_PATH = "";') + self.write_line('OTHER_CFLAGS = " ";') + self.write_line('OTHER_LDFLAGS = " ";') + self.write_line('OTHER_REZFLAGS = "";') + self.write_line('PRODUCT_NAME = ALL_BUILD;') + self.write_line('SECTORDER_FLAGS = "";') + self.write_line('SYMROOT = "%s";' % self.environment.get_build_dir()) + self.write_line('USE_HEADERMAP = NO;') + self.write_line('WARNING_CFLAGS = ("-Wmost", "-Wno-four-char-constants", "-Wno-unknown-pragmas", );') + self.indent_level-=1 + self.write_line('};') + self.write_line('name = %s;' % buildtype) + self.indent_level-=1 + self.write_line('};') + + # Then the test target. + for buildtype in self.buildtypes: + self.write_line('%s /* %s */ = {' % (self.test_configurations[buildtype], buildtype)) + self.indent_level+=1 + self.write_line('isa = XCBuildConfiguration;') + self.write_line('buildSettings = {') + self.indent_level += 1 + self.write_line('COMBINE_HIDPI_IMAGES = YES;') + self.write_line('GCC_GENERATE_DEBUGGING_SYMBOLS = NO;') + self.write_line('GCC_INLINES_ARE_PRIVATE_EXTERN = NO;') + self.write_line('GCC_OPTIMIZATION_LEVEL = 0;') + self.write_line('GCC_PREPROCESSOR_DEFINITIONS = ("");') + self.write_line('GCC_SYMBOLS_PRIVATE_EXTERN = NO;') + self.write_line('INSTALL_PATH = "";') + self.write_line('OTHER_CFLAGS = " ";') + self.write_line('OTHER_LDFLAGS = " ";') + self.write_line('OTHER_REZFLAGS = "";') + self.write_line('PRODUCT_NAME = RUN_TESTS;') + self.write_line('SECTORDER_FLAGS = "";') + self.write_line('SYMROOT = "%s";' % self.environment.get_build_dir()) + self.write_line('USE_HEADERMAP = NO;') + self.write_line('WARNING_CFLAGS = ("-Wmost", "-Wno-four-char-constants", "-Wno-unknown-pragmas", );') + self.indent_level-=1 + self.write_line('};') + self.write_line('name = %s;' % buildtype) + self.indent_level-=1 + self.write_line('};') + + # Now finally targets. + langnamemap = {'c' : 'C', 'cpp' : 'CPLUSPLUS', 'objc' : 'OBJC', 'objcpp' : 'OBJCPLUSPLUS'} + for target_name, target in self.build.targets.items(): + for buildtype in self.buildtypes: + dep_libs = [] + links_dylib = False + headerdirs = [] + for d in target.include_dirs: + for sd in d.incdirs: + cd = os.path.join(d.curdir, sd) + headerdirs.append(os.path.join(self.environment.get_source_dir(), cd)) + headerdirs.append(os.path.join(self.environment.get_build_dir(), cd)) + for l in target.link_targets: + abs_path = os.path.join(self.environment.get_build_dir(), + l.subdir, buildtype, l.get_osx_filename()) + dep_libs.append("'%s'" % abs_path) + if isinstance(l, build.SharedLibrary): + links_dylib = True + if links_dylib: + dep_libs = ['-Wl,-search_paths_first', '-Wl,-headerpad_max_install_names'] + dep_libs + dylib_version = None + if isinstance(target, build.SharedLibrary): + ldargs = ['-dynamiclib', '-Wl,-headerpad_max_install_names'] + dep_libs + install_path = os.path.join(self.environment.get_build_dir(), target.subdir, buildtype) + dylib_version = target.version + else: + ldargs = dep_libs + install_path = '' + if dylib_version is not None: + product_name = target_name + '.' + dylib_version + else: + product_name = target_name + ldargs += target.link_args + ldstr = ' '.join(ldargs) + valid = self.buildconfmap[target_name][buildtype] + langargs = {} + for lang in self.environment.coredata.compilers: + if lang not in langnamemap: + continue + gargs = self.build.global_args.get(lang, []) + targs = target.get_extra_args(lang) + args = gargs + targs + if len(args) > 0: + langargs[langnamemap[lang]] = args + symroot = os.path.join(self.environment.get_build_dir(), target.subdir) + self.write_line('%s /* %s */ = {' % (valid, buildtype)) + self.indent_level+=1 + self.write_line('isa = XCBuildConfiguration;') + self.write_line('buildSettings = {') + self.indent_level += 1 + self.write_line('COMBINE_HIDPI_IMAGES = YES;') + if dylib_version is not None: + self.write_line('DYLIB_CURRENT_VERSION = "%s";' % dylib_version) + self.write_line('EXECUTABLE_PREFIX = "%s";' % target.prefix) + if target.suffix == '': + suffix = '' + else: + suffix = '.' + target.suffix + self.write_line('EXECUTABLE_SUFFIX = "%s";' % suffix) + self.write_line('GCC_GENERATE_DEBUGGING_SYMBOLS = YES;') + self.write_line('GCC_INLINES_ARE_PRIVATE_EXTERN = NO;') + self.write_line('GCC_OPTIMIZATION_LEVEL = 0;') + self.write_line('GCC_PREPROCESSOR_DEFINITIONS = ("");') + self.write_line('GCC_SYMBOLS_PRIVATE_EXTERN = NO;') + if len(headerdirs) > 0: + quotedh = ','.join(['"\\"%s\\""' % i for i in headerdirs]) + self.write_line('HEADER_SEARCH_PATHS=(%s);' % quotedh) + self.write_line('INSTALL_PATH = "%s";' % install_path) + self.write_line('LIBRARY_SEARCH_PATHS = "";') + if isinstance(target, build.SharedLibrary): + self.write_line('LIBRARY_STYLE = DYNAMIC;') + for langname, args in langargs.items(): + argstr = ' '.join(args) + self.write_line('OTHER_%sFLAGS = "%s";' % (langname, argstr)) + self.write_line('OTHER_LDFLAGS = "%s";' % ldstr) + self.write_line('OTHER_REZFLAGS = "";') + self.write_line('PRODUCT_NAME = %s;' % product_name) + self.write_line('SECTORDER_FLAGS = "";') + self.write_line('SYMROOT = "%s";' % symroot) + self.write_line('USE_HEADERMAP = NO;') + self.write_line('WARNING_CFLAGS = ("-Wmost", "-Wno-four-char-constants", "-Wno-unknown-pragmas", );') + self.indent_level-=1 + self.write_line('};') + self.write_line('name = %s;' % buildtype) + self.indent_level-=1 + self.write_line('};') + self.ofile.write('/* End XCBuildConfiguration section */\n') + + def generate_xc_configurationList(self): + self.ofile.write('\n/* Begin XCConfigurationList section */\n') + self.write_line('%s /* Build configuration list for PBXProject "%s" */ = {' % (self.project_conflist, self.build.project_name)) + self.indent_level+=1 + self.write_line('isa = XCConfigurationList;') + self.write_line('buildConfigurations = (') + self.indent_level+=1 + for buildtype in self.buildtypes: + self.write_line('%s /* %s */,' % (self.project_configurations[buildtype], buildtype)) + self.indent_level-=1 + self.write_line(');') + self.write_line('defaultConfigurationIsVisible = 0;') + self.write_line('defaultConfigurationName = debug;') + self.indent_level-=1 + self.write_line('};') + + # Now the all target + self.write_line('%s /* Build configuration list for PBXAggregateTarget "ALL_BUILD" */ = {' % self.all_buildconf_id) + self.indent_level+=1 + self.write_line('isa = XCConfigurationList;') + self.write_line('buildConfigurations = (') + self.indent_level+=1 + for buildtype in self.buildtypes: + self.write_line('%s /* %s */,' % (self.buildall_configurations[buildtype], buildtype)) + self.indent_level-=1 + self.write_line(');') + self.write_line('defaultConfigurationIsVisible = 0;') + self.write_line('defaultConfigurationName = debug;') + self.indent_level-=1 + self.write_line('};') + + # Test target + self.write_line('%s /* Build configuration list for PBXAggregateTarget "ALL_BUILD" */ = {' % self.test_buildconf_id) + self.indent_level+=1 + self.write_line('isa = XCConfigurationList;') + self.write_line('buildConfigurations = (') + self.indent_level+=1 + for buildtype in self.buildtypes: + self.write_line('%s /* %s */,' % (self.test_configurations[buildtype], buildtype)) + self.indent_level-=1 + self.write_line(');') + self.write_line('defaultConfigurationIsVisible = 0;') + self.write_line('defaultConfigurationName = debug;') + self.indent_level-=1 + self.write_line('};') + + for target_name in self.build.targets: + listid = self.buildconflistmap[target_name] + self.write_line('%s /* Build configuration list for PBXNativeTarget "%s" */ = {' % (listid, target_name)) + self.indent_level += 1 + self.write_line('isa = XCConfigurationList;') + self.write_line('buildConfigurations = (') + self.indent_level += 1 + typestr = 'debug' + idval = self.buildconfmap[target_name][typestr] + self.write_line('%s /* %s */,' % (idval, typestr)) + self.indent_level -= 1 + self.write_line(');') + self.write_line('defaultConfigurationIsVisible = 0;') + self.write_line('defaultConfigurationName = %s;' % typestr) + self.indent_level -= 1 + self.write_line('};') + self.ofile.write('/* End XCConfigurationList section */\n') + + def generate_prefix(self): + self.ofile.write('// !$*UTF8*$!\n{\n') + self.indent_level += 1 + self.write_line('archiveVersion = 1;\n') + self.write_line('classes = {\n') + self.write_line('};\n') + self.write_line('objectVersion = 46;\n') + self.write_line('objects = {\n') + self.indent_level += 1 + + def generate_suffix(self): + self.indent_level -= 1 + self.write_line('};\n') + self.write_line('rootObject = ' + self.project_uid + ';') + self.indent_level -= 1 + self.write_line('}\n') |