The Meson Build System http://mesonbuild.com/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

454 lines
19 KiB

# 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))
11 years ago
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:
11 years ago
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')