|  | @@ -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;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  }
 |