Adding project resources to Gradle Custom plugin - gradle

I am building a Custom gradle plugin which when applied to the projects will use the configuration files in the project resources folder to fill some templates and generate some other configuration files.
But when I read the files in my plugin as classpath resources, it fails with cant find the File.
public class VideoBuildPlugin implements Plugin<Project> {
#Override
public void apply(Project target) {
String file = "app/config/dev.yml"; // These files reside in the resources of the project folder
VideoBuildPlugin.class.getClassLoader().getResourceAsStream(file); // This line fails
}
}
Do I have to do, to add the project resources to the classpath of the build plugin to get this working?

You can add explicitly files to the buildscript classpath(in your app where you apply the custom plugin):
buildscript {
dependencies {
classpath files("src/main/resources")
}
}
Although I'm not sure if this is the best approach.
Alternatively:
You can access the "resources" folder in the main sourceSet
def javaPlugin = project.convention.getPlugin(JavaPluginConvention.class)
def mainSourceSet = javaPlugin.sourceSets.getByName("main")
def resources = mainSourceSet.getResources()
resources.srcDirs[0].resolve("app/config/dev.yml")

Related

Gradle7 Version Catalog: How to use it with buildSrc?

I am very excited about the incubating Gradle's version catalogs and have been experimenting with it. I’ve found that the information in my gradle/libs.versions.toml is accessible in the build.gradle.kts scripts for my app and utility-lib projects.
However, I am unable to use the content of the toml file for buildSrc/build.gradle.kts or the convention files.
The only way that I could build was to hard-code the dependencies into those files, as I did before the version catalog feature.
In the buildSrc folder, I created a settings.gradle.kts file and inserted the dependencyResolutionManagement code for versionCatalogs, which is pointing to the same file as for my app and utility-lib projects.
Based on the Gradle7 docs, it seems that sharing a version catalog with buildSrc and modules is possible… I’d appreciate a nudge into getting it to work with buildSrc, if possible.
Here is a simple sample project, which I created via gradle init: my-version-catalog
Thank you for your time and help,
Mike
With Gradle 7.3.3, it is possible. Note version catalogs are GA since Gradle 7.4
The code snippet assumes Gradle is at least 7.4, but if you need them prior that version, insert enableFeaturePreview("VERSION_CATALOGS") at the beginning of each settings.gradle.kts.
Using buildSrc
buildSrc/settings.gradle.kts
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}
buildSrc/build.gradle.kts
dependencies {
implementation(libs.gradleplugin.intellij) // <- the lib reference
}
You can even use the version catalog for plugins
gradle/libs.versions.toml
...
[plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
jetbrains-changelog = { id = "org.jetbrains.changelog", version.ref = "changelog-plugin" }
jetbrains-intellij = { id = "org.jetbrains.intellij", version.ref = "intellij-plugin" }
hierynomus-license = { id = "com.github.hierynomus.license", version.ref = "license-plugin" }
nebula-integtest = { id = "nebula.integtest", version.ref = "nebula-integtest-plugin" }
build.gradle.kts
plugins {
id("java")
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.nebula.integtest)
alias(libs.plugins.jetbrains.intellij)
alias(libs.plugins.jetbrains.changelog)
alias(libs.plugins.hierynomus.license)
}
Note for accessing the catalog within scripts, please refer to the below section, the trick is the same.
Using convention plugins and included build
In the main project include a the Gradle project that holds the convention plugins.
build.gradle.kts
includeBuild("convention-plugins") // here it's a subfolder
convention-plugins/settings.gradle.kts
dependencyResolutionManagement {
repositories {
gradlePluginPortal()
}
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}
rootProject.name = "convention-plugins"
The trick to enable convention plugins to access the version catalog is split in two part, add an ugly implementation dependency that locate where the version catalog generated classes are located.
libs.javaClass.superclass.protectionDomain.codeSource.location
Then in the convention plugin refer to the libs extension via Project::the.
val libs = the<LibrariesForLibs>()
This is tracked by gradle/gradle#15383.
convention-plugins/build.gradle.kts
plugins {
`kotlin-dsl`
}
dependencies {
implementation(libs.gradleplugin.kotlin.jvm)
// https://github.com/gradle/gradle/issues/15383
implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location))
}
And in the actual convention plugin
import org.gradle.accessors.dm.LibrariesForLibs
plugins {
id("org.jetbrains.kotlin.jvm")
}
// https://github.com/gradle/gradle/issues/15383
val libs = the<LibrariesForLibs>()
dependencies {
detektPlugins(libs.bundles.kotlinStuff) // access catalog entries
}
The org.gradle.accessors.dm.LibrariesForLibs class is generated by gradle is somewhere in local gradle folder ./gradle/<version>/dependency-accessors/<hash>/classes
Quick note that older IntelliJ IDEA currently (2022.3) reports alias(libs.gradleplugin.thePlugin) as an error in the editor,
although the dependencies are correctly resolved.
This tracked by KTIJ-19369, the ticket indicates this is actually a bug in Gradle Kotlin DSL gradle/gradle#22797, and someone made a simple IntelliJ IDEA plugin to hide this error until resolved.
Brice, it looks like a can of worms to go down that path, particularly for my situation, where I'm trying to use a libs.version.toml file from an android project, but the custom plugin is of course from a java/kotlin project. I tried creating the libs file by hardwiring the path to the toml file in the custom plugin. It might work if both were java projects, but I never tried that since that's not what I'm after. The ideal solution would be for the plugin to use the libs file from the project it is applied to, but it looks like the version catalog needs to be created in the settings file, before you even have access to "Project", so that's why you would have to hardwire the path.
Short answer. No, but there are other techniques for a custom plugin to get project version data from the project it is applied to.

passing environment variables from build.gradle to custom plugin

I have a section that defines the environment variables in build.gradle and I want to pass this to my custom plugin.
Snippet of build.gradle as below:
apply "myplugin"
ext {
lombokVersion = '1.18.6'
setEnvironmnetVariables = {
environment -> environment.put{'RUNTIME_ENV', 'test')
}
I want to see this RUNTIME_ENV in my plugin 'myplugin'. I am new to this gradle plugin development. Could anyone help me out with this? I am using spring-boot project with groovy.
You can't set environment variables from Gradle nor Java in general.
You can however set dynamic project properties which is one way to convey information to your custom plugin.
Since you're already using the extra propeties, you can just set the values you need directly:
// Root project build.gradle
ext {
lombokVersion = "1.18.6"
RUNTIME_ENV = "test
}
Then your custom plugin, you access them like so:
import org.gradle.api.Plugin;
import org.gradle.api.Project;
public class MyPlugin implements Plugin<Project> {
#Override
public void apply(Project project) {
String runtimeEnv = (String) project.getExtensions().getExtraProperties().get("RUNTIME_ENV");
// do something with variable
}
}
Gradle has great guides on building plugins, give them a look.
https://docs.gradle.org/current/userguide/java_gradle_plugin.html#java_gradle_plugin
https://docs.gradle.org/current/userguide/custom_plugins.html
https://guides.gradle.org/implementing-gradle-plugins/
Additional, if you have Gradle installed locally, you can create a skeleton Gradle plugin project with the init task:
gradle help --task init

Using gradle to programmatically add resource folder to test compile/runtime classpath

We have a master build script for 60+ components. The individual components do not have build.gradle files. What I'm trying to do is programmatically (in the master build.gradle) add a resource folder to certain projects. This resource folder contains a file which must be in the classpath when unit tests are ran. I'm trying to add this in the subprojects block like this:
subprojects { proj ->
...
// this is the folder I need in the test task classpath
def resdir = sprintf("%s\\resources", project(':Common').projectDir)
sourceSets {
test {
java {
srcDir 'test'
}
resources {
srcDirs = [resdir]
}
}
}
}
...
if(proj.name == "APROJECT"){
proj.tasks['test'].getClasspath().each {
logger.info "COMPILE CLASSPATH: {}", it
}
}
}
However, if I query the classpath of the test task (see above) I do not see the folder in the classpath. Additionally, of course, the test is failing because the folder is not in the classpath.
If I put the sourceSet update in a build.gradle in the component folder, it works as expected.
Am I doing something wrong? How can I get this folder into the classpath for testing?
I wasn't able to get this to work by dynamically updating the sourceSet, however, I was able to get it to work by adding the necessary resource path to the testCompile dependency. See this for adding a class folder to a dependency.
Update: It's still not an ideal solution since the "solution" only adds the class folder to the compile path, it doesn't treat it as a resource (e.g., copy it to the runtime class folder).
Update #2: It's actually working as expected. It turns out that different tests were referencing slightly different resource paths. Adding all resource paths dynamically as noted above works fine!

