Obfuscated Jars - What are the best practices?


(Scott Palmer) #1

I want to publish an obfuscated jar (using Proguard) and I don’t want the un-obfuscated jar to “escape” so I don’t want it published.

When using the maven-publish plugin, the POM does not get the dependencies generated unless I use “from components.java” instead of “archive someJar” so when I have this:

publishing {
    publications {
        main(MavenPublication) {
            //from components.java // <-publishes unobfuscated jar
            // Below uses right jar, but dependency info is omitted from pom
            artifact(file("$projectDir/build/proguard/obfuscated.jar")) {
                builtBy obfuscate
            }
        }
    }
}

I get the right jar with a bad pom.xml

The obvious thing to try is to copy the obfuscated jar over top of the unobfuscated jar before the upload happens. That gets ugly though. Without eventually restoring the original jar, the jar task will see the mismatched output and always run for example.

Do we have any best-practices for dealing with obfuscation yet?


(Scott Palmer) #2

Answering my own question again:
The docs here:
https://docs.gradle.org/current/dsl/org.gradle.api.publish.maven.MavenPublication.html#org.gradle.api.publish.maven.MavenPublication:setArtifacts(java.lang.Iterable)

show this:

publishing {
    publications {
        maven(MavenPublication) {
            from components.java
            artifacts = ["my-custom-jar.jar", sourceJar]
        }
    }
}

by replacing the artifacts AFTER doing “from components.java” the pom is generated with the dependencies, but the artifact from components.java is not included. The docs here were very confusing as they stated, "For example, to exclude the dependencies declared by a component and instead use a custom set of artifacts"
I misinterpreted that as the dependencies would be excluded from the pom


(Nicholas Bransby-Williams) #3

@swpalmer do you have a complete example? Looks like you are one of a very few people who got this working and there’s still no good guides online on how to do it


(Scott Palmer) #4

I’m doing things differently these days. What I do now is add a classifier for the default jar:

jar {
    classifier 'unobfuscated'
}

that ensures that the unobfuscated jar isn’t used by accident.

Then I just add the obfuscated jar as a normal artifact without a classifier.

publications {
    main(MavenPublication) {
        from components.java
        artifact("$buildDir/proguard/${project.name}-${project.version}.jar") {
            builtBy obfuscate
        }
    }
}

This seems to have the least side-effects. The thing to note is that the default artifact is the unobfuscated jar. So sibling projects that simply declare a project dependency will get the unobfuscated jar. You can clear the artifacts and add the obfuscated jar to work around that.


(Nicholas Bransby-Williams) #5

thank you, for completeness what does your obfuscate task look like? how do you set libraryjars to the dependencies that don’t form part of the fat jar (and the java runtime?)


(Scott Palmer) #6

This is the basic template:

task obfuscate(type: proguard.gradle.ProGuardTask, dependsOn: jar) {
	mustRunAfter ('javadoc')
	inputs.file  file("${jar.archivePath}")
	outputs.file file("$buildDir/proguard/${project.name}-${project.version}.jar")

	injars  "${jar.archivePath}"

	// JDK 8 and below use jars on the classpath
	if (JavaVersion.current().java8Compatible && !JavaVersion.current().java9Compatible) {
		println "Obfuscation inputs based on JDK 8 layout."
		libraryjars "$javaHome/lib/rt.jar"
		libraryjars "$javaHome/lib/jce.jar"
		libraryjars "$javaHome/lib/ext/jfxrt.jar"
	} else {
		// JDK 9 and above use modules on the module-path
		println "Obfuscation inputs based on JDK 9+ module layout."
		def jdkModuleList = [
			'java.base', 'java.datatransfer', 'java.desktop',
			'java.instrument', 'java.logging',
			'java.management', 'java.prefs', 'java.rmi',
			'java.scripting', 'java.xml',
			'jdk.attach'
		]
		jdkModuleList.forEach {
			libraryjars "$javaHome/jmods/${it}.jmod", jarfilter: '!**.jar', filter: '!module-info.class'
		}
		target '10' // JDK 9 is obsolete, would target 11, but Proguard can't deal with 11's class files yet
	}
	// dependencies
	configurations.runtime.files.each {
		libraryjars it, filter: '!META-INF/versions/**'
	}
	outjars "$buildDir/proguard/${project.name}-${project.version}.jar"
	printseeds "$buildDir/proguard/proguard_seeds.txt"
	printmapping "$buildDir/proguard/proguard_map.txt"

	configuration 'src/main/proguard/configuration.pro'
}

I avoid fat jars, but there was a project that used them and for that I used a configuration for which dependencies should be included in the fat jar.
something like this:

configurations {
	infatjar
}

sourceSets {
	main.compileClasspath += configurations.infatjar
	main.runtimeClasspath += configurations.infatjar
	test.compileClasspath += configurations.infatjar
	test.runtimeClasspath += configurations.infatjar
}

and the obfuscate task is modified to include the dependencies like this:

configurations.infatjar.files.each {
	injars it
}
// dependencies
(configurations.runtime - configurations.infatjar).files.each {
	libraryjars it, filter: '!META-INF/versions/**'
}

So dependencies that are in the ‘infatjar’ configuration are added as injars instead of libraryjars