From d7ac53790f12fa6e169ae5aa625662e95e97dd44 Mon Sep 17 00:00:00 2001
From: Jon Nordby <jononor@gmail.com>
Date: Sun, 20 Apr 2025 13:32:26 +0200
Subject: [PATCH 1/7] trees: Merge .py code also for USER_C_MODULES

---
 Makefile                                      | 20 ++++++++++++++++++-
 src/emlearn_trees/Makefile                    |  2 +-
 .../{trees.py => emlearn_trees.py}            |  7 +++++++
 src/emlearn_trees/trees.c                     |  3 ++-
 src/manifest.py                               |  5 +++++
 5 files changed, 34 insertions(+), 3 deletions(-)
 rename src/emlearn_trees/{trees.py => emlearn_trees.py} (76%)
 create mode 100644 src/manifest.py

diff --git a/Makefile b/Makefile
index ec5dcf7..08b6f69 100644
--- a/Makefile
+++ b/Makefile
@@ -8,7 +8,13 @@ VERSION := $(shell git describe --tags --always)
 
 MPY_DIR_ABS = $(abspath $(MPY_DIR)) 
 
+C_MODULES_SRC_PATH = $(abspath ./src)
+MANIFEST_PATH = $(abspath ./src/manifest.py)
+
+PORT=unix
 MODULES_PATH = ./dist/$(ARCH)_$(MPY_ABI_VERSION)
+PORT_DIR = ./dist/ports/$(PORT)
+UNIX_MICROPYTHON = ./dist/ports/unix/micropython
 
 $(MODULES_PATH)/emlearn_trees.mpy:
 	make -C src/emlearn_trees/ ARCH=$(ARCH) MPY_DIR=$(MPY_DIR_ABS) V=1 clean dist
@@ -61,7 +67,19 @@ emlearn_iir_q15.results: $(MODULES_PATH)/emlearn_iir_q15.mpy
 emlearn_arrayutils.results: $(MODULES_PATH)/emlearn_arrayutils.mpy
 	MICROPYPATH=$(MODULES_PATH) $(MICROPYTHON_BIN) tests/test_arrayutils.py
 
-.PHONY: clean
+$(PORT_DIR):
+	mkdir -p $@
+
+$(UNIX_MICROPYTHON): $(PORT_DIR)
+	make -C $(MPY_DIR)/ports/unix USER_C_MODULES=$(C_MODULES_SRC_PATH) FROZEN_MANIFEST=$(MANIFEST_PATH) EXTRA_CFLAGS='-Wno-unused-function' -j4
+	cp $(MPY_DIR)/ports/unix/build-standard/micropython $@
+
+unix: $(UNIX_MICROPYTHON)
+
+check_unix: $(UNIX_MICROPYTHON)
+	$(UNIX_MICROPYTHON) tests/test_trees.py
+
+.PHONY: clean unix
 
 clean:
 	make -C src/emlearn_trees/ ARCH=$(ARCH) MPY_DIR=$(MPY_DIR_ABS) V=1 clean
diff --git a/src/emlearn_trees/Makefile b/src/emlearn_trees/Makefile
index 01220c9..eab5596 100644
--- a/src/emlearn_trees/Makefile
+++ b/src/emlearn_trees/Makefile
@@ -19,7 +19,7 @@ DIST_DIR := ../../dist/$(ARCH)_$(MPY_ABI_VERSION)
 MOD = emlearn_trees
 
 # Source files (.c or .py)
-SRC = trees.c trees.py
+SRC = trees.c emlearn_trees.py
 
 # Releases
 DIST_FILE = $(DIST_DIR)/$(MOD).mpy
diff --git a/src/emlearn_trees/trees.py b/src/emlearn_trees/emlearn_trees.py
similarity index 76%
rename from src/emlearn_trees/trees.py
rename to src/emlearn_trees/emlearn_trees.py
index a46b2b3..f652ec4 100644
--- a/src/emlearn_trees/trees.py
+++ b/src/emlearn_trees/emlearn_trees.py
@@ -1,4 +1,11 @@
 
+# When used as external C module, the .py is the top-level import,
+# and we need to merge the native module symbols at import time
+# When used as dynamic native modules (.mpy), .py and native code is merged at build time
+try:
+    from emlearn_trees_c import *
+except ImportError as e:
+    pass
 
 def load_model(builder, f):
 
diff --git a/src/emlearn_trees/trees.c b/src/emlearn_trees/trees.c
index 8cb3f45..bff1866 100644
--- a/src/emlearn_trees/trees.c
+++ b/src/emlearn_trees/trees.c
@@ -332,7 +332,8 @@ const mp_obj_module_t emlearn_trees_cmodule = {
     .globals = (mp_obj_dict_t *)&emlearn_trees_globals,
 };
 
-MP_REGISTER_MODULE(MP_QSTR_emlearn_trees, emlearn_trees_cmodule);
+// External module name is XXX_c to allow .py file to be the entrypoint
+MP_REGISTER_MODULE(MP_QSTR_emlearn_trees_c, emlearn_trees_cmodule);
 
 #endif
 
