Requires Gradle 6.7
Applying this plugin to a project declares that the project should create a CPK-format CorDapp. The CPK-format
CorDapp is a ZIP file with a .jar
extension that is the output of the jar
task, along
with that jar's dependent jars. In practice, the plugin will not include any of Corda's own jars among these
dependencies, nor any jars which should be provided by Corda, e.g. Kotlin or Quasar. The jar should also
contain sufficient OSGi metadata to be a valid OSGi bundle.
Include these lines at the top of the build.gradle
file of your CorDapp Gradle project:
plugins {
id 'net.corda.plugins.cordapp-cpk2'
}
You must also apply the net.corda.cordapp.cordapp-configuration
plugin to your "root" Gradle project,
to configure the cordapp-cpk2
plugin for your version of Corda:
plugins {
id 'net.corda.cordapp.cordapp-configuration'
}
If your Gradle project only contains a single module then you would apply both plugins together:
plugins {
id 'net.corda.cordapp.cordapp-configuration'
id 'net.corda.plugins.cordapp-cpk2'
}
You will also need to declare both plugins' versions in settings.gradle
:
pluginManagement {
plugins {
id 'net.corda.cordapp.cordapp-configuration' version cordaReleaseVersion
id 'net.corda.plugins.cordapp-cpk2' version cpkPluginVersion
}
}
where cpkPluginVersion
and cordaReleaseVersion
are both Gradle properties:
cpkPluginVersion = '6.0.0'
cordaReleaseVersion = '5.0.0'
Applying the cordapp-cpk2
plugin implicitly applies both Gradle's java-library
plugin and Bnd's builder
plugin,
which means that the output of the jar
task will also become an OSGi bundle. The cordapp-cpk2
plugin assigns
the following default OSGi attributes to the bundle:
Bundle-SymbolicName
:${project.group}.${archiveBaseName}[-${archiveAppendix}][-${archiveClassifier}]
Bundle-Version
:${project.version}
The plugin creates a cordapp
DSL extension, which currently bears a strong resemblance to the
legacy cordapp
plugin's DSL extension:
cordapp {
targetPlatformVersion = '<Corda 5 Platform Version>'
minimumPlatformVersion = '<Corda 5 Platform Version>'
contract {
name = 'Example CorDapp'
versionId = 1
licence = 'Test-Licence'
vendor = 'R3'
cpkCordappName = 'com.r3.example.simplecordapp.contract'
}
workflow {
name = 'Example CorDapp'
versionId = 1
licence = 'Test-Licence'
vendor = 'R3'
cpkCordappName = 'com.r3.example.simplecordapp.workflow'
}
signing {
enabled = (true | false)
// These options presumably mirror Ant's signJar task options.
options {
alias = '??'
storePassword = '??'
keyStore = file('/path/to/keystore')
storeType= ('PKCS12' | 'JKS')
keyPassword = '$storePassword'
signatureFileName = '$alias'
verbose = (true | false)
strict = (true | false)
internalSF = (true | false)
sectionsOnly = (true | false)
lazy = (true | false)
maxMemory = '??'
preserveLastModified = (true | false)
tsaUrl = '??'
tsaCert = '??'
tsaProxyHost = '??'
tsaProxyPort = '??'
executable = file('/path/to/alternate/jarsigner')
force = (true | false)
signatureAlgorithm = '??'
digestAlgorithm = '??'
tsaDigestAlgorithm = '??'
}
}
sealing = (true | false)
hashAlgorithm = 'SHA-256'
}
This extension is likely to change as Corda 5 matures and its requirements evolve. The name
,
licence
and vendor
fields are mapped to the Bundle-Name
, Bundle-License
and Bundle-Vendor
OSGi manifest tags respectively.
Applying cordapp-cpk2
creates the following new Gradle configurations:
-
cordaProvided
: This configuration declares a dependency that the CorDapp needs to compile against, but which should not become part of the CPK since it will be provided by the Corda node at runtime. This effectively replaces the legacycordaCompile
configuration. AnycordaProvided
dependency is also implicitly added to Gradle'scompileOnly
and*Implementation
configurations, and consequently is not included either in theruntimeClasspath
configuration, as a dependency in the published POM file, or packaged inside the CPK file. However, it will be included in thetestRuntimeClasspath
configuration, and will also become a transitivecordaProvided
dependency of any CorDapp which depends on this CorDapp. -
cordaPrivateProvided
: This configuration is likecordaProvided
, except that its contents do not become transitivecordaProvided
dependencies of any CorDapps which depend on this one. -
cordapp
: This declares a compile-time dependency against the jar of another CPK CorDapp. As withcordaProvided
, the dependency is also added implicitly to Gradle'scompileOnly
and*Implementation
configurations, and is excluded from theruntimeClasspath
configuration, the published POM file, and the contents of the CPK file. The jars of allcordapp
dependencies are listed as lines in this jar'sMETA-INF/CPKDependencies.json
file:
{
"formatVersion": "2.0",
"dependencies": [
{
"name": "$BUNDLE_SYMBOLIC_NAME",
"version": "$BUNDLE_VERSION",
"verifySameSignerAsMe": true
}
]
}
cordapp
dependencies are transitive in the sense that if CorDapp B
declares a cordapp
dependency on CorDapp A
, and then CorDapp C
declares a cordapp
dependency on CorDapp B
,
then CorDapp C
will acquire compile-time dependencies on the jars of both CorDapps A
and B
. The cordaProvided
dependencies of both A
and B
will also be added to CorDapp C
's
cordaProvided
configuration. This piece of Dark Magic is achieved by publishing each CPK with
a "companion" POM that contains the extra dependency information. The cordapp-cpk2
plugin resolves
these "companion" POMs transparently to the user so that CorDapps have the transitive relationships
that everyone expects.
Note that in order for everything to work as intended, the "companion" POM must be published into the same repository as its associated jar artifact. For a jar with Maven coordinates:
${group}:${artifact}:${version}
the "companion"'s Maven coordinates will be:
${group}.${artifact}:${artifact}.corda.cpk:${version}
-
cordaEmbedded
: This configuration behaves similarly tocordaProvided
in the sense that it declares acompileOnly
dependency that is excluded from both the CPK contents and from the published POM. The difference is that the dependent jar is also added to aMETA-INF/lib
folder inside the CorDapp's jar, and appended to the jar'sBundle-Classpath
manifest attribute. Note that an OSGi framework considers aBundle-Classpath
to contain ordinary jars and not bundles, even if those jars contain OSGi metadata of their own. Note also that the embedded jars' transitive dependencies will be embedded too, unless they are explicitly added to another Gradle configuration. Use embedding with care! It is provided as a tool for those cases where a dependency has no OSGi metadata, or its metadata is somehow unusable. I strongly recommend not embedding dependencies which already have valid OSGi metadata. -
cordaRuntimeOnly
: This declares a dependency that will be added to theruntimeClasspath
, but which the CorDapp doesn't need to compile against and must not be packaged into the CPK file either. It replaces the legacycordaRuntime
configuration.
The legacy cordaCompile
and cordaRuntime
configurations are built upon Gradle's deprecated
compile
and runtime
configurations, which have finally been removed in Gradle 7.0.
The cordapp-cpk2
plugin creates a new Gradle SoftwareComponent
named "cordapp", which you
can use to create a MavenPublication
:
plugins {
id 'net.corda.plugins.cordapp-cpk2'
id 'maven-publish'
}
publishing {
publications {
myCorDapp(MavenPublication) {
from components.cordapp
}
}
}
jar
: This is the standardJar
task created by Gradle'sjava-library
plugin, and then enhanced by Bnd'sbuilder
plugin to create an OSGi bundle. The contents of theruntimeClasspath
configuration is added to the jar'sMETA-INF/privatelib
folder, except for those jars which have been declared as either acordapp
,cordaProvided
,cordaEmbedded
orcordaRuntimeOnly
dependency.
The jar
task is an automatic dependency of Gradle's assemble
task.
These tasks perform intermediate steps as part of creating a CPK.
-
cordappDependencyCalculator
: Calculates which jars belong to which part of a CPK's packaging. -
cordappCPKDependencies
: Generates the jar'sMETA-INF/CPKDependencies.json
file. -
verifyBundle
: Verifies that the jar's OSGi metadata is consistent with the packages that have been included in the CPK. This task uses Bnd'sVerifier
class with "strict" verification enabled to ensure that everyImport-Package
element has an associated version too.
The cordapp-cpk2
plugin automatically adds these dependencies to the CorDapp:
compileOnly "biz.aQute.bnd:biz.aQute.bnd.annotation:$bndVersion"
compileOnly "org.osgi:osgi.annotation:8.1.0"
These annotations control how Bnd will generate OSGi metadata for the jar. In practice, the plugin already tries to handle the typical cases for creating CorDapps.
An OSGi bundle should be uniquely identifiable by the combination of its Bundle-SymbolicName
and Bundle-Version
manifest attributes. The cordapp-cpk2
plugin always sets the Bundle-Version
attribute to the Jar
task's archiveVersion
property, and it generates a default Bundle-SymbolicName
value according to the following pattern:
(${project.group}.)?${archiveBaseName}(-${archiveAppendix})?(-${archiveClassifier})?
However, if this default value is unacceptable for any reason, the Bundle-SymbolicName
can also be set explicitly via:
tasks.named('jar', Jar) {
osgi {
symbolicName = '<value>'
}
}
The cordapp-cpk2
plugin creates a Bnd -exportcontents
command to generate the jar's OSGi
Export-Package
header. By default, it will automatically add every package inside the jar to this
-exportcontents
command. The assumption here is that a CorDapp will not have a complicated package structure,
and that Corda's OSGi sandboxes will provide additional CorDapp isolation anyway.
CorDapp developers who wish to configure their package exports more precisely can disable this default behaviour
from the jar
task:
tasks.named('jar', Jar) {
osgi {
autoExport = false
}
}
You can then apply @org.osgi.annotation.bundle.Export
annotations "by hand" to selected package-info.java
files.
You can also export package names explicitly, although applying @Export
annotations would still be better:
tasks.named('jar', Jar) {
osgi {
exportPackage 'com.example.cordapp', 'com.example.cordapp.foo'
}
}
In an ideal world, Bnd would generate the correct OSGi Import-Package
manifest header automatically. That being
said, Bnd will occasionally also notice unexpected package references from unused code-paths within the byte-code.
The cordapp-cpk2
plugin provides the following options to override the detected package settings:
tasks.named('jar', Jar) {
osgi {
// Declares that this CorDapp requires the OSGi framework to provide the 'com.example.cordapp' package.
// This value is passed straight through to Bnd.
importPackage 'com.example.cordapp'
// Declares that this CorDapp uses the 'com.example.cordapp.foo` package.
// However, Corda will not complain if no-one provides it at runtime. This
// assumes that the missing package isn't really required at all.
optionalImport 'com.example.cordapp.foo'
// Like `optionalImport`, except that it also assigns this package an empty
// version range. This is useful when the unused package doesn't have a version
// range of its own because it does not belong to another OSGi bundle.
suppressImportVersion 'com.example.cordapp.bar'
}
}
Bundles that use java.util.ServiceLoader
require special handling to support their META-INF/services/
files.
Bnd provides @ServiceProvider
and @ServiceConsumer
annotations
to ensure that the bundle respects OSGi's Service Loader Mediator Specification.
The plugin will generate Corda-*-Classes
tags in the jar's MANIFEST.MF
. The generated
tags are controlled by the net.corda.cordapp.cordapp-configuration
Gradle plugin in the
corda-api repo.
Each tag contains a list of the classes within the jar that have been identified as being a Corda contract, a Corda flow etc. Each of these classes has also been confirmed as being public, static and non-abstract, which allows Corda to instantiate them. Empty tags are excluded from the final manifest, and so not every tag is guaranteed to be present.
The plugin generates these lists using Bnd's ${classes}
macro. However, we may also need to
update these macros as Corda evolves, and would prefer not to need to update the cordapp-cpk2
plugin at the same time. We can therefore update the macros by modifying the
net/corda/cordapp/cordapp-configuration.properties
file inside the net.corda.cordapp.cordapp-configuration
Gradle plugin, and then applying this new plugin to the CorDapps's root project. (The
cordapp-configuration
plugin is part of the Corda repository, and new versions of it
will be released as part of Corda, of course.)
Any property key inside this file that matches Corda-*-Classes
defines a filter to
generate a new manifest tag (or replace an existing tag). E.g.
Corda-Contract-Classes=IMPLEMENTS;net.corda.v10.ledger.contracts.Contract
The cordapp-cpk2
plugin will append additional clauses to each filter to ensure that it still
only selects public static non-abstract classes, since we don't expect this requirement to change.
The CPK needs to declare imports for these packages so that OSGi can create lazy proxies for any JPA entities the bundle may contain:
org.hibernate.proxy
javaassist.util.proxy
We must also allow the bundle to import this package, which contains Hibernate-specific annotations:
org.hibernate.annotations
We declare all these packages as "dynamic" imports to avoid binding the CPK to a specific version of Hibernate, which should make it easier for Corda itself to evolve without breaking everyone's CorDapps. (Future versions of Hibernate are also likely not to use Javassist.)
The plugin will declare these packages using the OSGI DynamicImport-Package
header.
If necessary, we can update these package names via the cordapp-configuration.properties
file by adding a comma-separated list to the Required-Packages
key:
Required-Packages=org.foo,org.bar
Note that doing this will completely override the plugin's hard-coded list of packages.
Our goal is for any CPK written for Corda 5.x to be compatible with every release of Corda 5.x.
This requires the cordapp-cpk2
plugin to apply an explicit OSGi "consumer policy" for every
Corda API package that the CPK may use:
version='${range;[=,+);${@}}'
We identify Corda's API packages using the Import-Policy-Packages
property:
Import-Policy-Packages=net.corda.v5.*
You can prevent the cordapp-cpk2
plugin from applying this policy by settings:
jar {
osgi {
applyImportPolicy = false
}
}
However, this will likely also prevent your CPK compiled for Corda 5.x from installing correctly into any Corda node where
0 <= Corda version < x