I'm currently building a Android application, and I would like to do some processing after the apk is built.
I've already succeeded to launch a gradle task after the build:
tasks.whenTaskAdded {
task ->
if(task.name == 'assembleRelease'){
task.finalizedBy postApkProcess
}
}
task postApkProcess{
doLast {
println 'OK'
}
}
But I struggle to launch a method inside that task. What I would like to do is to call something like new MyClass().postBuild() inside the class (or if it is not possible, run the main method of a Java class), but I don't find a way to do it.
I've tried to build a task task postProcess(type: JavaExec), but the line apply plugin: 'java'conflicts with the Android plugin.
Is there a way to do it ?
You could use commandLine to run the main method in MyClass which could call postBuild:
commandLine 'java' '<pathToSrc>/<mypackage>.MyClass'
Be careful to set the package name and source location properly. The path should point to the location (minus package directory tree) of the MyClass.class file, not MyClass.java. If your class is not compiled or it changes often, you could call javac to compile it each time before you run it (optionally using a shell script / batch file) to bundle both commands and simplify the gradle call and help you test the setup directly.
If your class uses code from libraries ensure you add them to the classpath:
'-classpath' 'mylib1.jar:mylib2.jar'
If you use java in the build process you might also have a look at scar
I'm attempting to use "HTTPBuilder" within my simple Groovy script. When I use '#Grab' to import the dependency, everything works fine. Though, I'd like to keep the jar within a different directory and import it using the classLoader function. I've copied the 'http-builder-0.7.jar' that '#Grab' placed into my grapes directory and pasted it into the same directory my Groovy script is running (on Windows). I then comment out the '#Grab' statement and include the classLoader, but get this error:
org.codehaus.groovy.control.MultipleCompilationErrorsException:
startup failed: C:\Groovy Scripts\test.groovy: 9: unable to resolve
class HTTPBuilder
Any ideas why the classLoader wouldn't be working in the script? I printed out the path of the jar when importing with '#Grab' and it's definitely using the one within the grape directory. If I uncomment the '#Grab' statement, it works again. Here's the small script...
//#Grab('org.codehaus.groovy.modules.http-builder:http-builder:0.7')
this.getClass().classLoader.rootLoader.addURL(new File("http-builder-0.7.jar").toURL())
//return new File(groovyx.net.http.HTTPBuilder.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath());
def http = new HTTPBuilder('http://httpbin.org/get')
As mentioned, you would be wise to use another method, such as Gradle's application plugin.
However, this is one way to do what you're asking.
First, to get the jar and all dependencies, consider the following Gradle build.gradle script:
apply plugin: 'java'
dependencies {
compile 'org.codehaus.groovy.modules.http-builder:http-builder:0.7'
}
repositories {
jcenter()
}
clean {
doLast {
ant.delete(dir: 'runtime')
}
}
task getDeps(type: Copy) {
from sourceSets.main.runtimeClasspath
into 'runtime/'
doFirst {
ant.delete(dir: 'runtime')
ant.mkdir(dir: 'runtime')
}
}
If you run gradle getDeps, it will write all of the jars into runtime.
Then, in a Unix terminal (for example), you could set the classpath with this (using wildcard syntax from Java 6+, and assuming the path is the same runtime as above):
export CLASSPATH=.:"/user/foo/some/path/runtime/*"
In the same terminal, this will work:
import groovyx.net.http.*
def http = new HTTPBuilder('http://httpbin.org/get')
println "Ready."
In Android Studio, there is a specific file (src/org/luaj/vm2/lib/jse/JavaMethod.java) that I need to overwrite from a package that is pulled in via Gradle (dependencies {compile 'org.luaj:luaj-jse:3.0.1'}).
I copied the file into my source directory with the exact same path and made my changes to it. This was working fine for an individual JUnit test case that was using it. It also looks like it is working for a normal compile of my project (unable to easily confirm at the moment).
However, when I try to run all my tests at once via a configuration of ProjectType="Android Tests", I get Error:Error converting bytecode to dex:
Cause: com.android.dex.DexException: Multiple dex files define Lorg/luaj/vm2/lib/jse/JavaMethod$Overload;.
Is there a specific task or command that I need to add to my Gradle file to make sure the project selects the file in my local source directory? I tried the Copy task and the sourceSets->main->java->exclude command, but neither seemed to work (I may have done them wrong). I also tried the "exclude module/group" directive under "compile" from this post.
The non-default settings for the Run/Debug Confirmation:
Type=Android Tests
Module=My module
Test: All in package
Package: "test"
All my JUnit test cases are in the "test" package.
Any answer that gets this to work is fine. If not Gradle, perhaps something in the android manifest or the local source file itself.
[Edit on 2016-07-24]
The error is also happening on a normal compile when my android emulator is running lower APIs. API 16 and 19 error out, but API 23 does not.
issue: when linking your app the linker finds two versions
org.luaj:luaj-jse:3.0.1:org.luaj.vm2.lib.jse.JavaMethod and
{localProject}:org.luaj.vm2.lib.jse.JavaMethod
howto fix: tell gradle to exclude org.luaj:luaj-jse:3.0.1:org.luaj.vm2.lib.jse.JavaMethod from building
android {
packagingOptions {
exclude '**/JavaMethod.class'
}
}
I have not tried this with "exclude class" but it works for removing duplicate gpl license files a la "COPYING".
If this "exclude" does not work you can
download the lib org.luaj:luaj-jse:3.0.1 to the local libs folder,
open jar/aar with a zip-app and manually remove the duplicate class.
remove org.luaj:luaj-jse:3.0.1 from dependencies since this is now loaded from lib folder
I am not completely sure I understand your problem; however, it sounds like a classpath ordering issue, not really a file overwrite one.
AFAIK, gradle does not make a 'guarantee' on the ordering from a 'dependencies' section, save for that it will be repeatable. As you are compiling a version of file that you want to customize, to make your test/system use that file, it must come earlier in the classpath than the jar file it is duplicated from.
Fortunately, gradle does allow a fairly easy method of 'prepending' to the classpath:
sourceSets.main.compileClasspath = file("path/to/builddir/named/classes") + sourceSets.main.compileClasspath
I don't know enough about your system to define that better. However, you should be able to easily customize to your needs. That is, you can change the 'compile' to one of the other classpath (runtime, testRuntime, etc) if needed. Also, you can specify the jarfile you build rather than the classes directory if that is better solution. Just remember, it may not be optimal, but it is fairly harmless to have something specified twice in the classpath definition.
This is rather convoluted but it is technically feasible. However it's not a single task as asked by the poster:
Exclude said dependency from build.gradle and make sure it's not indirectly included by another jar (hint: use ./gradlew dependencies to check it)
create a gradle task that downloads said dependency in a known folder
unpack such jar, remove offending .class file
include folder as compile dependency
If it's safe to assume that you're using Linux/Mac you can run a simple command line on item 3, it's only using widely available commands:
mkdir newFolder ; cd newFolder ; jar xf $filename ; rm $offendingFilePath
If you don't care about automatic dependency management you can download the jar file with curl, which I believe to be widely available on both linux and mac.
curl http://somehost.com/some.jar -o some.jar
For a more robust implementation you can substitute such simple command lines with groovy/java code. It's interesting to know that gradle can be seen as a superset of groovy, which is arguable a superset of java in most ways. That means you can put java/groovy code pretty much anywhere into a gradle.build file. It's not clean but it's effective, and it's just another option.
For 4 you can have something along either
sourceSets.main.java.srcDirs += ["newFolder/class"]
at the root level of build.gradle, or
dependencies {
. . .
compile fileTree(dir: 'newFolder', include: ['*.class'])
. . .
This is what I ended up adding after Fabio's suggestion:
//Get LUAJ
buildscript { dependencies { classpath 'de.undercouch:gradle-download-task:3.1.1' }}
apply plugin: 'de.undercouch.download'
task GetLuaJ {
//Configure
def JARDownloadURL='http://central.maven.org/maven2/org/luaj/luaj-jse/3.0.1/luaj-jse-3.0.1.jar' //compile 'org.luaj:luaj-jse:3.0.1'
def BaseDir="$projectDir/luaj"
def ExtractToDir='class'
def ConfirmAlreadyDownloadedFile="$BaseDir/$ExtractToDir/lua.class"
def JarFileName=JARDownloadURL.substring(JARDownloadURL.lastIndexOf('/')+1)
def ClassesToDeleteDir="$BaseDir/$ExtractToDir/org/luaj/vm2/lib/jse"
def ClassNamesToDelete=["JavaMethod", "LuajavaLib"]
//Only run if LuaJ does not already exist
if (!file(ConfirmAlreadyDownloadedFile).exists()) {
//Download and extract the source files to /luaj
println 'Setting up LuaJ' //TODO: For some reason, print statements are not working when the "copy" directive is included below
mkdir BaseDir
download {
src JARDownloadURL
dest BaseDir
}
copy {
from(zipTree("$BaseDir/$JarFileName"))
into("$BaseDir/$ExtractToDir")
}
//Remove the unneeded class files
ClassNamesToDelete=ClassNamesToDelete.join("|")
file(ClassesToDeleteDir).listFiles().each {
if(it.getPath().replace('\\', '/').matches('^.*?/(?:'+ClassNamesToDelete+')[^/]*\\.class$')) {
println "Deleting: $it"
it.delete()
}
}
}
}
I'll upload a version that works directly with the jar later.
Another solution if we got then source jar:
task downloadAndCopy {
def downloadDir = "${buildDir}/downloads"
def generatedSrcDir = "${buildDir}/depSrc"
copy {
from(configurations.detachedConfiguration(dependencies.add('implementation', 'xxx:source')))
file(downloadDir).mkdirs()
into(downloadDir)
}
println("downloading file into ${downloadDir}")
fileTree(downloadDir).visit { FileVisitDetails details ->
if (!details.file.name.endsWith("jar")) {
println("ignore ${details.file.name}")
return
}
println("downloaded ${details.file.name}")
def srcFiles = zipTree(details.file).matching {
include "**/*.java"
exclude "**/NeedEclude*java"
}
srcFiles.visit {FileVisitDetails sourceFile ->
println("include ${sourceFile}")
}
copy {
from(srcFiles)
into(generatedSrcDir)
}
}
}
and remember to add depSrc to srcDirs
android {
sourceSets {
`main.java.srcDirs = ['src/main/java', "${buildDir}/depSrc"]
}
}
I have a Gradle-driven project to which I want to add a simple Java task. This task is very specific to the project and if it can be helped I don't want to develop it as a separate plugin. So the question is can I define such custom task within the same build.gradle I'm using for my project? Or is it inevitable that I need to package it as a separate project (plugin) and install to the local repo?
Also it's probably important to note that the original project is not Java related (no other Java code needs to be build)
P.S. Based on comments below:
I would like to add src/main/java/SomeUsefulStuff.java to the existing project and have that file compiled and used as a custom task. I do understand that it needs to be compiled each time I run the build but again - the code will be small. However it will have some external dependencies such as Commons IO
Thanks to RaGe who pointed to JavaExec this turned out to be pretty simple. Here's what you do:
Put your Java code in /src/main/java just as you would in the regular Gradle-driven Java project. Make sure it has main method in the file you are going to call
Add apply plugin: 'java' to the build.gradle
If your Java code has any dependencies on 3rd party libs add these to dependencies section
Add new task section to build.gradle like so:
task usefulStuff(type: JavaExec) {
classpath = sourceSets.main.runtimeClasspath
main = 'com.me.gradle.UsefulStuff'
// arguments to pass to the application
args 'OhmyGod!'
}
Now you can refer to that task as any task in your build. For example imporantTask.dependsOn usefulStuff
I have the following multiproject structure in Gradle:
- root-project
- acceptance-tests
- sub-java-proj
acceptance-tests has a task cucumber (provided by Gradle Cucumber-plugin) which runs the Cucumber features in the project. However, I need to do some bootstrapping before the features are run. In particular, before thecucumber task Gradle should do the following:
Run tests in sub-java-proj and if they pass produce its jar file (if not fail)
Copy the jar file from 1) into the directory accentance-tests/proglib
Run the Cucumber features like normal
Delete the jar file copied in 2)
I'm completely new to Gradle so I am not sure how to properly implement this. My first thought was to make acceptance-tests depend on sub-java-proj (with testRuntime config), but I feel this is an abuse of the dependency mechanism: the only reason to do this is to ensure sub-java-proj is compiled before acceptance-tests is run, but in reality this also messes with the classpath of acceptance-tests by adding the jar from sub-java-proj and there's also problems with transitive dependencies. In short: acceptance-tests should not have anything to do with sub-java-proj besides copying it's jar around.
Suggestions on how to accomplish this are very much appreciated.
Update
So far I have the followingin acceptance-tests/build.gradle:
task fix(dependsOn: ':sub-java-proj:jar', type: Copy) {
from tasks.getByPath(':sub-java-proj:jar')
to 'proglib'
}
task unfix(type: Delete) {
delete fix
}
cucumber.dependsOn fix
cucumber.finalizedBy unfix
However, this works except that unfix deletes the proglib directory too. It should only delete the jar file. Any ideas how to fix this?