diff --git a/src/manifest.py b/src/manifest.py
new file mode 100644
index 0000000..c9d207e
--- /dev/null
+++ b/src/manifest.py
@@ -0,0 +1,5 @@
+
+# Manifest is used to include .py files for external C module build
+# NOTE: this is a different mechanism than
+# Ref https://docs.micropython.org/en/latest/reference/manifest.html
+module("emlearn_trees.py", base_path='./emlearn_trees')

From c3dfcadacd0d1f74cc2365a347623e22f28345c1 Mon Sep 17 00:00:00 2001
From: Jon Nordby <jononor@gmail.com>
Date: Sun, 20 Apr 2025 13:42:50 +0200
Subject: [PATCH 2/7] CI: Try run tests on Unix with user C modules

---
 .github/workflows/build.yaml | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index e989acb..759e1a6 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -10,7 +10,7 @@ jobs:
     runs-on: ubuntu-24.04
     env:
       MPY_DIR: ./micropython
-      MICROPYTHON_BIN: ./micropython/ports/unix/build-standard/micropython
+      MICROPYTHON_BIN: ./micropython/ports/unix/build-nomodules/micropython
     steps:
     - uses: actions/checkout@v4
       with:
@@ -29,8 +29,12 @@ jobs:
       run: pip install -r requirements.txt
     - name: Setup MicroPython X86
       working-directory: micropython
-      run: source tools/ci.sh && ci_unix_32bit_setup && ci_unix_standard_build
-    - name: Run test and build module x64
+      run: |
+        source tools/ci.sh && ci_unix_32bit_setup && ci_unix_standard_build
+        mv ./ports/unix/build-standard/ ./ports/unix/build-nomodules/
+    - name: Build custom firmware with user modules, and tests. Unix/x64
+      run: make check_unix V=1
+    - name: Build .mpy modules and run tests Unix/x64
       run: make check ARCH=x64 V=1
     - name: Setup MicroPython ARM
       working-directory: micropython

From 9b893046bd34e753f554c11b3a367abe12a89df5 Mon Sep 17 00:00:00 2001
From: Jon Nordby <jononor@gmail.com>
Date: Sun, 20 Apr 2025 14:37:52 +0200
Subject: [PATCH 3/7] unix: Fix passing of extra CFLAGS

---
 Makefile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Makefile b/Makefile
index 08b6f69..d84a91e 100644
--- a/Makefile
+++ b/Makefile
@@ -71,7 +71,7 @@ $(PORT_DIR):
 	mkdir -p $@
 
 $(UNIX_MICROPYTHON): $(PORT_DIR)
-	make -C $(MPY_DIR)/ports/unix USER_C_MODULES=$(C_MODULES_SRC_PATH) FROZEN_MANIFEST=$(MANIFEST_PATH) EXTRA_CFLAGS='-Wno-unused-function' -j4
+	make -C $(MPY_DIR)/ports/unix V=1 USER_C_MODULES=$(C_MODULES_SRC_PATH) FROZEN_MANIFEST=$(MANIFEST_PATH) CFLAGS_EXTRA='-Wno-unused-function' -j4
 	cp $(MPY_DIR)/ports/unix/build-standard/micropython $@
 
 unix: $(UNIX_MICROPYTHON)

From 374a11248bfcb6f6217e163a9a786cb450b5b603 Mon Sep 17 00:00:00 2001
From: Jon Nordby <jononor@gmail.com>
Date: Sun, 20 Apr 2025 15:37:27 +0200
Subject: [PATCH 4/7] requirements: Bump emlearn to 0.21.2

---
 requirements.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/requirements.txt b/requirements.txt
index 6ee4e56..3a89384 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
-emlearn>=0.21.1
+emlearn>=0.21.2
 scikit-learn>=1.0.0
 ar>=1.0.0
 pyelftools>=0.31

From 762e6b46238a2ff10d61bcf4d3b0015fe5f29f01 Mon Sep 17 00:00:00 2001
From: Jon Nordby <jononor@gmail.com>
Date: Sun, 20 Apr 2025 15:52:34 +0200
Subject: [PATCH 5/7] unix: Allow unused function

---
 Makefile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Makefile b/Makefile
index d84a91e..2df1df8 100644
--- a/Makefile
+++ b/Makefile
@@ -71,7 +71,7 @@ $(PORT_DIR):
 	mkdir -p $@
 
 $(UNIX_MICROPYTHON): $(PORT_DIR)
-	make -C $(MPY_DIR)/ports/unix V=1 USER_C_MODULES=$(C_MODULES_SRC_PATH) FROZEN_MANIFEST=$(MANIFEST_PATH) CFLAGS_EXTRA='-Wno-unused-function' -j4
+	make -C $(MPY_DIR)/ports/unix V=1 USER_C_MODULES=$(C_MODULES_SRC_PATH) FROZEN_MANIFEST=$(MANIFEST_PATH) CFLAGS_EXTRA='-Wno-unused-function -Wno-unused-function' -j4
 	cp $(MPY_DIR)/ports/unix/build-standard/micropython $@
 
 unix: $(UNIX_MICROPYTHON)

