Skip to content

Commit

Permalink
Merge pull request #7091 from murdos/gradle-remove-dependency
Browse files Browse the repository at this point in the history
Gradle : support for removing direct dependency
  • Loading branch information
murdos authored Aug 23, 2023
2 parents 64b9fb7 + 938787a commit fbb6c7c
Show file tree
Hide file tree
Showing 3 changed files with 227 additions and 58 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package tech.jhipster.lite.module.infrastructure.secondary.javadependency.gradle;

import java.nio.file.Files;
import java.nio.file.Path;
import static tech.jhipster.lite.module.domain.replacement.ReplacementCondition.*;

import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.commons.lang3.NotImplementedException;
import com.electronwill.nightconfig.core.Config;
import com.electronwill.nightconfig.core.file.FileConfig;
import com.electronwill.nightconfig.core.io.ParsingException;

import tech.jhipster.lite.module.domain.Indentation;
import tech.jhipster.lite.module.domain.JHipsterProjectFilePath;
Expand All @@ -19,12 +19,12 @@
import tech.jhipster.lite.module.domain.javabuild.command.RemoveJavaDependencyManagement;
import tech.jhipster.lite.module.domain.javabuild.command.SetVersion;
import tech.jhipster.lite.module.domain.javadependency.JavaDependency;
import tech.jhipster.lite.module.domain.javadependency.JavaDependencyVersion;
import tech.jhipster.lite.module.domain.properties.JHipsterProjectFolder;
import tech.jhipster.lite.module.domain.replacement.ContentReplacers;
import tech.jhipster.lite.module.domain.replacement.MandatoryFileReplacer;
import tech.jhipster.lite.module.domain.replacement.MandatoryReplacer;
import tech.jhipster.lite.module.domain.replacement.TextNeedleBeforeReplacer;
import tech.jhipster.lite.module.domain.replacement.RegexNeedleBeforeReplacer;
import tech.jhipster.lite.module.domain.replacement.RegexReplacer;
import tech.jhipster.lite.module.infrastructure.secondary.FileSystemReplacer;
import tech.jhipster.lite.module.infrastructure.secondary.javadependency.JavaDependenciesCommandHandler;
import tech.jhipster.lite.shared.error.domain.Assert;
Expand All @@ -34,97 +34,78 @@ public class GradleCommandHandler implements JavaDependenciesCommandHandler {

private static final String COMMAND = "command";
private static final String NOT_YET_IMPLEMENTED = "Not yet implemented";
private static final String VERSIONS_TOML_KEY = "versions";
private static final String LIBRARIES_TOML_KEY = "libraries";
private static final String BUILD_GRADLE_FILE = "build.gradle.kts";

private static final String GRADLE_DEPENDENCY_NEEDLE = "// jhipster-needle-gradle-add-dependency";
private static final String GRADLE_TEST_DEPENDENCY_NEEDLE = "// jhipster-needle-gradle-add-dependency-test";
private static final Pattern GRADLE_DEPENDENCY_NEEDLE = Pattern.compile("^\\s+// jhipster-needle-gradle-add-dependency$", Pattern.MULTILINE);
private static final Pattern GRADLE_TEST_DEPENDENCY_NEEDLE = Pattern.compile("^\\s+// jhipster-needle-gradle-add-dependency-test$", Pattern.MULTILINE);

private final Indentation indentation;
private final JHipsterProjectFolder projectFolder;
private final FileConfig versionsCatalog;
private final VersionsCatalog versionsCatalog;
private final FileSystemReplacer fileReplacer = new FileSystemReplacer();

public GradleCommandHandler(Indentation indentation, JHipsterProjectFolder projectFolder) {
Assert.notNull("indentation", indentation);
Assert.notNull("projectFolder", projectFolder);

this.indentation = indentation;
this.projectFolder = projectFolder;
Path tomlVersionCatalogFile = tomlVersionCatalogPath();
versionsCatalog = FileConfig.builder(tomlVersionCatalogFile)
.sync()
.build();

// Missing TOML file will be automatically created, but its parent folder should exist
if (!Files.exists(tomlVersionCatalogFile.getParent())) {
tomlVersionCatalogFile.toFile().getParentFile().mkdirs();
}

try {
versionsCatalog.load();
} catch (ParsingException exception) {
throw new InvalidTomlVersionCatalogException(exception);
}
}

private Path tomlVersionCatalogPath() {
return projectFolder.filePath("gradle").resolve("libs.versions.toml");
this.versionsCatalog = new VersionsCatalog(projectFolder);
}

@Override
public void handle(SetVersion command) {
Assert.notNull(COMMAND, command);

JavaDependencyVersion javaDependencyVersion = command.version();
versionsCatalog.set(VERSIONS_TOML_KEY + "." + javaDependencyVersion.slug().slug(), javaDependencyVersion.version().get());

versionsCatalog.save();
versionsCatalog.setVersion(command.version());
}

@Override
public void handle(AddDirectJavaDependency command) {
Assert.notNull(COMMAND, command);

addJavaDependencyToVersionCatalog(command.dependency());
versionsCatalog.addLibrary(command.dependency());
addJavaDependencyToBuildGradle(command.dependency());
}

private void addJavaDependencyToBuildGradle(JavaDependency dependency) {
GradleDependencyScope gradleScope = switch(dependency.scope()) {
case TEST -> GradleDependencyScope.TEST_IMPLEMENTATION;
case PROVIDED -> GradleDependencyScope.COMPILE_ONLY;
case RUNTIME -> GradleDependencyScope.RUNTIME_ONLY;
default -> GradleDependencyScope.IMPLEMENTATION;
};
GradleDependencyScope gradleScope = gradleDependencyScope(dependency);

MandatoryReplacer replacer = new MandatoryReplacer(
new TextNeedleBeforeReplacer(
new RegexNeedleBeforeReplacer(
(contentBeforeReplacement, newText) -> !contentBeforeReplacement.contains(newText),
gradleScope == GradleDependencyScope.TEST_IMPLEMENTATION ? GRADLE_TEST_DEPENDENCY_NEEDLE : GRADLE_DEPENDENCY_NEEDLE
),
"%s(libs.%s)".formatted(gradleScope.command(), dependencySlug(dependency).replace("-", "."))
"%s%s(libs.%s)".formatted(indentation.times(1), gradleScope.command(), VersionsCatalog.dependencySlug(dependency).replace("-", "."))
);
fileReplacer.handle(projectFolder, ContentReplacers.of(new MandatoryFileReplacer(new JHipsterProjectFilePath(BUILD_GRADLE_FILE), replacer)));
}

private void addJavaDependencyToVersionCatalog(JavaDependency dependency) {
Config libraryConfig = Config.inMemory();
libraryConfig.set("group", dependency.id().groupId().get());
libraryConfig.set("name", dependency.id().artifactId().get());
dependency.version().ifPresent(versionSlug -> libraryConfig.set("version.ref", versionSlug.slug()));
String libraryEntryKey = dependencySlug(dependency);
versionsCatalog.set(LIBRARIES_TOML_KEY + "." + libraryEntryKey, libraryConfig);
versionsCatalog.save();
}

private static String dependencySlug(JavaDependency dependency) {
return dependency.slug().map(DependencySlug::slug).orElse(dependency.id().artifactId().get());
private static GradleDependencyScope gradleDependencyScope(JavaDependency dependency) {
return switch(dependency.scope()) {
case TEST -> GradleDependencyScope.TEST_IMPLEMENTATION;
case PROVIDED -> GradleDependencyScope.COMPILE_ONLY;
case RUNTIME -> GradleDependencyScope.RUNTIME_ONLY;
default -> GradleDependencyScope.IMPLEMENTATION;
};
}

@Override
@ExcludeFromGeneratedCodeCoverage(reason = "Not yet implemented")
public void handle(RemoveDirectJavaDependency command) {
throw new NotImplementedException(NOT_YET_IMPLEMENTED);
versionsCatalog.retrieveDependencySlugsFrom(command.dependency()).forEach(this::removeJavaDependencyFromBuildGradle);
versionsCatalog.removeLibrary(command.dependency());
}

private void removeJavaDependencyFromBuildGradle(DependencySlug dependencySlug) {
String scopePattern = Stream.of(GradleDependencyScope.values())
.map(GradleDependencyScope::command)
.collect(Collectors.joining("|", "(", ")"));
Pattern dependencyLinePattern = Pattern.compile(
"^\\s+%s\\(libs\\.%s\\)$".formatted(scopePattern, dependencySlug.slug().replace("-", "\\.")),
Pattern.MULTILINE
);
MandatoryReplacer replacer = new MandatoryReplacer(new RegexReplacer(always(), dependencyLinePattern), "");
fileReplacer.handle(projectFolder, ContentReplacers.of(new MandatoryFileReplacer(new JHipsterProjectFilePath(BUILD_GRADLE_FILE), replacer)));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package tech.jhipster.lite.module.infrastructure.secondary.javadependency.gradle;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.List;
import java.util.function.Predicate;

import com.electronwill.nightconfig.core.Config;
import com.electronwill.nightconfig.core.Config.Entry;
import com.electronwill.nightconfig.core.UnmodifiableConfig;
import com.electronwill.nightconfig.core.file.FileConfig;
import com.electronwill.nightconfig.core.io.ParsingException;

import tech.jhipster.lite.module.domain.javabuild.DependencySlug;
import tech.jhipster.lite.module.domain.javadependency.DependencyId;
import tech.jhipster.lite.module.domain.javadependency.JavaDependency;
import tech.jhipster.lite.module.domain.javadependency.JavaDependencyVersion;
import tech.jhipster.lite.module.domain.properties.JHipsterProjectFolder;

class VersionsCatalog {

private static final String VERSIONS_TOML_KEY = "versions";
private static final String LIBRARIES_TOML_KEY = "libraries";

private final FileConfig tomlConfigFile;

public VersionsCatalog(JHipsterProjectFolder projectFolder) {
Path tomlVersionCatalogFile = tomlVersionCatalogPath(projectFolder);
tomlConfigFile = FileConfig.builder(tomlVersionCatalogFile)
.sync()
.build();

// Missing TOML file will be automatically created, but its parent folder should exist
if (!Files.exists(tomlVersionCatalogFile.getParent())) {
tomlVersionCatalogFile.toFile().getParentFile().mkdirs();
}

try {
tomlConfigFile.load();
} catch (ParsingException exception) {
throw new InvalidTomlVersionCatalogException(exception);
}
}

private static Path tomlVersionCatalogPath(JHipsterProjectFolder projectFolder) {
return projectFolder.filePath("gradle").resolve("libs.versions.toml");
}

private void save() {
tomlConfigFile.save();
}

public static String dependencySlug(JavaDependency dependency) {
return dependency.slug().map(DependencySlug::slug).orElse(dependency.id().artifactId().get());
}

public void setVersion(JavaDependencyVersion javaDependencyVersion) {
tomlConfigFile.set(List.of(VERSIONS_TOML_KEY, javaDependencyVersion.slug().slug()), javaDependencyVersion.version().get());
save();
}

public void addLibrary(JavaDependency dependency) {
Config libraryConfig = Config.inMemory();
libraryConfig.set("group", dependency.id().groupId().get());
libraryConfig.set("name", dependency.id().artifactId().get());
dependency.version().ifPresent(versionSlug -> libraryConfig.set("version.ref", versionSlug.slug()));
String libraryEntryKey = dependencySlug(dependency);
tomlConfigFile.set(List.of(LIBRARIES_TOML_KEY, libraryEntryKey), libraryConfig);
save();
}

public void removeLibrary(DependencyId dependency) {
libraryEntriesMatchingDependency(dependency).forEach(libraryConfig -> tomlConfigFile.remove(List.of(LIBRARIES_TOML_KEY, libraryConfig.getKey())));
save();
}

private List<? extends Entry> libraryEntriesMatchingDependency(DependencyId dependency) {
return tomlConfigFile.entrySet().stream()
.filter(entry -> entry.getKey().equals(LIBRARIES_TOML_KEY))
.map(Entry::getValue)
.filter(Config.class::isInstance)
.map(Config.class::cast)
.map(Config::entrySet)
.flatMap(Collection::stream)
.filter(groupShouldMatch(dependency))
.filter(nameShouldMatch(dependency))
.toList();
}

private static Predicate<Entry> groupShouldMatch(DependencyId dependency) {
return libraryConfig -> {
Object groupProperty = ((Config) libraryConfig.getValue()).get("group");
return dependency.groupId().get().equals(groupProperty);
};
}

private static Predicate<Entry> nameShouldMatch(DependencyId dependency) {
return libraryConfig -> {
Object groupProperty = ((Config) libraryConfig.getValue()).get("name");
return dependency.artifactId().get().equals(groupProperty);
};
}

public Collection<DependencySlug> retrieveDependencySlugsFrom(DependencyId dependency) {
return libraryEntriesMatchingDependency(dependency)
.stream()
.map(UnmodifiableConfig.Entry::getKey)
.map(DependencySlug::new)
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,82 @@ void shouldAddTestImplementationDependencyInBuildGradleFileForTestScope() {
}
}

@Nested
class HandleRemoveDirectJavaDependency {

private final JHipsterProjectFolder projectFolder = projectFrom("src/test/resources/projects/empty-gradle");

@Test
void shouldRemoveEntryInLibrariesSection() {
GradleCommandHandler gradleCommandHandler = new GradleCommandHandler(Indentation.DEFAULT, projectFolder);
gradleCommandHandler.handle(new AddDirectJavaDependency(springBootStarterWebDependency()));

gradleCommandHandler.handle(new RemoveDirectJavaDependency(springBootStarterWebDependency().id()));

assertThat(versionCatalogContent(projectFolder))
.doesNotContain("[libraries.spring-boot-starter-web]")
.doesNotContain("""
\t\tname = "spring-boot-starter-web"
\t\tgroup = "org.springframework.boot"
""");
}

@Test
void shouldRemoveDepencyInBuildGradleFile() {
GradleCommandHandler gradleCommandHandler = new GradleCommandHandler(Indentation.DEFAULT, projectFolder);
gradleCommandHandler.handle(new AddDirectJavaDependency(springBootStarterWebDependency()));

gradleCommandHandler.handle(new RemoveDirectJavaDependency(springBootStarterWebDependency().id()));

assertThat(buildGradleContent(projectFolder)).doesNotContain("implementation(libs.spring.boot.starter.web)");
}

@Test
void shouldRemoveTestDepencyInBuildGradleFile() {
GradleCommandHandler gradleCommandHandler = new GradleCommandHandler(Indentation.DEFAULT, projectFolder);
JavaDependency dependency = javaDependency()
.groupId("org.junit.jupiter")
.artifactId("junit-jupiter-engine")
.scope(JavaDependencyScope.TEST)
.build();
gradleCommandHandler.handle(new AddDirectJavaDependency(dependency));

gradleCommandHandler.handle(new RemoveDirectJavaDependency(dependency.id()));

assertThat(buildGradleContent(projectFolder)).doesNotContain("testImplementation(libs.junit.jupiter.engine)");
}

@Test
void shouldRemoveRuntimeDepencyInBuildGradleFile() {
GradleCommandHandler gradleCommandHandler = new GradleCommandHandler(Indentation.DEFAULT, projectFolder);
JavaDependency dependency = javaDependency()
.groupId("org.junit.jupiter")
.artifactId("junit-jupiter-engine")
.scope(JavaDependencyScope.RUNTIME)
.build();
gradleCommandHandler.handle(new AddDirectJavaDependency(dependency));

gradleCommandHandler.handle(new RemoveDirectJavaDependency(dependency.id()));

assertThat(buildGradleContent(projectFolder)).doesNotContain("runtimeOnly(libs.junit.jupiter.engine)");
}

@Test
void shouldRemoveProvidedDepencyInBuildGradleFile() {
GradleCommandHandler gradleCommandHandler = new GradleCommandHandler(Indentation.DEFAULT, projectFolder);
JavaDependency dependency = javaDependency()
.groupId("org.junit.jupiter")
.artifactId("junit-jupiter-engine")
.scope(JavaDependencyScope.PROVIDED)
.build();
gradleCommandHandler.handle(new AddDirectJavaDependency(dependency));

gradleCommandHandler.handle(new RemoveDirectJavaDependency(dependency.id()));

assertThat(buildGradleContent(projectFolder)).doesNotContain("compileOnly(libs.junit.jupiter.engine)");
}
}

private static String buildGradleContent(JHipsterProjectFolder projectFolder) {
return content(Paths.get(projectFolder.get()).resolve("build.gradle.kts"));
}
Expand Down

0 comments on commit fbb6c7c

Please sign in to comment.