-
Notifications
You must be signed in to change notification settings - Fork 59
[MBUILDCACHE-67] Bugfix artifact restoration error not handled properly #92
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8d5de9f
4d53a0a
d7169c3
3d1c908
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,12 +24,15 @@ | |
|
|
||
| import java.io.File; | ||
| import java.nio.file.Path; | ||
| import java.util.HashSet; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Set; | ||
|
|
||
| import org.apache.commons.lang3.ArrayUtils; | ||
| import org.apache.commons.lang3.StringUtils; | ||
| import org.apache.maven.SessionScoped; | ||
| import org.apache.maven.buildcache.artifact.ArtifactRestorationReport; | ||
| import org.apache.maven.buildcache.checksum.MavenProjectInput; | ||
| import org.apache.maven.buildcache.xml.Build; | ||
| import org.apache.maven.buildcache.xml.CacheConfig; | ||
|
|
@@ -109,8 +112,9 @@ public void execute( | |
| // Forked execution should be thought as a part of originating mojo internal implementation | ||
| // If forkedExecution is detected, it means that originating mojo is not cached so forks should rerun too | ||
| boolean forkedExecution = lifecyclePhasesHelper.isForkedProject(project); | ||
| List<MojoExecution> cleanPhase = null; | ||
| if (source == Source.LIFECYCLE && !forkedExecution) { | ||
| List<MojoExecution> cleanPhase = lifecyclePhasesHelper.getCleanSegment(project, mojoExecutions); | ||
| cleanPhase = lifecyclePhasesHelper.getCleanSegment(project, mojoExecutions); | ||
| for (MojoExecution mojoExecution : cleanPhase) { | ||
| mojoExecutionRunner.run(mojoExecution); | ||
| } | ||
|
|
@@ -128,8 +132,12 @@ public void execute( | |
| boolean restorable = result.isSuccess() || result.isPartialSuccess(); | ||
| boolean restored = result.isSuccess(); // if partially restored need to save increment | ||
| if (restorable) { | ||
| restored &= restoreProject(result, mojoExecutions, mojoExecutionRunner, cacheConfig); | ||
| } else { | ||
| CacheRestorationStatus cacheRestorationStatus = | ||
| restoreProject(result, mojoExecutions, mojoExecutionRunner, cacheConfig); | ||
| restored &= CacheRestorationStatus.SUCCESS == cacheRestorationStatus; | ||
| executeExtraCleanPhaseIfNeeded(cacheRestorationStatus, cleanPhase, mojoExecutionRunner); | ||
| } | ||
| if (!restored) { | ||
| for (MojoExecution mojoExecution : mojoExecutions) { | ||
| if (source == Source.CLI | ||
| || mojoExecution.getLifecyclePhase() == null | ||
|
|
@@ -155,6 +163,31 @@ public void execute( | |
| } | ||
| } | ||
|
|
||
| /** | ||
| * Cache configuration could demand to restore some files in the project directory (generated sources or even arbitrary content) | ||
| * If an error occurs during or after this kind of restoration AND a clean phase was required in the build : | ||
| * we execute an extra clean phase to remove any potential partially restored files | ||
| * | ||
| * @param cacheRestorationStatus the restoration status | ||
| * @param cleanPhase clean phase mojos | ||
| * @param mojoExecutionRunner mojo runner | ||
| * @throws LifecycleExecutionException | ||
| */ | ||
| private void executeExtraCleanPhaseIfNeeded( | ||
| final CacheRestorationStatus cacheRestorationStatus, | ||
| List<MojoExecution> cleanPhase, | ||
| MojoExecutionRunner mojoExecutionRunner) | ||
| throws LifecycleExecutionException { | ||
| if (CacheRestorationStatus.FAILURE_NEEDS_CLEAN == cacheRestorationStatus | ||
| && cleanPhase != null | ||
| && !cleanPhase.isEmpty()) { | ||
| LOGGER.info("Extra clean phase is executed as cache could be partially restored."); | ||
| for (MojoExecution mojoExecution : cleanPhase) { | ||
| mojoExecutionRunner.run(mojoExecution); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private Source getSource(List<MojoExecution> mojoExecutions) { | ||
| if (mojoExecutions == null || mojoExecutions.isEmpty()) { | ||
| return null; | ||
|
|
@@ -167,7 +200,7 @@ private Source getSource(List<MojoExecution> mojoExecutions) { | |
| return Source.LIFECYCLE; | ||
| } | ||
|
|
||
| private boolean restoreProject( | ||
| private CacheRestorationStatus restoreProject( | ||
| CacheResult cacheResult, | ||
| List<MojoExecution> mojoExecutions, | ||
| MojoExecutionRunner mojoExecutionRunner, | ||
|
|
@@ -183,14 +216,34 @@ private boolean restoreProject( | |
| final List<MojoExecution> cachedSegment = | ||
| lifecyclePhasesHelper.getCachedSegment(project, mojoExecutions, build); | ||
|
|
||
| boolean restored = cacheController.restoreProjectArtifacts(cacheResult); | ||
| if (!restored) { | ||
| // Verify cache consistency for cached mojos | ||
| LOGGER.debug("Verify consistency on cached mojos"); | ||
| Set<MojoExecution> forcedExecutionMojos = new HashSet<>(); | ||
| for (MojoExecution cacheCandidate : cachedSegment) { | ||
| if (cacheController.isForcedExecution(project, cacheCandidate)) { | ||
| forcedExecutionMojos.add(cacheCandidate); | ||
| } else { | ||
| if (!verifyCacheConsistency( | ||
| cacheCandidate, build, project, session, mojoExecutionRunner, cacheConfig)) { | ||
| LOGGER.info("A cached mojo is not consistent, continuing with non cached build"); | ||
| return CacheRestorationStatus.FAILURE; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Restore project artifacts | ||
| ArtifactRestorationReport restorationReport = cacheController.restoreProjectArtifacts(cacheResult); | ||
| if (!restorationReport.isSuccess()) { | ||
| LOGGER.info("Cannot restore project artifacts, continuing with non cached build"); | ||
| return false; | ||
| return restorationReport.isRestoredFilesInProjectDirectory() | ||
| ? CacheRestorationStatus.FAILURE_NEEDS_CLEAN | ||
| : CacheRestorationStatus.FAILURE; | ||
| } | ||
|
|
||
| // Execute mandatory mojos (forced by configuration) | ||
| LOGGER.debug("Execute mandatory mojos in the cache segment"); | ||
| for (MojoExecution cacheCandidate : cachedSegment) { | ||
| if (cacheController.isForcedExecution(project, cacheCandidate)) { | ||
| if (forcedExecutionMojos.contains(cacheCandidate)) { | ||
| LOGGER.info( | ||
| "Mojo execution is forced by project property: {}", | ||
| cacheCandidate.getMojoDescriptor().getFullGoalName()); | ||
|
|
@@ -206,31 +259,20 @@ private boolean restoreProject( | |
| // org.apache.maven.api.MojoExecution.class, new DefaultMojoExecution(cacheCandidate)); | ||
| mojoExecutionRunner.run(cacheCandidate); | ||
| } else { | ||
| restored = verifyCacheConsistency( | ||
| cacheCandidate, build, project, session, mojoExecutionRunner, cacheConfig); | ||
| if (!restored) { | ||
| break; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (!restored) { | ||
| // cleanup partial state | ||
| project.getArtifact().setFile(null); | ||
| project.getArtifact().setResolved(false); | ||
| mojoListener.remove(project); | ||
| // build as usual | ||
| for (MojoExecution mojoExecution : cachedSegment) { | ||
| mojoExecutionRunner.run(mojoExecution); | ||
| LOGGER.info( | ||
| "Skipping plugin execution (cached): {}", | ||
| cacheCandidate.getMojoDescriptor().getFullGoalName()); | ||
| } | ||
| } | ||
|
|
||
| // Execute mojos after the cache segment | ||
| LOGGER.debug("Execute mojos post cache segment"); | ||
| List<MojoExecution> postCachedSegment = | ||
| lifecyclePhasesHelper.getPostCachedSegment(project, mojoExecutions, build); | ||
| for (MojoExecution mojoExecution : postCachedSegment) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @kbuntrock, it is out of scope, but please notice two edge cases here:
|
||
| mojoExecutionRunner.run(mojoExecution); | ||
| } | ||
| return restored; | ||
| return CacheRestorationStatus.SUCCESS; | ||
| } finally { | ||
| mojoExecutionScope.exit(); | ||
| } | ||
|
|
@@ -263,13 +305,14 @@ private boolean verifyCacheConsistency( | |
|
|
||
| if (consistent) { | ||
| long elapsed = System.currentTimeMillis() - createdTimestamp; | ||
| LOGGER.info("Skipping plugin execution (reconciled in {} millis): {}", elapsed, fullGoalName); | ||
| } | ||
|
|
||
| if (LOGGER.isDebugEnabled()) { | ||
| LOGGER.debug( | ||
| "Checked {}, resolved mojo: {}, cached params: {}", fullGoalName, mojo, completedExecution); | ||
| "Plugin execution will be skipped ({} : reconciled in {} millis)", elapsed, fullGoalName); | ||
| } | ||
|
|
||
| LOGGER.debug( | ||
| "Checked {}, resolved mojo: {}, cached params: {}", fullGoalName, mojo, completedExecution); | ||
|
|
||
| } catch (PluginContainerException | PluginConfigurationException e) { | ||
| throw new LifecycleExecutionException("Cannot get configured mojo", e); | ||
| } finally { | ||
|
|
@@ -278,8 +321,8 @@ private boolean verifyCacheConsistency( | |
| } | ||
| } | ||
| } else { | ||
| LOGGER.info( | ||
| "Skipping plugin execution (cached): {}", | ||
| LOGGER.debug( | ||
| "Plugin execution will be skipped ({} : cached)", | ||
| cacheCandidate.getMojoDescriptor().getFullGoalName()); | ||
| } | ||
|
|
||
|
|
@@ -364,4 +407,10 @@ private static String normalizedPath(Path path, Path baseDirPath) { | |
| } | ||
| return normalizedPath; | ||
| } | ||
|
|
||
| private enum CacheRestorationStatus { | ||
| SUCCESS, | ||
| FAILURE, | ||
| FAILURE_NEEDS_CLEAN; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -46,6 +46,7 @@ | |
| import java.util.UUID; | ||
| import java.util.concurrent.ConcurrentHashMap; | ||
| import java.util.concurrent.ConcurrentMap; | ||
| import java.util.concurrent.ExecutionException; | ||
| import java.util.concurrent.Future; | ||
| import java.util.concurrent.FutureTask; | ||
| import java.util.regex.Pattern; | ||
|
|
@@ -55,8 +56,10 @@ | |
| import org.apache.commons.lang3.StringUtils; | ||
| import org.apache.commons.lang3.mutable.MutableBoolean; | ||
| import org.apache.maven.SessionScoped; | ||
| import org.apache.maven.artifact.InvalidArtifactRTException; | ||
| import org.apache.maven.artifact.handler.ArtifactHandler; | ||
| import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager; | ||
| import org.apache.maven.buildcache.artifact.ArtifactRestorationReport; | ||
| import org.apache.maven.buildcache.artifact.RestoredArtifact; | ||
| import org.apache.maven.buildcache.checksum.MavenProjectInput; | ||
| import org.apache.maven.buildcache.hash.HashAlgorithm; | ||
|
|
@@ -295,10 +298,13 @@ private boolean canIgnoreMissingSegment(MavenProject project, Build info, List<M | |
| } | ||
|
|
||
| @Override | ||
| public boolean restoreProjectArtifacts(CacheResult cacheResult) { | ||
| public ArtifactRestorationReport restoreProjectArtifacts(CacheResult cacheResult) { | ||
|
|
||
| LOGGER.debug("Restore project artifacts"); | ||
| final Build build = cacheResult.getBuildInfo(); | ||
| final CacheContext context = cacheResult.getContext(); | ||
| final MavenProject project = context.getProject(); | ||
| ArtifactRestorationReport restorationReport = new ArtifactRestorationReport(); | ||
|
|
||
| try { | ||
| RestoredArtifact restoredProjectArtifact = null; | ||
|
|
@@ -325,6 +331,8 @@ public boolean restoreProjectArtifacts(CacheResult cacheResult) { | |
| // it may also be disabled on a per-project level (defaults to true - enable) | ||
| if (cacheConfig.isRestoreGeneratedSources() | ||
| && MavenProjectInput.isRestoreGeneratedSources(project)) { | ||
| // Set this value before trying the restoration, to keep a trace of the attempt if it fails | ||
| restorationReport.setRestoredFilesInProjectDirectory(true); | ||
| // generated sources artifact | ||
| final Path attachedArtifactFile = | ||
| localCache.getArtifactFile(context, cacheResult.getSource(), attachedArtifactInfo); | ||
|
|
@@ -348,11 +356,11 @@ public boolean restoreProjectArtifacts(CacheResult cacheResult) { | |
| project.setArtifact(restoredProjectArtifact); | ||
| } | ||
| restoredAttachedArtifacts.forEach(project::addAttachedArtifact); | ||
| return true; | ||
| restorationReport.setSuccess(true); | ||
| } catch (Exception e) { | ||
| LOGGER.debug("Cannot restore cache, continuing with normal build.", e); | ||
| return false; | ||
| } | ||
| return restorationReport; | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -401,6 +409,26 @@ private Future<File> createDownloadTask( | |
| }); | ||
| if (!cacheConfig.isLazyRestore()) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like a more relevant name for the original intent is
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "LazyRestore" seems fine to me. I would instead replace "download" by "restore" since there is no downloading involved when working with a local cache. But it's not something I will do in this PR. :P |
||
| downloadTask.run(); | ||
| try { | ||
| downloadTask.get(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi Kevin. Please elaborate, what are the benefits of implementing this. Because drawbacks are apparent - cached artifacts not used in the actual build might fail the cache now (exotic classifiers, etc.). If that starts happening, it is a more serious issue than a delayed error. The change has its own merits, but the default behavior should be the most reasonable considering all the cases.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hello Alexander, nice to meet you. :) As I apprehend the "lazy restore" functionality, there are two approach, each one with their pros and cons. Non lazy restore (the default) : immediate restoration of artefact and other build outputs. Lazy restore : restoration of artefacts is done only when used. It can save some bandwidth and speedup the build when used with a remote cache. (Pros with a local cache are not so clear to me.) Con: If the cache is corrupted/not accessible and an artefact must be used as a dependency in another module : the build is doomed and will fail. Actually, the non lazy restore does not really work as expected since the restore is done (download is immediately launched when using a remote cache), but we don't check the result straight away. It might be far too late when we'll discover the artefact did not restored well. Am I missing something in the picture?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi. The main question is whether or not to fail the cache when unused (in reactor) artifacts cannot be downloaded. I’m trying to understand what are the benefits of failing the cache. Does it solve any practical issues? Because in terms of the cache hit rate, this change brings regression.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @kbuntrock could you please move this change to a separate pr
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @maximilian-novikov-db : I would rather not since I consider this as part of the bug I am willing to fix. Here are some other arguments. The documentation state the following in the "performance" page :
Implied by this piece of documentation : Contrary to the lazy restore, the pre-emptive default restore option supports fallback. This is actually not the case for artifacts, the most important build outputs. @AlexanderAshitkin
Yes. Actually, if a corrupted/non downloadable artefact is used in a dependant module while we have not used the "lazy restore" functionality : the build will fail. Seems pretty important that one should be able to choose stability over rapidity.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that keeping the fallback is important. Thanks for the clarifications! We can add such optimization in the future by tracking artifact usage downstream. |
||
| } catch (InterruptedException e) { | ||
| Thread.currentThread().interrupt(); | ||
| throw new InvalidArtifactRTException( | ||
| artifact.getGroupId(), | ||
| artifact.getArtifactId(), | ||
| artifact.getVersion(), | ||
| artifact.getType(), | ||
| RestoredArtifact.MSG_INTERRUPTED_WHILE_RETRIEVING_ARTIFACT_FILE, | ||
| e); | ||
| } catch (ExecutionException e) { | ||
| throw new InvalidArtifactRTException( | ||
| artifact.getGroupId(), | ||
| artifact.getArtifactId(), | ||
| artifact.getVersion(), | ||
| artifact.getType(), | ||
| RestoredArtifact.MSG_ERROR_RETRIEVING_ARTIFACT_FILE, | ||
| e.getCause()); | ||
| } | ||
| } | ||
| return downloadTask; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -77,8 +77,4 @@ public void afterExecutionFailure(MojoExecutionEvent event) { | |
| public Map<String, MojoExecutionEvent> getProjectExecutions(MavenProject project) { | ||
| return projectExecutions.get(project); | ||
| } | ||
|
|
||
| public void remove(MavenProject project) { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I understood well, this was used to "reset" the project mojo's execution spying after a "rollback" from using the cache to a regular build execution. |
||
| projectExecutions.remove(project); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| /* | ||
| * Licensed to the Apache Software Foundation (ASF) under one | ||
| * or more contributor license agreements. See the NOTICE file | ||
| * distributed with this work for additional information | ||
| * regarding copyright ownership. The ASF licenses this file | ||
| * to you under the Apache License, Version 2.0 (the | ||
| * "License"); you may not use this file except in compliance | ||
| * with the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, | ||
| * software distributed under the License is distributed on an | ||
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| * KIND, either express or implied. See the License for the | ||
| * specific language governing permissions and limitations | ||
| * under the License. | ||
| */ | ||
| package org.apache.maven.buildcache.artifact; | ||
|
|
||
| public class ArtifactRestorationReport { | ||
|
|
||
| /** | ||
| * Success restoration indicator | ||
| */ | ||
| private boolean success; | ||
|
|
||
| /** | ||
| * True if some files have been restored (or attempted in case of error) in the project directory | ||
| */ | ||
| private boolean restoredFilesInProjectDirectory; | ||
|
|
||
| public boolean isSuccess() { | ||
| return success; | ||
| } | ||
|
|
||
| public void setSuccess(boolean success) { | ||
| this.success = success; | ||
| } | ||
|
|
||
| public boolean isRestoredFilesInProjectDirectory() { | ||
| return restoredFilesInProjectDirectory; | ||
| } | ||
|
|
||
| public void setRestoredFilesInProjectDirectory(boolean restoredFilesInProjectDirectory) { | ||
| this.restoredFilesInProjectDirectory = restoredFilesInProjectDirectory; | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
perhaps we need to execute all mojos in this case including for Clean phase to remove any "garbage" which could be left after an unsuccessful restore
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. Restoring in the tmp + mv seems like a more robust approach.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea @maximilian-novikov-db.
@AlexanderAshitkin : do you mean we should change the way we restore artefacts from remote caches? I'm more inclined to the "second clean phase" idea since it covers any restoration problem (in case "clean" was asked in the build). For further changes, I guess it could be done in another PR.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is discussable:
cleanphase has been completed by this time, and repeating it sounds slightly off to me.cleanmight not even be requested.cleanmight wipe out other cached data and might be undesirableI see 2 options here: leave it as is (and rely on the user requested
clean), or implementmvlogic to minimize the risk of corrupted cache data in the target dir.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
all mojos except clean are executed again, if it falls back to the "normal" build, i'd say keeping any partially restored data is harmful and may lead to unpredictable result
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i suggest to remove the filter here
maven-build-cache-extension/src/main/java/org/apache/maven/buildcache/BuildCacheMojosExecutionStrategy.java
Line 137 in 4d53a0a
and not to add Clean if it's not requested by user
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that seeing a second clean phase seems a bit "off" but I could be in piece with it since it is a abnormal edge case (and we will write a beautiful log to explain it. :p)
Trying to summarize the exchange in order to check if we have a consensus in a situation where the cache fails the restoration
We change generically the log "Cannot restore project artifacts, continuing with non cached build" by :
"Cannot restore project artifacts, continuing with non cached build (clean phase will be re-executed if requested)".
And in another PR, the remote download could be enhanced by doing it in two steps : downloading in a temp directory + moving it
Reducing the risk of leftovers files.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some clarifications about the current implementation:
!restoredcondition should be read as "at least part of the project were rebuilt, not restored."restoreProjectshould returnfalseif it was anon-cachedpath and the build state differs from the cached one.So, in all, it feels like we might need fixes within the
restoreProject:falseto update the cache.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@AlexanderAshitkin : Sorry, did not had time to investigate this the past week, hope I'll have a bit more time on this one.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, finally had some time to work on what you pointed out @AlexanderAshitkin : regular build after a failed try to use the cache was handled differently depending the situation.
I will add comments on some lines of this commit to improve your ability to review it effectively. But here are some global points :
I'm removing the draft on this PR since I don't want anymore to link it with my other PR. The duplicated 3 line function used in IT test can be remove in a very small 3rd PR.