12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561 |
- # 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 os, pickle, re, shlex, shutil, subprocess, sys
- from collections import OrderedDict
- from . import backends
- from .. import modules
- from .. import environment, mesonlib
- from .. import build
- from .. import mlog
- from .. import dependencies
- from .. import compilers
- from ..compilers import CompilerArgs
- from ..linkers import ArLinker
- from ..mesonlib import File, MesonException, OrderedSet
- from ..mesonlib import get_meson_script, get_compiler_for_source
- from .backends import CleanTrees, InstallData
- from ..build import InvalidArguments
- if mesonlib.is_windows():
- quote_func = lambda s: '"{}"'.format(s)
- execute_wrapper = 'cmd /c'
- rmfile_prefix = 'del /f /s /q {} &&'
- else:
- quote_func = shlex.quote
- execute_wrapper = ''
- rmfile_prefix = 'rm -f {} &&'
- def ninja_quote(text):
- for char in ('$', ' ', ':'):
- text = text.replace(char, '$' + char)
- if '\n' in text:
- raise MesonException('Ninja does not support newlines in rules. '
- 'Please report this error with a test case to the Meson bug tracker.')
- return text
- class NinjaBuildElement:
- def __init__(self, all_outputs, outfilenames, rule, infilenames):
- if isinstance(outfilenames, str):
- self.outfilenames = [outfilenames]
- else:
- self.outfilenames = outfilenames
- assert(isinstance(rule, str))
- self.rule = rule
- if isinstance(infilenames, str):
- self.infilenames = [infilenames]
- else:
- self.infilenames = infilenames
- self.deps = set()
- self.orderdeps = set()
- self.elems = []
- self.all_outputs = all_outputs
- def add_dep(self, dep):
- if isinstance(dep, list):
- self.deps.update(dep)
- else:
- self.deps.add(dep)
- def add_orderdep(self, dep):
- if isinstance(dep, list):
- self.orderdeps.update(dep)
- else:
- self.orderdeps.add(dep)
- def add_item(self, name, elems):
- if isinstance(elems, str):
- elems = [elems]
- self.elems.append((name, elems))
- def write(self, outfile):
- self.check_outputs()
- line = 'build %s: %s %s' % (
- ' '.join([ninja_quote(i) for i in self.outfilenames]),
- self.rule,
- ' '.join([ninja_quote(i) for i in self.infilenames]))
- if len(self.deps) > 0:
- line += ' | ' + ' '.join([ninja_quote(x) for x in self.deps])
- if len(self.orderdeps) > 0:
- line += ' || ' + ' '.join([ninja_quote(x) for x in self.orderdeps])
- line += '\n'
- # This is the only way I could find to make this work on all
- # platforms including Windows command shell. Slash is a dir separator
- # on Windows, too, so all characters are unambiguous and, more importantly,
- # do not require quoting.
- line = line.replace('\\', '/')
- outfile.write(line)
- # All the entries that should remain unquoted
- raw_names = {'DEPFILE', 'DESC', 'pool', 'description'}
- for e in self.elems:
- (name, elems) = e
- should_quote = name not in raw_names
- line = ' %s = ' % name
- noq_templ = "%s"
- newelems = []
- for i in elems:
- if not should_quote or i == '&&': # Hackety hack hack
- quoter = ninja_quote
- else:
- quoter = lambda x: ninja_quote(quote_func(x))
- i = i.replace('\\', '\\\\')
- if quote_func('') == '""':
- i = i.replace('"', '\\"')
- newelems.append(quoter(i))
- line += ' '.join(newelems)
- line += '\n'
- outfile.write(line)
- outfile.write('\n')
- def check_outputs(self):
- for n in self.outfilenames:
- if n in self.all_outputs:
- raise MesonException('Multiple producers for Ninja target "%s". Please rename your targets.' % n)
- self.all_outputs[n] = True
- class NinjaBackend(backends.Backend):
- def __init__(self, build):
- super().__init__(build)
- self.name = 'ninja'
- self.ninja_filename = 'build.ninja'
- self.target_arg_cache = {}
- self.fortran_deps = {}
- self.all_outputs = {}
- def detect_vs_dep_prefix(self, tempfilename):
- '''VS writes its dependency in a locale dependent format.
- Detect the search prefix to use.'''
- # Of course there is another program called 'cl' on
- # some platforms. Let's just require that on Windows
- # cl points to msvc.
- if not mesonlib.is_windows() or shutil.which('cl') is None:
- return open(tempfilename, 'a')
- filename = os.path.join(self.environment.get_scratch_dir(),
- 'incdetect.c')
- with open(filename, 'w') as f:
- f.write('''#include<stdio.h>
- int dummy;
- ''')
- # The output of cl dependency information is language
- # and locale dependent. Any attempt at converting it to
- # Python strings leads to failure. We _must_ do this detection
- # in raw byte mode and write the result in raw bytes.
- pc = subprocess.Popen(['cl', '/showIncludes', '/c', 'incdetect.c'],
- cwd=self.environment.get_scratch_dir(),
- stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- (stdo, _) = pc.communicate()
- # We want to match 'Note: including file: ' in the line
- # 'Note: including file: d:\MyDir\include\stdio.h', however
- # different locales have different messages with a different
- # number of colons. Match up to the the drive name 'd:\'.
- matchre = re.compile(rb"^(.*\s)[a-zA-Z]:\\.*stdio.h$")
- for line in stdo.split(b'\r\n'):
- match = matchre.match(line)
- if match:
- with open(tempfilename, 'ab') as binfile:
- binfile.write(b'msvc_deps_prefix = ' + match.group(1) + b'\n')
- return open(tempfilename, 'a')
- raise MesonException('Could not determine vs dep dependency prefix string.')
- def generate(self, interp):
- self.interpreter = interp
- outfilename = os.path.join(self.environment.get_build_dir(), self.ninja_filename)
- tempfilename = outfilename + '~'
- with open(tempfilename, 'w') as outfile:
- outfile.write('# This is the build file for project "%s"\n' %
- self.build.get_project())
- outfile.write('# It is autogenerated by the Meson build system.\n')
- outfile.write('# Do not edit by hand.\n\n')
- outfile.write('ninja_required_version = 1.5.1\n\n')
- with self.detect_vs_dep_prefix(tempfilename) as outfile:
- self.generate_rules(outfile)
- self.generate_phony(outfile)
- outfile.write('# Build rules for targets\n\n')
- for t in self.build.get_targets().values():
- self.generate_target(t, outfile)
- outfile.write('# Test rules\n\n')
- self.generate_tests(outfile)
- outfile.write('# Install rules\n\n')
- self.generate_install(outfile)
- self.generate_dist(outfile)
- if 'b_coverage' in self.environment.coredata.base_options and \
- self.environment.coredata.base_options['b_coverage'].value:
- outfile.write('# Coverage rules\n\n')
- self.generate_coverage_rules(outfile)
- outfile.write('# Suffix\n\n')
- self.generate_utils(outfile)
- self.generate_ending(outfile)
- # Only ovewrite the old build file after the new one has been
- # fully created.
- os.replace(tempfilename, outfilename)
- self.generate_compdb()
- # http://clang.llvm.org/docs/JSONCompilationDatabase.html
- def generate_compdb(self):
- ninja_exe = environment.detect_ninja()
- pch_compilers = ['%s_PCH' % i for i in self.build.compilers]
- native_compilers = ['%s_COMPILER' % i for i in self.build.compilers]
- cross_compilers = ['%s_CROSS_COMPILER' % i for i in self.build.cross_compilers]
- ninja_compdb = [ninja_exe, '-t', 'compdb'] + pch_compilers + native_compilers + cross_compilers
- builddir = self.environment.get_build_dir()
- try:
- jsondb = subprocess.check_output(ninja_compdb, cwd=builddir)
- with open(os.path.join(builddir, 'compile_commands.json'), 'wb') as f:
- f.write(jsondb)
- except Exception:
- mlog.warning('Could not create compilation database.')
- # Get all generated headers. Any source file might need them so
- # we need to add an order dependency to them.
- def get_generated_headers(self, target):
- header_deps = []
- # XXX: Why don't we add deps to CustomTarget headers here?
- for genlist in target.get_generated_sources():
- if isinstance(genlist, build.CustomTarget):
- continue
- for src in genlist.get_outputs():
- if self.environment.is_header(src):
- header_deps.append(self.get_target_generated_dir(target, genlist, src))
- if 'vala' in target.compilers and not isinstance(target, build.Executable):
- vala_header = File.from_built_file(self.get_target_dir(target), target.vala_header)
- header_deps.append(vala_header)
- # Recurse and find generated headers
- for dep in target.link_targets:
- if isinstance(dep, (build.StaticLibrary, build.SharedLibrary)):
- header_deps += self.get_generated_headers(dep)
- return header_deps
- def get_target_generated_sources(self, target):
- """
- Returns a dictionary with the keys being the path to the file
- (relative to the build directory) of that type and the value
- being the GeneratorList or CustomTarget that generated it.
- """
- srcs = OrderedDict()
- for gensrc in target.get_generated_sources():
- for s in gensrc.get_outputs():
- f = self.get_target_generated_dir(target, gensrc, s)
- srcs[f] = s
- return srcs
- def get_target_sources(self, target):
- srcs = OrderedDict()
- for s in target.get_sources():
- # BuildTarget sources are always mesonlib.File files which are
- # either in the source root, or generated with configure_file and
- # in the build root
- if not isinstance(s, File):
- raise InvalidArguments('All sources in target {!r} must be of type mesonlib.File'.format(s))
- f = s.rel_to_builddir(self.build_to_src)
- srcs[f] = s
- return srcs
- # Languages that can mix with C or C++ but don't support unity builds yet
- # because the syntax we use for unity builds is specific to C/++/ObjC/++.
- # Assembly files cannot be unitified and neither can LLVM IR files
- langs_cant_unity = ('d', 'fortran')
- def get_target_source_can_unity(self, target, source):
- if isinstance(source, File):
- source = source.fname
- if self.environment.is_llvm_ir(source) or \
- self.environment.is_assembly(source):
- return False
- suffix = os.path.splitext(source)[1][1:]
- for lang in self.langs_cant_unity:
- if lang not in target.compilers:
- continue
- if suffix in target.compilers[lang].file_suffixes:
- return False
- return True
- def generate_target(self, target, outfile):
- if isinstance(target, build.CustomTarget):
- self.generate_custom_target(target, outfile)
- if isinstance(target, build.RunTarget):
- self.generate_run_target(target, outfile)
- name = target.get_id()
- if name in self.processed_targets:
- return
- self.processed_targets[name] = True
- # Generate rules for all dependency targets
- self.process_target_dependencies(target, outfile)
- # If target uses a language that cannot link to C objects,
- # just generate for that language and return.
- if isinstance(target, build.Jar):
- self.generate_jar_target(target, outfile)
- return
- if 'rust' in target.compilers:
- self.generate_rust_target(target, outfile)
- return
- if 'cs' in target.compilers:
- self.generate_cs_target(target, outfile)
- return
- if 'swift' in target.compilers:
- self.generate_swift_target(target, outfile)
- return
- # Now we handle the following languages:
- # ObjC++, ObjC, C++, C, D, Fortran, Vala
- # target_sources:
- # Pre-existing target C/C++ sources to be built; dict of full path to
- # source relative to build root and the original File object.
- # generated_sources:
- # GeneratedList and CustomTarget sources to be built; dict of the full
- # path to source relative to build root and the generating target/list
- # vala_generated_sources:
- # Array of sources generated by valac that have to be compiled
- if 'vala' in target.compilers:
- # Sources consumed by valac are filtered out. These only contain
- # C/C++ sources, objects, generated libs, and unknown sources now.
- target_sources, generated_sources, \
- vala_generated_sources = self.generate_vala_compile(target, outfile)
- else:
- target_sources = self.get_target_sources(target)
- generated_sources = self.get_target_generated_sources(target)
- vala_generated_sources = []
- self.scan_fortran_module_outputs(target)
- # Generate rules for GeneratedLists
- self.generate_generator_list_rules(target, outfile)
- # Generate rules for building the remaining source files in this target
- outname = self.get_target_filename(target)
- obj_list = []
- use_pch = self.environment.coredata.base_options.get('b_pch', False)
- is_unity = self.is_unity(target)
- if use_pch and target.has_pch():
- pch_objects = self.generate_pch(target, outfile)
- else:
- pch_objects = []
- header_deps = []
- unity_src = []
- unity_deps = [] # Generated sources that must be built before compiling a Unity target.
- header_deps += self.get_generated_headers(target)
- if is_unity:
- # Warn about incompatible sources if a unity build is enabled
- langs = set(target.compilers.keys())
- langs_cant = langs.intersection(self.langs_cant_unity)
- if langs_cant:
- langs_are = langs = ', '.join(langs_cant).upper()
- langs_are += ' are' if len(langs_cant) > 1 else ' is'
- msg = '{} not supported in Unity builds yet, so {} ' \
- 'sources in the {!r} target will be compiled normally' \
- ''.format(langs_are, langs, target.name)
- mlog.log(mlog.red('FIXME'), msg)
- # Get a list of all generated headers that will be needed while building
- # this target's sources (generated sources and pre-existing sources).
- # This will be set as dependencies of all the target's sources. At the
- # same time, also deal with generated sources that need to be compiled.
- generated_source_files = []
- for rel_src, gensrc in generated_sources.items():
- dirpart, fnamepart = os.path.split(rel_src)
- raw_src = File(True, dirpart, fnamepart)
- if self.environment.is_source(rel_src) and not self.environment.is_header(rel_src):
- if is_unity and self.get_target_source_can_unity(target, rel_src):
- unity_deps.append(raw_src)
- abs_src = os.path.join(self.environment.get_build_dir(), rel_src)
- unity_src.append(abs_src)
- else:
- generated_source_files.append(raw_src)
- elif self.environment.is_object(rel_src):
- obj_list.append(rel_src)
- elif self.environment.is_library(rel_src):
- pass
- else:
- # Assume anything not specifically a source file is a header. This is because
- # people generate files with weird suffixes (.inc, .fh) that they then include
- # in their source files.
- header_deps.append(raw_src)
- # These are the generated source files that need to be built for use by
- # this target. We create the Ninja build file elements for this here
- # because we need `header_deps` to be fully generated in the above loop.
- for src in generated_source_files:
- if self.environment.is_llvm_ir(src):
- o = self.generate_llvm_ir_compile(target, outfile, src)
- else:
- o = self.generate_single_compile(target, outfile, src, True,
- header_deps=header_deps)
- obj_list.append(o)
- # Generate compilation targets for C sources generated from Vala
- # sources. This can be extended to other $LANG->C compilers later if
- # necessary. This needs to be separate for at least Vala
- vala_generated_source_files = []
- for src in vala_generated_sources:
- dirpart, fnamepart = os.path.split(src)
- raw_src = File(True, dirpart, fnamepart)
- if is_unity:
- unity_src.append(os.path.join(self.environment.get_build_dir(), src))
- header_deps.append(raw_src)
- else:
- # Generated targets are ordered deps because the must exist
- # before the sources compiling them are used. After the first
- # compile we get precise dependency info from dep files.
- # This should work in all cases. If it does not, then just
- # move them from orderdeps to proper deps.
- if self.environment.is_header(src):
- header_deps.append(raw_src)
- else:
- # We gather all these and generate compile rules below
- # after `header_deps` (above) is fully generated
- vala_generated_source_files.append(raw_src)
- for src in vala_generated_source_files:
- # Passing 'vala' here signifies that we want the compile
- # arguments to be specialized for C code generated by
- # valac. For instance, no warnings should be emitted.
- obj_list.append(self.generate_single_compile(target, outfile, src, 'vala', [], header_deps))
- # Generate compile targets for all the pre-existing sources for this target
- for f, src in target_sources.items():
- if not self.environment.is_header(src):
- if self.environment.is_llvm_ir(src):
- obj_list.append(self.generate_llvm_ir_compile(target, outfile, src))
- elif is_unity and self.get_target_source_can_unity(target, src):
- abs_src = os.path.join(self.environment.get_build_dir(),
- src.rel_to_builddir(self.build_to_src))
- unity_src.append(abs_src)
- else:
- obj_list.append(self.generate_single_compile(target, outfile, src, False, [], header_deps))
- obj_list += self.flatten_object_list(target)
- if is_unity:
- for src in self.generate_unity_files(target, unity_src):
- obj_list.append(self.generate_single_compile(target, outfile, src, True, unity_deps + header_deps))
- linker = self.determine_linker(target)
- elem = self.generate_link(target, outfile, outname, obj_list, linker, pch_objects)
- self.generate_shlib_aliases(target, self.get_target_dir(target))
- elem.write(outfile)
- def process_target_dependencies(self, target, outfile):
- for t in target.get_dependencies():
- tname = t.get_basename() + t.type_suffix()
- if tname not in self.processed_targets:
- self.generate_target(t, outfile)
- def custom_target_generator_inputs(self, target, outfile):
- for s in target.sources:
- if hasattr(s, 'held_object'):
- s = s.held_object
- if isinstance(s, build.GeneratedList):
- self.generate_genlist_for_target(s, target, outfile)
- def unwrap_dep_list(self, target):
- deps = []
- for i in target.get_dependencies():
- # FIXME, should not grab element at zero but rather expand all.
- if isinstance(i, list):
- i = i[0]
- # Add a dependency on all the outputs of this target
- for output in i.get_outputs():
- deps.append(os.path.join(self.get_target_dir(i), output))
- return deps
- def generate_custom_target(self, target, outfile):
- self.custom_target_generator_inputs(target, outfile)
- (srcs, ofilenames, cmd) = self.eval_custom_target_command(target)
- deps = self.unwrap_dep_list(target)
- deps += self.get_custom_target_depend_files(target)
- desc = 'Generating {0} with a {1} command.'
- if target.build_always:
- deps.append('PHONY')
- if target.depfile is None:
- rulename = 'CUSTOM_COMMAND'
- else:
- rulename = 'CUSTOM_COMMAND_DEP'
- elem = NinjaBuildElement(self.all_outputs, ofilenames, rulename, srcs)
- elem.add_dep(deps)
- for d in target.extra_depends:
- # Add a dependency on all the outputs of this target
- for output in d.get_outputs():
- elem.add_dep(os.path.join(self.get_target_dir(d), output))
- # If the target requires capturing stdout, then use the serialized
- # executable wrapper to capture that output and save it to a file.
- #
- # If the command line requires a newline, also use the wrapper, as
- # ninja does not support them in its build rule syntax.
- #
- # Windows doesn't have -rpath, so for EXEs that need DLLs built within
- # the project, we need to set PATH so the DLLs are found. We use
- # a serialized executable wrapper for that and check if the
- # CustomTarget command needs extra paths first.
- if (target.capture or any('\n' in c for c in cmd) or
- ((mesonlib.is_windows() or mesonlib.is_cygwin()) and
- self.determine_windows_extra_paths(target.command[0]))):
- exe_data = self.serialize_executable(target.command[0], cmd[1:],
- # All targets are built from the build dir
- self.environment.get_build_dir(),
- capture=ofilenames[0] if target.capture else None)
- cmd = [sys.executable, self.environment.get_build_command(),
- '--internal', 'exe', exe_data]
- cmd_type = 'meson_exe.py custom'
- else:
- cmd_type = 'custom'
- if target.depfile is not None:
- rel_dfile = os.path.join(self.get_target_dir(target), target.depfile)
- abs_pdir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target))
- os.makedirs(abs_pdir, exist_ok=True)
- elem.add_item('DEPFILE', rel_dfile)
- cmd = self.replace_paths(target, cmd)
- elem.add_item('COMMAND', cmd)
- elem.add_item('description', desc.format(target.name, cmd_type))
- elem.write(outfile)
- self.processed_targets[target.name + target.type_suffix()] = True
- def generate_run_target(self, target, outfile):
- cmd = [sys.executable, self.environment.get_build_command(), '--internal', 'commandrunner']
- deps = self.unwrap_dep_list(target)
- arg_strings = []
- for i in target.args:
- if isinstance(i, str):
- arg_strings.append(i)
- elif isinstance(i, (build.BuildTarget, build.CustomTarget)):
- relfname = self.get_target_filename(i)
- arg_strings.append(os.path.join(self.environment.get_build_dir(), relfname))
- deps.append(relfname)
- elif isinstance(i, mesonlib.File):
- relfname = i.rel_to_builddir(self.build_to_src)
- arg_strings.append(os.path.join(self.environment.get_build_dir(), relfname))
- else:
- raise AssertionError('Unreachable code in generate_run_target: ' + str(i))
- elem = NinjaBuildElement(self.all_outputs, target.name, 'CUSTOM_COMMAND', [])
- cmd += [self.environment.get_source_dir(),
- self.environment.get_build_dir(),
- target.subdir,
- get_meson_script(self.environment, 'mesonintrospect')]
- texe = target.command
- try:
- texe = texe.held_object
- except AttributeError:
- pass
- if isinstance(texe, build.Executable):
- abs_exe = os.path.join(self.environment.get_build_dir(), self.get_target_filename(texe))
- deps.append(self.get_target_filename(texe))
- if self.environment.is_cross_build() and \
- self.environment.cross_info.need_exe_wrapper():
- exe_wrap = self.environment.cross_info.config['binaries'].get('exe_wrapper', None)
- if exe_wrap is not None:
- cmd += [exe_wrap]
- cmd.append(abs_exe)
- elif isinstance(texe, dependencies.ExternalProgram):
- cmd += texe.get_command()
- elif isinstance(texe, build.CustomTarget):
- deps.append(self.get_target_filename(texe))
- cmd += [os.path.join(self.environment.get_build_dir(), self.get_target_filename(texe))]
- elif isinstance(texe, mesonlib.File):
- cmd.append(texe.absolute_path(self.environment.get_source_dir(), self.environment.get_build_dir()))
- else:
- cmd.append(target.command)
- cmd += arg_strings
- elem.add_dep(deps)
- cmd = self.replace_paths(target, cmd)
- elem.add_item('COMMAND', cmd)
- elem.add_item('description', 'Running external command %s.' % target.name)
- elem.add_item('pool', 'console')
- elem.write(outfile)
- self.processed_targets[target.name + target.type_suffix()] = True
- def generate_coverage_rules(self, outfile):
- e = NinjaBuildElement(self.all_outputs, 'coverage', 'CUSTOM_COMMAND', 'PHONY')
- e.add_item('COMMAND', [sys.executable,
- self.environment.get_build_command(),
- '--internal', 'coverage',
- self.environment.get_source_dir(),
- self.environment.get_build_dir(),
- self.environment.get_log_dir()])
- e.add_item('description', 'Generates coverage reports.')
- e.write(outfile)
- self.generate_coverage_legacy_rules(outfile)
- def generate_coverage_legacy_rules(self, outfile):
- (gcovr_exe, lcov_exe, genhtml_exe) = environment.find_coverage_tools()
- added_rule = False
- if gcovr_exe:
- added_rule = True
- elem = NinjaBuildElement(self.all_outputs, 'coverage-xml', 'CUSTOM_COMMAND', '')
- elem.add_item('COMMAND', [gcovr_exe, '-x', '-r', self.environment.get_source_dir(),
- '-o', os.path.join(self.environment.get_log_dir(), 'coverage.xml')])
- elem.add_item('DESC', 'Generating XML coverage report.')
- elem.write(outfile)
- elem = NinjaBuildElement(self.all_outputs, 'coverage-text', 'CUSTOM_COMMAND', '')
- elem.add_item('COMMAND', [gcovr_exe, '-r', self.environment.get_source_dir(),
- '-o', os.path.join(self.environment.get_log_dir(), 'coverage.txt')])
- elem.add_item('DESC', 'Generating text coverage report.')
- elem.write(outfile)
- if lcov_exe and genhtml_exe:
- added_rule = True
- htmloutdir = os.path.join(self.environment.get_log_dir(), 'coveragereport')
- covinfo = os.path.join(self.environment.get_log_dir(), 'coverage.info')
- phony_elem = NinjaBuildElement(self.all_outputs, 'coverage-html', 'phony', os.path.join(htmloutdir, 'index.html'))
- phony_elem.write(outfile)
- elem = NinjaBuildElement(self.all_outputs, os.path.join(htmloutdir, 'index.html'), 'CUSTOM_COMMAND', '')
- command = [lcov_exe, '--directory', self.environment.get_build_dir(),
- '--capture', '--output-file', covinfo, '--no-checksum',
- '&&', genhtml_exe, '--prefix', self.environment.get_build_dir(),
- '--output-directory', htmloutdir, '--title', 'Code coverage',
- '--legend', '--show-details', covinfo]
- elem.add_item('COMMAND', command)
- elem.add_item('DESC', 'Generating HTML coverage report.')
- elem.write(outfile)
- if not added_rule:
- mlog.warning('coverage requested but neither gcovr nor lcov/genhtml found.')
- def generate_install(self, outfile):
- install_data_file = os.path.join(self.environment.get_scratch_dir(), 'install.dat')
- if self.environment.is_cross_build():
- bins = self.environment.cross_info.config['binaries']
- if 'strip' not in bins:
- mlog.warning('Cross file does not specify strip binary, result will not be stripped.')
- strip_bin = None
- else:
- strip_bin = mesonlib.stringlistify(bins['strip'])
- else:
- strip_bin = self.environment.native_strip_bin
- d = InstallData(self.environment.get_source_dir(),
- self.environment.get_build_dir(),
- self.environment.get_prefix(),
- strip_bin,
- get_meson_script(self.environment, 'mesonintrospect'))
- elem = NinjaBuildElement(self.all_outputs, 'install', 'CUSTOM_COMMAND', 'PHONY')
- elem.add_dep('all')
- elem.add_item('DESC', 'Installing files.')
- elem.add_item('COMMAND', [sys.executable, self.environment.get_build_command(), '--internal', 'install', install_data_file])
- elem.add_item('pool', 'console')
- self.generate_depmf_install(d)
- self.generate_target_install(d)
- self.generate_header_install(d)
- self.generate_man_install(d)
- self.generate_data_install(d)
- self.generate_custom_install_script(d)
- self.generate_subdir_install(d)
- elem.write(outfile)
- with open(install_data_file, 'wb') as ofile:
- pickle.dump(d, ofile)
- def generate_target_install(self, d):
- for t in self.build.get_targets().values():
- if not t.should_install():
- continue
- # Find the installation directory.
- outdirs = t.get_custom_install_dir()
- custom_install_dir = False
- if outdirs[0] is not None and outdirs[0] is not True:
- # Either the value is set, or is set to False which means
- # we want this specific output out of many outputs to not
- # be installed.
- custom_install_dir = True
- elif isinstance(t, build.SharedModule):
- outdirs[0] = self.environment.get_shared_module_dir()
- elif isinstance(t, build.SharedLibrary):
- outdirs[0] = self.environment.get_shared_lib_dir()
- elif isinstance(t, build.StaticLibrary):
- outdirs[0] = self.environment.get_static_lib_dir()
- elif isinstance(t, build.Executable):
- outdirs[0] = self.environment.get_bindir()
- else:
- assert(isinstance(t, build.BuildTarget))
- # XXX: Add BuildTarget-specific install dir cases here
- outdirs[0] = self.environment.get_libdir()
- # Sanity-check the outputs and install_dirs
- num_outdirs, num_out = len(outdirs), len(t.get_outputs())
- if num_outdirs != 1 and num_outdirs != num_out:
- m = 'Target {!r} has {} outputs: {!r}, but only {} "install_dir"s were found.\n' \
- "Pass 'false' for outputs that should not be installed and 'true' for\n" \
- 'using the default installation directory for an output.'
- raise MesonException(m.format(t.name, num_out, t.get_outputs(), num_outdirs))
- # Install the target output(s)
- if isinstance(t, build.BuildTarget):
- should_strip = self.get_option_for_target('strip', t)
- # Install primary build output (library/executable/jar, etc)
- # Done separately because of strip/aliases/rpath
- if outdirs[0] is not False:
- i = [self.get_target_filename(t), outdirs[0],
- t.get_aliases(), should_strip, t.install_rpath]
- d.targets.append(i)
- # On toolchains/platforms that use an import library for
- # linking (separate from the shared library with all the
- # code), we need to install that too (dll.a/.lib).
- if (isinstance(t, build.SharedLibrary) or
- isinstance(t, build.Executable)) and t.get_import_filename():
- if custom_install_dir:
- # If the DLL is installed into a custom directory,
- # install the import library into the same place so
- # it doesn't go into a surprising place
- implib_install_dir = outdirs[0]
- else:
- implib_install_dir = self.environment.get_import_lib_dir()
- # Install the import library.
- i = [self.get_target_filename_for_linking(t),
- implib_install_dir,
- # It has no aliases, should not be stripped, and
- # doesn't have an install_rpath
- {}, False, '']
- d.targets.append(i)
- # Install secondary outputs. Only used for Vala right now.
- if num_outdirs > 1:
- for output, outdir in zip(t.get_outputs()[1:], outdirs[1:]):
- # User requested that we not install this output
- if outdir is False:
- continue
- f = os.path.join(self.get_target_dir(t), output)
- d.targets.append([f, outdir, {}, False, None])
- elif isinstance(t, build.CustomTarget):
- # If only one install_dir is specified, assume that all
- # outputs will be installed into it. This is for
- # backwards-compatibility and because it makes sense to
- # avoid repetition since this is a common use-case.
- #
- # To selectively install only some outputs, pass `false` as
- # the install_dir for the corresponding output by index
- if num_outdirs == 1 and num_out > 1:
- for output in t.get_outputs():
- f = os.path.join(self.get_target_dir(t), output)
- d.targets.append([f, outdirs[0], {}, False, None])
- else:
- for output, outdir in zip(t.get_outputs(), outdirs):
- # User requested that we not install this output
- if outdir is False:
- continue
- f = os.path.join(self.get_target_dir(t), output)
- d.targets.append([f, outdir, {}, False, None])
- def generate_custom_install_script(self, d):
- result = []
- srcdir = self.environment.get_source_dir()
- builddir = self.environment.get_build_dir()
- for i in self.build.install_scripts:
- exe = i['exe']
- args = i['args']
- fixed_args = []
- for a in args:
- a = a.replace('@SOURCE_ROOT@', srcdir)
- a = a.replace('@BUILD_ROOT@', builddir)
- fixed_args.append(a)
- result.append(build.RunScript(exe, fixed_args))
- d.install_scripts = result
- def generate_header_install(self, d):
- incroot = self.environment.get_includedir()
- headers = self.build.get_headers()
- srcdir = self.environment.get_source_dir()
- builddir = self.environment.get_build_dir()
- for h in headers:
- outdir = h.get_custom_install_dir()
- if outdir is None:
- outdir = os.path.join(incroot, h.get_install_subdir())
- for f in h.get_sources():
- if not isinstance(f, File):
- msg = 'Invalid header type {!r} can\'t be installed'
- raise MesonException(msg.format(f))
- abspath = f.absolute_path(srcdir, builddir)
- i = [abspath, outdir]
- d.headers.append(i)
- def generate_man_install(self, d):
- manroot = self.environment.get_mandir()
- man = self.build.get_man()
- for m in man:
- for f in m.get_sources():
- num = f.split('.')[-1]
- subdir = m.get_custom_install_dir()
- if subdir is None:
- subdir = os.path.join(manroot, 'man' + num)
- srcabs = os.path.join(self.environment.get_source_dir(), m.get_source_subdir(), f)
- dstabs = os.path.join(subdir, os.path.split(f)[1] + '.gz')
- i = [srcabs, dstabs]
- d.man.append(i)
- def generate_data_install(self, d):
- data = self.build.get_data()
- srcdir = self.environment.get_source_dir()
- builddir = self.environment.get_build_dir()
- for de in data:
- assert(isinstance(de, build.Data))
- subdir = de.install_dir
- for f in de.sources:
- assert(isinstance(f, mesonlib.File))
- plain_f = os.path.split(f.fname)[1]
- dstabs = os.path.join(subdir, plain_f)
- i = [f.absolute_path(srcdir, builddir), dstabs, de.install_mode]
- d.data.append(i)
- def generate_subdir_install(self, d):
- for sd in self.build.get_install_subdirs():
- inst_subdir = sd.installable_subdir.rstrip('/')
- idir_parts = inst_subdir.split('/')
- if len(idir_parts) > 1:
- subdir = os.path.join(sd.source_subdir, '/'.join(idir_parts[:-1]))
- inst_dir = idir_parts[-1]
- else:
- subdir = sd.source_subdir
- inst_dir = sd.installable_subdir
- src_dir = os.path.join(self.environment.get_source_dir(), subdir)
- dst_dir = os.path.join(self.environment.get_prefix(), sd.install_dir)
- d.install_subdirs.append([src_dir, inst_dir, dst_dir, sd.install_mode])
- def generate_tests(self, outfile):
- self.serialize_tests()
- test_exe = get_meson_script(self.environment, 'mesontest')
- cmd = [sys.executable, '-u', test_exe, '--no-rebuild']
- if not self.environment.coredata.get_builtin_option('stdsplit'):
- cmd += ['--no-stdsplit']
- if self.environment.coredata.get_builtin_option('errorlogs'):
- cmd += ['--print-errorlogs']
- elem = NinjaBuildElement(self.all_outputs, 'test', 'CUSTOM_COMMAND', ['all', 'PHONY'])
- elem.add_item('COMMAND', cmd)
- elem.add_item('DESC', 'Running all tests.')
- elem.add_item('pool', 'console')
- elem.write(outfile)
- # And then benchmarks.
- cmd = [sys.executable, '-u', test_exe, '--benchmark', '--logbase',
- 'benchmarklog', '--num-processes=1', '--no-rebuild']
- elem = NinjaBuildElement(self.all_outputs, 'benchmark', 'CUSTOM_COMMAND', ['all', 'PHONY'])
- elem.add_item('COMMAND', cmd)
- elem.add_item('DESC', 'Running benchmark suite.')
- elem.add_item('pool', 'console')
- elem.write(outfile)
- def generate_rules(self, outfile):
- outfile.write('# Rules for compiling.\n\n')
- self.generate_compile_rules(outfile)
- outfile.write('# Rules for linking.\n\n')
- if self.environment.is_cross_build():
- self.generate_static_link_rules(True, outfile)
- self.generate_static_link_rules(False, outfile)
- self.generate_dynamic_link_rules(outfile)
- outfile.write('# Other rules\n\n')
- outfile.write('rule CUSTOM_COMMAND\n')
- outfile.write(' command = $COMMAND\n')
- outfile.write(' description = $DESC\n')
- outfile.write(' restat = 1\n\n')
- # Ninja errors out if you have deps = gcc but no depfile, so we must
- # have two rules for custom commands.
- outfile.write('rule CUSTOM_COMMAND_DEP\n')
- outfile.write(' command = $COMMAND\n')
- outfile.write(' description = $DESC\n')
- outfile.write(' deps = gcc\n')
- outfile.write(' depfile = $DEPFILE\n')
- outfile.write(' restat = 1\n\n')
- outfile.write('rule REGENERATE_BUILD\n')
- c = (ninja_quote(quote_func(sys.executable)),
- ninja_quote(quote_func(self.environment.get_build_command())),
- '--internal',
- 'regenerate',
- ninja_quote(quote_func(self.environment.get_source_dir())),
- ninja_quote(quote_func(self.environment.get_build_dir())))
- outfile.write(" command = %s %s %s %s %s %s --backend ninja\n" % c)
- outfile.write(' description = Regenerating build files.\n')
- outfile.write(' generator = 1\n\n')
- outfile.write('\n')
- def generate_phony(self, outfile):
- outfile.write('# Phony build target, always out of date\n')
- outfile.write('build PHONY: phony\n')
- outfile.write('\n')
- def generate_jar_target(self, target, outfile):
- fname = target.get_filename()
- outname_rel = os.path.join(self.get_target_dir(target), fname)
- src_list = target.get_sources()
- class_list = []
- compiler = target.compilers['java']
- c = 'c'
- m = ''
- e = ''
- f = 'f'
- main_class = target.get_main_class()
- if main_class != '':
- e = 'e'
- for src in src_list:
- plain_class_path = self.generate_single_java_compile(src, target, compiler, outfile)
- class_list.append(plain_class_path)
- class_dep_list = [os.path.join(self.get_target_private_dir(target), i) for i in class_list]
- jar_rule = 'java_LINKER'
- commands = [c + m + e + f]
- if e != '':
- commands.append(main_class)
- commands.append(self.get_target_filename(target))
- # Java compilation can produce an arbitrary number of output
- # class files for a single source file. Thus tell jar to just
- # grab everything in the final package.
- commands += ['-C', self.get_target_private_dir(target), '.']
- elem = NinjaBuildElement(self.all_outputs, outname_rel, jar_rule, [])
- elem.add_dep(class_dep_list)
- elem.add_item('ARGS', commands)
- elem.write(outfile)
- def generate_cs_resource_tasks(self, target, outfile):
- args = []
- deps = []
- for r in target.resources:
- rel_sourcefile = os.path.join(self.build_to_src, target.subdir, r)
- if r.endswith('.resources'):
- a = '-resource:' + rel_sourcefile
- elif r.endswith('.txt') or r.endswith('.resx'):
- ofilebase = os.path.splitext(os.path.basename(r))[0] + '.resources'
- ofilename = os.path.join(self.get_target_private_dir(target), ofilebase)
- elem = NinjaBuildElement(self.all_outputs, ofilename, "CUSTOM_COMMAND", rel_sourcefile)
- elem.add_item('COMMAND', ['resgen', rel_sourcefile, ofilename])
- elem.add_item('DESC', 'Compiling resource %s.' % rel_sourcefile)
- elem.write(outfile)
- deps.append(ofilename)
- a = '-resource:' + ofilename
- else:
- raise InvalidArguments('Unknown resource file %s.' % r)
- args.append(a)
- return args, deps
- def generate_cs_target(self, target, outfile):
- buildtype = self.get_option_for_target('buildtype', target)
- fname = target.get_filename()
- outname_rel = os.path.join(self.get_target_dir(target), fname)
- src_list = target.get_sources()
- compiler = target.compilers['cs']
- rel_srcs = [s.rel_to_builddir(self.build_to_src) for s in src_list]
- deps = []
- commands = target.extra_args.get('cs', [])
- commands += compiler.get_buildtype_args(buildtype)
- if isinstance(target, build.Executable):
- commands.append('-target:exe')
- elif isinstance(target, build.SharedLibrary):
- commands.append('-target:library')
- else:
- raise MesonException('Unknown C# target type.')
- (resource_args, resource_deps) = self.generate_cs_resource_tasks(target, outfile)
- commands += resource_args
- deps += resource_deps
- commands += compiler.get_output_args(outname_rel)
- for l in target.link_targets:
- lname = os.path.join(self.get_target_dir(l), l.get_filename())
- commands += compiler.get_link_args(lname)
- deps.append(lname)
- if '-g' in commands:
- outputs = [outname_rel, outname_rel + '.mdb']
- else:
- outputs = [outname_rel]
- elem = NinjaBuildElement(self.all_outputs, outputs, 'cs_COMPILER', rel_srcs)
- elem.add_dep(deps)
- elem.add_item('ARGS', commands)
- elem.write(outfile)
- def generate_single_java_compile(self, src, target, compiler, outfile):
- args = []
- args += compiler.get_buildtype_args(self.get_option_for_target('buildtype', target))
- args += self.build.get_global_args(compiler)
- args += self.build.get_project_args(compiler, target.subproject)
- args += target.get_java_args()
- args += compiler.get_output_args(self.get_target_private_dir(target))
- for i in target.include_dirs:
- for idir in i.get_incdirs():
- args += ['-sourcepath', os.path.join(self.build_to_src, i.curdir, idir)]
- rel_src = src.rel_to_builddir(self.build_to_src)
- plain_class_path = src.fname[:-4] + 'class'
- rel_obj = os.path.join(self.get_target_private_dir(target), plain_class_path)
- element = NinjaBuildElement(self.all_outputs, rel_obj, compiler.get_language() + '_COMPILER', rel_src)
- element.add_item('ARGS', args)
- element.write(outfile)
- return plain_class_path
- def generate_java_link(self, outfile):
- rule = 'rule java_LINKER\n'
- command = ' command = jar $ARGS\n'
- description = ' description = Creating JAR $out.\n'
- outfile.write(rule)
- outfile.write(command)
- outfile.write(description)
- outfile.write('\n')
- def determine_dep_vapis(self, target):
- """
- Peek into the sources of BuildTargets we're linking with, and if any of
- them was built with Vala, assume that it also generated a .vapi file of
- the same name as the BuildTarget and return the path to it relative to
- the build directory.
- """
- result = OrderedSet()
- for dep in target.link_targets + target.link_whole_targets:
- for i in dep.sources:
- if hasattr(i, 'fname'):
- i = i.fname
- if i.endswith('vala'):
- vapiname = dep.name + '.vapi'
- fullname = os.path.join(self.get_target_dir(dep), vapiname)
- result.add(fullname)
- break
- return list(result)
- def split_vala_sources(self, t):
- """
- Splits the target's sources into .vala, .gs, .vapi, and other sources.
- Handles both pre-existing and generated sources.
- Returns a tuple (vala, vapi, others) each of which is a dictionary with
- the keys being the path to the file (relative to the build directory)
- and the value being the object that generated or represents the file.
- """
- vala = OrderedDict()
- vapi = OrderedDict()
- others = OrderedDict()
- othersgen = OrderedDict()
- # Split pre-existing sources
- for s in t.get_sources():
- # BuildTarget sources are always mesonlib.File files which are
- # either in the source root, or generated with configure_file and
- # in the build root
- if not isinstance(s, File):
- msg = 'All sources in target {!r} must be of type ' \
- 'mesonlib.File, not {!r}'.format(t, s)
- raise InvalidArguments(msg)
- f = s.rel_to_builddir(self.build_to_src)
- if s.endswith(('.vala', '.gs')):
- srctype = vala
- elif s.endswith('.vapi'):
- srctype = vapi
- else:
- srctype = others
- srctype[f] = s
- # Split generated sources
- for gensrc in t.get_generated_sources():
- for s in gensrc.get_outputs():
- f = self.get_target_generated_dir(t, gensrc, s)
- if s.endswith(('.vala', '.gs')):
- srctype = vala
- elif s.endswith('.vapi'):
- srctype = vapi
- # Generated non-Vala (C/C++) sources. Won't be used for
- # generating the Vala compile rule below.
- else:
- srctype = othersgen
- # Duplicate outputs are disastrous
- if f in srctype and srctype[f] is not gensrc:
- msg = 'Duplicate output {0!r} from {1!r} {2!r}; ' \
- 'conflicts with {0!r} from {4!r} {3!r}' \
- ''.format(f, type(gensrc).__name__, gensrc.name,
- srctype[f].name, type(srctype[f]).__name__)
- raise InvalidArguments(msg)
- # Store 'somefile.vala': GeneratedList (or CustomTarget)
- srctype[f] = gensrc
- return vala, vapi, (others, othersgen)
- def generate_vala_compile(self, target, outfile):
- """Vala is compiled into C. Set up all necessary build steps here."""
- (vala_src, vapi_src, other_src) = self.split_vala_sources(target)
- extra_dep_files = []
- if not vala_src:
- msg = 'Vala library {!r} has no Vala or Genie source files.'
- raise InvalidArguments(msg.format(target.name))
- valac = target.compilers['vala']
- c_out_dir = self.get_target_private_dir(target)
- # C files generated by valac
- vala_c_src = []
- # Files generated by valac
- valac_outputs = []
- # All sources that are passed to valac on the commandline
- all_files = list(vapi_src.keys())
- for (vala_file, gensrc) in vala_src.items():
- all_files.append(vala_file)
- # Figure out where the Vala compiler will write the compiled C file
- # If the Vala file is in a subdir of the build dir (in our case
- # because it was generated/built by something else), the subdir path
- # components will be preserved in the output path. But if the Vala
- # file is outside the build directory, the path components will be
- # stripped and just the basename will be used.
- if isinstance(gensrc, (build.CustomTarget, build.GeneratedList)) or gensrc.is_built:
- vala_c_file = os.path.splitext(vala_file)[0] + '.c'
- else:
- vala_c_file = os.path.splitext(os.path.basename(vala_file))[0] + '.c'
- # All this will be placed inside the c_out_dir
- vala_c_file = os.path.join(c_out_dir, vala_c_file)
- vala_c_src.append(vala_c_file)
- valac_outputs.append(vala_c_file)
- args = self.generate_basic_compiler_args(target, valac)
- # Tell Valac to output everything in our private directory. Sadly this
- # means it will also preserve the directory components of Vala sources
- # found inside the build tree (generated sources).
- args += ['-d', c_out_dir]
- if not isinstance(target, build.Executable):
- # Library name
- args += ['--library=' + target.name]
- # Outputted header
- hname = os.path.join(self.get_target_dir(target), target.vala_header)
- args += ['-H', hname]
- if self.is_unity(target):
- # Without this the declarations will get duplicated in the .c
- # files and cause a build failure when all of them are
- # #include-d in one .c file.
- # https://github.com/mesonbuild/meson/issues/1969
- args += ['--use-header']
- valac_outputs.append(hname)
- # Outputted vapi file
- vapiname = os.path.join(self.get_target_dir(target), target.vala_vapi)
- # Force valac to write the vapi and gir files in the target build dir.
- # Without this, it will write it inside c_out_dir
- args += ['--vapi', os.path.join('..', target.vala_vapi)]
- valac_outputs.append(vapiname)
- target.outputs += [target.vala_header, target.vala_vapi]
- # Install header and vapi to default locations if user requests this
- if len(target.install_dir) > 1 and target.install_dir[1] is True:
- target.install_dir[1] = self.environment.get_includedir()
- if len(target.install_dir) > 2 and target.install_dir[2] is True:
- target.install_dir[2] = os.path.join(self.environment.get_datadir(), 'vala', 'vapi')
- # Generate GIR if requested
- if isinstance(target.vala_gir, str):
- girname = os.path.join(self.get_target_dir(target), target.vala_gir)
- args += ['--gir', os.path.join('..', target.vala_gir)]
- valac_outputs.append(girname)
- target.outputs.append(target.vala_gir)
- # Install GIR to default location if requested by user
- if len(target.install_dir) > 3 and target.install_dir[3] is True:
- target.install_dir[3] = os.path.join(self.environment.get_datadir(), 'gir-1.0')
- # Detect gresources and add --gresources arguments for each
- for (gres, gensrc) in other_src[1].items():
- if isinstance(gensrc, modules.GResourceTarget):
- gres_xml, = self.get_custom_target_sources(gensrc)
- args += ['--gresources=' + gres_xml]
- extra_args = []
- for a in target.extra_args.get('vala', []):
- if isinstance(a, File):
- relname = a.rel_to_builddir(self.build_to_src)
- extra_dep_files.append(relname)
- extra_args.append(relname)
- else:
- extra_args.append(a)
- dependency_vapis = self.determine_dep_vapis(target)
- extra_dep_files += dependency_vapis
- args += extra_args
- element = NinjaBuildElement(self.all_outputs, valac_outputs,
- valac.get_language() + '_COMPILER',
- all_files + dependency_vapis)
- element.add_item('ARGS', args)
- element.add_dep(extra_dep_files)
- element.write(outfile)
- return other_src[0], other_src[1], vala_c_src
- def generate_rust_target(self, target, outfile):
- rustc = target.compilers['rust']
- relsrc = []
- for i in target.get_sources():
- if not rustc.can_compile(i):
- raise InvalidArguments('Rust target %s contains a non-rust source file.' % target.get_basename())
- relsrc.append(i.rel_to_builddir(self.build_to_src))
- target_name = os.path.join(target.subdir, target.get_filename())
- args = ['--crate-type']
- if isinstance(target, build.Executable):
- cratetype = 'bin'
- elif hasattr(target, 'rust_crate_type'):
- cratetype = target.rust_crate_type
- elif isinstance(target, build.SharedLibrary):
- cratetype = 'dylib'
- elif isinstance(target, build.StaticLibrary):
- cratetype = 'rlib'
- else:
- raise InvalidArguments('Unknown target type for rustc.')
- args.append(cratetype)
- args += ['--crate-name', target.name]
- args += rustc.get_buildtype_args(self.get_option_for_target('buildtype', target))
- depfile = os.path.join(target.subdir, target.name + '.d')
- args += ['--emit', 'dep-info={}'.format(depfile), '--emit', 'link']
- args += target.get_extra_args('rust')
- args += ['-o', os.path.join(target.subdir, target.get_filename())]
- orderdeps = [os.path.join(t.subdir, t.get_filename()) for t in target.link_targets]
- linkdirs = OrderedDict()
- for d in target.link_targets:
- linkdirs[d.subdir] = True
- for d in linkdirs.keys():
- if d == '':
- d = '.'
- args += ['-L', d]
- has_shared_deps = False
- for dep in target.get_dependencies():
- if isinstance(dep, build.SharedLibrary):
- has_shared_deps = True
- if isinstance(target, build.SharedLibrary) or has_shared_deps:
- # add prefer-dynamic if any of the Rust libraries we link
- # against are dynamic, otherwise we'll end up with
- # multiple implementations of crates
- args += ['-C', 'prefer-dynamic']
- # build the usual rpath arguments as well...
- # Set runtime-paths so we can run executables without needing to set
- # LD_LIBRARY_PATH, etc in the environment. Doesn't work on Windows.
- if '/' in target.name or '\\' in target.name:
- # Target names really should not have slashes in them, but
- # unfortunately we did not check for that and some downstream projects
- # now have them. Once slashes are forbidden, remove this bit.
- target_slashname_workaround_dir = os.path.join(os.path.split(target.name)[0],
- self.get_target_dir(target))
- else:
- target_slashname_workaround_dir = self.get_target_dir(target)
- rpath_args = rustc.build_rpath_args(self.environment.get_build_dir(),
- target_slashname_workaround_dir,
- self.determine_rpath_dirs(target),
- target.build_rpath,
- target.install_rpath)
- # ... but then add rustc's sysroot to account for rustup
- # installations
- for rpath_arg in rpath_args:
- args += ['-C', 'link-arg=' + rpath_arg + ':' + os.path.join(rustc.get_sysroot(), 'lib')]
- element = NinjaBuildElement(self.all_outputs, target_name, 'rust_COMPILER', relsrc)
- if len(orderdeps) > 0:
- element.add_orderdep(orderdeps)
- element.add_item('ARGS', args)
- element.add_item('targetdep', depfile)
- element.add_item('cratetype', cratetype)
- element.write(outfile)
- if isinstance(target, build.SharedLibrary):
- self.generate_shsym(outfile, target)
- def swift_module_file_name(self, target):
- return os.path.join(self.get_target_private_dir(target),
- self.target_swift_modulename(target) + '.swiftmodule')
- def target_swift_modulename(self, target):
- return target.name
- def is_swift_target(self, target):
- for s in target.sources:
- if s.endswith('swift'):
- return True
- return False
- def determine_swift_dep_modules(self, target):
- result = []
- for l in target.link_targets:
- if self.is_swift_target(l):
- result.append(self.swift_module_file_name(l))
- return result
- def determine_swift_dep_dirs(self, target):
- result = []
- for l in target.link_targets:
- result.append(self.get_target_private_dir_abs(l))
- return result
- def get_swift_link_deps(self, target):
- result = []
- for l in target.link_targets:
- result.append(self.get_target_filename(l))
- return result
- def split_swift_generated_sources(self, target):
- all_srcs = self.get_target_generated_sources(target)
- srcs = []
- others = []
- for i in all_srcs:
- if i.endswith('.swift'):
- srcs.append(i)
- else:
- others.append(i)
- return srcs, others
- def generate_swift_target(self, target, outfile):
- module_name = self.target_swift_modulename(target)
- swiftc = target.compilers['swift']
- abssrc = []
- abs_headers = []
- header_imports = []
- for i in target.get_sources():
- if swiftc.can_compile(i):
- relsrc = i.rel_to_builddir(self.build_to_src)
- abss = os.path.normpath(os.path.join(self.environment.get_build_dir(), relsrc))
- abssrc.append(abss)
- elif self.environment.is_header(i):
- relh = i.rel_to_builddir(self.build_to_src)
- absh = os.path.normpath(os.path.join(self.environment.get_build_dir(), relh))
- abs_headers.append(absh)
- header_imports += swiftc.get_header_import_args(absh)
- else:
- raise InvalidArguments('Swift target %s contains a non-swift source file.' % target.get_basename())
- os.makedirs(self.get_target_private_dir_abs(target), exist_ok=True)
- compile_args = swiftc.get_compile_only_args()
- compile_args += swiftc.get_module_args(module_name)
- compile_args += self.build.get_project_args(swiftc, target.subproject)
- compile_args += self.build.get_global_args(swiftc)
- for i in reversed(target.get_include_dirs()):
- basedir = i.get_curdir()
- for d in i.get_incdirs():
- if d not in ('', '.'):
- expdir = os.path.join(basedir, d)
- else:
- expdir = basedir
- srctreedir = os.path.normpath(os.path.join(self.environment.get_build_dir(), self.build_to_src, expdir))
- sargs = swiftc.get_include_args(srctreedir)
- compile_args += sargs
- link_args = swiftc.get_output_args(os.path.join(self.environment.get_build_dir(), self.get_target_filename(target)))
- link_args += self.build.get_project_link_args(swiftc, target.subproject)
- link_args += self.build.get_global_link_args(swiftc)
- rundir = self.get_target_private_dir(target)
- out_module_name = self.swift_module_file_name(target)
- in_module_files = self.determine_swift_dep_modules(target)
- abs_module_dirs = self.determine_swift_dep_dirs(target)
- module_includes = []
- for x in abs_module_dirs:
- module_includes += swiftc.get_include_args(x)
- link_deps = self.get_swift_link_deps(target)
- abs_link_deps = [os.path.join(self.environment.get_build_dir(), x) for x in link_deps]
- for d in target.link_targets:
- reldir = self.get_target_dir(d)
- if reldir == '':
- reldir = '.'
- link_args += ['-L', os.path.normpath(os.path.join(self.environment.get_build_dir(), reldir))]
- (rel_generated, _) = self.split_swift_generated_sources(target)
- abs_generated = [os.path.join(self.environment.get_build_dir(), x) for x in rel_generated]
- # We need absolute paths because swiftc needs to be invoked in a subdir
- # and this is the easiest way about it.
- objects = [] # Relative to swift invocation dir
- rel_objects = [] # Relative to build.ninja
- for i in abssrc + abs_generated:
- base = os.path.split(i)[1]
- oname = os.path.splitext(base)[0] + '.o'
- objects.append(oname)
- rel_objects.append(os.path.join(self.get_target_private_dir(target), oname))
- # Swiftc does not seem to be able to emit objects and module files in one go.
- elem = NinjaBuildElement(self.all_outputs, rel_objects,
- 'swift_COMPILER',
- abssrc)
- elem.add_dep(in_module_files + rel_generated)
- elem.add_dep(abs_headers)
- elem.add_item('ARGS', compile_args + header_imports + abs_generated + module_includes)
- elem.add_item('RUNDIR', rundir)
- elem.write(outfile)
- elem = NinjaBuildElement(self.all_outputs, out_module_name,
- 'swift_COMPILER',
- abssrc)
- elem.add_dep(in_module_files + rel_generated)
- elem.add_item('ARGS', compile_args + abs_generated + module_includes + swiftc.get_mod_gen_args())
- elem.add_item('RUNDIR', rundir)
- elem.write(outfile)
- if isinstance(target, build.StaticLibrary):
- elem = self.generate_link(target, outfile, self.get_target_filename(target),
- rel_objects, self.build.static_linker)
- elem.write(outfile)
- elif isinstance(target, build.Executable):
- elem = NinjaBuildElement(self.all_outputs, self.get_target_filename(target), 'swift_COMPILER', [])
- elem.add_dep(rel_objects)
- elem.add_dep(link_deps)
- elem.add_item('ARGS', link_args + swiftc.get_std_exe_link_args() + objects + abs_link_deps)
- elem.add_item('RUNDIR', rundir)
- elem.write(outfile)
- else:
- raise MesonException('Swift supports only executable and static library targets.')
- def generate_static_link_rules(self, is_cross, outfile):
- if 'java' in self.build.compilers:
- if not is_cross:
- self.generate_java_link(outfile)
- if is_cross:
- if self.environment.cross_info.need_cross_compiler():
- static_linker = self.build.static_cross_linker
- else:
- static_linker = self.build.static_linker
- crstr = '_CROSS'
- else:
- static_linker = self.build.static_linker
- crstr = ''
- if static_linker is None:
- return
- rule = 'rule STATIC%s_LINKER\n' % crstr
- # We don't use @file.rsp on Windows with ArLinker because llvm-ar and
- # gcc-ar blindly pass the --plugin argument to `ar` and you cannot pass
- # options as arguments while using the @file.rsp syntax.
- # See: https://github.com/mesonbuild/meson/issues/1646
- if mesonlib.is_windows() and not isinstance(static_linker, ArLinker):
- command_template = ''' command = {executable} @$out.rsp
- rspfile = $out.rsp
- rspfile_content = $LINK_ARGS {output_args} $in
- '''
- else:
- command_template = ' command = {executable} $LINK_ARGS {output_args} $in\n'
- cmdlist = []
- # FIXME: Must normalize file names with pathlib.Path before writing
- # them out to fix this properly on Windows. See:
- # https://github.com/mesonbuild/meson/issues/1517
- # https://github.com/mesonbuild/meson/issues/1526
- if isinstance(static_linker, ArLinker) and not mesonlib.is_windows():
- # `ar` has no options to overwrite archives. It always appends,
- # which is never what we want. Delete an existing library first if
- # it exists. https://github.com/mesonbuild/meson/issues/1355
- cmdlist = [execute_wrapper, rmfile_prefix.format('$out')]
- cmdlist += static_linker.get_exelist()
- command = command_template.format(
- executable=' '.join(cmdlist),
- output_args=' '.join(static_linker.get_output_args('$out')))
- description = ' description = Linking static target $out.\n\n'
- outfile.write(rule)
- outfile.write(command)
- outfile.write(description)
- def generate_dynamic_link_rules(self, outfile):
- ctypes = [(self.build.compilers, False)]
- if self.environment.is_cross_build():
- if self.environment.cross_info.need_cross_compiler():
- ctypes.append((self.build.cross_compilers, True))
- else:
- # Native compiler masquerades as the cross compiler.
- ctypes.append((self.build.compilers, True))
- else:
- ctypes.append((self.build.cross_compilers, True))
- for (complist, is_cross) in ctypes:
- for langname, compiler in complist.items():
- if langname == 'java' \
- or langname == 'vala' \
- or langname == 'rust' \
- or langname == 'cs':
- continue
- crstr = ''
- cross_args = []
- if is_cross:
- crstr = '_CROSS'
- try:
- cross_args = self.environment.cross_info.config['properties'][langname + '_link_args']
- except KeyError:
- pass
- rule = 'rule %s%s_LINKER\n' % (langname, crstr)
- if mesonlib.is_windows():
- command_template = ''' command = {executable} @$out.rsp
- rspfile = $out.rsp
- rspfile_content = $ARGS {output_args} $in $LINK_ARGS {cross_args} $aliasing
- '''
- else:
- command_template = ' command = {executable} $ARGS {output_args} $in $LINK_ARGS {cross_args} $aliasing\n'
- command = command_template.format(
- executable=' '.join(compiler.get_linker_exelist()),
- cross_args=' '.join(cross_args),
- output_args=' '.join(compiler.get_linker_output_args('$out'))
- )
- description = ' description = Linking target $out.'
- outfile.write(rule)
- outfile.write(command)
- outfile.write(description)
- outfile.write('\n')
- outfile.write('\n')
- symrule = 'rule SHSYM\n'
- symcmd = ' command = "%s" "%s" %s %s %s %s $CROSS\n' % (ninja_quote(sys.executable),
- self.environment.get_build_command(),
- '--internal',
- 'symbolextractor',
- '$in',
- '$out')
- synstat = ' restat = 1\n'
- syndesc = ' description = Generating symbol file $out.\n'
- outfile.write(symrule)
- outfile.write(symcmd)
- outfile.write(synstat)
- outfile.write(syndesc)
- outfile.write('\n')
- def generate_java_compile_rule(self, compiler, outfile):
- rule = 'rule %s_COMPILER\n' % compiler.get_language()
- invoc = ' '.join([ninja_quote(i) for i in compiler.get_exelist()])
- command = ' command = %s $ARGS $in\n' % invoc
- description = ' description = Compiling Java object $in.\n'
- outfile.write(rule)
- outfile.write(command)
- outfile.write(description)
- outfile.write('\n')
- def generate_cs_compile_rule(self, compiler, outfile):
- rule = 'rule %s_COMPILER\n' % compiler.get_language()
- invoc = ' '.join([ninja_quote(i) for i in compiler.get_exelist()])
- command = ' command = %s $ARGS $in\n' % invoc
- description = ' description = Compiling C Sharp target $out.\n'
- outfile.write(rule)
- outfile.write(command)
- outfile.write(description)
- outfile.write('\n')
- def generate_vala_compile_rules(self, compiler, outfile):
- rule = 'rule %s_COMPILER\n' % compiler.get_language()
- invoc = ' '.join([ninja_quote(i) for i in compiler.get_exelist()])
- command = ' command = %s $ARGS $in\n' % invoc
- description = ' description = Compiling Vala source $in.\n'
- restat = ' restat = 1\n' # ValaC does this always to take advantage of it.
- outfile.write(rule)
- outfile.write(command)
- outfile.write(description)
- outfile.write(restat)
- outfile.write('\n')
- def generate_rust_compile_rules(self, compiler, outfile):
- rule = 'rule %s_COMPILER\n' % compiler.get_language()
- invoc = ' '.join([ninja_quote(i) for i in compiler.get_exelist()])
- command = ' command = %s $ARGS $in\n' % invoc
- description = ' description = Compiling Rust source $in.\n'
- depfile = ' depfile = $targetdep\n'
- depstyle = ' deps = gcc\n'
- outfile.write(rule)
- outfile.write(command)
- outfile.write(description)
- outfile.write(depfile)
- outfile.write(depstyle)
- outfile.write('\n')
- def generate_swift_compile_rules(self, compiler, outfile):
- rule = 'rule %s_COMPILER\n' % compiler.get_language()
- full_exe = [ninja_quote(sys.executable),
- ninja_quote(self.environment.get_build_command()),
- '--internal',
- 'dirchanger',
- '$RUNDIR']
- invoc = (' '.join(full_exe) + ' ' +
- ' '.join(ninja_quote(i) for i in compiler.get_exelist()))
- command = ' command = %s $ARGS $in\n' % invoc
- description = ' description = Compiling Swift source $in.\n'
- outfile.write(rule)
- outfile.write(command)
- outfile.write(description)
- outfile.write('\n')
- def generate_fortran_dep_hack(self, outfile):
- if mesonlib.is_windows():
- cmd = 'cmd /C ""'
- else:
- cmd = 'true'
- template = '''# Workaround for these issues:
- # https://groups.google.com/forum/#!topic/ninja-build/j-2RfBIOd_8
- # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485
- rule FORTRAN_DEP_HACK
- command = %s
- description = Dep hack
- restat = 1
- '''
- outfile.write(template % cmd)
- def generate_llvm_ir_compile_rule(self, compiler, is_cross, outfile):
- if getattr(self, 'created_llvm_ir_rule', False):
- return
- rule = 'rule llvm_ir{}_COMPILER\n'.format('_CROSS' if is_cross else '')
- if mesonlib.is_windows():
- command_template = ' command = {executable} @$out.rsp\n' \
- ' rspfile = $out.rsp\n' \
- ' rspfile_content = {cross_args} $ARGS {output_args} {compile_only_args} $in\n'
- else:
- command_template = ' command = {executable} {cross_args} $ARGS {output_args} {compile_only_args} $in\n'
- command = command_template.format(
- executable=' '.join([ninja_quote(i) for i in compiler.get_exelist()]),
- cross_args=' '.join(self.get_cross_info_lang_args(compiler.language, is_cross)),
- output_args=' '.join(compiler.get_output_args('$out')),
- compile_only_args=' '.join(compiler.get_compile_only_args())
- )
- description = ' description = Compiling LLVM IR object $in.\n'
- outfile.write(rule)
- outfile.write(command)
- outfile.write(description)
- outfile.write('\n')
- self.created_llvm_ir_rule = True
- def get_cross_info_lang_args(self, lang, is_cross):
- if is_cross:
- try:
- return self.environment.cross_info.config['properties'][lang + '_args']
- except KeyError:
- pass
- return []
- def generate_compile_rule_for(self, langname, compiler, is_cross, outfile):
- if langname == 'java':
- if not is_cross:
- self.generate_java_compile_rule(compiler, outfile)
- return
- if langname == 'cs':
- if not is_cross:
- self.generate_cs_compile_rule(compiler, outfile)
- return
- if langname == 'vala':
- if not is_cross:
- self.generate_vala_compile_rules(compiler, outfile)
- return
- if langname == 'rust':
- if not is_cross:
- self.generate_rust_compile_rules(compiler, outfile)
- return
- if langname == 'swift':
- if not is_cross:
- self.generate_swift_compile_rules(compiler, outfile)
- return
- if langname == 'fortran':
- self.generate_fortran_dep_hack(outfile)
- if is_cross:
- crstr = '_CROSS'
- else:
- crstr = ''
- rule = 'rule %s%s_COMPILER\n' % (langname, crstr)
- depargs = compiler.get_dependency_gen_args('$out', '$DEPFILE')
- quoted_depargs = []
- for d in depargs:
- if d != '$out' and d != '$in':
- d = quote_func(d)
- quoted_depargs.append(d)
- cross_args = self.get_cross_info_lang_args(langname, is_cross)
- if mesonlib.is_windows():
- command_template = ''' command = {executable} @$out.rsp
- rspfile = $out.rsp
- rspfile_content = {cross_args} $ARGS {dep_args} {output_args} {compile_only_args} $in
- '''
- else:
- command_template = ' command = {executable} {cross_args} $ARGS {dep_args} {output_args} {compile_only_args} $in\n'
- command = command_template.format(
- executable=' '.join([ninja_quote(i) for i in compiler.get_exelist()]),
- cross_args=' '.join(cross_args),
- dep_args=' '.join(quoted_depargs),
- output_args=' '.join(compiler.get_output_args('$out')),
- compile_only_args=' '.join(compiler.get_compile_only_args())
- )
- description = ' description = Compiling %s object $out.\n' % compiler.get_display_language()
- if compiler.get_id() == 'msvc':
- deps = ' deps = msvc\n'
- else:
- deps = ' deps = gcc\n'
- deps += ' depfile = $DEPFILE\n'
- outfile.write(rule)
- outfile.write(command)
- outfile.write(deps)
- outfile.write(description)
- outfile.write('\n')
- def generate_pch_rule_for(self, langname, compiler, is_cross, outfile):
- if langname != 'c' and langname != 'cpp':
- return
- if is_cross:
- crstr = '_CROSS'
- else:
- crstr = ''
- rule = 'rule %s%s_PCH\n' % (langname, crstr)
- depargs = compiler.get_dependency_gen_args('$out', '$DEPFILE')
- cross_args = []
- if is_cross:
- try:
- cross_args = self.environment.cross_info.config['properties'][langname + '_args']
- except KeyError:
- pass
- quoted_depargs = []
- for d in depargs:
- if d != '$out' and d != '$in':
- d = quote_func(d)
- quoted_depargs.append(d)
- if compiler.get_id() == 'msvc':
- output = ''
- else:
- output = ' '.join(compiler.get_output_args('$out'))
- command = " command = {executable} {cross_args} $ARGS {dep_args} {output_args} {compile_only_args} $in\n".format(
- executable=' '.join(compiler.get_exelist()),
- cross_args=' '.join(cross_args),
- dep_args=' '.join(quoted_depargs),
- output_args=output,
- compile_only_args=' '.join(compiler.get_compile_only_args())
- )
- description = ' description = Precompiling header %s.\n' % '$in'
- if compiler.get_id() == 'msvc':
- deps = ' deps = msvc\n'
- else:
- deps = ' deps = gcc\n'
- deps += ' depfile = $DEPFILE\n'
- outfile.write(rule)
- outfile.write(command)
- outfile.write(deps)
- outfile.write(description)
- outfile.write('\n')
- def generate_compile_rules(self, outfile):
- for langname, compiler in self.build.compilers.items():
- if compiler.get_id() == 'clang':
- self.generate_llvm_ir_compile_rule(compiler, False, outfile)
- self.generate_compile_rule_for(langname, compiler, False, outfile)
- self.generate_pch_rule_for(langname, compiler, False, outfile)
- if self.environment.is_cross_build():
- # In case we are going a target-only build, make the native compilers
- # masquerade as cross compilers.
- if self.environment.cross_info.need_cross_compiler():
- cclist = self.build.cross_compilers
- else:
- cclist = self.build.compilers
- for langname, compiler in cclist.items():
- if compiler.get_id() == 'clang':
- self.generate_llvm_ir_compile_rule(compiler, True, outfile)
- self.generate_compile_rule_for(langname, compiler, True, outfile)
- self.generate_pch_rule_for(langname, compiler, True, outfile)
- outfile.write('\n')
- def generate_generator_list_rules(self, target, outfile):
- # CustomTargets have already written their rules,
- # so write rules for GeneratedLists here
- for genlist in target.get_generated_sources():
- if isinstance(genlist, build.CustomTarget):
- continue
- self.generate_genlist_for_target(genlist, target, outfile)
- def replace_paths(self, target, args):
- source_target_dir = self.get_target_source_dir(target)
- relout = self.get_target_private_dir(target)
- args = [x.replace("@SOURCE_DIR@", self.build_to_src).replace("@BUILD_DIR@", relout)
- for x in args]
- args = [x.replace("@CURRENT_SOURCE_DIR@", source_target_dir) for x in args]
- args = [x.replace("@SOURCE_ROOT@", self.build_to_src).replace("@BUILD_ROOT@", '.')
- for x in args]
- return args
- def generate_genlist_for_target(self, genlist, target, outfile):
- generator = genlist.get_generator()
- exe = generator.get_exe()
- exe_arr = self.exe_object_to_cmd_array(exe)
- infilelist = genlist.get_inputs()
- outfilelist = genlist.get_outputs()
- base_args = generator.get_arglist()
- extra_dependencies = [os.path.join(self.build_to_src, i) for i in genlist.extra_depends]
- source_target_dir = self.get_target_source_dir(target)
- for i in range(len(infilelist)):
- if len(generator.outputs) == 1:
- sole_output = os.path.join(self.get_target_private_dir(target), outfilelist[i])
- else:
- sole_output = ''
- curfile = infilelist[i]
- infilename = curfile.rel_to_builddir(self.build_to_src)
- outfiles = genlist.get_outputs_for(curfile)
- outfiles = [os.path.join(self.get_target_private_dir(target), of) for of in outfiles]
- if generator.depfile is None:
- rulename = 'CUSTOM_COMMAND'
- args = base_args
- else:
- rulename = 'CUSTOM_COMMAND_DEP'
- depfilename = generator.get_dep_outname(infilename)
- depfile = os.path.join(self.get_target_private_dir(target), depfilename)
- args = [x.replace('@DEPFILE@', depfile) for x in base_args]
- args = [x.replace("@INPUT@", infilename).replace('@OUTPUT@', sole_output)
- for x in args]
- args = self.replace_outputs(args, self.get_target_private_dir(target), outfilelist)
- # We have consumed output files, so drop them from the list of remaining outputs.
- if sole_output == '':
- outfilelist = outfilelist[len(generator.outputs):]
- relout = self.get_target_private_dir(target)
- args = self.replace_paths(target, args)
- cmdlist = exe_arr + self.replace_extra_args(args, genlist)
- elem = NinjaBuildElement(self.all_outputs, outfiles, rulename, infilename)
- if generator.depfile is not None:
- elem.add_item('DEPFILE', depfile)
- if len(extra_dependencies) > 0:
- elem.add_dep(extra_dependencies)
- elem.add_item('DESC', 'Generating $out')
- if isinstance(exe, build.BuildTarget):
- elem.add_dep(self.get_target_filename(exe))
- elem.add_item('COMMAND', cmdlist)
- elem.write(outfile)
- def scan_fortran_module_outputs(self, target):
- compiler = None
- for lang, c in self.build.compilers.items():
- if lang == 'fortran':
- compiler = c
- break
- if compiler is None:
- self.fortran_deps[target.get_basename()] = {}
- return
- modre = re.compile(r"\s*module\s+(\w+)", re.IGNORECASE)
- module_files = {}
- for s in target.get_sources():
- # FIXME, does not work for Fortran sources generated by
- # custom_target() and generator() as those are run after
- # the configuration (configure_file() is OK)
- if not compiler.can_compile(s):
- continue
- filename = s.absolute_path(self.environment.get_source_dir(),
- self.environment.get_build_dir())
- with open(filename) as f:
- for line in f:
- modmatch = modre.match(line)
- if modmatch is not None:
- modname = modmatch.group(1).lower()
- if modname == 'procedure':
- # MODULE PROCEDURE construct
- continue
- if modname in module_files:
- raise InvalidArguments(
- 'Namespace collision: module %s defined in '
- 'two files %s and %s.' %
- (modname, module_files[modname], s))
- module_files[modname] = s
- self.fortran_deps[target.get_basename()] = module_files
- def get_fortran_deps(self, compiler, src, target):
- mod_files = []
- usere = re.compile(r"\s*use\s+(\w+)", re.IGNORECASE)
- dirname = self.get_target_private_dir(target)
- tdeps = self.fortran_deps[target.get_basename()]
- with open(src) as f:
- for line in f:
- usematch = usere.match(line)
- if usematch is not None:
- usename = usematch.group(1).lower()
- if usename not in tdeps:
- # The module is not provided by any source file. This
- # is due to:
- # a) missing file/typo/etc
- # b) using a module provided by the compiler, such as
- # OpenMP
- # There's no easy way to tell which is which (that I
- # know of) so just ignore this and go on. Ideally we
- # would print a warning message to the user but this is
- # a common occurrence, which would lead to lots of
- # distracting noise.
- continue
- mod_source_file = tdeps[usename]
- # Check if a source uses a module it exports itself.
- # Potential bug if multiple targets have a file with
- # the same name.
- if mod_source_file.fname == os.path.split(src)[1]:
- continue
- mod_name = compiler.module_name_to_filename(
- usematch.group(1))
- mod_files.append(os.path.join(dirname, mod_name))
- return mod_files
- def get_cross_stdlib_args(self, target, compiler):
- if not target.is_cross:
- return []
- if not self.environment.cross_info.has_stdlib(compiler.language):
- return []
- return compiler.get_no_stdinc_args()
- def get_compile_debugfile_args(self, compiler, target, objfile):
- if compiler.id != 'msvc':
- return []
- # The way MSVC uses PDB files is documented exactly nowhere so
- # the following is what we have been able to decipher via
- # reverse engineering.
- #
- # Each object file gets the path of its PDB file written
- # inside it. This can be either the final PDB (for, say,
- # foo.exe) or an object pdb (for foo.obj). If the former, then
- # each compilation step locks the pdb file for writing, which
- # is a bottleneck and object files from one target can not be
- # used in a different target. The latter seems to be the
- # sensible one (and what Unix does) but there is a catch. If
- # you try to use precompiled headers MSVC will error out
- # because both source and pch pdbs go in the same file and
- # they must be the same.
- #
- # This means:
- #
- # - pch files must be compiled anew for every object file (negating
- # the entire point of having them in the first place)
- # - when using pch, output must go to the target pdb
- #
- # Since both of these are broken in some way, use the one that
- # works for each target. This unfortunately means that you
- # can't combine pch and object extraction in a single target.
- #
- # PDB files also lead to filename collisions. A target foo.exe
- # has a corresponding foo.pdb. A shared library foo.dll _also_
- # has pdb file called foo.pdb. So will a static library
- # foo.lib, which clobbers both foo.pdb _and_ the dll file's
- # export library called foo.lib (by default, currently we name
- # them libfoo.a to avoidt this issue). You can give the files
- # unique names such as foo_exe.pdb but VC also generates a
- # bunch of other files which take their names from the target
- # basename (i.e. "foo") and stomp on each other.
- #
- # CMake solves this problem by doing two things. First of all
- # static libraries do not generate pdb files at
- # all. Presumably you don't need them and VC is smart enough
- # to look up the original data when linking (speculation, not
- # tested). The second solution is that you can only have
- # target named "foo" as an exe, shared lib _or_ static
- # lib. This makes filename collisions not happen. The downside
- # is that you can't have an executable foo that uses a shared
- # library libfoo.so, which is a common idiom on Unix.
- #
- # If you feel that the above is completely wrong and all of
- # this is actually doable, please send patches.
- if target.has_pch():
- tfilename = self.get_target_filename_abs(target)
- return compiler.get_compile_debugfile_args(tfilename, pch=True)
- else:
- return compiler.get_compile_debugfile_args(objfile, pch=False)
- def get_link_debugfile_args(self, linker, target, outname):
- return linker.get_link_debugfile_args(outname)
- def generate_llvm_ir_compile(self, target, outfile, src):
- compiler = get_compiler_for_source(target.compilers.values(), src)
- commands = CompilerArgs(compiler)
- # Compiler args for compiling this target
- commands += compilers.get_base_compile_args(self.environment.coredata.base_options,
- compiler)
- if isinstance(src, File):
- if src.is_built:
- src_filename = os.path.join(src.subdir, src.fname)
- else:
- src_filename = src.fname
- elif os.path.isabs(src):
- src_filename = os.path.basename(src)
- else:
- src_filename = src
- obj_basename = src_filename.replace('/', '_').replace('\\', '_')
- rel_obj = os.path.join(self.get_target_private_dir(target), obj_basename)
- rel_obj += '.' + self.environment.get_object_suffix()
- commands += self.get_compile_debugfile_args(compiler, target, rel_obj)
- if isinstance(src, File) and src.is_built:
- rel_src = src.fname
- elif isinstance(src, File):
- rel_src = src.rel_to_builddir(self.build_to_src)
- else:
- raise InvalidArguments('Invalid source type: {!r}'.format(src))
- # Write the Ninja build command
- compiler_name = 'llvm_ir{}_COMPILER'.format('_CROSS' if target.is_cross else '')
- element = NinjaBuildElement(self.all_outputs, rel_obj, compiler_name, rel_src)
- # Convert from GCC-style link argument naming to the naming used by the
- # current compiler.
- commands = commands.to_native()
- element.add_item('ARGS', commands)
- element.write(outfile)
- return rel_obj
- def get_source_dir_include_args(self, target, compiler):
- curdir = target.get_subdir()
- tmppath = os.path.normpath(os.path.join(self.build_to_src, curdir))
- return compiler.get_include_args(tmppath, False)
- def get_build_dir_include_args(self, target, compiler):
- curdir = target.get_subdir()
- if curdir == '':
- curdir = '.'
- return compiler.get_include_args(curdir, False)
- def get_custom_target_dir_include_args(self, target, compiler):
- custom_target_include_dirs = []
- for i in target.get_generated_sources():
- # Generator output goes into the target private dir which is
- # already in the include paths list. Only custom targets have their
- # own target build dir.
- if not isinstance(i, build.CustomTarget):
- continue
- idir = self.get_target_dir(i)
- if idir not in custom_target_include_dirs:
- custom_target_include_dirs.append(idir)
- incs = []
- for i in custom_target_include_dirs:
- incs += compiler.get_include_args(i, False)
- return incs
- def _generate_single_compile(self, target, compiler, is_generated=False):
- base_proxy = backends.OptionOverrideProxy(target.option_overrides,
- self.environment.coredata.base_options)
- # Create an empty commands list, and start adding arguments from
- # various sources in the order in which they must override each other
- commands = CompilerArgs(compiler)
- # Add compiler args for compiling this target derived from 'base' build
- # options passed on the command-line, in default_options, etc.
- # These have the lowest priority.
- commands += compilers.get_base_compile_args(base_proxy,
- compiler)
- # The code generated by valac is usually crap and has tons of unused
- # variables and such, so disable warnings for Vala C sources.
- no_warn_args = (is_generated == 'vala')
- # Add compiler args and include paths from several sources; defaults,
- # build options, external dependencies, etc.
- commands += self.generate_basic_compiler_args(target, compiler, no_warn_args)
- # Add include dirs from the `include_directories:` kwarg on the target
- # and from `include_directories:` of internal deps of the target.
- #
- # Target include dirs should override internal deps include dirs.
- # This is handled in BuildTarget.process_kwargs()
- #
- # Include dirs from internal deps should override include dirs from
- # external deps and must maintain the order in which they are specified.
- # Hence, we must reverse the list so that the order is preserved.
- for i in reversed(target.get_include_dirs()):
- basedir = i.get_curdir()
- for d in i.get_incdirs():
- # Avoid superfluous '/.' at the end of paths when d is '.'
- if d not in ('', '.'):
- expdir = os.path.join(basedir, d)
- else:
- expdir = basedir
- srctreedir = os.path.join(self.build_to_src, expdir)
- # Add source subdir first so that the build subdir overrides it
- sargs = compiler.get_include_args(srctreedir, i.is_system)
- commands += sargs
- # There may be include dirs where a build directory has not been
- # created for some source dir. For example if someone does this:
- #
- # inc = include_directories('foo/bar/baz')
- #
- # But never subdir()s into the actual dir.
- if os.path.isdir(os.path.join(self.environment.get_build_dir(), expdir)):
- bargs = compiler.get_include_args(expdir, i.is_system)
- else:
- bargs = []
- commands += bargs
- for d in i.get_extra_build_dirs():
- commands += compiler.get_include_args(d, i.is_system)
- # Add per-target compile args, f.ex, `c_args : ['-DFOO']`. We set these
- # near the end since these are supposed to override everything else.
- commands += self.escape_extra_args(compiler,
- target.get_extra_args(compiler.get_language()))
- # Add source dir and build dir. Project-specific and target-specific
- # include paths must override per-target compile args, include paths
- # from external dependencies, internal dependencies, and from
- # per-target `include_directories:`
- #
- # We prefer headers in the build dir and the custom target dir over the
- # source dir since, for instance, the user might have an
- # srcdir == builddir Autotools build in their source tree. Many
- # projects that are moving to Meson have both Meson and Autotools in
- # parallel as part of the transition.
- commands += self.get_source_dir_include_args(target, compiler)
- commands += self.get_custom_target_dir_include_args(target, compiler)
- commands += self.get_build_dir_include_args(target, compiler)
- # Finally add the private dir for the target to the include path. This
- # must override everything else and must be the final path added.
- commands += compiler.get_include_args(self.get_target_private_dir(target), False)
- return commands
- def generate_single_compile(self, target, outfile, src, is_generated=False, header_deps=[], order_deps=[]):
- """
- Compiles C/C++, ObjC/ObjC++, Fortran, and D sources
- """
- if isinstance(src, str) and src.endswith('.h'):
- raise AssertionError('BUG: sources should not contain headers {!r}'.format(src))
- compiler = get_compiler_for_source(target.compilers.values(), src)
- key = (target, compiler, is_generated)
- if key in self.target_arg_cache:
- commands = self.target_arg_cache[key]
- else:
- commands = self._generate_single_compile(target, compiler, is_generated)
- self.target_arg_cache[key] = commands
- commands = CompilerArgs(commands.compiler, commands)
- if isinstance(src, mesonlib.File) and src.is_built:
- rel_src = os.path.join(src.subdir, src.fname)
- if os.path.isabs(rel_src):
- assert(rel_src.startswith(self.environment.get_build_dir()))
- rel_src = rel_src[len(self.environment.get_build_dir()) + 1:]
- abs_src = os.path.join(self.environment.get_build_dir(), rel_src)
- elif isinstance(src, mesonlib.File):
- rel_src = src.rel_to_builddir(self.build_to_src)
- abs_src = src.absolute_path(self.environment.get_source_dir(),
- self.environment.get_build_dir())
- elif is_generated:
- raise AssertionError('BUG: broken generated source file handling for {!r}'.format(src))
- else:
- if isinstance(src, File):
- rel_src = src.rel_to_builddir(self.build_to_src)
- else:
- raise InvalidArguments('Invalid source type: {!r}'.format(src))
- abs_src = os.path.join(self.environment.get_build_dir(), rel_src)
- if isinstance(src, File):
- if src.is_built:
- src_filename = os.path.join(src.subdir, src.fname)
- if os.path.isabs(src_filename):
- assert(src_filename.startswith(self.environment.get_build_dir()))
- src_filename = src_filename[len(self.environment.get_build_dir()) + 1:]
- else:
- src_filename = src.fname
- elif os.path.isabs(src):
- src_filename = os.path.basename(src)
- else:
- src_filename = src
- obj_basename = src_filename.replace('/', '_').replace('\\', '_')
- rel_obj = os.path.join(self.get_target_private_dir(target), obj_basename)
- rel_obj += '.' + self.environment.get_object_suffix()
- dep_file = compiler.depfile_for_object(rel_obj)
- # Add MSVC debug file generation compile flags: /Fd /FS
- commands += self.get_compile_debugfile_args(compiler, target, rel_obj)
- # PCH handling
- if self.environment.coredata.base_options.get('b_pch', False):
- commands += self.get_pch_include_args(compiler, target)
- pchlist = target.get_pch(compiler.language)
- else:
- pchlist = []
- if not pchlist:
- pch_dep = []
- elif compiler.id == 'intel':
- pch_dep = []
- else:
- arr = []
- i = os.path.join(self.get_target_private_dir(target), compiler.get_pch_name(pchlist[0]))
- arr.append(i)
- pch_dep = arr
- crstr = ''
- if target.is_cross:
- crstr = '_CROSS'
- compiler_name = '%s%s_COMPILER' % (compiler.get_language(), crstr)
- extra_deps = []
- if compiler.get_language() == 'fortran':
- # Can't read source file to scan for deps if it's generated later
- # at build-time. Skip scanning for deps, and just set the module
- # outdir argument instead.
- # https://github.com/mesonbuild/meson/issues/1348
- if not is_generated:
- extra_deps += self.get_fortran_deps(compiler, abs_src, target)
- # Dependency hack. Remove once multiple outputs in Ninja is fixed:
- # https://groups.google.com/forum/#!topic/ninja-build/j-2RfBIOd_8
- for modname, srcfile in self.fortran_deps[target.get_basename()].items():
- modfile = os.path.join(self.get_target_private_dir(target),
- compiler.module_name_to_filename(modname))
- if srcfile == src:
- depelem = NinjaBuildElement(self.all_outputs, modfile, 'FORTRAN_DEP_HACK', rel_obj)
- depelem.write(outfile)
- commands += compiler.get_module_outdir_args(self.get_target_private_dir(target))
- element = NinjaBuildElement(self.all_outputs, rel_obj, compiler_name, rel_src)
- for d in header_deps:
- if isinstance(d, File):
- d = d.rel_to_builddir(self.build_to_src)
- elif not self.has_dir_part(d):
- d = os.path.join(self.get_target_private_dir(target), d)
- element.add_dep(d)
- for d in extra_deps:
- element.add_dep(d)
- for d in order_deps:
- if isinstance(d, File):
- d = d.rel_to_builddir(self.build_to_src)
- elif not self.has_dir_part(d):
- d = os.path.join(self.get_target_private_dir(target), d)
- element.add_orderdep(d)
- element.add_orderdep(pch_dep)
- # Convert from GCC-style link argument naming to the naming used by the
- # current compiler.
- commands = commands.to_native()
- for i in self.get_fortran_orderdeps(target, compiler):
- element.add_orderdep(i)
- element.add_item('DEPFILE', dep_file)
- element.add_item('ARGS', commands)
- element.write(outfile)
- return rel_obj
- def has_dir_part(self, fname):
- # FIXME FIXME: The usage of this is a terrible and unreliable hack
- if isinstance(fname, File):
- return fname.subdir != ''
- return '/' in fname or '\\' in fname
- # Fortran is a bit weird (again). When you link against a library, just compiling a source file
- # requires the mod files that are output when single files are built. To do this right we would need to
- # scan all inputs and write out explicit deps for each file. That is stoo slow and too much effort so
- # instead just have an ordered dependendy on the library. This ensures all required mod files are created.
- # The real deps are then detected via dep file generation from the compiler. This breaks on compilers that
- # produce incorrect dep files but such is life.
- def get_fortran_orderdeps(self, target, compiler):
- if compiler.language != 'fortran':
- return []
- return [os.path.join(self.get_target_dir(lt), lt.get_filename()) for lt in target.link_targets]
- def generate_msvc_pch_command(self, target, compiler, pch):
- if len(pch) != 2:
- raise RuntimeError('MSVC requires one header and one source to produce precompiled headers.')
- header = pch[0]
- source = pch[1]
- pchname = compiler.get_pch_name(header)
- dst = os.path.join(self.get_target_private_dir(target), pchname)
- commands = []
- commands += self.generate_basic_compiler_args(target, compiler)
- just_name = os.path.split(header)[1]
- (objname, pch_args) = compiler.gen_pch_args(just_name, source, dst)
- commands += pch_args
- commands += self.get_compile_debugfile_args(compiler, target, objname)
- dep = dst + '.' + compiler.get_depfile_suffix()
- return commands, dep, dst, [objname]
- def generate_gcc_pch_command(self, target, compiler, pch):
- commands = self._generate_single_compile(target, compiler)
- dst = os.path.join(self.get_target_private_dir(target),
- os.path.split(pch)[-1] + '.' + compiler.get_pch_suffix())
- dep = dst + '.' + compiler.get_depfile_suffix()
- return commands, dep, dst, [] # Gcc does not create an object file during pch generation.
- def generate_pch(self, target, outfile):
- cstr = ''
- pch_objects = []
- if target.is_cross:
- cstr = '_CROSS'
- for lang in ['c', 'cpp']:
- pch = target.get_pch(lang)
- if not pch:
- continue
- if '/' not in pch[0] or '/' not in pch[-1]:
- msg = 'Precompiled header of {!r} must not be in the same ' \
- 'directory as source, please put it in a subdirectory.' \
- ''.format(target.get_basename())
- raise InvalidArguments(msg)
- compiler = target.compilers[lang]
- if compiler.id == 'msvc':
- src = os.path.join(self.build_to_src, target.get_source_subdir(), pch[-1])
- (commands, dep, dst, objs) = self.generate_msvc_pch_command(target, compiler, pch)
- extradep = os.path.join(self.build_to_src, target.get_source_subdir(), pch[0])
- elif compiler.id == 'intel':
- # Intel generates on target generation
- continue
- else:
- src = os.path.join(self.build_to_src, target.get_source_subdir(), pch[0])
- (commands, dep, dst, objs) = self.generate_gcc_pch_command(target, compiler, pch[0])
- extradep = None
- pch_objects += objs
- rulename = compiler.get_language() + cstr + '_PCH'
- elem = NinjaBuildElement(self.all_outputs, dst, rulename, src)
- if extradep is not None:
- elem.add_dep(extradep)
- elem.add_item('ARGS', commands)
- elem.add_item('DEPFILE', dep)
- elem.write(outfile)
- return pch_objects
- def generate_shsym(self, outfile, target):
- target_name = self.get_target_filename(target)
- targetdir = self.get_target_private_dir(target)
- symname = os.path.join(targetdir, target_name + '.symbols')
- elem = NinjaBuildElement(self.all_outputs, symname, 'SHSYM', target_name)
- if self.environment.is_cross_build() and self.environment.cross_info.need_cross_compiler():
- elem.add_item('CROSS', '--cross-host=' + self.environment.cross_info.config['host_machine']['system'])
- elem.write(outfile)
- def get_cross_stdlib_link_args(self, target, linker):
- if isinstance(target, build.StaticLibrary) or not target.is_cross:
- return []
- if not self.environment.cross_info.has_stdlib(linker.language):
- return []
- return linker.get_no_stdlib_link_args()
- def get_target_type_link_args(self, target, linker):
- abspath = os.path.join(self.environment.get_build_dir(), target.subdir)
- commands = []
- if isinstance(target, build.Executable):
- # Currently only used with the Swift compiler to add '-emit-executable'
- commands += linker.get_std_exe_link_args()
- # If gui_app, and that's significant on this platform
- if target.gui_app and hasattr(linker, 'get_gui_app_args'):
- commands += linker.get_gui_app_args()
- # If implib, and that's significant on this platform (i.e. Windows using either GCC or Visual Studio)
- if target.import_filename:
- commands += linker.gen_import_library_args(os.path.join(target.subdir, target.import_filename))
- elif isinstance(target, build.SharedLibrary):
- if isinstance(target, build.SharedModule):
- commands += linker.get_std_shared_module_link_args()
- else:
- commands += linker.get_std_shared_lib_link_args()
- # All shared libraries are PIC
- commands += linker.get_pic_args()
- # Add -Wl,-soname arguments on Linux, -install_name on OS X
- commands += linker.get_soname_args(target.prefix, target.name, target.suffix,
- abspath, target.soversion,
- isinstance(target, build.SharedModule))
- # This is only visited when building for Windows using either GCC or Visual Studio
- if target.vs_module_defs and hasattr(linker, 'gen_vs_module_defs_args'):
- commands += linker.gen_vs_module_defs_args(target.vs_module_defs.rel_to_builddir(self.build_to_src))
- # This is only visited when building for Windows using either GCC or Visual Studio
- if target.import_filename:
- commands += linker.gen_import_library_args(os.path.join(target.subdir, target.import_filename))
- elif isinstance(target, build.StaticLibrary):
- commands += linker.get_std_link_args()
- else:
- raise RuntimeError('Unknown build target type.')
- return commands
- def get_link_whole_args(self, linker, target):
- target_args = self.build_target_link_arguments(linker, target.link_whole_targets)
- return linker.get_link_whole_for(target_args) if len(target_args) else []
- def generate_link(self, target, outfile, outname, obj_list, linker, extra_args=[]):
- if isinstance(target, build.StaticLibrary):
- linker_base = 'STATIC'
- else:
- linker_base = linker.get_language() # Fixme.
- if isinstance(target, build.SharedLibrary):
- self.generate_shsym(outfile, target)
- crstr = ''
- if target.is_cross:
- crstr = '_CROSS'
- linker_rule = linker_base + crstr + '_LINKER'
- # Create an empty commands list, and start adding link arguments from
- # various sources in the order in which they must override each other
- # starting from hard-coded defaults followed by build options and so on.
- #
- # Once all the linker options have been passed, we will start passing
- # libraries and library paths from internal and external sources.
- commands = CompilerArgs(linker)
- # First, the trivial ones that are impossible to override.
- #
- # Add linker args for linking this target derived from 'base' build
- # options passed on the command-line, in default_options, etc.
- # These have the lowest priority.
- if not isinstance(target, build.StaticLibrary):
- commands += compilers.get_base_link_args(self.environment.coredata.base_options,
- linker,
- isinstance(target, build.SharedModule))
- # Add -nostdlib if needed; can't be overriden
- commands += self.get_cross_stdlib_link_args(target, linker)
- # Add things like /NOLOGO; usually can't be overriden
- commands += linker.get_linker_always_args()
- # Add buildtype linker args: optimization level, etc.
- commands += linker.get_buildtype_linker_args(self.get_option_for_target('buildtype', target))
- # Add /DEBUG and the pdb filename when using MSVC
- commands += self.get_link_debugfile_args(linker, target, outname)
- # Add link args specific to this BuildTarget type, such as soname args,
- # PIC, import library generation, etc.
- commands += self.get_target_type_link_args(target, linker)
- # Archives that are copied wholesale in the result. Must be before any
- # other link targets so missing symbols from whole archives are found in those.
- if not isinstance(target, build.StaticLibrary):
- commands += self.get_link_whole_args(linker, target)
- if not isinstance(target, build.StaticLibrary):
- # Add link args added using add_project_link_arguments()
- commands += self.build.get_project_link_args(linker, target.subproject)
- # Add link args added using add_global_link_arguments()
- # These override per-project link arguments
- commands += self.build.get_global_link_args(linker)
- if not target.is_cross:
- # Link args added from the env: LDFLAGS. We want these to
- # override all the defaults but not the per-target link args.
- commands += self.environment.coredata.external_link_args[linker.get_language()]
- # Now we will add libraries and library paths from various sources
- # Add link args to link to all internal libraries (link_with:) and
- # internal dependencies needed by this target.
- if linker_base == 'STATIC':
- # Link arguments of static libraries are not put in the command
- # line of the library. They are instead appended to the command
- # line where the static library is used.
- dependencies = []
- else:
- dependencies = target.get_dependencies()
- commands += self.build_target_link_arguments(linker, dependencies)
- # For 'automagic' deps: Boost and GTest. Also dependency('threads').
- # pkg-config puts the thread flags itself via `Cflags:`
- for d in target.external_deps:
- if d.need_threads():
- commands += linker.thread_link_flags()
- # Only non-static built targets need link args and link dependencies
- if not isinstance(target, build.StaticLibrary):
- commands += target.link_args
- # External deps must be last because target link libraries may depend on them.
- for dep in target.get_external_deps():
- # Extend without reordering or de-dup to preserve `-L -l` sets
- # https://github.com/mesonbuild/meson/issues/1718
- commands.extend_direct(dep.get_link_args())
- for d in target.get_dependencies():
- if isinstance(d, build.StaticLibrary):
- for dep in d.get_external_deps():
- commands.extend_direct(dep.get_link_args())
- # Add link args for c_* or cpp_* build options. Currently this only
- # adds c_winlibs and cpp_winlibs when building for Windows. This needs
- # to be after all internal and external libraries so that unresolved
- # symbols from those can be found here. This is needed when the
- # *_winlibs that we want to link to are static mingw64 libraries.
- commands += linker.get_option_link_args(self.environment.coredata.compiler_options)
- # Set runtime-paths so we can run executables without needing to set
- # LD_LIBRARY_PATH, etc in the environment. Doesn't work on Windows.
- if '/' in target.name or '\\' in target.name:
- # Target names really should not have slashes in them, but
- # unfortunately we did not check for that and some downstream projects
- # now have them. Once slashes are forbidden, remove this bit.
- target_slashname_workaround_dir = os.path.join(
- os.path.split(target.name)[0],
- self.get_target_dir(target))
- else:
- target_slashname_workaround_dir = self.get_target_dir(target)
- commands += linker.build_rpath_args(self.environment.get_build_dir(),
- target_slashname_workaround_dir,
- self.determine_rpath_dirs(target),
- target.build_rpath,
- target.install_rpath)
- # Add libraries generated by custom targets
- custom_target_libraries = self.get_custom_target_provided_libraries(target)
- commands += extra_args
- commands += custom_target_libraries
- # Convert from GCC-style link argument naming to the naming used by the
- # current compiler.
- commands = commands.to_native()
- dep_targets = [self.get_dependency_filename(t) for t in dependencies]
- dep_targets.extend([self.get_dependency_filename(t)
- for t in target.link_depends])
- elem = NinjaBuildElement(self.all_outputs, outname, linker_rule, obj_list)
- elem.add_dep(dep_targets + custom_target_libraries)
- elem.add_item('LINK_ARGS', commands)
- return elem
- def get_dependency_filename(self, t):
- if isinstance(t, build.SharedLibrary):
- return os.path.join(self.get_target_private_dir(t), self.get_target_filename(t) + '.symbols')
- elif isinstance(t, mesonlib.File):
- if t.is_built:
- return t.relative_name()
- else:
- return t.absolute_path(self.environment.get_source_dir(),
- self.environment.get_build_dir())
- return self.get_target_filename(t)
- def generate_shlib_aliases(self, target, outdir):
- aliases = target.get_aliases()
- for alias, to in aliases.items():
- aliasfile = os.path.join(self.environment.get_build_dir(), outdir, alias)
- try:
- os.remove(aliasfile)
- except Exception:
- pass
- try:
- os.symlink(to, aliasfile)
- except NotImplementedError:
- mlog.debug("Library versioning disabled because symlinks are not supported.")
- except OSError:
- mlog.debug("Library versioning disabled because we do not have symlink creation privileges.")
- def generate_custom_target_clean(self, outfile, trees):
- e = NinjaBuildElement(self.all_outputs, 'clean-ctlist', 'CUSTOM_COMMAND', 'PHONY')
- d = CleanTrees(self.environment.get_build_dir(), trees)
- d_file = os.path.join(self.environment.get_scratch_dir(), 'cleantrees.dat')
- e.add_item('COMMAND', [sys.executable,
- self.environment.get_build_command(),
- '--internal', 'cleantrees', d_file])
- e.add_item('description', 'Cleaning custom target directories.')
- e.write(outfile)
- # Write out the data file passed to the script
- with open(d_file, 'wb') as ofile:
- pickle.dump(d, ofile)
- return 'clean-ctlist'
- def generate_gcov_clean(self, outfile):
- gcno_elem = NinjaBuildElement(self.all_outputs, 'clean-gcno', 'CUSTOM_COMMAND', 'PHONY')
- script_root = self.environment.get_script_dir()
- clean_script = os.path.join(script_root, 'delwithsuffix.py')
- gcno_elem.add_item('COMMAND', [sys.executable, clean_script, '.', 'gcno'])
- gcno_elem.add_item('description', 'Deleting gcno files.')
- gcno_elem.write(outfile)
- gcda_elem = NinjaBuildElement(self.all_outputs, 'clean-gcda', 'CUSTOM_COMMAND', 'PHONY')
- script_root = self.environment.get_script_dir()
- clean_script = os.path.join(script_root, 'delwithsuffix.py')
- gcda_elem.add_item('COMMAND', [sys.executable, clean_script, '.', 'gcda'])
- gcda_elem.add_item('description', 'Deleting gcda files.')
- gcda_elem.write(outfile)
- def get_user_option_args(self):
- cmds = []
- for (k, v) in self.environment.coredata.user_options.items():
- cmds.append('-D' + k + '=' + (v.value if isinstance(v.value, str) else str(v.value).lower()))
- # The order of these arguments must be the same between runs of Meson
- # to ensure reproducible output. The order we pass them shouldn't
- # affect behavior in any other way.
- return sorted(cmds)
- def generate_dist(self, outfile):
- elem = NinjaBuildElement(self.all_outputs, 'dist', 'CUSTOM_COMMAND', 'PHONY')
- elem.add_item('DESC', 'Creating source packages')
- elem.add_item('COMMAND', [sys.executable,
- self.environment.get_build_command(),
- '--internal', 'dist',
- self.environment.source_dir,
- self.environment.build_dir,
- sys.executable,
- self.environment.get_build_command()])
- elem.add_item('pool', 'console')
- elem.write(outfile)
- # For things like scan-build and other helper tools we might have.
- def generate_utils(self, outfile):
- cmd = [sys.executable, self.environment.get_build_command(),
- '--internal', 'scanbuild', self.environment.source_dir, self.environment.build_dir,
- sys.executable, self.environment.get_build_command()] + self.get_user_option_args()
- elem = NinjaBuildElement(self.all_outputs, 'scan-build', 'CUSTOM_COMMAND', 'PHONY')
- elem.add_item('COMMAND', cmd)
- elem.add_item('pool', 'console')
- elem.write(outfile)
- cmd = [sys.executable, self.environment.get_build_command(),
- '--internal', 'uninstall']
- elem = NinjaBuildElement(self.all_outputs, 'uninstall', 'CUSTOM_COMMAND', 'PHONY')
- elem.add_item('COMMAND', cmd)
- elem.add_item('pool', 'console')
- elem.write(outfile)
- def generate_ending(self, outfile):
- targetlist = []
- for t in self.get_build_by_default_targets().values():
- # Add the first output of each target to the 'all' target so that
- # they are all built
- targetlist.append(os.path.join(self.get_target_dir(t), t.get_outputs()[0]))
- elem = NinjaBuildElement(self.all_outputs, 'all', 'phony', targetlist)
- elem.write(outfile)
- default = 'default all\n\n'
- outfile.write(default)
- ninja_command = environment.detect_ninja()
- if ninja_command is None:
- raise MesonException('Could not detect Ninja v1.6 or newer')
- elem = NinjaBuildElement(self.all_outputs, 'clean', 'CUSTOM_COMMAND', 'PHONY')
- elem.add_item('COMMAND', [ninja_command, '-t', 'clean'])
- elem.add_item('description', 'Cleaning.')
- # If we have custom targets in this project, add all their outputs to
- # the list that is passed to the `cleantrees.py` script. The script
- # will manually delete all custom_target outputs that are directories
- # instead of files. This is needed because on platforms other than
- # Windows, Ninja only deletes directories while cleaning if they are
- # empty. https://github.com/mesonbuild/meson/issues/1220
- ctlist = []
- for t in self.build.get_targets().values():
- if isinstance(t, build.CustomTarget):
- # Create a list of all custom target outputs
- for o in t.get_outputs():
- ctlist.append(os.path.join(self.get_target_dir(t), o))
- if ctlist:
- elem.add_dep(self.generate_custom_target_clean(outfile, ctlist))
- if 'b_coverage' in self.environment.coredata.base_options and \
- self.environment.coredata.base_options['b_coverage'].value:
- self.generate_gcov_clean(outfile)
- elem.add_dep('clean-gcda')
- elem.add_dep('clean-gcno')
- elem.write(outfile)
- deps = self.get_regen_filelist()
- elem = NinjaBuildElement(self.all_outputs, 'build.ninja', 'REGENERATE_BUILD', deps)
- elem.add_item('pool', 'console')
- elem.write(outfile)
- elem = NinjaBuildElement(self.all_outputs, 'reconfigure', 'REGENERATE_BUILD', 'PHONY')
- elem.add_item('pool', 'console')
- elem.write(outfile)
- elem = NinjaBuildElement(self.all_outputs, deps, 'phony', '')
- elem.write(outfile)
|