Repository: viam-modules/mlmodel-tflite
Pull request #27: [RSDK-10269] Get the tflite_cpu module building on Windows
Author: login: acmorrow
Created: 2025-04-17T18:56:02Z | Merged: 2025-04-18T01:34:38Z

I've not yet verified that I can actually build for macOS or Linux with these changes, but I have been able to do workflow runs (thanks @abe-winter) that validate that this all works for getting a `module.tar.gz` attached to the workflow run, which can then be manually uploaded to the registry.



Diff:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 3d237a7..1d0567b 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -6,5 +6,12 @@ jobs:
     runs-on: windows-2019
     steps:
     - uses: actions/checkout@v4
-    - name: setup
-      run: make setup
+    - name: setup-and-module
+      run: |
+        make setup
+        make module.tar.gz
+    - name: upload-module
+      uses: actions/upload-artifact@v4
+      with:
+        name: module
+        path: ./module.tar.gz
diff --git a/Makefile b/Makefile
index 0ae9b8d..fb1ef85 100644
--- a/Makefile
+++ b/Makefile
@@ -1,30 +1,36 @@
 ifeq ($(OS),Windows_NT)
   BIN_EXT := .exe
-  SCRIPT_EXT := .ps1
-  SUBSHELL := powershell -ExecutionPolicy Bypass -File
+  SCRIPT_EXT := .bat
+  PATHSEP := \\
+  # Scripts for windows are written in powershell
+  # (e.g. setup.ps1). However, I was unable to find a way to invoke
+  # those scripts from here in make in a way where the exit status was
+  # returned in such a way that a failed script would terminate
+  # make. Instead, we invoke a little wrapper batch script with cmd
+  # syntax, which calls powershell for us and then explicitly sets the
+  # exit status.
+  SUBSHELL := cmd /C
 else
   BIN_EXT :=
   SCRIPT_EXT := .sh
+  PATHSEP := /
   SUBSHELL :=
 endif
+
 BIN := build-conan/build/Release/tflite_cpu$(BIN_EXT)
 
 .PHONY: tflite_cpu
 tflite_cpu: $(BIN)
 
