diff --git a/.gitignore b/.gitignore
index 14314b412..29ce75ec3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,3 +24,11 @@
**/src/site/apt/*.txt
**/.externalToolBuilders/**/*
**/.checkstyle
+
+# Gradle plugin
+apache-rat-gradle-plugin/build/**
+apache-rat-gradle-plugin/.idea
+apache-rat-gradle-plugin/.gradle
+# Gradle wrapper jar is downloaded and its checksum validated by apache-rat-gradle-plugin/gradle/gradlew-include.sh
+apache-rat-gradle-plugin/gradle/wrapper/*.jar
+apache-rat-gradle-plugin/gradle/wrapper/*.sha256
diff --git a/apache-rat-core/src/main/resources/org/apache/rat/html.xsl b/apache-rat-core/src/main/resources/org/apache/rat/html.xsl
new file mode 100644
index 000000000..967ff522b
--- /dev/null
+++ b/apache-rat-core/src/main/resources/org/apache/rat/html.xsl
@@ -0,0 +1,206 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Rat Report
+
This HTML version (yes, it is!) is generated from the RAT xml reports using Saxon9B. All the outputs required are displayed below, similar to the .txt version.
+ This is obviously a work in progress; and a prettier, easier to read and manage version will be available soon
+
+
+
+Table 1: A snapshot summary of this rat report.
+
+
+
Notes:
+
Binaries:
+
Archives:
+
Standards:
+
+
+
Apache Licensed:
+
Generated Documents:
+
+
+
Note: JavaDocs are generated and so license header is optional
+
Note: Generated files do not require license headers
+
+
+
+
+
Unknown Licenses - or files without a license.
+
+
+
Unknown Licenses - or files without a license.
+
+
+
+
+
+
+
Unapproved Licenses:
+
+
+
+
+
+
+
+
+
+
Archives:
+
+
+ +
+
+
+
+
+
+ Files with Apache License headers will be marked AL
+ Binary files (which do not require AL headers) will be marked B
+ Compressed archives will be marked A
+ Notices, licenses etc will be marked N
+
+
+
+
+ !
+
+
+
+ N
+ A
+ B
+
+ !!!!!
+
+
+
+
+
+
+
+
+
All properties are inherited from {@link RatOptions}.
+ *
+ * @see RatOptions
+ */
+public interface RatExtension extends RatOptions {
+ // empty comment for checkstyle
+}
diff --git a/apache-rat-gradle-plugin/src/main/java/org/apache/rat/gradle/RatPlugin.java b/apache-rat-gradle-plugin/src/main/java/org/apache/rat/gradle/RatPlugin.java
new file mode 100644
index 000000000..b71b9453f
--- /dev/null
+++ b/apache-rat-gradle-plugin/src/main/java/org/apache/rat/gradle/RatPlugin.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rat.gradle;
+
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.UnknownTaskException;
+import org.gradle.api.tasks.TaskProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Apache RAT Gradle plugin.
+ *
+ *
Apply this plugin to your project's root project.
+ *
+ *
Configurations should be configured on the {@code rat} extension of type {@link RatExtension}.
+ *
+ *
Registers the default {@code rat} task and lets the {@code check} task depend on it, if the
+ * {@code check} task exists at the time when this RAT plugin is applied. Task-specific properties,
+ * like the report output directory and/or report-specific output files, are configured on the task
+ * and not the extension.
+ *
+ *
If multiple RAT reports with different RAT configurations are needed, register a custom task
+ * of type {@link RatTask} and configure it accordingly. All {@link RatTask} RAT tasks inherit the
+ * configurations from the {@code rat} extension.
+ */
+@SuppressWarnings({"unused", "NullableProblems"})
+public abstract class RatPlugin implements Plugin {
+ /** SLF4j logger. */
+ private static final Logger LOGGER = LoggerFactory.getLogger(RatPlugin.class);
+
+ @Override
+ public void apply(final Project project) {
+ project.getExtensions().create("rat", RatExtension.class);
+
+ TaskProvider ratTask = project.getTasks().register("rat", RatTask.class);
+ RatTask.setupRatTaskConfigurations(ratTask, project);
+
+ try {
+ project.getTasks().named("check").configure(task -> task.dependsOn("rat"));
+ } catch (UnknownTaskException ignore) {
+ // Log at level 'INFO' to not spam people's build output.
+ LOGGER.info(
+ "The project '{}' does not have a 'check' task available when the Apache RAT Gradle plugin is applied. "
+ + "If another plugin registers a 'check' task, consider changing the order of the plugins in the 'plugins' block. "
+ + "If your build script registers a 'check' task later, consider configuring with with a 'dependsOn(\"rat\")'",
+ project.getParent());
+ }
+ }
+}
diff --git a/apache-rat-gradle-plugin/src/main/java/org/apache/rat/gradle/RatTask.java b/apache-rat-gradle-plugin/src/main/java/org/apache/rat/gradle/RatTask.java
new file mode 100644
index 000000000..794e9de95
--- /dev/null
+++ b/apache-rat-gradle-plugin/src/main/java/org/apache/rat/gradle/RatTask.java
@@ -0,0 +1,229 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rat.gradle;
+
+import static java.lang.String.format;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+import javax.inject.Inject;
+
+import org.apache.rat.gradle.internal.RatWorkAction;
+import org.apache.rat.gradle.internal.RatWorkParameters;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.NamedDomainObjectProvider;
+import org.gradle.api.Project;
+import org.gradle.api.Transformer;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.dsl.DependencyHandler;
+import org.gradle.api.file.DirectoryProperty;
+import org.gradle.api.file.RegularFileProperty;
+import org.gradle.api.tasks.Internal;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.api.tasks.TaskProvider;
+import org.gradle.workers.WorkQueue;
+import org.gradle.workers.WorkerExecutor;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Apache RAT execution task.
+ *
+ *
Default property values are taken from the Gradle project's {@link RatExtension}, named {@code
+ * rat}.
+ *
+ *
Available properties are all properties available on the {@link RatTaskProperties}, which
+ * inherits its properties from {@link RatOptions}.
+ */
+public abstract class RatTask extends DefaultTask implements RatTaskProperties {
+ @SuppressWarnings("unused")
+ public RatTask() {
+ RatExtension extension = getExtension();
+
+ applyConventions(extension);
+
+ getProjectBaseDir().convention(getProject().getLayout().getProjectDirectory());
+
+ getReportOutputDirectory()
+ .convention(getProject().getLayout().getBuildDirectory().dir("reports/" + getName()));
+ getRatTxtFile().convention(getReportOutputDirectory().file("rat-report.txt"));
+ getRatXmlFile().convention(getReportOutputDirectory().file("rat-report.xml"));
+ getRatHtmlFile().convention(getReportOutputDirectory().file("rat-report.html"));
+
+ // Task outputs are produced for a particular task input.
+ // Task inputs consist of properties and files.
+ // Unfortunately, we do not have a view to the same RAT processed files
+ // _without_ fully configuring RAT.
+ // Therefore, the task outputs are never up to date, leading to
+ // unconditional task execution.
+ getOutputs().upToDateWhen(t -> false);
+
+ setGroup("verification");
+ setDescription("Run Apache Release Audit Tool (RAT)");
+ }
+
+ /**
+ * Sets up the Gradle {@link Configuration}s that allow consuming the generated report files.
+ *
+ *
+ *
{@code XmlReportElements}, consumable
+ *
{@code XmlReport}, resolvable, extends from the former
+ *
{@code AllReportsElements}, consumable
+ *
{@code AllReports}, resolvable, extends from the former
+ *
+ *
+ *
This function is always called for the {@code rat} task. When registering a custom task of
+ * type {@link RatTask}, this function can be used to set up the configurations for that task as
+ * well.
+ *
+ *
This function is not called from the constructor to ensure that the configurations are
+ * immediately available.
+ */
+ public static void setupRatTaskConfigurations(
+ final TaskProvider ratTask, final Project project) {
+ String xmlReportElements = format("%sXmlReportElements", ratTask.getName());
+ NamedDomainObjectProvider xmlReportElementsConfig =
+ project
+ .getConfigurations()
+ .register(
+ xmlReportElements,
+ c -> {
+ c.setDescription(
+ format(
+ "Consumable configuration containing the XML report file generated by the '%s' task.",
+ ratTask.getName()));
+ c.setCanBeResolved(false);
+ c.setCanBeConsumed(true);
+ c.outgoing(
+ configurationPublications ->
+ configurationPublications.artifact(
+ ratTask.flatMap(RatTask::getRatXmlFile)));
+ });
+
+ String allReportsElements = format("%sAllReportsElements", ratTask.getName());
+ NamedDomainObjectProvider allReportsElementsConfig =
+ project
+ .getConfigurations()
+ .register(
+ allReportsElements,
+ c -> {
+ c.setDescription(
+ format(
+ "Consumable configuration containing all report files generated by the '%s' task.",
+ ratTask.getName()));
+ c.setCanBeResolved(false);
+ c.setCanBeConsumed(true);
+ c.outgoing(
+ configurationPublications -> {
+ for (Transformer reportFileFunction :
+ Arrays.>asList(
+ RatTask::getRatXmlFile,
+ RatTask::getRatTxtFile,
+ RatTask::getRatHtmlFile)) {
+ configurationPublications.artifact(ratTask.flatMap(reportFileFunction));
+ }
+ });
+ });
+
+ for (Function reportFileFunction :
+ Arrays.>asList(
+ RatTask::getRatXmlFile, RatTask::getRatTxtFile, RatTask::getRatHtmlFile)) {
+ project
+ .getArtifacts()
+ .add(
+ allReportsElements,
+ project.provider(() -> reportFileFunction.apply(ratTask.get())),
+ artifact -> artifact.builtBy(ratTask));
+ }
+
+ // Add resolvable configurations, extending the consumable '*Elements' configurations.
+
+ String xmlReport = format("%sXmlReport", ratTask.getName());
+ project
+ .getConfigurations()
+ .register(
+ xmlReport,
+ c -> {
+ c.setDescription(
+ format(
+ "Resolvable configuration containing the XML report file generated by the '%s' task, extends from the '%s' configuration.",
+ ratTask.getName(), xmlReportElementsConfig.getName()));
+ c.setCanBeResolved(true);
+ c.setCanBeConsumed(false);
+ c.extendsFrom(xmlReportElementsConfig.get());
+ });
+
+ String allReports = format("%sAllReports", ratTask.getName());
+ project
+ .getConfigurations()
+ .register(
+ allReports,
+ c -> {
+ c.setDescription(
+ format(
+ "Resolvable configuration containing the all report files generated by the '%s' task, extends from the '%s' configuration.",
+ ratTask.getName(), allReportsElementsConfig.getName()));
+ c.setCanBeResolved(true);
+ c.setCanBeConsumed(false);
+ c.extendsFrom(allReportsElementsConfig.get());
+ });
+
+ // Finally, map the dependencies
+ DependencyHandler dependencies = project.getDependencies();
+ Map xmlReportDependencyNotion = new HashMap<>();
+ xmlReportDependencyNotion.put("path", project.getPath());
+ xmlReportDependencyNotion.put("configuration", xmlReportElements);
+ dependencies.add(xmlReport, dependencies.project(xmlReportDependencyNotion));
+ Map allReportsDependencyNotion = new HashMap<>();
+ allReportsDependencyNotion.put("path", project.getPath());
+ allReportsDependencyNotion.put("configuration", allReportsElements);
+ dependencies.add(allReports, dependencies.project(allReportsDependencyNotion));
+ }
+
+ private @NotNull RatExtension getExtension() {
+ return getProject().getExtensions().getByType(RatExtension.class);
+ }
+
+ @Inject
+ protected abstract WorkerExecutor getWorkerExecutor();
+
+ @Internal
+ public abstract DirectoryProperty getProjectBaseDir();
+
+ @TaskAction
+ public void check() {
+ WorkQueue workQueue = getWorkerExecutor().classLoaderIsolation();
+
+ // Execute RAT in a classloader isolation worker.
+ // Some information is set using static fields in the RAT code base.
+ // Classloader isolation prevents potential "collisions" or races in the case of multiple
+ // Gradle projects (think: modules) running RAT at the same time / within the same build.
+ workQueue.submit(RatWorkAction.class, this::applyWorkParameters);
+ // Wait for RAT execution to finish.
+ workQueue.await();
+ }
+
+ void applyWorkParameters(final RatWorkParameters parameters) {
+ parameters.getProjectBaseDir().set(getProjectBaseDir());
+ parameters.applyTaskPropertiesConventions(this);
+ parameters.applyConventions(this);
+ }
+}
diff --git a/apache-rat-gradle-plugin/src/main/java/org/apache/rat/gradle/RatTaskProperties.java b/apache-rat-gradle-plugin/src/main/java/org/apache/rat/gradle/RatTaskProperties.java
new file mode 100644
index 000000000..97c03e24d
--- /dev/null
+++ b/apache-rat-gradle-plugin/src/main/java/org/apache/rat/gradle/RatTaskProperties.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rat.gradle;
+
+import org.gradle.api.file.DirectoryProperty;
+import org.gradle.api.file.RegularFileProperty;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.OutputFile;
+
+/** RAT task-specific properties. */
+public interface RatTaskProperties extends RatOptionsTaskBase {
+
+ /**
+ * The default output directory for RAT reports. The default value is the project's build
+ * directory plus {@code reports} plus the task name, for example {@code build/reports/rat}.
+ */
+ @OutputDirectory
+ DirectoryProperty getReportOutputDirectory();
+
+ /**
+ * The RAT XML report output file. Defaults to {@code rat-report.txt} in {@link
+ * #getReportOutputDirectory()}.
+ */
+ @OutputFile
+ RegularFileProperty getRatXmlFile();
+
+ /**
+ * The RAT text report output file. Defaults to {@code rat-report.txt} in {@link
+ * #getReportOutputDirectory()}.
+ */
+ @OutputFile
+ RegularFileProperty getRatTxtFile();
+
+ /**
+ * The RAT HTML report output file. Defaults to {@code rat-report.txt} in {@link
+ * #getReportOutputDirectory()}.
+ */
+ @OutputFile
+ RegularFileProperty getRatHtmlFile();
+
+ default void applyTaskPropertiesConventions(RatTaskProperties from) {
+ getReportOutputDirectory().convention(from.getReportOutputDirectory());
+ getRatTxtFile().convention(from.getRatTxtFile());
+ getRatXmlFile().convention(from.getRatXmlFile());
+ getRatHtmlFile().convention(from.getRatHtmlFile());
+ }
+}
diff --git a/apache-rat-gradle-plugin/src/main/java/org/apache/rat/gradle/internal/RatWorkAction.java b/apache-rat-gradle-plugin/src/main/java/org/apache/rat/gradle/internal/RatWorkAction.java
new file mode 100644
index 000000000..e9fb96781
--- /dev/null
+++ b/apache-rat-gradle-plugin/src/main/java/org/apache/rat/gradle/internal/RatWorkAction.java
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rat.gradle.internal;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.OutputStreamWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+
+import org.apache.rat.OptionCollection;
+import org.apache.rat.ReportConfiguration;
+import org.apache.rat.Reporter;
+import org.apache.rat.commandline.Arg;
+import org.apache.rat.commandline.StyleSheets;
+import org.apache.rat.document.DocumentName;
+import org.apache.rat.document.FileDocument;
+import org.apache.rat.gradle.RatTask;
+import org.apache.rat.license.ILicense;
+import org.apache.rat.license.LicenseSetFactory;
+import org.apache.rat.report.claim.ClaimStatistic;
+import org.apache.rat.utils.DefaultLog;
+import org.apache.rat.walker.DirectoryWalker;
+import org.gradle.api.GradleException;
+import org.gradle.workers.WorkAction;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Actual RAT checks and report generation happens via this Gradle work action, which is called from
+ * {@link RatTask} using classloader isolation.
+ */
+public abstract class RatWorkAction implements WorkAction {
+
+ /** SLF4j logger. */
+ private static final Logger LOGGER = LoggerFactory.getLogger(RatWorkAction.class);
+
+ @Override
+ public void execute() {
+
+ deleteFiles();
+
+ DefaultLog.setInstance(new Slf4jLogBridge(LOGGER));
+
+ RatOptionsToConfiguration ratArguments = new RatOptionsToConfiguration(getParameters());
+
+ ReportConfiguration config = getConfiguration(ratArguments);
+
+ logLicenses(config.getLicenses(LicenseSetFactory.LicenseFilter.ALL));
+ try {
+ Reporter reporter = new Reporter(config);
+
+ config.setStyleSheet(RatTask.class.getResource("/org/apache/rat/plain-rat.xsl"));
+ config.setOut(getParameters().getRatTxtFile().get().getAsFile());
+ reporter.output();
+
+ config.setStyleSheet(RatTask.class.getResource("/org/apache/rat/html.xsl"));
+ config.setOut(getParameters().getRatHtmlFile().get().getAsFile());
+ reporter.output();
+
+ config.setStyleSheet(RatTask.class.getResource("/org/apache/rat/xml.xsl"));
+ config.setOut(getParameters().getRatXmlFile().get().getAsFile());
+ reporter.output();
+
+ StringWriter summaryWriter = new StringWriter();
+ reporter.writeSummary(summaryWriter);
+
+ check(reporter, config);
+ } catch (Exception e) {
+ throw new GradleException(e.getMessage(), e);
+ }
+ }
+
+ private void deleteFiles() {
+ try {
+ Files.deleteIfExists(getParameters().getRatXmlFile().getAsFile().get().toPath());
+ Files.deleteIfExists(getParameters().getRatTxtFile().getAsFile().get().toPath());
+ Files.deleteIfExists(getParameters().getRatHtmlFile().getAsFile().get().toPath());
+
+ try (Stream pathStream =
+ Files.walk(getParameters().getReportOutputDirectory().getAsFile().get().toPath())) {
+ Iterator iter =
+ pathStream
+ .sorted(Comparator.comparingInt(p -> p.toString().length()).reversed())
+ .iterator();
+ while (iter.hasNext()) {
+ Files.deleteIfExists(iter.next());
+ }
+ }
+ } catch (Exception e) {
+ throw new RuntimeException("Unable to delete RAT report directory or files", e);
+ }
+ }
+
+ protected void logLicenses(final Collection licenses) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("The following {} licenses are activated:", licenses.size());
+ for (ILicense license : licenses) {
+ LOGGER.debug("* {}", license);
+ }
+ }
+ }
+
+ protected void check(final Reporter reporter, final ReportConfiguration config) throws Exception {
+ ClaimStatistic statistics = reporter.getClaimsStatistic();
+ reporter.writeSummary(DefaultLog.getInstance().asWriter());
+ if (config.getClaimValidator().hasErrors()) {
+ config.getClaimValidator().logIssues(statistics);
+ if (!config
+ .getClaimValidator()
+ .isValid(
+ ClaimStatistic.Counter.UNAPPROVED,
+ statistics.getCounter(ClaimStatistic.Counter.UNAPPROVED))) {
+ try {
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ reporter.output(StyleSheets.UNAPPROVED_LICENSES.getStyleSheet(), () -> buffer);
+ LOGGER.error(new String(buffer.toByteArray(), StandardCharsets.UTF_8));
+ } catch (Exception e) {
+ LOGGER.error("Unable to print the files with unapproved licenses to the console.", e);
+ }
+ }
+
+ String msg =
+ String.format(
+ "Counter(s) %s exceeded minimum or maximum values.\nSee RAT reports:\n- '%s'\n- '%s'\n- '%s'",
+ String.join(", ", config.getClaimValidator().listIssues(statistics)),
+ getParameters().getRatHtmlFile().get().getAsFile().toURI(),
+ getParameters().getRatTxtFile().get().getAsFile().toURI(),
+ getParameters().getRatXmlFile().get().getAsFile().toURI());
+
+ throw new Exception(msg);
+ }
+ }
+
+ // see org.apache.rat.mp.AbstractRatMojo.getConfiguration
+ protected ReportConfiguration getConfiguration(final RatOptionsToConfiguration ratArguments) {
+ try {
+ File basedir = getParameters().getProjectBaseDir().getAsFile().get();
+
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("RAT configuration options:");
+ for (Map.Entry> entry : ratArguments.getArgsMap().entrySet()) {
+ LOGGER.debug(" * {} {}}", entry.getKey(), String.join(", ", entry.getValue()));
+ }
+ }
+
+ boolean helpLicenses = !ratArguments.getValues(Arg.HELP_LICENSES).isEmpty();
+ ratArguments.removeKey(Arg.HELP_LICENSES);
+
+ ReportConfiguration config =
+ OptionCollection.parseCommands(
+ basedir,
+ ratArguments.args().toArray(new String[0]),
+ ignore -> LOGGER.warn("Help option not supported"),
+ true);
+
+ DocumentName dirName = DocumentName.builder(basedir).build();
+ config.addSource(
+ new DirectoryWalker(
+ new FileDocument(dirName, basedir, config.getDocumentExcluder(dirName))));
+
+ if (helpLicenses) {
+ Writer w = new OutputStreamWriter(System.out);
+ new org.apache.rat.help.Licenses(config, w).printHelp();
+ }
+ return config;
+ } catch (Exception e) {
+ throw new GradleException("Failed to build RAT ReportConfiguration", e);
+ }
+ }
+}
diff --git a/apache-rat-gradle-plugin/src/main/java/org/apache/rat/gradle/internal/RatWorkParameters.java b/apache-rat-gradle-plugin/src/main/java/org/apache/rat/gradle/internal/RatWorkParameters.java
new file mode 100644
index 000000000..a6b8b637a
--- /dev/null
+++ b/apache-rat-gradle-plugin/src/main/java/org/apache/rat/gradle/internal/RatWorkParameters.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rat.gradle.internal;
+
+import org.apache.rat.gradle.RatOptionsTaskBase;
+import org.apache.rat.gradle.RatTaskProperties;
+import org.gradle.api.Project;
+import org.gradle.api.file.DirectoryProperty;
+import org.gradle.workers.WorkParameters;
+
+/**
+ * RAT Gradle worker parameters.
+ *
+ *
Consists of all user-configurable properties ({@link RatTaskProperties} including {@link
+ * RatOptionsTaskBase}) plus the {@link Project#getProjectDir() project's base directory}.
+ */
+public interface RatWorkParameters extends WorkParameters, RatTaskProperties {
+ DirectoryProperty getProjectBaseDir();
+}
diff --git a/apache-rat-gradle-plugin/src/main/java/org/apache/rat/gradle/internal/Slf4jLogBridge.java b/apache-rat-gradle-plugin/src/main/java/org/apache/rat/gradle/internal/Slf4jLogBridge.java
new file mode 100644
index 000000000..c8d96ba5c
--- /dev/null
+++ b/apache-rat-gradle-plugin/src/main/java/org/apache/rat/gradle/internal/Slf4jLogBridge.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rat.gradle.internal;
+
+import org.apache.rat.utils.Log;
+import org.slf4j.Logger;
+
+/** RAT {@link Log} implementation backed by an SLF4J {@link Logger}. */
+public class Slf4jLogBridge implements Log {
+ /** The SLF4J logger to delegate to. */
+ private final Logger logger;
+
+ public Slf4jLogBridge(final Logger logger) {
+ this.logger = logger;
+ }
+
+ @Override
+ public Level getLevel() {
+ // We don't know the "minimum" log level for an SLF4J logger, so we just return DEBUG.
+ return Level.DEBUG;
+ }
+
+ @Override
+ public boolean isEnabled(final Level level) {
+ switch (level) {
+ case DEBUG:
+ return logger.isDebugEnabled();
+ case INFO:
+ return logger.isInfoEnabled();
+ case WARN:
+ return logger.isWarnEnabled();
+ case ERROR:
+ return logger.isErrorEnabled();
+ default:
+ throw new IllegalArgumentException("Unknown log level: " + level);
+ }
+ }
+
+ @Override
+ public void log(final Level level, final String message) {
+ switch (level) {
+ case DEBUG:
+ logger.debug(message);
+ break;
+ case INFO:
+ logger.info(message);
+ break;
+ case WARN:
+ logger.warn(message);
+ break;
+ case ERROR:
+ logger.error(message);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown log level: " + level);
+ }
+ }
+}
diff --git a/apache-rat-gradle-plugin/src/main/java/org/apache/rat/gradle/internal/package-info.java b/apache-rat-gradle-plugin/src/main/java/org/apache/rat/gradle/internal/package-info.java
new file mode 100644
index 000000000..f996ae159
--- /dev/null
+++ b/apache-rat-gradle-plugin/src/main/java/org/apache/rat/gradle/internal/package-info.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Apache RAT Gradle plugin internal classes.
+ *
+ *
Do not use any of the types in this package in your Gradle build scripts or downstream
+ * projects. Any kind of (breaking) changes to the types may happen in an Apache RAT release, even
+ * patch releases.
+ */
+package org.apache.rat.gradle.internal;
diff --git a/apache-rat-gradle-plugin/src/main/java/org/apache/rat/gradle/package-info.java b/apache-rat-gradle-plugin/src/main/java/org/apache/rat/gradle/package-info.java
new file mode 100644
index 000000000..152f333e3
--- /dev/null
+++ b/apache-rat-gradle-plugin/src/main/java/org/apache/rat/gradle/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/** Publicly available types of the Apache RAT Gradle plugin. */
+package org.apache.rat.gradle;
diff --git a/apache-rat-gradle-plugin/src/test/java/org/apache/rat/gradle/TestConventions.java b/apache-rat-gradle-plugin/src/test/java/org/apache/rat/gradle/TestConventions.java
new file mode 100644
index 000000000..4b7b1ae4b
--- /dev/null
+++ b/apache-rat-gradle-plugin/src/test/java/org/apache/rat/gradle/TestConventions.java
@@ -0,0 +1,270 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rat.gradle;
+
+import java.io.File;
+import java.util.Arrays;
+
+import org.apache.groovy.util.Maps;
+import org.apache.rat.config.exclusion.StandardCollection;
+import org.apache.rat.gradle.internal.RatWorkParameters;
+import org.apache.rat.report.claim.ClaimStatistic;
+import org.assertj.core.api.SoftAssertions;
+import org.assertj.core.api.junit.jupiter.InjectSoftAssertions;
+import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension;
+import org.gradle.api.Project;
+import org.gradle.api.provider.ListProperty;
+import org.gradle.api.provider.MapProperty;
+import org.gradle.api.provider.Property;
+import org.gradle.api.tasks.TaskProvider;
+import org.gradle.testfixtures.ProjectBuilder;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@ExtendWith(SoftAssertionsExtension.class)
+public class TestConventions {
+ @InjectSoftAssertions SoftAssertions soft;
+
+ /**
+ * Verify that the conventions mapping from the {@link RatExtension} to a {@code RatTask} down to
+ * the {@link RatWorkParameters} works.
+ *
+ *
Does not exercise all options/properties, but one of each type.
+ */
+ @Test
+ public void conventionsMapping() {
+ Project project = ProjectBuilder.builder().build();
+ project.getPluginManager().apply("org.apache.rat");
+
+ RatExtension extension = project.getExtensions().getByType(RatExtension.class);
+
+ // Property (no arg options)
+ extension.getConfigurationNoDefaults().set(true);
+ extension.getDryRun().set(false);
+
+ extension.getEditCopyright().set("my copyright");
+
+ extension.getCounterMins().put(ClaimStatistic.Counter.APPROVED, 42);
+ extension.getCounterMins().put(ClaimStatistic.Counter.UNAPPROVED, 666);
+
+ extension.getInputIncludes().addAll("one", "two", "three");
+
+ extension
+ .getInputExcludeParsedScms()
+ .addAll(StandardCollection.GIT, StandardCollection.SUBVERSION);
+
+ extension.getLicensesApprovedFile().set(project.file("approved.txt"));
+
+ extension.getConfigs().from(project.file("my-config.xml"), project.file("my-other-config.xml"));
+
+ TaskProvider customTaskProvider =
+ project.getTasks().register("customRat", RatTask.class);
+ RatTask customTask = customTaskProvider.get();
+
+ TaskProvider ratTaskProvider = project.getTasks().named("rat", RatTask.class);
+ ratTaskProvider.configure(
+ task -> {
+ task.getConfigurationNoDefaults().set(false);
+ task.getDryRun().set(true);
+
+ task.getEditCopyright().set("other copyright");
+
+ task.getCounterMins().put(ClaimStatistic.Counter.APPROVED, 666);
+ task.getCounterMins().put(ClaimStatistic.Counter.UNAPPROVED, 42);
+
+ task.getInputIncludes().set(Arrays.asList("1", "2", "3"));
+
+ task.getInputExcludeParsedScms()
+ .set(Arrays.asList(StandardCollection.CVS, StandardCollection.MERCURIAL));
+
+ task.getLicensesApprovedFile().set(project.file("more-approved.txt"));
+
+ // `.from()` is "additive", would need to "unset" the convention first to just have the
+ // files below.
+ // Example: `task.getConfigs().unsetConvention();`
+ task.getConfigs()
+ .from(project.file("more-config.xml"), project.file("more-other-config.xml"));
+ });
+
+ RatTask ratTask = ratTaskProvider.get();
+
+ // The 'customTask' has no properties on its own instance.
+ // All properties default to their convention, the extension object's properties.
+
+ soft.assertThat(customTask.getConfigurationNoDefaults())
+ .extracting(Property::isPresent, Property::get)
+ .containsExactly(true, true);
+ soft.assertThat(customTask.getDryRun())
+ .extracting(Property::isPresent, Property::get)
+ .containsExactly(true, false);
+ soft.assertThat(customTask.getEditCopyright())
+ .extracting(Property::isPresent, Property::get)
+ .containsExactly(true, "my copyright");
+ soft.assertThat(customTask.getCounterMins())
+ .extracting(MapProperty::isPresent, MapProperty::get)
+ .containsExactly(
+ true,
+ Maps.of(ClaimStatistic.Counter.APPROVED, 42, ClaimStatistic.Counter.UNAPPROVED, 666));
+ soft.assertThat(customTask.getInputIncludes())
+ .extracting(ListProperty::isPresent, ListProperty::get)
+ .containsExactly(true, Arrays.asList("one", "two", "three"));
+ soft.assertThat(customTask.getInputExcludeParsedScms())
+ .extracting(ListProperty::isPresent, ListProperty::get)
+ .containsExactly(
+ true, Arrays.asList(StandardCollection.GIT, StandardCollection.SUBVERSION));
+ soft.assertThat(customTask.getLicensesApprovedFile().getAsFile().get())
+ .hasFileName("approved.txt");
+ soft.assertThat(customTask.getConfigs().getFiles())
+ .map(File::getName)
+ .containsExactlyInAnyOrder("my-config.xml", "my-other-config.xml");
+
+ // same assertions for the 'customTask' task
+
+ RatWorkParameters customTaskWorkParameters =
+ project.getObjects().newInstance(RatWorkParameters.class);
+ customTask.applyWorkParameters(customTaskWorkParameters);
+ soft.assertThat(customTaskWorkParameters.getProjectBaseDir().getAsFile().get())
+ .isEqualTo(project.getProjectDir());
+ soft.assertThat(
+ customTaskWorkParameters
+ .getRatXmlFile()
+ .getAsFile()
+ .get()
+ .toString()
+ .replace('\\', '/'))
+ .endsWith("build/reports/customRat/rat-report.xml");
+ soft.assertThat(
+ customTaskWorkParameters
+ .getRatTxtFile()
+ .getAsFile()
+ .get()
+ .toString()
+ .replace('\\', '/'))
+ .endsWith("build/reports/customRat/rat-report.txt");
+ soft.assertThat(
+ customTaskWorkParameters
+ .getRatHtmlFile()
+ .getAsFile()
+ .get()
+ .toString()
+ .replace('\\', '/'))
+ .endsWith("build/reports/customRat/rat-report.html");
+
+ soft.assertThat(customTaskWorkParameters.getConfigurationNoDefaults())
+ .extracting(Property::isPresent, Property::get)
+ .containsExactly(true, true);
+ soft.assertThat(customTaskWorkParameters.getDryRun())
+ .extracting(Property::isPresent, Property::get)
+ .containsExactly(true, false);
+ soft.assertThat(customTaskWorkParameters.getEditCopyright())
+ .extracting(Property::isPresent, Property::get)
+ .containsExactly(true, "my copyright");
+ soft.assertThat(customTaskWorkParameters.getCounterMins())
+ .extracting(MapProperty::isPresent, MapProperty::get)
+ .containsExactly(
+ true,
+ Maps.of(ClaimStatistic.Counter.APPROVED, 42, ClaimStatistic.Counter.UNAPPROVED, 666));
+ soft.assertThat(customTaskWorkParameters.getInputIncludes())
+ .extracting(ListProperty::isPresent, ListProperty::get)
+ .containsExactly(true, Arrays.asList("one", "two", "three"));
+ soft.assertThat(customTaskWorkParameters.getInputExcludeParsedScms())
+ .extracting(ListProperty::isPresent, ListProperty::get)
+ .containsExactly(
+ true, Arrays.asList(StandardCollection.GIT, StandardCollection.SUBVERSION));
+ soft.assertThat(customTaskWorkParameters.getLicensesApprovedFile().getAsFile().get())
+ .hasFileName("approved.txt");
+ soft.assertThat(customTaskWorkParameters.getConfigs().getFiles())
+ .map(File::getName)
+ .containsExactlyInAnyOrder("my-config.xml", "my-other-config.xml");
+
+ // The 'ratTask' has custom property values.
+
+ soft.assertThat(ratTask.getConfigurationNoDefaults())
+ .extracting(Property::isPresent, Property::get)
+ .containsExactly(true, false);
+ soft.assertThat(ratTask.getDryRun())
+ .extracting(Property::isPresent, Property::get)
+ .containsExactly(true, true);
+ soft.assertThat(ratTask.getEditCopyright())
+ .extracting(Property::isPresent, Property::get)
+ .containsExactly(true, "other copyright");
+ soft.assertThat(ratTask.getCounterMins())
+ .extracting(MapProperty::isPresent, MapProperty::get)
+ .containsExactly(
+ true,
+ Maps.of(ClaimStatistic.Counter.APPROVED, 666, ClaimStatistic.Counter.UNAPPROVED, 42));
+ soft.assertThat(ratTask.getInputIncludes())
+ .extracting(ListProperty::isPresent, ListProperty::get)
+ .containsExactly(true, Arrays.asList("1", "2", "3"));
+ soft.assertThat(ratTask.getInputExcludeParsedScms())
+ .extracting(ListProperty::isPresent, ListProperty::get)
+ .containsExactly(true, Arrays.asList(StandardCollection.CVS, StandardCollection.MERCURIAL));
+ soft.assertThat(ratTask.getLicensesApprovedFile().getAsFile().get())
+ .hasFileName("more-approved.txt");
+ soft.assertThat(ratTask.getConfigs().getFiles())
+ .map(File::getName)
+ .containsExactlyInAnyOrder(
+ "my-config.xml", "my-other-config.xml",
+ "more-config.xml", "more-other-config.xml");
+
+ // same assertions for the 'rat' task
+
+ RatWorkParameters ratTaskWorkParameters =
+ project.getObjects().newInstance(RatWorkParameters.class);
+ ratTask.applyWorkParameters(ratTaskWorkParameters);
+ soft.assertThat(ratTask.getProjectBaseDir().getAsFile().get())
+ .isEqualTo(project.getProjectDir());
+ soft.assertThat(ratTask.getRatXmlFile().getAsFile().get().toString().replace('\\', '/'))
+ .endsWith("build/reports/rat/rat-report.xml");
+ soft.assertThat(ratTask.getRatXmlFile().getAsFile().get().toString().replace('\\', '/'))
+ .endsWith("build/reports/rat/rat-report.xml");
+ soft.assertThat(ratTask.getRatTxtFile().getAsFile().get().toString().replace('\\', '/'))
+ .endsWith("build/reports/rat/rat-report.txt");
+ soft.assertThat(ratTask.getRatHtmlFile().getAsFile().get().toString().replace('\\', '/'))
+ .endsWith("build/reports/rat/rat-report.html");
+
+ soft.assertThat(ratTaskWorkParameters.getConfigurationNoDefaults())
+ .extracting(Property::isPresent, Property::get)
+ .containsExactly(true, false);
+ soft.assertThat(ratTaskWorkParameters.getDryRun())
+ .extracting(Property::isPresent, Property::get)
+ .containsExactly(true, true);
+ soft.assertThat(ratTaskWorkParameters.getEditCopyright())
+ .extracting(Property::isPresent, Property::get)
+ .containsExactly(true, "other copyright");
+ soft.assertThat(ratTaskWorkParameters.getCounterMins())
+ .extracting(MapProperty::isPresent, MapProperty::get)
+ .containsExactly(
+ true,
+ Maps.of(ClaimStatistic.Counter.APPROVED, 666, ClaimStatistic.Counter.UNAPPROVED, 42));
+ soft.assertThat(ratTaskWorkParameters.getInputIncludes())
+ .extracting(ListProperty::isPresent, ListProperty::get)
+ .containsExactly(true, Arrays.asList("1", "2", "3"));
+ soft.assertThat(ratTaskWorkParameters.getInputExcludeParsedScms())
+ .extracting(ListProperty::isPresent, ListProperty::get)
+ .containsExactly(true, Arrays.asList(StandardCollection.CVS, StandardCollection.MERCURIAL));
+ soft.assertThat(ratTaskWorkParameters.getLicensesApprovedFile().getAsFile().get())
+ .hasFileName("more-approved.txt");
+ soft.assertThat(ratTaskWorkParameters.getConfigs().getFiles())
+ .map(File::getName)
+ .containsExactlyInAnyOrder(
+ "my-config.xml", "my-other-config.xml",
+ "more-config.xml", "more-other-config.xml");
+ }
+}
diff --git a/apache-rat-gradle-plugin/src/test/java/org/apache/rat/gradle/TestGradleIntegration.java b/apache-rat-gradle-plugin/src/test/java/org/apache/rat/gradle/TestGradleIntegration.java
new file mode 100644
index 000000000..6481de9ca
--- /dev/null
+++ b/apache-rat-gradle-plugin/src/test/java/org/apache/rat/gradle/TestGradleIntegration.java
@@ -0,0 +1,294 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rat.gradle;
+
+import static java.lang.String.format;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.FileVisitor;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Objects;
+
+import org.assertj.core.api.SoftAssertions;
+import org.assertj.core.api.junit.jupiter.InjectSoftAssertions;
+import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension;
+import org.gradle.testkit.runner.BuildResult;
+import org.gradle.testkit.runner.GradleRunner;
+import org.gradle.testkit.runner.TaskOutcome;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.io.TempDir;
+
+/**
+ * Gradle build integration tests.
+ *
+ *
Not many functional tests in this class, as integration tests are (by nature) rather slow.
+ *
+ *
Test build directories intentionally use Groovy instead of Kotlin-Script, because the latter
+ * requires some expensive preparation steps (Gradle API compilation for Kotlin). That test can be
+ * removed, as it is not strictly necessary.
+ *
+ *
The Gradle test-kit automatically "wires" the plugins in the current project to the test
+ * builds, a version reference is not needed, it is actually incorrect to specify one in the test
+ * builds.
+ */
+@ExtendWith(SoftAssertionsExtension.class)
+public class TestGradleIntegration {
+ @InjectSoftAssertions SoftAssertions soft;
+
+ @TempDir Path projectDir;
+
+ @BeforeEach
+ void setup(TestInfo testInfo) throws Exception {
+ String testCaseDir =
+ format(
+ "/%s/%s",
+ testInfo.getTestClass().get().getSimpleName(),
+ testInfo.getTestMethod().get().getName());
+ Path templateDir =
+ Paths.get(
+ Objects.requireNonNull(
+ TestGradleIntegration.class.getResource(testCaseDir),
+ "Test case Gradle project resource directory " + testCaseDir + " not found")
+ .toURI());
+
+ Files.walkFileTree(
+ templateDir,
+ new FileVisitor() {
+ @Override
+ public @NotNull FileVisitResult preVisitDirectory(
+ Path dir, @NotNull BasicFileAttributes attrs) throws IOException {
+ Path relativePath = templateDir.relativize(dir);
+ Path target = projectDir.resolve(relativePath);
+ if (!Files.isDirectory(target)) {
+ Files.createDirectory(target);
+ }
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public @NotNull FileVisitResult visitFile(Path file, @NotNull BasicFileAttributes attrs)
+ throws IOException {
+ Path relativePath = templateDir.relativize(file);
+ Path target = projectDir.resolve(relativePath);
+ Files.copy(file, target);
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public @NotNull FileVisitResult visitFileFailed(Path file, @NotNull IOException exc)
+ throws IOException {
+ return FileVisitResult.TERMINATE;
+ }
+
+ @Override
+ public @NotNull FileVisitResult postVisitDirectory(Path dir, @Nullable IOException exc)
+ throws IOException {
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ }
+
+ @Test
+ public void defaultSettings() {
+ assertThat(ratOutcome(standardGradleRunner().build()))
+ .matches(TestGradleIntegration::isSuccess);
+
+ soft.assertThat(projectDir.resolve("build/reports/rat/rat-report.xml")).isNotEmptyFile();
+ soft.assertThat(projectDir.resolve("build/reports/rat/rat-report.txt")).isNotEmptyFile();
+ soft.assertThat(projectDir.resolve("build/reports/rat/rat-report.html")).isNotEmptyFile();
+ }
+
+ /** Verify that the {@code rat} task is run as a dependency of the {@code check} task. */
+ @Test
+ public void withJava() {
+ assertThat(
+ ratOutcome(
+ createGradleRunner("--build-cache", "--info", "--stacktrace", "check").build()))
+ .matches(TestGradleIntegration::isSuccess);
+
+ soft.assertThat(projectDir.resolve("build/reports/rat/rat-report.xml")).isNotEmptyFile();
+ soft.assertThat(projectDir.resolve("build/reports/rat/rat-report.txt")).isNotEmptyFile();
+ soft.assertThat(projectDir.resolve("build/reports/rat/rat-report.html")).isNotEmptyFile();
+ }
+
+ /**
+ * Verify that the {@code rat} task's XML report can be consumed by another task in the same
+ * Gradle project.
+ *
+ *
Dependency chain is:
+ *
+ *
+ *
Task {@code other} declares a task-input from the {@code ratXmlReport} Gradle
+ * configuration.
+ *
The {@code ratXmlReport} Gradle configuration is configured with a single artifact for
+ * the XML report file, built by the {@code rat} task.
+ *
+ *
+ *
Executing the task {@code other} therefore depends on the {@code rat} task, and the XML
+ * report file must be available.
+ */
+ @Test
+ public void consumeXmlReportInCustomTask() {
+ BuildResult buildResult =
+ createGradleRunner("--build-cache", "--info", "--stacktrace", "other").build();
+ assertThat(ratOutcome(buildResult)).matches(TestGradleIntegration::isSuccess);
+ assertThat(buildResult.task(":other").getOutcome()).matches(TestGradleIntegration::isSuccess);
+
+ // We run the 'other' task, which has no "direct" task dependency (aka dependsOn()) to rat.
+ // The 'rat' task has to be run.
+
+ soft.assertThat(projectDir.resolve("build/reports/rat/rat-report.xml")).isNotEmptyFile();
+ soft.assertThat(projectDir.resolve("build/reports/rat/rat-report.txt")).isNotEmptyFile();
+ soft.assertThat(projectDir.resolve("build/reports/rat/rat-report.html")).isNotEmptyFile();
+ }
+
+ /** Verify that the {@code rat} task's XML report can be used as a "copy source". */
+ @Test
+ public void consumeXmlReportInCopyTask() {
+ BuildResult buildResult =
+ createGradleRunner("--build-cache", "--info", "--stacktrace", "copyXml").build();
+ assertThat(ratOutcome(buildResult)).matches(TestGradleIntegration::isSuccess);
+ assertThat(buildResult.task(":copyXml").getOutcome()).matches(TestGradleIntegration::isSuccess);
+
+ soft.assertThat(projectDir.resolve("build/reports/rat/rat-report.xml")).isNotEmptyFile();
+ soft.assertThat(projectDir.resolve("build/reports/rat/rat-report.txt")).isNotEmptyFile();
+ soft.assertThat(projectDir.resolve("build/reports/rat/rat-report.html")).isNotEmptyFile();
+
+ soft.assertThat(projectDir.resolve("build/copied/rat-report.xml")).isNotEmptyFile();
+ soft.assertThat(projectDir.resolve("build/copied/rat-report.txt")).doesNotExist();
+ soft.assertThat(projectDir.resolve("build/copied/rat-report.html")).doesNotExist();
+ }
+
+ /** Verify that the {@code rat} task's XML report can be used as a "copy source". */
+ @Test
+ public void consumeAllReportsInCopyTask() {
+ BuildResult buildResult =
+ createGradleRunner("--build-cache", "--info", "--stacktrace", "copyAll").build();
+ assertThat(ratOutcome(buildResult)).matches(TestGradleIntegration::isSuccess);
+ assertThat(buildResult.task(":copyAll").getOutcome()).matches(TestGradleIntegration::isSuccess);
+
+ soft.assertThat(projectDir.resolve("build/reports/rat/rat-report.xml")).isNotEmptyFile();
+ soft.assertThat(projectDir.resolve("build/reports/rat/rat-report.txt")).isNotEmptyFile();
+ soft.assertThat(projectDir.resolve("build/reports/rat/rat-report.html")).isNotEmptyFile();
+
+ soft.assertThat(projectDir.resolve("build/copied/rat-report.xml")).isNotEmptyFile();
+ soft.assertThat(projectDir.resolve("build/copied/rat-report.txt")).isNotEmptyFile();
+ soft.assertThat(projectDir.resolve("build/copied/rat-report.html")).isNotEmptyFile();
+ }
+
+ /** Verify that the {@code rat} task's XML report can be used as a "copy source". */
+ @Test
+ public void consumeXmlReportInOtherProject() {
+ BuildResult buildResult =
+ createGradleRunner("--build-cache", "--info", "--stacktrace", ":a:copyXml").build();
+ assertThat(ratOutcome(buildResult)).matches(TestGradleIntegration::isSuccess);
+ assertThat(buildResult.task(":a:copyXml").getOutcome())
+ .matches(TestGradleIntegration::isSuccess);
+
+ soft.assertThat(projectDir.resolve("build/reports/rat/rat-report.xml")).isNotEmptyFile();
+ soft.assertThat(projectDir.resolve("build/reports/rat/rat-report.txt")).isNotEmptyFile();
+ soft.assertThat(projectDir.resolve("build/reports/rat/rat-report.html")).isNotEmptyFile();
+
+ soft.assertThat(projectDir.resolve("a/build/copied/rat-report.xml")).isNotEmptyFile();
+ soft.assertThat(projectDir.resolve("a/build/copied/rat-report.txt")).isNotEmptyFile();
+ soft.assertThat(projectDir.resolve("a/build/copied/rat-report.html")).isNotEmptyFile();
+ }
+
+ @Test
+ public void kotlinDefaultSettings() {
+ assertThat(ratOutcome(standardGradleRunner().build()))
+ .matches(TestGradleIntegration::isSuccess);
+
+ soft.assertThat(projectDir.resolve("build/reports/rat/rat-report.xml")).isNotEmptyFile();
+ soft.assertThat(projectDir.resolve("build/reports/rat/rat-report.txt")).isNotEmptyFile();
+ soft.assertThat(projectDir.resolve("build/reports/rat/rat-report.html")).isNotEmptyFile();
+ }
+
+ @Test
+ public void customOutputDirectory() {
+ assertThat(ratOutcome(standardGradleRunner().build()))
+ .matches(TestGradleIntegration::isSuccess);
+
+ soft.assertThat(projectDir.resolve("build/reports/rat")).doesNotExist();
+
+ soft.assertThat(projectDir.resolve("custom/output/rat-report.xml")).isNotEmptyFile();
+ soft.assertThat(projectDir.resolve("custom/output/rat-report.txt")).isNotEmptyFile();
+ soft.assertThat(projectDir.resolve("custom/output/rat-report.html")).isNotEmptyFile();
+ }
+
+ @Test
+ public void customOutputFiles() {
+ assertThat(ratOutcome(standardGradleRunner().build()))
+ .matches(TestGradleIntegration::isSuccess);
+
+ soft.assertThat(projectDir.resolve("build/reports/rat")).doesNotExist();
+
+ // Not great that this directory is created, but it doesn't hurt at all.
+ // This assertion isn't really needed and can be changed or removed when necessary.
+ soft.assertThat(projectDir.resolve("custom")).isEmptyDirectory();
+
+ soft.assertThat(projectDir.resolve("tech/that.xml")).isNotEmptyFile();
+ soft.assertThat(projectDir.resolve("my/output.txt")).isNotEmptyFile();
+ soft.assertThat(projectDir.resolve("my/output.txt")).isNotEmptyFile();
+ }
+
+ @Test
+ public void badLicenseFailsBuild() {
+ assertThat(ratOutcome(standardGradleRunner().buildAndFail())).isSameAs(TaskOutcome.FAILED);
+ }
+
+ private static boolean isSuccess(TaskOutcome outcome) {
+ // All these outcomes represent a successful task execution.
+ // SUCCESS -> task successfully executed
+ // UP_TO_DATE -> task inputs match the inputs of a previous successful execution, output is
+ // up-to-date
+ // FROM_CACHE -> task output was loaded from the build cache using the inputs of the task
+ // (Note: the latter two are currently impossible, see RatTask constructor)
+ return outcome == TaskOutcome.SUCCESS
+ || outcome == TaskOutcome.UP_TO_DATE
+ || outcome == TaskOutcome.FROM_CACHE;
+ }
+
+ private static TaskOutcome ratOutcome(BuildResult result) {
+ return result.task(":rat").getOutcome();
+ }
+
+ private GradleRunner standardGradleRunner(String... args) {
+ return createGradleRunner("--build-cache", "--info", "--stacktrace", "rat");
+ }
+
+ private GradleRunner createGradleRunner(String... args) {
+ return GradleRunner.create()
+ .withPluginClasspath()
+ .withProjectDir(projectDir.toFile())
+ .withArguments(args)
+ .withDebug(true)
+ .forwardOutput();
+ }
+}
diff --git a/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/badLicenseFailsBuild/build.gradle b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/badLicenseFailsBuild/build.gradle
new file mode 100644
index 000000000..b5b2744a9
--- /dev/null
+++ b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/badLicenseFailsBuild/build.gradle
@@ -0,0 +1,3 @@
+plugins {
+ id 'org.apache.rat'
+}
diff --git a/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/badLicenseFailsBuild/settings.gradle b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/badLicenseFailsBuild/settings.gradle
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/badLicenseFailsBuild/settings.gradle
@@ -0,0 +1 @@
+
diff --git a/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/consumeAllReportsInCopyTask/build.gradle b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/consumeAllReportsInCopyTask/build.gradle
new file mode 100644
index 000000000..9284e7d6e
--- /dev/null
+++ b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/consumeAllReportsInCopyTask/build.gradle
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+plugins {
+ id 'org.apache.rat'
+}
+
+tasks.register('copyAll', Copy) {
+ it.destinationDir = project.layout.buildDirectory.dir('copied').get().asFile
+ it.from(configurations.ratAllReports)
+}
diff --git a/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/consumeAllReportsInCopyTask/settings.gradle b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/consumeAllReportsInCopyTask/settings.gradle
new file mode 100644
index 000000000..bd244d07a
--- /dev/null
+++ b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/consumeAllReportsInCopyTask/settings.gradle
@@ -0,0 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
diff --git a/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/consumeXmlReportInCopyTask/build.gradle b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/consumeXmlReportInCopyTask/build.gradle
new file mode 100644
index 000000000..00e554743
--- /dev/null
+++ b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/consumeXmlReportInCopyTask/build.gradle
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+plugins {
+ id 'org.apache.rat'
+}
+
+tasks.register('copyXml', Copy) {
+ it.destinationDir = project.layout.buildDirectory.dir('copied').get().asFile
+ it.from(configurations.ratXmlReport)
+}
diff --git a/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/consumeXmlReportInCopyTask/settings.gradle b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/consumeXmlReportInCopyTask/settings.gradle
new file mode 100644
index 000000000..bd244d07a
--- /dev/null
+++ b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/consumeXmlReportInCopyTask/settings.gradle
@@ -0,0 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
diff --git a/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/consumeXmlReportInCustomTask/build.gradle b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/consumeXmlReportInCustomTask/build.gradle
new file mode 100644
index 000000000..85a5c45b7
--- /dev/null
+++ b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/consumeXmlReportInCustomTask/build.gradle
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+plugins {
+ id 'org.apache.rat'
+}
+
+tasks.register('other') {
+ inputs.files(configurations.named('ratXmlReport'))
+}
diff --git a/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/consumeXmlReportInCustomTask/settings.gradle b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/consumeXmlReportInCustomTask/settings.gradle
new file mode 100644
index 000000000..bd244d07a
--- /dev/null
+++ b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/consumeXmlReportInCustomTask/settings.gradle
@@ -0,0 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
diff --git a/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/consumeXmlReportInOtherProject/a/build.gradle b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/consumeXmlReportInOtherProject/a/build.gradle
new file mode 100644
index 000000000..1792dd42a
--- /dev/null
+++ b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/consumeXmlReportInOtherProject/a/build.gradle
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+var resolvableConfig = configurations.register('resolvableConfig') {
+ canBeConsumed = false
+ canBeResolved = true
+}
+
+dependencies {
+ add('resolvableConfig', project(['path': ':', 'configuration': 'ratAllReportsElements']))
+}
+
+tasks.register('copyXml', Copy) {
+ it.destinationDir = project.layout.buildDirectory.dir('copied').get().asFile
+ it.from(resolvableConfig)
+}
diff --git a/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/consumeXmlReportInOtherProject/build.gradle b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/consumeXmlReportInOtherProject/build.gradle
new file mode 100644
index 000000000..2cffc86c6
--- /dev/null
+++ b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/consumeXmlReportInOtherProject/build.gradle
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+plugins {
+ id 'org.apache.rat'
+}
diff --git a/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/consumeXmlReportInOtherProject/settings.gradle b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/consumeXmlReportInOtherProject/settings.gradle
new file mode 100644
index 000000000..3a4fa693d
--- /dev/null
+++ b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/consumeXmlReportInOtherProject/settings.gradle
@@ -0,0 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+include('a')
diff --git a/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/customOutputDirectory/build.gradle b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/customOutputDirectory/build.gradle
new file mode 100644
index 000000000..8f9dc0fe4
--- /dev/null
+++ b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/customOutputDirectory/build.gradle
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.rat.gradle.RatTask
+
+plugins {
+ id 'org.apache.rat'
+}
+
+tasks.named('rat', RatTask) {
+ reportOutputDirectory = file('custom/output')
+}
diff --git a/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/customOutputDirectory/settings.gradle b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/customOutputDirectory/settings.gradle
new file mode 100644
index 000000000..bd244d07a
--- /dev/null
+++ b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/customOutputDirectory/settings.gradle
@@ -0,0 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
diff --git a/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/customOutputFiles/build.gradle b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/customOutputFiles/build.gradle
new file mode 100644
index 000000000..1c88ef69e
--- /dev/null
+++ b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/customOutputFiles/build.gradle
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.rat.gradle.RatTask
+
+plugins {
+ id 'org.apache.rat'
+}
+
+tasks.named('rat', RatTask) {
+ reportOutputDirectory = file('custom/output')
+ ratTxtFile = file('my/output.txt')
+ ratHtmlFile = file('some/index.html')
+ ratXmlFile = file('tech/that.xml')
+}
diff --git a/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/customOutputFiles/settings.gradle b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/customOutputFiles/settings.gradle
new file mode 100644
index 000000000..bd244d07a
--- /dev/null
+++ b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/customOutputFiles/settings.gradle
@@ -0,0 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
diff --git a/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/defaultSettings/build.gradle b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/defaultSettings/build.gradle
new file mode 100644
index 000000000..2cffc86c6
--- /dev/null
+++ b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/defaultSettings/build.gradle
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+plugins {
+ id 'org.apache.rat'
+}
diff --git a/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/defaultSettings/settings.gradle b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/defaultSettings/settings.gradle
new file mode 100644
index 000000000..bd244d07a
--- /dev/null
+++ b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/defaultSettings/settings.gradle
@@ -0,0 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
diff --git a/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/kotlinDefaultSettings/build.gradle.kts b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/kotlinDefaultSettings/build.gradle.kts
new file mode 100644
index 000000000..8b693a926
--- /dev/null
+++ b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/kotlinDefaultSettings/build.gradle.kts
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+plugins {
+ id("org.apache.rat")
+}
diff --git a/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/kotlinDefaultSettings/settings.gradle.kts b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/kotlinDefaultSettings/settings.gradle.kts
new file mode 100644
index 000000000..bd244d07a
--- /dev/null
+++ b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/kotlinDefaultSettings/settings.gradle.kts
@@ -0,0 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
diff --git a/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/withJava/build.gradle b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/withJava/build.gradle
new file mode 100644
index 000000000..d88de024c
--- /dev/null
+++ b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/withJava/build.gradle
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+plugins {
+ id 'java-library'
+ id 'org.apache.rat'
+}
diff --git a/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/withJava/settings.gradle b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/withJava/settings.gradle
new file mode 100644
index 000000000..bd244d07a
--- /dev/null
+++ b/apache-rat-gradle-plugin/src/test/resources/TestGradleIntegration/withJava/settings.gradle
@@ -0,0 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
diff --git a/apache-rat-tools/src/main/java/org/apache/rat/documentation/options/GradleOption.java b/apache-rat-tools/src/main/java/org/apache/rat/documentation/options/GradleOption.java
new file mode 100644
index 000000000..932370e56
--- /dev/null
+++ b/apache-rat-tools/src/main/java/org/apache/rat/documentation/options/GradleOption.java
@@ -0,0 +1,287 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rat.documentation.options;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import org.apache.commons.cli.Option;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.commons.text.WordUtils;
+import org.apache.rat.OptionCollection;
+import org.apache.rat.commandline.Arg;
+import org.apache.rat.commandline.Converters;
+import org.apache.rat.report.claim.ClaimStatistic;
+import org.apache.rat.utils.CasedString;
+
+import static java.lang.String.format;
+
+/**
+ * A representation of a CLI option for a Gradle plugin extension and task property.
+ */
+public class GradleOption extends AbstractOption {
+ /**
+ * Default values for CLI options
+ */
+ private static final Map DEFAULT_VALUES = new HashMap<>();
+ /**
+ * List of CLI options that are not supported by Gradle.
+ */
+ private static final Set