@@ -491,10 +491,11 @@ public Builder workingDirectory(@Nullable Path workingDirectory) {
491491
492492 /**
493493 * Set the base pip packages directory.
494- * When set and the required version is not already available in the Python
495- * interpreter, a version-specific subdirectory (e.g., {@code <pipPackagesPath>/8.74.1/}
496- * or {@code <pipPackagesPath>/8.75.0.dev0/}) is resolved and the openrewrite
497- * package is automatically installed there via pip.
494+ * When set and the required release version is not already available in the
495+ * Python interpreter, a version-specific subdirectory (e.g.,
496+ * {@code <pipPackagesPath>/8.74.1/}) is resolved and the openrewrite package is
497+ * automatically installed there via pip. Dev builds ({@code .dev0}) are not
498+ * installed this way and require the interpreter to already have the package.
498499 *
499500 * @param pipPackagesPath The base directory under which version-specific pip packages are installed
500501 * @return This builder
@@ -538,10 +539,11 @@ public Builder pythonVersion(String pythonVersion) {
538539 public PythonRewriteRpc get () {
539540 String version = StringUtils .readFully (
540541 PythonRewriteRpc .class .getResourceAsStream ("/META-INF/rewrite-python-version.txt" )).trim ();
542+ boolean isDevBuild = version .isEmpty () || version .endsWith (".dev0" );
541543
542544 Path resolvedPipPackagesPath = null ;
543- if (!version . isEmpty () ) {
544- // Known version (release or dev pre-release) — try to find or install it .
545+ if (!isDevBuild ) {
546+ // Release version — try to find or install the pinned version .
545547 // 1. Check pipPackagesPath for an existing install
546548 // 2. Check if the interpreter already has the right version
547549 // 3. Install to pipPackagesPath if available, otherwise fail
@@ -559,8 +561,8 @@ public PythonRewriteRpc get() {
559561 "or install the package manually: pip install openrewrite==" + version );
560562 }
561563 } else {
562- // No version info (local dev build) — require the interpreter to already
563- // have the rewrite package (e.g., from a venv with an editable install).
564+ // Local dev build — require the interpreter to already have the rewrite
565+ // package (e.g., from a venv with an editable install).
564566 if (!canImportRewrite (pythonPath )) {
565567 throw new IllegalStateException (
566568 "The Python interpreter at " + pythonPath + " cannot import the 'rewrite' package. " +
@@ -609,9 +611,9 @@ public PythonRewriteRpc get() {
609611 // If debug source path is set, use it
610612 if (debugRewriteSourcePath != null ) {
611613 pythonPathParts .add (debugRewriteSourcePath .toAbsolutePath ().normalize ().toString ());
612- } else if (version . isEmpty () ) {
613- // For local dev builds without a version file , try to find the Python
614- // source in the project structure (as a fallback for PYTHONPATH)
614+ } else if (isDevBuild ) {
615+ // For local dev builds, try to find the Python source in the project
616+ // structure (as a fallback for PYTHONPATH)
615617 Path basePath = workingDirectory != null ? workingDirectory : Paths .get (System .getProperty ("user.dir" ));
616618
617619 // Check common locations
@@ -730,30 +732,30 @@ private void bootstrapOpenrewrite(Path pipPackagesPath, String version) {
730732 pb .redirectOutput (ProcessBuilder .Redirect .appendTo (logFile ));
731733 }
732734 Process process = pb .start ();
735+ String pipOutput = "" ;
733736 if (log == null ) {
734- // Drain stdout+stderr to prevent pipe buffer from filling and blocking
735- Thread drainer = new Thread (() -> {
736- try (InputStream is = process .getInputStream ()) {
737- byte [] buf = new byte [4096 ];
738- //noinspection StatementWithEmptyBody
739- while (is .read (buf ) != -1 ) {
740- }
741- } catch (IOException ignored ) {
742- }
743- });
744- drainer .setDaemon (true );
745- drainer .start ();
737+ // Capture stdout+stderr so we can include it in error messages
738+ try (InputStream is = process .getInputStream ()) {
739+ pipOutput = StringUtils .readFully (is );
740+ }
746741 }
747742 boolean completed = process .waitFor (2 , TimeUnit .MINUTES );
748743
749744 if (!completed ) {
750745 process .destroyForcibly ();
751- throw new RuntimeException ("Timed out bootstrapping openrewrite package" );
746+ throw new RuntimeException ("Timed out bootstrapping openrewrite==" + version );
752747 }
753748
754749 int exitCode = process .exitValue ();
755750 if (exitCode != 0 ) {
756- throw new RuntimeException ("Failed to bootstrap openrewrite package, pip install exited with code " + exitCode );
751+ String message = "Failed to install openrewrite==" + version +
752+ " (pip exited with code " + exitCode + ")" ;
753+ if (!pipOutput .isEmpty ()) {
754+ message += ":\n " + pipOutput .trim ();
755+ } else if (log != null ) {
756+ message += ". See " + log .toAbsolutePath ().normalize () + " for details" ;
757+ }
758+ throw new RuntimeException (message );
757759 }
758760
759761 Files .write (pipPackagesPath .resolve (".openrewrite-version" ), version .getBytes (StandardCharsets .UTF_8 ));
0 commit comments