Status Update
Comments
ni...@gmail.com <ni...@gmail.com> #2
We are currently using AGP internal task types to flag memory-intensive tasks to enforce a reduced parallelism at execution time. I've raised this separately (with a lot more detail) as a feature request (
al...@android.com <al...@android.com>
cr...@gmail.com <cr...@gmail.com> #4
Another use case that we have is to reactively respond to the creation of APKs and AABs. The new AGP APIs allow us to connect out tasks into the artifact pipeline via wiredWith
but the best we can come up with to receive the completed artifact is to wire in toTransform
. This A) does not guarantee that we will receive the final artifact as more transforms may be applied after our task is called, and B) requires us to copy the input property file/dir to our tasks output property file/dir in order to not break the build cache.
The reactive behavior of the above is the complicating factor.
A non-reactive approach could simply depend upon the task name and then look for a hardcoded path in the build directory (which is still sort of gross, since the build output paths are not documented as public API and change from time to time).
Another approach would be to wire a custom task to consume the output of the build via the built artifacts loader feeding an input property. However, this approach cannot be applied reactively. Either the custom task is included in the build and causes the creation of the binary artifact, or it is not included in the build and never gets invoked.
ni...@gmail.com <ni...@gmail.com> #6
We didn't provide a task wiring helper for that case as there's only one thing to wire, but I can see how the inconsistency can be misleading
au...@google.com <au...@google.com> #7
WRT variant.artifacts.get(SingleArtifact.APK))
, if the task is included in the build it will cause the creation of the artifact. Our build is currently defined to reactively perform some actions (predominantly some fancy reporting) only if work is actually performed.
We had previously been pushing our build to wire in to task outputs by locating tasks by type and referencing output properties as inputs to tasks registered via task finalizes
or dependsOn
relationships. This started getting more and more fragile as the AGP APIs migration proceeded/matured. I'm to the point now where I think the notion of reactive execution is hostile to the direction/expectations of both Gradle and AGP and want to start moving away from it, yet our build as it currently stands does rely on this behavior.
I bring up this up as a gap only because I don't know if I'll be able to completely refactor our CI pipeline's expectations in time for Gradle 8+.
[Deleted User] <[Deleted User]> #8
ni...@gmail.com <ni...@gmail.com> #9
Another minor functionality gap: We have a build that has test coverage enabled during test execution but then we manually disable the coverage report generation for all project modules as we have a custom coverage report task that creates an aggregate test coverage report for the entire project. This saves us the execution time, I/O, and protects us from Jacoco implementation instabilities.
We're currently using the following to accomplish this:
project.tasks.withType(JacocoReportTask::class.java) {
enabled = false
}
ni...@gmail.com <ni...@gmail.com> #10
Another gap, though my perhaps there's a better way to express this? Some of our builds leverage Flank to run instrumentation tests on Firebase Test Lab. These builds run as a single CI stage so as to afford Gradle the best opportunity to parallelize work. In this context, we have found that prioritizing instrumentation test assembly work early in the build allows the tests to dispatch to FTL earlier, minimizing overall build times. To implement this, we have chosen to be explicit on the inverse side by pushing lint and local unit test execution to be shouldRunAfter
the flank tasks which in turn depend on the instrumentation test assembly, etc.
Specifically:
private fun bumpFlankTask(project: Project, flankTasks: TaskCollection<FlankExecutionTask>) {
listOf(AndroidLintTask::class.java, AndroidLintAnalysisTask::class.java, AndroidUnitTest::class.java)
.forEach {
project.tasks.withType(it).configureEach {
shouldRunAfter(flankTasks)
}
}
}
This seems fairly specific to our project's desires and not necessarily transferable to other projects. I think our best option for the future Gradle 9+ might be to fallback to leveraging task names rather than leveraging task types in a generic fashion. Mentioning it here in case there is a better approach/option once the types are no longer available.
au...@google.com <au...@google.com> #11
Another gap we've found but no longer directly depend upon: when invoking BundleToStandaloneApkTask
the resulting universal APK does not appear to be accessible via the Artifacts API / ArtifactType.APK
- at least as of AGP 7.0.
We are able to no longer directly depend upon it because we are using the task name and a hardcoded build output directory path to locate the APK if/when it gets built. This is another symptom of our reactively defined build implementation. However, if we were to relay on
(phew! I think that's it for now? sorry for the dump, we're just starting to get caught up!)
ni...@gmail.com <ni...@gmail.com> #12
That last one (
[Deleted User] <[Deleted User]> #13
Ran into another use case that the API does not yet seem to support: AndroidUnitTest configuration for offline Jacoco instrumentation. Given that we've had to tweak task outputs to get this to work reliably with the build cache it is probably best corrected on the AGP side. Probably easiest just to paste the relevant code here:
/*
* -Djacoco-agent.destfile arg is used to configure the offline mode behavior of jacoco. The offline
* behavior is what is used when dependency module code is executed as it has already been
* instrumented by the jacoco-agent in previous executions. We redirect this to record the offline
* results under the build directory and give it a more explicit/identifiable name (it defaults
* to the project dir as jacoco.exec).
*
* NOTE: Attempts at using JacocoTaskExtension.setDestinationFile were unsuccessful in capturing coverage
*/
project.tasks.withType(AndroidUnitTest::class.java).configureEach {
val execFile = project.layout.buildDirectory.file("jacoco/offlineDependencies.exec").get()
jvmArgs("-Dfile.encoding=UTF-8", "-Djacoco-agent.destfile=${execFile}")
// Register our file as a task output to ensure it is restored via the build cache when execution is avoided
outputs.file(execFile)
doFirst {
// Make sure the coverage file is removed if it exists from a previous run
execFile.asFile.deleteRecursively()
}
}
ak...@gmail.com <ak...@gmail.com> #14
And one question:
We have some convention plugin code which is applied to many project module. It uses the components extension's onVariants
callback to reactively trigger some data capture but needs to know whether or not minification has been enabled for the build type, configured by a separate convention plugin. We have code similar to the following:
project.plugins.withId("com.android.application") { plugin ->
val extension = project.extensions.getByType(ApplicationAndroidComponentsExtension::class.java)
val android = project.extensions.getByType(ApplicationExtension::class.java)
extension.onVariants { variant ->
...
if (android.buildTypes.getByName(variant.buildType!!).isMinifyEnabled) {
...
}
ApplicationExtension
feels like more of an input API for AGP and not something we should be programmatically querying within the onVariants
callback. Given the exposure of other configuration values (e.g. variant.pseudoLocalesEnabled
as Property<Boolean>
), should isMinifyEnabled
also be exposed?
th...@gmail.com <th...@gmail.com> #15
Alex, can you look at #11 first, then at #13.
vn...@gmail.com <vn...@gmail.com> #16
#14, yes, it should probably be offered in ApplicationVariantBuilder.
gd...@gmail.com <gd...@gmail.com> #17
Alex and I looked a bit more carefully and we cannot make APK the result of the bundleToAPK task. The reason is that only one task can produce an artifact type at a time.
You cannot have an artifact type being produced by either the normal APK packaging task or the APKFromBundle task depending on what the user requested. In particular, if any script/plugin request the public APK artifact type, which task is supposed to run ?
The only way we can somehow satisfy #11 would be to have another public artifact type called APK_FROM_BUNDLE which would ensure that the bundle is created first, then the APK from the bundle. would that work ?
sh...@gmail.com <sh...@gmail.com> #18
We should really file separate bugs for all these comments. We can't just use a single bug for all of this.
au...@google.com <au...@google.com>
bo...@gmail.com <bo...@gmail.com> #19
I filed the following specific bugs:
->comment #4 Issue 232323922 ->comment #9 Issue 232324065 ->comment #11 Issue 232325458 ->comment #14 Issue 232325329
I have not yet filed anything related to Jacoco, as we probably need to discuss things a bit internally first.
ga...@google.com <ga...@google.com> #20
We develop a convention plugin which adds some code quality tasks(detekt, checklist, lint) to the build, whenever assemble task is invoked. We achieved this on the old api by obtaining the assemble task provider with BaseVariant.getAssembleProvider() method then adding our tasks as a dependency to it. However, I couldn't find any equivalent method in the new Variant API. I thought of registering a custom Default task, which depends on SingleArtifact.APK and all of our other custom tasks, then host apps could use this new task on their local and CI machines to create apk. However, this method is not optimal since some developers still can run the assemble task directly and bypass our code quality tasks and also, this new task will not be executed when developers run their project on the Android Studio by default. So I would like a way to include some tasks to the artifact creation even if they do not produce or use this artifact. What I can suggest is a way to make artifacts depend on tasks similar to how tasks can depend on artifacts. For instance, something similar to the following would solve our use case:
variant.artifacts.get(SingleArtifact.APK).getTaskProvider().configure {
it.dependsOn("detekt${variant.name.capitalize()}")
}
Currently, what we are doing to solve this problem is the following:
project.extensions.getByType(AndroidComponentsExtension::class.java).onVariants { variant ->
project.afterEvaluate {
project.tasks.named("assemble${variant.name.capitalize()}").configure {
it.dependsOn("detekt${variant.name.capitalize()}")
}
}
}
which I know is not recommended. Therefore, could you consider adding a new api for this case?
Another thing is I couldn't find a way to obtain "lint" task for the current variant without tasks.named("lint${variant.name.capitalize()}"). Could you also create a method to obtain lint task for the current variant, add dependency tasks for it. Adding dependency for lint is important for us since we download our lint configuration with a custom task and we want this configuration to be ready when "lint" needs it. (This case could also be solved by using Provider API in the lint block of the finalizeDsl, however currently the "lintConfig" property is declared as File in this block)
And lastly, it would be nice if InternalArtifactType.JAVA_DOC_DIR was a public artifact. If our convention is applied to a library project, whenever we publish the aar, we also publish its javadoc(and kdoc) to a remote server. For this we need to add custom Javadoc task and configure it properly for the variant. However, I think, this process is a bit complex. I could not properly configure the custom Javadoc task using the new variant api. With the old API I was using the following configuration:
source = variant.getJavaCompileProvider().get().source
classpath += project.files(project.provider { androidExtension.bootClasspath })
classpath += project.files(variant.getJavaCompileProvider().map { it.classpath })
Therefore, making InternalArtifactType.JAVA_DOC_DIR public would greatly simplify our implementation and solve our problems. Currently, only solution I found was to add dependency to "javaDoc${variant.name.capitalize()}Generation" task and hardcode its output path as "intermediates${File.separator}java_doc_dir${File.separator}$componentName". But I know this is really fragile and would love to see an easier and more conventional way to accomplish this.
ka...@gmail.com <ka...@gmail.com> #21
I'm developing a plugin for external sources compilation into *.so
files.
How can I inject final *.so
files into the final APK/AAR? I saw that AGP has AndroidArtifacts.ArtifactType.JNI{_SHARED}
But I have no idea how to use it and can't find any samples. I also can't put SingleArtifact.APK
because it is transformable but not appendable.
variant.artifacts.use(task)
.wiredWith { it.outputSoFolder }
.toAppendTo(...) // <-- what to put here?
EXTRA: how to add their debug symbols to LLDB during debugging from the plugin (same as Makefiles/CMake does).
cm...@gmail.com <cm...@gmail.com> #22
AndroidArtifacts.ArtifactType
is internal and not meant to be used by our API. The thing to pass to toAppendTo
would have to be a MultipleArtifact
but we don't expose many of them yet, and none that are useful for your use case.
At some point we may expose the intermediate artifact that is the final folder of all the .so
files, but that may not be what you want either. If it's the final folder, then it's a single folder, so you cannot append to it, and you can only transform it (which means taking the content, processing it, and writing the output in a different folder). This is not efficient when you want to add new files to it.
So, your use case actually is better positioned to use sourcesets rather than inject in an intermediate. We recently introduced
bo...@gmail.com <bo...@gmail.com> #23
I just ran into a need to modify android test manifests in my convention plugin. I intended to use the artifacts API to do this but it looks like there is no SingleArtifact.*
making this available. As per the AGP 7.2 docs on MERGED_MANIFEST
: "For each module, unit test and android test variants will not have a manifest file available.". Can we please get these added as well?
pu...@gmail.com <pu...@gmail.com> #25
We are currently using the old AndroidSourceSet
APIs to configure Checkstyle and Detekt for Android projects, as described in this issue:
As far as I can tell, this is not yet covered by the new APIs and this would likely apply to other static analysis tools that need to process source files as well.
ga...@gmail.com <ga...@gmail.com> #26
I created a new ticket talking about this AndroidSourceSet
use case here:
sr...@gmail.com <sr...@gmail.com> #27
My project currently uses the javaCompileProvider
and preBuildProvider
APIs of the BaseVariant
class.
Fir the javaCompileProvider
case, we use this mainly in Application projects. We have some custom code generation tasks that we run that require the classpath of the application be available (so we use the output of the task), and that we want to be done "at the same time" before tasks that depend on JavaCompile complete have the code we generate ready as well. Example usage is:
android.applicationVariants.configureEach { variant ->
val customTaskOne = project.tasks.register("customTaskOne${variant.name.capitalized()") {
dependsOn(variant.javaCompileProvider, kotlinCompileTask)
}
val javaCompileOutput = variant.javaCompileProvider.get().destinationDirectory.get().asFile
val codeGenerationAction = CodeGenAction(javaCompileOutput, variant)
variant.javaCompileProvider.configure {
finalizedBy(customTaskOne)
doLast(codeGenerationAction)
}
}
I understand this is a bit janky, but I'm looking to update the code and make sure we're doing things "The Right Way(tm)" going forward. If there's a similar way to accomplish what we're looking for, I'd love to know about it, especially if it's a tool or technique that I'm not familiar with.
For the preBuildProvider
usecase, we're essentially generating some code early on in the process that just needs to be ready. I can likely use some other method of having this task run (as it doesn't really depend on anything else other than being done before the APK is packaged). What would be the suggested way with the new gradle-api
classes to perform this sort of work?
EDIT: I've spent the last few days looking through the .class
file for inclusion in the dex or generate a JSON file for inclusion in the application's assets
.
For reference, all of this is with AGP 7.4.
I tried using the same task for both to see if AGP would know how to handle that:
variant.artifacts
.forScope(ScopedArtifacts.Scope.ALL)
.use(scannerTask)
.toAppend(
ScopedArtifact.CLASSES,
ScannerTask::output
)
variant.artifacts.forScope(ScopedArtifacts.Scope.ALL)
.use(scannerTask)
.toGet(ScopedArtifact.CLASSES,
ScannerTask::allJars,
ScannerTask::allDirectories)
but that led to things just not executing. I didn't see anything with the task name in the --debug
output. So, I went ahead and tried using two tasks: one for toGet
that would scan all the input and generate the .class
file, and one that would then take that output of that scan task and then add it using toAppend
. Attempting to do this led to a circular dependency, leading me to believe that toGet
is ALWAYS executed last.
So, I went ahead and tried using toTransform
:
variant.artifacts.forScope(ScopedArtifacts.Scope.ALL)
.use(scannerTask)
.toTransform(
ScopedArtifact.CLASSES,
ScannerTask::allJars,
ScannerTask::allDirectories,
ScannerTask::output
)
And that worked! The class was generated, and included in the dex
file. The problem was that the API was expecting me to essentially touch every input file and then add them to the output. That sounds like it's going to kill my build times.
Am I on the right track here and maybe just missing an API to use? Or is this use case not supported by the current APIs?
af...@google.com <af...@google.com> #28
You are correct, the toTransform
is the only API you can use in your case because you are trying to get the final version of the artifact in your scannerTask
while also trying to append (from the same Task). Even if you used 2 tasks, you would end up in a circular dependency.
You are also correct this is not going to be great for your build time.
One of the way I can think of would be to make a new version of toTransform
that would be a lot smarter and allow you to tag unchanged jars/directories. I think that would solve your case completely ?
But in the meantime, maybe using a KSP or plain old annotation processor might be another solution, not exactly sure about your constraints.
Description
Version used: 25.0.0
Theme used: Any
Devices/Android versions reproduced on: Any >= KitKat
BottomNavigationView is missing:
- Scrolling behavior inside CoordinatorLayout
- 8dp elevation in order to be above the snackbar.
Relevant link: