-
-
Notifications
You must be signed in to change notification settings - Fork 8.2k
py/builtinimport: Allow overriding of mp_builtin___import__. #9026
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
Conversation
Codecov Report
@@ Coverage Diff @@
## master #9026 +/- ##
=======================================
Coverage 98.37% 98.37%
=======================================
Files 156 156
Lines 20281 20281
=======================================
Hits 19951 19951
Misses 330 330
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. |
If a port wants to provide "custom" imports the idea is for it to enable But it looks like you actually want more sophisticated import behaviour, to support built-in packages. #5701 intended to do this but was closed. Maybe it needs to be revisited? Or even the patch you link above could be merged here? I'm not against being able to override |
Our main resource constraint is build size. On the minimal port,
The current PR is motivated by adding another (future) customization inspired by #8381, so that's why I started looking for a more generic way to customize imports. If adding that gets us closer in terms of build size, maybe we'll just enable external imports though.
The patch we used served us well over the past years but I wasn't sure if it was generic enough to be useful in MicroPython.
Thanks, I'll have a look at how that works. Another option would be to add a hook that gets called just prior to raising the Like this, for example: diff --git a/py/builtin.h b/py/builtin.h
index a6f824ca2..398470bef 100644
--- a/py/builtin.h
+++ b/py/builtin.h
@@ -65,6 +65,13 @@ MP_DECLARE_CONST_FUN_OBJ_KW(mp_builtin_open_obj);
#endif
mp_obj_t mp_builtin___import__(size_t n_args, const mp_obj_t *args);
+
+// A port can extend mp_builtin___import__ to handle cases where builtin import
+// can't find the requested module.
+#if MICROPY_MODULE_BUILTIN_IMPORT_EXTRA
+mp_obj_t mp_builtin_import_extra(size_t n_args, const mp_obj_t *args);
+#endif
+
mp_obj_t mp_micropython_mem_info(size_t n_args, const mp_obj_t *args);
MP_DECLARE_CONST_FUN_OBJ_VAR(mp_builtin___build_class___obj);
diff --git a/py/builtinimport.c b/py/builtinimport.c
index cd9636ccd..c6d601144 100644
--- a/py/builtinimport.c
+++ b/py/builtinimport.c
@@ -591,6 +591,14 @@ mp_obj_t mp_builtin___import__(size_t n_args, const mp_obj_t *args) {
}
#endif
+ #if MICROPY_MODULE_BUILTIN_IMPORT_EXTRA
+ // Call port-specific import extension.
+ module_obj = mp_builtin_import_extra(n_args, args);
+ if (module_obj != MP_OBJ_NULL) {
+ return module_obj;
+ }
+ #endif
+
// Couldn't find the module, so fail
#if MICROPY_ERROR_REPORTING <= MICROPY_ERROR_REPORTING_TERSE
mp_raise_msg(&mp_type_ImportError, MP_ERROR_TEXT("module not found"));
diff --git a/py/mpconfig.h b/py/mpconfig.h
index d70d39ae9..7a20b47c9 100644
--- a/py/mpconfig.h
+++ b/py/mpconfig.h
@@ -834,6 +834,12 @@ typedef double mp_float_t;
#define MICROPY_MODULE_BUILTIN_INIT (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES)
#endif
+// Hook to run port-specific import handler if standard builtin import fails.
+// This is only used if MICROPY_ENABLE_EXTERNAL_IMPORT is disabled.
+#ifndef MICROPY_MODULE_BUILTIN_IMPORT_EXTRA
+#define MICROPY_MODULE_BUILTIN_IMPORT_EXTRA (0)
+#endif
+
// Whether to support module-level __getattr__ (see PEP 562)
#ifndef MICROPY_MODULE_GETATTR
#define MICROPY_MODULE_GETATTR (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES)
|
If done this way then for it to be general there would need to be a hook at the start and end of the import function. I think best to allow the port to override Eg have a |
c0ac815
to
9a0e2d9
Compare
If I'm understanding this right, this is now done with the updated version. Just let me know if you intended something else. This approach works fine for our use case as well. Being more general, it can now be used even if external imports are enabled (even if that isn't all that useful).
One minor downside to this approach (compared to the hook approach mentioned above) is that if you just want to extend the default behavior, you have to catch the exception raised by the default handler before you can proceed. But this is not much of a downside if external imports are disabled (which is the main use case anyway), because then |
mp_obj_t mp_builtin___import__(size_t n_args, const mp_obj_t *args); | ||
#else | ||
#define mp_builtin___import__ mp_builtin___import___default |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please use a static inline definition here, it's more robust than a #define
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done and rebased. It does increase the code size by a few bytes (+4 on minimal embedded port).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah! So the reason is because of this line:
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin___import___obj, 1, 5, mp_builtin___import__);
which forces both the default and static-inline wrapper function to be linked in to the firmware (instead of just the default one).
I guess we need to go back to using the #define
to make it efficient...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I was wondering about that. I reverted it and rebased.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry to go back and forth on this.... We don't really have a consistent way to override an arbitrary function in the core, but there is some precedence set by py/mphal.h
. Following the way it does things, we could do the following here:
#ifndef mp_builtin___import__
#define mp_builtin___import__ mp_builtin___import___default
#endif
That would be pretty clean, and doesn't require an entry in py/mpconfig.h
. It's also how ports/stm32/boardctrl.h
does things.
Then in your port you do:
mp_obj_t custom_import(size_t n_args, const mp_obj_t *args);
#define mp_builtin___import__ custom_import
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's one of the first approaches I tried, but I got a bit stuck on where to do this exactly.
I'm able to make this work following the same mphal
approach (the ifndef
in py/mphal.h
and custom_import
in mphalport.h
), but I figured this one doesn't belong there since it doesn't concern hardware.
It seems like the custom implementation would be provided via mpconfigport.h
, but I'm not sure how since the declaration relies on mp_obj_t
which isn't defined at that point.
What would be the right places to put the suggested snippets above?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should go here in py/builtin.h
.
How about this:
#ifndef mp_builtin___import__
#define mp_builtin___import__ mp_builtin___import___default
#endif
mp_obj_t mp_builtin___import__(size_t n_args, const mp_obj_t *args);
mp_obj_t mp_builtin___import__default(size_t n_args, const mp_obj_t *args);
?
It's a bit tricky but should work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, that works! Updated and rebased.
I think the approach here is fine, it should be rare that a port wants to override this import behaviour. |
167f980
to
10a752f
Compare
10a752f
to
ac16032
Compare
ac16032
to
68e7455
Compare
This allows ports to override mp_builtin___import__. This can be useful in MicroPython applications where MICROPY_ENABLE_EXTERNAL_IMPORT has to be disabled due to its impact on build size (2% to 2.5% of the minimal port). By overriding the otherwise very minimal mp_builtin___import__, ports can still allow limited forms of application-specific imports. Signed-off-by: Laurens Valk <laurens@pybricks.com>
68e7455
to
aa76e8a
Compare
Thanks for updating. Merged in d8ad878 |
It is likely due to the multiple heap setup on ESP32. An allocation fails into one heap because the size is too large but caused an assertion instead of failing over to the next heap. Fixes micropython#9026
This allows MicroPython ports without external imports enabled to override mp_builtin___import__.
This is useful for MicroPython applications without enough resources to have
MICROPY_ENABLE_EXTERNAL_IMPORT
enabled, but which still want to allow users to do some application-specific imports beyond builtins.Ports can already provide things like
mp_builtin_open
, so providingmp_builtin___import__
seems to fit reasonably well.Instead of doing it this way, it could be made more generic by changing
MICROPY_ENABLE_EXTERNAL_IMPORT
to a configuration settingMICROPY_ENABLE_IMPORT
with three options, such as:MICROPY_ENABLE_IMPORT_EXTERNAL
,MICROPY_ENABLE_IMPORT_BUILTIN_ONLY
, andMICROPY_ENABLE_IMPORT_CUSTOM
.Context: We are currently overriding it in MicroPython for Pybricks. I was considering adding certain new functionality (inspired by #8381), but overriding it locally without forking would be a bit cleaner and easier to maintain.