Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
621 changes: 156 additions & 465 deletions bundles/org.openhab.automation.pythonscripting/README.md

Large diffs are not rendered by default.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 17 additions & 1 deletion bundles/org.openhab.automation.pythonscripting/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<name>openHAB Add-ons :: Bundles :: Automation :: Python Scripting</name>

<properties>
<helperlib.version>v1.0.0</helperlib.version>
<helperlib.version>1.0.8</helperlib.version>
</properties>

<dependencies>
Expand All @@ -31,6 +31,22 @@

<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>properties-maven-plugin</artifactId>
<version>1.0.0</version>
<executions>
<execution>
<goals>
<goal>write-project-properties</goal>
</goals>
<phase>generate-resources</phase>
<configuration>
<outputFile>${project.build.outputDirectory}/build.properties</outputFile>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-scm-plugin</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,24 @@

<feature name="openhab-automation-pythonscripting" description="Python Scripting" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle dependency="true">mvn:org.openhab.osgiify/org.graalvm.llvm.llvm-api/24.2.1</bundle>
<bundle dependency="true">mvn:org.openhab.osgiify/org.graalvm.polyglot.polyglot/24.2.1</bundle>
<bundle dependency="true" start-level="78">mvn:org.openhab.osgiify/org.graalvm.python.python-language/24.2.1</bundle>
<bundle dependency="true" start-level="78">mvn:org.openhab.osgiify/org.graalvm.python.python-resources/24.2.1</bundle>
<bundle dependency="true" start-level="78">mvn:org.openhab.osgiify/org.graalvm.regex.regex/24.2.1</bundle>
<bundle dependency="true">mvn:org.openhab.osgiify/org.graalvm.sdk.collections/24.2.1</bundle>
<bundle dependency="true">mvn:org.openhab.osgiify/org.graalvm.sdk.jniutils/24.2.1</bundle>
<bundle dependency="true">mvn:org.openhab.osgiify/org.graalvm.sdk.nativeimage/24.2.1</bundle>
<bundle dependency="true">mvn:org.openhab.osgiify/org.graalvm.sdk.word/24.2.1</bundle>
<bundle dependency="true">mvn:org.openhab.osgiify/org.graalvm.shadowed.icu4j/24.2.1</bundle>
<bundle dependency="true">mvn:org.openhab.osgiify/org.graalvm.llvm.llvm-api/24.2.1</bundle>
<bundle dependency="true">mvn:org.openhab.osgiify/org.graalvm.shadowed.json/24.2.1</bundle>
<bundle dependency="true">mvn:org.openhab.osgiify/org.graalvm.shadowed.xz/24.2.1</bundle>
<bundle dependency="true">mvn:org.openhab.osgiify/org.graalvm.tools.profiler-tool/24.2.1</bundle>
<bundle dependency="true" start-level="79">mvn:org.openhab.osgiify/org.graalvm.truffle.truffle-api/24.2.1</bundle>
<bundle dependency="true">mvn:org.openhab.osgiify/org.graalvm.polyglot.polyglot/24.2.1</bundle>
<bundle dependency="true">mvn:org.openhab.osgiify/org.graalvm.truffle.truffle-runtime/24.2.1</bundle>
<bundle dependency="true">mvn:org.openhab.osgiify/org.graalvm.truffle.truffle-compiler/24.2.1</bundle>
<bundle dependency="true" start-level="78">mvn:org.openhab.osgiify/org.graalvm.regex.regex/24.2.1</bundle>
<bundle dependency="true" start-level="78">mvn:org.openhab.osgiify/org.graalvm.python.python-resources/24.2.1</bundle>
<bundle dependency="true" start-level="78">mvn:org.openhab.osgiify/org.graalvm.python.python-language/24.2.1</bundle>
<bundle dependency="true" start-level="78">mvn:org.openhab.osgiify/org.graalvm.truffle.truffle-nfi/24.2.1</bundle>
<bundle dependency="true" start-level="78">mvn:org.openhab.osgiify/org.graalvm.truffle.truffle-nfi-libffi/24.2.1</bundle>
<bundle dependency="true">mvn:org.openhab.osgiify/org.graalvm.truffle.truffle-runtime/24.2.1</bundle>
<bundle dependency="true" start-level="79">mvn:org.openhab.osgiify/org.graalvm.truffle.truffle-api/24.2.1</bundle>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.automation.pythonscripting/${project.version}</bundle>
</feature>
</features>

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,32 @@
*/
package org.openhab.automation.pythonscripting.internal;

