How can I add dependencies that must use plugins and classpath? - android-studio

I am trying to make a bundle-library that contains commonly used library. For example, retrofit2, okhttp, hilt, viewmodel, livedata, navigation, room, etc.
And then, I want to just import,
dependencies{
implementation "com.github.my_account:common-library:1.0.0"
// rest of them...
}
Then, the gradle file will look clean.
However, when the libraries that need to add plugin and classpath seems doesn't work.
For example, hilt need to do...
buildscript {
ext.kotlin_version = '1.4.20'
ext.hilt_version = '2.33-beta' // THIS
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.2.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" // THIS
// ...
}
}
// ..
And
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
...
id 'dagger.hilt.android.plugin' // THIS
}
// ..
dependencies{
// ...
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-compiler:$hilt_version"
}
But when I import implementation "com.github.my_account:common-library:1.0.0" in my gradle(module), it doesn't import hilt libraries, so, IDE can't detect any class or annotation from hilt library. So, I added its classpath in gradle.build(project), and plugin in thee plugins of gradle.build(module). But didn't add it in dependencies in it. Then, this message comes up.
The Hilt Android Gradle plugin is applied but no com.google.dagger:hilt-android dependency was found.
So, what I think of is, if the library needs to write 'classpath' and 'plugin', I am not able to add them in the bundle library.
How can I solve this issue or what's the problem?

Related

Kotlin / Gradle DSL integration to manage dependency version variables

Kotlin / Gradle DSL integration to manage dependency version variables
My question is about the best way to manage dependency version variables in IntelliJ Idea / Android Studio using Gradle
Kotlin DSL
The secondary purpose is to have the IDE perform version upgrades upon clicking the suggestion to change to newer
version automatically instead of manually editing the versioning file entry.
the simple way
definition
build.gradle.kts
plugins {
application
kotlin("jvm") version "1.7.10"
}
dependencies {
implementation("io.ktor:ktor-server-core-jvm:2.1.1")
}
Comments
This is working but "clumsy" and does not comply to single source of truth (SSOT)
I have seen several ways that attempt having a SSOT for the dependency versions
by Project
Definition
gradle.properties
ktor_version = 2.1.0
kotlin_version = 1.7.10
build.gradle.kts
val ktor_version: String by project // NOTE the complaint Property name 'ktor_version' should not contain underscores
val kotlin_version: String by project
plugins {
application
kotlin("jvm") version "1.7.10" // NOTE you cannot replace this with the variable!
// 'val kotlin_version: String' can't be called in this context by implicit receiver. Use the explicit one if necessary
}
dependencies {
implementation("io.ktor:ktor-server-core-jvm:$ktor_version")
}
Comments
This does not update the suggested version numbers via "Project Structure/Suggestions" in AS/IJ
my inline NOTES indicate several issues with this method!
by buildSrc java Module
Definition
object Ktor {
private const val ktorVersion = "2.0.0"
const val core = "io.ktor:ktor-client-core:${ktorVersion}"
const val android = "io.ktor:ktor-client-android:${ktorVersion}"
}
implementation in build.gradle.kts
implementation(Ktor.android)
Comments
This does not update the suggested version numbers via "Project Structure/Suggestions" in AS/IJ, is seemed to
be totally de-coupled, and it is a nightmare to manually find and update the versions manually to the latest!
However: doing it by buildSrc java Module makes it much easier to re-use in other projects
by Gradle Version Catalogs
Definition
libs.versions.toml // in the gradle directory next to wrapper
[versions]
plugin-kotlin = "1.7.10"
ktor = "2.1.0"
[libraries]
plugin-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "plugin-kotlin" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
[bundles]
plugins = ["plugin-android", "plugin-kotlin", "plugin-kotlin-serialization", "plugin-sqldelight"]
implementation in build.gradle.kts(project)
buildscript {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
dependencies {
classpath(libs.bundles.plugins)
}
}
implementation in build.gradle.kts(shared)
sourceSets {
val commonMain by getting {
dependencies {
implementation(libs.ktor.client.core)
}
}
}
Comments
This is by far the favorite of mine, the reason is clear, it allows SSOT, and it allows grouping/bundling to make
"sets" that is more understandable later on
However!
This does not update the suggested version numbers via "Project Structure/Suggestions" in AS/IJ
it is a nightmare to manually find and update the versions manually to the latest!
Main question
Is there a better way of managing dependency versions?
Is there a way that works inside the IDE AS/IJ suggestions to update the versions correctly at the definition
location?

What the difference in applying gradle plugin

