Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Version 1.8
* [#127](https://github.com/gradlex-org/java-module-dependencies/issues/127) Less configuration cache misses when modifying `module-info.java` (Thanks [TheGoesen](https://github.com/TheGoesen))
* [#128](https://github.com/gradlex-org/java-module-dependencies/issues/128) Less configuration cache misses when using Settings plugin (Thanks [TheGoesen](https://github.com/TheGoesen))

## Version 1.7.1
* Update module name mappings
Expand Down
4 changes: 4 additions & 0 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ javaModules { // use instead of 'include(...)'
group = "org.example" // group for all Modules
plugin("java-library") // apply plugin to all Modules' subprojects
module("app") { ... } // individualise Module (only if needed)

// To optimze Configuration Cache hits:
exclusions.add("_.*") // do not inspect certain folders (regex)
requiresBuildFile // only look at folder containing a build.gradle(.kts)
}

versions("gradle/versions") // subproject configured as Platform Project
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;

import javax.inject.Inject;
import java.io.File;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;

Expand All @@ -41,14 +43,17 @@ public abstract class Directory {
*/
public abstract ListProperty<String> getPlugins();

@Inject
protected abstract ObjectFactory getObjects();

@Inject
public Directory(File root) {
this.root = root;
getExclusions().convention(Arrays.asList("build", "\\..*"));
getRequiresBuildFile().convention(false);
}

@Inject
protected abstract ObjectFactory getObjects();

/**
* {@link Module#plugin(String)}
*/
Expand Down Expand Up @@ -81,4 +86,19 @@ Module addModule(String subDirectory) {
module.getPlugins().addAll(getPlugins());
return module;
}

/**
* Configure which folders should be ignored when searching for Modules.
* This can be tweaked to optimize the configuration cache hit ratio.
* Defaults to: 'build', '.*'
*/
public abstract ListProperty<String> getExclusions();

/**
* Configure if only folders that contain a 'build.gradle' or 'build.gradle.kts'
* should be considered when searching for Modules.
* Setting this to true may improve configuration cache hit ratio if you know
* that all modules have build files in addition to the 'module-info.java' files.
*/
public abstract Property<Boolean> getRequiresBuildFile();
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,14 @@
import org.gradle.api.plugins.JavaApplication;
import org.gradle.api.plugins.JavaPlatformExtension;
import org.gradle.api.plugins.JavaPlatformPlugin;
import org.gradle.api.provider.Provider;
import org.gradle.api.provider.ProviderFactory;
import org.gradlex.javamodule.dependencies.JavaModuleDependenciesExtension;
import org.gradlex.javamodule.dependencies.JavaModuleDependenciesPlugin;
import org.gradlex.javamodule.dependencies.JavaModuleVersionsPlugin;
import org.gradlex.javamodule.dependencies.internal.utils.ModuleInfo;
import org.gradlex.javamodule.dependencies.internal.utils.ModuleInfoCache;
import org.gradlex.javamodule.dependencies.internal.utils.ValueModuleDirectoryListing;

import javax.annotation.Nullable;
import javax.inject.Inject;
Expand All @@ -47,6 +50,9 @@ public abstract class JavaModulesExtension {
@Inject
public abstract ObjectFactory getObjects();

@Inject
public abstract ProviderFactory getProviders();

@Inject
public JavaModulesExtension(Settings settings) {
this.settings = settings;
Expand Down Expand Up @@ -85,22 +91,21 @@ public void directory(String directory, Action<Directory> action) {
Directory moduleDirectory = getObjects().newInstance(Directory.class, modulesDirectory);
action.execute(moduleDirectory);

File[] projectDirs = modulesDirectory.listFiles();
if (projectDirs == null) {
throw new RuntimeException("Failed to inspect: " + modulesDirectory);
}

for (Module module : moduleDirectory.customizedModules.values()) {
includeModule(module, new File(modulesDirectory, module.getDirectory().get()));
}

for (File projectDir : projectDirs) {
if (!moduleDirectory.customizedModules.containsKey(projectDir.getName())) {
Module module = moduleDirectory.addModule(projectDir.getName());
if (!module.getModuleInfoPaths().get().isEmpty()) {
// only auto-include if there is at least one module-info.java
includeModule(module, projectDir);
}
Provider<List<String>> listProvider = getProviders().of(ValueModuleDirectoryListing.class, spec -> {
spec.getParameters().getExclusions().set(moduleDirectory.getExclusions());
spec.getParameters().getExplicitlyConfiguredFolders().set(moduleDirectory.customizedModules.keySet());
spec.getParameters().getDir().set(modulesDirectory);
spec.getParameters().getRequiresBuildFile().set(moduleDirectory.getRequiresBuildFile());
});

for (String projectDir : listProvider.get()) {
Module module = moduleDirectory.addModule(projectDir);
if (!module.getModuleInfoPaths().get().isEmpty()) {
// only auto-include if there is at least one module-info.java
includeModule(module, new File(modulesDirectory, projectDir));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright the GradleX team.
*
* Licensed 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.gradlex.javamodule.dependencies.internal.utils;

import org.gradle.api.provider.Property;
import org.gradle.api.provider.SetProperty;
import org.gradle.api.provider.ValueSource;
import org.gradle.api.provider.ValueSourceParameters;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public abstract class ValueModuleDirectoryListing implements ValueSource<List<String>, ValueModuleDirectoryListing.Parameter> {

public interface Parameter extends ValueSourceParameters {
Property<File> getDir();
SetProperty<String> getExplicitlyConfiguredFolders();
SetProperty<String> getExclusions();
Property<Boolean> getRequiresBuildFile();
}

@Override
public List<String> obtain() {
Path path = getParameters().getDir().get().toPath();
try (Stream<Path> directoryStream = Files.find(path, 1, (unused, basicFileAttributes) -> basicFileAttributes.isDirectory())) {
return directoryStream
.filter(x -> !getParameters().getExplicitlyConfiguredFolders().get().contains(x.getFileName().toString()))
.filter(x -> getParameters().getExclusions().get().stream().noneMatch(r -> x.getFileName().toString().matches(r)))
.filter(x -> checkBuildFile(x, getParameters()))
.map(x -> x.getFileName().toString())
.sorted()
.collect(Collectors.toList());

} catch (IOException e) {
throw new RuntimeException("Failed to inspect: " + path, e);
}
}

private boolean checkBuildFile(Path x, Parameter parameters) {
if (!parameters.getRequiresBuildFile().get()) {
return true;
}
return Files.isRegularFile(x.resolve("build.gradle.kts")) || Files.isRegularFile(x.resolve("build.gradle"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,72 @@ class SettingsPluginTest extends Specification {
result.getOutput().contains("Calculating task graph as no cached configuration is available for tasks: :app:compileJava")

when:
runner.build() // https://github.com/gradlex-org/java-module-dependencies/issues/128
result = runner.build()

then:
result.getOutput().contains("Reusing configuration cache.")
}

def "configurationCacheHitExtraDir"() {
given:
settingsFile << '''
javaModules {
directory(".") { plugin("java-library") }
}
'''
libModuleInfoFile << 'module abc.lib { }'
appModuleInfoFile << '''
module org.gradlex.test.app {
requires abc.lib;
}
'''

def runner = runner(':app:compileJava')
when:
def result = runner.build()

then:
result.getOutput().contains("Calculating task graph as no cached configuration is available for tasks: :app:compileJava")

when:
new File(settingsFile.parentFile, ".thisShallBeIgnored").mkdir()

result = runner.build()

then:
result.getOutput().contains("Reusing configuration cache.")
}

def "configurationCacheHitExtraNotIgnored"() {
given:
settingsFile << '''
javaModules {
directory(".") { plugin("java-library") }
}
'''
libModuleInfoFile << 'module abc.lib { }'
appModuleInfoFile << '''
module org.gradlex.test.app {
requires abc.lib;
}
'''

def runner = runner(':app:compileJava')
when:
def result = runner.build()

then:
result.getOutput().contains("Calculating task graph as no cached configuration is available for tasks: :app:compileJava")

when:
new File(settingsFile.parentFile, "thisShallNotBeIgnored").mkdir()

result = runner.build()

then:
result.getOutput().contains("Calculating task graph as configuration cache cannot be reused because a build logic input of type 'ValueModuleDirectoryListing' has changed.")
}

def "configurationCacheHitIrrelevantChange"() {
given:
settingsFile << '''
Expand All @@ -119,7 +178,6 @@ class SettingsPluginTest extends Specification {
result.getOutput().contains("Calculating task graph as no cached configuration is available for tasks: :app:compileJava")

when:
runner.build() // https://github.com/gradlex-org/java-module-dependencies/issues/128
appModuleInfoFile.write('''
module org.gradlex.test.app {
requires abc.lib; //This is a comment and should not break the configurationCache
Expand Down Expand Up @@ -153,7 +211,6 @@ class SettingsPluginTest extends Specification {
result.getOutput().contains("Calculating task graph as no cached configuration is available for tasks: :app:compileJava")

when:
runner.build() // https://github.com/gradlex-org/java-module-dependencies/issues/128
appModuleInfoFile.write('''
module org.gradlex.test.app {
//dependency removed; so thats indeed a configuration change
Expand Down