Skip to content

Commit

Permalink
Record the build timestamp and assume every file changed that is newer
Browse files Browse the repository at this point in the history
Currently we only analyze the current delta but this can have several
subtile problems:

- when a file is modified outside of eclipse it might not be recognized
by eclipse immediately
- when files are modified outside the "interesting folders" the delta
maybe not includes this
- if a build fails the next delta will not necessarily include all files
of the previous delta because the error might be in a java source file
and a resource file was changed

This adds a project scoped timestamp to record when the last successful
build happens and assumes every file changed that has a timestamp larger
than the last build, this still does not handle deletes properly but
improves the situation already for existing files. It also leads to
probably more files queried/assumed changed in case of failing builds or
when the build timestamp is unkown but ensures that we only ever process
more files than required but never less ones.
  • Loading branch information
laeubi committed Aug 2, 2023
1 parent 40d3b44 commit 51609ad
Showing 1 changed file with 79 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,18 @@
import static org.eclipse.core.resources.IncrementalProjectBuilder.FULL_BUILD;

import java.io.File;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;

import org.slf4j.Logger;
Expand All @@ -37,7 +40,9 @@
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Adapters;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.QualifiedName;
Expand Down Expand Up @@ -78,6 +83,8 @@ public class MavenBuilderImpl {

private final List<IIncrementalBuildFramework> incrementalBuildFrameworks;

private final Map<IProject, ProjectBuildState> deltaState = new ConcurrentHashMap<>();

public MavenBuilderImpl(DeltaProvider deltaProvider) {
this.deltaProvider = deltaProvider;
this.incrementalBuildFrameworks = loadIncrementalBuildFrameworks();
Expand Down Expand Up @@ -113,9 +120,10 @@ public Set<IProject> build(MavenSession session, IMavenProjectFacade projectFaca
if(!hasRelevantDelta(projectFacade, delta)) {
return Set.of(project);
}

ProjectBuildState buildState = deltaState.computeIfAbsent(project, ProjectBuildState::new);
final BuildResultCollector participantResults = new BuildResultCollector();
List<BuildContext> incrementalContexts = setupProjectBuildContext(project, kind, delta, participantResults);
List<BuildContext> incrementalContexts = setupProjectBuildContext(project, kind, delta, participantResults,
buildState);

debugBuildStart(debugHooks, projectFacade, kind, args, participants, delta, monitor);

Expand Down Expand Up @@ -184,7 +192,10 @@ public Set<IProject> build(MavenSession session, IMavenProjectFacade projectFaca
// Process errors and warnings
MavenExecutionResult result = session.getResult();
processBuildResults(project, mavenProject, result, participantResults, buildErrors);

if(buildErrors.isEmpty()) {
//we only commit this when there are no errors so just in case a failure is cased by a changed file it is again queried afterwards
buildState.commit();
}
return dependencies;
}

Expand Down Expand Up @@ -227,10 +238,12 @@ private boolean hasRelevantDelta(IMavenProjectFacade projectFacade, IResourceDel
}

private List<IIncrementalBuildFramework.BuildContext> setupProjectBuildContext(IProject project, int kind,
IResourceDelta delta, IIncrementalBuildFramework.BuildResultCollector results) throws CoreException {
IResourceDelta delta, IIncrementalBuildFramework.BuildResultCollector results, ProjectBuildState buildState)
throws CoreException {
List<IIncrementalBuildFramework.BuildContext> contexts = new ArrayList<>();

BuildDelta buildDelta = delta != null ? new EclipseResourceBuildDelta(delta) : null;
BuildDelta buildDelta = delta != null ? new ProjectBuildStateDelta(buildState, new EclipseResourceBuildDelta(delta))
: null;
for(IIncrementalBuildFramework framework : incrementalBuildFrameworks) {
contexts.add(framework.setupProjectBuildContext(project, kind, buildDelta, results));
}
Expand Down Expand Up @@ -408,7 +421,7 @@ public void clean(MavenSession session, IMavenProjectFacade projectFacade,

final BuildResultCollector participantResults = new BuildResultCollector();
List<BuildContext> incrementalContexts = setupProjectBuildContext(project, IncrementalProjectBuilder.CLEAN_BUILD,
null, participantResults);
null, participantResults, null);

Map<Throwable, MojoExecutionKey> buildErrors = new LinkedHashMap<>();
try {
Expand Down Expand Up @@ -452,4 +465,64 @@ public void clean(MavenSession session, IMavenProjectFacade projectFacade,
DeltaProvider getDeltaProvider() {
return deltaProvider;
}

private static final class ProjectBuildState {

private long lastBuild;

private IProject project;

public ProjectBuildState(IProject project) {
this.project = project;
}

public void commit() {
this.lastBuild = System.currentTimeMillis();
}

@Override
public String toString() {
return "BuildState for " + project + " lat reccorded timestamp "
+ DateFormat.getDateTimeInstance().format(new Date(lastBuild));
}
}

private static final class ProjectBuildStateDelta implements BuildDelta, IAdaptable {

private ProjectBuildState buildState;

private BuildDelta delegate;

/**
* @param buildState
* @param eclipseResourceBuildDelta
*/
public ProjectBuildStateDelta(ProjectBuildState buildState, BuildDelta delegate) {
this.buildState = buildState;
this.delegate = delegate;
}

@Override
public boolean hasDelta(File file) {
//first check the delegate...
if(delegate != null && delegate.hasDelta(file)) {
return true;
}
//... now perform additional checks
if(file.isFile()) {
long lastModified = file.lastModified();
if(lastModified > buildState.lastBuild) {
//if the file is modified after the last build timestamp we assume it was modified even though not part of the current delta!
return true;
}
}
return false;
}

@Override
public <T> T getAdapter(Class<T> adapter) {
return Adapters.adapt(delegate, adapter);
}

}
}

0 comments on commit 51609ad

Please sign in to comment.