Skip to content

Commit d944683

Browse files
whitewhite
authored andcommitted
Maven plugin feature parity - VenvManager cross-compile fix, SetupMojoTest parameter tests, maven example enhancements
1 parent 16affca commit d944683

6 files changed

Lines changed: 526 additions & 6 deletions

File tree

.output.txt

Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
Command finished with exit code 0.
2+
Command output:
3+
---
4+
tags:
5+
- tasks
6+
---
7+
8+
# Completed Tasks
9+
10+
> Related: [[Tasks/active]] | [[2026-06-21 - Competitive Analysis & Strategic Positioning]]
11+
12+
---
13+
14+
## Protocol & Core Features
15+
16+
### Binary Protocol (MessagePack)
17+
- [x] Binary protocol replacing JSON (MessagePack with length-prefixed frames)
18+
- [x] Python object handle system (reference by numeric ID)
19+
- [x] Streaming results (generator/yield support via Java Iterator)
20+
- [x] Type conversion: Python dict/list to Java Map/List via MessagePack extension types
21+
- [x] Batch execution: batchEval/batchExec (single round-trip, JSR-223 compatible)
22+
23+
### Python to Java Callbacks
24+
- [x] _bridge.call() command with Java handler registration API
25+
- [x] Progress/log/intermediate result push via _bridge.push()
26+
- [x] Arity-specific callback handler architecture (CallbackHandler0-CallbackHandler5)
27+
- [x] Push handler with unknown name error reporting
28+
29+
### Proxy Objects
30+
- [x] Java dynamic proxies for Python objects with handle lifecycle management
31+
- [x] Proxy method resolution caching (ConcurrentHashMap, CALL/GETATTR strategies)
32+
- [x] PythonEmbedPool.proxy() shortcut
33+
- [x] PythonHandle cleanup on pool scale-down (handle.release() + forgetHandle)
34+
35+
---
36+
37+
## Pool & Concurrency
38+
39+
### PythonEmbedPool
40+
- [x] Auto-managed pool with round-robin/least-loaded task distribution
41+
- [x] Future/PythonValue/CompletableFuture async API
42+
- [x] Auto-restart with minPool guarantee on crash
43+
- [x] Pool-level batchEval/batchExec/ref support
44+
- [x] Pool close-timeout configurable via DisposableBean
45+
46+
### Health & Stability
47+
- [x] Health check: periodic ping/pong with RSS memory, ref count, GC status
48+
- [x] Health check ping failure logging at WARNING level
49+
- [x] Python log forwarding to SLF4J via `python.*` logger namespace
50+
- [x] Crash isolation: Python segfaults never kill the JVM
51+
52+
---
53+
54+
## Developer Experience
55+
56+
### API Improvements
57+
- [x] Include Python traceback in Java exceptions
58+
- [x] eval/exec timeout with configurable granularity
59+
- [x] Graceful shutdown (close with timeout, close-hook support)
60+
- [x] Unchecked API: PythonExecutionException extends RuntimeException
61+
- [x] Safe argument injection: PythonEmbed.arg(Object) with type conversion
62+
- [x] Stream timeout renaming: timeoutMs to pollTimeoutMs (old deprecated)
63+
- [x] PythonEmbed.execFile(Path) convenience method
64+
- [x] PythonEmbed.arg() datetime/time type support (Instant, LocalDateTime, etc.)
65+
- [x] PythonEmbed.eval() variable binding (Map of String, Object locals)
66+
- [x] PythonValue.toJson() convenience method
67+
68+
### Builder API
69+
- [x] Fluent builders on PythonEmbed and PythonEmbedPool
70+
71+
---
72+
73+
## Build & Distribution
74+
75+
### venv & Packaging
76+
- [x] requirements.txt support
77+
- [x] pyproject.toml support (auto-extract dependencies)
78+
- [x] Incremental venv: rebuild only on dependency changes
79+
- [x] PEP 440 venv path validation (normalize + validate)
80+
- [x] pip index URL / extra args support (GPU-ready)
81+
82+
### Publishing
83+
- [x] python-embed-runtime v0.1.0-SNAPSHOT deployed to Maven Central (Sonatype OSSRH)
84+
- [x] python-embed-gradle-plugin v0.1.0-SNAPSHOT deployed to Gradle Plugin Portal
85+
- [x] Publishing plugin configuration (vanniktech/gradle-maven-publish-plugin 0.34.0)
86+
87+
### Security Hardening
88+
- [x] Tar/ZipSlip path validation
89+
90+
---
91+
92+
## Test Coverage
93+
94+
### Test Gap Closure
95+
| Date | Description | Tests Added |
96+
| ---------- | ------------------------------------------------------------------------------ | ----------- |
97+
| 2026-06-22 | PythonValue.asBytes(), VenvExtractor, MsgpackProtocol, pool batch | +43 |
98+
| 2026-06-22 | resolveExplicitPath, findPythonInDir, push handler, cross-module contract | +12 |
99+
| 2026-06-23 | PythonEmbedOptionsTest (22 unit), warmupScripts, getPid, stream close | +26 |
100+
| 2026-06-23 | Spring Boot starter: options-build, health-indicator, auto-config, integration | +19 |
101+
102+
### Flaky Test Fixes
103+
- [x] scaleDown_removesIdleInstancesAboveMinPool race condition fixed
104+
105+
---
106+
107+
## Planning & Analysis
108+
109+
### Competitive Analysis (2026-06-22)
110+
- [x] python-embed vs JEP, GraalPy, Py4J, JPype across 6 dimensions
111+
- [x] 12 improvement recommendations with priority tiers
112+
- [x] See [[2026-06-21 - Competitive Analysis & Strategic Positioning]]
113+
114+
### Development Plans (2026-06-23)
115+
- [x] [[2026-06-23 - Maven plugin development plan]]
116+
- [x] [[2026-06-23 - Spring Boot auto-configuration plan]]
117+
- [x] [[2026-06-23 - New API proposals plan]]
118+
119+
### Spring Boot Auto-Configuration (2026-06-23)
120+
- [x] Example Spring Boot app: python-embed-examples/spring-boot-example/
121+
- [x] REST controller (/eval, /exec, /info) using auto-configured PythonEmbed bean
122+
- [x] Actuator health endpoint with PythonEmbedHealthIndicator
123+
- [x] SINGLE and POOL mode profiles (application.yml, application-pool.yml)
124+
125+
### Decision Management
126+
- [x] 40 decision files tagged with status field (39 completed, 1 not_implemented)
127+
- [x] Bidirectional cross-references added to all decisions
128+
- [x] _Index.md rebuilt with 8 thematic clusters
129+
- [x] 5 topic groups consolidated (50 to 40 decisions)
130+
131+
---
132+
133+
## Documentation & Vault
134+
135+
### Vault Organization (2026-06-22)
136+
- [x] Vault naming convention established
137+
- [x] Session files consolidated into active/completed task tracking
138+
- [x] All 19 vault files translated to English
139+
140+
### Documentation Sync
141+
- [x] Projects/python-embed.md synchronized with current implementation
142+
- [x] Obsolete sections removed (e.g., Micrometer observability)
143+
144+
### README
145+
- [x] Expanded from 26 lines to 308 lines
146+
- [x] Quick Start guide, architecture diagrams, 12 cookbook examples
147+
148+
---
149+
150+
## Environment & Tooling
151+
- [x] IntelliJ 'Unable to load class PythonEmbedPlugin' error fixed (stale .idea/gradle.xml)
152+
- [x] Close-hook support: PythonEmbed/PythonEmbedPool shutdown hook registration
153+
154+
155+
### 2026-06-23
156+
- [x] Commit working tree (v1.0.0 release prep: CHANGELOG, README updates, version bump to 1.0.1-SNAPSHOT)
157+
## Recently Completed
158+
159+
### Maven Central & Gradle Plugin Publishing
160+
- [x] Publish python-embed-runtime to Maven Central v0.1.0-SNAPSHOT (commit 458165e)
161+
- [x] Publish plugin to Gradle Plugin Portal v0.1.0-SNAPSHOT
162+
163+
### Spring Boot Auto-Configuration
164+
- [x] Module: python-embed-spring-boot-starter/
165+
- [x] PythonEmbedProperties, PythonEmbedAutoConfiguration (SINGLE + POOL)
166+
- [x] PythonEmbedHealthIndicator, AutoConfiguration.imports
167+
- [x] 35 tests (property 8 + health 7 + auto-config 8 + options-build 7 + integration 4)
168+
- [x] Example Spring Boot app: python-embed-examples/spring-boot-example/ with REST controller and Actuator health
169+
170+
### Spring Boot Configuration Metadata (2026-06-24)
171+
- [x] spring-boot-configuration-processor added for auto-generated metadata
172+
- [x] 13 properties with Javadoc descriptions, types, and default values in spring-configuration-metadata.json
173+
- [x] 5 missing options added: maxCodeLength, startupTimeoutMs, pythonExecutable, warmupScripts, lenientWarmup
174+
- [x] 10 new unit tests for property-to-option mappings (27 total)
175+
- [x] Redundant additional-spring-configuration-metadata.json removed (processor auto-generates instead)
176+
177+
### Documentation & Examples
178+
- [x] Quick Start guide integrated into README.md
179+
- [x] Cookbook: 12 examples in python-embed-examples/
180+
- [x] Architecture diagrams in README.md
181+
182+
### New API Proposals
183+
- **Plan**: [[2026-06-23 - New API proposals plan]] (2026-06-23)
184+
- [x] PythonEmbed.execFile(Path) -- execute Python files directly
185+
- [x] PythonEmbed.arg() datetime/time type support
186+
- [x] PythonEmbed.eval() variable binding (Map<String, Object> locals)
187+
- [x] PythonValue.toJson() convenience method
188+
189+
190+
### v1.0.0 Release (2026-06-23)
191+
- [x] Publish python-embed-runtime to Maven Central v1.0.0
192+
- [x] Publish python-embed-gradle-plugin to Gradle Plugin Portal v1.0.0
193+
- [x] Publish python-embed-spring-boot-starter to Maven Central v1.0.0
194+
- [x] Remove SNAPSHOT from version, cleanup README pre-release warning
195+
- [x] Bump version to 1.0.1-SNAPSHOT for next development cycle
196+
197+
### v1.0.1 Release (2026-06-23)
198+
- [x] Publish python-embed-runtime to Maven Central v1.0.1
199+
- [ ] Publish python-embed-gradle-plugin to Gradle Plugin Portal v1.0.1 (missing credentials, see [[2026-06-23 - Gradle Plugin Portal credential gap]])
200+
- [x] Publish python-embed-spring-boot-starter to Maven Central v1.0.1
201+
- [x] Bump version to 1.0.2-SNAPSHOT for next development cycle
202+
203+
### targetOs Cross-Compilation (2026-06-24)
204+
- [x] Add targetOs property to PythonEmbedExtension
205+
- [x] Wire targetOs from extension to VenvTask in PythonEmbedPlugin
206+
- [x] Add resolveTargetOs(), isTargetWindows() helpers; update detectTargetTriple(), findPythonInDir(), makeExecutables()
207+
- [x] Add 10 tests for detectTargetTriple and findPythonInDir with explicit target OS
208+
- [x] Commit
209+
210+
- **2026-06-24** Documentation overhaul and CI examples — completed all 5 steps: README reduced to landing page, MkDocs documentation site with 30+ pages, GitHub Actions for docs deployment and CI examples, aggregate runAllExamples Gradle task with per-example timeouts
211+
- [x] MkDocs documentation review and fixes (2026-06-24): Fixed wrong method names, wrong defaults, missing properties, inaccurate health check response, incomplete plugin docs, missing datetime types
212+
213+
214+
### runAllExamples ClassNotFoundException fix (2026-06-24)
215+
- [x] Add evaluationDependsOnChildren() and dependsOn classes tasks to runAllExamples
216+
- [x] Commit (9dd28e2)
217+
218+
### Documentation Review and Fixes (2026-06-24)
219+
- [x] Fix wrong method names in docs (`5dcb926`)
220+
- [x] Fix wrong defaults in documentation
221+
- [x] Fix emoji rendering with pymdownx.emoji extension (`663d323`)
222+
- [x] Simplify architecture and batch documentation
223+
- [x] Remove unmeasured performance claims and library comparisons (`f11ea25`)
224+
- [x] Add missing datetime types to documentation
225+
- [x] Commit (`c6922f5`)
226+
227+
### AGENTS.md Vault Rename (2026-06-24)
228+
- [x] Rename vault from `agent-memory` to `python-embed`
229+
- [x] Simplify paths (remove `Projects/` prefix)
230+
- [x] Streamline workflow instructions
231+
- [x] Commit (`085f28c`)
232+
233+
### README Fix (2026-06-25)
234+
- [x] Add missing dependency declaration in Gradle example
235+
- [x] Commit (`0f645a1`)
236+
237+
### Spring Boot HealthIndicator NoClassDefFoundError Fix (2026-06-25)
238+
- [x] Isolate HealthIndicator beans in static inner class with `@ConditionalOnClass`
239+
- [x] Starter version bumped to 1.0.3
240+
- [x] Commit (`5dcb926`, `915077e`)
241+
- **Decision**: [[2026-06-25 - Spring Boot HealthIndicator NoClassDefFoundError fix]]
242+
243+
### Fingerprint Edge-Case Coverage (2026-06-26)
244+
- [x] Add 3 edge-case tests: null pythonSource, python version mismatch, missing python executable
245+
- [x] 129 lines of test coverage for incremental venv rebuild skip logic
246+
- [x] Commit (`7b62bf0`)
247+
- **Decision**: [[2026-06-26 - Fingerprint edge-case coverage]]
248+
249+
### Builder Validation (2026-06-26)
250+
- [x] Add input validation in PythonEmbed.Options.Builder.build()
251+
- [x] Validate timeoutMs > 0, maxCodeLength > 0, startupTimeoutMs > 0
252+
- [x] Throw IllegalArgumentException with descriptive messages
253+
- [x] 3 unit tests for Builder validation
254+
- [x] Commit (7b2c610)
255+
- **Decision**: [[2026-06-26 - Builder validation]]
256+
257+
### Test Coverage Enhancement (2026-06-26)
258+
- [x] Add 2 pool exhaustion tests (CallerRunsPolicy backpressure)
259+
- [x] Add 11 PythonValue null-safety tests
260+
- [x] Add 1 integration test for code length validation
261+
- [x] 14 total new tests across 3 test classes
262+
- [x] Commit (7b2c610)
263+
- **Decision**: [[2026-06-26 - Test coverage enhancement]]
264+
265+
266+
### Fail-fast Parameter Validation for PythonEmbed (2026-06-26)
267+
- [x] Add null/blank validation to all public PythonEmbed methods (eval, exec, execFile, warmup, batchEval, batchExec, stream, ref)
268+
- [x] Add variables null check to eval/exec with variables
269+
- [x] Create PythonEmbedValidationTest with 24 test cases
270+
- [x] All existing tests (runtime + spring-boot-starter) pass
271+
- **Decision**: [[2026-06-26 - Fail-fast parameter validation for PythonEmbed]]
272+
273+
### CHANGELOG Updates for 1.0.4 / Gradle Plugin 1.0.3 (2026-06-26)
274+
- [x] Root CHANGELOG.md updated with 1.0.4 entry (build-common, maven-plugin, gradle-plugin refactoring, CI workflow split)
275+
- [x] Gradle plugin CHANGELOG.md updated with 1.0.3 entry (build-common extraction, VenvTask simplification, new properties)
276+
- [x] Build-common and maven-plugin CHANGELOGs verified complete
277+
278+
279+
---
280+
281+
## Pool Cancel & Graceful Shutdown + Refactoring (2026-06-27)
282+
283+
**Goal**: Add task cancel and graceful shutdown to PythonEmbedPool, reduce code duplication.
284+
285+
- [x] D2 Task Cancel — CompletableFuture.cancel(true) interrupts thread, marks instance dirty, auto-replaces contaminated instance
286+
- [x] D3 Graceful close() — waits for in-flight tasks before shutdown; close(0, SECONDS) preserves immediate forced shutdown
287+
- [x] CloseReason.CANCELLED enum value added
288+
- [x] PythonEmbed.java refactored — timeout-less methods delegate to timeout variants (~51 lines removed)
289+
- [x] PythonEmbedPool.java refactored — extracted submitWithEmbed helper, ~216 lines removed
290+
- [x] All 87 tests pass (PythonEmbedPoolTest: 58 tests including 4 new cancel/graceful-close tests)
291+
- **Decision**: [[2026-06-27 - Pool cancel and graceful shutdown|Pool Cancel & Graceful Shutdown]]
292+
293+
294+
## Vault Cleanup (2026-06-27)
295+
296+
- [x] Move orphan files to Decisions/ (2026-06-26 - Maven plugin deploy readiness.md, 2026-06-26 - Fail-fast parameter validation for PythonEmbed.md)
297+
- [x] Fix broken Session reference in _Index.md (2026-06-23 - python-embed ghost file)
298+
- [x] Update D4 description reflecting Micrometer YAGNI decision
299+
- [x] Add new Decisions to _Index.md
300+
- [x] Rebuild _Index.md

