From de40fda42f3b90bcc7f5664cbb1f2555bdcffc14 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Fri, 4 Apr 2025 16:13:41 -0700 Subject: [PATCH 1/4] Two enhancements for launcher 1. Set working directory for subsequent file. 2. Add `pathlib` for easy file discovery. --- .gitmodules | 2 +- extmod/vfs_fat.c | 6 +- main.c | 10 +- ports/atmel-samd/mpconfigport.mk | 2 + py/circuitpy_defns.mk | 5 + py/circuitpy_mpconfig.mk | 3 + shared-bindings/pathlib/PosixPath.c | 242 +++++++++++++++++++ shared-bindings/pathlib/PosixPath.h | 27 +++ shared-bindings/pathlib/__init__.c | 46 ++++ shared-bindings/pathlib/__init__.h | 11 + shared-bindings/supervisor/__init__.c | 50 +++- shared-bindings/supervisor/__init__.h | 5 +- shared-bindings/util.c | 15 ++ shared-bindings/util.h | 1 + shared-module/pathlib/PosixPath.c | 333 ++++++++++++++++++++++++++ shared-module/pathlib/PosixPath.h | 22 ++ shared-module/pathlib/__init__.c | 34 +++ shared-module/pathlib/__init__.h | 12 + 18 files changed, 815 insertions(+), 11 deletions(-) create mode 100644 shared-bindings/pathlib/PosixPath.c create mode 100644 shared-bindings/pathlib/PosixPath.h create mode 100644 shared-bindings/pathlib/__init__.c create mode 100644 shared-bindings/pathlib/__init__.h create mode 100644 shared-module/pathlib/PosixPath.c create mode 100644 shared-module/pathlib/PosixPath.h create mode 100644 shared-module/pathlib/__init__.c create mode 100644 shared-module/pathlib/__init__.h diff --git a/.gitmodules b/.gitmodules index f52ca8e7fa756..3c6aac61cadc3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -173,7 +173,7 @@ [submodule "ports/raspberrypi/sdk"] path = ports/raspberrypi/sdk url = https://github.com/adafruit/pico-sdk.git - branch = force_inline_critical_section + branch = force_inline_critical_section_2.1.1 [submodule "data/nvm.toml"] path = data/nvm.toml url = https://github.com/adafruit/nvm.toml.git diff --git a/extmod/vfs_fat.c b/extmod/vfs_fat.c index 929bd5fd98e0d..154dfec13f1a7 100644 --- a/extmod/vfs_fat.c +++ b/extmod/vfs_fat.c @@ -38,6 +38,7 @@ // CIRCUITPY-CHANGE: extra includes #include +#include "py/gc.h" #include "py/obj.h" #include "py/objproperty.h" #include "py/runtime.h" @@ -342,7 +343,10 @@ static mp_obj_t fat_vfs_stat(mp_obj_t vfs_in, mp_obj_t path_in) { FRESULT res = f_stat(&self->fatfs, path, &fno); if (res != FR_OK) { // CIRCUITPY-CHANGE - mp_raise_OSError_fresult(res); + if (gc_alloc_possible()) { + mp_raise_OSError_fresult(res); + } + return mp_const_none; } } diff --git a/main.c b/main.c index 0ea389635f40f..db5add2ebfa25 100644 --- a/main.c +++ b/main.c @@ -457,14 +457,19 @@ static bool __attribute__((noinline)) run_code_py(safe_mode_t safe_mode, bool *s usb_setup_with_vm(); #endif + // Always return to root before trying to run files. + common_hal_os_chdir("/"); // Check if a different run file has been allocated if (next_code_configuration != NULL) { next_code_configuration->options &= ~SUPERVISOR_NEXT_CODE_OPT_NEWLY_SET; next_code_options = next_code_configuration->options; if (next_code_configuration->filename[0] != '\0') { + if (next_code_configuration->working_directory != NULL) { + common_hal_os_chdir(next_code_configuration->working_directory); + } // This is where the user's python code is actually executed: const char *const filenames[] = { next_code_configuration->filename }; - found_main = maybe_run_list(filenames, MP_ARRAY_SIZE(filenames)); + found_main = maybe_run_list(filenames, 1); if (!found_main) { serial_write(next_code_configuration->filename); serial_write_compressed(MP_ERROR_TEXT(" not found.\n")); @@ -1109,9 +1114,6 @@ int __attribute__((used)) main(void) { } simulate_reset = false; - // Always return to root before trying to run files. - common_hal_os_chdir("/"); - if (pyexec_mode_kind == PYEXEC_MODE_FRIENDLY_REPL) { // If code.py did a fake deep sleep, pretend that we // are running code.py for the first time after a hard diff --git a/ports/atmel-samd/mpconfigport.mk b/ports/atmel-samd/mpconfigport.mk index 861ef37464633..6677d4309990c 100644 --- a/ports/atmel-samd/mpconfigport.mk +++ b/ports/atmel-samd/mpconfigport.mk @@ -47,6 +47,7 @@ CIRCUITPY_JSON ?= 0 CIRCUITPY_KEYPAD ?= 0 CIRCUITPY_MSGPACK ?= 0 CIRCUITPY_OS_GETENV ?= 0 +CIRCUITPY_PATHLIB ?= 0 CIRCUITPY_PIXELMAP ?= 0 CIRCUITPY_RE ?= 0 CIRCUITPY_SDCARDIO ?= 0 @@ -120,6 +121,7 @@ CIRCUITPY_BITMAPFILTER ?= 0 CIRCUITPY_FLOPPYIO ?= $(CIRCUITPY_FULL_BUILD) CIRCUITPY_FRAMEBUFFERIO ?= $(CIRCUITPY_FULL_BUILD) CIRCUITPY_MAX3421E ?= $(HAS_1MB_FLASH) +CIRCUITPY_PATHLIB ?= 0 CIRCUITPY_PS2IO ?= 1 CIRCUITPY_RGBMATRIX ?= $(CIRCUITPY_FRAMEBUFFERIO) CIRCUITPY_SAMD ?= 1 diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index 640ca70bf3543..0401c0448fd66 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -303,6 +303,9 @@ endif ifeq ($(CIRCUITPY_OS),1) SRC_PATTERNS += os/% endif +ifeq ($(CIRCUITPY_PATHLIB),1) +SRC_PATTERNS += pathlib/% +endif ifeq ($(CIRCUITPY_DUALBANK),1) SRC_PATTERNS += dualbank/% endif @@ -744,6 +747,8 @@ SRC_SHARED_MODULE_ALL = \ onewireio/__init__.c \ onewireio/OneWire.c \ os/__init__.c \ + pathlib/__init__.c \ + pathlib/PosixPath.c \ paralleldisplaybus/ParallelBus.c \ qrio/__init__.c \ qrio/QRDecoder.c \ diff --git a/py/circuitpy_mpconfig.mk b/py/circuitpy_mpconfig.mk index c277c04c9d4d5..3f55520ab63d3 100644 --- a/py/circuitpy_mpconfig.mk +++ b/py/circuitpy_mpconfig.mk @@ -410,6 +410,9 @@ CFLAGS += -DCIRCUITPY_OPT_MAP_LOOKUP_CACHE=$(CIRCUITPY_OPT_MAP_LOOKUP_CACHE) CIRCUITPY_OS ?= 1 CFLAGS += -DCIRCUITPY_OS=$(CIRCUITPY_OS) +CIRCUITPY_PATHLIB ?= $(CIRCUITPY_FULL_BUILD) +CFLAGS += -DCIRCUITPY_PATHLIB=$(CIRCUITPY_PATHLIB) + CIRCUITPY_PEW ?= 0 CFLAGS += -DCIRCUITPY_PEW=$(CIRCUITPY_PEW) diff --git a/shared-bindings/pathlib/PosixPath.c b/shared-bindings/pathlib/PosixPath.c new file mode 100644 index 0000000000000..04fd01a559562 --- /dev/null +++ b/shared-bindings/pathlib/PosixPath.c @@ -0,0 +1,242 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#include + +#include "py/obj.h" +#include "py/objproperty.h" +#include "py/runtime.h" +#include "py/mperrno.h" +#include "py/mphal.h" +#include "py/gc.h" +#include "py/objstr.h" +#include "py/objtuple.h" +#include "py/objtype.h" + +#include "shared-bindings/pathlib/PosixPath.h" +#include "shared-module/pathlib/PosixPath.h" + +//| class PosixPath: +//| """Object representing a path on Posix systems""" +//| +//| def __init__(self, path: Union[str, "PosixPath"]) -> None: +//| """Construct a PosixPath object from a string or another PosixPath object. +//| +//| :param path: A string or PosixPath object representing a path +//| """ +//| ... +//| + +static mp_obj_t pathlib_posixpath_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, 1, false); + return common_hal_pathlib_posixpath_new(args[0]); +} + +//| def joinpath(self, *args: Union[str, "PosixPath"]) -> "PosixPath": +//| """Join path components. +//| +//| :param args: Path components to join +//| :return: A new PosixPath object +//| """ +//| ... +//| +//| def __truediv__(self, other: Union[str, "PosixPath"]) -> "PosixPath": +//| """Implement path / other for joining paths. +//| +//| :param other: Path component to join +//| :return: A new PosixPath object +//| """ +//| ... +//| + +static mp_obj_t pathlib_posixpath_joinpath(mp_obj_t self_in, mp_obj_t arg) { + pathlib_posixpath_obj_t *self = MP_OBJ_TO_PTR(self_in); + return common_hal_pathlib_posixpath_joinpath(self, arg); +} +MP_DEFINE_CONST_FUN_OBJ_2(pathlib_posixpath_joinpath_obj, pathlib_posixpath_joinpath); + +// Binary operator for implementing the / operator +static mp_obj_t pathlib_posixpath_binary_op(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs) { + switch (op) { + case MP_BINARY_OP_TRUE_DIVIDE: { + // Implement path / other + pathlib_posixpath_obj_t *self = MP_OBJ_TO_PTR(lhs); + return common_hal_pathlib_posixpath_joinpath(self, rhs); + } + default: + return MP_OBJ_NULL; // op not supported + } +} + +//| @property +//| def parent(self) -> "PosixPath": +//| """The logical parent of the path.""" +//| ... +//| + +static mp_obj_t pathlib_posixpath_parent(mp_obj_t self_in) { + pathlib_posixpath_obj_t *self = MP_OBJ_TO_PTR(self_in); + return common_hal_pathlib_posixpath_parent(self); +} +MP_DEFINE_CONST_FUN_OBJ_1(pathlib_posixpath_parent_obj, pathlib_posixpath_parent); + +MP_PROPERTY_GETTER(pathlib_posixpath_parent_property_obj, + (mp_obj_t)&pathlib_posixpath_parent_obj); + +//| @property +//| def name(self) -> str: +//| """The final path component, excluding the drive and root, if any.""" +//| ... +//| + +static mp_obj_t pathlib_posixpath_name(mp_obj_t self_in) { + pathlib_posixpath_obj_t *self = MP_OBJ_TO_PTR(self_in); + return common_hal_pathlib_posixpath_name(self); +} +MP_DEFINE_CONST_FUN_OBJ_1(pathlib_posixpath_name_obj, pathlib_posixpath_name); + +MP_PROPERTY_GETTER(pathlib_posixpath_name_property_obj, + (mp_obj_t)&pathlib_posixpath_name_obj); + +//| @property +//| def stem(self) -> str: +//| """The final path component, without its suffix.""" +//| ... +//| + +static mp_obj_t pathlib_posixpath_stem(mp_obj_t self_in) { + pathlib_posixpath_obj_t *self = MP_OBJ_TO_PTR(self_in); + return common_hal_pathlib_posixpath_stem(self); +} +MP_DEFINE_CONST_FUN_OBJ_1(pathlib_posixpath_stem_obj, pathlib_posixpath_stem); + +MP_PROPERTY_GETTER(pathlib_posixpath_stem_property_obj, + (mp_obj_t)&pathlib_posixpath_stem_obj); + +//| @property +//| def suffix(self) -> str: +//| """The final component's extension.""" +//| ... +//| + +static mp_obj_t pathlib_posixpath_suffix(mp_obj_t self_in) { + pathlib_posixpath_obj_t *self = MP_OBJ_TO_PTR(self_in); + return common_hal_pathlib_posixpath_suffix(self); +} +MP_DEFINE_CONST_FUN_OBJ_1(pathlib_posixpath_suffix_obj, pathlib_posixpath_suffix); + +MP_PROPERTY_GETTER(pathlib_posixpath_suffix_property_obj, + (mp_obj_t)&pathlib_posixpath_suffix_obj); + +//| def exists(self) -> bool: +//| """Check whether the path exists.""" +//| ... +//| + +static mp_obj_t pathlib_posixpath_exists(mp_obj_t self_in) { + pathlib_posixpath_obj_t *self = MP_OBJ_TO_PTR(self_in); + return common_hal_pathlib_posixpath_exists(self); +} +MP_DEFINE_CONST_FUN_OBJ_1(pathlib_posixpath_exists_obj, pathlib_posixpath_exists); + +//| def is_dir(self) -> bool: +//| """Check whether the path is a directory.""" +//| ... +//| + +static mp_obj_t pathlib_posixpath_is_dir(mp_obj_t self_in) { + pathlib_posixpath_obj_t *self = MP_OBJ_TO_PTR(self_in); + return common_hal_pathlib_posixpath_is_dir(self); +} +MP_DEFINE_CONST_FUN_OBJ_1(pathlib_posixpath_is_dir_obj, pathlib_posixpath_is_dir); + +//| def is_file(self) -> bool: +//| """Check whether the path is a regular file.""" +//| ... +//| + +static mp_obj_t pathlib_posixpath_is_file(mp_obj_t self_in) { + pathlib_posixpath_obj_t *self = MP_OBJ_TO_PTR(self_in); + return common_hal_pathlib_posixpath_is_file(self); +} +MP_DEFINE_CONST_FUN_OBJ_1(pathlib_posixpath_is_file_obj, pathlib_posixpath_is_file); + +//| def absolute(self) -> "PosixPath": +//| """Return an absolute version of this path.""" +//| ... +//| + +static mp_obj_t pathlib_posixpath_absolute(mp_obj_t self_in) { + pathlib_posixpath_obj_t *self = MP_OBJ_TO_PTR(self_in); + return common_hal_pathlib_posixpath_absolute(self); +} +MP_DEFINE_CONST_FUN_OBJ_1(pathlib_posixpath_absolute_obj, pathlib_posixpath_absolute); + +//| def resolve(self) -> "PosixPath": +//| """Make the path absolute, resolving any symlinks.""" +//| ... +//| + +static mp_obj_t pathlib_posixpath_resolve(mp_obj_t self_in) { + pathlib_posixpath_obj_t *self = MP_OBJ_TO_PTR(self_in); + return common_hal_pathlib_posixpath_resolve(self); +} +MP_DEFINE_CONST_FUN_OBJ_1(pathlib_posixpath_resolve_obj, pathlib_posixpath_resolve); + +//| def iterdir(self) -> Iterator["PosixPath"]: +//| """Iterate over the files in this directory. +//| Does not include the special paths '.' and '..'. +//| +//| :return: An iterator yielding path objects +//| +//| Example:: +//| +//| for path in Path('.').iterdir(): +//| print(path) +//| """ +//| ... +//| + +static mp_obj_t pathlib_posixpath_iterdir(mp_obj_t self_in) { + pathlib_posixpath_obj_t *self = MP_OBJ_TO_PTR(self_in); + return common_hal_pathlib_posixpath_iterdir(self); +} +MP_DEFINE_CONST_FUN_OBJ_1(pathlib_posixpath_iterdir_obj, pathlib_posixpath_iterdir); + +static const mp_rom_map_elem_t pathlib_posixpath_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_joinpath), MP_ROM_PTR(&pathlib_posixpath_joinpath_obj) }, + { MP_ROM_QSTR(MP_QSTR_exists), MP_ROM_PTR(&pathlib_posixpath_exists_obj) }, + { MP_ROM_QSTR(MP_QSTR_is_dir), MP_ROM_PTR(&pathlib_posixpath_is_dir_obj) }, + { MP_ROM_QSTR(MP_QSTR_is_file), MP_ROM_PTR(&pathlib_posixpath_is_file_obj) }, + { MP_ROM_QSTR(MP_QSTR_absolute), MP_ROM_PTR(&pathlib_posixpath_absolute_obj) }, + { MP_ROM_QSTR(MP_QSTR_resolve), MP_ROM_PTR(&pathlib_posixpath_resolve_obj) }, + { MP_ROM_QSTR(MP_QSTR_iterdir), MP_ROM_PTR(&pathlib_posixpath_iterdir_obj) }, + + // Properties + { MP_ROM_QSTR(MP_QSTR_parent), MP_ROM_PTR(&pathlib_posixpath_parent_property_obj) }, + { MP_ROM_QSTR(MP_QSTR_name), MP_ROM_PTR(&pathlib_posixpath_name_property_obj) }, + { MP_ROM_QSTR(MP_QSTR_stem), MP_ROM_PTR(&pathlib_posixpath_stem_property_obj) }, + { MP_ROM_QSTR(MP_QSTR_suffix), MP_ROM_PTR(&pathlib_posixpath_suffix_property_obj) }, +}; + +static MP_DEFINE_CONST_DICT(pathlib_posixpath_locals_dict, pathlib_posixpath_locals_dict_table); + +// String representation for PosixPath objects +static void pathlib_posixpath_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + pathlib_posixpath_obj_t *self = MP_OBJ_TO_PTR(self_in); + // Just print the path string directly + mp_obj_print_helper(print, self->path_str, kind); +} + +MP_DEFINE_CONST_OBJ_TYPE( + pathlib_posixpath_type, + MP_QSTR_PosixPath, + MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS, + make_new, pathlib_posixpath_make_new, + print, pathlib_posixpath_print, + binary_op, pathlib_posixpath_binary_op, + locals_dict, &pathlib_posixpath_locals_dict + ); diff --git a/shared-bindings/pathlib/PosixPath.h b/shared-bindings/pathlib/PosixPath.h new file mode 100644 index 0000000000000..23260f163fac7 --- /dev/null +++ b/shared-bindings/pathlib/PosixPath.h @@ -0,0 +1,27 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "py/obj.h" + +extern const mp_obj_type_t pathlib_posixpath_type; + +// Include the struct definition from shared-module +#include "shared-module/pathlib/PosixPath.h" + +mp_obj_t common_hal_pathlib_posixpath_new(mp_obj_t path); +mp_obj_t common_hal_pathlib_posixpath_joinpath(pathlib_posixpath_obj_t *self, mp_obj_t arg); +mp_obj_t common_hal_pathlib_posixpath_parent(pathlib_posixpath_obj_t *self); +mp_obj_t common_hal_pathlib_posixpath_name(pathlib_posixpath_obj_t *self); +mp_obj_t common_hal_pathlib_posixpath_stem(pathlib_posixpath_obj_t *self); +mp_obj_t common_hal_pathlib_posixpath_suffix(pathlib_posixpath_obj_t *self); +mp_obj_t common_hal_pathlib_posixpath_exists(pathlib_posixpath_obj_t *self); +mp_obj_t common_hal_pathlib_posixpath_is_dir(pathlib_posixpath_obj_t *self); +mp_obj_t common_hal_pathlib_posixpath_is_file(pathlib_posixpath_obj_t *self); +mp_obj_t common_hal_pathlib_posixpath_absolute(pathlib_posixpath_obj_t *self); +mp_obj_t common_hal_pathlib_posixpath_resolve(pathlib_posixpath_obj_t *self); +mp_obj_t common_hal_pathlib_posixpath_iterdir(pathlib_posixpath_obj_t *self); diff --git a/shared-bindings/pathlib/__init__.c b/shared-bindings/pathlib/__init__.c new file mode 100644 index 0000000000000..d09ec51c42d79 --- /dev/null +++ b/shared-bindings/pathlib/__init__.c @@ -0,0 +1,46 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#include + +#include "py/obj.h" +#include "py/runtime.h" +#include "py/objtype.h" + +#include "shared-bindings/pathlib/__init__.h" +#include "shared-bindings/pathlib/PosixPath.h" + +//| """Filesystem path operations""" +//| + +//| class Path: +//| """Factory function that returns a new PosixPath.""" +//| +//| def __new__(cls, *args) -> PosixPath: +//| """Create a new Path object. +//| +//| :param args: Path components +//| :return: A new PosixPath object +//| """ +//| ... +//| + +/* Path is just an alias for PosixPath in CircuitPython */ + +static const mp_rom_map_elem_t pathlib_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_pathlib) }, + { MP_ROM_QSTR(MP_QSTR_Path), MP_ROM_PTR(&pathlib_posixpath_type) }, + { MP_ROM_QSTR(MP_QSTR_PosixPath), MP_ROM_PTR(&pathlib_posixpath_type) }, +}; + +static MP_DEFINE_CONST_DICT(pathlib_module_globals, pathlib_module_globals_table); + +const mp_obj_module_t pathlib_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&pathlib_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_pathlib, pathlib_module); diff --git a/shared-bindings/pathlib/__init__.h b/shared-bindings/pathlib/__init__.h new file mode 100644 index 0000000000000..ed63e47ceaed3 --- /dev/null +++ b/shared-bindings/pathlib/__init__.h @@ -0,0 +1,11 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "py/obj.h" + +mp_obj_t common_hal_pathlib_path_new(size_t n_args, const mp_obj_t *args); diff --git a/shared-bindings/supervisor/__init__.c b/shared-bindings/supervisor/__init__.c index 7b1eac7c2a712..f2fc14c3c9a5d 100644 --- a/shared-bindings/supervisor/__init__.c +++ b/shared-bindings/supervisor/__init__.c @@ -3,6 +3,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2016-2017 Scott Shawcroft for Adafruit Industries // // SPDX-License-Identifier: MIT +#include #include #include "py/obj.h" @@ -25,6 +26,7 @@ #include "shared-bindings/time/__init__.h" #include "shared-bindings/supervisor/Runtime.h" #include "shared-bindings/supervisor/StatusBar.h" +#include "shared-bindings/util.h" //| """Supervisor settings""" //| @@ -57,6 +59,7 @@ MP_DEFINE_CONST_FUN_OBJ_0(supervisor_reload_obj, supervisor_reload); //| def set_next_code_file( //| filename: Optional[str], //| *, +//| working_directory: Optional[str] = None, //| reload_on_success: bool = False, //| reload_on_error: bool = False, //| sticky_on_success: bool = False, @@ -99,6 +102,7 @@ MP_DEFINE_CONST_FUN_OBJ_0(supervisor_reload_obj, supervisor_reload); static mp_obj_t supervisor_set_next_code_file(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { static const mp_arg_t allowed_args[] = { { MP_QSTR_filename, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none} }, + { MP_QSTR_working_directory, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = mp_const_none} }, { MP_QSTR_reload_on_success, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, { MP_QSTR_reload_on_error, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, { MP_QSTR_sticky_on_success, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, @@ -107,6 +111,7 @@ static mp_obj_t supervisor_set_next_code_file(size_t n_args, const mp_obj_t *pos }; struct { mp_arg_val_t filename; + mp_arg_val_t working_directory; mp_arg_val_t reload_on_success; mp_arg_val_t reload_on_error; mp_arg_val_t sticky_on_success; @@ -118,6 +123,11 @@ static mp_obj_t supervisor_set_next_code_file(size_t n_args, const mp_obj_t *pos if (!mp_obj_is_str_or_bytes(filename_obj) && filename_obj != mp_const_none) { mp_raise_TypeError_varg(MP_ERROR_TEXT("%q must be of type %q or %q, not %q"), MP_QSTR_filename, MP_QSTR_str, MP_QSTR_None, mp_obj_get_type(filename_obj)->name); } + + mp_obj_t working_directory_obj = args.working_directory.u_obj; + if (!mp_obj_is_str_or_bytes(working_directory_obj) && working_directory_obj != mp_const_none) { + mp_raise_TypeError_varg(MP_ERROR_TEXT("%q must be of type %q or %q, not %q"), MP_QSTR_working_directory, MP_QSTR_str, MP_QSTR_None, mp_obj_get_type(working_directory_obj)->name); + } if (filename_obj == mp_const_none) { filename_obj = mp_const_empty_bytes; } @@ -139,18 +149,50 @@ static mp_obj_t supervisor_set_next_code_file(size_t n_args, const mp_obj_t *pos } size_t len; const char *filename = mp_obj_str_get_data(filename_obj, &len); + if (!path_exists(filename)) { + mp_raise_ValueError(MP_ERROR_TEXT("File not found")); + } + + size_t working_directory_len = 0; + const char *working_directory = NULL; + if (working_directory_obj != mp_const_none) { + working_directory = mp_obj_str_get_data(working_directory_obj, &working_directory_len); + if (!path_exists(working_directory)) { + mp_raise_ValueError_varg(MP_ERROR_TEXT("Invalid %q"), MP_QSTR_working_directory); + } + } if (next_code_configuration != NULL) { port_free(next_code_configuration); next_code_configuration = NULL; } if (options != 0 || len != 0) { - next_code_configuration = port_malloc(sizeof(supervisor_next_code_info_t) + len + 1, false); + + size_t next_code_size = sizeof(supervisor_next_code_info_t) + len + 1; + if (working_directory_len > 0) { + next_code_size += working_directory_len + 1; + } + next_code_configuration = port_malloc(next_code_size, false); if (next_code_configuration == NULL) { - m_malloc_fail(sizeof(supervisor_next_code_info_t) + len + 1); + m_malloc_fail(next_code_size); + } + char *filename_ptr = (char *)next_code_configuration + sizeof(supervisor_next_code_info_t); + + // Copy filename + memcpy(filename_ptr, filename, len); + filename_ptr[len] = '\0'; + + char *working_directory_ptr = NULL; + // Copy working directory after filename if present + if (working_directory_len > 0) { + working_directory_ptr = filename_ptr + len + 1; + memcpy(working_directory_ptr, working_directory, working_directory_len); + working_directory_ptr[working_directory_len] = '\0'; } + // Set everything up last. We may have raised an exception early and we + // don't want to free the memory if we failed. + next_code_configuration->filename = filename_ptr; + next_code_configuration->working_directory = working_directory_ptr; next_code_configuration->options = options | SUPERVISOR_NEXT_CODE_OPT_NEWLY_SET; - memcpy(&next_code_configuration->filename, filename, len); - next_code_configuration->filename[len] = '\0'; } return mp_const_none; } diff --git a/shared-bindings/supervisor/__init__.h b/shared-bindings/supervisor/__init__.h index c442534072ae7..4be9667ef563a 100644 --- a/shared-bindings/supervisor/__init__.h +++ b/shared-bindings/supervisor/__init__.h @@ -6,6 +6,8 @@ #pragma once +#include +#include // #include "py/mpconfig.h" #include "py/obj.h" @@ -18,7 +20,8 @@ typedef struct { uint8_t options; - char filename[]; + const char *working_directory; + const char *filename; } supervisor_next_code_info_t; extern const super_runtime_obj_t common_hal_supervisor_runtime_obj; diff --git a/shared-bindings/util.c b/shared-bindings/util.c index 0711428b4de03..0bcaa2eef41e9 100644 --- a/shared-bindings/util.c +++ b/shared-bindings/util.c @@ -8,6 +8,8 @@ #include "shared-bindings/util.h" +#include "shared-bindings/os/__init__.h" + // If so, deinit() has already been called on the object, so complain. void raise_deinited_error(void) { mp_raise_ValueError(MP_ERROR_TEXT("Object has been deinitialized and can no longer be used. Create a new object.")); @@ -33,3 +35,16 @@ void properties_construct_helper(mp_obj_t self_in, const mp_arg_t *args, const m } } } + +bool path_exists(const char *path) { + // Use common_hal_os_stat to check if path exists + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + common_hal_os_stat(path); + nlr_pop(); + return true; + } else { + // Path doesn't exist + return false; + } +} diff --git a/shared-bindings/util.h b/shared-bindings/util.h index f1e4dca13aa43..821695e47d0e8 100644 --- a/shared-bindings/util.h +++ b/shared-bindings/util.h @@ -12,3 +12,4 @@ void raise_deinited_error(void); void properties_print_helper(const mp_print_t *print, mp_obj_t self_in, const mp_arg_t *properties, size_t n_properties); void properties_construct_helper(mp_obj_t self_in, const mp_arg_t *args, const mp_arg_val_t *vals, size_t n_properties); +bool path_exists(const char *path); diff --git a/shared-module/pathlib/PosixPath.c b/shared-module/pathlib/PosixPath.c new file mode 100644 index 0000000000000..189f0905ad0d9 --- /dev/null +++ b/shared-module/pathlib/PosixPath.c @@ -0,0 +1,333 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#include + +#include "py/obj.h" +#include "py/runtime.h" +#include "py/mperrno.h" +#include "py/mphal.h" +#include "py/objstr.h" +#include "py/gc.h" + +#include "shared-bindings/os/__init__.h" +#include "shared-bindings/pathlib/PosixPath.h" +#include "shared-bindings/util.h" + +// Path directory iterator type +typedef struct _mp_obj_path_dir_iter_t { + mp_obj_base_t base; + mp_fun_1_t iternext; // Iterator function pointer + mp_obj_t path; // Parent path + mp_obj_t iter; // Iterator for the filenames +} mp_obj_path_dir_iter_t; + +mp_obj_t pathlib_posixpath_from_str(mp_obj_t path_str) { + pathlib_posixpath_obj_t *self = m_new_obj(pathlib_posixpath_obj_t); + self->base.type = &pathlib_posixpath_type; + self->path_str = path_str; + return MP_OBJ_FROM_PTR(self); +} + +mp_obj_t common_hal_pathlib_posixpath_new(mp_obj_t path) { + if (mp_obj_is_str(path)) { + return pathlib_posixpath_from_str(path); + } else if (mp_obj_is_type(path, &pathlib_posixpath_type)) { + // Return a copy to ensure immutability + pathlib_posixpath_obj_t *p = MP_OBJ_TO_PTR(path); + return pathlib_posixpath_from_str(p->path_str); + } + + const mp_obj_type_t *path_type = mp_obj_get_type(path); + mp_raise_TypeError_varg(MP_ERROR_TEXT("%q must be of type %q or %q, not %q"), MP_QSTR_path, MP_QSTR_str, MP_QSTR_PosixPath, path_type->name); + return mp_const_none; +} + +mp_obj_t common_hal_pathlib_posixpath_joinpath(pathlib_posixpath_obj_t *self, mp_obj_t arg) { + + mp_obj_t path_part; + if (mp_obj_is_str(arg)) { + path_part = arg; + } else if (mp_obj_is_type(arg, &pathlib_posixpath_type)) { + pathlib_posixpath_obj_t *p = MP_OBJ_TO_PTR(arg); + path_part = p->path_str; + } else { + const mp_obj_type_t *arg_type = mp_obj_get_type(arg); + mp_raise_TypeError_varg(MP_ERROR_TEXT("%q must be of type %q or %q, not %q"), MP_QSTR_path, MP_QSTR_str, MP_QSTR_PosixPath, arg_type->name); + return mp_const_none; + } + + // Get string values + GET_STR_DATA_LEN(self->path_str, path_str, path_len); + GET_STR_DATA_LEN(path_part, part_str, part_len); + + // If either part is empty, return the other + if (path_len == 0) { + return pathlib_posixpath_from_str(path_part); + } + if (part_len == 0) { + return pathlib_posixpath_from_str(self->path_str); + } + + // Calculate the required buffer size (path + '/' + part + '\0') + size_t need_slash = (path_str[path_len - 1] != '/') ? 1 : 0; + size_t new_len = path_len + need_slash + part_len; + + // Allocate new string + byte *new_str = m_new(byte, new_len + 1); + + // Copy first part + memcpy(new_str, path_str, path_len); + + // Add slash if needed + if (need_slash) { + new_str[path_len] = '/'; + } + + // Copy second part + memcpy(new_str + path_len + need_slash, part_str, part_len); + new_str[new_len] = '\0'; + + // Create a new string object + mp_obj_t result_str = mp_obj_new_str((const char *)new_str, new_len); + + // Free the temporary buffer + m_del(byte, new_str, new_len + 1); + + return pathlib_posixpath_from_str(result_str); +} + +mp_obj_t common_hal_pathlib_posixpath_parent(pathlib_posixpath_obj_t *self) { + GET_STR_DATA_LEN(self->path_str, path_str, path_len); + + // Handle root directory and empty path + if (path_len == 0 || (path_len == 1 && path_str[0] == '/')) { + return MP_OBJ_FROM_PTR(self); // Root is its own parent + } + + // Find the last slash + int last_slash = -1; + for (int i = path_len - 1; i >= 0; i--) { + if (path_str[i] == '/') { + last_slash = i; + break; + } + } + + // No slash found, return "." + if (last_slash == -1) { + return pathlib_posixpath_from_str(MP_OBJ_NEW_QSTR(MP_QSTR_dot)); + } + + // Root directory + if (last_slash == 0) { + return pathlib_posixpath_from_str(MP_OBJ_NEW_QSTR(MP_QSTR_slash)); + } + + // Create parent path + mp_obj_t parent = mp_obj_new_str((const char *)path_str, last_slash); + return pathlib_posixpath_from_str(parent); +} + +mp_obj_t common_hal_pathlib_posixpath_name(pathlib_posixpath_obj_t *self) { + GET_STR_DATA_LEN(self->path_str, path_str, path_len); + + // Empty path + if (path_len == 0) { + return MP_OBJ_NEW_QSTR(MP_QSTR_); + } + + // Find the last slash + int last_slash = -1; + for (int i = path_len - 1; i >= 0; i--) { + if (path_str[i] == '/') { + last_slash = i; + break; + } + } + + // No slash found, return the whole path + if (last_slash == -1) { + return self->path_str; + } + + // Slash at the end + if (last_slash == (int)(path_len - 1)) { + return MP_OBJ_NEW_QSTR(MP_QSTR_); + } + + // Return the component after the last slash + return mp_obj_new_str((const char *)(path_str + last_slash + 1), path_len - last_slash - 1); +} + +mp_obj_t common_hal_pathlib_posixpath_stem(pathlib_posixpath_obj_t *self) { + mp_obj_t name = common_hal_pathlib_posixpath_name(self); + GET_STR_DATA_LEN(name, name_str, name_len); + + // Empty name + if (name_len == 0) { + return name; + } + + // Find the last dot + int last_dot = -1; + for (int i = name_len - 1; i >= 0; i--) { + if (name_str[i] == '.') { + last_dot = i; + break; + } + } + + // No dot found or dot at the start, return the whole name + if (last_dot <= 0) { + return name; + } + + // Return the part before the last dot + return mp_obj_new_str((const char *)name_str, last_dot); +} + +mp_obj_t common_hal_pathlib_posixpath_suffix(pathlib_posixpath_obj_t *self) { + mp_obj_t name = common_hal_pathlib_posixpath_name(self); + GET_STR_DATA_LEN(name, name_str, name_len); + + // Empty name + if (name_len == 0) { + return MP_OBJ_NEW_QSTR(MP_QSTR_); + } + + // Find the last dot + int last_dot = -1; + for (int i = name_len - 1; i >= 0; i--) { + if (name_str[i] == '.') { + last_dot = i; + break; + } + } + + // No dot found or dot at the start, return empty string + if (last_dot <= 0) { + return MP_OBJ_NEW_QSTR(MP_QSTR_); + } + + // Return the part after the last dot (including the dot) + return mp_obj_new_str((const char *)(name_str + last_dot), name_len - last_dot); +} + +mp_obj_t common_hal_pathlib_posixpath_exists(pathlib_posixpath_obj_t *self) { + if (path_exists(mp_obj_str_get_str(self->path_str))) { + return mp_const_true; + } else { + return mp_const_false; + } +} + +mp_obj_t common_hal_pathlib_posixpath_is_dir(pathlib_posixpath_obj_t *self) { + + // Use common_hal_os_stat to check if path is directory + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + const char *path = mp_obj_str_get_str(self->path_str); + mp_obj_t stat_result = common_hal_os_stat(path); + mp_obj_tuple_t *stat_result_tpl = MP_OBJ_TO_PTR(stat_result); + + // Check if it's a directory (S_IFDIR flag in st_mode) + mp_obj_t mode_obj = stat_result_tpl->items[0]; + mp_int_t mode_val = mp_obj_get_int(mode_obj); + nlr_pop(); + + // 0x4000 is S_IFDIR (directory) + return mp_obj_new_bool((mode_val & 0x4000) != 0); + } else { + // Path doesn't exist + return mp_const_false; + } +} + +mp_obj_t common_hal_pathlib_posixpath_is_file(pathlib_posixpath_obj_t *self) { + + // Use common_hal_os_stat to check if path is a regular file + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + const char *path = mp_obj_str_get_str(self->path_str); + mp_obj_t stat_result = common_hal_os_stat(path); + mp_obj_tuple_t *stat_result_tpl = MP_OBJ_TO_PTR(stat_result); + + // Check if it's a regular file (S_IFREG flag in st_mode) + mp_obj_t mode_obj = stat_result_tpl->items[0]; + mp_int_t mode_val = mp_obj_get_int(mode_obj); + nlr_pop(); + + // 0x8000 is S_IFREG (regular file) + return mp_obj_new_bool((mode_val & 0x8000) != 0); + } else { + // Path doesn't exist + return mp_const_false; + } +} + +mp_obj_t common_hal_pathlib_posixpath_absolute(pathlib_posixpath_obj_t *self) { + GET_STR_DATA_LEN(self->path_str, path_str, path_len); + + // Already absolute + if (path_len > 0 && path_str[0] == '/') { + return MP_OBJ_FROM_PTR(self); + } + + // Get current directory using common_hal_os_getcwd() + mp_obj_t cwd = common_hal_os_getcwd(); + + // Join with current path + mp_obj_t abs_path = common_hal_pathlib_posixpath_joinpath( + MP_OBJ_TO_PTR(pathlib_posixpath_from_str(cwd)), + MP_OBJ_FROM_PTR(self) + ); + + return abs_path; +} + +mp_obj_t common_hal_pathlib_posixpath_resolve(pathlib_posixpath_obj_t *self) { + // For now, just call absolute() since CircuitPython doesn't support symlinks + return common_hal_pathlib_posixpath_absolute(self); +} + +// Iterator iternext function - called for each iteration +static mp_obj_t path_dir_iter_iternext(mp_obj_t self_in) { + mp_obj_path_dir_iter_t *self = MP_OBJ_TO_PTR(self_in); + + // Get the next filename from the iterator + mp_obj_t next = mp_iternext(self->iter); + if (next == MP_OBJ_STOP_ITERATION) { + return MP_OBJ_STOP_ITERATION; // End of iteration + } + + // Create a new path by joining the parent path with the filename + return common_hal_pathlib_posixpath_joinpath(self->path, next); +} + +// Create a new path directory iterator +mp_obj_t mp_obj_new_path_dir_iter(mp_obj_t path, mp_obj_t iter) { + mp_obj_path_dir_iter_t *o = m_new_obj(mp_obj_path_dir_iter_t); + o->base.type = &mp_type_polymorph_iter; + o->iternext = path_dir_iter_iternext; + o->path = path; + o->iter = iter; + return MP_OBJ_FROM_PTR(o); +} + +mp_obj_t common_hal_pathlib_posixpath_iterdir(pathlib_posixpath_obj_t *self) { + // Get the path as a C string + const char *path = mp_obj_str_get_str(self->path_str); + + // Use os.listdir to get the directory contents + mp_obj_t dir_list = common_hal_os_listdir(path); + + // Get an iterator for the list of filenames + mp_obj_t iter = mp_getiter(dir_list, NULL); + + // Create and return a path directory iterator + return mp_obj_new_path_dir_iter(MP_OBJ_FROM_PTR(self), iter); +} diff --git a/shared-module/pathlib/PosixPath.h b/shared-module/pathlib/PosixPath.h new file mode 100644 index 0000000000000..cb1ef13f4fe68 --- /dev/null +++ b/shared-module/pathlib/PosixPath.h @@ -0,0 +1,22 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "py/obj.h" +#include "py/objstr.h" + +typedef struct { + mp_obj_base_t base; + mp_obj_t path_str; +} pathlib_posixpath_obj_t; + +// Forward declaration +extern const mp_obj_type_t pathlib_posixpath_type; + +// Helper functions +mp_obj_t pathlib_posixpath_from_str(mp_obj_t path_str); +mp_obj_t mp_obj_new_path_dir_iter(mp_obj_t path, mp_obj_t iter); diff --git a/shared-module/pathlib/__init__.c b/shared-module/pathlib/__init__.c new file mode 100644 index 0000000000000..bae5b62dce538 --- /dev/null +++ b/shared-module/pathlib/__init__.c @@ -0,0 +1,34 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#include + +#include "py/obj.h" +#include "py/runtime.h" + +#include "shared-module/pathlib/__init__.h" +#include "shared-bindings/pathlib/PosixPath.h" + +mp_obj_t common_hal_pathlib_path_new(size_t n_args, const mp_obj_t *args) { + if (n_args == 0) { + return pathlib_posixpath_from_str(MP_OBJ_NEW_QSTR(MP_QSTR_)); + } + + if (n_args == 1) { + return common_hal_pathlib_posixpath_new(args[0]); + } + + // Join multiple path components + mp_obj_t path = args[0]; + for (size_t i = 1; i < n_args; i++) { + path = common_hal_pathlib_posixpath_joinpath( + common_hal_pathlib_posixpath_new(path), + args[i] + ); + } + + return path; +} diff --git a/shared-module/pathlib/__init__.h b/shared-module/pathlib/__init__.h new file mode 100644 index 0000000000000..07501e0a6efe8 --- /dev/null +++ b/shared-module/pathlib/__init__.h @@ -0,0 +1,12 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "py/obj.h" + +// Forward declaration to Path constructor +mp_obj_t common_hal_pathlib_path_new(size_t n_args, const mp_obj_t *args); From 6327eafea2b08fcaac063eabf86b957104d66e82 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Thu, 10 Apr 2025 12:24:33 -0700 Subject: [PATCH 2/4] Fix slash & dot. Fix builds --- ports/atmel-samd/mpconfigport.mk | 3 +-- .../boards/nordic/nrf5340dk/autogen_board_info.toml | 1 + .../boards/nordic/nrf54l15dk/autogen_board_info.toml | 1 + .../boards/nordic/nrf7002dk/autogen_board_info.toml | 1 + .../boards/renesas/ek_ra6m5/autogen_board_info.toml | 1 + .../boards/renesas/ek_ra8d1/autogen_board_info.toml | 1 + .../boards/st/nucleo_u575zi_q/autogen_board_info.toml | 1 + .../boards/st/stm32h7b3i_dk/autogen_board_info.toml | 1 + shared-bindings/pathlib/__init__.c | 11 ++--------- shared-module/pathlib/PosixPath.c | 4 ++-- 10 files changed, 12 insertions(+), 13 deletions(-) diff --git a/ports/atmel-samd/mpconfigport.mk b/ports/atmel-samd/mpconfigport.mk index 6677d4309990c..7e2d31da35c52 100644 --- a/ports/atmel-samd/mpconfigport.mk +++ b/ports/atmel-samd/mpconfigport.mk @@ -13,6 +13,7 @@ CIRCUITPY_LTO = 1 CIRCUITPY_KEYPAD_DEMUX ?= 0 CIRCUITPY_LVFONTIO ?= 0 +CIRCUITPY_PATHLIB ?= 0 ###################################################################### # Put samd21-only choices here. @@ -47,7 +48,6 @@ CIRCUITPY_JSON ?= 0 CIRCUITPY_KEYPAD ?= 0 CIRCUITPY_MSGPACK ?= 0 CIRCUITPY_OS_GETENV ?= 0 -CIRCUITPY_PATHLIB ?= 0 CIRCUITPY_PIXELMAP ?= 0 CIRCUITPY_RE ?= 0 CIRCUITPY_SDCARDIO ?= 0 @@ -121,7 +121,6 @@ CIRCUITPY_BITMAPFILTER ?= 0 CIRCUITPY_FLOPPYIO ?= $(CIRCUITPY_FULL_BUILD) CIRCUITPY_FRAMEBUFFERIO ?= $(CIRCUITPY_FULL_BUILD) CIRCUITPY_MAX3421E ?= $(HAS_1MB_FLASH) -CIRCUITPY_PATHLIB ?= 0 CIRCUITPY_PS2IO ?= 1 CIRCUITPY_RGBMATRIX ?= $(CIRCUITPY_FRAMEBUFFERIO) CIRCUITPY_SAMD ?= 1 diff --git a/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml b/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml index 624e7e69a3fd1..4bd765d845974 100644 --- a/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml @@ -71,6 +71,7 @@ nvm = false onewireio = false os = true paralleldisplaybus = false +pathlib = false ps2io = false pulseio = false pwmio = false diff --git a/ports/zephyr-cp/boards/nordic/nrf54l15dk/autogen_board_info.toml b/ports/zephyr-cp/boards/nordic/nrf54l15dk/autogen_board_info.toml index 07ab1f7f4dfc6..8896b7951a8df 100644 --- a/ports/zephyr-cp/boards/nordic/nrf54l15dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nordic/nrf54l15dk/autogen_board_info.toml @@ -71,6 +71,7 @@ nvm = false onewireio = false os = true paralleldisplaybus = false +pathlib = false ps2io = false pulseio = false pwmio = false diff --git a/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml b/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml index b10bbe69a0a10..e3bcf206899ec 100644 --- a/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml @@ -71,6 +71,7 @@ nvm = false onewireio = false os = true paralleldisplaybus = false +pathlib = false ps2io = false pulseio = false pwmio = false diff --git a/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml b/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml index fcfeec097d124..2f4fe6d493d9e 100644 --- a/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml @@ -71,6 +71,7 @@ nvm = false onewireio = false os = true paralleldisplaybus = false +pathlib = false ps2io = false pulseio = false pwmio = false diff --git a/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml b/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml index 69481e904fea4..376b5a43b0a48 100644 --- a/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml @@ -71,6 +71,7 @@ nvm = false onewireio = false os = true paralleldisplaybus = false +pathlib = false ps2io = false pulseio = false pwmio = false diff --git a/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml b/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml index e7e179e4c2cc9..57639f11cb3c7 100644 --- a/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml @@ -71,6 +71,7 @@ nvm = false onewireio = false os = true paralleldisplaybus = false +pathlib = false ps2io = false pulseio = false pwmio = false diff --git a/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml b/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml index d4fa229b7e67d..1b78f5432c4d3 100644 --- a/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml @@ -71,6 +71,7 @@ nvm = false onewireio = false os = true paralleldisplaybus = false +pathlib = false ps2io = false pulseio = false pwmio = false diff --git a/shared-bindings/pathlib/__init__.c b/shared-bindings/pathlib/__init__.c index d09ec51c42d79..badfd3863d63f 100644 --- a/shared-bindings/pathlib/__init__.c +++ b/shared-bindings/pathlib/__init__.c @@ -16,16 +16,9 @@ //| """Filesystem path operations""" //| -//| class Path: +//| def Path(path: Union[str, "PosixPath"]) -> "PosixPath": //| """Factory function that returns a new PosixPath.""" -//| -//| def __new__(cls, *args) -> PosixPath: -//| """Create a new Path object. -//| -//| :param args: Path components -//| :return: A new PosixPath object -//| """ -//| ... +//| ... //| /* Path is just an alias for PosixPath in CircuitPython */ diff --git a/shared-module/pathlib/PosixPath.c b/shared-module/pathlib/PosixPath.c index 189f0905ad0d9..a37a0f77f8581 100644 --- a/shared-module/pathlib/PosixPath.c +++ b/shared-module/pathlib/PosixPath.c @@ -119,12 +119,12 @@ mp_obj_t common_hal_pathlib_posixpath_parent(pathlib_posixpath_obj_t *self) { // No slash found, return "." if (last_slash == -1) { - return pathlib_posixpath_from_str(MP_OBJ_NEW_QSTR(MP_QSTR_dot)); + return pathlib_posixpath_from_str(MP_OBJ_NEW_QSTR(MP_QSTR__dot_)); } // Root directory if (last_slash == 0) { - return pathlib_posixpath_from_str(MP_OBJ_NEW_QSTR(MP_QSTR_slash)); + return pathlib_posixpath_from_str(MP_OBJ_NEW_QSTR(MP_QSTR__slash_)); } // Create parent path From 12377c9fde6692e5d78e651885af92bcb878fdf6 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Mon, 14 Apr 2025 13:21:09 -0700 Subject: [PATCH 3/4] Turn off codeop on two samd21 boards due to overflow --- ports/atmel-samd/boards/circuitbrains_basic_m0/mpconfigboard.mk | 1 + ports/atmel-samd/boards/feather_m0_supersized/mpconfigboard.mk | 2 ++ 2 files changed, 3 insertions(+) diff --git a/ports/atmel-samd/boards/circuitbrains_basic_m0/mpconfigboard.mk b/ports/atmel-samd/boards/circuitbrains_basic_m0/mpconfigboard.mk index c647039bc8aaf..7c301e3ea9692 100755 --- a/ports/atmel-samd/boards/circuitbrains_basic_m0/mpconfigboard.mk +++ b/ports/atmel-samd/boards/circuitbrains_basic_m0/mpconfigboard.mk @@ -10,4 +10,5 @@ SPI_FLASH_FILESYSTEM = 1 EXTERNAL_FLASH_DEVICES = "W25Q32JVxQ" LONGINT_IMPL = MPZ +CIRCUITPY_CODEOP = 0 CIRCUITPY_JPEGIO = 0 diff --git a/ports/atmel-samd/boards/feather_m0_supersized/mpconfigboard.mk b/ports/atmel-samd/boards/feather_m0_supersized/mpconfigboard.mk index 811336885b86d..573a69bee327c 100644 --- a/ports/atmel-samd/boards/feather_m0_supersized/mpconfigboard.mk +++ b/ports/atmel-samd/boards/feather_m0_supersized/mpconfigboard.mk @@ -9,3 +9,5 @@ CHIP_FAMILY = samd21 SPI_FLASH_FILESYSTEM = 1 EXTERNAL_FLASH_DEVICES = "S25FL064L" LONGINT_IMPL = MPZ + +CIRCUITPY_CODEOP = 0 From 98b4d7522ea4451496df8da02fda0896b3d0dd5e Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Tue, 15 Apr 2025 16:15:57 -0700 Subject: [PATCH 4/4] Keep our own cwd and resolve paths in `os` --- shared-bindings/os/__init__.h | 1 + shared-module/os/__init__.c | 96 ++++++++++++++++++++++++++++--- shared-module/os/__init__.h | 2 + shared-module/pathlib/PosixPath.c | 12 ++-- 4 files changed, 96 insertions(+), 15 deletions(-) diff --git a/shared-bindings/os/__init__.h b/shared-bindings/os/__init__.h index 56b643e2edfbe..3729f43d5b6b1 100644 --- a/shared-bindings/os/__init__.h +++ b/shared-bindings/os/__init__.h @@ -10,6 +10,7 @@ #include #include "py/objtuple.h" +#include "shared-module/os/__init__.h" void common_hal_os_chdir(const char *path); mp_obj_t common_hal_os_getcwd(void); diff --git a/shared-module/os/__init__.c b/shared-module/os/__init__.c index 25e8ba28a885a..432cada156919 100644 --- a/shared-module/os/__init__.c +++ b/shared-module/os/__init__.c @@ -6,6 +6,7 @@ // // SPDX-License-Identifier: MIT +#include #include #include "extmod/vfs.h" @@ -63,9 +64,73 @@ static mp_obj_t mp_vfs_proxy_call(mp_vfs_mount_t *vfs, qstr meth_name, size_t n_ return mp_call_method_n_kw(n_args, 0, meth); } +const char *common_hal_os_path_abspath(const char *path) { + const char *cwd; + if (path[0] == '/') { + cwd = ""; + } else { + cwd = MP_STATE_VM(cwd_path); + if (cwd == NULL) { + char *new_cwd = m_malloc(2); + strcpy(new_cwd, "/"); + MP_STATE_VM(cwd_path) = new_cwd; + cwd = new_cwd; + } + } + // Scan to see if the path has any `..` in it and return the same string if it doesn't + bool found_dot_dot = false; + size_t slash_count = 0; + for (size_t i = 0; i < strlen(path); i++) { + if (path[i] == '/') { + slash_count++; + } + if (i + 2 < strlen(path) && path[i] == '/' && path[i + 1] == '.' && path[i + 2] == '.' && (i + 3 == strlen(path) || path[i + 3] == '/')) { + found_dot_dot = true; + } + } + if (!found_dot_dot) { + return path; + } + + // Store the current output length for previous components so we can rewind to before them. + size_t slashes[slash_count]; + char *full_path = m_malloc(strlen(cwd) + strlen(path) + 1); + memcpy(full_path, cwd, strlen(cwd)); + memcpy(full_path + strlen(cwd), path, strlen(path) + 1); + size_t output_len = 0; + size_t component_len = 0; + slash_count = 0; + + // Remove `..` and `.` + size_t original_len = strlen(full_path); + for (size_t i = 0; i <= original_len; i++) { + full_path[output_len++] = full_path[i]; + // Treat the final nul character as a slash. + if (full_path[i] == '/' || full_path[i] == '\0') { + if (component_len == 1 && full_path[i - 1] == '.') { + // Remove the dot + output_len = slashes[slash_count - 1]; + } else if (component_len == 2 && full_path[i - 1] == '.' && full_path[i - 2] == '.') { + // Remove the double dot and the previous component if it exists + slash_count--; + output_len = slashes[slash_count - 1]; + } else { + slashes[slash_count] = output_len; + slash_count++; + } + component_len = 0; + } else { + component_len++; + } + } + full_path[output_len] = '\0'; + return full_path; +} + void common_hal_os_chdir(const char *path) { + MP_STATE_VM(cwd_path) = common_hal_os_path_abspath(path); mp_obj_t path_out; - mp_vfs_mount_t *vfs = lookup_dir_path(path, &path_out); + mp_vfs_mount_t *vfs = lookup_dir_path(MP_STATE_VM(cwd_path), &path_out); MP_STATE_VM(vfs_cur) = vfs; if (vfs == MP_VFS_ROOT) { // If we change to the root dir and a VFS is mounted at the root then @@ -84,12 +149,17 @@ void common_hal_os_chdir(const char *path) { } mp_obj_t common_hal_os_getcwd(void) { - return mp_vfs_getcwd(); + const char *cwd = MP_STATE_VM(cwd_path); + if (cwd == NULL) { + return MP_OBJ_NEW_QSTR(MP_QSTR__slash_); + } + return mp_obj_new_str_of_type(&mp_type_str, (const byte *)cwd, strlen(cwd)); } mp_obj_t common_hal_os_listdir(const char *path) { + const char *abspath = common_hal_os_path_abspath(path); mp_obj_t path_out; - mp_vfs_mount_t *vfs = lookup_dir_path(path, &path_out); + mp_vfs_mount_t *vfs = lookup_dir_path(abspath, &path_out); if (vfs == MP_VFS_ROOT) { vfs = MP_STATE_VM(vfs_mount_table); while (vfs != NULL) { @@ -114,8 +184,9 @@ mp_obj_t common_hal_os_listdir(const char *path) { } void common_hal_os_mkdir(const char *path) { + const char *abspath = common_hal_os_path_abspath(path); mp_obj_t path_out; - mp_vfs_mount_t *vfs = lookup_dir_path(path, &path_out); + mp_vfs_mount_t *vfs = lookup_dir_path(abspath, &path_out); if (vfs == MP_VFS_ROOT || (vfs != MP_VFS_NONE && !strcmp(mp_obj_str_get_str(path_out), "/"))) { mp_raise_OSError(MP_EEXIST); } @@ -123,8 +194,9 @@ void common_hal_os_mkdir(const char *path) { } void common_hal_os_remove(const char *path) { + const char *abspath = common_hal_os_path_abspath(path); mp_obj_t path_out; - mp_vfs_mount_t *vfs = lookup_path(path, &path_out); + mp_vfs_mount_t *vfs = lookup_path(abspath, &path_out); mp_vfs_proxy_call(vfs, MP_QSTR_remove, 1, &path_out); } @@ -140,14 +212,16 @@ void common_hal_os_rename(const char *old_path, const char *new_path) { } void common_hal_os_rmdir(const char *path) { + const char *abspath = common_hal_os_path_abspath(path); mp_obj_t path_out; - mp_vfs_mount_t *vfs = lookup_dir_path(path, &path_out); + mp_vfs_mount_t *vfs = lookup_dir_path(abspath, &path_out); mp_vfs_proxy_call(vfs, MP_QSTR_rmdir, 1, &path_out); } mp_obj_t common_hal_os_stat(const char *path) { + const char *abspath = common_hal_os_path_abspath(path); mp_obj_t path_out; - mp_vfs_mount_t *vfs = lookup_path(path, &path_out); + mp_vfs_mount_t *vfs = lookup_path(abspath, &path_out); if (vfs == MP_VFS_ROOT) { mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL)); t->items[0] = MP_OBJ_NEW_SMALL_INT(MP_S_IFDIR); // st_mode @@ -160,8 +234,9 @@ mp_obj_t common_hal_os_stat(const char *path) { } mp_obj_t common_hal_os_statvfs(const char *path) { + const char *abspath = common_hal_os_path_abspath(path); mp_obj_t path_out; - mp_vfs_mount_t *vfs = lookup_path(path, &path_out); + mp_vfs_mount_t *vfs = lookup_path(abspath, &path_out); if (vfs == MP_VFS_ROOT) { // statvfs called on the root directory, see if there's anything mounted there for (vfs = MP_STATE_VM(vfs_mount_table); vfs != NULL; vfs = vfs->next) { @@ -192,8 +267,11 @@ mp_obj_t common_hal_os_statvfs(const char *path) { } void common_hal_os_utime(const char *path, mp_obj_t times) { + const char *abspath = common_hal_os_path_abspath(path); mp_obj_t args[2]; - mp_vfs_mount_t *vfs = lookup_path(path, &args[0]); + mp_vfs_mount_t *vfs = lookup_path(abspath, &args[0]); args[1] = times; mp_vfs_proxy_call(vfs, MP_QSTR_utime, 2, args); } + +MP_REGISTER_ROOT_POINTER(const char *cwd_path); diff --git a/shared-module/os/__init__.h b/shared-module/os/__init__.h index be9c077b2006f..611a0edab6860 100644 --- a/shared-module/os/__init__.h +++ b/shared-module/os/__init__.h @@ -26,3 +26,5 @@ os_getenv_err_t common_hal_os_getenv_str(const char *key, char *value, size_t va // If any error code is returned, value is guaranteed not modified // An error that is not 'open' or 'not found' is printed on the repl. os_getenv_err_t common_hal_os_getenv_int(const char *key, mp_int_t *value); + +const char *common_hal_os_path_abspath(const char *path); diff --git a/shared-module/pathlib/PosixPath.c b/shared-module/pathlib/PosixPath.c index a37a0f77f8581..92e53ec6f8c3d 100644 --- a/shared-module/pathlib/PosixPath.c +++ b/shared-module/pathlib/PosixPath.c @@ -26,8 +26,7 @@ typedef struct _mp_obj_path_dir_iter_t { } mp_obj_path_dir_iter_t; mp_obj_t pathlib_posixpath_from_str(mp_obj_t path_str) { - pathlib_posixpath_obj_t *self = m_new_obj(pathlib_posixpath_obj_t); - self->base.type = &pathlib_posixpath_type; + pathlib_posixpath_obj_t *self = mp_obj_malloc(pathlib_posixpath_obj_t, &pathlib_posixpath_type); self->path_str = path_str; return MP_OBJ_FROM_PTR(self); } @@ -290,8 +289,10 @@ mp_obj_t common_hal_pathlib_posixpath_absolute(pathlib_posixpath_obj_t *self) { } mp_obj_t common_hal_pathlib_posixpath_resolve(pathlib_posixpath_obj_t *self) { - // For now, just call absolute() since CircuitPython doesn't support symlinks - return common_hal_pathlib_posixpath_absolute(self); + const char *path = mp_obj_str_get_str(self->path_str); + const char *abspath = common_hal_os_path_abspath(path); + mp_obj_t abspath_obj = mp_obj_new_str(abspath, strlen(abspath)); + return pathlib_posixpath_from_str(abspath_obj); } // Iterator iternext function - called for each iteration @@ -310,8 +311,7 @@ static mp_obj_t path_dir_iter_iternext(mp_obj_t self_in) { // Create a new path directory iterator mp_obj_t mp_obj_new_path_dir_iter(mp_obj_t path, mp_obj_t iter) { - mp_obj_path_dir_iter_t *o = m_new_obj(mp_obj_path_dir_iter_t); - o->base.type = &mp_type_polymorph_iter; + mp_obj_path_dir_iter_t *o = mp_obj_malloc(mp_obj_path_dir_iter_t, &mp_type_polymorph_iter); o->iternext = path_dir_iter_iternext; o->path = path; o->iter = iter;