import java.io.IOException;
import java.io.InputStream;
import java.lang.module.ModuleDescriptor.Version;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Collectors;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.config.core.ConfigParser;
import org.eclipse.jdt.annotation.Nullable;
import org.graalvm.polyglot.Context;
import org.openhab.core.OpenHAB;
import org.openhab.core.automation.module.script.ScriptEngineFactory;
import org.openhab.core.config.core.Configuration;
import org.osgi.framework.FrameworkUtil;
import org.osgi.service.component.annotations.Activate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
* Processes Python Configuration Parameters.
*
Expand All @@ -29,62 +48,224 @@ public class PythonScriptEngineConfiguration {

private final Logger logger = LoggerFactory.getLogger(PythonScriptEngineConfiguration.class);

private static final String CFG_INJECTION_ENABLED = "injectionEnabled";
private static final String CFG_HELPER_ENABLED = "helperEnabled";
private static final String CFG_SCOPE_ENABLED = "scopeEnabled";
private static final String CFG_DEPENDENCY_TRACKING_ENABLED = "dependencyTrackingEnabled";
private static final String CFG_CACHING_ENABLED = "cachingEnabled";
private static final String CFG_JYTHON_EMULATION = "jythonEmulation";
private static final String SYSTEM_PROPERTY_POLYGLOT_ENGINE_USERRESOURCECACHE = "polyglot.engine.userResourceCache";

private static final String SYSTEM_PROPERTY_JAVA_IO_TMPDIR = "java.io.tmpdir";

public static final String PATH_SEPARATOR = FileSystems.getDefault().getSeparator();
public static final String RESOURCE_SEPARATOR = "/";

public static final Path PYTHON_DEFAULT_PATH = Paths.get(OpenHAB.getConfigFolder(), "automation", "python");
public static final Path PYTHON_LIB_PATH = PYTHON_DEFAULT_PATH.resolve("lib");
public static final Path PYTHON_TYPINGS_PATH = PYTHON_DEFAULT_PATH.resolve("typings");
public static final Path PYTHON_OPENHAB_LIB_PATH = PYTHON_LIB_PATH.resolve("openhab");

public static final Path PYTHON_WRAPPER_FILE_PATH = PYTHON_OPENHAB_LIB_PATH.resolve("__wrapper__.py");
public static final Path PYTHON_INIT_FILE_PATH = PYTHON_OPENHAB_LIB_PATH.resolve("__init__.py");

public static final int INJECTION_DISABLED = 0;
public static final int INJECTION_ENABLED_FOR_ALL_SCRIPTS = 1;
public static final int INJECTION_ENABLED_FOR_NON_FILE_BASED_SCRIPTS = 2;
private int injectionEnabled = 0;
private boolean helperEnabled = false;
private boolean scopeEnabled = false;
private boolean dependencyTrackingEnabled = false;
private boolean cachingEnabled = false;
private boolean jythonEmulation = false;

// The variable names must match the configuration keys in config.xml
public static class PythonScriptingConfiguration {
public boolean scopeEnabled = true;
public boolean helperEnabled = true;
public int injectionEnabled = INJECTION_ENABLED_FOR_NON_FILE_BASED_SCRIPTS;
public boolean dependencyTrackingEnabled = true;
public boolean cachingEnabled = true;
public boolean jythonEmulation = false;
public boolean nativeModules = false;
public String pipModules = "";
}

private PythonScriptingConfiguration configuration = new PythonScriptingConfiguration();
private Path bytecodeDirectory;
private Path tempDirectory;
private Path venvDirectory;
private @Nullable Path venvExecutable = null;

private Version bundleVersion = Version
.parse(FrameworkUtil.getBundle(PythonScriptEngineConfiguration.class).getVersion().toString());

private Version graalVersion = Version.parse("0.0.0");
private Version providedHelperLibVersion = Version.parse("0.0.0");
private @Nullable Version installedHelperLibVersion = null;

public static Version parseHelperLibVersion(@Nullable String version) throws IllegalArgumentException {
// substring(1) => remove leading 'v'
return Version.parse(version != null && version.startsWith("v") ? version.substring(1) : version);
}

@Activate
public PythonScriptEngineConfiguration(Map<String, Object> config, PythonScriptEngineFactory factory) {
Path userdataDir = Paths.get(OpenHAB.getUserDataFolder());

String tmpDir = System.getProperty(SYSTEM_PROPERTY_JAVA_IO_TMPDIR);
if (tmpDir != null) {
tempDirectory = Paths.get(tmpDir);
} else {
tempDirectory = userdataDir.resolve("tmp");
}

try {
InputStream is = PythonScriptEngineConfiguration.class
.getResourceAsStream(RESOURCE_SEPARATOR + "build.properties");
if (is != null) {
Properties p = new Properties();
p.load(is);
String version = p.getProperty("helperlib.version");
providedHelperLibVersion = parseHelperLibVersion(version);
version = FrameworkUtil.getBundle(Context.class).getVersion().toString();
graalVersion = Version.parse(version);
}
} catch (IOException e) {
throw new IllegalArgumentException("Unable to load build.properties");
}

String packageName = PythonScriptEngineConfiguration.class.getPackageName();
packageName = packageName.substring(0, packageName.lastIndexOf("."));
Path bindingDirectory = userdataDir.resolve("cache").resolve(packageName);

Properties props = System.getProperties();
props.setProperty(SYSTEM_PROPERTY_POLYGLOT_ENGINE_USERRESOURCECACHE, bindingDirectory.toString());
bytecodeDirectory = PythonScriptEngineHelper.initDirectory(bindingDirectory.resolve("resources"));
venvDirectory = PythonScriptEngineHelper.initDirectory(bindingDirectory.resolve("venv"));

Path venvPythonBin = venvDirectory.resolve("bin").resolve("graalpy");
if (Files.exists(venvPythonBin)) {
venvExecutable = venvPythonBin;
}

installedHelperLibVersion = PythonScriptEngineHelper.initHelperLib(this, providedHelperLibVersion);

this.update(config, factory);
}

/**
* Update configuration
*
* @param config Configuration parameters to apply to ScriptEngine
* @param initial
*/
void update(Map<String, ?> config) {
public void modified(Map<String, Object> config, ScriptEngineFactory factory) {
boolean oldScopeEnabled = configuration.scopeEnabled;
boolean oldInjectionEnabled = !isInjection(PythonScriptEngineConfiguration.INJECTION_DISABLED);
boolean oldDependencyTrackingEnabled = isDependencyTrackingEnabled();

this.update(config, factory);

if (oldScopeEnabled != isScopeEnabled()) {
logger.info("{} scope for Python Scripting. Please resave your scripts to apply this change.",
isScopeEnabled() ? "Enabled" : "Disabled");
}
if (oldInjectionEnabled != !isInjection(PythonScriptEngineConfiguration.INJECTION_DISABLED)) {
logger.info("{} injection for Python Scripting. Please resave your UI-based scripts to apply this change.",
!isInjection(PythonScriptEngineConfiguration.INJECTION_DISABLED) ? "Enabled" : "Disabled");
}
if (oldDependencyTrackingEnabled != isDependencyTrackingEnabled()) {
logger.info("{} dependency tracking for Python Scripting. Please resave your scripts to apply this change.",
isDependencyTrackingEnabled() ? "Enabled" : "Disabled");
}
}

private void update(Map<String, Object> config, ScriptEngineFactory factory) {
logger.trace("Python Script Engine Configuration: {}", config);

this.scopeEnabled = ConfigParser.valueAsOrElse(config.get(CFG_SCOPE_ENABLED), Boolean.class, true);
this.helperEnabled = ConfigParser.valueAsOrElse(config.get(CFG_HELPER_ENABLED), Boolean.class, true);
this.injectionEnabled = ConfigParser.valueAsOrElse(config.get(CFG_INJECTION_ENABLED), Integer.class,
INJECTION_ENABLED_FOR_NON_FILE_BASED_SCRIPTS);
this.dependencyTrackingEnabled = ConfigParser.valueAsOrElse(config.get(CFG_DEPENDENCY_TRACKING_ENABLED),
Boolean.class, true);
this.cachingEnabled = ConfigParser.valueAsOrElse(config.get(CFG_CACHING_ENABLED), Boolean.class, true);
this.jythonEmulation = ConfigParser.valueAsOrElse(config.get(CFG_JYTHON_EMULATION), Boolean.class, false);
String oldPipModules = configuration.pipModules;
configuration = new Configuration(config).as(PythonScriptingConfiguration.class);

if (!oldPipModules.equals(configuration.pipModules)) {
PythonScriptEngineHelper.initPipModules(this, factory);
}
}

public void setHelperLibVersion(Version version) {
installedHelperLibVersion = version;
}

public boolean isScopeEnabled() {
return scopeEnabled;
return configuration.scopeEnabled;
}

public boolean isHelperEnabled() {
return helperEnabled;
return configuration.helperEnabled;
}

public boolean isInjection(int type) {
return injectionEnabled == type;
return configuration.injectionEnabled == type;
}

public boolean isDependencyTrackingEnabled() {
return dependencyTrackingEnabled;
return configuration.dependencyTrackingEnabled;
}

public boolean isCachingEnabled() {
return cachingEnabled;
return configuration.cachingEnabled;
}

public boolean isJythonEmulation() {
return jythonEmulation;
return configuration.jythonEmulation;
}

public boolean isNativeModulesEnabled() {
return configuration.nativeModules;
}

public String getPIPModules() {
return configuration.pipModules;
}

public Path getBytecodeDirectory() {
return bytecodeDirectory;
}

public Path getTempDirectory() {
return tempDirectory;
}

public Path getVEnvDirectory() {
return venvDirectory;
}

public @Nullable Path getVEnvExecutable() {
return venvExecutable;
}

public boolean isVEnvEnabled() {
return venvExecutable != null;
}

public Version getBundleVersion() {
return bundleVersion;
}

public Version getGraalVersion() {
return graalVersion;
}

public Version getProvidedHelperLibVersion() {
return providedHelperLibVersion;
}

public @Nullable Version getInstalledHelperLibVersion() {
return installedHelperLibVersion;
}

/**
* Returns the current configuration as a map.
* This is used to display the configuration in the console.
*/
public Map<String, String> getConfigurations() {
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> objectMap = objectMapper.convertValue(configuration,
new TypeReference<Map<String, Object>>() {
});
return objectMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> {
if (entry.getValue() instanceof List<?> listValue) {
return listValue.stream().map(Object::toString).collect(Collectors.joining("\n"));
}
return entry.getValue().toString();
}));
}
}
Loading