Moved gradle plugins to another repo (#2520)

This commit is contained in:
Clinton 2018-02-14 17:39:56 +00:00 committed by GitHub
parent 311475a81c
commit 1cd028ebbe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 0 additions and 2990 deletions

View File

@ -1,29 +0,0 @@
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

View File

@ -1,79 +0,0 @@
# 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.*` packages, 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.

View File

@ -1,21 +0,0 @@
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 {
mavenLocal()
mavenCentral()
}
dependencies {
compile gradleApi()
compile "io.github.lukehutch:fast-classpath-scanner:2.7.0"
testCompile "junit:junit:4.12"
}
publish {
name project.name
}

View File

@ -1,70 +0,0 @@
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"));
}
}

View File

@ -1,61 +0,0 @@
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);
}
}
}
}

View File

@ -1,402 +0,0 @@
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;
}
if (className.contains("$$inlined$")) {
/*
* These classes are internally generated by the Kotlin compiler
* and are not exposed as part of the public API
* TODO: Filter out using EnclosingMethod attribute in classfile
*/
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
&& !hasCordaInternal(method.getAnnotationNames()) // Excludes methods annotated as @CordaInternal
&& !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 hasCordaInternal(Collection<String> annotationNames) {
return annotationNames.contains("net.corda.core.CordaInternal");
}
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()]);
}
}

View File

@ -1,37 +0,0 @@
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;
}
}

View File

@ -1 +0,0 @@
implementation-class=net.corda.plugins.ApiScanner

View File

@ -1,84 +0,0 @@
// 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')
snake_yaml_version = constants.getProperty('snakeYamlVersion')
}
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())
}
}
}
}

View File

@ -1,10 +0,0 @@
# 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.

View File

@ -1,19 +0,0 @@
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
}

View File

@ -1,75 +0,0 @@
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.enterprise 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.enterprise.")) {
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()
}
}

View File

@ -1,35 +0,0 @@
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)
}
}
}
}

View File

@ -1 +0,0 @@
implementation-class=net.corda.plugins.CordappPlugin

View File

@ -1,4 +0,0 @@
# 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.

View File

@ -1,23 +0,0 @@
apply plugin: 'java'
apply plugin: 'kotlin'
apply plugin: 'maven-publish'
apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'com.jfrog.artifactory'
repositories {
mavenCentral()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
// 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
}

View File

@ -1,7 +0,0 @@
package net.corda.cordform;
import java.nio.file.Path;
public interface CordformContext {
Path baseDirectory(String nodeName);
}

View File

@ -1,48 +0,0 @@
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<>();
/**
* A list of Cordapp maven coordinates and project name
*
* If maven coordinates are set project name is ignored
*/
private final List<CordappDependency> cordappDeps = 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);
}
/**
* Cordapp maven coordinates or project names (ie; net.corda:finance:0.1 or ":finance") to scan for when resolving cordapp JARs
*/
public List<CordappDependency> getCordappDependencies() {
return cordappDeps;
}
/**
* 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);
}

View File

@ -1,190 +0,0 @@
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. Node will be placed in directory based on this name - all lowercase with whitespaces removed.
* Actual node name inside node.conf will be as set here.
*/
private String name;
public String getName() {
return name;
}
/**
* p2p Port.
*/
private int p2pPort = 10002;
public int getP2pPort() { return p2pPort; }
/**
* RPC Port.
*/
private int rpcPort = 10003;
public int getRpcPort() { return rpcPort; }
/**
* 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);
this.p2pPort = 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);
this.rpcPort = 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));
}
}

View File

@ -1,9 +0,0 @@
package net.corda.cordform;
import com.typesafe.config.Config;
public interface NodeDefinition {
String getName();
Config getConfig();
}

View File

@ -1,80 +0,0 @@
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();
private int port = 10003;
private int adminPort = 10005;
public int getPort() {
return port;
}
public int getAdminPort() {
return adminPort;
}
/**
* RPC address for the node.
*/
public final void address(final String value) {
setValue("address", value);
}
/**
* RPC Port for the node
*/
public final void port(final int value) {
this.port = value;
setValue("address", "localhost:"+port);
}
/**
* RPC admin address for the node (necessary if [useSsl] is false or unset).
*/
public final void adminAddress(final String value) {
setValue("adminAddress", value);
}
public final void adminPort(final int value) {
this.adminPort = value;
setValue("adminAddress", "localhost:"+adminPort);
}
/**
* 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);
}
public 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));
}
}

View File

@ -1,56 +0,0 @@
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);
}
public 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));
}
}

View File

@ -1 +0,0 @@
Please refer to the documentation in <corda-root>/doc/build/html/running-a-node.html#cordformation.

View File

@ -1,69 +0,0 @@
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'kotlin'
apply plugin: 'java-gradle-plugin'
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 {
gradleApi()
compile project(":cordapp")
compile project(':cordform-common')
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
compile "commons-io:commons-io:2.6"
noderunner "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
testCompile "junit:junit:4.12" // TODO: Unify with core
testCompile "org.assertj:assertj-core:3.8.0"
// Docker-compose file generation
compile "org.yaml:snakeyaml:$snake_yaml_version"
}
task createNodeRunner(type: Jar) {
manifest {
attributes('Main-Class': 'net.corda.plugins.NodeRunnerKt')
}
classifier = 'fatjar'
from { configurations.noderunner.collect { it.isDirectory() ? it : zipTree(it) } }
from sourceSets.runnodes.output
}
publish {
name project.name
}
processResources {
from(createNodeRunner) {
rename { 'net/corda/plugins/runnodes.jar' }
}
}

View File

@ -1,166 +0,0 @@
package net.corda.plugins
import groovy.lang.Closure
import net.corda.cordform.CordformDefinition
import org.gradle.api.DefaultTask
import org.gradle.api.plugins.JavaPluginConvention
import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME
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 Baseform : 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
var directory = defaultDirectory
protected 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 }
/**
* 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")
}
/**
* Installs the corda fat JAR to the root directory, for the network bootstrapper to use.
*/
protected fun installCordaJar() {
val cordaJar = Cordformation.verifyAndGetRuntimeJar(project, "corda")
project.copy {
it.apply {
from(cordaJar)
into(directory)
rename(cordaJar.name, nodeJarName)
fileMode = Cordformation.executableFileMode
}
}
}
internal 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) {
project.logger.info("User has used '$directory', default directory is '${defaultDirectory}'")
"'directory' cannot be used when 'definitionClass' is specified. Use CordformDefinition.nodesDirectory instead."
}
directory = cd.nodesDirectory
val cordapps = cd.cordappDependencies
cd.nodeConfigurers.forEach {
val node = node { }
it.accept(node)
cordapps.forEach {
if (it.mavenCoordinates != null) {
node.cordapp(project.project(it.mavenCoordinates!!))
} else {
node.cordapp(it.projectName!!)
}
}
node.rootDir(directory)
}
cd.setup { nodeName -> project.projectDir.toPath().resolve(getNodeByName(nodeName)!!.nodeDir.toPath()) }
} else {
nodes.forEach {
it.rootDir(directory)
}
}
}
protected 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 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
}
}
}

View File

