1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317 |
- # 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.
- import copy, os, re
- from collections import OrderedDict
- import itertools, pathlib
- import hashlib
- import pickle
- from functools import lru_cache
- from . import environment
- from . import dependencies
- from . import mlog
- from .mesonlib import (
- File, MesonException, listify, extract_as_list, OrderedSet,
- typeslistify, stringlistify, classify_unity_sources,
- get_filenames_templates_dict, substitute_values,
- for_windows, for_darwin, for_cygwin, for_android, has_path_sep
- )
- from .compilers import is_object, clink_langs, sort_clink, lang_suffixes, get_macos_dylib_install_name
- from .interpreterbase import FeatureNew
- pch_kwargs = set(['c_pch', 'cpp_pch'])
- lang_arg_kwargs = set([
- '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',
- ])
- vala_kwargs = set(['vala_header', 'vala_gir', 'vala_vapi'])
- rust_kwargs = set(['rust_crate_type'])
- cs_kwargs = set(['resources', 'cs_args'])
- buildtarget_kwargs = set([
- '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',
- 'name_prefix',
- 'name_suffix',
- 'native',
- 'objects',
- 'override_options',
- 'sources',
- 'gnu_symbol_visibility',
- ])
- 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
- known_stlib_kwargs = known_build_target_kwargs | {'pic'}
- known_jar_kwargs = known_exe_kwargs | {'main_class'}
- @lru_cache(maxsize=None)
- def get_target_macos_dylib_install_name(ld):
- return get_macos_dylib_install_name(ld.prefix, ld.name, ld.suffix, ld.soversion)
- class InvalidArguments(MesonException):
- pass
- class Build:
- """A class that holds the status of one build including
- all dependencies and so on.
- """
- def __init__(self, environment):
- self.project_name = 'name of master project'
- self.project_version = None
- self.environment = environment
- self.projects = {}
- self.targets = OrderedDict()
- # Coredata holds the state. This is just here for convenience.
- self.compilers = environment.coredata.compilers
- self.cross_compilers = environment.coredata.cross_compilers
- self.global_args = {}
- self.projects_args = {}
- self.global_link_args = {}
- self.projects_link_args = {}
- self.cross_global_args = {}
- self.cross_projects_args = {}
- self.cross_global_link_args = {}
- self.cross_projects_link_args = {}
- self.tests = []
- self.benchmarks = []
- self.headers = []
- self.man = []
- self.data = []
- self.static_linker = None
- self.static_cross_linker = None
- self.subprojects = {}
- self.subproject_dir = ''
- self.install_scripts = []
- self.postconf_scripts = []
- self.dist_scripts = []
- self.install_dirs = []
- self.dep_manifest_name = None
- self.dep_manifest = {}
- self.cross_stdlibs = {}
- self.test_setups = {}
- self.test_setup_default_name = None
- self.find_overrides = {}
- self.searched_programs = set() # The list of all programs that have been searched for.
- def copy(self):
- other = Build(self.environment)
- for k, v in self.__dict__.items():
- if k in ['compilers', 'cross_compilers']:
- # These alias coredata's fields of the same name, and must not
- # become copies.
- continue
- if isinstance(v, (list, dict, set, OrderedDict)):
- other.__dict__[k] = v.copy()
- else:
- other.__dict__[k] = v
- return other
- def merge(self, other):
- for k, v in other.__dict__.items():
- self.__dict__[k] = v
- def ensure_static_linker(self, compiler):
- if self.static_linker is None and compiler.needs_static_linker():
- self.static_linker = self.environment.detect_static_linker(compiler)
- def ensure_static_cross_linker(self, compiler):
- if self.static_cross_linker is None and compiler.needs_static_linker():
- self.static_cross_linker = self.environment.detect_static_linker(compiler)
- def get_project(self):
- return self.projects['']
- def get_subproject_dir(self):
- return self.subproject_dir
- def get_targets(self):
- return self.targets
- def get_tests(self):
- return self.tests
- def get_benchmarks(self):
- return self.benchmarks
- def get_headers(self):
- return self.headers
- def get_man(self):
- return self.man
- def get_data(self):
- return self.data
- def get_install_subdirs(self):
- return self.install_dirs
- def get_global_args(self, compiler, for_cross):
- d = self.cross_global_args if for_cross else self.global_args
- return d.get(compiler.get_language(), [])
- def get_project_args(self, compiler, project, for_cross):
- d = self.cross_projects_args if for_cross else self.projects_args
- args = d.get(project)
- if not args:
- return []
- return args.get(compiler.get_language(), [])
- def get_global_link_args(self, compiler, for_cross):
- d = self.cross_global_link_args if for_cross else self.global_link_args
- return d.get(compiler.get_language(), [])
- def get_project_link_args(self, compiler, project, for_cross):
- d = self.cross_projects_link_args if for_cross else self.projects_link_args
- link_args = d.get(project)
- if not link_args:
- return []
- return link_args.get(compiler.get_language(), [])
- class IncludeDirs:
- def __init__(self, curdir, dirs, is_system, extra_build_dirs=None):
- self.curdir = curdir
- self.incdirs = dirs
- self.is_system = is_system
- # Interpreter has validated that all given directories
- # actually exist.
- if extra_build_dirs is None:
- self.extra_build_dirs = []
- else:
- self.extra_build_dirs = extra_build_dirs
- def __repr__(self):
- r = '<{} {}/{}>'
- return r.format(self.__class__.__name__, self.curdir, self.incdirs)
- def get_curdir(self):
- return self.curdir
- def get_incdirs(self):
- return self.incdirs
- def get_extra_build_dirs(self):
- return self.extra_build_dirs
- class ExtractedObjects:
- '''
- Holds a list of sources for which the objects must be extracted
- '''
- def __init__(self, target, srclist=[], genlist=[], objlist=[], recursive=True):
- self.target = target
- self.recursive = recursive
- self.srclist = srclist
- self.genlist = genlist
- self.objlist = objlist
- if self.target.is_unity:
- self.check_unity_compatible()
- def __repr__(self):
- r = '<{0} {1!r}: {2}>'
- return r.format(self.__class__.__name__, self.target.name, self.srclist)
- def classify_all_sources(self, sources, generated_sources):
- # 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
- sources = [s for s in sources if environment.is_source(s) and not environment.is_header(s)]
- return classify_unity_sources(self.target.compilers.values(), sources)
- def check_unity_compatible(self):
- # 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.')
- class EnvironmentVariables:
- def __init__(self):
- self.envvars = []
- def __repr__(self):
- repr_str = "<{0}: {1}>"
- return repr_str.format(self.__class__.__name__, self.envvars)
- def get_value(self, values, kwargs):
- separator = kwargs.get('separator', os.pathsep)
- value = ''
- for var in values:
- value += separator + var
- return separator, value.strip(separator)
- def set(self, env, name, values, kwargs):
- return self.get_value(values, kwargs)[1]
- def append(self, env, name, values, kwargs):
- sep, value = self.get_value(values, kwargs)
- if name in env:
- return env[name] + sep + value
- return value
- def prepend(self, env, name, values, kwargs):
- sep, value = self.get_value(values, kwargs)
- if name in env:
- return value + sep + env[name]
- return value
- def get_env(self, full_env):
- env = full_env.copy()
- for method, name, values, kwargs in self.envvars:
- env[name] = method(full_env, name, values, kwargs)
- return env
- class Target:
- def __init__(self, name, subdir, subproject, build_by_default):
- if has_path_sep(name):
- # Fix failing test 53 when this becomes an error.
- mlog.warning('''Target "%s" 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.''' % name)
- self.name = name
- self.subdir = subdir
- self.subproject = subproject
- self.build_by_default = build_by_default
- self.install = False
- self.build_always_stale = False
- self.option_overrides = {}
- if not hasattr(self, 'typename'):
- raise RuntimeError('Target type is not set for target class "{}". This is a bug'.format(type(self).__name__))
- def get_install_dir(self, environment):
- # Find the installation directory.
- default_install_dir = self.get_default_install_dir(environment)
- outdirs = self.get_custom_install_dir()
- if outdirs[0] is not None 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
- outdirs[0] = default_install_dir
- return outdirs, custom_install_dir
- def get_basename(self):
- return self.name
- def get_subdir(self):
- return self.subdir
- def get_typename(self):
- 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, name, type_suffix):
- """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):
- return self.construct_id_from_path(
- self.subdir, self.name, self.type_suffix())
- def process_kwargs(self, kwargs):
- 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
- self.option_overrides = self.parse_overrides(kwargs)
- def parse_overrides(self, kwargs):
- result = {}
- overrides = stringlistify(kwargs.get('override_options', []))
- for o in overrides:
- if '=' not in o:
- raise InvalidArguments('Overrides must be of form "key=value"')
- k, v = o.split('=', 1)
- k = k.strip()
- v = v.strip()
- result[k] = v
- return result
- def is_linkable_target(self):
- return False
- class BuildTarget(Target):
- known_kwargs = known_build_target_kwargs
- def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs):
- super().__init__(name, subdir, subproject, True)
- self.is_cross = is_cross
- unity_opt = environment.coredata.get_builtin_option('unity')
- self.is_unity = unity_opt == 'on' or (unity_opt == 'subprojects' and subproject != '')
- self.environment = environment
- self.sources = []
- self.compilers = OrderedDict()
- self.objects = []
- self.external_deps = []
- self.include_dirs = []
- self.link_targets = []
- self.link_whole_targets = []
- self.link_depends = []
- 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 = {}
- self.extra_args = {}
- self.generated = []
- self.extra_files = []
- self.d_features = {}
- self.pic = False
- self.pie = False
- # Sources can be:
- # 1. Pre-existing source files in the source tree
- # 2. Pre-existing sources generated by configure_file in the build tree
- # 3. Sources files generated by another target or a Generator
- 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('Build target %s has no sources.' % name)
- self.process_compilers_late()
- self.validate_sources()
- self.validate_cross_install(environment)
- self.check_module_linking()
- def __lt__(self, other):
- return self.get_id() < other.get_id()
- def __repr__(self):
- repr_str = "<{0} {1}: {2}>"
- return repr_str.format(self.__class__.__name__, self.get_id(), self.filename)
- def validate_cross_install(self, environment):
- if environment.is_cross_build() and not self.is_cross and self.need_install:
- raise InvalidArguments('Tried to install a natively built target 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 %s: %s.' %
- (self.name, ', '.join(unknowns)))
- def process_objectlist(self, objects):
- assert(isinstance(objects, list))
- for s in objects:
- if hasattr(s, 'held_object'):
- s = s.held_object
- if isinstance(s, (str, File, ExtractedObjects)):
- self.objects.append(s)
- elif isinstance(s, (GeneratedList, CustomTarget)):
- msg = 'Generated files are not allowed in the \'objects\' kwarg ' + \
- 'for target {!r}.\nIt is meant only for '.format(self.name) + \
- 'pre-built object files that are shipped with the\nsource ' + \
- 'tree. Try adding it in the list of sources.'
- raise InvalidArguments(msg)
- else:
- msg = 'Bad object of type {!r} in target {!r}.'.format(type(s).__name__, self.name)
- raise InvalidArguments(msg)
- def process_sourcelist(self, sources):
- sources = listify(sources)
- added_sources = {} # If the same source is defined multiple times, use it only once.
- for s in sources:
- # Holder unpacking. Ugly.
- if hasattr(s, 'held_object'):
- s = s.held_object
- if isinstance(s, File):
- if s not in added_sources:
- self.sources.append(s)
- added_sources[s] = True
- elif isinstance(s, (GeneratedList, CustomTarget, CustomTargetIndex)):
- self.generated.append(s)
- else:
- msg = 'Bad source of type {!r} in target {!r}.'.format(type(s).__name__, self.name)
- raise InvalidArguments(msg)
- @staticmethod
- def can_compile_remove_sources(compiler, sources):
- 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
- if self.is_cross:
- compilers = self.environment.coredata.cross_compilers
- else:
- compilers = self.environment.coredata.compilers
- # 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):
- for name, compiler in t.compilers.items():
- if name in clink_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 clink_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
- if self.is_cross:
- compilers = self.environment.coredata.cross_compilers
- else:
- compilers = self.environment.coredata.compilers
- # 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:
- if hasattr(d, 'held_object'):
- d = d.held_object
- 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.
- # It's ok if no compilers can do so, 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
- # 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']
- 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):
- m = 'No {} sources found in target {!r}'.format(lang, self.name)
- raise InvalidArguments(m)
- 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 hasattr(s, 'held_object'):
- s = s.held_object
- 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.extend(
- [File.from_built_file(s.subdir, p) for p in s.get_outputs()])
- 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 unpack_holder(self, d):
- d = listify(d)
- newd = []
- for i in d:
- if isinstance(i, list):
- i = self.unpack_holder(i)
- elif hasattr(i, 'held_object'):
- i = i.held_object
- for t in ['dependencies', 'link_with', 'include_directories', 'sources']:
- if hasattr(i, t):
- setattr(i, t, self.unpack_holder(getattr(i, t)))
- newd.append(i)
- return newd
- def copy_kwargs(self, kwargs):
- self.kwargs = copy.copy(kwargs)
- # This sucks quite badly. Arguments
- # are holders but they can't be pickled
- # so unpack those known.
- for k, v in self.kwargs.items():
- if isinstance(v, list):
- self.kwargs[k] = self.unpack_holder(v)
- if hasattr(v, 'held_object'):
- self.kwargs[k] = v.held_object
- for t in ['dependencies', 'link_with', 'include_directories', 'sources']:
- if t in self.kwargs:
- self.kwargs[t] = self.unpack_holder(self.kwargs[t])
- def extract_objects(self, srclist):
- obj_src = []
- for src in srclist:
- if not isinstance(src, str):
- raise MesonException('Object extraction arguments must be strings.')
- src = File(False, self.subdir, src)
- # FIXME: It could be a generated source
- if src not in self.sources:
- raise MesonException('Tried to extract unknown source %s.' % src)
- obj_src.append(src)
- return ExtractedObjects(self, obj_src)
- def extract_all_objects(self, recursive=True):
- return ExtractedObjects(self, self.sources, self.generated, self.objects,
- recursive)
- def get_all_link_deps(self):
- return self.get_transitive_link_deps()
- @lru_cache(maxsize=None)
- def get_transitive_link_deps(self):
- result = []
- for i in self.link_targets:
- result += i.get_all_link_deps()
- return result
- def get_link_deps_mapping(self, prefix, environment):
- return self.get_transitive_link_deps_mapping(prefix, environment)
- @lru_cache(maxsize=None)
- def get_transitive_link_deps_mapping(self, prefix, environment):
- result = {}
- 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):
- result = OrderedSet()
- for i in self.link_targets:
- result.add(i.get_subdir())
- result.update(i.get_link_dep_subdirs())
- return result
- def get_default_install_dir(self, environment):
- return environment.get_libdir()
- def get_custom_install_dir(self):
- return self.install_dir
- def get_custom_install_mode(self):
- return self.install_mode
- def process_kwargs(self, kwargs, environment):
- super().process_kwargs(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:
- # Sorry for this hack. Keyword targets are kept in holders
- # in kwargs. Unpack here without looking at the exact type.
- if hasattr(linktarget, "held_object"):
- linktarget = linktarget.held_object
- if isinstance(linktarget, dependencies.ExternalLibrary):
- raise MesonException('''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_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 = dict()
- 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', unholder=True)
- 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('''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', [None]),
- (str, bool))
- self.install_mode = kwargs.get('install_mode', 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):
- self.gui_app = kwargs.get('gui_app', False)
- if not isinstance(self.gui_app, bool):
- raise InvalidArguments('Argument gui_app must be boolean.')
- elif 'gui_app' in kwargs:
- raise InvalidArguments('Argument gui_app can only be used on executables.')
- extra_files = 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('Tried to add non-existing extra file %s.' % i)
- self.extra_files = extra_files
- self.install_rpath = kwargs.get('install_rpath', '')
- if not isinstance(self.install_rpath, str):
- raise InvalidArguments('Install_rpath is not a string.')
- 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('Tried to add non-existing resource %s.' % r)
- self.resources = resources
- if 'name_prefix' in kwargs:
- name_prefix = kwargs['name_prefix']
- if isinstance(name_prefix, list):
- if name_prefix:
- raise InvalidArguments('name_prefix array must be empty to signify null.')
- elif not isinstance(name_prefix, str):
- raise InvalidArguments('name_prefix must be a string.')
- self.prefix = name_prefix
- 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 null.')
- 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)
- if for_darwin(self.is_cross, self.environment) or for_windows(self.is_cross, self.environment):
- self.pic = True
- else:
- self.pic = self._extract_pic_pie(kwargs, 'pic')
- if isinstance(self, Executable):
- # Executables must be PIE on Android
- if for_android(self.is_cross, self.environment):
- self.pie = True
- else:
- self.pie = self._extract_pic_pie(kwargs, '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 %s not one of: %s',
- self.symbol_visibility, ', '.join(permitted))
- def _extract_pic_pie(self, kwargs, arg):
- # 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("Use the '{}' kwarg instead of passing '{}' manually to {!r}".format(arg, '-f' + arg, self.name))
- return True
- val = kwargs.get(arg, False)
- if not isinstance(val, bool):
- raise InvalidArguments('Argument {} to {!r} must be boolean'.format(arg, self.name))
- return val
- def get_filename(self):
- return self.filename
- def get_outputs(self):
- return self.outputs
- def get_extra_args(self, language):
- return self.extra_args.get(language, [])
- def get_dependencies(self, exclude=None, internal=True):
- transitive_deps = []
- if exclude is None:
- exclude = []
- if internal:
- link_targets = itertools.chain(self.link_targets, self.link_whole_targets)
- else:
- # We don't want the 'internal' libraries when generating the
- # `Libs:` and `Libs.private:` lists in pkg-config files.
- link_targets = self.link_targets
- for t in link_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, internal)
- return transitive_deps
- def get_source_subdir(self):
- return self.subdir
- def get_sources(self):
- return self.sources
- def get_objects(self):
- return self.objects
- def get_generated_sources(self):
- return self.generated
- def should_install(self):
- return self.need_install
- def has_pch(self):
- return len(self.pch) > 0
- def get_pch(self, language):
- try:
- return self.pch[language]
- except KeyError:
- return[]
- def get_include_dirs(self):
- return self.include_dirs
- def add_deps(self, deps):
- deps = listify(deps)
- for dep in deps:
- if hasattr(dep, 'held_object'):
- dep = dep.held_object
- if isinstance(dep, dependencies.InternalDependency):
- # Those parts that are internal.
- self.process_sourcelist(dep.sources)
- self.add_include_dirs(dep.include_directories)
- for l in dep.libraries:
- self.link(l)
- for l in dep.whole_libraries:
- self.link_whole(l)
- if dep.compile_args or dep.link_args:
- # Those parts that are external.
- extpart = dependencies.InternalDependency('undefined',
- [],
- dep.compile_args,
- dep.link_args,
- [], [], [], [])
- self.external_deps.append(extpart)
- # Deps of deps.
- self.add_deps(dep.ext_deps)
- elif isinstance(dep, dependencies.Dependency):
- self.external_deps.append(dep)
- self.process_sourcelist(dep.get_sources())
- 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('Argument is of an unacceptable type {!r}.\nMust be '
- 'either an external dependency (returned by find_library() or '
- 'dependency()) or an internal dependency (returned by '
- 'declare_dependency()).'.format(type(dep).__name__))
- def get_external_deps(self):
- return self.external_deps
- def link(self, target):
- for t in listify(target, unholder=True):
- if not isinstance(t, Target):
- raise InvalidArguments('{!r} is not a target.'.format(t))
- if not t.is_linkable_target():
- raise InvalidArguments('Link target {!r} is not linkable.'.format(t))
- if isinstance(self, SharedLibrary) and isinstance(t, StaticLibrary) and not t.pic:
- msg = "Can't link non-PIC static library {!r} into shared library {!r}. ".format(t.name, self.name)
- msg += "Use the 'pic' option to static_library to build with PIC."
- raise InvalidArguments(msg)
- if self.is_cross != t.is_cross:
- raise InvalidArguments('Tried to mix cross built and native libraries in target {!r}'.format(self.name))
- self.link_targets.append(t)
- def link_whole(self, target):
- for t in listify(target, unholder=True):
- if not isinstance(t, StaticLibrary):
- raise InvalidArguments('{!r} is not a static library.'.format(t))
- if isinstance(self, SharedLibrary) and not t.pic:
- msg = "Can't link non-PIC static library {!r} into shared library {!r}. ".format(t.name, self.name)
- msg += "Use the 'pic' option to static_library to build with PIC."
- raise InvalidArguments(msg)
- if self.is_cross != t.is_cross:
- raise InvalidArguments('Tried to mix cross built and native libraries in target {!r}'.format(self.name))
- self.link_whole_targets.append(t)
- def add_pch(self, language, pchlist):
- if not pchlist:
- return
- elif len(pchlist) == 1:
- if not environment.is_header(pchlist[0]):
- raise InvalidArguments('PCH argument %s is not a header.' % pchlist[0])
- elif len(pchlist) == 2:
- if environment.is_header(pchlist[0]):
- if not environment.is_source(pchlist[1]):
- raise InvalidArguments('PCH definition must contain one header and at most one source.')
- elif environment.is_source(pchlist[0]):
- if not environment.is_header(pchlist[1]):
- raise InvalidArguments('PCH definition must contain one header and at most one source.')
- pchlist = [pchlist[1], pchlist[0]]
- else:
- raise InvalidArguments('PCH argument %s is of unknown type.' % pchlist[0])
- if (os.path.dirname(pchlist[0]) != os.path.dirname(pchlist[1])):
- raise InvalidArguments('PCH files must be stored in the same folder.')
- elif len(pchlist) > 2:
- raise InvalidArguments('PCH definition may have a maximum of 2 files.')
- for f in pchlist:
- if not os.path.isfile(os.path.join(self.environment.source_dir, self.subdir, f)):
- raise MesonException('File %s does not exist.' % f)
- self.pch[language] = pchlist
- def add_include_dirs(self, args):
- ids = []
- for a in args:
- # FIXME same hack, forcibly unpack from holder.
- if hasattr(a, 'held_object'):
- a = a.held_object
- if not isinstance(a, IncludeDirs):
- raise InvalidArguments('Include directory to be added is not an include directory object.')
- ids.append(a)
- self.include_dirs += ids
- def add_compiler_args(self, language, args):
- args = 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):
- return {}
- def get_langs_used_by_deps(self):
- '''
- 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 = []
- # 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):
- for language in link_target.compilers:
- if language not in langs:
- langs.append(language)
- return langs
- def get_clink_dynamic_linker_and_stdlibs(self):
- '''
- 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
- if self.is_cross:
- all_compilers = self.environment.coredata.cross_compilers
- else:
- all_compilers = self.environment.coredata.compilers
- # 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(
- 'Could not get a dynamic linker for build target {!r}. '
- 'Requires a linker for language "{}", but that is not '
- 'a project language.'.format(self.name, l))
- stdlib_args = []
- added_languages = set()
- for dl in itertools.chain(self.compilers, dep_langs):
- if dl != linker.language:
- stdlib_args += all_compilers[dl].language_stdlib_only_link_flags()
- added_languages.add(dl)
- return linker, stdlib_args
- m = 'Could not get a dynamic linker for build target {!r}'
- raise AssertionError(m.format(self.name))
- def get_using_msvc(self):
- '''
- 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.
- '''
- linker, _ = self.get_clink_dynamic_linker_and_stdlibs()
- # Mixing many languages with MSVC is not supported yet so ignore stdlibs.
- if linker and linker.get_id() in ['msvc', 'clang-cl', 'llvm', 'dmd']:
- return True
- return False
- 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):
- if for_darwin(self.is_cross, self.environment):
- raise MesonException('''target links against shared modules.
- This is not permitted on OSX''')
- else:
- mlog.warning('''target links against shared modules. This is not
- recommended as it is not supported on some platforms''')
- return
- class Generator:
- def __init__(self, args, kwargs):
- if len(args) != 1:
- raise InvalidArguments('Generator requires exactly one positional argument: the executable')
- exe = args[0]
- if hasattr(exe, 'held_object'):
- exe = exe.held_object
- if not isinstance(exe, (Executable, dependencies.ExternalProgram)):
- raise InvalidArguments('First generator argument must be an executable.')
- self.exe = exe
- self.depfile = None
- self.capture = False
- self.process_kwargs(kwargs)
- def __repr__(self):
- repr_str = "<{0}: {1}>"
- return repr_str.format(self.__class__.__name__, self.exe)
- def get_exe(self):
- return self.exe
- def process_kwargs(self, kwargs):
- if 'arguments' not in kwargs:
- raise InvalidArguments('Generator must have "arguments" keyword argument.')
- args = kwargs['arguments']
- if isinstance(args, str):
- args = [args]
- if not isinstance(args, list):
- raise InvalidArguments('"Arguments" keyword argument must be a string or a list of strings.')
- for a in args:
- if not isinstance(a, str):
- raise InvalidArguments('A non-string object in "arguments" keyword argument.')
- self.arglist = args
- if 'output' not in kwargs:
- raise InvalidArguments('Generator must have "output" keyword argument.')
- outputs = listify(kwargs['output'])
- for rule in outputs:
- if not isinstance(rule, str):
- raise InvalidArguments('"output" may only contain strings.')
- if '@BASENAME@' not in rule and '@PLAINNAME@' not in rule:
- raise InvalidArguments('Every element of "output" must contain @BASENAME@ or @PLAINNAME@.')
- if has_path_sep(rule):
- raise InvalidArguments('"outputs" must not contain a directory separator.')
- if len(outputs) > 1:
- for o in outputs:
- if '@OUTPUT@' in o:
- raise InvalidArguments('Tried to use @OUTPUT@ in a rule with more than one output.')
- self.outputs = outputs
- if 'depfile' in kwargs:
- depfile = kwargs['depfile']
- if not isinstance(depfile, str):
- raise InvalidArguments('Depfile must be a string.')
- if os.path.basename(depfile) != depfile:
- raise InvalidArguments('Depfile must be a plain filename without a subdirectory.')
- self.depfile = depfile
- if 'capture' in kwargs:
- capture = kwargs['capture']
- if not isinstance(capture, bool):
- raise InvalidArguments('Capture must be boolean.')
- self.capture = capture
- def get_base_outnames(self, inname):
- 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):
- 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):
- plainname = os.path.basename(inname)
- basename = os.path.splitext(plainname)[0]
- return [x.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname) for x in self.arglist]
- def is_parent_path(self, parent, trial):
- relpath = pathlib.PurePath(trial).relative_to(parent)
- return relpath.parts[0] != '..' # For subdirs we can only go "down".
- def process_files(self, name, files, state, preserve_path_from=None, extra_args=[]):
- output = GeneratedList(self, state.subdir, preserve_path_from, extra_args=extra_args)
- for f in files:
- if isinstance(f, str):
- f = File.from_source_file(state.environment.source_dir, state.subdir, f)
- elif not isinstance(f, File):
- raise InvalidArguments('{} arguments must be strings or files not {!r}.'.format(name, f))
- 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('When using preserve_path_from, all input files must be in a subdirectory of the given dir.')
- output.add_file(f, state)
- return output
- class GeneratedList:
- def __init__(self, generator, subdir, preserve_path_from=None, extra_args=[]):
- if hasattr(generator, 'held_object'):
- generator = generator.held_object
- self.generator = generator
- self.name = self.generator.exe
- self.subdir = subdir
- self.infilelist = []
- self.outfilelist = []
- self.outmap = {}
- self.extra_depends = []
- self.preserve_path_from = preserve_path_from
- self.extra_args = extra_args
- def add_preserved_path_segment(self, infile, outfiles, state):
- result = []
- 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, state):
- 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):
- return self.infilelist
- def get_outputs(self):
- return self.outfilelist
- def get_outputs_for(self, filename):
- return self.outmap[filename]
- def get_generator(self):
- return self.generator
- def get_extra_args(self):
- return self.extra_args
- class Executable(BuildTarget):
- known_kwargs = known_exe_kwargs
- def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs):
- self.typename = 'executable'
- if 'pie' not in kwargs and 'b_pie' in environment.coredata.base_options:
- kwargs['pie'] = environment.coredata.base_options['b_pie'].value
- super().__init__(name, subdir, subproject, is_cross, 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'):
- # Executable for Windows or C#/Mono
- if (for_windows(is_cross, environment) or
- for_cygwin(is_cross, environment) or 'cs' in self.compilers):
- self.suffix = 'exe'
- elif ('c' in self.compilers and self.compilers['c'].get_id().startswith('arm') or
- 'cpp' in self.compilers and self.compilers['cpp'].get_id().startswith('arm')):
- 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'
- else:
- self.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
- # 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')
- # 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 for_windows(is_cross, environment) or for_cygwin(is_cross, environment):
- self.vs_import_filename = '{0}.lib'.format(implib_basename)
- self.gcc_import_filename = 'lib{0}.a'.format(implib_basename)
- if self.get_using_msvc():
- self.import_filename = self.vs_import_filename
- else:
- self.import_filename = self.gcc_import_filename
- # Only linkwithable if using export_dynamic
- self.is_linkwithable = self.export_dynamic
- def get_default_install_dir(self, environment):
- return environment.get_bindir()
- def description(self):
- '''Human friendly description of the executable'''
- return self.name
- def type_suffix(self):
- return "@exe"
- def get_import_filename(self):
- """
- The name of the import library that will be outputted by the compiler
- Returns None if there is no import library required for this platform
- """
- return self.import_filename
- def get_import_filenameslist(self):
- if self.import_filename:
- return [self.vs_import_filename, self.gcc_import_filename]
- return []
- def is_linkable_target(self):
- return self.is_linkwithable
- class StaticLibrary(BuildTarget):
- known_kwargs = known_stlib_kwargs
- def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs):
- self.typename = 'static library'
- if 'pic' not in kwargs and 'b_staticpic' in environment.coredata.base_options:
- kwargs['pic'] = environment.coredata.base_options['b_staticpic'].value
- super().__init__(name, subdir, subproject, is_cross, 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('Crate type "{0}" invalid for static libraries; must be "rlib" or "staticlib"'.format(self.rust_crate_type))
- # 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]
- def get_link_deps_mapping(self, prefix, environment):
- return {}
- def get_default_install_dir(self, environment):
- return environment.get_static_lib_dir()
- 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('Invalid rust_crate_type "{0}": must be a string.'.format(rust_crate_type))
- def is_linkable_target(self):
- return True
- class SharedLibrary(BuildTarget):
- known_kwargs = known_shlib_kwargs
- def __init__(self, name, subdir, subproject, is_cross, 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
- super().__init__(name, subdir, subproject, is_cross, 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('Crate type "{0}" invalid for dynamic libraries; must be "dylib" or "cdylib"'.format(self.rust_crate_type))
- if not hasattr(self, 'prefix'):
- self.prefix = None
- if not hasattr(self, 'suffix'):
- self.suffix = None
- self.basic_filename_tpl = '{0.prefix}{0.name}.{0.suffix}'
- self.determine_filenames(is_cross, environment)
- def get_link_deps_mapping(self, prefix, environment):
- result = {}
- 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):
- return environment.get_shared_lib_dir()
- def determine_filenames(self, is_cross, env):
- """
- See https://github.com/mesonbuild/meson/pull/417 for details.
- First we determine the filename template (self.filename_tpl), then we
- set the output filename (self.filename).
- The template is needed while creating aliases (self.get_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 = ''
- 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}'
- # C, C++, Swift, Vala
- # Only Windows uses a separate import library for linking
- # For all other targets/platforms import_filename stays None
- elif for_windows(is_cross, env):
- suffix = 'dll'
- self.vs_import_filename = '{0}{1}.lib'.format(self.prefix if self.prefix is not None else '', self.name)
- self.gcc_import_filename = '{0}{1}.dll.a'.format(self.prefix if self.prefix is not None else 'lib', self.name)
- if 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
- # Assume GCC-compatible naming
- else:
- # Shared library is of the form libfoo.dll
- prefix = 'lib'
- # Import library is called libfoo.dll.a
- self.import_filename = self.gcc_import_filename
- # Shared library has the soversion if it is defined
- if self.soversion:
- self.filename_tpl = '{0.prefix}{0.name}-{0.soversion}.{0.suffix}'
- else:
- self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}'
- elif for_cygwin(is_cross, env):
- suffix = 'dll'
- self.gcc_import_filename = '{0}{1}.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 for_darwin(is_cross, env):
- 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 for_android(is_cross, env):
- 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]
- @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,'
- 'or a list, not {!r}'.format(darwin_versions))
- 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 '
- 'must be strings or integers, not {!r}'.format(v))
- 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 for_android(self.is_cross, self.environment):
- 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('Invalid Shared library version "{0}". Must be of the form X.Y.Z where all three are numbers. Y and Z are optional.'.format(self.ltversion))
- # Try to extract/deduce the soversion
- if 'soversion' in kwargs:
- self.soversion = kwargs['soversion']
- if isinstance(self.soversion, int):
- self.soversion = str(self.soversion)
- if not isinstance(self.soversion, str):
- raise InvalidArguments('Shared library soversion is not a string or integer.')
- 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 and iOS 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 hasattr(path, 'held_object'):
- path = path.held_object
- 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)
- self.link_depends.append(self.vs_module_defs)
- elif isinstance(path, File):
- # When passing a generated file.
- self.vs_module_defs = path
- self.link_depends.append(path)
- elif hasattr(path, 'get_filename'):
- # When passing output of a Custom Target
- path = File.from_built_file(path.subdir, path.get_filename())
- self.vs_module_defs = path
- self.link_depends.append(path)
- else:
- raise InvalidArguments(
- 'Shared library vs_module_defs must be either a string, '
- 'a file object or a Custom Target')
- 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('Invalid rust_crate_type "{0}": must be a string.'.format(rust_crate_type))
- def get_import_filename(self):
- """
- The name of the import library that will be outputted by the compiler
- Returns None if there is no import library required for this platform
- """
- return self.import_filename
- def get_import_filenameslist(self):
- if self.import_filename:
- return [self.vs_import_filename, self.gcc_import_filename]
- return []
- def get_all_link_deps(self):
- return [self] + self.get_transitive_link_deps()
- def get_aliases(self):
- """
- 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 = {}
- # 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 {}
- # 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)
- aliases[ltversion_filename] = self.filename
- # 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
- aliases[self.basic_filename_tpl.format(self)] = ltversion_filename
- 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, is_cross, 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, is_cross, sources, objects, environment, kwargs)
- self.typename = 'shared module'
- def get_default_install_dir(self, environment):
- return environment.get_shared_module_dir()
- class CustomTarget(Target):
- known_kwargs = set([
- 'input',
- 'output',
- 'command',
- 'capture',
- 'install',
- 'install_dir',
- 'install_mode',
- 'build_always',
- 'build_always_stale',
- 'depends',
- 'depend_files',
- 'depfile',
- 'build_by_default',
- 'override_options',
- 'console',
- ])
- def __init__(self, name, subdir, subproject, kwargs, absolute_paths=False):
- self.typename = 'custom'
- super().__init__(name, subdir, subproject, False)
- self.dependencies = []
- self.extra_depends = []
- self.depend_files = [] # Files that this target depends on but are not on the command line.
- self.depfile = None
- self.process_kwargs(kwargs)
- self.extra_files = []
- # Whether to use absolute paths for all files on the commandline
- self.absolute_paths = absolute_paths
- unknowns = []
- for k in kwargs:
- if k not in CustomTarget.known_kwargs:
- unknowns.append(k)
- if len(unknowns) > 0:
- mlog.warning('Unknown keyword arguments in target %s: %s' %
- (self.name, ', '.join(unknowns)))
- def get_default_install_dir(self, environment):
- return None
- def __lt__(self, other):
- return self.get_id() < other.get_id()
- def __repr__(self):
- repr_str = "<{0} {1}: {2}>"
- return repr_str.format(self.__class__.__name__, self.get_id(), self.command)
- def get_id(self):
- return self.name + self.type_suffix()
- def get_target_dependencies(self):
- deps = self.dependencies[:]
- deps += self.extra_depends
- for c in self.sources:
- if hasattr(c, 'held_object'):
- c = c.held_object
- if isinstance(c, (BuildTarget, CustomTarget)):
- deps.append(c)
- return deps
- def get_transitive_build_target_deps(self):
- '''
- 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 = 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 flatten_command(self, cmd):
- cmd = listify(cmd, unholder=True)
- final_cmd = []
- 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, dependencies.ExternalProgram):
- if not c.found():
- m = 'Tried to use not-found external program {!r} in "command"'
- raise InvalidArguments(m.format(c.name))
- 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, list):
- final_cmd += self.flatten_command(c)
- else:
- raise InvalidArguments('Argument {!r} in "command" is invalid'.format(c))
- return final_cmd
- def process_kwargs(self, kwargs):
- super().process_kwargs(kwargs)
- self.sources = extract_as_list(kwargs, 'input', unholder=True)
- if 'output' not in kwargs:
- raise InvalidArguments('Missing keyword argument "output".')
- self.outputs = listify(kwargs['output'])
- # This will substitute values from the input into output and return it.
- inputs = get_sources_string_names(self.sources)
- values = get_filenames_templates_dict(inputs, [])
- for i in self.outputs:
- if not(isinstance(i, str)):
- raise InvalidArguments('Output argument not a string.')
- if i == '':
- raise InvalidArguments('Output must not be empty.')
- if i.strip() == '':
- raise InvalidArguments('Output must not consist only of whitespace.')
- if has_path_sep(i):
- raise InvalidArguments('Output {!r} must not contain a path segment.'.format(i))
- if '@INPUT@' in i or '@INPUT0@' in i:
- m = 'Output cannot contain @INPUT@ or @INPUT0@, did you ' \
- 'mean @PLAINNAME@ or @BASENAME@?'
- raise InvalidArguments(m)
- # We already check this during substitution, but the error message
- # will be unclear/confusing, so check it here.
- if len(inputs) != 1 and ('@PLAINNAME@' in i or '@BASENAME@' in i):
- m = "Output cannot contain @PLAINNAME@ or @BASENAME@ when " \
- "there is more than one input (we can't know which to use)"
- raise InvalidArguments(m)
- self.outputs = substitute_values(self.outputs, values)
- self.capture = kwargs.get('capture', False)
- if self.capture and len(self.outputs) != 1:
- raise InvalidArguments('Capturing can only output to a single file.')
- self.console = kwargs.get('console', False)
- if not isinstance(self.console, bool):
- raise InvalidArguments('"console" kwarg only accepts booleans')
- if self.capture and self.console:
- raise InvalidArguments("Can't both capture output and output to console")
- if 'command' not in kwargs:
- raise InvalidArguments('Missing keyword argument "command".')
- if 'depfile' in kwargs:
- depfile = kwargs['depfile']
- if not isinstance(depfile, str):
- raise InvalidArguments('Depfile must be a string.')
- if os.path.basename(depfile) != depfile:
- raise InvalidArguments('Depfile must be a plain filename without a subdirectory.')
- self.depfile = depfile
- self.command = self.flatten_command(kwargs['command'])
- if self.capture:
- for c in self.command:
- if isinstance(c, str) and '@OUTPUT@' in c:
- raise InvalidArguments('@OUTPUT@ is not allowed when capturing output.')
- if 'install' in kwargs:
- self.install = kwargs['install']
- if not isinstance(self.install, bool):
- raise InvalidArguments('"install" must be boolean.')
- if self.install:
- if 'install_dir' not in kwargs:
- raise InvalidArguments('"install_dir" must be specified '
- 'when installing a target')
- if isinstance(kwargs['install_dir'], list):
- FeatureNew('multiple install_dir for custom_target', '0.40.0').use(self.subproject)
- # 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['install_dir'], (str, bool))
- self.install_mode = kwargs.get('install_mode', None)
- else:
- self.install = False
- self.install_dir = [None]
- self.install_mode = None
- if 'build_always' in kwargs and 'build_always_stale' in kwargs:
- raise InvalidArguments('build_always and build_always_stale are mutually exclusive. Combine build_by_default and build_always_stale.')
- elif 'build_always' in kwargs:
- mlog.deprecation('build_always is deprecated. Combine build_by_default and build_always_stale instead.')
- if 'build_by_default' not in kwargs:
- self.build_by_default = kwargs['build_always']
- self.build_always_stale = kwargs['build_always']
- elif 'build_always_stale' in kwargs:
- self.build_always_stale = kwargs['build_always_stale']
- if not isinstance(self.build_always_stale, bool):
- raise InvalidArguments('Argument build_always_stale must be a boolean.')
- extra_deps, depend_files = extract_as_list(kwargs, 'depends', 'depend_files', pop = False)
- for ed in extra_deps:
- while hasattr(ed, 'held_object'):
- ed = ed.held_object
- if not isinstance(ed, (CustomTarget, BuildTarget)):
- raise InvalidArguments('Can only depend on toplevel targets: custom_target or build_target (executable or a library) got: %s(%s)'
- % (type(ed), ed))
- self.extra_depends.append(ed)
- for i in depend_files:
- if isinstance(i, (File, str)):
- self.depend_files.append(i)
- else:
- mlog.debug(i)
- raise InvalidArguments('Unknown type {!r} in depend_files.'.format(type(i).__name__))
- def get_dependencies(self):
- return self.dependencies
- def should_install(self):
- return self.install
- def get_custom_install_dir(self):
- return self.install_dir
- def get_custom_install_mode(self):
- return self.install_mode
- def get_outputs(self):
- return self.outputs
- def get_filename(self):
- return self.outputs[0]
- def get_sources(self):
- return self.sources
- def get_generated_lists(self):
- genlists = []
- for c in self.sources:
- if hasattr(c, 'held_object'):
- c = c.held_object
- if isinstance(c, GeneratedList):
- genlists.append(c)
- return genlists
- def get_generated_sources(self):
- 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 len(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 type_suffix(self):
- return "@cus"
- def __getitem__(self, index):
- return CustomTargetIndex(self, self.outputs[index])
- def __setitem__(self, index, value):
- raise NotImplementedError
- def __delitem__(self, index):
- raise NotImplementedError
- class RunTarget(Target):
- def __init__(self, name, command, args, dependencies, subdir, subproject):
- self.typename = 'run'
- super().__init__(name, subdir, subproject, False)
- self.command = command
- self.args = args
- self.dependencies = dependencies
- def __lt__(self, other):
- return self.get_id() < other.get_id()
- def __repr__(self):
- repr_str = "<{0} {1}: {2}>"
- return repr_str.format(self.__class__.__name__, self.get_id(), self.command)
- def get_dependencies(self):
- return self.dependencies
- def get_generated_sources(self):
- return []
- def get_sources(self):
- return []
- def should_install(self):
- return False
- def get_filename(self):
- return self.name
- def get_outputs(self):
- 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):
- return "@run"
- class Jar(BuildTarget):
- known_kwargs = known_jar_kwargs
- def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs):
- self.typename = 'jar'
- super().__init__(name, subdir, subproject, is_cross, sources, objects, environment, kwargs)
- for s in self.sources:
- if not s.endswith('.java'):
- raise InvalidArguments('Jar source %s is not a java file.' % s)
- for t in self.link_targets:
- if not isinstance(t, Jar):
- raise InvalidArguments('Link target %s is not a jar target.' % t)
- 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_cross_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 []
- class CustomTargetIndex:
- """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.
- """
- def __init__(self, target, output):
- self.typename = 'custom'
- self.target = target
- self.output = output
- def __repr__(self):
- return '<CustomTargetIndex: {!r}[{}]>'.format(
- self.target, self.target.get_outputs().index(self.output))
- def get_outputs(self):
- return [self.output]
- def get_subdir(self):
- return self.target.get_subdir()
- class ConfigureFile:
- def __init__(self, subdir, sourcename, targetname, configuration_data):
- self.subdir = subdir
- self.sourcename = sourcename
- self.targetname = targetname
- self.configuration_data = configuration_data
- def __repr__(self):
- repr_str = "<{0}: {1} -> {2}>"
- src = os.path.join(self.subdir, self.sourcename)
- dst = os.path.join(self.subdir, self.targetname)
- return repr_str.format(self.__class__.__name__, src, dst)
- def get_configuration_data(self):
- return self.configuration_data
- def get_subdir(self):
- return self.subdir
- def get_source_name(self):
- return self.sourcename
- def get_target_name(self):
- return self.targetname
- class ConfigurationData:
- def __init__(self):
- super().__init__()
- self.values = {}
- def __repr__(self):
- return repr(self.values)
- def __contains__(self, value):
- return value in self.values
- def get(self, name):
- return self.values[name] # (val, desc)
- def keys(self):
- return self.values.keys()
- # A bit poorly named, but this represents plain data files to copy
- # during install.
- class Data:
- def __init__(self, sources, install_dir, install_mode=None, rename=None):
- self.sources = sources
- self.install_dir = install_dir
- self.install_mode = install_mode
- self.sources = listify(self.sources)
- for s in self.sources:
- assert(isinstance(s, File))
- if rename is None:
- self.rename = [os.path.basename(f.fname) for f in self.sources]
- else:
- self.rename = stringlistify(rename)
- if len(self.rename) != len(self.sources):
- raise MesonException('Size of rename argument is different from number of sources')
- class RunScript(dict):
- def __init__(self, script, args):
- super().__init__()
- assert(isinstance(script, list))
- assert(isinstance(args, list))
- self['exe'] = script
- self['args'] = args
- class TestSetup:
- def __init__(self, *, exe_wrapper=None, gdb=None, timeout_multiplier=None, env=None):
- self.exe_wrapper = exe_wrapper
- self.gdb = gdb
- self.timeout_multiplier = timeout_multiplier
- self.env = env
- def get_sources_string_names(sources):
- '''
- For the specified list of @sources which can be strings, Files, or targets,
- get all the output basenames.
- '''
- names = []
- for s in sources:
- if hasattr(s, 'held_object'):
- s = s.held_object
- if isinstance(s, str):
- names.append(s)
- elif isinstance(s, (BuildTarget, CustomTarget, CustomTargetIndex, GeneratedList)):
- names += s.get_outputs()
- elif isinstance(s, File):
- names.append(s.fname)
- else:
- raise AssertionError('Unknown source type: {!r}'.format(s))
- return names
- def load(build_dir):
- filename = os.path.join(build_dir, 'meson-private', 'build.dat')
- load_fail_msg = 'Build data file {!r} is corrupted. Try with a fresh build tree.'.format(filename)
- nonexisting_fail_msg = 'No such build data file as "{!r}".'.format(filename)
- try:
- with open(filename, 'rb') as f:
- obj = pickle.load(f)
- except FileNotFoundError:
- raise MesonException(nonexisting_fail_msg)
- except pickle.UnpicklingError:
- raise MesonException(load_fail_msg)
- if not isinstance(obj, Build):
- raise MesonException(load_fail_msg)
- return obj
- def save(obj, filename):
- with open(filename, 'wb') as f:
- pickle.dump(obj, f)
|