mirror of
https://github.com/corda/corda.git
synced 2024-12-27 08:22:35 +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