Skip to content

tools/mpremote: Add mpremote mip install to install packages. #9467

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Oct 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/reference/mpremote.rst
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,14 @@ The full list of supported commands are:
variable ``$EDITOR``). If the editor exits successfully, the updated file will
be copied back to the device.

- install packages from :term:`micropython-lib` (or GitHub) using the ``mip`` tool:

.. code-block:: bash

$ mpremote mip install <packages...>

See :ref:`packages` for more information.

- mount the local directory on the remote device:

.. code-block:: bash
Expand Down Expand Up @@ -269,3 +277,9 @@ Examples
mpremote cp -r dir/ :

mpremote cp a.py b.py : + repl

mpremote mip install aioble

mpremote mip install github:org/repo@branch

mpremote mip install --target /flash/third-party functools
16 changes: 8 additions & 8 deletions docs/reference/packages.rst
Original file line number Diff line number Diff line change
Expand Up @@ -78,17 +78,17 @@ The :term:`mpremote` tool also includes the same functionality as ``mip`` and
can be used from a host PC to install packages to a locally connected device
(e.g. via USB or UART)::

$ mpremote install pkgname
$ mpremote install pkgname@x.y
$ mpremote install http://example.com/x/y/foo.py
$ mpremote install github:org/repo
$ mpremote install github:org/repo@branch-or-tag
$ mpremote mip install pkgname
$ mpremote mip install pkgname@x.y
$ mpremote mip install http://example.com/x/y/foo.py
$ mpremote mip install github:org/repo
$ mpremote mip install github:org/repo@branch-or-tag

The ``--target=path``, ``--no-mpy``, and ``--index`` arguments can be set::

$ mpremote install --target=/flash/third-party pkgname
$ mpremote install --no-mpy pkgname
$ mpremote install --index https://host/pi pkgname
$ mpremote mip install --target=/flash/third-party pkgname
$ mpremote mip install --no-mpy pkgname
$ mpremote mip install --index https://host/pi pkgname

Installing packages manually
----------------------------
Expand Down
41 changes: 24 additions & 17 deletions tools/mpremote/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,28 @@ This will automatically connect to the device and provide an interactive REPL.

The full list of supported commands are:

mpremote connect <device> -- connect to given device
device may be: list, auto, id:x, port:x
or any valid device name/path
mpremote disconnect -- disconnect current device
mpremote mount <local-dir> -- mount local directory on device
mpremote eval <string> -- evaluate and print the string
mpremote exec <string> -- execute the string
mpremote run <file> -- run the given local script
mpremote fs <command> <args...> -- execute filesystem commands on the device
command may be: cat, ls, cp, rm, mkdir, rmdir
use ":" as a prefix to specify a file on the device
mpremote repl -- enter REPL
options:
--capture <file>
--inject-code <string>
--inject-file <file>
mpremote help -- print list of commands and exit
mpremote connect <device> -- connect to given device
device may be: list, auto, id:x, port:x
or any valid device name/path
mpremote disconnect -- disconnect current device
mpremote mount <local-dir> -- mount local directory on device
mpremote eval <string> -- evaluate and print the string
mpremote exec <string> -- execute the string
mpremote run <file> -- run the given local script
mpremote fs <command> <args...> -- execute filesystem commands on the device
command may be: cat, ls, cp, rm, mkdir, rmdir
use ":" as a prefix to specify a file on the device
mpremote repl -- enter REPL
options:
--capture <file>
--inject-code <string>
--inject-file <file>
mpremote mip install <package...> -- Install packages (from micropython-lib or third-party sources)
options:
--target <path>
--index <url>
--no-mpy
mpremote help -- print list of commands and exit

Multiple commands can be specified and they will be run sequentially. Connection
and disconnection will be done automatically at the start and end of the execution
Expand Down Expand Up @@ -73,3 +78,5 @@ Examples:
mpremote cp :main.py .
mpremote cp main.py :
mpremote cp -r dir/ :
mpremote mip install aioble
mpremote mip install github:org/repo@branch
237 changes: 237 additions & 0 deletions tools/mpremote/mpremote/commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
import os
import sys
import tempfile

import serial.tools.list_ports

from . import pyboardextended as pyboard


class CommandError(Exception):
pass


def do_connect(state, args=None):
dev = args.device[0] if args else "auto"
do_disconnect(state)

try:
if dev == "list":
# List attached devices.
for p in sorted(serial.tools.list_ports.comports()):
print(
"{} {} {:04x}:{:04x} {} {}".format(
p.device,
p.serial_number,
p.vid if isinstance(p.vid, int) else 0,
p.pid if isinstance(p.pid, int) else 0,
p.manufacturer,
p.product,
)
)
# Don't do implicit REPL command.
state.did_action()
elif dev == "auto":
# Auto-detect and auto-connect to the first available device.
for p in sorted(serial.tools.list_ports.comports()):
try:
state.pyb = pyboard.PyboardExtended(p.device, baudrate=115200)
return
except pyboard.PyboardError as er:
if not er.args[0].startswith("failed to access"):
raise er
raise pyboard.PyboardError("no device found")
elif dev.startswith("id:"):
# Search for a device with the given serial number.
serial_number = dev[len("id:") :]
dev = None
for p in serial.tools.list_ports.comports():
if p.serial_number == serial_number:
state.pyb = pyboard.PyboardExtended(p.device, baudrate=115200)
return
raise pyboard.PyboardError("no device with serial number {}".format(serial_number))
else:
# Connect to the given device.
if dev.startswith("port:"):
dev = dev[len("port:") :]
state.pyb = pyboard.PyboardExtended(dev, baudrate=115200)
return
except pyboard.PyboardError as er:
msg = er.args[0]
if msg.startswith("failed to access"):
msg += " (it may be in use by another program)"
print(msg)
sys.exit(1)


