How do I create distributions with different dependencies using Gradle application/distribution plugin? - gradle

I am building a small Kotlin project in IntelliJ, Idea, and trying to figure out how to create multiple tar/zip files with customizations for each OS I want to support.
It seems like the distribution plugin (which is included when you use the application plugin) is the right direction, but I can't seem to figure out how to get it to do what I want.
I have read the documentation on the plugin, which can be found here, but it's not really clear to me how to accomplish what I want to do.
Here is an example build.gradle that shows at least the idea of what I want to do, that is to have a base application setup and then have some minor tailoring for each of the 3 OSs.
For example each of the 3 OSs need a unique version of the SWT library. The macos version needs a specific JVM setting, and for the linux version, I need to tailor the startup script to add some environment variables.
Is this possible with the distribution plugin? If not can someone suggest a different solution?
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.2.61'
}
apply plugin: 'application'
mainClassName = "MainKt"
version '1.0-SNAPSHOT'
repositories {
// my common required repos
}
dependencies {
// my common dependencies
}
distributions {
macos {
contents { from 'src' }
applicationDefaultJvmArgs.push("-XstartOnFirstThread")
dependencies {
implementation "org.eclipse.swt:org.eclipse.swt.cocoa.macosx.x86_64:4.5.2"
}
}
linux {
contents { from 'src' }
dependencies {
implementation "org.eclipse.swt:org.eclipse.swt.gtk.linux.x86_64:4.5.2"
}
startScripts.doLast {
def lines = unixScript.text.readLines()
println lines.add(1, '# add some stuff')
println lines.add(2, '# add some more stuff')
unixScript.text = lines.join("\n")
}
}
windows {
contents { from 'src' }
dependencies {
implementation "org.eclipse.swt:org.eclipse.swt.win32.win32.x86_64:4.5.2"
}
}
}
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
Update
This is what I am doing now, but I would like to improve on this.
I have a variable
def deploy = false
if (!deploy) {
applicationDefaultJvmArgs.push("-XstartOnFirstThread")
dependencies {
implementation "org.eclipse.swt:org.eclipse.swt.cocoa.macosx.x86_64:4.5.2"
}
} else {
dependencies {
implementation "org.eclipse.swt:org.eclipse.swt.gtk.linux.x86_64:4.5.2"
}
startScripts.doLast {
def lines = unixScript.text.readLines()
println lines.add(1, 'export foo=foo')
println lines.add(2, 'export bar=bar')
}
}
Right now I develop on my mac and set deploy to false. When I want to generate the distribution for linux I set deploy to true. I could add more code and do the same thing for windows, but I would like to just generate all the code in one task, and have it in different tar/zip files.

Opal provided some very good suggestions in the question comments that I tried, but in the end, I was not able to really get what I wanted.
Here is my current workaround solution it does pretty much what I was hoping I could have done with the distribution plugin.
Basically I create an osType property and set it's "default" to the os I am developing on, in this case macos. Then I have all my common dependencies in one dependency closure and add in the the os specific stuff in a groovy switch statement. I then just coded up a short shell script that overrides the osType property to each of the os's I want to support and calls the gradle task to build each in turn.
Here is my build.gradle file.
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.2.61'
id 'application'
}
ext {
swtVersion = "4.5.2"
}
ext.osType = project.properties['osType'] ?: 'macos'
println "building for ${osType}"
mainClassName = "MainKt"
repositories {
mavenCentral()
maven { url "http://maven-eclipse.github.io/maven" }
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
}
switch(osType) {
case 'macos':
dependencies {
implementation "org.eclipse.swt:org.eclipse.swt.cocoa.macosx.x86_64:${swtVersion}"
}
applicationDefaultJvmArgs.push("-XstartOnFirstThread")
break
case 'linux':
dependencies {
implementation "org.eclipse.swt:org.eclipse.swt.gtk.linux.x86_64:${swtVersion}"
}
startScripts.doLast {
def lines = unixScript.text.readLines()
println lines.add(1, 'export FOO=foo')
println lines.add(2, 'export BAR=bar')
unixScript.text = lines.join("\n")
}
break
case 'windows':
dependencies {
implementation "org.eclipse.swt:org.eclipse.swt.win32.win32.x86_64:${swtVersion}"
}
}
version "${osType}-1.0-SNAPSHOT"
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
and here is my shell script (buildAll.sh) that I run from the terminal in Idea.
#!/bin/sh
./gradlew -PosType=macos distTar
./gradlew -PosType=linux distTar
./gradlew -PosType=windows distTar
./gradlew -PosType=windows distZip

Related

Two versions of module javafx.base in gradle/JavaFX project

My team is working on a java project. (git repo # https://github.com/RaiderRobotix/Scouting-Client-FX). We are trying to package it using jlink. The following is displayed when running the jlink task (gradlew jlink). I'm using gradle 6.1.1, with jdk 11 on macOS. If you'd like to see our module-info.java, please check the repo. I didn't want to make this question too lengthy.
BTW, I have multiple JDK's on my machine (8,11). Building the project works, but running it with gradlew run does not (I think its an unrelated issue with lombok).
Full error: Error: Two versions of module javafx.base found in ./build/jlinkbase/jlinkjars (Infinite Recharge Client-v4.0.0-alpha.jar and javafx-base-11-mac.jar)
build.gradle
plugins {
id 'java'
id 'idea'
id "org.openjfx.javafxplugin" version "0.0.8"
id 'org.beryx.jlink' version '2.17.2'
id "com.github.johnrengelman.shadow" version "5.2.0"
id 'org.jetbrains.kotlin.jvm' version '1.3.61'
id 'com.github.gmazzo.buildconfig' version '1.6.2'
}
group = "com.github.RaiderRobotix"
version = "v4.0.0-alpha"
repositories {
maven { url 'https://jitpack.io' }
jcenter()
}
java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
jar {
manifest {
attributes (
'Implementation-Title': 'Raider Robotix Scouting Client',
'Implementation-Version': project.version,
'Main-Class': 'org.usfirst.frc.team25.scouting.client.ui.Main'
)
}
from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
}
dependencies {
implementation 'com.github.RaiderRobotix:Scouting-Models:29617b7dcc'
implementation 'com.github.RaiderRobotix:blue-alliance-api-java-library:3.0.0'
implementation 'org.projectlombok:lombok:1.18.10'
annotationProcessor 'org.projectlombok:lombok:1.18.10'
implementation 'com.google.code.gson:gson:2.+'
implementation 'commons-io:commons-io:2.+'
implementation 'org.apache.commons:commons-math3:3.+'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
implementation "org.jetbrains.kotlin:kotlin-reflect:1.3.61"
}
buildConfig {
packageName "com.raiderrobotix"
buildConfigField 'String', 'TBA_API_KEY', project['TBA_API_KEY']
// The escaped quotes here are NEEDED. The plugin copies the TEXT LITERAL given to it. The quotes are part of this.
buildConfigField 'String', 'VERSION', "\"$version\""
}
javafx {
version = "11"
modules = [
'javafx.base',
'javafx.graphics',
'javafx.controls',
'javafx.fxml',
]
}
jlink {
launcher {
name = 'Scouting Client'
}
}
application {
mainClassName = 'org.raiderrobotix.scouting.client/org.raiderrobotix.scouting.client.ui.Main'
}
wrapper {
gradleVersion = '6.1.1'
}
compileKotlin {
kotlinOptions {
jvmTarget = "11"
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "11"
}
}
I had a similar issue, my specific error during jlink was:
Error: Two versions of module javafx.base found in C:\Users\tareh\code\cleopetra\build\jlinkbase\jlinkjars (javafx-base-11.0.2-win.jar and javafx-base-11.0.2-linux.jar)
Execution failed for task ':jlink'.
I got some inspiration from https://github.com/openjfx/javafx-gradle-plugin/issues/65 and changed one of my dependencies in build.gradle:
dependencies {
// Get rid of this
// compile group: 'org.controlsfx', name: 'controlsfx', version: '11.0.3'
// Use this instead
implementation('org.controlsfx:controlsfx:11.0.3') {
exclude group: 'org.openjfx'
}
}
After I did that, the jlink error went away, and also I noticed the linux jars which had been listed in my IntelliJ module dependencies had disappeared (I'm on Windows).
This doesn't address your specific problem, but hopefully this will be helpful to others.
In my case I had to comment jar block in build.gradle to perform jlink command. I guess that problem is related to how plugin 'org.beryx.jlink' resolves modules when jar block is appear.

Adding external source files to a kotlin project

I have Kotlin sources located at, say, repo/project_a/src/. I created a Kotlin Gradle project in IntelliJ IDEA, located at repo/project_b/.... And I can't for the life of me figure out how to add the sources. If I add them through project structure menu it works fine, but as soon as it wants to re-read the gradle file id deletes the structure (It warns as much in the UI).
This is my gradle file:
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.2.70'
}
group 'cli'
version '1.0'
repositories {
mavenCentral()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
}
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
I've tried adding all variations of
sourceSets {
main {
kotlin {
srcDirs += "repo/project_a/"
}
}
}
But it does absolutely nothing.
Any ideas?
The path you are giving to Gradle will compile to the current project path plus "repo/project_a/". Try with:
sourceSets {
main {
kotlin {
srcDirs += "../project_a/"
}
}
}

Kotlin and Gradle - Reading from stdio

I am trying to execute my Kotlin class using the command:
./gradlew -q run < src/main/kotlin/samples/input.txt
Here is my HelloWorld.kt class:
package samples
fun main(args: Array<String>) {
println("Hello, world!")
val lineRead = readLine()
println(lineRead)
}
Here is my build.gradle.kts:
plugins {
kotlin("jvm")
application
}
application {
mainClassName = "samples.HelloWorldKt"
}
dependencies {
compile(kotlin("stdlib"))
}
repositories {
jcenter()
}
The code executes, but the data contained inside the input.txt file is not displayed. Here is the output I get:
Hello, world!
null
I want to be able to execute the gradlew command above and the input.txt stream be redirected to stdio. I can easily do that in C++. Once I compile my .cpp file, I can run:
./my_code < input.txt
and it executes as expected.
How can I achieve the same thing with Kotlin and Gradle?
Update: Based on this answer, I've tried adding this to build.gradle.kts but it is not a valid syntax:
AjahnCharles suggestion about run { standardInput = System.in } is correct, but to port it to kotlin-dsl you need a different syntax.
run in this case is the task name and you configure existing task of application plugin.
To configure existing task in kotlin-dsl you should use one of this ways:
val run by tasks.getting(JavaExec::class) {
standardInput = System.`in`
}
or
val run: JavaExec by tasks
run.standardInput = System.`in`
The upcoming version of Gradle 4.3 should provide API for plugin writers to read user input.
The reason of difference between of Groovy and Kotlin in this case because Groovy uses dynamic types, but in Kotlin you must specify task type to have autocompletion and just to compile config script
I finally settled on this (Gradle 7.1.1):
plugins {
application
}
tasks.getByName("run", JavaExec::class) {
standardInput = System.`in`
}
I don't know enough Kotlin yet to judge whether this is equivalent to https://stackoverflow.com/a/46662535/253921.
Almost, but this doesn't work :'(
In Theory
My understanding: < input.txt sets the standard input for the gradlew process, but by default this is not forwarded to your program.
You want to add this to your build.gradle.kts:
run {
standardInput = System.`in`
}
Sources:
https://discuss.gradle.org/t/why-doesnt-system-in-read-block-when-im-using-gradle/3308/2
https://discuss.gradle.org/t/how-can-i-execute-a-java-application-that-asks-for-user-input/3264
In Practice
These build configs look about the same to me, yet Groovy works and Kotlin doesn't. I'm starting to think that the Gradle Kotlin DSL doesn't support the standardInput term yet :/
Here's my working Groovy version if that's any help:
apply plugin: 'kotlin'
apply plugin: 'application'
buildscript {
ext.kotlin_version = '1.1.4'
repositories {
jcenter()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
repositories {
jcenter()
}
dependencies {
// api => exported to consumers (found on their compile classpath)
// implementation => used internally (not exposed to consumers)
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}
mainClassName = "samples.HelloWorldKt"
run {
standardInput = System.in
}

Gatling running multiple simulations with Gradle plugin

The Gradle plugin (https://github.com/commercehub-oss/gatling-gradle-plugin, v2.1) claims The ability to configure multiple simulations per gradle project
Given the following gradle file:
apply plugin: 'scala'
group '****'
version '1.0-SNAPSHOT'
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.commercehub:gatling-gradle-plugin:2.1'
}
}
repositories {
jcenter()
}
ext {
SCALA_VERSION = "2.11.7"
GATLING_VERSION = "2.2.3"
}
dependencies {
compile "org.scala-lang:scala-library:${SCALA_VERSION}"
testCompile 'com.typesafe:config:1.3.1'
testCompile "io.gatling.highcharts:gatling-charts-highcharts:${GATLING_VERSION}"
testCompile "io.gatling:gatling-test-framework:${GATLING_VERSION}"
}
apply plugin: 'gatling'
import com.commercehub.gradle.plugin.GatlingTask
task loadTest(type: GatlingTask, dependsOn: ['testClasses']) {
gatlingSimulation = 'HealthSimulation,PlaceAttributesSimulation'
jvmOptions {
jvmArgs = [
"-Dlogback.configurationFile=${logbackGatlingConfig()}",
"-Denv=${System.getProperty('env', 'stg')}",
]
minHeapSize = "1024m"
maxHeapSize = "1024m"
}
}
def logbackGatlingConfig() {
return sourceSets.test.resources.find { it.name == 'logback-gatling.xml' }
}
in particular the line gatlingSimulation = 'HealthSimulation,PlaceAttributesSimulation', I would expect both simulation to be run. Why? Because looking at the Gradle plugin, gatlingSimulation is then passed to Gatling -s flag.
Long story short, Gatling -s does not support multiple simulations anymore (see https://github.com/gatling/gatling/issues/363 for details), so I think I am missing a bit in the gradle plugin I am using to enable multiple simulations.
Both simulations run fine when run individually, but if I try to run them together, Gatling stucks asking which one to execute. Any suggestion how to achieve running multiple simulations (sequentially) in the same gradle project?
EDIT: my currently ugly workaround (suggestions welcome to make it less ugly, but still workaround imo) is to modify the Gradle build file with:
task loadTests;
fileTree(dir: 'src/test').include("**/*Simulation.scala").each {target ->
ext {
filename = target.toString()
filename = filename.substring(filename.indexOf("scala") + "scala".length() + 1)
filename = filename.substring(0, filename.indexOf(".")).replaceAll("/", ".")
}
task("loadTest${filename}", type: GatlingTask, dependsOn: 'testClasses', description : "Load test ${filename}s") {
gatlingSimulation = filename
jvmOptions {
jvmArgs = [
"-Dlogback.configurationFile=${logbackGatlingConfig()}",
"-Denv=${System.getProperty('env', 'stg')}",
]
minHeapSize = "1024m"
maxHeapSize = "1024m"
}
}
loadTests.dependsOn("loadTest${filename}")
}
def logbackGatlingConfig() {
return sourceSets.test.resources.find { it.name == 'logback-gatling.xml' }
}
Basically, I create a GatlingTask for each *Simulation.scala file (naming convention) and then create a bulk loadTests task that depends on all.

avro gradle plugin sample usage

I am trying to use the avro-gradle-plugin on github, but have not gotten any luck getting it to work. Does anyone have any sample code on how they get it to work?
I figured out how to do it myself. The following is a snippet that I would like to share for people who might run into the same issues as I did:
apply plugin: 'java'
apply plugin: 'avro-gradle-plugin'
sourceCompatibility = "1.6"
targetCompatibility = "1.6"
buildscript {
repositories {
maven {
// your maven repo information here
}
}
dependencies {
classpath 'org.apache.maven:maven-artifact:2.2.1'
classpath 'org.apache.avro:avro-compiler:1.7.1'
classpath 'org.apache.avro.gradle:avro-gradle-plugin:1.7.1'
}
}
compileAvro.source = 'src/main/avro'
compileAvro.destinationDir = file("$buildDir/generated-sources/avro")
sourceSets {
main {
java {
srcDir compileAvro.destinationDir
}
}
}
dependencies {
compileAvro
}
I found "com.commercehub.gradle.plugin.avro" gradle plugin to work better.
use the folllowing:
// Gradle 2.1 and later
plugins {
id "com.commercehub.gradle.plugin.avro" version "VERSION"
}
// Earlier versions of Gradle
buildscript {
repositories {
jcenter()
}
dependencies {
classpath "com.commercehub.gradle.plugin:gradle-avro-plugin:VERSION"
}
}
apply plugin: "com.commercehub.gradle.plugin.avro"
more details at https://github.com/commercehub-oss/gradle-avro-plugin
When evaluating a plugin the following questions needs to be asked:
Are generated files included into source jar?
Is plugin fast? Good plugin use avro tools api instead of forking VM for every file. For large amount of files creating VM for every file can take 10min to compile.
Do you need intermediate avsc files?
Is build incremental (i.e. do not regenerate all files unless one of the sources changed)?
Is plugin flexible enough to give access to generated schema files, so further actions, such as registration schema in schema repository can be made?
It is easy enough to implement without any plugin if you are not happy with plugin or need more flexibility.
//
// define source and destination
//
def avdlFiles = fileTree('src/Schemas').include('**/*.avdl')
// Do NOT generate into $buildDir, because IntelliJ will ignore files in
// this location and will show errors in source code
def generatedJavaDir = "generated/avro/java"
sourceSets.main.java.srcDir generatedJavaDir
//
// Make avro-tools available to the build script
//
buildscript {
dependencies {
classpath group:'org.apache.avro', name:'avro-tools' ,version: avro_version
}
}
//
// Define task's input and output, compile idl to schema and schema to java
//
task buildAvroDtos(){
group = "build"
inputs.files avdlFiles
outputs.dir generatedJavaDir
doLast{
avdlFiles.each { avdlFile ->
def parser = new org.apache.avro.compiler.idl.Idl(avdlFile)
parser.CompilationUnit().getTypes().each { schema ->
def compiler = new org.apache.avro.compiler.specific.SpecificCompiler(schema)
compiler.compileToDestination(avdlFile, new File(generatedJavaDir))
}
}
}
}
//
// Publish source jar, including generated files
//
task sourceJar(type: Jar, dependsOn: buildAvroDtos) {
from sourceSets.main.allSource
// Package schemas into source jar
into("Schemas") { from avdlFiles }
}
// Clean "generated" folder upon "clean" task
clean {
delete('generated')
}
Configuration for avro with gradle as build tool need to add along with applying java plugin.
below changes in settings.gradle
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
}
}
below changes in build.gradle
plugins {
id "com.github.davidmc24.gradle.plugin.avro" version "1.3.0"
}
repositories {
mavenCentral()
}
dependencies {
implementation "org.apache.avro:avro:1.11.0"
}
generateAvroJava {
source("${projectDir}/src/main/resources/avro")//sourcepath avrofile
}
if you want to generate setter methods too add this task too in build.gradle
avro {
createSetters = true
}
link for reference

Resources