1192 lines
45 KiB
Python
1192 lines
45 KiB
Python
# 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, re
|
|
from .mesonlib import File, flatten, MesonException
|
|
from .environment import for_windows, for_darwin
|
|
|
|
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,
|
|
'name_prefix' : True,
|
|
'name_suffix' : True,
|
|
'vs_module_defs' : True})
|
|
|
|
def sources_are_suffix(sources, suffix):
|
|
for source in sources:
|
|
if source.endswith('.' + suffix):
|
|
return True
|
|
return False
|
|
|
|
def compiler_is_msvc(sources, is_cross, env):
|
|
"""
|
|
Since each target does not currently have the compiler information attached
|
|
to it, we must do this detection manually here.
|
|
|
|
This detection is purposely incomplete and will cause bugs if other code is
|
|
extended and this piece of code is forgotten.
|
|
"""
|
|
compiler = None
|
|
if sources_are_suffix(sources, 'c'):
|
|
try:
|
|
compiler = env.detect_c_compiler(is_cross)
|
|
except MesonException:
|
|
return False
|
|
elif sources_are_suffix(sources, 'cxx') or \
|
|
sources_are_suffix(sources, 'cpp') or \
|
|
sources_are_suffix(sources, 'cc'):
|
|
try:
|
|
compiler = env.detect_cpp_compiler(is_cross)
|
|
except MesonException:
|
|
return False
|
|
if compiler and compiler.get_id() == 'msvc':
|
|
return True
|
|
return False
|
|
|
|
|
|
class InvalidArguments(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.global_link_args = {}
|
|
self.tests = []
|
|
self.benchmarks = []
|
|
self.headers = []
|
|
self.man = []
|
|
self.data = []
|
|
self.static_linker = None
|
|
self.static_cross_linker = None
|
|
self.subprojects = {}
|
|
self.install_scripts = []
|
|
self.postconf_scripts = []
|
|
self.install_dirs = []
|
|
self.dep_manifest_name = None
|
|
self.dep_manifest = {}
|
|
self.cross_stdlibs = {}
|
|
|
|
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(), [])
|
|
|
|
def get_global_link_args(self, compiler):
|
|
return self.global_link_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 __repr__(self):
|
|
repr_str = "<{0} {1}: {2}>"
|
|
return repr_str.format(self.__class__.__name__, self.get_id(), self.filename)
|
|
|
|
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)
|
|
elif isinstance(s, GeneratedList) or isinstance(s, CustomTarget):
|
|
msg = 'Generated files are not allowed in the \'objects\' kwarg ' + \
|
|
'for target {!r}.\nIt is meant only for '.format(self.name) + \
|
|
'pre-built object files that are shipped with the\nsource ' + \
|
|
'tree. Try adding it in the list of sources.'
|
|
raise InvalidArguments(msg)
|
|
else:
|
|
msg = 'Bad object of type {!r} in target {!r}.'.format(type(s).__name__, self.name)
|
|
raise InvalidArguments(msg)
|
|
|
|
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:
|
|
msg = 'Bad source of type {!r} in target {!r}.'.format(type(s).__name__, self.name)
|
|
raise InvalidArguments(msg)
|
|
|
|
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 MesonException('Extraction arguments must be strings.')
|
|
src = File(False, self.subdir, src)
|
|
if src not in self.sources:
|
|
raise 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
|
|
if 'name_prefix' in kwargs:
|
|
name_prefix = kwargs['name_prefix']
|
|
if isinstance(name_prefix, list):
|
|
if len(name_prefix) != 0:
|
|
raise InvalidArguments('Array must be empty to signify null.')
|
|
elif not isinstance(name_prefix, str):
|
|
raise InvalidArguments('Name prefix must be a string.')
|
|
self.prefix = name_prefix
|
|
if 'name_suffix' in kwargs:
|
|
name_suffix = kwargs['name_suffix']
|
|
if isinstance(name_suffix, list):
|
|
if len(name_suffix) != 0:
|
|
raise InvalidArguments('Array must be empty to signify null.')
|
|
else:
|
|
if not isinstance(name_suffix, str):
|
|
raise InvalidArguments('Name suffix must be a string.')
|
|
self.suffix = name_suffix
|
|
|
|
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):
|
|
# Those parts that are internal.
|
|
self.process_sourcelist(dep.sources)
|
|
self.add_include_dirs(dep.include_directories)
|
|
for l in dep.libraries:
|
|
self.link(l)
|
|
# Those parts that are external.
|
|
extpart = dependencies.InternalDependency('undefined',
|
|
[],
|
|
dep.compile_args,
|
|
dep.link_args,
|
|
[], [], [])
|
|
self.external_deps.append(extpart)
|
|
# Deps of deps.
|
|
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 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 __repr__(self):
|
|
repr_str = "<{0}: {1}>"
|
|
return repr_str.format(self.__class__.__name__, self.exe)
|
|
|
|
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)
|
|
# Unless overriden, executables have no suffix or prefix. Except on
|
|
# Windows and with C#/Mono executables where the suffix is 'exe'
|
|
if not hasattr(self, 'prefix'):
|
|
self.prefix = ''
|
|
if not hasattr(self, 'suffix'):
|
|
# Executable for Windows or C#/Mono
|
|
if for_windows(is_cross, environment) or sources_are_suffix(self.sources, 'cs'):
|
|
self.suffix = 'exe'
|
|
else:
|
|
self.suffix = ''
|
|
self.filename = self.name
|
|
if self.suffix:
|
|
self.filename += '.' + self.suffix
|
|
|
|
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 sources_are_suffix(self.sources, 'cs'):
|
|
raise InvalidArguments('Static libraries not supported for C#.')
|
|
# By default a static library is named libfoo.a even on Windows because
|
|
# MSVC does not have a consistent convention for what static libraries
|
|
# are called. The MSVC CRT uses libfoo.lib syntax but nothing else uses
|
|
# it and GCC only looks for static libraries called foo.lib and
|
|
# libfoo.a. However, we cannot use foo.lib because that's the same as
|
|
# the import library. Using libfoo.a is ok because people using MSVC
|
|
# always pass the library filename while linking anyway.
|
|
if not hasattr(self, 'prefix'):
|
|
self.prefix = 'lib'
|
|
if not hasattr(self, 'suffix'):
|
|
# Rust static library crates have .rlib suffix
|
|
if sources_are_suffix(self.sources, 'rs'):
|
|
self.suffix = 'rlib'
|
|
else:
|
|
self.suffix = 'a'
|
|
self.filename = self.prefix + self.name + '.' + self.suffix
|
|
|
|
def type_suffix(self):
|
|
return "@sta"
|
|
|
|
class SharedLibrary(BuildTarget):
|
|
def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs):
|
|
self.soversion = None
|
|
self.ltversion = None
|
|
self.vs_module_defs = None
|
|
# The import library this target will generate
|
|
self.import_filename = None
|
|
# The import library that Visual Studio would generate (and accept)
|
|
self.vs_import_filename = None
|
|
# The import library that GCC would generate (and prefer)
|
|
self.gcc_import_filename = None
|
|
super().__init__(name, subdir, subproject, is_cross, sources, objects, environment, kwargs)
|
|
if not hasattr(self, 'prefix'):
|
|
self.prefix = None
|
|
if not hasattr(self, 'suffix'):
|
|
self.suffix = None
|
|
self.basic_filename_tpl = '{0.prefix}{0.name}.{0.suffix}'
|
|
self.determine_filenames(is_cross, environment)
|
|
|
|
def determine_filenames(self, is_cross, env):
|
|
"""
|
|
See https://github.com/mesonbuild/meson/pull/417 for details.
|
|
|
|
First we determine the filename template (self.filename_tpl), then we
|
|
set the output filename (self.filename).
|
|
|
|
The template is needed while creating aliases (self.get_aliaslist),
|
|
which are needed while generating .so shared libraries for Linux.
|
|
|
|
Besides this, there's also the import library name, which is only used
|
|
on Windows since on that platform the linker uses a separate library
|
|
called the "import library" during linking instead of the shared
|
|
library (DLL). The toolchain will output an import library in one of
|
|
two formats: GCC or Visual Studio.
|
|
|
|
When we're building with Visual Studio, the import library that will be
|
|
generated by the toolchain is self.vs_import_filename, and with
|
|
MinGW/GCC, it's self.gcc_import_filename. self.import_filename will
|
|
always contain the import library name this target will generate.
|
|
"""
|
|
prefix = ''
|
|
suffix = ''
|
|
self.filename_tpl = self.basic_filename_tpl
|
|
# If the user already provided the prefix and suffix to us, we don't
|
|
# need to do any filename suffix/prefix detection.
|
|
# NOTE: manual prefix/suffix override is currently only tested for C/C++
|
|
if self.prefix != None and self.suffix != None:
|
|
pass
|
|
# C# and Mono
|
|
elif sources_are_suffix(self.sources, 'cs'):
|
|
prefix = ''
|
|
suffix = 'dll'
|
|
self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}'
|
|
# Rust
|
|
elif sources_are_suffix(self.sources, 'rs'):
|
|
# Currently, we always build --crate-type=rlib
|
|
prefix = 'lib'
|
|
suffix = 'rlib'
|
|
self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}'
|
|
# C, C++, Swift, Vala
|
|
# Only Windows uses a separate import library for linking
|
|
# For all other targets/platforms import_filename stays None
|
|
elif for_windows(is_cross, env):
|
|
suffix = 'dll'
|
|
self.vs_import_filename = '{0}.lib'.format(self.name)
|
|
self.gcc_import_filename = 'lib{0}.dll.a'.format(self.name)
|
|
if compiler_is_msvc(self.sources, is_cross, env):
|
|
# Shared library is of the form foo.dll
|
|
prefix = ''
|
|
# Import library is called foo.lib
|
|
self.import_filename = self.vs_import_filename
|
|
# Assume GCC-compatible naming
|
|
else:
|
|
# Shared library is of the form libfoo.dll
|
|
prefix = 'lib'
|
|
# Import library is called libfoo.dll.a
|
|
self.import_filename = self.gcc_import_filename
|
|
# Shared library has the soversion if it is defined
|
|
if self.soversion:
|
|
self.filename_tpl = '{0.prefix}{0.name}-{0.soversion}.{0.suffix}'
|
|
else:
|
|
self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}'
|
|
elif for_darwin(is_cross, env):
|
|
prefix = 'lib'
|
|
suffix = 'dylib'
|
|
if self.soversion:
|
|
# libfoo.X.dylib
|
|
self.filename_tpl = '{0.prefix}{0.name}.{0.soversion}.{0.suffix}'
|
|
else:
|
|
# libfoo.dylib
|
|
self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}'
|
|
else:
|
|
prefix = 'lib'
|
|
suffix = 'so'
|
|
if self.ltversion:
|
|
# libfoo.so.X[.Y[.Z]] (.Y and .Z are optional)
|
|
self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}.{0.ltversion}'
|
|
elif self.soversion:
|
|
# libfoo.so.X
|
|
self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}.{0.soversion}'
|
|
else:
|
|
# No versioning, libfoo.so
|
|
self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}'
|
|
if self.prefix == None:
|
|
self.prefix = prefix
|
|
if self.suffix == None:
|
|
self.suffix = suffix
|
|
self.filename = self.filename_tpl.format(self)
|
|
|
|
def process_kwargs(self, kwargs, environment):
|
|
super().process_kwargs(kwargs, environment)
|
|
# Shared library version
|
|
if 'version' in kwargs:
|
|
self.ltversion = kwargs['version']
|
|
if not isinstance(self.ltversion, str):
|
|
raise InvalidArguments('Shared library version needs to be a string, not ' + type(self.ltversion).__name__)
|
|
if not re.fullmatch(r'[0-9]+(\.[0-9]+){0,2}', self.ltversion):
|
|
raise InvalidArguments('Invalid Shared library version "{0}". Must be of the form X.Y.Z where all three are numbers. Y and Z are optional.'.format(self.ltversion))
|
|
# Try to extract/deduce the soversion
|
|
if 'soversion' in kwargs:
|
|
self.soversion = kwargs['soversion']
|
|
if isinstance(self.soversion, int):
|
|
self.soversion = str(self.soversion)
|
|
if not isinstance(self.soversion, str):
|
|
raise InvalidArguments('Shared library soversion is not a string or integer.')
|
|
try:
|
|
int(self.soversion)
|
|
except ValueError:
|
|
raise InvalidArguments('Shared library soversion must be a valid integer')
|
|
elif self.ltversion:
|
|
# library version is defined, get the soversion from that
|
|
self.soversion = self.ltversion.split('.')[0]
|
|
# Visual Studio module-definitions file
|
|
if 'vs_module_defs' in kwargs:
|
|
path = kwargs['vs_module_defs']
|
|
if (os.path.isabs(path)):
|
|
self.vs_module_defs = File.from_absolute_file(path)
|
|
else:
|
|
self.vs_module_defs = File.from_source_file(environment.source_dir, self.subdir, path)
|
|
# link_depends can be an absolute path or relative to self.subdir
|
|
self.link_depends.append(path)
|
|
|
|
def check_unknown_kwargs(self, kwargs):
|
|
self.check_unknown_kwargs_int(kwargs, known_shlib_kwargs)
|
|
|
|
def get_import_filename(self):
|
|
"""
|
|
The name of the import library that will be outputted by the compiler
|
|
|
|
Returns None if there is no import library required for this platform
|
|
"""
|
|
return self.import_filename
|
|
|
|
def get_import_filenameslist(self):
|
|
if self.import_filename:
|
|
return [self.vs_import_filename, self.gcc_import_filename]
|
|
return []
|
|
|
|
def get_all_link_deps(self):
|
|
return [self] + self.get_transitive_link_deps()
|
|
|
|
def get_aliaslist(self):
|
|
"""
|
|
If the versioned library name is libfoo.so.0.100.0, aliases are:
|
|
* libfoo.so.0 (soversion)
|
|
* libfoo.so (unversioned; for linking)
|
|
"""
|
|
# Aliases are only useful with .so libraries. Also if the .so library
|
|
# ends with .so (no versioning), we don't need aliases.
|
|
if self.suffix != 'so' or self.filename.endswith('.so'):
|
|
return []
|
|
# Unversioned alias: libfoo.so
|
|
aliases = [self.basic_filename_tpl.format(self)]
|
|
# If ltversion != soversion we create an soversion alias: libfoo.so.X
|
|
if self.ltversion and self.ltversion != self.soversion:
|
|
if not self.soversion:
|
|
# This is done in self.process_kwargs()
|
|
raise AssertionError('BUG: If library version is defined, soversion must have been defined')
|
|
alias_tpl = self.filename_tpl.replace('ltversion', 'soversion')
|
|
aliases.append(alias_tpl.format(self))
|
|
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,
|
|
'depfile' : 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.depfile = None
|
|
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 __repr__(self):
|
|
repr_str = "<{0} {1}: {2}>"
|
|
return repr_str.format(self.__class__.__name__, self.get_id(), self.command)
|
|
|
|
def get_id(self):
|
|
return self.name + self.type_suffix()
|
|
|
|
def get_target_dependencies(self):
|
|
deps = self.dependencies[:]
|
|
deps += self.extra_depends
|
|
for c in self.sources:
|
|
if hasattr(c, 'held_object'):
|
|
c = c.held_object
|
|
if isinstance(c, BuildTarget) or isinstance(c, CustomTarget) or isinstance(c, GeneratedList):
|
|
deps.append(c)
|
|
return deps
|
|
|
|
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".')
|
|
if 'depfile' in kwargs:
|
|
depfile = kwargs['depfile']
|
|
if not isinstance(depfile, str):
|
|
raise InvalidArguments('Depfile must be a string.')
|
|
if os.path.split(depfile)[1] != depfile:
|
|
raise InvalidArguments('Depfile must be a plain filename without a subdirectory.')
|
|
self.depfile = depfile
|
|
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) or isinstance(c, File):
|
|
final_cmd.append(c)
|
|
elif isinstance(c, dependencies.ExternalProgram):
|
|
if not c.found():
|
|
raise InvalidArguments('Tried to use not found external program in a build rule.')
|
|
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 self.install:
|
|
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, dependencies, subdir):
|
|
self.name = name
|
|
self.command = command
|
|
self.args = args
|
|
self.dependencies = dependencies
|
|
self.subdir = subdir
|
|
|
|
def __repr__(self):
|
|
repr_str = "<{0} {1}: {2}>"
|
|
return repr_str.format(self.__class__.__name__, self.get_id(), self.command)
|
|
|
|
def get_id(self):
|
|
return self.name + self.type_suffix()
|
|
|
|
def get_basename(self):
|
|
return self.name
|
|
|
|
def get_dependencies(self):
|
|
return self.dependencies
|
|
|
|
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 __repr__(self):
|
|
repr_str = "<{0}: {1} -> {2}>"
|
|
src = os.path.join(self.subdir, self.sourcename)
|
|
dst = os.path.join(self.subdir, self.targetname)
|
|
return repr_str.format(self.__class__.__name__, src, dst)
|
|
|
|
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 __repr__(self):
|
|
return repr(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
|