Getting started with developing a native NPL Contributor Library
New feature (added in 2024.1.6)
Imagine needing a functionality that isn't natively available in NPL, for example encryption. Such a use-case could be
satisfied using native NPL contributor library! A native NPL contributor library is very similar to an
NPL contributor library. You can provide additional NPL constructs, and more
importantly, define native
NPL functions that should use JVM-based implementations instead of a normal NPL body.
Referencing and declaring protocols is currently unsupported.
Writing a native NPL Contributor Library and general guidelines
To get you started, we recommend perusing Platform's npl-contrib-native-sample
.
The general components/layout of such a native NPL contributor library project are:
- JVM sources
src/main/{kotlin, java, etc.}/
- An empty NPL
src/npl/.gitkeep
directory and.gitkeep
file - A
src/main/resources
directory containing: - a
META-INF/services/com.noumenadigital.npl.contrib.NativeImplWithoutRuntimePlugin
file - a
npl-contrib-native
directory containing your native NPL source files - JVM test sources
src/test/{kotlin, java, etc.}/
, - NPL test sources
src/test/npl/
directory - A Maven
pom.xml
Your JVM sources should have 1 class that implements the NativeImplWithoutRuntimePlugin
abstract class, and provides
an implementation for public abstract void initialize()
. In this function, you should use the
NativeImplWithoutRuntimePlugin
registry
to register your native JVM implementations:
class NativeConcatenate : NativeImplWithoutRuntimePlugin() {
private fun nativeConcatenate(values: List<Value>): TextValue {
val (first, second) = values
return TextValue(first.toString() + second.toString())
}
override fun initialize() {
registry["/nativesample/v1/concatenate"] = NativeWithoutRuntime.create(::nativeConcatenate)
}
}
Great care must be taken when inserting your native implementations in the registry
. If a name collision occurs, you
will break other native NPL contributor libraries. You are responsible for safely inserting your native implementations
and error handling. Your native JVM implementations must be pure; they must not perform any file/network I/O operations.
This may be subject to change over time. The Platform team will ensure of these requirements when reviewing your pull
requests.
The NPL sources src/npl
should remain completely empty but the directory structure should be present, hence the
.gitkeep
file. This is due to the nature of native NPL contrib library development, testing and packaging. Instead,
your native NPL sources must live inside a resource folder called npl-contrib-native
inside the main resources
directory. Failure to do so will prevent you from testing your library and from making your native NPL source files
discoverable; nothing will work. The first directory inside npl-contrib-native
should be the main namespace of your
library, i.e. nativesample
.
To define an NPL function as having a native JVM-based implementation, you declare the function without a body, starting
with the reserved keyword native
:
package nativesample.v1;
native function concatenate(first: Text, second: Text) returns Text;
Its associated NativeImplWithoutRuntimePlugin
registry
key would then be: "/nativesample/v1/concatenate"
.
As with Kotlin's extension function, you can do something similar in NPL for standard library types:
package nativesample.v1;
native function Text.concatenate(other: Text) returns Text;
Its associated NativeImplWithoutRuntimePlugin
registry
key would then be: "/Text::nativesample/v1/concatenate"
.
Similarly, you can also create an extension function using your own NPL constructs:
package nativesample.v1;
struct MyStruct { myField: Text };
native function MyStruct.concatenate(other: Text) returns Text;
Its associated NativeImplWithoutRuntimePlugin
registry
key would then be:
"//nativesample/v1/MyStruct::nativesample/v1/concatenate"
.
Function arguments are passed in-order in the list of values in NativeWithoutRuntime.create
.
When using the NPL "extension function" notation Text.myFunction
, the self reference value is always passed as the
first argument in the list of values in NativeWithoutRuntime.create
.
In addition to the npl-contrib-native
resource you must also provide a mandatory plugin file resource:
META-INF/services/com.noumenadigital.npl.contrib.NativeImplWithoutRuntimePlugin
. This file must exist inside a
resources/META-INF/services
directory structure and must be named exactly
com.noumenadigital.npl.contrib.NativeImplWithoutRuntimePlugin
. The content of this file must be 1 plain-text line of
the fully qualified class name implementing NativeImplWithoutRuntimePlugin
, i.e.:
com.noumenadigital.nativesample.NativeConcatenate
Your JVM test sources should assert that your JVM implementations behave as expected, while your NPL test sources should
verify that NPL code can correctly call into the native JVM implementations. It is advisable to also test any additional
NPL constructs that you may have provided with your native NPL library such as enum
, struct
, etc.
Your Maven project pom.xml
should depend on Platform's language-builtins-native
using a provided scope.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.noumenadigital.platform</groupId>
<artifactId>npl-contrib-native-sample</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<!-- the latest platform version whenever possible -->
<platform.version>2024.1.7</platform.version>
</properties>
<dependencies>
<dependency>
<groupId>com.noumenadigital.platform</groupId>
<artifactId>language-builtins-native</artifactId>
<version>${platform.version}</version>
<!-- Since Platform will include this dependency, it should be provided -->
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Build Plugins to build your native NPL contributor library -->
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>1.9.10</version>
</plugin>
<!-- ... -->
<!-- Platform's npl-maven-plugin for NPL testing of your library -->
<plugin>
<groupId>com.noumenadigital.platform</groupId>
<artifactId>npl-maven-plugin</artifactId>
<version>${platform.version}</version>
<executions>
<execution>
<goals>
<goal>npl-compile</goal>
<goal>npl-test-compile</goal>
<goal>npl-test</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Versioning
You are free to choose how to version your native NPL contributor library source files and your produced jar file.
However, we recommend to version your native NPL source files using a major version namespace & directories such as v1
and v2
. This should make your life easier for patch & minor releases.
All versions of your native NPL contributor library source files should be present in a single jar file for every release. Clients (Platform) using your native NPL contributor library defined NPL constructs must continue to use it once they use it in production, otherwise their environment will break.
Packaging
Your project should produce a library jar
file, containing both META-INF
and npl-contrib-native
as resources at
the root of the jar
file.
Deployment
You are free to choose where & how to publish your jar
library artefact as long as it supports Maven. It must be
publicly available on maven-central
or similar public artefact repository.
Integrating a native NPL Contributor Library into an NPL project and in IntelliJ
The Platform team reserves the right to decide which native NPL contributor libraries to include as part of its release. This is initially the only way to have access to your native NPL contrib library available for production and development using our IntelliJ NPL Plugin. In other words, you will need the latest platform release version supporting your native NPL contrib library. Once Platform includes your library as a dependency, it will become automatically available in the IntelliJ NPL plugin and for production usage.