I don't understand gradle plugins block
apply plugin: 'someplugin1'
apply plugin: 'maven'
and other one:
plugins {
id 'org.hidetake.ssh' version '1.1.2'
}
In first block We have some plugin name. in second one package and version. I don't understand where I should use first block and when second one.
The plugins block is the newer method of applying plugins, and they must be available in the Gradle plugin repository. The apply approach is the older, yet more flexible method of adding a plugin to your build.
The new plugins method does not work in multi-project configurations (subprojects, allprojects), but will work on the build configuration for each child project.
I would think that as functionality progresses, the plugins configuration method will overtake the older approach, but at this point both can be and are used concurrently.
As already mentioned by #cjstehno the apply plugin is a legacy method that you should avoid.
With the introduction of the plugins DSL, users should have little reason to use the legacy method of applying plugins. It is documented here in case a build author cannot use the plugins DSL due to restrictions in how it currently works.
With the new plugins block method, you can add a plugin and control when to apply it using an optional parameter apply:
plugins {
id «plugin id» version «plugin version» [apply «false»]
}
You would still use the legacy method in situations where you want to apply an already added but not applied plugin in your plugins block. E.g, in the master project a plugin xyz is added but not applied and it should be applied only in a subproject subPro:
plugins {
id "xyz" version "1.0.0" apply false
}
subprojects { subproject ->
if (subproject.name == "subPro") {
apply plugin: 'xyz'
}
}
Notice that you don't need the version anymore. The version is required in the plugins block unless you are using one of the Core Gradle plugins, such as java, scala, ...
I spent some time understanding the difference while trying to create a Spring Boot application, and that's why I am answering this again after a while. The following example for using Spring Boot plugin helped me a lot:
What should currently be used:
plugins {
id "org.springframework.boot" version "2.0.1.RELEASE"
}
What had been used before Gradle 2.1:
buildscript {
repositories {
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath "org.springframework.boot:spring-boot-gradle-plugin:2.0.1.RELEASE"
}
}
apply plugin: "org.springframework.boot"
These are two different ways to use Gradle plugin。
The apply plugin way: First resolve plugin you needed from root build.gradle like:
buildscript {
repositories {
// other repositories...
mavenCentral()
}
dependencies {
// other plugins...
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.44'
}
Then in the build.gradle of your Android Gradle modules apply the plugin:
apply plugin: 'com.android.application'
apply plugin: 'com.google.dagger.hilt.android'
The plugins way:combine resovle and apply where in your root build.gradle like:
plugins {
// other plugins...
id 'com.google.dagger.hilt.android' version '2.44' apply false
}
Then in the build.gradle of your Android Gradle modules apply the plugin:
plugins {
// other plugins...
id 'com.android.application'
id 'com.google.dagger.hilt.android'
}
android {
// ...
}
Now ( In Gradle 6) you can give repositories name for plugins without using build script.
In settings.gradle, we can add plugin pluginManagement
pluginManagement {
repositories {
maven {
url '../maven-repo'
}
gradlePluginPortal()
ivy {
url '../ivy-repo'
}
}
}
Reference: https://docs.gradle.org/current/userguide/plugins.html#sec:custom_plugin_repositories
I would like to point out though, that is it not required for a plugin to be published remotely to be able to use it!
It can also be a UNPUBLISHED locally available plugin (be it convention plugins or otherwise) just as well.
In case one wishes to refer to such an unpublished locally-available plugin,
you'll have to include it's so-called "build" within the desired component/build (identified via the settings.gradle(.kts)-file) like so:
pluginManagement {
includeBuild '<path-to-the-plugin-dir-containing-the-settings-file>'
}
Afther that is done, one may use the local plugin within the plugins {}-DSL-block via its pluginId.
If the plugin needs a version then it's safer to put the version number in the pluginManagement block in your settings.gradle file, rather than in plugins block.
By safer I mean that you won't encounter an error like plugin request for plugin already on the classpath must not include a version. That can happen if you includeFlat a project into another project that uses the same plugin and your plugin versions are in the plugins block.
So rather than:
plugins {
id 'pl.allegro.tech.build.axion-release' version '1.10.3'
}
Do:
plugins {
id 'pl.allegro.tech.build.axion-release'
}
and then in your settings.gradle file:
pluginManagement {
plugins {
id 'pl.allegro.tech.build.axion-release' version '1.10.3'
}
}
I'm going to add a little twist to what's been said. Gradle introduced the concept of a plugins block as a technique to speed up and optimize the build process. Here's what Gradle's documentation says:
This way of adding plugins to a project is much more than a more convenient syntax. The plugins DSL is processed in a way which allows Gradle to determine the plugins in use very early and very quickly. This allows Gradle to do smart things such as:
Optimize the loading and reuse of plugin classes.
Provide editors detailed information about the potential properties and values in the buildscript for editing assistance.
This requires that plugins be specified in a way that Gradle can easily and quickly extract, before executing the rest of the build script. It also requires that the definition of plugins to use be somewhat static.
It's not just a newer way of dealing with plugins, it's a way of improving the build process and/or user's editing experience.
In order for it to work, it needs to be specified at the top of the build, but it also needs to be specified after the buildscript block if one is included. Why is that? Because the code in the build scripts is evaluated in the order its written. The buildscript block must be evaluated before the plugins block is evaluated. Remember, the buildscript block is about setting up of the plugin environment. Hence the rule that the plugins block must be specified after the buildscript block.
The new plugins block not only specifies the plugins that the project is using, but it also specifies whether the plugin is applied. By default, all plugins in the plugins block are automatically applied, unless it is specifically declared not to be applied (i.e., adding "apply false" after the plugin declaration in the plugins block).
So why would you declare a plugin and not apply it. There are two main reasons that I can think of:
1.) so you can declare the version of the plugin you want used. After you've declared a plugin, the plugin is now on the "classpath". Once a plugin is on the classpath you no longer need to specify the version of the plugin when you apply it later. In multiproject builds, that makes supporting buildscripts a little easier. (i.e., you only have one place where the plugin version is specified.)
2.) Sometimes, you may have a plugin, that requires certain things defined before they are applied. In that case, you can declare a plugin in the plugins block, and defer the plugin from being applied until after you define the things that the plugin requires as input. For example, I have a custom plugin that looks for a configuration named "mavenResource". In the dependencies block I'll added a dependency like: "mavenResource(maven_coordinate)". That plugin will find all the dependencies contained in the mavenResource configuration and copy the associated maven artifact to the projects "src/main/resources" directory. As you can see, I don't want to apply that plugin until after the mavenResource configuration is added to that project, and the mavenResource dependencies are defined. Hence, I define my custom plugin the plugins block, and I apply it after the project dependencies have been defined. So, the concept that applying a plugin is old style and wrong is a misconception.
Some of you might wonder what it means to apply a plugin. It's pretty straightforward. It means that you call the plugin's apply function passing it the Gradle Project object for the project where the plugin is being applied. What the plugin does from there on is totally at the discretion of the plugin. Most commonly, the apply function usually creates some Gradle tasks and adds them to the Gradle build task dependency graph. When Gradle starts its execution phase, those tasks will get executed at the appropriate time in the build process. The plugin apply function can also do things like deferring some of it work until afterEvaluate. That's a way to allow other things in the build script to be setup even though they are defined later on in the buildscript. So, you might ask why I didn't do that trick in my custom plugin. What I've observed is that the next subproject starts processing after the root project finishes being evaluated. In my case, I needed the resource added before the next subproject began. So, there was a race condition, that I avoided by not doing the afterEvaluate technique and specifically applying the plugin once the things I needed setup was completed.

