diff --git a/backends.py b/backends.py index 054130e77..d4b8b73dd 100644 --- a/backends.py +++ b/backends.py @@ -16,7 +16,8 @@ import os, sys, re, pickle import interpreter, nodes import environment, mlog from meson_install import InstallData -from interpreter import InvalidArguments +from build import InvalidArguments +import build import shutil from coredata import MesonException @@ -53,7 +54,7 @@ def do_replacement(regex, line, confdata): def do_mesondefine(line, confdata): arr = line.split() if len(arr) != 2: - raise interpreter.InvalidArguments('#mesondefine does not contain exactly two tokens: %s', line.strip()) + raise build.InvalidArguments('#mesondefine does not contain exactly two tokens: %s', line.strip()) varname = arr[1] try: v = confdata.get(varname) @@ -71,7 +72,7 @@ def do_mesondefine(line, confdata): elif isinstance(v, str): return '#define %s %s\n' % (varname, v) else: - raise interpreter.InvalidArguments('#mesondefine argument "%s" is of unknown type.' % varname) + raise build.InvalidArguments('#mesondefine argument "%s" is of unknown type.' % varname) def do_conf_file(src, dst, confdata): data = open(src).readlines() @@ -205,11 +206,11 @@ class Backend(): commands += compiler.get_std_opt_flags() if self.environment.coredata.coverage: commands += compiler.get_coverage_flags() - if isinstance(target, interpreter.SharedLibrary): + if isinstance(target, build.SharedLibrary): commands += compiler.get_pic_flags() for dep in target.get_external_deps(): commands += dep.get_compile_flags() - if isinstance(target, interpreter.Executable): + if isinstance(target, build.Executable): commands += dep.get_exe_flags() return commands @@ -217,8 +218,8 @@ class Backend(): def build_target_link_arguments(self, compiler, deps): args = [] for d in deps: - if not isinstance(d, interpreter.StaticLibrary) and\ - not isinstance(d, interpreter.SharedLibrary): + 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': @@ -425,7 +426,7 @@ class NinjaBackend(Backend): should_strip = self.environment.coredata.strip for t in self.build.get_targets().values(): if t.should_install(): - if isinstance(t, interpreter.Executable): + if isinstance(t, build.Executable): outdir = bindir else: outdir = libdir @@ -682,7 +683,7 @@ class NinjaBackend(Backend): generator = genlist.get_generator() exe = generator.get_exe() if self.environment.is_cross_build() and \ - isinstance(exe, interpreter.BuildTarget) and exe.is_cross: + 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.' @@ -690,7 +691,7 @@ class NinjaBackend(Backend): raise MesonException(s) infilelist = genlist.get_infilelist() outfilelist = genlist.get_outfilelist() - if isinstance(exe, interpreter.BuildTarget): + 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() @@ -714,7 +715,7 @@ class NinjaBackend(Backend): cmdlist = [exe_file] + args elem = NinjaBuildElement(outfiles, 'CUSTOM_COMMAND', infilename) elem.add_item('DESC', 'Generating $out') - if isinstance(exe, interpreter.BuildTarget): + if isinstance(exe, build.BuildTarget): elem.add_dep(self.get_target_filename(exe)) elem.add_item('COMMAND', cmdlist) elem.write(outfile) @@ -805,7 +806,7 @@ class NinjaBackend(Backend): if len(pch) == 0: continue if '/' not in pch[0] or '/' not in pch[-1]: - raise interpreter.InvalidArguments('Precompiled header of "%s" must not be in the same direcotory as source, please put it in a subdirectory.' % target.get_basename()) + raise build.InvalidArguments('Precompiled header of "%s" must not be in the same direcotory 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]) @@ -833,25 +834,25 @@ class NinjaBackend(Backend): elem.write(outfile) def generate_link(self, target, outfile, outname, obj_list): - if isinstance(target, interpreter.StaticLibrary): + if isinstance(target, build.StaticLibrary): linker = self.build.static_linker linker_base = 'STATIC' else: linker = self.build.compilers[0] linker_base = linker.get_language() # Fixme. - if isinstance(target, interpreter.SharedLibrary): + if isinstance(target, build.SharedLibrary): self.generate_shsym(outfile, target) crstr = '' if target.is_cross: crstr = '_CROSS' linker_rule = linker_base + crstr + '_LINKER' commands = [] - if isinstance(target, interpreter.Executable): + if isinstance(target, build.Executable): commands += linker.get_std_exe_link_flags() - elif isinstance(target, interpreter.SharedLibrary): + elif isinstance(target, build.SharedLibrary): commands += linker.get_std_shared_lib_link_flags() commands += linker.get_pic_flags() - elif isinstance(target, interpreter.StaticLibrary): + elif isinstance(target, build.StaticLibrary): commands += linker.get_std_link_flags() else: raise RuntimeError('Unknown build target type.') @@ -868,7 +869,7 @@ class NinjaBackend(Backend): return elem def get_dependency_filename(self, t): - if isinstance(t, interpreter.SharedLibrary): + 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) diff --git a/build.py b/build.py index 3e9ce42a8..401d5924a 100644 --- a/build.py +++ b/build.py @@ -12,6 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +import coredata +import copy + +class InvalidArguments(coredata.MesonException): + pass + class Build: """A class that holds the status of one build including all dependencies and so on. @@ -66,3 +72,236 @@ class Build: def get_global_flags(self, compiler): return self.global_args.get(compiler.get_language(), []) + +class BuildTarget(): + def __init__(self, name, subdir, is_cross, sources, environment, kwargs): + self.name = name + self.subdir = subdir + self.is_cross = is_cross + self.sources = [] + self.external_deps = [] + self.include_dirs = [] + self.link_targets = [] + self.filename = 'no_name' + self.need_install = False + self.pch = {} + self.extra_args = {} + self.generated = [] + self.process_sourcelist(sources) + self.process_kwargs(kwargs) + if len(self.sources) == 0 and len(self.generated) == 0: + raise InvalidArguments('Build target %s has no sources.' % name) + + def process_sourcelist(self, sources): + if not isinstance(sources, list): + sources = [sources] + for s in sources: + if isinstance(s, str): + self.sources.append(s) + elif isinstance(s, GeneratedList): + self.generated.append(s) + else: + raise InvalidArguments('Bad source in target %s.' % self.name) + + def get_original_kwargs(self): + return self.kwargs + + def process_kwargs(self, kwargs): + self.kwargs = copy.copy(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: + 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) + if 'version' in kwargs: + self.set_version(kwargs['version']) + if 'soversion' in kwargs: + self.set_soversion(kwargs['soversion']) + inclist = kwargs.get('include_dirs', []) + if not isinstance(inclist, list): + inclist = [inclist] + self.add_include_dirs(inclist) + deplist = kwargs.get('deps', []) + if not isinstance(deplist, list): + deplist = [deplist] + self.add_external_deps(deplist) + + 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): + return self.link_targets + + def get_basename(self): + return self.name + + def get_source_subdir(self): + return self.subdir + + def get_sources(self): + return self.sources + + 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): + for dep in deps: + if not isinstance(dep, dependencies.Dependency) and\ + not isinstance(dep, ExternalLibraryHolder): + raise InvalidArguments('Argument is not an external dependency') + self.external_deps.append(dep) + if isinstance(dep, dependencies.Dependency): + self.process_sourcelist(dep.get_sources()) + + def get_external_deps(self): + return self.external_deps + + def add_dep(self, args): + [self.add_external_dep(dep) for dep in args] + + def link(self, target): + if not isinstance(target, StaticLibrary) and \ + not isinstance(target, SharedLibrary): + print(target) + raise InvalidArguments('Link target is not library.') + self.link_targets.append(target) + + 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 + if len(pchlist) == 2: + if environment.is_header(pchlist[0]): + if not environment.is_source(pchlist[1]): + raise InterpreterException('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 InterpreterException('PCH definition must contain one header and at most one source.') + pchlist = [pchlist[1], pchlist[0]] + else: + raise InterpreterException('PCH argument %s is of unknown type.' % pchlist[0]) + elif len(pchlist) > 2: + raise InterpreterException('PCH definition may have a maximum of 2 files.') + self.pch[language] = pchlist + + def add_include_dirs(self, args): + for a in args: + if not isinstance(a, IncludeDirs): + raise InvalidArguments('Include directory to be added is not an include directory object.') + self.include_dirs += args + + def add_compiler_args(self, language, flags): + for a in flags: + if not isinstance(a, str): + raise InvalidArguments('A non-string passed to compiler args.') + if language in self.extra_args: + self.extra_args[language] += flags + else: + self.extra_args[language] = flags + + def get_aliaslist(self): + return [] + +class Executable(BuildTarget): + def __init__(self, name, subdir, is_cross, sources, environment, kwargs): + super().__init__(name, subdir, is_cross, sources, environment, kwargs) + suffix = environment.get_exe_suffix() + if suffix != '': + self.filename = self.name + '.' + suffix + else: + self.filename = self.name + + +class StaticLibrary(BuildTarget): + def __init__(self, name, subdir, is_cross, sources, environment, kwargs): + super().__init__(name, subdir, is_cross, sources, environment, kwargs) + prefix = environment.get_static_lib_prefix() + suffix = environment.get_static_lib_suffix() + self.filename = prefix + self.name + '.' + suffix + +class SharedLibrary(BuildTarget): + def __init__(self, name, subdir, is_cross, sources, environment, kwargs): + self.version = None + self.soversion = None + super().__init__(name, subdir, is_cross, sources, environment, kwargs); + self.prefix = environment.get_shared_lib_prefix() + self.suffix = environment.get_shared_lib_suffix() + + def get_shbase(self): + return self.prefix + self.name + '.' + self.suffix + + def get_filename(self): + fname = self.get_shbase() + if self.version is None: + return fname + else: + return fname + '.' + self.version + + def set_version(self, version): + if isinstance(version, nodes.StringStatement): + version = version.get_value() + if not isinstance(version, str): + print(version) + raise InvalidArguments('Shared library version is not a string.') + self.version = version + + def set_soversion(self, version): + if isinstance(version, nodes.StringStatement) or isinstance(version, nodes.IntStatement): + version = version.get_value() + 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 diff --git a/interpreter.py b/interpreter.py index 917caf035..fc1242077 100644 --- a/interpreter.py +++ b/interpreter.py @@ -18,7 +18,8 @@ import environment import coredata import dependencies import mlog -import os, sys, platform, copy, subprocess, shutil +import build +import os, sys, platform, subprocess, shutil class InterpreterException(coredata.MesonException): pass @@ -26,9 +27,6 @@ class InterpreterException(coredata.MesonException): class InvalidCode(InterpreterException): pass -class InvalidArguments(InterpreterException): - pass - class InterpreterObject(): def __init__(self): self.methods = {} @@ -409,237 +407,21 @@ class Man(InterpreterObject): def get_sources(self): return self.sources -class BuildTarget(InterpreterObject): - def __init__(self, name, subdir, is_cross, sources, kwargs): - InterpreterObject.__init__(self) - self.name = name - self.subdir = subdir - self.is_cross = is_cross - self.sources = [] - self.external_deps = [] - self.include_dirs = [] - self.link_targets = [] - self.filename = 'no_name' - self.need_install = False - self.pch = {} - self.extra_args = {} - self.generated = [] - self.process_sourcelist(sources) - self.process_kwargs(kwargs) - if len(self.sources) == 0 and len(self.generated) == 0: - raise InvalidArguments('Build target %s has no sources.' % name) - - def process_sourcelist(self, sources): - if not isinstance(sources, list): - sources = [sources] - for s in sources: - if isinstance(s, str): - self.sources.append(s) - elif isinstance(s, GeneratedList): - self.generated.append(s) - else: - raise InvalidArguments('Bad source in target %s.' % self.name) - - def get_original_kwargs(self): - return self.kwargs - - def process_kwargs(self, kwargs): - self.kwargs = copy.copy(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: - 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) - if 'version' in kwargs: - self.set_version(kwargs['version']) - if 'soversion' in kwargs: - self.set_soversion(kwargs['soversion']) - inclist = kwargs.get('include_dirs', []) - if not isinstance(inclist, list): - inclist = [inclist] - self.add_include_dirs(inclist) - deplist = kwargs.get('deps', []) - if not isinstance(deplist, list): - deplist = [deplist] - self.add_external_deps(deplist) - - 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): - return self.link_targets - - def get_basename(self): - return self.name - - def get_source_subdir(self): - return self.subdir +class BuildTargetHolder(InterpreterObject): + def __init__(self, targetttype, name, subdir, is_cross, sources, environment, kwargs): + self.target = targetttype(name, subdir, is_cross, sources, environment, kwargs) - def get_sources(self): - return self.sources - - 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): - for dep in deps: - if not isinstance(dep, dependencies.Dependency) and\ - not isinstance(dep, ExternalLibraryHolder): - raise InvalidArguments('Argument is not an external dependency') - self.external_deps.append(dep) - if isinstance(dep, dependencies.Dependency): - self.process_sourcelist(dep.get_sources()) - - def get_external_deps(self): - return self.external_deps - - def add_dep(self, args): - [self.add_external_dep(dep) for dep in args] - - def link(self, target): - if not isinstance(target, StaticLibrary) and \ - not isinstance(target, SharedLibrary): - raise InvalidArguments('Link target is not library.') - self.link_targets.append(target) - - 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 - if len(pchlist) == 2: - if environment.is_header(pchlist[0]): - if not environment.is_source(pchlist[1]): - raise InterpreterException('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 InterpreterException('PCH definition must contain one header and at most one source.') - pchlist = [pchlist[1], pchlist[0]] - else: - raise InterpreterException('PCH argument %s is of unknown type.' % pchlist[0]) - elif len(pchlist) > 2: - raise InterpreterException('PCH definition may have a maximum of 2 files.') - self.pch[language] = pchlist - - def add_include_dirs(self, args): - for a in args: - if not isinstance(a, IncludeDirs): - raise InvalidArguments('Include directory to be added is not an include directory object.') - self.include_dirs += args - - def add_compiler_args(self, language, flags): - for a in flags: - if not isinstance(a, str): - raise InvalidArguments('A non-string passed to compiler args.') - if language in self.extra_args: - self.extra_args[language] += flags - else: - self.extra_args[language] = flags - - def get_aliaslist(self): - return [] - -class Executable(BuildTarget): +class ExecutableHolder(BuildTargetHolder): def __init__(self, name, subdir, is_cross, sources, environment, kwargs): - BuildTarget.__init__(self, name, subdir, is_cross, sources, kwargs) - suffix = environment.get_exe_suffix() - if suffix != '': - self.filename = self.name + '.' + suffix - else: - self.filename = self.name + super().__init__(build.Executable, name, subdir, is_cross, sources, environment, kwargs) -class StaticLibrary(BuildTarget): +class StaticLibraryHolder(BuildTargetHolder): def __init__(self, name, subdir, is_cross, sources, environment, kwargs): - BuildTarget.__init__(self, name, subdir, is_cross, sources, kwargs) - prefix = environment.get_static_lib_prefix() - suffix = environment.get_static_lib_suffix() - self.filename = prefix + self.name + '.' + suffix + super().__init__(build.StaticLibrary, name, subdir, is_cross, sources, environment, kwargs) -class SharedLibrary(BuildTarget): +class SharedLibraryHolder(BuildTargetHolder): def __init__(self, name, subdir, is_cross, sources, environment, kwargs): - self.version = None - self.soversion = None - BuildTarget.__init__(self, name, subdir, is_cross, sources, kwargs) - self.prefix = environment.get_shared_lib_prefix() - self.suffix = environment.get_shared_lib_suffix() - - def get_shbase(self): - return self.prefix + self.name + '.' + self.suffix - - def get_filename(self): - fname = self.get_shbase() - if self.version is None: - return fname - else: - return fname + '.' + self.version - - def set_version(self, version): - if isinstance(version, nodes.StringStatement): - version = version.get_value() - if not isinstance(version, str): - print(version) - raise InvalidArguments('Shared library version is not a string.') - self.version = version - - def set_soversion(self, version): - if isinstance(version, nodes.StringStatement) or isinstance(version, nodes.IntStatement): - version = version.get_value() - 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 + super().__init__(build.SharedLibrary, name, subdir, is_cross, sources, environment, kwargs) class Test(InterpreterObject): def __init__(self, name, exe, is_parallel): @@ -1107,13 +889,13 @@ class Interpreter(): return dep def func_executable(self, node, args, kwargs): - return self.build_target(node, args, kwargs, Executable) + return self.build_target(node, args, kwargs, ExecutableHolder) def func_static_lib(self, node, args, kwargs): - return self.build_target(node, args, kwargs, StaticLibrary) + return self.build_target(node, args, kwargs, StaticLibraryHolder) def func_shared_lib(self, node, args, kwargs): - return self.build_target(node, args, kwargs, SharedLibrary) + return self.build_target(node, args, kwargs, SharedLibraryHolder) def func_generator(self, node, args, kwargs): gen = Generator(args, kwargs) @@ -1121,11 +903,11 @@ class Interpreter(): return gen def func_test(self, node, args, kwargs): - self.validate_arguments(args, 2, [str, Executable]) + self.validate_arguments(args, 2, [str, ExecutableHolder]) par = kwargs.get('is_parallel', True) if not isinstance(par, bool): raise InterpreterException('Keyword argument is_parallel must be a boolean.') - t = Test(args[0], args[1], par) + t = Test(args[0], args[1].target, par) self.build.tests.append(t) mlog.debug('Adding test "', mlog.bold(args[0]), '".', sep='') @@ -1265,7 +1047,7 @@ class Interpreter(): raise InvalidCode('Tried to create target "%s", but a target of that name already exists.' % name) self.check_sources_exist(os.path.join(self.environment.source_dir, self.subdir), sources) l = targetclass(name, self.subdir, is_cross, sources, self.environment, kwargs) - self.build.targets[name] = l + self.build.targets[name] = l.target if self.environment.is_cross_build() and l.is_cross: txt = ' cross build ' else: