mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
aecb2bd8ee
Can still be enabled by passing --cmake-warn-uninitialized on the command line Prevents CMake warnings printed by default if IDF_PATH is underneath the CMake project directory. The reason for this is that CMake --warn-uninitialized only enables checks inside the project directory (ie top-level CMakeLists.txt directory and subdirectories), it doesn't enable for files included from other directories. (The only way to enable warnings in other directories is to pass --check-system-dirs and this looks like it's only useful for CMake's own developers as it prints a lot of warnings from inside CMake otherwise - see https://gitlab.kitware.com/cmake/cmake/-/issues/19645 ) Plan to follow up with a later commit to clean up most of the warnings (which aren't problems for CMake execution), but we'll also disable this option by default to avoid this unexpected triggering of IDF warnings.
445 lines
18 KiB
Python
445 lines
18 KiB
Python
import fnmatch
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
|
|
import click
|
|
|
|
from idf_py_actions.constants import GENERATORS, SUPPORTED_TARGETS
|
|
from idf_py_actions.errors import FatalError
|
|
from idf_py_actions.global_options import global_options
|
|
from idf_py_actions.tools import ensure_build_directory, idf_version, merge_action_lists, realpath, run_tool
|
|
|
|
|
|
def action_extensions(base_actions, project_path):
|
|
def run_target(target_name, args):
|
|
generator_cmd = GENERATORS[args.generator]["command"]
|
|
|
|
if args.verbose:
|
|
generator_cmd += [GENERATORS[args.generator]["verbose_flag"]]
|
|
|
|
run_tool(generator_cmd[0], generator_cmd + [target_name], args.build_dir)
|
|
|
|
def build_target(target_name, ctx, args):
|
|
"""
|
|
Execute the target build system to build target 'target_name'
|
|
|
|
Calls ensure_build_directory() which will run cmake to generate a build
|
|
directory (with the specified generator) as needed.
|
|
"""
|
|
ensure_build_directory(args, ctx.info_name)
|
|
run_target(target_name, args)
|
|
|
|
def menuconfig(target_name, ctx, args, style):
|
|
"""
|
|
Menuconfig target is build_target extended with the style argument for setting the value for the environment
|
|
variable.
|
|
"""
|
|
if sys.version_info[0] < 3:
|
|
# The subprocess lib cannot accept environment variables as "unicode".
|
|
# This encoding step is required only in Python 2.
|
|
style = style.encode(sys.getfilesystemencoding() or 'utf-8')
|
|
os.environ['MENUCONFIG_STYLE'] = style
|
|
build_target(target_name, ctx, args)
|
|
|
|
def fallback_target(target_name, ctx, args):
|
|
"""
|
|
Execute targets that are not explicitly known to idf.py
|
|
"""
|
|
ensure_build_directory(args, ctx.info_name)
|
|
|
|
try:
|
|
subprocess.check_output(GENERATORS[args.generator]["dry_run"] + [target_name], cwd=args.build_dir)
|
|
|
|
except Exception:
|
|
raise FatalError(
|
|
'command "%s" is not known to idf.py and is not a %s target' % (target_name, args.generator))
|
|
|
|
run_target(target_name, args)
|
|
|
|
def verbose_callback(ctx, param, value):
|
|
if not value or ctx.resilient_parsing:
|
|
return
|
|
|
|
for line in ctx.command.verbose_output:
|
|
print(line)
|
|
|
|
return value
|
|
|
|
def clean(action, ctx, args):
|
|
if not os.path.isdir(args.build_dir):
|
|
print("Build directory '%s' not found. Nothing to clean." % args.build_dir)
|
|
return
|
|
build_target("clean", ctx, args)
|
|
|
|
def _delete_windows_symlinks(directory):
|
|
"""
|
|
It deletes symlinks recursively on Windows. It is useful for Python 2 which doesn't detect symlinks on Windows.
|
|
"""
|
|
deleted_paths = []
|
|
if os.name == "nt":
|
|
import ctypes
|
|
|
|
for root, dirnames, _filenames in os.walk(directory):
|
|
for d in dirnames:
|
|
full_path = os.path.join(root, d)
|
|
try:
|
|
full_path = full_path.decode("utf-8")
|
|
except Exception:
|
|
pass
|
|
if ctypes.windll.kernel32.GetFileAttributesW(full_path) & 0x0400:
|
|
os.rmdir(full_path)
|
|
deleted_paths.append(full_path)
|
|
return deleted_paths
|
|
|
|
def fullclean(action, ctx, args):
|
|
build_dir = args.build_dir
|
|
if not os.path.isdir(build_dir):
|
|
print("Build directory '%s' not found. Nothing to clean." % build_dir)
|
|
return
|
|
if len(os.listdir(build_dir)) == 0:
|
|
print("Build directory '%s' is empty. Nothing to clean." % build_dir)
|
|
return
|
|
|
|
if not os.path.exists(os.path.join(build_dir, "CMakeCache.txt")):
|
|
raise FatalError(
|
|
"Directory '%s' doesn't seem to be a CMake build directory. Refusing to automatically "
|
|
"delete files in this directory. Delete the directory manually to 'clean' it." % build_dir)
|
|
red_flags = ["CMakeLists.txt", ".git", ".svn"]
|
|
for red in red_flags:
|
|
red = os.path.join(build_dir, red)
|
|
if os.path.exists(red):
|
|
raise FatalError(
|
|
"Refusing to automatically delete files in directory containing '%s'. Delete files manually if you're sure."
|
|
% red)
|
|
# OK, delete everything in the build directory...
|
|
# Note: Python 2.7 doesn't detect symlinks on Windows (it is supported form 3.2). Tools promising to not
|
|
# follow symlinks will actually follow them. Deleting the build directory with symlinks deletes also items
|
|
# outside of this directory.
|
|
deleted_symlinks = _delete_windows_symlinks(build_dir)
|
|
if args.verbose and len(deleted_symlinks) > 1:
|
|
print("The following symlinks were identified and removed:\n%s" % "\n".join(deleted_symlinks))
|
|
for f in os.listdir(build_dir): # TODO: once we are Python 3 only, this can be os.scandir()
|
|
f = os.path.join(build_dir, f)
|
|
if args.verbose:
|
|
print("Removing: %s" % f)
|
|
if os.path.isdir(f):
|
|
shutil.rmtree(f)
|
|
else:
|
|
os.remove(f)
|
|
|
|
def python_clean(action, ctx, args):
|
|
for root, dirnames, filenames in os.walk(os.environ["IDF_PATH"]):
|
|
for d in dirnames:
|
|
if d == "__pycache__":
|
|
dir_to_delete = os.path.join(root, d)
|
|
if args.verbose:
|
|
print("Removing: %s" % dir_to_delete)
|
|
shutil.rmtree(dir_to_delete)
|
|
for filename in fnmatch.filter(filenames, '*.py[co]'):
|
|
file_to_delete = os.path.join(root, filename)
|
|
if args.verbose:
|
|
print("Removing: %s" % file_to_delete)
|
|
os.remove(file_to_delete)
|
|
|
|
def set_target(action, ctx, args, idf_target):
|
|
args.define_cache_entry.append("IDF_TARGET=" + idf_target)
|
|
sdkconfig_path = os.path.join(args.project_dir, 'sdkconfig')
|
|
sdkconfig_old = sdkconfig_path + ".old"
|
|
if os.path.exists(sdkconfig_old):
|
|
os.remove(sdkconfig_old)
|
|
if os.path.exists(sdkconfig_path):
|
|
os.rename(sdkconfig_path, sdkconfig_old)
|
|
print("Set Target to: %s, new sdkconfig created. Existing sdkconfig renamed to sdkconfig.old." % idf_target)
|
|
ensure_build_directory(args, ctx.info_name, True)
|
|
|
|
def reconfigure(action, ctx, args):
|
|
ensure_build_directory(args, ctx.info_name, True)
|
|
|
|
def validate_root_options(ctx, args, tasks):
|
|
args.project_dir = realpath(args.project_dir)
|
|
if args.build_dir is not None and args.project_dir == realpath(args.build_dir):
|
|
raise FatalError(
|
|
"Setting the build directory to the project directory is not supported. Suggest dropping "
|
|
"--build-dir option, the default is a 'build' subdirectory inside the project directory.")
|
|
if args.build_dir is None:
|
|
args.build_dir = os.path.join(args.project_dir, "build")
|
|
args.build_dir = realpath(args.build_dir)
|
|
|
|
def idf_version_callback(ctx, param, value):
|
|
if not value or ctx.resilient_parsing:
|
|
return
|
|
|
|
version = idf_version()
|
|
|
|
if not version:
|
|
raise FatalError("ESP-IDF version cannot be determined")
|
|
|
|
print("ESP-IDF %s" % version)
|
|
sys.exit(0)
|
|
|
|
def list_targets_callback(ctx, param, value):
|
|
if not value or ctx.resilient_parsing:
|
|
return
|
|
|
|
for target in SUPPORTED_TARGETS:
|
|
print(target)
|
|
|
|
sys.exit(0)
|
|
|
|
root_options = {
|
|
"global_options": [
|
|
{
|
|
"names": ["--version"],
|
|
"help": "Show IDF version and exit.",
|
|
"is_flag": True,
|
|
"expose_value": False,
|
|
"callback": idf_version_callback
|
|
},
|
|
{
|
|
"names": ["--list-targets"],
|
|
"help": "Print list of supported targets and exit.",
|
|
"is_flag": True,
|
|
"expose_value": False,
|
|
"callback": list_targets_callback
|
|
},
|
|
{
|
|
"names": ["-C", "--project-dir"],
|
|
"help": "Project directory.",
|
|
"type": click.Path(),
|
|
"default": os.getcwd(),
|
|
},
|
|
{
|
|
"names": ["-B", "--build-dir"],
|
|
"help": "Build directory.",
|
|
"type": click.Path(),
|
|
"default": None,
|
|
},
|
|
{
|
|
"names": ["-w/-n", "--cmake-warn-uninitialized/--no-warnings"],
|
|
"help": ("Enable CMake uninitialized variable warnings for CMake files inside the project directory. "
|
|
"(--no-warnings is now the default, and doesn't need to be specified.)"),
|
|
"envvar": "IDF_CMAKE_WARN_UNINITIALIZED",
|
|
"is_flag": True,
|
|
"default": False,
|
|
},
|
|
{
|
|
"names": ["-v", "--verbose"],
|
|
"help": "Verbose build output.",
|
|
"is_flag": True,
|
|
"is_eager": True,
|
|
"default": False,
|
|
"callback": verbose_callback
|
|
},
|
|
{
|
|
"names": ["--ccache/--no-ccache"],
|
|
"help": (
|
|
"Use ccache in build. Disabled by default, unless "
|
|
"IDF_CCACHE_ENABLE environment variable is set to a non-zero value."),
|
|
"is_flag": True,
|
|
"default": os.getenv("IDF_CCACHE_ENABLE") not in [None, "", "0"],
|
|
},
|
|
{
|
|
"names": ["-G", "--generator"],
|
|
"help": "CMake generator.",
|
|
"type": click.Choice(GENERATORS.keys()),
|
|
},
|
|
{
|
|
"names": ["--dry-run"],
|
|
"help": "Only process arguments, but don't execute actions.",
|
|
"is_flag": True,
|
|
"hidden": True,
|
|
"default": False
|
|
},
|
|
],
|
|
"global_action_callbacks": [validate_root_options],
|
|
}
|
|
|
|
build_actions = {
|
|
"actions": {
|
|
"all": {
|
|
"aliases": ["build"],
|
|
"callback": build_target,
|
|
"short_help": "Build the project.",
|
|
"help": (
|
|
"Build the project. This can involve multiple steps:\n\n"
|
|
"1. Create the build directory if needed. "
|
|
"The sub-directory 'build' is used to hold build output, "
|
|
"although this can be changed with the -B option.\n\n"
|
|
"2. Run CMake as necessary to configure the project "
|
|
"and generate build files for the main build tool.\n\n"
|
|
"3. Run the main build tool (Ninja or GNU Make). "
|
|
"By default, the build tool is automatically detected "
|
|
"but it can be explicitly set by passing the -G option to idf.py.\n\n"),
|
|
"options": global_options,
|
|
"order_dependencies": [
|
|
"reconfigure",
|
|
"menuconfig",
|
|
"clean",
|
|
"fullclean",
|
|
],
|
|
},
|
|
"menuconfig": {
|
|
"callback": menuconfig,
|
|
"help": 'Run "menuconfig" project configuration tool.',
|
|
"options": global_options + [
|
|
{
|
|
"names": ["--style", "--color-scheme", "style"],
|
|
"help": (
|
|
"Menuconfig style.\n"
|
|
"Is it possible to customize the menuconfig style by either setting the MENUCONFIG_STYLE "
|
|
"environment variable or through this option. The built-in styles include:\n\n"
|
|
"- default - a yellowish theme,\n\n"
|
|
"- monochrome - a black and white theme, or\n"
|
|
"- aquatic - a blue theme.\n\n"
|
|
"The default value is \"aquatic\". It is possible to customize these themes further "
|
|
"as it is described in the Color schemes section of the kconfiglib documentation."),
|
|
"default": os.environ.get('MENUCONFIG_STYLE', 'aquatic'),
|
|
}
|
|
],
|
|
},
|
|
"confserver": {
|
|
"callback": build_target,
|
|
"help": "Run JSON configuration server.",
|
|
"options": global_options,
|
|
},
|
|
"size": {
|
|
"callback": build_target,
|
|
"help": "Print basic size information about the app.",
|
|
"options": global_options,
|
|
"dependencies": ["app"],
|
|
},
|
|
"size-components": {
|
|
"callback": build_target,
|
|
"help": "Print per-component size information.",
|
|
"options": global_options,
|
|
"dependencies": ["app"],
|
|
},
|
|
"size-files": {
|
|
"callback": build_target,
|
|
"help": "Print per-source-file size information.",
|
|
"options": global_options,
|
|
"dependencies": ["app"],
|
|
},
|
|
"bootloader": {
|
|
"callback": build_target,
|
|
"help": "Build only bootloader.",
|
|
"options": global_options,
|
|
},
|
|
"app": {
|
|
"callback": build_target,
|
|
"help": "Build only the app.",
|
|
"order_dependencies": ["clean", "fullclean", "reconfigure"],
|
|
"options": global_options,
|
|
},
|
|
"efuse_common_table": {
|
|
"callback": build_target,
|
|
"help": "Genereate C-source for IDF's eFuse fields.",
|
|
"order_dependencies": ["reconfigure"],
|
|
"options": global_options,
|
|
},
|
|
"efuse_custom_table": {
|
|
"callback": build_target,
|
|
"help": "Genereate C-source for user's eFuse fields.",
|
|
"order_dependencies": ["reconfigure"],
|
|
"options": global_options,
|
|
},
|
|
"show_efuse_table": {
|
|
"callback": build_target,
|
|
"help": "Print eFuse table.",
|
|
"order_dependencies": ["reconfigure"],
|
|
"options": global_options,
|
|
},
|
|
"partition_table": {
|
|
"callback": build_target,
|
|
"help": "Build only partition table.",
|
|
"order_dependencies": ["reconfigure"],
|
|
"options": global_options,
|
|
},
|
|
"erase_otadata": {
|
|
"callback": build_target,
|
|
"help": "Erase otadata partition.",
|
|
"options": global_options,
|
|
},
|
|
"read_otadata": {
|
|
"callback": build_target,
|
|
"help": "Read otadata partition.",
|
|
"options": global_options,
|
|
},
|
|
"fallback": {
|
|
"callback": fallback_target,
|
|
"help": "Handle for targets not known for idf.py.",
|
|
"hidden": True
|
|
}
|
|
}
|
|
}
|
|
|
|
clean_actions = {
|
|
"actions": {
|
|
"reconfigure": {
|
|
"callback": reconfigure,
|
|
"short_help": "Re-run CMake.",
|
|
"help": (
|
|
"Re-run CMake even if it doesn't seem to need re-running. "
|
|
"This isn't necessary during normal usage, "
|
|
"but can be useful after adding/removing files from the source tree, "
|
|
"or when modifying CMake cache variables. "
|
|
"For example, \"idf.py -DNAME='VALUE' reconfigure\" "
|
|
'can be used to set variable "NAME" in CMake cache to value "VALUE".'),
|
|
"options": global_options,
|
|
"order_dependencies": ["menuconfig", "fullclean"],
|
|
},
|
|
"set-target": {
|
|
"callback": set_target,
|
|
"short_help": "Set the chip target to build.",
|
|
"help": (
|
|
"Set the chip target to build. This will remove the "
|
|
"existing sdkconfig file and corresponding CMakeCache and "
|
|
"create new ones according to the new target.\nFor example, "
|
|
"\"idf.py set-target esp32\" will select esp32 as the new chip "
|
|
"target."),
|
|
"arguments": [
|
|
{
|
|
"names": ["idf-target"],
|
|
"nargs": 1,
|
|
"type": click.Choice(SUPPORTED_TARGETS),
|
|
},
|
|
],
|
|
"dependencies": ["fullclean"],
|
|
},
|
|
"clean": {
|
|
"callback": clean,
|
|
"short_help": "Delete build output files from the build directory.",
|
|
"help": (
|
|
"Delete build output files from the build directory, "
|
|
"forcing a 'full rebuild' the next time "
|
|
"the project is built. Cleaning doesn't delete "
|
|
"CMake configuration output and some other files"),
|
|
"order_dependencies": ["fullclean"],
|
|
},
|
|
"fullclean": {
|
|
"callback": fullclean,
|
|
"short_help": "Delete the entire build directory contents.",
|
|
"help": (
|
|
"Delete the entire build directory contents. "
|
|
"This includes all CMake configuration output."
|
|
"The next time the project is built, "
|
|
"CMake will configure it from scratch. "
|
|
"Note that this option recursively deletes all files "
|
|
"in the build directory, so use with care."
|
|
"Project configuration is not deleted.")
|
|
},
|
|
"python-clean": {
|
|
"callback": python_clean,
|
|
"short_help": "Delete generated Python byte code from the IDF directory",
|
|
"help": (
|
|
"Delete generated Python byte code from the IDF directory "
|
|
"which may cause issues when switching between IDF and Python versions. "
|
|
"It is advised to run this target after switching versions.")
|
|
},
|
|
}
|
|
}
|
|
|
|
return merge_action_lists(root_options, build_actions, clean_actions)
|