Hi, I am trying to migrate a Gradle Groovy DSL to ...
# gradle
j
Hi, I am trying to migrate a Gradle Groovy DSL to Kotlin DSL and I am trying to declare dependencies in buildSrc in a way similar to this: https://proandroiddev.com/gradle-dependency-management-with-kotlin-94eed4df9a28). The problem is that I have some dependencies that are not just the simple artifact and version and need some extra configuration. On the Groovy DSL I used code like this:
Copy code
dep = dependencies.create('com.squareup.retrofit2:converter-simplexml:' + constants.versions.retrofit) {
    // We need to exclude dependencies which are already provided by the Android platform
    exclude module: 'stax-api'
    exclude module: 'stax'
    exclude module: 'xpp3'
  }
But I can't figure out how to convert that to Kotlin DSL and I don't even have access to the "dependencies.create" method on the buildSrc object where I am declaring the dependencies. Is it possible to define a dependency with exclusions like that outside a DependencyHandler scope? Is there a standalone constructor / builder for dependencies that is not an extension method only available at specific places?
g
In this case you have not just version, but config snippet also you need dependencies object to do that So possible way just extract it to extension function for dependencies and apply where you need this
But I believe you need this only if you use it multiple times in different modules, otherwise easier just define in it on place
j
Yes, I want it for multi-module projects to declare dependencies with its configuration only once. The problem with the extension function would be that I can not declare it on a big Dependencies object and access it like:
Dependencies.retrofit2ConverterSimpleXML
as the ones that I have with no configuration. Also, don't know how to do the configuration like in Groovy.
g
Problem that you need project instance to create dependency
So or it should be extension function, or retrofit2ConverterSimpleXML must be function, not a property
And you pass DependencyHandler or Project instance, you just cannot resolve it statically, you need instance of DependencyHandler to create dependency
j
it does not seem to work if a declare it as
fun DependencyHandler.retrofit2ConverterSimpleXML()
inside the Dependencies object
g
Why?
j
If I try to autocomplete in a line like:
implementation(Dependencies.)
I don't get the function appearing at all
neither it works as
implementation(Dependencies.retrofit2ConverterSimpleXML())
or offers anything to import as
implementation(retrofit2ConverterSimpleXML())
g
Maybe just some tooling problem, if you have function in buildSrc it must be available on you build.gradle
j
yeah, after several tries now I got the last one
implementation(retrofit2ConverterSimpleXML())
to offer to import it as
import Dependencies.retrofit2ConverterSimpleXML
but that is not ideal either cause I lose autocompletion, so I have to look them up on the object manually and also to add imports, which the other dependencies do not need
Why is a project / DependencyHandler needed for creating a dependency when it is not used on creation?
shouldn't it just be a simple object / model?
g
Apparently it used. create is method of DependencyHandler
You shouldn't import Dependencies, not sure what is exactly you problem. If your Dependencies object uses default package it just should work, same for any top level extension
j
yeah, I saw that, but it may be just a convenience extension and it could be possible to create it in some other way that is not scoped to DependencyHandler, but I haven't found any doc about that
Also the config does not work easily, it expects a Closure<Any>
g
If you have:
Copy code
object Dependencies {
  fun retrofitSimpleXml(dep: DependencyHandler)
}
Than I don't see any problem
j
maybe doing:
closureOf<ExternalModuleDependency> { }
? 😅
oh, so no extension function but a function receiving the dependency handler?
How that would be called?:
Dependencies.retrofitSimpleXml(this)
?
g
Yes
Extension function is just an option, it's easier to use, but you right, it's not related anymore to your Dependencies object
Also the config does not work easily, it expects a Closure<Any>
Just create dependency, cast it to ExternalDependency and use apply to configure
j
This seems to compile:
Copy code
fun retrofit2ConverterSimpleXML(dependencyHandler: DependencyHandler): Dependency =
    dependencyHandler.create(
        "com.squareup.retrofit2:converter:-simplexml${Versions.retrofit}", closureOf<ExternalModuleDependency> {
      exclude(module = "stax-api")
      exclude(module = "stax")
      exclude(module = "xpp3")
    })
