# See the License for the specific language governing permissions and
# limitations under the License.
#
+
+# Note: we don't check for Python build-time dependencies until
+# check_environment() function below. If possible, avoid importing
+# any external libraries here - put in external script, or import in
+# their specific function instead.
import sys
import argparse
import os
import re
import shutil
import json
+import serial.tools.list_ports
class FatalError(RuntimeError):
"""
PYTHON=sys.executable
# note: os.environ changes don't automatically propagate to child processes,
-# you have to pass this in explicitly
+# you have to pass env=os.environ explicitly anywhere that we create a process
os.environ["PYTHON"]=sys.executable
# Make flavors, across the various kinds of Windows environments & POSIX...
print("WARNING: IDF_PATH environment variable is set to %s but idf.py path indicates IDF directory %s. Using the environment variable directory, but results may be unexpected..."
% (set_idf_path, detected_idf_path))
else:
+ print("Setting IDF_PATH environment variable: %s" % detected_idf_path)
os.environ["IDF_PATH"] = detected_idf_path
+ # check Python dependencies
+ print("Checking Python dependencies...")
+ try:
+ subprocess.check_call([ os.environ["PYTHON"],
+ os.path.join(os.environ["IDF_PATH"], "tools", "check_python_dependencies.py")],
+ env=os.environ)
+ except subprocess.CalledProcessError:
+ raise SystemExit(1)
+
def executable_exists(args):
try:
subprocess.check_output(args)
if args.generator is None:
args.generator = detect_cmake_generator()
try:
- cmake_args = ["cmake", "-G", args.generator]
+ cmake_args = ["cmake", "-G", args.generator, "-DPYTHON_DEPS_CHECKED=1"]
if not args.no_warnings:
cmake_args += [ "--warn-uninitialized" ]
if args.no_ccache:
def _get_esptool_args(args):
esptool_path = os.path.join(os.environ["IDF_PATH"], "components/esptool_py/esptool/esptool.py")
+ if args.port is None:
+ args.port = get_default_serial_port()
result = [ PYTHON, esptool_path ]
- if args.port is not None:
- result += [ "-p", args.port ]
+ result += [ "-p", args.port ]
result += [ "-b", str(args.baud) ]
return result
"""
Run idf_monitor.py to watch build output
"""
+ if args.port is None:
+ args.port = get_default_serial_port()
desc_path = os.path.join(args.build_dir, "project_description.json")
if not os.path.exists(desc_path):
_ensure_build_directory(args)
monitor_args += [ "-p", args.port ]
monitor_args += [ "-b", project_desc["monitor_baud"] ]
monitor_args += [ elf_file ]
+
+ idf_py = [ PYTHON ] + get_commandline_options() # commands to re-run idf.py
+ monitor_args += [ "-m", " ".join("'%s'" % a for a in idf_py) ]
+
if "MSYSTEM" is os.environ:
monitor_args = [ "winpty" ] + monitor_args
- _run_tool("idf_monitor", monitor_args, args.build_dir)
+ _run_tool("idf_monitor", monitor_args, args.project_dir)
def clean(action, args):
else:
os.remove(f)
+def print_closing_message(args):
+ # print a closing message of some kind
+ #
+
+ if "flash" in str(args.actions):
+ print("Done")
+ return
+
+ # Otherwise, if we built any binaries print a message about
+ # how to flash them
+ def print_flashing_message(title, key):
+ print("\n%s build complete. To flash, run this command:" % title)
+
+ with open(os.path.join(args.build_dir, "flasher_args.json")) as f:
+ flasher_args = json.load(f)
+
+ def flasher_path(f):
+ return os.path.relpath(os.path.join(args.build_dir, f))
+
+ if key != "project": # flashing a single item
+ cmd = ""
+ if key == "bootloader": # bootloader needs --flash-mode, etc to be passed in
+ cmd = " ".join(flasher_args["write_flash_args"]) + " "
+
+ cmd += flasher_args[key]["offset"] + " "
+ cmd += flasher_path(flasher_args[key]["file"])
+ else: # flashing the whole project
+ cmd = " ".join(flasher_args["write_flash_args"]) + " "
+ flash_items = sorted(((o,f) for (o,f) in flasher_args["flash_files"].items() if len(o) > 0),
+ key = lambda x: int(x[0], 0))
+ for o,f in flash_items:
+ cmd += o + " " + flasher_path(f) + " "
+
+ print("%s -p %s -b %s write_flash %s" % (
+ os.path.relpath("%s/components/esptool_py/esptool/esptool.py" % os.environ["IDF_PATH"]),
+ args.port or "(PORT)",
+ args.baud,
+ cmd.strip()))
+ print("or run 'idf.py -p %s %s'" % (args.port or "(PORT)", key + "-flash" if key != "project" else "flash",))
+
+ if "all" in args.actions or "build" in args.actions:
+ print_flashing_message("Project", "project")
+ else:
+ if "app" in args.actions:
+ print_flashing_message("App", "app")
+ if "partition_table" in args.actions:
+ print_flashing_message("Partition Table", "partition_table")
+ if "bootloader" in args.actions:
+ print_flashing_message("Bootloader", "bootloader")
+
ACTIONS = {
# action name : ( function (or alias), dependencies, order-only dependencies )
"all" : ( build_target, [], [ "reconfigure", "menuconfig", "clean", "fullclean" ] ),
"build": ( "all", [], [] ), # build is same as 'all' target
"clean": ( clean, [], [ "fullclean" ] ),
"fullclean": ( fullclean, [], [] ),
- "reconfigure": ( reconfigure, [], [] ),
+ "reconfigure": ( reconfigure, [], [ "menuconfig" ] ),
"menuconfig": ( build_target, [], [] ),
- "size": ( build_target, [], [ "app" ] ),
- "size-components": ( build_target, [], [ "app" ] ),
- "size-files": ( build_target, [], [ "app" ] ),
+ "confserver": ( build_target, [], [] ),
+ "size": ( build_target, [ "app" ], [] ),
+ "size-components": ( build_target, [ "app" ], [] ),
+ "size-files": ( build_target, [ "app" ], [] ),
"bootloader": ( build_target, [], [] ),
"bootloader-clean": ( build_target, [], [] ),
- "bootloader-flash": ( flash, [ "bootloader" ], [] ),
+ "bootloader-flash": ( flash, [ "bootloader" ], [ "erase_flash"] ),
"app": ( build_target, [], [ "clean", "fullclean", "reconfigure" ] ),
- "app-flash": ( flash, [], [ "app" ]),
+ "app-flash": ( flash, [ "app" ], [ "erase_flash"]),
"partition_table": ( build_target, [], [ "reconfigure" ] ),
- "partition_table-flash": ( flash, [ "partition_table" ], []),
- "flash": ( flash, [ "all" ], [ ] ),
+ "partition_table-flash": ( flash, [ "partition_table" ], [ "erase_flash" ]),
+ "flash": ( flash, [ "all" ], [ "erase_flash" ] ),
"erase_flash": ( erase_flash, [], []),
"monitor": ( monitor, [], [ "flash", "partition_table-flash", "bootloader-flash", "app-flash" ]),
}
+def get_commandline_options():
+ """ Return all the command line options up to but not including the action """
+ result = []
+ for a in sys.argv:
+ if a in ACTIONS.keys():
+ break
+ else:
+ result.append(a)
+ return result
+
+def get_default_serial_port():
+ """ Return a default serial port. esptool can do this (smarter), but it can create
+ inconsistencies where esptool.py uses one port and idf_monitor uses another.
+
+ Same logic as esptool.py search order, reverse sort by name and choose the first port.
+ """
+ ports = list(reversed(sorted(
+ p.device for p in serial.tools.list_ports.comports() )))
+ try:
+ print ("Choosing default port %s (use '-p PORT' option to set a specific serial port)" % ports[0])
+ return ports[0]
+ except IndexError:
+ raise RuntimeError("No serial ports found. Connect a device, or use '-p PORT' option to set a specific port.")
+
+
def main():
+ if sys.version_info[0] != 2 or sys.version_info[1] != 7:
+ raise FatalError("ESP-IDF currently only supports Python 2.7, and this is Python %d.%d.%d. Search for 'Setting the Python Interpreter' in the ESP-IDF docs for some tips to handle this." % sys.version_info[:3])
+
parser = argparse.ArgumentParser(description='ESP-IDF build management tool')
- parser.add_argument('-p', '--port', help="Serial port", default=None)
- parser.add_argument('-b', '--baud', help="Baud rate", default=460800)
+ parser.add_argument('-p', '--port', help="Serial port",
+ default=os.environ.get('ESPPORT', None))
+ parser.add_argument('-b', '--baud', help="Baud rate",
+ default=os.environ.get('ESPBAUD', 460800))
parser.add_argument('-C', '--project-dir', help="Project directory", default=os.getcwd())
parser.add_argument('-B', '--build-dir', help="Build directory", default=None)
parser.add_argument('-G', '--generator', help="Cmake generator", choices=GENERATOR_CMDS.keys())
completed_actions.add(action)
- while len(args.actions) > 0:
- execute_action(args.actions[0], args.actions[1:])
- args.actions.pop(0)
+ actions = list(args.actions)
+ while len(actions) > 0:
+ execute_action(actions[0], actions[1:])
+ actions.pop(0)
+ print_closing_message(args)
if __name__ == "__main__":
try: