Skip to content

Commit 846d795

Browse files
committed
Pin openrewrite Python package version in PythonRewriteRpc bootstrap
PythonRewriteRpc.bootstrapOpenrewrite() was running bare `pip install openrewrite` without a version specifier, unlike the JavaScript side which reads /META-INF/version.txt and pins `@openrewrite/rewrite@<version>`. This meant the Python RPC could end up with a mismatched openrewrite package version, and the existence-only check meant users could get stuck on a stale version indefinitely. Changes: - Generate META-INF/version.txt in the build (PEP 440 format) so the Java code can read the expected Python package version at runtime - Pin `openrewrite==<version>` in bootstrapOpenrewrite() for release and CI builds; skip pinning for local .dev0 builds - Track installed version via a marker file to detect and upgrade stale installs - Add license exclude for version.txt and .gitignore entry
1 parent e7f8710 commit 846d795

3 files changed

Lines changed: 59 additions & 5 deletions

File tree

rewrite-python/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
src/main/resources/META-INF/version.txt

rewrite-python/build.gradle.kts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
@file:Suppress("UnstableApiUsage")
22

3+
import nl.javadude.gradle.plugins.license.LicenseExtension
34
import java.time.LocalDateTime
45
import java.time.format.DateTimeFormatter
56

@@ -196,7 +197,7 @@ tasks.withType<Test> {
196197
// This is separate from Gradle because IntelliJ's Gradle integration doesn't support Python source roots.
197198

198199
// ============================================
199-
// Python Publishing Tasks (PyPI)
200+
// Version Resource (for RPC version pinning)
200201
// ============================================
201202

202203
// Generate a PEP 440 compliant version for CI builds
@@ -211,6 +212,31 @@ val pythonVersion: String = if (System.getenv("CI") != null) {
211212
project.version.toString().replace("-SNAPSHOT", ".dev0")
212213
}
213214

215+
// Write version.txt resource so PythonRewriteRpc can pin the pip package version
216+
val generateVersionTxt by tasks.registering {
217+
group = "python"
218+
description = "Generate META-INF/version.txt for RPC version pinning"
219+
220+
val versionTxt = file("src/main/resources/META-INF/version.txt")
221+
inputs.property("version", pythonVersion)
222+
outputs.file(versionTxt)
223+
224+
doLast {
225+
versionTxt.parentFile.mkdirs()
226+
versionTxt.writeText(pythonVersion)
227+
}
228+
}
229+
230+
listOf("sourcesJar", "processResources", "licenseMain", "assemble").forEach {
231+
tasks.named(it) {
232+
dependsOn(generateVersionTxt)
233+
}
234+
}
235+
236+
// ============================================
237+
// Python Publishing Tasks (PyPI)
238+
// ============================================
239+
214240
// Task to update version in pyproject.toml
215241
val pythonUpdateVersion by tasks.registering {
216242
group = "python"
@@ -364,3 +390,7 @@ val printTestClasspath by tasks.registering {
364390
}
365391
}
366392

393+
extensions.configure<LicenseExtension> {
394+
exclude("**/version.txt")
395+
}
396+

rewrite-python/src/main/java/org/openrewrite/python/rpc/PythonRewriteRpc.java

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import lombok.RequiredArgsConstructor;
2020
import org.jspecify.annotations.Nullable;
2121
import org.openrewrite.*;
22+
import org.openrewrite.internal.StringUtils;
2223
import org.openrewrite.marketplace.RecipeBundleResolver;
2324
import org.openrewrite.marketplace.RecipeMarketplace;
2425
import org.openrewrite.python.*;
@@ -607,19 +608,36 @@ public PythonRewriteRpc get() {
607608
* This is required for the RPC server to start.
608609
*/
609610
private void bootstrapOpenrewrite(Path pipPackagesPath) {
610-
Path rewriteModule = pipPackagesPath.resolve("rewrite");
611-
if (Files.exists(rewriteModule)) {
612-
return; // Already installed
611+
String version = StringUtils.readFully(
612+
PythonRewriteRpc.class.getResourceAsStream("/META-INF/version.txt"));
613+
boolean pinVersion = !version.isEmpty() && !version.endsWith(".dev0");
614+
615+
Path versionMarker = pipPackagesPath.resolve(".openrewrite-version");
616+
if (Files.exists(pipPackagesPath.resolve("rewrite"))) {
617+
// Already installed — check if version matches
618+
if (!pinVersion) {
619+
return;
620+
}
621+
try {
622+
if (Files.exists(versionMarker) &&
623+
version.equals(new String(Files.readAllBytes(versionMarker), StandardCharsets.UTF_8).trim())) {
624+
return; // Correct version already installed
625+
}
626+
} catch (IOException ignored) {
627+
// Can't read marker, reinstall to be safe
628+
}
613629
}
614630

631+
String packageSpec = pinVersion ? "openrewrite==" + version : "openrewrite";
632+
615633
try {
616634
Files.createDirectories(pipPackagesPath);
617635

618636
ProcessBuilder pb = new ProcessBuilder(
619637
pythonPath.toString(),
620638
"-m", "pip", "install",
621639
"--target=" + pipPackagesPath.toAbsolutePath().normalize(),
622-
"openrewrite"
640+
packageSpec
623641
);
624642
pb.redirectErrorStream(true);
625643
if (log != null) {
@@ -652,6 +670,11 @@ private void bootstrapOpenrewrite(Path pipPackagesPath) {
652670
if (exitCode != 0) {
653671
throw new RuntimeException("Failed to bootstrap openrewrite package, pip install exited with code " + exitCode);
654672
}
673+
674+
// Write version marker so we can detect stale installs
675+
if (pinVersion) {
676+
Files.write(versionMarker, version.getBytes(StandardCharsets.UTF_8));
677+
}
655678
} catch (IOException e) {
656679
throw new UncheckedIOException("Failed to bootstrap openrewrite package", e);
657680
} catch (InterruptedException e) {

0 commit comments

Comments
 (0)