Is there a way if a given `implementation` depende...
# gradle
u
Is there a way if a given
implementation
dependency is used in a given module? (for all modules; other than obviously trying each one and running build)
u
oh great!
I tried it, it does give false positives on what needs to be removed but better than nothing Btw it also suggests what should be
api
instead of
implementaion
and I kind of disagree What's your policy on that?
api
if the given dependency is part of public api?
e
yes, although there are many ways to accidentally make something part of the "public api" without intentionally doing so
for example, because of how Java resolves overloads (without looking at visibility), types used by private functions are exposed publicly, even though that's almost never what you want
and I don't believe it has any understanding of Kotlin's
internal
visibility - it just looks
public
to Java
u
Copy code
module A
interface A

module B
interface B : A
Copy code
module C want to depends on interface B, should it `implementaion both a, b` or should b `api a`?
btw is there any pentaly if multiple dependencies of C would have
api A
in them? i.e. muple dependencies expose the same transitive one
e
1. b.dependencies { api(a) } because it is exposed as part of the public API 2. no
u
how about this?
Copy code
interface Stateful<S> {
	val state: rxjava.Observable<S>
}
should this module
api rxjava
? to me its obvious that it should, but the plugin suggests I remove rxjava ..
e
hmm. seems odd, what does the
:reason
task say?
u
there is one? 😄 I only ran the buildHealth, will investigate
I think I know what it struggles with .. its when there are multiple classes in one file
Copy code
data class SomethingElse(
    ...
)

@JsonClass(generateAdapter = true)
data class MccSubscriber(
    val id: SubscriberId
)

complains I can remove epdendency which provides the SubscriberId type
e
ah, sounds like it's worth filing a bug
u
btw can I bounce one more thing off ya. I have this gradle setup where each feature is split into
contract
module which only contains interfaces and
impl
module which contains implmentations of those interfaces. Everybody dpeends on contract, nobody but the final
:app
on the impl .. should the impl
api contract
? so
˛:app
has only :impl module includes basically? if so, should :impl module also
api
basically everything?
e
impl should
api
the contracts it implements, as those are exposed types. if it depends on other things that it does not expose, then those can be
implementation
. maybe you want it as
api
as a convenience for
:app
to use, that would be allowed, it just makes incremental compilation less effective
u
yea the latter part is what I mean, say impl depends on okhttp; so if I api okhttp in impl module, which is only included in :app module, then it makes builds less efficient as opposed to having implementation okhttp in impl and app?
e
no, that probably doesn't matter. if it's a source module, then incremental compilation matters when you change it, but external dependencies don't
u
so what did u mean by "it just makes incremental compilation less effective"
e
if you edit a file, it may cause more recompilation of other modules if it is depended upon via api than if it were via implementation
u
well, does it matter? it's gonna happen no matter what no? if I api a transitive dependency, or implementaion a dependency, where the dependand needs it, so he will implementation it anyways; in mind mind its just more convenient for the dependant since he needs only one include; other than behavior is the same, no?
e
it does matter - if it's implementation, and the incremental compilation analysis says the direct dependents doesn't actually change as a result, then further dependents don't need to be analyzed
public API is what matters to downstream consumers
u
I dont follow. I mean this
Copy code
:base-user
interface BaseUser

:app1-user
interface App1User : BaseUser
api project(":base-user")

:some-call-site-needing-app1user
implementation project(":app1-user")

-- vs --

:base-user
interface BaseUser

:app1-user
interface App1User : BaseUser
implementation project(":base-user")

:some-call-site-needing-app1user
implementation project(":app1-user")
implementation project(":base-user")
if I add a field onto
BaseUser
then both
:app1-user
and
:some-call-site-needing-app1user
need to be recompiled in either case, no? and if so, only difference is in the convenience of the
:some-call-site-needing-app1user
only needing 1 include line, no?
e
in this case it's api no matter what.
the case of
Copy code
:lib1
class Foo

:lib2
class Bar {
    fun bar() = Foo().toString()
}

:app
fun main() {
    Bar.bar()
}
then
api
versus
implementation
changes how much incremental compile needs to happen as a result of editing
lib1
u
yes, but why would you use
api
here, dependencies are always private, not part of public api
btw on topic of incremental compilation, isnt that ortogonal to the compilation avoidance given by the way modules are setup? Naively in my head, lib1 changes so it needs to be recompiled + all its dependants, because thats the way depenendecy graph looks like where is the incremental compilation? i thought it just "only compiles code changes" via some magic, and not the whole thing, unrelated to the way dependencies are, no?
e
not all dependents need to be rebuilt, only ones affected by public API changes
that's part of the whole point of using Gradle over other build systems
u
okay so if dependant depends on lib, but it doesnt use libs interface, then its a no-op for the given dependant?
e
app.dependencies { implementation project(:lib) } if lib's public api changes, including any of its api dependencies, gradle will rebuilt app. (it will try to be smart about it, but some things such as
const val
are inlined into all use sites as per JVM spec which makes analysis much harder. see https://blog.gradle.org/incremental-compiler-avoidance#:~:text=constant) if a non-api dependency of lib changes, and lib's public api does not change as a result, app does not get rebuilt
u
Yes, but isnt that "compilation avoidance"? (and not incremental compile?); for some reason I remember the impl/api thing being about compilation avoidancei, i.e. only changed modules are recompiled. And incremental compilation a sort a low level compiler black magic, not related to gralde
e
it's somewhat related, in that compile avoidance is how Gradle's Java plugin works. the Kotlin plugin is a bit behind on it though
u
im confused, is the following correct? if there was no incremental compilation; all dependant modules would get recompiled fully if there was incremental compilation; all dependant modules would get recompiled partially via some magic of the intremental compiler, which determined what exact public interfaces changed and how?
and if it correct, then does it not mean that incremental compilation is functionally superset of compilation avoidance, since if I have that, it pointless how module's graph looks etc, it just some sort of a patch on the bytecode already generated via previously build, no?
e
1. compilation avoidance reduces the amount of work that incremental compilation has to do - less in the compile classpath, fewer affected downstream modules, less state to keep track of 2. incremental compilation doesn't cache well - Kotlin in particular is path-sensitive and has had various issues with kapt before
in fact, I'm not sure how well Kotlin's incremental compile works with classpath changes currently. in the past it went completely non-incremental for many changes
u
yes which is why I have multimodule even for single app, I wanted it because of parallel module compilation -- however the counter argument was that it's incremental compiler's job to do this (i.e. to make builds fast)
if there was no incremental compilation; all dependant modules would get recompiled fully
is this correct then?
also, how would this discussion change if it were java? would it? presuming their incremental compile is working well
e
https://youtrack.jetbrains.com/issue/KT-24203 seems to indicate that Gradle's ABI-based compile avoidance doesn't work for the Kotlin plugin at all - so declaring your API dependencies tightly seems more important with Kotlin than Java
in fact the meta-issue https://youtrack.jetbrains.com/issue/KT-42309 shows several other areas that Kotlin is still lacking as well
in my company's projects we've actually disabled build caching for specific Kotlin compile tasks due to how it causes the incremental compile to be completely non-incremental half the time
if there was no incremental compilation; all dependant modules would get recompiled fully
not with ABI-aware compile avoidance, but seemingly true for Kotlin
u
lots of great info, thank you!