I want to debug some JVM instances that are running at the same time. I know that I can run gradle using --debug-jvm so that the JVM will wait until I start the IDE debugger so that it connects to the JVM but it uses port 5005 by default. That's fine for debugging one instance of JVM... but if I want to debug more than one instance, I'll need to define a different port from 5005. How can I achieve this with gradle?
In my case I wanted to debug a specific file, so I included the following code in build.gradle:
task execFile(type: JavaExec) {
main = mainClass
classpath = sourceSets.main.runtimeClasspath
if (System.getProperty('debug', 'false') == 'true') {
jvmArgs "-Xdebug", "-agentlib:jdwp=transport=dt_socket,address=8787,server=y,suspend=y"
}
systemProperties System.getProperties()
}
and I can run with:
gradle execFile -PmainClass=com.MyClass -Dmyprop=somevalue -Ddebug=true
The custom execFile task receives:
-PmainClass=com.MyClass: the class with the main method I want to execute (in the script, main = mainClass)
-Dmyprop=somevalue: a property whose value be retrieved in the application calling System.getProperty("myprop") (in the script, systemProperties System.getProperties() was needed for that)
-Ddebug=true: a flag to enable debugging on port 8787 (in the script, see the if condition, and also address=8787, but the port could be changed, and this flag name also could be changed). Using suspend=y the execution is suspended until the debugger is attached to the port (if you don't want this behaviour, you could use suspend=n)
For your use case, you could try to apply the logic behind the line jvmArgs ... to your specific task (or use tasks.withType(JavaExec) { ... } to apply to all tasks of this type).
Using this solution, don't use the --debug-jvm option because you may receive an error about the property jdwp being defined twice.
Update (2020-08-10)
To make sure that the code runs only when I execute the task execFile explicitly (so as to not run when I just build gradle, for example), I changed the code to:
task execFile {
dependsOn 'build'
doLast {
tasks.create('execFileJavaExec', JavaExec) {
main = mainClass
classpath = sourceSets.main.runtimeClasspath
if (System.getProperty('debug', 'false') == 'true') {
jvmArgs "-Xdebug", "-agentlib:jdwp=transport=dt_socket,address=*:8787,server=y,suspend=y"
}
systemProperties System.getProperties()
}.exec()
}
}
See more at: Run gradle task only when called specifically
You could modify GRADLE_OPTS environment variable and add standard Java debugger syntax e.g. to use port 8888:
-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8888
Option 1 - Directly pass the JVM arguments that start up the debugger
task exampleProgram(type: JavaExec) {
classpath = sourceSets.main.runtimeClasspath
description = "Your Description"
main = 'Example.java' // <package>.<path>.<to>.<YourMainClass>.java
// Change `1805` to whatever port you want.
jvmArgs=["-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=1805"]
}
If it doesn't work right away, try stopping all existing Daemons with gradle --stop so Gradle isn't influenced by any past settings when building/running your project.
Option 2 - Use Gradle's debugOptions object
Alternatively, according to Gradle's documentation, the following should also do the trick; however, it didn't work for me. I'm including it for completeness and in the hope that it works in the future.
task runApp(type: JavaExec) {
...
debugOptions {
enabled = true
port = 5566
server = true
suspend = false
}
}
References:
JVM Debugger Args: https://docs.oracle.com/cd/E19146-01/821-1828/gdabx/index.html
Similar question: how to debug spring application with gradle
Related
I have a test that correctly fails with an InaccessibleObjectException when I run it with JVM args --illegal-access=deny in Eclipse. I want it to fail the same way when I run gradle check.
I tried the solution from How to pass args to JVM which runs tests with Gradle:
# build.gradle
apply plugin: 'java'
test {
jvmArgs '--illegal-access=deny'
# also tried
# jvmArgs('--illegal-access', 'deny')
# jvmArgs '-Dillegal-access=deny'
}
The test passed instead of failing. I did see tests saying they were dirty because jvmArgs had changed.
Here's the JUnit test that fails to fail. Sorry it doesn't have an "expectedException" set up, but it does throw when run with --illegal-access=deny from Eclipse.
import static org.junit.Assert.fail;
import java.lang.reflect.Field;
import org.junit.Test;
public class IllegalAccessTest {
#Test
public void testIllegalAccess() throws NoSuchFieldException, SecurityException {
Field libraries = ClassLoader.class.getDeclaredField("loadedLibraryNames");
System.out.println("About to set accessible");
libraries.setAccessible(true);
fail("Should fail before getting here when run with --illegal-access=deny");
}
}
The output from this test when run with Gradle shows -Dillegal-access=deny is getting passed to Gradle, just not causing the test to fail:
Starting process 'Gradle Test Executor 33'. Working directory: xxx Command: /usr/java/jdk-11.0.4/bin/java -Dillegal-access=deny -Dorg.gradle.native=false -javaagent:xxx,jmx=false #/tmp/gradle-worker-classpath17509364376879385105txt -Xmx512m -Dfile.encoding=UTF-8 -Duser.country=US -Duser.language=en -Duser.variant -ea worker.org.gradle.process.internal.worker.GradleWorkerMain 'Gradle Test Executor 33'
Successfully started process 'Gradle Test Executor 33'
x.y.z.IllegalAccessTest > testIllegalAccessQS STANDARD_OUT
About to set accessible
x.y.z.IllegalAccessTest > testIllegalAccessQS FAILED
java.lang.AssertionError: Should fail before getting here when run with --illegal-access=deny
at org.junit.Assert.fail(Assert.java:88)
at x.y.z.IllegalAccessTest.testIllegalAccessQS(IllegalAccessTest.java:36)
The error message when run with Eclipse is the correct
java.lang.reflect.InaccessibleObjectException: Unable to make field private static final java.util.Set java.lang.ClassLoader.loadedLibraryNames accessible: module java.base does not "opens java.lang" to unnamed module #6b9651f3
The documentation for Test tasks reads: List<String> jvmArgs:
The extra arguments to use to launch the JVM for the process. Does not include system properties and the minimum/maximum heap size.
And there is nothing else, which would make sense - therefore this might be:
test.jvmArgs = ["--illegal-access=deny"]
Possibly with or without -- or -. Be aware that JUnit 5 may behave differently.
As discussed in the question comments, a minimal reproducible example would help in tracking down the issue. Given that it wasn’t possible to provide one, maybe it’ll help to have a minimal example that works.
Minimal Working Setup
This is the complete setup (excluding the Gradle 5.6.2 Wrapper files):
.
├── build.gradle
└── src
└── test
└── java
└── IllegalAccessTest.java
build.gradle
plugins {
id 'java'
}
repositories {
jcenter()
}
dependencies {
testImplementation 'junit:junit:4.10'
}
test {
jvmArgs '--illegal-access=deny'
}
src/test/java/IllegalAccessTest.java
exactly the same as given in the question
Testing if it Works
Running ./gradlew test yields the expected java.lang.reflect.InaccessibleObjectException at line 13 of src/test/java/IllegalAccessTest.java:
> Task :test FAILED
IllegalAccessTest > testIllegalAccess FAILED
java.lang.reflect.InaccessibleObjectException at IllegalAccessTest.java:13
1 test completed, 1 failed
FAILURE: Build failed with an exception.
I have used OpenJDK 11.0.5 in this test.
A Note on Different Methods for Changing the Test JVM Arguments
If the following makes a difference (as suggested in this comment)
test.jvmArgs = ["--illegal-access=deny"]
compared to
test.jvmArgs '--illegal-access=deny'
then you may have set JVM args in multiple locations which interfere with each other. The former replaces all previously set JVM args with only --illegal-access=deny while the latter only adds the --illegal-access=deny option.
Please try this. This is how I pass JVM arguments to my tests from gradle and it works.
test {
jvmArgs '-Dillegal-access=deny'
}
I have integration tests setup in my build.gradle file as such:
task integrationSetup(dependsOn: jar, type: Exec) {
workingDir "$projectDir/resources/integration"
commandLine 'sh', './start_service.sh'
}
task testIntegration(dependsOn: integrationSetup, type: Test) {
testClassesDirs = sourceSets.testIntegration.output.classesDirs
classpath = sourceSets.testIntegration.runtimeClasspath
ignoreFailures = true
}
task integrationTearDown(dependsOn: testIntegration, type: Exec) {
workingDir "$projectDir/resources/integration"
commandLine 'sh', './stop_service.sh'
}
testIntegration.mustRunAfter integrationSetup
testIntegration.finalizedBy integrationTearDown
integrationTearDown.mustRunAfter testIntegration
However since upgrading the Gradle Wrapper to version 4+ the tasks no longer execute correctly. The final tear down never runs and the service continues. What has changed between version 3 and 4 to change this behaviour. Pretty upsetting Gradle did this without warning or deprecation notices.
One dumb option is to downgrade the Gradle wrapper version (can confirm this setup still works on 3.1). But that shouldn't be necessary IMO.
UPDATE: Made some changes per user #Opal. However still have issue where if any errors occur during integration tests the final tear down does not run.
> Task :compileTestIntegrationJava
Putting task artifact state for task ':compileTestIntegrationJava' into context took 0.0 secs.
file or directory '/home/project/cleaner/src/testIntegration/java', not found
file or directory '/home/project/cleaner/src/testIntegration/java', not found
Executing task ':compileTestIntegrationJava' (up-to-date check took 0.072 secs) due to:
Output property 'destinationDir' file /home/project/cleaner/build/classes/java/testIntegration has changed.
Output property 'destinationDir' file /home/project/cleaner/build/classes/java/testIntegration/com has been removed.
Output property 'destinationDir' file /home/project/cleaner/build/classes/java/testIntegration/com/project has been removed.
All input files are considered out-of-date for incremental task ':compileTestIntegrationJava'.
file or directory '/home/project/cleaner/src/testIntegration/java', not found
Compiling with JDK Java compiler API.
/home/project/cleaner/src/integration/java/com/project/cleaner/CleansRequestsTests.java:415: error: reached end of file while parsing
}
^
1 error
:compileTestIntegrationJava (Thread[Daemon worker Thread 8,5,main]) completed. Took 0.162 secs.
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':compileTestIntegrationJava'.
> Compilation failed; see the compiler error output for details.
* Try:
Run with --stacktrace option to get the stack trace. Run with --debug option to get more log output.
BUILD FAILED in 8s
8 actionable tasks: 8 executed
Stopped 0 worker daemon(s).
In discussion it turned out that OP wants to stop the service started before running test no matter what e.g. compilation errors. It can be done with the following script:
ext.integrationTearDown = {
workingDir "$projectDir/resources/integration"
commandLine 'sh', './stop_service.sh'
}
task(type: Exec, 'stop_service', integrationTearDown)
gradle.buildFinished {
exec integrationTearDown
}
testIntegration.dependsOn integrationSetup
testIntegration.finalizedBy stop_service
With this piece of code the service will be stopped after every build - event if it succeeds. To avoid this behaviour BuildResult which is passed to buildFinished may be used to determine the required behaviour.
To run my application from the command line, I run:
java -Dconfig.file=./config/devApp.config -jar ./build/libs/myJar.jar
and inside my code, I have:
String configPath = System.getProperty("config.file");
Which gets the property just fine. However, when I try to debug using the built in debug Netbeans task, the property is null. The output of my run is:
Executing: gradle debug
Arguments: [-Dconfig.file=./config/devApp.config, -PmainClass=com.comp.entrypoints.Runner, -c, /home/me/Documents/projects/proj/settings.gradle]
JVM Arguments: [-Dconfig.file=./config/devApp.config]
Which is coming from:
I set it in both the arguments and JVM arguemtns to see if either would set it. Regardless of what I do, it is null. Can someone help me figure out how to set the system property so my app can get it?
You are setting the property on the Gradle JVM which has almost nothing to do with the JVM your application runs in. If you want to use Gradle to start your app for debugging, you have to tweak your Gradle build file to set or forward the system property to the debug task.
Assuming the debug task is of type JavaExec this would be something like
systemProperty 'config.file', System.properties.'config.file'
in the configuration of your debug task to forward what you set up in the "JVM Arguments" field in Netbeans.
It seems that the "Arguments (each line is an argument):" and "JVM Arguments (each line is an argument):" fields provide values to the Gradle task itself. How I managed to pass properties over to the application was to append them to the jvmLineArgs argument (see image).
My application is now receiving the profiles property.
Thanks to #Vampire for the "guess work", lol!
My goal is to develop a Gradle script that starts my Wildfly before the tests start running, and stop it after the tests complete, this way, Selenium tests can run.
To achieve this goal, I've decided to do the following at my build.gradle:
Before the test (test.doFirst)
Check if JBOSS_HOME environment variable exists;
If it exists, I run the following to start my Wildfly:
ext.jbossHome = System.getenv('JBOSS_HOME')
ext.isWildflyAvailable = (jbossHome != null)
task startWildfly(type:Exec) {
if (!isWildflyAvailable) {
return;
}
println 'Starting Wildfly...'
println 'JBOSS_HOME: ' + jbossHome
workingDir = file(jbossHome + '\\bin')
commandLine = ['cmd', '/C', 'standalone.bat']
}
test.doFirst {
startWildfly.execute()
}
// Ommited logic for stopping Wildfly
My Wildfly starts as I can read the log on console, but after it's startup, Gradle stuck on it and never proceed with the rest of the build.
Trying to avoid that, I appended an & at the end of the command line, as I would do if I were starting my server manually on console, but Gradle started to raise erros, in both attempts:
commandLine = ['cmd', '/C', 'standalone.bat &']
commandLine = ['cmd', '/C', 'standalone.bat', '&&']
After some googling, I found something about running the commandLine on a different thread, but, I will lost track of the process and won't be able to know when my Wildfly started.
Is there another alternative?
What is a clean and elegant way to copy a bunch of files via scp with Gradle?
Two ways I currently see are:
Using Apache Wagon, as described here: http://markmail.org/message/2tmtaffayhq25g4s
Executing scp via command line with the Exec task
Are there any better (more obvious) ways to approach this?
A few years after the original question, I like the Gradle SSH Plugin. A small quote of its extensive documentation:
We can describe SSH operations in the session closure.
session(remotes.web01) {
// Execute a command
def result = execute 'uptime'
// Any Gradle methods or properties are available in a session closure
copy {
from "src/main/resources/example"
into "$buildDir/tmp"
}
// Also Groovy methods or properties are available in a session closure
println result
}
Following methods are available in a session closure.
execute - Execute a command.
executeBackground - Execute a command in background.
executeSudo - Execute a command with sudo support.
shell - Execute a shell.
put - Put a file or directory into the remote host.
get - Get a file or directory from the remote host.
...and allows for, for example:
task deploy(dependsOn: war) << {
ssh.run {
session(remotes.staging) {
put from: war.archivePath.path, into: '/webapps'
execute 'sudo service tomcat restart'
}
}
}
From a project of mine that I use to SCP files to an EC2 server.
The jar files there are local files that are part of my project, I forget where I got them from. There's probably a more concise way of doing all this, but I like to be very explicit in my build scripts.
configurations {
sshAntTask
}
dependencies {
sshAntTask fileTree(dir:'buildSrc/lib', include:'jsch*.jar')
sshAntTask fileTree(dir:'buildSrc/lib', include:'ant-jsch*.jar')
}
ant.taskdef(
name: 'scp',
classname: 'org.apache.tools.ant.taskdefs.optional.ssh.Scp',
classpath: configurations.sshAntTask.asPath)
task uploadDbServer() {
doLast {
ant.scp(
file: '...',
todir: '...',
keyfile: '...' )
}
}