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