How do I run a task created by a plugin in gradle

I've written a gradle plugin that adds a custom task called generateTestDocs, which depends on the task groovydoc, which itself is created by the groovy plugin.
//MyPlugin.groovy
#Override
void apply(Project project) {
project.apply(plugin: 'groovy')
project.task(type: GenerateTestDocsTask, dependsOn: ':groovydoc', 'generateTestDocs')
}
project.tasks.groovydoc.doFirst {
println "I should see this message but I don't"
}
I'm trying to test this plugin by running the task generateTestDocs
#Test
void testRunGenerateTestDocs() {
Project project = ProjectBuilder.builder().build()
project.apply(plugin: 'my.gradle.plugin')
project.tasks.generateTestDocs.actions*.execute(project.tasks.generateTestDocs)
}
For the last line in my test, I'd like to instead just say project.task.generateTestDocs.execute() and have it run the task with all of its dependencies, but that doesn't seem to work. The documentation for writing gradle plugins only shows assertions like assertTrue(project.tasks.hello instanceof GreetingTask) which shows the task is added to the project, but doesn't show how to run that task.
ProjectBuilder is only meant for unit tests. To run a build as part of a test, you'll need to use the Gradle tooling API, or a third-party plugin such as nebula-test (which builds upon the tooling API).
I faced the same question, I solved this by add
apply plugin: CustomPluginName
in current gradle.build :
apply plugin: 'groovy'
...
// in this case Plugin name is MyPlugin
apply plugin: MyPlugin

Gradle plugin for default properties

