← 返回首页
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.Properties;
// Tool to get directory where Python's shared object/dll and where
// Python executable lives and write those to a Properties file specified.
public class GetPythonEnvironment {
private GetPythonEnvironment() {
}
public enum OS {
WINDOWS,
LINUX,
MAC,
UNKNOWN
}
// detect the current OS
private static OS getCurrentOS() {
String osName = System.getProperty("os.name").toLowerCase();
if (osName.contains("linux")) {
return OS.LINUX;
} else if (osName.contains("mac")) {
return OS.MAC;
} else if (osName.contains("win")) {
return OS.WINDOWS;
} else {
return OS.UNKNOWN;
}
}
private static final OS THE_OS = getCurrentOS();
// result from a child Process
record ExecResult(String output, int exitCode) {
}
private static ExecResult runCommand(String... commands) throws IOException, InterruptedException {
return runCommand(false, commands);
}
// run a command in a child process and get its output
private static ExecResult runCommand(boolean firstLine, String... commands) throws IOException, InterruptedException {
ProcessBuilder builder = new ProcessBuilder(commands);
builder.redirectErrorStream(true);
Process process = builder.start();
// collect all output lines
var lines = new ArrayList();
try (var reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
lines.add(line);
}
}
return new ExecResult(firstLine
? (lines.isEmpty() ? null : lines.get(0))
: lines.stream().collect(Collectors.joining("\n")),
process.waitFor());
}
// find a OS command using OS specific command search command
private static String findCommand(String command) throws IOException, InterruptedException {
ExecResult execResult;
if (THE_OS == OS.WINDOWS) {
execResult = runCommand(true, "where.exe", command);
} else {
execResult = runCommand(true, "which", command);
}
return execResult.exitCode() == 0 ? execResult.output() : null;
}
// check python version is expected minor version or not
private static boolean checkPythonVersion(String pythonBin, String pythonMinor) throws IOException, InterruptedException {
// Run 'python --version' and capture output (both stdout and stderr)
ExecResult result = runCommand(pythonBin, "--version");
String versionStr = result.output();
if (versionStr == null || versionStr.trim().isEmpty()) {
IO.println("Could not parse Python version.");
return false;
}
// Regex to match 'Python 3.14.2' (or similar)
var pattern = Pattern.compile("Python\\s+([0-9]+)\\.([0-9]+)\\.[0-9]+");
var matcher = pattern.matcher(versionStr);
if (matcher.matches()) {
String major = matcher.group(1);
String minor = matcher.group(2);
if ("3".equals(major) && pythonMinor.equals(minor)) {
IO.println("Python 3." + pythonMinor + ".x detected: " + versionStr);
return true;
} else {
IO.println("Python is not 3." + pythonMinor + ".x: " + versionStr);
return false;
}
} else {
IO.println("Could not parse Python version.");
return false;
}
}
private static String getPythonBasePrefix(String pythonBin) throws IOException, InterruptedException {
// get Python's sys.base_prefix value
ExecResult execResult = runCommand(pythonBin, "-c", "import sys; print(sys.base_prefix)");
if (execResult.exitCode != 0) {
throw new IOException("getting python sys.base_prefix failed");
}
return execResult.output();
}
public static void main(String[] args) {
try {
// should pass version line 3.14 or 3.13 etc. Only major
// and minor versions. Do not pass 3.13.2 for example!
if (args.length != 2) {
System.err.println("java JextractHelper ");
System.exit(1);
}
String[] parts = args[0].split("-")[0].split("\\.");
String pythonVersion = parts[0] + "." + parts[1];
Path targetFile = Paths.get(args[1]);
if (THE_OS == OS.UNKNOWN) {
System.err.println("Unsupported OS: " + System.getProperty("os.name"));
System.exit(2);
}
// Try python version specified, else 3, else python
String pythonBin = findCommand("python");
if (pythonBin == null) {
pythonBin = findCommand("python3");
}
if (pythonBin == null) {
pythonBin = findCommand("python" + pythonVersion);
}
if (pythonBin != null) {
IO.println("Using Python @ " + pythonBin);
} else {
IO.println("Python not found!");
System.exit(3);
}
// make sure that we are on expected python version
if (!checkPythonVersion(pythonBin, pythonVersion.substring("3.".length()))) {
System.exit(4);
}
String pythonLibDir;
if (THE_OS == OS.WINDOWS) {
pythonLibDir = getPythonBasePrefix(pythonBin);
} else {
ExecResult execResult = runCommand(pythonBin, "-c",
"import sysconfig; print(sysconfig.get_config_var('LIBDIR'))");
if (execResult.exitCode() != 0) {
System.err.println("getting python sysconfig.get_config_var('LIBDIR') failed");
System.exit(5);
}
pythonLibDir = execResult.output();
}
ExecResult execResult = runCommand(pythonBin, "-c",
"import sys; print(sys.executable)");
if (execResult.exitCode() != 0) {
System.err.println("getting python sys.executable failed");
System.exit(6);
}
String pythonExecutable = execResult.output();
Properties props = new Properties();
props.setProperty("python.libdir", pythonLibDir);
props.setProperty("python.program.name", pythonExecutable);
targetFile.getParent().toFile().mkdirs();
try (var writer = Files.newBufferedWriter(targetFile)) {
props.store(writer, "Python shared object and program locations");
}
} catch (IOException | InterruptedException ex) {
ex.printStackTrace(System.err);
System.exit(5);
}
}
}