-
-
Notifications
You must be signed in to change notification settings - Fork 8.2k
Implement a module system for external C modules. #3871
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
73f1b12
to
7813846
Compare
Pretty neat but perhaps make the modules directory configurable? |
You mean like |
Yeah something like that. So modules kan be kept out of the source dir. You also wouldn't need the .gitignore file and modules.md could go in the root dir or so. |
py/mkrules.mk
Outdated
@@ -105,7 +105,7 @@ endif | |||
ifneq ($(FROZEN_MPY_DIR),) | |||
# to build the MicroPython cross compiler | |||
$(TOP)/mpy-cross/mpy-cross: $(TOP)/py/*.[ch] $(TOP)/mpy-cross/*.[ch] $(TOP)/ports/windows/fmode.c | |||
$(Q)$(MAKE) -C $(TOP)/mpy-cross | |||
$(Q)$(MAKE) -C $(TOP)/mpy-cross MODULE_DIR= |
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.
Note: this is required to get mpy-cross
to work. The cross compiler won't need these custom modules anyway.
EDIT: probably not as necessary now as it was before, due to other changes.
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.
I think it's still necessary, in case your modules use features that are not enabled (or different) in the cross compiler.
57bdf3c
to
f10fe9c
Compare
I've updated the PR with the option of setting |
b9fa52c
to
a7e2885
Compare
py/mkrules.mk
Outdated
@@ -105,7 +105,7 @@ endif | |||
ifneq ($(FROZEN_MPY_DIR),) | |||
# to build the MicroPython cross compiler | |||
$(TOP)/mpy-cross/mpy-cross: $(TOP)/py/*.[ch] $(TOP)/mpy-cross/*.[ch] $(TOP)/ports/windows/fmode.c | |||
$(Q)$(MAKE) -C $(TOP)/mpy-cross | |||
$(Q)$(MAKE) -C $(TOP)/mpy-cross MODULE_DIR= |
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.
I think it's still necessary, in case your modules use features that are not enabled (or different) in the cross compiler.
tools/gen-modules.py
Outdated
def update_modules(path): | ||
modules = [] | ||
for module in sorted(os.listdir(path)): | ||
if not os.path.isfile('%s/%s/module.mk' % (path, module)): |
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.
So, this puts a restriction on the directory structure, in that your module name must match the directory it is in. It's probably worth documenting this fact.
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.
So, this puts a restriction on the directory structure, in that your module name must match the directory it is in. It's probably worth documenting this fact.
Good point, I've added it to the docs.
tools/gen-modules.py
Outdated
# Print header file for all external modules. | ||
print('// Automatically generated by genmodules.py.\n') | ||
for module in modules: | ||
print('extern const struct _mp_obj_module_t %s_module;' % module) |
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.
This also puts a restriction: the module struct in your .c file must be <name>_module
. That's worth documenting.
Also, to avoid possible clash with other names, it's probably worth making this name more descriptive, like <name>_user_cmodule
.
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 in the new commit.
py/mpconfig.h
Outdated
// Enable extra modules. This will be set by py/py.mk, don't set it in a | ||
// config header file. | ||
#ifndef MICROPY_HAS_EXTRA_MODULES | ||
#define MICROPY_HAS_EXTRA_MODULES (0) |
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.
Instead of this config option, you might be able to get away with just using:
#ifndef MICROPY_EXTRA_BUILTIN_MODULES
#define MICROPY_EXTRA_BUILTIN_MODULES
#endif
like it's done with MICROPY_PORT_BUILTIN_MODULES
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.
Sadly that isn't directly possible due to a chicken-and-egg problem at the top of objmodule.c so I've left it in place. It would be nice to get rid of it, however.
A way to do that is by always generating genhdr/modules.h
(even if empty). I'm not sure what the better option is.
py/py.mk
Outdated
@@ -103,6 +103,13 @@ $(BUILD)/$(BTREE_DIR)/%.o: CFLAGS += -Wno-old-style-definition -Wno-sign-compare | |||
$(BUILD)/extmod/modbtree.o: CFLAGS += $(BTREE_DEFS) | |||
endif | |||
|
|||
# External modules written in C. | |||
ifneq ($(MODULE_DIR),) | |||
CFLAGS_MOD += -DMICROPY_HAS_EXTRA_MODULES |
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.
This line won't be necessary if you get rid of MICROPY_HAS_EXTRA_MODULES
(as discussed above).
Thanks for the contribution, it does look pretty neat. I didn't really come across the need for this so far, but I can see how it would be useful for users to easily build in their own C modules.
Well, I hope that we can eventually get to adding dynamic native code support, so this wouldn't be a replacement for that feature. |
I'd rather keep the root directory as clean as possible, so don't know if adding MODULES.md there is the way to go. It's a bit long to add to the root README.md, maybe it can go in tools (and referenced from the main README.md so it can be found)? Or add a simple example of extending the unix port with custom modules in a new dir in examples/? Or even put it in the docs? I think the feature itself should be given a more unique name, rather than the generic name of "modules". I think "C" should be in the name to make it explicit that it's allowing additional C modules. And they are modules added by the user, so maybe |
I have updated this PR in several ways:
The biggest (non-name change) is moving the documentation to the actual MicroPython docs, in the section titled "The MicroPython language". The documentation should now also be a bit more helpful. I've pushed this as a separate commit for easier diff-viewing, but feel free to squash as needed (or I can do that as well). |
I've needed it from time to time, mostly because the nrf targets are very tiny so won't include less commonly used modules like
Yeah, that would be nice. I think both have their uses. Dynamically loadable modules may be easier to use while this PR is more resource-efficient and easier to implement. |
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 for updating. It looks good, just a few more minor things.
docs/reference/cmodules.rst
Outdated
|
||
$ make axtls | ||
$ make USER_C_MODULES=path-to-modules-folder | ||
$ make deploy |
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.
You don't need an explicit make axtls
because that should be done automatically. And with make deploy
it's wise to keep the extra make options there, like make USER_C_MODULES=path-to-modules-folder deploy
. This is in case the make runs (part of) the build again before deploying, you don't want it to use different CFLAGS for certain parts of the build.
Instead of typing this long make command over and over again (and sometimes forgetting), it's easier to create a local makefile
or GNUmakefile
with your custom settings and then include the MicroPython Makefile
.
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.
Note that if you don't have an explicit make axtls
then these instructions become generic for any 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.
You don't need an explicit make axtls because that should be done automatically
It's only done automatically in few (one?) ports. (E.g. not done in unix 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.
I've removed the ESP8266-specific parts. The only goal of it was showing how the make flag works (for those not that familiar with make), not as a guide for how to deploy.
It may be useful to write something about make flags and GNUmakefile
files in a compilation guide, but I think that's outside of the scope of this PR.
It's only done automatically in few (one?) ports. (E.g. not done in unix port).
Not in the ESP8266 port either, while I seem to remember it worked before. It would be nice if this were fixed, though. I have so many times run make
just to discover I had to run make axtls
first. Again, that's outside the scope of this PR.
docs/reference/cmodules.rst
Outdated
|
||
To build such a module, compile MicroPython (see `getting started | ||
<https://github.com/micropython/micropython/wiki/Getting-Started>`_) with an | ||
extra ``make`` flag named ``USER_C_MODULES`` to the directory containing all |
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.
... named USER_C_MODULES
set to the ...
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.
Good catch. Fixed.
py/py.mk
Outdated
CFLAGS_MOD += -DMICROPY_HAS_EXTRA_MODULES | ||
include $(USER_C_MODULES)/*/micropython.mk | ||
SRC_QSTR += $(BUILD)/genhdr/modules.h | ||
endif |
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.
How about calling it cmodules.h
instead of modules.h
?
And then to eliminate the need for MICROPY_HAS_EXTRA_MODULES
you could do this:
CFLAGS_MOD += -DMICROPY_CMODULES_INCLUDE_H="genhdr/cmodules.h"
Then at the top of objmodule.c use:
#ifdef MICROPY_CMODULES_INCLUDE_H
#include MICROPY_CMODULES_INCLUDE_H
#endif
And I wouldn't add this MICROPY_CMODULES_INCLUDE_H
option to mpconfig.h because it's not really settable by the user, it's handled by this Makefile.
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.
Good idea! I've applied it.
What this stuff has to do with "MicroPython language"? "MicroPython language" is the language implemented by MicroPython (i.e. Python). C-level hacks have very little to do with that. Taking CPython docs (https://docs.python.org/3/index.html) as an example, "The MicroPython language" roughly corresponds to "Language Reference" https://docs.python.org/3/reference/index.html . Where "quick way to add C modules" stuff would belong is "Extending and Embedding |
@pfalcon what would your suggestion be, then? Create a new section, or something else? |
Thank you for the review. EDIT: fixed a bug and renamed gen-modules.py to gen-cmodules.py (consistent with the other renames). |
docs/reference/cmodules.rst
Outdated
.. code:: | ||
|
||
# Add all C files to SRC_MOD. | ||
SRC_MOD += $(MODULE_DIR)/example/example.c |
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 worked a bit better with USER_C_MODULES instead of MODULE_DIR.
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.
Oops, my bad. I need to fix the docs there.
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.
Fixed!
I'm dry-running the proposal on my perlin noise c-module (https://github.com/glennrub/unoise/blob/adopt_to_ayke_cmodule_proposal/micropython.mk). The genhdr/cmodules.h looks as expected:
However, it does not look like the SRC_MOD files get's embedded into the build automatically as i would expect. Have i missed something essential that should have enabled the file into the build? |
I don't see any references to MICROPY_EXTRA_BUILTIN_MODULES in the code. In particular, this is where all of the modules come together: Line 128 in a3ba5f1
I've always added to the MICROPY_PORT_BUILTIN_MODULES #define |
@dhylands are you sure you're on the right branch/commit? It certainly is in the diff (see the diff in GitHub for example). |
@glennrub I've found the issue. Most ports add |
Thanks, @aykevl ! I tested out the proposed fix, adding SRC_MOD to SRC_C. The framework work as expected and i get my module loaded. |
Rebased to master and squashed the first commit. While testing the nrf port, I found a bug: the object files are put in a seemingly random directory ( I'm thinking about the best way to solve this, probably by putting the object files in build/cmodules or something like that. |
Generic suggestion would be either do it right, introduce "Developing and building MicroPython" top-level section to host all docs related to "C" things, or do it in the simplest manner, not adding kind of info the docs aren't ready to take in yet, and keep it in README or similar.
All these chapters share the same high-level topic: they about "the MicroPython language" or infrastructure directly related to the language (variant of Python). For example, people who would receive a board with preprogrammed MicroPython or downloaded a firmware and flashed to their board, ideally would be interested in reading all docs in "the MicroPython language". However, they don't have to be interested, and likely will be confused, to read about "C language" and related stuff. That's why CPython has a strict separation in the docs between Python and C world, and it only makes sense to follow that separation. |
This system makes it a lot easier to include external libraries as native modules in MicroPython. Simply pass USER_C_MODULES (like FROZEN_MPY_DIR) as a make parameter.
I have moved the documentation to a new section. I didn't add more information than the absolute minimum, because I don't think that belongs in this PR. (Also, rebased + squashed several commits). Note: I have added it to wipy and pyboard as well, but I haven't tested this system on them. |
I've fixed the incorrect build location of the C modules by symlinking the module directory in the build directory (see 4cd27d0). Not very nice, but I can't think of a better way (suggestions welcome!). |
I've been using this PR for the last couple of days with great success, I strongly support this being accepted. I do have a couple of requested additions beforehand however, described in my matching branch: aykevl/micropython@modules...andrewleech:modules Did you have a particular use case for linking the C files into the build folder? Without the linking, you need to be careful when you define Also when using this mechanism to include C files that already have the micropython module definitions in place I don't want the definitions to be auto-generated by the python script, so my update checks for defs in files and using them first. If there are none, it still generates one as before. |
Didn't look into this in detail, but windows does support symbolic links so wouldn't the solution be to not create *nix links but windows links instead? |
Yes technically it's possible to do windows or *nix links as appropriate (with more makefile ifs and buts) but my point was I'm pretty sure it's complely unneccesary to link the files at all. Nothing else I've seen in the codebase symlinks during build to copy c files around, and this works fine for me without doing so, as such it's much cleaner to not need to do it at all. fwiw I'm actually running make through the official bash on windows, so as far as the Makefile is concerned it's actually running on linux, but then the debugging is in VSCode and windows gdb. This works great for me, it's really seamless. Until symlinks get into the c code path... |
There is a reason these files are symlinked, and the reason is that otherwise the object files end up in the wrong place (not in the build directory). I hit this problem when I built something for an ARM chip, and then built the same module for an ESP (or the other way around) and the linker complained about linking object files for different architectures. You may consider copying the files instead of linking if it is difficult on Windows. Or maybe you can come up with a different solution. |
@aykevl Ah I see, I'd noticed a similar issue at work yesterday where files from a different port which I was building into an external module had their object files in the wrong place, now I see that all cmodule files are doing that (just even further away again. I fixed my problem yesterday by getting rid of the relative entries in the path with a bit of Actually, this trick probably wont work on a native windows build where you've got a "C:" or similar in the absolute path that can't just be joined on the end of the build folder. |
I've got another solution for keeping the object files inside the build folder: I was about to go back to your symlink solution, however that also breaks if the build folder is outside the micropython tree. It also still breaks confuses debugging as the debug symbols point to the symlinks rather than the real c files. My new approach requires adding the |
My cmodules PR #4195 includes and extends upon this one, hopefully as a replacement that meets all the original needs of this one. |
This system makes it a lot easier to include external libraries as native modules in MicroPython. Simply set
MODULE_DIR
to the directory containing the modules. It was originally inspired by this solution by @glennrub.A module contains a
module.mk
file, which adds required C files toSRC_MOD
. For example, for a module calledfoobar
, this could be themodule.mk
file:Compared with #1627, this is a much simpler solution to implement, though maybe harder for a user. It would make a system like the NodeMCU custom build service much easier to implement, however.
EDIT: updated to new system with
MODULES_DIR
make param.