| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437 |
- 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_target, TargetChoice
- def action_extensions(base_actions, project_path):
- 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": "Generate C-source for IDF's eFuse fields.",
- "order_dependencies": ["reconfigure"],
- "options": global_options,
- },
- "efuse_custom_table": {
- "callback": build_target,
- "help": "Generate 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": TargetChoice(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)
|