but not on usage 😅
does not accept this later:
implementation(Dependencies.retrofitConverterSimplexml(this))
` ^ Type mismatch: inferred type is Dependency? but Any was expected``
g
Your dependency is nullable
Not sure why
Looks that your code explicitly defines it as non nullable
j
also tried this based on what you said:
Copy code
fun retrofit2ConverterSimpleXML(dependencyHandler: DependencyHandler): Dependency =
    (dependencyHandler.create(
        "com.squareup.retrofit2:converter:-simplexml${Versions.retrofit}"
    ) as ExternalDependency).apply {
      exclude(module = "stax-api")
      exclude(module = "stax")
      exclude(module = "xpp3")
    }
g
It's the same
j
it also compiles that part, but fails with same error on implementation reference
g
Check why dependency is nullable
It just doesn't allow to pass nullable
But I don't see the reason
Just for test add !! To usage
j
it seems not that, but that implementation expects either any or 2 parameters one of which is a Dependency?
so it is going to
Copy code
fun DependencyHandler.`implementation`(dependencyNotation: Any): Dependency?
instead of:
Copy code
fun <T : ModuleDependency> DependencyHandler.`implementation`(
    dependency: T,
    dependencyConfiguration: T.() -> Unit
): T
g
Ah, I see
Yeah, type safe accessor generated oy for string
You have to use
Copy code
add("implementation", dependency)
Maybe you can solve this in another way and just globally exclude those dependencies for all modules instead attaching this to your dependency definition
j
same complain 😅
g
Sorry, I confused
Where is type mismatch?
j
that one also expects a Any
not a Dependency
g
Any is super type of Dependency
So it's fine
Your problem with nullability
Not with class
j
I don't know how to get it to use this one:
Copy code
fun <T : ModuleDependency> DependencyHandler.`implementation`(
    dependency: T,
    dependencyConfiguration: T.() -> Unit
): T
seem that is the one that allows to pass a Dependency as the created one
g
Pass empty lambda
j
but since it expects a second parameter
g
Second param just an empty lambda
implementation(dependency, {})
j
like this?:
implementation(Dependencies.retrofitConverterSimplexml(this), {})
also tried, but it then gives this different error:
Copy code
implementation(Dependencies.retrofitConverterSimplexml(this), {})
              ^ None of the following functions can be called with the arguments supplied: 
                  public fun <T : Dependency> DependencyHandler.implementation(dependency: TypeVariable(T), action: Action<TypeVariable(T)>): TypeVariable(T) defined in org.gradle.kotlin.dsl
                  public fun DependencyHandler.implementation(group: String, name: String, version: String? = ..., configuration: String? = ..., classifier: String? = ..., ext: String? = ..., dependencyConfiguration: Action<ExternalModuleDependency>? = ...): ExternalModuleDependency defined in org.gradle.kotlin.dsl
                  public fun DependencyHandler.implementation(dependencyNotation: String, dependencyConfiguration: Action<ExternalModuleDependency>): ExternalModuleDependency defined in org.gradle.kotlin.dsl
g
I'm away from my PC and IDE, just check types that possible to pass to type safe accessors and to dynamic add setters, one of them should allow pass Dependency
Probably your method should return ExternalDependency, not sure
j
ok, thanks!, it has been really helpful, at least looks there is some path this way 🙂. I will keep tinkering..
g
Ahh, correct, I see it now in generic, function that you showed above, only for module dependencies, not for external dependency
I still think that global configuration resolution is just better solution
j
but the odd thing is that with Groovy it works
g
Exclude those dependencies from all modules in allproject block
This also will work
j
I mean, I can pass a dependency created with the create() method to implementation() and it just takes it
g
Because groovy dynamic, try to use
add()
, it probably also will work
It should have compatible overload for this case
But usage of not type safe
add
Imo just destroying all the idea of type safe dependency definition
So global configuration resolution strategy imo the best, keeps your dependency definition and usage simple and obvious
Also solves problem of transitive dependencies which also can bring excluded dependencies
j
yes, it kinda also feels like if somehow the Groovy variant of implementation method allowing what I did before is not accesible through the Kotlin DSL
there are some calls of add with an ExternalModuleDependency but it requires like 3 parameters 😅
oh, not even that, is not an ExternalModuleDependency but its configuration
so I guess, yes, I should go the global route
this also seems to work:
Copy code
implementation(Dependencies.retrofitConverterSimplexml) {
    exclude(module = "stax-api")
    exclude(module = "stax")
    exclude(module = "xpp3")
  }
which would mean basic unconfigured dependency declaration and duplicated config block per usage
g
add("implementation", ...) is the same as "implementation"(...), But in both cases you lose type safety
j
yes, deleted cause I think it was wrong, was trying to trick it to use the groovy version of implementation that supports a dependency as single parameter 😅
it seems to accept several things, but I can not fully compile the project yet to be sure they are valid and doing what I intend. This compiles too:
Copy code
withGroovyBuilder {   "implementation(${Dependencies.retrofit2ConverterSimpleXML(this@dependencies)})"
  }
but I am only guessing there and it is type unsafe as soon as you add groovy in
The global exclude is also a bit tricky, may work for this one, but on others I am excluding for different reasons, not all versions of a module but only those coming transitively on a specific dependency due to conflicts. The duplicated config section may be better for now to not have several different ways to do the same
Also, luckily I don't have many that need configuration
and it even seems some of those issues are fixed since last time I checked, so maybe I can remove a few more customizations
Thanks for all your help, 1am here, time to sleep 😄
g
You don't need groovy builder for this, I will check later today
I agree, global exclude may be not so straight forward, but usually it's more stable and clean solution. To solve version conflict you also can use global configuration resolution, I would still take a look on this solution
j
Also, there is another type of configuration I have on some dependencies, that require
isTransitive = true
which I am not sure if it has a global way.
g
Why is your use case for isTransitive = true? This is default behavior of any dependency
j
It fails without it on an old Twitter dependency I have not had time to remove since they discontinued it. I will post when I'm back at home in a bit, I have no idea why it fails without the
isTransitive
either.
I have also seen instructions for several libs that say to include it like that so there may be some differences. Others like glide had it and then seemingly removed the requirement but I'm not sure what changed.
g
Could you show this dependency?
j
This is it on "normal" syntax:
Copy code
implementation("com.twitter.sdk.android:tweet-composer:3.3.0@aar") {
    isTransitive = true
  }
without the isTransitive my build fails with unknown
com.twitter.sdk.android.core
package
g
Just remove
<@U27HSU2U9>
and isTransitive
Those guys from Twitter don't know how to use dependencies probably. Because explicit declaration of resources extension obviously disables transitive dependencies (because you cannot get transitive dependencies without pom file)
👌 1
j
Oh, ok, so maybe the others I have with
@aar
also should get rid of it. I thought it was something to distinguish aar vs jar or something like that.
seems to compile now with just
implementation(Dependencies.twitterSdkTweetComposer)
🎉
g
👍
Really not sure how they decided to use such config, maybe some super old legacy when they don't have pom, only raw aar. Funny, that Crashlytics used the same strange config in their docs when they owned by Twitter, now Google acquired them and this thing moved also to Firebase docs for Crashlytics 😬