Hello :wave: Which is the way to get all depende...
# community-support
j
Hello 👋 Which is the way to get all dependencies, direct and transitive ones in a module? I tried with no luck by getting the
implementation
configuration and looking for the dependencies there.
m
There are different kind of dependencies. Usually for the JVM, you’re looking for
runtimeClasspath
j
it is an android app
m
Yea, you’ll have several configurations per variant etc…
./gradle dependencies
should show everything
Then probably something like
Copy code
configurations.getByName("debugRuntimeClasspath").resolvedConfiguration.resolvedArtifacts
Something like this
j
Thank you! I will try 🙂
@Martin looks like the Android app does not have any
runtimeClasspath
configuration, only
runtimeOnly
, which is not resolvable 🤔
Even on libraries there is no
cruntimeClasspath
😕
m
Yea, you want the
debugRuntimeClasspath
or so
j
There is no debug or release
m
Hold on, trying with one of my Android apps
j
only
runtimeOnly
m
Copy code
Confetti $ ./gradlew :androidApp:dependencies |grep -i runtimeclasspath
debugAndroidTestRuntimeClasspath - Runtime classpath of '/debugAndroidTest'.
debugRuntimeClasspath - Runtime classpath of '/debug'.
What does your
./gradlew dependencies
say?
j
I think we are getting it too early 🤔
I was using
named { }
, is it not lazy?
m
Nah,
named {}
is not lazy
I mean it is if the provider already exists.
Copy code
configurations.configureEach {
  if (name == "debugRuntimeClasspath") {
    resolvedConfiguration.resolvedArtifacts.forEach { println(it.id) }
  }
}
#funtimes
j
Okay, that is working, but it fails saying we are changing the dependencies, but we are just printing the file paths
Copy code
configurations.configureEach {
    if(...) this.resolvedConfiguration.resolvedArtifacts
}
Is it working for you calling
resolvedConfiguration
?
m
You need to postpone this as much as you can since AGP is probably going to do stuff with it
j
How? using their callbacks?
j
Will ping you back 😛 thank you!
I got the configurations but they have no artifacts 🤔
And I keep the build running, it fails with the same issue
m
I'm afk right now but if you want to dig more into this, you can look into Tony's dependency analysis plugin. There has to be code like this in there
👍 1
thank you 1
v
Nah,
named {}
is not lazy
Of course it is
It is as lazy as
matching {}
. So unless you only call
configureEach {}
on the result, you are safe / lazy
If you iterate it, you are of course breaking laziness and also for tasks in the task set you call this on task-configuration avoidance
👍 1
m
It configures (sic) the configuration lazily but doesn't react to the configuration being added to the container
v
Of course it does
Well, not to the configuration being added, but neither is
configurations.configureEach { }
It configures the domain object when it is realized due to some reason just like in all other cases. Configurations are not special here.
configurations.named { it == "foo" }.configureEach { ... }
===
configurations.matching { it.name == "foo" }.configureEach { ... }
===
configurations.configureEach { if (name == "foo") { ... }
"being added" cannot be done in a lazy way at all currently afair
m
So it's different from
tasks.named("") {}
?
l
> “Which is the way to get all dependencies, direct and transitive ones in a module?” On Android, you don’t usually look at `implementation`; you look at per-variant configs like
debugRuntimeClasspath
and `releaseRuntimeClasspath`:
configurations.getByName("debugRuntimeClasspath").resolvedConfiguration.resolvedArtifacts
These are all lazy:
configurations.named("foo").configure { ... }        // direct by name
configurations.named { it.name == "foo" }.configureEach { ... }
configurations.matching { ... }.configureEach { ... }
configurations.configureEach { if (name == "foo") { ... } }
You need to call
resolvedConfiguration
at the right time.
m
Those two are not the same:
Copy code
// Configuration with name 'foo' not found
configurations.named("foo")
vs
Copy code
// Hooray!
configurations.configureEach {
    if (name == "foo") {
        println("Hoorayy")
    }
}
configurations.create("foo")
I'd argue the 2nd one is "lazier" than the first one
But 🤷
l
Lazy in the Gradle sense since
.named
returns a Provider
But yes, the first statement will fail if "foo" doesn't exist... Also 🤷‍♀️
m
Yea, sorry I'm a bit sour about the whole thing. There are just so many ways to do the same thing and I just got bitten again by
.named(String)
vs
.named(Spec<String>)
, etc...
1
Back to the initial point, this worked for me @Javi
Copy code
androidComponents {
    onVariants(selector().withName("debug")) {
        val configuration = it.runtimeConfiguration
        val provider = provider {
            configuration.resolvedConfiguration.resolvedArtifacts.map { it.id }
        }

        tasks.register("dumpDependencies") {
            doFirst {
                println(provider.get().joinToString("\n"))
            }
        }
    }
}
🙌 1
v
So it's different from
tasks.named("") {}
?
Yes,
tasks.named(...)
requires that a task is already registered and you get a
TaskProvider
. With
tasks.named { ... }
you get a lazy task collection with all matching tasks (which might be 0) and as long as you only use
configureEach
on it, it is also configuration-avoidance safe.
👍 1
j
@Martin is it not possible to call it in configuration phase? I need to do two things based on that list: Add them as dependencies to the
implementation
configuration. Get the Jar files, and get a list of classes using a specific annotation with
ClassGraph
.
AFAIK, I cannot do something like
implementation(providerOfAListOfStrings)
. And I think you cannot pass a lazy object like provider to the BuildConfig 🤔
m
Add them as dependencies to the
implementation
configuration
Mmmm they should already be there?
debugRuntimeClasspath
should have everything from
implementation
, not the other way around. If you need more things in
implementation
you probably need to model your dependencies differently using
extendsFrom
or something similar. What is the use case?
Get the Jar files, and get a list of classes using a specific annotation with
ClassGraph
.
That part definitely needs to happen in a task, this is a lot of work that you don't want to do at configuration time.
j
But I need to pass the list of files to the build config
m
If the Android
BuildConfig
doesn't allow providers, you can use https://github.com/gmazzo/gradle-buildconfig-plugin instead
In all cases, you need to compute the value lazily or you're going to play cat and mouse with AGP about who resolves that configuration first
j
It is indeed a circular dependency, I am getting things from implementation and, after that, trying to add them too. So no only the build config issue.
v
You probably want
Configuration#withDependencies { ... }
. This hook es executed right before a configuration takes part in dependency resolution first and is the place to go to add last-minute dependencies, for example based on other dependencies in that configuration.
thank you 1
👍 1
j
No luck with
withDependencies
, I get a circular dependency if I map the dependencies (project oens) from
debugRuntimeClasspath
to project paths and I trying to add them.
Copy code
project
        .configurations
        .named { it == "implementation" }
        .configureEach {
            withDependencies {
                val projectDependencies: Provider<List<ProjectDependency>> =
                    allModuleGradlePaths.map { paths -> paths.map { project.dependencies.project(it) } }
                addAllLater(projectDependencies)
            }
        }
Copy code
Circular evaluation detected: list(interface org.gradle.api.artifacts.Dependency, map(map(map(provider(?)))))
   -> map(map(map(provider(?))))
   -> map(map(provider(?)))
   -> map(provider(?))
   -> provider(?)
   -> list(interface org.gradle.api.artifacts.Dependency, map(map(map(provider(?)))))
Basically, we have blocked the usage of
api
in our project, so I need to add all transitive project dependencies to the
implementation
configuration automatically to ensure all symbols are resolved. In an ideal scenario, this wouldn't be necessary because any project that expose those relevant public symbols would be added as
api
, but... I cannot change this.
Meanwhile, the same code is working if I do that in a task:
Copy code
project.tasks.register("javi").configure {
        doLast {
            val projectDependencies: List<ProjectDependency> =
                allModuleGradlePaths.get().map { project.dependencies.project(it) }
            println("JAVI:")
            println(projectDependencies.joinToString("\n") { "  - ${it.path}" })
        }
    }
So, I think, the issue is trying to add inside
withDependencies
any dependency that is being got from another
configuration
that is extending to the one using
withDependencies
v
Of course, that's a chicken-and-egg problem. You add a provider to
implementation
that depends on what
runtimeClasspath
(or whatever) returns, and at the time the
runtimeClasspath
is resolved, the provider is resolved and you have the recursion. What is
allModuleGradlePaths
?
j
It is just mapping what I get from
runtimeClasspath
. So, it is not possible to do this
v
Maybe you simply should not do it, but let projects just properly declare their dependencies
j
it is a generated project, so developers do not touch it
Dependencies can change, we want to avoid rerunning the script to check any dependency being changed, etc
v
If it is a generated project, also generate the dependencies?
j
yeah, that would work for the first generation, but if the developer change any dependency in the other projects, this generated project would stop working
v
How do dependencies in the other projects stop that project from working?
j
dependent projects can change the dependencies, adding a new one, the new one contains a symbol that is necessary for the generated project, if it is not added manually, the generated project would fail
v
But how can the generated project suddenly need that symbol? The code in it did not change.
j
Because the generated project has a file that will be regenerated automatically by ksp with that symbol
v
Seems to be a bad idea then 😄
j
To be honest there are a lot of things that are wrong on the setup and blocks me to do correct things, the main one, not using
api
. Additionally,
ksp
is a bull****, I hope compiler plugins get a standardized-stable API and most projects drop the usage of KSP. I got a compiler plugin working, fast and easily in a few days without having to fight with any Gradle thing, but
ksp
cannot work with the compiler plugin as it runs before any Kotlin compilation runs...
Do you know if it would be possible to: • `project-a`: creates a new configuration and add a txt file including the list of jars of this project and its transitive dependencies (the text file is created by a task). •
project-b
(depends on
project-a
): uses the configuration to get the txt file and add the list of dependencies to its
implementation
configuration. Currently, I need to do a double build to first generate the file, and later being able to compile with the new dependencies. Same about syncing the IDE, the txt file must be first generated to be able to see the symbols.
v
When
project-a
already executes tasks, it is too late to reconfigure
project-b
with the content of what
project-a
generated.
👍 1
thank you 1
j
Is there anything lazy similar to
configureEach
, but to get the provider? or I just need to use
provider { }
or similar
v
What do you mean with "to get the provider"? Which provider?
j
With the configuration, mapping it but keeping it lazy
v
If you use
matching { ... }
or
named { ... }
, you cannot get a single instance provider, because your closures could match between Zero and Infinity instances.
j
So I need to use
provider {}
because there is no way to get a
Provider
by calling something similar to
configureEach
but that return a provider with a list of things I can map or something like that
v
configureEach
configures things and does not provide a provider. From the little information you provide it is hard to give substantial advice, but it sounds like you should not do what you try to do
j
That is the reason, and
map {}
over
named{}
is not lazy. So I cannot get something lazy to consume it later