mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
idf.py: Improve status output, error message output
This commit is contained in:
parent
abef220b13
commit
4d7bc8e8ba
74
tools/idf.py
74
tools/idf.py
@ -31,6 +31,12 @@ import re
|
|||||||
import shutil
|
import shutil
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
class FatalError(RuntimeError):
|
||||||
|
"""
|
||||||
|
Wrapper class for runtime errors that aren't caused by bugs in idf.py or the build proces.s
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
# Use this Python interpreter for any subprocesses we launch
|
# Use this Python interpreter for any subprocesses we launch
|
||||||
PYTHON=sys.executable
|
PYTHON=sys.executable
|
||||||
|
|
||||||
@ -56,6 +62,22 @@ GENERATORS = [
|
|||||||
]
|
]
|
||||||
GENERATOR_CMDS = dict( (a[0], a[1]) for a in GENERATORS )
|
GENERATOR_CMDS = dict( (a[0], a[1]) for a in GENERATORS )
|
||||||
|
|
||||||
|
def _run_tool(tool_name, args, cwd):
|
||||||
|
def quote_arg(arg):
|
||||||
|
" Quote 'arg' if necessary "
|
||||||
|
if " " in arg and not (arg.startswith('"') or arg.startswith("'")):
|
||||||
|
return "'" + arg + "'"
|
||||||
|
return arg
|
||||||
|
display_args = " ".join(quote_arg(arg) for arg in args)
|
||||||
|
print("Running %s in directory %s" % (tool_name, quote_arg(cwd)))
|
||||||
|
print('Executing "%s"...' % display_args)
|
||||||
|
try:
|
||||||
|
# Note: we explicitly pass in os.environ here, as we may have set IDF_PATH there during startup
|
||||||
|
subprocess.check_call(args, env=os.environ, cwd=cwd)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
raise FatalError("%s failed with exit code %d" % (tool_name, e.returncode))
|
||||||
|
|
||||||
|
|
||||||
def check_environment():
|
def check_environment():
|
||||||
"""
|
"""
|
||||||
Verify the environment contains the top-level tools we need to operate
|
Verify the environment contains the top-level tools we need to operate
|
||||||
@ -63,7 +85,7 @@ def check_environment():
|
|||||||
(cmake will check a lot of other things)
|
(cmake will check a lot of other things)
|
||||||
"""
|
"""
|
||||||
if not executable_exists(["cmake", "--version"]):
|
if not executable_exists(["cmake", "--version"]):
|
||||||
raise RuntimeError("'cmake' must be available on the PATH to use idf.py")
|
raise FatalError("'cmake' must be available on the PATH to use idf.py")
|
||||||
# find the directory idf.py is in, then the parent directory of this, and assume this is IDF_PATH
|
# find the directory idf.py is in, then the parent directory of this, and assume this is IDF_PATH
|
||||||
detected_idf_path = os.path.realpath(os.path.join(os.path.dirname(__file__), ".."))
|
detected_idf_path = os.path.realpath(os.path.join(os.path.dirname(__file__), ".."))
|
||||||
if "IDF_PATH" in os.environ:
|
if "IDF_PATH" in os.environ:
|
||||||
@ -88,7 +110,7 @@ def detect_cmake_generator():
|
|||||||
for (generator, _, version_check) in GENERATORS:
|
for (generator, _, version_check) in GENERATORS:
|
||||||
if executable_exists(version_check):
|
if executable_exists(version_check):
|
||||||
return generator
|
return generator
|
||||||
raise RuntimeError("To use idf.py, either the 'ninja' or 'GNU make' build tool must be available in the PATH")
|
raise FatalError("To use idf.py, either the 'ninja' or 'GNU make' build tool must be available in the PATH")
|
||||||
|
|
||||||
def _ensure_build_directory(args):
|
def _ensure_build_directory(args):
|
||||||
"""Check the build directory exists and that cmake has been run there.
|
"""Check the build directory exists and that cmake has been run there.
|
||||||
@ -104,11 +126,11 @@ def _ensure_build_directory(args):
|
|||||||
# Verify the project directory
|
# Verify the project directory
|
||||||
if not os.path.isdir(project_dir):
|
if not os.path.isdir(project_dir):
|
||||||
if not os.path.exists(project_dir):
|
if not os.path.exists(project_dir):
|
||||||
raise RuntimeError("Project directory %s does not exist")
|
raise FatalError("Project directory %s does not exist")
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("%s must be a project directory")
|
raise FatalError("%s must be a project directory")
|
||||||
if not os.path.exists(os.path.join(project_dir, "CMakeLists.txt")):
|
if not os.path.exists(os.path.join(project_dir, "CMakeLists.txt")):
|
||||||
raise RuntimeError("CMakeLists.txt not found in project directory %s" % project_dir)
|
raise FatalError("CMakeLists.txt not found in project directory %s" % project_dir)
|
||||||
|
|
||||||
# Verify/create the build directory
|
# Verify/create the build directory
|
||||||
build_dir = args.build_dir
|
build_dir = args.build_dir
|
||||||
@ -118,12 +140,12 @@ def _ensure_build_directory(args):
|
|||||||
if not os.path.exists(cache_path):
|
if not os.path.exists(cache_path):
|
||||||
if args.generator is None:
|
if args.generator is None:
|
||||||
args.generator = detect_cmake_generator()
|
args.generator = detect_cmake_generator()
|
||||||
print("Running cmake...")
|
|
||||||
# Note: we explicitly pass in os.environ here, as we may have set IDF_PATH there during startup
|
|
||||||
try:
|
try:
|
||||||
subprocess.check_call(["cmake", "-G", args.generator, project_dir], env=os.environ, cwd=args.build_dir)
|
_run_tool("cmake", ["cmake", "-G", args.generator, project_dir], cwd=args.build_dir)
|
||||||
except:
|
except:
|
||||||
if os.path.exists(cache_path): # don't allow partial CMakeCache.txt files, to keep the "should I run cmake?" logic simple
|
# don't allow partially valid CMakeCache.txt files,
|
||||||
|
# to keep the "should I run cmake?" logic simple
|
||||||
|
if os.path.exists(cache_path):
|
||||||
os.remove(cache_path)
|
os.remove(cache_path)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@ -136,13 +158,13 @@ def _ensure_build_directory(args):
|
|||||||
if args.generator is None:
|
if args.generator is None:
|
||||||
args.generator = generator # reuse the previously configured generator, if none was given
|
args.generator = generator # reuse the previously configured generator, if none was given
|
||||||
if generator != args.generator:
|
if generator != args.generator:
|
||||||
raise RuntimeError("Build is configured for generator '%s' not '%s'. Run 'idf.py fullclean' to start again."
|
raise FatalError("Build is configured for generator '%s' not '%s'. Run 'idf.py fullclean' to start again."
|
||||||
% (generator, args.generator))
|
% (generator, args.generator))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
home_dir = cache["CMAKE_HOME_DIRECTORY"]
|
home_dir = cache["CMAKE_HOME_DIRECTORY"]
|
||||||
if os.path.realpath(home_dir) != os.path.realpath(project_dir):
|
if os.path.realpath(home_dir) != os.path.realpath(project_dir):
|
||||||
raise RuntimeError("Build directory '%s' configured for project '%s' not '%s'. Run 'idf.py fullclean' to start again."
|
raise FatalError("Build directory '%s' configured for project '%s' not '%s'. Run 'idf.py fullclean' to start again."
|
||||||
% (build_dir, os.path.realpath(home_dir), os.path.realpath(project_dir)))
|
% (build_dir, os.path.realpath(home_dir), os.path.realpath(project_dir)))
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass # if cmake failed part way, CMAKE_HOME_DIRECTORY may not be set yet
|
pass # if cmake failed part way, CMAKE_HOME_DIRECTORY may not be set yet
|
||||||
@ -175,8 +197,8 @@ def build_target(target_name, args):
|
|||||||
"""
|
"""
|
||||||
_ensure_build_directory(args)
|
_ensure_build_directory(args)
|
||||||
generator_cmd = GENERATOR_CMDS[args.generator]
|
generator_cmd = GENERATOR_CMDS[args.generator]
|
||||||
print("Running '%s %s' in %s..." % (generator_cmd, target_name, args.build_dir))
|
_run_tool(generator_cmd[0], generator_cmd + [target_name], args.build_dir)
|
||||||
subprocess.check_call(generator_cmd + [target_name], cwd=args.build_dir)
|
|
||||||
|
|
||||||
def _get_esptool_args(args):
|
def _get_esptool_args(args):
|
||||||
esptool_path = os.path.join(os.environ["IDF_PATH"], "components/esptool_py/esptool/esptool.py")
|
esptool_path = os.path.join(os.environ["IDF_PATH"], "components/esptool_py/esptool/esptool.py")
|
||||||
@ -190,7 +212,7 @@ def flash(action, args):
|
|||||||
"""
|
"""
|
||||||
Run esptool to flash the entire project, from an argfile generated by the build system
|
Run esptool to flash the entire project, from an argfile generated by the build system
|
||||||
"""
|
"""
|
||||||
flasher_args_path = {
|
flasher_args_path = { # action -> name of flasher args file generated by build system
|
||||||
"bootloader-flash": "flash_bootloader_args",
|
"bootloader-flash": "flash_bootloader_args",
|
||||||
"partition_table-flash": "flash_partition_table_args",
|
"partition_table-flash": "flash_partition_table_args",
|
||||||
"app-flash": "flash_app_args",
|
"app-flash": "flash_app_args",
|
||||||
@ -198,12 +220,14 @@ def flash(action, args):
|
|||||||
}[action]
|
}[action]
|
||||||
esptool_args = _get_esptool_args(args)
|
esptool_args = _get_esptool_args(args)
|
||||||
esptool_args += [ "write_flash", "@"+flasher_args_path ]
|
esptool_args += [ "write_flash", "@"+flasher_args_path ]
|
||||||
subprocess.check_call(esptool_args, cwd=args.build_dir)
|
_run_tool("esptool.py", esptool_args, args.build_dir)
|
||||||
|
|
||||||
|
|
||||||
def erase_flash(action, args):
|
def erase_flash(action, args):
|
||||||
esptool_args = _get_esptool_args(args)
|
esptool_args = _get_esptool_args(args)
|
||||||
esptool_args += [ "erase_flash" ]
|
esptool_args += [ "erase_flash" ]
|
||||||
subprocess.check_call(esptool_args, cwd=args.build_dir)
|
_run_tool("esptool.py", esptool_args, args.build_dir)
|
||||||
|
|
||||||
|
|
||||||
def monitor(action, args):
|
def monitor(action, args):
|
||||||
"""
|
"""
|
||||||
@ -217,14 +241,15 @@ def monitor(action, args):
|
|||||||
|
|
||||||
elf_file = os.path.join(args.build_dir, project_desc["app_elf"])
|
elf_file = os.path.join(args.build_dir, project_desc["app_elf"])
|
||||||
if not os.path.exists(elf_file):
|
if not os.path.exists(elf_file):
|
||||||
raise RuntimeError("ELF file '%s' not found. You need to build & flash the project before running 'monitor', and the binary on the device must match the one in the build directory exactly. Try 'idf.py flash monitor'." % elf_file)
|
raise FatalError("ELF file '%s' not found. You need to build & flash the project before running 'monitor', and the binary on the device must match the one in the build directory exactly. Try 'idf.py flash monitor'." % elf_file)
|
||||||
idf_monitor = os.path.join(os.environ["IDF_PATH"], "tools/idf_monitor.py")
|
idf_monitor = os.path.join(os.environ["IDF_PATH"], "tools/idf_monitor.py")
|
||||||
monitor_args = [PYTHON, idf_monitor ]
|
monitor_args = [PYTHON, idf_monitor ]
|
||||||
if args.port is not None:
|
if args.port is not None:
|
||||||
monitor_args += [ "-p", args.port ]
|
monitor_args += [ "-p", args.port ]
|
||||||
monitor_args += [ "-b", project_desc["monitor_baud"] ]
|
monitor_args += [ "-b", project_desc["monitor_baud"] ]
|
||||||
monitor_args += [ elf_file ]
|
monitor_args += [ elf_file ]
|
||||||
subprocess.check_call(monitor_args, cwd=args.build_dir)
|
_run_tool("idf_monitor", monitor_args, args.build_dir)
|
||||||
|
|
||||||
|
|
||||||
def clean(action, args):
|
def clean(action, args):
|
||||||
if not os.path.isdir(args.build_dir):
|
if not os.path.isdir(args.build_dir):
|
||||||
@ -242,12 +267,12 @@ def fullclean(action, args):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if not os.path.exists(os.path.join(build_dir, "CMakeCache.txt")):
|
if not os.path.exists(os.path.join(build_dir, "CMakeCache.txt")):
|
||||||
raise RuntimeError("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)
|
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" ]
|
red_flags = [ "CMakeLists.txt", ".git", ".svn" ]
|
||||||
for red in red_flags:
|
for red in red_flags:
|
||||||
red = os.path.join(build_dir, red)
|
red = os.path.join(build_dir, red)
|
||||||
if os.path.exists(red):
|
if os.path.exists(red):
|
||||||
raise RuntimeError("Refusing to automatically delete files in directory containing '%s'. Delete files manually if you're sure." % 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...
|
# OK, delete everything in the build directory...
|
||||||
for f in os.listdir(build_dir): # TODO: once we are Python 3 only, this can be os.scandir()
|
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)
|
f = os.path.join(build_dir, f)
|
||||||
@ -295,7 +320,7 @@ def main():
|
|||||||
|
|
||||||
# Advanced parameter checks
|
# Advanced parameter checks
|
||||||
if args.build_dir is not None and os.path.realpath(args.project_dir) == os.path.realpath(args.build_dir):
|
if args.build_dir is not None and os.path.realpath(args.project_dir) == os.path.realpath(args.build_dir):
|
||||||
raise RuntimeError("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.")
|
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:
|
if args.build_dir is None:
|
||||||
args.build_dir = os.path.join(args.project_dir, "build")
|
args.build_dir = os.path.join(args.project_dir, "build")
|
||||||
args.build_dir = os.path.realpath(args.build_dir)
|
args.build_dir = os.path.realpath(args.build_dir)
|
||||||
@ -327,5 +352,10 @@ def main():
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
try:
|
||||||
|
main()
|
||||||
|
except FatalError as e:
|
||||||
|
print(e)
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user