Depend on Kotlin Multiplatfrom JS Module from JVM

I have a Kotlin Multiplatform project with a common, a JS and a JVM module. The JVM module uses a JavaFX WebView to display a GUI. This GUI however shall be implemented as the JS module. How do I add the JS module as a dependency correctly? I tried
dependencies {
compile project(":myproject-js")
}
however, this does not include the resulting JS files anywhere in the classpath. The JS module does indeed create a JAR file with the required dependencies, but I could not find a way to access them.
I also tried simply copying the JS files into my build output, but they are still ignored:
configurations {
js
}
dependencies {
js project(":myproject-js")
}
project.afterEvaluate {
build.dependsOn copyResources
}
task copyResources(type: Copy) {
dependsOn configurations.js
into file("${project.buildDir}/resources")
from {
configurations.js.asFileTree.each {
from (zipTree(it))
}
null
}
}
Is there a way to achieve this?
Here's what should work:
Create a configuration for the myproject-js dependency:
configurations {
js
}
Add the project dependency to that configuration:
dependencies {
js project(":myproject-js")
}
Add the configuration files to the processResources task with .from(...), and the corresponding build dependency:
processResources {
dependsOn configurations.js
from(configurations.js.collect { zipTree(it) })
}
Then, whenever you build the JVM project, the JS module's files get unpacked into the resources output directory and then packed into the JAR.

Utilize ant in settings.gradle during configuration phase

I want to apply a shared gradle file to my projects settings.gradle. The shared file is located in a jar which must be downloaded and extracted during the configuration phase. This is because is applies a plugin which must be applied in the configuration phase. I found this related question: How to share a common build.gradle via a repository? My preferred way is described in this answer: https://stackoverflow.com/a/39228611/987860
However, this appears to be working in build.gradle only. I tried to move the buildscript block to my settings.gradle.
settings.gradle
buildscript {
ext {
dependencyVersion = '0.1.2'
}
repositories {
maven {
credentials {
username 'user'
password 'password'
}
url 'https://my-private-maven-repo.com'
}
}
dependencies {
classpath "my.group:myartifact:$dependencyVersion"
}
dependencies {
def gradleScripts = new File(rootDir, '/build/gradle')
delete gradleScripts
def jars = configurations.classpath.files as List<File>
ant.unjar(src: jars.find { it.name.matches '.*myartifact.*' }, dest: gradleScripts) {
patternset {
include(name:'*.gradle')
}
}
}
}
apply from: new File(rootDir, '/build/gradle/myscript.gradle')
But this results in the following exception:
FAILURE: Build failed with an exception.
* Where:
Settings file 'settings.gradle' line: 24
* What went wrong:
A problem occurred evaluating settings 'journal'.
> Could not get unknown property 'ant' for object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
BUILD FAILED
Total time: 0.019 secs
Could not get unknown property 'ant' for object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.
Is there any way to utilize ant int the confiuration phase before my settings.gradle is evaluated? I need to have the dependency downloaded and extractet before the to-be-downloaded file gets applied.
This is a really unusual way to do things. I'd really recommend not doing what you're trying to do because it'll make your build much slower than it should be. You're deleting build/gradle and extracting the contents of the plugin's jar on every build.
Everything inside a build.gradle (or settings.gradle) can be put into a plugin and distributed that way. You already have a jar that needs to be downloaded, so converting myscript.gradle into a plugin is very easy to roughly convert.
Put this in src/main/groovy/some/package/MyPlugin.groovy in the project that's producing the plugin jar already:
package some.package
import org.gradle.api.*
class MyPlugin implements Plugin<Project> {
void apply(Project project) {
project.with {
// contents of script
}
}
}
For plugins applied to settings.gradle:
package some.package
import org.gradle.api.*
class MyPlugin implements Plugin<Settings> {
void apply(Settings settings) {
settings.with {
// contents of script
}
}
}
Then you can just add the dependency to the plugin and use apply plugin: some.package.MyPlugin.
There are a lot of other advantages of developing/distributing plugins in this way. You can find more information on plugin development in the Gradle Guides.
Alternatively, if you absolutely must keep the separate .gradle script. If you can serve it separately (outside of the jar), you can do:
apply from: "http://example.com/some/url/myscript.gradle"
The downside with this is that it'll download the file on every build (this is fixed in Gradle 4.2).

Resources