Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Monorepo DeployUtils #207

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions DeployUtils/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
DeployUtils
====
Deploy to Embedded Targets in both Java and C++.

For all projects, you can define deployment targets and artifacts. The deploy process works over SSH/SFTP and
is extremely quick.

For the previous functionality for native library building, see edu.wpi.first.NativeUtils

Commands:
`gradlew deploy` will deploy all artifacts
`gradlew deployStandalone` will deploy all artifacts marked for standalone use with `allowStandaloneDeploy`
`gradlew deploy<artifact name><target name>` will deploy only the specified artifact to the specified target

Properties:
`gradlew deploy -Pdeploy-dirty` will skip the cache check and force redeployment of all files
`gradlew deploy -Pdeploy-dry` will do a 'dry run' (will not connect or deploy to target, instead only printing to console)

## Installing plugin
Include the following in your `build.gradle`
```gradle
plugins {
id "edu.wpi.first.DeployUtils" version "<latest version>"
}
```

See [https://plugins.gradle.org/plugin/edu.wpi.first.DeployUtils](https://plugins.gradle.org/plugin/edu.first.wpi.DeployUtils) for the latest version

## Spec

```gradle

// DSL (all properties optional unless stated as required)
deploy {
targets {
myTarget(getTargetTypeClass('RemoteTarget')) { // name is first, parameter to getTargetTypeClass is type
directory = '/home/myuser' // The root directory to start deploying to. Default: user home
maxChannels = 1 // The number of channels to open on the target (how many files / commands to run at the same time). Default: 1
timeout = 3 // Timeout to use when connecting to target. Default: 3 (seconds)
failOnMissing = true // Should the build fail if the target can't be found? Default: true

locations {
ssh(getLocationTypeClass('SshDeployLocation')) {
address = "mytarget.local" // Required. The address to try
user = 'myuser' // Required. The user to login as
password = '' // The password for the user. Default: blank (empty) string
ipv6 = false // Are IPv6 addresses permitted? Default: false
}
}

// Artifacts are specific per target
artifacts {
// COMMON PROPERTIES FOR ALL ARTIFACTS //
all {
directory = 'mydir' // Subdirectory to use. Relative to target directory

onlyIf = { execute('echo Hi').result == 'Hi' } // Check closure for artifact. Will not deploy if evaluates to false

predeploy << { execute 'echo Pre' } // After onlyIf, but before deploy logic
postdeploy << { execute 'echo Post' } // After this artifact's deploy logic

disabled = true // Disable this artifact. Default: false.

dependsOn('someTask') // Make this artifact depend on a task, both standalone and main deploy tasks

dependsOnForDeployTask('someTask') // Make main artifact deploy task only depend on task

dependsOnForStandaloneDeployTask('someTask') // Make standalone artifact deploy task only depend on task
}
// END COMMON //

myFileArtifact(getArtifactTypeClass('FileArtifact)) {
file = file('myFile') // Set the file to deploy. Required.
filename = 'myFile.dat' // Set the filename to deploy to. Default: same name as file
}

// FileCollectionArtifact is a flat collection of files - directory structure is not preserved
myFileCollectionArtifact(getArtifactTypeClass('FileCollectionArtifact)) {
files = fileTree(dir: 'myDir') // Required. Set the filecollection (e.g. filetree, files, etc) to deploy
}

// FileTreeArtifact is like a FileCollectionArtifact, but the directory structure is preserved
myFileTreeArtifact(getArtifactTypeClass('FileTreeArtifact)) {
files = fileTree(dir: 'mydir') // Required. Set the fileTree (e.g. filetree, ziptree) to deploy
}

myCommandArtifact(getArtifactTypeClass('CommandArtifact)) {
command = 'echo Hello' // The command to run. Required.
// Output will be stored in 'result' after execution
}

// JavaArtifact inherits from FileArtifact
myJavaArtifact(getArtifactTypeClass('JavaArtifact)) {
// The binary to deploy is not configured by default. To configure,
// assign the exectuable property to the binary you want to run.
// See below for how to do this.
// High level plugins can provide an easier way to do this.
}

myNativeArtifact(getArtifactTypeClass('NativeExecutableArtifact)) {
// The binary to deploy is not configured by default. To configure,
// assign the exectuable property to the binary you want to run.
// See below for how to do this.
// High level plugins can provide an easier way to do this.
}
}
}
}
}

// For Java
deploy.targets.myTarget.artifacts.myJavaArtifact.jarTask = jar // Assuming you have a standard 'java' plugin

// For Native Code
model {
components {
my_program(NativeExecutableSpec) {
binaries.all {
// Filter to binary you want to deploy here.
// For instace
if (it.targetPlatform.name == 'SomeCrossBuild' && it.buildType.name == 'debug') {
deploy.targets.myTarget.artifacts.myNativeArtifact.binary = it
}
}
}
}
}
```
60 changes: 60 additions & 0 deletions DeployUtils/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
plugins {
id 'com.gradle.plugin-publish'
id 'java-gradle-plugin'
id 'groovy'
id 'maven-publish'
id 'idea'
}

repositories {
maven {
url "https://plugins.gradle.org/m2/"
}
mavenLocal()
mavenCentral()
}

dependencies {
api 'com.jcraft:jsch:0.1.55'
api 'com.google.code.gson:gson:2.8.6'

testImplementation('org.spockframework:spock-core:2.0-M4-groovy-3.0') {
exclude group: 'org.codehaus.groovy'
}
testImplementation("org.junit.jupiter:junit-jupiter-api:5.7.1")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
testImplementation('cglib:cglib-nodep:2.2')
testImplementation('org.objenesis:objenesis:1.2')
testImplementation gradleTestKit()
}

tasks.withType(Test).configureEach {
useJUnitPlatform()
}

base {
archivesName = "DeployUtils"
}

gradlePlugin {
website = 'https://github.com/wpilibsuite/native-utils'
vcsUrl = 'https://github.com/wpilibsuite/native-utils'
plugins {
DeployUtils {
id = 'edu.wpi.first.DeployUtils'
displayName = 'DeployUtils'
implementationClass = 'edu.wpi.first.deployutils.DeployUtils'
description = 'Additions to the model-based DSL for deploying Java and Native projects to remote targets'
tags = ['remote', 'target', 'deploy', 'java', 'native']
}
}
}

tasks.withType(JavaCompile) {
options.compilerArgs << '-Xlint:unchecked'
options.deprecation = true
}

tasks.withType(Javadoc) {
options.addBooleanOption('Xdoclint:all,-missing', true)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package edu.wpi.first.deployutils;

import org.gradle.api.Action;

import groovy.lang.Closure;

public class ActionWrapper<T> implements Action<T> {
private final Closure<T> closure;

public ActionWrapper(Closure<T> closure) {
this.closure = closure;
}

@Override
public void execute(T t) {
ClosureUtils.delegateCall(t, closure);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package edu.wpi.first.deployutils;

import groovy.lang.Closure;

public class ClosureUtils {
public static <T> Object delegateCall(Object object, Closure<T> closure, Object... args) {
closure.setResolveStrategy(Closure.DELEGATE_FIRST);
closure.setDelegate(object);
Object[] passArgs = new Object[args.length + 1];
passArgs[0] = object;
for (int i = 0; i < args.length; i++) {
passArgs[i + 1] = args[i];
}
return closure.call(passArgs);
}

public static <T> Object delegateCall(Object object, Closure<T> closure) {
closure.setResolveStrategy(Closure.DELEGATE_FIRST);
closure.setDelegate(object);
return closure.call(object);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package edu.wpi.first.deployutils;

import com.jcraft.jsch.JSch;

import org.gradle.api.Plugin;
import org.gradle.api.Project;

import edu.wpi.first.deployutils.deploy.DeployPlugin;
import edu.wpi.first.deployutils.log.ETLoggerFactory;

public class DeployUtils implements Plugin<Project> {

@Override
public void apply(Project project) {

ETLoggerFactory.INSTANCE.addColorOutput(project);

project.getPluginManager().apply(DeployPlugin.class);
}

private static JSch jsch;
public static JSch getJsch() {
if (jsch == null) jsch = new JSch();
return jsch;
}

public static boolean isDryRun(Project project) {
return project.hasProperty("deploy-dry");
}

public static boolean isSkipCache(Project project) {
return project.hasProperty("deploy-dirty");
}
}
28 changes: 28 additions & 0 deletions DeployUtils/src/main/java/edu/wpi/first/deployutils/PathUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package edu.wpi.first.deployutils;

import java.util.Stack;

public class PathUtils {
public static String combine(String root, String relative) {
return normalize(relative == null ? root : join(root, relative));
}

public static String join(String root, String relative) {
if (relative.startsWith("/")) return relative;
if (root.charAt(root.length() - 1) != '/') root += '/';
return root += relative;
}

public static String normalize(String filepath) {
String[] strings = filepath.split("/");
Stack<String> s = new Stack<>();
for (String str : strings) {
if (str.trim().equals("..")) {
s.pop();
} else {
s.push(str);
}
}
return String.join("/", s);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package edu.wpi.first.deployutils;

import java.util.function.Predicate;

import groovy.lang.Closure;

public class PredicateWrapper<T> implements Predicate<T> {
private final Closure<T> closure;

public PredicateWrapper(Closure<T> closure) {
this.closure = closure;
}

@Override
public boolean test(T t) {
return ((Boolean)ClosureUtils.delegateCall(t, closure)).booleanValue();
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package edu.wpi.first.deployutils.deploy;

import java.util.Objects;

public class CommandDeployResult {
private final String command;

public final String getCommand() {
return command;
}

private final String result;

public final String getResult() {
return result;
}

private final int exitCode;

public final int getExitCode() {
return exitCode;
}

public CommandDeployResult(String command, String result, int exitCode) {
this.command = command;
this.result = result;
this.exitCode = exitCode;
}

@Override
public int hashCode() {
return Objects.hash(command, result, exitCode);
}

@Override
public boolean equals(Object other) {
if (other instanceof CommandDeployResult) {
CommandDeployResult cdr = (CommandDeployResult)other;
return Objects.equals(command, cdr.command) &&
Objects.equals(result, cdr.result) &&
exitCode == cdr.exitCode;
}
return false;
}

@Override
public String toString() {
return "CommandDeployResult(" + getCommand() + ", " + getResult() + ", " + getExitCode() + ")";
}

}
Loading