# 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)
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" ] ),
"fullclean": ( fullclean, [], [] ),
"reconfigure": ( reconfigure, [], [ "menuconfig" ] ),
"menuconfig": ( build_target, [], [] ),
+ "confserver": ( build_target, [], [] ),
"size": ( build_target, [ "app" ], [] ),
"size-components": ( build_target, [ "app" ], [] ),
"size-files": ( build_target, [ "app" ], [] ),
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:
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: