Gradle: Distributing Executable, Obfuscated Jar File - gradle

I'm trying to use gradle with proguard to obfuscate the code then generate a zip file to distribute. I'd like to use the distribution plugin, but it always includes the jar that is generated by the jar task. Is there some way to force the distribution plugin to omit the original (non-obfuscated) jar and only include the obfuscated jar? I can easily add the obfuscated jar in addition to the original, but I want to distribute the obfuscated jar instead of the original so the generated execution scripts run against the obfuscated version.
Here's my abridged build.gradle file:
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'net.sf.proguard:proguard-gradle:5.3.3'
}
}
apply plugin: 'java'
apply plugin: 'application'
task obfuscate(type: proguard.gradle.ProGuardTask) {
configuration 'proguard.txt'
injars "build/libs/${rootProject.name}.jar"
outjars "build/libs/${rootProject.name}-release.jar"
}
jar.finalizedBy(project.tasks.obfuscate)
distributions {
main {
contents {
from(obfuscate) {
into "lib"
}
from(jar) {
exclude "*.jar"
}
}
}
}
I've tried a number of things in the distributions block to try to exclude the original jar, but nothing seems to work.
Any ideas would be greatly appreciated.

This isn't the best solution, but I was able to work around the issue by renaming the jars at the end of the obfuscation step. Now, I name the original jar something like <JAR_NAME>-original.jar and I give the obfuscated jar the original jar's name. I still wish there was a better way to do it, but this seems to work.
Here is the updated, abridged build.gradle file:
import java.nio.file.Paths
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'net.sf.proguard:proguard-gradle:5.3.3'
}
}
apply plugin: 'java'
apply plugin: 'application'
def jarNameWithoutExtension = jar.archiveName.with { it.take(it.lastIndexOf(".")) }
def obfuscatedJarName = "${jarNameWithoutExtension}-release.jar"
def jarFileLocation = jar.archivePath.parent
def obfuscatedFilePath = Paths.get(jarFileLocation, obfuscatedJarName)
task obfuscate(type: proguard.gradle.ProGuardTask) {
configuration 'proguard.txt'
injars jar.archivePath
outjars obfuscatedFilePath.toString()
// Rename the original and obfuscated jars. We want the obfuscated jar to
// have the original jar's name so it will get included in the distributable
// package (generated by installDist / distZip / distTar / assembleDist).
doLast {
jar.archivePath.renameTo(Paths.get(jarFileLocation, "$jarNameWithoutExtension-original.jar").toFile())
obfuscatedFilePath.toFile().renameTo(jar.archivePath)
}
}
jar.finalizedBy(project.tasks.obfuscate)

Related

Gradle - uploadArchives generated snapshot is different from createJar

With my build.gradle file I am creating a jar file using a createJar task, which I later upload using an upload using an uploadArchives task.
The project is in groovy, structured like:
/src/main/groovy/package.name
/src/test/groovy
It's a test jar file, where the main class is in /src/test/groovy and it uses for processing classes from /src/main/groovy/package.name
The createJar task works and creates the jar that I need correctly.
So far so good.
The problem is when I run uploadArchives, which generates a snapshot.jar and it only contains the classes from /src/main/groovy/package.name
Why are the 2 jar files different ?
How can I make uploadArchives to upload the jar from createJar (or at least include the test classes as well & run it using the main class specified in createJar) ?
In build.gradle I have something like :
apply plugin: 'groovy'
apply plugin: 'java'
apply plugin: 'maven'
sourceCompatibility = 1.5
repositories {
mavenCentral()
}
artifacts {
archives file: file('./build/libs/name-'+version+'.jar'), name: 'name', type: 'jar'
}
task createJar(type: Jar) {
classifier 'test'
from sourceSets.test.output
manifest {
attributes 'Implementation-Title': 'Gradle Jar File',
'Implementation-Version': version,
'Main-Class': 'MainClassName'
}
}
uploadArchives {
repositories {
mavenDeployer {
repository(url: "http://url/snapshotsFolder/") {
authentication(userName: "user", password: "pass")
}
}
}
}
To extend my comment, let me add an example:
jar {
classifier 'test'
from sourceSets.test.output
manifest {
attributes 'Implementation-Title': 'Gradle Jar File', 'Implementation-Version': version, 'Main-Class': 'MainClassName'
}
}
This will configure the task jar of type Jar, which is created by the java plugin.

