diff options
Diffstat (limited to 'mesonbuild/backends.py')
-rw-r--r-- | mesonbuild/backends.py | 423 |
1 files changed, 423 insertions, 0 deletions
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) |