Clarify what "being in a build directory" exactly means. Signed-off-by: Carles Cufi <carles.cufi@nordicsemi.no>
285 lines
12 KiB
Python
285 lines
12 KiB
Python
# 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)
|