@ -1,71 +0,0 @@
package net.corda.plugins
import org.apache.tools.ant.filters.FixCrLfFilter
import org.gradle.api.tasks.TaskAction
import java.nio.file.Path
import java.nio.file.Paths
/**
* Creates nodes based on the configuration of this task in the gradle configuration DSL.
*
* See documentation for examples.
*/
@Suppress("unused")
open class Cordform : Baseform() {
internal companion object {
val nodeJarName = "corda.jar"
}
/**
* 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, "runnodes.jar"))
fileMode = Cordformation.executableFileMode
into("$directory/")
}
}
project.copy {
it.apply {
from(Cordformation.getPluginFile(project, "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, "runnodes.bat"))
into("$directory/")
}
}
}
/**
* 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)
}
}

View File

@ -1,63 +0,0 @@
package net.corda.plugins
import org.gradle.api.Plugin
import org.gradle.api.Project
import java.io.File
import java.io.InputStream
/**
* 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 by creating an intermediate tmp dir
*
* @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 tmpDir = File(project.buildDir, "tmp")
val outputFile = File(tmpDir, filePathInJar)
tmpDir.mkdir()
outputFile.outputStream().use {
Cordformation::class.java.getResourceAsStream(filePathInJar).copyTo(it)
}
return outputFile
}
/**
* 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")
}
}

View File

@ -1,66 +0,0 @@
package net.corda.plugins
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 org.yaml.snakeyaml.DumperOptions
import java.nio.file.Path
import java.nio.file.Paths
import org.yaml.snakeyaml.Yaml
import java.nio.charset.StandardCharsets
import java.nio.file.Files
/**
* Creates docker-compose file and image definitions based on the configuration of this task in the gradle configuration DSL.
*
* See documentation for examples.
*/
@Suppress("unused")
open class Dockerform : Baseform() {
private companion object {
val nodeJarName = "corda.jar"
private val defaultDirectory: Path = Paths.get("build", "docker")
private val dockerComposeFileVersion = "3"
private val yamlOptions = DumperOptions().apply {
indent = 2
defaultFlowStyle = DumperOptions.FlowStyle.BLOCK
}
private val yaml = Yaml(yamlOptions)
}
private val directoryPath = project.projectDir.toPath().resolve(directory)
val dockerComposePath = directoryPath.resolve("docker-compose.yml")
/**
* 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::installDockerConfig)
installCordaJar()
bootstrapNetwork()
nodes.forEach(Node::buildDocker)
// Transform nodes path the absolute ones
val services = nodes.map { it.containerName to mapOf(
"build" to directoryPath.resolve(it.nodeDir.name).toAbsolutePath().toString(),
"ports" to listOf(it.rpcPort)) }.toMap()
val dockerComposeObject = mapOf(
"version" to dockerComposeFileVersion,
"services" to services)
val dockerComposeContent = yaml.dump(dockerComposeObject)
Files.write(dockerComposePath, dockerComposeContent.toByteArray(StandardCharsets.UTF_8))
}
}

View File

@ -1,409 +0,0 @@
package net.corda.plugins
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigObject
import com.typesafe.config.ConfigRenderOptions
import com.typesafe.config.ConfigValueFactory
import groovy.lang.Closure
import net.corda.cordform.CordformNode
import net.corda.cordform.RpcSettings
import org.apache.commons.io.FilenameUtils
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.artifacts.ProjectDependency
import java.io.File
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Path
import javax.inject.Inject
/**
* Represents a node that will be installed.
*/
open class Node @Inject constructor(private val project: Project) : CordformNode() {
private data class ResolvedCordapp(val jarFile: File, val config: String?)
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: MutableList<Any>
get() = internalCordapps as MutableList<Any>
@Deprecated("Use cordapp instead - setter will be removed by Corda V4.0")
set(value) {
value.forEach {
cordapp(it.toString())
}
}
private val internalCordapps = mutableListOf<Cordapp>()
private val builtCordapp = Cordapp(project)
internal lateinit var nodeDir: File
private set
internal lateinit var rootDir: File
private set
internal lateinit var containerName: String
private set
internal var rpcSettings: RpcSettings = RpcSettings()
private set
internal var webserverJar: String? = null
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>) {
rpcSettings = project.configure(RpcSettings(), 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("sshdAddress",
ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$sshdPort"))
}
/**
* Install a cordapp to this node
*
* @param coordinates The coordinates of the [Cordapp]
* @param configureClosure A groovy closure to configure a [Cordapp] object
* @return The created and inserted [Cordapp]
*/
fun cordapp(coordinates: String, configureClosure: Closure<in Cordapp>): Cordapp {
val cordapp = project.configure(Cordapp(coordinates), configureClosure) as Cordapp
internalCordapps += cordapp
return cordapp
}
/**
* Install a cordapp to this node
*
* @param cordappProject A project that produces a cordapp JAR
* @param configureClosure A groovy closure to configure a [Cordapp] object
* @return The created and inserted [Cordapp]
*/
fun cordapp(cordappProject: Project, configureClosure: Closure<in Cordapp>): Cordapp {
val cordapp = project.configure(Cordapp(cordappProject), configureClosure) as Cordapp
internalCordapps += cordapp
return cordapp
}
/**
* Install a cordapp to this node
*
* @param cordappProject A project that produces a cordapp JAR
* @return The created and inserted [Cordapp]
*/
fun cordapp(cordappProject: Project): Cordapp {
return Cordapp(cordappProject).apply {
internalCordapps += this
}
}
/**
* Install a cordapp to this node
*
* @param coordinates The coordinates of the [Cordapp]
* @return The created and inserted [Cordapp]
*/
fun cordapp(coordinates: String): Cordapp {
return Cordapp(coordinates).apply {
internalCordapps += this
}
}
/**
* Install a cordapp to this node
*
* @param configureFunc A lambda to configure a [Cordapp] object
* @return The created and inserted [Cordapp]
*/
fun cordapp(coordinates: String, configureFunc: Cordapp.() -> Unit): Cordapp {
return Cordapp(coordinates).apply {
configureFunc()
internalCordapps += this
}
}
/**
* Configures the default cordapp automatically added to this node from this project
*
* @param configureClosure A groovy closure to configure a [Cordapp] object
* @return The created and inserted [Cordapp]
*/
fun projectCordapp(configureClosure: Closure<in Cordapp>): Cordapp {
project.configure(builtCordapp, configureClosure) as Cordapp
return builtCordapp
}
/**
* The webserver JAR to be used by this node.
*
* If not provided, the default development webserver is used.
*
* @param webserverJar The file path of the webserver JAR to use.
*/
fun webserverJar(webserverJar: String) {
this.webserverJar = webserverJar
}
internal fun build() {
if (config.hasPath("webAddress")) {
installWebserverJar()
}
installAgentJar()
installCordapps()
installConfig()
}
internal fun buildDocker() {
project.copy {
it.apply {
from(Cordformation.getPluginFile(project, "net/corda/plugins/Dockerfile"))
from(Cordformation.getPluginFile(project, "net/corda/plugins/run-corda.sh"))
into("$nodeDir/")
}
}
installAgentJar()
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
containerName = dirName.replace("\\s+".toRegex(), "-").toLowerCase()
this.rootDir = rootDir.toFile()
nodeDir = File(this.rootDir, dirName.replace("\\s", ""))
Files.createDirectories(nodeDir.toPath())
}
private fun configureProperties() {
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() {
// If no webserver JAR is provided, the default development webserver is used.
val webJar = if (webserverJar == null) {
project.logger.info("Using default development webserver.")
Cordformation.verifyAndGetRuntimeJar(project, "corda-webserver")
} else {
project.logger.info("Using custom webserver: $webserverJar.")
File(webserverJar)
}
project.copy {
it.apply {
from(webJar)
into(nodeDir)
rename(webJar.name, webJarName)
}
}
}
/**
* 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 installCordappConfigs(cordapps: Collection<ResolvedCordapp>) {
val cordappsDir = project.file(File(nodeDir, "cordapps"))
cordappsDir.mkdirs()
cordapps.filter { it.config != null }
.map { Pair<String, String>("${FilenameUtils.removeExtension(it.jarFile.name)}.conf", it.config!!) }
.forEach { project.file(File(cordappsDir, it.first)).writeText(it.second) }
}
private fun createTempConfigFile(configObject: ConfigObject): File {
val options = ConfigRenderOptions
.defaults()
.setOriginComments(false)
.setComments(false)
.setFormatted(true)
.setJson(false)
val configFileText = configObject.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.
*/
fun installConfig() {
configureProperties()
val tmpConfFile = createTempConfigFile(config.root())
appendOptionalConfig(tmpConfFile)
project.copy {
it.apply {
from(tmpConfFile)
into(rootDir)
}
}
}
/**
* Installs the Dockerized configuration file to the root directory and detokenises it.
*/
internal fun installDockerConfig() {
configureProperties()
val dockerConf = config
.withValue("p2pAddress", ConfigValueFactory.fromAnyRef("$containerName:$p2pPort"))
.withValue("rpcSettings.address", ConfigValueFactory.fromAnyRef("$containerName:${rpcSettings.port}"))
.withValue("rpcSettings.adminAddress", ConfigValueFactory.fromAnyRef("$containerName:${rpcSettings.adminPort}"))
.withValue("detectPublicIp", ConfigValueFactory.fromAnyRef(false))
val tmpConfFile = createTempConfigFile(dockerConf.root())
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 the jolokia monitoring agent JAR to the node/drivers directory
*/
private fun installCordapps() {
val cordapps = getCordappList()
val cordappsDir = File(nodeDir, "cordapps")
project.copy {
it.apply {
from(cordapps.map { it.jarFile })
into(project.file(cordappsDir))
}
}
installCordappConfigs(cordapps)
}
/**
* Gets a list of cordapps based on what dependent cordapps were specified.
*
* @return List of this node's cordapps.
*/
private fun getCordappList(): Collection<ResolvedCordapp> =
internalCordapps.map { cordapp -> resolveCordapp(cordapp) } + resolveBuiltCordapp()
private fun resolveCordapp(cordapp: Cordapp): ResolvedCordapp {
val cordappConfiguration = project.configuration("cordapp")
val cordappName = if (cordapp.project != null) cordapp.project.name else cordapp.coordinates
val cordappFile = cordappConfiguration.files {
when {
(it is ProjectDependency) && (cordapp.project != null) -> it.dependencyProject == cordapp.project
cordapp.coordinates != null -> {
// Cordapps can sometimes contain a GString instance which fails the equality test with the Java string
@Suppress("RemoveRedundantCallsOfConversionMethods")
val coordinates = cordapp.coordinates.toString()
coordinates == (it.group + ":" + it.name + ":" + it.version)
}
else -> false
}
}
return when {
cordappFile.size == 0 -> throw GradleException("Cordapp $cordappName not found in cordapps configuration.")
cordappFile.size > 1 -> throw GradleException("Multiple files found for $cordappName")
else -> ResolvedCordapp(cordappFile.single(), cordapp.config)
}
}
private fun resolveBuiltCordapp(): ResolvedCordapp {
val projectCordappFile = project.tasks.getByName("jar").outputs.files.singleFile
return ResolvedCordapp(projectCordappFile, builtCordapp.config)
}
}

View File

@ -1 +0,0 @@
implementation-class=net.corda.plugins.Cordformation

View File

@ -1,44 +0,0 @@
# Base image from (http://phusion.github.io/baseimage-docker)
FROM openjdk:8u151-jre-alpine
ENV CORDA_VERSION=${BUILDTIME_CORDA_VERSION}
ENV JAVA_OPTIONS=${BUILDTIME_JAVA_OPTIONS}
# Set image labels
LABEL net.corda.version = ${CORDA_VERSION} \
maintainer = "<devops@r3.com>" \
vendor = "R3"
RUN apk upgrade --update && \
apk add --update --no-cache bash iputils && \
rm -rf /var/cache/apk/* && \
# Add user to run the app && \
addgroup corda && \
adduser -G corda -D -s /bin/bash corda && \
# Create /opt/corda directory && \
mkdir -p /opt/corda/plugins && \
mkdir -p /opt/corda/logs
# Copy corda files
ADD --chown=corda:corda corda.jar /opt/corda/corda.jar
ADD --chown=corda:corda node.conf /opt/corda/node.conf
ADD --chown=corda:corda network-parameters /opt/corda/
ADD --chown=corda:corda cordapps/ /opt/corda/cordapps
ADD --chown=corda:corda additional-node-infos/ /opt/corda/additional-node-infos
ADD --chown=corda:corda certificates/ /opt/corda/certificates
ADD --chown=corda:corda drivers/ /opt/corda/drivers
ADD --chown=corda:corda persistence* /opt/corda/
COPY run-corda.sh /run-corda.sh
RUN chmod +x /run-corda.sh && \
sync && \
chown -R corda:corda /opt/corda
# Working directory for Corda
WORKDIR /opt/corda
ENV HOME=/opt/corda
USER corda
# Start it
CMD ["/run-corda.sh"]

View File

@ -1,10 +0,0 @@
#!/bin/sh
# If variable not present use default values
: ${CORDA_HOME:=/opt/corda}
: ${JAVA_OPTIONS:=-Xmx512m}
export CORDA_HOME JAVA_OPTIONS
cd ${CORDA_HOME}
java $JAVA_OPTIONS -jar ${CORDA_HOME}/corda.jar 2>&1

View File

@ -1,13 +0,0 @@
#!/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

View File

@ -1,8 +0,0 @@
@echo off
REM Change to the directory of this script (%~dp0)
Pushd %~dp0
java -jar runnodes.jar %*
Popd

View File

@ -1,159 +0,0 @@
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 {
println("Running command: ${command.joinToString(" ")}")
return 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 {
val params = 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)
}
}
}
println("Running command: ${params.joinToString(" ")}")
return ProcessBuilder(params)
}
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

View File

@ -1,92 +0,0 @@
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
}
}

View File

@ -1,109 +0,0 @@
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')

View File

@ -1,39 +0,0 @@
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
}

View File

@ -1,167 +0,0 @@
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()
}
/**
* This call must come at the end of any publish block because it configures the publishing and any
* values set after this call in the DSL will not be configured properly (and will use the default value)
*/
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.logger.info("Configuring maven publish for $publishName")
project.apply([plugin: 'maven-publish'])
project.publishing.publications.create(publishName, MavenPublication) {
groupId project.group
artifactId publishName
if (publishConfig.publishSources) {
project.logger.info("Publishing sources for $publishName")
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")
}
}

View File

@ -1,70 +0,0 @@
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)
}
}

View File

@ -1,16 +0,0 @@
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
}

View File

@ -1,16 +0,0 @@
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
}

View File

@ -1 +0,0 @@
implementation-class=net.corda.plugins.PublishTasks

View File

@ -1,4 +0,0 @@
Quasar Utils
============
Quasar utilities adds several tasks and configuration that provide a default Quasar setup and removes some boilerplate.

View File

@ -1,19 +0,0 @@
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
}

View File

@ -1,28 +0,0 @@
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", "${project.rootProject.ext.quasar_group}: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"
}
}
}

View File

@ -1 +0,0 @@
implementation-class=net.corda.plugins.QuasarPlugin

View File

@ -1,7 +0,0 @@
rootProject.name = 'corda-gradle-plugins'
include 'publish-utils'
include 'quasar-utils'
include 'cordformation'
include 'cordform-common'
include 'api-scanner'
include 'cordapp'