/* * Copyright 2015 Nokia Solutions and Networks * Licensed under the Apache License, Version 2.0, * see license.txt file for details. */ package org.rf.ide.core.executor; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URI; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.StandardCopyOption; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.EnumSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.function.Consumer; @SuppressWarnings({ "PMD.GodClass", "PMD.TooManyMethods" }) public class RobotRuntimeEnvironment { private static Path temporaryDirectory = null; private final File location; private final String version; public static void addProcessListener(final PythonProcessListener listener) { PythonInterpretersCommandExecutors.getInstance().addProcessListener(listener); } public static void removeProcessListener(final PythonProcessListener listener) { PythonInterpretersCommandExecutors.getInstance().removeProcessListener(listener); } public static int runExternalProcess(final List<String> command, final Consumer<String> lineHandler) throws IOException { try { final Process process = new ProcessBuilder(command).redirectErrorStream(true).start(); final InputStream inputStream = process.getInputStream(); final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); String line; while ((line = reader.readLine()) != null) { lineHandler.accept(line); } return process.waitFor(); } catch (final InterruptedException e) { throw new IOException(e); } } /** * Locates directories in which python interpreters pointed in environment * path are located. Uses where command in Windows and which command under * Unix. * * @return Directories where python interpreters are installed or empty list * if there is no python at all. */ public static List<PythonInstallationDirectory> whereArePythonInterpreters() { final List<PythonInstallationDirectory> paths = new ArrayList<>(); for (final SuiteExecutor interpreter : EnumSet.allOf(SuiteExecutor.class)) { paths.addAll(whereIsPythonInterpreter(interpreter)); } return paths; } public static String getVersion(final SuiteExecutor interpreter) throws RobotEnvironmentException { final Collection<PythonInstallationDirectory> interpreterLocations = whereIsPythonInterpreter(interpreter); final Optional<PythonInstallationDirectory> installationDirectory = interpreterLocations.stream().findFirst(); if (!installationDirectory.isPresent()) { throw new RobotEnvironmentException( "There is no " + interpreter.name() + " interpreter in system PATH environment variable"); } final RobotCommandExecutor executor = PythonInterpretersCommandExecutors.getInstance() .getDirectRobotCommandExecutor(installationDirectory.get()); return exactVersion(interpreter, executor.getRobotVersion()); } private static String exactVersion(final SuiteExecutor interpreter, final String version) { return version != null && interpreter == SuiteExecutor.IronPython64 ? version.replaceAll("IronPython", "IronPython x64") : version; } private static Collection<PythonInstallationDirectory> whereIsPythonInterpreter(final SuiteExecutor interpreter) { final List<String> paths = new ArrayList<>(); try { final String cmd = RedSystemProperties.isWindowsPlatform() ? "where" : "which"; final int exitCode = runExternalProcess(Arrays.asList(cmd, interpreter.executableName()), line -> paths.add(line)); if (exitCode == 0) { final List<PythonInstallationDirectory> installationDirectories = new ArrayList<>(); for (final String path : paths) { final URI dirUri = new File(path).getParentFile().toURI(); installationDirectories.add(new PythonInstallationDirectory(dirUri, interpreter)); } return installationDirectories; } else { return new ArrayList<>(); } } catch (final IOException e) { return new ArrayList<>(); } } /** * Checks if given location is a directory which contains python * interpreter. The {@link IllegalArgumentException} exception is thrown if * given location does not contain python executable. Otherwise * {@link PythonInstallationDirectory} instance (copy of location, but with * other type) is returned. * * @param location * Location to check * @return the same location given as {@link File} subtype * @throws IllegalArgumentException * thrown when given location is not a directory or does not * contain python interpreter executables. */ private static PythonInstallationDirectory checkPythonInstallationDir(final File location) throws IllegalArgumentException { if (!location.isDirectory()) { throw new IllegalArgumentException("The location " + location.getAbsolutePath() + " is not a directory."); } final List<PythonInstallationDirectory> installations = possibleInstallationsFor(location); if (installations.isEmpty()) { throw new IllegalArgumentException("The location: " + location.getAbsolutePath() + " does not seem to be a valid python installation directory"); } return installations.get(0); } public static List<PythonInstallationDirectory> possibleInstallationsFor(final File location) { final List<PythonInstallationDirectory> installations = new ArrayList<>(); if (!location.exists()) { return installations; } for (final File file : location.listFiles()) { final String fileName = file.getName(); if (file.isFile() && (fileName.equals("python") || fileName.equals("python.exe"))) { installations.add(new PythonInstallationDirectory(location.toURI(), SuiteExecutor.Python)); } if (file.isFile() && (fileName.equals("jython") || fileName.equals("jython.exe"))) { installations.add(new PythonInstallationDirectory(location.toURI(), SuiteExecutor.Jython)); } if (file.isFile() && (fileName.equals("ipy") || fileName.equals("ipy.exe"))) { installations.add(new PythonInstallationDirectory(location.toURI(), SuiteExecutor.IronPython)); } if (file.isFile() && (fileName.equals("ipy64") || fileName.equals("ipy64.exe"))) { installations.add(new PythonInstallationDirectory(location.toURI(), SuiteExecutor.IronPython64)); } if (file.isFile() && (fileName.equals("pypy") || fileName.equals("pypy.exe"))) { installations.add(new PythonInstallationDirectory(location.toURI(), SuiteExecutor.PyPy)); } } return installations; } /** * Gets robot framework version. * * @param pythonLocation * @return Robot version as returned by robot */ private static String getRobotFrameworkVersion(final PythonInstallationDirectory pythonLocation) { final RobotCommandExecutor executor = PythonInterpretersCommandExecutors.getInstance() .getRobotCommandExecutor(pythonLocation); return exactVersion(pythonLocation.getInterpreter(), executor.getRobotVersion()); } public String getPythonExecutablePath() { final PythonInstallationDirectory pyLocation = (PythonInstallationDirectory) location; final String pythonExec = pyLocation.interpreter.executableName(); return findFile(pyLocation, pythonExec).getAbsolutePath(); } private static File findFile(final PythonInstallationDirectory pythonLocation, final String name) { for (final File file : pythonLocation.listFiles()) { if (name.equals(file.getName())) { return file; } } return null; } public static File createTemporaryFile(final String filename) throws IOException { final Path tempDir = createTemporaryDirectory(); final File tempFile = new File(tempDir.toString() + File.separator + filename); tempFile.delete(); tempFile.createNewFile(); return tempFile; } public static File copyScriptFile(final String filename) throws IOException { final Path tempDir = createTemporaryDirectory(); final File scriptFile = new File(tempDir.toString() + File.separator + filename); if (!scriptFile.exists()) { Files.copy(getScriptFileAsStream(filename), scriptFile.toPath(), StandardCopyOption.REPLACE_EXISTING); } return scriptFile; } public static InputStream getScriptFileAsStream(final String filename) throws IOException { return RobotRuntimeEnvironment.class.getResourceAsStream("/scripts/" + filename); } static synchronized Path createTemporaryDirectory() throws IOException { if (temporaryDirectory != null) { return temporaryDirectory; } temporaryDirectory = Files.createTempDirectory("RobotTempDir"); addRemoveTemporaryDirectoryHook(); return temporaryDirectory; } private static void addRemoveTemporaryDirectoryHook() { Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { if (temporaryDirectory == null) { return; } try { Files.walkFileTree(temporaryDirectory, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException { Files.delete(file); return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException { Files.delete(dir); return FileVisitResult.CONTINUE; } }); } catch (final IOException e) { e.printStackTrace(); } } }); } static String wrapArgumentIfNeeded(final String argument) { return argument.contains(" ") ? "\"" + argument + "\"" : argument; } private RobotRuntimeEnvironment(final File location, final String version) { this.location = location; this.version = version; } public static RobotRuntimeEnvironment create(final String pathToPython) { return create(new File(pathToPython)); } public static RobotRuntimeEnvironment create(final File pathToPython) { try { final PythonInstallationDirectory location = checkPythonInstallationDir(pathToPython); return new RobotRuntimeEnvironment(location, getRobotFrameworkVersion(location)); } catch (final IllegalArgumentException e) { return new RobotRuntimeEnvironment(pathToPython, null); } } public static RobotRuntimeEnvironment create(final String pathToPython, final SuiteExecutor interpreter) { return create(new File(pathToPython), interpreter); } public static RobotRuntimeEnvironment create(final File pathToPython, final SuiteExecutor interpreter) { try { final PythonInstallationDirectory location = checkPythonInstallationDir(pathToPython); final PythonInstallationDirectory correctedLocation = new PythonInstallationDirectory(location.toURI(), interpreter); return new RobotRuntimeEnvironment(correctedLocation, getRobotFrameworkVersion(correctedLocation)); } catch (final IllegalArgumentException e) { return new RobotRuntimeEnvironment(pathToPython, null); } } public boolean isValidPythonInstallation() { return location instanceof PythonInstallationDirectory; } public boolean hasRobotInstalled() { return isValidPythonInstallation() && version != null; } public String getVersion() { return version; } public SuiteExecutor getInterpreter() { return location instanceof PythonInstallationDirectory ? ((PythonInstallationDirectory) location).interpreter : null; } public List<File> getModuleSearchPaths() { if (hasRobotInstalled()) { final RobotCommandExecutor executor = PythonInterpretersCommandExecutors.getInstance() .getRobotCommandExecutor((PythonInstallationDirectory) location); return executor.getModulesSearchPaths(); } return new ArrayList<>(); } public Optional<File> getModulePath(final String moduleName, final EnvironmentSearchPaths additionalPaths) { if (hasRobotInstalled()) { final RobotCommandExecutor executor = PythonInterpretersCommandExecutors.getInstance() .getRobotCommandExecutor((PythonInstallationDirectory) location); return executor.getModulePath(moduleName, additionalPaths); } return Optional.empty(); } public File getFile() { return location; } public void resetCommandExecutors() { if (hasRobotInstalled()) { PythonInterpretersCommandExecutors.getInstance().resetExecutorFor((PythonInstallationDirectory) location); } } public void createLibdocForStdLibrary(final String libName, final File outputFile) throws RobotEnvironmentException { if (hasRobotInstalled()) { final RobotCommandExecutor executor = PythonInterpretersCommandExecutors.getInstance() .getRobotCommandExecutor((PythonInstallationDirectory) location); executor.createLibdocForStdLibrary(outputFile.getAbsolutePath(), libName, ""); } } public void createLibdocForStdLibraryForcibly(final String libName, final File outputFile) throws RobotEnvironmentException { if (hasRobotInstalled()) { final RobotCommandExecutor executor = PythonInterpretersCommandExecutors.getInstance() .getDirectRobotCommandExecutor((PythonInstallationDirectory) location); executor.createLibdocForStdLibrary(outputFile.getAbsolutePath(), libName, ""); } } public void createLibdocForThirdPartyLibrary(final String libName, final String libPath, final EnvironmentSearchPaths additionalPaths, final File outputFile) throws RobotEnvironmentException { if (hasRobotInstalled()) { final RobotCommandExecutor executor = PythonInterpretersCommandExecutors.getInstance() .getRobotCommandExecutor((PythonInstallationDirectory) location); executor.createLibdocForThirdPartyLibrary(outputFile.getAbsolutePath(), libName, libPath, additionalPaths); } } public void createLibdocForThirdPartyLibraryForcibly(final String libName, final String libPath, final EnvironmentSearchPaths additionalPaths, final File outputFile) throws RobotEnvironmentException { if (hasRobotInstalled()) { final RobotCommandExecutor executor = PythonInterpretersCommandExecutors.getInstance() .getDirectRobotCommandExecutor((PythonInstallationDirectory) location); executor.createLibdocForThirdPartyLibrary(outputFile.getAbsolutePath(), libName, libPath, additionalPaths); } } public List<String> getStandardLibrariesNames() { if (hasRobotInstalled()) { final RobotCommandExecutor executor = PythonInterpretersCommandExecutors.getInstance() .getRobotCommandExecutor((PythonInstallationDirectory) location); final List<String> libs = executor.getStandardLibrariesNames(); // Remote is a library without keywords and libdoc throws // exceptions when trying to generate its specification libs.remove("Remote"); return libs; } else { return new ArrayList<>(); } } public File getStandardLibraryPath(final String libraryName) { if (hasRobotInstalled()) { final RobotCommandExecutor executor = PythonInterpretersCommandExecutors.getInstance() .getRobotCommandExecutor((PythonInstallationDirectory) location); final String pycPath = executor.getStandardLibraryPath(libraryName); if (pycPath == null) { return null; } else if (pycPath.endsWith(".py")) { return new File(pycPath); } else if (pycPath.endsWith(".pyc")) { return new File(pycPath.substring(0, pycPath.length() - 1)); } else if (pycPath.endsWith("$py.class")) { return new File(pycPath.substring(0, pycPath.length() - 9) + ".py"); } else { return null; } } return null; } /** * Return names of python classes contained in module point by argument and * all of its submodules. For packages-module __init__.py file path should * be provided. * * @param moduleLocation * Module location * @param moduleName * Module name or null * @return list of class names or empty list * @throws RobotEnvironmentException */ public List<String> getClassesFromModule(final File moduleLocation, final String moduleName, final EnvironmentSearchPaths additionalPaths) throws RobotEnvironmentException { if (hasRobotInstalled()) { final RobotCommandExecutor executor = PythonInterpretersCommandExecutors.getInstance() .getRobotCommandExecutor((PythonInstallationDirectory) location); return executor.getClassesFromModule(moduleLocation, moduleName, additionalPaths); } return new ArrayList<>(); } public Map<String, Object> getGlobalVariables() { if (hasRobotInstalled()) { final RobotCommandExecutor executor = PythonInterpretersCommandExecutors.getInstance() .getRobotCommandExecutor((PythonInstallationDirectory) location); return executor.getGlobalVariables(); } return new LinkedHashMap<>(); } public Map<String, Object> getVariablesFromFile(final String path, final List<String> args) { if (hasRobotInstalled()) { final String normalizedPath = path.replace('\\', '/'); final RobotCommandExecutor executor = PythonInterpretersCommandExecutors.getInstance() .getRobotCommandExecutor((PythonInstallationDirectory) location); return executor.getVariables(normalizedPath, args); } return new LinkedHashMap<>(); } public boolean isVirtualenv() { if (hasRobotInstalled()) { final RobotCommandExecutor executor = PythonInterpretersCommandExecutors.getInstance() .getRobotCommandExecutor((PythonInstallationDirectory) location); final Boolean result = executor.isVirtualenv(); if (result != null) { return result; } } return false; } @Override public int hashCode() { return Objects.hash(location, version); } @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final RobotRuntimeEnvironment other = (RobotRuntimeEnvironment) obj; return Objects.equals(location, other.location) && Objects.equals(version, other.version); } @SuppressWarnings("serial") public static class RobotEnvironmentException extends RuntimeException { public RobotEnvironmentException(final String message) { super(message); } public RobotEnvironmentException(final String message, final Throwable cause) { super(message, cause); } } @SuppressWarnings("serial") public static class RobotEnvironmentDetailedException extends RobotEnvironmentException { private final String details; public RobotEnvironmentDetailedException(final String details, final String reason) { super(reason); this.details = details; } public RobotEnvironmentDetailedException(final String details, final String reason, final Throwable cause) { super(reason, cause); this.details = details; } public String getReason() { return getMessage(); } public String getDetails() { return details; } } @SuppressWarnings("serial") public static class PythonInstallationDirectory extends File { private final SuiteExecutor interpreter; // we dont' want anyone to create those objects; they should only be // created // when given uri is valid python location private PythonInstallationDirectory(final URI uri, final SuiteExecutor interpreter) { super(uri); this.interpreter = interpreter; } public SuiteExecutor getInterpreter() { return interpreter; } @Override public boolean equals(final Object obj) { if (obj instanceof PythonInstallationDirectory) { final PythonInstallationDirectory that = (PythonInstallationDirectory) obj; return super.equals(obj) && this.interpreter == that.interpreter; } return false; } @Override public int hashCode() { return 31 * super.hashCode() + ((interpreter == null) ? 0 : interpreter.hashCode()); } } }