Explorar o código

Replace getProject() references with injected services in task implementations where possible (#81681)

- Rework task implementations to avoid project usage at execution time
- In general usages of getProject() within a task should be avoided as it is not compatible
with gradle configuration cache. Related to #57918
Rene Groeschke %!s(int64=3) %!d(string=hai) anos
pai
achega
c4686128f0
Modificáronse 23 ficheiros con 445 adicións e 248 borrados
  1. 41 38
      build-conventions/src/main/java/org/elasticsearch/gradle/internal/conventions/precommit/LicenseHeadersTask.java
  2. 9 1
      build-conventions/src/main/java/org/elasticsearch/gradle/internal/conventions/precommit/PomValidationTask.java
  3. 14 3
      build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/DependenciesGraphTask.java
  4. 25 16
      build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/DependenciesInfoTask.java
  5. 0 7
      build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/EmptyDirTask.java
  6. 6 2
      build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ExportElasticsearchBuildResourcesTask.java
  7. 7 3
      build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/JavaClassPublicifier.java
  8. 34 15
      build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/NoticeTask.java
  9. 3 3
      build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/docker/DockerBuildTask.java
  10. 32 17
      build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/precommit/DependencyLicensesTask.java
  11. 14 1
      build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/precommit/LoggerUsagePrecommitPlugin.java
  12. 15 15
      build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/precommit/LoggerUsageTask.java
  13. 14 0
      build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/precommit/SplitPackagesAuditPrecommitPlugin.java
  14. 12 15
      build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/precommit/SplitPackagesAuditTask.java
  15. 20 8
      build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/precommit/TestingConventionsPrecommitPlugin.java
  16. 23 19
      build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/precommit/TestingConventionsTasks.java
  17. 22 2
      build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/precommit/ThirdPartyAuditPrecommitPlugin.java
  18. 75 55
      build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/precommit/ThirdPartyAuditTask.java
  19. 26 7
      build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/test/DistroTestPlugin.java
  20. 28 3
      build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/test/GradleDistroTestTask.java
  21. 4 6
      build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/vagrant/VagrantMachine.java
  22. 17 12
      build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/vagrant/VagrantShellTask.java
  23. 4 0
      build-tools/src/main/java/org/elasticsearch/gradle/util/GradleUtils.java

+ 41 - 38
build-conventions/src/main/java/org/elasticsearch/gradle/internal/conventions/precommit/LicenseHeadersTask.java

@@ -23,6 +23,8 @@ import org.apache.rat.report.xml.writer.impl.base.XmlWriter;
 import org.gradle.api.DefaultTask;
 import org.gradle.api.GradleException;
 import org.gradle.api.file.FileCollection;
+import org.gradle.api.file.ProjectLayout;
+import org.gradle.api.file.RegularFileProperty;
 import org.gradle.api.provider.ListProperty;
 import org.gradle.api.tasks.CacheableTask;
 import org.gradle.api.tasks.IgnoreEmptyDirectories;
@@ -55,12 +57,42 @@ import java.util.stream.Collectors;
 import javax.inject.Inject;
 import java.io.Serializable;
 
+import javax.inject.Inject;
+
 /**
  * Checks files for license headers..
  */
 @CacheableTask
 public abstract class LicenseHeadersTask extends DefaultTask {
-    public LicenseHeadersTask() {
+
+    private final RegularFileProperty reportFile;
+
+    private static List<License> conventionalLicenses = Arrays.asList(
+            // Dual SSPLv1 and Elastic
+            new License("DUAL", "SSPL+Elastic License", "the Elastic License 2.0 or the Server")
+    );
+
+    /**
+     * Allowed license families for this project.
+     */
+    @Input
+    private List<String> approvedLicenses = new ArrayList<String>(Arrays.asList("SSPL+Elastic License", "Generated", "Vendored", "Apache LZ4-Java"));
+    /**
+     * Files that should be excluded from the license header check. Use with extreme care, only in situations where the license on the
+     * source file is compatible with the codebase but we do not want to add the license to the list of approved headers (to avoid the
+     * possibility of inadvertently using the license on our own source files).
+     */
+    @Input
+    private List<String> excludes = new ArrayList<String>();
+
+    private ListProperty<License> additionalLicenses;
+
+    @Inject
+    public LicenseHeadersTask(ObjectFactory objectFactory, ProjectLayout projectLayout) {
+        additionalLicenses = objectFactory.listProperty(License.class).convention(conventionalLicenses);
+        reportFile = objectFactory.fileProperty().convention(
+            projectLayout.getBuildDirectory().file("reports/licenseHeaders/rat.xml")
+        );
         setDescription("Checks sources for missing, incorrect, or unacceptable license headers");
     }
 
@@ -79,14 +111,11 @@ public abstract class LicenseHeadersTask extends DefaultTask {
     @Internal
     public abstract ListProperty<FileCollection> getSourceFolders();
 
-    public File getReportFile() {
+    @OutputFile
+    public RegularFileProperty getReportFile() {
         return reportFile;
     }
 
-    public void setReportFile(File reportFile) {
-        this.reportFile = reportFile;
-    }
-
     public List<String> getApprovedLicenses() {
         return approvedLicenses;
     }
@@ -103,29 +132,6 @@ public abstract class LicenseHeadersTask extends DefaultTask {
         this.excludes = excludes;
     }
 
-    @OutputFile
-    private File reportFile = new File(getProject().getBuildDir(), "reports/licenseHeaders/rat.xml");
-
-    private static List<License> conventionalLicenses = Arrays.asList(
-            // Dual SSPLv1 and Elastic
-            new License("DUAL", "SSPL+Elastic License", "the Elastic License 2.0 or the Server")
-    );
-
-    /**
-     * Allowed license families for this project.
-     */
-    @Input
-    private List<String> approvedLicenses = new ArrayList<String>(Arrays.asList("SSPL+Elastic License", "Generated", "Vendored", "Apache LZ4-Java"));
-    /**
-     * Files that should be excluded from the license header check. Use with extreme care, only in situations where the license on the
-     * source file is compatible with the codebase but we do not want to add the license to the list of approved headers (to avoid the
-     * possibility of inadvertently using the license on our own source files).
-     */
-    @Input
-    private List<String> excludes = new ArrayList<String>();
-
-    private ListProperty<License> additionalLicenses;
-
     /**
      * Additional license families that may be found. The key is the license category name (5 characters),
      * followed by the family name and the value list of patterns to search for.
@@ -151,11 +157,6 @@ public abstract class LicenseHeadersTask extends DefaultTask {
         additionalLicenses.add(new License(categoryName, familyName, pattern));
     }
 
-    @Inject
-    public LicenseHeadersTask(ObjectFactory objectFactory) {
-        additionalLicenses = objectFactory.listProperty(License.class).convention(conventionalLicenses);
-    }
-
     @TaskAction
     public void runRat() {
         ReportConfiguration reportConfiguration = new ReportConfiguration();
@@ -186,13 +187,15 @@ public abstract class LicenseHeadersTask extends DefaultTask {
             return simpleLicenseFamily;
         }).toArray(SimpleLicenseFamily[]::new));
 
-        ClaimStatistic stats = generateReport(reportConfiguration, getReportFile());
+        File repFile = getReportFile().getAsFile().get();
+        ClaimStatistic stats = generateReport(reportConfiguration, repFile);
         boolean unknownLicenses = stats.getNumUnknown() > 0;
         boolean unApprovedLicenses = stats.getNumUnApproved() > 0;
         if (unknownLicenses || unApprovedLicenses) {
             getLogger().error("The following files contain unapproved license headers:");
-            unapprovedFiles(getReportFile()).stream().forEachOrdered(unapprovedFile -> getLogger().error(unapprovedFile));
-            throw new GradleException("Check failed. License header problems were found. Full details: " + reportFile.getAbsolutePath());
+            unapprovedFiles(repFile).stream().forEachOrdered(unapprovedFile -> getLogger().error(unapprovedFile));
+            throw new GradleException("Check failed. License header problems were found. Full details: " +
+                    repFile.getAbsolutePath());
         }
     }
 
@@ -208,7 +211,7 @@ public abstract class LicenseHeadersTask extends DefaultTask {
 
     private ClaimStatistic generateReport(ReportConfiguration config, File xmlReportFile) {
         try {
-            Files.deleteIfExists(reportFile.toPath());
+            Files.deleteIfExists(reportFile.get().getAsFile().toPath());
             BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(xmlReportFile));
             return toXmlReportFile(config, bufferedWriter);
         } catch (IOException | RatException exception) {

+ 9 - 1
build-conventions/src/main/java/org/elasticsearch/gradle/internal/conventions/precommit/PomValidationTask.java

@@ -13,6 +13,7 @@ import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
 import org.elasticsearch.gradle.internal.conventions.precommit.PrecommitTask;
 import org.gradle.api.GradleException;
 import org.gradle.api.file.RegularFileProperty;
+import org.gradle.api.model.ObjectFactory;
 import org.gradle.api.tasks.InputFile;
 import org.gradle.api.tasks.TaskAction;
 
@@ -21,12 +22,19 @@ import java.util.Collection;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
+import javax.inject.Inject;
+
 public class PomValidationTask extends PrecommitTask {
 
-    private final RegularFileProperty pomFile = getProject().getObjects().fileProperty();
+    private final RegularFileProperty pomFile;
 
     private boolean foundError;
 
+    @Inject
+    public PomValidationTask(ObjectFactory objects) {
+        pomFile = objects.fileProperty();
+    }
+
     @InputFile
     public RegularFileProperty getPomFile() {
         return pomFile;

+ 14 - 3
build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/DependenciesGraphTask.java

@@ -14,6 +14,7 @@ import org.apache.http.entity.StringEntity;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClients;
 import org.apache.http.util.EntityUtils;
+import org.gradle.StartParameter;
 import org.gradle.api.DefaultTask;
 import org.gradle.api.GradleException;
 import org.gradle.api.artifacts.Configuration;
@@ -27,6 +28,10 @@ import org.gradle.api.tasks.TaskAction;
 import java.util.HashSet;
 import java.util.Set;
 
+import javax.inject.Inject;
+
+import static org.elasticsearch.gradle.util.GradleUtils.projectPath;
+
 /**
  * A task to generate a dependency graph of our runtime dependencies and push that via
  * an API call to a given endpoint of a SCA tool/service.
@@ -43,6 +48,7 @@ public class DependenciesGraphTask extends DefaultTask {
     private Configuration runtimeConfiguration;
     private String token;
     private String url;
+    private StartParameter startParameter;
 
     @Input
     public String getUrl() {
@@ -71,10 +77,14 @@ public class DependenciesGraphTask extends DefaultTask {
         this.runtimeConfiguration = runtimeConfiguration;
     }
 
+    @Inject
+    public DependenciesGraphTask(StartParameter startParameter) {
+        this.startParameter = startParameter;
+    }
+
     @TaskAction
     void generateDependenciesGraph() {
-
-        if (getProject().getGradle().getStartParameter().isOffline()) {
+        if (startParameter.isOffline()) {
             throw new GradleException("Must run in online mode in order to submit the dependency graph to the SCA service");
         }
 
@@ -102,7 +112,7 @@ public class DependenciesGraphTask extends DefaultTask {
         }
         // We add one package and one node for each dependency, it suffices to check packages.
         if (packages.size() > 0) {
-            final String projectName = "elastic/elasticsearch" + getProject().getPath();
+            final String projectName = "elastic/elasticsearch" + projectPath(getPath());
             final String output = """
                 {
                   "depGraph": {
@@ -138,4 +148,5 @@ public class DependenciesGraphTask extends DefaultTask {
             }
         }
     }
+
 }

+ 25 - 16
build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/DependenciesInfoTask.java

@@ -15,7 +15,10 @@ import org.gradle.api.artifacts.Dependency;
 import org.gradle.api.artifacts.DependencySet;
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
 import org.gradle.api.artifacts.ProjectDependency;
+import org.gradle.api.file.DirectoryProperty;
+import org.gradle.api.file.ProjectLayout;
 import org.gradle.api.internal.ConventionTask;
+import org.gradle.api.model.ObjectFactory;
 import org.gradle.api.tasks.Input;
 import org.gradle.api.tasks.InputDirectory;
 import org.gradle.api.tasks.InputFiles;
@@ -33,6 +36,8 @@ import java.util.Set;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
+import javax.inject.Inject;
+
 /**
  * A task to gather information about the dependencies and export them into a csv file.
  * <p>
@@ -45,17 +50,12 @@ import java.util.stream.Collectors;
  * </ul>
  */
 public class DependenciesInfoTask extends ConventionTask {
-    /**
-     * Directory to read license files
-     */
-    @Optional
-    @InputDirectory
-    private File licensesDir = new File(getProject().getProjectDir(), "licenses").exists()
-        ? new File(getProject().getProjectDir(), "licenses")
-        : null;
+
+    private final DirectoryProperty licensesDir;
 
     @OutputFile
-    private File outputFile = new File(getProject().getBuildDir(), "reports/dependencies/dependencies.csv");
+    private File outputFile;
+
     private LinkedHashMap<String, String> mappings;
 
     public Configuration getRuntimeConfiguration() {
@@ -74,12 +74,17 @@ public class DependenciesInfoTask extends ConventionTask {
         this.compileOnlyConfiguration = compileOnlyConfiguration;
     }
 
-    public File getLicensesDir() {
+    /**
+     * Directory to read license files
+     */
+    @Optional
+    @InputDirectory
+    public DirectoryProperty getLicensesDir() {
         return licensesDir;
     }
 
     public void setLicensesDir(File licensesDir) {
-        this.licensesDir = licensesDir;
+        this.licensesDir.set(licensesDir);
     }
 
     public File getOutputFile() {
@@ -101,13 +106,16 @@ public class DependenciesInfoTask extends ConventionTask {
     @InputFiles
     private Configuration compileOnlyConfiguration;
 
-    public DependenciesInfoTask() {
+    @Inject
+    public DependenciesInfoTask(ProjectLayout projectLayout, ObjectFactory objectFactory) {
+        this.licensesDir = objectFactory.directoryProperty();
+        this.licensesDir.set(projectLayout.getProjectDirectory().dir("licenses"));
+        this.outputFile = projectLayout.getBuildDirectory().dir("reports/dependencies").get().file("dependencies.csv").getAsFile();
         setDescription("Create a CSV file with dependencies information.");
     }
 
     @TaskAction
     public void generateDependenciesInfo() throws IOException {
-
         final DependencySet runtimeDependencies = runtimeConfiguration.getAllDependencies();
         // we have to resolve the transitive dependencies and create a group:artifactId:version map
 
@@ -203,8 +211,9 @@ public class DependenciesInfoTask extends ConventionTask {
     }
 
     protected File getDependencyInfoFile(final String group, final String name, final String infoFileSuffix) {
-        java.util.Optional<File> license = licensesDir != null
-            ? Arrays.stream(licensesDir.listFiles((dir, fileName) -> Pattern.matches(".*-" + infoFileSuffix + ".*", fileName)))
+        File licenseDirFile = licensesDir.getAsFile().get();
+        java.util.Optional<File> license = licenseDirFile.exists()
+            ? Arrays.stream(licenseDirFile.listFiles((dir, fileName) -> Pattern.matches(".*-" + infoFileSuffix + ".*", fileName)))
                 .filter(file -> {
                     String prefix = file.getName().split("-" + infoFileSuffix + ".*")[0];
                     return group.contains(prefix) || name.contains(prefix);
@@ -221,7 +230,7 @@ public class DependenciesInfoTask extends ConventionTask {
                     + ":"
                     + name
                     + " in "
-                    + getLicensesDir().getAbsolutePath()
+                    + licenseDirFile.getAbsolutePath()
             )
         );
     }

+ 0 - 7
build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/EmptyDirTask.java

@@ -56,13 +56,6 @@ public class EmptyDirTask extends DefaultTask {
         this.dir = dir;
     }
 
-    /**
-     * @param dir The path of the directory to create. Takes a String and coerces it to a file.
-     */
-    public void setDir(String dir) {
-        this.dir = getProject().file(dir);
-    }
-
     @Input
     public int getDirMode() {
         return dirMode;

+ 6 - 2
build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ExportElasticsearchBuildResourcesTask.java

@@ -12,6 +12,7 @@ import org.gradle.api.GradleException;
 import org.gradle.api.file.DirectoryProperty;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
+import org.gradle.api.model.ObjectFactory;
 import org.gradle.api.tasks.Classpath;
 import org.gradle.api.tasks.Input;
 import org.gradle.api.tasks.OutputDirectory;
@@ -28,6 +29,8 @@ import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
 
+import javax.inject.Inject;
+
 /**
  * Export Elasticsearch build resources to configurable paths
  * <p>
@@ -43,8 +46,9 @@ public class ExportElasticsearchBuildResourcesTask extends DefaultTask {
 
     private DirectoryProperty outputDir;
 
-    public ExportElasticsearchBuildResourcesTask() {
-        outputDir = getProject().getObjects().directoryProperty();
+    @Inject
+    public ExportElasticsearchBuildResourcesTask(ObjectFactory objects) {
+        outputDir = objects.directoryProperty();
     }
 
     @OutputDirectory

+ 7 - 3
build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/JavaClassPublicifier.java

@@ -10,6 +10,7 @@ package org.elasticsearch.gradle.internal;
 
 import org.gradle.api.DefaultTask;
 import org.gradle.api.file.DirectoryProperty;
+import org.gradle.api.model.ObjectFactory;
 import org.gradle.api.tasks.Input;
 import org.gradle.api.tasks.InputDirectory;
 import org.gradle.api.tasks.OutputDirectory;
@@ -26,6 +27,8 @@ import java.nio.file.Files;
 import java.util.List;
 import java.util.function.Consumer;
 
+import javax.inject.Inject;
+
 import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
 import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
 
@@ -38,9 +41,10 @@ public class JavaClassPublicifier extends DefaultTask {
     private DirectoryProperty inputDir;
     private DirectoryProperty outputDir;
 
-    public JavaClassPublicifier() {
-        this.inputDir = getProject().getObjects().directoryProperty();
-        this.outputDir = getProject().getObjects().directoryProperty();
+    @Inject
+    public JavaClassPublicifier(ObjectFactory objects) {
+        this.inputDir = objects.directoryProperty();
+        this.outputDir = objects.directoryProperty();
     }
 
     @Input

+ 34 - 15
build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/NoticeTask.java

@@ -13,19 +13,26 @@ import org.elasticsearch.gradle.util.FileUtils;
 import org.gradle.api.DefaultTask;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.file.FileTree;
+import org.gradle.api.file.ProjectLayout;
 import org.gradle.api.file.SourceDirectorySet;
+import org.gradle.api.internal.file.FileOperations;
+import org.gradle.api.model.ObjectFactory;
+import org.gradle.api.provider.ListProperty;
 import org.gradle.api.tasks.InputFile;
 import org.gradle.api.tasks.InputFiles;
 import org.gradle.api.tasks.Optional;
 import org.gradle.api.tasks.OutputFile;
 import org.gradle.api.tasks.TaskAction;
+import org.gradle.initialization.layout.BuildLayout;
 
 import java.io.File;
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
+import java.util.stream.Collectors;
+
+import javax.inject.Inject;
 
 import static org.apache.commons.io.FileUtils.readFileToString;
 
@@ -35,22 +42,31 @@ import static org.apache.commons.io.FileUtils.readFileToString;
 public class NoticeTask extends DefaultTask {
 
     @InputFile
-    private File inputFile = getProject().getRootProject().file("NOTICE.txt");
+    private File inputFile;
+
     @OutputFile
-    private File outputFile = new File(getProject().getBuildDir(), "notices/" + getName() + "/NOTICE.txt");
+    private File outputFile;
+
     private FileTree sources;
+
     /**
      * Directories to include notices from
      */
-    private List<File> licensesDirs = new ArrayList<File>();
+    private final ListProperty<File> licensesDirs;
+
+    private final FileOperations fileOperations;
+    private ObjectFactory objectFactory;
 
-    public NoticeTask() {
+    @Inject
+    public NoticeTask(BuildLayout buildLayout, ProjectLayout projectLayout, FileOperations fileOperations, ObjectFactory objectFactory) {
+        this.objectFactory = objectFactory;
+        this.fileOperations = fileOperations;
         setDescription("Create a notice file from dependencies");
         // Default licenses directory is ${projectDir}/licenses (if it exists)
-        File licensesDir = new File(getProject().getProjectDir(), "licenses");
-        if (licensesDir.exists()) {
-            licensesDirs.add(licensesDir);
-        }
+        licensesDirs = objectFactory.listProperty(File.class);
+        licensesDirs.add(projectLayout.getProjectDirectory().dir("licenses").getAsFile());
+        inputFile = new File(buildLayout.getRootDirectory(), "NOTICE.txt");
+        outputFile = projectLayout.getBuildDirectory().dir("notices/" + getName()).get().file("NOTICE.txt").getAsFile();
     }
 
     /**
@@ -62,9 +78,9 @@ public class NoticeTask extends DefaultTask {
 
     public void source(Object source) {
         if (sources == null) {
-            sources = getProject().fileTree(source);
+            sources = fileOperations.fileTree(source);
         } else {
-            sources = sources.plus(getProject().fileTree(source));
+            sources = sources.plus(fileOperations.fileTree(source));
         }
 
     }
@@ -75,7 +91,6 @@ public class NoticeTask extends DefaultTask {
         } else {
             sources = sources.plus(source);
         }
-
     }
 
     @TaskAction
@@ -153,16 +168,20 @@ public class NoticeTask extends DefaultTask {
     @Optional
     public FileCollection getNoticeFiles() {
         FileTree tree = null;
-        for (File dir : licensesDirs) {
+        for (File dir : existingLicenseDirs()) {
             if (tree == null) {
-                tree = getProject().fileTree(dir);
+                tree = fileOperations.fileTree(dir);
             } else {
-                tree = tree.plus(getProject().fileTree(dir));
+                tree = tree.plus(fileOperations.fileTree(dir));
             }
         }
         return tree == null ? null : tree.matching(patternFilterable -> patternFilterable.include("**/*-NOTICE.txt"));
     }
 
+    private List<File> existingLicenseDirs() {
+        return licensesDirs.get().stream().filter(d -> d.exists()).collect(Collectors.toList());
+    }
+
     @InputFiles
     @Optional
     public FileCollection getSources() {

+ 3 - 3
build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/docker/DockerBuildTask.java

@@ -11,6 +11,7 @@ import org.elasticsearch.gradle.LoggedExec;
 import org.gradle.api.DefaultTask;
 import org.gradle.api.GradleException;
 import org.gradle.api.file.DirectoryProperty;
+import org.gradle.api.file.ProjectLayout;
 import org.gradle.api.file.RegularFileProperty;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
@@ -55,13 +56,12 @@ public class DockerBuildTask extends DefaultTask {
     private MapProperty<String, String> buildArgs;
 
     @Inject
-    public DockerBuildTask(WorkerExecutor workerExecutor, ObjectFactory objectFactory) {
+    public DockerBuildTask(WorkerExecutor workerExecutor, ObjectFactory objectFactory, ProjectLayout projectLayout) {
         this.workerExecutor = workerExecutor;
         this.markerFile = objectFactory.fileProperty();
         this.dockerContext = objectFactory.directoryProperty();
         this.buildArgs = objectFactory.mapProperty(String.class, String.class);
-
-        this.markerFile.set(getProject().getLayout().getBuildDirectory().file("markers/" + this.getName() + ".marker"));
+        this.markerFile.set(projectLayout.getBuildDirectory().file("markers/" + this.getName() + ".marker"));
     }
 
     @TaskAction

+ 32 - 17
build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/precommit/DependencyLicensesTask.java

@@ -12,9 +12,14 @@ import org.elasticsearch.gradle.internal.precommit.LicenseAnalyzer.LicenseInfo;
 import org.gradle.api.DefaultTask;
 import org.gradle.api.GradleException;
 import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.file.Directory;
+import org.gradle.api.file.DirectoryProperty;
 import org.gradle.api.file.FileCollection;
+import org.gradle.api.file.ProjectLayout;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
+import org.gradle.api.model.ObjectFactory;
+import org.gradle.api.provider.Provider;
 import org.gradle.api.tasks.Input;
 import org.gradle.api.tasks.InputDirectory;
 import org.gradle.api.tasks.InputFiles;
@@ -43,6 +48,8 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
+import javax.inject.Inject;
+
 /**
  * A task to check licenses for dependencies.
  * <p>
@@ -103,7 +110,7 @@ public class DependencyLicensesTask extends DefaultTask {
     /**
      * The directory to find the license and sha files in.
      */
-    private File licensesDir = new File(getProject().getProjectDir(), "licenses");
+    private final DirectoryProperty licensesDir;
 
     /**
      * A map of patterns to prefix, used to find the LICENSE and NOTICE file.
@@ -119,6 +126,7 @@ public class DependencyLicensesTask extends DefaultTask {
      *  Names of files that should be ignored by the check
      */
     private LinkedHashSet<String> ignoreFiles = new LinkedHashSet<>();
+    private ProjectLayout projectLayout;
 
     /**
      * Add a mapping from a regex pattern for the jar name, to a prefix to find
@@ -139,6 +147,12 @@ public class DependencyLicensesTask extends DefaultTask {
         mappings.put(from, to);
     }
 
+    @Inject
+    public DependencyLicensesTask(ObjectFactory objects, ProjectLayout projectLayout) {
+        this.projectLayout = projectLayout;
+        licensesDir = objects.directoryProperty().convention(projectLayout.getProjectDirectory().dir("licenses"));
+    }
+
     @InputFiles
     public FileCollection getDependencies() {
         return dependencies;
@@ -151,15 +165,16 @@ public class DependencyLicensesTask extends DefaultTask {
     @Optional
     @InputDirectory
     public File getLicensesDir() {
-        if (licensesDir.exists()) {
-            return licensesDir;
+        File asFile = licensesDir.get().getAsFile();
+        if (asFile.exists()) {
+            return asFile;
         }
 
         return null;
     }
 
     public void setLicensesDir(File licensesDir) {
-        this.licensesDir = licensesDir;
+        this.licensesDir.set(licensesDir);
     }
 
     /**
@@ -182,26 +197,25 @@ public class DependencyLicensesTask extends DefaultTask {
         if (dependencies == null) {
             throw new GradleException("No dependencies variable defined.");
         }
-
+        File licensesDirAsFile = licensesDir.get().getAsFile();
         if (dependencies.isEmpty()) {
-            if (licensesDir.exists()) {
-                throw new GradleException("Licenses dir " + licensesDir + " exists, but there are no dependencies");
+            if (licensesDirAsFile.exists()) {
+                throw new GradleException("Licenses dir " + licensesDirAsFile + " exists, but there are no dependencies");
             }
             return; // no dependencies to check
-        } else if (licensesDir.exists() == false) {
+        } else if (licensesDirAsFile.exists() == false) {
             String deps = "";
             for (File file : dependencies) {
                 deps += file.getName() + "\n";
             }
-            throw new GradleException("Licences dir " + licensesDir + " does not exist, but there are dependencies: " + deps);
+            throw new GradleException("Licences dir " + licensesDirAsFile + " does not exist, but there are dependencies: " + deps);
         }
 
         Map<String, Boolean> licenses = new HashMap<>();
         Map<String, Boolean> notices = new HashMap<>();
         Map<String, Boolean> sources = new HashMap<>();
         Set<File> shaFiles = new HashSet<>();
-
-        for (File file : licensesDir.listFiles()) {
+        for (File file : licensesDirAsFile.listFiles()) {
             String name = file.getName();
             if (name.endsWith(SHA_EXTENSION)) {
                 shaFiles.add(file);
@@ -237,8 +251,8 @@ public class DependencyLicensesTask extends DefaultTask {
     // The check logic is exception driven so a failed tasks will not be defined
     // by this output but when successful we can safely mark the task as up-to-date.
     @OutputDirectory
-    public File getOutputMarker() {
-        return new File(getProject().getBuildDir(), "dependencyLicense");
+    public Provider<Directory> getOutputMarker() {
+        return projectLayout.getBuildDirectory().dir("dependencyLicense");
     }
 
     private void failIfAnyMissing(String item, Boolean exists, String type) {
@@ -264,7 +278,7 @@ public class DependencyLicensesTask extends DefaultTask {
             checkFile(dependencyName, jarName, licenses, "LICENSE");
             checkFile(dependencyName, jarName, notices, "NOTICE");
 
-            File licenseFile = new File(licensesDir, getFileName(dependencyName, licenses, "LICENSE"));
+            File licenseFile = new File(licensesDir.get().getAsFile(), getFileName(dependencyName, licenses, "LICENSE"));
             LicenseInfo licenseInfo = LicenseAnalyzer.licenseType(licenseFile);
             if (licenseInfo.sourceRedistributionRequired()) {
                 checkFile(dependencyName, jarName, sources, "SOURCES");
@@ -362,14 +376,15 @@ public class DependencyLicensesTask extends DefaultTask {
     }
 
     File getShaFile(String jarName) {
-        return new File(licensesDir, jarName + SHA_EXTENSION);
+        return new File(licensesDir.get().getAsFile(), jarName + SHA_EXTENSION);
     }
 
     @Internal
     Set<File> getShaFiles() {
-        File[] array = licensesDir.listFiles();
+        File licenseDirAsFile = licensesDir.get().getAsFile();
+        File[] array = licenseDirAsFile.listFiles();
         if (array == null) {
-            throw new GradleException("\"" + licensesDir.getPath() + "\" isn't a valid directory");
+            throw new GradleException("\"" + licenseDirAsFile.getPath() + "\" isn't a valid directory");
         }
 
         return Arrays.stream(array).filter(file -> file.getName().endsWith(SHA_EXTENSION)).collect(Collectors.toSet());

+ 14 - 1
build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/precommit/LoggerUsagePrecommitPlugin.java

@@ -13,6 +13,9 @@ import org.elasticsearch.gradle.internal.conventions.precommit.PrecommitPlugin;
 import org.gradle.api.Project;
 import org.gradle.api.Task;
 import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.plugins.JavaPluginExtension;
+import org.gradle.api.tasks.SourceSet;
+import org.gradle.api.tasks.SourceSetContainer;
 import org.gradle.api.tasks.TaskProvider;
 
 public class LoggerUsagePrecommitPlugin extends PrecommitPlugin implements InternalPlugin {
@@ -25,7 +28,17 @@ public class LoggerUsagePrecommitPlugin extends PrecommitPlugin implements Inter
             project.getDependencies().add("loggerUsagePlugin", project.project(":test:logger-usage"));
         }
         TaskProvider<LoggerUsageTask> loggerUsage = project.getTasks().register("loggerUsageCheck", LoggerUsageTask.class);
-        loggerUsage.configure(t -> t.setClasspath(loggerUsageConfig));
+
+        SourceSetContainer sourceSets = project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets();
+        sourceSets.matching(
+            sourceSet -> sourceSet.getName().equals(SourceSet.MAIN_SOURCE_SET_NAME)
+                || sourceSet.getName().equals(SourceSet.TEST_SOURCE_SET_NAME)
+        ).all(sourceSet -> loggerUsage.configure(t -> t.addSourceSet(sourceSet)));
+
+        loggerUsage.configure(
+            t -> t.setClasspath(loggerUsageConfig)
+
+        );
         return loggerUsage;
     }
 }

+ 15 - 15
build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/precommit/LoggerUsageTask.java

@@ -12,7 +12,8 @@ import org.elasticsearch.gradle.LoggedExec;
 import org.elasticsearch.gradle.internal.conventions.precommit.PrecommitTask;
 import org.gradle.api.file.ConfigurableFileCollection;
 import org.gradle.api.file.FileCollection;
-import org.gradle.api.plugins.JavaPluginExtension;
+import org.gradle.api.model.ObjectFactory;
+import org.gradle.api.provider.ListProperty;
 import org.gradle.api.tasks.CacheableTask;
 import org.gradle.api.tasks.Classpath;
 import org.gradle.api.tasks.InputFiles;
@@ -39,7 +40,14 @@ public abstract class LoggerUsageTask extends PrecommitTask {
 
     private FileCollection classpath;
 
-    public LoggerUsageTask() {
+    private final ListProperty<FileCollection> classesDirs;
+
+    private ObjectFactory objectFactory;
+
+    @Inject
+    public LoggerUsageTask(ObjectFactory objectFactory) {
+        this.classesDirs = objectFactory.listProperty(FileCollection.class);
+        this.objectFactory = objectFactory;
         setDescription("Runs LoggerUsageCheck on output directories of all source sets");
     }
 
@@ -68,19 +76,11 @@ public abstract class LoggerUsageTask extends PrecommitTask {
     @PathSensitive(PathSensitivity.RELATIVE)
     @SkipWhenEmpty
     public FileCollection getClassDirectories() {
-        return getProject().getExtensions()
-            .getByType(JavaPluginExtension.class)
-            .getSourceSets()
-            .stream()
-            // Don't pick up all source sets like the java9 ones as logger-check doesn't support the class format
-            .filter(
-                sourceSet -> sourceSet.getName().equals(SourceSet.MAIN_SOURCE_SET_NAME)
-                    || sourceSet.getName().equals(SourceSet.TEST_SOURCE_SET_NAME)
-            )
-            .map(sourceSet -> sourceSet.getOutput().getClassesDirs())
-            .reduce(FileCollection::plus)
-            .orElse(getProject().files())
-            .filter(File::exists);
+        return classesDirs.get().stream().reduce(FileCollection::plus).orElse(objectFactory.fileCollection()).filter(File::exists);
+    }
+
+    public void addSourceSet(SourceSet sourceSet) {
+        classesDirs.add(sourceSet.getOutput().getClassesDirs());
     }
 
     abstract static class LoggerUsageWorkAction implements WorkAction<Parameters> {

+ 14 - 0
build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/precommit/SplitPackagesAuditPrecommitPlugin.java

@@ -16,6 +16,10 @@ import org.gradle.api.plugins.JavaPlugin;
 import org.gradle.api.tasks.SourceSet;
 import org.gradle.api.tasks.TaskProvider;
 
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
 public class SplitPackagesAuditPrecommitPlugin extends PrecommitPlugin {
     public static final String TASK_NAME = "splitPackagesAudit";
 
@@ -23,10 +27,20 @@ public class SplitPackagesAuditPrecommitPlugin extends PrecommitPlugin {
     public TaskProvider<? extends Task> createTask(Project project) {
         TaskProvider<SplitPackagesAuditTask> task = project.getTasks().register(TASK_NAME, SplitPackagesAuditTask.class);
         task.configure(t -> {
+            t.setProjectBuildDirs(getProjectBuildDirs(project));
             t.setClasspath(project.getConfigurations().getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME));
             SourceSet mainSourceSet = GradleUtils.getJavaSourceSets(project).findByName(SourceSet.MAIN_SOURCE_SET_NAME);
             t.getSrcDirs().set(project.provider(() -> mainSourceSet.getAllSource().getSrcDirs()));
         });
         return task;
     }
+
+    private static Map<File, String> getProjectBuildDirs(Project project) {
+        // while this is done in every project, it should be cheap to calculate
+        Map<File, String> buildDirs = new HashMap<>();
+        for (Project p : project.getRootProject().getAllprojects()) {
+            buildDirs.put(p.getBuildDir(), p.getPath());
+        }
+        return buildDirs;
+    }
 }

+ 12 - 15
build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/precommit/SplitPackagesAuditTask.java

@@ -10,9 +10,9 @@ package org.elasticsearch.gradle.internal.precommit;
 
 import org.gradle.api.DefaultTask;
 import org.gradle.api.GradleException;
-import org.gradle.api.Project;
 import org.gradle.api.file.ConfigurableFileCollection;
 import org.gradle.api.file.FileCollection;
+import org.gradle.api.file.ProjectLayout;
 import org.gradle.api.file.RegularFileProperty;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
@@ -52,6 +52,8 @@ import java.util.stream.Collectors;
 
 import javax.inject.Inject;
 
+import static org.elasticsearch.gradle.util.GradleUtils.projectPath;
+
 /**
  * Checks for split packages with dependencies. These are not allowed in a future modularized world.
  */
@@ -64,22 +66,22 @@ public class SplitPackagesAuditTask extends DefaultTask {
     private final SetProperty<File> srcDirs;
     private final SetProperty<String> ignoreClasses;
     private final RegularFileProperty markerFile;
+    private Map<File, String> projectBuildDirs;
 
     @Inject
-    public SplitPackagesAuditTask(WorkerExecutor workerExecutor, ObjectFactory objectFactory) {
+    public SplitPackagesAuditTask(WorkerExecutor workerExecutor, ObjectFactory objectFactory, ProjectLayout projectLayout) {
         this.workerExecutor = workerExecutor;
         this.srcDirs = objectFactory.setProperty(File.class);
         this.ignoreClasses = objectFactory.setProperty(String.class);
         this.markerFile = objectFactory.fileProperty();
-
-        this.markerFile.set(getProject().getLayout().getBuildDirectory().file("markers/" + this.getName() + ".marker"));
+        this.markerFile.set(projectLayout.getBuildDirectory().file("markers/" + this.getName() + ".marker"));
     }
 
     @TaskAction
     public void auditSplitPackages() {
         workerExecutor.noIsolation().submit(SplitPackagesAuditAction.class, params -> {
-            params.getProjectPath().set(getProject().getPath());
-            params.getProjectBuildDirs().set(getProjectBuildDirs());
+            params.getProjectPath().set(projectPath(getPath()));
+            params.getProjectBuildDirs().set(projectBuildDirs);
             params.getClasspath().from(classpath);
             params.getSrcDirs().set(srcDirs);
             params.getIgnoreClasses().set(ignoreClasses);
@@ -87,15 +89,6 @@ public class SplitPackagesAuditTask extends DefaultTask {
         });
     }
 
-    private Map<File, String> getProjectBuildDirs() {
-        // while this is done in every project, it should be cheap to calculate
-        Map<File, String> buildDirs = new HashMap<>();
-        for (Project project : getProject().getRootProject().getAllprojects()) {
-            buildDirs.put(project.getBuildDir(), project.getPath());
-        }
-        return buildDirs;
-    }
-
     @CompileClasspath
     public FileCollection getClasspath() {
         return classpath.filter(File::exists);
@@ -131,6 +124,10 @@ public class SplitPackagesAuditTask extends DefaultTask {
         return markerFile;
     }
 
+    public void setProjectBuildDirs(Map<File, String> projectBuildDirs) {
+        this.projectBuildDirs = projectBuildDirs;
+    }
+
     public abstract static class SplitPackagesAuditAction implements WorkAction<Parameters> {
         @Override
         public void execute() {

+ 20 - 8
build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/precommit/TestingConventionsPrecommitPlugin.java

@@ -10,22 +10,34 @@ package org.elasticsearch.gradle.internal.precommit;
 
 import org.elasticsearch.gradle.internal.InternalPlugin;
 import org.elasticsearch.gradle.internal.conventions.precommit.PrecommitPlugin;
+import org.elasticsearch.gradle.util.GradleUtils;
+import org.gradle.api.NamedDomainObjectContainer;
 import org.gradle.api.Project;
 import org.gradle.api.Task;
+import org.gradle.api.tasks.SourceSetContainer;
 import org.gradle.api.tasks.TaskProvider;
+import org.gradle.api.tasks.testing.Test;
 
 public class TestingConventionsPrecommitPlugin extends PrecommitPlugin implements InternalPlugin {
     @Override
     public TaskProvider<? extends Task> createTask(Project project) {
         TaskProvider<TestingConventionsTasks> testingConventions = project.getTasks()
-            .register("testingConventions", TestingConventionsTasks.class);
-        testingConventions.configure(t -> {
-            TestingConventionRule testsRule = t.getNaming().maybeCreate("Tests");
-            testsRule.baseClass("org.apache.lucene.tests.util.LuceneTestCase");
-            TestingConventionRule itRule = t.getNaming().maybeCreate("IT");
-            itRule.baseClass("org.elasticsearch.test.ESIntegTestCase");
-            itRule.baseClass("org.elasticsearch.test.rest.ESRestTestCase");
-        });
+            .register("testingConventions", TestingConventionsTasks.class, t -> {
+                NamedDomainObjectContainer<TestingConventionRule> namings = project.container(TestingConventionRule.class);
+                TestingConventionRule testsRule = namings.maybeCreate("Tests");
+                testsRule.baseClass("org.apache.lucene.tests.util.LuceneTestCase");
+                TestingConventionRule itRule = namings.maybeCreate("IT");
+                itRule.baseClass("org.elasticsearch.test.ESIntegTestCase");
+                itRule.baseClass("org.elasticsearch.test.rest.ESRestTestCase");
+
+                t.setNaming(namings);
+                t.setTestTasks(project.getTasks().withType(Test.class).matching(test -> test.isEnabled()));
+
+                SourceSetContainer javaSourceSets = GradleUtils.getJavaSourceSets(project);
+                t.setSourceSets(javaSourceSets);
+                // Run only after everything is compiled
+                javaSourceSets.all(sourceSet -> t.dependsOn(sourceSet.getOutput().getClassesDirs()));
+            });
         return testingConventions;
     }
 }

+ 23 - 19
build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/precommit/TestingConventionsTasks.java

@@ -9,8 +9,6 @@ package org.elasticsearch.gradle.internal.precommit;
 
 import groovy.lang.Closure;
 
-import org.elasticsearch.gradle.internal.conventions.util.Util;
-import org.elasticsearch.gradle.util.GradleUtils;
 import org.gradle.api.DefaultTask;
 import org.gradle.api.NamedDomainObjectContainer;
 import org.gradle.api.Task;
@@ -23,6 +21,7 @@ import org.gradle.api.tasks.OutputFile;
 import org.gradle.api.tasks.SourceSet;
 import org.gradle.api.tasks.SourceSetContainer;
 import org.gradle.api.tasks.TaskAction;
+import org.gradle.api.tasks.TaskCollection;
 import org.gradle.api.tasks.testing.Test;
 
 import java.io.File;
@@ -56,32 +55,27 @@ public class TestingConventionsTasks extends DefaultTask {
 
     private Map<String, File> testClassNames;
 
-    private final NamedDomainObjectContainer<TestingConventionRule> naming;
+    private NamedDomainObjectContainer<TestingConventionRule> naming;
     private ProjectLayout projectLayout;
 
+    private SourceSetContainer sourceSets;
+    private TaskCollection<Test> testTasks;
+
     @Inject
     public TestingConventionsTasks(ProjectLayout projectLayout) {
         this.projectLayout = projectLayout;
         setDescription("Tests various testing conventions");
-        // Run only after everything is compiled
-        GradleUtils.getJavaSourceSets(getProject()).all(sourceSet -> dependsOn(sourceSet.getOutput().getClassesDirs()));
-        naming = getProject().container(TestingConventionRule.class);
     }
 
     @Input
     public Map<String, Set<File>> getClassFilesPerEnabledTask() {
-        return getProject().getTasks()
-            .withType(Test.class)
-            .stream()
-            .filter(Task::getEnabled)
-            .collect(Collectors.toMap(Task::getPath, task -> task.getCandidateClassFiles().getFiles()));
+        return testTasks.stream().collect(Collectors.toMap(Task::getPath, task -> task.getCandidateClassFiles().getFiles()));
     }
 
     @Input
     public Map<String, File> getTestClassNames() {
         if (testClassNames == null) {
-            testClassNames = Util.getJavaTestSourceSet(getProject())
-                .get()
+            testClassNames = sourceSets.getByName(SourceSet.TEST_SOURCE_SET_NAME)
                 .getOutput()
                 .getClassesDirs()
                 .getFiles()
@@ -100,7 +94,7 @@ public class TestingConventionsTasks extends DefaultTask {
 
     @OutputFile
     public File getSuccessMarker() {
-        return new File(getProject().getBuildDir(), "markers/" + getName());
+        return new File(projectLayout.getBuildDirectory().getAsFile().get(), "markers/" + getName());
     }
 
     public void naming(Closure<?> action) {
@@ -109,12 +103,11 @@ public class TestingConventionsTasks extends DefaultTask {
 
     @Input
     public Set<String> getMainClassNamedLikeTests() {
-        SourceSetContainer javaSourceSets = GradleUtils.getJavaSourceSets(getProject());
-        if (javaSourceSets.findByName(SourceSet.MAIN_SOURCE_SET_NAME) == null) {
+        if (sourceSets.findByName(SourceSet.MAIN_SOURCE_SET_NAME) == null) {
             // some test projects don't have a main source set
             return Collections.emptySet();
         }
-        return javaSourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME)
+        return sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME)
             .getOutput()
             .getClassesDirs()
             .getAsFileTree()
@@ -156,7 +149,7 @@ public class TestingConventionsTasks extends DefaultTask {
 
             final Map<String, Set<File>> classFilesPerTask = getClassFilesPerEnabledTask();
 
-            final Set<File> testSourceSetFiles = Util.getJavaTestSourceSet(getProject()).get().getRuntimeClasspath().getFiles();
+            final Set<File> testSourceSetFiles = sourceSets.getByName(SourceSet.TEST_SOURCE_SET_NAME).getRuntimeClasspath().getFiles();
             final Map<String, Set<Class<?>>> testClassesPerTask = classFilesPerTask.entrySet()
                 .stream()
                 .filter(entry -> testSourceSetFiles.containsAll(entry.getValue()))
@@ -347,7 +340,7 @@ public class TestingConventionsTasks extends DefaultTask {
 
     @Classpath
     public FileCollection getTestsClassPath() {
-        return Util.getJavaTestSourceSet(getProject()).get().getRuntimeClasspath();
+        return sourceSets.getByName(SourceSet.TEST_SOURCE_SET_NAME).getRuntimeClasspath();
     }
 
     private Map<String, File> walkPathAndLoadClasses(File testRoot) {
@@ -417,4 +410,15 @@ public class TestingConventionsTasks extends DefaultTask {
         }
     }
 
+    public void setTestTasks(TaskCollection<Test> testTasks) {
+        this.testTasks = testTasks;
+    }
+
+    public void setSourceSets(SourceSetContainer sourceSets) {
+        this.sourceSets = sourceSets;
+    }
+
+    public void setNaming(NamedDomainObjectContainer<TestingConventionRule> naming) {
+        this.naming = naming;
+    }
 }

+ 22 - 2
build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/precommit/ThirdPartyAuditPrecommitPlugin.java

@@ -46,13 +46,33 @@ public class ThirdPartyAuditPrecommitPlugin extends PrecommitPlugin implements I
             t.copy("forbidden/third-party-audit.txt");
         });
         TaskProvider<ThirdPartyAuditTask> audit = project.getTasks().register("thirdPartyAudit", ThirdPartyAuditTask.class);
-        audit.configure(t -> {
+        // usually only one task is created. but this construct makes our integTests easier to setup
+        project.getTasks().withType(ThirdPartyAuditTask.class).configureEach(t -> {
+            Configuration runtimeConfiguration = getRuntimeConfiguration(project);
+            Configuration compileOnly = project.getConfigurations()
+                .getByName(CompileOnlyResolvePlugin.RESOLVEABLE_COMPILE_ONLY_CONFIGURATION_NAME);
+            t.setClasspath(runtimeConfiguration.plus(compileOnly));
+            t.setJarsToScan(runtimeConfiguration.fileCollection(dep -> {
+                // These are SelfResolvingDependency, and some of them backed by file collections, like the Gradle API files,
+                // or dependencies added as `files(...)`, we can't be sure if those are third party or not.
+                // err on the side of scanning these to make sure we don't miss anything
+                return dep.getGroup() != null && dep.getGroup().startsWith("org.elasticsearch") == false;
+            }));
             t.dependsOn(resourcesTask);
             t.setJavaHome(Jvm.current().getJavaHome().getPath());
             t.getTargetCompatibility().set(project.provider(BuildParams::getRuntimeJavaVersion));
             t.setSignatureFile(resourcesDir.resolve("forbidden/third-party-audit.txt").toFile());
+            t.setJdkJarHellClasspath(jdkJarHellConfig);
+            t.setForbiddenAPIsClasspath(project.getConfigurations().getByName("forbiddenApisCliJar").plus(compileOnly));
         });
-        project.getTasks().withType(ThirdPartyAuditTask.class).configureEach(t -> t.setJdkJarHellClasspath(jdkJarHellConfig));
         return audit;
     }
+
+    private Configuration getRuntimeConfiguration(Project project) {
+        Configuration runtime = project.getConfigurations().findByName("runtimeClasspath");
+        if (runtime == null) {
+            return project.getConfigurations().getByName("testCompileClasspath");
+        }
+        return runtime;
+    }
 }

+ 75 - 55
build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/precommit/ThirdPartyAuditTask.java

@@ -11,15 +11,15 @@ import de.thetaphi.forbiddenapis.cli.CliMain;
 
 import org.apache.commons.io.output.NullOutputStream;
 import org.elasticsearch.gradle.OS;
-import org.elasticsearch.gradle.dependencies.CompileOnlyResolvePlugin;
 import org.gradle.api.DefaultTask;
 import org.gradle.api.JavaVersion;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.file.ArchiveOperations;
 import org.gradle.api.file.FileCollection;
+import org.gradle.api.file.FileSystemOperations;
 import org.gradle.api.file.FileTree;
+import org.gradle.api.file.ProjectLayout;
+import org.gradle.api.model.ObjectFactory;
 import org.gradle.api.provider.Property;
-import org.gradle.api.specs.Spec;
 import org.gradle.api.tasks.CacheableTask;
 import org.gradle.api.tasks.Classpath;
 import org.gradle.api.tasks.CompileClasspath;
@@ -33,6 +33,7 @@ import org.gradle.api.tasks.PathSensitive;
 import org.gradle.api.tasks.PathSensitivity;
 import org.gradle.api.tasks.SkipWhenEmpty;
 import org.gradle.api.tasks.TaskAction;
+import org.gradle.process.ExecOperations;
 import org.gradle.process.ExecResult;
 
 import java.io.ByteArrayOutputStream;
@@ -51,6 +52,8 @@ import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 import java.util.stream.Stream;
 
+import javax.inject.Inject;
+
 @CacheableTask
 public class ThirdPartyAuditTask extends DefaultTask {
 
@@ -79,7 +82,36 @@ public class ThirdPartyAuditTask extends DefaultTask {
 
     private FileCollection jdkJarHellClasspath;
 
-    private final Property<JavaVersion> targetCompatibility = getProject().getObjects().property(JavaVersion.class);
+    private final Property<JavaVersion> targetCompatibility;
+
+    private final ArchiveOperations archiveOperations;
+
+    private final ExecOperations execOperations;
+
+    private final FileSystemOperations fileSystemOperations;
+
+    private final ProjectLayout projectLayout;
+
+    private FileCollection classpath;
+
+    private FileCollection jarsToScan;
+
+    private FileCollection forbiddenApisClasspath;
+
+    @Inject
+    public ThirdPartyAuditTask(
+        ArchiveOperations archiveOperations,
+        ExecOperations execOperations,
+        FileSystemOperations fileSystemOperations,
+        ProjectLayout projectLayout,
+        ObjectFactory objectFactory
+    ) {
+        this.archiveOperations = archiveOperations;
+        this.execOperations = execOperations;
+        this.fileSystemOperations = fileSystemOperations;
+        this.projectLayout = projectLayout;
+        this.targetCompatibility = objectFactory.property(JavaVersion.class);
+    }
 
     @Input
     public Property<JavaVersion> getTargetCompatibility() {
@@ -88,8 +120,12 @@ public class ThirdPartyAuditTask extends DefaultTask {
 
     @InputFiles
     @PathSensitive(PathSensitivity.NAME_ONLY)
-    public Configuration getForbiddenAPIsConfiguration() {
-        return getProject().getConfigurations().getByName("forbiddenApisCliJar");
+    public FileCollection getForbiddenAPIsClasspath() {
+        return forbiddenApisClasspath;
+    }
+
+    public void setForbiddenAPIsClasspath(FileCollection forbiddenApisClasspath) {
+        this.forbiddenApisClasspath = forbiddenApisClasspath;
     }
 
     @InputFile
@@ -114,12 +150,12 @@ public class ThirdPartyAuditTask extends DefaultTask {
 
     @Internal
     public File getJarExpandDir() {
-        return new File(new File(getProject().getBuildDir(), "precommit/thirdPartyAudit"), getName());
+        return projectLayout.getBuildDirectory().dir("precommit/thirdPartyAudit").get().dir(getName()).getAsFile();
     }
 
     @OutputFile
     public File getSuccessMarker() {
-        return new File(getProject().getBuildDir(), "markers/" + getName());
+        return projectLayout.getBuildDirectory().dir("precommit/thirdPartyAudit").get().dir("markers/").file(getName()).getAsFile();
     }
 
     // We use compile classpath normalization here because class implementation changes are irrelevant for the purposes of jdk jar hell.
@@ -171,31 +207,15 @@ public class ThirdPartyAuditTask extends DefaultTask {
 
     @Classpath
     @SkipWhenEmpty
-    public Set<File> getJarsToScan() {
-        // These are SelfResolvingDependency, and some of them backed by file collections, like the Gradle API files,
-        // or dependencies added as `files(...)`, we can't be sure if those are third party or not.
-        // err on the side of scanning these to make sure we don't miss anything
-        Spec<Dependency> reallyThirdParty = dep -> dep.getGroup() != null && dep.getGroup().startsWith("org.elasticsearch") == false;
-        Set<File> jars = getRuntimeConfiguration().getResolvedConfiguration().getFiles(reallyThirdParty);
-        Set<File> compileOnlyConfiguration = getProject().getConfigurations()
-            .getByName(CompileOnlyResolvePlugin.RESOLVEABLE_COMPILE_ONLY_CONFIGURATION_NAME)
-            .getResolvedConfiguration()
-            .getFiles(reallyThirdParty);
-        // don't scan provided dependencies that we already scanned, e.x. don't scan cores dependencies for every plugin
-        if (compileOnlyConfiguration != null) {
-            jars.removeAll(compileOnlyConfiguration);
-        }
-        return jars;
+    public FileCollection getJarsToScan() {
+        return jarsToScan;
     }
 
     @TaskAction
     public void runThirdPartyAudit() throws IOException {
-        Set<File> jars = getJarsToScan();
-
-        extractJars(jars);
-
+        Set<File> jars = jarsToScan.getFiles();
+        extractJars(jars, getJarExpandDir());
         final String forbiddenApisOutput = runForbiddenAPIsCli();
-
         final Set<String> missingClasses = new TreeSet<>();
         Matcher missingMatcher = MISSING_CLASS_PATTERN.matcher(forbiddenApisOutput);
         while (missingMatcher.find()) {
@@ -248,7 +268,11 @@ public class ThirdPartyAuditTask extends DefaultTask {
 
         assertNoJarHell(jdkJarHellClasses);
 
-        // Mark successful third party audit check
+        success();
+    }
+
+    // Mark successful third party audit check
+    private void success() throws IOException {
         getSuccessMarker().getParentFile().mkdirs();
         Files.write(getSuccessMarker().toPath(), new byte[] {});
     }
@@ -261,14 +285,18 @@ public class ThirdPartyAuditTask extends DefaultTask {
         throw new IllegalArgumentException("Audit of third party dependencies is not configured correctly");
     }
 
-    private void extractJars(Set<File> jars) {
-        File jarExpandDir = getJarExpandDir();
+    /**
+     * Ideally we would do unpacking already via artifact transform and keep unpacked jars across builds.
+     * At the moment transform target folder is not configurable and forbidden CLI only takes one common
+     * directory as input which makes it incompatible with gradle artifact transforms as we use them today.
+     * */
+    private void extractJars(Set<File> jars, File jarExpandDir) {
         // We need to clean up to make sure old dependencies don't linger
-        getProject().delete(jarExpandDir);
+        fileSystemOperations.delete(d -> d.delete(jarExpandDir));
 
         jars.forEach(jar -> {
-            FileTree jarFiles = getProject().zipTree(jar);
-            getProject().copy(spec -> {
+            FileTree jarFiles = archiveOperations.zipTree(jar);
+            fileSystemOperations.copy(spec -> {
                 spec.from(jarFiles);
                 spec.into(jarExpandDir);
                 // exclude classes from multi release jars
@@ -287,8 +315,8 @@ public class ThirdPartyAuditTask extends DefaultTask {
             IntStream.rangeClosed(
                 Integer.parseInt(JavaVersion.VERSION_1_9.getMajorVersion()),
                 Integer.parseInt(targetCompatibility.get().getMajorVersion())
-            ).forEach(majorVersion -> getProject().copy(spec -> {
-                spec.from(getProject().zipTree(jar));
+            ).forEach(majorVersion -> fileSystemOperations.copy(spec -> {
+                spec.from(archiveOperations.zipTree(jar));
                 spec.into(jarExpandDir);
                 String metaInfPrefix = "META-INF/versions/" + majorVersion;
                 spec.include(metaInfPrefix + "/**");
@@ -325,15 +353,11 @@ public class ThirdPartyAuditTask extends DefaultTask {
 
     private String runForbiddenAPIsCli() throws IOException {
         ByteArrayOutputStream errorOut = new ByteArrayOutputStream();
-        ExecResult result = getProject().javaexec(spec -> {
+        ExecResult result = execOperations.javaexec(spec -> {
             if (javaHome != null) {
                 spec.setExecutable(javaHome + "/bin/java");
             }
-            spec.classpath(
-                getForbiddenAPIsConfiguration(),
-                getRuntimeConfiguration(),
-                getProject().getConfigurations().getByName(CompileOnlyResolvePlugin.RESOLVEABLE_COMPILE_ONLY_CONFIGURATION_NAME)
-            );
+            spec.classpath(forbiddenApisClasspath, classpath);
             spec.jvmArgs("-Xmx1g");
             spec.getMainClass().set("de.thetaphi.forbiddenapis.cli.CliMain");
             spec.args("-f", getSignatureFile().getAbsolutePath(), "-d", getJarExpandDir(), "--allowmissingclasses");
@@ -358,12 +382,8 @@ public class ThirdPartyAuditTask extends DefaultTask {
 
     private Set<String> runJdkJarHellCheck() throws IOException {
         ByteArrayOutputStream standardOut = new ByteArrayOutputStream();
-        ExecResult execResult = getProject().javaexec(spec -> {
-            spec.classpath(
-                jdkJarHellClasspath,
-                getRuntimeConfiguration(),
-                getProject().getConfigurations().getByName(CompileOnlyResolvePlugin.RESOLVEABLE_COMPILE_ONLY_CONFIGURATION_NAME)
-            );
+        ExecResult execResult = execOperations.javaexec(spec -> {
+            spec.classpath(jdkJarHellClasspath, classpath);
 
             spec.getMainClass().set(JDK_JAR_HELL_MAIN_CLASS);
             spec.args(getJarExpandDir());
@@ -383,11 +403,11 @@ public class ThirdPartyAuditTask extends DefaultTask {
         return new TreeSet<>(Arrays.asList(jdkJarHellCheckList.split("\\r?\\n")));
     }
 
-    private Configuration getRuntimeConfiguration() {
-        Configuration runtime = getProject().getConfigurations().findByName("runtimeClasspath");
-        if (runtime == null) {
-            return getProject().getConfigurations().getByName("testCompileClasspath");
-        }
-        return runtime;
+    public void setClasspath(FileCollection classpath) {
+        this.classpath = classpath;
+    }
+
+    public void setJarsToScan(FileCollection jarsToScan) {
+        this.jarsToScan = jarsToScan;
     }
 }

+ 26 - 7
build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/test/DistroTestPlugin.java

@@ -25,6 +25,7 @@ import org.elasticsearch.gradle.internal.docker.DockerSupportService;
 import org.elasticsearch.gradle.internal.info.BuildParams;
 import org.elasticsearch.gradle.internal.vagrant.VagrantBasePlugin;
 import org.elasticsearch.gradle.internal.vagrant.VagrantExtension;
+import org.elasticsearch.gradle.internal.vagrant.VagrantMachine;
 import org.elasticsearch.gradle.test.SystemPropertyCommandLineArgumentProvider;
 import org.elasticsearch.gradle.util.GradleUtils;
 import org.gradle.api.Action;
@@ -41,6 +42,7 @@ import org.gradle.api.specs.Specs;
 import org.gradle.api.tasks.Copy;
 import org.gradle.api.tasks.TaskProvider;
 import org.gradle.api.tasks.testing.Test;
+import org.gradle.initialization.layout.BuildLayout;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -52,6 +54,8 @@ import java.util.Map;
 import java.util.function.Supplier;
 import java.util.stream.Stream;
 
+import javax.inject.Inject;
+
 import static org.elasticsearch.gradle.distribution.ElasticsearchDistributionTypes.ARCHIVE;
 import static org.elasticsearch.gradle.internal.distribution.InternalElasticsearchDistributionTypes.ALL_INTERNAL;
 import static org.elasticsearch.gradle.internal.distribution.InternalElasticsearchDistributionTypes.DEB;
@@ -80,6 +84,13 @@ public class DistroTestPlugin implements Plugin<Project> {
     private static final String BWC_DISTRIBUTION_SYSPROP = "tests.bwc-distribution";
     private static final String EXAMPLE_PLUGIN_SYSPROP = "tests.example-plugin";
 
+    private final File rootDir;
+
+    @Inject
+    public DistroTestPlugin(BuildLayout buildLayout) {
+        this.rootDir = buildLayout.getRootDirectory();
+    }
+
     @Override
     public void apply(Project project) {
         project.getRootProject().getPluginManager().apply(DockerSupportPlugin.class);
@@ -172,7 +183,7 @@ public class DistroTestPlugin implements Plugin<Project> {
             vmProject.getPluginManager().apply(VagrantBasePlugin.class);
             TaskProvider<Copy> gradleJdk = isWindows(vmProject) ? windowsGradleJdk : linuxGradleJdk;
             TaskProvider<Copy> systemJdk = isWindows(vmProject) ? windowsSystemJdk : linuxSystemJdk;
-            configureVM(vmProject, gradleJdk, systemJdk);
+            configureVM(vmProject, rootDir, gradleJdk, systemJdk);
             List<Object> vmDependencies = Arrays.asList(
                 gradleJdk,
                 systemJdk,
@@ -277,16 +288,21 @@ public class DistroTestPlugin implements Plugin<Project> {
         return copyTask;
     }
 
-    private static void configureVM(Project project, TaskProvider<Copy> gradleJdkProvider, TaskProvider<Copy> systemJdkProvider) {
+    private static void configureVM(
+        Project project,
+        File rootDir,
+        TaskProvider<Copy> gradleJdkProvider,
+        TaskProvider<Copy> systemJdkProvider
+    ) {
         String box = project.getName();
 
         // setup VM used by these tests
         VagrantExtension vagrant = project.getExtensions().getByType(VagrantExtension.class);
         vagrant.setBox(box);
 
-        vagrant.vmEnv("SYSTEM_JAVA_HOME", convertPath(project, vagrant, systemJdkProvider, "", ""));
+        vagrant.vmEnv("SYSTEM_JAVA_HOME", convertPath(rootDir, vagrant, systemJdkProvider, "", ""));
         // set java home for gradle to use. package tests will overwrite/remove this for each test case
-        vagrant.vmEnv("JAVA_HOME", convertPath(project, vagrant, gradleJdkProvider, "", ""));
+        vagrant.vmEnv("JAVA_HOME", convertPath(rootDir, vagrant, gradleJdkProvider, "", ""));
         if (System.getenv("JENKINS_URL") != null) {
             Stream.of("JOB_NAME", "JENKINS_URL", "BUILD_NUMBER", "BUILD_URL").forEach(name -> vagrant.vmEnv(name, System.getenv(name)));
         }
@@ -294,7 +310,7 @@ public class DistroTestPlugin implements Plugin<Project> {
     }
 
     private static Object convertPath(
-        Project project,
+        File rootDirectory,
         VagrantExtension vagrant,
         TaskProvider<Copy> jdkProvider,
         String additionaLinux,
@@ -303,9 +319,9 @@ public class DistroTestPlugin implements Plugin<Project> {
         return Util.toStringable(() -> {
             String hostPath = jdkProvider.get().getDestinationDir().toString();
             if (vagrant.isWindowsVM()) {
-                return convertWindowsPath(project, hostPath) + additionalWindows;
+                return convertWindowsPath(rootDirectory, hostPath) + additionalWindows;
             } else {
-                return convertLinuxPath(project, hostPath) + additionaLinux;
+                return convertLinuxPath(rootDirectory, hostPath) + additionaLinux;
             }
         });
     }
@@ -336,6 +352,9 @@ public class DistroTestPlugin implements Plugin<Project> {
                 t.extraArg("-D'" + IN_VM_SYSPROP + "'");
                 t.dependsOn(depsTasks.get(destructiveTaskName));
                 t.dependsOn(additionalDeps);
+                t.setLogLevel(project.getGradle().getStartParameter().getLogLevel().toString());
+                t.setExtension(project.getExtensions().findByType(VagrantExtension.class));
+                t.setService(project.getExtensions().getByType(VagrantMachine.class));
             });
             configure.execute(vmTask);
         }

+ 28 - 3
build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/test/GradleDistroTestTask.java

@@ -9,13 +9,17 @@
 package org.elasticsearch.gradle.internal.test;
 
 import org.elasticsearch.gradle.internal.vagrant.VagrantShellTask;
+import org.gradle.api.file.ProjectLayout;
 import org.gradle.api.tasks.Input;
 import org.gradle.api.tasks.options.Option;
+import org.gradle.initialization.layout.BuildLayout;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
+import javax.inject.Inject;
+
 import static org.elasticsearch.gradle.internal.vagrant.VagrantMachine.convertLinuxPath;
 import static org.elasticsearch.gradle.internal.vagrant.VagrantMachine.convertWindowsPath;
 
@@ -26,8 +30,21 @@ public class GradleDistroTestTask extends VagrantShellTask {
 
     private String taskName;
     private String testClass;
+
     private List<String> extraArgs = new ArrayList<>();
 
+    private final ProjectLayout projectLayout;
+    private final BuildLayout buildLayout;
+
+    private String logLevel;
+
+    @Inject
+    public GradleDistroTestTask(BuildLayout buildLayout, ProjectLayout projectLayout) {
+        super(buildLayout);
+        this.buildLayout = buildLayout;
+        this.projectLayout = projectLayout;
+    }
+
     public void setTaskName(String taskName) {
         this.taskName = taskName;
     }
@@ -51,6 +68,10 @@ public class GradleDistroTestTask extends VagrantShellTask {
         this.extraArgs.add(arg);
     }
 
+    public void setLogLevel(String logLevel) {
+        this.logLevel = logLevel;
+    }
+
     @Override
     protected List<String> getWindowsScript() {
         return getScript(true);
@@ -62,15 +83,19 @@ public class GradleDistroTestTask extends VagrantShellTask {
     }
 
     private List<String> getScript(boolean isWindows) {
-        String cacheDir = getProject().getBuildDir() + "/gradle-cache";
+        String cacheDir = projectLayout.getBuildDirectory().dir("gradle-cache").get().getAsFile().getAbsolutePath();
         StringBuilder line = new StringBuilder();
         line.append(isWindows ? "& .\\gradlew " : "./gradlew ");
         line.append(taskName);
         line.append(" --project-cache-dir ");
-        line.append(isWindows ? convertWindowsPath(getProject(), cacheDir) : convertLinuxPath(getProject(), cacheDir));
+        line.append(
+            isWindows
+                ? convertWindowsPath(buildLayout.getRootDirectory(), cacheDir)
+                : convertLinuxPath(buildLayout.getRootDirectory(), cacheDir)
+        );
         line.append(" -S");
         line.append(" --parallel");
-        line.append(" -D'org.gradle.logging.level'=" + getProject().getGradle().getStartParameter().getLogLevel());
+        line.append(" -D'org.gradle.logging.level'=" + logLevel);
         if (testClass != null) {
             line.append(" --tests=");
             line.append(testClass);

+ 4 - 6
build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/vagrant/VagrantMachine.java

@@ -14,7 +14,6 @@ import org.elasticsearch.gradle.ReaperService;
 import org.elasticsearch.gradle.internal.LoggingOutputStream;
 import org.elasticsearch.gradle.internal.conventions.util.Util;
 import org.gradle.api.Action;
-import org.gradle.api.Project;
 import org.gradle.api.provider.Provider;
 import org.gradle.internal.logging.progress.ProgressLogger;
 import org.gradle.internal.logging.progress.ProgressLoggerFactory;
@@ -136,13 +135,12 @@ public class VagrantMachine {
         }
     }
 
-    // convert the given path from an elasticsearch repo path to a VM path
-    public static String convertLinuxPath(Project project, String path) {
-        return "/elasticsearch/" + project.getRootDir().toPath().relativize(Paths.get(path));
+    public static String convertLinuxPath(File rootDir, String path) {
+        return "/elasticsearch/" + rootDir.toPath().relativize(Paths.get(path));
     }
 
-    public static String convertWindowsPath(Project project, String path) {
-        return "C:\\elasticsearch\\" + project.getRootDir().toPath().relativize(Paths.get(path)).toString().replace('/', '\\');
+    public static String convertWindowsPath(File rootDir, String path) {
+        return "C:\\elasticsearch\\" + rootDir.toPath().relativize(Paths.get(path)).toString().replace('/', '\\');
     }
 
     public static class VagrantExecSpec {

+ 17 - 12
build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/vagrant/VagrantShellTask.java

@@ -11,6 +11,7 @@ package org.elasticsearch.gradle.internal.vagrant;
 import org.gradle.api.DefaultTask;
 import org.gradle.api.tasks.Input;
 import org.gradle.api.tasks.TaskAction;
+import org.gradle.initialization.layout.BuildLayout;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -28,16 +29,14 @@ import static org.elasticsearch.gradle.internal.vagrant.VagrantMachine.convertWi
  */
 public abstract class VagrantShellTask extends DefaultTask {
 
-    private final VagrantExtension extension;
-    private final VagrantMachine service;
+    private VagrantExtension extension;
+    private VagrantMachine service;
+
     private UnaryOperator<String> progressHandler = UnaryOperator.identity();
+    private BuildLayout buildLayout;
 
-    public VagrantShellTask() {
-        extension = getProject().getExtensions().findByType(VagrantExtension.class);
-        if (extension == null) {
-            throw new IllegalStateException("elasticsearch.vagrant-base must be applied to create " + getClass().getName());
-        }
-        service = getProject().getExtensions().getByType(VagrantMachine.class);
+    public VagrantShellTask(BuildLayout buildLayout) {
+        this.buildLayout = buildLayout;
     }
 
     @Input
@@ -55,16 +54,22 @@ public abstract class VagrantShellTask extends DefaultTask {
         this.progressHandler = progressHandler;
     }
 
+    public void setExtension(VagrantExtension extension) {
+        this.extension = extension;
+    }
+
+    public void setService(VagrantMachine service) {
+        this.service = service;
+    }
+
     @TaskAction
     public void runScript() {
-        String rootDir = getProject().getRootDir().toString();
         if (extension.isWindowsVM()) {
             service.execute(spec -> {
                 spec.setCommand("winrm");
-
                 List<String> script = new ArrayList<>();
                 script.add("try {");
-                script.add("cd " + convertWindowsPath(getProject(), rootDir));
+                script.add("cd " + convertWindowsPath(buildLayout.getRootDirectory(), buildLayout.getRootDirectory().toString()));
                 extension.getVmEnv().forEach((k, v) -> script.add("$Env:" + k + " = \"" + v + "\""));
                 script.addAll(getWindowsScript().stream().map(s -> "    " + s).collect(Collectors.toList()));
                 script.addAll(
@@ -88,7 +93,7 @@ public abstract class VagrantShellTask extends DefaultTask {
                     List<String> script = new ArrayList<>();
                     script.add("sudo bash -c '"); // start inline bash script
                     script.add("pwd");
-                    script.add("cd " + convertLinuxPath(getProject(), rootDir));
+                    script.add("cd " + convertLinuxPath(buildLayout.getRootDirectory(), buildLayout.getRootDirectory().toString()));
                     extension.getVmEnv().forEach((k, v) -> script.add("export " + k + "=" + v));
                     script.addAll(getLinuxScript());
                     script.add("'"); // end inline bash script

+ 4 - 0
build-tools/src/main/java/org/elasticsearch/gradle/util/GradleUtils.java

@@ -215,4 +215,8 @@ public abstract class GradleUtils {
             }
         });
     }
+
+    public static String projectPath(String taskPath) {
+        return taskPath.lastIndexOf(':') == 0 ? ":" : taskPath.substring(0, taskPath.lastIndexOf(':'));
+    }
 }