| # Copyright (c) 2018 Foundries.io |
| # |
| # SPDX-License-Identifier: Apache-2.0 |
| |
| import argparse |
| import os |
| |
| from west import log |
| from west import cmake |
| from west.build import DEFAULT_CMAKE_GENERATOR, is_zephyr_build |
| |
| from zephyr_ext_common import find_build_dir, Forceable, BUILD_DIR_DESCRIPTION |
| |
| |
| BUILD_DESCRIPTION = '''\ |
| Convenience wrapper for building Zephyr applications. |
| |
| This command attempts to do what you mean when run from a Zephyr |
| application source or a pre-existing build directory: |
| |
| - When the build directory ('./build' by default, see below) exists and is |
| already a Zephyr build directory, the source directory is obtained from the |
| CMake cache, and that build directory is re-compiled. |
| |
| - Otherwise, the source directory defaults to the current working |
| directory, so running "west build" from a Zephyr application's |
| source directory compiles it. |
| |
| The source and build directories can be explicitly set with the |
| --source-dir and --build-dir options. The build directory defaults to |
| 'build' if it is not auto-detected. The build directory is always |
| created if it does not exist. |
| |
| This command runs CMake to generate a build system if one is not |
| present in the build directory, then builds the application. |
| Subsequent builds try to avoid re-running CMake; you can force it |
| to run by setting --cmake. |
| |
| To pass additional options to CMake, give them as extra arguments |
| after a '--'. For example, this sets an overlay config file: |
| |
| west build [...] -- -DOVERLAY_CONFIG=some.conf |
| |
| (Doing this forces a CMake run.)''' |
| |
| |
| class Build(Forceable): |
| |
| def __init__(self): |
| super(Build, self).__init__( |
| 'build', |
| # Keep this in sync with the string in west-commands.yml. |
| 'compile a Zephyr application', |
| BUILD_DESCRIPTION, |
| accepts_unknown_args=False) |
| |
| self.source_dir = None |
| '''Source directory for the build, or None on error.''' |
| |
| self.build_dir = None |
| '''Final build directory used to run the build, or None on error.''' |
| |
| self.created_build_dir = False |
| '''True if the build directory was created; False otherwise.''' |
| |
| self.run_cmake = False |
| '''True if CMake was run; False otherwise. |
| |
| Note: this only describes CMake runs done by this command. The |
| build system generated by CMake may also update itself due to |
| internal logic.''' |
| |
| self.cmake_cache = None |
| '''Final parsed CMake cache for the build, or None on error.''' |
| |
| def do_add_parser(self, parser_adder): |
| parser = parser_adder.add_parser( |
| self.name, |
| help=self.help, |
| formatter_class=argparse.RawDescriptionHelpFormatter, |
| description=self.description) |
| |
| # Remember to update scripts/west-completion.bash if you add or remove |
| # flags |
| |
| parser.add_argument('-b', '--board', |
| help='''Board to build for (must be given for the |
| first build, can be omitted later)''') |
| parser.add_argument('-s', '--source-dir', |
| help='''Explicitly set the source directory. |
| If not given and rebuilding an existing Zephyr |
| build directory, this is taken from the CMake |
| cache. Otherwise, the current directory is |
| assumed.''') |
| parser.add_argument('-d', '--build-dir', |
| help=BUILD_DIR_DESCRIPTION + |
| "The directory is created if it doesn't exist.") |
| parser.add_argument('-t', '--target', |
| help='''Override the build system target (e.g. |
| 'clean', 'pristine', etc.)''') |
| parser.add_argument('-c', '--cmake', action='store_true', |
| help='Force CMake to run') |
| self.add_force_arg(parser) |
| parser.add_argument('cmake_opts', nargs='*', metavar='cmake_opt', |
| help='Extra option to pass to CMake; implies -c') |
| |
| return parser |
| |
| def do_run(self, args, ignored): |
| self.args = args # Avoid having to pass them around |
| log.dbg('args:', args, level=log.VERBOSE_EXTREME) |
| self._sanity_precheck() |
| self._setup_build_dir() |
| if is_zephyr_build(self.build_dir): |
| self._update_cache() |
| if self.args.cmake or self.args.cmake_opts: |
| self.run_cmake = True |
| else: |
| self.run_cmake = True |
| self._setup_source_dir() |
| self._sanity_check() |
| |
| log.inf('source directory: {}'.format(self.source_dir), colorize=True) |
| log.inf('build directory: {}{}'. |
| format(self.build_dir, |
| (' (created)' if self.created_build_dir |
| else '')), |
| colorize=True) |
| if self.cmake_cache: |
| board = self.cmake_cache.get('CACHED_BOARD') |
| elif self.args.board: |
| board = self.args.board |
| else: |
| board = 'UNKNOWN' # shouldn't happen |
| log.inf('BOARD:', board, colorize=True) |
| |
| self._run_cmake(self.args.cmake_opts) |
| self._sanity_check() |
| self._update_cache() |
| |
| extra_args = ['--target', args.target] if args.target else [] |
| cmake.run_build(self.build_dir, extra_args=extra_args) |
| |
| def _sanity_precheck(self): |
| app = self.args.source_dir |
| if app: |
| self.check_force( |
| os.path.isdir(app), |
| 'source directory {} does not exist'.format(app)) |
| self.check_force( |
| 'CMakeLists.txt' in os.listdir(app), |
| "{} doesn't contain a CMakeLists.txt".format(app)) |
| |
| def _update_cache(self): |
| try: |
| self.cmake_cache = cmake.CMakeCache.from_build_dir(self.build_dir) |
| except FileNotFoundError: |
| pass |
| |
| def _setup_build_dir(self): |
| # Initialize build_dir and created_build_dir attributes. |
| # If we created the build directory, we must run CMake. |
| log.dbg('setting up build directory', level=log.VERBOSE_EXTREME) |
| build_dir = find_build_dir(self.args.build_dir) |
| |
| if os.path.exists(build_dir): |
| if not os.path.isdir(build_dir): |
| log.die('build directory {} exists and is not a directory'. |
| format(build_dir)) |
| else: |
| os.makedirs(build_dir, exist_ok=False) |
| self.created_build_dir = True |
| self.run_cmake = True |
| |
| self.build_dir = build_dir |
| |
| def _setup_source_dir(self): |
| # Initialize source_dir attribute, either from command line argument, |
| # implicitly from the build directory's CMake cache, or using the |
| # default (current working directory). |
| log.dbg('setting up source directory', level=log.VERBOSE_EXTREME) |
| if self.args.source_dir: |
| source_dir = self.args.source_dir |
| elif self.cmake_cache: |
| source_dir = self.cmake_cache.get('APPLICATION_SOURCE_DIR') |
| if not source_dir: |
| # Maybe Zephyr changed the key? Give the user a way |
| # to retry, at least. |
| log.die("can't determine application from build directory " |
| "{}, please specify an application to build". |
| format(self.build_dir)) |
| else: |
| source_dir = os.getcwd() |
| self.source_dir = os.path.abspath(source_dir) |
| |
| def _sanity_check(self): |
| # Sanity check the build configuration. |
| # Side effect: may update cmake_cache attribute. |
| log.dbg('sanity checking the build', level=log.VERBOSE_EXTREME) |
| if self.source_dir == self.build_dir: |
| # There's no forcing this. |
| log.die('source and build directory {} cannot be the same; ' |
| 'use --build-dir {} to specify a build directory'. |
| format(self.source_dir, self.build_dir)) |
| |
| srcrel = os.path.relpath(self.source_dir) |
| self.check_force( |
| not is_zephyr_build(self.source_dir), |
| 'it looks like {srcrel} is a build directory: ' |
| 'did you mean --build-dir {srcrel} instead?'. |
| format(srcrel=srcrel)) |
| self.check_force( |
| 'CMakeLists.txt' in os.listdir(self.source_dir), |
| 'source directory "{srcrel}" does not contain ' |
| 'a CMakeLists.txt; is this really what you ' |
| 'want to build? (Use -s SOURCE_DIR to specify ' |
| 'the application source directory)'. |
| format(srcrel=srcrel)) |
| self.check_force( |
| is_zephyr_build(self.build_dir) or self.args.board, |
| 'this looks like a new or clean build, please provide --board') |
| |
| if not self.cmake_cache: |
| return # That's all we can check without a cache. |
| |
| cached_app = self.cmake_cache.get('APPLICATION_SOURCE_DIR') |
| log.dbg('APPLICATION_SOURCE_DIR:', cached_app, |
| level=log.VERBOSE_EXTREME) |
| source_abs = (os.path.abspath(self.args.source_dir) |
| if self.args.source_dir else None) |
| cached_abs = os.path.abspath(cached_app) if cached_app else None |
| |
| # If the build directory specifies a source app, make sure it's |
| # consistent with --source-dir. |
| apps_mismatched = (source_abs and cached_abs and |
| source_abs != cached_abs) |
| self.check_force( |
| not apps_mismatched, |
| 'Build directory "{}" is for application "{}", but source ' |
| 'directory "{}" was specified; please clean it or use --build-dir ' |
| 'to set another build directory'. |
| format(self.build_dir, cached_abs, source_abs)) |
| if apps_mismatched: |
| self.run_cmake = True # If they insist, we need to re-run cmake. |
| |
| # If CACHED_BOARD is not defined, we need --board from the |
| # command line. |
| cached_board = self.cmake_cache.get('CACHED_BOARD') |
| log.dbg('CACHED_BOARD:', cached_board, level=log.VERBOSE_EXTREME) |
| self.check_force(cached_board or self.args.board, |
| 'Cached board not defined, please provide --board') |
| |
| # Check consistency between cached board and --board. |
| boards_mismatched = (self.args.board and cached_board and |
| self.args.board != cached_board) |
| self.check_force( |
| not boards_mismatched, |
| 'Build directory {} targets board {}, but board {} was specified. ' |
| '(Clean the directory or use --build-dir to specify a different ' |
| 'one.)'. |
| format(self.build_dir, cached_board, self.args.board)) |
| |
| def _run_cmake(self, cmake_opts): |
| if not self.run_cmake: |
| log.dbg('not running cmake; build system is present') |
| return |
| |
| # It's unfortunate to have to use the undocumented -B and -H |
| # options to set the source and binary directories. |
| # |
| # However, it's the only known way to set that directory and |
| # run CMake from the current working directory. This is |
| # important because users expect invocations like this to Just |
| # Work: |
| # |
| # west build -- -DOVERLAY_CONFIG=relative-path.conf |
| final_cmake_args = ['-B{}'.format(self.build_dir), |
| '-H{}'.format(self.source_dir), |
| '-G{}'.format(DEFAULT_CMAKE_GENERATOR)] |
| if self.args.board: |
| final_cmake_args.append('-DBOARD={}'.format(self.args.board)) |
| if cmake_opts: |
| final_cmake_args.extend(cmake_opts) |
| cmake.run_cmake(final_cmake_args) |