Skip to content

0.1.6#5

Merged
jurgenei merged 4 commits into
mainfrom
0.1.6
Jul 14, 2026
Merged

0.1.6#5
jurgenei merged 4 commits into
mainfrom
0.1.6

Conversation

@jurgenei

Copy link
Copy Markdown
Owner

Misc fixes maturing the plugin

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Release 0.1.6: Schematron cache/params + serializer outputMethod support

✨ Enhancement 🐞 Bug fix 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Add persistent compiled Schematron stylesheet caching and SchXslt transpiler parameter support.
• Add configurable/auto-inferred Saxon serializer output method for XSLT/XQuery transforms.
• Fix Gradle configuration-cache/warning-mode issues by avoiding execution-time Project access.
Diagram

graph TD
  A["Gradle build"] --> B["SchematronTask"] --> C{{"SchXslt transpiler (Saxon)"}} --> D[("Compiled XSL cache")]
  B --> E{{"Saxon XSLT validator"}} --> F[("SVRL/JUnit reports")]
  A --> G["XsltTask/XQueryTask"] --> H{{"Saxon serializer"}}
  subgraph Legend
    direction LR
    _task["Task"] ~~~ _file[("File") ] ~~~ _lib{{"Library"}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Rely purely on Gradle up-to-date checking (declare inputs/outputs)
  • ➕ Avoid custom SHA marker files and manual timestamp logic
  • ➕ Lets Gradle handle correctness across build cache/up-to-date semantics
  • ➖ Harder to express “recompile only when transpiler params changed” without a single aggregated input
  • ➖ May require modeling transpiler params as a structured @input map and possibly splitting compilation into a separate task
2. Split Schematron compilation into a dedicated task producing the compiled XSL
  • ➕ Cleaner separation of concerns: compile once, validate many
  • ➕ Gradle can incrementally manage compiled stylesheet as a first-class output
  • ➖ More user-facing tasks/wiring (needs consumers to depend on compiled output)
  • ➖ May complicate simple one-task usage for newcomers
3. Use an in-memory cache via a shared BuildService (per-build)
  • ➕ Avoids filesystem marker management
  • ➕ Can be faster for repeated validations within one build invocation
  • ➖ Does not persist across builds (unlike the new style file option)
  • ➖ Requires careful concurrency and lifecycle handling

Recommendation: The PR’s approach (optional persistent compiled stylesheet file + parameter fingerprint) is a good fit for a single-task UX and cross-build reuse. Consider, as a follow-up, whether the fingerprint marker should be treated as a declared output (or moved alongside the compiled XSL with clear cleanup semantics) to make task outputs more explicit and avoid surprising extra files.

Files changed (13) +932 / -20

Enhancement (4) +544 / -15
AbstractXmlTransformTask.javaAdd outputMethod option and inference for Saxon serializer +62/-0

Add outputMethod option and inference for Saxon serializer

• Introduces an optional 'outputMethod' task input (xml/json/text) and infers the serializer method from the output file extension when unset. Includes normalization/validation and adds 'outputMethod' to the non-file input fingerprint for correctness of incremental behavior.

src/main/java/name/jurgenei/gradle/xml/AbstractXmlTransformTask.java

SchematronTask.javaAdd compiled stylesheet caching and SchXslt transpiler parameterization +480/-12

Add compiled stylesheet caching and SchXslt transpiler parameterization

• Adds an optional 'style' output file to persist and reuse the compiled Schematron stylesheet across runs, with recompilation triggered by schema/transpiler timestamps and a SHA-256 fingerprint of relevant inputs/parameters. Switches compilation/validation to Saxon s9api and exposes many SchXslt transpiler parameters as optional '@Input' properties, passing them only when explicitly set; debug mode also enables readable indentation for SVRL output.

src/main/java/name/jurgenei/gradle/xml/SchematronTask.java

XQueryTask.javaUse resolved serializer output method instead of hardcoded xml +1/-2

Use resolved serializer output method instead of hardcoded xml

• Updates XQuery output serialization to use 'resolveSerializerMethod(outputFile)' so JSON/text outputs can be generated when configured or inferred.

src/main/java/name/jurgenei/gradle/xml/XQueryTask.java

XsltTask.javaUse resolved serializer output method instead of hardcoded xml +1/-1

Use resolved serializer output method instead of hardcoded xml

• Updates XSLT output serialization to use 'resolveSerializerMethod(outputFile)' so JSON/text outputs can be generated when configured or inferred.

src/main/java/name/jurgenei/gradle/xml/XsltTask.java

Bug fix (1) +12 / -2
AbstractXmlValidationTask.javaAvoid execution-time Project access by injecting projectDir property +12/-2

Avoid execution-time Project access by injecting projectDir property

• Adds an internal 'projectDir' DirectoryProperty and uses it for execution-time relative path calculations instead of calling 'getProject().getProjectDir()' during execution. This removes configuration-cache/warning-mode issues related to deprecated task project access.

src/main/java/name/jurgenei/gradle/xml/AbstractXmlValidationTask.java

Tests (3) +352 / -0
SchematronTaskIntegrationTest.javaAdd integration coverage for style caching, debug formatting, and config-cache safety +180/-0

Add integration coverage for style caching, debug formatting, and config-cache safety

• Adds tests verifying persisted compiled stylesheet reuse and recompilation when transpiler parameters change, debug mode indentation in SVRL output, and compatibility with '--configuration-cache' and '--warning-mode=fail' (no execution-time task project usage).

src/test/java/name/jurgenei/gradle/xml/SchematronTaskIntegrationTest.java

XQueryTaskIntegrationTest.javaTest outputMethod inference and explicit override for XQuery outputs +81/-0

Test outputMethod inference and explicit override for XQuery outputs

• Adds integration tests that confirm JSON output is inferred from '.json' outputExtension and that an explicit 'outputMethod' (e.g., 'text') overrides extension inference.

src/test/java/name/jurgenei/gradle/xml/XQueryTaskIntegrationTest.java

XsltTaskIntegrationTest.javaTest outputMethod inference and explicit override for XSLT outputs +91/-0

Test outputMethod inference and explicit override for XSLT outputs

• Adds integration tests that confirm JSON output is inferred from '.json' outputExtension and that an explicit 'outputMethod' (e.g., 'text') overrides extension inference.

src/test/java/name/jurgenei/gradle/xml/XsltTaskIntegrationTest.java

Documentation (2) +18 / -1
README.mdDocument Schematron stylesheet cache and transpiler parameters +17/-0

Document Schematron stylesheet cache and transpiler parameters

• Extends the SchematronTask usage example with the optional persistent compiled stylesheet ('style') and shows sample transpiler parameter usage (e.g., 'phase', 'severityThreshold'). Adds a dedicated section enumerating Schematron-specific options and which SchXslt params are supported.

README.md

README.mdUpdate validation sample description for persisted Schematron style/params +1/-1

Update validation sample description for persisted Schematron style/params

• Clarifies that the validation sample demonstrates persisted compiled Schematron stylesheets and transpiler parameters in addition to SVRL/JUnit reporting.

samples/README.md

Other (3) +6 / -2
build.gradle.ktsBump plugin version to 0.1.6 +1/-1

Bump plugin version to 0.1.6

• Updates the project version from 0.1.3 to 0.1.6 to match the release.

build.gradle.kts

build.gradleAdd Schematron style cache and transpiler params to validation sample +4/-0

Add Schematron style cache and transpiler params to validation sample

• Configures 'SchematronTask' with a persisted compiled stylesheet ('style') and sets example SchXslt parameters ('phase', 'severityThreshold'). Updates sample verification to assert the compiled stylesheet is produced.

samples/validation-basic/build.gradle

settings.gradle.ktsRename root project to gradle-xml-plugin +1/-1

Rename root project to gradle-xml-plugin

• Changes the root project name from "gradle-xml-transform" to "gradle-xml-plugin".

settings.gradle.kts

@jurgenei
jurgenei merged commit 15022f1 into main Jul 14, 2026
3 checks passed
@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Stale marker skips recompile 🐞 Bug ☼ Reliability
Description
SchematronTask only updates the fingerprint marker after compileSchematron succeeds; if compilation
fails after truncating/partially writing the compiled stylesheet, the old marker can remain valid
and needsRecompile() may incorrectly skip recompilation on the next run, reusing a corrupted
stylesheet.
Code

src/main/java/name/jurgenei/gradle/xml/SchematronTask.java[R363-368]

+        File compiledFile = getStyle().get().getAsFile();
+        synchronized (transpilerCompileLock) {
+            if (needsRecompile(compiledFile, transpilerParameters)) {
+                compileSchematron(compiledFile, transpilerParameters);
+                writeFingerprint(fingerprintMarkerFor(compiledFile), computeTranspilerFingerprint(transpilerParameters));
+            }
Evidence
The compiled stylesheet is rewritten before the marker is updated, and the recompile decision trusts
the marker match; this can allow a partially-written compiled stylesheet to be treated as current on
a subsequent run.

src/main/java/name/jurgenei/gradle/xml/SchematronTask.java[355-393]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`SchematronTask` persists a compiled stylesheet and a `.transpiler.sha256` fingerprint marker. The marker is only written after compilation succeeds. If compilation fails after it has already modified the compiled stylesheet file (e.g., truncated/partially written), the old marker can remain and still match the current parameter fingerprint. On the next run, `needsRecompile()` can treat the compiled stylesheet as current and skip recompiling, causing repeated failures (or incorrect behavior) until the compiled file is manually deleted.

### Issue Context
- `resolveCompiledStylesheet()` calls `compileSchematron(compiledFile, ...)` and then writes the fingerprint marker.
- `needsRecompile()` relies on the marker match to skip recompilation.

### Fix Focus Areas
- src/main/java/name/jurgenei/gradle/xml/SchematronTask.java[355-393]

### Suggested implementation approach
- Compile to a temp file in the same directory and then atomically move/replace `compiledFile` (best effort: `Files.move(..., REPLACE_EXISTING, ATOMIC_MOVE)` with fallback).
- Only write the fingerprint marker after the move/replace succeeds.
- If compilation throws, delete/clear the fingerprint marker (and optionally delete the partially-written compiled file) so the next run will recompile.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

2. Undeclared marker output 🐞 Bug ⚙ Maintainability
Description
SchematronTask writes a sidecar fingerprint file '<compiled>.transpiler.sha256' but does not declare
it as an output, so Gradle output tracking/cleanup does not model this generated file and it may be
unexpectedly created alongside user-chosen style paths.
Code

src/main/java/name/jurgenei/gradle/xml/SchematronTask.java[R455-473]

+    private File fingerprintMarkerFor(File compiledFile) {
+        return new File(compiledFile.getParentFile(), compiledFile.getName() + ".transpiler.sha256");
+    }
+
+    private boolean isFingerprintCurrent(File markerFile, String currentFingerprint) {
+        if (!markerFile.isFile()) {
+            return false;
+        }
+        try {
+            String recorded = Files.readString(markerFile.toPath(), StandardCharsets.UTF_8).trim();
+            return currentFingerprint.equals(recorded);
+        } catch (Exception ignored) {
+            return false;
+        }
+    }
+
+    private void writeFingerprint(File markerFile, String fingerprint) throws Exception {
+        Files.writeString(markerFile.toPath(), fingerprint, StandardCharsets.UTF_8);
+    }
Evidence
The task explicitly constructs and writes the marker file, but only getStyle() is declared as an
output; the marker file itself is not declared despite being generated.

src/main/java/name/jurgenei/gradle/xml/SchematronTask.java[96-99]
src/main/java/name/jurgenei/gradle/xml/SchematronTask.java[455-473]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`SchematronTask` generates a fingerprint sidecar file (`.transpiler.sha256`) next to the persisted compiled stylesheet. This file is written during task execution but is not declared as an output (`@OutputFile` / `@OutputDirectory`), which makes Gradle's output model incomplete and can leave untracked generated files.

### Issue Context
- The compiled stylesheet is declared via `getStyle()` as `@OutputFile`.
- The marker file is created via `fingerprintMarkerFor(...)` and `writeFingerprint(...)`.

### Fix Focus Areas
- src/main/java/name/jurgenei/gradle/xml/SchematronTask.java[88-100]
- src/main/java/name/jurgenei/gradle/xml/SchematronTask.java[455-473]

### Suggested implementation approach
- Add a derived task property for the marker file (e.g., `getStyleFingerprintFile()`) annotated as `@OutputFile` and computed from `getStyle()`.
- Alternatively, store the fingerprint under an existing declared output directory and declare that directory as an `@OutputDirectory` for the marker(s).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment on lines +363 to +368
File compiledFile = getStyle().get().getAsFile();
synchronized (transpilerCompileLock) {
if (needsRecompile(compiledFile, transpilerParameters)) {
compileSchematron(compiledFile, transpilerParameters);
writeFingerprint(fingerprintMarkerFor(compiledFile), computeTranspilerFingerprint(transpilerParameters));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

1. Stale marker skips recompile 🐞 Bug ☼ Reliability

SchematronTask only updates the fingerprint marker after compileSchematron succeeds; if compilation
fails after truncating/partially writing the compiled stylesheet, the old marker can remain valid
and needsRecompile() may incorrectly skip recompilation on the next run, reusing a corrupted
stylesheet.
Agent Prompt
### Issue description
`SchematronTask` persists a compiled stylesheet and a `.transpiler.sha256` fingerprint marker. The marker is only written after compilation succeeds. If compilation fails after it has already modified the compiled stylesheet file (e.g., truncated/partially written), the old marker can remain and still match the current parameter fingerprint. On the next run, `needsRecompile()` can treat the compiled stylesheet as current and skip recompiling, causing repeated failures (or incorrect behavior) until the compiled file is manually deleted.

### Issue Context
- `resolveCompiledStylesheet()` calls `compileSchematron(compiledFile, ...)` and then writes the fingerprint marker.
- `needsRecompile()` relies on the marker match to skip recompilation.

### Fix Focus Areas
- src/main/java/name/jurgenei/gradle/xml/SchematronTask.java[355-393]

### Suggested implementation approach
- Compile to a temp file in the same directory and then atomically move/replace `compiledFile` (best effort: `Files.move(..., REPLACE_EXISTING, ATOMIC_MOVE)` with fallback).
- Only write the fingerprint marker after the move/replace succeeds.
- If compilation throws, delete/clear the fingerprint marker (and optionally delete the partially-written compiled file) so the next run will recompile.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +455 to +473
private File fingerprintMarkerFor(File compiledFile) {
return new File(compiledFile.getParentFile(), compiledFile.getName() + ".transpiler.sha256");
}

private boolean isFingerprintCurrent(File markerFile, String currentFingerprint) {
if (!markerFile.isFile()) {
return false;
}
try {
String recorded = Files.readString(markerFile.toPath(), StandardCharsets.UTF_8).trim();
return currentFingerprint.equals(recorded);
} catch (Exception ignored) {
return false;
}
}

private void writeFingerprint(File markerFile, String fingerprint) throws Exception {
Files.writeString(markerFile.toPath(), fingerprint, StandardCharsets.UTF_8);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Informational

2. Undeclared marker output 🐞 Bug ⚙ Maintainability

SchematronTask writes a sidecar fingerprint file '<compiled>.transpiler.sha256' but does not declare
it as an output, so Gradle output tracking/cleanup does not model this generated file and it may be
unexpectedly created alongside user-chosen style paths.
Agent Prompt
### Issue description
`SchematronTask` generates a fingerprint sidecar file (`.transpiler.sha256`) next to the persisted compiled stylesheet. This file is written during task execution but is not declared as an output (`@OutputFile` / `@OutputDirectory`), which makes Gradle's output model incomplete and can leave untracked generated files.

### Issue Context
- The compiled stylesheet is declared via `getStyle()` as `@OutputFile`.
- The marker file is created via `fingerprintMarkerFor(...)` and `writeFingerprint(...)`.

### Fix Focus Areas
- src/main/java/name/jurgenei/gradle/xml/SchematronTask.java[88-100]
- src/main/java/name/jurgenei/gradle/xml/SchematronTask.java[455-473]

### Suggested implementation approach
- Add a derived task property for the marker file (e.g., `getStyleFingerprintFile()`) annotated as `@OutputFile` and computed from `getStyle()`.
- Alternatively, store the fingerprint under an existing declared output directory and declare that directory as an `@OutputDirectory` for the marker(s).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@jurgenei
jurgenei deleted the 0.1.6 branch July 14, 2026 12:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant