# Copyright 2012 Jussi Pakkanen # 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 mparser import os, re, pickle import build from coredata import MesonException 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, mparser.StringNode): var = var.value 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 build.InvalidArguments('#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, mparser.BooleanNode): v = v.value 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 build.InvalidArguments('#mesondefine argument "%s" is of unknown type.' % varname) 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 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) replace_if_different(dst, dst_tmp) 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 TestSerialisation: def __init__(self, name, fname, is_cross, exe_wrapper, is_parallel, cmd_args, env): self.name = name 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 # 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, interp): self.build = build self.environment = build.environment self.interpreter = interp self.processed_targets = {} self.dep_rules = {} self.build_to_src = os.path.relpath(self.environment.get_source_dir(), self.environment.get_build_dir()) 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 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): dirname = target.get_subdir() os.makedirs(os.path.join(self.environment.get_build_dir(), dirname), exist_ok=True) return dirname def get_target_private_dir(self, target): dirname = os.path.join(self.get_target_dir(target), target.get_basename() + '.dir') os.makedirs(os.path.join(self.environment.get_build_dir(), dirname), exist_ok=True) 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(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()] [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() def has_vala(self, target): for s in target.get_sources(): if s.endswith('.vala'): return True return False def has_rust(self, target): for s in target.get_sources(): if s.endswith('.rs'): return True return False def has_cs(self, target): for s in target.get_sources(): if s.endswith('.cs'): return True return False 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_basename() 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) # 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.use_pch is_unity = self.environment.coredata.unity if use_pch and target.has_pch(): self.generate_pch(target, outfile) header_deps = gen_other_deps unity_src = [] unity_deps = [] # Generated sources that must be built before compiling a Unity target. for gensource in target.get_generated_sources(): if isinstance(gensource, build.CustomTarget): for src in gensource.output: src = os.path.join(gensource.subdir, src) if self.environment.is_header(src): header_deps.append(RawFilename(src)) elif self.environment.is_source(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)) else: pass # perhaps print warning about the unknown file? break # just to cut down on indentation size for src in gensource.get_outfilelist(): if self.environment.is_object(src): obj_list.append(os.path.join(self.get_target_dir(target), target.get_basename() + '.dir', src)) elif not self.environment.is_header(src): if is_unity: if '/' in 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)) else: header_deps.append(src) 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_source_dir(), target.get_subdir(), 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) # Sort object list to preserve command line over multiple invocations. elem = self.generate_link(target, outfile, outname, sorted(obj_list), linker) self.generate_shlib_aliases(target, self.get_target_dir(target), outfile, elem) self.processed_targets[name] = True 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: if not self.source_suffix_in_objs: osrc = '.'.join(osrc.split('.')[:-1]) objname = os.path.join(proj_dir_to_build_root, targetdir, os.path.basename(osrc) + suffix) result.append(objname) return result def process_target_dependencies(self, target, outfile): for t in target.get_dependencies(): tname = t.get_basename() if not tname in self.processed_targets: self.generate_target(t, outfile) def get_pch_include_args(self, compiler, target): args = [] pchpath = self.get_target_private_dir(target) includearg = compiler.get_include_arg(pchpath) 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 = [includearg] + args return args def generate_basic_compiler_args(self, target, compiler): commands = [] commands += compiler.get_always_args() commands += self.build.get_global_args(compiler) commands += self.environment.coredata.external_args[compiler.get_language()] commands += target.get_extra_args(compiler.get_language()) if self.environment.coredata.buildtype != 'plain': commands += compiler.get_std_warn_args() commands += compiler.get_buildtype_args(self.environment.coredata.buildtype) if self.environment.coredata.coverage: commands += compiler.get_coverage_args() if self.environment.coredata.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() 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) return args def generate_configure_files(self): for cf in self.build.get_configure_files(): infile = os.path.join(self.environment.get_source_dir(), cf.get_subdir(), cf.get_source_name()) outdir = os.path.join(self.environment.get_build_dir(), cf.get_subdir()) os.makedirs(outdir, exist_ok=True) outfile = os.path.join(outdir, cf.get_target_name()) confdata = cf.get_configuration_data() do_conf_file(infile, outfile, confdata) def write_test_file(self, datafile): arr = [] for t in self.build.get_tests(): fname = os.path.join(self.environment.get_build_dir(), self.get_target_filename(t.get_exe())) is_cross = self.environment.is_cross_build() if is_cross: exe_wrapper = self.environment.cross_info.get('exe_wrapper', None) else: exe_wrapper = None ts = TestSerialisation(t.get_name(), fname, is_cross, exe_wrapper, t.is_parallel, t.cmd_args, t.env) arr.append(ts) pickle.dump(arr, datafile) def generate_pkgconfig_files(self): for p in self.build.pkgconfig_gens: outdir = self.environment.scratch_dir fname = os.path.join(outdir, p.filebase + '.pc') ofile = open(fname, 'w') ofile.write('prefix=%s\n' % self.environment.get_coredata().prefix) ofile.write('libdir=${prefix}/%s\n' % self.environment.get_coredata().libdir) ofile.write('includedir=${prefix}/%s\n\n' % self.environment.get_coredata().includedir) ofile.write('Name: %s\n' % p.name) if len(p.description) > 0: ofile.write('Description: %s\n' % p.description) if len(p.version) > 0: ofile.write('Version: %s\n' % p.version) ofile.write('Libs: -L${libdir} ') for l in p.libraries: ofile.write('-l%s ' % l.name) ofile.write('\n') ofile.write('CFlags: ') for h in p.subdirs: if h == '.': h = '' ofile.write(os.path.join('-I${includedir}', h)) ofile.write(' ') ofile.write('\n')