Using War plugin but without creating an archive

I'm trying to get Gradle to handle the deployment of a very large Web application. In the past when we used Ant, instead of creating a very large .war file, we would simply assemble all the code in one folder--libraries, .jsp's etc--and then scp them to the deployment destination. This would speed deployment since we would be moving only the files that changed.
I'm having trouble trying to do this with Gradle, however. Using the War plugin creates an actual .war file, which we don't want. I've tried simply creating a task that depends on 'classes' and that generates the necessary classes and resources folders. However, where are the library dependencies? How can I get this all in one place so I can do an scp?
Current build:
apply plugin: "java"
apply plugin: "maven"
apply plugin: 'war'
apply plugin: 'eclipse-wtp'
sourceCompatibility = 1.6
targetCompatibility = 1.6
repositories {
maven {
url 'http://buildserver/artifactory/repo'
}
}
sourceSets {
main {
java {
srcDir 'src'
}
resources {
srcDir 'src'
}
}
}
webAppDirName = 'web'
dependencies {
compile 'antlr:antlr:2.7.6'
compile 'antlr:antlr:2.7.7'
*** etc ***
}
task deploystage(dependsOn: 'classes') << {
println 'assemble and scp code here'
}
your right Opal ... no easynway to use the War plugin for this ... although it would be nice if it had a createExplodedWar option. I just used the Jar plugin methods.
The .jars were available, along with other things in war.classpath.files:
gradle.taskGraph.whenReady { TaskExecutionGraph taskGraph ->
project.ext.set("allclasspathjars", files(war.classpath.files))
}
I then when through these to separate out the .jars from the other stuff:
task deployJars(dependsOn: 'classes') << {
FileUtils.cleanDirectory(new File("web/WEB-INF/lib/"))
project.allclasspathjars.each {File file ->
if(file.name.endsWith(".jar")) {
println "Including .jar: " + file.name
org.apache.commons.io.FileUtils.copyFile(file, new File("web/WEB-INF/lib/" + file.name))
}
}
After this I could copy all the classes & resources into the .war structure:
task createExplodedWar(type: Copy, dependsOn: 'deployJars') {
from 'build/classes/main'
from 'build/resources/main'
into 'web/WEB-INF/classes'
}
Finally used Gradle SSH Plugin to push it up to tomcat and restart the server.
All good.

Gradle Setting Manifest Class-Path on Jars in Ear

Set Up
I'm using Gradle and have a multi-project build using Java EE with IBM WebSphere Application Server. The project directory structure looks like this:
--/build.gradle
--/defaults.gradle
--/settings.gradle
--/common-ejb
--/common-ejb/build.gradle
--/logging
--/logging/build.gradle
--/project1
--/project1/build.gradle
--/project1-ejb
--/project1-ejb/build.gradle
--/project2
--/project2/build.gradle
--/project2-ejb
--/project2-ejb/build.gradle
project1 and project2 are individual ears that get deployed. They both reuse a number of EJBs from common-ejb and share some other library dependencies that aren't relevant for this question.
After performing the build: project1.ear looks like:
--/lib/log4j.jar
--/lib/logging.jar
--/META-INF/application.xml
--/META-INF/MANIFEST.MF
--/common-ejb.jar
--/project1-ejb.jar
Gradle properly creates the application.xml to load EJBs from both projects. Unfortunately, project1-ejb.jar will fail to load due to dependencies on common-ejb.jar. The project1-ejb.jar/META-INF/MANIFEST.MF needs to have the Class-Path set with common-ejb.jar since it's not in the lib/ directory.
I was able to set it by explicitly defining it as done below. Gradle knows the dependencies for the Class-Path, so it should be able do this automatically. Is there a way to set this up?
Gradle Files
Not including project2, but you can guess what it looks like.
--/build.gradle
apply from: 'defaults.gradle'
defaultTasks 'clean', 'build'
--/defaults.gradle
defaultTasks 'build'
repositories {
mavenCentral()
}
--/settings.gradle
include 'common-ejb'
include 'project1'
include 'project1-ejb'
include 'logging'
--/logging/build.gradle
apply from: '../defaults.gradle'
apply plugin: 'java'
dependencies {
compile 'log4j:log4j:1.2.+'
}
--/common-ejb/build.gradle
apply from: '../defaults.gradle'
apply plugin: 'java'
dependencies {
compile 'javax:javaee-api:6.0'
compile project(':logging')
}
--/project1-ejb/build.gradle
apply from: '../defaults.gradle'
apply plugin: 'java'
dependencies {
compile 'javax:javaee-api:6.0'
compile project(':common-ejb')
compile project(':logging')
}
// THIS IS THE WORKAROUND, I don't want to explicitly modify the Class-Path for each EJB based on the EAR the EJB is going to be included in.
jar {
manifest {
attributes("Class-Path": project(':common-ejb').jar.archiveName)
}
}
--/project1/build.gradle
apply from: '../defaults.gradle'
apply plugin: 'ear'
apply plugin: 'java'
dependencies {
deploy project(':project1-ejb')
deploy project(':common-ejb')
earlib project(':logging')
}
Using some information from and modifying code from a question about getting all dependencies of a project from the Gradle forums.
Essentially, you want to take the EAR's deploy dependencies and see if any deploy dependencies depend on each other. If they do, you set the Class-Path to include the referenced jars.
Remove the manifest lines from project1-ejb and project2-ejb. Add the following to your defaults.gradle:
def getAllDependentProjects(project) {
if ( !project.configurations.hasProperty("runtime") ) {
return []
}
def projectDependencies = project.configurations.runtime.getAllDependencies().withType(ProjectDependency)
def dependentProjects = projectDependencies*.dependencyProject
if (dependentProjects.size > 0) {
dependentProjects.each { dependentProjects += getAllDependentProjects(it) }
}
return dependentProjects.unique()
}
gradle.projectsEvaluated {
if (plugins.hasPlugin('ear')) {
def deployProjectDependencies = configurations.deploy.getAllDependencies().withType(ProjectDependency)*.dependencyProject
deployProjectDependencies.each {
def cur = it
def cur_deps = getAllDependentProjects(cur)
def depJars = []
deployProjectDependencies.each {
def search = it
if ( cur_deps.contains(search)) {
depJars += search.jar.archiveName
}
}
depJars = depJars.unique()
if ( depJars.size() > 0 ) {
logger.info("META-INF Dependencies for deploy dependency " + cur.name + ": " + depJars)
cur.jar.manifest.attributes(
'Class-Path': depJars.join(' ')
)
}
}
}
}
This will have the desired affect. Directly after the configuration step and before build, the EAR projects will reevaluate their dependencies to see if any are cross-referenced. There may be a more efficient way, but this gets the job done.

Gradle war ignores transitive dependencies when using 'configurations.runtime.asPath' in custom task

I'm facing behavior that I can't explain, using gradle 1.10 I have:
settings.gradle:
include('lib1', 'lib2', 'web')
build.gradle:
subprojects {
apply plugin: 'java'
}
project(':web') {
apply plugin: 'war'
dependencies {
compile project(':lib1')
}
task myTask(type: JavaExec, dependsOn: 'compileJava') {
main = "some.thirdparty.Class"
args "--searchPath", configurations.runtime.asPath
}
}
project(':lib1') {
dependencies {
compile project(':lib2')
}
}
project(':lib2') {
}
When I run gradle clean war I only have lib1.jar in war/build/libs/web.war/WEB-INF/lib.
To make WEB-INF/lib contain both lib1.jar and lib2.jar I have to:
move project('web') block to the end of the file
update configurations.runtime.asPath to configurations.runtime (but I need to provide class path as a path, so it is not a solution)
I read the build lifecycle description, tried to compare --debug outputs but that didn't help.
Why is this happening? And what would be a good solution to provide the module runtime class path as a path in JavaExec task please?
asPath resolves the configuration, but resolution will only work correctly if it happens at execution time rather than configuration time (in particular in the presence of project dependencies). Try to wrap the args line with doFirst { ... }.

avro gradle plugin sample usage

I am trying to use the avro-gradle-plugin on github, but have not gotten any luck getting it to work. Does anyone have any sample code on how they get it to work?
I figured out how to do it myself. The following is a snippet that I would like to share for people who might run into the same issues as I did:
apply plugin: 'java'
apply plugin: 'avro-gradle-plugin'
sourceCompatibility = "1.6"
targetCompatibility = "1.6"
buildscript {
repositories {
maven {
// your maven repo information here
}
}
dependencies {
classpath 'org.apache.maven:maven-artifact:2.2.1'
classpath 'org.apache.avro:avro-compiler:1.7.1'
classpath 'org.apache.avro.gradle:avro-gradle-plugin:1.7.1'
}
}
compileAvro.source = 'src/main/avro'
compileAvro.destinationDir = file("$buildDir/generated-sources/avro")
sourceSets {
main {
java {
srcDir compileAvro.destinationDir
}
}
}
dependencies {
compileAvro
}
I found "com.commercehub.gradle.plugin.avro" gradle plugin to work better.
use the folllowing:
// Gradle 2.1 and later
plugins {
id "com.commercehub.gradle.plugin.avro" version "VERSION"
}
// Earlier versions of Gradle
buildscript {
repositories {
jcenter()
}
dependencies {
classpath "com.commercehub.gradle.plugin:gradle-avro-plugin:VERSION"
}
}
apply plugin: "com.commercehub.gradle.plugin.avro"
more details at https://github.com/commercehub-oss/gradle-avro-plugin
When evaluating a plugin the following questions needs to be asked:
Are generated files included into source jar?
Is plugin fast? Good plugin use avro tools api instead of forking VM for every file. For large amount of files creating VM for every file can take 10min to compile.
Do you need intermediate avsc files?
Is build incremental (i.e. do not regenerate all files unless one of the sources changed)?
Is plugin flexible enough to give access to generated schema files, so further actions, such as registration schema in schema repository can be made?
It is easy enough to implement without any plugin if you are not happy with plugin or need more flexibility.
//
// define source and destination
//
def avdlFiles = fileTree('src/Schemas').include('**/*.avdl')
// Do NOT generate into $buildDir, because IntelliJ will ignore files in
// this location and will show errors in source code
def generatedJavaDir = "generated/avro/java"
sourceSets.main.java.srcDir generatedJavaDir
//
// Make avro-tools available to the build script
//
buildscript {
dependencies {
classpath group:'org.apache.avro', name:'avro-tools' ,version: avro_version
}
}
//
// Define task's input and output, compile idl to schema and schema to java
//
task buildAvroDtos(){
group = "build"
inputs.files avdlFiles
outputs.dir generatedJavaDir
doLast{
avdlFiles.each { avdlFile ->
def parser = new org.apache.avro.compiler.idl.Idl(avdlFile)
parser.CompilationUnit().getTypes().each { schema ->
def compiler = new org.apache.avro.compiler.specific.SpecificCompiler(schema)
compiler.compileToDestination(avdlFile, new File(generatedJavaDir))
}
}
}
}
//
// Publish source jar, including generated files
//
task sourceJar(type: Jar, dependsOn: buildAvroDtos) {
from sourceSets.main.allSource
// Package schemas into source jar
into("Schemas") { from avdlFiles }
}
// Clean "generated" folder upon "clean" task
clean {
delete('generated')
}
Configuration for avro with gradle as build tool need to add along with applying java plugin.
below changes in settings.gradle
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
}
}
below changes in build.gradle
plugins {
id "com.github.davidmc24.gradle.plugin.avro" version "1.3.0"
}
repositories {
mavenCentral()
}
dependencies {
implementation "org.apache.avro:avro:1.11.0"
}
generateAvroJava {
source("${projectDir}/src/main/resources/avro")//sourcepath avrofile
}
if you want to generate setter methods too add this task too in build.gradle
avro {
createSetters = true
}
link for reference

Resources