Skip to content

Commit f3c92a1

Browse files
author
GroG
committed
fixed py4j
1 parent 81efafc commit f3c92a1

File tree

1 file changed

+82
-66
lines changed
  • src/main/java/org/myrobotlab/service

1 file changed

+82
-66
lines changed

src/main/java/org/myrobotlab/service/Py4j.java

+82-66
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import java.util.List;
1111
import java.util.Map;
1212

13-
import org.bytedeco.javacpp.Loader;
1413
import org.myrobotlab.codec.CodecUtils;
1514
import org.myrobotlab.framework.Message;
1615
import org.myrobotlab.framework.Platform;
@@ -156,10 +155,6 @@ public void run() {
156155
*/
157156
protected Py4jClient pythonProcess = null;
158157

159-
/**
160-
* The base command to launch the Python interpreter without any arguments.
161-
*/
162-
protected transient String pythonCommand = "python";
163158

164159
public Py4j(String n, String id) {
165160
super(n, id);
@@ -172,9 +167,9 @@ public Py4j(String n, String id) {
172167
* data/Py4j/{serviceName}
173168
*
174169
* @param scriptName
175-
* - name of the script
170+
* - name of the script
176171
* @param code
177-
* - code block
172+
* - code block
178173
*/
179174
public void addScript(String scriptName, String code) {
180175
Py4jConfig c = (Py4jConfig) config;
@@ -208,7 +203,7 @@ public boolean autostartPython(boolean b) {
208203
* removes script from memory of openScripts
209204
*
210205
* @param scriptName
211-
* The name of the script to close.
206+
* The name of the script to close.
212207
*/
213208
public void closeScript(String scriptName) {
214209
openedScripts.remove(scriptName);
@@ -244,7 +239,7 @@ public void connectionStopped(Py4JServerConnection gatewayConnection) {
244239
* One of 3 methods supported on the MessageHandler() callbacks
245240
*
246241
* @param code
247-
* The Python code to execute in the interpreter.
242+
* The Python code to execute in the interpreter.
248243
*/
249244
@Override
250245
public boolean exec(String code) {
@@ -263,7 +258,8 @@ public boolean exec(String code) {
263258
}
264259

265260
private String getClientKey(Py4JServerConnection gatewayConnection) {
266-
return String.format("%s:%d", gatewayConnection.getSocket().getInetAddress(), gatewayConnection.getSocket().getPort());
261+
return String.format("%s:%d", gatewayConnection.getSocket().getInetAddress(),
262+
gatewayConnection.getSocket().getPort());
267263
}
268264

269265
/**
@@ -291,7 +287,7 @@ public List<String> getScriptList() throws IOException {
291287
* immediately publishes the output on {@link #publishStdOut(String)}.
292288
*
293289
* @param msg
294-
* The output from a py4j related subprocess.
290+
* The output from a py4j related subprocess.
295291
*/
296292
public void handleStdOut(String msg) {
297293
if (!"\n".equals(msg)) {
@@ -322,7 +318,7 @@ public String onPythonMessage(Message msg) {
322318
* Opens an example "service" script maintained in myrobotlab
323319
*
324320
* @param serviceType
325-
* the type of service
321+
* the type of service
326322
* @throws IOException
327323
*/
328324
public void openExampleScript(String serviceType) throws IOException {
@@ -342,8 +338,8 @@ public void openExampleScript(String serviceType) throws IOException {
342338
* data/Py4j/{serviceName} directory.
343339
*
344340
* @param scriptName
345-
* - name of the script file relatie to scriptRootDir
346-
* data/Py4j/{serviceName}/
341+
* - name of the script file relatie to scriptRootDir
342+
* data/Py4j/{serviceName}/
347343
* @throws IOException
348344
*/
349345
public void openScript(String scriptName) throws IOException {
@@ -472,81 +468,101 @@ public void start() {
472468
* MessageHandler and setup for runtime references to work
473469
*/
474470
public void startPythonProcess() {
475-
try {
476-
477-
// Specify the Python script path and arguments
478-
String pythonScript = new File(getResourceDir() + fs + "Py4j.py").getAbsolutePath();
479-
480-
// Script requires full name as first command line argument
481-
String[] pythonArgs = { getFullName() };
482-
483-
// Build the command to start the Python process
484-
ProcessBuilder processBuilder;
485-
if (((Py4jConfig) config).useBundledPython) {
486-
String venv = getDataDir() + fs + "venv";
487-
pythonCommand = (Platform.getLocalInstance().isWindows()) ? venv + fs + "Scripts" + fs + "python.exe" : venv + fs + "bin" + fs + "python";
488-
if (!FileIO.checkDir(venv)) {
489-
// We don't have an initialized virtual environment, so lets make one
490-
// and install our required packages
491-
String python = Loader.load(org.bytedeco.cpython.python.class);
492-
String venvLib = new File(python).getParent() + fs + "lib" + fs + "venv" + fs + "scripts" + fs + "nt";
493-
if (Platform.getLocalInstance().isWindows()) {
494-
// Super hacky workaround, venv works differently on Windows and
495-
// requires these two
496-
// files, but they are not distributed in bare-bones Python or in
497-
// any pip packages.
498-
// So we copy them where it expects, and it seems to work now
499-
FileIO.copy(getResourceDir() + fs + "python.exe", venvLib + fs + "python.exe");
500-
FileIO.copy(getResourceDir() + fs + "pythonw.exe", venvLib + fs + "pythonw.exe");
471+
try {
472+
// Determine Python executable
473+
String pythonCommand = findPythonExecutable();
474+
if (pythonCommand == null) {
475+
error("Python is not installed or not found in system PATH.");
476+
return;
501477
}
502-
ProcessBuilder installProcess = new ProcessBuilder(python, "-m", "venv", venv);
503-
int ret = installProcess.inheritIO().start().waitFor();
504-
if (ret != 0) {
505-
error("Could not create virtual environment, subprocess returned {}. If on Windows, make sure there is a python.exe file in {}", ret, venvLib);
506-
return;
478+
479+
// Define paths
480+
String venv = getDataDir() + fs + "venv";
481+
String pythonScript = new File(getResourceDir() + fs + "Py4j.py").getAbsolutePath();
482+
String[] pythonArgs = { getFullName() };
483+
484+
// Check if virtual environment exists, if not, create and install dependencies
485+
File venvDir = new File(venv);
486+
String venvPython = Platform.getLocalInstance().isWindows()
487+
? venv + fs + "Scripts" + fs + "python.exe"
488+
: venv + fs + "bin" + fs + "python";
489+
490+
if (!venvDir.exists()) {
491+
ProcessBuilder venvProcess = new ProcessBuilder(pythonCommand, "-m", "venv", venv);
492+
int ret = venvProcess.inheritIO().start().waitFor();
493+
if (ret != 0) {
494+
error("Failed to create virtual environment.");
495+
return;
496+
}
497+
498+
// Install py4j inside venv
499+
ProcessBuilder pipProcess = new ProcessBuilder(venvPython, "-m", "pip", "install", "py4j");
500+
ret = pipProcess.inheritIO().start().waitFor();
501+
if (ret != 0) {
502+
error("Failed to install py4j in the virtual environment.");
503+
return;
504+
}
507505
}
508506

509-
installProcess = new ProcessBuilder(pythonCommand, "-m", "pip", "install", "py4j");
510-
ret = installProcess.inheritIO().start().waitFor();
511-
if (ret != 0) {
512-
error("Could not install package, subprocess returned " + ret);
513-
return;
507+
// Validate py4j is installed
508+
ProcessBuilder checkPy4j = new ProcessBuilder(venvPython, "-c", "import py4j");
509+
int checkRet = checkPy4j.inheritIO().start().waitFor();
510+
if (checkRet != 0) {
511+
error("py4j is not installed properly in the virtual environment.");
512+
return;
514513
}
515514

516-
}
515+
// Ensure the virtual environment's Python is used
516+
ProcessBuilder processBuilder = new ProcessBuilder(venvPython, pythonScript);
517+
processBuilder.redirectErrorStream(true);
518+
processBuilder.command().addAll(List.of(pythonArgs));
517519

518-
// Virtual environment should exist, so lets use that python
519-
} else {
520-
// Just use the system python
521-
pythonCommand = "python";
520+
pythonProcess = new Py4jClient(this, processBuilder.start());
521+
522+
} catch (Exception e) {
523+
error(e);
522524
}
523-
processBuilder = new ProcessBuilder(pythonCommand, pythonScript);
524-
processBuilder.redirectErrorStream(true);
525-
processBuilder.command().addAll(List.of(pythonArgs));
525+
}
526526

527-
// Start the Python process
528-
pythonProcess = new Py4jClient(this, processBuilder.start());
527+
/**
528+
* Finds the Python executable and returns its path.
529+
* Checks both "python3" and "python" commands.
530+
*/
531+
private String findPythonExecutable() {
532+
try {
533+
ProcessBuilder processBuilder = new ProcessBuilder("python3", "--version");
534+
Process process = processBuilder.start();
535+
int exitCode = process.waitFor();
536+
if (exitCode == 0) return "python3";
537+
} catch (Exception ignored) {}
529538

530-
} catch (Exception e) {
531-
error(e);
532-
}
539+
try {
540+
ProcessBuilder processBuilder = new ProcessBuilder("python", "--version");
541+
Process process = processBuilder.start();
542+
int exitCode = process.waitFor();
543+
if (exitCode == 0) return "python";
544+
} catch (Exception ignored) {}
545+
546+
return null; // Python not found
533547
}
534548

549+
550+
535551
/**
536552
* Install a list of packages into the environment Py4j is running in. Py4j
537553
* does not need to be running/connected to call this method as it spawns a
538554
* new subprocess to invoke Pip. Output from pip is echoed via
539555
* {@link #handleStdOut(String)}.
540556
*
541557
* @param packages
542-
* The list of packages to install. Must be findable by Pip
558+
* The list of packages to install. Must be findable by Pip
543559
* @throws IOException
544-
* If an I/O error occurs running Pip.
560+
* If an I/O error occurs running Pip.
545561
*/
546562
public void installPipPackages(List<String> packages) throws IOException {
547563
List<String> commandArgs = new ArrayList<>(List.of("-m", "pip", "install"));
548564
commandArgs.addAll(packages);
549-
ProcessBuilder pipProcess = new ProcessBuilder(pythonCommand);
565+
ProcessBuilder pipProcess = new ProcessBuilder(findPythonExecutable());
550566
pipProcess.command().addAll(commandArgs);
551567
Process proc = pipProcess.redirectErrorStream(true).start();
552568
new Thread(() -> {

0 commit comments

Comments
 (0)