Skip to content

Commit

Permalink
Fix/multi project leaking (#167)
Browse files Browse the repository at this point in the history
  • Loading branch information
marchermans authored May 13, 2024
1 parent 8faf299 commit c63c7c8
Show file tree
Hide file tree
Showing 12 changed files with 339 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.TaskProvider;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.VisibleForTesting;

import javax.inject.Inject;
Expand Down Expand Up @@ -128,7 +127,7 @@ void handleDependency(final Configuration configuration, final ModuleDependency
dependencyReplacementInformation.put(dependency, configuration, candidate);
} else if (dependencyReplacementInformation.contains(dependency, configuration)) {
candidate = dependencyReplacementInformation.get(dependency, configuration);
if (candidate == null) {
if (candidate == null || !candidate.isPresent()) {
candidate = Optional.empty();
dependencyReplacementInformation.remove(dependency, configuration);
}
Expand Down Expand Up @@ -236,7 +235,7 @@ void createDependencyReplacementResult(final List<Configuration> configurations,
builder -> configureRepositoryReference(result, externalModuleDependency, builder),
reference -> processRepositoryReference(configurations, result, reference),
builder -> configureRepositoryEntry(result, externalModuleDependency, builder),
entry -> processRepositoryEntry(originalConfiguration, result, generator, repoBaseDir, entry),
entry -> processRepositoryEntry(configurations, result, generator, repoBaseDir, entry),
result.getProcessImmediately()
);
} catch (XMLStreamException | IOException e) {
Expand All @@ -256,7 +255,7 @@ private void processRepositoryReference(List<Configuration> configurations, Depe
}


private void processRepositoryEntry(Configuration originalConfiguration, DependencyReplacementResult result, TaskProviderGenerator generator, Provider<Directory> repoBaseDir, RepositoryEntry<?, ?> entry) {
private void processRepositoryEntry(List<Configuration> originalConfiguration, DependencyReplacementResult result, TaskProviderGenerator generator, Provider<Directory> repoBaseDir, RepositoryEntry<?, ?> entry) {
final ModuleReference reference = entry.toModuleReference();
if (configuredReferences.contains(reference))
return;
Expand All @@ -265,7 +264,7 @@ private void processRepositoryEntry(Configuration originalConfiguration, Depende

final RepositoryEntryGenerationTasks entryGenerationTasks = generator.generate(repoBaseDir, entry);
final Dependency replacedDependency = this.dependencyCreator.from(entryGenerationTasks.rawJarProvider);
originalConfiguration.getDependencies().add(replacedDependency);
originalConfiguration.forEach(config -> config.getDependencies().add(replacedDependency));
result.getOnRepoWritingTaskRegisteredCallback().accept(entryGenerationTasks.rawJarProvider);

afterDefinitionBake(projectAfterBake -> {
Expand Down Expand Up @@ -304,7 +303,7 @@ public void afterDefinitionBake(final Consumer<Project> callback) {
this.afterDefinitionBakeCallbacks.add(callback);
}

private static class RepositoryEntryGenerationTasks {
static class RepositoryEntryGenerationTasks {
private final TaskProvider<? extends WithOutput> rawJarProvider;
private final TaskProvider<? extends WithOutput> sourceJarProvider;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package net.neoforged.gradle.common.util;

import net.neoforged.gradle.dsl.common.util.ConfigurationUtils;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.plugins.ExtensionContainer;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.SourceSetContainer;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatchers;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import java.util.*;
import java.util.function.Consumer;

import static org.mockito.Mockito.*;

public class ConfigurationUtilsTest {

@Test
public void findCompileClasspathSourceSetHandlesImplementationAndCompileClasspath() {
final Configuration compileClasspath = mock(Configuration.class);
final Configuration compileOnly = mock(Configuration.class);
final Configuration implementation = mock(Configuration.class);

final ConfigurationContainer configurations = mock(ConfigurationContainer.class);
final Project project = mock(Project.class);
final ExtensionContainer extensions = mock(ExtensionContainer.class);

final SourceSetContainer sourceSets = mock(SourceSetContainer.class);
final SourceSet mainSourceSet = mock(SourceSet.class);

when(configurations.findByName("compileOnly")).thenReturn(compileOnly);
when(configurations.findByName("compileClasspath")).thenReturn(compileClasspath);
when(configurations.findByName("implementation")).thenReturn(implementation);

when(project.getConfigurations()).thenReturn(configurations);

when(project.getExtensions()).thenReturn(extensions);
when(extensions.getByType(SourceSetContainer.class)).thenReturn(sourceSets);

when(sourceSets.getByName("main")).thenReturn(mainSourceSet);
when(mainSourceSet.getCompileClasspathConfigurationName()).thenReturn("compileClasspath");
when(mainSourceSet.getImplementationConfigurationName()).thenReturn("implementation");
when(mainSourceSet.getCompileOnlyConfigurationName()).thenReturn("compileOnly");
doAnswer(invocationOnMock -> {
final Consumer<SourceSet> argument = invocationOnMock.getArgument(0);
argument.accept(mainSourceSet);
return null;
}).when(sourceSets).forEach(ArgumentMatchers.any());

when(compileOnly.getName()).thenReturn("compileOnly");
when(compileClasspath.getName()).thenReturn("compileClasspath");
when(implementation.getName()).thenReturn("implementation");

when(compileClasspath.getExtendsFrom()).thenReturn(buildConfigurationSet(implementation, compileOnly));

final List<Configuration> result = ConfigurationUtils.findCompileOnlyConfigurationForSourceSetReplacement(project, implementation);

Assertions.assertEquals(buildConfigurationList(compileOnly), result);
}

@Test
public void findRuntimeClasspathSourceSetHandlesImplementationAndRuntimeClasspath() {
final Configuration runtimeClasspath = mock(Configuration.class);
final Configuration implementation = mock(Configuration.class);

final ConfigurationContainer configurations = mock(ConfigurationContainer.class);
final Project project = mock(Project.class);
final ExtensionContainer extensions = mock(ExtensionContainer.class);

final SourceSetContainer sourceSets = mock(SourceSetContainer.class);
final SourceSet mainSourceSet = mock(SourceSet.class);

when(configurations.findByName("runtimeClasspath")).thenReturn(runtimeClasspath);
when(configurations.findByName("implementation")).thenReturn(implementation);

final List<Configuration> newConfigurations = new ArrayList<>();
when(configurations.maybeCreate(any())).thenAnswer((Answer<Configuration>) invocationOnMock -> {
final String name = invocationOnMock.getArgument(0);
final Configuration configuration = mock(Configuration.class);
when(configuration.getName()).thenReturn(name);

newConfigurations.add(configuration);

return configuration;
});

when(project.getConfigurations()).thenReturn(configurations);

when(project.getExtensions()).thenReturn(extensions);
when(extensions.getByType(SourceSetContainer.class)).thenReturn(sourceSets);

when(sourceSets.getByName("main")).thenReturn(mainSourceSet);
when(mainSourceSet.getRuntimeClasspathConfigurationName()).thenReturn("runtimeClasspath");
when(mainSourceSet.getImplementationConfigurationName()).thenReturn("implementation");

doAnswer(invocationOnMock -> {
final Consumer<SourceSet> argument = invocationOnMock.getArgument(0);
argument.accept(mainSourceSet);
return null;
}).when(sourceSets).forEach(ArgumentMatchers.any());

when(runtimeClasspath.getName()).thenReturn("runtimeClasspath");
when(implementation.getName()).thenReturn("implementation");

when(runtimeClasspath.getExtendsFrom()).thenReturn(buildConfigurationSet(implementation));

final List<Configuration> result = ConfigurationUtils.findRuntimeOnlyConfigurationFromSourceSetReplacement(project, implementation);

Assertions.assertEquals(newConfigurations, result);
}

@Test
public void getAllExtendingConfigurationOneDeepOnSuperLookup() {
final Configuration source = mock(Configuration.class);
final Configuration target = mock(Configuration.class);

when(source.getExtendsFrom()).thenReturn(buildConfigurationSet(target));

final Set<Configuration> configurations = ConfigurationUtils.getAllSuperConfigurations(source);

Assertions.assertEquals(buildConfigurationSet(target), configurations);
}

@Test
public void getAllExtendingConfigurationRecursiveOnSuperLookup() {
final Configuration source = mock(Configuration.class);
final Configuration levelOne = mock(Configuration.class);
final Configuration levelTwo = mock(Configuration.class);

when(source.getExtendsFrom()).thenReturn(buildConfigurationSet(levelOne));
when(levelOne.getExtendsFrom()).thenReturn(buildConfigurationSet(levelTwo));

final Set<Configuration> configurations = ConfigurationUtils.getAllSuperConfigurations(source);

Assertions.assertEquals(buildConfigurationSet(levelOne, levelTwo), configurations);
}

private Set<Configuration> buildConfigurationSet(Configuration... configurations) {
final Set<Configuration> configurationSet = new HashSet<>();
Collections.addAll(configurationSet, configurations);
return configurationSet;
}

private List<Configuration> buildConfigurationList(Configuration... configurations) {
final List<Configuration> configurationSet = new ArrayList<>();
Collections.addAll(configurationSet, configurations);
return configurationSet;
}

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package net.neoforged.gradle.common.util;

import net.neoforged.gradle.dsl.common.util.NamingConstants;
import org.junit.Test;
import org.junit.jupiter.api.Test;

import java.util.HashMap;
import java.util.Map;
Expand All @@ -10,6 +10,7 @@
import static org.junit.jupiter.api.Assertions.assertThrows;

public class MappingUtilsTest {

@Test
public void mappingVersionIsFoundInMappingVersionData() {
final Map<String, String> mappingVersionData = new HashMap<>();
Expand Down
4 changes: 3 additions & 1 deletion dsl/common/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ plugins {
}

dependencies {
api "org.codehaus.groovy:groovy-all:${project.groovy_version}"
api ("org.codehaus.groovy:groovy-all:${project.groovy_version}", {
exclude group: 'junit'
})
api "commons-io:commons-io:${project.commons_io_version}"
api "org.apache.maven:maven-artifact:${project.maven_artifact_version}"
api "com.google.guava:guava:${project.guava_version}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ConfigurationContainer
import org.gradle.api.artifacts.Dependency
import org.gradle.api.file.RegularFile
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.SourceSetContainer
Expand Down Expand Up @@ -71,54 +70,62 @@ class ConfigurationUtils {
static List<Configuration> findReplacementConfigurations(final Project project, final Configuration configuration) {
final Set<Configuration> resultContainer = new HashSet<>();

resultContainer.addAll(findCompileClasspathSourceSet(project, configuration))
resultContainer.addAll(findRuntimeClasspathSourceSet(project, configuration))
resultContainer.addAll(findCompileOnlyConfigurationForSourceSetReplacement(project, configuration))
resultContainer.addAll(findRuntimeOnlyConfigurationFromSourceSetReplacement(project, configuration))

return resultContainer.toList()
}

static List<Configuration> findCompileClasspathSourceSet(final Project project, final Configuration configuration) {
static List<Configuration> findCompileOnlyConfigurationForSourceSetReplacement(final Project project, final Configuration configuration) {
final SourceSetContainer sourceSetContainer = project.getExtensions().getByType(SourceSetContainer.class)
final List<Configuration> targets = new ArrayList<>();

sourceSetContainer.forEach {sourceSet -> {
final Configuration sourceSetConfiguration = project.getConfigurations().findByName(sourceSet.getCompileClasspathConfigurationName())
if (sourceSetConfiguration == null)
final Configuration compileOnly = project.getConfigurations().findByName(sourceSet.getCompileOnlyConfigurationName())
final Configuration compileClasspath = project.getConfigurations().findByName(sourceSet.getCompileClasspathConfigurationName());
if (compileOnly == null)
return;

if (configuration == sourceSetConfiguration) {
if (configuration == compileOnly) {
targets.clear()
targets.add(sourceSetConfiguration)
targets.add(compileOnly)
return targets
}

final Set<Configuration> supers = getAllSuperConfigurations(sourceSetConfiguration)
if (supers.contains(configuration)) {
targets.add(sourceSetConfiguration)
final Set<Configuration> supers = getAllSuperConfigurations(compileClasspath)
if (supers.contains(compileOnly) && supers.contains(configuration)) {
targets.add(compileOnly)
}
}}

return targets
}

static List<Configuration> findRuntimeClasspathSourceSet(final Project project, final Configuration configuration) {
static List<Configuration> findRuntimeOnlyConfigurationFromSourceSetReplacement(final Project project, final Configuration configuration) {
final SourceSetContainer sourceSetContainer = project.getExtensions().getByType(SourceSetContainer.class)
final List<Configuration> targets = new ArrayList<>();

sourceSetContainer.forEach {sourceSet -> {
final Configuration sourceSetConfiguration = project.getConfigurations().findByName(sourceSet.getRuntimeClasspathConfigurationName())
if (sourceSetConfiguration == null)
final Configuration runtimeOnly = project.getConfigurations().findByName(sourceSet.getRuntimeOnlyConfigurationName())
final Configuration runtimeClasspath = project.getConfigurations().findByName(sourceSet.getRuntimeClasspathConfigurationName());
if (runtimeOnly == null)
return;

if (configuration == sourceSetConfiguration) {
if (configuration == runtimeOnly) {
targets.clear()
targets.add(sourceSetConfiguration)
targets.add(runtimeOnly)
return targets
}

final Set<Configuration> supers = getAllSuperConfigurations(sourceSetConfiguration)
if (supers.contains(configuration)) {
targets.add(sourceSetConfiguration)
final Set<Configuration> supers = getAllSuperConfigurations(runtimeClasspath)
if (supers.contains(runtimeOnly) && supers.contains(configuration)) {
//Runtime is a special bunny, we need to make our own configuration in this state to handle it.
//TODO: Once we add the conventions subsystem use its standardized approach.
final Configuration reallyRuntimeOnly = project.getConfigurations().maybeCreate(
getSourceSetName(sourceSet, "runtimeNotPublished")
)
runtimeClasspath.extendsFrom(reallyRuntimeOnly)
targets.add(reallyRuntimeOnly)
}
}}

Expand All @@ -141,4 +148,18 @@ class ConfigurationUtils {
}
}}
}

/**
* Gets the name of the source set with the given post fix
*
* @param sourceSet The source set to get the name of
* @param postFix The post fix to append to the source set name
* @return The name of the source set with the post fix
*/
private static String getSourceSetName(SourceSet sourceSet, String postFix) {
final String capitalized = postFix.capitalize()
final String name = sourceSet.getName() == SourceSet.MAIN_SOURCE_SET_NAME ? "" : sourceSet.getName().capitalize()

return (name + capitalized).uncapitalize()
}
}
4 changes: 3 additions & 1 deletion dsl/mixin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ plugins {
}

dependencies {
api "org.codehaus.groovy:groovy-all:${project.groovy_version}"
api("org.codehaus.groovy:groovy-all:${project.groovy_version}", {
exclude group: 'junit'
})
api "com.google.code.gson:gson:${project.gson_version}"
api "net.neoforged:groovydslimprover:${project.groovy_dsl_improver_version}"
}
4 changes: 3 additions & 1 deletion dsl/neoform/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ dependencies {
api project(':dsl-common')
api project(':utils')

api "org.codehaus.groovy:groovy-all:${project.groovy_version}"
api("org.codehaus.groovy:groovy-all:${project.groovy_version}", {
exclude group: 'junit'
})
api "com.google.code.gson:gson:${project.gson_version}"
api "net.neoforged:groovydslimprover:${project.groovy_dsl_improver_version}"
}
4 changes: 3 additions & 1 deletion dsl/platform/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ plugins {
}

dependencies {
api "org.codehaus.groovy:groovy-all:${project.groovy_version}"
api("org.codehaus.groovy:groovy-all:${project.groovy_version}", {
exclude group: 'junit'
})
api "com.google.code.gson:gson:${project.gson_version}"
api "net.neoforged:groovydslimprover:${project.groovy_dsl_improver_version}"

Expand Down
4 changes: 3 additions & 1 deletion dsl/userdev/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ plugins {
}

dependencies {
api "org.codehaus.groovy:groovy-all:${project.groovy_version}"
api("org.codehaus.groovy:groovy-all:${project.groovy_version}", {
exclude group: 'junit'
})
api "com.google.code.gson:gson:${project.gson_version}"
api "net.neoforged:groovydslimprover:${project.groovy_dsl_improver_version}"

Expand Down
Loading

0 comments on commit c63c7c8

Please sign in to comment.