I am trying (and failing :) ) to create a gradle plugin that has a default set of versions for dependencies and can be overridden in the gradle.build file that is calling my plugin. Ideally something like the sudo-code below
MyDefaultPropertiesPlugin.groovy
project.versions.springBoot="1.0.0-RELEASE"
MyPlugin.groovy
project.apply plugin: MyDefaultPropertiesPlugin
compile("org.springframework.boot:spring-boot-starter-web:${project.versions.springBoot}")
build.gradle
versions.springBoot = "1.1.0-RELEASE"
project.apply plugin "my.plugin"
I attempted to do using extensions but ran into isssue's via the ordering when overriding. (versions doesnt exist)
I would greatly appreciate any advise on this, maven would be easy, but my gradle knowledge is still evolving :)
Thanks in advance for any insight!
Plugins have to defer accessing the build model until after build scripts have been evaluated. Easiest solution is to use project.afterEvaluate {}, but there are others. For more information, see answers to similar questions here or on http://forums.gradle.org.
Came up with a pretty workable if not perfect solution, I will update if i think of anything better, my gradle is at a learning level, so please commend if this can be improved.
This allows me to define a set of versions and clients to overwrite those versions with a simple property
MyDefaultVersionsPlugin.groovy
class MyDefaultVersionsPlugin implements Plugin<Project>{
project.extensions.create('versions', MyVersions, project)
}
class MyVersions{
String spring
String slf4j
public MyVersions (Project project){
spring = setVersion(project,'springVersion', 'x.x.x.x')
slf4j = setVersion(project,'slf4jVersion', 'x.x.x.x')
}
private static String setVersion(Project project, String name, String version){
if(project.hasProperties(name)){
return project.getProperties().get(name)
}
else {
return version
}
}
}
MyPlugin.groovy
project.apply plugin: MyDefaultVersionsPlugin
compile("org.springframework.boot:spring-boot-starter-web:${project.versions.spring}")
build.gradle
buildscript { ext { springVersion = 'x.x.x.x'} }

gradle NoSuchMethodError: org.apache.log4j.PatternLayout.<init>(Ljava/lang/String;)

(My second Gradle problem...)
I'm trying to setup a call to Velocity in one of my tasks. Velocity does a direct call to log4j which Gradle somehow seems to divert to its log4j-over-slf4j-1.7.2.jar, causing a NoSuchMethodError.
So I have:
buildscript {
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
classpath 'org.apache.velocity:velocity:1.7'
// ... asumes provided:
classpath 'log4j:log4j:1.2.12'
}
}
And then later in a task I do...
def template = Velocity.getTemplate('src\\jnlp\\jnlpTemplate.vm')
This raises an exception. And the last part of the stacktrace is
Caused by: java.lang.NoSuchMethodError: org.apache.log4j.PatternLayout.<init>(Ljava/lang/String;)V
at org.apache.velocity.runtime.log.Log4JLogChute.initAppender(Log4JLogChute.java:117)
at org.apache.velocity.runtime.log.Log4JLogChute.init(Log4JLogChute.java:85)
at org.apache.velocity.runtime.log.LogManager.createLogChute(LogManager.java:157)
at org.apache.velocity.runtime.log.LogManager.updateLog(LogManager.java:269)
at org.apache.velocity.runtime.RuntimeInstance.initializeLog(RuntimeInstance.java:871)
at org.apache.velocity.runtime.RuntimeInstance.init(RuntimeInstance.java:262)
at org.apache.velocity.runtime.RuntimeInstance.requireInitialization(RuntimeInstance.java:302)
at org.apache.velocity.runtime.RuntimeInstance.getTemplate(RuntimeInstance.java:1531)
at org.apache.velocity.runtime.RuntimeInstance.getTemplate(RuntimeInstance.java:1514)
at org.apache.velocity.runtime.RuntimeSingleton.getTemplate(RuntimeSingleton.java:299)
at org.apache.velocity.app.Velocity.getTemplate(Velocity.java:358)
at org.apache.velocity.app.Velocity$getTemplate.call(Unknown Source)
at build_49a099islbrnod06rkiudfavms$_run_closure1.doCall(C:\home\...\build.gradle:67)
My analysis so far is as follows:
The constructor PatternLayout(String), that is called by Velocity is part of log4j's PatternLayout class, but it seems Gradle ends up resolving PatternLayout to a class in $GRADLE_HOME/lib/log4j-over-slf4j-1.7.2.jar. And that jar's PatternLayout seems to be a very minimal implementation lacking the requested constructor.
Is there some workaround here? Did I make some beginners mistake?
You have to remove the log4j jar from your app: the bridge log4j-over-slf4j and the log4j have the same methods, with the same signature (there could be some issues caused by different loading order into the classpath). If you want to use the bridge you don't need the log4j lib, you have to choose another logging framework which implements slf4j (for example log-back).
It could be a version issue. The version 1.7.2 of the bridge could be too high, try to downgrade to a lower version.

Resources