As part of writing a Gradle plugin for Flyway, we stumbled upon a problem when dealing with Java migrations.
What is the best way to provide a Gradle plugin access on its classpath to the compiled classes of the project so that it can load and execute them?
So the situation is that we have a plugin that adds a task that wants to execute code contained in the project that the plugin is applied to. In this case, the task (class) should have an input property of type Iterable<File> that gets configured (by the plugin) with the class path of the code to be executed (e.g. sourceSets.main.runtimeClasspath). The task can then choose between the following ways to execute the code:
The task uses project.javaexec {} to execute the code in a separate JVM. If the code isn't directly executable, the task may need to inject some bootstrap code onto the javaexec class path. A potential alternative to using project.javaexec is to use a JavaExec task in the first place.
The task creates a new class loader, populates it with the class path, loads and instantiates the classes that serve as the entry point(s) to the API, and makes use of them as appropriate. If the task is written in Groovy, it can leverage duck typing, and no reflective code will be necessary beyond creating the entry points.
Related
I have a Gradle plugin that works well so far, however, I am facing an issue in a multi-module project.
The plugin runs its task in every module of the project, but sometimes, I need to access classes from other modules to analyze them - typically, in the context of hexagonal architecture, I want to perform some checks on classes in infrastructure that implement an interface defined in domain module.
So, when the task runs (after the tests) within infrastructure module, it finds the class. But it's not able to load the interface (from domain) that the class implements. When I do :
Object interfaceLoadedDynamically = Class.forName(i.getName());
I clearly see it can't be loaded, and that prevents me from performing the checks I need to do.
here are :
the plugin
the task
Is there a way to configure one of them to say : "when you run within a module, make sure all the other project modules that have been compiled before are available in the classpath" ?
I've seen maybe I could use Gradle's Configuration , but I am really not sure where/how to start.
I usually define tasks in Gradle (using Groovy) like tasks.withType(Type); e.g.: tasks.withType(JavaCompile), tasks.withType(Test), etc.
Now, I want to do the same with some provided Spring Boot tasks, namely: bootRun and bootStartScripts, but Gradle cannot find it.
I know it's silly and I could get away just by using bootRun and bootStartScripts, but I would like to understand why those cannot be configured/defined in such way.
I guess with define you mean configure, because withType can only be used to configure existing tasks. It takes a task type (a class) and a closure that can be used to configure all available tasks of that type. This needs to be considered, because a project may contain multiple tasks of the same type that should actually do completely different things. Whether to configure all those tasks or just a specific one is important!
To pass the task type to the method withType you need to know the name of the class implementing the task type. This name is not necessarily related to the name(s) of the actual task(s). For the tasks test and compileJava of the Gradle Java Plugin those classes are org.gradle.api.tasks.testing.Test and org.gradle.api.tasks.compile.JavaCompile. Since those classes are provided by Gradle, they are automatically imported and can be referenced via their simple names Test and JavaCompile. But the Spring Boot Plugin is a third-party plugin, so the classes need to be referenced by their full names.
The task bootStartScripts from your question is of type CreateStartScript, that is provided by Gradle. Therefore it can be configured like this:
tasks.withType(CreateStartScripts) {
// configure
}
The task bootRun is of type org.springframework.boot.gradle.tasks.run.BootRun, that is provided by the Spring Boot Plugin. So you need to specify the full name:
tasks.withType(org.springframework.boot.gradle.tasks.run.BootRun) {
// configure
}
When working with gradle multimodule project, is it possible to define functions in parent project but use them in submodules build.gradle.kts?
Note i do not need untyped tasks registered and called with strings... I want actual typesafe code to be shared to submodules.
Ideally without creating a plugin or using buildSrc.
Whats the most to the point way to share a class from parents build.gradle.kts to all submodules?
NOTE : this is not the same as sharing closure trough ext... you loose type safety, what i ask for is Type safety on submodule side.
I'm thinking that there is no way. When a Kotlin build script gets compiled, it will end up as a class called Build_gradle in the default package (i.e. empty package) that extends CompiledKotlinBuildScript. Any class that is defined within a script becomes a public nested class in its corresponding Build_gradle. When the subproject build script gets compiled, it has no access to the classpath that contains the parent projects build script. This makes sense, as there would be multiple Build_gradle files in the same (default) package.
I'd go for buildSrc, to solve the problem, but I'm speculating that what you also wanted was some sort of nice separation of concerns in a multimodule project, not having unrelated projects knowing about what others need to communicate. What you could do to minimize the exposure is to only keep the API (interfaces, data classes) in buildSrc and have a script plugin in a parent project provide the implementation.
I have a file MyTaskListeners.gradle located somewhere and in each of my projects I want to import this file and only add the desired listeners.
E.g. MyTaskListeners.gradle might have TimingsListener, FlowListener, SomeOtherListener1, SomeOtherListener1, ... and in a certain projects build.gradle i would like you write something like:
apply from: 'utils.gradle'
gradle.addListener new TaskFlowListener()
How can I get the classes from MyTaskListeners.gradle to be available and known in the projects build.gradle? With what I have written above the classes are not known
unable to resolve class TaskFlowListener
Classes (and even methods for that matter) defined in script plugins are not directly accessible outside of that individual script. If you have utility classes that you want on your build script classpath and want to avoid packaging/publishing them as an individual project, you can use the buildSrc project.
I'm having ordering issues with Gradle task configuration.
I'm writing a plugin that creates a task based on information that is only available when supplied via the build script. I can create the task in the plugin's apply() method, and configure it in its doFirst() method (by which time the necessary information is available). However, I also want to make sure that the task is only executed when necessary via the inputs/outputs properties.
This is problematic, because I have to do this in the apply() method (as far as I can tell), but at that point the information required to specify the inputs/outputs property values isn't available.
I tried using a convention object but that's not available under after apply() has completed.
Is there any way around this that won't make me feel dirty?
For those parts of the task configuration that need to be deferred, you can either use a hook like project.afterEvaluate {} or gradle.projectsEvaluated {}, or use convention mapping. The latter is used extensively by Gradle's own plugins, but isn't currently considered a public API.