docs/examples/maven-example.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,15 @@ try (PythonEmbed py = PythonEmbed.create()) {
6464
}
6565
```
6666

67+
## Advanced Configuration
68+
69+
The example `pom.xml` includes commented-out examples of advanced settings:
70+
71+
- **Cross-compilation**: Use `<targetOs>` to build Python for a different OS (e.g., `windows`, `linux`, `macos`)
72+
- **Custom pip index**: Use `<pipIndexUrl>` to install from a custom package index (e.g., PyTorch CUDA)
73+
- **Extra pip arguments**: Use `<pipExtraArgs>` for flags like `--extra-index-url`
74+
- **pyproject.toml**: Use `<pyprojectTomlFile>` to install dependencies from a `pyproject.toml`
75+
- **Python version**: Use `<pythonVersion>` to specify the standalone Python version
76+
- **requirements.txt**: Use `<requirementsFile>` to load dependencies from `requirements.txt`
77+
6778
See [Installation](../installation.md) for detailed Maven configuration.

python-embed-build-common/src/main/java/io/github/howtis/pythonembed/build/VenvManager.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ private VenvManager() {
3333
public static PythonEnvironment setup(VenvConfig config) throws IOException {
3434
Consumer<String> log = config.logger();
3535
Path venvDir = config.venvDir();
36+
String resolvedTargetOs = PythonResolver.resolveTargetOs(config.targetOs());
3637

3738
// Collect packages
3839
List<String> packages = new ArrayList<>(config.packages());
@@ -73,14 +74,14 @@ public static PythonEnvironment setup(VenvConfig config) throws IOException {
7374

7475
// Check stored fingerprint
7576
FingerprintManager.Fingerprint stored = FingerprintManager.read(venvDir);
76-
boolean pythonPresent = PythonResolver.findPythonInDir(venvDir) != null;
77+
boolean pythonPresent = PythonResolver.findPythonInDir(venvDir, resolvedTargetOs) != null;
7778
boolean pythonVersionMatch = stored != null && pythonVersion.equals(stored.pythonVersion);
7879
boolean packagesMatch = stored != null && currentPackageHash.equals(stored.packageHash);
7980
boolean sourceKnown = stored != null && stored.pythonSource != null;
8081

8182
if (pythonPresent && pythonVersionMatch && packagesMatch && sourceKnown) {
8283
log.accept("Python environment is up to date, skipping setup");
83-
Path pythonExe = PythonResolver.findPythonInDir(venvDir);
84+
Path pythonExe = PythonResolver.findPythonInDir(venvDir, resolvedTargetOs);
8485
return new PythonEnvironment(pythonExe, venvDir, stored.pythonSource);
8586
}
8687

@@ -99,21 +100,19 @@ public static PythonEnvironment setup(VenvConfig config) throws IOException {
99100
if (systemPython != null && !systemPython.contains(java.io.File.separator)) {
100101
// System Python found
101102
pythonSource = "system";
102-
String targetOs = PythonResolver.resolveTargetOs(config.targetOs());
103103
log.accept("Creating venv...");
104104
runCommand(log, systemPython, "-m", "venv", venvDir.toString());
105105
pythonExe = PythonResolver.resolveVenvPython(venvDir);
106106
} else {
107107
// Download python-build-standalone
108108
pythonSource = "bundled";
109-
String targetOs = PythonResolver.resolveTargetOs(config.targetOs());
110109
Path cacheDir = Path.of(System.getProperty("user.home"),
111110
".python-embed");
112111
log.accept("System Python not found. Downloading python-build-standalone...");
113-
pythonExe = PythonDownloader.download(pythonVersion, targetOs, cacheDir, venvDir, log);
112+
pythonExe = PythonDownloader.download(pythonVersion, resolvedTargetOs, cacheDir, venvDir, log);
114113
}
115114
} else {
116-
pythonExe = PythonResolver.findPythonInDir(venvDir);
115+
pythonExe = PythonResolver.findPythonInDir(venvDir, resolvedTargetOs);
117116
if (pythonExe == null) {
118117
throw new IOException("Python executable not found in: " + venvDir);
119118
}

0 commit comments

Comments
 (0)