py: Implement a module system for external, user C modules.

This system makes it a lot easier to include external libraries as static,
native modules in MicroPython.  Simply pass USER_C_MODULES (like
FROZEN_MPY_DIR) as a make parameter.
This commit is contained in:
Ayke van Laethem 2018-06-14 15:57:29 +02:00 committed by Damien George
parent cf22f4793c
commit 2e516074da
8 changed files with 141 additions and 4 deletions

View File

@ -0,0 +1,86 @@
Extending MicroPython with C
============================
Some specialized code would be unacceptably slow or needs to access hardware in
a way that cannot be done from MicroPython. Therefore, it supports a way of
extending the language with custom modules written in C. But before you consider
writing a module in C, please take a look at :ref:`speed_python`.
`Unlike CPython <https://docs.python.org/3/extending/building.html>`_, these
modules are (currently) embedded directly in the program image instead of being
dynamically loaded. This requires a `custom build of MicroPython
<https://github.com/micropython/micropython/wiki/Getting-Started>`_.
Writing a module
----------------
A module is a directory with the following files:
* ``micropython.mk``, which contains the Makefile fragment for this module.
* All C files you would like included.
Put the required build commands in ``micropython.mk``. For a simple module, you
will only have to add the file paths to ``SRC_MOD``, which will include these C
files in the build:
.. highlight:: make
.. code::
# Add all C files to SRC_MOD.
SRC_MOD += $(USER_C_MODULES)/example/example.c
This is a very bare bones module named ``example`` that provides
``example.double(x)``. Note that the name of the module must be equal to the
directory name and is also used in the name of the ``mp_obj_module_t`` object at
the bottom.
.. highlight:: c
.. code::
// Include required definitions first.
#include "py/obj.h"
#include "py/runtime.h"
// This is the function you will call using example.double(n).
STATIC mp_obj_t example_double(mp_obj_t x_obj) {
// Check input value and convert it to a C type.
if (!MP_OBJ_IS_SMALL_INT(x_obj)) {
mp_raise_ValueError("x is not a small int");
}
int x = mp_obj_int_get_truncated(x_obj);
// Calculate the double, and convert back to MicroPython object.
return mp_obj_new_int(x + x);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(example_double_obj, example_double);
// Define all properties of the example module, which currently are the name (a
// string) and a function.
// All identifiers and strings are written as MP_QSTR_xxx and will be
// optimized to word-sized integers by the build system (interned strings).
STATIC const mp_rom_map_elem_t example_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_example) },
{ MP_ROM_QSTR(MP_QSTR_double), MP_ROM_PTR(&example_double_obj) },
};
STATIC MP_DEFINE_CONST_DICT(example_module_globals, example_module_globals_table);
// Define module object.
const mp_obj_module_t example_user_cmodule = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t*)&example_module_globals,
};
Using a module
--------------
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`` set to the directory containing
all modules you want included (not to the module itself!). For example:
.. highlight:: shell
.. code::
$ make USER_C_MODULES=path-to-modules-folder all

View File

@ -26,3 +26,11 @@ implementation and the best practices to use them.
constrained.rst
packages.rst
asm_thumb2_index.rst
cmodules.rst
.. only:: port_pyboard
.. toctree::
:maxdepth: 1
asm_thumb2_index.rst

View File

@ -1,3 +1,5 @@
.. _speed_python:
Maximising MicroPython Speed
============================

View File

@ -61,6 +61,7 @@ endif
MAKE_FROZEN = $(PYTHON) $(TOP)/tools/make-frozen.py
MPY_CROSS = $(TOP)/mpy-cross/mpy-cross
MPY_TOOL = $(PYTHON) $(TOP)/tools/mpy-tool.py
GEN_CMODULES = $(PYTHON) $(TOP)/tools/gen-cmodules.py
all:
.PHONY: all

View File

@ -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 USER_C_MODULES=
# make a list of all the .py files that need compiling and freezing
FROZEN_MPY_PY_FILES := $(shell find -L $(FROZEN_MPY_DIR) -type f -name '*.py' | $(SED) -e 's=^$(FROZEN_MPY_DIR)/==')
@ -123,6 +123,13 @@ $(BUILD)/frozen_mpy.c: $(FROZEN_MPY_MPY_FILES) $(BUILD)/genhdr/qstrdefs.generate
$(Q)$(MPY_TOOL) -f -q $(BUILD)/genhdr/qstrdefs.preprocessed.h $(FROZEN_MPY_MPY_FILES) > $@
endif
# to build a list of modules for py/objmodule.c.
ifneq ($(USER_C_MODULES),)
$(BUILD)/genhdr/cmodules.h: | $(HEADER_BUILD)/mpversion.h
@$(ECHO) "GEN $@"
$(Q)$(GEN_CMODULES) $(USER_C_MODULES) > $@
endif
ifneq ($(PROG),)
# Build a standalone executable (unix does this)

View File

@ -31,8 +31,6 @@
#include "py/runtime.h"
#include "py/builtin.h"
#include "genhdr/moduledefs.h"
STATIC void module_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
(void)kind;
mp_obj_module_t *self = MP_OBJ_TO_PTR(self_in);

View File

@ -129,6 +129,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 ($(USER_C_MODULES),)
CFLAGS_MOD += -DMICROPY_CMODULES_INCLUDE_H='"genhdr/cmodules.h"'
include $(USER_C_MODULES)/*/micropython.mk
SRC_QSTR += $(BUILD)/genhdr/cmodules.h
endif
# py object files
PY_CORE_O_BASENAME = $(addprefix py/,\
mpstate.o \
@ -300,7 +307,7 @@ endif
# Sources that may contain qstrings
SRC_QSTR_IGNORE = py/nlr%
SRC_QSTR = $(SRC_MOD) $(filter-out $(SRC_QSTR_IGNORE),$(PY_CORE_O_BASENAME:.o=.c)) $(PY_EXTMOD_O_BASENAME:.o=.c)
SRC_QSTR += $(SRC_MOD) $(filter-out $(SRC_QSTR_IGNORE),$(PY_CORE_O_BASENAME:.o=.c)) $(PY_EXTMOD_O_BASENAME:.o=.c)
# Anything that depends on FORCE will be considered out-of-date
FORCE:

28
tools/gen-cmodules.py Executable file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env python
# Generate genhdr/cmodules.h for inclusion in py/objmodule.c.
from __future__ import print_function
import sys
import os
from glob import glob
def update_modules(path):
modules = []
for module in sorted(os.listdir(path)):
if not os.path.isfile('%s/%s/micropython.mk' % (path, module)):
continue # not a module
modules.append(module)
# 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_user_cmodule;' % module)
print('\n#define MICROPY_EXTRA_BUILTIN_MODULES \\')
for module in modules:
print(' { MP_ROM_QSTR(MP_QSTR_%s), MP_ROM_PTR(&%s_user_cmodule) }, \\' % (module, module))
print()
if __name__ == '__main__':
update_modules(sys.argv[1])