1# Copyright 2012-2017 The Meson development team 2 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6 7# http://www.apache.org/licenses/LICENSE-2.0 8 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14import typing as T 15import os 16import re 17import pickle 18import shlex 19import subprocess 20from collections import OrderedDict 21from enum import Enum, unique 22import itertools 23from pathlib import PurePath, Path 24from functools import lru_cache 25 26from . import backends 27from .. import modules 28from .. import environment, mesonlib 29from .. import build 30from .. import mlog 31from .. import dependencies 32from .. import compilers 33from ..arglist import CompilerArgs 34from ..compilers import ( 35 Compiler, CCompiler, 36 DmdDCompiler, 37 FortranCompiler, PGICCompiler, 38 VisualStudioCsCompiler, 39 VisualStudioLikeCompiler, 40) 41from ..linkers import ArLinker, VisualStudioLinker 42from ..mesonlib import ( 43 File, LibType, MachineChoice, MesonException, OrderedSet, PerMachine, 44 ProgressBar, quote_arg, unholder, 45) 46from ..mesonlib import get_compiler_for_source, has_path_sep 47from .backends import CleanTrees 48from ..build import InvalidArguments 49from ..interpreter import Interpreter 50 51FORTRAN_INCLUDE_PAT = r"^\s*#?include\s*['\"](\w+\.\w+)['\"]" 52FORTRAN_MODULE_PAT = r"^\s*\bmodule\b\s+(\w+)\s*(?:!+.*)*$" 53FORTRAN_SUBMOD_PAT = r"^\s*\bsubmodule\b\s*\((\w+:?\w+)\)\s*(\w+)" 54FORTRAN_USE_PAT = r"^\s*use,?\s*(?:non_intrinsic)?\s*(?:::)?\s*(\w+)" 55 56def cmd_quote(s): 57 # see: https://docs.microsoft.com/en-us/windows/desktop/api/shellapi/nf-shellapi-commandlinetoargvw#remarks 58 59 # backslash escape any existing double quotes 60 # any existing backslashes preceding a quote are doubled 61 s = re.sub(r'(\\*)"', lambda m: '\\' * (len(m.group(1)) * 2 + 1) + '"', s) 62 # any terminal backslashes likewise need doubling 63 s = re.sub(r'(\\*)$', lambda m: '\\' * (len(m.group(1)) * 2), s) 64 # and double quote 65 s = '"{}"'.format(s) 66 67 return s 68 69def gcc_rsp_quote(s): 70 # see: the function buildargv() in libiberty 71 # 72 # this differs from sh-quoting in that a backslash *always* escapes the 73 # following character, even inside single quotes. 74 75 s = s.replace('\\', '\\\\') 76 77 return shlex.quote(s) 78 79# How ninja executes command lines differs between Unix and Windows 80# (see https://ninja-build.org/manual.html#ref_rule_command) 81if mesonlib.is_windows(): 82 quote_func = cmd_quote 83 execute_wrapper = ['cmd', '/c'] # unused 84 rmfile_prefix = ['del', '/f', '/s', '/q', '{}', '&&'] 85else: 86 quote_func = quote_arg 87 execute_wrapper = [] 88 rmfile_prefix = ['rm', '-f', '{}', '&&'] 89 90def get_rsp_threshold(): 91 '''Return a conservative estimate of the commandline size in bytes 92 above which a response file should be used. May be overridden for 93 debugging by setting environment variable MESON_RSP_THRESHOLD.''' 94 95 if mesonlib.is_windows(): 96 # Usually 32k, but some projects might use cmd.exe, 97 # and that has a limit of 8k. 98 limit = 8192 99 else: 100 # On Linux, ninja always passes the commandline as a single 101 # big string to /bin/sh, and the kernel limits the size of a 102 # single argument; see MAX_ARG_STRLEN 103 limit = 131072 104 # Be conservative 105 limit = limit / 2 106 return int(os.environ.get('MESON_RSP_THRESHOLD', limit)) 107 108# a conservative estimate of the command-line length limit 109rsp_threshold = get_rsp_threshold() 110 111# ninja variables whose value should remain unquoted. The value of these ninja 112# variables (or variables we use them in) is interpreted directly by ninja 113# (e.g. the value of the depfile variable is a pathname that ninja will read 114# from, etc.), so it must not be shell quoted. 115raw_names = {'DEPFILE_UNQUOTED', 'DESC', 'pool', 'description', 'targetdep'} 116 117def ninja_quote(text, is_build_line=False): 118 if is_build_line: 119 qcs = ('$', ' ', ':') 120 else: 121 qcs = ('$', ' ') 122 for char in qcs: 123 text = text.replace(char, '$' + char) 124 if '\n' in text: 125 errmsg = '''Ninja does not support newlines in rules. The content was: 126 127{} 128 129Please report this error with a test case to the Meson bug tracker.'''.format(text) 130 raise MesonException(errmsg) 131 return text 132 133@unique 134class Quoting(Enum): 135 both = 0 136 notShell = 1 137 notNinja = 2 138 none = 3 139 140class NinjaCommandArg: 141 def __init__(self, s, quoting = Quoting.both): 142 self.s = s 143 self.quoting = quoting 144 145 def __str__(self): 146 return self.s 147 148 @staticmethod 149 def list(l, q): 150 return [NinjaCommandArg(i, q) for i in l] 151 152class NinjaComment: 153 def __init__(self, comment): 154 self.comment = comment 155 156 def write(self, outfile): 157 for l in self.comment.split('\n'): 158 outfile.write('# ') 159 outfile.write(l) 160 outfile.write('\n') 161 outfile.write('\n') 162 163class NinjaRule: 164 def __init__(self, rule, command, args, description, 165 rspable = False, deps = None, depfile = None, extra = None, 166 rspfile_quote_style = 'gcc'): 167 168 def strToCommandArg(c): 169 if isinstance(c, NinjaCommandArg): 170 return c 171 172 # deal with common cases here, so we don't have to explicitly 173 # annotate the required quoting everywhere 174 if c == '&&': 175 # shell constructs shouldn't be shell quoted 176 return NinjaCommandArg(c, Quoting.notShell) 177 if c.startswith('$'): 178 var = re.search(r'\$\{?(\w*)\}?', c).group(1) 179 if var not in raw_names: 180 # ninja variables shouldn't be ninja quoted, and their value 181 # is already shell quoted 182 return NinjaCommandArg(c, Quoting.none) 183 else: 184 # shell quote the use of ninja variables whose value must 185 # not be shell quoted (as it also used by ninja) 186 return NinjaCommandArg(c, Quoting.notNinja) 187 188 return NinjaCommandArg(c) 189 190 self.name = rule 191 self.command = list(map(strToCommandArg, command)) # includes args which never go into a rspfile 192 self.args = list(map(strToCommandArg, args)) # args which will go into a rspfile, if used 193 self.description = description 194 self.deps = deps # depstyle 'gcc' or 'msvc' 195 self.depfile = depfile 196 self.extra = extra 197 self.rspable = rspable # if a rspfile can be used 198 self.refcount = 0 199 self.rsprefcount = 0 200 self.rspfile_quote_style = rspfile_quote_style # rspfile quoting style is 'gcc' or 'cl' 201 202 if self.depfile == '$DEPFILE': 203 self.depfile += '_UNQUOTED' 204 205 @staticmethod 206 def _quoter(x, qf = quote_func): 207 if isinstance(x, NinjaCommandArg): 208 if x.quoting == Quoting.none: 209 return x.s 210 elif x.quoting == Quoting.notNinja: 211 return qf(x.s) 212 elif x.quoting == Quoting.notShell: 213 return ninja_quote(x.s) 214 # fallthrough 215 return ninja_quote(qf(str(x))) 216 217 def write(self, outfile): 218 if self.rspfile_quote_style == 'cl': 219 rspfile_quote_func = cmd_quote 220 else: 221 rspfile_quote_func = gcc_rsp_quote 222 223 def rule_iter(): 224 if self.refcount: 225 yield '' 226 if self.rsprefcount: 227 yield '_RSP' 228 229 for rsp in rule_iter(): 230 outfile.write('rule {}{}\n'.format(self.name, rsp)) 231 if rsp == '_RSP': 232 outfile.write(' command = {} @$out.rsp\n'.format(' '.join([self._quoter(x) for x in self.command]))) 233 outfile.write(' rspfile = $out.rsp\n') 234 outfile.write(' rspfile_content = {}\n'.format(' '.join([self._quoter(x, rspfile_quote_func) for x in self.args]))) 235 else: 236 outfile.write(' command = {}\n'.format(' '.join([self._quoter(x) for x in (self.command + self.args)]))) 237 if self.deps: 238 outfile.write(' deps = {}\n'.format(self.deps)) 239 if self.depfile: 240 outfile.write(' depfile = {}\n'.format(self.depfile)) 241 outfile.write(' description = {}\n'.format(self.description)) 242 if self.extra: 243 for l in self.extra.split('\n'): 244 outfile.write(' ') 245 outfile.write(l) 246 outfile.write('\n') 247 outfile.write('\n') 248 249 def length_estimate(self, infiles, outfiles, elems): 250 # determine variables 251 # this order of actions only approximates ninja's scoping rules, as 252 # documented at: https://ninja-build.org/manual.html#ref_scope 253 ninja_vars = {} 254 for e in elems: 255 (name, value) = e 256 ninja_vars[name] = value 257 ninja_vars['deps'] = self.deps 258 ninja_vars['depfile'] = self.depfile 259 ninja_vars['in'] = infiles 260 ninja_vars['out'] = outfiles 261 262 # expand variables in command 263 command = ' '.join([self._quoter(x) for x in self.command + self.args]) 264 expanded_command = '' 265 for m in re.finditer(r'(\${\w*})|(\$\w*)|([^$]*)', command): 266 chunk = m.group() 267 if chunk.startswith('$'): 268 chunk = chunk[1:] 269 chunk = re.sub(r'{(.*)}', r'\1', chunk) 270 chunk = ninja_vars.get(chunk, []) # undefined ninja variables are empty 271 chunk = ' '.join(chunk) 272 expanded_command += chunk 273 274 # determine command length 275 return len(expanded_command) 276 277class NinjaBuildElement: 278 def __init__(self, all_outputs, outfilenames, rulename, infilenames, implicit_outs=None): 279 self.implicit_outfilenames = implicit_outs or [] 280 if isinstance(outfilenames, str): 281 self.outfilenames = [outfilenames] 282 else: 283 self.outfilenames = outfilenames 284 assert(isinstance(rulename, str)) 285 self.rulename = rulename 286 if isinstance(infilenames, str): 287 self.infilenames = [infilenames] 288 else: 289 self.infilenames = infilenames 290 self.deps = OrderedSet() 291 self.orderdeps = OrderedSet() 292 self.elems = [] 293 self.all_outputs = all_outputs 294 295 def add_dep(self, dep): 296 if isinstance(dep, list): 297 self.deps.update(dep) 298 else: 299 self.deps.add(dep) 300 301 def add_orderdep(self, dep): 302 if isinstance(dep, list): 303 self.orderdeps.update(dep) 304 else: 305 self.orderdeps.add(dep) 306 307 def add_item(self, name, elems): 308 # Always convert from GCC-style argument naming to the naming used by the 309 # current compiler. Also filter system include paths, deduplicate, etc. 310 if isinstance(elems, CompilerArgs): 311 elems = elems.to_native() 312 if isinstance(elems, str): 313 elems = [elems] 314 self.elems.append((name, elems)) 315 316 if name == 'DEPFILE': 317 self.elems.append((name + '_UNQUOTED', elems)) 318 319 def _should_use_rspfile(self): 320 # 'phony' is a rule built-in to ninja 321 if self.rulename == 'phony': 322 return False 323 324 if not self.rule.rspable: 325 return False 326 327 infilenames = ' '.join([ninja_quote(i, True) for i in self.infilenames]) 328 outfilenames = ' '.join([ninja_quote(i, True) for i in self.outfilenames]) 329 330 return self.rule.length_estimate(infilenames, 331 outfilenames, 332 self.elems) >= rsp_threshold 333 334 def count_rule_references(self): 335 if self.rulename != 'phony': 336 if self._should_use_rspfile(): 337 self.rule.rsprefcount += 1 338 else: 339 self.rule.refcount += 1 340 341 def write(self, outfile): 342 self.check_outputs() 343 ins = ' '.join([ninja_quote(i, True) for i in self.infilenames]) 344 outs = ' '.join([ninja_quote(i, True) for i in self.outfilenames]) 345 implicit_outs = ' '.join([ninja_quote(i, True) for i in self.implicit_outfilenames]) 346 if implicit_outs: 347 implicit_outs = ' | ' + implicit_outs 348 use_rspfile = self._should_use_rspfile() 349 if use_rspfile: 350 rulename = self.rulename + '_RSP' 351 mlog.debug("Command line for building %s is long, using a response file" % self.outfilenames) 352 else: 353 rulename = self.rulename 354 line = 'build {}{}: {} {}'.format(outs, implicit_outs, rulename, ins) 355 if len(self.deps) > 0: 356 line += ' | ' + ' '.join([ninja_quote(x, True) for x in self.deps]) 357 if len(self.orderdeps) > 0: 358 line += ' || ' + ' '.join([ninja_quote(x, True) for x in self.orderdeps]) 359 line += '\n' 360 # This is the only way I could find to make this work on all 361 # platforms including Windows command shell. Slash is a dir separator 362 # on Windows, too, so all characters are unambiguous and, more importantly, 363 # do not require quoting, unless explicitly specified, which is necessary for 364 # the csc compiler. 365 line = line.replace('\\', '/') 366 outfile.write(line) 367 368 if use_rspfile: 369 if self.rule.rspfile_quote_style == 'cl': 370 qf = cmd_quote 371 else: 372 qf = gcc_rsp_quote 373 else: 374 qf = quote_func 375 376 for e in self.elems: 377 (name, elems) = e 378 should_quote = name not in raw_names 379 line = ' {} = '.format(name) 380 newelems = [] 381 for i in elems: 382 if not should_quote or i == '&&': # Hackety hack hack 383 quoter = ninja_quote 384 else: 385 quoter = lambda x: ninja_quote(qf(x)) 386 newelems.append(quoter(i)) 387 line += ' '.join(newelems) 388 line += '\n' 389 outfile.write(line) 390 outfile.write('\n') 391 392 def check_outputs(self): 393 for n in self.outfilenames: 394 if n in self.all_outputs: 395 raise MesonException('Multiple producers for Ninja target "{}". Please rename your targets.'.format(n)) 396 self.all_outputs[n] = True 397 398class NinjaBackend(backends.Backend): 399 400 def __init__(self, build: T.Optional[build.Build], interpreter: T.Optional[Interpreter]): 401 super().__init__(build, interpreter) 402 self.name = 'ninja' 403 self.ninja_filename = 'build.ninja' 404 self.fortran_deps = {} 405 self.all_outputs = {} 406 self.introspection_data = {} 407 self.created_llvm_ir_rule = PerMachine(False, False) 408 409 def create_target_alias(self, to_target): 410 # We need to use aliases for targets that might be used as directory 411 # names to workaround a Ninja bug that breaks `ninja -t clean`. 412 # This is used for 'reserved' targets such as 'test', 'install', 413 # 'benchmark', etc, and also for RunTargets. 414 # https://github.com/mesonbuild/meson/issues/1644 415 if not to_target.startswith('meson-'): 416 m = 'Invalid usage of create_target_alias with {!r}' 417 raise AssertionError(m.format(to_target)) 418 from_target = to_target[len('meson-'):] 419 elem = NinjaBuildElement(self.all_outputs, from_target, 'phony', to_target) 420 self.add_build(elem) 421 422 def detect_vs_dep_prefix(self, tempfilename): 423 '''VS writes its dependency in a locale dependent format. 424 Detect the search prefix to use.''' 425 # TODO don't hard-code host 426 for compiler in self.environment.coredata.compilers.host.values(): 427 # Have to detect the dependency format 428 429 # IFort on windows is MSVC like, but doesn't have /showincludes 430 if isinstance(compiler, FortranCompiler): 431 continue 432 if isinstance(compiler, PGICCompiler) and mesonlib.is_windows(): 433 # for the purpose of this function, PGI doesn't act enough like MSVC 434 return open(tempfilename, 'a', encoding='utf-8') 435 if isinstance(compiler, VisualStudioLikeCompiler): 436 break 437 else: 438 # None of our compilers are MSVC, we're done. 439 return open(tempfilename, 'a', encoding='utf-8') 440 filename = os.path.join(self.environment.get_scratch_dir(), 441 'incdetect.c') 442 with open(filename, 'w') as f: 443 f.write('''#include<stdio.h> 444int dummy; 445''') 446 447 # The output of cl dependency information is language 448 # and locale dependent. Any attempt at converting it to 449 # Python strings leads to failure. We _must_ do this detection 450 # in raw byte mode and write the result in raw bytes. 451 pc = subprocess.Popen(compiler.get_exelist() + 452 ['/showIncludes', '/c', 'incdetect.c'], 453 cwd=self.environment.get_scratch_dir(), 454 stdout=subprocess.PIPE, stderr=subprocess.PIPE) 455 (stdout, stderr) = pc.communicate() 456 457 # We want to match 'Note: including file: ' in the line 458 # 'Note: including file: d:\MyDir\include\stdio.h', however 459 # different locales have different messages with a different 460 # number of colons. Match up to the the drive name 'd:\'. 461 # When used in cross compilation, the path separator is a 462 # backslash rather than a forward slash so handle both. 463 matchre = re.compile(rb"^(.*\s)([a-zA-Z]:\\|\/).*stdio.h$") 464 465 def detect_prefix(out): 466 for line in re.split(rb'\r?\n', out): 467 match = matchre.match(line) 468 if match: 469 with open(tempfilename, 'ab') as binfile: 470 binfile.write(b'msvc_deps_prefix = ' + match.group(1) + b'\n') 471 return open(tempfilename, 'a', encoding='utf-8') 472 return None 473 474 # Some cl wrappers (e.g. Squish Coco) output dependency info 475 # to stderr rather than stdout 476 result = detect_prefix(stdout) or detect_prefix(stderr) 477 if result: 478 return result 479 480 raise MesonException('Could not determine vs dep dependency prefix string.') 481 482 def generate(self): 483 ninja = environment.detect_ninja_command_and_version(log=True) 484 if ninja is None: 485 raise MesonException('Could not detect Ninja v1.7 or newer') 486 (self.ninja_command, self.ninja_version) = ninja 487 outfilename = os.path.join(self.environment.get_build_dir(), self.ninja_filename) 488 tempfilename = outfilename + '~' 489 with open(tempfilename, 'w', encoding='utf-8') as outfile: 490 outfile.write('# This is the build file for project "{}"\n'.format(self.build.get_project())) 491 outfile.write('# It is autogenerated by the Meson build system.\n') 492 outfile.write('# Do not edit by hand.\n\n') 493 outfile.write('ninja_required_version = 1.7.1\n\n') 494 495 num_pools = self.environment.coredata.backend_options['backend_max_links'].value 496 if num_pools > 0: 497 outfile.write('''pool link_pool 498 depth = {} 499 500'''.format(num_pools)) 501 502 with self.detect_vs_dep_prefix(tempfilename) as outfile: 503 self.generate_rules() 504 505 self.build_elements = [] 506 self.generate_phony() 507 self.add_build_comment(NinjaComment('Build rules for targets')) 508 for t in ProgressBar(self.build.get_targets().values(), desc='Generating targets'): 509 self.generate_target(t) 510 self.add_build_comment(NinjaComment('Test rules')) 511 self.generate_tests() 512 self.add_build_comment(NinjaComment('Install rules')) 513 self.generate_install() 514 self.generate_dist() 515 if 'b_coverage' in self.environment.coredata.base_options and \ 516 self.environment.coredata.base_options['b_coverage'].value: 517 self.add_build_comment(NinjaComment('Coverage rules')) 518 self.generate_coverage_rules() 519 self.add_build_comment(NinjaComment('Suffix')) 520 self.generate_utils() 521 self.generate_ending() 522 523 self.write_rules(outfile) 524 self.write_builds(outfile) 525 526 default = 'default all\n\n' 527 outfile.write(default) 528 # Only overwrite the old build file after the new one has been 529 # fully created. 530 os.replace(tempfilename, outfilename) 531 mlog.cmd_ci_include(outfilename) # For CI debugging 532 self.generate_compdb() 533 534 # http://clang.llvm.org/docs/JSONCompilationDatabase.html 535 def generate_compdb(self): 536 rules = [] 537 # TODO: Rather than an explicit list here, rules could be marked in the 538 # rule store as being wanted in compdb 539 for for_machine in MachineChoice: 540 for lang in self.environment.coredata.compilers[for_machine]: 541 rules += [ "%s%s" % (rule, ext) for rule in [self.get_compiler_rule_name(lang, for_machine)] 542 for ext in ['', '_RSP']] 543 rules += [ "%s%s" % (rule, ext) for rule in [self.get_pch_rule_name(lang, for_machine)] 544 for ext in ['', '_RSP']] 545 compdb_options = ['-x'] if mesonlib.version_compare(self.ninja_version, '>=1.9') else [] 546 ninja_compdb = self.ninja_command + ['-t', 'compdb'] + compdb_options + rules 547 builddir = self.environment.get_build_dir() 548 try: 549 jsondb = subprocess.check_output(ninja_compdb, cwd=builddir) 550 with open(os.path.join(builddir, 'compile_commands.json'), 'wb') as f: 551 f.write(jsondb) 552 except Exception: 553 mlog.warning('Could not create compilation database.') 554 555 # Get all generated headers. Any source file might need them so 556 # we need to add an order dependency to them. 557 def get_generated_headers(self, target): 558 if hasattr(target, 'cached_generated_headers'): 559 return target.cached_generated_headers 560 header_deps = [] 561 # XXX: Why don't we add deps to CustomTarget headers here? 562 for genlist in target.get_generated_sources(): 563 if isinstance(genlist, (build.CustomTarget, build.CustomTargetIndex)): 564 continue 565 for src in genlist.get_outputs(): 566 if self.environment.is_header(src): 567 header_deps.append(self.get_target_generated_dir(target, genlist, src)) 568 if 'vala' in target.compilers and not isinstance(target, build.Executable): 569 vala_header = File.from_built_file(self.get_target_dir(target), target.vala_header) 570 header_deps.append(vala_header) 571 # Recurse and find generated headers 572 for dep in itertools.chain(target.link_targets, target.link_whole_targets): 573 if isinstance(dep, (build.StaticLibrary, build.SharedLibrary)): 574 header_deps += self.get_generated_headers(dep) 575 target.cached_generated_headers = header_deps 576 return header_deps 577 578 def get_target_generated_sources(self, target): 579 """ 580 Returns a dictionary with the keys being the path to the file 581 (relative to the build directory) of that type and the value 582 being the GeneratorList or CustomTarget that generated it. 583 """ 584 srcs = OrderedDict() 585 for gensrc in target.get_generated_sources(): 586 for s in gensrc.get_outputs(): 587 f = self.get_target_generated_dir(target, gensrc, s) 588 srcs[f] = s 589 return srcs 590 591 def get_target_sources(self, target): 592 srcs = OrderedDict() 593 for s in target.get_sources(): 594 # BuildTarget sources are always mesonlib.File files which are 595 # either in the source root, or generated with configure_file and 596 # in the build root 597 if not isinstance(s, File): 598 raise InvalidArguments('All sources in target {!r} must be of type mesonlib.File'.format(s)) 599 f = s.rel_to_builddir(self.build_to_src) 600 srcs[f] = s 601 return srcs 602 603 # Languages that can mix with C or C++ but don't support unity builds yet 604 # because the syntax we use for unity builds is specific to C/++/ObjC/++. 605 # Assembly files cannot be unitified and neither can LLVM IR files 606 langs_cant_unity = ('d', 'fortran') 607 608 def get_target_source_can_unity(self, target, source): 609 if isinstance(source, File): 610 source = source.fname 611 if self.environment.is_llvm_ir(source) or \ 612 self.environment.is_assembly(source): 613 return False 614 suffix = os.path.splitext(source)[1][1:] 615 for lang in self.langs_cant_unity: 616 if lang not in target.compilers: 617 continue 618 if suffix in target.compilers[lang].file_suffixes: 619 return False 620 return True 621 622 def create_target_source_introspection(self, target: build.Target, comp: compilers.Compiler, parameters, sources, generated_sources): 623 ''' 624 Adds the source file introspection information for a language of a target 625 626 Internal introspection storage formart: 627 self.introspection_data = { 628 '<target ID>': { 629 <id tuple>: { 630 'language: 'lang', 631 'compiler': ['comp', 'exe', 'list'], 632 'parameters': ['UNIQUE', 'parameter', 'list'], 633 'sources': [], 634 'generated_sources': [], 635 } 636 } 637 } 638 ''' 639 tid = target.get_id() 640 lang = comp.get_language() 641 tgt = self.introspection_data[tid] 642 # Find an existing entry or create a new one 643 id_hash = (lang, tuple(parameters)) 644 src_block = tgt.get(id_hash, None) 645 if src_block is None: 646 # Convert parameters 647 if isinstance(parameters, CompilerArgs): 648 parameters = parameters.to_native(copy=True) 649 parameters = comp.compute_parameters_with_absolute_paths(parameters, self.build_dir) 650 # The new entry 651 src_block = { 652 'language': lang, 653 'compiler': comp.get_exelist(), 654 'parameters': parameters, 655 'sources': [], 656 'generated_sources': [], 657 } 658 tgt[id_hash] = src_block 659 # Make source files absolute 660 sources = [x.absolute_path(self.source_dir, self.build_dir) if isinstance(x, File) else os.path.normpath(os.path.join(self.build_dir, x)) 661 for x in sources] 662 generated_sources = [x.absolute_path(self.source_dir, self.build_dir) if isinstance(x, File) else os.path.normpath(os.path.join(self.build_dir, x)) 663 for x in generated_sources] 664 # Add the source files 665 src_block['sources'] += sources 666 src_block['generated_sources'] += generated_sources 667 668 def is_rust_target(self, target): 669 if len(target.sources) > 0: 670 first_file = target.sources[0] 671 if first_file.fname.endswith('.rs'): 672 return True 673 return False 674 675 def generate_target(self, target): 676 if isinstance(target, build.CustomTarget): 677 self.generate_custom_target(target) 678 if isinstance(target, build.RunTarget): 679 self.generate_run_target(target) 680 name = target.get_id() 681 if name in self.processed_targets: 682 return 683 self.processed_targets[name] = True 684 # Initialize an empty introspection source list 685 self.introspection_data[name] = {} 686 # Generate rules for all dependency targets 687 self.process_target_dependencies(target) 688 # If target uses a language that cannot link to C objects, 689 # just generate for that language and return. 690 if isinstance(target, build.Jar): 691 self.generate_jar_target(target) 692 return 693 if self.is_rust_target(target): 694 self.generate_rust_target(target) 695 return 696 if 'cs' in target.compilers: 697 self.generate_cs_target(target) 698 return 699 if 'swift' in target.compilers: 700 self.generate_swift_target(target) 701 return 702 703 # Now we handle the following languages: 704 # ObjC++, ObjC, C++, C, D, Fortran, Vala 705 706 # target_sources: 707 # Pre-existing target C/C++ sources to be built; dict of full path to 708 # source relative to build root and the original File object. 709 # generated_sources: 710 # GeneratedList and CustomTarget sources to be built; dict of the full 711 # path to source relative to build root and the generating target/list 712 # vala_generated_sources: 713 # Array of sources generated by valac that have to be compiled 714 if 'vala' in target.compilers: 715 # Sources consumed by valac are filtered out. These only contain 716 # C/C++ sources, objects, generated libs, and unknown sources now. 717 target_sources, generated_sources, \ 718 vala_generated_sources = self.generate_vala_compile(target) 719 else: 720 target_sources = self.get_target_sources(target) 721 generated_sources = self.get_target_generated_sources(target) 722 vala_generated_sources = [] 723 self.scan_fortran_module_outputs(target) 724 # Generate rules for GeneratedLists 725 self.generate_generator_list_rules(target) 726 727 # Generate rules for building the remaining source files in this target 728 outname = self.get_target_filename(target) 729 obj_list = [] 730 is_unity = self.is_unity(target) 731 header_deps = [] 732 unity_src = [] 733 unity_deps = [] # Generated sources that must be built before compiling a Unity target. 734 header_deps += self.get_generated_headers(target) 735 736 if is_unity: 737 # Warn about incompatible sources if a unity build is enabled 738 langs = set(target.compilers.keys()) 739 langs_cant = langs.intersection(self.langs_cant_unity) 740 if langs_cant: 741 langs_are = langs = ', '.join(langs_cant).upper() 742 langs_are += ' are' if len(langs_cant) > 1 else ' is' 743 msg = '{} not supported in Unity builds yet, so {} ' \ 744 'sources in the {!r} target will be compiled normally' \ 745 ''.format(langs_are, langs, target.name) 746 mlog.log(mlog.red('FIXME'), msg) 747 748 # Get a list of all generated headers that will be needed while building 749 # this target's sources (generated sources and pre-existing sources). 750 # This will be set as dependencies of all the target's sources. At the 751 # same time, also deal with generated sources that need to be compiled. 752 generated_source_files = [] 753 for rel_src in generated_sources.keys(): 754 dirpart, fnamepart = os.path.split(rel_src) 755 raw_src = File(True, dirpart, fnamepart) 756 if self.environment.is_source(rel_src) and not self.environment.is_header(rel_src): 757 if is_unity and self.get_target_source_can_unity(target, rel_src): 758 unity_deps.append(raw_src) 759 abs_src = os.path.join(self.environment.get_build_dir(), rel_src) 760 unity_src.append(abs_src) 761 else: 762 generated_source_files.append(raw_src) 763 elif self.environment.is_object(rel_src): 764 obj_list.append(rel_src) 765 elif self.environment.is_library(rel_src) or modules.is_module_library(rel_src): 766 pass 767 else: 768 # Assume anything not specifically a source file is a header. This is because 769 # people generate files with weird suffixes (.inc, .fh) that they then include 770 # in their source files. 771 header_deps.append(raw_src) 772 # These are the generated source files that need to be built for use by 773 # this target. We create the Ninja build file elements for this here 774 # because we need `header_deps` to be fully generated in the above loop. 775 for src in generated_source_files: 776 if self.environment.is_llvm_ir(src): 777 o = self.generate_llvm_ir_compile(target, src) 778 else: 779 o = self.generate_single_compile(target, src, True, 780 order_deps=header_deps) 781 obj_list.append(o) 782 783 use_pch = self.environment.coredata.base_options.get('b_pch', False) 784 if use_pch and target.has_pch(): 785 pch_objects = self.generate_pch(target, header_deps=header_deps) 786 else: 787 pch_objects = [] 788 789 # Generate compilation targets for C sources generated from Vala 790 # sources. This can be extended to other $LANG->C compilers later if 791 # necessary. This needs to be separate for at least Vala 792 vala_generated_source_files = [] 793 for src in vala_generated_sources: 794 dirpart, fnamepart = os.path.split(src) 795 raw_src = File(True, dirpart, fnamepart) 796 if is_unity: 797 unity_src.append(os.path.join(self.environment.get_build_dir(), src)) 798 header_deps.append(raw_src) 799 else: 800 # Generated targets are ordered deps because the must exist 801 # before the sources compiling them are used. After the first 802 # compile we get precise dependency info from dep files. 803 # This should work in all cases. If it does not, then just 804 # move them from orderdeps to proper deps. 805 if self.environment.is_header(src): 806 header_deps.append(raw_src) 807 else: 808 # We gather all these and generate compile rules below 809 # after `header_deps` (above) is fully generated 810 vala_generated_source_files.append(raw_src) 811 for src in vala_generated_source_files: 812 # Passing 'vala' here signifies that we want the compile 813 # arguments to be specialized for C code generated by 814 # valac. For instance, no warnings should be emitted. 815 obj_list.append(self.generate_single_compile(target, src, 'vala', [], header_deps)) 816 817 # Generate compile targets for all the pre-existing sources for this target 818 for src in target_sources.values(): 819 if not self.environment.is_header(src): 820 if self.environment.is_llvm_ir(src): 821 obj_list.append(self.generate_llvm_ir_compile(target, src)) 822 elif is_unity and self.get_target_source_can_unity(target, src): 823 abs_src = os.path.join(self.environment.get_build_dir(), 824 src.rel_to_builddir(self.build_to_src)) 825 unity_src.append(abs_src) 826 else: 827 obj_list.append(self.generate_single_compile(target, src, False, [], header_deps)) 828 obj_list += self.flatten_object_list(target) 829 if is_unity: 830 for src in self.generate_unity_files(target, unity_src): 831 obj_list.append(self.generate_single_compile(target, src, True, unity_deps + header_deps)) 832 linker, stdlib_args = self.determine_linker_and_stdlib_args(target) 833 elem = self.generate_link(target, outname, obj_list, linker, pch_objects, stdlib_args=stdlib_args) 834 self.generate_shlib_aliases(target, self.get_target_dir(target)) 835 self.add_build(elem) 836 837 def process_target_dependencies(self, target): 838 for t in target.get_dependencies(): 839 if t.get_id() not in self.processed_targets: 840 self.generate_target(t) 841 842 def custom_target_generator_inputs(self, target): 843 for s in unholder(target.sources): 844 if isinstance(s, build.GeneratedList): 845 self.generate_genlist_for_target(s, target) 846 847 def unwrap_dep_list(self, target): 848 deps = [] 849 for i in target.get_dependencies(): 850 # FIXME, should not grab element at zero but rather expand all. 851 if isinstance(i, list): 852 i = i[0] 853 # Add a dependency on all the outputs of this target 854 for output in i.get_outputs(): 855 deps.append(os.path.join(self.get_target_dir(i), output)) 856 return deps 857 858 def generate_custom_target(self, target): 859 self.custom_target_generator_inputs(target) 860 (srcs, ofilenames, cmd) = self.eval_custom_target_command(target) 861 deps = self.unwrap_dep_list(target) 862 deps += self.get_custom_target_depend_files(target) 863 desc = 'Generating {0} with a {1} command' 864 if target.build_always_stale: 865 deps.append('PHONY') 866 if target.depfile is None: 867 rulename = 'CUSTOM_COMMAND' 868 else: 869 rulename = 'CUSTOM_COMMAND_DEP' 870 elem = NinjaBuildElement(self.all_outputs, ofilenames, rulename, srcs) 871 elem.add_dep(deps) 872 for d in target.extra_depends: 873 # Add a dependency on all the outputs of this target 874 for output in d.get_outputs(): 875 elem.add_dep(os.path.join(self.get_target_dir(d), output)) 876 877 meson_exe_cmd = self.as_meson_exe_cmdline(target.name, target.command[0], cmd[1:], 878 for_machine=target.for_machine, 879 extra_bdeps=target.get_transitive_build_target_deps(), 880 capture=ofilenames[0] if target.capture else None) 881 if meson_exe_cmd: 882 cmd = meson_exe_cmd 883 cmd_type = 'meson_exe.py custom' 884 else: 885 cmd_type = 'custom' 886 if target.depfile is not None: 887 depfile = target.get_dep_outname(elem.infilenames) 888 rel_dfile = os.path.join(self.get_target_dir(target), depfile) 889 abs_pdir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target)) 890 os.makedirs(abs_pdir, exist_ok=True) 891 elem.add_item('DEPFILE', rel_dfile) 892 if target.console: 893 elem.add_item('pool', 'console') 894 cmd = self.replace_paths(target, cmd) 895 elem.add_item('COMMAND', cmd) 896 elem.add_item('description', desc.format(target.name, cmd_type)) 897 self.add_build(elem) 898 self.processed_targets[target.get_id()] = True 899 900 def build_run_target_name(self, target): 901 if target.subproject != '': 902 subproject_prefix = '{}@@'.format(target.subproject) 903 else: 904 subproject_prefix = '' 905 return '{}{}'.format(subproject_prefix, target.name) 906 907 def generate_run_target(self, target): 908 cmd = self.environment.get_build_command() + ['--internal', 'commandrunner'] 909 deps = self.unwrap_dep_list(target) 910 arg_strings = [] 911 for i in target.args: 912 if isinstance(i, str): 913 arg_strings.append(i) 914 elif isinstance(i, (build.BuildTarget, build.CustomTarget)): 915 relfname = self.get_target_filename(i) 916 arg_strings.append(os.path.join(self.environment.get_build_dir(), relfname)) 917 deps.append(relfname) 918 elif isinstance(i, mesonlib.File): 919 relfname = i.rel_to_builddir(self.build_to_src) 920 arg_strings.append(os.path.join(self.environment.get_build_dir(), relfname)) 921 else: 922 raise AssertionError('Unreachable code in generate_run_target: ' + str(i)) 923 cmd += [self.environment.get_source_dir(), 924 self.environment.get_build_dir(), 925 target.subdir] + self.environment.get_build_command() 926 texe = target.command 927 try: 928 texe = texe.held_object 929 except AttributeError: 930 pass 931 if isinstance(texe, build.Executable): 932 abs_exe = os.path.join(self.environment.get_build_dir(), self.get_target_filename(texe)) 933 deps.append(self.get_target_filename(texe)) 934 if self.environment.is_cross_build(): 935 exe_wrap = self.environment.get_exe_wrapper() 936 if exe_wrap: 937 if not exe_wrap.found(): 938 msg = 'The exe_wrapper {!r} defined in the cross file is ' \ 939 'needed by run target {!r}, but was not found. ' \ 940 'Please check the command and/or add it to PATH.' 941 raise MesonException(msg.format(exe_wrap.name, target.name)) 942 cmd += exe_wrap.get_command() 943 cmd.append(abs_exe) 944 elif isinstance(texe, dependencies.ExternalProgram): 945 cmd += texe.get_command() 946 elif isinstance(texe, build.CustomTarget): 947 deps.append(self.get_target_filename(texe)) 948 cmd += [os.path.join(self.environment.get_build_dir(), self.get_target_filename(texe))] 949 elif isinstance(texe, mesonlib.File): 950 cmd.append(texe.absolute_path(self.environment.get_source_dir(), self.environment.get_build_dir())) 951 else: 952 cmd.append(target.command) 953 cmd += arg_strings 954 955 if texe: 956 target_name = 'meson-{}'.format(self.build_run_target_name(target)) 957 elem = NinjaBuildElement(self.all_outputs, target_name, 'CUSTOM_COMMAND', []) 958 elem.add_item('COMMAND', cmd) 959 elem.add_item('description', 'Running external command {}'.format(target.name)) 960 elem.add_item('pool', 'console') 961 # Alias that runs the target defined above with the name the user specified 962 self.create_target_alias(target_name) 963 else: 964 target_name = self.build_run_target_name(target) 965 elem = NinjaBuildElement(self.all_outputs, target_name, 'phony', []) 966 967 elem.add_dep(deps) 968 self.add_build(elem) 969 self.processed_targets[target.get_id()] = True 970 971 def generate_coverage_command(self, elem, outputs): 972 targets = self.build.get_targets().values() 973 use_llvm_cov = False 974 for target in targets: 975 if not hasattr(target, 'compilers'): 976 continue 977 for compiler in target.compilers.values(): 978 if compiler.get_id() == 'clang' and not compiler.info.is_darwin(): 979 use_llvm_cov = True 980 break 981 elem.add_item('COMMAND', self.environment.get_build_command() + 982 ['--internal', 'coverage'] + 983 outputs + 984 [self.environment.get_source_dir(), 985 os.path.join(self.environment.get_source_dir(), 986 self.build.get_subproject_dir()), 987 self.environment.get_build_dir(), 988 self.environment.get_log_dir()] + 989 (['--use_llvm_cov'] if use_llvm_cov else [])) 990 991 def generate_coverage_rules(self): 992 e = NinjaBuildElement(self.all_outputs, 'meson-coverage', 'CUSTOM_COMMAND', 'PHONY') 993 self.generate_coverage_command(e, []) 994 e.add_item('description', 'Generates coverage reports') 995 self.add_build(e) 996 # Alias that runs the target defined above 997 self.create_target_alias('meson-coverage') 998 self.generate_coverage_legacy_rules() 999 1000 def generate_coverage_legacy_rules(self): 1001 e = NinjaBuildElement(self.all_outputs, 'meson-coverage-xml', 'CUSTOM_COMMAND', 'PHONY') 1002 self.generate_coverage_command(e, ['--xml']) 1003 e.add_item('description', 'Generates XML coverage report') 1004 self.add_build(e) 1005 # Alias that runs the target defined above 1006 self.create_target_alias('meson-coverage-xml') 1007 1008 e = NinjaBuildElement(self.all_outputs, 'meson-coverage-text', 'CUSTOM_COMMAND', 'PHONY') 1009 self.generate_coverage_command(e, ['--text']) 1010 e.add_item('description', 'Generates text coverage report') 1011 self.add_build(e) 1012 # Alias that runs the target defined above 1013 self.create_target_alias('meson-coverage-text') 1014 1015 e = NinjaBuildElement(self.all_outputs, 'meson-coverage-html', 'CUSTOM_COMMAND', 'PHONY') 1016 self.generate_coverage_command(e, ['--html']) 1017 e.add_item('description', 'Generates HTML coverage report') 1018 self.add_build(e) 1019 # Alias that runs the target defined above 1020 self.create_target_alias('meson-coverage-html') 1021 1022 def generate_install(self): 1023 self.create_install_data_files() 1024 elem = NinjaBuildElement(self.all_outputs, 'meson-install', 'CUSTOM_COMMAND', 'PHONY') 1025 elem.add_dep('all') 1026 elem.add_item('DESC', 'Installing files.') 1027 elem.add_item('COMMAND', self.environment.get_build_command() + ['install', '--no-rebuild']) 1028 elem.add_item('pool', 'console') 1029 self.add_build(elem) 1030 # Alias that runs the target defined above 1031 self.create_target_alias('meson-install') 1032 1033 def generate_tests(self): 1034 self.serialize_tests() 1035 cmd = self.environment.get_build_command(True) + ['test', '--no-rebuild'] 1036 if not self.environment.coredata.get_builtin_option('stdsplit'): 1037 cmd += ['--no-stdsplit'] 1038 if self.environment.coredata.get_builtin_option('errorlogs'): 1039 cmd += ['--print-errorlogs'] 1040 elem = NinjaBuildElement(self.all_outputs, 'meson-test', 'CUSTOM_COMMAND', ['all', 'PHONY']) 1041 elem.add_item('COMMAND', cmd) 1042 elem.add_item('DESC', 'Running all tests.') 1043 elem.add_item('pool', 'console') 1044 self.add_build(elem) 1045 # Alias that runs the above-defined meson-test target 1046 self.create_target_alias('meson-test') 1047 1048 # And then benchmarks. 1049 cmd = self.environment.get_build_command(True) + [ 1050 'test', '--benchmark', '--logbase', 1051 'benchmarklog', '--num-processes=1', '--no-rebuild'] 1052 elem = NinjaBuildElement(self.all_outputs, 'meson-benchmark', 'CUSTOM_COMMAND', ['all', 'PHONY']) 1053 elem.add_item('COMMAND', cmd) 1054 elem.add_item('DESC', 'Running benchmark suite.') 1055 elem.add_item('pool', 'console') 1056 self.add_build(elem) 1057 # Alias that runs the above-defined meson-benchmark target 1058 self.create_target_alias('meson-benchmark') 1059 1060 def generate_rules(self): 1061 self.rules = [] 1062 self.ruledict = {} 1063 1064 self.add_rule_comment(NinjaComment('Rules for compiling.')) 1065 self.generate_compile_rules() 1066 self.add_rule_comment(NinjaComment('Rules for linking.')) 1067 self.generate_static_link_rules() 1068 self.generate_dynamic_link_rules() 1069 self.add_rule_comment(NinjaComment('Other rules')) 1070 # Ninja errors out if you have deps = gcc but no depfile, so we must 1071 # have two rules for custom commands. 1072 self.add_rule(NinjaRule('CUSTOM_COMMAND', ['$COMMAND'], [], '$DESC', 1073 extra='restat = 1')) 1074 self.add_rule(NinjaRule('CUSTOM_COMMAND_DEP', ['$COMMAND'], [], '$DESC', 1075 deps='gcc', depfile='$DEPFILE', 1076 extra='restat = 1')) 1077 1078 c = self.environment.get_build_command() + \ 1079 ['--internal', 1080 'regenerate', 1081 self.environment.get_source_dir(), 1082 self.environment.get_build_dir(), 1083 '--backend', 1084 'ninja'] 1085 self.add_rule(NinjaRule('REGENERATE_BUILD', 1086 c, [], 1087 'Regenerating build files.', 1088 extra='generator = 1')) 1089 1090 def add_rule_comment(self, comment): 1091 self.rules.append(comment) 1092 1093 def add_build_comment(self, comment): 1094 self.build_elements.append(comment) 1095 1096 def add_rule(self, rule): 1097 self.rules.append(rule) 1098 self.ruledict[rule.name] = rule 1099 1100 def add_build(self, build): 1101 self.build_elements.append(build) 1102 1103 if build.rulename != 'phony': 1104 # reference rule 1105 build.rule = self.ruledict[build.rulename] 1106 1107 def write_rules(self, outfile): 1108 for b in self.build_elements: 1109 if isinstance(b, NinjaBuildElement): 1110 b.count_rule_references() 1111 1112 for r in self.rules: 1113 r.write(outfile) 1114 1115 def write_builds(self, outfile): 1116 for b in ProgressBar(self.build_elements, desc='Writing build.ninja'): 1117 b.write(outfile) 1118 1119 def generate_phony(self): 1120 self.add_build_comment(NinjaComment('Phony build target, always out of date')) 1121 elem = NinjaBuildElement(self.all_outputs, 'PHONY', 'phony', '') 1122 self.add_build(elem) 1123 1124 def generate_jar_target(self, target): 1125 fname = target.get_filename() 1126 outname_rel = os.path.join(self.get_target_dir(target), fname) 1127 src_list = target.get_sources() 1128 class_list = [] 1129 compiler = target.compilers['java'] 1130 c = 'c' 1131 m = 'm' 1132 e = '' 1133 f = 'f' 1134 main_class = target.get_main_class() 1135 if main_class != '': 1136 e = 'e' 1137 1138 # Add possible java generated files to src list 1139 generated_sources = self.get_target_generated_sources(target) 1140 gen_src_list = [] 1141 for rel_src in generated_sources.keys(): 1142 dirpart, fnamepart = os.path.split(rel_src) 1143 raw_src = File(True, dirpart, fnamepart) 1144 if rel_src.endswith('.java'): 1145 gen_src_list.append(raw_src) 1146 1147 compile_args = self.determine_single_java_compile_args(target, compiler) 1148 for src in src_list + gen_src_list: 1149 plain_class_path = self.generate_single_java_compile(src, target, compiler, compile_args) 1150 class_list.append(plain_class_path) 1151 class_dep_list = [os.path.join(self.get_target_private_dir(target), i) for i in class_list] 1152 manifest_path = os.path.join(self.get_target_private_dir(target), 'META-INF', 'MANIFEST.MF') 1153 manifest_fullpath = os.path.join(self.environment.get_build_dir(), manifest_path) 1154 os.makedirs(os.path.dirname(manifest_fullpath), exist_ok=True) 1155 with open(manifest_fullpath, 'w') as manifest: 1156 if any(target.link_targets): 1157 manifest.write('Class-Path: ') 1158 cp_paths = [os.path.join(self.get_target_dir(l), l.get_filename()) for l in target.link_targets] 1159 manifest.write(' '.join(cp_paths)) 1160 manifest.write('\n') 1161 jar_rule = 'java_LINKER' 1162 commands = [c + m + e + f] 1163 commands.append(manifest_path) 1164 if e != '': 1165 commands.append(main_class) 1166 commands.append(self.get_target_filename(target)) 1167 # Java compilation can produce an arbitrary number of output 1168 # class files for a single source file. Thus tell jar to just 1169 # grab everything in the final package. 1170 commands += ['-C', self.get_target_private_dir(target), '.'] 1171 elem = NinjaBuildElement(self.all_outputs, outname_rel, jar_rule, []) 1172 elem.add_dep(class_dep_list) 1173 elem.add_item('ARGS', commands) 1174 self.add_build(elem) 1175 # Create introspection information 1176 self.create_target_source_introspection(target, compiler, compile_args, src_list, gen_src_list) 1177 1178 def generate_cs_resource_tasks(self, target): 1179 args = [] 1180 deps = [] 1181 for r in target.resources: 1182 rel_sourcefile = os.path.join(self.build_to_src, target.subdir, r) 1183 if r.endswith('.resources'): 1184 a = '-resource:' + rel_sourcefile 1185 elif r.endswith('.txt') or r.endswith('.resx'): 1186 ofilebase = os.path.splitext(os.path.basename(r))[0] + '.resources' 1187 ofilename = os.path.join(self.get_target_private_dir(target), ofilebase) 1188 elem = NinjaBuildElement(self.all_outputs, ofilename, "CUSTOM_COMMAND", rel_sourcefile) 1189 elem.add_item('COMMAND', ['resgen', rel_sourcefile, ofilename]) 1190 elem.add_item('DESC', 'Compiling resource {}'.format(rel_sourcefile)) 1191 self.add_build(elem) 1192 deps.append(ofilename) 1193 a = '-resource:' + ofilename 1194 else: 1195 raise InvalidArguments('Unknown resource file {}.'.format(r)) 1196 args.append(a) 1197 return args, deps 1198 1199 def generate_cs_target(self, target): 1200 buildtype = self.get_option_for_target('buildtype', target) 1201 fname = target.get_filename() 1202 outname_rel = os.path.join(self.get_target_dir(target), fname) 1203 src_list = target.get_sources() 1204 compiler = target.compilers['cs'] 1205 rel_srcs = [os.path.normpath(s.rel_to_builddir(self.build_to_src)) for s in src_list] 1206 deps = [] 1207 commands = compiler.compiler_args(target.extra_args.get('cs', [])) 1208 commands += compiler.get_buildtype_args(buildtype) 1209 commands += compiler.get_optimization_args(self.get_option_for_target('optimization', target)) 1210 commands += compiler.get_debug_args(self.get_option_for_target('debug', target)) 1211 if isinstance(target, build.Executable): 1212 commands.append('-target:exe') 1213 elif isinstance(target, build.SharedLibrary): 1214 commands.append('-target:library') 1215 else: 1216 raise MesonException('Unknown C# target type.') 1217 (resource_args, resource_deps) = self.generate_cs_resource_tasks(target) 1218 commands += resource_args 1219 deps += resource_deps 1220 commands += compiler.get_output_args(outname_rel) 1221 for l in target.link_targets: 1222 lname = os.path.join(self.get_target_dir(l), l.get_filename()) 1223 commands += compiler.get_link_args(lname) 1224 deps.append(lname) 1225 if '-g' in commands: 1226 outputs = [outname_rel, outname_rel + '.mdb'] 1227 else: 1228 outputs = [outname_rel] 1229 generated_sources = self.get_target_generated_sources(target) 1230 generated_rel_srcs = [] 1231 for rel_src in generated_sources.keys(): 1232 if rel_src.lower().endswith('.cs'): 1233 generated_rel_srcs.append(os.path.normpath(rel_src)) 1234 deps.append(os.path.normpath(rel_src)) 1235 1236 for dep in target.get_external_deps(): 1237 commands.extend_direct(dep.get_link_args()) 1238 commands += self.build.get_project_args(compiler, target.subproject, target.for_machine) 1239 commands += self.build.get_global_args(compiler, target.for_machine) 1240 1241 elem = NinjaBuildElement(self.all_outputs, outputs, self.get_compiler_rule_name('cs', target.for_machine), rel_srcs + generated_rel_srcs) 1242 elem.add_dep(deps) 1243 elem.add_item('ARGS', commands) 1244 self.add_build(elem) 1245 1246 self.generate_generator_list_rules(target) 1247 self.create_target_source_introspection(target, compiler, commands, rel_srcs, generated_rel_srcs) 1248 1249 def determine_single_java_compile_args(self, target, compiler): 1250 args = [] 1251 args += compiler.get_buildtype_args(self.get_option_for_target('buildtype', target)) 1252 args += self.build.get_global_args(compiler, target.for_machine) 1253 args += self.build.get_project_args(compiler, target.subproject, target.for_machine) 1254 args += target.get_java_args() 1255 args += compiler.get_output_args(self.get_target_private_dir(target)) 1256 args += target.get_classpath_args() 1257 curdir = target.get_subdir() 1258 sourcepath = os.path.join(self.build_to_src, curdir) + os.pathsep 1259 sourcepath += os.path.normpath(curdir) + os.pathsep 1260 for i in target.include_dirs: 1261 for idir in i.get_incdirs(): 1262 sourcepath += os.path.join(self.build_to_src, i.curdir, idir) + os.pathsep 1263 args += ['-sourcepath', sourcepath] 1264 return args 1265 1266 def generate_single_java_compile(self, src, target, compiler, args): 1267 deps = [os.path.join(self.get_target_dir(l), l.get_filename()) for l in target.link_targets] 1268 generated_sources = self.get_target_generated_sources(target) 1269 for rel_src in generated_sources.keys(): 1270 if rel_src.endswith('.java'): 1271 deps.append(rel_src) 1272 rel_src = src.rel_to_builddir(self.build_to_src) 1273 plain_class_path = src.fname[:-4] + 'class' 1274 rel_obj = os.path.join(self.get_target_private_dir(target), plain_class_path) 1275 element = NinjaBuildElement(self.all_outputs, rel_obj, self.compiler_to_rule_name(compiler), rel_src) 1276 element.add_dep(deps) 1277 element.add_item('ARGS', args) 1278 self.add_build(element) 1279 return plain_class_path 1280 1281 def generate_java_link(self): 1282 rule = 'java_LINKER' 1283 command = ['jar', '$ARGS'] 1284 description = 'Creating JAR $out' 1285 self.add_rule(NinjaRule(rule, command, [], description)) 1286 1287 def determine_dep_vapis(self, target): 1288 """ 1289 Peek into the sources of BuildTargets we're linking with, and if any of 1290 them was built with Vala, assume that it also generated a .vapi file of 1291 the same name as the BuildTarget and return the path to it relative to 1292 the build directory. 1293 """ 1294 result = OrderedSet() 1295 for dep in itertools.chain(target.link_targets, target.link_whole_targets): 1296 if not dep.is_linkable_target(): 1297 continue 1298 for i in dep.sources: 1299 if hasattr(i, 'fname'): 1300 i = i.fname 1301 if i.endswith('vala'): 1302 vapiname = dep.vala_vapi 1303 fullname = os.path.join(self.get_target_dir(dep), vapiname) 1304 result.add(fullname) 1305 break 1306 return list(result) 1307 1308 def split_vala_sources(self, t): 1309 """ 1310 Splits the target's sources into .vala, .gs, .vapi, and other sources. 1311 Handles both pre-existing and generated sources. 1312 1313 Returns a tuple (vala, vapi, others) each of which is a dictionary with 1314 the keys being the path to the file (relative to the build directory) 1315 and the value being the object that generated or represents the file. 1316 """ 1317 vala = OrderedDict() 1318 vapi = OrderedDict() 1319 others = OrderedDict() 1320 othersgen = OrderedDict() 1321 # Split pre-existing sources 1322 for s in t.get_sources(): 1323 # BuildTarget sources are always mesonlib.File files which are 1324 # either in the source root, or generated with configure_file and 1325 # in the build root 1326 if not isinstance(s, File): 1327 msg = 'All sources in target {!r} must be of type ' \ 1328 'mesonlib.File, not {!r}'.format(t, s) 1329 raise InvalidArguments(msg) 1330 f = s.rel_to_builddir(self.build_to_src) 1331 if s.endswith(('.vala', '.gs')): 1332 srctype = vala 1333 elif s.endswith('.vapi'): 1334 srctype = vapi 1335 else: 1336 srctype = others 1337 srctype[f] = s 1338 # Split generated sources 1339 for gensrc in t.get_generated_sources(): 1340 for s in gensrc.get_outputs(): 1341 f = self.get_target_generated_dir(t, gensrc, s) 1342 if s.endswith(('.vala', '.gs')): 1343 srctype = vala 1344 elif s.endswith('.vapi'): 1345 srctype = vapi 1346 # Generated non-Vala (C/C++) sources. Won't be used for 1347 # generating the Vala compile rule below. 1348 else: 1349 srctype = othersgen 1350 # Duplicate outputs are disastrous 1351 if f in srctype and srctype[f] is not gensrc: 1352 msg = 'Duplicate output {0!r} from {1!r} {2!r}; ' \ 1353 'conflicts with {0!r} from {4!r} {3!r}' \ 1354 ''.format(f, type(gensrc).__name__, gensrc.name, 1355 srctype[f].name, type(srctype[f]).__name__) 1356 raise InvalidArguments(msg) 1357 # Store 'somefile.vala': GeneratedList (or CustomTarget) 1358 srctype[f] = gensrc 1359 return vala, vapi, (others, othersgen) 1360 1361 def generate_vala_compile(self, target): 1362 """Vala is compiled into C. Set up all necessary build steps here.""" 1363 (vala_src, vapi_src, other_src) = self.split_vala_sources(target) 1364 extra_dep_files = [] 1365 if not vala_src: 1366 msg = 'Vala library {!r} has no Vala or Genie source files.' 1367 raise InvalidArguments(msg.format(target.name)) 1368 1369 valac = target.compilers['vala'] 1370 c_out_dir = self.get_target_private_dir(target) 1371 # C files generated by valac 1372 vala_c_src = [] 1373 # Files generated by valac 1374 valac_outputs = [] 1375 # All sources that are passed to valac on the commandline 1376 all_files = list(vapi_src.keys()) 1377 # Passed as --basedir 1378 srcbasedir = os.path.join(self.build_to_src, target.get_subdir()) 1379 for (vala_file, gensrc) in vala_src.items(): 1380 all_files.append(vala_file) 1381 # Figure out where the Vala compiler will write the compiled C file 1382 # 1383 # If the Vala file is in a subdir of the build dir (in our case 1384 # because it was generated/built by something else), and is also 1385 # a subdir of --basedir (because the builddir is in the source 1386 # tree, and the target subdir is the source root), the subdir 1387 # components from the source root till the private builddir will be 1388 # duplicated inside the private builddir. Otherwise, just the 1389 # basename will be used. 1390 # 1391 # If the Vala file is outside the build directory, the paths from 1392 # the --basedir till the subdir will be duplicated inside the 1393 # private builddir. 1394 if isinstance(gensrc, (build.CustomTarget, build.GeneratedList)) or gensrc.is_built: 1395 vala_c_file = os.path.splitext(os.path.basename(vala_file))[0] + '.c' 1396 # Check if the vala file is in a subdir of --basedir 1397 abs_srcbasedir = os.path.join(self.environment.get_source_dir(), target.get_subdir()) 1398 abs_vala_file = os.path.join(self.environment.get_build_dir(), vala_file) 1399 if PurePath(os.path.commonpath((abs_srcbasedir, abs_vala_file))) == PurePath(abs_srcbasedir): 1400 vala_c_subdir = PurePath(abs_vala_file).parent.relative_to(abs_srcbasedir) 1401 vala_c_file = os.path.join(str(vala_c_subdir), vala_c_file) 1402 else: 1403 path_to_target = os.path.join(self.build_to_src, target.get_subdir()) 1404 if vala_file.startswith(path_to_target): 1405 vala_c_file = os.path.splitext(os.path.relpath(vala_file, path_to_target))[0] + '.c' 1406 else: 1407 vala_c_file = os.path.splitext(os.path.basename(vala_file))[0] + '.c' 1408 # All this will be placed inside the c_out_dir 1409 vala_c_file = os.path.join(c_out_dir, vala_c_file) 1410 vala_c_src.append(vala_c_file) 1411 valac_outputs.append(vala_c_file) 1412 1413 args = self.generate_basic_compiler_args(target, valac) 1414 args += valac.get_colorout_args(self.environment.coredata.base_options.get('b_colorout').value) 1415 # Tell Valac to output everything in our private directory. Sadly this 1416 # means it will also preserve the directory components of Vala sources 1417 # found inside the build tree (generated sources). 1418 args += ['--directory', c_out_dir] 1419 args += ['--basedir', srcbasedir] 1420 if target.is_linkable_target(): 1421 # Library name 1422 args += ['--library', target.name] 1423 # Outputted header 1424 hname = os.path.join(self.get_target_dir(target), target.vala_header) 1425 args += ['--header', hname] 1426 if self.is_unity(target): 1427 # Without this the declarations will get duplicated in the .c 1428 # files and cause a build failure when all of them are 1429 # #include-d in one .c file. 1430 # https://github.com/mesonbuild/meson/issues/1969 1431 args += ['--use-header'] 1432 valac_outputs.append(hname) 1433 # Outputted vapi file 1434 vapiname = os.path.join(self.get_target_dir(target), target.vala_vapi) 1435 # Force valac to write the vapi and gir files in the target build dir. 1436 # Without this, it will write it inside c_out_dir 1437 args += ['--vapi', os.path.join('..', target.vala_vapi)] 1438 valac_outputs.append(vapiname) 1439 target.outputs += [target.vala_header, target.vala_vapi] 1440 # Install header and vapi to default locations if user requests this 1441 if len(target.install_dir) > 1 and target.install_dir[1] is True: 1442 target.install_dir[1] = self.environment.get_includedir() 1443 if len(target.install_dir) > 2 and target.install_dir[2] is True: 1444 target.install_dir[2] = os.path.join(self.environment.get_datadir(), 'vala', 'vapi') 1445 # Generate GIR if requested 1446 if isinstance(target.vala_gir, str): 1447 girname = os.path.join(self.get_target_dir(target), target.vala_gir) 1448 args += ['--gir', os.path.join('..', target.vala_gir)] 1449 valac_outputs.append(girname) 1450 target.outputs.append(target.vala_gir) 1451 # Install GIR to default location if requested by user 1452 if len(target.install_dir) > 3 and target.install_dir[3] is True: 1453 target.install_dir[3] = os.path.join(self.environment.get_datadir(), 'gir-1.0') 1454 # Detect gresources and add --gresources arguments for each 1455 for gensrc in other_src[1].values(): 1456 if isinstance(gensrc, modules.GResourceTarget): 1457 gres_xml, = self.get_custom_target_sources(gensrc) 1458 args += ['--gresources=' + gres_xml] 1459 extra_args = [] 1460 1461 for a in target.extra_args.get('vala', []): 1462 if isinstance(a, File): 1463 relname = a.rel_to_builddir(self.build_to_src) 1464 extra_dep_files.append(relname) 1465 extra_args.append(relname) 1466 else: 1467 extra_args.append(a) 1468 dependency_vapis = self.determine_dep_vapis(target) 1469 extra_dep_files += dependency_vapis 1470 args += extra_args 1471 element = NinjaBuildElement(self.all_outputs, valac_outputs, 1472 self.compiler_to_rule_name(valac), 1473 all_files + dependency_vapis) 1474 element.add_item('ARGS', args) 1475 element.add_dep(extra_dep_files) 1476 self.add_build(element) 1477 self.create_target_source_introspection(target, valac, args, all_files, []) 1478 return other_src[0], other_src[1], vala_c_src 1479 1480 def generate_rust_target(self, target): 1481 rustc = target.compilers['rust'] 1482 # Rust compiler takes only the main file as input and 1483 # figures out what other files are needed via import 1484 # statements and magic. 1485 main_rust_file = None 1486 for i in target.get_sources(): 1487 if not rustc.can_compile(i): 1488 raise InvalidArguments('Rust target {} contains a non-rust source file.'.format(target.get_basename())) 1489 if main_rust_file is None: 1490 main_rust_file = i.rel_to_builddir(self.build_to_src) 1491 if main_rust_file is None: 1492 raise RuntimeError('A Rust target has no Rust sources. This is weird. Also a bug. Please report') 1493 target_name = os.path.join(target.subdir, target.get_filename()) 1494 args = ['--crate-type'] 1495 if isinstance(target, build.Executable): 1496 cratetype = 'bin' 1497 elif hasattr(target, 'rust_crate_type'): 1498 cratetype = target.rust_crate_type 1499 elif isinstance(target, build.SharedLibrary): 1500 cratetype = 'dylib' 1501 elif isinstance(target, build.StaticLibrary): 1502 cratetype = 'rlib' 1503 else: 1504 raise InvalidArguments('Unknown target type for rustc.') 1505 args.append(cratetype) 1506 1507 # If we're dynamically linking, add those arguments 1508 # 1509 # Rust is super annoying, calling -C link-arg foo does not work, it has 1510 # to be -C link-arg=foo 1511 if cratetype in {'bin', 'dylib'}: 1512 for a in rustc.linker.get_always_args(): 1513 args += ['-C', 'link-arg={}'.format(a)] 1514 1515 args += ['--crate-name', target.name] 1516 args += rustc.get_buildtype_args(self.get_option_for_target('buildtype', target)) 1517 args += rustc.get_debug_args(self.get_option_for_target('debug', target)) 1518 args += rustc.get_optimization_args(self.get_option_for_target('optimization', target)) 1519 args += self.build.get_global_args(rustc, target.for_machine) 1520 args += self.build.get_project_args(rustc, target.subproject, target.for_machine) 1521 depfile = os.path.join(target.subdir, target.name + '.d') 1522 args += ['--emit', 'dep-info={}'.format(depfile), '--emit', 'link'] 1523 args += target.get_extra_args('rust') 1524 args += ['-o', os.path.join(target.subdir, target.get_filename())] 1525 orderdeps = [os.path.join(t.subdir, t.get_filename()) for t in target.link_targets] 1526 linkdirs = OrderedDict() 1527 for d in target.link_targets: 1528 linkdirs[d.subdir] = True 1529 # specify `extern CRATE_NAME=OUTPUT_FILE` for each Rust 1530 # dependency, so that collisions with libraries in rustc's 1531 # sysroot don't cause ambiguity 1532 args += ['--extern', '{}={}'.format(d.name, os.path.join(d.subdir, d.filename))] 1533 for d in linkdirs.keys(): 1534 if d == '': 1535 d = '.' 1536 args += ['-L', d] 1537 has_shared_deps = False 1538 for dep in target.get_dependencies(): 1539 if isinstance(dep, build.SharedLibrary): 1540 has_shared_deps = True 1541 if isinstance(target, build.SharedLibrary) or has_shared_deps: 1542 # add prefer-dynamic if any of the Rust libraries we link 1543 # against are dynamic, otherwise we'll end up with 1544 # multiple implementations of crates 1545 args += ['-C', 'prefer-dynamic'] 1546 1547 # build the usual rpath arguments as well... 1548 1549 # Set runtime-paths so we can run executables without needing to set 1550 # LD_LIBRARY_PATH, etc in the environment. Doesn't work on Windows. 1551 if has_path_sep(target.name): 1552 # Target names really should not have slashes in them, but 1553 # unfortunately we did not check for that and some downstream projects 1554 # now have them. Once slashes are forbidden, remove this bit. 1555 target_slashname_workaround_dir = os.path.join(os.path.dirname(target.name), 1556 self.get_target_dir(target)) 1557 else: 1558 target_slashname_workaround_dir = self.get_target_dir(target) 1559 (rpath_args, target.rpath_dirs_to_remove) = \ 1560 rustc.build_rpath_args(self.environment, 1561 self.environment.get_build_dir(), 1562 target_slashname_workaround_dir, 1563 self.determine_rpath_dirs(target), 1564 target.build_rpath, 1565 target.install_rpath) 1566 # ... but then add rustc's sysroot to account for rustup 1567 # installations 1568 for rpath_arg in rpath_args: 1569 args += ['-C', 'link-arg=' + rpath_arg + ':' + os.path.join(rustc.get_sysroot(), 'lib')] 1570 compiler_name = self.get_compiler_rule_name('rust', target.for_machine) 1571 element = NinjaBuildElement(self.all_outputs, target_name, compiler_name, main_rust_file) 1572 if len(orderdeps) > 0: 1573 element.add_orderdep(orderdeps) 1574 element.add_item('ARGS', args) 1575 element.add_item('targetdep', depfile) 1576 element.add_item('cratetype', cratetype) 1577 self.add_build(element) 1578 if isinstance(target, build.SharedLibrary): 1579 self.generate_shsym(target) 1580 self.create_target_source_introspection(target, rustc, args, [main_rust_file], []) 1581 1582 @staticmethod 1583 def get_rule_suffix(for_machine: MachineChoice) -> str: 1584 return PerMachine('_FOR_BUILD', '')[for_machine] 1585 1586 @classmethod 1587 def get_compiler_rule_name(cls, lang: str, for_machine: MachineChoice) -> str: 1588 return '{}_COMPILER{}'.format(lang, cls.get_rule_suffix(for_machine)) 1589 1590 @classmethod 1591 def get_pch_rule_name(cls, lang: str, for_machine: MachineChoice) -> str: 1592 return '{}_PCH{}'.format(lang, cls.get_rule_suffix(for_machine)) 1593 1594 @classmethod 1595 def compiler_to_rule_name(cls, compiler: Compiler) -> str: 1596 return cls.get_compiler_rule_name(compiler.get_language(), compiler.for_machine) 1597 1598 @classmethod 1599 def compiler_to_pch_rule_name(cls, compiler: Compiler) -> str: 1600 return cls.get_pch_rule_name(compiler.get_language(), compiler.for_machine) 1601 1602 def swift_module_file_name(self, target): 1603 return os.path.join(self.get_target_private_dir(target), 1604 self.target_swift_modulename(target) + '.swiftmodule') 1605 1606 def target_swift_modulename(self, target): 1607 return target.name 1608 1609 def is_swift_target(self, target): 1610 for s in target.sources: 1611 if s.endswith('swift'): 1612 return True 1613 return False 1614 1615 def determine_swift_dep_modules(self, target): 1616 result = [] 1617 for l in target.link_targets: 1618 if self.is_swift_target(l): 1619 result.append(self.swift_module_file_name(l)) 1620 return result 1621 1622 def determine_swift_dep_dirs(self, target): 1623 result = [] 1624 for l in target.link_targets: 1625 result.append(self.get_target_private_dir_abs(l)) 1626 return result 1627 1628 def get_swift_link_deps(self, target): 1629 result = [] 1630 for l in target.link_targets: 1631 result.append(self.get_target_filename(l)) 1632 return result 1633 1634 def split_swift_generated_sources(self, target): 1635 all_srcs = self.get_target_generated_sources(target) 1636 srcs = [] 1637 others = [] 1638 for i in all_srcs: 1639 if i.endswith('.swift'): 1640 srcs.append(i) 1641 else: 1642 others.append(i) 1643 return srcs, others 1644 1645 def generate_swift_target(self, target): 1646 module_name = self.target_swift_modulename(target) 1647 swiftc = target.compilers['swift'] 1648 abssrc = [] 1649 relsrc = [] 1650 abs_headers = [] 1651 header_imports = [] 1652 for i in target.get_sources(): 1653 if swiftc.can_compile(i): 1654 rels = i.rel_to_builddir(self.build_to_src) 1655 abss = os.path.normpath(os.path.join(self.environment.get_build_dir(), rels)) 1656 relsrc.append(rels) 1657 abssrc.append(abss) 1658 elif self.environment.is_header(i): 1659 relh = i.rel_to_builddir(self.build_to_src) 1660 absh = os.path.normpath(os.path.join(self.environment.get_build_dir(), relh)) 1661 abs_headers.append(absh) 1662 header_imports += swiftc.get_header_import_args(absh) 1663 else: 1664 raise InvalidArguments('Swift target {} contains a non-swift source file.'.format(target.get_basename())) 1665 os.makedirs(self.get_target_private_dir_abs(target), exist_ok=True) 1666 compile_args = swiftc.get_compile_only_args() 1667 compile_args += swiftc.get_optimization_args(self.get_option_for_target('optimization', target)) 1668 compile_args += swiftc.get_debug_args(self.get_option_for_target('debug', target)) 1669 compile_args += swiftc.get_module_args(module_name) 1670 compile_args += self.build.get_project_args(swiftc, target.subproject, target.for_machine) 1671 compile_args += self.build.get_global_args(swiftc, target.for_machine) 1672 for i in reversed(target.get_include_dirs()): 1673 basedir = i.get_curdir() 1674 for d in i.get_incdirs(): 1675 if d not in ('', '.'): 1676 expdir = os.path.join(basedir, d) 1677 else: 1678 expdir = basedir 1679 srctreedir = os.path.normpath(os.path.join(self.environment.get_build_dir(), self.build_to_src, expdir)) 1680 sargs = swiftc.get_include_args(srctreedir) 1681 compile_args += sargs 1682 link_args = swiftc.get_output_args(os.path.join(self.environment.get_build_dir(), self.get_target_filename(target))) 1683 link_args += self.build.get_project_link_args(swiftc, target.subproject, target.for_machine) 1684 link_args += self.build.get_global_link_args(swiftc, target.for_machine) 1685 rundir = self.get_target_private_dir(target) 1686 out_module_name = self.swift_module_file_name(target) 1687 in_module_files = self.determine_swift_dep_modules(target) 1688 abs_module_dirs = self.determine_swift_dep_dirs(target) 1689 module_includes = [] 1690 for x in abs_module_dirs: 1691 module_includes += swiftc.get_include_args(x) 1692 link_deps = self.get_swift_link_deps(target) 1693 abs_link_deps = [os.path.join(self.environment.get_build_dir(), x) for x in link_deps] 1694 for d in target.link_targets: 1695 reldir = self.get_target_dir(d) 1696 if reldir == '': 1697 reldir = '.' 1698 link_args += ['-L', os.path.normpath(os.path.join(self.environment.get_build_dir(), reldir))] 1699 (rel_generated, _) = self.split_swift_generated_sources(target) 1700 abs_generated = [os.path.join(self.environment.get_build_dir(), x) for x in rel_generated] 1701 # We need absolute paths because swiftc needs to be invoked in a subdir 1702 # and this is the easiest way about it. 1703 objects = [] # Relative to swift invocation dir 1704 rel_objects = [] # Relative to build.ninja 1705 for i in abssrc + abs_generated: 1706 base = os.path.basename(i) 1707 oname = os.path.splitext(base)[0] + '.o' 1708 objects.append(oname) 1709 rel_objects.append(os.path.join(self.get_target_private_dir(target), oname)) 1710 1711 rulename = self.get_compiler_rule_name('swift', target.for_machine) 1712 1713 # Swiftc does not seem to be able to emit objects and module files in one go. 1714 elem = NinjaBuildElement(self.all_outputs, rel_objects, rulename, abssrc) 1715 elem.add_dep(in_module_files + rel_generated) 1716 elem.add_dep(abs_headers) 1717 elem.add_item('ARGS', compile_args + header_imports + abs_generated + module_includes) 1718 elem.add_item('RUNDIR', rundir) 1719 self.add_build(elem) 1720 elem = NinjaBuildElement(self.all_outputs, out_module_name, 1721 self.get_compiler_rule_name('swift', target.for_machine), 1722 abssrc) 1723 elem.add_dep(in_module_files + rel_generated) 1724 elem.add_item('ARGS', compile_args + abs_generated + module_includes + swiftc.get_mod_gen_args()) 1725 elem.add_item('RUNDIR', rundir) 1726 self.add_build(elem) 1727 if isinstance(target, build.StaticLibrary): 1728 elem = self.generate_link(target, self.get_target_filename(target), 1729 rel_objects, self.build.static_linker[target.for_machine]) 1730 self.add_build(elem) 1731 elif isinstance(target, build.Executable): 1732 elem = NinjaBuildElement(self.all_outputs, self.get_target_filename(target), rulename, []) 1733 elem.add_dep(rel_objects) 1734 elem.add_dep(link_deps) 1735 elem.add_item('ARGS', link_args + swiftc.get_std_exe_link_args() + objects + abs_link_deps) 1736 elem.add_item('RUNDIR', rundir) 1737 self.add_build(elem) 1738 else: 1739 raise MesonException('Swift supports only executable and static library targets.') 1740 # Introspection information 1741 self.create_target_source_introspection(target, swiftc, compile_args + header_imports + module_includes, relsrc, rel_generated) 1742 1743 def generate_static_link_rules(self): 1744 num_pools = self.environment.coredata.backend_options['backend_max_links'].value 1745 if 'java' in self.environment.coredata.compilers.host: 1746 self.generate_java_link() 1747 for for_machine in MachineChoice: 1748 static_linker = self.build.static_linker[for_machine] 1749 if static_linker is None: 1750 return 1751 rule = 'STATIC_LINKER{}'.format(self.get_rule_suffix(for_machine)) 1752 cmdlist = [] 1753 args = ['$in'] 1754 # FIXME: Must normalize file names with pathlib.Path before writing 1755 # them out to fix this properly on Windows. See: 1756 # https://github.com/mesonbuild/meson/issues/1517 1757 # https://github.com/mesonbuild/meson/issues/1526 1758 if isinstance(static_linker, ArLinker) and not mesonlib.is_windows(): 1759 # `ar` has no options to overwrite archives. It always appends, 1760 # which is never what we want. Delete an existing library first if 1761 # it exists. https://github.com/mesonbuild/meson/issues/1355 1762 cmdlist = execute_wrapper + [c.format('$out') for c in rmfile_prefix] 1763 cmdlist += static_linker.get_exelist() 1764 cmdlist += ['$LINK_ARGS'] 1765 cmdlist += NinjaCommandArg.list(static_linker.get_output_args('$out'), Quoting.none) 1766 description = 'Linking static target $out' 1767 if num_pools > 0: 1768 pool = 'pool = link_pool' 1769 else: 1770 pool = None 1771 self.add_rule(NinjaRule(rule, cmdlist, args, description, 1772 rspable=static_linker.can_linker_accept_rsp(), 1773 rspfile_quote_style='cl' if isinstance(static_linker, VisualStudioLinker) else 'gcc', 1774 extra=pool)) 1775 1776 def generate_dynamic_link_rules(self): 1777 num_pools = self.environment.coredata.backend_options['backend_max_links'].value 1778 for for_machine in MachineChoice: 1779 complist = self.environment.coredata.compilers[for_machine] 1780 for langname, compiler in complist.items(): 1781 if langname == 'java' \ 1782 or langname == 'vala' \ 1783 or langname == 'rust' \ 1784 or langname == 'cs': 1785 continue 1786 rule = '{}_LINKER{}'.format(langname, self.get_rule_suffix(for_machine)) 1787 command = compiler.get_linker_exelist() 1788 args = ['$ARGS'] + NinjaCommandArg.list(compiler.get_linker_output_args('$out'), Quoting.none) + ['$in', '$LINK_ARGS'] 1789 description = 'Linking target $out' 1790 if num_pools > 0: 1791 pool = 'pool = link_pool' 1792 else: 1793 pool = None 1794 self.add_rule(NinjaRule(rule, command, args, description, 1795 rspable=compiler.can_linker_accept_rsp(), 1796 rspfile_quote_style='cl' if (compiler.get_argument_syntax() == 'msvc' or 1797 isinstance(compiler, DmdDCompiler)) else 'gcc', 1798 extra=pool)) 1799 1800 args = self.environment.get_build_command() + \ 1801 ['--internal', 1802 'symbolextractor', 1803 self.environment.get_build_dir(), 1804 '$in', 1805 '$IMPLIB', 1806 '$out'] 1807 symrule = 'SHSYM' 1808 symcmd = args + ['$CROSS'] 1809 syndesc = 'Generating symbol file $out' 1810 synstat = 'restat = 1' 1811 self.add_rule(NinjaRule(symrule, symcmd, [], syndesc, extra=synstat)) 1812 1813 def generate_java_compile_rule(self, compiler): 1814 rule = self.compiler_to_rule_name(compiler) 1815 command = compiler.get_exelist() + ['$ARGS', '$in'] 1816 description = 'Compiling Java object $in' 1817 self.add_rule(NinjaRule(rule, command, [], description)) 1818 1819 def generate_cs_compile_rule(self, compiler): 1820 rule = self.compiler_to_rule_name(compiler) 1821 command = compiler.get_exelist() 1822 args = ['$ARGS', '$in'] 1823 description = 'Compiling C Sharp target $out' 1824 self.add_rule(NinjaRule(rule, command, args, description, 1825 rspable=mesonlib.is_windows(), 1826 rspfile_quote_style='cl' if isinstance(compiler, VisualStudioCsCompiler) else 'gcc')) 1827 1828 def generate_vala_compile_rules(self, compiler): 1829 rule = self.compiler_to_rule_name(compiler) 1830 command = compiler.get_exelist() + ['$ARGS', '$in'] 1831 description = 'Compiling Vala source $in' 1832 self.add_rule(NinjaRule(rule, command, [], description, extra='restat = 1')) 1833 1834 def generate_rust_compile_rules(self, compiler): 1835 rule = self.compiler_to_rule_name(compiler) 1836 command = compiler.get_exelist() + ['$ARGS', '$in'] 1837 description = 'Compiling Rust source $in' 1838 depfile = '$targetdep' 1839 depstyle = 'gcc' 1840 self.add_rule(NinjaRule(rule, command, [], description, deps=depstyle, 1841 depfile=depfile)) 1842 1843 def generate_swift_compile_rules(self, compiler): 1844 rule = self.compiler_to_rule_name(compiler) 1845 full_exe = self.environment.get_build_command() + [ 1846 '--internal', 1847 'dirchanger', 1848 '$RUNDIR', 1849 ] 1850 invoc = full_exe + compiler.get_exelist() 1851 command = invoc + ['$ARGS', '$in'] 1852 description = 'Compiling Swift source $in' 1853 self.add_rule(NinjaRule(rule, command, [], description)) 1854 1855 def generate_fortran_dep_hack(self, crstr): 1856 rule = 'FORTRAN_DEP_HACK{}'.format(crstr) 1857 if mesonlib.is_windows(): 1858 cmd = ['cmd', '/C'] 1859 else: 1860 cmd = ['true'] 1861 self.add_rule_comment(NinjaComment('''Workaround for these issues: 1862https://groups.google.com/forum/#!topic/ninja-build/j-2RfBIOd_8 1863https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) 1864 self.add_rule(NinjaRule(rule, cmd, [], 'Dep hack', extra='restat = 1')) 1865 1866 def generate_llvm_ir_compile_rule(self, compiler): 1867 if self.created_llvm_ir_rule[compiler.for_machine]: 1868 return 1869 rule = self.get_compiler_rule_name('llvm_ir', compiler.for_machine) 1870 command = compiler.get_exelist() 1871 args = ['$ARGS'] + NinjaCommandArg.list(compiler.get_output_args('$out'), Quoting.none) + compiler.get_compile_only_args() + ['$in'] 1872 description = 'Compiling LLVM IR object $in' 1873 self.add_rule(NinjaRule(rule, command, args, description, 1874 rspable=compiler.can_linker_accept_rsp())) 1875 self.created_llvm_ir_rule[compiler.for_machine] = True 1876 1877 def generate_compile_rule_for(self, langname, compiler): 1878 if langname == 'java': 1879 if self.environment.machines.matches_build_machine(compiler.for_machine): 1880 self.generate_java_compile_rule(compiler) 1881 return 1882 if langname == 'cs': 1883 if self.environment.machines.matches_build_machine(compiler.for_machine): 1884 self.generate_cs_compile_rule(compiler) 1885 return 1886 if langname == 'vala': 1887 self.generate_vala_compile_rules(compiler) 1888 return 1889 if langname == 'rust': 1890 self.generate_rust_compile_rules(compiler) 1891 return 1892 if langname == 'swift': 1893 if self.environment.machines.matches_build_machine(compiler.for_machine): 1894 self.generate_swift_compile_rules(compiler) 1895 return 1896 crstr = self.get_rule_suffix(compiler.for_machine) 1897 if langname == 'fortran': 1898 self.generate_fortran_dep_hack(crstr) 1899 rule = self.get_compiler_rule_name(langname, compiler.for_machine) 1900 depargs = NinjaCommandArg.list(compiler.get_dependency_gen_args('$out', '$DEPFILE'), Quoting.none) 1901 command = compiler.get_exelist() 1902 args = ['$ARGS'] + depargs + NinjaCommandArg.list(compiler.get_output_args('$out'), Quoting.none) + compiler.get_compile_only_args() + ['$in'] 1903 description = 'Compiling {} object $out'.format(compiler.get_display_language()) 1904 if isinstance(compiler, VisualStudioLikeCompiler): 1905 deps = 'msvc' 1906 depfile = None 1907 else: 1908 deps = 'gcc' 1909 depfile = '$DEPFILE' 1910 self.add_rule(NinjaRule(rule, command, args, description, 1911 rspable=compiler.can_linker_accept_rsp(), 1912 rspfile_quote_style='cl' if (compiler.get_argument_syntax() == 'msvc' or 1913 isinstance(compiler, DmdDCompiler)) else 'gcc', 1914 deps=deps, depfile=depfile)) 1915 1916 def generate_pch_rule_for(self, langname, compiler): 1917 if langname != 'c' and langname != 'cpp': 1918 return 1919 rule = self.compiler_to_pch_rule_name(compiler) 1920 depargs = compiler.get_dependency_gen_args('$out', '$DEPFILE') 1921 1922 if isinstance(compiler, VisualStudioLikeCompiler): 1923 output = [] 1924 else: 1925 output = NinjaCommandArg.list(compiler.get_output_args('$out'), Quoting.none) 1926 command = compiler.get_exelist() + ['$ARGS'] + depargs + output + compiler.get_compile_only_args() + ['$in'] 1927 description = 'Precompiling header $in' 1928 if isinstance(compiler, VisualStudioLikeCompiler): 1929 deps = 'msvc' 1930 depfile = None 1931 else: 1932 deps = 'gcc' 1933 depfile = '$DEPFILE' 1934 self.add_rule(NinjaRule(rule, command, [], description, deps=deps, 1935 depfile=depfile)) 1936 1937 def generate_compile_rules(self): 1938 for for_machine in MachineChoice: 1939 clist = self.environment.coredata.compilers[for_machine] 1940 for langname, compiler in clist.items(): 1941 if compiler.get_id() == 'clang': 1942 self.generate_llvm_ir_compile_rule(compiler) 1943 self.generate_compile_rule_for(langname, compiler) 1944 self.generate_pch_rule_for(langname, compiler) 1945 1946 def generate_generator_list_rules(self, target): 1947 # CustomTargets have already written their rules and 1948 # CustomTargetIndexes don't actually get generated, so write rules for 1949 # GeneratedLists here 1950 for genlist in target.get_generated_sources(): 1951 if isinstance(genlist, (build.CustomTarget, build.CustomTargetIndex)): 1952 continue 1953 self.generate_genlist_for_target(genlist, target) 1954 1955 def replace_paths(self, target, args, override_subdir=None): 1956 if override_subdir: 1957 source_target_dir = os.path.join(self.build_to_src, override_subdir) 1958 else: 1959 source_target_dir = self.get_target_source_dir(target) 1960 relout = self.get_target_private_dir(target) 1961 args = [x.replace("@SOURCE_DIR@", self.build_to_src).replace("@BUILD_DIR@", relout) 1962 for x in args] 1963 args = [x.replace("@CURRENT_SOURCE_DIR@", source_target_dir) for x in args] 1964 args = [x.replace("@SOURCE_ROOT@", self.build_to_src).replace("@BUILD_ROOT@", '.') 1965 for x in args] 1966 args = [x.replace('\\', '/') for x in args] 1967 return args 1968 1969 def generate_genlist_for_target(self, genlist, target): 1970 generator = genlist.get_generator() 1971 subdir = genlist.subdir 1972 exe = generator.get_exe() 1973 exe_arr = self.build_target_to_cmd_array(exe) 1974 infilelist = genlist.get_inputs() 1975 outfilelist = genlist.get_outputs() 1976 extra_dependencies = self.get_custom_target_depend_files(genlist) 1977 for i in range(len(infilelist)): 1978 curfile = infilelist[i] 1979 if len(generator.outputs) == 1: 1980 sole_output = os.path.join(self.get_target_private_dir(target), outfilelist[i]) 1981 else: 1982 sole_output = '{}'.format(curfile) 1983 infilename = curfile.rel_to_builddir(self.build_to_src) 1984 base_args = generator.get_arglist(infilename) 1985 outfiles = genlist.get_outputs_for(curfile) 1986 outfiles = [os.path.join(self.get_target_private_dir(target), of) for of in outfiles] 1987 if generator.depfile is None: 1988 rulename = 'CUSTOM_COMMAND' 1989 args = base_args 1990 else: 1991 rulename = 'CUSTOM_COMMAND_DEP' 1992 depfilename = generator.get_dep_outname(infilename) 1993 depfile = os.path.join(self.get_target_private_dir(target), depfilename) 1994 args = [x.replace('@DEPFILE@', depfile) for x in base_args] 1995 args = [x.replace("@INPUT@", infilename).replace('@OUTPUT@', sole_output) 1996 for x in args] 1997 args = self.replace_outputs(args, self.get_target_private_dir(target), outfilelist) 1998 # We have consumed output files, so drop them from the list of remaining outputs. 1999 if len(generator.outputs) > 1: 2000 outfilelist = outfilelist[len(generator.outputs):] 2001 args = self.replace_paths(target, args, override_subdir=subdir) 2002 cmdlist = exe_arr + self.replace_extra_args(args, genlist) 2003 meson_exe_cmd = self.as_meson_exe_cmdline('generator ' + cmdlist[0], 2004 cmdlist[0], cmdlist[1:], 2005 capture=outfiles[0] if generator.capture else None) 2006 if meson_exe_cmd: 2007 cmdlist = meson_exe_cmd 2008 abs_pdir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target)) 2009 os.makedirs(abs_pdir, exist_ok=True) 2010 2011 elem = NinjaBuildElement(self.all_outputs, outfiles, rulename, infilename) 2012 elem.add_dep([self.get_target_filename(x) for x in generator.depends]) 2013 if generator.depfile is not None: 2014 elem.add_item('DEPFILE', depfile) 2015 if len(extra_dependencies) > 0: 2016 elem.add_dep(extra_dependencies) 2017 if len(generator.outputs) == 1: 2018 elem.add_item('DESC', 'Generating {!r}.'.format(sole_output)) 2019 else: 2020 # since there are multiple outputs, we log the source that caused the rebuild 2021 elem.add_item('DESC', 'Generating source from {!r}.'.format(sole_output)) 2022 if isinstance(exe, build.BuildTarget): 2023 elem.add_dep(self.get_target_filename(exe)) 2024 elem.add_item('COMMAND', cmdlist) 2025 self.add_build(elem) 2026 2027 def scan_fortran_module_outputs(self, target): 2028 """ 2029 Find all module and submodule made available in a Fortran code file. 2030 """ 2031 compiler = None 2032 # TODO other compilers 2033 for lang, c in self.environment.coredata.compilers.host.items(): 2034 if lang == 'fortran': 2035 compiler = c 2036 break 2037 if compiler is None: 2038 self.fortran_deps[target.get_basename()] = {} 2039 return 2040 2041 modre = re.compile(FORTRAN_MODULE_PAT, re.IGNORECASE) 2042 submodre = re.compile(FORTRAN_SUBMOD_PAT, re.IGNORECASE) 2043 module_files = {} 2044 submodule_files = {} 2045 for s in target.get_sources(): 2046 # FIXME, does not work for Fortran sources generated by 2047 # custom_target() and generator() as those are run after 2048 # the configuration (configure_file() is OK) 2049 if not compiler.can_compile(s): 2050 continue 2051 filename = s.absolute_path(self.environment.get_source_dir(), 2052 self.environment.get_build_dir()) 2053 # Fortran keywords must be ASCII. 2054 with open(filename, encoding='ascii', errors='ignore') as f: 2055 for line in f: 2056 modmatch = modre.match(line) 2057 if modmatch is not None: 2058 modname = modmatch.group(1).lower() 2059 if modname in module_files: 2060 raise InvalidArguments( 2061 'Namespace collision: module {} defined in ' 2062 'two files {} and {}.'.format(modname, module_files[modname], s)) 2063 module_files[modname] = s 2064 else: 2065 submodmatch = submodre.match(line) 2066 if submodmatch is not None: 2067 # '_' is arbitrarily used to distinguish submod from mod. 2068 parents = submodmatch.group(1).lower().split(':') 2069 submodname = parents[0] + '_' + submodmatch.group(2).lower() 2070 2071 if submodname in submodule_files: 2072 raise InvalidArguments( 2073 'Namespace collision: submodule {} defined in ' 2074 'two files {} and {}.'.format(submodname, submodule_files[submodname], s)) 2075 submodule_files[submodname] = s 2076 2077 self.fortran_deps[target.get_basename()] = {**module_files, **submodule_files} 2078 2079 def get_fortran_deps(self, compiler: FortranCompiler, src: Path, target) -> T.List[str]: 2080 """ 2081 Find all module and submodule needed by a Fortran target 2082 """ 2083 2084 dirname = Path(self.get_target_private_dir(target)) 2085 tdeps = self.fortran_deps[target.get_basename()] 2086 srcdir = Path(self.source_dir) 2087 2088 mod_files = _scan_fortran_file_deps(src, srcdir, dirname, tdeps, compiler) 2089 return mod_files 2090 2091 def get_cross_stdlib_args(self, target, compiler): 2092 if self.environment.machines.matches_build_machine(target.for_machine): 2093 return [] 2094 if not self.environment.properties.host.has_stdlib(compiler.language): 2095 return [] 2096 return compiler.get_no_stdinc_args() 2097 2098 def get_compile_debugfile_args(self, compiler, target, objfile): 2099 # The way MSVC uses PDB files is documented exactly nowhere so 2100 # the following is what we have been able to decipher via 2101 # reverse engineering. 2102 # 2103 # Each object file gets the path of its PDB file written 2104 # inside it. This can be either the final PDB (for, say, 2105 # foo.exe) or an object pdb (for foo.obj). If the former, then 2106 # each compilation step locks the pdb file for writing, which 2107 # is a bottleneck and object files from one target can not be 2108 # used in a different target. The latter seems to be the 2109 # sensible one (and what Unix does) but there is a catch. If 2110 # you try to use precompiled headers MSVC will error out 2111 # because both source and pch pdbs go in the same file and 2112 # they must be the same. 2113 # 2114 # This means: 2115 # 2116 # - pch files must be compiled anew for every object file (negating 2117 # the entire point of having them in the first place) 2118 # - when using pch, output must go to the target pdb 2119 # 2120 # Since both of these are broken in some way, use the one that 2121 # works for each target. This unfortunately means that you 2122 # can't combine pch and object extraction in a single target. 2123 # 2124 # PDB files also lead to filename collisions. A target foo.exe 2125 # has a corresponding foo.pdb. A shared library foo.dll _also_ 2126 # has pdb file called foo.pdb. So will a static library 2127 # foo.lib, which clobbers both foo.pdb _and_ the dll file's 2128 # export library called foo.lib (by default, currently we name 2129 # them libfoo.a to avoidt this issue). You can give the files 2130 # unique names such as foo_exe.pdb but VC also generates a 2131 # bunch of other files which take their names from the target 2132 # basename (i.e. "foo") and stomp on each other. 2133 # 2134 # CMake solves this problem by doing two things. First of all 2135 # static libraries do not generate pdb files at 2136 # all. Presumably you don't need them and VC is smart enough 2137 # to look up the original data when linking (speculation, not 2138 # tested). The second solution is that you can only have 2139 # target named "foo" as an exe, shared lib _or_ static 2140 # lib. This makes filename collisions not happen. The downside 2141 # is that you can't have an executable foo that uses a shared 2142 # library libfoo.so, which is a common idiom on Unix. 2143 # 2144 # If you feel that the above is completely wrong and all of 2145 # this is actually doable, please send patches. 2146 2147 if target.has_pch(): 2148 tfilename = self.get_target_filename_abs(target) 2149 return compiler.get_compile_debugfile_args(tfilename, pch=True) 2150 else: 2151 return compiler.get_compile_debugfile_args(objfile, pch=False) 2152 2153 def get_link_debugfile_name(self, linker, target, outname): 2154 return linker.get_link_debugfile_name(outname) 2155 2156 def get_link_debugfile_args(self, linker, target, outname): 2157 return linker.get_link_debugfile_args(outname) 2158 2159 def generate_llvm_ir_compile(self, target, src): 2160 compiler = get_compiler_for_source(target.compilers.values(), src) 2161 commands = compiler.compiler_args() 2162 # Compiler args for compiling this target 2163 commands += compilers.get_base_compile_args(self.environment.coredata.base_options, 2164 compiler) 2165 if isinstance(src, File): 2166 if src.is_built: 2167 src_filename = os.path.join(src.subdir, src.fname) 2168 else: 2169 src_filename = src.fname 2170 elif os.path.isabs(src): 2171 src_filename = os.path.basename(src) 2172 else: 2173 src_filename = src 2174 obj_basename = self.canonicalize_filename(src_filename) 2175 rel_obj = os.path.join(self.get_target_private_dir(target), obj_basename) 2176 rel_obj += '.' + self.environment.machines[target.for_machine].get_object_suffix() 2177 commands += self.get_compile_debugfile_args(compiler, target, rel_obj) 2178 if isinstance(src, File) and src.is_built: 2179 rel_src = src.fname 2180 elif isinstance(src, File): 2181 rel_src = src.rel_to_builddir(self.build_to_src) 2182 else: 2183 raise InvalidArguments('Invalid source type: {!r}'.format(src)) 2184 # Write the Ninja build command 2185 compiler_name = self.get_compiler_rule_name('llvm_ir', compiler.for_machine) 2186 element = NinjaBuildElement(self.all_outputs, rel_obj, compiler_name, rel_src) 2187 element.add_item('ARGS', commands) 2188 self.add_build(element) 2189 return rel_obj 2190 2191 def get_source_dir_include_args(self, target, compiler): 2192 curdir = target.get_subdir() 2193 tmppath = os.path.normpath(os.path.join(self.build_to_src, curdir)) 2194 return compiler.get_include_args(tmppath, False) 2195 2196 def get_build_dir_include_args(self, target, compiler): 2197 curdir = target.get_subdir() 2198 if curdir == '': 2199 curdir = '.' 2200 return compiler.get_include_args(curdir, False) 2201 2202 @lru_cache(maxsize=None) 2203 def get_normpath_target(self, source) -> str: 2204 return os.path.normpath(source) 2205 2206 def get_custom_target_dir_include_args(self, target, compiler): 2207 custom_target_include_dirs = [] 2208 for i in target.get_generated_sources(): 2209 # Generator output goes into the target private dir which is 2210 # already in the include paths list. Only custom targets have their 2211 # own target build dir. 2212 if not isinstance(i, (build.CustomTarget, build.CustomTargetIndex)): 2213 continue 2214 idir = self.get_normpath_target(self.get_target_dir(i)) 2215 if not idir: 2216 idir = '.' 2217 if idir not in custom_target_include_dirs: 2218 custom_target_include_dirs.append(idir) 2219 incs = [] 2220 for i in custom_target_include_dirs: 2221 incs += compiler.get_include_args(i, False) 2222 return incs 2223 2224 @lru_cache(maxsize=None) 2225 def generate_inc_dir(self, compiler, d, basedir, is_system): 2226 # Avoid superfluous '/.' at the end of paths when d is '.' 2227 if d not in ('', '.'): 2228 expdir = os.path.normpath(os.path.join(basedir, d)) 2229 else: 2230 expdir = basedir 2231 srctreedir = os.path.normpath(os.path.join(self.build_to_src, expdir)) 2232 sargs = compiler.get_include_args(srctreedir, is_system) 2233 # There may be include dirs where a build directory has not been 2234 # created for some source dir. For example if someone does this: 2235 # 2236 # inc = include_directories('foo/bar/baz') 2237 # 2238 # But never subdir()s into the actual dir. 2239 if os.path.isdir(os.path.join(self.environment.get_build_dir(), expdir)): 2240 bargs = compiler.get_include_args(expdir, is_system) 2241 else: 2242 bargs = [] 2243 return (sargs, bargs) 2244 2245 @lru_cache(maxsize=None) 2246 def _generate_single_compile(self, target, compiler, is_generated=False): 2247 base_proxy = self.get_base_options_for_target(target) 2248 # Create an empty commands list, and start adding arguments from 2249 # various sources in the order in which they must override each other 2250 commands = compiler.compiler_args() 2251 # Start with symbol visibility. 2252 commands += compiler.gnu_symbol_visibility_args(target.gnu_symbol_visibility) 2253 # Add compiler args for compiling this target derived from 'base' build 2254 # options passed on the command-line, in default_options, etc. 2255 # These have the lowest priority. 2256 commands += compilers.get_base_compile_args(base_proxy, 2257 compiler) 2258 # The code generated by valac is usually crap and has tons of unused 2259 # variables and such, so disable warnings for Vala C sources. 2260 no_warn_args = (is_generated == 'vala') 2261 # Add compiler args and include paths from several sources; defaults, 2262 # build options, external dependencies, etc. 2263 commands += self.generate_basic_compiler_args(target, compiler, no_warn_args) 2264 # Add custom target dirs as includes automatically, but before 2265 # target-specific include directories. 2266 # XXX: Not sure if anyone actually uses this? It can cause problems in 2267 # situations which increase the likelihood for a header name collision, 2268 # such as in subprojects. 2269 commands += self.get_custom_target_dir_include_args(target, compiler) 2270 # Add include dirs from the `include_directories:` kwarg on the target 2271 # and from `include_directories:` of internal deps of the target. 2272 # 2273 # Target include dirs should override internal deps include dirs. 2274 # This is handled in BuildTarget.process_kwargs() 2275 # 2276 # Include dirs from internal deps should override include dirs from 2277 # external deps and must maintain the order in which they are specified. 2278 # Hence, we must reverse the list so that the order is preserved. 2279 for i in reversed(target.get_include_dirs()): 2280 basedir = i.get_curdir() 2281 # We should iterate include dirs in reversed orders because 2282 # -Ipath will add to begin of array. And without reverse 2283 # flags will be added in reversed order. 2284 for d in reversed(i.get_incdirs()): 2285 # Add source subdir first so that the build subdir overrides it 2286 (compile_obj, includeargs) = self.generate_inc_dir(compiler, d, basedir, i.is_system) 2287 commands += compile_obj 2288 commands += includeargs 2289 for d in i.get_extra_build_dirs(): 2290 commands += compiler.get_include_args(d, i.is_system) 2291 # Add per-target compile args, f.ex, `c_args : ['-DFOO']`. We set these 2292 # near the end since these are supposed to override everything else. 2293 commands += self.escape_extra_args(compiler, 2294 target.get_extra_args(compiler.get_language())) 2295 2296 # D specific additional flags 2297 if compiler.language == 'd': 2298 commands += compiler.get_feature_args(target.d_features, self.build_to_src) 2299 2300 # Add source dir and build dir. Project-specific and target-specific 2301 # include paths must override per-target compile args, include paths 2302 # from external dependencies, internal dependencies, and from 2303 # per-target `include_directories:` 2304 # 2305 # We prefer headers in the build dir over the source dir since, for 2306 # instance, the user might have an srcdir == builddir Autotools build 2307 # in their source tree. Many projects that are moving to Meson have 2308 # both Meson and Autotools in parallel as part of the transition. 2309 if target.implicit_include_directories: 2310 commands += self.get_source_dir_include_args(target, compiler) 2311 if target.implicit_include_directories: 2312 commands += self.get_build_dir_include_args(target, compiler) 2313 # Finally add the private dir for the target to the include path. This 2314 # must override everything else and must be the final path added. 2315 commands += compiler.get_include_args(self.get_target_private_dir(target), False) 2316 return commands 2317 2318 def generate_single_compile(self, target, src, is_generated=False, header_deps=None, order_deps=None): 2319 """ 2320 Compiles C/C++, ObjC/ObjC++, Fortran, and D sources 2321 """ 2322 header_deps = header_deps if header_deps is not None else [] 2323 order_deps = order_deps if order_deps is not None else [] 2324 2325 if isinstance(src, str) and src.endswith('.h'): 2326 raise AssertionError('BUG: sources should not contain headers {!r}'.format(src)) 2327 2328 compiler = get_compiler_for_source(target.compilers.values(), src) 2329 commands = self._generate_single_compile(target, compiler, is_generated) 2330 commands = commands.compiler.compiler_args(commands) 2331 2332 # Create introspection information 2333 if is_generated is False: 2334 self.create_target_source_introspection(target, compiler, commands, [src], []) 2335 else: 2336 self.create_target_source_introspection(target, compiler, commands, [], [src]) 2337 2338 build_dir = self.environment.get_build_dir() 2339 if isinstance(src, File): 2340 rel_src = src.rel_to_builddir(self.build_to_src) 2341 if os.path.isabs(rel_src): 2342 # Source files may not be from the source directory if they originate in source-only libraries, 2343 # so we can't assert that the absolute path is anywhere in particular. 2344 if src.is_built: 2345 assert rel_src.startswith(build_dir) 2346 rel_src = rel_src[len(build_dir) + 1:] 2347 elif is_generated: 2348 raise AssertionError('BUG: broken generated source file handling for {!r}'.format(src)) 2349 else: 2350 raise InvalidArguments('Invalid source type: {!r}'.format(src)) 2351 obj_basename = self.object_filename_from_source(target, src) 2352 rel_obj = os.path.join(self.get_target_private_dir(target), obj_basename) 2353 dep_file = compiler.depfile_for_object(rel_obj) 2354 2355 # Add MSVC debug file generation compile flags: /Fd /FS 2356 commands += self.get_compile_debugfile_args(compiler, target, rel_obj) 2357 2358 # PCH handling 2359 if self.environment.coredata.base_options.get('b_pch', False): 2360 commands += self.get_pch_include_args(compiler, target) 2361 pchlist = target.get_pch(compiler.language) 2362 else: 2363 pchlist = [] 2364 if not pchlist: 2365 pch_dep = [] 2366 elif compiler.id == 'intel': 2367 pch_dep = [] 2368 else: 2369 arr = [] 2370 i = os.path.join(self.get_target_private_dir(target), compiler.get_pch_name(pchlist[0])) 2371 arr.append(i) 2372 pch_dep = arr 2373 2374 compiler_name = self.compiler_to_rule_name(compiler) 2375 extra_deps = [] 2376 if compiler.get_language() == 'fortran': 2377 # Can't read source file to scan for deps if it's generated later 2378 # at build-time. Skip scanning for deps, and just set the module 2379 # outdir argument instead. 2380 # https://github.com/mesonbuild/meson/issues/1348 2381 if not is_generated: 2382 abs_src = Path(build_dir) / rel_src 2383 extra_deps += self.get_fortran_deps(compiler, abs_src, target) 2384 # Dependency hack. Remove once multiple outputs in Ninja is fixed: 2385 # https://groups.google.com/forum/#!topic/ninja-build/j-2RfBIOd_8 2386 for modname, srcfile in self.fortran_deps[target.get_basename()].items(): 2387 modfile = os.path.join(self.get_target_private_dir(target), 2388 compiler.module_name_to_filename(modname)) 2389 2390 if srcfile == src: 2391 crstr = self.get_rule_suffix(target.for_machine) 2392 depelem = NinjaBuildElement(self.all_outputs, modfile, 'FORTRAN_DEP_HACK' + crstr, rel_obj) 2393 self.add_build(depelem) 2394 commands += compiler.get_module_outdir_args(self.get_target_private_dir(target)) 2395 2396 element = NinjaBuildElement(self.all_outputs, rel_obj, compiler_name, rel_src) 2397 self.add_header_deps(target, element, header_deps) 2398 for d in extra_deps: 2399 element.add_dep(d) 2400 for d in order_deps: 2401 if isinstance(d, File): 2402 d = d.rel_to_builddir(self.build_to_src) 2403 elif not self.has_dir_part(d): 2404 d = os.path.join(self.get_target_private_dir(target), d) 2405 element.add_orderdep(d) 2406 element.add_dep(pch_dep) 2407 for i in self.get_fortran_orderdeps(target, compiler): 2408 element.add_orderdep(i) 2409 element.add_item('DEPFILE', dep_file) 2410 element.add_item('ARGS', commands) 2411 self.add_build(element) 2412 return rel_obj 2413 2414 def add_header_deps(self, target, ninja_element, header_deps): 2415 for d in header_deps: 2416 if isinstance(d, File): 2417 d = d.rel_to_builddir(self.build_to_src) 2418 elif not self.has_dir_part(d): 2419 d = os.path.join(self.get_target_private_dir(target), d) 2420 ninja_element.add_dep(d) 2421 2422 def has_dir_part(self, fname): 2423 # FIXME FIXME: The usage of this is a terrible and unreliable hack 2424 if isinstance(fname, File): 2425 return fname.subdir != '' 2426 return has_path_sep(fname) 2427 2428 # Fortran is a bit weird (again). When you link against a library, just compiling a source file 2429 # requires the mod files that are output when single files are built. To do this right we would need to 2430 # scan all inputs and write out explicit deps for each file. That is stoo slow and too much effort so 2431 # instead just have an ordered dependency on the library. This ensures all required mod files are created. 2432 # The real deps are then detected via dep file generation from the compiler. This breaks on compilers that 2433 # produce incorrect dep files but such is life. 2434 def get_fortran_orderdeps(self, target, compiler): 2435 if compiler.language != 'fortran': 2436 return [] 2437 return [os.path.join(self.get_target_dir(lt), lt.get_filename()) for lt in target.link_targets] 2438 2439 def generate_msvc_pch_command(self, target, compiler, pch): 2440 header = pch[0] 2441 pchname = compiler.get_pch_name(header) 2442 dst = os.path.join(self.get_target_private_dir(target), pchname) 2443 2444 commands = [] 2445 commands += self.generate_basic_compiler_args(target, compiler) 2446 2447 if len(pch) == 1: 2448 # Auto generate PCH. 2449 source = self.create_msvc_pch_implementation(target, compiler.get_language(), pch[0]) 2450 pch_header_dir = os.path.dirname(os.path.join(self.build_to_src, target.get_source_subdir(), header)) 2451 commands += compiler.get_include_args(pch_header_dir, False) 2452 else: 2453 source = os.path.join(self.build_to_src, target.get_source_subdir(), pch[1]) 2454 2455 just_name = os.path.basename(header) 2456 (objname, pch_args) = compiler.gen_pch_args(just_name, source, dst) 2457 commands += pch_args 2458 commands += self._generate_single_compile(target, compiler) 2459 commands += self.get_compile_debugfile_args(compiler, target, objname) 2460 dep = dst + '.' + compiler.get_depfile_suffix() 2461 return commands, dep, dst, [objname], source 2462 2463 def generate_gcc_pch_command(self, target, compiler, pch): 2464 commands = self._generate_single_compile(target, compiler) 2465 if pch.split('.')[-1] == 'h' and compiler.language == 'cpp': 2466 # Explicitly compile pch headers as C++. If Clang is invoked in C++ mode, it actually warns if 2467 # this option is not set, and for gcc it also makes sense to use it. 2468 commands += ['-x', 'c++-header'] 2469 dst = os.path.join(self.get_target_private_dir(target), 2470 os.path.basename(pch) + '.' + compiler.get_pch_suffix()) 2471 dep = dst + '.' + compiler.get_depfile_suffix() 2472 return commands, dep, dst, [] # Gcc does not create an object file during pch generation. 2473 2474 def generate_pch(self, target, header_deps=None): 2475 header_deps = header_deps if header_deps is not None else [] 2476 pch_objects = [] 2477 for lang in ['c', 'cpp']: 2478 pch = target.get_pch(lang) 2479 if not pch: 2480 continue 2481 if not has_path_sep(pch[0]) or not has_path_sep(pch[-1]): 2482 msg = 'Precompiled header of {!r} must not be in the same ' \ 2483 'directory as source, please put it in a subdirectory.' \ 2484 ''.format(target.get_basename()) 2485 raise InvalidArguments(msg) 2486 compiler = target.compilers[lang] 2487 if isinstance(compiler, VisualStudioLikeCompiler): 2488 (commands, dep, dst, objs, src) = self.generate_msvc_pch_command(target, compiler, pch) 2489 extradep = os.path.join(self.build_to_src, target.get_source_subdir(), pch[0]) 2490 elif compiler.id == 'intel': 2491 # Intel generates on target generation 2492 continue 2493 else: 2494 src = os.path.join(self.build_to_src, target.get_source_subdir(), pch[0]) 2495 (commands, dep, dst, objs) = self.generate_gcc_pch_command(target, compiler, pch[0]) 2496 extradep = None 2497 pch_objects += objs 2498 rulename = self.compiler_to_pch_rule_name(compiler) 2499 elem = NinjaBuildElement(self.all_outputs, dst, rulename, src) 2500 if extradep is not None: 2501 elem.add_dep(extradep) 2502 self.add_header_deps(target, elem, header_deps) 2503 elem.add_item('ARGS', commands) 2504 elem.add_item('DEPFILE', dep) 2505 self.add_build(elem) 2506 return pch_objects 2507 2508 def get_target_shsym_filename(self, target): 2509 # Always name the .symbols file after the primary build output because it always exists 2510 targetdir = self.get_target_private_dir(target) 2511 return os.path.join(targetdir, target.get_filename() + '.symbols') 2512 2513 def generate_shsym(self, target): 2514 target_file = self.get_target_filename(target) 2515 symname = self.get_target_shsym_filename(target) 2516 elem = NinjaBuildElement(self.all_outputs, symname, 'SHSYM', target_file) 2517 # The library we will actually link to, which is an import library on Windows (not the DLL) 2518 elem.add_item('IMPLIB', self.get_target_filename_for_linking(target)) 2519 if self.environment.is_cross_build(): 2520 elem.add_item('CROSS', '--cross-host=' + self.environment.machines[target.for_machine].system) 2521 self.add_build(elem) 2522 2523 def get_cross_stdlib_link_args(self, target, linker): 2524 if isinstance(target, build.StaticLibrary) or \ 2525 self.environment.machines.matches_build_machine(target.for_machine): 2526 return [] 2527 if not self.environment.properties.host.has_stdlib(linker.language): 2528 return [] 2529 return linker.get_no_stdlib_link_args() 2530 2531 def get_import_filename(self, target): 2532 return os.path.join(self.get_target_dir(target), target.import_filename) 2533 2534 def get_target_type_link_args(self, target, linker): 2535 commands = [] 2536 if isinstance(target, build.Executable): 2537 # Currently only used with the Swift compiler to add '-emit-executable' 2538 commands += linker.get_std_exe_link_args() 2539 # If export_dynamic, add the appropriate linker arguments 2540 if target.export_dynamic: 2541 commands += linker.gen_export_dynamic_link_args(self.environment) 2542 # If implib, and that's significant on this platform (i.e. Windows using either GCC or Visual Studio) 2543 if target.import_filename: 2544 commands += linker.gen_import_library_args(self.get_import_filename(target)) 2545 if target.pie: 2546 commands += linker.get_pie_link_args() 2547 elif isinstance(target, build.SharedLibrary): 2548 if isinstance(target, build.SharedModule): 2549 options = self.environment.coredata.base_options 2550 commands += linker.get_std_shared_module_link_args(options) 2551 else: 2552 commands += linker.get_std_shared_lib_link_args() 2553 # All shared libraries are PIC 2554 commands += linker.get_pic_args() 2555 # Add -Wl,-soname arguments on Linux, -install_name on OS X 2556 commands += linker.get_soname_args( 2557 self.environment, target.prefix, target.name, target.suffix, 2558 target.soversion, target.darwin_versions, 2559 isinstance(target, build.SharedModule)) 2560 # This is only visited when building for Windows using either GCC or Visual Studio 2561 if target.vs_module_defs and hasattr(linker, 'gen_vs_module_defs_args'): 2562 commands += linker.gen_vs_module_defs_args(target.vs_module_defs.rel_to_builddir(self.build_to_src)) 2563 # This is only visited when building for Windows using either GCC or Visual Studio 2564 if target.import_filename: 2565 commands += linker.gen_import_library_args(self.get_import_filename(target)) 2566 elif isinstance(target, build.StaticLibrary): 2567 commands += linker.get_std_link_args() 2568 else: 2569 raise RuntimeError('Unknown build target type.') 2570 return commands 2571 2572 def get_target_type_link_args_post_dependencies(self, target, linker): 2573 commands = [] 2574 if isinstance(target, build.Executable): 2575 # If gui_app is significant on this platform, add the appropriate linker arguments. 2576 # Unfortunately this can't be done in get_target_type_link_args, because some misguided 2577 # libraries (such as SDL2) add -mwindows to their link flags. 2578 commands += linker.get_gui_app_args(target.gui_app) 2579 return commands 2580 2581 def get_link_whole_args(self, linker, target): 2582 target_args = self.build_target_link_arguments(linker, target.link_whole_targets) 2583 return linker.get_link_whole_for(target_args) if len(target_args) else [] 2584 2585 @lru_cache(maxsize=None) 2586 def guess_library_absolute_path(self, linker, libname, search_dirs, patterns): 2587 for d in search_dirs: 2588 for p in patterns: 2589 trial = CCompiler._get_trials_from_pattern(p, d, libname) 2590 if not trial: 2591 continue 2592 trial = CCompiler._get_file_from_list(self.environment, trial) 2593 if not trial: 2594 continue 2595 # Return the first result 2596 return trial 2597 2598 def guess_external_link_dependencies(self, linker, target, commands, internal): 2599 # Ideally the linker would generate dependency information that could be used. 2600 # But that has 2 problems: 2601 # * currently ld can not create dependency information in a way that ninja can use: 2602 # https://sourceware.org/bugzilla/show_bug.cgi?id=22843 2603 # * Meson optimizes libraries from the same build using the symbol extractor. 2604 # Just letting ninja use ld generated dependencies would undo this optimization. 2605 search_dirs = OrderedSet() 2606 libs = OrderedSet() 2607 absolute_libs = [] 2608 2609 build_dir = self.environment.get_build_dir() 2610 # the following loop sometimes consumes two items from command in one pass 2611 it = iter(linker.native_args_to_unix(commands)) 2612 for item in it: 2613 if item in internal and not item.startswith('-'): 2614 continue 2615 2616 if item.startswith('-L'): 2617 if len(item) > 2: 2618 path = item[2:] 2619 else: 2620 try: 2621 path = next(it) 2622 except StopIteration: 2623 mlog.warning("Generated linker command has -L argument without following path") 2624 break 2625 if not os.path.isabs(path): 2626 path = os.path.join(build_dir, path) 2627 search_dirs.add(path) 2628 elif item.startswith('-l'): 2629 if len(item) > 2: 2630 lib = item[2:] 2631 else: 2632 try: 2633 lib = next(it) 2634 except StopIteration: 2635 mlog.warning("Generated linker command has '-l' argument without following library name") 2636 break 2637 libs.add(lib) 2638 elif os.path.isabs(item) and self.environment.is_library(item) and os.path.isfile(item): 2639 absolute_libs.append(item) 2640 2641 guessed_dependencies = [] 2642 # TODO The get_library_naming requirement currently excludes link targets that use d or fortran as their main linker 2643 if hasattr(linker, 'get_library_naming'): 2644 search_dirs = tuple(search_dirs) + tuple(linker.get_library_dirs(self.environment)) 2645 static_patterns = linker.get_library_naming(self.environment, LibType.STATIC, strict=True) 2646 shared_patterns = linker.get_library_naming(self.environment, LibType.SHARED, strict=True) 2647 for libname in libs: 2648 # be conservative and record most likely shared and static resolution, because we don't know exactly 2649 # which one the linker will prefer 2650 staticlibs = self.guess_library_absolute_path(linker, libname, 2651 search_dirs, static_patterns) 2652 sharedlibs = self.guess_library_absolute_path(linker, libname, 2653 search_dirs, shared_patterns) 2654 if staticlibs: 2655 guessed_dependencies.append(staticlibs.resolve().as_posix()) 2656 if sharedlibs: 2657 guessed_dependencies.append(sharedlibs.resolve().as_posix()) 2658 2659 return guessed_dependencies + absolute_libs 2660 2661 def generate_link(self, target, outname, obj_list, linker, extra_args=None, stdlib_args=None): 2662 extra_args = extra_args if extra_args is not None else [] 2663 stdlib_args = stdlib_args if stdlib_args is not None else [] 2664 implicit_outs = [] 2665 if isinstance(target, build.StaticLibrary): 2666 linker_base = 'STATIC' 2667 else: 2668 linker_base = linker.get_language() # Fixme. 2669 if isinstance(target, build.SharedLibrary): 2670 self.generate_shsym(target) 2671 crstr = self.get_rule_suffix(target.for_machine) 2672 linker_rule = linker_base + '_LINKER' + crstr 2673 # Create an empty commands list, and start adding link arguments from 2674 # various sources in the order in which they must override each other 2675 # starting from hard-coded defaults followed by build options and so on. 2676 # 2677 # Once all the linker options have been passed, we will start passing 2678 # libraries and library paths from internal and external sources. 2679 commands = linker.compiler_args() 2680 # First, the trivial ones that are impossible to override. 2681 # 2682 # Add linker args for linking this target derived from 'base' build 2683 # options passed on the command-line, in default_options, etc. 2684 # These have the lowest priority. 2685 if isinstance(target, build.StaticLibrary): 2686 commands += linker.get_base_link_args(self.get_base_options_for_target(target)) 2687 else: 2688 commands += compilers.get_base_link_args(self.get_base_options_for_target(target), 2689 linker, 2690 isinstance(target, build.SharedModule)) 2691 # Add -nostdlib if needed; can't be overridden 2692 commands += self.get_cross_stdlib_link_args(target, linker) 2693 # Add things like /NOLOGO; usually can't be overridden 2694 commands += linker.get_linker_always_args() 2695 # Add buildtype linker args: optimization level, etc. 2696 commands += linker.get_buildtype_linker_args(self.get_option_for_target('buildtype', target)) 2697 # Add /DEBUG and the pdb filename when using MSVC 2698 if self.get_option_for_target('debug', target): 2699 commands += self.get_link_debugfile_args(linker, target, outname) 2700 debugfile = self.get_link_debugfile_name(linker, target, outname) 2701 if debugfile is not None: 2702 implicit_outs += [debugfile] 2703 # Add link args specific to this BuildTarget type, such as soname args, 2704 # PIC, import library generation, etc. 2705 commands += self.get_target_type_link_args(target, linker) 2706 # Archives that are copied wholesale in the result. Must be before any 2707 # other link targets so missing symbols from whole archives are found in those. 2708 if not isinstance(target, build.StaticLibrary): 2709 commands += self.get_link_whole_args(linker, target) 2710 2711 if not isinstance(target, build.StaticLibrary): 2712 # Add link args added using add_project_link_arguments() 2713 commands += self.build.get_project_link_args(linker, target.subproject, target.for_machine) 2714 # Add link args added using add_global_link_arguments() 2715 # These override per-project link arguments 2716 commands += self.build.get_global_link_args(linker, target.for_machine) 2717 # Link args added from the env: LDFLAGS. We want these to override 2718 # all the defaults but not the per-target link args. 2719 commands += self.environment.coredata.get_external_link_args(target.for_machine, linker.get_language()) 2720 2721 # Now we will add libraries and library paths from various sources 2722 2723 # Add link args to link to all internal libraries (link_with:) and 2724 # internal dependencies needed by this target. 2725 if linker_base == 'STATIC': 2726 # Link arguments of static libraries are not put in the command 2727 # line of the library. They are instead appended to the command 2728 # line where the static library is used. 2729 dependencies = [] 2730 else: 2731 dependencies = target.get_dependencies() 2732 internal = self.build_target_link_arguments(linker, dependencies) 2733 commands += internal 2734 # Only non-static built targets need link args and link dependencies 2735 if not isinstance(target, build.StaticLibrary): 2736 # For 'automagic' deps: Boost and GTest. Also dependency('threads'). 2737 # pkg-config puts the thread flags itself via `Cflags:` 2738 2739 commands += linker.get_target_link_args(target) 2740 # External deps must be last because target link libraries may depend on them. 2741 for dep in target.get_external_deps(): 2742 # Extend without reordering or de-dup to preserve `-L -l` sets 2743 # https://github.com/mesonbuild/meson/issues/1718 2744 commands.extend_preserving_lflags(linker.get_dependency_link_args(dep)) 2745 for d in target.get_dependencies(): 2746 if isinstance(d, build.StaticLibrary): 2747 for dep in d.get_external_deps(): 2748 commands.extend_preserving_lflags(linker.get_dependency_link_args(dep)) 2749 2750 # Add link args specific to this BuildTarget type that must not be overridden by dependencies 2751 commands += self.get_target_type_link_args_post_dependencies(target, linker) 2752 2753 # Add link args for c_* or cpp_* build options. Currently this only 2754 # adds c_winlibs and cpp_winlibs when building for Windows. This needs 2755 # to be after all internal and external libraries so that unresolved 2756 # symbols from those can be found here. This is needed when the 2757 # *_winlibs that we want to link to are static mingw64 libraries. 2758 if hasattr(linker, 'get_language'): 2759 # The static linker doesn't know what language it is building, so we 2760 # don't know what option. Fortunately, it doesn't care to see the 2761 # language-specific options either. 2762 # 2763 # We shouldn't check whether we are making a static library, because 2764 # in the LTO case we do use a real compiler here. 2765 commands += linker.get_option_link_args(self.environment.coredata.compiler_options[target.for_machine][linker.get_language()]) 2766 2767 dep_targets = [] 2768 dep_targets.extend(self.guess_external_link_dependencies(linker, target, commands, internal)) 2769 2770 # Set runtime-paths so we can run executables without needing to set 2771 # LD_LIBRARY_PATH, etc in the environment. Doesn't work on Windows. 2772 if has_path_sep(target.name): 2773 # Target names really should not have slashes in them, but 2774 # unfortunately we did not check for that and some downstream projects 2775 # now have them. Once slashes are forbidden, remove this bit. 2776 target_slashname_workaround_dir = os.path.join( 2777 os.path.dirname(target.name), 2778 self.get_target_dir(target)) 2779 else: 2780 target_slashname_workaround_dir = self.get_target_dir(target) 2781 (rpath_args, target.rpath_dirs_to_remove) = \ 2782 linker.build_rpath_args(self.environment, 2783 self.environment.get_build_dir(), 2784 target_slashname_workaround_dir, 2785 self.determine_rpath_dirs(target), 2786 target.build_rpath, 2787 target.install_rpath) 2788 commands += rpath_args 2789 # Add libraries generated by custom targets 2790 custom_target_libraries = self.get_custom_target_provided_libraries(target) 2791 commands += extra_args 2792 commands += custom_target_libraries 2793 commands += stdlib_args # Standard library arguments go last, because they never depend on anything. 2794 dep_targets.extend([self.get_dependency_filename(t) for t in dependencies]) 2795 dep_targets.extend([self.get_dependency_filename(t) 2796 for t in target.link_depends]) 2797 elem = NinjaBuildElement(self.all_outputs, outname, linker_rule, obj_list, implicit_outs=implicit_outs) 2798 elem.add_dep(dep_targets + custom_target_libraries) 2799 elem.add_item('LINK_ARGS', commands) 2800 return elem 2801 2802 def get_dependency_filename(self, t): 2803 if isinstance(t, build.SharedLibrary): 2804 return self.get_target_shsym_filename(t) 2805 elif isinstance(t, mesonlib.File): 2806 if t.is_built: 2807 return t.relative_name() 2808 else: 2809 return t.absolute_path(self.environment.get_source_dir(), 2810 self.environment.get_build_dir()) 2811 return self.get_target_filename(t) 2812 2813 def generate_shlib_aliases(self, target, outdir): 2814 aliases = target.get_aliases() 2815 for alias, to in aliases.items(): 2816 aliasfile = os.path.join(self.environment.get_build_dir(), outdir, alias) 2817 try: 2818 os.remove(aliasfile) 2819 except Exception: 2820 pass 2821 try: 2822 os.symlink(to, aliasfile) 2823 except NotImplementedError: 2824 mlog.debug("Library versioning disabled because symlinks are not supported.") 2825 except OSError: 2826 mlog.debug("Library versioning disabled because we do not have symlink creation privileges.") 2827 2828 def generate_custom_target_clean(self, trees): 2829 e = NinjaBuildElement(self.all_outputs, 'meson-clean-ctlist', 'CUSTOM_COMMAND', 'PHONY') 2830 d = CleanTrees(self.environment.get_build_dir(), trees) 2831 d_file = os.path.join(self.environment.get_scratch_dir(), 'cleantrees.dat') 2832 e.add_item('COMMAND', self.environment.get_build_command() + ['--internal', 'cleantrees', d_file]) 2833 e.add_item('description', 'Cleaning custom target directories') 2834 self.add_build(e) 2835 # Alias that runs the target defined above 2836 self.create_target_alias('meson-clean-ctlist') 2837 # Write out the data file passed to the script 2838 with open(d_file, 'wb') as ofile: 2839 pickle.dump(d, ofile) 2840 return 'clean-ctlist' 2841 2842 def generate_gcov_clean(self): 2843 gcno_elem = NinjaBuildElement(self.all_outputs, 'meson-clean-gcno', 'CUSTOM_COMMAND', 'PHONY') 2844 gcno_elem.add_item('COMMAND', mesonlib.meson_command + ['--internal', 'delwithsuffix', '.', 'gcno']) 2845 gcno_elem.add_item('description', 'Deleting gcno files') 2846 self.add_build(gcno_elem) 2847 # Alias that runs the target defined above 2848 self.create_target_alias('meson-clean-gcno') 2849 2850 gcda_elem = NinjaBuildElement(self.all_outputs, 'meson-clean-gcda', 'CUSTOM_COMMAND', 'PHONY') 2851 gcda_elem.add_item('COMMAND', mesonlib.meson_command + ['--internal', 'delwithsuffix', '.', 'gcda']) 2852 gcda_elem.add_item('description', 'Deleting gcda files') 2853 self.add_build(gcda_elem) 2854 # Alias that runs the target defined above 2855 self.create_target_alias('meson-clean-gcda') 2856 2857 def get_user_option_args(self): 2858 cmds = [] 2859 for (k, v) in self.environment.coredata.user_options.items(): 2860 cmds.append('-D' + k + '=' + (v.value if isinstance(v.value, str) else str(v.value).lower())) 2861 # The order of these arguments must be the same between runs of Meson 2862 # to ensure reproducible output. The order we pass them shouldn't 2863 # affect behavior in any other way. 2864 return sorted(cmds) 2865 2866 def generate_dist(self): 2867 elem = NinjaBuildElement(self.all_outputs, 'meson-dist', 'CUSTOM_COMMAND', 'PHONY') 2868 elem.add_item('DESC', 'Creating source packages') 2869 elem.add_item('COMMAND', self.environment.get_build_command() + ['dist']) 2870 elem.add_item('pool', 'console') 2871 self.add_build(elem) 2872 # Alias that runs the target defined above 2873 self.create_target_alias('meson-dist') 2874 2875 def generate_scanbuild(self): 2876 if not environment.detect_scanbuild(): 2877 return 2878 if ('', 'scan-build') in self.build.run_target_names: 2879 return 2880 cmd = self.environment.get_build_command() + \ 2881 ['--internal', 'scanbuild', self.environment.source_dir, self.environment.build_dir] + \ 2882 self.environment.get_build_command() + self.get_user_option_args() 2883 elem = NinjaBuildElement(self.all_outputs, 'meson-scan-build', 'CUSTOM_COMMAND', 'PHONY') 2884 elem.add_item('COMMAND', cmd) 2885 elem.add_item('pool', 'console') 2886 self.add_build(elem) 2887 # Alias that runs the target defined above 2888 self.create_target_alias('meson-scan-build') 2889 2890 def generate_clangtool(self, name): 2891 target_name = 'clang-' + name 2892 if not os.path.exists(os.path.join(self.environment.source_dir, '.clang-' + name)) and \ 2893 not os.path.exists(os.path.join(self.environment.source_dir, '_clang-' + name)): 2894 return 2895 if target_name in self.all_outputs: 2896 return 2897 if ('', target_name) in self.build.run_target_names: 2898 return 2899 cmd = self.environment.get_build_command() + \ 2900 ['--internal', 'clang' + name, self.environment.source_dir, self.environment.build_dir] 2901 elem = NinjaBuildElement(self.all_outputs, 'meson-' + target_name, 'CUSTOM_COMMAND', 'PHONY') 2902 elem.add_item('COMMAND', cmd) 2903 elem.add_item('pool', 'console') 2904 self.add_build(elem) 2905 self.create_target_alias('meson-' + target_name) 2906 2907 def generate_clangformat(self): 2908 if not environment.detect_clangformat(): 2909 return 2910 self.generate_clangtool('format') 2911 2912 def generate_clangtidy(self): 2913 import shutil 2914 if not shutil.which('clang-tidy'): 2915 return 2916 self.generate_clangtool('tidy') 2917 2918 def generate_tags(self, tool, target_name): 2919 import shutil 2920 if not shutil.which(tool): 2921 return 2922 if ('', target_name) in self.build.run_target_names: 2923 return 2924 if target_name in self.all_outputs: 2925 return 2926 cmd = self.environment.get_build_command() + \ 2927 ['--internal', 'tags', tool, self.environment.source_dir] 2928 elem = NinjaBuildElement(self.all_outputs, 'meson-' + target_name, 'CUSTOM_COMMAND', 'PHONY') 2929 elem.add_item('COMMAND', cmd) 2930 elem.add_item('pool', 'console') 2931 self.add_build(elem) 2932 # Alias that runs the target defined above 2933 self.create_target_alias('meson-' + target_name) 2934 2935 # For things like scan-build and other helper tools we might have. 2936 def generate_utils(self): 2937 self.generate_scanbuild() 2938 self.generate_clangformat() 2939 self.generate_clangtidy() 2940 self.generate_tags('etags', 'TAGS') 2941 self.generate_tags('ctags', 'ctags') 2942 self.generate_tags('cscope', 'cscope') 2943 cmd = self.environment.get_build_command() + ['--internal', 'uninstall'] 2944 elem = NinjaBuildElement(self.all_outputs, 'meson-uninstall', 'CUSTOM_COMMAND', 'PHONY') 2945 elem.add_item('COMMAND', cmd) 2946 elem.add_item('pool', 'console') 2947 self.add_build(elem) 2948 # Alias that runs the target defined above 2949 self.create_target_alias('meson-uninstall') 2950 2951 def generate_ending(self): 2952 targetlist = [] 2953 for t in self.get_build_by_default_targets().values(): 2954 # Add the first output of each target to the 'all' target so that 2955 # they are all built 2956 targetlist.append(os.path.join(self.get_target_dir(t), t.get_outputs()[0])) 2957 2958 elem = NinjaBuildElement(self.all_outputs, 'all', 'phony', targetlist) 2959 self.add_build(elem) 2960 2961 elem = NinjaBuildElement(self.all_outputs, 'meson-clean', 'CUSTOM_COMMAND', 'PHONY') 2962 elem.add_item('COMMAND', self.ninja_command + ['-t', 'clean']) 2963 elem.add_item('description', 'Cleaning') 2964 # Alias that runs the above-defined meson-clean target 2965 self.create_target_alias('meson-clean') 2966 2967 # If we have custom targets in this project, add all their outputs to 2968 # the list that is passed to the `cleantrees.py` script. The script 2969 # will manually delete all custom_target outputs that are directories 2970 # instead of files. This is needed because on platforms other than 2971 # Windows, Ninja only deletes directories while cleaning if they are 2972 # empty. https://github.com/mesonbuild/meson/issues/1220 2973 ctlist = [] 2974 for t in self.build.get_targets().values(): 2975 if isinstance(t, build.CustomTarget): 2976 # Create a list of all custom target outputs 2977 for o in t.get_outputs(): 2978 ctlist.append(os.path.join(self.get_target_dir(t), o)) 2979 if ctlist: 2980 elem.add_dep(self.generate_custom_target_clean(ctlist)) 2981 2982 if 'b_coverage' in self.environment.coredata.base_options and \ 2983 self.environment.coredata.base_options['b_coverage'].value: 2984 self.generate_gcov_clean() 2985 elem.add_dep('clean-gcda') 2986 elem.add_dep('clean-gcno') 2987 self.add_build(elem) 2988 2989 deps = self.get_regen_filelist() 2990 elem = NinjaBuildElement(self.all_outputs, 'build.ninja', 'REGENERATE_BUILD', deps) 2991 elem.add_item('pool', 'console') 2992 self.add_build(elem) 2993 2994 elem = NinjaBuildElement(self.all_outputs, 'reconfigure', 'REGENERATE_BUILD', 'PHONY') 2995 elem.add_item('pool', 'console') 2996 self.add_build(elem) 2997 2998 elem = NinjaBuildElement(self.all_outputs, deps, 'phony', '') 2999 self.add_build(elem) 3000 3001 def get_introspection_data(self, target_id, target): 3002 if target_id not in self.introspection_data or len(self.introspection_data[target_id]) == 0: 3003 return super().get_introspection_data(target_id, target) 3004 3005 result = [] 3006 for i in self.introspection_data[target_id].values(): 3007 result += [i] 3008 return result 3009 3010def load(build_dir): 3011 filename = os.path.join(build_dir, 'meson-private', 'install.dat') 3012 with open(filename, 'rb') as f: 3013 obj = pickle.load(f) 3014 return obj 3015 3016 3017def _scan_fortran_file_deps(src: Path, srcdir: Path, dirname: Path, tdeps, compiler) -> T.List[str]: 3018 """ 3019 scan a Fortran file for dependencies. Needs to be distinct from target 3020 to allow for recursion induced by `include` statements.er 3021 3022 It makes a number of assumptions, including 3023 3024 * `use`, `module`, `submodule` name is not on a continuation line 3025 3026 Regex 3027 ----- 3028 3029 * `incre` works for `#include "foo.f90"` and `include "foo.f90"` 3030 * `usere` works for legacy and Fortran 2003 `use` statements 3031 * `submodre` is for Fortran >= 2008 `submodule` 3032 """ 3033 3034 incre = re.compile(FORTRAN_INCLUDE_PAT, re.IGNORECASE) 3035 usere = re.compile(FORTRAN_USE_PAT, re.IGNORECASE) 3036 submodre = re.compile(FORTRAN_SUBMOD_PAT, re.IGNORECASE) 3037 3038 mod_files = [] 3039 src = Path(src) 3040 with src.open(encoding='ascii', errors='ignore') as f: 3041 for line in f: 3042 # included files 3043 incmatch = incre.match(line) 3044 if incmatch is not None: 3045 incfile = srcdir / incmatch.group(1) 3046 if incfile.suffix.lower()[1:] in compiler.file_suffixes: 3047 mod_files.extend(_scan_fortran_file_deps(incfile, srcdir, dirname, tdeps, compiler)) 3048 # modules 3049 usematch = usere.match(line) 3050 if usematch is not None: 3051 usename = usematch.group(1).lower() 3052 if usename == 'intrinsic': # this keeps the regex simpler 3053 continue 3054 if usename not in tdeps: 3055 # The module is not provided by any source file. This 3056 # is due to: 3057 # a) missing file/typo/etc 3058 # b) using a module provided by the compiler, such as 3059 # OpenMP 3060 # There's no easy way to tell which is which (that I 3061 # know of) so just ignore this and go on. Ideally we 3062 # would print a warning message to the user but this is 3063 # a common occurrence, which would lead to lots of 3064 # distracting noise. 3065 continue 3066 srcfile = srcdir / tdeps[usename].fname # type: Path 3067 if not srcfile.is_file(): 3068 if srcfile.name != src.name: # generated source file 3069 pass 3070 else: # subproject 3071 continue 3072 elif srcfile.samefile(src): # self-reference 3073 continue 3074 3075 mod_name = compiler.module_name_to_filename(usename) 3076 mod_files.append(str(dirname / mod_name)) 3077 else: # submodules 3078 submodmatch = submodre.match(line) 3079 if submodmatch is not None: 3080 parents = submodmatch.group(1).lower().split(':') 3081 assert len(parents) in (1, 2), ( 3082 'submodule ancestry must be specified as' 3083 ' ancestor:parent but Meson found {}'.format(parents)) 3084 3085 ancestor_child = '_'.join(parents) 3086 if ancestor_child not in tdeps: 3087 raise MesonException("submodule {} relies on ancestor module {} that was not found.".format(submodmatch.group(2).lower(), ancestor_child.split('_')[0])) 3088 submodsrcfile = srcdir / tdeps[ancestor_child].fname # type: Path 3089 if not submodsrcfile.is_file(): 3090 if submodsrcfile.name != src.name: # generated source file 3091 pass 3092 else: # subproject 3093 continue 3094 elif submodsrcfile.samefile(src): # self-reference 3095 continue 3096 mod_name = compiler.module_name_to_filename(ancestor_child) 3097 mod_files.append(str(dirname / mod_name)) 3098 return mod_files 3099