I have question regarding configuration cache. I w...
# caching
d
I have question regarding configuration cache. I want to execute task only when other task is execute without configuration-cache i just use
onlyIf(task.project.tasks.getByName('compileJava').state.skipped)
v
Create a shared build service that is also implementing
OperationCompletionListener
. Register it as the latter. In the listener implementation check the outcome of the "other" task in question and store it in a property in the build service, then check that property in
onlyIf { ... }
of the dependent task.
a
Maybe you can solve this by changing a perspective and not thinking "if task X ran, run task Y" but "if outputs of task X changed, run task Y". Then it's much simpler job, since you can just define outputs of X as inputs of Y
v
Or if it is a situation like "shut down the test webserver if the test task did actual work", then neither nor, but more fully use a shared build service by starting the webserver in the shared build service constructor, shut it down in the
close
method while implementing
AutoCloseable
and then just use the service, then it will automatically started only when necessary and shut down when necessary.
d
Not its simple thing its about bootBuildImage and i don't want to build bootImage if compileJava is skipped or output of comileJava come from cache.
@Anze Sodja so its more "If task X output is from Cache, skip running task Y"
v
That output is coming from cache does not mean that the downstream task is up-to-date. You can switch to an older commit, run the build, that state was not in cache so task is run and image updated. Then you switch back to newer commit, run the build, that sate was already in cache so task is not executed and your image stays on the old state. ...
d
I know this, but we have monorepo usual use case is to build just 1 project do need to come into place. I want only build project if their shared library is changed.
v
Gradle is usually pretty good in avoiding unnecessary work
d
Here is problem is springBootBuild image is not cached and i don't want it rebuild it when cache doesnt change. This step is locally skipped but in CI/CD not
v
If it is expensive to execute, why is it not cacheable?
d
because that mean you need to have docker registry in each builder with images.
🧐 1
Ok i solved it creating plugin:
Copy code
public abstract class CompilationDetectionPlugin implements Plugin<Project> {
    @Inject
    public abstract BuildEventsListenerRegistry getEventsListenerRegistry();

    public abstract static class CompileJavaDetectService implements BuildService<BuildServiceParameters.None>,
            OperationCompletionListener {

        private Map<String,Boolean> projectRebuild;
        private String projectPath;


        public boolean projectRebuild(String projectPath) {
            return projectRebuild.getOrDefault(projectPath, false);
        }


        public CompileJavaDetectService() {
            this.projectRebuild = new HashMap<>();
            this.projectPath = ":compileJava";
        }

        @Override
        public void onFinish(FinishEvent finishEvent) {
            if (finishEvent instanceof TaskFinishEvent taskFinishEvent) {
                String taskPath = taskFinishEvent.getDescriptor().getTaskPath();
                if (taskPath.endsWith(projectPath)) {
                    if (taskFinishEvent.getResult() instanceof TaskSuccessResult result) {
                        boolean rebuild = !(result.isFromCache() || result.isUpToDate());
                        this.projectRebuild.put(taskPath.substring(0,taskPath.length() - projectPath.length()), rebuild);
                    }
                }
            }
        }
    }

    @Override
    public void apply(Project project) {
        Provider<CompileJavaDetectService> serviceProvider =
                project.getGradle().getSharedServices().registerIfAbsent(
                        "compilationJavaDetection", CompileJavaDetectService.class, spec -> {});

        getEventsListenerRegistry().onTaskCompletion(serviceProvider);
    }
}
And then using it:
Copy code
project.plugins.apply(CompilationDetectionPlugin)
bootBuildImage {
    afterEvaluate {

        var projectRelativePathToRoot = project.getRootDir().relativePath(project.getProjectDir())
        var service = project.gradle.sharedServices.registrations.compilationJavaDetection.service
        var projectPath = project.path
        onlyIf(task ->  service.get().projectRebuild(projectPath)
                || changedFiles.stream().anyMatch(x -> x.startsWith(projectRelativePathToRoot)))   
}}
v
You should not use
project.plugins
(look at its JavaDoc, but directly
project.apply
or better a
plugins { ... }
block if you are in a build script or precompiled script plugin. Why do you think you need the highly discouraged and bad practice
afterEvaluate { ... }
? The main earnings you get from using it are ordering problems, timing problems, and race conditions. Besides that it is not obvious to me why it should change anything in that snippet. You should probably declare the task's usage of the service with
usesService(...)
.
d
Thanks I changed to
project.apply({ plugin(CompilationDetectionPlugin) })
. Another thing afterEvalution is used because in each submodule we define variable which is used as image name and when i remove this image name is not defined.
Ok i find it correct way is to remove afterEvaluate and use:
project.provider { "$signedImageName" }
v
project.apply({ plugin(CompilationDetectionPlugin) })
or just
project.apply(plugin: CompilationDetectionPlugin)
🙂 Btw. I strongly recommend Kotlin DSL. By now it is the default DSL, you immediately get type-safe build scripts, actually helpful error messages if you mess up the syntax, and amazingly better IDE support if you use a good IDE like IntelliJ IDEA or Android Studio. 🙂
d
Thanks i refactored to
project.apply({ plugin(CompilationDetectionPlugin) })
For moving from Groovy to Kotlin DSL we willl do ti later on.
👌 1