Skip to content

Commit 5e50975

Browse files
committed
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>
1 parent 7d2ee8a commit 5e50975

9 files changed

Lines changed: 93 additions & 42 deletions

File tree

ports/unix/main.c

Lines changed: 35 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,14 @@ long heap_size = 1024 * 1024 * (sizeof(mp_uint_t) / 4);
6969
#define MICROPY_GC_SPLIT_HEAP_N_HEAPS (1)
7070
#endif
7171

72+
#if !MICROPY_PY_SYS_PATH
73+
#error "The unix port requires MICROPY_PY_SYS_PATH=1"
74+
#endif
75+
76+
#if !MICROPY_PY_SYS_ARGV
77+
#error "The unix port requires MICROPY_PY_SYS_ARGV=1"
78+
#endif
79+
7280
STATIC void stderr_print_strn(void *env, const char *str, size_t len) {
7381
(void)env;
7482
ssize_t ret;
@@ -538,44 +546,40 @@ MP_NOINLINE int main_(int argc, char **argv) {
538546
}
539547
#endif
540548

541-
char *home = getenv("HOME");
542-
char *path = getenv("MICROPYPATH");
543-
if (path == NULL) {
544-
path = MICROPY_PY_SYS_PATH_DEFAULT;
545-
}
546-
size_t path_num = 1; // [0] is for current dir (or base dir of the script)
547-
if (*path == PATHLIST_SEP_CHAR) {
548-
path_num++;
549-
}
550-
for (char *p = path; p != NULL; p = strchr(p, PATHLIST_SEP_CHAR)) {
551-
path_num++;
552-
if (p != NULL) {
553-
p++;
554-
}
555-
}
556-
mp_obj_list_init(MP_OBJ_TO_PTR(mp_sys_path), path_num);
557-
mp_obj_t *path_items;
558-
mp_obj_list_get(mp_sys_path, &path_num, &path_items);
559-
path_items[0] = MP_OBJ_NEW_QSTR(MP_QSTR_);
560549
{
561-
char *p = path;
562-
for (mp_uint_t i = 1; i < path_num; i++) {
563-
char *p1 = strchr(p, PATHLIST_SEP_CHAR);
564-
if (p1 == NULL) {
565-
p1 = p + strlen(p);
550+
// sys.path starts as [""]
551+
mp_sys_path = mp_obj_new_list(0, NULL);
552+
mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR_));
553+
554+
// Add colon-separated entries from MICROPYPATH.
555+
char *home = getenv("HOME");
556+
char *path = getenv("MICROPYPATH");
557+
if (path == NULL) {
558+
path = MICROPY_PY_SYS_PATH_DEFAULT;
559+
}
560+
if (*path == PATHLIST_SEP_CHAR) {
561+
// First entry is empty. We've already added an empty entry to sys.path, so skip it.
562+
++path;
563+
}
564+
bool path_remaining = *path;
565+
while (path_remaining) {
566+
char *path_entry_end = strchr(path, PATHLIST_SEP_CHAR);
567+
if (path_entry_end == NULL) {
568+
path_entry_end = path + strlen(path);
569+
path_remaining = false;
566570
}
567-
if (p[0] == '~' && p[1] == '/' && home != NULL) {
571+
if (path[0] == '~' && path[1] == '/' && home != NULL) {
568572
// Expand standalone ~ to $HOME
569573
int home_l = strlen(home);
570574
vstr_t vstr;
571-
vstr_init(&vstr, home_l + (p1 - p - 1) + 1);
575+
vstr_init(&vstr, home_l + (path_entry_end - path - 1) + 1);
572576
vstr_add_strn(&vstr, home, home_l);
573-
vstr_add_strn(&vstr, p + 1, p1 - p - 1);
574-
path_items[i] = mp_obj_new_str_from_vstr(&vstr);
577+
vstr_add_strn(&vstr, path + 1, path_entry_end - path - 1);
578+
mp_obj_list_append(mp_sys_path, mp_obj_new_str_from_vstr(&vstr));
575579
} else {
576-
path_items[i] = mp_obj_new_str_via_qstr(p, p1 - p);
580+
mp_obj_list_append(mp_sys_path, mp_obj_new_str_via_qstr(path, path_entry_end - path));
577581
}
578-
p = p1 + 1;
582+
path = path_entry_end + 1;
579583
}
580584
}
581585

@@ -710,7 +714,7 @@ MP_NOINLINE int main_(int argc, char **argv) {
710714

711715
// Set base dir of the script as first entry in sys.path.
712716
char *p = strrchr(basedir, '/');
713-
path_items[0] = mp_obj_new_str_via_qstr(basedir, p - basedir);
717+
mp_obj_list_store(mp_sys_path, MP_OBJ_NEW_SMALL_INT(0), mp_obj_new_str_via_qstr(basedir, p - basedir));
714718
free(pathbuf);
715719

716720
set_sys_argv(argv, argc, a);

ports/unix/mpconfigport.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ typedef long mp_off_t;
154154
// Ensure builtinimport.c works with -m.
155155
#define MICROPY_MODULE_OVERRIDE_MAIN_IMPORT (1)
156156

157-
// Don't default sys.argv because we do that in main.
157+
// Don't default sys.argv and sys.path because we do that in main.
158158
#define MICROPY_PY_SYS_PATH_ARGV_DEFAULTS (0)
159159

160160
// Enable sys.executable.

py/builtinimport.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ STATIC mp_import_stat_t stat_top_level(qstr mod_name, vstr_t *dest) {
118118
#if MICROPY_PY_SYS
119119
size_t path_num;
120120
mp_obj_t *path_items;
121-
mp_obj_list_get(mp_sys_path, &path_num, &path_items);
121+
mp_obj_get_array(mp_sys_path, &path_num, &path_items);
122122

123123
// go through each sys.path entry, trying to import "<entry>/<mod_name>".
124124
for (size_t i = 0; i < path_num; i++) {
@@ -365,7 +365,7 @@ STATIC mp_obj_t process_import_at_level(qstr full_mod_name, qstr level_mod_name,
365365
// which may have come from the filesystem.
366366
size_t path_num;
367367
mp_obj_t *path_items;
368-
mp_obj_list_get(mp_sys_path, &path_num, &path_items);
368+
mp_obj_get_array(mp_sys_path, &path_num, &path_items);
369369
if (path_num)
370370
#endif
371371
{

py/modsys.c

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,9 @@ STATIC mp_obj_t mp_sys_settrace(mp_obj_t obj) {
195195
MP_DEFINE_CONST_FUN_OBJ_1(mp_sys_settrace_obj, mp_sys_settrace);
196196
#endif // MICROPY_PY_SYS_SETTRACE
197197

198+
#if MICROPY_PY_SYS_PATH && !MICROPY_PY_SYS_ATTR_DELEGATION
199+
#error "MICROPY_PY_SYS_PATH requires MICROPY_PY_SYS_ATTR_DELEGATION"
200+
#endif
198201

199202
#if MICROPY_PY_SYS_PS1_PS2 && !MICROPY_PY_SYS_ATTR_DELEGATION
200203
#error "MICROPY_PY_SYS_PS1_PS2 requires MICROPY_PY_SYS_ATTR_DELEGATION"
@@ -211,6 +214,11 @@ MP_DEFINE_CONST_FUN_OBJ_1(mp_sys_settrace_obj, mp_sys_settrace);
211214
#if MICROPY_PY_SYS_ATTR_DELEGATION
212215
// Must be kept in sync with the enum at the top of mpstate.h.
213216
STATIC const uint16_t sys_mutable_keys[] = {
217+
#if MICROPY_PY_SYS_PATH
218+
// Code should access this (as an mp_obj_t) for use with e.g.
219+
// mp_obj_list_append by using the `mp_sys_path` macro defined in runtime.h.
220+
MP_QSTR_path,
221+
#endif
214222
#if MICROPY_PY_SYS_PS1_PS2
215223
MP_QSTR_ps1,
216224
MP_QSTR_ps2,
@@ -231,8 +239,9 @@ void mp_module_sys_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
231239
STATIC const mp_rom_map_elem_t mp_module_sys_globals_table[] = {
232240
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_sys) },
233241

234-
{ MP_ROM_QSTR(MP_QSTR_path), MP_ROM_PTR(&MP_STATE_VM(mp_sys_path_obj)) },
242+
#if MICROPY_PY_SYS_ARGV
235243
{ MP_ROM_QSTR(MP_QSTR_argv), MP_ROM_PTR(&MP_STATE_VM(mp_sys_argv_obj)) },
244+
#endif
236245
{ MP_ROM_QSTR(MP_QSTR_version), MP_ROM_PTR(&mp_sys_version_obj) },
237246
{ MP_ROM_QSTR(MP_QSTR_version_info), MP_ROM_PTR(&mp_sys_version_info_obj) },
238247
{ MP_ROM_QSTR(MP_QSTR_implementation), MP_ROM_PTR(&mp_sys_implementation_obj) },
@@ -308,10 +317,11 @@ const mp_obj_module_t mp_module_sys = {
308317
// available.
309318
MP_REGISTER_MODULE(MP_QSTR_sys, mp_module_sys);
310319

311-
// If MICROPY_PY_SYS_PATH_ARGV_DEFAULTS is not enabled then these two lists
312-
// must be initialised after the call to mp_init.
313-
MP_REGISTER_ROOT_POINTER(mp_obj_list_t mp_sys_path_obj);
320+
#if MICROPY_PY_SYS_ARGV
321+
// Code should access this (as an mp_obj_t) for use with e.g.
322+
// mp_obj_list_append by using the `mp_sys_argv` macro defined in runtime.h.
314323
MP_REGISTER_ROOT_POINTER(mp_obj_list_t mp_sys_argv_obj);
324+
#endif
315325

316326
#if MICROPY_PY_SYS_EXC_INFO
317327
// current exception being handled, for sys.exc_info()

py/mpconfig.h

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1421,6 +1421,23 @@ typedef double mp_float_t;
14211421
#define MICROPY_PY_SYS_ATEXIT (0)
14221422
#endif
14231423

1424+
// Whether to provide the "sys.path" attribute (which forces module delegation
1425+
// and mutable sys attributes to be enabled).
1426+
// If MICROPY_PY_SYS_PATH_ARGV_DEFAULTS is enabled, this is initialised in
1427+
// mp_init to an empty list. Otherwise the port must initialise it using
1428+
// `mp_sys_path = mp_obj_new_list(...)`.
1429+
#ifndef MICROPY_PY_SYS_PATH
1430+
#define MICROPY_PY_SYS_PATH (1)
1431+
#endif
1432+
1433+
// Whether to provide the "sys.argv" attribute.
1434+
// If MICROPY_PY_SYS_PATH_ARGV_DEFAULTS is enabled, this is initialised in
1435+
// mp_init to an empty list. Otherwise the port must initialise it using
1436+
// `mp_obj_list_init(MP_OBJ_TO_PTR(mp_sys_argv), ...);`
1437+
#ifndef MICROPY_PY_SYS_ARGV
1438+
#define MICROPY_PY_SYS_ARGV (1)
1439+
#endif
1440+
14241441
// Whether to provide sys.{ps1,ps2} mutable attributes, to control REPL prompts
14251442
#ifndef MICROPY_PY_SYS_PS1_PS2
14261443
#define MICROPY_PY_SYS_PS1_PS2 (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES)
@@ -1455,7 +1472,7 @@ typedef double mp_float_t;
14551472
// Whether the sys module supports attribute delegation
14561473
// This is enabled automatically when needed by other features
14571474
#ifndef MICROPY_PY_SYS_ATTR_DELEGATION
1458-
#define MICROPY_PY_SYS_ATTR_DELEGATION (MICROPY_PY_SYS_PS1_PS2 || MICROPY_PY_SYS_TRACEBACKLIMIT)
1475+
#define MICROPY_PY_SYS_ATTR_DELEGATION (MICROPY_PY_SYS_PATH || MICROPY_PY_SYS_PS1_PS2 || MICROPY_PY_SYS_TRACEBACKLIMIT)
14591476
#endif
14601477

14611478
// Whether to provide "errno" module

py/mpstate.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@
4343
#if MICROPY_PY_SYS_ATTR_DELEGATION
4444
// Must be kept in sync with sys_mutable_keys in modsys.c.
4545
enum {
46+
#if MICROPY_PY_SYS_PATH
47+
MP_SYS_MUTABLE_PATH,
48+
#endif
4649
#if MICROPY_PY_SYS_PS1_PS2
4750
MP_SYS_MUTABLE_PS1,
4851
MP_SYS_MUTABLE_PS2,

py/runtime.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,13 +136,17 @@ void mp_init(void) {
136136
#endif
137137

138138
#if MICROPY_PY_SYS_PATH_ARGV_DEFAULTS
139-
mp_obj_list_init(MP_OBJ_TO_PTR(mp_sys_path), 0);
139+
#if MICROPY_PY_SYS_PATH
140+
mp_sys_path = mp_obj_new_list(0, NULL);
140141
mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR_)); // current dir (or base dir of the script)
141142
#if MICROPY_MODULE_FROZEN
142143
mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR__dot_frozen));
143144
#endif
145+
#endif
146+
#if MICROPY_PY_SYS_ARGV
144147
mp_obj_list_init(MP_OBJ_TO_PTR(mp_sys_argv), 0);
145148
#endif
149+
#endif // MICROPY_PY_SYS_PATH_ARGV_DEFAULTS
146150

147151
#if MICROPY_PY_SYS_ATEXIT
148152
MP_STATE_VM(sys_exitfunc) = mp_const_none;

py/runtime.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,8 +227,13 @@ int mp_native_type_from_qstr(qstr qst);
227227
mp_uint_t mp_native_from_obj(mp_obj_t obj, mp_uint_t type);
228228
mp_obj_t mp_native_to_obj(mp_uint_t val, mp_uint_t type);
229229

230-
#define mp_sys_path (MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_sys_path_obj)))
230+
#if MICROPY_PY_SYS_PATH
231+
#define mp_sys_path (MP_STATE_VM(sys_mutable[MP_SYS_MUTABLE_PATH]))
232+
#endif
233+
234+
#if MICROPY_PY_SYS_ARGV
231235
#define mp_sys_argv (MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_sys_argv_obj)))
236+
#endif
232237

233238
#if MICROPY_WARNINGS
234239
#ifndef mp_warning

shared/upytesthelper/upytesthelper.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@
3131
#include "py/compile.h"
3232
#include "upytesthelper.h"
3333

34+
#if !MICROPY_PY_SYS_PATH
35+
#error "upytesthelper requires MICROPY_PY_SYS_PATH=1"
36+
#endif
37+
38+
#if !MICROPY_PY_SYS_ARGV
39+
#error "upytesthelper requires MICROPY_PY_SYS_ARGV=1"
40+
#endif
41+
3442
static const char *test_exp_output;
3543
static int test_exp_output_len, test_rem_output_len;
3644
static int test_failed;
@@ -93,7 +101,7 @@ void upytest_execute_test(const char *src) {
93101
// reinitialized before running each.
94102
gc_init(heap_start, heap_end);
95103
mp_init();
96-
mp_obj_list_init(mp_sys_path, 0);
104+
mp_sys_path = mp_obj_new_list(0, NULL);
97105
#if MICROPY_MODULE_FROZEN
98106
mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR__dot_frozen));
99107
#endif

0 commit comments

Comments
 (0)