This message was deleted.
# community-support
s
This message was deleted.
v
Not sure what you mean. I guess you do not mean "implicit dependencies" but "missing dependencies". Having "implicit dependencies" is still the idiomatic way to go. Explicit dependencies should practically only declared with lifecycle tasks on the right-hand side of the
dependsOn
. But I'm not sure how
layout.buildDirectory
or its absence or presence should be anyhow related to broken builds where tasks uses outputs of other tasks without having a proper dependency and thus are flaky and broken by nature. Just have all dependencies you should have, preferably implicitly, then there should be no problem for upgrading in that regard.
Where you could get into trouble if plugins do stupid things, like declaring a
Copy
task with
layout.buildDirectory
as
into
. But that is majorly evil and should be fixed anyway.
z
why is that a problem?
what I mean is:
Copy code
build/task1Output/
build/task2Output/
both
task1
and
task2
are coupled to the creation of the
build/
folder
v
Because then the whole build directory is the output of the task. Any file present there is considered an output even if not really an output. That disturbs up-to-date checks, it disturbs cachability, it hurts performance during fingerprinting, it can make problems if there are unreadable files, .........
Nah, that is not a problem
Whether the intermediary
build
is created or not is meaningless
If those tasks have the dedicated
task1Output
directory as output and
task2Output
as output, there is no problem at all
build
is not an output, it is just an automatically created parent directory.
The problem would be if
build
directly would be the output directory of a task
z
I’m getting errors because:
Copy code
build/someAgpTask/
build/myTask/
someAgpTask
created the original
build
folder, and
myTask
uses it. so I’m getting the implicit dependency error
v
I think you misinterpret what you see
z
I hope so bc otherwise it’s a huge problem lol
v
It would be pretty much non-sense actually
If
someAgpTask
defines
build
as output directory and then in there creates
someAgpTask
, that would be evil and bad. But if
someAgpTask
is the output directory, there is no problem.
If
myTask
uses the contents of
someAgpTask
directory and has no implicit or explicit dependency, that is when you get an error
But you would need to at least show the error message
z
Copy code
- Gradle detected a problem with the following location: '/Users/zak/Development/git/my-app/my-android-lib/build'.
    
    Reason: Task ':my-android-lib:createMyDirectory' uses this output of task ':my-android-lib:compileReleaseRenderscript' without declaring an explicit or implicit dependency. This can lead to incorrect results being produced, depending on what order the tasks are executed.
    
    Possible solutions:
      1. Declare task ':my-android-lib:compileReleaseRenderscript' as an input of ':my-android-lib:createMyDirectory'.
      2. Declare an explicit dependency on ':my-android-lib:compileReleaseRenderscript' from ':my-android-lib:createMyDirectory' using Task#dependsOn.
      3. Declare an explicit dependency on ':my-android-lib:compileReleaseRenderscript' from ':my-android-lib:createMyDirectory' using Task#mustRunAfter.
compileReleaseRenderscript
(AGP task) creates the
build
folder on the first run bc it doesn’t exist, but my
:my-android-lib:createMyDirectory
task fails because it needs that
build
folder as input to create
build/myDirectory
v
Well yeah, seems
compileReleaseRenderscript
has
build
defined as output directory. Again, it has nothing to do with who creates the directory. The task evilly defines the whole
build
directory as output directory, that is the problem.
That's exactly such a misbehavior I mentioned above
z
so it’s an AGP bug then
v
I don't know, I'm not an Android developer. Do you have an MCVE?
e
I thought the Android build tools were dropping support for renderscript anyway
v
Yes, starting with Android 12, the RenderScript APIs are deprecated
e
https://developer.android.com/guide/topics/renderscript/compute
Following the deprecation of RenderScript in the Android platform, we are also removing support for RenderScript in the Android Gradle plugin. Starting with Android Gradle plugin 7.2, the RenderScript APIs are deprecated. They will continue to function, but will invoke warnings, and will be completely removed in future versions of AGP.
it doesn't say which future version of AGP, but at some unspecified point…
anyhow, even if it's an AGP bug I think it'll be unlikely to be fixed
v
But with an MCVE maybe someone can provide a work-around
👍 1
In the worst case by monkey-patching AGP
z
for the record, I’m seeing errors with these AGP tasks too.
Copy code
processReleaseManifest
generateReleaseResValues
compileReleaseRenderscript
Copy code
com.android.build.gradle.tasks.RenderscriptCompile
com.android.build.gradle.tasks.ProcessLibraryManifest
com.android.build.gradle.tasks.AidlCompile
com.android.build.gradle.tasks.GenerateResValues
I’m using AGP
7.4.1
- which I guess is then incompatible with Gradle 8.0 because of that. But of course, I can’t upgrade to AGP 8.0 due to an unrelated issue. Presuming this is an AGP bug, Gradle could have given AGP/plugin authors a flag to turn this implicit dependency warning into an error
v
What for?
z
so AGP
7.x
could turn these warnings into errors instead of having to wait for
8.x
for that behavior
is defining this like so correct?
Copy code
// @OutputDirectory
// val output = objects.directoryProperty()
output.set(
    layout
        .buildDirectory
        .dir("generatedBuildConfigConstant/$name/$sourceSet")
)
or do I need to create a dedicated task to create
build/generatedBuildConfigConstant/name/sourceSet/
v
No, `@OutputDirectory`s are generated automatically by Gradle before your task runs
So yes, that's probably ok
z
okay so here’s where I’m hitting the problem then - I’m using tasks to generate directories. Which means, at some point, a non-existent
layout.buildDir
is an
@InputDirectory
v
Again, existing or non-existing is irrelevant. But why the heck should the whole build directory be your input? What crazy task is that? And if that is really the case, well, then it probably needs dependencies on all other tasks.
Otherwise some inputs might or might not be there
z
But why the heck should the whole build directory be your input?
I started that workaround for possible solution #2 below:
Copy code
org.gradle.internal.execution.WorkValidationException: A problem was found with the configuration of task ':my-android-lib:createEmptyObfuscationFile' (type 'CreateEmptyObfuscationFile').	
  - Type 'my.app.publishing.internal.tasks.CreateEmptyObfuscationFile' property 'outputDir' specifies directory '/Users/zak/Development/git/my-app/my-android-lib/build/artifactory-publishing/artifacts' which doesn't exist.	
    	
    Reason: An input file was expected to be present but it doesn't exist.	
    	
    Possible solutions:	
      1. Make sure the directory exists before the task is called.	
      2. Make sure that the task which produces the directory is declared as an input.	
    	
    Please refer to <https://docs.gradle.org/8.0/userguide/validation_problems.html#input_file_does_not_exist> for more details about this problem.
v
Sounds like you are doing the opposite though
z
:my-android-lib:createEmptyObfuscationFile
receives the output of
:my-android-lib:createMyDirectory
as its
@InputDirectory
.
:my-android-lib:createMyDirectory
uses
layout.buildDirectory
as its
@InputDirectory
to create
build/myDirectory
. However, gradle then detects that a bunch of other AGP tasks end up created
build/
- which puts me in this problem
v
Also, why is a property called
outputDir
annotated with
@InputDirectory
?
:my-android-lib:createMyDirectory
uses
layout.buildDirectory
as its
@InputDirectory
to create
build/myDirectory
.
This is the non-sense part
z
Copy code
internal open class CreateDirectoryTask @Inject constructor(
    objects: ObjectFactory,
) : DefaultTask() {

    // this ends up being `layout.buildDirectory`
    @InputDirectory
    val parentDir: DirectoryProperty = objects.directoryProperty()

    @Input
    val directoryName: Property<String> = objects.property(String::class.java)

    @OutputDirectory
    val outputDir: Provider<Directory> = parentDir
        .map { it.dir(directoryName.get()) }

    @TaskAction
    fun doWork() {
        outputDir.get().asFile.mkdirs()
    }
}
v
layout.buildDirectory
is not an input directory, you do not use the files in there as input for your task. It simply is the parent directory of your
@OutputDirectory
, that's all
z
ohhh
lol
this part of gradle has confused me so much, lol
where
@OutputDirectory
is kind of input, really
v
It's not.
@OutputDirectory
is the output directory
Just mark
parentDir
as
@Internal
and actually you could do so for
directoryName
too
z
I plan on chaining multiple
CreateDirectoryTask
btw
v
Because both combined are already considered in the
@OutputDirectory
already. If you change either of those properties, the
@OutputDirectory
changes too and thus the task already is out-of-date
e
why do you even need tasks to mkdir
⁉️ 2
👀 2
that should be up to whatever tasks produces output in those directories
v
Not even that, if it is an
@OutputDirectory
of a task, Gradle already creates it before the task begins
So this task could actually be a no-op
z
why do you even need tasks to mkdir
I previously used
mkDirs()
in the configuration phase to workaround dirs not existing. Which does not work well with the config cache
e
that too, yes. but I mean like, making subdirectories of the output directory: that should just be done by whatever task is writing files into them
do it in the execution phase, same as when you produce the output files
having one task mkdir and another task write into the same directory is overlapping outputs, don't do that
☝️ 1
v
Copy code
internal abstract class CreateDirectoryTask : DefaultTask() {
    @Internal
    abstract val parentDir: DirectoryProperty

    @Internal
    abstract val directoryName: Property<String>

    @OutputDirectory
    val outputDir: Provider<Directory> = parentDir
        .map { it.dir(directoryName.get()) }
}
❤️ 1
😄
As @ephemient said, you must not have any other task writing files to those directories
z
having one task mkdir and another task write into the same directory is overlapping outputs, don’t do that
I’ll generally have
build/myPlugin
and then:
Copy code
build/myPlugin/task1Output/
build/myPlugin/task2Output/
build/myPlugin/task3Output/
e
this might even work:
Copy code
val outputDir
    @OutputDirectory get() = parentDir.dir(directoryName.get())
v
Also thought about it, but that would eagerly call
directoryName.get()
e
not if you write it in
get()
v
Ah, right
But anyway,
I’ll generally have
build/myPlugin
and then:
```build/myPlugin/task1Output/
build/myPlugin/task2Output/
build/myPlugin/task3Output/```
Baaaaad, as @ephemient said
The task that generates
task1Output
writes in the output directory of the task that created
myPlugin
, so you are doomed!
e
well, it's OK to group them as long as
build/myPlugin
itself isn't the output of any task
but yeah, that
v
It is, he said he chains those tasks
So that is most probably exactly what he does
z
What I was doing before was:
Copy code
internal val ProjectLayout.artifactsDir
    get(): Provider<Directory> = buildDirectory.dir("my-publishing-plugin")
        .map { it.dir("artifacts") }
internal val ProjectLayout.mavenPublishPluginDir
    get(): Provider<Directory> = artifactsDir
        .map { it.dir("maven-publish-plugin-output") }
internal val ProjectLayout.libraryArtifactDir
    get(): Provider<Directory> = artifactsDir
        .map { it.dir("library") }
so I have nice little accessors:
Copy code
layout.artifactsDir
layout.mavenPublishPluginDir
layout.libraryArtifactDir
But I think I hit bugs in my code around the directories not existing because these are used as `@InputDirectory`s. It may be bc I am not using
@OutputDirectory
correctly to have gradle automatically create these dirs
e
if they are output directories, they should be
@OutputDirectory
, even if they are a
DirectoryProperty
that is configurable as an "input"
❤️ 1
it being a settable property doesn't change the fact that it is the task output
👍 1
v
And using those nice little accessors for configuring your inputs is bad too, because you then exactly miss the dependency to the task producing it. Instead wire the task producing those outputs as inputs to the tasks needing it
👍 1
e
oh I missed that was the intention… looks like it is. yes, definitely. Gradle will complain about undeclared dependencies if you use those accessors to set up inputs.
👍 1
e.g. this wires everything up with dependency tracking:
Copy code
val task1 = tasks.register<Task1> {
    outputDirectory.set(layout.buildDirectory.dir("my/foo"))
}
val task2 = tasks.register<Task2> {
    inputDirectory.set(task1.flatMap { it.outputDirectory })
}
👍 1
z
My flaw was here:
Copy code
@InputDirectory
val outputDir = layout.artifactsDir
v
Why do you create an empty obfuscation file? Do you then with another task fill it with content? Then you are doomed yet again.
z
no it’s just a placeholder no-op file
v
Never ever ever have two tasks with the same or overlapping outputs or a task that manipulates the outputs of another task, never, ..., ever, ... unless you really know what dirty thing you do and why and what the implications are.
👍 1
Ok, good 🙂
e
having
ProjectLayout
and
TaskContainer
injected into a task looks a bit odd. you shouldn't be using them at execution time and you don't need them at configuration time. I don't think an immediate problem though
👍 1
z
silly little workaround for an AGP missing feature
e
I'm not sure what this is working around, but if you need an empty file as an input for something, I would just check one in.
v
Yeah, no, not really. You do not use
exec
, you do not use
tasks
.
layout
is probably fine, but of course again having it as
@InputDirectory
is non-sense
👍 1
1
z
I would just check one in.
it’s on a per-project basis and whether it is needed or not is dynamic, based on whether proguard/R8 are leveraged
e
you'd usually set it outside of the task… but even from inside, isn't
project.layout
fine during the configuration phase? as long as it's not getting serialized
are you trying to set an empty proguard rules file? why not just disable minification in the variant you want to do that in