-$(BIN): src/*
-	$(SUBSHELL) ./bin/build$(SCRIPT_EXT)
+$(BIN): conanfile.py src/*
+	$(SUBSHELL) bin$(PATHSEP)build$(SCRIPT_EXT)
 
 .PHONY: setup
 setup:
-	$(SUBSHELL) ./bin/setup$(SCRIPT_EXT)
+	$(SUBSHELL) bin$(PATHSEP)setup$(SCRIPT_EXT)
 
-module.tar.gz: tflite_cpu
-ifeq ($(OS),Windows_NT)
-	7z a -tgzip module.tar.gz $(BIN)
-else
-	tar -czvf module.tar.gz $(BIN)
-endif
+module.tar.gz: $(BIN) meta.json
+	$(SUBSHELL) bin$(PATHSEP)package$(SCRIPT_EXT) $^
 
 .PHONY: lint
 lint:
diff --git a/bin/build.bat b/bin/build.bat
new file mode 100644
index 0000000..5bbaef9
--- /dev/null
+++ b/bin/build.bat
@@ -0,0 +1,2 @@
+:: Delegate to powershell rather than trying to script in batch
+Powershell.exe -ExecutionPolicy Bypass -Command "& { bin\build.ps1; exit $LASTEXITCODE }"
diff --git a/bin/build.ps1 b/bin/build.ps1
index 6b43bea..8c5275d 100755
--- a/bin/build.ps1
+++ b/bin/build.ps1
@@ -1,7 +1,20 @@
+# Fail fast
 $ErrorActionPreference = "Stop"
 
+# Ensure that things installed with choco are visible to us
+Import-Module $env:ChocolateyInstall\helpers\chocolateyProfile.psm1
+refreshenv
+
+# Clean up any prior build
 Remove-Item -Recurse -Force build-conan -ErrorAction SilentlyContinue
 
+# Build the tflite_cpu module
+#
+# We want a static binary, so we turn off shared. Elect for C++17
+# compilation, since it seems some of the dependencies we pick mandate
+# it anyway. Pin to the Windows 10 1809 associated windows SDK, and
+# opt for the static compiler runtime so we don't have a dependency on
+# the VC redistributable.
 conan build . `
       --output-folder=build-conan `
       --build=missing `
diff --git a/bin/build.sh b/bin/build.sh
index 56cc3d8..0c2160b 100755
--- a/bin/build.sh
+++ b/bin/build.sh
@@ -5,8 +5,14 @@
 #     -o pipefail: if something in the middle of a pipeline fails, the whole thing fails
 set -euxo pipefail
 
+# Clean up any prior build
 rm -rf build-conan
 
+# Build the tflite_cpu module
+#
+# We want a static binary, so we turn off shared. Elect for C++17
+# compilation, since it seems some of the dependencies we pick mandate
+# it anyway.
 conan build . \
       --output-folder=build-conan \
       --build=missing \
diff --git a/bin/fixup_windows_entrypoint.jq b/bin/fixup_windows_entrypoint.jq
new file mode 100644
index 0000000..9e8fee8
--- /dev/null
+++ b/bin/fixup_windows_entrypoint.jq
@@ -0,0 +1 @@
+.entrypoint += ".exe"
diff --git a/bin/package.bat b/bin/package.bat
new file mode 100644
index 0000000..3d8ce47
--- /dev/null
+++ b/bin/package.bat
@@ -0,0 +1,2 @@
+:: Delegate to powershell rather than trying to script in batch
+Powershell.exe -ExecutionPolicy Bypass -Command "& { bin\package.ps1 %*; exit $LASTEXITCODE }"
diff --git a/bin/package.ps1 b/bin/package.ps1
new file mode 100644
index 0000000..1aa39ae
--- /dev/null
+++ b/bin/package.ps1
@@ -0,0 +1,18 @@
+# Fail fast
+$ErrorActionPreference = "Stop"
+
+# Ensure that things installed with choco are visible to us
+Import-Module $env:ChocolateyInstall\helpers\chocolateyProfile.psm1
+refreshenv
+
+# Fixup `meta.json` to have `.exe at the end
+Copy-Item meta.json meta.json.backup
+jq -f .\bin\fixup_windows_entrypoint.jq meta.json.backup | Out-File -FilePath meta.json -Encoding ASCII
+
+# Tar up all the command line arguments, then gzip them
+7z a -ttar module.tar $args
+7z a -tgzip -sdel module.tar.gz module.tar
+
+# Clean house
+Remove-Item meta.json
+Move-Item meta.json.backup meta.json
diff --git a/bin/package.sh b/bin/package.sh
new file mode 100644
index 0000000..f6e3fd0
--- /dev/null
+++ b/bin/package.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+# set -e: exit with errors if anything fails
+#     -u: it's an error to use an undefined variable
+#     -x: print out every command before it runs
+#     -o pipefail: if something in the middle of a pipeline fails, the whole thing fails
+set -euxo pipefail
+
+# Just tar up all the files we were given on the command line
+tar -czvf module.tar.gz "$@"
diff --git a/bin/setup.bat b/bin/setup.bat
new file mode 100644
index 0000000..f13919b
--- /dev/null
+++ b/bin/setup.bat
@@ -0,0 +1,2 @@
+:: Delegate to powershell rather than trying to script in batch
+Powershell.exe -ExecutionPolicy Bypass -Command "& { bin\setup.ps1; exit $LASTEXITCODE }"
diff --git a/bin/setup.ps1 b/bin/setup.ps1
index 9f330a9..9267c95 100755
--- a/bin/setup.ps1
+++ b/bin/setup.ps1
@@ -1,11 +1,14 @@
+# Fail fast
 $ErrorActionPreference = "Stop"
 
-# Set up conan
-choco install -y conan cmake git 7zip
+# Set up conan and other friends we met along the way
+choco install -y conan cmake git 7zip jq
 
+# Ensure that things installed with choco are visible to us
 Import-Module $env:ChocolateyInstall\helpers\chocolateyProfile.psm1
 refreshenv
 
+# Initialize conan if it hasn't been already
 conan profile detect
 if (!$?) { Write-Host "Conan is already installed" }
 
@@ -16,10 +19,16 @@ git clone https://github.com/viamrobotics/viam-cpp-sdk.git
 Push-Location viam-cpp-sdk
 
 # NOTE: If you change this version, also change it in the `conanfile.py` requirements
-git checkout releases/v0.8.0
+git checkout releases/v0.9.0
 
 # Build the C++ SDK repo.
 #
+# We want a static binary, so we turn off shared. Elect for C++17
+# compilation, since it seems some of the dependencies we pick mandate
+# it anyway. Pin to the Windows 10 1809 associated windows SDK, and
+# opt for the static compiler runtime so we don't have a dependency on
+# the VC redistributable.
+#
 # TODO: Note `-tf ""`, which disables the self test. I have not been
 # able to get this working on windows.
 conan create . `
@@ -30,6 +39,7 @@ conan create . `
       -s:h compiler.runtime=static `
       -tf `"`"
 
+# Clean up
 Pop-Location  # viam-cpp-sdk
 Pop-Location  # tmp_cpp_sdk
 Remove-Item -Recurse -Force tmp_cpp_sdk
diff --git a/bin/setup.sh b/bin/setup.sh
index 8e14f99..b3d143a 100755
--- a/bin/setup.sh
+++ b/bin/setup.sh
@@ -16,14 +16,19 @@ git clone https://github.com/viamrobotics/viam-cpp-sdk.git
 pushd viam-cpp-sdk
 
 # NOTE: If you change this version, also change it in the `conanfile.py` requirements
-git checkout releases/v0.8.0
+git checkout releases/v0.9.0
 
 # Build the C++ SDK repo
+#
+# We want a static binary, so we turn off shared. Elect for C++17
+# compilation, since it seems some of the dependencies we pick mandate
+# it anyway.
 conan create . \
       --build=missing \
       -o "&:shared=False" \
       -s:h compiler.cppstd=17
 
+# Cleanup
 popd  # viam-cpp-sdk
 popd  # tmp_cpp_sdk
 rm -rf tmp_cpp_sdk
diff --git a/conanfile.py b/conanfile.py
index cb8c337..e7adbac 100644
--- a/conanfile.py
+++ b/conanfile.py
@@ -43,7 +43,7 @@ def configure(self):
     def requirements(self):
         # NOTE: If you update the `viam-cpp-sdk` dependency here, it
         # should also be updated in `bin/setup.{sh,ps1}`.
-        self.requires("viam-cpp-sdk/0.8.0")
+        self.requires("viam-cpp-sdk/0.9.0")
         self.requires("tensorflow-lite/2.15.0")
         self.requires("abseil/20240116.2", override=True)
 
diff --git a/meta.json b/meta.json
index 2edd9f2..36d9613 100644
--- a/meta.json
+++ b/meta.json
@@ -3,7 +3,7 @@
     "build": {
         "setup": "make setup",
         "build": "make module.tar.gz",
-        "arch": ["linux/amd64", "linux/arm64", "darwin/arm64"],
+        "arch": ["linux/amd64", "linux/arm64", "darwin/arm64", "windows/amd64"],
         "darwin_deps": ["pkg-config"]
     },
     "visibility": "public",
@@ -15,5 +15,5 @@
         "model": "viam:mlmodel-tflite:tflite_cpu"
       }
     ],
-    "entrypoint": "build/Release/tflite_cpu"
+    "entrypoint": "build-conan/build/Release/tflite_cpu"
 }


Reviews:
user: login: penguinland
body: All my feedback is typos and other little things. Fix them, and the big parts LGTM
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T19:46:34Z
user: login: acmorrow
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-17T20:07:57Z
user: login: acmorrow
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-17T20:08:55Z
user: login: acmorrow
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-17T20:10:23Z
user: login: acmorrow
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-17T20:10:26Z
user: login: acmorrow
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-17T20:10:36Z
user: login: acmorrow
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-17T20:25:17Z
user: login: penguinland
body: :shrug: The one remaining typo can be fixed next PR if you don't want to update this one any more. Everything important still LGTM.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T20:30:12Z

Review comments:
diff_hunk: @@ -1,30 +1,29 @@
 ifeq ($(OS),Windows_NT)
   BIN_EXT := .exe
-  SCRIPT_EXT := .ps1
-  SUBSHELL := powershell -ExecutionPolicy Bypass -File
path: Makefile
user: login: penguinland
body: We used to run powershell to do this. Now, we run a 1-line batch script, where the 1 line is running powershell. Put in a comment explaining why we shouldn't go back to the older, simpler approach. I infer you've learned something subtle about why the straightforward approach is a bad idea, and want to have that knowledge recorded somewhere.
created_at: 2025-04-17T19:34:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,7 +1,20 @@
+# Fail fast
 $ErrorActionPreference = "Stop"
 
+# Ensure that things installed with choco are visible to us
path: bin/build.ps1
user: login: penguinland
body: :+1: Thanks for the explanatory comments!
created_at: 2025-04-17T19:34:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,30 +1,29 @@
 ifeq ($(OS),Windows_NT)
   BIN_EXT := .exe
-  SCRIPT_EXT := .ps1
-  SUBSHELL := powershell -ExecutionPolicy Bypass -File
+  SCRIPT_EXT := .bat
+  PATHSEP := \\
+  SUBSHELL := cmd /C
 else
   BIN_EXT :=
   SCRIPT_EXT := .sh
+  PATHSEP := /
   SUBSHELL :=
 endif
+
 BIN := build-conan/build/Release/tflite_cpu$(BIN_EXT)
 
 .PHONY: tflite_cpu
 tflite_cpu: $(BIN)
 
-$(BIN): src/*
-	$(SUBSHELL) ./bin/build$(SCRIPT_EXT)
+$(BIN): conanfile.py src/*
+	$(SUBSHELL) bin$(PATHSEP)build$(SCRIPT_EXT)
 
 .PHONY: setup
 setup:
-	$(SUBSHELL) ./bin/setup$(SCRIPT_EXT)
+	$(SUBSHELL) bin$(PATHSEP)setup$(SCRIPT_EXT)
 
-module.tar.gz: tflite_cpu
-ifeq ($(OS),Windows_NT)
-	7z a -tgzip module.tar.gz $(BIN)
-else
-	tar -czvf module.tar.gz $(BIN)
-endif
+module.tar.gz: $(BIN) meta.json
+	$(SUBSHELL) bin$(PATHSEP)package$(SCRIPT_EXT) $(BIN) meta.json
path: Makefile
user: login: penguinland
body: You might replace `$(BIN) meta.json` on this line with `$^`, which represents all files that this make target depends on (everything to the right of the `:` on the previous line). That way, if there are new dependencies in the future, you only need to add them once.
created_at: 2025-04-17T19:41:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -16,14 +16,19 @@ git clone https://github.com/viamrobotics/viam-cpp-sdk.git
 pushd viam-cpp-sdk
 
 # NOTE: If you change this version, also change it in the `conanfile.py` requirements
-git checkout releases/v0.8.0
+git checkout releases/v0.9.0
path: bin/setup.sh
user: login: penguinland
body: Per the comment, also update conanfile.py.
created_at: 2025-04-17T19:44:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,11 +1,14 @@
+# Fail fastp
path: bin/setup.ps1
user: login: penguinland
body: Is the `p` supposed to be there? If so, I don't know what fastp is, but maybe it's a Windows thing.
created_at: 2025-04-17T19:45:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -16,10 +19,16 @@ git clone https://github.com/viamrobotics/viam-cpp-sdk.git
 Push-Location viam-cpp-sdk
 
 # NOTE: If you change this version, also change it in the `conanfile.py` requirements
-git checkout releases/v0.8.0
+git checkout releases/v0.9.0
 
 # Build the C++ SDK repo.
 #
+# We are want a static binary, so we turn of shared. Elect for C++17
path: bin/setup.ps1
user: login: penguinland
body: typo: turn off shared

edit: and typo: we are want
created_at: 2025-04-17T19:46:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,30 +1,29 @@
 ifeq ($(OS),Windows_NT)
   BIN_EXT := .exe
-  SCRIPT_EXT := .ps1
-  SUBSHELL := powershell -ExecutionPolicy Bypass -File
+  SCRIPT_EXT := .bat
+  PATHSEP := \\
+  SUBSHELL := cmd /C
 else
   BIN_EXT :=
   SCRIPT_EXT := .sh
+  PATHSEP := /
   SUBSHELL :=
 endif
+
 BIN := build-conan/build/Release/tflite_cpu$(BIN_EXT)
 
 .PHONY: tflite_cpu
 tflite_cpu: $(BIN)
 
-$(BIN): src/*
-	$(SUBSHELL) ./bin/build$(SCRIPT_EXT)
+$(BIN): conanfile.py src/*
+	$(SUBSHELL) bin$(PATHSEP)build$(SCRIPT_EXT)
 
 .PHONY: setup
 setup:
-	$(SUBSHELL) ./bin/setup$(SCRIPT_EXT)
+	$(SUBSHELL) bin$(PATHSEP)setup$(SCRIPT_EXT)
 
-module.tar.gz: tflite_cpu
-ifeq ($(OS),Windows_NT)
-	7z a -tgzip module.tar.gz $(BIN)
-else
-	tar -czvf module.tar.gz $(BIN)
-endif
+module.tar.gz: $(BIN) meta.json
+	$(SUBSHELL) bin$(PATHSEP)package$(SCRIPT_EXT) $(BIN) meta.json
path: Makefile
user: login: acmorrow
body: I had that originally, for the reasons you suggest, but I was getting results that didn't make sense (the tarfile contained dozens of files). I'll try it again and update it if works.
created_at: 2025-04-17T20:07:56Z
author_association: COLLABORATOR
diff_hunk: @@ -16,14 +16,19 @@ git clone https://github.com/viamrobotics/viam-cpp-sdk.git
 pushd viam-cpp-sdk
 
 # NOTE: If you change this version, also change it in the `conanfile.py` requirements
-git checkout releases/v0.8.0
+git checkout releases/v0.9.0
path: bin/setup.sh
user: login: acmorrow
body: Hah. Thanks.
created_at: 2025-04-17T20:08:54Z
author_association: COLLABORATOR
diff_hunk: @@ -16,10 +19,16 @@ git clone https://github.com/viamrobotics/viam-cpp-sdk.git
 Push-Location viam-cpp-sdk
 
 # NOTE: If you change this version, also change it in the `conanfile.py` requirements
-git checkout releases/v0.8.0
+git checkout releases/v0.9.0
 
 # Build the C++ SDK repo.
 #
+# We are want a static binary, so we turn of shared. Elect for C++17
path: bin/setup.ps1
user: login: acmorrow
body: Done
created_at: 2025-04-17T20:10:23Z
author_association: COLLABORATOR
diff_hunk: @@ -1,11 +1,14 @@
+# Fail fastp
path: bin/setup.ps1
user: login: acmorrow
body: Done
created_at: 2025-04-17T20:10:26Z
author_association: COLLABORATOR
diff_hunk: @@ -1,30 +1,29 @@
 ifeq ($(OS),Windows_NT)
   BIN_EXT := .exe
-  SCRIPT_EXT := .ps1
-  SUBSHELL := powershell -ExecutionPolicy Bypass -File
path: Makefile
user: login: acmorrow
body: Done
created_at: 2025-04-17T20:10:36Z
author_association: COLLABORATOR
diff_hunk: @@ -1,30 +1,29 @@
 ifeq ($(OS),Windows_NT)
   BIN_EXT := .exe
-  SCRIPT_EXT := .ps1
-  SUBSHELL := powershell -ExecutionPolicy Bypass -File
+  SCRIPT_EXT := .bat
+  PATHSEP := \\
+  SUBSHELL := cmd /C
 else
   BIN_EXT :=
   SCRIPT_EXT := .sh
+  PATHSEP := /
   SUBSHELL :=
 endif
+
 BIN := build-conan/build/Release/tflite_cpu$(BIN_EXT)
 
 .PHONY: tflite_cpu
 tflite_cpu: $(BIN)
 
-$(BIN): src/*
-	$(SUBSHELL) ./bin/build$(SCRIPT_EXT)
+$(BIN): conanfile.py src/*
+	$(SUBSHELL) bin$(PATHSEP)build$(SCRIPT_EXT)
 
 .PHONY: setup
 setup:
-	$(SUBSHELL) ./bin/setup$(SCRIPT_EXT)
+	$(SUBSHELL) bin$(PATHSEP)setup$(SCRIPT_EXT)
 
-module.tar.gz: tflite_cpu
-ifeq ($(OS),Windows_NT)
-	7z a -tgzip module.tar.gz $(BIN)
-else
-	tar -czvf module.tar.gz $(BIN)
-endif
+module.tar.gz: $(BIN) meta.json
+	$(SUBSHELL) bin$(PATHSEP)package$(SCRIPT_EXT) $(BIN) meta.json
path: Makefile
user: login: acmorrow
body: Yes, working now. I suspect the issue I had earlier was with argument passing, not `$^`.
created_at: 2025-04-17T20:25:16Z
author_association: COLLABORATOR
diff_hunk: @@ -2,6 +2,13 @@ ifeq ($(OS),Windows_NT)
   BIN_EXT := .exe
   SCRIPT_EXT := .bat
   PATHSEP := \\
+  # Scripts for windows are written in powershell
+  # (e.g. setup.ps1). However, I was unable to find a way to invoke
+  # those scripts from here in make in a way where the exit status was
+  # returned in such a way that a failed script would terminate
+  # make. Instead, we invoke a little wrapper batch script with cmd
+  # syntax, which calls powershell for us and then explicitly sets the
+  # exit status.
path: Makefile
user: login: penguinland
body: :+1: Excellent comment, thanks!
created_at: 2025-04-17T20:29:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -23,7 +23,7 @@ git checkout releases/v0.9.0
 
 # Build the C++ SDK repo.
 #
-# We are want a static binary, so we turn of shared. Elect for C++17
+# We are want a static binary, so we turn off shared. Elect for C++17
path: bin/setup.ps1
user: login: penguinland
body: One more typo of "We are want"
created_at: 2025-04-17T20:29:39Z
author_association: CONTRIBUTOR

PR comments:
user: login: acmorrow
created_at: 2025-04-17T19:02:55Z
body: A workflow run against this state is here: https://github.com/acmorrow/mlmodel-tflite/actions/runs/14522573104

It'll take about 2hrs to work or not.
author_association: COLLABORATOR
user: login: acmorrow
created_at: 2025-04-17T20:26:42Z
body: @penguinland - Updated if you would like to take another look.
author_association: COLLABORATOR
user: login: acmorrow
created_at: 2025-04-17T20:32:30Z
body: New workflow run: https://github.com/acmorrow/mlmodel-tflite/actions/runs/14524275827

If this is green, I will hand test it on my test machine. If it looks good, I plan to merge this, tag it as 0.1.0-rc4, and manually push the associated artifacts to the registry as 0.1.0-rc4.
author_association: COLLABORATOR