Skip to content

Commit a43e267

Browse files
committed
Update to support building
1 parent 0640c56 commit a43e267

File tree

17 files changed

+1088
-13
lines changed

17 files changed

+1088
-13
lines changed

BUILD.md

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
# Building Plugin Executables
2+
3+
This guide explains how to build Python plugins into standalone single-file executables that can be used with [mcpd](https://github.com/mozilla-ai/mcpd).
4+
5+
## Why Build Executables?
6+
7+
`mcpd` requires plugins to be standalone executables that it can spawn as separate processes. Python plugins need to be packaged with their dependencies and the Python runtime into a single executable file.
8+
9+
## Prerequisites
10+
11+
Install build dependencies:
12+
13+
```bash
14+
uv sync --group build
15+
```
16+
17+
This installs both PyInstaller (for development) and Nuitka (for production builds).
18+
19+
## Quick Start
20+
21+
Build any plugin executable:
22+
23+
```bash
24+
make build-plugin PLUGIN=examples/simple_plugin
25+
```
26+
27+
The executable will be created in `dist/simple_plugin` (or `dist/simple_plugin.exe` on Windows).
28+
29+
Run the built plugin:
30+
31+
```bash
32+
./dist/simple_plugin
33+
```
34+
35+
## Build Tools
36+
37+
We support two build tools with different trade-offs:
38+
39+
### PyInstaller (Recommended for Development)
40+
41+
**Pros:**
42+
- Fast builds (~5-10 seconds)
43+
- Mature and well-documented
44+
- Good compatibility with most packages
45+
- Single-file executables for easy distribution
46+
47+
**Cons:**
48+
- Slightly slower startup time (~100-200ms overhead)
49+
- Runtime performance same as standard Python
50+
51+
**When to use:** During development and testing when fast iteration is important.
52+
53+
### Nuitka (Recommended for Production)
54+
55+
**Pros:**
56+
- 2-3x faster startup time
57+
- Compiled to native C code
58+
- Better performance for CPU-bound operations
59+
- More secure (harder to decompile)
60+
61+
**Cons:**
62+
- Much longer build times (5-30 minutes)
63+
- Larger executable sizes (~2x PyInstaller)
64+
- Requires C compiler toolchain
65+
66+
**When to use:** For production releases where performance and security matter.
67+
68+
## Building with PyInstaller
69+
70+
### Basic Build
71+
72+
```bash
73+
# Using Makefile
74+
make build-plugin PLUGIN=examples/simple_plugin
75+
76+
# Or directly
77+
./scripts/build_plugin.sh examples/simple_plugin
78+
```
79+
80+
### Advanced Options
81+
82+
```bash
83+
# Build with custom name
84+
./scripts/build_plugin.sh examples/simple_plugin --name my-plugin
85+
86+
# Build in debug mode (shows console output)
87+
./scripts/build_plugin.sh examples/simple_plugin --debug
88+
```
89+
90+
## Building with Nuitka
91+
92+
### Basic Build
93+
94+
```bash
95+
# Using Makefile
96+
make build-plugin-prod PLUGIN=examples/simple_plugin
97+
98+
# Or directly
99+
./scripts/build_plugin.sh examples/simple_plugin --nuitka
100+
```
101+
102+
### Advanced Options
103+
104+
```bash
105+
# Optimize for size
106+
./scripts/build_plugin.sh examples/simple_plugin --nuitka --optimize-size
107+
108+
# Optimize for speed
109+
./scripts/build_plugin.sh examples/simple_plugin --nuitka --optimize-speed
110+
111+
# Enable all optimizations (slow build)
112+
./scripts/build_plugin.sh examples/simple_plugin --nuitka --full-compat
113+
```
114+
115+
## Platform-Specific Notes
116+
117+
### macOS
118+
119+
**Requirements:**
120+
- Xcode Command Line Tools: `xcode-select --install`
121+
- For Nuitka: Full Xcode (for compiling to native code)
122+
123+
**Code Signing:**
124+
If you need to distribute your plugin, you may need to sign it:
125+
126+
```bash
127+
codesign --force --sign - dist/simple_plugin
128+
```
129+
130+
### Linux
131+
132+
**Requirements:**
133+
- GCC/G++ compiler: `sudo apt-get install build-essential` (Ubuntu/Debian)
134+
- For Nuitka: Additional dev packages may be needed
135+
136+
**Permissions:**
137+
Make executable:
138+
139+
```bash
140+
chmod +x dist/simple_plugin
141+
```
142+
143+
### Windows
144+
145+
**Requirements:**
146+
- For Nuitka: Visual Studio Build Tools or MinGW-w64
147+
148+
**Note:** Executables will have `.exe` extension automatically.
149+
150+
## Troubleshooting
151+
152+
### ImportError: No module named 'grpc'
153+
154+
The gRPC library has native C extensions. PyInstaller should detect these automatically, but if you encounter issues:
155+
156+
```bash
157+
# Add hidden imports explicitly
158+
./scripts/build_plugin.sh examples/simple_plugin --hidden-import=grpc._cython.cygrpc
159+
```
160+
161+
### AsyncIO Runtime Errors
162+
163+
If you see `RuntimeError: This event loop is already running`:
164+
165+
```bash
166+
# Rebuild with asyncio hooks
167+
./scripts/build_plugin.sh examples/simple_plugin --collect-all=asyncio
168+
```
169+
170+
### Missing Protobuf Files
171+
172+
If protobuf definitions aren't found:
173+
174+
```bash
175+
# Ensure proto files are included as data files
176+
./scripts/build_plugin.sh examples/simple_plugin --add-data "src/mcpd_plugins/v1/plugins:mcpd_plugins/v1/plugins"
177+
```
178+
179+
### Large Executable Size
180+
181+
To reduce size:
182+
183+
1. Use `--exclude-module` to remove unused dependencies
184+
2. Use `--strip` to remove debug symbols (Linux/macOS)
185+
3. Use Nuitka with `--optimize-size`
186+
187+
### Slow Nuitka Builds
188+
189+
Nuitka builds can take 5-30 minutes. To speed up:
190+
191+
1. Use `--show-progress` to see what's happening
192+
2. Use `--jobs=N` to parallelize (N = CPU cores)
193+
3. Cache compiled modules with `--module-cache-dir=.nuitka_cache`
194+
195+
## How the Build Process Works
196+
197+
### PyInstaller Process
198+
199+
1. **Analysis:** PyInstaller analyzes your code to find all dependencies
200+
2. **Collection:** Collects Python modules, native libraries, and data files
201+
3. **Bundling:** Packages everything with a bootloader
202+
4. **Output:** Creates executable that extracts and runs at startup
203+
204+
### Nuitka Process
205+
206+
1. **Transpilation:** Converts Python code to C code
207+
2. **Compilation:** Compiles C code to native machine code
208+
3. **Linking:** Links with Python runtime and dependencies
209+
4. **Output:** Creates native executable
210+
211+
## Testing Built Plugins
212+
213+
After building, test your plugin:
214+
215+
```bash
216+
# Start the plugin server
217+
./dist/simple_plugin
218+
219+
# In another terminal, verify it's listening
220+
lsof -i :50051 # macOS/Linux
221+
netstat -an | grep 50051 # Windows
222+
223+
# Test with grpcurl (if installed)
224+
grpcurl -plaintext localhost:50051 list
225+
```
226+
227+
## CI/CD Integration
228+
229+
Example GitHub Actions workflow:
230+
231+
```yaml
232+
- name: Install build dependencies
233+
run: uv sync --group build
234+
235+
- name: Build plugin with PyInstaller
236+
run: make build-plugin PLUGIN=examples/simple_plugin
237+
238+
- name: Upload artifact
239+
uses: actions/upload-artifact@v3
240+
with:
241+
name: simple-plugin-${{ runner.os }}
242+
path: dist/simple_plugin*
243+
```
244+
245+
## Best Practices
246+
247+
1. **Development:** Use PyInstaller for fast iteration
248+
2. **Testing:** Test both source and built versions
249+
3. **Production:** Use Nuitka for final releases
250+
4. **Versioning:** Include version in executable name
251+
5. **Documentation:** Update plugin README with build instructions
252+
6. **Size:** Monitor executable size and optimize if needed
253+
7. **Performance:** Benchmark critical paths in built executables
254+
255+
## Further Reading
256+
257+
- [PyInstaller Documentation](https://pyinstaller.org/en/stable/)
258+
- [Nuitka Documentation](https://nuitka.net/doc/user-manual.html)
259+
- [gRPC Python Performance Best Practices](https://grpc.io/docs/guides/performance/)

Makefile

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,22 @@ lint: ## Run pre-commit hooks on all files
2323
generate-protos: ensure-scripts-exec ## Download proto files and generate Python code
2424
./scripts/generate_protos.sh
2525

26+
.PHONY: build-plugin
27+
build-plugin: ensure-scripts-exec ## Build a plugin executable with PyInstaller (usage: make build-plugin PLUGIN=examples/simple_plugin)
28+
@if [ -z "$(PLUGIN)" ]; then \
29+
echo "Error: PLUGIN variable not set. Usage: make build-plugin PLUGIN=examples/simple_plugin"; \
30+
exit 1; \
31+
fi
32+
./scripts/build_plugin.sh $(PLUGIN)
33+
34+
.PHONY: build-plugin-prod
35+
build-plugin-prod: ensure-scripts-exec ## Build a plugin with Nuitka for production (usage: make build-plugin-prod PLUGIN=examples/simple_plugin)
36+
@if [ -z "$(PLUGIN)" ]; then \
37+
echo "Error: PLUGIN variable not set. Usage: make build-plugin-prod PLUGIN=examples/simple_plugin"; \
38+
exit 1; \
39+
fi
40+
./scripts/build_plugin.sh $(PLUGIN) --nuitka
41+
2642
.PHONY: clean
2743
clean: ## Clean generated files and caches
2844
rm -rf tmp/

0 commit comments

Comments
 (0)