I am attempting to configure a gradle (version 4.6) build (as a part of a multi-module project) that uses a Java "script" to generate resources into the main sourceSet, which are then referenced in my test configuration. The idea is that I'd ultimately like to create a jar that is simply a resource bundle for inclusion in another module of my build, but I have java files both to generate these resources and to execute verification tests on them before packaging.
I currently have three sourceSets configured: the standard "main" and "test", and a custom sourceSet "generator" which holds resources used as inputs to the generator "script" and the source for the generator script itself. I've registered a main output directory according to this documentation (see "working with generated resources"), pointing to a JavaExec class that runs the generator with the "generator" sourceSet runtime classpath to output resources into the main classpath.
All of this appears to work- I can find the output in the correct directory when running :<module>:build, showing that the script is both running properly and that it is done as a dependency to the main compile task. However, when I try to reference the generated output in the tests with getClass().getClassLoader().getResource("<baseGeneratedOutputDirectory>"), I get a null value back, suggesting that my generated output is not being included in the test runtime classpath. The documentation clearly states that the...
Java plugin will use those dirs in calculating class paths and for jarring the content
...so I'm not sure why my files aren't getting picked up. Below is my abridged build.gradle file. Note that I'm overriding the generator task type in order to set it up with build caching, but I still see this bug with caching turned off.
apply plugin: 'java-library'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
sourceSets {
generator {
java.srcDirs = ['src/generator/java']
resources.srcDirs = ['src/generator/resources']
}
main {
java
resources
output.dir("$buildDir/generated-files/main", builtBy: 'generateConfig')
}
test {
java
resources
}
}
dependencies {
api project(':server:server_protobuf_classes');
api project(':common:game-config-util')
api 'com.google.protobuf:protobuf-java:3.5.1'
generatorImplementation project(':common:game-config-util')
generatorImplementation project(':server:server_protobuf_classes');
generatorImplementation group: 'commons-io', name: 'commons-io', version: '2.5'
// ...
testCompile group: 'junit', name: 'junit', version: '4.11'
testCompile group: 'org.hamcrest', name: 'hamcrest-all', version: '1.3'
testCompile group: 'commons-collections', name: 'commons-collections', version: '3.2.2'
// ...
}
task generateConfig(type: GenerateConfig) {
outputs.dir("$buildDir/generated-files/main")
classpath sourceSets.generator.runtimeClasspath
main = "com.project.ProtobufConfigurationProcessor"
}
#CacheableTask
class GenerateConfig extends JavaExec {
}
EDIT: Adding the following makes the tests pass, but I'm confused why I'd need to manually configure the test resource directory like this. Shouldn't the test task pick up build output from the main source set by default?
sourceSets {
test {
resources.srcDirs = ["$buildDir/generated-files/main"]
}
}
Related
I have a java project using gradle as my build system. I am developing in VSCode using the Java extension by Redhat. I am developing on Ubuntu 20.04 with openjdk 11.
When I build the project from the command line using './gradlew assemble', I don't get any build error.
But when I open the project in vscode, I get the following problem in the problems view...
The project was not built since its build path is incomplete. Cannot find the class file for javax.servlet.http.HttpServletResponse. Fix the build path then try building this project
and...
The type javax.servlet.http.HttpServletResponse cannot be resolved. It is indirectly referenced from required .class files
My build.gradle is as follows....
plugins {
id 'com.google.cloud.tools.jib' version '2.4.0'
}
apply plugin: 'java'
// Use maven repository
repositories {
mavenCentral()
google()
maven { url 'https://jitpack.io' }
}
dependencies {
implementation 'org.eclipse.jetty:jetty-servlet:9.4.19.v20190610'
implementation 'org.eclipse.jetty:jetty-client:9.4.19.v20190610'
implementation group: 'org.eclipse.jetty', name: 'jetty-util', version: '9.4.29.v20200521'
implementation 'mysql:mysql-connector-java:8.0.16'
implementation 'com.google.cloud.sql:mysql-socket-factory-connector-j-8:1.0.16'
implementation 'org.apache.commons:commons-dbcp2:2.7.0'
implementation 'org.jdbi:jdbi3-core:3.10.1'
implementation 'com.google.firebase:firebase-admin:6.11.0'
implementation 'com.google.apis:google-api-services-oauth2:v1-rev155-1.25.0'
implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3'
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.0.1'
implementation group: 'org.reflections', name: 'reflections', version: '0.9.12'
implementation 'com.github.bhlangonijr:chesslib:1.2.3'
// compile project('chesslib')
//runtime files('../../chesslib/build/libs/chesslib-1.1.12.jar')
}
task runServer(type: JavaExec) {
enableAssertions = true
classpath = sourceSets.main.runtimeClasspath
main = 'ChessServer'
}
task runClient(type: JavaExec) {
classpath = sourceSets.main.runtimeClasspath
main = 'ChessClient'
standardInput = System.in // without this, using Scanner on System.in won't work
}
task runCLITestApp(type: JavaExec) {
classpath = sourceSets.main.runtimeClasspath
main = 'CLITestApp'
standardInput = System.in
}
// use cloudBuilder.py to invoke this jib task to build the container
// use cloudConfiguration.json to configure things like container version.
jib {
if (project.hasProperty('googleCloudProjectName') && project.hasProperty('imageName')) {
to {
image = "gcr.io/$googleCloudProjectName/$imageName"
}
container {
mainClass = 'ChessServer'
ports = ['8080']
creationTime = "USE_CURRENT_TIMESTAMP"
}
}
}
// make sure the required project properties are set for jib
task jibCheck {
doLast {
if (!project.hasProperty('googleCloudProjectName'))
throw new GradleException("When invoking the jib task, make sure to pass the cli arg -PgoogleCloudProject=[myGoogleCloudProject]\nThis should be the name of the google cloud project which container registry you want to push to.");
if (!project.hasProperty('imageName'))
throw new GradleException("When invoking the jib task, make sure to pass the cli arg -PimageName=[imageName]");
}
}
tasks.jib.dependsOn tasks.jibCheck
// src/main/jib is the default folder that jib looks for files to add to your container
// these files will be placed in the root / dir of the container
task moveFilesForJib(type: Copy) {
from "releaseFiles"
into "src/main/jib"
}
tasks.jib.dependsOn tasks.moveFilesForJib
Add the following to build.gradle. After rebuilding the project, see if the error goes away.
compileOnly group: 'javax.servlet', name: 'javax.servlet-api', version: '4.0.1'
The solution was to run
./gradlew wrapper
Then in vscode run Java: Clean Workspace.
I would like to create a stand-alone (thin jar) jar without dependencies for Appium test scripts.
I have a Runner class
import org.junit.runner.JUnitCore;
import java.net.MalformedURLException;
public class Runner {
public static void main(String[] args) throws MalformedURLException {
try{
JUnitCore.runClasses(Calculator.class);
}finally {
}
}
}
and
I have a Calculator test class
import java.net.MalformedURLException;
import java.net.URL;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
//import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
import io.appium.java_client.MobileElement;
import io.appium.java_client.android.AndroidDriver;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class Calculator {
// WebDriver driver;
public AndroidDriver<MobileElement> driver;
#Before
public void setUp() throws MalformedURLException{
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("udid", "ZH33L2Z6KL"); //Give Device ID of your mobile phone
caps.setCapability("platformName", "Android");
caps.setCapability("platformVersion", "6.0.1");
caps.setCapability("automationName", "uiautomator2");
caps.setCapability("skipUnlock","true");
caps.setCapability("appPackage", "com.google.android.calculator");
caps.setCapability("appActivity", "com.android.calculator2.Calculator");
caps.setCapability("noReset","true");
driver = new AndroidDriver<MobileElement>(new URL("http://127.0.0.1:4723/wd/hub"), caps);
}
#Test
public void testCal() throws Exception {
//locate the Text on the calculator by using By.name()
WebElement two=driver.findElement(By.id("digit_2"));
two.click();
WebElement plus=driver.findElement(By.id("op_add"));
plus.click();
WebElement four=driver.findElement(By.id("digit_4"));
four.click();
WebElement equalTo=driver.findElement(By.id("eq"));
equalTo.click();
//locate the edit box of the calculator by using By.tagName()
WebElement results=driver.findElement(By.id("result_final"));
//Check the calculated value on the edit box
assert results.getText().equals("6"):"Actual value is : "+results.getText()+" did not match with expected value: 6";
}
#After
public void teardown(){
//close the app
driver.quit();
}
}
I have gone through one article about ThinJar and hollowJar.
https://dzone.com/articles/the-skinny-on-fat-thin-hollow-and-uber
Questions
How to add Gradle task (in intellij)to build thin jar as per the article?
How to add Gradle task to build 'Hollow' jar as per the article?
If I build a 'fat' jar my jar size is 18mb. How to build skinny or thin jar with less size, and keep dependencies separately?
How to run the created 'skinny' or 'thin' jar in different PC?
The terminology used in your link is a bit strange. With gradle, the "skinny" jar is always built. It is the default artifact, check the build/libs folder. If you apply the application plugin, there is a distribution zip built as well under build/distribution which is pretty much the fat jar (it is a zip of all relevant jars). But by definition you cannot build a fat jar into a smaller size, and you cannot simply run the "skinny" or "thin" jar on the target host.
Running your application always requires just three things:
The compiled artifact of your code - a bunch of .class files corresponding to, and only to, the code you write, usually packaged in a jar format. This is the skinny jar in that terminology. This is also the commonest artifact produced out of a build (any build system, maven, gradle etc) if you don't do anything special.
The library dependencies - all 3rd party jars
The runtime - this usually refers to Java itself.
All of the above need to present on the host where you are about to run your application. Now, what gets a bit complicated is the stuff you actually need to ship to that host (this is called deployment):
Obviously you will need No. 1 shipped to the host
Usually you would expect/assume No. 3 is pre-installed on the host
What about No.2 the 3rd party dependencies? The answer is it depends.
If (some of) these dependencies can be pre-installed on the target
host, you don't need to ship them. In this case usually people would
just call these dependencies as also the "runtime". For example,
Maven is a runtime, so is Gradle. These are, in themselves, Java
libraries to you when you are writing a Maven/Gradle plugin. You
would normally expect people using your code to have maven/gradle
installed already. They run your code through maven/gradle, and
maven/gradle will provide the dependencies your code requires
when running it. This is why in maven this kind of dependencies is
called "Provided". Maven has a dedicated dependency scope for it.
If any of your dependencies is not provided on the target host, you
need to ship it, period.
In Gradle, if you apply the application plugin (which will automatically apply the distribution plugin), you can have both your artifact and your dependencies (exclude java runtime) in a single zip - this is called a distribution.
plugins {
id 'java'
id 'application'
}
Once you build, you will find a zip file under build/distributions. There are two folders if you unzip the file: bin and lib. Within the lib folder sits your jar, and all your dependencies jars. This technically is not a fat jar, because it is not a single jar. But jar-or-not is just a format. Eventually what you are after is just to get your code and dependencies across to the target host. The distribution zip does not mess around with jar because jar-merging is not as simple as folder merges. Instead distribution zip expects you to unzip on the target host and invoke the script under bin folder to start the application.
The following article answers these questions:
Java Build Automation Part 2: Create executable jar using Gradle
https://vocon-it.com/2016/11/15/how-to-build-a-lean-jar-file-with-gradle/
The corresponding sample code is available under:
https://github.com/oveits/gradle-tutorial-build-executable-jar/releases/tag/v1.0
The final build.gradle file is given below:
group 'io.cloudgrey.appiumpro'
apply plugin: 'java'
sourceCompatibility = 1.8
repositories {
maven { url "https://jitpack.io" }
mavenCentral()
maven { url "https://repo.maven.apache.org/maven2" }
}
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
testCompile group: 'log4j', name: 'log4j', version:'1.2.17'
testCompile group: 'io.appium', name: 'java-client', version: '7.3.0'
testCompile group: 'org.seleniumhq.selenium', name: 'selenium-java', version: '3.13.0'
testCompile group: 'org.hamcrest', name: 'hamcrest-library', version: '1.3'
testCompile group: 'com.eclipsesource.minimal-json', name: 'minimal-json', version: '0.9.5'
testCompile group: 'org.java-websocket', name: 'Java-WebSocket', version: '1.3.9'
testCompile group: 'org.zeroturnaround', name: 'zt-exec', version: '1.10'
testCompile group: 'me.xdrop', name: 'fuzzywuzzy', version: '1.2.0'
testCompile group: 'io.appium', name: 'mitmproxy-java', version: '1.6.1'
testCompile group: 'com.applitools', name: 'eyes-appium-java4', version: '4.2.1'
testCompile group: 'com.github.testdotai', name: 'classifier-client-java', version: '1.0.0'
testCompile group: 'com.google.guava', name: 'guava', version: '28.1-jre'
}
test {
outputs.upToDateWhen {false}
useJUnit()
testLogging {
exceptionFormat = 'full'
showStandardStreams = true
}
maxParallelForks = 3
forkEvery = 1
}
task copyJarsToLib (type: Copy) {
def toDir = "build/libs/dependency-jars"
// create directories, if not already done:
file(toDir).mkdirs()
// copy jars to lib folder:
from configurations.compile
into toDir
}
task marshallClasspathDeps(type: Copy) {
from sourceSets.test.runtimeClasspath
// if you need this from the dependencies in the build.gradle then it should be :
// from sourceSets.main.runtimeClasspath
include '**/*.class'
include '**/*.jar'
exclude { FileTreeElement details ->
details.file.name.contains('Edition') || details.file.name.contains('kotlin')
}
into 'build/libs/dependency-jars'
}
build.dependsOn(marshallClasspathDeps)
task buildJar(dependsOn:classes,type: Jar) {
// exclude log properties (recommended)
exclude ("log4j.properties")
// make jar executable: see http://stackoverflow.com/questions/21721119/creating-runnable-jar-with-gradle
manifest {
attributes (
'Main-Class': 'com.example.appium.Runner',
// add classpath to Manifest; see http://stackoverflow.com/questions/30087427/add-classpath-in-manifest-file-of-jar-in-gradle
"Class-Path": '. dependency-jars/' + sourceSets.test.runtimeClasspath.collect { it.getName() }.join(' dependency-jars/')
)
}
baseName = "AppiumTests"
from sourceSets.test.output
include 'com/example/appium/*.class'
}
// always call copyJarsToLib when building jars:
jar.dependsOn copyJarsToLib
build.dependsOn(buildJar)
// END AUTOMATICALLY INSERTED
The test cases can be executed using:
java -jar -Dlog4j.configuration=file:%cd%\log4j.properties build\libs\AppiumTests.jar
I'm restructuring/refactoring build process for a big(ish) project. Currently it contains over a dozen separate modules built with standalone build scripts each. I want to integrate them all into a single multiproject build in Gradle.
After I integrated all sources into a single tree, fixed build.gradles, I came upon the following problem. Dependencies for many modules contain something like:
dependencies {
compile group: 'com.company', name: 'Module', version: '1.2.3'
// ...
testCompile group: 'com.company', name: 'Module', version: '1.2.3', classifier: 'tests'
}
I want the build to use jars from the subproject, not from a repository. I replaced compile ... with compile project(':Module') and it works fine. However, I cannot find the way to pass 'tests' specifier to the testCompile project... dependency.
Is there a way to pick up the tests jar as a dependency to testCompile?
In the producing project you will need to declare the "Test" JAR as outgoing artifact.
configurations {
testUtils
}
task testUtilsJar(type: Jar) {
...
}
artifacts {
testUtils testUtilsJar
}
In the consuming project you depend on it as such:
dependencies {
testCompile project(path: ':Module', configuration: 'testUtils')
}
I'm new to Java world and Gradle. I've made a JSerial lib which will support multiple platforms (Android, Linux and Windows).
To be able to choose the platform I'm targetting, I've defined some sourceSets in my JSerial gradle file:
sourceSets {
windows {
compileClasspath += sourceSets.main.output
runtimeClasspath += sourceSets.main.output
}
linux {
compileClasspath += sourceSets.main.output
runtimeClasspath += sourceSets.main.output
}
}
dependencies {
linuxCompile 'net.java.dev.jna:jna:4.1.0'
linuxCompile 'net.java.dev.jna:jna-platform:4.1.0'
windowsCompile 'net.java.dev.jna:jna:4.1.0'
windowsCompile 'net.java.dev.jna:jna-platform:4.1.0'
}
The default main sourceSets builds the common interface, etc. Then the windows sourceSet will build windows implementation (and same for Linux and Android).
I create a project which uses this library and depends on it using gradle's includeFlat. Here is the dependency part of my gradle file:
dependencies {
compile project(':JSerial')
testCompile group: 'junit', name: 'junit', version: '4.11'
}
This works. But I would like to depends on the "windows" sourceSet, because this project is a windows application. I tried the following:
dependencies {
compile project(':JSerial').sourceSets.windows.output
testCompile group: 'junit', name: 'junit', version: '4.11'
}
But it doesn't work, I have the following error:
Could not find property 'windows' on SourceSet container.
What's wrong ?
PS: If there is a better way to do what I'm trying without using sourceSets, please tell me !
I finally found a solution which I think is elegant. Instead of using sourceSets I used multi-project. Here is my project:
Serial/
build.gradle
src/main/java/com.package/
SerialPort.java
windows/
build.gradle
src/main/java/com.package/
SerialPortWindows.java
Application/
build.gradle
settings.gradle
In my Application's settings.gradle:
includeFlat 'Serial'
includeFlat 'Serial/windows'
In my Application's build.gradle:
dependencies {
project(':Serial/windows')
}
In my Serial/windows's build.gradle (which requires SerialPort interface to compile):
dependencies {
project(':Serial')
}
Then when I build my application, it requires Serial/windows which requires Serial. I think I will be able to defines multiple build.gradle files for my application (for example one for Linux and one for Windows), whith different dependencies.
In gradle you can achieve it using:
apply plugin: 'enhance'
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'org.hibernate:hibernate-gradle-plugin:VERSION'
}
}
dependencies {
compile group: 'org.hibernate.javax.persistence', name: 'hibernate-jpa-[SPEC-VERSION]-api', version: '[IMPL-VERSION]'
compile group: 'org.hibernate', name: 'hibernate-gradle-plugin', version: 'VERSION'
}
What if instead of running the project through Gradle, I want to run my main class directly through Intellij (shift-F10). Is it possible to also perform build-time bytecode instrumentation just before the application run? How should I achieve this?
Hibernate does "bytecode instrumentation" at runtime, so you don't have to do anything special for it to happen.
Actually it is not bytecode instrumentation, which means changing existing classes, but proxying, which means, the existing classes are used by classes that get generated on the fly.