def do_disconnect(state, _args=None):
if not state.pyb:
return

try:
if state.pyb.mounted:
if not state.pyb.in_raw_repl:
state.pyb.enter_raw_repl(soft_reset=False)
state.pyb.umount_local()
if state.pyb.in_raw_repl:
state.pyb.exit_raw_repl()
except OSError:
# Ignore any OSError exceptions when shutting down, eg:
# - pyboard.filesystem_command will close the connecton if it had an error
# - umounting will fail if serial port disappeared
pass
state.pyb.close()
state.pyb = None
state._auto_soft_reset = True


def show_progress_bar(size, total_size, op="copying"):
if not sys.stdout.isatty():
return
verbose_size = 2048
bar_length = 20
if total_size < verbose_size:
return
elif size >= total_size:
# Clear progress bar when copy completes
print("\r" + " " * (13 + len(op) + bar_length) + "\r", end="")
else:
bar = size * bar_length // total_size
progress = size * 100 // total_size
print(
"\r ... {} {:3d}% [{}{}]".format(op, progress, "#" * bar, "-" * (bar_length - bar)),
end="",
)


def do_filesystem(state, args):
state.ensure_raw_repl()
state.did_action()

def _list_recursive(files, path):
if os.path.isdir(path):
for entry in os.listdir(path):
_list_recursive(files, "/".join((path, entry)))
else:
files.append(os.path.split(path))

command = args.command[0]
paths = args.path

if command == "cat":
# Don't be verbose by default when using cat, so output can be
# redirected to something.
verbose = args.verbose == True
else:
verbose = args.verbose != False

if command == "cp" and args.recursive:
if paths[-1] != ":":
raise CommandError("'cp -r' destination must be ':'")
paths.pop()
src_files = []
for path in paths:
if path.startswith(":"):
raise CommandError("'cp -r' source files must be local")
_list_recursive(src_files, path)
known_dirs = {""}
state.pyb.exec_("import uos")
for dir, file in src_files:
dir_parts = dir.split("/")
for i in range(len(dir_parts)):
d = "/".join(dir_parts[: i + 1])
if d not in known_dirs:
state.pyb.exec_("try:\n uos.mkdir('%s')\nexcept OSError as e:\n print(e)" % d)
known_dirs.add(d)
pyboard.filesystem_command(
state.pyb,
["cp", "/".join((dir, file)), ":" + dir + "/"],
progress_callback=show_progress_bar,
verbose=verbose,
)
else:
if args.recursive:
raise CommandError("'-r' only supported for 'cp'")
try:
pyboard.filesystem_command(
state.pyb, [command] + paths, progress_callback=show_progress_bar, verbose=verbose
)
except OSError as er:
raise CommandError(er)


def do_edit(state, args):
state.ensure_raw_repl()
state.did_action()

if not os.getenv("EDITOR"):
raise pyboard.PyboardError("edit: $EDITOR not set")
for src in args.files:
src = src.lstrip(":")
dest_fd, dest = tempfile.mkstemp(suffix=os.path.basename(src))
try:
print("edit :%s" % (src,))
os.close(dest_fd)
state.pyb.fs_touch(src)
state.pyb.fs_get(src, dest, progress_callback=show_progress_bar)
if os.system("$EDITOR '%s'" % (dest,)) == 0:
state.pyb.fs_put(dest, src, progress_callback=show_progress_bar)
finally:
os.unlink(dest)


def _do_execbuffer(state, buf, follow):
state.ensure_raw_repl()
state.did_action()

try:
state.pyb.exec_raw_no_follow(buf)
if follow:
ret, ret_err = state.pyb.follow(timeout=None, data_consumer=pyboard.stdout_write_bytes)
if ret_err:
pyboard.stdout_write_bytes(ret_err)
sys.exit(1)
except pyboard.PyboardError as er:
print(er)
sys.exit(1)
except KeyboardInterrupt:
sys.exit(1)


def do_exec(state, args):
_do_execbuffer(state, args.expr[0], args.follow)


def do_eval(state, args):
buf = "print(" + args.expr[0] + ")"
_do_execbuffer(state, buf, args.follow)


def do_run(state, args):
filename = args.path[0]
try:
with open(filename, "rb") as f:
buf = f.read()
except OSError:
raise CommandError(f"could not read file '{filename}'")
_do_execbuffer(state, buf, args.follow)


def do_mount(state, args):
state.ensure_raw_repl()
path = args.path[0]
state.pyb.mount_local(path, unsafe_links=args.unsafe_links)
print(f"Local directory {path} is mounted at /remote")


def do_umount(state, path):
state.ensure_raw_repl()
state.pyb.umount_local()


def do_resume(state, _args=None):
state._auto_soft_reset = False


def do_soft_reset(state, _args=None):
state.ensure_raw_repl(soft_reset=True)
Loading