# 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 , stringlistify
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 ,
' d_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} . \n It is meant only for ' . format ( self . name ) + \
' pre-built object files that are shipped with the \n source ' + \
' 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 )
dlist = stringlistify ( kwargs . get ( ' d_args ' , [ ] ) )
self . add_compiler_args ( ' d ' , dlist )
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 '
# libfoo.dylib
self . filename_tpl = ' {0.prefix} {0.name} . {0.suffix} '
# On OS X, the filename should never have the soversion
# See: https://github.com/mesonbuild/meson/pull/680
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 ,
}
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 . 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 " . ' )
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