py/modsys: Allow sys.path to be assigned to.

Previously sys.path could be modified by append/pop or slice assignment.

This allows `sys.path = [...]`, which can be simpler in many cases, but
also improves CPython compatibility.

It also allows sys.path to be set to a tuple which means that you can
clear sys.path (e.g. temporarily) with no allocations.

This also makes sys.path (and sys.argv for consistency) able to be disabled
via mpconfig. The unix port (and upytesthelper) require them, so they
explicitly verify that they're enabled.

This work was funded through GitHub Sponsors.

Signed-off-by: Jim Mussared <jim.mussared@gmail.com>
This commit is contained in:
Jim Mussared
2023-06-05 16:52:29 +10:00
parent 7d2ee8aed0
commit 5e50975a6d
9 changed files with 93 additions and 42 deletions

View File

@@ -69,6 +69,14 @@ long heap_size = 1024 * 1024 * (sizeof(mp_uint_t) / 4);
#define MICROPY_GC_SPLIT_HEAP_N_HEAPS (1)
#endif
#if !MICROPY_PY_SYS_PATH
#error "The unix port requires MICROPY_PY_SYS_PATH=1"
#endif
#if !MICROPY_PY_SYS_ARGV
#error "The unix port requires MICROPY_PY_SYS_ARGV=1"
#endif
STATIC void stderr_print_strn(void *env, const char *str, size_t len) {
(void)env;
ssize_t ret;
@@ -538,44 +546,40 @@ MP_NOINLINE int main_(int argc, char **argv) {
}
#endif
char *home = getenv("HOME");
char *path = getenv("MICROPYPATH");
if (path == NULL) {
path = MICROPY_PY_SYS_PATH_DEFAULT;
}
size_t path_num = 1; // [0] is for current dir (or base dir of the script)
if (*path == PATHLIST_SEP_CHAR) {
path_num++;
}
for (char *p = path; p != NULL; p = strchr(p, PATHLIST_SEP_CHAR)) {
path_num++;
if (p != NULL) {
p++;
}
}
mp_obj_list_init(MP_OBJ_TO_PTR(mp_sys_path), path_num);
mp_obj_t *path_items;
mp_obj_list_get(mp_sys_path, &path_num, &path_items);
path_items[0] = MP_OBJ_NEW_QSTR(MP_QSTR_);
{
char *p = path;
for (mp_uint_t i = 1; i < path_num; i++) {
char *p1 = strchr(p, PATHLIST_SEP_CHAR);
if (p1 == NULL) {
p1 = p + strlen(p);
// sys.path starts as [""]
mp_sys_path = mp_obj_new_list(0, NULL);
mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR_));
// Add colon-separated entries from MICROPYPATH.
char *home = getenv("HOME");
char *path = getenv("MICROPYPATH");
if (path == NULL) {
path = MICROPY_PY_SYS_PATH_DEFAULT;
}
if (*path == PATHLIST_SEP_CHAR) {
// First entry is empty. We've already added an empty entry to sys.path, so skip it.
++path;
}
bool path_remaining = *path;
while (path_remaining) {
char *path_entry_end = strchr(path, PATHLIST_SEP_CHAR);
if (path_entry_end == NULL) {
path_entry_end = path + strlen(path);
path_remaining = false;
}
if (p[0] == '~' && p[1] == '/' && home != NULL) {
if (path[0] == '~' && path[1] == '/' && home != NULL) {
// Expand standalone ~ to $HOME
int home_l = strlen(home);
vstr_t vstr;
vstr_init(&vstr, home_l + (p1 - p - 1) + 1);
vstr_init(&vstr, home_l + (path_entry_end - path - 1) + 1);
vstr_add_strn(&vstr, home, home_l);
vstr_add_strn(&vstr, p + 1, p1 - p - 1);
path_items[i] = mp_obj_new_str_from_vstr(&vstr);
vstr_add_strn(&vstr, path + 1, path_entry_end - path - 1);
mp_obj_list_append(mp_sys_path, mp_obj_new_str_from_vstr(&vstr));
} else {
path_items[i] = mp_obj_new_str_via_qstr(p, p1 - p);
mp_obj_list_append(mp_sys_path, mp_obj_new_str_via_qstr(path, path_entry_end - path));
}
p = p1 + 1;
path = path_entry_end + 1;
}
}
@@ -710,7 +714,7 @@ MP_NOINLINE int main_(int argc, char **argv) {
// Set base dir of the script as first entry in sys.path.
char *p = strrchr(basedir, '/');
path_items[0] = mp_obj_new_str_via_qstr(basedir, p - basedir);
mp_obj_list_store(mp_sys_path, MP_OBJ_NEW_SMALL_INT(0), mp_obj_new_str_via_qstr(basedir, p - basedir));
free(pathbuf);
set_sys_argv(argv, argc, a);

View File

@@ -154,7 +154,7 @@ typedef long mp_off_t;
// Ensure builtinimport.c works with -m.
#define MICROPY_MODULE_OVERRIDE_MAIN_IMPORT (1)
// Don't default sys.argv because we do that in main.
// Don't default sys.argv and sys.path because we do that in main.
#define MICROPY_PY_SYS_PATH_ARGV_DEFAULTS (0)
// Enable sys.executable.