# Copyright 2012-2017 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 __future__ import annotations
from collections import defaultdict , OrderedDict
from dataclasses import dataclass , field
from functools import lru_cache
import copy
import hashlib
import itertools , pathlib
import os
import pickle
import re
import textwrap
import typing as T
from . import environment
from . import dependencies
from . import mlog
from . import programs
from . mesonlib import (
HoldableObject , SecondLevelHolder ,
File , MesonException , MachineChoice , PerMachine , OrderedSet , listify ,
extract_as_list , typeslistify , stringlistify , classify_unity_sources ,
get_filenames_templates_dict , substitute_values , has_path_sep ,
OptionKey , PerMachineDefaultable ,
MesonBugException ,
)
from . compilers import (
Compiler , is_object , clink_langs , sort_clink , lang_suffixes ,
is_known_suffix , detect_static_linker , detect_compiler_for
)
from . linkers import StaticLinker
from . interpreterbase import FeatureNew , FeatureDeprecated
if T . TYPE_CHECKING :
from typing_extensions import Literal
from . _typing import ImmutableListProtocol , ImmutableSetProtocol
from . backend . backends import Backend , ExecutableSerialisation
from . interpreter . interpreter import Test , SourceOutputs , Interpreter
from . interpreterbase import SubProject
from . mesonlib import FileMode , FileOrString
from . modules import ModuleState
from . mparser import BaseNode
GeneratedTypes = T . Union [ ' CustomTarget ' , ' CustomTargetIndex ' , ' GeneratedList ' ]
pch_kwargs = { ' c_pch ' , ' cpp_pch ' }
lang_arg_kwargs = {
' c_args ' ,
' cpp_args ' ,
' cuda_args ' ,
' d_args ' ,
' d_import_dirs ' ,
' d_unittest ' ,
' d_module_versions ' ,
' d_debug ' ,
' fortran_args ' ,
' java_args ' ,
' objc_args ' ,
' objcpp_args ' ,
' rust_args ' ,
' vala_args ' ,
' cs_args ' ,
' cython_args ' ,
}
vala_kwargs = { ' vala_header ' , ' vala_gir ' , ' vala_vapi ' }
rust_kwargs = { ' rust_crate_type ' }
cs_kwargs = { ' resources ' , ' cs_args ' }
buildtarget_kwargs = {
' build_by_default ' ,
' build_rpath ' ,
' dependencies ' ,
' extra_files ' ,
' gui_app ' ,
' link_with ' ,
' link_whole ' ,
' link_args ' ,
' link_depends ' ,
' implicit_include_directories ' ,
' include_directories ' ,
' install ' ,
' install_rpath ' ,
' install_dir ' ,
' install_mode ' ,
' install_tag ' ,
' name_prefix ' ,
' name_suffix ' ,
' native ' ,
' objects ' ,
' override_options ' ,
' sources ' ,
' gnu_symbol_visibility ' ,
' link_language ' ,
' win_subsystem ' ,
}
known_build_target_kwargs = (
buildtarget_kwargs |
lang_arg_kwargs |
pch_kwargs |
vala_kwargs |
rust_kwargs |
cs_kwargs )
known_exe_kwargs = known_build_target_kwargs | { ' implib ' , ' export_dynamic ' , ' pie ' }
known_shlib_kwargs = known_build_target_kwargs | { ' version ' , ' soversion ' , ' vs_module_defs ' , ' darwin_versions ' }
known_shmod_kwargs = known_build_target_kwargs | { ' vs_module_defs ' }
known_stlib_kwargs = known_build_target_kwargs | { ' pic ' , ' prelink ' }
known_jar_kwargs = known_exe_kwargs | { ' main_class ' }
@lru_cache ( maxsize = None )
def get_target_macos_dylib_install_name ( ld ) - > str :
name = [ ' @rpath/ ' , ld . prefix , ld . name ]
if ld . soversion is not None :
name . append ( ' . ' + ld . soversion )
name . append ( ' .dylib ' )
return ' ' . join ( name )
class InvalidArguments ( MesonException ) :
pass
@dataclass ( eq = False )
class DependencyOverride ( HoldableObject ) :
dep : dependencies . Dependency
node : ' BaseNode '
explicit : bool = True
@dataclass ( eq = False )
class Headers ( HoldableObject ) :
sources : T . List [ File ]
install_subdir : T . Optional [ str ]
custom_install_dir : T . Optional [ str ]
custom_install_mode : ' FileMode '
subproject : str
# TODO: we really don't need any of these methods, but they're preserved to
# keep APIs relying on them working.
def set_install_subdir ( self , subdir : str ) - > None :
self . install_subdir = subdir
def get_install_subdir ( self ) - > T . Optional [ str ] :
return self . install_subdir
def get_sources ( self ) - > T . List [ File ] :
return self . sources
def get_custom_install_dir ( self ) - > T . Optional [ str ] :
return self . custom_install_dir
def get_custom_install_mode ( self ) - > ' FileMode ' :
return self . custom_install_mode
@dataclass ( eq = False )
class Man ( HoldableObject ) :
sources : T . List [ File ]
custom_install_dir : T . Optional [ str ]
custom_install_mode : ' FileMode '
subproject : str
locale : T . Optional [ str ]
def get_custom_install_dir ( self ) - > T . Optional [ str ] :
return self . custom_install_dir
def get_custom_install_mode ( self ) - > ' FileMode ' :
return self . custom_install_mode
def get_sources ( self ) - > T . List [ ' File ' ] :
return self . sources
@dataclass ( eq = False )
class EmptyDir ( HoldableObject ) :
path : str
install_mode : ' FileMode '
subproject : str
install_tag : T . Optional [ str ] = None
@dataclass ( eq = False )
class InstallDir ( HoldableObject ) :
source_subdir : str
installable_subdir : str
install_dir : str
install_mode : ' FileMode '
exclude : T . Tuple [ T . Set [ str ] , T . Set [ str ] ]
strip_directory : bool
subproject : str
from_source_dir : bool = True
install_tag : T . Optional [ str ] = None
@dataclass ( eq = False )
class DepManifest :
version : str
license : T . List [ str ]
def to_json ( self ) - > T . Dict [ str , T . Union [ str , T . List [ str ] ] ] :
return {
' version ' : self . version ,
' license ' : self . license ,
}
# literally everything isn't dataclass stuff
class Build :
""" A class that holds the status of one build including
all dependencies and so on .
"""
def __init__ ( self , environment : environment . Environment ) :
self . project_name = ' name of master project '
self . project_version = None
self . environment = environment
self . projects = { }
self . targets : ' T.OrderedDict[str, T.Union[CustomTarget, BuildTarget]] ' = OrderedDict ( )
self . run_target_names : T . Set [ T . Tuple [ str , str ] ] = set ( )
self . global_args : PerMachine [ T . Dict [ str , T . List [ str ] ] ] = PerMachine ( { } , { } )
self . global_link_args : PerMachine [ T . Dict [ str , T . List [ str ] ] ] = PerMachine ( { } , { } )
self . projects_args : PerMachine [ T . Dict [ str , T . Dict [ str , T . List [ str ] ] ] ] = PerMachine ( { } , { } )
self . projects_link_args : PerMachine [ T . Dict [ str , T . Dict [ str , T . List [ str ] ] ] ] = PerMachine ( { } , { } )
self . tests : T . List [ ' Test ' ] = [ ]
self . benchmarks : T . List [ ' Test ' ] = [ ]
self . headers : T . List [ Headers ] = [ ]
self . man : T . List [ Man ] = [ ]
self . emptydir : T . List [ EmptyDir ] = [ ]
self . data : T . List [ Data ] = [ ]
self . symlinks : T . List [ SymlinkData ] = [ ]
self . static_linker : PerMachine [ StaticLinker ] = PerMachine ( None , None )
self . subprojects = { }
self . subproject_dir = ' '
self . install_scripts : T . List [ ' ExecutableSerialisation ' ] = [ ]
self . postconf_scripts : T . List [ ' ExecutableSerialisation ' ] = [ ]
self . dist_scripts : T . List [ ' ExecutableSerialisation ' ] = [ ]
self . install_dirs : T . List [ InstallDir ] = [ ]
self . dep_manifest_name : T . Optional [ str ] = None
self . dep_manifest : T . Dict [ str , DepManifest ] = { }
self . stdlibs = PerMachine ( { } , { } )
self . test_setups : T . Dict [ str , TestSetup ] = { }
self . test_setup_default_name = None
self . find_overrides : T . Dict [ str , T . Union [ ' Executable ' , programs . ExternalProgram , programs . OverrideProgram ] ] = { }
self . searched_programs : T . Set [ str ] = set ( ) # The list of all programs that have been searched for.
# If we are doing a cross build we need two caches, if we're doing a
# build == host compilation the both caches should point to the same place.
self . dependency_overrides : PerMachine [ T . Dict [ T . Tuple , DependencyOverride ] ] = PerMachineDefaultable . default (
environment . is_cross_build ( ) , { } , { } )
self . devenv : T . List [ EnvironmentVariables ] = [ ]
self . modules : T . List [ str ] = [ ]
self . need_vsenv = False
def get_build_targets ( self ) :
build_targets = OrderedDict ( )
for name , t in self . targets . items ( ) :
if isinstance ( t , BuildTarget ) :
build_targets [ name ] = t
return build_targets
def get_custom_targets ( self ) :
custom_targets = OrderedDict ( )
for name , t in self . targets . items ( ) :
if isinstance ( t , CustomTarget ) :
custom_targets [ name ] = t
return custom_targets
def copy ( self ) - > Build :
other = Build ( self . environment )
for k , v in self . __dict__ . items ( ) :
if isinstance ( v , ( list , dict , set , OrderedDict ) ) :
other . __dict__ [ k ] = v . copy ( )
else :
other . __dict__ [ k ] = v
return other
def merge ( self , other : Build ) - > None :
for k , v in other . __dict__ . items ( ) :
self . __dict__ [ k ] = v
def ensure_static_linker ( self , compiler : Compiler ) - > None :
if self . static_linker [ compiler . for_machine ] is None and compiler . needs_static_linker ( ) :
self . static_linker [ compiler . for_machine ] = detect_static_linker ( self . environment , compiler )
def get_project ( self ) :
return self . projects [ ' ' ]
def get_subproject_dir ( self ) :
return self . subproject_dir
def get_targets ( self ) - > ' T.OrderedDict[str, T.Union[CustomTarget, BuildTarget]] ' :
return self . targets
def get_tests ( self ) - > T . List [ ' Test ' ] :
return self . tests
def get_benchmarks ( self ) - > T . List [ ' Test ' ] :
return self . benchmarks
def get_headers ( self ) - > T . List [ ' Headers ' ] :
return self . headers
def get_man ( self ) - > T . List [ ' Man ' ] :
return self . man
def get_data ( self ) - > T . List [ ' Data ' ] :
return self . data
def get_symlinks ( self ) - > T . List [ ' SymlinkData ' ] :
return self . symlinks
def get_emptydir ( self ) - > T . List [ ' EmptyDir ' ] :
return self . emptydir
def get_install_subdirs ( self ) - > T . List [ ' InstallDir ' ] :
return self . install_dirs
def get_global_args ( self , compiler : ' Compiler ' , for_machine : ' MachineChoice ' ) - > T . List [ str ] :
d = self . global_args [ for_machine ]
return d . get ( compiler . get_language ( ) , [ ] )
def get_project_args ( self , compiler : ' Compiler ' , project : str , for_machine : ' MachineChoice ' ) - > T . List [ str ] :
d = self . projects_args [ for_machine ]
args = d . get ( project )
if not args :
return [ ]
return args . get ( compiler . get_language ( ) , [ ] )
def get_global_link_args ( self , compiler : ' Compiler ' , for_machine : ' MachineChoice ' ) - > T . List [ str ] :
d = self . global_link_args [ for_machine ]
return d . get ( compiler . get_language ( ) , [ ] )
def get_project_link_args ( self , compiler : ' Compiler ' , project : str , for_machine : ' MachineChoice ' ) - > T . List [ str ] :
d = self . projects_link_args [ for_machine ]
link_args = d . get ( project )
if not link_args :
return [ ]
return link_args . get ( compiler . get_language ( ) , [ ] )
@dataclass ( eq = False )
class IncludeDirs ( HoldableObject ) :
""" Internal representation of an include_directories call. """
curdir : str
incdirs : T . List [ str ]
is_system : bool
# Interpreter has validated that all given directories
# actually exist.
extra_build_dirs : T . List [ str ] = field ( default_factory = list )
def __repr__ ( self ) - > str :
r = ' < {} {} / {} > '
return r . format ( self . __class__ . __name__ , self . curdir , self . incdirs )
def get_curdir ( self ) - > str :
return self . curdir
def get_incdirs ( self ) - > T . List [ str ] :
return self . incdirs
def get_extra_build_dirs ( self ) - > T . List [ str ] :
return self . extra_build_dirs
def to_string_list ( self , sourcedir : str , builddir : T . Optional [ str ] = None ) - > T . List [ str ] :
""" Convert IncludeDirs object to a list of strings.
: param sourcedir : The absolute source directory
: param builddir : The absolute build directory , option , buid dir will not
be added if this is unset
: returns : A list of strings ( without compiler argument )
"""
strlist : T . List [ str ] = [ ]
for idir in self . incdirs :
strlist . append ( os . path . join ( sourcedir , self . curdir , idir ) )
if builddir :
strlist . append ( os . path . join ( builddir , self . curdir , idir ) )
return strlist
@dataclass ( eq = False )
class ExtractedObjects ( HoldableObject ) :
'''
Holds a list of sources for which the objects must be extracted
'''
target : ' BuildTarget '
srclist : T . List [ File ] = field ( default_factory = list )
genlist : T . List [ ' GeneratedTypes ' ] = field ( default_factory = list )
objlist : T . List [ T . Union [ str , ' File ' , ' ExtractedObjects ' ] ] = field ( default_factory = list )
recursive : bool = True
def __post_init__ ( self ) - > None :
if self . target . is_unity :
self . check_unity_compatible ( )
def __repr__ ( self ) - > str :
r = ' < {0} {1!r} : {2} > '
return r . format ( self . __class__ . __name__ , self . target . name , self . srclist )
@staticmethod
def get_sources ( sources : T . Sequence [ ' FileOrString ' ] , generated_sources : T . Sequence [ ' GeneratedTypes ' ] ) - > T . List [ ' FileOrString ' ] :
# Merge sources and generated sources
sources = list ( sources )
for gensrc in generated_sources :
for s in gensrc . get_outputs ( ) :
# We cannot know the path where this source will be generated,
# but all we need here is the file extension to determine the
# compiler.
sources . append ( s )
# Filter out headers and all non-source files
return [ s for s in sources if environment . is_source ( s ) and not environment . is_header ( s ) ]
def classify_all_sources ( self , sources : T . List [ FileOrString ] , generated_sources : T . Sequence [ ' GeneratedTypes ' ] ) - > T . Dict [ ' Compiler ' , T . List [ ' FileOrString ' ] ] :
sources_ = self . get_sources ( sources , generated_sources )
return classify_unity_sources ( self . target . compilers . values ( ) , sources_ )
def check_unity_compatible ( self ) - > None :
# Figure out if the extracted object list is compatible with a Unity
# build. When we're doing a Unified build, we go through the sources,
# and create a single source file from each subset of the sources that
# can be compiled with a specific compiler. Then we create one object
# from each unified source file. So for each compiler we can either
# extra all its sources or none.
cmpsrcs = self . classify_all_sources ( self . target . sources , self . target . generated )
extracted_cmpsrcs = self . classify_all_sources ( self . srclist , self . genlist )
for comp , srcs in extracted_cmpsrcs . items ( ) :
if set ( srcs ) != set ( cmpsrcs [ comp ] ) :
raise MesonException ( ' Single object files can not be extracted '
' in Unity builds. You can only extract all '
' the object files for each compiler at once. ' )
def get_outputs ( self , backend : ' Backend ' ) - > T . List [ str ] :
return [
backend . object_filename_from_source ( self . target , source )
for source in self . get_sources ( self . srclist , self . genlist )
]
@dataclass ( eq = False , order = False )
class StructuredSources ( HoldableObject ) :
""" A container for sources in languages that use filesystem hierarchy.
Languages like Rust and Cython rely on the layout of files in the filesystem
as part of the compiler implementation . This structure allows us to
represent the required filesystem layout .
"""
sources : T . DefaultDict [ str , T . List [ T . Union [ str , File , CustomTarget , CustomTargetIndex , GeneratedList ] ] ] = field (
default_factory = lambda : defaultdict ( list ) )
def __add__ ( self , other : StructuredSources ) - > StructuredSources :
sources = self . sources . copy ( )
for k , v in other . sources . items ( ) :
sources [ k ] . extend ( v )
return StructuredSources ( sources )
def __bool__ ( self ) - > bool :
return bool ( self . sources )
def first_file ( self ) - > T . Union [ str , File , CustomTarget , CustomTargetIndex , GeneratedList ] :
""" Get the first source in the root
: return : The first source in the root
"""
return self . sources [ ' ' ] [ 0 ]
def as_list ( self ) - > T . List [ T . Union [ str , File , CustomTarget , CustomTargetIndex , GeneratedList ] ] :
return list ( itertools . chain . from_iterable ( self . sources . values ( ) ) )
def needs_copy ( self , target : BuildTarget ) - > bool :
""" Do we need to create a structure in the build directory.
This allows us to avoid making copies if the structures exists in the
source dir . Which could happen in situations where a generated source
only exists in some configurations
"""
p = pathlib . Path ( target . subdir )
for files in self . sources . values ( ) :
for f in files :
if isinstance ( f , str ) :
if not ( target . environment . source_dir / p / f ) . exists ( ) :
return True
elif isinstance ( f , File ) :
if f . is_built :
return True
else :
return True
return False
EnvInitValueType = T . Dict [ str , T . Union [ str , T . List [ str ] ] ]
class EnvironmentVariables ( HoldableObject ) :
def __init__ ( self , values : T . Optional [ EnvInitValueType ] = None ,
init_method : Literal [ ' set ' , ' prepend ' , ' append ' ] = ' set ' , separator : str = os . pathsep ) - > None :
self . envvars : T . List [ T . Tuple [ T . Callable [ [ T . Dict [ str , str ] , str , T . List [ str ] , str ] , str ] , str , T . List [ str ] , str ] ] = [ ]
# The set of all env vars we have operations for. Only used for self.has_name()
self . varnames : T . Set [ str ] = set ( )
if values :
init_func = getattr ( self , init_method )
for name , value in values . items ( ) :
init_func ( name , listify ( value ) , separator )
def __repr__ ( self ) - > str :
repr_str = " < {0} : {1} > "
return repr_str . format ( self . __class__ . __name__ , self . envvars )
def hash ( self , hasher : T . Any ) :
myenv = self . get_env ( { } )
for key in sorted ( myenv . keys ( ) ) :
hasher . update ( bytes ( key , encoding = ' utf-8 ' ) )
hasher . update ( b ' , ' )
hasher . update ( bytes ( myenv [ key ] , encoding = ' utf-8 ' ) )
hasher . update ( b ' ; ' )
def has_name ( self , name : str ) - > bool :
return name in self . varnames
def get_names ( self ) - > T . Set [ str ] :
return self . varnames
def set ( self , name : str , values : T . List [ str ] , separator : str = os . pathsep ) - > None :
self . varnames . add ( name )
self . envvars . append ( ( self . _set , name , values , separator ) )
def append ( self , name : str , values : T . List [ str ] , separator : str = os . pathsep ) - > None :
self . varnames . add ( name )
self . envvars . append ( ( self . _append , name , values , separator ) )
def prepend ( self , name : str , values : T . List [ str ] , separator : str = os . pathsep ) - > None :
self . varnames . add ( name )
self . envvars . append ( ( self . _prepend , name , values , separator ) )
@staticmethod
def _set ( env : T . Dict [ str , str ] , name : str , values : T . List [ str ] , separator : str ) - > str :
return separator . join ( values )
@staticmethod
def _append ( env : T . Dict [ str , str ] , name : str , values : T . List [ str ] , separator : str ) - > str :
curr = env . get ( name )
return separator . join ( values if curr is None else [ curr ] + values )
@staticmethod
def _prepend ( env : T . Dict [ str , str ] , name : str , values : T . List [ str ] , separator : str ) - > str :
curr = env . get ( name )
return separator . join ( values if curr is None else values + [ curr ] )
def get_env ( self , full_env : T . Dict [ str , str ] ) - > T . Dict [ str , str ] :
env = full_env . copy ( )
for method , name , values , separator in self . envvars :
env [ name ] = method ( env , name , values , separator )
return env
@dataclass ( eq = False )
class Target ( HoldableObject ) :
# TODO: should Target be an abc.ABCMeta?
name : str
subdir : str
subproject : ' SubProject '
build_by_default : bool
for_machine : MachineChoice
def __post_init__ ( self ) - > None :
if has_path_sep ( self . name ) :
# Fix failing test 53 when this becomes an error.
mlog . warning ( textwrap . dedent ( f ''' \
Target " {self.name} " has a path separator in its name .
This is not supported , it can cause unexpected failures and will become
a hard error in the future .
''' ))
self . install = False
self . build_always_stale = False
self . option_overrides_base : T . Dict [ OptionKey , str ] = { }
self . option_overrides_compiler : T . Dict [ OptionKey , str ] = { }
self . extra_files = [ ] # type: T.List[File]
if not hasattr ( self , ' typename ' ) :
raise RuntimeError ( f ' Target type is not set for target class " { type ( self ) . __name__ } " . This is a bug ' )
# dataclass comparators?
def __lt__ ( self , other : object ) - > bool :
if not isinstance ( other , Target ) :
return NotImplemented
return self . get_id ( ) < other . get_id ( )
def __le__ ( self , other : object ) - > bool :
if not isinstance ( other , Target ) :
return NotImplemented
return self . get_id ( ) < = other . get_id ( )
def __gt__ ( self , other : object ) - > bool :
if not isinstance ( other , Target ) :
return NotImplemented
return self . get_id ( ) > other . get_id ( )
def __ge__ ( self , other : object ) - > bool :
if not isinstance ( other , Target ) :
return NotImplemented
return self . get_id ( ) > = other . get_id ( )
def get_default_install_dir ( self , env : environment . Environment ) - > T . Tuple [ str , str ] :
raise NotImplementedError
def get_custom_install_dir ( self ) - > T . List [ T . Union [ str , bool ] ] :
raise NotImplementedError
def get_install_dir ( self , environment : environment . Environment ) - > T . Tuple [ T . Any , str , bool ] :
# Find the installation directory.
default_install_dir , install_dir_name = self . get_default_install_dir ( environment )
outdirs = self . get_custom_install_dir ( )
if outdirs and outdirs [ 0 ] != default_install_dir and outdirs [ 0 ] is not True :
# Either the value is set to a non-default value, or is set to
# False (which means we want this specific output out of many
# outputs to not be installed).
custom_install_dir = True
else :
custom_install_dir = False
# if outdirs is empty we need to set to something, otherwise we set
# only the first value to the default
if outdirs :
outdirs [ 0 ] = default_install_dir
else :
outdirs = [ default_install_dir ]
return outdirs , install_dir_name , custom_install_dir
def get_basename ( self ) - > str :
return self . name
def get_subdir ( self ) - > str :
return self . subdir
def get_typename ( self ) - > str :
return self . typename
@staticmethod
def _get_id_hash ( target_id ) :
# We don't really need cryptographic security here.
# Small-digest hash function with unlikely collision is good enough.
h = hashlib . sha256 ( )
h . update ( target_id . encode ( encoding = ' utf-8 ' , errors = ' replace ' ) )
# This ID should be case-insensitive and should work in Visual Studio,
# e.g. it should not start with leading '-'.
return h . hexdigest ( ) [ : 7 ]
@staticmethod
def construct_id_from_path ( subdir : str , name : str , type_suffix : str ) - > str :
""" Construct target ID from subdir, name and type suffix.
This helper function is made public mostly for tests . """
# This ID must also be a valid file name on all OSs.
# It should also avoid shell metacharacters for obvious
# reasons. '@' is not used as often as '_' in source code names.
# In case of collisions consider using checksums.
# FIXME replace with assert when slash in names is prohibited
name_part = name . replace ( ' / ' , ' @ ' ) . replace ( ' \\ ' , ' @ ' )
assert not has_path_sep ( type_suffix )
my_id = name_part + type_suffix
if subdir :
subdir_part = Target . _get_id_hash ( subdir )
# preserve myid for better debuggability
return subdir_part + ' @@ ' + my_id
return my_id
def get_id ( self ) - > str :
return self . construct_id_from_path (
self . subdir , self . name , self . type_suffix ( ) )
def process_kwargs_base ( self , kwargs : T . Dict [ str , T . Any ] ) - > None :
if ' build_by_default ' in kwargs :
self . build_by_default = kwargs [ ' build_by_default ' ]
if not isinstance ( self . build_by_default , bool ) :
raise InvalidArguments ( ' build_by_default must be a boolean value. ' )
elif kwargs . get ( ' install ' , False ) :
# For backward compatibility, if build_by_default is not explicitly
# set, use the value of 'install' if it's enabled.
self . build_by_default = True
option_overrides = self . parse_overrides ( kwargs )
for k , v in option_overrides . items ( ) :
if k . lang :
self . option_overrides_compiler [ k . evolve ( machine = self . for_machine ) ] = v
continue
self . option_overrides_base [ k ] = v
@staticmethod
def parse_overrides ( kwargs : T . Dict [ str , T . Any ] ) - > T . Dict [ OptionKey , str ] :
opts = kwargs . get ( ' override_options ' , [ ] )
# In this case we have an already parsed and ready to go dictionary
# provided by typed_kwargs
if isinstance ( opts , dict ) :
return T . cast ( T . Dict [ OptionKey , str ] , opts )
result : T . Dict [ OptionKey , str ] = { }
overrides = stringlistify ( opts )
for o in overrides :
if ' = ' not in o :
raise InvalidArguments ( ' Overrides must be of form " key=value " ' )
k , v = o . split ( ' = ' , 1 )
key = OptionKey . from_string ( k . strip ( ) )
v = v . strip ( )
result [ key ] = v
return result
def is_linkable_target ( self ) - > bool :
return False
def get_outputs ( self ) - > T . List [ str ] :
return [ ]
def should_install ( self ) - > bool :
return False
class BuildTarget ( Target ) :
known_kwargs = known_build_target_kwargs
install_dir : T . List [ T . Union [ str , bool ] ]
def __init__ ( self , name : str , subdir : str , subproject : ' SubProject ' , for_machine : MachineChoice ,
sources : T . List [ ' SourceOutputs ' ] , objects , environment : environment . Environment , kwargs ) :
super ( ) . __init__ ( name , subdir , subproject , True , for_machine )
unity_opt = environment . coredata . get_option ( OptionKey ( ' unity ' ) )
self . is_unity = unity_opt == ' on ' or ( unity_opt == ' subprojects ' and subproject != ' ' )
self . environment = environment
self . compilers = OrderedDict ( ) # type: OrderedDict[str, Compiler]
self . objects : T . List [ T . Union [ str , ' File ' , ' ExtractedObjects ' ] ] = [ ]
self . external_deps : T . List [ dependencies . Dependency ] = [ ]
self . include_dirs : T . List [ ' IncludeDirs ' ] = [ ]
self . link_language = kwargs . get ( ' link_language ' )
self . link_targets : T . List [ T . Union [ ' BuildTarget ' , ' CustomTarget ' , ' CustomTargetIndex ' ] ] = [ ]
self . link_whole_targets = [ ]
self . link_depends = [ ]
self . added_deps = set ( )
self . name_prefix_set = False
self . name_suffix_set = False
self . filename = ' no_name '
# The list of all files outputted by this target. Useful in cases such
# as Vala which generates .vapi and .h besides the compiled output.
self . outputs = [ self . filename ]
self . need_install = False
self . pch : T . Dict [ str , T . List [ str ] ] = { }
self . extra_args : T . Dict [ str , T . List [ ' FileOrString ' ] ] = { }
self . sources : T . List [ File ] = [ ]
self . generated : T . List [ ' GeneratedTypes ' ] = [ ]
self . d_features = defaultdict ( list )
self . pic = False
self . pie = False
# Track build_rpath entries so we can remove them at install time
self . rpath_dirs_to_remove : T . Set [ bytes ] = set ( )
self . process_sourcelist ( sources )
# Objects can be:
# 1. Pre-existing objects provided by the user with the `objects:` kwarg
# 2. Compiled objects created by and extracted from another target
self . process_objectlist ( objects )
self . process_kwargs ( kwargs , environment )
self . check_unknown_kwargs ( kwargs )
self . process_compilers ( )
if not any ( [ self . sources , self . generated , self . objects , self . link_whole ] ) :
raise InvalidArguments ( f ' Build target { name } has no sources. ' )
self . process_compilers_late ( )
self . validate_sources ( )
self . validate_install ( environment )
self . check_module_linking ( )
def __repr__ ( self ) :
repr_str = " < {0} {1} : {2} > "
return repr_str . format ( self . __class__ . __name__ , self . get_id ( ) , self . filename )
def __str__ ( self ) :
return f " { self . name } "
def validate_install ( self , environment ) :
if self . for_machine is MachineChoice . BUILD and self . need_install :
if environment . is_cross_build ( ) :
raise InvalidArguments ( ' Tried to install a target for the build machine in a cross build. ' )
else :
mlog . warning ( ' Installing target build for the build machine. This will fail in a cross build. ' )
def check_unknown_kwargs ( self , kwargs ) :
# Override this method in derived classes that have more
# keywords.
self . check_unknown_kwargs_int ( kwargs , self . known_kwargs )
def check_unknown_kwargs_int ( self , kwargs , known_kwargs ) :
unknowns = [ ]
for k in kwargs :
if k not in known_kwargs :
unknowns . append ( k )
if len ( unknowns ) > 0 :
mlog . warning ( ' Unknown keyword argument(s) in target {} : {} . ' . format ( self . name , ' , ' . join ( unknowns ) ) )
def process_objectlist ( self , objects ) :
assert isinstance ( objects , list )
for s in objects :
if isinstance ( s , ( str , File , ExtractedObjects ) ) :
self . objects . append ( s )
elif isinstance ( s , ( GeneratedList , CustomTarget ) ) :
msg = ' Generated files are not allowed in the \' objects \' kwarg ' + \
f ' for target { self . name !r} . \n It is meant only for ' + \
' pre-built object files that are shipped with the \n source ' + \
' tree. Try adding it in the list of sources. '
raise InvalidArguments ( msg )
else :
raise InvalidArguments ( f ' Bad object of type { type ( s ) . __name__ !r} in target { self . name !r} . ' )
def process_sourcelist ( self , sources : T . List [ ' SourceOutputs ' ] ) - > None :
""" Split sources into generated and static sources.
Sources can be :
1. Pre - existing source files in the source tree ( static )
2. Pre - existing sources generated by configure_file in the build tree .
( static as they are only regenerated if meson itself is regenerated )
3. Sources files generated by another target or a Generator ( generated )
"""
added_sources : T . Set [ File ] = set ( ) # If the same source is defined multiple times, use it only once.
for s in sources :
if isinstance ( s , File ) :
if s not in added_sources :
self . sources . append ( s )
added_sources . add ( s )
elif isinstance ( s , ( CustomTarget , CustomTargetIndex , GeneratedList ) ) :
self . generated . append ( s )
@staticmethod
def can_compile_remove_sources ( compiler : ' Compiler ' , sources : T . List [ ' FileOrString ' ] ) - > bool :
removed = False
for s in sources [ : ] :
if compiler . can_compile ( s ) :
sources . remove ( s )
removed = True
return removed
def process_compilers_late ( self ) :
""" Processes additional compilers after kwargs have been evaluated.
This can add extra compilers that might be required by keyword
arguments , such as link_with or dependencies . It will also try to guess
which compiler to use if one hasn ' t been selected already.
"""
# Populate list of compilers
compilers = self . environment . coredata . compilers [ self . for_machine ]
# did user override clink_langs for this target?
link_langs = [ self . link_language ] if self . link_language else clink_langs
# If this library is linked against another library we need to consider
# the languages of those libraries as well.
if self . link_targets or self . link_whole_targets :
extra = set ( )
for t in itertools . chain ( self . link_targets , self . link_whole_targets ) :
if isinstance ( t , CustomTarget ) or isinstance ( t , CustomTargetIndex ) :
continue # We can't know anything about these.
for name , compiler in t . compilers . items ( ) :
if name in link_langs :
extra . add ( ( name , compiler ) )
for name , compiler in sorted ( extra , key = lambda p : sort_clink ( p [ 0 ] ) ) :
self . compilers [ name ] = compiler
if not self . compilers :
# No source files or parent targets, target consists of only object
# files of unknown origin. Just add the first clink compiler
# that we have and hope that it can link these objects
for lang in link_langs :
if lang in compilers :
self . compilers [ lang ] = compilers [ lang ]
break
def process_compilers ( self ) :
'''
Populate self . compilers , which is the list of compilers that this
target will use for compiling all its sources .
We also add compilers that were used by extracted objects to simplify
dynamic linker determination .
'''
if not self . sources and not self . generated and not self . objects :
return
# Populate list of compilers
compilers = self . environment . coredata . compilers [ self . for_machine ]
# Pre-existing sources
sources = list ( self . sources )
# All generated sources
for gensrc in self . generated :
for s in gensrc . get_outputs ( ) :
# Generated objects can't be compiled, so don't use them for
# compiler detection. If our target only has generated objects,
# we will fall back to using the first c-like compiler we find,
# which is what we need.
if not is_object ( s ) :
sources . append ( s )
for d in self . external_deps :
for s in d . sources :
if isinstance ( s , ( str , File ) ) :
sources . append ( s )
# Sources that were used to create our extracted objects
for o in self . objects :
if not isinstance ( o , ExtractedObjects ) :
continue
for s in o . srclist :
# Don't add Vala sources since that will pull in the Vala
# compiler even though we will never use it since we are
# dealing with compiled C code.
if not s . endswith ( lang_suffixes [ ' vala ' ] ) :
sources . append ( s )
if sources :
# For each source, try to add one compiler that can compile it.
#
# If it has a suffix that belongs to a known language, we must have
# a compiler for that language.
#
# Otherwise, it's ok if no compilers can compile it, because users
# are expected to be able to add arbitrary non-source files to the
# sources list
for s in sources :
for lang , compiler in compilers . items ( ) :
if compiler . can_compile ( s ) :
if lang not in self . compilers :
self . compilers [ lang ] = compiler
break
else :
if is_known_suffix ( s ) :
raise MesonException ( ' No {} machine compiler for " {} " ' .
format ( self . for_machine . get_lower_case_name ( ) , s ) )
# Re-sort according to clink_langs
self . compilers = OrderedDict ( sorted ( self . compilers . items ( ) ,
key = lambda t : sort_clink ( t [ 0 ] ) ) )
# If all our sources are Vala, our target also needs the C compiler but
# it won't get added above.
if ' vala ' in self . compilers and ' c ' not in self . compilers :
self . compilers [ ' c ' ] = compilers [ ' c ' ]
if ' cython ' in self . compilers :
key = OptionKey ( ' language ' , machine = self . for_machine , lang = ' cython ' )
if key in self . option_overrides_compiler :
value = self . option_overrides_compiler [ key ]
else :
value = self . environment . coredata . options [ key ] . value
try :
self . compilers [ value ] = compilers [ value ]
except KeyError :
# TODO: it would be nice to not have to do this here, but we
# have two problems to work around:
# 1. If this is set via an override we have no way to know
# before now that we need a compiler for the non-default language
# 2. Because Cython itself initializes the `cython_language`
# option, we have no good place to insert that you need it
# before now, so we just have to do it here.
comp = detect_compiler_for ( self . environment , value , self . for_machine )
# This is copied verbatim from the interpreter
if self . for_machine == MachineChoice . HOST or self . environment . is_cross_build ( ) :
logger_fun = mlog . log
else :
logger_fun = mlog . debug
logger_fun ( comp . get_display_language ( ) , ' compiler for the ' , self . for_machine . get_lower_case_name ( ) , ' machine: ' ,
mlog . bold ( ' ' . join ( comp . get_exelist ( ) ) ) , comp . get_version_string ( ) )
if comp . linker is not None :
logger_fun ( comp . get_display_language ( ) , ' linker for the ' , self . for_machine . get_lower_case_name ( ) , ' machine: ' ,
mlog . bold ( ' ' . join ( comp . linker . get_exelist ( ) ) ) , comp . linker . id , comp . linker . version )
if comp is None :
raise MesonException ( f ' Cannot find required compiler { value } ' )
self . compilers [ value ] = comp
def validate_sources ( self ) :
if not self . sources :
return
for lang in ( ' cs ' , ' java ' ) :
if lang in self . compilers :
check_sources = list ( self . sources )
compiler = self . compilers [ lang ]
if not self . can_compile_remove_sources ( compiler , check_sources ) :
raise InvalidArguments ( f ' No { lang } sources found in target { self . name !r} ' )
if check_sources :
m = ' {0} targets can only contain {0} files: \n ' . format ( lang . capitalize ( ) )
m + = ' \n ' . join ( [ repr ( c ) for c in check_sources ] )
raise InvalidArguments ( m )
# CSharp and Java targets can't contain any other file types
assert len ( self . compilers ) == 1
return
def process_link_depends ( self , sources , environment ) :
""" Process the link_depends keyword argument.
This is designed to handle strings , Files , and the output of Custom
Targets . Notably it doesn ' t handle generator() returned objects, since
adding them as a link depends would inherently cause them to be
generated twice , since the output needs to be passed to the ld_args and
link_depends .
"""
sources = listify ( sources )
for s in sources :
if isinstance ( s , File ) :
self . link_depends . append ( s )
elif isinstance ( s , str ) :
self . link_depends . append (
File . from_source_file ( environment . source_dir , self . subdir , s ) )
elif hasattr ( s , ' get_outputs ' ) :
self . link_depends . append ( s )
else :
raise InvalidArguments (
' Link_depends arguments must be strings, Files, '
' or a Custom Target, or lists thereof. ' )
def get_original_kwargs ( self ) :
return self . kwargs
def copy_kwargs ( self , kwargs ) :
self . kwargs = copy . copy ( kwargs )
for k , v in self . kwargs . items ( ) :
if isinstance ( v , list ) :
self . kwargs [ k ] = listify ( v , flatten = True )
for t in [ ' dependencies ' , ' link_with ' , ' include_directories ' , ' sources ' ] :
if t in self . kwargs :
self . kwargs [ t ] = listify ( self . kwargs [ t ] , flatten = True )
def extract_objects ( self , srclist : T . List [ T . Union [ ' FileOrString ' , ' GeneratedTypes ' ] ] ) - > ExtractedObjects :
sources_set = set ( self . sources )
generated_set = set ( self . generated )
obj_src : T . List [ ' File ' ] = [ ]
obj_gen : T . List [ ' GeneratedTypes ' ] = [ ]
for src in srclist :
if isinstance ( src , ( str , File ) ) :
if isinstance ( src , str ) :
src = File ( False , self . subdir , src )
else :
FeatureNew . single_use ( ' File argument for extract_objects ' , ' 0.50.0 ' , self . subproject )
if src not in sources_set :
raise MesonException ( f ' Tried to extract unknown source { src } . ' )
obj_src . append ( src )
elif isinstance ( src , ( CustomTarget , CustomTargetIndex , GeneratedList ) ) :
FeatureNew . single_use ( ' Generated sources for extract_objects ' , ' 0.61.0 ' , self . subproject )
target = src . target if isinstance ( src , CustomTargetIndex ) else src
if src not in generated_set and target not in generated_set :
raise MesonException ( f ' Tried to extract unknown source { target . get_basename ( ) } . ' )
obj_gen . append ( src )
else :
raise MesonException ( f ' Object extraction arguments must be strings, Files or targets (got { type ( src ) . __name__ } ). ' )
return ExtractedObjects ( self , obj_src , obj_gen )
def extract_all_objects ( self , recursive : bool = True ) - > ExtractedObjects :
return ExtractedObjects ( self , self . sources , self . generated , self . objects ,
recursive )
def get_all_link_deps ( self ) - > ' ImmutableListProtocol[T.Union[BuildTarget, CustomTarget, CustomTargetIndex]] ' :
return self . get_transitive_link_deps ( )
@lru_cache ( maxsize = None )
def get_transitive_link_deps ( self ) - > ' ImmutableListProtocol[T.Union[BuildTarget, CustomTarget, CustomTargetIndex]] ' :
result : T . List [ Target ] = [ ]
for i in self . link_targets :
result + = i . get_all_link_deps ( )
return result
def get_link_deps_mapping ( self , prefix : str , environment : environment . Environment ) - > T . Mapping [ str , str ] :
return self . get_transitive_link_deps_mapping ( prefix , environment )
@lru_cache ( maxsize = None )
def get_transitive_link_deps_mapping ( self , prefix : str , environment : environment . Environment ) - > T . Mapping [ str , str ] :
result : T . Dict [ str , str ] = { }
for i in self . link_targets :
mapping = i . get_link_deps_mapping ( prefix , environment )
#we are merging two dictionaries, while keeping the earlier one dominant
result_tmp = mapping . copy ( )
result_tmp . update ( result )
result = result_tmp
return result
@lru_cache ( maxsize = None )
def get_link_dep_subdirs ( self ) - > ' ImmutableSetProtocol[str] ' :
result : OrderedSet [ str ] = OrderedSet ( )
for i in self . link_targets :
if not isinstance ( i , StaticLibrary ) :
result . add ( i . get_subdir ( ) )
result . update ( i . get_link_dep_subdirs ( ) )
return result
def get_default_install_dir ( self , environment : environment . Environment ) - > T . Tuple [ str , str ] :
return environment . get_libdir ( ) , ' {libdir} '
def get_custom_install_dir ( self ) - > T . List [ T . Union [ str , bool ] ] :
return self . install_dir
def get_custom_install_mode ( self ) - > T . Optional [ ' FileMode ' ] :
return self . install_mode
def process_kwargs ( self , kwargs , environment ) :
self . process_kwargs_base ( kwargs )
self . copy_kwargs ( kwargs )
kwargs . get ( ' modules ' , [ ] )
self . need_install = kwargs . get ( ' install ' , self . need_install )
llist = extract_as_list ( kwargs , ' link_with ' )
for linktarget in llist :
if isinstance ( linktarget , dependencies . ExternalLibrary ) :
raise MesonException ( textwrap . dedent ( ''' \
An external library was used in link_with keyword argument , which
is reserved for libraries built as part of this project . External
libraries must be passed using the dependencies keyword argument
instead , because they are conceptually " external dependencies " ,
just like those detected with the dependency ( ) function .
''' ))
self . link ( linktarget )
lwhole = extract_as_list ( kwargs , ' link_whole ' )
for linktarget in lwhole :
self . link_whole ( linktarget )
c_pchlist , cpp_pchlist , clist , cpplist , cudalist , cslist , valalist , objclist , objcpplist , fortranlist , rustlist \
= ( extract_as_list ( kwargs , c ) for c in [ ' c_pch ' , ' cpp_pch ' , ' c_args ' , ' cpp_args ' , ' cuda_args ' , ' cs_args ' , ' vala_args ' , ' objc_args ' , ' objcpp_args ' , ' fortran_args ' , ' rust_args ' ] )
self . add_pch ( ' c ' , c_pchlist )
self . add_pch ( ' cpp ' , cpp_pchlist )
compiler_args = { ' c ' : clist , ' cpp ' : cpplist , ' cuda ' : cudalist , ' cs ' : cslist , ' vala ' : valalist , ' objc ' : objclist , ' objcpp ' : objcpplist ,
' fortran ' : fortranlist , ' rust ' : rustlist
}
for key , value in compiler_args . items ( ) :
self . add_compiler_args ( key , value )
if not isinstance ( self , Executable ) or ' export_dynamic ' in kwargs :
self . vala_header = kwargs . get ( ' vala_header ' , self . name + ' .h ' )
self . vala_vapi = kwargs . get ( ' vala_vapi ' , self . name + ' .vapi ' )
self . vala_gir = kwargs . get ( ' vala_gir ' , None )
dlist = stringlistify ( kwargs . get ( ' d_args ' , [ ] ) )
self . add_compiler_args ( ' d ' , dlist )
dfeatures = defaultdict ( list )
dfeature_unittest = kwargs . get ( ' d_unittest ' , False )
if dfeature_unittest :
dfeatures [ ' unittest ' ] = dfeature_unittest
dfeature_versions = kwargs . get ( ' d_module_versions ' , [ ] )
if dfeature_versions :
dfeatures [ ' versions ' ] = dfeature_versions
dfeature_debug = kwargs . get ( ' d_debug ' , [ ] )
if dfeature_debug :
dfeatures [ ' debug ' ] = dfeature_debug
if ' d_import_dirs ' in kwargs :
dfeature_import_dirs = extract_as_list ( kwargs , ' d_import_dirs ' )
for d in dfeature_import_dirs :
if not isinstance ( d , IncludeDirs ) :
raise InvalidArguments ( ' Arguments to d_import_dirs must be include_directories. ' )
dfeatures [ ' import_dirs ' ] = dfeature_import_dirs
if dfeatures :
self . d_features = dfeatures
self . link_args = extract_as_list ( kwargs , ' link_args ' )
for i in self . link_args :
if not isinstance ( i , str ) :
raise InvalidArguments ( ' Link_args arguments must be strings. ' )
for l in self . link_args :
if ' -Wl,-rpath ' in l or l . startswith ( ' -rpath ' ) :
mlog . warning ( textwrap . dedent ( ''' \
Please do not define rpath with a linker argument , use install_rpath
or build_rpath properties instead .
This will become a hard error in a future Meson release .
''' ))
self . process_link_depends ( kwargs . get ( ' link_depends ' , [ ] ) , environment )
# Target-specific include dirs must be added BEFORE include dirs from
# internal deps (added inside self.add_deps()) to override them.
inclist = extract_as_list ( kwargs , ' include_directories ' )
self . add_include_dirs ( inclist )
# Add dependencies (which also have include_directories)
deplist = extract_as_list ( kwargs , ' dependencies ' )
self . add_deps ( deplist )
# If an item in this list is False, the output corresponding to
# the list index of that item will not be installed
self . install_dir = typeslistify ( kwargs . get ( ' install_dir ' , [ ] ) ,
( str , bool ) )
self . install_mode = kwargs . get ( ' install_mode ' , None )
self . install_tag = stringlistify ( kwargs . get ( ' install_tag ' , [ None ] ) )
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 ) :
# This kwarg is deprecated. The value of "none" means that the kwarg
# was not specified and win_subsystem should be used instead.
self . gui_app = None
if ' gui_app ' in kwargs :
if ' win_subsystem ' in kwargs :
raise InvalidArguments ( ' Can specify only gui_app or win_subsystem for a target, not both. ' )
self . gui_app = kwargs [ ' gui_app ' ]
if not isinstance ( self . gui_app , bool ) :
raise InvalidArguments ( ' Argument gui_app must be boolean. ' )
self . win_subsystem = self . validate_win_subsystem ( kwargs . get ( ' win_subsystem ' , ' console ' ) )
elif ' gui_app ' in kwargs :
raise InvalidArguments ( ' Argument gui_app can only be used on executables. ' )
elif ' win_subsystem ' in kwargs :
raise InvalidArguments ( ' Argument win_subsystem can only be used on executables. ' )
extra_files = extract_as_list ( kwargs , ' extra_files ' )
for i in extra_files :
assert isinstance ( i , File )
trial = os . path . join ( environment . get_source_dir ( ) , i . subdir , i . fname )
if not os . path . isfile ( trial ) :
raise InvalidArguments ( f ' Tried to add non-existing extra file { i } . ' )
self . extra_files = extra_files
self . install_rpath : str = kwargs . get ( ' install_rpath ' , ' ' )
if not isinstance ( self . install_rpath , str ) :
raise InvalidArguments ( ' Install_rpath is not a string. ' )
self . build_rpath = kwargs . get ( ' build_rpath ' , ' ' )
if not isinstance ( self . build_rpath , str ) :
raise InvalidArguments ( ' Build_rpath is not a string. ' )
resources = extract_as_list ( kwargs , ' 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 ( f ' Tried to add non-existing resource { r } . ' )
self . resources = resources
if ' name_prefix ' in kwargs :
name_prefix = kwargs [ ' name_prefix ' ]
if isinstance ( name_prefix , list ) :
if name_prefix :
raise InvalidArguments ( ' name_prefix array must be empty to signify default. ' )
else :
if not isinstance ( name_prefix , str ) :
raise InvalidArguments ( ' name_prefix must be a string. ' )
self . prefix = name_prefix
self . name_prefix_set = True
if ' name_suffix ' in kwargs :
name_suffix = kwargs [ ' name_suffix ' ]
if isinstance ( name_suffix , list ) :
if name_suffix :
raise InvalidArguments ( ' name_suffix array must be empty to signify default. ' )
else :
if not isinstance ( name_suffix , str ) :
raise InvalidArguments ( ' name_suffix must be a string. ' )
if name_suffix == ' ' :
raise InvalidArguments ( ' name_suffix should not be an empty string. '
' If you want meson to use the default behaviour '
' for each platform pass `[]` (empty array) ' )
self . suffix = name_suffix
self . name_suffix_set = True
if isinstance ( self , StaticLibrary ) :
# You can't disable PIC on OS X. The compiler ignores -fno-PIC.
# PIC is always on for Windows (all code is position-independent
# since library loading is done differently)
m = self . environment . machines [ self . for_machine ]
if m . is_darwin ( ) or m . is_windows ( ) :
self . pic = True
else :
self . pic = self . _extract_pic_pie ( kwargs , ' pic ' , environment , ' b_staticpic ' )
if isinstance ( self , Executable ) or ( isinstance ( self , StaticLibrary ) and not self . pic ) :
# Executables must be PIE on Android
if self . environment . machines [ self . for_machine ] . is_android ( ) :
self . pie = True
else :
self . pie = self . _extract_pic_pie ( kwargs , ' pie ' , environment , ' b_pie ' )
self . implicit_include_directories = kwargs . get ( ' implicit_include_directories ' , True )
if not isinstance ( self . implicit_include_directories , bool ) :
raise InvalidArguments ( ' Implicit_include_directories must be a boolean. ' )
self . gnu_symbol_visibility = kwargs . get ( ' gnu_symbol_visibility ' , ' ' )
if not isinstance ( self . gnu_symbol_visibility , str ) :
raise InvalidArguments ( ' GNU symbol visibility must be a string. ' )
if self . gnu_symbol_visibility != ' ' :
permitted = [ ' default ' , ' internal ' , ' hidden ' , ' protected ' , ' inlineshidden ' ]
if self . gnu_symbol_visibility not in permitted :
raise InvalidArguments ( ' GNU symbol visibility arg {} not one of: {} ' . format ( self . symbol_visibility , ' , ' . join ( permitted ) ) )
def validate_win_subsystem ( self , value : str ) - > str :
value = value . lower ( )
if re . fullmatch ( r ' (boot_application|console|efi_application|efi_boot_service_driver|efi_rom|efi_runtime_driver|native|posix|windows)(, \ d+( \ . \ d+)?)? ' , value ) is None :
raise InvalidArguments ( f ' Invalid value for win_subsystem: { value } . ' )
return value
def _extract_pic_pie ( self , kwargs , arg : str , environment , option : str ) :
# Check if we have -fPIC, -fpic, -fPIE, or -fpie in cflags
all_flags = self . extra_args [ ' c ' ] + self . extra_args [ ' cpp ' ]
if ' -f ' + arg . lower ( ) in all_flags or ' -f ' + arg . upper ( ) in all_flags :
mlog . warning ( f " Use the ' { arg } ' kwarg instead of passing ' -f { arg } ' manually to { self . name !r} " )
return True
k = OptionKey ( option )
if arg in kwargs :
val = kwargs [ arg ]
elif k in environment . coredata . options :
val = environment . coredata . options [ k ] . value
else :
val = False
if not isinstance ( val , bool ) :
raise InvalidArguments ( f ' Argument { arg } to { self . name !r} must be boolean ' )
return val
def get_filename ( self ) - > str :
return self . filename
def get_outputs ( self ) - > T . List [ str ] :
return self . outputs
def get_extra_args ( self , language ) :
return self . extra_args . get ( language , [ ] )
def get_dependencies ( self , exclude = None ) :
transitive_deps = [ ]
if exclude is None :
exclude = [ ]
for t in itertools . chain ( self . link_targets , self . link_whole_targets ) :
if t in transitive_deps or t in exclude :
continue
transitive_deps . append ( t )
if isinstance ( t , StaticLibrary ) :
transitive_deps + = t . get_dependencies ( transitive_deps + exclude )
return transitive_deps
def get_source_subdir ( self ) :
return self . subdir
def get_sources ( self ) :
return self . sources
def get_objects ( self ) - > T . List [ T . Union [ str , ' File ' , ' ExtractedObjects ' ] ] :
return self . objects
def get_generated_sources ( self ) - > T . List [ ' GeneratedTypes ' ] :
return self . generated
def should_install ( self ) - > bool :
return self . need_install
def has_pch ( self ) - > bool :
return bool ( self . pch )
def get_pch ( self , language : str ) - > T . List [ str ] :
return self . pch . get ( language , [ ] )
def get_include_dirs ( self ) - > T . List [ ' IncludeDirs ' ] :
return self . include_dirs
def add_deps ( self , deps ) :
deps = listify ( deps )
for dep in deps :
if dep in self . added_deps :
continue
dep_d_features = dep . d_features
for feature in ( ' versions ' , ' import_dirs ' ) :
if feature in dep_d_features :
self . d_features [ feature ] . extend ( dep_d_features [ feature ] )
if isinstance ( dep , dependencies . InternalDependency ) :
# Those parts that are internal.
self . process_sourcelist ( dep . sources )
self . add_include_dirs ( dep . include_directories , dep . get_include_type ( ) )
for l in dep . libraries :
self . link ( l )
for l in dep . whole_libraries :
self . link_whole ( l )
if dep . get_compile_args ( ) or dep . get_link_args ( ) :
# Those parts that are external.
extpart = dependencies . InternalDependency ( ' undefined ' ,
[ ] ,
dep . get_compile_args ( ) ,
dep . get_link_args ( ) ,
[ ] , [ ] , [ ] , [ ] , { } , [ ] , [ ] )
self . external_deps . append ( extpart )
# Deps of deps.
self . add_deps ( dep . ext_deps )
elif isinstance ( dep , dependencies . Dependency ) :
if dep not in self . external_deps :
self . external_deps . append ( dep )
self . process_sourcelist ( dep . get_sources ( ) )
self . add_deps ( dep . ext_deps )
elif isinstance ( dep , BuildTarget ) :
raise InvalidArguments ( ''' Tried to use a build target as a dependency.
You probably should put it in link_with instead . ''' )
else :
# This is a bit of a hack. We do not want Build to know anything
# about the interpreter so we can't import it and use isinstance.
# This should be reliable enough.
if hasattr ( dep , ' project_args_frozen ' ) or hasattr ( dep , ' global_args_frozen ' ) :
raise InvalidArguments ( ' Tried to use subproject object as a dependency. \n '
' You probably wanted to use a dependency declared in it instead. \n '
' Access it by calling get_variable() on the subproject object. ' )
raise InvalidArguments ( f ' Argument is of an unacceptable type { type ( dep ) . __name__ !r} . \n Must be '
' either an external dependency (returned by find_library() or '
' dependency()) or an internal dependency (returned by '
' declare_dependency()). ' )
self . added_deps . add ( dep )
def get_external_deps ( self ) - > T . List [ dependencies . Dependency ] :
return self . external_deps
def is_internal ( self ) - > bool :
return isinstance ( self , StaticLibrary ) and not self . need_install
def link ( self , target ) :
for t in listify ( target ) :
if isinstance ( self , StaticLibrary ) and self . need_install :
if isinstance ( t , ( CustomTarget , CustomTargetIndex ) ) :
if not t . should_install ( ) :
mlog . warning ( f ' Try to link an installed static library target { self . name } with a '
' custom target that is not installed, this might cause problems '
' when you try to use this static library ' )
elif t . is_internal ( ) :
# When we're a static library and we link_with to an
# internal/convenience library, promote to link_whole.
return self . link_whole ( t )
if not isinstance ( t , ( Target , CustomTargetIndex ) ) :
raise InvalidArguments ( f ' { t !r} is not a target. ' )
if not t . is_linkable_target ( ) :
raise InvalidArguments ( f " Link target ' { t !s} ' is not linkable. " )
if isinstance ( self , SharedLibrary ) and isinstance ( t , StaticLibrary ) and not t . pic :
msg = f " Can ' t link non-PIC static library { t . name !r} into shared library { self . name !r} . "
msg + = " Use the ' pic ' option to static_library to build with PIC. "
raise InvalidArguments ( msg )
if self . for_machine is not t . for_machine :
msg = f ' Tried to mix libraries for machines { self . for_machine } and { t . for_machine } in target { self . name !r} '
if self . environment . is_cross_build ( ) :
raise InvalidArguments ( msg + ' This is not possible in a cross build. ' )
else :
mlog . warning ( msg + ' This will fail in cross build. ' )
self . link_targets . append ( t )
def link_whole ( self , target ) :
for t in listify ( target ) :
if isinstance ( t , ( CustomTarget , CustomTargetIndex ) ) :
if not t . is_linkable_target ( ) :
raise InvalidArguments ( f ' Custom target { t !r} is not linkable. ' )
if not t . get_filename ( ) . endswith ( ' .a ' ) :
raise InvalidArguments ( ' Can only link_whole custom targets that are .a archives. ' )
if isinstance ( self , StaticLibrary ) :
# FIXME: We could extract the .a archive to get object files
raise InvalidArguments ( ' Cannot link_whole a custom target into a static library ' )
elif not isinstance ( t , StaticLibrary ) :
raise InvalidArguments ( f ' { t !r} is not a static library. ' )
elif isinstance ( self , SharedLibrary ) and not t . pic :
msg = f " Can ' t link non-PIC static library { t . name !r} into shared library { self . name !r} . "
msg + = " Use the ' pic ' option to static_library to build with PIC. "
raise InvalidArguments ( msg )
if self . for_machine is not t . for_machine :
msg = f ' Tried to mix libraries for machines { self . for_machine } and { t . for_machine } in target { self . name !r} '
if self . environment . is_cross_build ( ) :
raise InvalidArguments ( msg + ' This is not possible in a cross build. ' )
else :
mlog . warning ( msg + ' This will fail in cross build. ' )
if isinstance ( self , StaticLibrary ) :
# When we're a static library and we link_whole: to another static
# library, we need to add that target's objects to ourselves.
self . objects + = t . extract_all_objects_recurse ( )
self . link_whole_targets . append ( t )
def extract_all_objects_recurse ( self ) - > T . List [ T . Union [ str , ' ExtractedObjects ' ] ] :
objs = [ self . extract_all_objects ( ) ]
for t in self . link_targets :
if t . is_internal ( ) :
objs + = t . extract_all_objects_recurse ( )
return objs
def add_pch ( self , language : str , pchlist : T . List [ str ] ) - > None :
if not pchlist :
return
elif len ( pchlist ) == 1 :
if not environment . is_header ( pchlist [ 0 ] ) :
raise InvalidArguments ( f ' PCH argument { pchlist [ 0 ] } is not a header. ' )
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 ( f ' PCH argument { pchlist [ 0 ] } is of unknown type. ' )
if os . path . dirname ( pchlist [ 0 ] ) != os . path . dirname ( pchlist [ 1 ] ) :
raise InvalidArguments ( ' PCH files must be stored in the same folder. ' )
FeatureDeprecated . single_use ( ' PCH source files ' , ' 0.50.0 ' , self . subproject ,
' Only a single header file should be used. ' )
elif len ( pchlist ) > 2 :
raise InvalidArguments ( ' PCH definition may have a maximum of 2 files. ' )
for f in pchlist :
if not isinstance ( f , str ) :
raise MesonException ( ' PCH arguments must be strings. ' )
if not os . path . isfile ( os . path . join ( self . environment . source_dir , self . subdir , f ) ) :
raise MesonException ( f ' File { f } does not exist. ' )
self . pch [ language ] = pchlist
def add_include_dirs ( self , args : T . Sequence [ ' IncludeDirs ' ] , set_is_system : T . Optional [ str ] = None ) - > None :
ids : T . List [ ' IncludeDirs ' ] = [ ]
for a in args :
if not isinstance ( a , IncludeDirs ) :
raise InvalidArguments ( ' Include directory to be added is not an include directory object. ' )
ids . append ( a )
if set_is_system is None :
set_is_system = ' preserve '
if set_is_system != ' preserve ' :
is_system = set_is_system == ' system '
ids = [ IncludeDirs ( x . get_curdir ( ) , x . get_incdirs ( ) , is_system , x . get_extra_build_dirs ( ) ) for x in ids ]
self . include_dirs + = ids
def add_compiler_args ( self , language : str , args : T . List [ ' FileOrString ' ] ) - > None :
args = listify ( 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_aliases ( self ) - > T . List [ T . Tuple [ str , str , str ] ] :
return [ ]
def get_langs_used_by_deps ( self ) - > T . List [ str ] :
'''
Sometimes you want to link to a C + + library that exports C API , which
means the linker must link in the C + + stdlib , and we must use a C + +
compiler for linking . The same is also applicable for objc / objc + + , etc ,
so we can keep using clink_langs for the priority order .
See : https : / / github . com / mesonbuild / meson / issues / 1653
'''
langs = [ ] # type: T.List[str]
# Check if any of the external libraries were written in this language
for dep in self . external_deps :
if dep . language is None :
continue
if dep . language not in langs :
langs . append ( dep . language )
# Check if any of the internal libraries this target links to were
# written in this language
for link_target in itertools . chain ( self . link_targets , self . link_whole_targets ) :
if isinstance ( link_target , ( CustomTarget , CustomTargetIndex ) ) :
continue
for language in link_target . compilers :
if language not in langs :
langs . append ( language )
return langs
def get_prelinker ( self ) :
all_compilers = self . environment . coredata . compilers [ self . for_machine ]
if self . link_language :
comp = all_compilers [ self . link_language ]
return comp
for l in clink_langs :
if l in self . compilers :
try :
prelinker = all_compilers [ l ]
except KeyError :
raise MesonException (
f ' Could not get a prelinker linker for build target { self . name !r} . '
f ' Requires a compiler for language " { l } " , but that is not '
' a project language. ' )
return prelinker
raise MesonException ( f ' Could not determine prelinker for { self . name !r} . ' )
def get_clink_dynamic_linker_and_stdlibs ( self ) - > T . Tuple [ ' Compiler ' , T . List [ str ] ] :
'''
We use the order of languages in ` clink_langs ` to determine which
linker to use in case the target has sources compiled with multiple
compilers . All languages other than those in this list have their own
linker .
Note that Vala outputs C code , so Vala sources can use any linker
that can link compiled C . We don ' t actually need to add an exception
for Vala here because of that .
'''
# Populate list of all compilers, not just those being used to compile
# sources in this target
all_compilers = self . environment . coredata . compilers [ self . for_machine ]
# If the user set the link_language, just return that.
if self . link_language :
comp = all_compilers [ self . link_language ]
return comp , comp . language_stdlib_only_link_flags ( self . environment )
# Languages used by dependencies
dep_langs = self . get_langs_used_by_deps ( )
# Pick a compiler based on the language priority-order
for l in clink_langs :
if l in self . compilers or l in dep_langs :
try :
linker = all_compilers [ l ]
except KeyError :
raise MesonException (
f ' Could not get a dynamic linker for build target { self . name !r} . '
f ' Requires a linker for language " { l } " , but that is not '
' a project language. ' )
stdlib_args : T . List [ str ] = [ ]
added_languages : T . Set [ str ] = set ( )
for dl in itertools . chain ( self . compilers , dep_langs ) :
if dl != linker . language :
stdlib_args + = all_compilers [ dl ] . language_stdlib_only_link_flags ( self . environment )
added_languages . add ( dl )
# Type of var 'linker' is Compiler.
# Pretty hard to fix because the return value is passed everywhere
return linker , stdlib_args
raise AssertionError ( f ' Could not get a dynamic linker for build target { self . name !r} ' )
def uses_rust ( self ) - > bool :
""" Is this target a rust target. """
if self . sources :
first_file = self . sources [ 0 ]
if first_file . fname . endswith ( ' .rs ' ) :
return True
elif self . generated :
if self . generated [ 0 ] . get_outputs ( ) [ 0 ] . endswith ( ' .rs ' ) :
return True
return False
def get_using_msvc ( self ) - > bool :
'''
Check if the dynamic linker is MSVC . Used by Executable , StaticLibrary ,
and SharedLibrary for deciding when to use MSVC - specific file naming
and debug filenames .
If at least some code is built with MSVC and the final library is
linked with MSVC , we can be sure that some debug info will be
generated . We only check the dynamic linker here because the static
linker is guaranteed to be of the same type .
Interesting cases :
1. The Vala compiler outputs C code to be compiled by whatever
C compiler we ' re using, so all objects will still be created by the
MSVC compiler .
2. If the target contains only objects , process_compilers guesses and
picks the first compiler that smells right .
'''
# Rustc can use msvc style linkers
if self . uses_rust ( ) :
compiler = self . environment . coredata . compilers [ self . for_machine ] [ ' rust ' ]
else :
compiler , _ = self . get_clink_dynamic_linker_and_stdlibs ( )
# Mixing many languages with MSVC is not supported yet so ignore stdlibs.
return compiler and compiler . get_linker_id ( ) in { ' link ' , ' lld-link ' , ' xilink ' , ' optlink ' }
def check_module_linking ( self ) :
'''
Warn if shared modules are linked with target : ( link_with ) #2865
'''
for link_target in self . link_targets :
if isinstance ( link_target , SharedModule ) and not link_target . force_soname :
if self . environment . machines [ self . for_machine ] . is_darwin ( ) :
raise MesonException (
f ' target { self . name } links against shared module { link_target . name } . This is not permitted on OSX ' )
elif self . environment . machines [ self . for_machine ] . is_android ( ) and isinstance ( self , SharedModule ) :
# Android requires shared modules that use symbols from other shared modules to
# be linked before they can be dlopen()ed in the correct order. Not doing so
# leads to a missing symbol error: https://github.com/android/ndk/issues/201
link_target . force_soname = True
else :
mlog . deprecation ( f ' target { self . name } links against shared module { link_target . name } , which is incorrect. '
' \n '
f ' This will be an error in the future, so please use shared_library() for { link_target . name } instead. '
' \n '
f ' If shared_module() was used for { link_target . name } because it has references to undefined symbols, '
' \n '
' use shared_libary() with `override_options: [ \' b_lundef=false \' ]` instead. ' )
link_target . force_soname = True
class Generator ( HoldableObject ) :
def __init__ ( self , exe : T . Union [ ' Executable ' , programs . ExternalProgram ] ,
arguments : T . List [ str ] ,
output : T . List [ str ] ,
# how2dataclass
* ,
depfile : T . Optional [ str ] = None ,
capture : bool = False ,
depends : T . Optional [ T . List [ T . Union [ BuildTarget , ' CustomTarget ' ] ] ] = None ,
name : str = ' Generator ' ) :
self . exe = exe
self . depfile = depfile
self . capture = capture
self . depends : T . List [ T . Union [ BuildTarget , ' CustomTarget ' ] ] = depends or [ ]
self . arglist = arguments
self . outputs = output
self . name = name
def __repr__ ( self ) - > str :
repr_str = " < {0} : {1} > "
return repr_str . format ( self . __class__ . __name__ , self . exe )
def get_exe ( self ) - > T . Union [ ' Executable ' , programs . ExternalProgram ] :
return self . exe
def get_base_outnames ( self , inname : str ) - > T . List [ str ] :
plainname = os . path . basename ( inname )
basename = os . path . splitext ( plainname ) [ 0 ]
bases = [ x . replace ( ' @BASENAME@ ' , basename ) . replace ( ' @PLAINNAME@ ' , plainname ) for x in self . outputs ]
return bases
def get_dep_outname ( self , inname : str ) - > T . List [ str ] :
if self . depfile is None :
raise InvalidArguments ( ' Tried to get dep name for rule that does not have dependency file defined. ' )
plainname = os . path . basename ( inname )
basename = os . path . splitext ( plainname ) [ 0 ]
return self . depfile . replace ( ' @BASENAME@ ' , basename ) . replace ( ' @PLAINNAME@ ' , plainname )
def get_arglist ( self , inname : str ) - > T . List [ str ] :
plainname = os . path . basename ( inname )
basename = os . path . splitext ( plainname ) [ 0 ]
return [ x . replace ( ' @BASENAME@ ' , basename ) . replace ( ' @PLAINNAME@ ' , plainname ) for x in self . arglist ]
@staticmethod
def is_parent_path ( parent : str , trial : str ) - > bool :
relpath = pathlib . PurePath ( trial ) . relative_to ( parent )
return relpath . parts [ 0 ] != ' .. ' # For subdirs we can only go "down".
def process_files ( self , files : T . Iterable [ T . Union [ str , File , ' CustomTarget ' , ' CustomTargetIndex ' , ' GeneratedList ' ] ] ,
state : T . Union [ ' Interpreter ' , ' ModuleState ' ] ,
preserve_path_from : T . Optional [ str ] = None ,
extra_args : T . Optional [ T . List [ str ] ] = None ) - > ' GeneratedList ' :
output = GeneratedList ( self , state . subdir , preserve_path_from , extra_args = extra_args if extra_args is not None else [ ] )
for e in files :
if isinstance ( e , CustomTarget ) :
output . depends . add ( e )
if isinstance ( e , CustomTargetIndex ) :
output . depends . add ( e . target )
if isinstance ( e , ( CustomTarget , CustomTargetIndex , GeneratedList ) ) :
self . depends . append ( e ) # BUG: this should go in the GeneratedList object, not this object.
fs = [ File . from_built_file ( state . subdir , f ) for f in e . get_outputs ( ) ]
elif isinstance ( e , str ) :
fs = [ File . from_source_file ( state . environment . source_dir , state . subdir , e ) ]
else :
fs = [ e ]
for f in fs :
if preserve_path_from :
abs_f = f . absolute_path ( state . environment . source_dir , state . environment . build_dir )
if not self . is_parent_path ( preserve_path_from , abs_f ) :
raise InvalidArguments ( ' generator.process: When using preserve_path_from, all input files must be in a subdirectory of the given dir. ' )
output . add_file ( f , state )
return output
@dataclass ( eq = False )
class GeneratedList ( HoldableObject ) :
""" The output of generator.process. """
generator : Generator
subdir : str
preserve_path_from : T . Optional [ str ]
extra_args : T . List [ str ]
def __post_init__ ( self ) - > None :
self . name = self . generator . exe
self . depends : T . Set [ ' CustomTarget ' ] = set ( ) # Things this target depends on (because e.g. a custom target was used as input)
self . infilelist : T . List [ ' File ' ] = [ ]
self . outfilelist : T . List [ str ] = [ ]
self . outmap : T . Dict [ File , T . List [ str ] ] = { }
self . extra_depends = [ ] # XXX: Doesn't seem to be used?
self . depend_files : T . List [ File ] = [ ]
if self . extra_args is None :
self . extra_args : T . List [ str ] = [ ]
if isinstance ( self . generator . exe , programs . ExternalProgram ) :
if not self . generator . exe . found ( ) :
raise InvalidArguments ( ' Tried to use not-found external program as generator ' )
path = self . generator . exe . get_path ( )
if os . path . isabs ( path ) :
# Can only add a dependency on an external program which we
# know the absolute path of
self . depend_files . append ( File . from_absolute_file ( path ) )
def add_preserved_path_segment ( self , infile : File , outfiles : T . List [ str ] , state : T . Union [ ' Interpreter ' , ' ModuleState ' ] ) - > T . List [ str ] :
result : T . List [ str ] = [ ]
in_abs = infile . absolute_path ( state . environment . source_dir , state . environment . build_dir )
assert os . path . isabs ( self . preserve_path_from )
rel = os . path . relpath ( in_abs , self . preserve_path_from )
path_segment = os . path . dirname ( rel )
for of in outfiles :
result . append ( os . path . join ( path_segment , of ) )
return result
def add_file ( self , newfile : File , state : T . Union [ ' Interpreter ' , ' ModuleState ' ] ) - > None :
self . infilelist . append ( newfile )
outfiles = self . generator . get_base_outnames ( newfile . fname )
if self . preserve_path_from :
outfiles = self . add_preserved_path_segment ( newfile , outfiles , state )
self . outfilelist + = outfiles
self . outmap [ newfile ] = outfiles
def get_inputs ( self ) - > T . List [ ' File ' ] :
return self . infilelist
def get_outputs ( self ) - > T . List [ str ] :
return self . outfilelist
def get_outputs_for ( self , filename : ' File ' ) - > T . List [ str ] :
return self . outmap [ filename ]
def get_generator ( self ) - > ' Generator ' :
return self . generator
def get_extra_args ( self ) - > T . List [ str ] :
return self . extra_args
def get_subdir ( self ) - > str :
return self . subdir
class Executable ( BuildTarget ) :
known_kwargs = known_exe_kwargs
def __init__ ( self , name : str , subdir : str , subproject : str , for_machine : MachineChoice ,
sources : T . List [ File ] , objects , environment : environment . Environment , kwargs ) :
self . typename = ' executable '
key = OptionKey ( ' b_pie ' )
if ' pie ' not in kwargs and key in environment . coredata . options :
kwargs [ ' pie ' ] = environment . coredata . options [ key ] . value
super ( ) . __init__ ( name , subdir , subproject , for_machine , sources , objects , environment , kwargs )
# Unless overridden, 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 ' ) :
machine = environment . machines [ for_machine ]
# Executable for Windows or C#/Mono
if machine . is_windows ( ) or machine . is_cygwin ( ) or ' cs ' in self . compilers :
self . suffix = ' exe '
elif machine . system . startswith ( ' wasm ' ) or machine . system == ' emscripten ' :
self . suffix = ' js '
elif ( ' c ' in self . compilers and self . compilers [ ' c ' ] . get_id ( ) . startswith ( ' armclang ' ) or
' cpp ' in self . compilers and self . compilers [ ' cpp ' ] . get_id ( ) . startswith ( ' armclang ' ) ) :
self . suffix = ' axf '
elif ( ' c ' in self . compilers and self . compilers [ ' c ' ] . get_id ( ) . startswith ( ' ccrx ' ) or
' cpp ' in self . compilers and self . compilers [ ' cpp ' ] . get_id ( ) . startswith ( ' ccrx ' ) ) :
self . suffix = ' abs '
elif ( ' c ' in self . compilers and self . compilers [ ' c ' ] . get_id ( ) . startswith ( ' xc16 ' ) ) :
self . suffix = ' elf '
elif ( ' c ' in self . compilers and self . compilers [ ' c ' ] . get_id ( ) in ( ' ti ' , ' c2000 ' ) or
' cpp ' in self . compilers and self . compilers [ ' cpp ' ] . get_id ( ) in ( ' ti ' , ' c2000 ' ) ) :
self . suffix = ' out '
else :
self . suffix = environment . machines [ for_machine ] . get_exe_suffix ( )
self . filename = self . name
if self . suffix :
self . filename + = ' . ' + self . suffix
self . outputs = [ self . filename ]
# 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
# The debugging information file this target will generate
self . debug_filename = None
# Check for export_dynamic
self . export_dynamic = False
if kwargs . get ( ' export_dynamic ' ) :
if not isinstance ( kwargs [ ' export_dynamic ' ] , bool ) :
raise InvalidArguments ( ' " export_dynamic " keyword argument must be a boolean ' )
self . export_dynamic = True
if kwargs . get ( ' implib ' ) :
self . export_dynamic = True
if self . export_dynamic and kwargs . get ( ' implib ' ) is False :
raise InvalidArguments ( ' " implib " keyword argument must not be false for if " export_dynamic " is true ' )
m = environment . machines [ for_machine ]
# If using export_dynamic, set the import library name
if self . export_dynamic :
implib_basename = self . name + ' .exe '
if not isinstance ( kwargs . get ( ' implib ' , False ) , bool ) :
implib_basename = kwargs [ ' implib ' ]
if m . is_windows ( ) or m . is_cygwin ( ) :
self . vs_import_filename = f ' { implib_basename } .lib '
self . gcc_import_filename = f ' lib { implib_basename } .a '
if self . get_using_msvc ( ) :
self . import_filename = self . vs_import_filename
else :
self . import_filename = self . gcc_import_filename
if m . is_windows ( ) and ( ' cs ' in self . compilers or
self . uses_rust ( ) or
self . get_using_msvc ( ) ) :
self . debug_filename = self . name + ' .pdb '
# Only linkwithable if using export_dynamic
self . is_linkwithable = self . export_dynamic
# Remember that this exe was returned by `find_program()` through an override
self . was_returned_by_find_program = False
def get_default_install_dir ( self , environment : environment . Environment ) - > T . Tuple [ str , str ] :
return environment . get_bindir ( ) , ' {bindir} '
def description ( self ) :
''' Human friendly description of the executable '''
return self . name
def type_suffix ( self ) :
return " @exe "
def get_import_filename ( self ) - > T . Optional [ str ] :
"""
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_debug_filename ( self ) - > T . Optional [ str ] :
"""
The name of debuginfo file that will be created by the compiler
Returns None if the build won ' t create any debuginfo file
"""
return self . debug_filename
def is_linkable_target ( self ) :
return self . is_linkwithable
def get_command ( self ) - > ' ImmutableListProtocol[str] ' :
""" Provides compatibility with ExternalProgram.
Since you can override ExternalProgram instances with Executables .
"""
return self . outputs
class StaticLibrary ( BuildTarget ) :
known_kwargs = known_stlib_kwargs
def __init__ ( self , name , subdir , subproject , for_machine : MachineChoice , sources , objects , environment , kwargs ) :
self . typename = ' static library '
super ( ) . __init__ ( name , subdir , subproject , for_machine , sources , objects , environment , kwargs )
if ' cs ' in self . compilers :
raise InvalidArguments ( ' Static libraries not supported for C#. ' )
if ' rust ' in self . compilers :
# If no crate type is specified, or it's the generic lib type, use rlib
if not hasattr ( self , ' rust_crate_type ' ) or self . rust_crate_type == ' lib ' :
mlog . debug ( ' Defaulting Rust static library target crate type to rlib ' )
self . rust_crate_type = ' rlib '
# Don't let configuration proceed with a non-static crate type
elif self . rust_crate_type not in [ ' rlib ' , ' staticlib ' ] :
raise InvalidArguments ( f ' Crate type " { self . rust_crate_type } " invalid for static libraries; must be " rlib " or " staticlib " ' )
# 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 ' ) :
if ' rust ' in self . compilers :
if not hasattr ( self , ' rust_crate_type ' ) or self . rust_crate_type == ' rlib ' :
# default Rust static library suffix
self . suffix = ' rlib '
elif self . rust_crate_type == ' staticlib ' :
self . suffix = ' a '
else :
self . suffix = ' a '
self . filename = self . prefix + self . name + ' . ' + self . suffix
self . outputs = [ self . filename ]
self . prelink = kwargs . get ( ' prelink ' , False )
if not isinstance ( self . prelink , bool ) :
raise InvalidArguments ( ' Prelink keyword argument must be a boolean. ' )
def get_link_deps_mapping ( self , prefix : str , environment : environment . Environment ) - > T . Mapping [ str , str ] :
return { }
def get_default_install_dir ( self , environment ) - > T . Tuple [ str , str ] :
return environment . get_static_lib_dir ( ) , ' {libdir_static} '
def type_suffix ( self ) :
return " @sta "
def process_kwargs ( self , kwargs , environment ) :
super ( ) . process_kwargs ( kwargs , environment )
if ' rust_crate_type ' in kwargs :
rust_crate_type = kwargs [ ' rust_crate_type ' ]
if isinstance ( rust_crate_type , str ) :
self . rust_crate_type = rust_crate_type
else :
raise InvalidArguments ( f ' Invalid rust_crate_type " { rust_crate_type } " : must be a string. ' )
def is_linkable_target ( self ) :
return True
class SharedLibrary ( BuildTarget ) :
known_kwargs = known_shlib_kwargs
def __init__ ( self , name , subdir , subproject , for_machine : MachineChoice , sources , objects , environment , kwargs ) :
self . typename = ' shared library '
self . soversion = None
self . ltversion = None
# Max length 2, first element is compatibility_version, second is current_version
self . darwin_versions = [ ]
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
# The debugging information file this target will generate
self . debug_filename = None
# Use by the pkgconfig module
self . shared_library_only = False
super ( ) . __init__ ( name , subdir , subproject , for_machine , sources , objects , environment , kwargs )
if ' rust ' in self . compilers :
# If no crate type is specified, or it's the generic lib type, use dylib
if not hasattr ( self , ' rust_crate_type ' ) or self . rust_crate_type == ' lib ' :
mlog . debug ( ' Defaulting Rust dynamic library target crate type to " dylib " ' )
self . rust_crate_type = ' dylib '
# Don't let configuration proceed with a non-dynamic crate type
elif self . rust_crate_type not in [ ' dylib ' , ' cdylib ' ] :
raise InvalidArguments ( f ' Crate type " { self . rust_crate_type } " invalid for dynamic libraries; must be " dylib " or " cdylib " ' )
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 ( environment )
def get_link_deps_mapping ( self , prefix : str , environment : environment . Environment ) - > T . Mapping [ str , str ] :
result : T . Dict [ str , str ] = { }
mappings = self . get_transitive_link_deps_mapping ( prefix , environment )
old = get_target_macos_dylib_install_name ( self )
if old not in mappings :
fname = self . get_filename ( )
outdirs , _ , _ = self . get_install_dir ( self . environment )
new = os . path . join ( prefix , outdirs [ 0 ] , fname )
result . update ( { old : new } )
mappings . update ( result )
return mappings
def get_default_install_dir ( self , environment ) - > T . Tuple [ str , str ] :
return environment . get_shared_lib_dir ( ) , ' {libdir_shared} '
def determine_filenames ( self , 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_aliases ) ,
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 = ' '
create_debug_file = False
self . filename_tpl = self . basic_filename_tpl
# NOTE: manual prefix/suffix override is currently only tested for C/C++
# C# and Mono
if ' cs ' in self . compilers :
prefix = ' '
suffix = ' dll '
self . filename_tpl = ' {0.prefix} {0.name} . {0.suffix} '
create_debug_file = True
# C, C++, Swift, Vala
# Only Windows uses a separate import library for linking
# For all other targets/platforms import_filename stays None
elif env . machines [ self . for_machine ] . is_windows ( ) :
suffix = ' dll '
self . vs_import_filename = ' {} {} .lib ' . format ( self . prefix if self . prefix is not None else ' ' , self . name )
self . gcc_import_filename = ' {} {} .dll.a ' . format ( self . prefix if self . prefix is not None else ' lib ' , self . name )
if self . uses_rust ( ) :
# Shared library is of the form foo.dll
prefix = ' '
# Import library is called foo.dll.lib
self . import_filename = f ' { self . name } .dll.lib '
create_debug_file = True
elif self . get_using_msvc ( ) :
# Shared library is of the form foo.dll
prefix = ' '
# Import library is called foo.lib
self . import_filename = self . vs_import_filename
create_debug_file = True
# 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 env . machines [ self . for_machine ] . is_cygwin ( ) :
suffix = ' dll '
self . gcc_import_filename = ' {} {} .dll.a ' . format ( self . prefix if self . prefix is not None else ' lib ' , self . name )
# Shared library is of the form cygfoo.dll
# (ld --dll-search-prefix=cyg is the default)
prefix = ' cyg '
# Import library is called libfoo.dll.a
self . import_filename = self . gcc_import_filename
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 env . machines [ self . for_machine ] . is_darwin ( ) :
prefix = ' lib '
suffix = ' dylib '
# On macOS, the filename can only contain the major version
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} '
elif env . machines [ self . for_machine ] . is_android ( ) :
prefix = ' lib '
suffix = ' so '
# Android doesn't support shared_library versioning
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 is None :
self . prefix = prefix
if self . suffix is None :
self . suffix = suffix
self . filename = self . filename_tpl . format ( self )
self . outputs = [ self . filename ]
if create_debug_file :
self . debug_filename = os . path . splitext ( self . filename ) [ 0 ] + ' .pdb '
@staticmethod
def _validate_darwin_versions ( darwin_versions ) :
try :
if isinstance ( darwin_versions , int ) :
darwin_versions = str ( darwin_versions )
if isinstance ( darwin_versions , str ) :
darwin_versions = 2 * [ darwin_versions ]
if not isinstance ( darwin_versions , list ) :
raise InvalidArguments ( ' Shared library darwin_versions: must be a string, integer, '
f ' or a list, not { darwin_versions !r} ' )
if len ( darwin_versions ) > 2 :
raise InvalidArguments ( ' Shared library darwin_versions: list must contain 2 or fewer elements ' )
if len ( darwin_versions ) == 1 :
darwin_versions = 2 * darwin_versions
for i , v in enumerate ( darwin_versions [ : ] ) :
if isinstance ( v , int ) :
v = str ( v )
if not isinstance ( v , str ) :
raise InvalidArguments ( ' Shared library darwin_versions: list elements '
f ' must be strings or integers, not { v !r} ' )
if not re . fullmatch ( r ' [0-9]+( \ .[0-9]+) { 0,2} ' , v ) :
raise InvalidArguments ( ' Shared library darwin_versions: must be X.Y.Z where '
' X, Y, Z are numbers, and Y and Z are optional ' )
parts = v . split ( ' . ' )
if len ( parts ) in ( 1 , 2 , 3 ) and int ( parts [ 0 ] ) > 65535 :
raise InvalidArguments ( ' Shared library darwin_versions: must be X.Y.Z '
' where X is [0, 65535] and Y, Z are optional ' )
if len ( parts ) in ( 2 , 3 ) and int ( parts [ 1 ] ) > 255 :
raise InvalidArguments ( ' Shared library darwin_versions: must be X.Y.Z '
' where Y is [0, 255] and Y, Z are optional ' )
if len ( parts ) == 3 and int ( parts [ 2 ] ) > 255 :
raise InvalidArguments ( ' Shared library darwin_versions: must be X.Y.Z '
' where Z is [0, 255] and Y, Z are optional ' )
darwin_versions [ i ] = v
except ValueError :
raise InvalidArguments ( ' Shared library darwin_versions: value is invalid ' )
return darwin_versions
def process_kwargs ( self , kwargs , environment ) :
super ( ) . process_kwargs ( kwargs , environment )
if not self . environment . machines [ self . for_machine ] . is_android ( ) :
supports_versioning = True
else :
supports_versioning = False
if supports_versioning :
# 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 ( f ' Invalid Shared library version " { self . ltversion } " . Must be of the form X.Y.Z where all three are numbers. Y and Z are optional. ' )
# 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. ' )
elif self . ltversion :
# library version is defined, get the soversion from that
# We replicate what Autotools does here and take the first
# number of the version by default.
self . soversion = self . ltversion . split ( ' . ' ) [ 0 ]
# macOS, iOS and tvOS dylib compatibility_version and current_version
if ' darwin_versions ' in kwargs :
self . darwin_versions = self . _validate_darwin_versions ( kwargs [ ' darwin_versions ' ] )
elif self . soversion :
# If unspecified, pick the soversion
self . darwin_versions = 2 * [ self . soversion ]
# Visual Studio module-definitions file
if ' vs_module_defs ' in kwargs :
path = kwargs [ ' vs_module_defs ' ]
if isinstance ( path , str ) :
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 )
elif isinstance ( path , File ) :
# When passing a generated file.
self . vs_module_defs = path
elif hasattr ( path , ' get_filename ' ) :
# When passing output of a Custom Target
self . vs_module_defs = File . from_built_file ( path . subdir , path . get_filename ( ) )
else :
raise InvalidArguments (
' Shared library vs_module_defs must be either a string, '
' a file object or a Custom Target ' )
self . process_link_depends ( path , environment )
if ' rust_crate_type ' in kwargs :
rust_crate_type = kwargs [ ' rust_crate_type ' ]
if isinstance ( rust_crate_type , str ) :
self . rust_crate_type = rust_crate_type
else :
raise InvalidArguments ( f ' Invalid rust_crate_type " { rust_crate_type } " : must be a string. ' )
def get_import_filename ( self ) - > T . Optional [ str ] :
"""
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_debug_filename ( self ) - > T . Optional [ str ] :
"""
The name of debuginfo file that will be created by the compiler
Returns None if the build won ' t create any debuginfo file
"""
return self . debug_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_aliases ( self ) - > T . List [ T . Tuple [ str , str , str ] ] :
"""
If the versioned library name is libfoo . so .0 .100 .0 , aliases are :
* libfoo . so .0 ( soversion ) - > libfoo . so .0 .100 .0
* libfoo . so ( unversioned ; for linking ) - > libfoo . so .0
Same for dylib :
* libfoo . dylib ( unversioned ; for linking ) - > libfoo .0 . dylib
"""
aliases : T . List [ T . Tuple [ str , str , str ] ] = [ ]
# Aliases are only useful with .so and .dylib libraries. Also if
# there's no self.soversion (no versioning), we don't need aliases.
if self . suffix not in ( ' so ' , ' dylib ' ) or not self . soversion :
return aliases
# With .so libraries, the minor and micro versions are also in the
# filename. If ltversion != soversion we create an soversion alias:
# libfoo.so.0 -> libfoo.so.0.100.0
# Where libfoo.so.0.100.0 is the actual library
if self . suffix == ' so ' and self . ltversion and self . ltversion != self . soversion :
alias_tpl = self . filename_tpl . replace ( ' ltversion ' , ' soversion ' )
ltversion_filename = alias_tpl . format ( self )
tag = self . install_tag [ 0 ] or ' runtime '
aliases . append ( ( ltversion_filename , self . filename , tag ) )
# libfoo.so.0/libfoo.0.dylib is the actual library
else :
ltversion_filename = self . filename
# Unversioned alias:
# libfoo.so -> libfoo.so.0
# libfoo.dylib -> libfoo.0.dylib
tag = self . install_tag [ 0 ] or ' devel '
aliases . append ( ( self . basic_filename_tpl . format ( self ) , ltversion_filename , tag ) )
return aliases
def type_suffix ( self ) :
return " @sha "
def is_linkable_target ( self ) :
return True
# A shared library that is meant to be used with dlopen rather than linking
# into something else.
class SharedModule ( SharedLibrary ) :
known_kwargs = known_shmod_kwargs
def __init__ ( self , name , subdir , subproject , for_machine : MachineChoice , sources , objects , environment , kwargs ) :
if ' version ' in kwargs :
raise MesonException ( ' Shared modules must not specify the version kwarg. ' )
if ' soversion ' in kwargs :
raise MesonException ( ' Shared modules must not specify the soversion kwarg. ' )
super ( ) . __init__ ( name , subdir , subproject , for_machine , sources , objects , environment , kwargs )
self . typename = ' shared module '
# We need to set the soname in cases where build files link the module
# to build targets, see: https://github.com/mesonbuild/meson/issues/9492
self . force_soname = False
def get_default_install_dir ( self , environment ) - > T . Tuple [ str , str ] :
return environment . get_shared_module_dir ( ) , ' {moduledir_shared} '
class BothLibraries ( SecondLevelHolder ) :
def __init__ ( self , shared : SharedLibrary , static : StaticLibrary ) - > None :
self . _preferred_library = ' shared '
self . shared = shared
self . static = static
self . subproject = self . shared . subproject
def __repr__ ( self ) - > str :
return f ' <BothLibraries: static= { repr ( self . static ) } ; shared= { repr ( self . shared ) } > '
def get_default_object ( self ) - > BuildTarget :
if self . _preferred_library == ' shared ' :
return self . shared
elif self . _preferred_library == ' static ' :
return self . static
raise MesonBugException ( f ' self._preferred_library == " { self . _preferred_library } " is neither " shared " nor " static " . ' )
class CommandBase :
depend_files : T . List [ File ]
dependencies : T . List [ T . Union [ BuildTarget , ' CustomTarget ' ] ]
subproject : str
def flatten_command ( self , cmd : T . Sequence [ T . Union [ str , File , programs . ExternalProgram , ' BuildTarget ' , ' CustomTarget ' , ' CustomTargetIndex ' ] ] ) - > \
T . List [ T . Union [ str , File , BuildTarget , ' CustomTarget ' ] ] :
cmd = listify ( cmd )
final_cmd : T . List [ T . Union [ str , File , BuildTarget , ' CustomTarget ' ] ] = [ ]
for c in cmd :
if isinstance ( c , str ) :
final_cmd . append ( c )
elif isinstance ( c , File ) :
self . depend_files . append ( c )
final_cmd . append ( c )
elif isinstance ( c , programs . ExternalProgram ) :
if not c . found ( ) :
raise InvalidArguments ( ' Tried to use not-found external program in " command " ' )
path = c . get_path ( )
if os . path . isabs ( path ) :
# Can only add a dependency on an external program which we
# know the absolute path of
self . depend_files . append ( File . from_absolute_file ( path ) )
final_cmd + = c . get_command ( )
elif isinstance ( c , ( BuildTarget , CustomTarget ) ) :
self . dependencies . append ( c )
final_cmd . append ( c )
elif isinstance ( c , CustomTargetIndex ) :
FeatureNew . single_use ( ' CustomTargetIndex for command argument ' , ' 0.60 ' , self . subproject )
self . dependencies . append ( c . target )
final_cmd + = self . flatten_command ( File . from_built_file ( c . get_subdir ( ) , c . get_filename ( ) ) )
elif isinstance ( c , list ) :
final_cmd + = self . flatten_command ( c )
else :
raise InvalidArguments ( f ' Argument { c !r} in " command " is invalid ' )
return final_cmd
class CustomTarget ( Target , CommandBase ) :
typename = ' custom '
def __init__ ( self ,
name : T . Optional [ str ] ,
subdir : str ,
subproject : str ,
command : T . Sequence [ T . Union [
str , BuildTarget , CustomTarget , CustomTargetIndex , GeneratedList , programs . ExternalProgram , File ] ] ,
sources : T . Sequence [ T . Union [
str , File , BuildTarget , CustomTarget , CustomTargetIndex ,
ExtractedObjects , GeneratedList , programs . ExternalProgram ] ] ,
outputs : T . List [ str ] ,
* ,
build_always_stale : bool = False ,
build_by_default : T . Optional [ bool ] = None ,
capture : bool = False ,
console : bool = False ,
depend_files : T . Optional [ T . Sequence [ FileOrString ] ] = None ,
extra_depends : T . Optional [ T . Sequence [ T . Union [ str , SourceOutputs ] ] ] = None ,
depfile : T . Optional [ str ] = None ,
env : T . Optional [ EnvironmentVariables ] = None ,
feed : bool = False ,
install : bool = False ,
install_dir : T . Optional [ T . Sequence [ T . Union [ str , bool ] ] ] = None ,
install_mode : T . Optional [ FileMode ] = None ,
install_tag : T . Optional [ T . Sequence [ T . Optional [ str ] ] ] = None ,
override_options : T . Optional [ T . Dict [ OptionKey , str ] ] = None ,
absolute_paths : bool = False ,
backend : T . Optional [ ' Backend ' ] = None ,
) :
# TODO expose keyword arg to make MachineChoice.HOST configurable
super ( ) . __init__ ( name , subdir , subproject , False , MachineChoice . HOST )
self . sources = list ( sources )
self . outputs = substitute_values (
outputs , get_filenames_templates_dict (
get_sources_string_names ( sources , backend ) ,
[ ] ) )
self . build_by_default = build_by_default if build_by_default is not None else install
self . build_always_stale = build_always_stale
self . capture = capture
self . console = console
self . depend_files = list ( depend_files or [ ] )
self . dependencies : T . List [ T . Union [ CustomTarget , BuildTarget ] ] = [ ]
# must be after depend_files and dependencies
self . command = self . flatten_command ( command )
self . depfile = depfile
self . env = env or EnvironmentVariables ( )
self . extra_depends = list ( extra_depends or [ ] )
self . feed = feed
self . install = install
self . install_dir = list ( install_dir or [ ] )
self . install_mode = install_mode
_install_tag : T . List [ T . Optional [ str ] ]
if not install_tag :
_install_tag = [ None ] * len ( self . outputs )
elif len ( install_tag ) == 1 :
_install_tag = list ( install_tag ) * len ( self . outputs )
else :
_install_tag = list ( install_tag )
self . install_tag = _install_tag
self . name = name if name else self . outputs [ 0 ]
if override_options :
for k , v in override_options . items ( ) :
if k . lang :
self . option_overrides_compiler [ k . evolve ( machine = self . for_machine ) ] = v
else :
self . option_overrides_base [ k ] = v
# Whether to use absolute paths for all files on the commandline
self . absolute_paths = absolute_paths
def get_default_install_dir ( self , environment ) - > T . Tuple [ str , str ] :
return None , None
def __repr__ ( self ) :
repr_str = " < {0} {1} : {2} > "
return repr_str . format ( self . __class__ . __name__ , self . get_id ( ) , self . command )
def get_target_dependencies ( self ) - > T . List [ T . Union [ ' BuildTarget ' , ' CustomTarget ' ] ] :
deps = self . dependencies [ : ]
deps + = self . extra_depends
for c in self . sources :
if isinstance ( c , ( BuildTarget , CustomTarget ) ) :
deps . append ( c )
if isinstance ( c , CustomTargetIndex ) :
deps . append ( c . target )
return deps
def get_transitive_build_target_deps ( self ) - > T . Set [ T . Union [ BuildTarget , ' CustomTarget ' ] ] :
'''
Recursively fetch the build targets that this custom target depends on ,
whether through ` command : ` , ` depends : ` , or ` sources : ` The recursion is
only performed on custom targets .
This is useful for setting PATH on Windows for finding required DLLs .
F . ex , if you have a python script that loads a C module that links to
other DLLs in your project .
'''
bdeps : T . Set [ T . Union [ BuildTarget , ' CustomTarget ' ] ] = set ( )
deps = self . get_target_dependencies ( )
for d in deps :
if isinstance ( d , BuildTarget ) :
bdeps . add ( d )
elif isinstance ( d , CustomTarget ) :
bdeps . update ( d . get_transitive_build_target_deps ( ) )
return bdeps
def get_dependencies ( self ) :
return self . dependencies
def should_install ( self ) - > bool :
return self . install
def get_custom_install_dir ( self ) - > T . List [ T . Union [ str , bool ] ] :
return self . install_dir
def get_custom_install_mode ( self ) - > T . Optional [ ' FileMode ' ] :
return self . install_mode
def get_outputs ( self ) - > T . List [ str ] :
return self . outputs
def get_filename ( self ) - > str :
return self . outputs [ 0 ]
def get_sources ( self ) - > T . List [ T . Union [ str , File , ' CustomTarget ' , ' CustomTargetIndex ' , ' GeneratedList ' , ' ExtractedObjects ' ] ] :
return self . sources
def get_generated_lists ( self ) - > T . List [ GeneratedList ] :
genlists : T . List [ GeneratedList ] = [ ]
for c in self . sources :
if isinstance ( c , GeneratedList ) :
genlists . append ( c )
return genlists
def get_generated_sources ( self ) - > T . List [ GeneratedList ] :
return self . get_generated_lists ( )
def get_dep_outname ( self , infilenames ) :
if self . depfile is None :
raise InvalidArguments ( ' Tried to get depfile name for custom_target that does not have depfile defined. ' )
if infilenames :
plainname = os . path . basename ( infilenames [ 0 ] )
basename = os . path . splitext ( plainname ) [ 0 ]
return self . depfile . replace ( ' @BASENAME@ ' , basename ) . replace ( ' @PLAINNAME@ ' , plainname )
else :
if ' @BASENAME@ ' in self . depfile or ' @PLAINNAME@ ' in self . depfile :
raise InvalidArguments ( ' Substitution in depfile for custom_target that does not have an input file. ' )
return self . depfile
def is_linkable_target ( self ) - > bool :
if len ( self . outputs ) != 1 :
return False
suf = os . path . splitext ( self . outputs [ 0 ] ) [ - 1 ]
return suf in { ' .a ' , ' .dll ' , ' .lib ' , ' .so ' , ' .dylib ' }
def get_link_deps_mapping ( self , prefix : str , environment : environment . Environment ) - > T . Mapping [ str , str ] :
return { }
def get_link_dep_subdirs ( self ) :
return OrderedSet ( )
def get_all_link_deps ( self ) :
return [ ]
def is_internal ( self ) - > bool :
'''
Returns True iif this is a not installed static library .
'''
if len ( self . outputs ) != 1 :
return False
return CustomTargetIndex ( self , self . outputs [ 0 ] ) . is_internal ( )
def extract_all_objects_recurse ( self ) - > T . List [ T . Union [ str , ' ExtractedObjects ' ] ] :
return self . get_outputs ( )
def type_suffix ( self ) :
return " @cus "
def __getitem__ ( self , index : int ) - > ' CustomTargetIndex ' :
return CustomTargetIndex ( self , self . outputs [ index ] )
def __setitem__ ( self , index , value ) :
raise NotImplementedError
def __delitem__ ( self , index ) :
raise NotImplementedError
def __iter__ ( self ) :
for i in self . outputs :
yield CustomTargetIndex ( self , i )
def __len__ ( self ) - > int :
return len ( self . outputs )
class RunTarget ( Target , CommandBase ) :
def __init__ ( self , name : str ,
command : T . Sequence [ T . Union [ str , File , BuildTarget , ' CustomTarget ' , ' CustomTargetIndex ' , programs . ExternalProgram ] ] ,
dependencies : T . Sequence [ Target ] ,
subdir : str ,
subproject : str ,
env : T . Optional [ ' EnvironmentVariables ' ] = None ) :
self . typename = ' run '
# These don't produce output artifacts
super ( ) . __init__ ( name , subdir , subproject , False , MachineChoice . BUILD )
self . dependencies = dependencies
self . depend_files = [ ]
self . command = self . flatten_command ( command )
self . absolute_paths = False
self . env = env
def __repr__ ( self ) - > str :
repr_str = " < {0} {1} : {2} > "
return repr_str . format ( self . __class__ . __name__ , self . get_id ( ) , self . command [ 0 ] )
def get_dependencies ( self ) - > T . List [ T . Union [ BuildTarget , ' CustomTarget ' ] ] :
return self . dependencies
def get_generated_sources ( self ) - > T . List [ ' GeneratedTypes ' ] :
return [ ]
def get_sources ( self ) - > T . List [ File ] :
return [ ]
def should_install ( self ) - > bool :
return False
def get_filename ( self ) - > str :
return self . name
def get_outputs ( self ) - > T . List [ str ] :
if isinstance ( self . name , str ) :
return [ self . name ]
elif isinstance ( self . name , list ) :
return self . name
else :
raise RuntimeError ( ' RunTarget: self.name is neither a list nor a string. This is a bug ' )
def type_suffix ( self ) - > str :
return " @run "
class AliasTarget ( RunTarget ) :
def __init__ ( self , name : str , dependencies : T . Sequence [ ' Target ' ] ,
subdir : str , subproject : str ) :
super ( ) . __init__ ( name , [ ] , dependencies , subdir , subproject )
def __repr__ ( self ) :
repr_str = " < {0} {1} > "
return repr_str . format ( self . __class__ . __name__ , self . get_id ( ) )
class Jar ( BuildTarget ) :
known_kwargs = known_jar_kwargs
def __init__ ( self , name , subdir , subproject , for_machine : MachineChoice , sources , objects , environment , kwargs ) :
self . typename = ' jar '
super ( ) . __init__ ( name , subdir , subproject , for_machine , sources , objects , environment , kwargs )
for s in self . sources :
if not s . endswith ( ' .java ' ) :
raise InvalidArguments ( f ' Jar source { s } is not a java file. ' )
for t in self . link_targets :
if not isinstance ( t , Jar ) :
raise InvalidArguments ( f ' Link target { t } is not a jar target. ' )
self . filename = self . name + ' .jar '
self . outputs = [ self . filename ]
self . java_args = kwargs . get ( ' java_args ' , [ ] )
def get_main_class ( self ) :
return self . main_class
def type_suffix ( self ) :
return " @jar "
def get_java_args ( self ) :
return self . java_args
def validate_install ( self , environment ) :
# All jar targets are installable.
pass
def is_linkable_target ( self ) :
return True
def get_classpath_args ( self ) :
cp_paths = [ os . path . join ( l . get_subdir ( ) , l . get_filename ( ) ) for l in self . link_targets ]
cp_string = os . pathsep . join ( cp_paths )
if cp_string :
return [ ' -cp ' , os . pathsep . join ( cp_paths ) ]
return [ ]
def get_default_install_dir ( self , environment : environment . Environment ) - > T . Tuple [ str , str ] :
return environment . get_jar_dir ( ) , ' {jardir} '
@dataclass ( eq = False )
class CustomTargetIndex ( HoldableObject ) :
""" A special opaque object returned by indexing a CustomTarget. This object
exists in Meson , but acts as a proxy in the backends , making targets depend
on the CustomTarget it ' s derived from, but only adding one source file to
the sources .
"""
target : CustomTarget
output : str
def __post_init__ ( self ) - > None :
self . typename = ' custom '
self . for_machine = self . target . for_machine
@property
def name ( self ) - > str :
return f ' { self . target . name } [ { self . output } ] '
def __repr__ ( self ) :
return ' <CustomTargetIndex: {!r} [ {} ]> ' . format (
self . target , self . target . get_outputs ( ) . index ( self . output ) )
def get_outputs ( self ) - > T . List [ str ] :
return [ self . output ]
def get_subdir ( self ) - > str :
return self . target . get_subdir ( )
def get_filename ( self ) - > str :
return self . output
def get_id ( self ) - > str :
return self . target . get_id ( )
def get_all_link_deps ( self ) :
return self . target . get_all_link_deps ( )
def get_link_deps_mapping ( self , prefix : str , environment : environment . Environment ) - > T . Mapping [ str , str ] :
return self . target . get_link_deps_mapping ( prefix , environment )
def get_link_dep_subdirs ( self ) :
return self . target . get_link_dep_subdirs ( )
def is_linkable_target ( self ) - > bool :
suf = os . path . splitext ( self . output ) [ - 1 ]
return suf in { ' .a ' , ' .dll ' , ' .lib ' , ' .so ' }
def should_install ( self ) - > bool :
return self . target . should_install ( )
def is_internal ( self ) - > bool :
'''
Returns True iif this is a not installed static library
'''
suf = os . path . splitext ( self . output ) [ - 1 ]
return suf in { ' .a ' , ' .lib ' } and not self . should_install ( )
def extract_all_objects_recurse ( self ) - > T . List [ T . Union [ str , ' ExtractedObjects ' ] ] :
return self . target . extract_all_objects_recurse ( )
def get_custom_install_dir ( self ) - > T . List [ T . Union [ str , bool ] ] :
return self . target . get_custom_install_dir ( )
class ConfigurationData ( HoldableObject ) :
def __init__ ( self , initial_values : T . Optional [ T . Union [
T . Dict [ str , T . Tuple [ T . Union [ str , int , bool ] , T . Optional [ str ] ] ] ,
T . Dict [ str , T . Union [ str , int , bool ] ] ]
] = None ) :
super ( ) . __init__ ( )
self . values : T . Dict [ str , T . Tuple [ T . Union [ str , int , bool ] , T . Optional [ str ] ] ] = \
{ k : v if isinstance ( v , tuple ) else ( v , None ) for k , v in initial_values . items ( ) } if initial_values else { }
self . used : bool = False
def __repr__ ( self ) - > str :
return repr ( self . values )
def __contains__ ( self , value : str ) - > bool :
return value in self . values
def __bool__ ( self ) - > bool :
return bool ( self . values )
def get ( self , name : str ) - > T . Tuple [ T . Union [ str , int , bool ] , T . Optional [ str ] ] :
return self . values [ name ] # (val, desc)
def keys ( self ) - > T . Iterator [ str ] :
return self . values . keys ( )
# A bit poorly named, but this represents plain data files to copy
# during install.
@dataclass ( eq = False )
class Data ( HoldableObject ) :
sources : T . List [ File ]
install_dir : str
install_dir_name : str
install_mode : ' FileMode '
subproject : str
rename : T . List [ str ] = None
install_tag : T . Optional [ str ] = None
data_type : str = None
def __post_init__ ( self ) - > None :
if self . rename is None :
self . rename = [ os . path . basename ( f . fname ) for f in self . sources ]
@dataclass ( eq = False )
class SymlinkData ( HoldableObject ) :
target : str
name : str
install_dir : str
subproject : str
install_tag : T . Optional [ str ] = None
def __post_init__ ( self ) - > None :
if self . name != os . path . basename ( self . name ) :
raise InvalidArguments ( f ' Link name is " { self . name } " , but link names cannot contain path separators. '
' The dir part should be in install_dir. ' )
@dataclass ( eq = False )
class TestSetup :
exe_wrapper : T . List [ str ]
gdb : bool
timeout_multiplier : int
env : EnvironmentVariables
exclude_suites : T . List [ str ]
def get_sources_string_names ( sources , backend ) :
'''
For the specified list of @sources which can be strings , Files , or targets ,
get all the output basenames .
'''
names = [ ]
for s in sources :
if isinstance ( s , str ) :
names . append ( s )
elif isinstance ( s , ( BuildTarget , CustomTarget , CustomTargetIndex , GeneratedList ) ) :
names + = s . get_outputs ( )
elif isinstance ( s , ExtractedObjects ) :
names + = s . get_outputs ( backend )
elif isinstance ( s , File ) :
names . append ( s . fname )
else :
raise AssertionError ( f ' Unknown source type: { s !r} ' )
return names
def load ( build_dir : str ) - > Build :
filename = os . path . join ( build_dir , ' meson-private ' , ' build.dat ' )
load_fail_msg = f ' Build data file { filename !r} is corrupted. Try with a fresh build tree. '
nonexisting_fail_msg = f ' No such build data file as " { filename !r} " . '
try :
with open ( filename , ' rb ' ) as f :
obj = pickle . load ( f )
except FileNotFoundError :
raise MesonException ( nonexisting_fail_msg )
except ( pickle . UnpicklingError , EOFError ) :
raise MesonException ( load_fail_msg )
except AttributeError :
raise MesonException (
f " Build data file { filename !r} references functions or classes that don ' t "
" exist. This probably means that it was generated with an old "
" version of meson. Try running from the source directory "
f " meson { build_dir } --wipe " )
if not isinstance ( obj , Build ) :
raise MesonException ( load_fail_msg )
return obj
def save ( obj : Build , filename : str ) - > None :
with open ( filename , ' wb ' ) as f :
pickle . dump ( obj , f )