The way of packaging code changes all the time. In this article we will show you how to pack Java code in a JAR file with Gradle and Maven plugins and give an Android ProGuard obfuscation example with ProGuard library.
Contents
We are using IntelliJ community edition to write server side java code for our Android Application. The code base is not large, so we make a simple API and obfuscate the implementation. The project structure will look like this:
-src/main/java --api --internal -src/main/resources -src/test/java -src/test/resources
Gradle is a packaging library that comes preinstalled with many development IDEs like Android Studio. It is a Groovy-based domain specific language where all build specific settings are defined in gradle.build
and gradle.settings
files.
You tell Gradle to build things by writing a set of tasks. An important thing to understand here is that ALL tasks that are provided within one .build file will be executed no matter what. You can control the order of execution, but you cannot skip any one. If you need different tasks for different packages – you need to write different .build files.
Here is an example of a simple Gradle task that creates an archive with java files:
apply plugin: 'idea' apply plugin: 'java' group 'com.verifone' version '0.0.2' def javaName = archivesBaseName + '-java' sourceSets { main { java { srcDir 'src/main/java' } } } dependencies { compile 'org.slf4j:slf4j-api:1.7.13' runtime 'org.apache.logging.log4j:log4j-slf4j-impl:2.5' runtime 'org.apache.logging.log4j:log4j-api:2.5' runtime 'org.apache.logging.log4j:log4j-core:2.5' // test testCompile 'junit:junit:4.11' } jar { baseName = javaName manifest { attributes "Implementation-Title": javaName attributes "Implementation-Version": version } }
Within Gradle you can apply a Maven plugin in order to install the resulting JAR file into maven repository. Once the file is installed, it can be used in all other applications. This is done with a simple syntax:
apply plugin: 'maven'
Maven provides a default task install that moves the packaged java file into maven repository. Here is an example of simple set of tasks to package and move files into local maven repository:
apply plugin: 'idea' apply plugin: 'java' apply plugin: 'maven' group 'com.mycompany' version '0.0.1' def javaName = archivesBaseName + '-java' sourceSets { main { java { srcDir 'src/main/java' } } } repositories { mavenCentral() } dependencies { compile 'org.slf4j:slf4j-api:1.7.13' runtime 'org.apache.logging.log4j:log4j-slf4j-impl:2.5' runtime 'org.apache.logging.log4j:log4j-api:2.5' runtime 'org.apache.logging.log4j:log4j-core:2.5' // test testCompile 'junit:junit:4.11' } jar { baseName = javaName manifest { attributes "Implementation-Title": javaName attributes "Implementation-Version": version } } buildscript { repositories { mavenCentral() } } install { repositories.mavenInstaller { addFilter(javaName) { artifact, file -> artifact.name.endsWith(javaName) } pom(javaName).whenConfigured { p -> p.dependencies = p.dependencies.findAll { dep -> dep.artifactId != "junit" } } } }
Proguard is a library to make Java code better – optimized, shrinked, obfuscated or preverified. ProGuard should be imported within a gradle.build file:
import proguard.gradle.ProGuardTask
With ProGuard we can obfuscate (change all class names to something unreadable) our internal implementation files. This is useful when we don’t want to spend time building separate API packages, but place everything in one project. In this case, when we share our code with other parties they can come to use our internal implementation classes instead of intended interfaces. We change the names of internal classes to “a,b,c,d…” and make it more difficult for people to use our internal code.
The process of obfuscating with gradle is as follows:
Per default, ProGuard library obfuscates all files in the given JAR package. We need to obfuscate internal package only. For this we create a ProGuard settings file proguard.txt where we explicitly specify what to obfuscate and optimize and what not to obfuscate:
-keepparameternames -renamesourcefileattribute SourceFile -keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,*Annotation*,EnclosingMethod -keep class com.myfiles.api.** { *; } -keep class com.myfiles.internal.json.** { *; } -keepclassmembernames class * { java.lang.Class class$(java.lang.String); java.lang.Class class$(java.lang.String, boolean); } -optimizations !code/allocation/variable -keep class javax.** { *; } -keep class java.** { *; } -keep class org.** { *; } -keepclasseswithmembernames class * { native <methods>; } -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); }
Our proguard.txt file defines configuration or exceptions from default obfuscation behavior:
Building jars with ProGuard happens in a task “proguard” where you specify:
Here is a full example of gradle.build configuration file that uses our custom proguard.txt:
import proguard.gradle.ProGuardTask apply plugin: 'idea' apply plugin: 'java' apply plugin: 'maven' group 'com.mycompany' version '0.0.1' def javaName = archivesBaseName + '-java' def fullJavaName = archivesBaseName + '-full' sourceSets { main { java { srcDir 'src/main/java' } } } repositories { mavenCentral() } dependencies { compile 'com.google.code.gson:gson:2.3.1' compile 'org.slf4j:slf4j-api:1.7.13' runtime 'org.apache.logging.log4j:log4j-slf4j-impl:2.5' runtime 'org.apache.logging.log4j:log4j-api:2.5' runtime 'org.apache.logging.log4j:log4j-core:2.5' // test testCompile 'junit:junit:4.11' } jar { baseName = javaName manifest { attributes "Implementation-Title": javaName attributes "Implementation-Version": version } } task fullJavaJar(type: Jar) { baseName = fullJavaName manifest { attributes "Implementation-Title": baseName attributes "Implementation-Version": version } from sourceSets.main.output artifacts { archives fullJavaJar } } buildscript { repositories { mavenCentral() } dependencies { classpath 'net.sf.proguard:proguard-gradle:5.2.1' } } task proguard(type: ProGuardTask, dependsOn: 'obfuscatedJar') { configuration 'proguard.txt' injars "$buildDir/libs/${fullJavaName}-${version}.jar" outjars "$buildDir/libs/${javaName}-${version}.jar" libraryjars "${System.getProperty('java.home')}/lib/rt.jar" libraryjars configurations.compile.find { it.name.startsWith("gson") } libraryjars configurations.compile.find { it.name.startsWith("slf4j-api") } } task obfuscate(dependsOn: tasks.withType(ProGuardTask)) << {} install.dependsOn(obfuscate) install { repositories.mavenInstaller { addFilter(javaName) { artifact, file -> artifact.name.endsWith(javaName) } pom(javaName).whenConfigured { p -> p.dependencies = p.dependencies.findAll { dep -> dep.artifactId != "junit" } } } }