mirror of
https://github.com/corda/corda.git
synced 2024-12-28 16:58:55 +00:00
Revert "Removed gradle plugins from repo to ensure plugins are never changed in R3 corda"
This reverts commit c91d9b5507
.
This commit is contained in:
parent
c91d9b5507
commit
e8f8ff7c94
29
gradle-plugins/README.rst
Normal file
29
gradle-plugins/README.rst
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
Gradle Plugins for Cordapps
|
||||||
|
===========================
|
||||||
|
|
||||||
|
The projects at this level of the project are gradle plugins for cordapps and are published to Maven Local with
|
||||||
|
the rest of the Corda libraries.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Some of the plugins here are duplicated with the ones in buildSrc. While the duplication is unwanted any
|
||||||
|
currently known solution (such as publishing from buildSrc or setting up a separate project/repo) would
|
||||||
|
introduce a two step build which is less convenient.
|
||||||
|
|
||||||
|
Version number
|
||||||
|
--------------
|
||||||
|
|
||||||
|
To modify the version number edit constants.properties in root dir
|
||||||
|
|
||||||
|
Installing
|
||||||
|
----------
|
||||||
|
|
||||||
|
If you need to bootstrap the corda repository you can install these plugins with
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
cd publish-utils
|
||||||
|
../../gradlew -u install
|
||||||
|
cd ../
|
||||||
|
../gradlew install
|
||||||
|
|
79
gradle-plugins/api-scanner/README.md
Normal file
79
gradle-plugins/api-scanner/README.md
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
# API Scanner
|
||||||
|
|
||||||
|
Generates a text summary of Corda's public API that we can check for API-breaking changes.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ gradlew generateApi
|
||||||
|
```
|
||||||
|
|
||||||
|
See [here](../../docs/source/corda-api.rst) for Corda's public API strategy. We will need to
|
||||||
|
apply this plugin to other modules in future Corda releases as those modules' APIs stabilise.
|
||||||
|
|
||||||
|
Basically, this plugin will document a module's `public` and `protected` classes/methods/fields,
|
||||||
|
excluding those from our `*.internal.*` packgages, any synthetic methods, bridge methods, or methods
|
||||||
|
identified as having Kotlin's `internal` scope. (Kotlin doesn't seem to have implemented `internal`
|
||||||
|
scope for classes or fields yet as these are currently `public` inside the `.class` file.)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
Include this line in the `build.gradle` file of every Corda module that exports public API:
|
||||||
|
|
||||||
|
```gradle
|
||||||
|
apply plugin: 'net.corda.plugins.api-scanner'
|
||||||
|
```
|
||||||
|
|
||||||
|
This will create a Gradle task called `scanApi` which will analyse that module's Jar artifacts. More precisely,
|
||||||
|
it will analyse all of the Jar artifacts that have not been assigned a Maven classifier, on the basis
|
||||||
|
that these should be the module's main artifacts.
|
||||||
|
|
||||||
|
The `scanApi` task supports the following configuration options:
|
||||||
|
```gradle
|
||||||
|
scanApi {
|
||||||
|
// Make the classpath-scanning phase more verbose.
|
||||||
|
verbose = {true|false}
|
||||||
|
|
||||||
|
// Enable / disable the task within this module.
|
||||||
|
enabled = {true|false}
|
||||||
|
|
||||||
|
// Names of classes that should be excluded from the output.
|
||||||
|
excludeClasses = [
|
||||||
|
...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
All of the `ScanApi` tasks write their output files to their own `$buildDir/api` directory, where they
|
||||||
|
are collated into a single output file by the `GenerateApi` task. The `GenerateApi` task is declared
|
||||||
|
in the root project's `build.gradle` file:
|
||||||
|
|
||||||
|
```gradle
|
||||||
|
task generateApi(type: net.corda.plugins.GenerateApi){
|
||||||
|
baseName = "api-corda"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The final API file is written to `$buildDir/api/$baseName-$project.version.txt`
|
||||||
|
|
||||||
|
### Sample Output
|
||||||
|
```
|
||||||
|
public interface net.corda.core.contracts.Attachment extends net.corda.core.contracts.NamedByHash
|
||||||
|
public abstract void extractFile(String, java.io.OutputStream)
|
||||||
|
@org.jetbrains.annotations.NotNull public abstract List getSigners()
|
||||||
|
@org.jetbrains.annotations.NotNull public abstract java.io.InputStream open()
|
||||||
|
@org.jetbrains.annotations.NotNull public abstract jar.JarInputStream openAsJAR()
|
||||||
|
##
|
||||||
|
public interface net.corda.core.contracts.AttachmentConstraint
|
||||||
|
public abstract boolean isSatisfiedBy(net.corda.core.contracts.Attachment)
|
||||||
|
##
|
||||||
|
public final class net.corda.core.contracts.AttachmentResolutionException extends net.corda.core.flows.FlowException
|
||||||
|
public <init>(net.corda.core.crypto.SecureHash)
|
||||||
|
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getHash()
|
||||||
|
##
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Notes
|
||||||
|
The `GenerateApi` task will collate the output of every `ScanApi` task found either in the same project,
|
||||||
|
or in any of that project's subprojects. So it is _theoretically_ possible also to collate the API output
|
||||||
|
from subtrees of modules simply by defining a new `GenerateApi` task at the root of that subtree.
|
||||||
|
|
||||||
|
## Plugin Installation
|
||||||
|
See [here](../README.rst) for full installation instructions.
|
19
gradle-plugins/api-scanner/build.gradle
Normal file
19
gradle-plugins/api-scanner/build.gradle
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
apply plugin: 'java'
|
||||||
|
apply plugin: 'net.corda.plugins.publish-utils'
|
||||||
|
apply plugin: 'com.jfrog.artifactory'
|
||||||
|
|
||||||
|
description "Generates a summary of the artifact's public API"
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile gradleApi()
|
||||||
|
compile "io.github.lukehutch:fast-classpath-scanner:2.7.0"
|
||||||
|
testCompile "junit:junit:4.12"
|
||||||
|
}
|
||||||
|
|
||||||
|
publish {
|
||||||
|
name project.name
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
package net.corda.plugins;
|
||||||
|
|
||||||
|
import org.gradle.api.Plugin;
|
||||||
|
import org.gradle.api.Project;
|
||||||
|
import org.gradle.api.artifacts.ConfigurationContainer;
|
||||||
|
import org.gradle.api.file.FileCollection;
|
||||||
|
import org.gradle.api.tasks.TaskCollection;
|
||||||
|
import org.gradle.jvm.tasks.Jar;
|
||||||
|
|
||||||
|
public class ApiScanner implements Plugin<Project> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identify the Gradle Jar tasks creating jars
|
||||||
|
* without Maven classifiers, and generate API
|
||||||
|
* documentation for them.
|
||||||
|
* @param p Current project.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void apply(Project p) {
|
||||||
|
p.getLogger().info("Applying API scanner to {}", p.getName());
|
||||||
|
|
||||||
|
ScannerExtension extension = p.getExtensions().create("scanApi", ScannerExtension.class);
|
||||||
|
|
||||||
|
p.afterEvaluate(project -> {
|
||||||
|
TaskCollection<Jar> jarTasks = project.getTasks()
|
||||||
|
.withType(Jar.class)
|
||||||
|
.matching(jarTask -> jarTask.getClassifier().isEmpty() && jarTask.isEnabled());
|
||||||
|
if (jarTasks.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
project.getLogger().info("Adding scanApi task to {}", project.getName());
|
||||||
|
project.getTasks().create("scanApi", ScanApi.class, scanTask -> {
|
||||||
|
scanTask.setClasspath(compilationClasspath(project.getConfigurations()));
|
||||||
|
// Automatically creates a dependency on jar tasks.
|
||||||
|
scanTask.setSources(project.files(jarTasks));
|
||||||
|
scanTask.setExcludeClasses(extension.getExcludeClasses());
|
||||||
|
scanTask.setVerbose(extension.isVerbose());
|
||||||
|
scanTask.setEnabled(extension.isEnabled());
|
||||||
|
|
||||||
|
// Declare this ScanApi task to be a dependency of any
|
||||||
|
// GenerateApi tasks belonging to any of our ancestors.
|
||||||
|
project.getRootProject().getTasks()
|
||||||
|
.withType(GenerateApi.class)
|
||||||
|
.matching(generateTask -> isAncestorOf(generateTask.getProject(), project))
|
||||||
|
.forEach(generateTask -> generateTask.dependsOn(scanTask));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Recurse through a child project's parents until we reach the root,
|
||||||
|
* and return true iff we find our target project along the way.
|
||||||
|
*/
|
||||||
|
private static boolean isAncestorOf(Project target, Project child) {
|
||||||
|
Project p = child;
|
||||||
|
while (p != null) {
|
||||||
|
if (p == target) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
p = p.getParent();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FileCollection compilationClasspath(ConfigurationContainer configurations) {
|
||||||
|
return configurations.getByName("compile")
|
||||||
|
.plus(configurations.getByName("compileOnly"));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
package net.corda.plugins;
|
||||||
|
|
||||||
|
import org.gradle.api.DefaultTask;
|
||||||
|
import org.gradle.api.file.FileCollection;
|
||||||
|
import org.gradle.api.tasks.InputFiles;
|
||||||
|
import org.gradle.api.tasks.OutputFile;
|
||||||
|
import org.gradle.api.tasks.TaskAction;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
|
||||||
|
import static java.util.Comparator.comparing;
|
||||||
|
import static java.util.stream.Collectors.toList;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class GenerateApi extends DefaultTask {
|
||||||
|
|
||||||
|
private final File outputDir;
|
||||||
|
private String baseName;
|
||||||
|
|
||||||
|
public GenerateApi() {
|
||||||
|
outputDir = new File(getProject().getBuildDir(), "api");
|
||||||
|
baseName = "api-" + getProject().getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBaseName(String baseName) {
|
||||||
|
this.baseName = baseName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@InputFiles
|
||||||
|
public FileCollection getSources() {
|
||||||
|
return getProject().files(getProject().getAllprojects().stream()
|
||||||
|
.flatMap(project -> project.getTasks()
|
||||||
|
.withType(ScanApi.class)
|
||||||
|
.matching(ScanApi::isEnabled)
|
||||||
|
.stream())
|
||||||
|
.flatMap(scanTask -> scanTask.getTargets().getFiles().stream())
|
||||||
|
.sorted(comparing(File::getName))
|
||||||
|
.collect(toList())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OutputFile
|
||||||
|
public File getTarget() {
|
||||||
|
return new File(outputDir, String.format("%s-%s.txt", baseName, getProject().getVersion()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@TaskAction
|
||||||
|
public void generate() {
|
||||||
|
FileCollection apiFiles = getSources();
|
||||||
|
if (!apiFiles.isEmpty()) {
|
||||||
|
try (OutputStream output = new BufferedOutputStream(new FileOutputStream(getTarget()))) {
|
||||||
|
for (File apiFile : apiFiles) {
|
||||||
|
Files.copy(apiFile.toPath(), output);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
getLogger().error("Failed to generate API file", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,389 @@
|
|||||||
|
package net.corda.plugins;
|
||||||
|
|
||||||
|
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner;
|
||||||
|
import io.github.lukehutch.fastclasspathscanner.scanner.ClassInfo;
|
||||||
|
import io.github.lukehutch.fastclasspathscanner.scanner.FieldInfo;
|
||||||
|
import io.github.lukehutch.fastclasspathscanner.scanner.MethodInfo;
|
||||||
|
import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult;
|
||||||
|
import org.gradle.api.DefaultTask;
|
||||||
|
import org.gradle.api.file.ConfigurableFileCollection;
|
||||||
|
import org.gradle.api.file.FileCollection;
|
||||||
|
import org.gradle.api.tasks.CompileClasspath;
|
||||||
|
import org.gradle.api.tasks.Input;
|
||||||
|
import org.gradle.api.tasks.InputFiles;
|
||||||
|
import org.gradle.api.tasks.OutputFiles;
|
||||||
|
import org.gradle.api.tasks.TaskAction;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.annotation.Inherited;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLClassLoader;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
|
import static java.util.Collections.*;
|
||||||
|
import static java.util.stream.Collectors.*;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class ScanApi extends DefaultTask {
|
||||||
|
private static final int CLASS_MASK = Modifier.classModifiers();
|
||||||
|
private static final int INTERFACE_MASK = Modifier.interfaceModifiers() & ~Modifier.ABSTRACT;
|
||||||
|
private static final int METHOD_MASK = Modifier.methodModifiers();
|
||||||
|
private static final int FIELD_MASK = Modifier.fieldModifiers();
|
||||||
|
private static final int VISIBILITY_MASK = Modifier.PUBLIC | Modifier.PROTECTED;
|
||||||
|
|
||||||
|
private static final Set<String> ANNOTATION_BLACKLIST;
|
||||||
|
static {
|
||||||
|
Set<String> blacklist = new LinkedHashSet<>();
|
||||||
|
blacklist.add("kotlin.jvm.JvmOverloads");
|
||||||
|
ANNOTATION_BLACKLIST = unmodifiableSet(blacklist);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This information has been lifted from:
|
||||||
|
* @link <a href="https://github.com/JetBrains/kotlin/blob/master/core/runtime.jvm/src/kotlin/Metadata.kt">Metadata.kt</a>
|
||||||
|
*/
|
||||||
|
private static final String KOTLIN_METADATA = "kotlin.Metadata";
|
||||||
|
private static final String KOTLIN_CLASSTYPE_METHOD = "k";
|
||||||
|
private static final int KOTLIN_SYNTHETIC = 3;
|
||||||
|
|
||||||
|
private final ConfigurableFileCollection sources;
|
||||||
|
private final ConfigurableFileCollection classpath;
|
||||||
|
private final Set<String> excludeClasses;
|
||||||
|
private final File outputDir;
|
||||||
|
private boolean verbose;
|
||||||
|
|
||||||
|
public ScanApi() {
|
||||||
|
sources = getProject().files();
|
||||||
|
classpath = getProject().files();
|
||||||
|
excludeClasses = new LinkedHashSet<>();
|
||||||
|
outputDir = new File(getProject().getBuildDir(), "api");
|
||||||
|
}
|
||||||
|
|
||||||
|
@InputFiles
|
||||||
|
public FileCollection getSources() {
|
||||||
|
return sources;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSources(FileCollection sources) {
|
||||||
|
this.sources.setFrom(sources);
|
||||||
|
}
|
||||||
|
|
||||||
|
@CompileClasspath
|
||||||
|
@InputFiles
|
||||||
|
public FileCollection getClasspath() {
|
||||||
|
return classpath;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setClasspath(FileCollection classpath) {
|
||||||
|
this.classpath.setFrom(classpath);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input
|
||||||
|
public Collection<String> getExcludeClasses() {
|
||||||
|
return unmodifiableSet(excludeClasses);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setExcludeClasses(Collection<String> excludeClasses) {
|
||||||
|
this.excludeClasses.clear();
|
||||||
|
this.excludeClasses.addAll(excludeClasses);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OutputFiles
|
||||||
|
public FileCollection getTargets() {
|
||||||
|
return getProject().files(
|
||||||
|
StreamSupport.stream(sources.spliterator(), false)
|
||||||
|
.map(this::toTarget)
|
||||||
|
.collect(toList())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isVerbose() {
|
||||||
|
return verbose;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setVerbose(boolean verbose) {
|
||||||
|
this.verbose = verbose;
|
||||||
|
}
|
||||||
|
|
||||||
|
private File toTarget(File source) {
|
||||||
|
return new File(outputDir, source.getName().replaceAll(".jar$", ".txt"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@TaskAction
|
||||||
|
public void scan() {
|
||||||
|
try (Scanner scanner = new Scanner(classpath)) {
|
||||||
|
for (File source : sources) {
|
||||||
|
scanner.scan(source);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
getLogger().error("Failed to write API file", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Scanner implements Closeable {
|
||||||
|
private final URLClassLoader classpathLoader;
|
||||||
|
private final Class<? extends Annotation> metadataClass;
|
||||||
|
private final Method classTypeMethod;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Scanner(URLClassLoader classpathLoader) {
|
||||||
|
this.classpathLoader = classpathLoader;
|
||||||
|
|
||||||
|
Class<? extends Annotation> kClass;
|
||||||
|
Method kMethod;
|
||||||
|
try {
|
||||||
|
kClass = (Class<Annotation>) Class.forName(KOTLIN_METADATA, true, classpathLoader);
|
||||||
|
kMethod = kClass.getDeclaredMethod(KOTLIN_CLASSTYPE_METHOD);
|
||||||
|
} catch (ClassNotFoundException | NoSuchMethodException e) {
|
||||||
|
kClass = null;
|
||||||
|
kMethod = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
metadataClass = kClass;
|
||||||
|
classTypeMethod = kMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
Scanner(FileCollection classpath) throws MalformedURLException {
|
||||||
|
this(new URLClassLoader(toURLs(classpath)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
classpathLoader.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void scan(File source) {
|
||||||
|
File target = toTarget(source);
|
||||||
|
try (
|
||||||
|
URLClassLoader appLoader = new URLClassLoader(new URL[]{ toURL(source) }, classpathLoader);
|
||||||
|
PrintWriter writer = new PrintWriter(target, "UTF-8")
|
||||||
|
) {
|
||||||
|
scan(writer, appLoader);
|
||||||
|
} catch (IOException e) {
|
||||||
|
getLogger().error("API scan has failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void scan(PrintWriter writer, ClassLoader appLoader) {
|
||||||
|
ScanResult result = new FastClasspathScanner(getScanSpecification())
|
||||||
|
.overrideClassLoaders(appLoader)
|
||||||
|
.ignoreParentClassLoaders()
|
||||||
|
.ignoreMethodVisibility()
|
||||||
|
.ignoreFieldVisibility()
|
||||||
|
.enableMethodInfo()
|
||||||
|
.enableFieldInfo()
|
||||||
|
.verbose(verbose)
|
||||||
|
.scan();
|
||||||
|
writeApis(writer, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String[] getScanSpecification() {
|
||||||
|
String[] spec = new String[2 + excludeClasses.size()];
|
||||||
|
spec[0] = "!"; // Don't blacklist system classes from the output.
|
||||||
|
spec[1] = "-dir:"; // Ignore classes on the filesystem.
|
||||||
|
|
||||||
|
int i = 2;
|
||||||
|
for (String excludeClass : excludeClasses) {
|
||||||
|
spec[i++] = '-' + excludeClass;
|
||||||
|
}
|
||||||
|
return spec;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeApis(PrintWriter writer, ScanResult result) {
|
||||||
|
Map<String, ClassInfo> allInfo = result.getClassNameToClassInfo();
|
||||||
|
result.getNamesOfAllClasses().forEach(className -> {
|
||||||
|
if (className.contains(".internal.")) {
|
||||||
|
// These classes belong to internal Corda packages.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ClassInfo classInfo = allInfo.get(className);
|
||||||
|
if (classInfo.getClassLoaders() == null) {
|
||||||
|
// Ignore classes that belong to one of our target ClassLoader's parents.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?> javaClass = result.classNameToClassRef(className);
|
||||||
|
if (!isVisible(javaClass.getModifiers())) {
|
||||||
|
// Excludes private and package-protected classes
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int kotlinClassType = getKotlinClassType(javaClass);
|
||||||
|
if (kotlinClassType == KOTLIN_SYNTHETIC) {
|
||||||
|
// Exclude classes synthesised by the Kotlin compiler.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeClass(writer, classInfo, javaClass.getModifiers());
|
||||||
|
writeMethods(writer, classInfo.getMethodAndConstructorInfo());
|
||||||
|
writeFields(writer, classInfo.getFieldInfo());
|
||||||
|
writer.println("##");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeClass(PrintWriter writer, ClassInfo classInfo, int modifiers) {
|
||||||
|
if (classInfo.isAnnotation()) {
|
||||||
|
/*
|
||||||
|
* Annotation declaration.
|
||||||
|
*/
|
||||||
|
writer.append(Modifier.toString(modifiers & INTERFACE_MASK));
|
||||||
|
writer.append(" @interface ").print(classInfo);
|
||||||
|
} else if (classInfo.isStandardClass()) {
|
||||||
|
/*
|
||||||
|
* Class declaration.
|
||||||
|
*/
|
||||||
|
List<String> annotationNames = toNames(readClassAnnotationsFor(classInfo));
|
||||||
|
if (!annotationNames.isEmpty()) {
|
||||||
|
writer.append(asAnnotations(annotationNames));
|
||||||
|
}
|
||||||
|
writer.append(Modifier.toString(modifiers & CLASS_MASK));
|
||||||
|
writer.append(" class ").print(classInfo);
|
||||||
|
Set<ClassInfo> superclasses = classInfo.getDirectSuperclasses();
|
||||||
|
if (!superclasses.isEmpty()) {
|
||||||
|
writer.append(" extends ").print(stringOf(superclasses));
|
||||||
|
}
|
||||||
|
Set<ClassInfo> interfaces = classInfo.getDirectlyImplementedInterfaces();
|
||||||
|
if (!interfaces.isEmpty()) {
|
||||||
|
writer.append(" implements ").print(stringOf(interfaces));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* Interface declaration.
|
||||||
|
*/
|
||||||
|
List<String> annotationNames = toNames(readInterfaceAnnotationsFor(classInfo));
|
||||||
|
if (!annotationNames.isEmpty()) {
|
||||||
|
writer.append(asAnnotations(annotationNames));
|
||||||
|
}
|
||||||
|
writer.append(Modifier.toString(modifiers & INTERFACE_MASK));
|
||||||
|
writer.append(" interface ").print(classInfo);
|
||||||
|
Set<ClassInfo> superinterfaces = classInfo.getDirectSuperinterfaces();
|
||||||
|
if (!superinterfaces.isEmpty()) {
|
||||||
|
writer.append(" extends ").print(stringOf(superinterfaces));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writer.println();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeMethods(PrintWriter writer, List<MethodInfo> methods) {
|
||||||
|
sort(methods);
|
||||||
|
for (MethodInfo method : methods) {
|
||||||
|
if (isVisible(method.getAccessFlags()) // Only public and protected methods
|
||||||
|
&& isValid(method.getAccessFlags(), METHOD_MASK) // Excludes bridge and synthetic methods
|
||||||
|
&& !isKotlinInternalScope(method)) {
|
||||||
|
writer.append(" ").println(filterAnnotationsFor(method));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeFields(PrintWriter output, List<FieldInfo> fields) {
|
||||||
|
sort(fields);
|
||||||
|
for (FieldInfo field : fields) {
|
||||||
|
if (isVisible(field.getAccessFlags()) && isValid(field.getAccessFlags(), FIELD_MASK)) {
|
||||||
|
output.append(" ").println(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getKotlinClassType(Class<?> javaClass) {
|
||||||
|
if (metadataClass != null) {
|
||||||
|
Annotation metadata = javaClass.getAnnotation(metadataClass);
|
||||||
|
if (metadata != null) {
|
||||||
|
try {
|
||||||
|
return (int) classTypeMethod.invoke(metadata);
|
||||||
|
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||||
|
getLogger().error("Failed to read Kotlin annotation", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> toNames(Collection<ClassInfo> classes) {
|
||||||
|
return classes.stream()
|
||||||
|
.map(ClassInfo::toString)
|
||||||
|
.filter(ScanApi::isApplicationClass)
|
||||||
|
.collect(toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<ClassInfo> readClassAnnotationsFor(ClassInfo classInfo) {
|
||||||
|
Set<ClassInfo> annotations = new HashSet<>(classInfo.getAnnotations());
|
||||||
|
annotations.addAll(selectInheritedAnnotations(classInfo.getSuperclasses()));
|
||||||
|
annotations.addAll(selectInheritedAnnotations(classInfo.getImplementedInterfaces()));
|
||||||
|
return annotations;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<ClassInfo> readInterfaceAnnotationsFor(ClassInfo classInfo) {
|
||||||
|
Set<ClassInfo> annotations = new HashSet<>(classInfo.getAnnotations());
|
||||||
|
annotations.addAll(selectInheritedAnnotations(classInfo.getSuperinterfaces()));
|
||||||
|
return annotations;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns those annotations which have themselves been annotated as "Inherited".
|
||||||
|
*/
|
||||||
|
private List<ClassInfo> selectInheritedAnnotations(Collection<ClassInfo> classes) {
|
||||||
|
return classes.stream()
|
||||||
|
.flatMap(cls -> cls.getAnnotations().stream())
|
||||||
|
.filter(ann -> ann.hasMetaAnnotation(Inherited.class.getName()))
|
||||||
|
.collect(toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private MethodInfo filterAnnotationsFor(MethodInfo method) {
|
||||||
|
return new MethodInfo(
|
||||||
|
method.getClassName(),
|
||||||
|
method.getMethodName(),
|
||||||
|
method.getAccessFlags(),
|
||||||
|
method.getTypeDescriptor(),
|
||||||
|
method.getAnnotationNames().stream()
|
||||||
|
.filter(ScanApi::isVisibleAnnotation)
|
||||||
|
.collect(toList())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isVisibleAnnotation(String annotationName) {
|
||||||
|
return !ANNOTATION_BLACKLIST.contains(annotationName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isKotlinInternalScope(MethodInfo method) {
|
||||||
|
return method.getMethodName().indexOf('$') >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isValid(int modifiers, int mask) {
|
||||||
|
return (modifiers & mask) == modifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isVisible(int accessFlags) {
|
||||||
|
return (accessFlags & VISIBILITY_MASK) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String stringOf(Collection<ClassInfo> items) {
|
||||||
|
return items.stream().map(ClassInfo::toString).collect(joining(", "));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String asAnnotations(Collection<String> items) {
|
||||||
|
return items.stream().collect(joining(" @", "@", " "));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isApplicationClass(String typeName) {
|
||||||
|
return !typeName.startsWith("java.") && !typeName.startsWith("kotlin.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static URL toURL(File file) throws MalformedURLException {
|
||||||
|
return file.toURI().toURL();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static URL[] toURLs(Iterable<File> files) throws MalformedURLException {
|
||||||
|
List<URL> urls = new LinkedList<>();
|
||||||
|
for (File file : files) {
|
||||||
|
urls.add(toURL(file));
|
||||||
|
}
|
||||||
|
return urls.toArray(new URL[urls.size()]);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package net.corda.plugins;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class ScannerExtension {
|
||||||
|
|
||||||
|
private boolean verbose;
|
||||||
|
private boolean enabled = true;
|
||||||
|
private List<String> excludeClasses = emptyList();
|
||||||
|
|
||||||
|
public boolean isVerbose() {
|
||||||
|
return verbose;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVerbose(boolean verbose) {
|
||||||
|
this.verbose = verbose;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getExcludeClasses() {
|
||||||
|
return excludeClasses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExcludeClasses(List<String> excludeClasses) {
|
||||||
|
this.excludeClasses = excludeClasses;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
implementation-class=net.corda.plugins.ApiScanner
|
83
gradle-plugins/build.gradle
Normal file
83
gradle-plugins/build.gradle
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
// This script exists just to allow bootstrapping the gradle plugins if maven central or jcenter are unavailable
|
||||||
|
// or if you are developing these plugins. See the readme for more information.
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
// For sharing constants between builds
|
||||||
|
Properties constants = new Properties()
|
||||||
|
file("$projectDir/../constants.properties").withInputStream { constants.load(it) }
|
||||||
|
|
||||||
|
// If you bump this version you must re-bootstrap the codebase. See the README for more information.
|
||||||
|
ext {
|
||||||
|
gradle_plugins_version = constants.getProperty("gradlePluginsVersion")
|
||||||
|
bouncycastle_version = constants.getProperty("bouncycastleVersion")
|
||||||
|
typesafe_config_version = constants.getProperty("typesafeConfigVersion")
|
||||||
|
jsr305_version = constants.getProperty("jsr305Version")
|
||||||
|
kotlin_version = constants.getProperty("kotlinVersion")
|
||||||
|
artifactory_plugin_version = constants.getProperty('artifactoryPluginVersion')
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath "net.corda.plugins:publish-utils:$gradle_plugins_version"
|
||||||
|
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3'
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
classpath "org.jfrog.buildinfo:build-info-extractor-gradle:$artifactory_plugin_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'net.corda.plugins.publish-utils'
|
||||||
|
apply plugin: 'com.jfrog.artifactory'
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
version gradle_plugins_version
|
||||||
|
group 'net.corda.plugins'
|
||||||
|
}
|
||||||
|
|
||||||
|
bintrayConfig {
|
||||||
|
user = System.getenv('CORDA_BINTRAY_USER')
|
||||||
|
key = System.getenv('CORDA_BINTRAY_KEY')
|
||||||
|
repo = 'corda'
|
||||||
|
org = 'r3'
|
||||||
|
licenses = ['Apache-2.0']
|
||||||
|
vcsUrl = 'https://github.com/corda/corda'
|
||||||
|
projectUrl = 'https://github.com/corda/corda'
|
||||||
|
gpgSign = true
|
||||||
|
gpgPassphrase = System.getenv('CORDA_BINTRAY_GPG_PASSPHRASE')
|
||||||
|
publications = ['cordformation', 'quasar-utils', 'cordform-common', 'api-scanner', 'cordapp']
|
||||||
|
license {
|
||||||
|
name = 'Apache-2.0'
|
||||||
|
url = 'https://www.apache.org/licenses/LICENSE-2.0'
|
||||||
|
distribution = 'repo'
|
||||||
|
}
|
||||||
|
developer {
|
||||||
|
id = 'R3'
|
||||||
|
name = 'R3'
|
||||||
|
email = 'dev@corda.net'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
artifactory {
|
||||||
|
publish {
|
||||||
|
contextUrl = 'https://ci-artifactory.corda.r3cev.com/artifactory'
|
||||||
|
repository {
|
||||||
|
repoKey = 'corda-dev'
|
||||||
|
username = 'teamcity'
|
||||||
|
password = System.getenv('CORDA_ARTIFACTORY_PASSWORD')
|
||||||
|
}
|
||||||
|
|
||||||
|
defaults {
|
||||||
|
// Publish utils does not have a publish block because it would be circular for it to apply it's own
|
||||||
|
// extensions to itself
|
||||||
|
if(project.name == 'publish-utils') {
|
||||||
|
publications('publishUtils')
|
||||||
|
// Root project applies the plugin (for this block) but does not need to be published
|
||||||
|
} else if(project != rootProject) {
|
||||||
|
publications(project.extensions.publish.name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
gradle-plugins/cordapp/README.md
Normal file
10
gradle-plugins/cordapp/README.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# Cordapp Gradle Plugin
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
To transform any project this plugin is applied to into a cordapp project that generates a cordapp JAR.
|
||||||
|
|
||||||
|
## Effects
|
||||||
|
|
||||||
|
Will modify the default JAR task to create a CorDapp format JAR instead [see here](https://docs.corda.net/cordapp-build-systems.html)
|
||||||
|
for more information.
|
19
gradle-plugins/cordapp/build.gradle
Normal file
19
gradle-plugins/cordapp/build.gradle
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
apply plugin: 'kotlin'
|
||||||
|
apply plugin: 'net.corda.plugins.publish-utils'
|
||||||
|
apply plugin: 'com.jfrog.artifactory'
|
||||||
|
|
||||||
|
description 'Turns a project into a cordapp project that produces cordapp fat JARs'
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile gradleApi()
|
||||||
|
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
|
||||||
|
}
|
||||||
|
|
||||||
|
publish {
|
||||||
|
name project.name
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
package net.corda.plugins
|
||||||
|
|
||||||
|
import org.gradle.api.*
|
||||||
|
import org.gradle.api.artifacts.*
|
||||||
|
import org.gradle.jvm.tasks.Jar
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Cordapp plugin will turn a project into a cordapp project which builds cordapp JARs with the correct format
|
||||||
|
* and with the information needed to run on Corda.
|
||||||
|
*/
|
||||||
|
class CordappPlugin : Plugin<Project> {
|
||||||
|
override fun apply(project: Project) {
|
||||||
|
project.logger.info("Configuring ${project.name} as a cordapp")
|
||||||
|
|
||||||
|
Utils.createCompileConfiguration("cordapp", project)
|
||||||
|
Utils.createCompileConfiguration("cordaCompile", project)
|
||||||
|
|
||||||
|
val configuration: Configuration = project.configurations.create("cordaRuntime")
|
||||||
|
configuration.isTransitive = false
|
||||||
|
project.configurations.single { it.name == "runtime" }.extendsFrom(configuration)
|
||||||
|
|
||||||
|
configureCordappJar(project)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures this project's JAR as a Cordapp JAR
|
||||||
|
*/
|
||||||
|
private fun configureCordappJar(project: Project) {
|
||||||
|
// Note: project.afterEvaluate did not have full dependency resolution completed, hence a task is used instead
|
||||||
|
val task = project.task("configureCordappFatJar")
|
||||||
|
val jarTask = project.tasks.getByName("jar") as Jar
|
||||||
|
task.doLast {
|
||||||
|
jarTask.from(getDirectNonCordaDependencies(project).map { project.zipTree(it)}).apply {
|
||||||
|
exclude("META-INF/*.SF")
|
||||||
|
exclude("META-INF/*.DSA")
|
||||||
|
exclude("META-INF/*.RSA")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jarTask.dependsOn(task)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getDirectNonCordaDependencies(project: Project): Set<File> {
|
||||||
|
project.logger.info("Finding direct non-corda dependencies for inclusion in CorDapp JAR")
|
||||||
|
val excludes = listOf(
|
||||||
|
mapOf("group" to "org.jetbrains.kotlin", "name" to "kotlin-stdlib"),
|
||||||
|
mapOf("group" to "org.jetbrains.kotlin", "name" to "kotlin-stdlib-jre8"),
|
||||||
|
mapOf("group" to "org.jetbrains.kotlin", "name" to "kotlin-reflect"),
|
||||||
|
mapOf("group" to "co.paralleluniverse", "name" to "quasar-core")
|
||||||
|
)
|
||||||
|
|
||||||
|
val runtimeConfiguration = project.configuration("runtime")
|
||||||
|
// The direct dependencies of this project
|
||||||
|
val excludeDeps = project.configuration("cordapp").allDependencies +
|
||||||
|
project.configuration("cordaCompile").allDependencies +
|
||||||
|
project.configuration("cordaRuntime").allDependencies
|
||||||
|
val directDeps = runtimeConfiguration.allDependencies - excludeDeps
|
||||||
|
// We want to filter out anything Corda related or provided by Corda, like kotlin-stdlib and quasar
|
||||||
|
val filteredDeps = directDeps.filter { dep ->
|
||||||
|
excludes.none { exclude -> (exclude["group"] == dep.group) && (exclude["name"] == dep.name) }
|
||||||
|
}
|
||||||
|
filteredDeps.forEach {
|
||||||
|
// net.corda or com.r3.corda may be a core dependency which shouldn't be included in this cordapp so give a warning
|
||||||
|
val group = it.group?.toString() ?: ""
|
||||||
|
if (group.startsWith("net.corda.") || group.startsWith("com.r3.corda.")) {
|
||||||
|
project.logger.warn("You appear to have included a Corda platform component ($it) using a 'compile' or 'runtime' dependency." +
|
||||||
|
"This can cause node stability problems. Please use 'corda' instead." +
|
||||||
|
"See http://docs.corda.net/cordapp-build-systems.html")
|
||||||
|
} else {
|
||||||
|
project.logger.info("Including dependency in CorDapp JAR: $it")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filteredDeps.map { runtimeConfiguration.files(it) }.flatten().toSet()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package net.corda.plugins
|
||||||
|
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.api.Task
|
||||||
|
import org.gradle.api.artifacts.Configuration
|
||||||
|
import org.gradle.api.plugins.ExtraPropertiesExtension
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mimics the "project.ext" functionality in groovy which provides a direct
|
||||||
|
* accessor to the "ext" extention (See: ExtraPropertiesExtension)
|
||||||
|
*/
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun <T : Any> Project.ext(name: String): T = (extensions.findByName("ext") as ExtraPropertiesExtension).get(name) as T
|
||||||
|
fun Project.configuration(name: String): Configuration = configurations.single { it.name == name }
|
||||||
|
|
||||||
|
class Utils {
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
fun createCompileConfiguration(name: String, project: Project) {
|
||||||
|
if(!project.configurations.any { it.name == name }) {
|
||||||
|
val configuration = project.configurations.create(name)
|
||||||
|
configuration.isTransitive = false
|
||||||
|
project.configurations.single { it.name == "compile" }.extendsFrom(configuration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun createRuntimeConfiguration(name: String, project: Project) {
|
||||||
|
if(!project.configurations.any { it.name == name }) {
|
||||||
|
val configuration = project.configurations.create(name)
|
||||||
|
configuration.isTransitive = false
|
||||||
|
project.configurations.single { it.name == "runtime" }.extendsFrom(configuration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
implementation-class=net.corda.plugins.CordappPlugin
|
4
gradle-plugins/cordform-common/README.md
Normal file
4
gradle-plugins/cordform-common/README.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Cordform Common
|
||||||
|
|
||||||
|
This project contains common node types that both the Corda gradle plugin suite and Corda project
|
||||||
|
require in order to build Corda nodes.
|
24
gradle-plugins/cordform-common/build.gradle
Normal file
24
gradle-plugins/cordform-common/build.gradle
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
apply plugin: 'java'
|
||||||
|
apply plugin: 'maven-publish'
|
||||||
|
apply plugin: 'net.corda.plugins.publish-utils'
|
||||||
|
apply plugin: 'com.jfrog.artifactory'
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
// This tracks the gradle plugins version and not Corda
|
||||||
|
version gradle_plugins_version
|
||||||
|
group 'net.corda.plugins'
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
// JSR 305: Nullability annotations
|
||||||
|
compile "com.google.code.findbugs:jsr305:$jsr305_version"
|
||||||
|
|
||||||
|
// TypeSafe Config: for simple and human friendly config files.
|
||||||
|
compile "com.typesafe:config:$typesafe_config_version"
|
||||||
|
}
|
||||||
|
|
||||||
|
publish {
|
||||||
|
name project.name
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package net.corda.cordform;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public interface CordformContext {
|
||||||
|
Path baseDirectory(String nodeName);
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package net.corda.cordform;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public abstract class CordformDefinition {
|
||||||
|
private Path nodesDirectory = Paths.get("build", "nodes");
|
||||||
|
private final List<Consumer<CordformNode>> nodeConfigurers = new ArrayList<>();
|
||||||
|
private final List<String> cordappPackages = new ArrayList<>();
|
||||||
|
|
||||||
|
public Path getNodesDirectory() {
|
||||||
|
return nodesDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNodesDirectory(Path nodesDirectory) {
|
||||||
|
this.nodesDirectory = nodesDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Consumer<CordformNode>> getNodeConfigurers() {
|
||||||
|
return nodeConfigurers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addNode(Consumer<CordformNode> configurer) {
|
||||||
|
nodeConfigurers.add(configurer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getCordappPackages() {
|
||||||
|
return cordappPackages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make arbitrary changes to the node directories before they are started.
|
||||||
|
* @param context Lookup of node directory by node name.
|
||||||
|
*/
|
||||||
|
public abstract void setup(@Nonnull CordformContext context);
|
||||||
|
}
|
@ -0,0 +1,173 @@
|
|||||||
|
package net.corda.cordform;
|
||||||
|
|
||||||
|
import com.typesafe.config.Config;
|
||||||
|
import com.typesafe.config.ConfigFactory;
|
||||||
|
import com.typesafe.config.ConfigValueFactory;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
|
||||||
|
public class CordformNode implements NodeDefinition {
|
||||||
|
/**
|
||||||
|
* Path relative to the running node where the serialized NodeInfos are stored.
|
||||||
|
*/
|
||||||
|
public static final String NODE_INFO_DIRECTORY = "additional-node-infos";
|
||||||
|
|
||||||
|
protected static final String DEFAULT_HOST = "localhost";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of the node.
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the RPC users for this node. This configuration block allows arbitrary configuration.
|
||||||
|
* The recommended current structure is:
|
||||||
|
* [[['username': "username_here", 'password': "password_here", 'permissions': ["permissions_here"]]]
|
||||||
|
* The above is a list to a map of keys to values using Groovy map and list shorthands.
|
||||||
|
*
|
||||||
|
* Incorrect configurations will not cause a DSL error.
|
||||||
|
*/
|
||||||
|
public List<Map<String, Object>> rpcUsers = emptyList();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the notary configuration if this node is a notary. The map is the config structure of
|
||||||
|
* net.corda.node.services.config.NotaryConfig
|
||||||
|
*/
|
||||||
|
public Map<String, Object> notary = null;
|
||||||
|
|
||||||
|
public Map<String, Object> extraConfig = null;
|
||||||
|
|
||||||
|
protected Config config = ConfigFactory.empty();
|
||||||
|
|
||||||
|
public Config getConfig() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the name of the node.
|
||||||
|
*
|
||||||
|
* @param name The node name.
|
||||||
|
*/
|
||||||
|
public void name(String name) {
|
||||||
|
this.name = name;
|
||||||
|
setValue("myLegalName", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the artemis address for this node.
|
||||||
|
*
|
||||||
|
* @return This node's P2P address.
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
public String getP2pAddress() {
|
||||||
|
return config.getString("p2pAddress");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the Artemis P2P port for this node on localhost.
|
||||||
|
*
|
||||||
|
* @param p2pPort The Artemis messaging queue port.
|
||||||
|
*/
|
||||||
|
public void p2pPort(int p2pPort) {
|
||||||
|
p2pAddress(DEFAULT_HOST + ':' + p2pPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the Artemis P2P address for this node.
|
||||||
|
*
|
||||||
|
* @param p2pAddress The Artemis messaging queue host and port.
|
||||||
|
*/
|
||||||
|
public void p2pAddress(String p2pAddress) {
|
||||||
|
setValue("p2pAddress", p2pAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the RPC address for this node, or null if one hasn't been specified.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public String getRpcAddress() {
|
||||||
|
if (config.hasPath("rpcSettings.address")) {
|
||||||
|
return config.getConfig("rpcSettings").getString("address");
|
||||||
|
}
|
||||||
|
return getOptionalString("rpcAddress");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the Artemis RPC port for this node on localhost.
|
||||||
|
*
|
||||||
|
* @param rpcPort The Artemis RPC queue port.
|
||||||
|
* @deprecated Use {@link CordformNode#rpcSettings(RpcSettings)} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public void rpcPort(int rpcPort) {
|
||||||
|
rpcAddress(DEFAULT_HOST + ':' + rpcPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the Artemis RPC address for this node.
|
||||||
|
*
|
||||||
|
* @param rpcAddress The Artemis RPC queue host and port.
|
||||||
|
* @deprecated Use {@link CordformNode#rpcSettings(RpcSettings)} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public void rpcAddress(String rpcAddress) {
|
||||||
|
setValue("rpcAddress", rpcAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the address of the web server that will connect to the node, or null if one hasn't been specified.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public String getWebAddress() {
|
||||||
|
return getOptionalString("webAddress");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure a webserver to connect to the node via RPC. This port will specify the port it will listen on. The node
|
||||||
|
* must have an RPC address configured.
|
||||||
|
*/
|
||||||
|
public void webPort(int webPort) {
|
||||||
|
webAddress(DEFAULT_HOST + ':' + webPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure a webserver to connect to the node via RPC. This address will specify the port it will listen on. The node
|
||||||
|
* must have an RPC address configured.
|
||||||
|
*/
|
||||||
|
public void webAddress(String webAddress) {
|
||||||
|
setValue("webAddress", webAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies RPC settings for the node.
|
||||||
|
*/
|
||||||
|
public void rpcSettings(RpcSettings settings) {
|
||||||
|
config = settings.addTo("rpcSettings", config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the path to a file with optional properties, which are appended to the generated node.conf file.
|
||||||
|
*
|
||||||
|
* @param configFile The file path.
|
||||||
|
*/
|
||||||
|
public void configFile(String configFile) {
|
||||||
|
setValue("configFile", configFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getOptionalString(String path) {
|
||||||
|
return config.hasPath(path) ? config.getString(path) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setValue(String path, Object value) {
|
||||||
|
config = config.withValue(path, ConfigValueFactory.fromAnyRef(value));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package net.corda.cordform;
|
||||||
|
|
||||||
|
import com.typesafe.config.Config;
|
||||||
|
|
||||||
|
public interface NodeDefinition {
|
||||||
|
String getName();
|
||||||
|
|
||||||
|
Config getConfig();
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
package net.corda.cordform;
|
||||||
|
|
||||||
|
import com.typesafe.config.Config;
|
||||||
|
import com.typesafe.config.ConfigFactory;
|
||||||
|
import com.typesafe.config.ConfigValueFactory;
|
||||||
|
|
||||||
|
public final class RpcSettings {
|
||||||
|
|
||||||
|
private Config config = ConfigFactory.empty();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RPC address for the node.
|
||||||
|
*/
|
||||||
|
public final void address(final String value) {
|
||||||
|
setValue("address", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RPC admin address for the node (necessary if [useSsl] is false or unset).
|
||||||
|
*/
|
||||||
|
public final void adminAddress(final String value) {
|
||||||
|
setValue("adminAddress", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies whether the node RPC layer will require SSL from clients.
|
||||||
|
*/
|
||||||
|
public final void useSsl(final Boolean value) {
|
||||||
|
setValue("useSsl", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies whether the RPC broker is separate from the node.
|
||||||
|
*/
|
||||||
|
public final void standAloneBroker(final Boolean value) {
|
||||||
|
setValue("standAloneBroker", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies SSL certificates options for the RPC layer.
|
||||||
|
*/
|
||||||
|
public final void ssl(final SslOptions options) {
|
||||||
|
config = options.addTo("ssl", config);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Config addTo(final String key, final Config config) {
|
||||||
|
if (this.config.isEmpty()) {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
return config.withValue(key, this.config.root());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setValue(String path, Object value) {
|
||||||
|
config = config.withValue(path, ConfigValueFactory.fromAnyRef(value));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
package net.corda.cordform;
|
||||||
|
|
||||||
|
import com.typesafe.config.Config;
|
||||||
|
import com.typesafe.config.ConfigFactory;
|
||||||
|
import com.typesafe.config.ConfigValueFactory;
|
||||||
|
|
||||||
|
public final class SslOptions {
|
||||||
|
|
||||||
|
private Config config = ConfigFactory.empty();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Password for the keystore.
|
||||||
|
*/
|
||||||
|
public final void keyStorePassword(final String value) {
|
||||||
|
setValue("keyStorePassword", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Password for the truststore.
|
||||||
|
*/
|
||||||
|
public final void trustStorePassword(final String value) {
|
||||||
|
setValue("trustStorePassword", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Directory under which key stores are to be placed.
|
||||||
|
*/
|
||||||
|
public final void certificatesDirectory(final String value) {
|
||||||
|
setValue("certificatesDirectory", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Absolute path to SSL keystore. Default: "[certificatesDirectory]/sslkeystore.jks"
|
||||||
|
*/
|
||||||
|
public final void sslKeystore(final String value) {
|
||||||
|
setValue("sslKeystore", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Absolute path to SSL truststore. Default: "[certificatesDirectory]/truststore.jks"
|
||||||
|
*/
|
||||||
|
public final void trustStoreFile(final String value) {
|
||||||
|
setValue("trustStoreFile", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Config addTo(final String key, final Config config) {
|
||||||
|
if (this.config.isEmpty()) {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
return config.withValue(key, this.config.root());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setValue(String path, Object value) {
|
||||||
|
config = config.withValue(path, ConfigValueFactory.fromAnyRef(value));
|
||||||
|
}
|
||||||
|
}
|
1
gradle-plugins/cordformation/README.rst
Normal file
1
gradle-plugins/cordformation/README.rst
Normal file
@ -0,0 +1 @@
|
|||||||
|
Please refer to the documentation in <corda-root>/doc/build/html/running-a-node.html#cordformation.
|
62
gradle-plugins/cordformation/build.gradle
Normal file
62
gradle-plugins/cordformation/build.gradle
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'kotlin'
|
||||||
|
apply plugin: 'net.corda.plugins.publish-utils'
|
||||||
|
apply plugin: 'com.jfrog.artifactory'
|
||||||
|
|
||||||
|
description 'A small gradle plugin for adding some basic Quasar tasks and configurations to reduce build.gradle bloat.'
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
noderunner
|
||||||
|
compile.extendsFrom noderunner
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
runnodes {
|
||||||
|
kotlin {
|
||||||
|
srcDir file('src/noderunner/kotlin')
|
||||||
|
compileClasspath += configurations.noderunner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile gradleApi()
|
||||||
|
compile project(":cordapp")
|
||||||
|
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
|
||||||
|
|
||||||
|
noderunner "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
|
||||||
|
|
||||||
|
compile project(':cordform-common')
|
||||||
|
}
|
||||||
|
|
||||||
|
task createNodeRunner(type: Jar, dependsOn: [classes]) {
|
||||||
|
manifest {
|
||||||
|
attributes('Main-Class': 'net.corda.plugins.NodeRunnerKt')
|
||||||
|
}
|
||||||
|
classifier = 'fatjar'
|
||||||
|
from { configurations.noderunner.collect { it.isDirectory() ? it : zipTree(it) } }
|
||||||
|
from sourceSets.runnodes.output
|
||||||
|
}
|
||||||
|
|
||||||
|
jar {
|
||||||
|
from(createNodeRunner) {
|
||||||
|
rename { 'net/corda/plugins/runnodes.jar' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
publish {
|
||||||
|
name project.name
|
||||||
|
}
|
@ -0,0 +1,218 @@
|
|||||||
|
package net.corda.plugins
|
||||||
|
|
||||||
|
import groovy.lang.Closure
|
||||||
|
import net.corda.cordform.CordformDefinition
|
||||||
|
import org.apache.tools.ant.filters.FixCrLfFilter
|
||||||
|
import org.gradle.api.DefaultTask
|
||||||
|
import org.gradle.api.plugins.JavaPluginConvention
|
||||||
|
import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME
|
||||||
|
import org.gradle.api.tasks.TaskAction
|
||||||
|
import java.io.File
|
||||||
|
import java.lang.reflect.InvocationTargetException
|
||||||
|
import java.net.URLClassLoader
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import java.util.jar.JarInputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates nodes based on the configuration of this task in the gradle configuration DSL.
|
||||||
|
*
|
||||||
|
* See documentation for examples.
|
||||||
|
*/
|
||||||
|
@Suppress("unused")
|
||||||
|
open class Cordform : DefaultTask() {
|
||||||
|
private companion object {
|
||||||
|
val nodeJarName = "corda.jar"
|
||||||
|
private val defaultDirectory: Path = Paths.get("build", "nodes")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optionally the name of a CordformDefinition subclass to which all configuration will be delegated.
|
||||||
|
*/
|
||||||
|
@Suppress("MemberVisibilityCanPrivate")
|
||||||
|
var definitionClass: String? = null
|
||||||
|
private var directory = defaultDirectory
|
||||||
|
private val nodes = mutableListOf<Node>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the directory to install nodes into.
|
||||||
|
*
|
||||||
|
* @param directory The directory the nodes will be installed into.
|
||||||
|
*/
|
||||||
|
fun directory(directory: String) {
|
||||||
|
this.directory = Paths.get(directory)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a node configuration.
|
||||||
|
*
|
||||||
|
* @param configureClosure A node configuration that will be deployed.
|
||||||
|
*/
|
||||||
|
@Suppress("MemberVisibilityCanPrivate")
|
||||||
|
fun node(configureClosure: Closure<in Node>) {
|
||||||
|
nodes += project.configure(Node(project), configureClosure) as Node
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a node configuration
|
||||||
|
*
|
||||||
|
* @param configureFunc A node configuration that will be deployed
|
||||||
|
*/
|
||||||
|
@Suppress("MemberVisibilityCanPrivate")
|
||||||
|
fun node(configureFunc: Node.() -> Any?): Node {
|
||||||
|
val node = Node(project).apply { configureFunc() }
|
||||||
|
nodes += node
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a node by name.
|
||||||
|
*
|
||||||
|
* @param name The name of the node as specified in the node configuration DSL.
|
||||||
|
* @return A node instance.
|
||||||
|
*/
|
||||||
|
private fun getNodeByName(name: String): Node? = nodes.firstOrNull { it.name == name }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs the run script into the nodes directory.
|
||||||
|
*/
|
||||||
|
private fun installRunScript() {
|
||||||
|
project.copy {
|
||||||
|
it.apply {
|
||||||
|
from(Cordformation.getPluginFile(project, "net/corda/plugins/runnodes.jar"))
|
||||||
|
fileMode = Cordformation.executableFileMode
|
||||||
|
into("$directory/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
project.copy {
|
||||||
|
it.apply {
|
||||||
|
from(Cordformation.getPluginFile(project, "net/corda/plugins/runnodes"))
|
||||||
|
// Replaces end of line with lf to avoid issues with the bash interpreter and Windows style line endings.
|
||||||
|
filter(mapOf("eol" to FixCrLfFilter.CrLf.newInstance("lf")), FixCrLfFilter::class.java)
|
||||||
|
fileMode = Cordformation.executableFileMode
|
||||||
|
into("$directory/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
project.copy {
|
||||||
|
it.apply {
|
||||||
|
from(Cordformation.getPluginFile(project, "net/corda/plugins/runnodes.bat"))
|
||||||
|
into("$directory/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The definitionClass needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath.
|
||||||
|
*/
|
||||||
|
private fun loadCordformDefinition(): CordformDefinition {
|
||||||
|
val plugin = project.convention.getPlugin(JavaPluginConvention::class.java)
|
||||||
|
val classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath
|
||||||
|
val urls = classpath.files.map { it.toURI().toURL() }.toTypedArray()
|
||||||
|
return URLClassLoader(urls, CordformDefinition::class.java.classLoader)
|
||||||
|
.loadClass(definitionClass)
|
||||||
|
.asSubclass(CordformDefinition::class.java)
|
||||||
|
.newInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The NetworkBootstrapper needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath.
|
||||||
|
*/
|
||||||
|
private fun loadNetworkBootstrapperClass(): Class<*> {
|
||||||
|
val plugin = project.convention.getPlugin(JavaPluginConvention::class.java)
|
||||||
|
val classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath
|
||||||
|
val urls = classpath.files.map { it.toURI().toURL() }.toTypedArray()
|
||||||
|
return URLClassLoader(urls, javaClass.classLoader).loadClass("net.corda.nodeapi.internal.network.NetworkBootstrapper")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This task action will create and install the nodes based on the node configurations added.
|
||||||
|
*/
|
||||||
|
@TaskAction
|
||||||
|
fun build() {
|
||||||
|
project.logger.info("Running Cordform task")
|
||||||
|
initializeConfiguration()
|
||||||
|
nodes.forEach(Node::installConfig)
|
||||||
|
installCordaJar()
|
||||||
|
installRunScript()
|
||||||
|
bootstrapNetwork()
|
||||||
|
nodes.forEach(Node::build)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs the corda fat JAR to the root directory, for the network bootstrapper to use.
|
||||||
|
*/
|
||||||
|
private fun installCordaJar() {
|
||||||
|
val cordaJar = Cordformation.verifyAndGetRuntimeJar(project, "corda")
|
||||||
|
project.copy {
|
||||||
|
it.apply {
|
||||||
|
from(cordaJar)
|
||||||
|
into(directory)
|
||||||
|
rename(cordaJar.name, nodeJarName)
|
||||||
|
fileMode = Cordformation.executableFileMode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initializeConfiguration() {
|
||||||
|
if (definitionClass != null) {
|
||||||
|
val cd = loadCordformDefinition()
|
||||||
|
// If the user has specified their own directory (even if it's the same default path) then let them know
|
||||||
|
// it's not used and should just rely on the one in CordformDefinition
|
||||||
|
require(directory === defaultDirectory) {
|
||||||
|
"'directory' cannot be used when 'definitionClass' is specified. Use CordformDefinition.nodesDirectory instead."
|
||||||
|
}
|
||||||
|
directory = cd.nodesDirectory
|
||||||
|
val cordapps = cd.getMatchingCordapps()
|
||||||
|
cd.nodeConfigurers.forEach {
|
||||||
|
val node = node { }
|
||||||
|
it.accept(node)
|
||||||
|
node.additionalCordapps.addAll(cordapps)
|
||||||
|
node.rootDir(directory)
|
||||||
|
}
|
||||||
|
cd.setup { nodeName -> project.projectDir.toPath().resolve(getNodeByName(nodeName)!!.nodeDir.toPath()) }
|
||||||
|
} else {
|
||||||
|
nodes.forEach {
|
||||||
|
it.rootDir(directory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bootstrapNetwork() {
|
||||||
|
val networkBootstrapperClass = loadNetworkBootstrapperClass()
|
||||||
|
val networkBootstrapper = networkBootstrapperClass.newInstance()
|
||||||
|
val bootstrapMethod = networkBootstrapperClass.getMethod("bootstrap", Path::class.java).apply { isAccessible = true }
|
||||||
|
// Call NetworkBootstrapper.bootstrap
|
||||||
|
try {
|
||||||
|
val rootDir = project.projectDir.toPath().resolve(directory).toAbsolutePath().normalize()
|
||||||
|
bootstrapMethod.invoke(networkBootstrapper, rootDir)
|
||||||
|
} catch (e: InvocationTargetException) {
|
||||||
|
throw e.cause!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun CordformDefinition.getMatchingCordapps(): List<File> {
|
||||||
|
val cordappJars = project.configuration("cordapp").files
|
||||||
|
return cordappPackages.map { `package` ->
|
||||||
|
val cordappsWithPackage = cordappJars.filter { it.containsPackage(`package`) }
|
||||||
|
when (cordappsWithPackage.size) {
|
||||||
|
0 -> throw IllegalArgumentException("There are no cordapp dependencies containing the package $`package`")
|
||||||
|
1 -> cordappsWithPackage[0]
|
||||||
|
else -> throw IllegalArgumentException("More than one cordapp dependency contains the package $`package`: $cordappsWithPackage")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun File.containsPackage(`package`: String): Boolean {
|
||||||
|
JarInputStream(inputStream()).use {
|
||||||
|
while (true) {
|
||||||
|
val name = it.nextJarEntry?.name ?: break
|
||||||
|
if (name.endsWith(".class") && name.replace('/', '.').startsWith(`package`)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
package net.corda.plugins
|
||||||
|
|
||||||
|
import org.gradle.api.Plugin
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Cordformation plugin deploys nodes to a directory in a state ready to be used by a developer for experimentation,
|
||||||
|
* testing, and debugging. It will prepopulate several fields in the configuration and create a simple node runner.
|
||||||
|
*/
|
||||||
|
class Cordformation : Plugin<Project> {
|
||||||
|
internal companion object {
|
||||||
|
const val CORDFORMATION_TYPE = "cordformationInternal"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a resource file from this plugin's JAR file.
|
||||||
|
*
|
||||||
|
* @param project The project environment this plugin executes in.
|
||||||
|
* @param filePathInJar The file in the JAR, relative to root, you wish to access.
|
||||||
|
* @return A file handle to the file in the JAR.
|
||||||
|
*/
|
||||||
|
fun getPluginFile(project: Project, filePathInJar: String): File {
|
||||||
|
val archive = project.rootProject.buildscript.configurations
|
||||||
|
.single { it.name == "classpath" }
|
||||||
|
.first { it.name.contains("cordformation") }
|
||||||
|
return project.rootProject.resources.text
|
||||||
|
.fromArchiveEntry(archive, filePathInJar)
|
||||||
|
.asFile()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a current built corda jar file
|
||||||
|
*
|
||||||
|
* @param project The project environment this plugin executes in.
|
||||||
|
* @param jarName The name of the JAR you wish to access.
|
||||||
|
* @return A file handle to the file in the JAR.
|
||||||
|
*/
|
||||||
|
fun verifyAndGetRuntimeJar(project: Project, jarName: String): File {
|
||||||
|
val releaseVersion = project.rootProject.ext<String>("corda_release_version")
|
||||||
|
val maybeJar = project.configuration("runtime").filter {
|
||||||
|
"$jarName-$releaseVersion.jar" in it.toString() || "$jarName-r3-$releaseVersion.jar" in it.toString()
|
||||||
|
}
|
||||||
|
if (maybeJar.isEmpty) {
|
||||||
|
throw IllegalStateException("No $jarName JAR found. Have you deployed the Corda project to Maven? Looked for \"$jarName-$releaseVersion.jar\"")
|
||||||
|
} else {
|
||||||
|
val jar = maybeJar.singleFile
|
||||||
|
require(jar.isFile)
|
||||||
|
return jar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val executableFileMode = "0755".toInt(8)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun apply(project: Project) {
|
||||||
|
Utils.createCompileConfiguration("cordapp", project)
|
||||||
|
Utils.createRuntimeConfiguration(CORDFORMATION_TYPE, project)
|
||||||
|
// TODO: improve how we re-use existing declared external variables from root gradle.build
|
||||||
|
val jolokiaVersion = try { project.rootProject.ext<String>("jolokia_version") } catch (e: Exception) { "1.3.7" }
|
||||||
|
project.dependencies.add(CORDFORMATION_TYPE, "org.jolokia:jolokia-jvm:$jolokiaVersion:agent")
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,239 @@
|
|||||||
|
package net.corda.plugins
|
||||||
|
|
||||||
|
import com.typesafe.config.ConfigFactory
|
||||||
|
import com.typesafe.config.ConfigRenderOptions
|
||||||
|
import com.typesafe.config.ConfigValueFactory
|
||||||
|
import groovy.lang.Closure
|
||||||
|
import net.corda.cordform.CordformNode
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import java.io.File
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a node that will be installed.
|
||||||
|
*/
|
||||||
|
class Node(private val project: Project) : CordformNode() {
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
val webJarName = "corda-webserver.jar"
|
||||||
|
private val configFileProperty = "configFile"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the list of CorDapps to install to the plugins directory. Each cordapp is a fully qualified Maven
|
||||||
|
* dependency name, eg: com.example:product-name:0.1
|
||||||
|
*
|
||||||
|
* @note Your app will be installed by default and does not need to be included here.
|
||||||
|
* @note Type is any due to gradle's use of "GStrings" - each value will have "toString" called on it
|
||||||
|
*/
|
||||||
|
var cordapps = mutableListOf<Any>()
|
||||||
|
internal var additionalCordapps = mutableListOf<File>()
|
||||||
|
internal lateinit var nodeDir: File
|
||||||
|
private set
|
||||||
|
internal lateinit var rootDir: File
|
||||||
|
private set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether this node will use HTTPS communication.
|
||||||
|
*
|
||||||
|
* @param isHttps True if this node uses HTTPS communication.
|
||||||
|
*/
|
||||||
|
fun https(isHttps: Boolean) {
|
||||||
|
config = config.withValue("useHTTPS", ConfigValueFactory.fromAnyRef(isHttps))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the H2 port for this node
|
||||||
|
*/
|
||||||
|
fun h2Port(h2Port: Int) {
|
||||||
|
config = config.withValue("h2port", ConfigValueFactory.fromAnyRef(h2Port))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun useTestClock(useTestClock: Boolean) {
|
||||||
|
config = config.withValue("useTestClock", ConfigValueFactory.fromAnyRef(useTestClock))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies RPC settings for the node.
|
||||||
|
*/
|
||||||
|
fun rpcSettings(configureClosure: Closure<in RpcSettings>) {
|
||||||
|
val rpcSettings = project.configure(RpcSettings(project), configureClosure) as RpcSettings
|
||||||
|
config = rpcSettings.addTo("rpcSettings", config)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables SSH access on given port
|
||||||
|
*
|
||||||
|
* @param sshdPort The port for SSH server to listen on
|
||||||
|
*/
|
||||||
|
fun sshdPort(sshdPort: Int?) {
|
||||||
|
config = config.withValue("sshd.port", ConfigValueFactory.fromAnyRef(sshdPort))
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun build() {
|
||||||
|
if (config.hasPath("webAddress")) {
|
||||||
|
installWebserverJar()
|
||||||
|
}
|
||||||
|
installAgentJar()
|
||||||
|
installBuiltCordapp()
|
||||||
|
installCordapps()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun rootDir(rootDir: Path) {
|
||||||
|
if (name == null) {
|
||||||
|
project.logger.error("Node has a null name - cannot create node")
|
||||||
|
throw IllegalStateException("Node has a null name - cannot create node")
|
||||||
|
}
|
||||||
|
// Parsing O= part directly because importing BouncyCastle provider in Cordformation causes problems
|
||||||
|
// with loading our custom X509EdDSAEngine.
|
||||||
|
val organizationName = name.trim().split(",").firstOrNull { it.startsWith("O=") }?.substringAfter("=")
|
||||||
|
val dirName = organizationName ?: name
|
||||||
|
this.rootDir = rootDir.toFile()
|
||||||
|
nodeDir = File(this.rootDir, dirName.replace("\\s", ""))
|
||||||
|
Files.createDirectories(nodeDir.toPath())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun configureProperties() {
|
||||||
|
config = config.withValue("database.runMigration", ConfigValueFactory.fromAnyRef(true))
|
||||||
|
config = config.withValue("rpcUsers", ConfigValueFactory.fromIterable(rpcUsers))
|
||||||
|
if (notary != null) {
|
||||||
|
config = config.withValue("notary", ConfigValueFactory.fromMap(notary))
|
||||||
|
}
|
||||||
|
if (extraConfig != null) {
|
||||||
|
config = config.withFallback(ConfigFactory.parseMap(extraConfig))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs the corda webserver JAR to the node directory
|
||||||
|
*/
|
||||||
|
private fun installWebserverJar() {
|
||||||
|
val webJar = Cordformation.verifyAndGetRuntimeJar(project, "corda-webserver")
|
||||||
|
project.copy {
|
||||||
|
it.apply {
|
||||||
|
from(webJar)
|
||||||
|
into(nodeDir)
|
||||||
|
rename(webJar.name, webJarName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs this project's cordapp to this directory.
|
||||||
|
*/
|
||||||
|
private fun installBuiltCordapp() {
|
||||||
|
val cordappsDir = File(nodeDir, "cordapps")
|
||||||
|
project.copy {
|
||||||
|
it.apply {
|
||||||
|
from(project.tasks.getByName("jar"))
|
||||||
|
into(cordappsDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs the jolokia monitoring agent JAR to the node/drivers directory
|
||||||
|
*/
|
||||||
|
private fun installAgentJar() {
|
||||||
|
// TODO: improve how we re-use existing declared external variables from root gradle.build
|
||||||
|
val jolokiaVersion = try { project.rootProject.ext<String>("jolokia_version") } catch (e: Exception) { "1.3.7" }
|
||||||
|
val agentJar = project.configuration("runtime").files {
|
||||||
|
(it.group == "org.jolokia") &&
|
||||||
|
(it.name == "jolokia-jvm") &&
|
||||||
|
(it.version == jolokiaVersion)
|
||||||
|
// TODO: revisit when classifier attribute is added. eg && (it.classifier = "agent")
|
||||||
|
}.first() // should always be the jolokia agent fat jar: eg. jolokia-jvm-1.3.7-agent.jar
|
||||||
|
project.logger.info("Jolokia agent jar: $agentJar")
|
||||||
|
if (agentJar.isFile) {
|
||||||
|
val driversDir = File(nodeDir, "drivers")
|
||||||
|
project.copy {
|
||||||
|
it.apply {
|
||||||
|
from(agentJar)
|
||||||
|
into(driversDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createTempConfigFile(): File {
|
||||||
|
val options = ConfigRenderOptions
|
||||||
|
.defaults()
|
||||||
|
.setOriginComments(false)
|
||||||
|
.setComments(false)
|
||||||
|
.setFormatted(true)
|
||||||
|
.setJson(false)
|
||||||
|
val configFileText = config.root().render(options).split("\n").toList()
|
||||||
|
// Need to write a temporary file first to use the project.copy, which resolves directories correctly.
|
||||||
|
val tmpDir = File(project.buildDir, "tmp")
|
||||||
|
Files.createDirectories(tmpDir.toPath())
|
||||||
|
var fileName = "${nodeDir.name}.conf"
|
||||||
|
val tmpConfFile = File(tmpDir, fileName)
|
||||||
|
Files.write(tmpConfFile.toPath(), configFileText, StandardCharsets.UTF_8)
|
||||||
|
return tmpConfFile
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs the configuration file to the root directory and detokenises it.
|
||||||
|
*/
|
||||||
|
internal fun installConfig() {
|
||||||
|
configureProperties()
|
||||||
|
val tmpConfFile = createTempConfigFile()
|
||||||
|
appendOptionalConfig(tmpConfFile)
|
||||||
|
project.copy {
|
||||||
|
it.apply {
|
||||||
|
from(tmpConfFile)
|
||||||
|
into(rootDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends installed config file with properties from an optional file.
|
||||||
|
*/
|
||||||
|
private fun appendOptionalConfig(confFile: File) {
|
||||||
|
val optionalConfig: File? = when {
|
||||||
|
project.findProperty(configFileProperty) != null -> //provided by -PconfigFile command line property when running Gradle task
|
||||||
|
File(project.findProperty(configFileProperty) as String)
|
||||||
|
config.hasPath(configFileProperty) -> File(config.getString(configFileProperty))
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (optionalConfig != null) {
|
||||||
|
if (!optionalConfig.exists()) {
|
||||||
|
project.logger.error("$configFileProperty '$optionalConfig' not found")
|
||||||
|
} else {
|
||||||
|
confFile.appendBytes(optionalConfig.readBytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs other cordapps to this node's cordapps directory.
|
||||||
|
*/
|
||||||
|
internal fun installCordapps() {
|
||||||
|
additionalCordapps.addAll(getCordappList())
|
||||||
|
val cordappsDir = File(nodeDir, "cordapps")
|
||||||
|
project.copy {
|
||||||
|
it.apply {
|
||||||
|
from(additionalCordapps)
|
||||||
|
into(cordappsDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of cordapps based on what dependent cordapps were specified.
|
||||||
|
*
|
||||||
|
* @return List of this node's cordapps.
|
||||||
|
*/
|
||||||
|
private fun getCordappList(): Collection<File> {
|
||||||
|
// Cordapps can sometimes contain a GString instance which fails the equality test with the Java string
|
||||||
|
@Suppress("RemoveRedundantCallsOfConversionMethods")
|
||||||
|
val cordapps: List<String> = cordapps.map { it.toString() }
|
||||||
|
return project.configuration("cordapp").files {
|
||||||
|
cordapps.contains(it.group + ":" + it.name + ":" + it.version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
package net.corda.plugins
|
||||||
|
|
||||||
|
import com.typesafe.config.Config
|
||||||
|
import com.typesafe.config.ConfigFactory
|
||||||
|
import com.typesafe.config.ConfigValueFactory
|
||||||
|
import groovy.lang.Closure
|
||||||
|
import org.gradle.api.Project
|
||||||
|
|
||||||
|
class RpcSettings(private val project: Project) {
|
||||||
|
private var config: Config = ConfigFactory.empty()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RPC address for the node.
|
||||||
|
*/
|
||||||
|
fun address(value: String) {
|
||||||
|
config += "address" to value
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RPC admin address for the node (necessary if [useSsl] is false or unset).
|
||||||
|
*/
|
||||||
|
fun adminAddress(value: String) {
|
||||||
|
config += "adminAddress" to value
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies whether the node RPC layer will require SSL from clients.
|
||||||
|
*/
|
||||||
|
fun useSsl(value: Boolean) {
|
||||||
|
config += "useSsl" to value
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies whether the RPC broker is separate from the node.
|
||||||
|
*/
|
||||||
|
fun standAloneBroker(value: Boolean) {
|
||||||
|
config += "standAloneBroker" to value
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies SSL certificates options for the RPC layer.
|
||||||
|
*/
|
||||||
|
fun ssl(configureClosure: Closure<in SslOptions>) {
|
||||||
|
val sslOptions = project.configure(SslOptions(), configureClosure) as SslOptions
|
||||||
|
config = sslOptions.addTo("ssl", config)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun addTo(key: String, config: Config): Config {
|
||||||
|
if (this.config.isEmpty) {
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
return config + (key to this.config.root())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal operator fun Config.plus(entry: Pair<String, Any>): Config {
|
||||||
|
|
||||||
|
return withValue(entry.first, ConfigValueFactory.fromAnyRef(entry.second))
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package net.corda.plugins
|
||||||
|
|
||||||
|
import com.typesafe.config.Config
|
||||||
|
import com.typesafe.config.ConfigFactory
|
||||||
|
|
||||||
|
class SslOptions {
|
||||||
|
private var config: Config = ConfigFactory.empty()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Password for the keystore.
|
||||||
|
*/
|
||||||
|
fun keyStorePassword(value: String) {
|
||||||
|
config += "keyStorePassword" to value
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Password for the truststore.
|
||||||
|
*/
|
||||||
|
fun trustStorePassword(value: String) {
|
||||||
|
config += "trustStorePassword" to value
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Directory under which key stores are to be placed.
|
||||||
|
*/
|
||||||
|
fun certificatesDirectory(value: String) {
|
||||||
|
config += "certificatesDirectory" to value
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Absolute path to SSL keystore. Default: "[certificatesDirectory]/sslkeystore.jks"
|
||||||
|
*/
|
||||||
|
fun sslKeystore(value: String) {
|
||||||
|
config += "sslKeystore" to value
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Absolute path to SSL truststore. Default: "[certificatesDirectory]/truststore.jks"
|
||||||
|
*/
|
||||||
|
fun trustStoreFile(value: String) {
|
||||||
|
config += "trustStoreFile" to value
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun addTo(key: String, config: Config): Config {
|
||||||
|
if (this.config.isEmpty) {
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
return config + (key to this.config.root())
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
implementation-class=net.corda.plugins.Cordformation
|
@ -0,0 +1,13 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Allow the script to be run from outside the nodes directory.
|
||||||
|
basedir=$( dirname "$0" )
|
||||||
|
cd "$basedir"
|
||||||
|
|
||||||
|
if which osascript >/dev/null; then
|
||||||
|
/usr/libexec/java_home -v 1.8 --exec java -jar runnodes.jar "$@"
|
||||||
|
else
|
||||||
|
"${JAVA_HOME:+$JAVA_HOME/bin/}java" -jar runnodes.jar "$@"
|
||||||
|
fi
|
@ -0,0 +1,8 @@
|
|||||||
|
@echo off
|
||||||
|
|
||||||
|
REM Change to the directory of this script (%~dp0)
|
||||||
|
Pushd %~dp0
|
||||||
|
|
||||||
|
java -jar runnodes.jar %*
|
||||||
|
|
||||||
|
Popd
|
@ -0,0 +1,151 @@
|
|||||||
|
package net.corda.plugins
|
||||||
|
|
||||||
|
import java.awt.GraphicsEnvironment
|
||||||
|
import java.io.File
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
private val HEADLESS_FLAG = "--headless"
|
||||||
|
private val CAPSULE_DEBUG_FLAG = "--capsule-debug"
|
||||||
|
|
||||||
|
private val os by lazy {
|
||||||
|
val osName = System.getProperty("os.name", "generic").toLowerCase(Locale.ENGLISH)
|
||||||
|
if ("mac" in osName || "darwin" in osName) OS.MACOS
|
||||||
|
else if ("win" in osName) OS.WINDOWS
|
||||||
|
else OS.LINUX
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum class OS { MACOS, WINDOWS, LINUX }
|
||||||
|
|
||||||
|
private object debugPortAlloc {
|
||||||
|
private var basePort = 5005
|
||||||
|
internal fun next() = basePort++
|
||||||
|
}
|
||||||
|
|
||||||
|
private object monitoringPortAlloc {
|
||||||
|
private var basePort = 7005
|
||||||
|
internal fun next() = basePort++
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
val startedProcesses = mutableListOf<Process>()
|
||||||
|
val headless = GraphicsEnvironment.isHeadless() || args.contains(HEADLESS_FLAG)
|
||||||
|
val capsuleDebugMode = args.contains(CAPSULE_DEBUG_FLAG)
|
||||||
|
val workingDir = File(System.getProperty("user.dir"))
|
||||||
|
val javaArgs = args.filter { it != HEADLESS_FLAG && it != CAPSULE_DEBUG_FLAG }
|
||||||
|
val jvmArgs = if (capsuleDebugMode) listOf("-Dcapsule.log=verbose") else emptyList<String>()
|
||||||
|
println("Starting nodes in $workingDir")
|
||||||
|
workingDir.listFiles { file -> file.isDirectory }.forEach { dir ->
|
||||||
|
listOf(NodeJarType, WebJarType).forEach { jarType ->
|
||||||
|
jarType.acceptDirAndStartProcess(dir, headless, javaArgs, jvmArgs)?.let { startedProcesses += it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println("Started ${startedProcesses.size} processes")
|
||||||
|
println("Finished starting nodes")
|
||||||
|
}
|
||||||
|
|
||||||
|
private abstract class JarType(private val jarName: String) {
|
||||||
|
internal abstract fun acceptNodeConf(nodeConf: File): Boolean
|
||||||
|
internal fun acceptDirAndStartProcess(dir: File, headless: Boolean, javaArgs: List<String>, jvmArgs: List<String>): Process? {
|
||||||
|
if (!File(dir, jarName).exists()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (!File(dir, "node.conf").let { it.exists() && acceptNodeConf(it) }) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val debugPort = debugPortAlloc.next()
|
||||||
|
val monitoringPort = monitoringPortAlloc.next()
|
||||||
|
println("Starting $jarName in $dir on debug port $debugPort")
|
||||||
|
val process = (if (headless) ::HeadlessJavaCommand else ::TerminalWindowJavaCommand)(jarName, dir, debugPort, monitoringPort, javaArgs, jvmArgs).start()
|
||||||
|
if (os == OS.MACOS) Thread.sleep(1000)
|
||||||
|
return process
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private object NodeJarType : JarType("corda.jar") {
|
||||||
|
override fun acceptNodeConf(nodeConf: File) = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private object WebJarType : JarType("corda-webserver.jar") {
|
||||||
|
// TODO: Add a webserver.conf, or use TypeSafe config instead of this hack
|
||||||
|
override fun acceptNodeConf(nodeConf: File) = Files.lines(nodeConf.toPath()).anyMatch { "webAddress" in it }
|
||||||
|
}
|
||||||
|
|
||||||
|
private abstract class JavaCommand(
|
||||||
|
jarName: String,
|
||||||
|
internal val dir: File,
|
||||||
|
debugPort: Int?,
|
||||||
|
monitoringPort: Int?,
|
||||||
|
internal val nodeName: String,
|
||||||
|
init: MutableList<String>.() -> Unit, args: List<String>,
|
||||||
|
jvmArgs: List<String>
|
||||||
|
) {
|
||||||
|
private val jolokiaJar by lazy {
|
||||||
|
File("$dir/drivers").listFiles { _, filename ->
|
||||||
|
filename.matches("jolokia-jvm-.*-agent\\.jar$".toRegex())
|
||||||
|
}.first().name
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val command: List<String> = mutableListOf<String>().apply {
|
||||||
|
add(getJavaPath())
|
||||||
|
addAll(jvmArgs)
|
||||||
|
add("-Dname=$nodeName")
|
||||||
|
val jvmArgs: MutableList<String> = mutableListOf()
|
||||||
|
null != debugPort && jvmArgs.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=$debugPort")
|
||||||
|
null != monitoringPort && jvmArgs.add("-javaagent:drivers/$jolokiaJar=port=$monitoringPort")
|
||||||
|
if (jvmArgs.isNotEmpty()) {
|
||||||
|
add("-Dcapsule.jvm.args=${jvmArgs.joinToString(separator = " ")}")
|
||||||
|
}
|
||||||
|
add("-jar")
|
||||||
|
add(jarName)
|
||||||
|
init()
|
||||||
|
addAll(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal abstract fun processBuilder(): ProcessBuilder
|
||||||
|
internal fun start() = processBuilder().directory(dir).start()
|
||||||
|
internal abstract fun getJavaPath(): String
|
||||||
|
}
|
||||||
|
|
||||||
|
private class HeadlessJavaCommand(jarName: String, dir: File, debugPort: Int?, monitoringPort: Int?, args: List<String>, jvmArgs: List<String>)
|
||||||
|
: JavaCommand(jarName, dir, debugPort, monitoringPort, dir.name, { add("--no-local-shell") }, args, jvmArgs) {
|
||||||
|
override fun processBuilder() = ProcessBuilder(command).redirectError(File("error.$nodeName.log")).inheritIO()
|
||||||
|
override fun getJavaPath() = File(File(System.getProperty("java.home"), "bin"), "java").path
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TerminalWindowJavaCommand(jarName: String, dir: File, debugPort: Int?, monitoringPort: Int?, args: List<String>, jvmArgs: List<String>)
|
||||||
|
: JavaCommand(jarName, dir, debugPort, monitoringPort, "${dir.name}-$jarName", {}, args, jvmArgs) {
|
||||||
|
override fun processBuilder() = ProcessBuilder(when (os) {
|
||||||
|
OS.MACOS -> {
|
||||||
|
listOf("osascript", "-e", """tell app "Terminal"
|
||||||
|
activate
|
||||||
|
delay 0.5
|
||||||
|
tell app "System Events" to tell process "Terminal" to keystroke "t" using command down
|
||||||
|
delay 0.5
|
||||||
|
do script "bash -c 'cd \"$dir\" ; \"${command.joinToString("""\" \"""")}\" && exit'" in selected tab of the front window
|
||||||
|
end tell""")
|
||||||
|
}
|
||||||
|
OS.WINDOWS -> {
|
||||||
|
listOf("cmd", "/C", "start ${command.joinToString(" ") { windowsSpaceEscape(it) }}")
|
||||||
|
}
|
||||||
|
OS.LINUX -> {
|
||||||
|
// Start shell to keep window open unless java terminated normally or due to SIGTERM:
|
||||||
|
val command = "${unixCommand()}; [ $? -eq 0 -o $? -eq 143 ] || sh"
|
||||||
|
if (isTmux()) {
|
||||||
|
listOf("tmux", "new-window", "-n", nodeName, command)
|
||||||
|
} else {
|
||||||
|
listOf("xterm", "-T", nodeName, "-e", command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
private fun unixCommand() = command.map(::quotedFormOf).joinToString(" ")
|
||||||
|
override fun getJavaPath(): String = File(File(System.getProperty("java.home"), "bin"), "java").path
|
||||||
|
|
||||||
|
// Replace below is to fix an issue with spaces in paths on Windows.
|
||||||
|
// Quoting the entire path does not work, only the space or directory within the path.
|
||||||
|
private fun windowsSpaceEscape(s:String) = s.replace(" ", "\" \"")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun quotedFormOf(text: String) = "'${text.replace("'", "'\\''")}'" // Suitable for UNIX shells.
|
||||||
|
private fun isTmux() = System.getenv("TMUX")?.isNotEmpty() ?: false
|
92
gradle-plugins/publish-utils/README.rst
Normal file
92
gradle-plugins/publish-utils/README.rst
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
Publish Utils
|
||||||
|
=============
|
||||||
|
|
||||||
|
Publishing utilities adds a couple of tasks to any project it is applied to that hide some boilerplate that would
|
||||||
|
otherwise be placed in the Cordapp template's build.gradle.
|
||||||
|
|
||||||
|
There are two tasks exposed: `sourceJar` and `javadocJar` and both return a `FileCollection`.
|
||||||
|
|
||||||
|
It is used within the `publishing` block of a build.gradle as such;
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
// This will publish the sources, javadoc, and Java components to Maven.
|
||||||
|
// See the `maven-publish` plugin for more info: https://docs.gradle.org/current/userguide/publishing_maven.html
|
||||||
|
publishing {
|
||||||
|
publications {
|
||||||
|
jarAndSources(MavenPublication) {
|
||||||
|
from components.java
|
||||||
|
// The two lines below are the tasks added by this plugin.
|
||||||
|
artifact sourceJar
|
||||||
|
artifact javadocJar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Bintray Publishing
|
||||||
|
------------------
|
||||||
|
|
||||||
|
For large multibuild projects it can be inconvenient to store the entire configuration for bintray and maven central
|
||||||
|
per project (with a bintray and publishing block with extended POM information). Publish utils can bring the number of
|
||||||
|
configuration blocks down to one in the ideal scenario.
|
||||||
|
|
||||||
|
To use this plugin you must first apply it to both the root project and any project that will be published with
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
apply plugin: 'net.corda.plugins.publish-utils'
|
||||||
|
|
||||||
|
Next you must setup the general bintray configuration you wish to use project wide, for example:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
bintrayConfig {
|
||||||
|
user = <your bintray username>
|
||||||
|
key = <your bintray user key>
|
||||||
|
repo = 'example repo'
|
||||||
|
org = 'example organisation'
|
||||||
|
licenses = ['a license']
|
||||||
|
vcsUrl = 'https://example.com'
|
||||||
|
projectUrl = 'https://example.com'
|
||||||
|
gpgSign = true // Whether to GPG sign
|
||||||
|
gpgPassphrase = <your bintray GPG key passphrase> // Only required if gpgSign is true and your key is passworded
|
||||||
|
publications = ['example'] // a list of publications (see below)
|
||||||
|
license {
|
||||||
|
name = 'example'
|
||||||
|
url = 'https://example.com'
|
||||||
|
distribution = 'repo'
|
||||||
|
}
|
||||||
|
developer {
|
||||||
|
id = 'a developer id'
|
||||||
|
name = 'a developer name'
|
||||||
|
email = 'example@example.com'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.. note:: You can currently only have one license and developer in the maven POM sections
|
||||||
|
|
||||||
|
**Publications**
|
||||||
|
|
||||||
|
This plugin assumes, by default, that publications match the name of the project. This means, by default, you can
|
||||||
|
just list the names of the projects you wish to publish (e.g. to publish `test:myapp` you need `publications = ['myapp']`.
|
||||||
|
If a project requires a different name you can configure it *per project* with the project configuration block.
|
||||||
|
|
||||||
|
The project configuration block has the following structure:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
publish {
|
||||||
|
disableDefaultJar = false // set to true to disable the default JAR being created (e.g. when creating a fat JAR)
|
||||||
|
name 'non-default-project-name' // Always put this last because it causes configuration to happen
|
||||||
|
}
|
||||||
|
|
||||||
|
**Artifacts**
|
||||||
|
|
||||||
|
To add additional artifacts to the project you can use the default gradle `artifacts` block with the `publish`
|
||||||
|
configuration. For example:
|
||||||
|
|
||||||
|
artifacts {
|
||||||
|
publish buildFatJar {
|
||||||
|
// You can configure this as a regular maven publication
|
||||||
|
}
|
||||||
|
}
|
109
gradle-plugins/publish-utils/build.gradle
Normal file
109
gradle-plugins/publish-utils/build.gradle
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
apply plugin: 'groovy'
|
||||||
|
apply plugin: 'maven-publish'
|
||||||
|
apply plugin: 'com.jfrog.bintray'
|
||||||
|
apply plugin: 'com.jfrog.artifactory'
|
||||||
|
|
||||||
|
// Used for bootstrapping project
|
||||||
|
buildscript {
|
||||||
|
Properties constants = new Properties()
|
||||||
|
file("../../constants.properties").withInputStream { constants.load(it) }
|
||||||
|
|
||||||
|
ext {
|
||||||
|
gradle_plugins_version = constants.getProperty("gradlePluginsVersion")
|
||||||
|
artifactory_plugin_version = constants.getProperty('artifactoryPluginVersion')
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.4'
|
||||||
|
classpath "org.jfrog.buildinfo:build-info-extractor-gradle:$artifactory_plugin_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
version "$gradle_plugins_version"
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile gradleApi()
|
||||||
|
compile localGroovy()
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
task("sourceJar", type: Jar, dependsOn: classes) {
|
||||||
|
classifier = 'sources'
|
||||||
|
from sourceSets.main.allSource
|
||||||
|
}
|
||||||
|
|
||||||
|
task("javadocJar", type: Jar, dependsOn: javadoc) {
|
||||||
|
classifier = 'javadoc'
|
||||||
|
from javadoc.destinationDir
|
||||||
|
}
|
||||||
|
|
||||||
|
bintray {
|
||||||
|
user = System.getenv('CORDA_BINTRAY_USER')
|
||||||
|
key = System.getenv('CORDA_BINTRAY_KEY')
|
||||||
|
publications = ['publishUtils']
|
||||||
|
dryRun = false
|
||||||
|
pkg {
|
||||||
|
repo = 'corda'
|
||||||
|
name = 'publish-utils'
|
||||||
|
userOrg = 'r3'
|
||||||
|
licenses = ['Apache-2.0']
|
||||||
|
|
||||||
|
version {
|
||||||
|
gpg {
|
||||||
|
sign = true
|
||||||
|
passphrase = System.getenv('CORDA_BINTRAY_GPG_PASSPHRASE')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
publishing {
|
||||||
|
publications {
|
||||||
|
publishUtils(MavenPublication) {
|
||||||
|
from components.java
|
||||||
|
groupId 'net.corda.plugins'
|
||||||
|
artifactId 'publish-utils'
|
||||||
|
|
||||||
|
artifact sourceJar
|
||||||
|
artifact javadocJar
|
||||||
|
|
||||||
|
pom.withXml {
|
||||||
|
asNode().children().last() + {
|
||||||
|
resolveStrategy = Closure.DELEGATE_FIRST
|
||||||
|
name 'publish-utils'
|
||||||
|
description 'A small gradle plugin that adds a couple of convenience functions for publishing to Maven'
|
||||||
|
url 'https://github.com/corda/corda'
|
||||||
|
scm {
|
||||||
|
url 'https://github.com/corda/corda'
|
||||||
|
}
|
||||||
|
|
||||||
|
licenses {
|
||||||
|
license {
|
||||||
|
name 'Apache-2.0'
|
||||||
|
url 'https://www.apache.org/licenses/LICENSE-2.0'
|
||||||
|
distribution 'repo'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
developers {
|
||||||
|
developer {
|
||||||
|
id 'R3'
|
||||||
|
name 'R3'
|
||||||
|
email 'dev@corda.net'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aliasing the publishToMavenLocal for simplicity.
|
||||||
|
task(install, dependsOn: 'publishToMavenLocal')
|
@ -0,0 +1,39 @@
|
|||||||
|
package net.corda.plugins
|
||||||
|
|
||||||
|
class ProjectPublishExtension {
|
||||||
|
private PublishTasks task
|
||||||
|
|
||||||
|
void setPublishTask(PublishTasks task) {
|
||||||
|
this.task = task
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use a different name from the current project name for publishing.
|
||||||
|
* Set this after all other settings that need to be configured
|
||||||
|
*/
|
||||||
|
void name(String name) {
|
||||||
|
task.setPublishName(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the publishing name for this project.
|
||||||
|
*/
|
||||||
|
String name() {
|
||||||
|
return task.getPublishName()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True when we do not want to publish default Java components
|
||||||
|
*/
|
||||||
|
Boolean disableDefaultJar = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if publishing a WAR instead of a JAR. Forces disableDefaultJAR to "true" when true
|
||||||
|
*/
|
||||||
|
Boolean publishWar = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if publishing sources to remote repositories
|
||||||
|
*/
|
||||||
|
Boolean publishSources = true
|
||||||
|
}
|
@ -0,0 +1,161 @@
|
|||||||
|
package net.corda.plugins
|
||||||
|
|
||||||
|
import org.gradle.api.*
|
||||||
|
import org.gradle.api.tasks.bundling.Jar
|
||||||
|
import org.gradle.api.tasks.javadoc.Javadoc
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.api.publish.maven.MavenPublication
|
||||||
|
import org.gradle.api.publish.maven.MavenPom
|
||||||
|
import net.corda.plugins.bintray.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A utility plugin that when applied will automatically create source and javadoc publishing tasks
|
||||||
|
* To apply this plugin you must also add 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.4' to your
|
||||||
|
* buildscript's classpath dependencies.
|
||||||
|
*
|
||||||
|
* To use this plugin you can add a new configuration block (extension) to your root build.gradle. See the fields
|
||||||
|
* in BintrayConfigExtension.
|
||||||
|
*/
|
||||||
|
class PublishTasks implements Plugin<Project> {
|
||||||
|
Project project
|
||||||
|
String publishName
|
||||||
|
ProjectPublishExtension publishConfig
|
||||||
|
|
||||||
|
void apply(Project project) {
|
||||||
|
this.project = project
|
||||||
|
this.publishName = project.name
|
||||||
|
|
||||||
|
createTasks()
|
||||||
|
createExtensions()
|
||||||
|
createConfigurations()
|
||||||
|
}
|
||||||
|
|
||||||
|
void setPublishName(String publishName) {
|
||||||
|
project.logger.info("Changing publishing name from ${project.name} to ${publishName}")
|
||||||
|
this.publishName = publishName
|
||||||
|
checkAndConfigurePublishing()
|
||||||
|
}
|
||||||
|
|
||||||
|
void checkAndConfigurePublishing() {
|
||||||
|
project.logger.info("Checking whether to publish $publishName")
|
||||||
|
def bintrayConfig = project.rootProject.extensions.findByType(BintrayConfigExtension.class)
|
||||||
|
if((bintrayConfig != null) && (bintrayConfig.publications) && (bintrayConfig.publications.findAll { it == publishName }.size() > 0)) {
|
||||||
|
configurePublishing(bintrayConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void configurePublishing(BintrayConfigExtension bintrayConfig) {
|
||||||
|
project.logger.info("Configuring bintray for ${publishName}")
|
||||||
|
configureMavenPublish(bintrayConfig)
|
||||||
|
configureBintray(bintrayConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
void configureMavenPublish(BintrayConfigExtension bintrayConfig) {
|
||||||
|
project.apply([plugin: 'maven-publish'])
|
||||||
|
project.publishing.publications.create(publishName, MavenPublication) {
|
||||||
|
groupId project.group
|
||||||
|
artifactId publishName
|
||||||
|
|
||||||
|
if (publishConfig.publishSources) {
|
||||||
|
artifact project.tasks.sourceJar
|
||||||
|
}
|
||||||
|
artifact project.tasks.javadocJar
|
||||||
|
|
||||||
|
project.configurations.publish.artifacts.each {
|
||||||
|
project.logger.debug("Adding artifact: $it")
|
||||||
|
delegate.artifact it
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!publishConfig.disableDefaultJar && !publishConfig.publishWar) {
|
||||||
|
from project.components.java
|
||||||
|
} else if (publishConfig.publishWar) {
|
||||||
|
from project.components.web
|
||||||
|
}
|
||||||
|
|
||||||
|
extendPomForMavenCentral(pom, bintrayConfig)
|
||||||
|
}
|
||||||
|
project.task("install", dependsOn: "publishToMavenLocal")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maven central requires all of the below fields for this to be a valid POM
|
||||||
|
void extendPomForMavenCentral(MavenPom pom, BintrayConfigExtension config) {
|
||||||
|
pom.withXml {
|
||||||
|
asNode().children().last() + {
|
||||||
|
resolveStrategy = Closure.DELEGATE_FIRST
|
||||||
|
name publishName
|
||||||
|
description project.description
|
||||||
|
url config.projectUrl
|
||||||
|
scm {
|
||||||
|
url config.vcsUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
licenses {
|
||||||
|
license {
|
||||||
|
name config.license.name
|
||||||
|
url config.license.url
|
||||||
|
distribution config.license.url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
developers {
|
||||||
|
developer {
|
||||||
|
id config.developer.id
|
||||||
|
name config.developer.name
|
||||||
|
email config.developer.email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void configureBintray(BintrayConfigExtension bintrayConfig) {
|
||||||
|
project.apply([plugin: 'com.jfrog.bintray'])
|
||||||
|
project.bintray {
|
||||||
|
user = bintrayConfig.user
|
||||||
|
key = bintrayConfig.key
|
||||||
|
publications = [ publishName ]
|
||||||
|
dryRun = bintrayConfig.dryRun ?: false
|
||||||
|
pkg {
|
||||||
|
repo = bintrayConfig.repo
|
||||||
|
name = publishName
|
||||||
|
userOrg = bintrayConfig.org
|
||||||
|
licenses = bintrayConfig.licenses
|
||||||
|
|
||||||
|
version {
|
||||||
|
gpg {
|
||||||
|
sign = bintrayConfig.gpgSign ?: false
|
||||||
|
passphrase = bintrayConfig.gpgPassphrase
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void createTasks() {
|
||||||
|
if(project.hasProperty('classes')) {
|
||||||
|
project.task("sourceJar", type: Jar, dependsOn: project.classes) {
|
||||||
|
classifier = 'sources'
|
||||||
|
from project.sourceSets.main.allSource
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(project.hasProperty('javadoc')) {
|
||||||
|
project.task("javadocJar", type: Jar, dependsOn: project.javadoc) {
|
||||||
|
classifier = 'javadoc'
|
||||||
|
from project.javadoc.destinationDir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void createExtensions() {
|
||||||
|
if(project == project.rootProject) {
|
||||||
|
project.extensions.create("bintrayConfig", BintrayConfigExtension)
|
||||||
|
}
|
||||||
|
publishConfig = project.extensions.create("publish", ProjectPublishExtension)
|
||||||
|
publishConfig.setPublishTask(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
void createConfigurations() {
|
||||||
|
project.configurations.create("publish")
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
package net.corda.plugins.bintray
|
||||||
|
|
||||||
|
import org.gradle.util.ConfigureUtil
|
||||||
|
|
||||||
|
class BintrayConfigExtension {
|
||||||
|
/**
|
||||||
|
* Bintray username
|
||||||
|
*/
|
||||||
|
String user
|
||||||
|
/**
|
||||||
|
* Bintray access key
|
||||||
|
*/
|
||||||
|
String key
|
||||||
|
/**
|
||||||
|
* Bintray repository
|
||||||
|
*/
|
||||||
|
String repo
|
||||||
|
/**
|
||||||
|
* Bintray organisation
|
||||||
|
*/
|
||||||
|
String org
|
||||||
|
/**
|
||||||
|
* Licenses for packages uploaded by this configuration
|
||||||
|
*/
|
||||||
|
String[] licenses
|
||||||
|
/**
|
||||||
|
* Whether to sign packages uploaded by this configuration
|
||||||
|
*/
|
||||||
|
Boolean gpgSign
|
||||||
|
/**
|
||||||
|
* The passphrase for the key used to sign releases.
|
||||||
|
*/
|
||||||
|
String gpgPassphrase
|
||||||
|
/**
|
||||||
|
* VCS URL
|
||||||
|
*/
|
||||||
|
String vcsUrl
|
||||||
|
/**
|
||||||
|
* Project URL
|
||||||
|
*/
|
||||||
|
String projectUrl
|
||||||
|
/**
|
||||||
|
* The publications that will be uploaded as a part of this configuration. These must match both the name on
|
||||||
|
* bintray and the gradle module name. ie; it must be "some-package" as a gradle sub-module (root project not
|
||||||
|
* supported, this extension is to improve multi-build bintray uploads). The publication must also be called
|
||||||
|
* "some-package". Only one publication can be uploaded per module (a bintray plugin restriction(.
|
||||||
|
* If any of these conditions are not met your package will not be uploaded.
|
||||||
|
*/
|
||||||
|
String[] publications
|
||||||
|
/**
|
||||||
|
* Whether to test the publication without uploading to bintray.
|
||||||
|
*/
|
||||||
|
Boolean dryRun
|
||||||
|
/**
|
||||||
|
* The license this project will use (currently limited to one)
|
||||||
|
*/
|
||||||
|
License license = new License()
|
||||||
|
/**
|
||||||
|
* The developer of this project (currently limited to one)
|
||||||
|
*/
|
||||||
|
Developer developer = new Developer()
|
||||||
|
|
||||||
|
void license(Closure closure) {
|
||||||
|
ConfigureUtil.configure(closure, license)
|
||||||
|
}
|
||||||
|
|
||||||
|
void developer(Closure closure) {
|
||||||
|
ConfigureUtil.configure(closure, developer)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package net.corda.plugins.bintray
|
||||||
|
|
||||||
|
class Developer {
|
||||||
|
/**
|
||||||
|
* A unique identifier the developer (eg; organisation ID)
|
||||||
|
*/
|
||||||
|
String id
|
||||||
|
/**
|
||||||
|
* The full name of the developer
|
||||||
|
*/
|
||||||
|
String name
|
||||||
|
/**
|
||||||
|
* An email address for contacting the developer
|
||||||
|
*/
|
||||||
|
String email
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package net.corda.plugins.bintray
|
||||||
|
|
||||||
|
class License {
|
||||||
|
/**
|
||||||
|
* The name of license (eg; Apache 2.0)
|
||||||
|
*/
|
||||||
|
String name
|
||||||
|
/**
|
||||||
|
* URL to the full license file
|
||||||
|
*/
|
||||||
|
String url
|
||||||
|
/**
|
||||||
|
* The distribution level this license corresponds to (eg: repo)
|
||||||
|
*/
|
||||||
|
String distribution
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
implementation-class=net.corda.plugins.PublishTasks
|
4
gradle-plugins/quasar-utils/README.rst
Normal file
4
gradle-plugins/quasar-utils/README.rst
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
Quasar Utils
|
||||||
|
============
|
||||||
|
|
||||||
|
Quasar utilities adds several tasks and configuration that provide a default Quasar setup and removes some boilerplate.
|
19
gradle-plugins/quasar-utils/build.gradle
Normal file
19
gradle-plugins/quasar-utils/build.gradle
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
apply plugin: 'groovy'
|
||||||
|
apply plugin: 'maven-publish'
|
||||||
|
apply plugin: 'net.corda.plugins.publish-utils'
|
||||||
|
apply plugin: 'com.jfrog.artifactory'
|
||||||
|
|
||||||
|
description 'A small gradle plugin for adding some basic Quasar tasks and configurations to reduce build.gradle bloat.'
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile gradleApi()
|
||||||
|
compile localGroovy()
|
||||||
|
}
|
||||||
|
|
||||||
|
publish {
|
||||||
|
name project.name
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package net.corda.plugins
|
||||||
|
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.api.Plugin
|
||||||
|
import org.gradle.api.tasks.testing.Test
|
||||||
|
import org.gradle.api.tasks.JavaExec
|
||||||
|
|
||||||
|
/**
|
||||||
|
* QuasarPlugin creates a "quasar" configuration and adds quasar as a dependency.
|
||||||
|
*/
|
||||||
|
class QuasarPlugin implements Plugin<Project> {
|
||||||
|
void apply(Project project) {
|
||||||
|
project.configurations.create("quasar")
|
||||||
|
// To add a local .jar dependency:
|
||||||
|
// project.dependencies.add("quasar", project.files("${project.rootProject.projectDir}/lib/quasar.jar"))
|
||||||
|
project.dependencies.add("quasar", "co.paralleluniverse:quasar-core:${project.rootProject.ext.quasar_version}:jdk8@jar")
|
||||||
|
project.dependencies.add("runtime", project.configurations.getByName("quasar"))
|
||||||
|
|
||||||
|
project.tasks.withType(Test) {
|
||||||
|
jvmArgs "-javaagent:${project.configurations.quasar.singleFile}"
|
||||||
|
jvmArgs "-Dco.paralleluniverse.fibers.verifyInstrumentation"
|
||||||
|
}
|
||||||
|
project.tasks.withType(JavaExec) {
|
||||||
|
jvmArgs "-javaagent:${project.configurations.quasar.singleFile}"
|
||||||
|
jvmArgs "-Dco.paralleluniverse.fibers.verifyInstrumentation"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
implementation-class=net.corda.plugins.QuasarPlugin
|
7
gradle-plugins/settings.gradle
Normal file
7
gradle-plugins/settings.gradle
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
rootProject.name = 'corda-gradle-plugins'
|
||||||
|
include 'publish-utils'
|
||||||
|
include 'quasar-utils'
|
||||||
|
include 'cordformation'
|
||||||
|
include 'cordform-common'
|
||||||
|
include 'api-scanner'
|
||||||
|
include 'cordapp'
|
Loading…
Reference in New Issue
Block a user