From 47788359186e8930dc822c5ac3a42272ca2d9348 Mon Sep 17 00:00:00 2001
From: Jon Nordby <jononor@gmail.com>
Date: Sun, 20 Apr 2025 16:10:02 +0200
Subject: [PATCH 6/7] unix: Also test emlearn_iir

---
 Makefile | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Makefile b/Makefile
index 2df1df8..02bf1e8 100644
--- a/Makefile
+++ b/Makefile
@@ -78,6 +78,7 @@ unix: $(UNIX_MICROPYTHON)
 
 check_unix: $(UNIX_MICROPYTHON)
 	$(UNIX_MICROPYTHON) tests/test_trees.py
+	$(UNIX_MICROPYTHON) tests/test_iir.py
 
 .PHONY: clean unix
 

From 539a10eb8a7b2b6d3826c7624d6273f2dae879e3 Mon Sep 17 00:00:00 2001
From: Jon Nordby <jononor@gmail.com>
Date: Sun, 20 Apr 2025 16:10:27 +0200
Subject: [PATCH 7/7] Allow unused functions in native module builds

---
 src/emlearn_fft/Makefile       | 2 +-
 src/emlearn_iir/Makefile       | 2 +-
 src/emlearn_iir_q15/Makefile   | 1 +
 src/emlearn_neighbors/Makefile | 2 +-
 src/emlearn_trees/Makefile     | 2 +-
 5 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/src/emlearn_fft/Makefile b/src/emlearn_fft/Makefile
index 857868c..a9836ee 100644
--- a/src/emlearn_fft/Makefile
+++ b/src/emlearn_fft/Makefile
@@ -33,6 +33,6 @@ $(DIST_FILE): $(MOD).mpy $(DIST_DIR)
 include $(MPY_DIR)/py/dynruntime.mk
 
 
-CFLAGS += -I$(EMLEARN_DIR)
+CFLAGS += -I$(EMLEARN_DIR) -Wno-unused-function
 
 dist: $(DIST_FILE)
diff --git a/src/emlearn_iir/Makefile b/src/emlearn_iir/Makefile
index d884e95..5160c7d 100644
--- a/src/emlearn_iir/Makefile
+++ b/src/emlearn_iir/Makefile
@@ -32,6 +32,6 @@ $(DIST_FILE): $(MOD).mpy $(DIST_DIR)
 # Include to get the rules for compiling and linking the module
 include $(MPY_DIR)/py/dynruntime.mk
 
-CFLAGS += -I$(EMLEARN_DIR)
+CFLAGS += -I$(EMLEARN_DIR) -Wno-unused-function
 
 dist: $(DIST_FILE)
diff --git a/src/emlearn_iir_q15/Makefile b/src/emlearn_iir_q15/Makefile
index 7a8005f..e5f0faf 100644
--- a/src/emlearn_iir_q15/Makefile
+++ b/src/emlearn_iir_q15/Makefile
@@ -41,6 +41,7 @@ LIBGCC_FILENAME = $(shell $(CROSS)gcc $(CFLAGS) -print-libgcc-file-name)
 $(info $(LIBGCC_FILENAME))
 
 CFLAGS += -I$(CMSIS_DSP_DIR)/Include
+CFLAGS += -Wno-unused-function
 
 $(CMSIS_DSP_DIR)/iir_q15.patched:
 	cd $(CMSIS_DSP_DIR) && git apply -v ../df1_q15_disable_dsp.patch
diff --git a/src/emlearn_neighbors/Makefile b/src/emlearn_neighbors/Makefile
index 53ee216..cadd861 100644
--- a/src/emlearn_neighbors/Makefile
+++ b/src/emlearn_neighbors/Makefile
@@ -33,6 +33,6 @@ $(DIST_DIR):
 $(DIST_FILE): $(MOD).mpy $(DIST_DIR)
 	cp $< $@
 
-CFLAGS += -I$(EMLEARN_DIR)
+CFLAGS += -I$(EMLEARN_DIR) -Wno-unused-function
 
 dist: $(DIST_FILE)
diff --git a/src/emlearn_trees/Makefile b/src/emlearn_trees/Makefile
index eab5596..cb30c04 100644
--- a/src/emlearn_trees/Makefile
+++ b/src/emlearn_trees/Makefile
@@ -32,6 +32,6 @@ $(DIST_FILE): $(MOD).mpy $(DIST_DIR)
 # Include to get the rules for compiling and linking the module
 include $(MPY_DIR)/py/dynruntime.mk
 
-CFLAGS += -I$(EMLEARN_DIR) -DDYNAMIC_RUNTIME=1
+CFLAGS += -I$(EMLEARN_DIR) -Wno-unused-function
 
 dist: $(DIST_FILE)