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