1# Copyright (c) 2018 Foundries.io
2#
3# SPDX-License-Identifier: Apache-2.0
4
5import argparse
6import os
7import pathlib
8import shlex
9import sys
10import yaml
11
12from west import log
13from west.configuration import config
14from zcmake import DEFAULT_CMAKE_GENERATOR, run_cmake, run_build, CMakeCache
15from build_helpers import is_zephyr_build, find_build_dir, load_domains, \
16    FIND_BUILD_DIR_DESCRIPTION
17
18from zephyr_ext_common import Forceable
19
20_ARG_SEPARATOR = '--'
21
22SYSBUILD_PROJ_DIR = pathlib.Path(__file__).resolve().parent.parent.parent \
23                    / pathlib.Path('share/sysbuild')
24
25BUILD_USAGE = '''\
26west build [-h] [-b BOARD[@REV]]] [-d BUILD_DIR]
27           [-S SNIPPET] [--shield SHIELD]
28           [-t TARGET] [-p {auto, always, never}] [-c] [--cmake-only]
29           [-n] [-o BUILD_OPT] [-f]
30           [--sysbuild | --no-sysbuild] [--domain DOMAIN]
31           [source_dir] -- [cmake_opt [cmake_opt ...]]
32'''
33
34BUILD_DESCRIPTION = f'''\
35Convenience wrapper for building Zephyr applications.
36
37{FIND_BUILD_DIR_DESCRIPTION}
38
39positional arguments:
40  source_dir            application source directory
41  cmake_opt             extra options to pass to cmake; implies -c
42                        (these must come after "--" as shown above)
43'''
44
45PRISTINE_DESCRIPTION = """\
46A "pristine" build directory is empty. The -p option controls
47whether the build directory is made pristine before the build
48is done. A bare '--pristine' with no value is the same as
49--pristine=always. Setting --pristine=auto uses heuristics to
50guess if a pristine build may be necessary."""
51
52def _banner(msg):
53    log.inf('-- west build: ' + msg, colorize=True)
54
55def config_get(option, fallback):
56    return config.get('build', option, fallback=fallback)
57
58def config_getboolean(option, fallback):
59    return config.getboolean('build', option, fallback=fallback)
60
61class AlwaysIfMissing(argparse.Action):
62
63    def __call__(self, parser, namespace, values, option_string=None):
64        setattr(namespace, self.dest, values or 'always')
65
66class Build(Forceable):
67
68    def __init__(self):
69        super(Build, self).__init__(
70            'build',
71            # Keep this in sync with the string in west-commands.yml.
72            'compile a Zephyr application',
73            BUILD_DESCRIPTION,
74            accepts_unknown_args=True)
75
76        self.source_dir = None
77        '''Source directory for the build, or None on error.'''
78
79        self.build_dir = None
80        '''Final build directory used to run the build, or None on error.'''
81
82        self.created_build_dir = False
83        '''True if the build directory was created; False otherwise.'''
84
85        self.run_cmake = False
86        '''True if CMake was run; False otherwise.
87
88        Note: this only describes CMake runs done by this command. The
89        build system generated by CMake may also update itself due to
90        internal logic.'''
91
92        self.cmake_cache = None
93        '''Final parsed CMake cache for the build, or None on error.'''
94
95    def do_add_parser(self, parser_adder):
96        parser = parser_adder.add_parser(
97            self.name,
98            help=self.help,
99            formatter_class=argparse.RawDescriptionHelpFormatter,
100            description=self.description,
101            usage=BUILD_USAGE)
102
103        # Remember to update west-completion.bash if you add or remove
104        # flags
105
106        parser.add_argument('-b', '--board',
107                        help='board to build for with optional board revision')
108        # Hidden option for backwards compatibility
109        parser.add_argument('-s', '--source-dir', help=argparse.SUPPRESS)
110        parser.add_argument('-d', '--build-dir',
111                            help='build directory to create or use')
112        self.add_force_arg(parser)
113
114        group = parser.add_argument_group('cmake and build tool')
115        group.add_argument('-c', '--cmake', action='store_true',
116                           help='force a cmake run')
117        group.add_argument('--cmake-only', action='store_true',
118                           help="just run cmake; don't build (implies -c)")
119        group.add_argument('--domain', action='append',
120                           help='''execute build tool (make or ninja) only for
121                           given domain''')
122        group.add_argument('-t', '--target',
123                           help='''run build system target TARGET
124                           (try "-t usage")''')
125        group.add_argument('-T', '--test-item',
126                           help='''Build based on test data in testcase.yaml
127                           or sample.yaml. If source directory is not used
128                           an argument has to be defined as
129                           SOURCE_PATH/TEST_NAME.
130                           E.g. samples/hello_world/sample.basic.helloworld.
131                           If source directory is passed
132                           then "TEST_NAME" is enough.''')
133        group.add_argument('-o', '--build-opt', default=[], action='append',
134                           help='''options to pass to the build tool
135                           (make or ninja); may be given more than once''')
136        group.add_argument('-n', '--just-print', '--dry-run', '--recon',
137                            dest='dry_run', action='store_true',
138                            help="just print build commands; don't run them")
139        group.add_argument('-S', '--snippet', dest='snippets', metavar='SNIPPET',
140                           action='append', default=[],
141                           help='''add the argument to SNIPPET; may be given
142                           multiple times. Forces CMake to run again if given.
143                           Do not use this option with manually specified
144                           -DSNIPPET... cmake arguments: the results are
145                           undefined''')
146        group.add_argument('--shield', dest='shields', metavar='SHIELD',
147                           action='append', default=[],
148                           help='''add the argument to SHIELD; may be given
149                           multiple times. Forces CMake to run again if given.
150                           Do not use this option with manually specified
151                           -DSHIELD... cmake arguments: the results are
152                           undefined''')
153
154        group = parser.add_mutually_exclusive_group()
155        group.add_argument('--sysbuild', action='store_true',
156                           help='''create multi domain build system''')
157        group.add_argument('--no-sysbuild', action='store_true',
158                           help='''do not create multi domain build system
159                                   (default)''')
160
161        group = parser.add_argument_group('pristine builds',
162                                          PRISTINE_DESCRIPTION)
163        group.add_argument('-p', '--pristine', choices=['auto', 'always',
164                            'never'], action=AlwaysIfMissing, nargs='?',
165                            help='pristine build folder setting')
166
167        return parser
168
169    def do_run(self, args, remainder):
170        self.args = args        # Avoid having to pass them around
171        self.config_board = config_get('board', None)
172        log.dbg('args: {} remainder: {}'.format(args, remainder),
173                level=log.VERBOSE_EXTREME)
174        # Store legacy -s option locally
175        source_dir = self.args.source_dir
176        self._parse_remainder(remainder)
177        # Parse testcase.yaml or sample.yaml files for additional options.
178        if self.args.test_item:
179            # we get path + testitem
180            item = os.path.basename(self.args.test_item)
181            if self.args.source_dir:
182                test_path = self.args.source_dir
183            else:
184                test_path = os.path.dirname(self.args.test_item)
185            if test_path and os.path.exists(test_path):
186                self.args.source_dir = test_path
187                if not self._parse_test_item(item):
188                    log.die("No test metadata found")
189            else:
190                log.die("test item path does not exist")
191
192        if source_dir:
193            if self.args.source_dir:
194                log.die("source directory specified twice:({} and {})".format(
195                                            source_dir, self.args.source_dir))
196            self.args.source_dir = source_dir
197        log.dbg('source_dir: {} cmake_opts: {}'.format(self.args.source_dir,
198                                                       self.args.cmake_opts),
199                level=log.VERBOSE_EXTREME)
200        self._sanity_precheck()
201        self._setup_build_dir()
202
203        if args.pristine is not None:
204            pristine = args.pristine
205        else:
206            # Load the pristine={auto, always, never} configuration value
207            pristine = config_get('pristine', 'never')
208            if pristine not in ['auto', 'always', 'never']:
209                log.wrn(
210                    'treating unknown build.pristine value "{}" as "never"'.
211                    format(pristine))
212                pristine = 'never'
213        self.auto_pristine = pristine == 'auto'
214
215        log.dbg('pristine: {} auto_pristine: {}'.format(pristine,
216                                                        self.auto_pristine),
217                level=log.VERBOSE_VERY)
218        if is_zephyr_build(self.build_dir):
219            if pristine == 'always':
220                self._run_pristine()
221                self.run_cmake = True
222            else:
223                self._update_cache()
224                if (self.args.cmake or self.args.cmake_opts or
225                        self.args.cmake_only or self.args.snippets or
226                        self.args.shields):
227                    self.run_cmake = True
228        else:
229            self.run_cmake = True
230        self.source_dir = self._find_source_dir()
231        self._sanity_check()
232
233        board, origin = self._find_board()
234        self._run_cmake(board, origin, self.args.cmake_opts)
235        if args.cmake_only:
236            return
237
238        self._sanity_check()
239        self._update_cache()
240        self.domains = load_domains(self.build_dir)
241
242        self._run_build(args.target, args.domain)
243
244    def _find_board(self):
245        board, origin = None, None
246        if self.cmake_cache:
247            board, origin = (self.cmake_cache.get('CACHED_BOARD'),
248                             'CMakeCache.txt')
249
250            # A malformed CMake cache may exist, but not have a board.
251            # This happens if there's a build error from a previous run.
252            if board is not None:
253                return (board, origin)
254
255        if self.args.board:
256            board, origin = self.args.board, 'command line'
257        elif 'BOARD' in os.environ:
258            board, origin = os.environ['BOARD'], 'env'
259        elif self.config_board is not None:
260            board, origin = self.config_board, 'configfile'
261        return board, origin
262
263    def _parse_remainder(self, remainder):
264        self.args.source_dir = None
265        self.args.cmake_opts = None
266
267        try:
268            # Only one source_dir is allowed, as the first positional arg
269            if remainder[0] != _ARG_SEPARATOR:
270                self.args.source_dir = remainder[0]
271                remainder = remainder[1:]
272            # Only the first argument separator is consumed, the rest are
273            # passed on to CMake
274            if remainder[0] == _ARG_SEPARATOR:
275                remainder = remainder[1:]
276            if remainder:
277                self.args.cmake_opts = remainder
278        except IndexError:
279            pass
280
281    def _parse_test_item(self, test_item):
282        found_test_metadata = False
283        for yp in ['sample.yaml', 'testcase.yaml']:
284            yf = os.path.join(self.args.source_dir, yp)
285            if not os.path.exists(yf):
286                continue
287            found_test_metadata = True
288            with open(yf, 'r') as stream:
289                try:
290                    y = yaml.safe_load(stream)
291                except yaml.YAMLError as exc:
292                    log.die(exc)
293            common = y.get('common')
294            tests = y.get('tests')
295            if not tests:
296                log.die(f"No tests found in {yf}")
297            if test_item not in tests:
298                log.die(f"Test item {test_item} not found in {yf}")
299            item = tests.get(test_item)
300
301            sysbuild = False
302            extra_dtc_overlay_files = []
303            extra_overlay_confs = []
304            extra_conf_files = []
305            required_snippets = []
306            for section in [common, item]:
307                if not section:
308                    continue
309                sysbuild = section.get('sysbuild', sysbuild)
310                for data in [
311                        'extra_args',
312                        'extra_configs',
313                        'extra_conf_files',
314                        'extra_overlay_confs',
315                        'extra_dtc_overlay_files',
316                        'required_snippets'
317                        ]:
318                    extra = section.get(data)
319                    if not extra:
320                        continue
321                    if isinstance(extra, str):
322                        arg_list = extra.split(" ")
323                    else:
324                        arg_list = extra
325
326                    if data == 'extra_configs':
327                        args = ["-D{}".format(arg.replace('"', '\"')) for arg in arg_list]
328                    elif data == 'extra_args':
329                        # Retain quotes around config options
330                        config_options = [arg for arg in arg_list if arg.startswith("CONFIG_")]
331                        non_config_options = [arg for arg in arg_list if not arg.startswith("CONFIG_")]
332                        args = ["-D{}".format(a.replace('"', '\"')) for a in config_options]
333                        args.extend(["-D{}".format(arg.replace('"', '')) for arg in non_config_options])
334                    elif data == 'extra_conf_files':
335                        extra_conf_files.extend(arg_list)
336                        continue
337                    elif data == 'extra_overlay_confs':
338                        extra_overlay_confs.extend(arg_list)
339                        continue
340                    elif data == 'extra_dtc_overlay_files':
341                        extra_dtc_overlay_files.extend(arg_list)
342                        continue
343                    elif data == 'required_snippets':
344                        required_snippets.extend(arg_list)
345                        continue
346
347                    if self.args.cmake_opts:
348                        self.args.cmake_opts.extend(args)
349                    else:
350                        self.args.cmake_opts = args
351
352            self.args.sysbuild = sysbuild
353
354        if found_test_metadata:
355            args = []
356            if extra_conf_files:
357                args.append(f"CONF_FILE=\"{';'.join(extra_conf_files)}\"")
358
359            if extra_dtc_overlay_files:
360                args.append(f"DTC_OVERLAY_FILE=\"{';'.join(extra_dtc_overlay_files)}\"")
361
362            if extra_overlay_confs:
363                args.append(f"OVERLAY_CONFIG=\"{';'.join(extra_overlay_confs)}\"")
364
365            if required_snippets:
366                args.append(f"SNIPPET=\"{';'.join(required_snippets)}\"")
367
368            # Build the final argument list
369            args_expanded = ["-D{}".format(a.replace('"', '')) for a in args]
370
371            if self.args.cmake_opts:
372                self.args.cmake_opts.extend(args_expanded)
373            else:
374                self.args.cmake_opts = args_expanded
375
376        return found_test_metadata
377
378    def _sanity_precheck(self):
379        app = self.args.source_dir
380        if app:
381            self.check_force(
382                os.path.isdir(app),
383                'source directory {} does not exist'.format(app))
384            self.check_force(
385                'CMakeLists.txt' in os.listdir(app),
386                "{} doesn't contain a CMakeLists.txt".format(app))
387
388    def _update_cache(self):
389        try:
390            self.cmake_cache = CMakeCache.from_build_dir(self.build_dir)
391        except FileNotFoundError:
392            pass
393
394    def _setup_build_dir(self):
395        # Initialize build_dir and created_build_dir attributes.
396        # If we created the build directory, we must run CMake.
397        log.dbg('setting up build directory', level=log.VERBOSE_EXTREME)
398        # The CMake Cache has not been loaded yet, so this is safe
399        board, _ = self._find_board()
400        source_dir = self._find_source_dir()
401        app = os.path.split(source_dir)[1]
402        build_dir = find_build_dir(self.args.build_dir, board=board,
403                                   source_dir=source_dir, app=app)
404        if not build_dir:
405            log.die('Unable to determine a default build folder. Check '
406                    'your build.dir-fmt configuration option')
407
408        if os.path.exists(build_dir):
409            if not os.path.isdir(build_dir):
410                log.die('build directory {} exists and is not a directory'.
411                        format(build_dir))
412        else:
413            os.makedirs(build_dir, exist_ok=False)
414            self.created_build_dir = True
415            self.run_cmake = True
416
417        self.build_dir = build_dir
418
419    def _find_source_dir(self):
420        # Initialize source_dir attribute, either from command line argument,
421        # implicitly from the build directory's CMake cache, or using the
422        # default (current working directory).
423        log.dbg('setting up source directory', level=log.VERBOSE_EXTREME)
424        if self.args.source_dir:
425            source_dir = self.args.source_dir
426        elif self.cmake_cache:
427            source_dir = self.cmake_cache.get('APP_DIR')
428
429            if not source_dir:
430                source_dir = self.cmake_cache.get('APPLICATION_SOURCE_DIR')
431
432            if not source_dir:
433                source_dir = self.cmake_cache.get('CMAKE_HOME_DIRECTORY')
434
435            if not source_dir:
436                # This really ought to be there. The build directory
437                # must be corrupted somehow. Let's see what we can do.
438                log.die('build directory', self.build_dir,
439                        'CMake cache has no CMAKE_HOME_DIRECTORY;',
440                        'please give a source_dir')
441        else:
442            source_dir = os.getcwd()
443        return os.path.abspath(source_dir)
444
445    def _sanity_check_source_dir(self):
446        if self.source_dir == self.build_dir:
447            # There's no forcing this.
448            log.die('source and build directory {} cannot be the same; '
449                    'use --build-dir {} to specify a build directory'.
450                    format(self.source_dir, self.build_dir))
451
452        srcrel = os.path.relpath(self.source_dir)
453        self.check_force(
454            not is_zephyr_build(self.source_dir),
455            'it looks like {srcrel} is a build directory: '
456            'did you mean --build-dir {srcrel} instead?'.
457            format(srcrel=srcrel))
458        self.check_force(
459            'CMakeLists.txt' in os.listdir(self.source_dir),
460            'source directory "{srcrel}" does not contain '
461            'a CMakeLists.txt; is this really what you '
462            'want to build? (Use -s SOURCE_DIR to specify '
463            'the application source directory)'.
464            format(srcrel=srcrel))
465
466    def _sanity_check(self):
467        # Sanity check the build configuration.
468        # Side effect: may update cmake_cache attribute.
469        log.dbg('sanity checking the build', level=log.VERBOSE_EXTREME)
470        self._sanity_check_source_dir()
471
472        if not self.cmake_cache:
473            return          # That's all we can check without a cache.
474
475        if "CMAKE_PROJECT_NAME" not in self.cmake_cache:
476            # This happens sometimes when a build system is not
477            # completely generated due to an error during the
478            # CMake configuration phase.
479            self.run_cmake = True
480
481        cached_proj = self.cmake_cache.get('APPLICATION_SOURCE_DIR')
482        cached_app = self.cmake_cache.get('APP_DIR')
483        # if APP_DIR is None but APPLICATION_SOURCE_DIR is set, that indicates
484        # an older build folder, this still requires pristine.
485        if cached_app is None and cached_proj:
486            cached_app = cached_proj
487
488        log.dbg('APP_DIR:', cached_app, level=log.VERBOSE_EXTREME)
489        source_abs = (os.path.abspath(self.args.source_dir)
490                      if self.args.source_dir else None)
491        cached_abs = os.path.abspath(cached_app) if cached_app else None
492
493        log.dbg('pristine:', self.auto_pristine, level=log.VERBOSE_EXTREME)
494
495        # If the build directory specifies a source app, make sure it's
496        # consistent with --source-dir.
497        apps_mismatched = (source_abs and cached_abs and
498            pathlib.Path(source_abs).resolve() != pathlib.Path(cached_abs).resolve())
499
500        self.check_force(
501            not apps_mismatched or self.auto_pristine,
502            'Build directory "{}" is for application "{}", but source '
503            'directory "{}" was specified; please clean it, use --pristine, '
504            'or use --build-dir to set another build directory'.
505            format(self.build_dir, cached_abs, source_abs))
506
507        if apps_mismatched:
508            self.run_cmake = True  # If they insist, we need to re-run cmake.
509
510        # If CACHED_BOARD is not defined, we need some other way to
511        # find the board.
512        cached_board = self.cmake_cache.get('CACHED_BOARD')
513        log.dbg('CACHED_BOARD:', cached_board, level=log.VERBOSE_EXTREME)
514        # If apps_mismatched and self.auto_pristine are true, we will
515        # run pristine on the build, invalidating the cached
516        # board. In that case, we need some way of getting the board.
517        self.check_force((cached_board and
518                          not (apps_mismatched and self.auto_pristine))
519                         or self.args.board or self.config_board or
520                         os.environ.get('BOARD'),
521                         'Cached board not defined, please provide it '
522                         '(provide --board, set default with '
523                         '"west config build.board <BOARD>", or set '
524                         'BOARD in the environment)')
525
526        # Check consistency between cached board and --board.
527        boards_mismatched = (self.args.board and cached_board and
528                             self.args.board != cached_board)
529        self.check_force(
530            not boards_mismatched or self.auto_pristine,
531            'Build directory {} targets board {}, but board {} was specified. '
532            '(Clean the directory, use --pristine, or use --build-dir to '
533            'specify a different one.)'.
534            format(self.build_dir, cached_board, self.args.board))
535
536        if self.auto_pristine and (apps_mismatched or boards_mismatched):
537            self._run_pristine()
538            self.cmake_cache = None
539            log.dbg('run_cmake:', True, level=log.VERBOSE_EXTREME)
540            self.run_cmake = True
541
542            # Tricky corner-case: The user has not specified a build folder but
543            # there was one in the CMake cache. Since this is going to be
544            # invalidated, reset to CWD and re-run the basic tests.
545            if ((boards_mismatched and not apps_mismatched) and
546                    (not source_abs and cached_abs)):
547                self.source_dir = self._find_source_dir()
548                self._sanity_check_source_dir()
549
550    def _run_cmake(self, board, origin, cmake_opts):
551        if board is None and config_getboolean('board_warn', True):
552            log.wrn('This looks like a fresh build and BOARD is unknown;',
553                    "so it probably won't work. To fix, use",
554                    '--board=<your-board>.')
555            log.inf('Note: to silence the above message, run',
556                    "'west config build.board_warn false'")
557
558        if not self.run_cmake:
559            return
560
561        _banner('generating a build system')
562
563        if board is not None and origin != 'CMakeCache.txt':
564            cmake_opts = ['-DBOARD={}'.format(board)]
565        else:
566            cmake_opts = []
567        if self.args.cmake_opts:
568            cmake_opts.extend(self.args.cmake_opts)
569        if self.args.snippets:
570            cmake_opts.append(f'-DSNIPPET={";".join(self.args.snippets)}')
571        if self.args.shields:
572            cmake_opts.append(f'-DSHIELD={";".join(self.args.shields)}')
573
574        user_args = config_get('cmake-args', None)
575        if user_args:
576            cmake_opts.extend(shlex.split(user_args))
577
578        config_sysbuild = config_getboolean('sysbuild', False)
579        if self.args.sysbuild or (config_sysbuild and not self.args.no_sysbuild):
580            cmake_opts.extend(['-S{}'.format(SYSBUILD_PROJ_DIR),
581                               '-DAPP_DIR:PATH={}'.format(self.source_dir)])
582        else:
583            # self.args.no_sysbuild == True or config sysbuild False
584            cmake_opts.extend(['-S{}'.format(self.source_dir)])
585
586        # Invoke CMake from the current working directory using the
587        # -S and -B options (officially introduced in CMake 3.13.0).
588        # This is important because users expect invocations like this
589        # to Just Work:
590        #
591        # west build -- -DOVERLAY_CONFIG=relative-path.conf
592        final_cmake_args = ['-DWEST_PYTHON={}'.format(pathlib.Path(sys.executable).as_posix()),
593                            '-B{}'.format(self.build_dir),
594                            '-G{}'.format(config_get('generator',
595                                                     DEFAULT_CMAKE_GENERATOR))]
596        if cmake_opts:
597            final_cmake_args.extend(cmake_opts)
598        run_cmake(final_cmake_args, dry_run=self.args.dry_run)
599
600    def _run_pristine(self):
601        _banner('making build dir {} pristine'.format(self.build_dir))
602        if not is_zephyr_build(self.build_dir):
603            log.die('Refusing to run pristine on a folder that is not a '
604                    'Zephyr build system')
605
606        cache = CMakeCache.from_build_dir(self.build_dir)
607
608        app_src_dir = cache.get('APPLICATION_SOURCE_DIR')
609        app_bin_dir = cache.get('APPLICATION_BINARY_DIR')
610
611        cmake_args = [f'-DBINARY_DIR={app_bin_dir}',
612                      f'-DSOURCE_DIR={app_src_dir}',
613                      '-P', cache['ZEPHYR_BASE'] + '/cmake/pristine.cmake']
614        run_cmake(cmake_args, cwd=self.build_dir, dry_run=self.args.dry_run)
615
616    def _run_build(self, target, domain):
617        if target:
618            _banner('running target {}'.format(target))
619        elif self.run_cmake:
620            _banner('building application')
621        extra_args = ['--target', target] if target else []
622        if self.args.build_opt:
623            extra_args.append('--')
624            extra_args.extend(self.args.build_opt)
625        if self.args.verbose:
626            self._append_verbose_args(extra_args,
627                                      not bool(self.args.build_opt))
628
629        domains = load_domains(self.build_dir)
630        build_dir_list = []
631
632        if domain is None:
633            # If no domain is specified, we just build top build dir as that
634            # will build all domains.
635            build_dir_list = [domains.get_top_build_dir()]
636        else:
637            _banner('building domain(s): {}'.format(' '.join(domain)))
638            domain_list = domains.get_domains(domain)
639            for d in domain_list:
640                build_dir_list.append(d.build_dir)
641
642        for b in build_dir_list:
643            run_build(b, extra_args=extra_args,
644                      dry_run=self.args.dry_run)
645
646    def _append_verbose_args(self, extra_args, add_dashes):
647        # These hacks are only needed for CMake versions earlier than
648        # 3.14. When Zephyr's minimum version is at least that, we can
649        # drop this nonsense and just run "cmake --build BUILD -v".
650        self._update_cache()
651        if not self.cmake_cache:
652            return
653        generator = self.cmake_cache.get('CMAKE_GENERATOR')
654        if not generator:
655            return
656        # Substring matching is for things like "Eclipse CDT4 - Ninja".
657        if 'Ninja' in generator:
658            if add_dashes:
659                extra_args.append('--')
660            extra_args.append('-v')
661        elif generator == 'Unix Makefiles':
662            if add_dashes:
663                extra_args.append('--')
664            extra_args.append('VERBOSE=1')
665