All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openrewrite.python.PythonParser Maven / Gradle / Ivy

There is a newer version: 1.24.4
Show newest version
/*
 * Copyright 2023 the original author or authors.
 * 

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* https://www.apache.org/licenses/LICENSE-2.0 *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.openrewrite.python; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.internal.EncodingDetectingInputStream; import org.openrewrite.java.internal.JavaTypeCache; import org.openrewrite.python.tree.Py; import org.openrewrite.remote.ReceiverContext; import org.openrewrite.remote.RemotingContext; import org.openrewrite.remote.RemotingExecutionContextView; import org.openrewrite.remote.java.RemotingClient; import org.openrewrite.style.NamedStyles; import org.openrewrite.text.PlainTextParser; import org.openrewrite.tree.ParseError; import org.openrewrite.tree.ParsingEventListener; import org.openrewrite.tree.ParsingExecutionContextView; import java.io.*; import java.net.InetAddress; import java.net.Socket; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.Stream; import static java.util.Collections.singletonList; import static java.util.Objects.requireNonNull; @SuppressWarnings("unused") @RequiredArgsConstructor(access = AccessLevel.PRIVATE) public class PythonParser implements Parser { private final Collection styles; private final boolean logCompilationWarningsAndErrors; private final JavaTypeCache typeCache; private final List pythonPath; private final @Nullable Path logFile; private final int parseTimeoutMs; private @Nullable Process pythonProcess; private @Nullable RemotingContext remotingContext; private @Nullable RemotingClient client; @Override public Stream parse(String... sources) { List inputs = new ArrayList<>(sources.length); for (int i = 0; i < sources.length; i++) { Path path = Paths.get("p" + i + ".py"); int j = i; inputs.add(new Input( path, null, () -> new ByteArrayInputStream(sources[j].getBytes(StandardCharsets.UTF_8)), true )); } return parseInputs( inputs, null, new InMemoryExecutionContext() ); } @Override public Stream parseInputs(Iterable inputs, @Nullable Path relativeTo, ExecutionContext ctx) { if (!ensureServerRunning(ctx) || client == null) { return PlainTextParser.builder().build().parseInputs(inputs, relativeTo, ctx); } ParsingExecutionContextView pctx = ParsingExecutionContextView.view(ctx); ParsingEventListener parsingListener = pctx.getParsingListener(); return acceptedInputs(inputs).map(input -> { Path path = input.getRelativePath(relativeTo); parsingListener.startedParsing(input); try (EncodingDetectingInputStream is = input.getSource(ctx)) { SourceFile parsed = client.withNewSocket((socket, messenger) -> requireNonNull(messenger.sendRequest(generator -> { if (input.isSynthetic() || !Files.isRegularFile(input.getPath())) { generator.writeString("parse-python-source"); generator.writeString(is.readFully()); } else { generator.writeString("parse-python-file"); generator.writeString(input.getPath().toString()); } // make sure we don't block forever when reading if (parseTimeoutMs > 0) { socket.setSoTimeout(parseTimeoutMs); } }, parser -> { Tree tree = new ReceiverContext(remotingContext.newReceiver(parser), remotingContext).receiveTree(null); return (SourceFile) tree; }, socket))) .withSourcePath(path) .withFileAttributes(FileAttributes.fromPath(input.getPath())) .withCharset(getCharset(ctx)); if (parsed instanceof ParseError) { ctx.getOnError().accept(new AssertionError(parsed)); return parsed; } Py.CompilationUnit py = (Py.CompilationUnit) parsed; parsingListener.parsed(input, py); return requirePrintEqualsInput(py, input, relativeTo, ctx); } catch (Throwable t) { ctx.getOnError().accept(t); return ParseError.build(this, input, relativeTo, ctx, t); } finally { // NOTE: this is only because we parse one source at the time client.getContext().reset(); } }); } private boolean ensureServerRunning(ExecutionContext ctx) { if (client == null || !isAlive()) { try { initializeRemoting(ctx); } catch (IOException e) { return false; } } else { requireNonNull(remotingContext).reset(); } return client != null && isAlive(); } private boolean isAlive() { try { return requireNonNull(client).runUsingSocket((socket, messenger) -> { messenger.sendReset(socket); return true; }); } catch (Exception e) { return false; } } private void initializeRemoting(ExecutionContext ctx) throws IOException { RemotingExecutionContextView view = RemotingExecutionContextView.view(ctx); remotingContext = view.getRemotingContext(); if (remotingContext == null) { remotingContext = new RemotingContext(PythonParser.class.getClassLoader(), false); view.setRemotingContext(remotingContext); } else { remotingContext.reset(); } int port = 54322; if (!isServerRunning(port)) { ProcessBuilder processBuilder = new ProcessBuilder("python3", "-m", "rewrite.remote.server", Integer.toString(port)); if (!pythonPath.isEmpty()) { Map environment = processBuilder.environment(); environment.compute("PYTHONPATH", (k, current) -> (current != null ? current + File.pathSeparator : "") + pythonPath.stream().map(Path::toString).collect(Collectors.joining(File.pathSeparator))); } File redirectTo = logFile != null ? logFile.toFile() : new File(System.getProperty("os.name").startsWith("Windows") ? "NULL" : "/dev/null"); processBuilder.redirectOutput(redirectTo); processBuilder.redirectError(redirectTo); pythonProcess = processBuilder.start(); for (int i = 0; i < 30 && pythonProcess.isAlive(); i++) { if (isServerRunning(port)) { break; } try { Thread.sleep(100); } catch (InterruptedException ignore) { } } if (pythonProcess == null || !pythonProcess.isAlive()) { remotingContext = null; return; } Runtime.getRuntime().addShutdownHook(new Thread(() -> { if (pythonProcess != null && pythonProcess.isAlive()) { pythonProcess.destroy(); } })); } client = RemotingClient.create(ctx, PythonParser.class, () -> { try { return new Socket(InetAddress.getLoopbackAddress(), port); } catch (IOException e) { throw new UncheckedIOException(e); } }); } public static boolean isServerRunning(int port) { try (Socket socket = new Socket(InetAddress.getLoopbackAddress(), port)) { return true; } catch (IOException e) { return false; } } @Override public boolean accept(Path path) { return path.toString().endsWith(".py"); } @Override public PythonParser reset() { typeCache.clear(); if (remotingContext != null) { remotingContext.reset(); remotingContext = null; } if (pythonProcess != null) { pythonProcess.destroy(); pythonProcess = null; } client = null; return this; } @Override public Path sourcePathFromSourceText(Path prefix, String sourceCode) { return prefix.resolve("file.py"); } public static Builder builder() { return new Builder(); } public static Builder usingRemotingInstallation(Path dir) { try { return verifyRemotingInstallation(dir); } catch (IOException | InterruptedException ignore) { } return builder(); } private static Builder verifyRemotingInstallation(Path dir) throws IOException, InterruptedException { if (!Files.isDirectory(dir)) { Files.createDirectories(dir); } try (InputStream inputStream = requireNonNull(PythonParser.class.getClassLoader().getResourceAsStream("META-INF/python-requirements.txt"))) { List packages = new BufferedReader(new InputStreamReader(inputStream)).lines().filter(l -> !l.isEmpty()).collect(Collectors.toList()); List command = new ArrayList<>(Arrays.asList("python3", "-m", "pip", "install", "--target", dir.toString())); command.addAll(packages); ProcessBuilder processBuilder = new ProcessBuilder(command); Process process = processBuilder.start(); int exitCode = process.waitFor(); return new Builder().pythonPath(singletonList(dir)); } } @SuppressWarnings("unused") public static class Builder extends Parser.Builder { private JavaTypeCache typeCache = new JavaTypeCache(); private boolean logCompilationWarningsAndErrors; private final Collection styles = new ArrayList<>(); private List pythonPath = new ArrayList<>(); private @Nullable Path logFile; private long parseTimeoutMs = -1; private Builder() { super(Py.CompilationUnit.class); } public Builder logCompilationWarningsAndErrors(boolean logCompilationWarningsAndErrors) { this.logCompilationWarningsAndErrors = logCompilationWarningsAndErrors; return this; } public Builder typeCache(JavaTypeCache typeCache) { this.typeCache = typeCache; return this; } public Builder styles(Iterable styles) { for (NamedStyles style : styles) { this.styles.add(style); } return this; } public Builder logFile(Path path) { this.logFile = path; return this; } public Builder parseTimeout(long timeout, TimeUnit timeUnit) { this.parseTimeoutMs = timeUnit.toMillis(timeout); return this; } public Builder pythonPath(List path) { this.pythonPath = new ArrayList<>(path); return this; } @Override public PythonParser build() { return new PythonParser(styles, logCompilationWarningsAndErrors, typeCache, pythonPath, logFile, (int) parseTimeoutMs); } @Override public String getDslName() { return "python"; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy