Skip to content

Don't write locks if both the locks and props change #67

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

Merged
merged 17 commits into from
Nov 12, 2024
5 changes: 5 additions & 0 deletions changelog/@unreleased/pr-67.v2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type: fix
fix:
description: Don't write locks if both the locks and props change
links:
- https://github.com/palantir/gradle-consistent-versions-idea-plugin/pull/67
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.palantir.gradle.versions.intellij;

import com.google.common.collect.MapMaker;
import com.intellij.execution.executors.DefaultRunExecutor;
import com.intellij.openapi.components.ComponentManager;
import com.intellij.openapi.externalSystem.importing.ImportSpecBuilder;
Expand All @@ -37,6 +38,10 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.gradle.util.GradleConstants;
Expand All @@ -47,6 +52,14 @@ public final class VersionPropsFileListener implements AsyncFileListener {
private static final Logger log = LoggerFactory.getLogger(VersionPropsFileListener.class);
private static final String TASK_NAME = "writeVersionsLock";

// Shared state to track changes per project
private final ConcurrentMap<Project, ChangeFlags> projectChangeMap =
new MapMaker().weakKeys().makeMap();

// Executor for debouncing
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
private static final long DEBOUNCE_DELAY_MS = 350;

@Nullable
@Override
public ChangeApplier prepareChange(List<? extends VFileEvent> events) {
Expand All @@ -56,38 +69,83 @@ public ChangeApplier prepareChange(List<? extends VFileEvent> events) {
.filter(event -> "versions.props".equals(event.getFile().getName()))
.toList();

if (versionPropsEvents.isEmpty()) {
List<VFileContentChangeEvent> versionLockEvents = events.stream()
.filter(event -> event instanceof VFileContentChangeEvent)
.map(event -> (VFileContentChangeEvent) event)
.filter(event -> "versions.lock".equals(event.getFile().getName()))
.toList();

if (versionPropsEvents.isEmpty() && versionLockEvents.isEmpty()) {
return null;
}

List<Project> projectsAffected = Arrays.stream(
List<Project> allOpenProjects = Arrays.stream(
ProjectManager.getInstance().getOpenProjects())
.filter(Project::isInitialized)
.filter(Predicate.not(ComponentManager::isDisposed))
.filter(project -> versionPropsEvents.stream()
.anyMatch(event -> event.getPath().startsWith(project.getBasePath())
&& !isFileMalformed(project, event.getFile())))
.toList();

// Update the shared state based on current events
allOpenProjects.forEach(project -> {
String basePath = project.getBasePath();
boolean hasPropsChange = versionPropsEvents.stream()
.anyMatch(event ->
event.getPath().startsWith(basePath) && !isFileMalformed(project, event.getFile()));
boolean hasLockChange =
versionLockEvents.stream().anyMatch(event -> event.getPath().startsWith(basePath));

if (hasPropsChange || hasLockChange) {
projectChangeMap.compute(project, (proj, flags) -> {
if (flags == null) {
return ChangeFlags.of(hasPropsChange, hasLockChange);
} else {
return ChangeFlags.of(
flags.hasPropsChange() || hasPropsChange, flags.hasLockChange() || hasLockChange);
}
});
}
});

return new ChangeApplier() {
@Override
public void afterVfsChange() {
projectsAffected.forEach(project -> {
VersionPropsProjectSettings settings = VersionPropsProjectSettings.getInstance(project);
if (!settings.isEnabled()) {
return;
}

if (hasBuildSrc(project)) {
runTaskThenRefresh(project);
} else {
refreshProjectWithTask(project);
}
// Schedule processing after VFS changes have been applied
projectChangeMap.keySet().forEach(project -> {
scheduler.schedule(() -> processChanges(project), DEBOUNCE_DELAY_MS, TimeUnit.MILLISECONDS);
});
}
};
}

private void processChanges(Project project) {
ChangeFlags flags = projectChangeMap.remove(project);
if (flags == null) {
return;
}

if (flags.hasPropsChange && flags.hasLockChange) {
// Both changes detected; do not process
log.debug(
"Project {} has both versions.props and versions.lock changes. Skipping processing.",
project.getName());
return;
}

if (flags.hasPropsChange) {
// Only props changed; process the project
VersionPropsProjectSettings settings = VersionPropsProjectSettings.getInstance(project);
if (!settings.isEnabled()) {
return;
}

if (hasBuildSrc(project)) {
runTaskThenRefresh(project);
} else {
refreshProjectWithTask(project);
}
}
}

private boolean hasBuildSrc(Project project) {
return Files.exists(Paths.get(project.getBasePath(), "buildSrc"));
}
Expand Down Expand Up @@ -146,4 +204,10 @@ private static boolean isFileMalformed(Project project, VirtualFile file) {

return PsiTreeUtil.hasErrorElements(psiFile);
}

private record ChangeFlags(boolean hasPropsChange, boolean hasLockChange) {
public static ChangeFlags of(boolean hasPropsChange, boolean hasLockChange) {
return new ChangeFlags(hasPropsChange, hasLockChange);
}
}
}