summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--docs/zephyr/quickref.rst2
-rw-r--r--ports/zephyr/CMakeLists.txt1
-rw-r--r--ports/zephyr/Kconfig8
-rw-r--r--ports/zephyr/boards/beagleconnect_freedom.conf1
-rw-r--r--ports/zephyr/main.c8
-rw-r--r--ports/zephyr/src/usbd.c183
-rw-r--r--ports/zephyr/zephyr_storage.c19
-rw-r--r--py/malloc.c33
-rw-r--r--tests/extmod/tls_noleak.py (renamed from tests/extmod/ssl_noleak.py)0
-rw-r--r--tests/extmod/tls_threads.py57
-rw-r--r--tests/ports/rp2/rp2_dma.py39
-rwxr-xr-xtools/ci.sh2
-rw-r--r--tools/mpremote/mpremote/mip.py16
13 files changed, 346 insertions, 23 deletions
diff --git a/docs/zephyr/quickref.rst b/docs/zephyr/quickref.rst
index a7ae698607..0e985c70b3 100644
--- a/docs/zephyr/quickref.rst
+++ b/docs/zephyr/quickref.rst
@@ -153,6 +153,8 @@ Use the :ref:`zephyr.FlashArea <zephyr.FlashArea>` class to support filesystem::
f.write('Hello world') # write to the file
print(open('/flash/hello.txt').read()) # print contents of the file
+The FlashAreas' IDs that are available are listed in the FlashArea module, as ID_*.
+
Sensor
------
diff --git a/ports/zephyr/CMakeLists.txt b/ports/zephyr/CMakeLists.txt
index 4f457f4a58..debf2bd2c1 100644
--- a/ports/zephyr/CMakeLists.txt
+++ b/ports/zephyr/CMakeLists.txt
@@ -128,6 +128,7 @@ add_dependencies(${MICROPY_TARGET} zephyr_generated_headers)
target_sources(app PRIVATE
src/zephyr_start.c
src/zephyr_getchar.c
+ src/usbd.c
)
target_link_libraries(app PRIVATE ${MICROPY_TARGET})
diff --git a/ports/zephyr/Kconfig b/ports/zephyr/Kconfig
index f8d1543155..6db0133f4f 100644
--- a/ports/zephyr/Kconfig
+++ b/ports/zephyr/Kconfig
@@ -49,6 +49,14 @@ config MICROPY_FROZEN_MANIFEST
depends on MICROPY_FROZEN_MODULES
default "boards/manifest.py"
+config MICROPY_USB_DEVICE_VID
+ hex "USB VID"
+ default 0x2fe3
+
+config MICROPY_USB_DEVICE_PID
+ hex "USB PID"
+ default 0x0001
+
endmenu # MicroPython Options
source "Kconfig.zephyr"
diff --git a/ports/zephyr/boards/beagleconnect_freedom.conf b/ports/zephyr/boards/beagleconnect_freedom.conf
index 1e3f6037bd..8fe2583205 100644
--- a/ports/zephyr/boards/beagleconnect_freedom.conf
+++ b/ports/zephyr/boards/beagleconnect_freedom.conf
@@ -1,4 +1,5 @@
# Hardware features
+CONFIG_ADC=y
CONFIG_PWM=y
CONFIG_I2C=y
CONFIG_SPI=y
diff --git a/ports/zephyr/main.c b/ports/zephyr/main.c
index 206b7f92d3..d4498c1079 100644
--- a/ports/zephyr/main.c
+++ b/ports/zephyr/main.c
@@ -63,6 +63,10 @@
static char heap[MICROPY_HEAP_SIZE];
+#if defined(CONFIG_USB_DEVICE_STACK_NEXT)
+extern int mp_usbd_init(void);
+#endif // defined(CONFIG_USB_DEVICE_STACK_NEXT)
+
void init_zephyr(void) {
// We now rely on CONFIG_NET_APP_SETTINGS to set up bootstrap
// network addresses.
@@ -143,6 +147,10 @@ soft_reset:
usb_enable(NULL);
#endif
+ #ifdef CONFIG_USB_DEVICE_STACK_NEXT
+ mp_usbd_init();
+ #endif
+
#if MICROPY_VFS
vfs_init();
#endif
diff --git a/ports/zephyr/src/usbd.c b/ports/zephyr/src/usbd.c
new file mode 100644
index 0000000000..2444706cbe
--- /dev/null
+++ b/ports/zephyr/src/usbd.c
@@ -0,0 +1,183 @@
+/*
+* This file is part of the MicroPython project, http://micropython.org/
+*
+* The MIT License (MIT)
+*
+* Copyright (c) 2024-2025 MASSDRIVER EI (massdriver.space)
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in
+* all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+* THE SOFTWARE.
+*/
+#include <stdint.h>
+
+#include <zephyr/device.h>
+#include <zephyr/usb/usbd.h>
+#include <zephyr/usb/bos.h>
+
+#if defined(CONFIG_USB_DEVICE_STACK_NEXT)
+
+#include <zephyr/logging/log.h>
+LOG_MODULE_REGISTER(mp_usbd);
+
+/* By default, do not register the USB DFU class DFU mode instance. */
+static const char *const blocklist[] = {
+ "dfu_dfu",
+ NULL,
+};
+
+USBD_DEVICE_DEFINE(mp_usbd,
+ DEVICE_DT_GET(DT_NODELABEL(zephyr_udc0)),
+ CONFIG_MICROPY_USB_DEVICE_VID, CONFIG_MICROPY_USB_DEVICE_PID);
+
+USBD_DESC_LANG_DEFINE(mp_lang);
+USBD_DESC_MANUFACTURER_DEFINE(mp_mfr, "Zephyr Project");
+USBD_DESC_PRODUCT_DEFINE(mp_product, "Micropython on Zephyr RTOS");
+USBD_DESC_SERIAL_NUMBER_DEFINE(mp_sn);
+
+USBD_DESC_CONFIG_DEFINE(fs_cfg_desc, "FS Configuration");
+USBD_DESC_CONFIG_DEFINE(hs_cfg_desc, "HS Configuration");
+
+/* not self-powered, no remote wakeup */
+static const uint8_t attributes = 0;
+
+/* Full speed configuration
+* power = 250 * 2 mA = 500mA
+*/
+USBD_CONFIGURATION_DEFINE(mp_fs_config,
+ attributes,
+ 250, &fs_cfg_desc);
+
+/* High speed configuration */
+USBD_CONFIGURATION_DEFINE(mp_hs_config,
+ attributes,
+ 250, &hs_cfg_desc);
+
+static void mp_fix_code_triple(struct usbd_context *uds_ctx,
+ const enum usbd_speed speed) {
+ /* Always use class code information from Interface Descriptors */
+ if (IS_ENABLED(CONFIG_USBD_CDC_ACM_CLASS) ||
+ IS_ENABLED(CONFIG_USBD_CDC_ECM_CLASS) ||
+ IS_ENABLED(CONFIG_USBD_CDC_NCM_CLASS) ||
+ IS_ENABLED(CONFIG_USBD_AUDIO2_CLASS)) {
+ /*
+ * Class with multiple interfaces have an Interface
+ * Association Descriptor available, use an appropriate triple
+ * to indicate it.
+ */
+ usbd_device_set_code_triple(uds_ctx, speed,
+ USB_BCC_MISCELLANEOUS, 0x02, 0x01);
+ } else {
+ usbd_device_set_code_triple(uds_ctx, speed, 0, 0, 0);
+ }
+}
+
+struct usbd_context *mp_usbd_init_device(usbd_msg_cb_t msg_cb) {
+ int err;
+
+ err = usbd_add_descriptor(&mp_usbd, &mp_lang);
+ if (err) {
+ LOG_ERR("Failed to initialize language descriptor (%d)", err);
+ return NULL;
+ }
+
+ err = usbd_add_descriptor(&mp_usbd, &mp_mfr);
+ if (err) {
+ LOG_ERR("Failed to initialize manufacturer descriptor (%d)", err);
+ return NULL;
+ }
+
+ err = usbd_add_descriptor(&mp_usbd, &mp_product);
+ if (err) {
+ LOG_ERR("Failed to initialize product descriptor (%d)", err);
+ return NULL;
+ }
+
+ err = usbd_add_descriptor(&mp_usbd, &mp_sn);
+ if (err) {
+ LOG_ERR("Failed to initialize SN descriptor (%d)", err);
+ return NULL;
+ }
+
+ if (usbd_caps_speed(&mp_usbd) == USBD_SPEED_HS) {
+ err = usbd_add_configuration(&mp_usbd, USBD_SPEED_HS,
+ &mp_hs_config);
+ if (err) {
+ LOG_ERR("Failed to add High-Speed configuration");
+ return NULL;
+ }
+
+ err = usbd_register_all_classes(&mp_usbd, USBD_SPEED_HS, 1, blocklist);
+ if (err) {
+ LOG_ERR("Failed to add register classes");
+ return NULL;
+ }
+
+ mp_fix_code_triple(&mp_usbd, USBD_SPEED_HS);
+ }
+
+ err = usbd_add_configuration(&mp_usbd, USBD_SPEED_FS,
+ &mp_fs_config);
+ if (err) {
+ LOG_ERR("Failed to add Full-Speed configuration");
+ return NULL;
+ }
+
+ err = usbd_register_all_classes(&mp_usbd, USBD_SPEED_FS, 1, blocklist);
+ if (err) {
+ LOG_ERR("Failed to add register classes");
+ return NULL;
+ }
+
+ mp_fix_code_triple(&mp_usbd, USBD_SPEED_FS);
+
+ if (msg_cb != NULL) {
+ err = usbd_msg_register_cb(&mp_usbd, msg_cb);
+ if (err) {
+ LOG_ERR("Failed to register message callback");
+ return NULL;
+ }
+ }
+
+ err = usbd_init(&mp_usbd);
+ if (err) {
+ LOG_ERR("Failed to initialize device support");
+ return NULL;
+ }
+
+ return &mp_usbd;
+}
+
+static struct usbd_context *mp_usbd_context;
+
+int mp_usbd_init(void) {
+ int err;
+
+ mp_usbd_context = mp_usbd_init_device(NULL);
+ if (mp_usbd_context == NULL) {
+ return -ENODEV;
+ }
+
+ err = usbd_enable(mp_usbd_context);
+ if (err) {
+ return err;
+ }
+
+ return 0;
+}
+
+#endif // defined(CONFIG_USB_DEVICE_STACK_NEXT)
diff --git a/ports/zephyr/zephyr_storage.c b/ports/zephyr/zephyr_storage.c
index 484feb1130..40bcef7338 100644
--- a/ports/zephyr/zephyr_storage.c
+++ b/ports/zephyr/zephyr_storage.c
@@ -139,6 +139,20 @@ MP_DEFINE_CONST_OBJ_TYPE(
#endif // CONFIG_DISK_ACCESS
#ifdef CONFIG_FLASH_MAP
+
+#define FLASH_AREA_DEFINE_LABEL(part) CONCAT(MP_QSTR_ID_, DT_STRING_TOKEN(part, label))
+#define FLASH_AREA_DEFINE_NB(part) CONCAT(MP_QSTR_ID_, DT_FIXED_PARTITION_ID(part))
+
+#define FLASH_AREA_DEFINE_GETNAME(part) COND_CODE_1(DT_NODE_HAS_PROP(part, label), \
+ (FLASH_AREA_DEFINE_LABEL(part)), (FLASH_AREA_DEFINE_NB(part)))
+
+#define FLASH_AREA_DEFINE_DEFINE(part) { MP_ROM_QSTR(FLASH_AREA_DEFINE_GETNAME(part)), MP_ROM_INT(DT_FIXED_PARTITION_ID(part)) },
+
+#define FLASH_AREA_DEFINE(part) COND_CODE_1(DT_NODE_HAS_STATUS_OKAY(DT_MTD_FROM_FIXED_PARTITION(part)), \
+ (FLASH_AREA_DEFINE_DEFINE(part)), ())
+
+#define FOREACH_PARTITION(n) DT_FOREACH_CHILD(n, FLASH_AREA_DEFINE)
+
const mp_obj_type_t zephyr_flash_area_type;
typedef struct _zephyr_flash_area_obj_t {
@@ -244,9 +258,8 @@ static const mp_rom_map_elem_t zephyr_flash_area_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_readblocks), MP_ROM_PTR(&zephyr_flash_area_readblocks_obj) },
{ MP_ROM_QSTR(MP_QSTR_writeblocks), MP_ROM_PTR(&zephyr_flash_area_writeblocks_obj) },
{ MP_ROM_QSTR(MP_QSTR_ioctl), MP_ROM_PTR(&zephyr_flash_area_ioctl_obj) },
- #if FIXED_PARTITION_EXISTS(storage_partition)
- { MP_ROM_QSTR(MP_QSTR_STORAGE), MP_ROM_INT(FIXED_PARTITION_ID(storage_partition)) },
- #endif
+ /* Generate list of partition IDs from Zephyr Devicetree */
+ DT_FOREACH_STATUS_OKAY(fixed_partitions, FOREACH_PARTITION)
};
static MP_DEFINE_CONST_DICT(zephyr_flash_area_locals_dict, zephyr_flash_area_locals_dict_table);
diff --git a/py/malloc.c b/py/malloc.c
index f557ade44f..05daeb35d0 100644
--- a/py/malloc.c
+++ b/py/malloc.c
@@ -209,6 +209,31 @@ void m_free(void *ptr)
#if MICROPY_TRACKED_ALLOC
+#if MICROPY_PY_THREAD && !MICROPY_PY_THREAD_GIL
+// If there's no GIL, use the GC recursive mutex to protect the tracked node linked list
+// under m_tracked_head.
+//
+// (For ports with GIL, the expectation is to only call tracked alloc functions
+// while holding the GIL.)
+
+static inline void m_tracked_node_lock(void) {
+ mp_thread_recursive_mutex_lock(&MP_STATE_MEM(gc_mutex), 1);
+}
+
+static inline void m_tracked_node_unlock(void) {
+ mp_thread_recursive_mutex_unlock(&MP_STATE_MEM(gc_mutex));
+}
+
+#else
+
+static inline void m_tracked_node_lock(void) {
+}
+
+static inline void m_tracked_node_unlock(void) {
+}
+
+#endif
+
#define MICROPY_TRACKED_ALLOC_STORE_SIZE (!MICROPY_ENABLE_GC)
typedef struct _m_tracked_node_t {
@@ -222,6 +247,7 @@ typedef struct _m_tracked_node_t {
#if MICROPY_DEBUG_VERBOSE
static size_t m_tracked_count_links(size_t *nb) {
+ m_tracked_node_lock();
m_tracked_node_t *node = MP_STATE_VM(m_tracked_head);
size_t n = 0;
*nb = 0;
@@ -234,6 +260,7 @@ static size_t m_tracked_count_links(size_t *nb) {
#endif
node = node->next;
}
+ m_tracked_node_unlock();
return n;
}
#endif
@@ -248,12 +275,14 @@ void *m_tracked_calloc(size_t nmemb, size_t size) {
size_t n = m_tracked_count_links(&nb);
DEBUG_printf("m_tracked_calloc(%u, %u) -> (%u;%u) %p\n", (int)nmemb, (int)size, (int)n, (int)nb, node);
#endif
+ m_tracked_node_lock();
if (MP_STATE_VM(m_tracked_head) != NULL) {
MP_STATE_VM(m_tracked_head)->prev = node;
}
node->prev = NULL;
node->next = MP_STATE_VM(m_tracked_head);
MP_STATE_VM(m_tracked_head) = node;
+ m_tracked_node_unlock();
#if MICROPY_TRACKED_ALLOC_STORE_SIZE
node->size = nmemb * size;
#endif
@@ -278,7 +307,8 @@ void m_tracked_free(void *ptr_in) {
size_t nb;
size_t n = m_tracked_count_links(&nb);
DEBUG_printf("m_tracked_free(%p, [%p, %p], nbytes=%u, links=%u;%u)\n", node, node->prev, node->next, (int)data_bytes, (int)n, (int)nb);
- #endif
+ #endif // MICROPY_DEBUG_VERBOSE
+ m_tracked_node_lock();
if (node->next != NULL) {
node->next->prev = node->prev;
}
@@ -287,6 +317,7 @@ void m_tracked_free(void *ptr_in) {
} else {
MP_STATE_VM(m_tracked_head) = node->next;
}
+ m_tracked_node_unlock();
m_free(node
#if MICROPY_MALLOC_USES_ALLOCATED_SIZE
#if MICROPY_TRACKED_ALLOC_STORE_SIZE
diff --git a/tests/extmod/ssl_noleak.py b/tests/extmod/tls_noleak.py
index 870032d58e..870032d58e 100644
--- a/tests/extmod/ssl_noleak.py
+++ b/tests/extmod/tls_noleak.py
diff --git a/tests/extmod/tls_threads.py b/tests/extmod/tls_threads.py
new file mode 100644
index 0000000000..4564abd3d8
--- /dev/null
+++ b/tests/extmod/tls_threads.py
@@ -0,0 +1,57 @@
+# Ensure that SSL sockets can be allocated from multiple
+# threads without thread safety issues
+import unittest
+
+try:
+ import _thread
+ import io
+ import tls
+ import time
+except ImportError:
+ print("SKIP")
+ raise SystemExit
+
+
+class TestSocket(io.IOBase):
+ def write(self, buf):
+ return len(buf)
+
+ def readinto(self, buf):
+ return 0
+
+ def ioctl(self, cmd, arg):
+ return 0
+
+ def setblocking(self, value):
+ pass
+
+
+ITERS = 256
+
+
+class TLSThreads(unittest.TestCase):
+ def test_sslsocket_threaded(self):
+ self.done = False
+ # only run in two threads: too much RAM demand otherwise, and rp2 only
+ # supports two anyhow
+ _thread.start_new_thread(self._alloc_many_sockets, (True,))
+ self._alloc_many_sockets(False)
+ while not self.done:
+ time.sleep(0.1)
+ print("done")
+
+ def _alloc_many_sockets(self, set_done_flag):
+ print("start", _thread.get_ident())
+ ctx = tls.SSLContext(tls.PROTOCOL_TLS_CLIENT)
+ ctx.verify_mode = tls.CERT_NONE
+ for n in range(ITERS):
+ s = TestSocket()
+ s = ctx.wrap_socket(s, do_handshake_on_connect=False)
+ s.close() # Free associated resources now from thread, not in a GC pass
+ print("done", _thread.get_ident())
+ if set_done_flag:
+ self.done = True
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/ports/rp2/rp2_dma.py b/tests/ports/rp2/rp2_dma.py
index bd7f17913e..436e5ee48e 100644
--- a/tests/ports/rp2/rp2_dma.py
+++ b/tests/ports/rp2/rp2_dma.py
@@ -20,7 +20,7 @@ class Test(unittest.TestCase):
def test_printing(self):
dma = self.dma
- self.assertEqual(str(dma), "DMA(0)")
+ self.assertEqual(str(dma), "DMA({})".format(dma.channel))
def test_pack_unpack_ctrl(self):
dma = self.dma
@@ -34,7 +34,7 @@ class Test(unittest.TestCase):
self.assertEqual(ctrl_dict["ahb_err"], 0)
self.assertEqual(ctrl_dict["bswap"], 0)
self.assertEqual(ctrl_dict["busy"], 0)
- self.assertEqual(ctrl_dict["chain_to"], 0)
+ self.assertEqual(ctrl_dict["chain_to"], dma.channel)
self.assertEqual(ctrl_dict["enable"], 1)
self.assertEqual(ctrl_dict["high_pri"], 0)
self.assertEqual(ctrl_dict["inc_read"], 1)
@@ -58,7 +58,7 @@ class Test(unittest.TestCase):
self.assertEqual(dma.write, 0)
self.assertEqual(dma.count, 0)
self.assertEqual(dma.ctrl & 0x01F, 25)
- self.assertEqual(dma.channel, 0)
+ self.assertIn(dma.channel, range(16))
self.assertIsInstance(dma.registers, memoryview)
def test_close(self):
@@ -86,26 +86,32 @@ class Test(unittest.TestCase):
def test_time_taken_for_large_memory_copy(self):
def run_and_time_dma(dma):
ticks_us = time.ticks_us
+ active = dma.active
irq_state = machine.disable_irq()
t0 = ticks_us()
- dma.active(True)
- while dma.active():
+ active(True)
+ while active():
pass
t1 = ticks_us()
machine.enable_irq(irq_state)
return time.ticks_diff(t1, t0)
- dma = self.dma
- dest = bytearray(16 * 1024)
- dma.read = SRC
- dma.write = dest
- dma.count = len(dest) // 4
- dma.ctrl = dma.pack_ctrl()
- dt = run_and_time_dma(dma)
- expected_dt_range = range(40, 70) if is_rp2350 else range(70, 125)
- self.assertIn(dt, expected_dt_range)
- self.assertEqual(dest[:8], SRC[:8])
- self.assertEqual(dest[-8:], SRC[-8:])
+ num_average = 10
+ dt_sum = 0
+ for _ in range(num_average):
+ dma = self.dma
+ dest = bytearray(16 * 1024)
+ dma.read = SRC
+ dma.write = dest
+ dma.count = len(dest) // 4
+ dma.ctrl = dma.pack_ctrl()
+ dt_sum += run_and_time_dma(dma)
+ self.assertEqual(dest[:8], SRC[:8])
+ self.assertEqual(dest[-8:], SRC[-8:])
+ self.tearDown()
+ self.setUp()
+ dt = dt_sum // num_average
+ self.assertIn(dt, range(30, 80))
def test_config_trigger(self):
# Test using .config(trigger=True) to start DMA immediately.
@@ -136,6 +142,7 @@ class Test(unittest.TestCase):
)
while dma.active():
pass
+ time.sleep_ms(1) # when running as native code, give the scheduler a chance to run
self.assertEqual(irq_flags, 1)
self.assertEqual(dest[:8], SRC[:8])
self.assertEqual(dest[-8:], SRC[-8:])
diff --git a/tools/ci.sh b/tools/ci.sh
index 6f8d1cb80c..a4bd43567c 100755
--- a/tools/ci.sh
+++ b/tools/ci.sh
@@ -209,7 +209,7 @@ function ci_esp32_build_s3_c3 {
function ci_esp8266_setup {
sudo pip3 install pyserial esptool==3.3.1 pyelftools ar
- wget https://github.com/jepler/esp-open-sdk/releases/download/2018-06-10/xtensa-lx106-elf-standalone.tar.gz
+ wget https://micropython.org/resources/xtensa-lx106-elf-standalone.tar.gz
zcat xtensa-lx106-elf-standalone.tar.gz | tar x
# Remove this esptool.py so pip version is used instead
rm xtensa-lx106-elf/bin/esptool.py
diff --git a/tools/mpremote/mpremote/mip.py b/tools/mpremote/mpremote/mip.py
index 26ae8bec5e..fa7974053f 100644
--- a/tools/mpremote/mpremote/mip.py
+++ b/tools/mpremote/mpremote/mip.py
@@ -34,6 +34,15 @@ def _ensure_path_exists(transport, path):
prefix += "/"
+# Check if the specified path exists and matches the hash.
+def _check_exists(transport, path, short_hash):
+ try:
+ remote_hash = transport.fs_hashfile(path, "sha256")
+ except FileNotFoundError:
+ return False
+ return remote_hash.hex()[: len(short_hash)] == short_hash
+
+
def _rewrite_url(url, branch=None):
if not branch:
branch = "HEAD"
@@ -115,8 +124,11 @@ def _install_json(transport, package_json_url, index, target, version, mpy):
raise CommandError(f"Invalid url for package: {package_json_url}")
for target_path, short_hash in package_json.get("hashes", ()):
fs_target_path = target + "/" + target_path
- file_url = f"{index}/file/{short_hash[:2]}/{short_hash}"
- _download_file(transport, file_url, fs_target_path)
+ if _check_exists(transport, fs_target_path, short_hash):
+ print("Exists:", fs_target_path)
+ else:
+ file_url = f"{index}/file/{short_hash[:2]}/{short_hash}"
+ _download_file(transport, file_url, fs_target_path)
for target_path, url in package_json.get("urls", ()):
fs_target_path = target + "/" + target_path
if base_url and not url.startswith(allowed_mip_url_prefixes):