diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 9c2ebd9..f064064 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -9,7 +9,7 @@ name: Java CI with Gradle on: push: - branches: [ "main" ] + branches: [ "main", "release/**", "feature/**" ] pull_request: branches: [ "main" ] diff --git a/README.md b/README.md index 33faaac..b12508f 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,8 @@ plugins { id("io.gofannon.cots-report") version ("0.1.0-SNAPSHOT") } ``` + + ### Extension usage The plugin provides the `cotsReport` extension. This extension contains 3 properties: @@ -49,7 +51,10 @@ This extension contains 3 properties: * **configurations** which contains the list of the configurations to parse. This property is optional. By default, the configuration is `runtimeClasspath`. * **ignorableGroupIds** which contains the list of the groups to ignore. This property is optional. By default, there is no ignorable group. -Example: + +### Examples: + +__Groovy__ ```groovy cotsReporting { reportFile = layout.buildDirectory.file("my-report.txt") @@ -58,8 +63,19 @@ cotsReporting { } ``` +__Kotlin__ +```kotlin +cotsReporting { + reportFile = layout.buildDirectory.file("sample.txt") + configurations.set(listOf("runtimeClasspath","testRuntimeClasspath")) + ignorableGroupIds.set(listOf("commons-io", "com.fasterxml.jackson.module", "jackson-module-kotlin")) +} +``` + + ## Run To run the plugin, just execute the task + ```shell gradle :cotsReport ``` diff --git a/build.gradle.kts b/build.gradle.kts index eae8ae3..c129718 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -34,6 +34,7 @@ dependencies { compileOnly("com.google.code.findbugs:jsr305:3.0.2") testImplementation("org.codehaus.groovy:groovy:3.0.21") + //testImplementation("commons-io:commons-io:2.16.0") testImplementation(platform("org.junit:junit-bom:5.10.1")) testImplementation("org.junit.jupiter:junit-jupiter") diff --git a/gradle.properties b/gradle.properties index c39bfa4..23ae36a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ kotlin.code.style=official -version=1.0.0-SNAPSHOT \ No newline at end of file +version=1.0.0 \ No newline at end of file diff --git a/src/main/java/io/gofannon/gradle/cots/report/CotsReportGenerationTask.java b/src/main/java/io/gofannon/gradle/cots/report/CotsReportGenerationTask.java index 8ec2980..129ce43 100644 --- a/src/main/java/io/gofannon/gradle/cots/report/CotsReportGenerationTask.java +++ b/src/main/java/io/gofannon/gradle/cots/report/CotsReportGenerationTask.java @@ -45,7 +45,8 @@ public void initialize(CotsReportExtension extension) { private void initializeReportRenderer(CotsReportExtension extension) { CotsContext context = createContext(extension); DependencyCollector dependencyCollector = new DependencyCollector(context); - DependencyReportRenderer reportRenderer = new CotsReportRenderer(dependencyCollector); + ReportFormatter formatter = new TextReportFormatter(); + DependencyReportRenderer reportRenderer = new CotsReportRenderer(dependencyCollector, formatter); setRenderer(reportRenderer); } diff --git a/src/main/java/io/gofannon/gradle/cots/report/CotsReportRenderer.java b/src/main/java/io/gofannon/gradle/cots/report/CotsReportRenderer.java index 60f32f7..23a79e3 100644 --- a/src/main/java/io/gofannon/gradle/cots/report/CotsReportRenderer.java +++ b/src/main/java/io/gofannon/gradle/cots/report/CotsReportRenderer.java @@ -30,21 +30,19 @@ @NonNullApi public class CotsReportRenderer extends TextReportRenderer implements DependencyReportRenderer { - + private final ReportFormatter formatter; private final DependencyCollector dependencyCollector; - private DependencyGraphsParser dependencyGraphParser; - public CotsReportRenderer(DependencyCollector dependencyCollector) { + public CotsReportRenderer(DependencyCollector dependencyCollector, ReportFormatter formatter) { this.dependencyCollector = dependencyCollector; + this.formatter = formatter; } @Override public void startProject(ProjectDetails project) { - getTextOutput().println("---------------------------------"); - getTextOutput().println("--- " + project.getDisplayName()); - getTextOutput().println("---------------------------------"); - + this.formatter.setOutput(getTextOutput()); + formatter.printProjectHeader(project); dependencyGraphParser = new DependencyGraphsParser(dependencyCollector); } @@ -88,18 +86,7 @@ public void parseRenderableDependency(RenderableDependency root) { @Override public void complete() { - getTextOutput().println("--- COTS configurations"); - for (var configurationName : dependencyCollector.getConfigurationList()) { - getTextOutput().println(configurationName); - } - - getTextOutput().println(); - - getTextOutput().println("--- COTS dependencies"); - List dependencyList = dependencyCollector.getDependencyIdList(); - dependencyList.sort(String::compareTo); - for (var dependencyId : dependencyList) { - getTextOutput().println(dependencyId); - } + formatter.printConfigurations(dependencyCollector.getConfigurationList()); + formatter.printDependencies(dependencyCollector.getDependencyIdList()); } } \ No newline at end of file diff --git a/src/main/java/io/gofannon/gradle/cots/report/DependencyCollector.java b/src/main/java/io/gofannon/gradle/cots/report/DependencyCollector.java index 03350fa..e696db6 100644 --- a/src/main/java/io/gofannon/gradle/cots/report/DependencyCollector.java +++ b/src/main/java/io/gofannon/gradle/cots/report/DependencyCollector.java @@ -32,8 +32,8 @@ public class DependencyCollector implements DependencyVisitor { private final DependencyCollectorConfiguration configuration; - private final SortedSet dependencyIdSet = new TreeSet<>(); - private final SortedSet configurationNameSet = new TreeSet<>(); + private final SortedSet dependencyIdSet = new TreeSet<>(); + private final SortedSet configurationNameSet = new TreeSet<>(); /** @@ -42,11 +42,10 @@ public class DependencyCollector implements DependencyVisitor { * @param configuration all information to select the dependencies to collect */ public DependencyCollector(DependencyCollectorConfiguration configuration) { -this.configuration = configuration; + this.configuration = configuration; } - /** * Visit a Gradle dependency (aka JAR) * diff --git a/src/main/java/io/gofannon/gradle/cots/report/ReportFormatter.java b/src/main/java/io/gofannon/gradle/cots/report/ReportFormatter.java new file mode 100644 index 0000000..967e63d --- /dev/null +++ b/src/main/java/io/gofannon/gradle/cots/report/ReportFormatter.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.gofannon.gradle.cots.report; + +import org.gradle.api.NonNullApi; +import org.gradle.api.tasks.diagnostics.internal.ProjectDetails; +import org.gradle.internal.logging.text.StyledTextOutput; + +import java.util.List; + +/** + * Formatter for dependency report + */ +@NonNullApi +public interface ReportFormatter { + + /** + * Inject the console output + * + * @param output the console output + */ + void setOutput(StyledTextOutput output); + + /** + * Print the header of the project + * + * @param project the project to print + */ + void printProjectHeader(ProjectDetails project); + + /** + * Print the configurations in the project + * + * @param configurationNames the names of the configurations + */ + void printConfigurations(List configurationNames); + + /** + * Print the dependencies in the project + * + * @param dependencyIdList the list of the dependency identifiers + */ + void printDependencies(List dependencyIdList); +} diff --git a/src/main/java/io/gofannon/gradle/cots/report/TextReportFormatter.java b/src/main/java/io/gofannon/gradle/cots/report/TextReportFormatter.java new file mode 100644 index 0000000..d01c301 --- /dev/null +++ b/src/main/java/io/gofannon/gradle/cots/report/TextReportFormatter.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.gofannon.gradle.cots.report; + +import org.gradle.api.NonNullApi; +import org.gradle.api.tasks.diagnostics.internal.ProjectDetails; +import org.gradle.internal.logging.text.StyledTextOutput; + +import java.util.List; + +/** + * Dependency report formatter that generates report in text format + */ +@NonNullApi +public class TextReportFormatter implements ReportFormatter { + + private StyledTextOutput output; + + @Override + public void setOutput(StyledTextOutput output) { + this.output = output; + } + + @Override + public void printProjectHeader(ProjectDetails project) { + output.println("---------------------------------") + .println("--- " + project.getDisplayName()) + .println("---------------------------------"); + } + + @Override + public void printConfigurations(List configurationNames) { + output.println("--- COTS configurations"); + configurationNames.stream() + .sorted(String::compareTo) + .forEach(output::println); + output.println(); + } + + + @Override + public void printDependencies(List dependencyIdList) { + output.println("--- COTS dependencies"); + dependencyIdList.stream() + .sorted(String::compareTo) + .forEach(output::println); + } +} diff --git a/src/test/groovy/io/gofannon/gradle/cots/report/BuildLogicFunctionalTest.groovy b/src/test/groovy/io/gofannon/gradle/cots/report/BuildLogicFunctionalTest.groovy new file mode 100644 index 0000000..883a560 --- /dev/null +++ b/src/test/groovy/io/gofannon/gradle/cots/report/BuildLogicFunctionalTest.groovy @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.gofannon.gradle.cots.report + +import org.gradle.testkit.runner.BuildResult +import org.gradle.testkit.runner.GradleRunner +import spock.lang.Specification +import spock.lang.TempDir + +import static io.gofannon.gradle.cots.report.CotsReportHelper.* +import static org.gradle.testkit.runner.TaskOutcome.SUCCESS + +class BuildLogicFunctionalTest extends Specification { + + @TempDir + File testProjectDir + File settingsFile + File buildFile + File defaultReportFile + BuildResult buildResult + + private static String baseBuildGradle = """ + plugins { + id 'java' + id 'io.gofannon.cots-report' version("1.0.0-SNAPSHOT") + } + + dependencies { + implementation "org.junit.platform:junit-platform-commons:1.10.2" + testImplementation("commons-io:commons-io:2.16.0") + } + + repositories { + mavenCentral() + } + """ + + def setup() { + settingsFile = new File(testProjectDir, 'settings.gradle') + buildFile = new File(testProjectDir, 'build.gradle') + defaultReportFile = new File(testProjectDir, "build/reports/project/cots-report.txt") + } + + def "can execute cotsReport task with no cotsReporting extension"() { + given: + buildFile << baseBuildGradle + settingsFile << "" + + + when: + runGradleCotsReportTask() + def reportFile = defaultReportFile + + + then: + buildResult.task(":cotsReport").outcome == SUCCESS + buildResult.output.contains("See the report at: " + formatToClickableUrl(reportFile)) + reportFile.exists() + extractConfigurations(reportFile) == List.of("runtimeClasspath") + extractDependencies(reportFile) == List.of("org.junit.platform:junit-platform-commons:1.10.2", "org.junit:junit-bom:5.10.2") + } + + private def runGradleCotsReportTask() { + buildResult= GradleRunner.create() + .withGradleVersion("8.4") + .withProjectDir(testProjectDir) + .withPluginClasspath() + .withArguments('cotsReport') + .build() + } + + + def "can execute cotsReport task with empty cotsReporting extension"() { + given: + buildFile << baseBuildGradle+""" + cotsReporting {} + """ + settingsFile << "" + + when: + runGradleCotsReportTask() + def reportFile = defaultReportFile + + + then: + buildResult.task(":cotsReport").outcome == SUCCESS + buildResult.output.contains("See the report at: " + formatToClickableUrl(reportFile)) + reportFile.exists() + extractConfigurations(reportFile) == List.of("runtimeClasspath") + extractDependencies(reportFile) == List.of("org.junit.platform:junit-platform-commons:1.10.2", "org.junit:junit-bom:5.10.2") + } + + + + + def "can execute cotsReport task with reportFile property set"() { + given: + buildFile << baseBuildGradle+""" + cotsReporting { + reportFile = layout.buildDirectory.file("sample.txt") + } + """ + settingsFile << "" + + when: + runGradleCotsReportTask() + def reportFile = new File(testProjectDir, "build/sample.txt") + + + then: + buildResult.task(":cotsReport").outcome == SUCCESS + buildResult.output.contains("See the report at: " + formatToClickableUrl(reportFile)) + reportFile.exists() + extractConfigurations(reportFile) == List.of("runtimeClasspath") + extractDependencies(reportFile) == List.of("org.junit.platform:junit-platform-commons:1.10.2", "org.junit:junit-bom:5.10.2") + } + + + def "can execute cotsReport task with ignorableGroupIds property set"() { + given: + buildFile << baseBuildGradle+""" + cotsReporting { + ignorableGroupIds = ["org.junit"] + } + """ + settingsFile << "" + + when: + runGradleCotsReportTask() + def reportFile = defaultReportFile + + + then: + buildResult.task(":cotsReport").outcome == SUCCESS + buildResult.output.contains("See the report at: " + formatToClickableUrl(reportFile)) + reportFile.exists() + extractConfigurations(reportFile) == List.of("runtimeClasspath") + extractDependencies(reportFile) == List.of("org.junit.platform:junit-platform-commons:1.10.2") + } + + + def "can execute cotsReport task with configurations property set"() { + given: + buildFile << baseBuildGradle+""" + cotsReporting { + configurations = ["testRuntimeClasspath"] + } + """ + settingsFile << "" + + when: + runGradleCotsReportTask() + def reportFile = defaultReportFile + + + then: + buildResult.task(":cotsReport").outcome == SUCCESS + buildResult.output.contains("See the report at: " + formatToClickableUrl(reportFile)) + reportFile.exists() + extractConfigurations(reportFile) == List.of("testRuntimeClasspath") + extractDependencies(reportFile) == List.of("commons-io:commons-io:2.16.0","org.junit.platform:junit-platform-commons:1.10.2", "org.junit:junit-bom:5.10.2") + } + +} diff --git a/src/test/groovy/io/gofannon/gradle/cots/report/CotsReportGenerationTaskTest.groovy b/src/test/groovy/io/gofannon/gradle/cots/report/CotsReportGenerationTaskTest.groovy deleted file mode 100644 index 5764890..0000000 --- a/src/test/groovy/io/gofannon/gradle/cots/report/CotsReportGenerationTaskTest.groovy +++ /dev/null @@ -1,10 +0,0 @@ -package io.gofannon.gradle.cots.report - -import spock.lang.Specification -//import org.gradle.test.fixtures.AbstractProjectBuilderSpec -//import org.gradle.util.TestUtil -import org.gradle.testkit.runner.GradleRunner - -class CotsReportGenerationTaskTest /*extends AbstractProjectBuilderSpec*/ { - -} diff --git a/src/test/groovy/io/gofannon/gradle/cots/report/CotsReportHelper.groovy b/src/test/groovy/io/gofannon/gradle/cots/report/CotsReportHelper.groovy new file mode 100644 index 0000000..c491e18 --- /dev/null +++ b/src/test/groovy/io/gofannon/gradle/cots/report/CotsReportHelper.groovy @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.gofannon.gradle.cots.report + +class CotsReportHelper { + + static def extractConfigurations(File reportFile) { + return extractSectionElements(reportFile, "--- COTS configurations") + } + + private static def extractSectionElements(File reportFile, String sectionTitle) { + def inside = false + def configurationList = new ArrayList() + for (def line : reportFile.text.lines()) { + if (line == sectionTitle) { + inside = true + } else if (inside) { + if (line.isEmpty()) + break + configurationList.add(line) + } + } + return configurationList + } + + static def extractDependencies(File reportFile) { + return extractSectionElements(reportFile, "--- COTS dependencies") + } + + + static def formatToClickableUrl(File file) { + def url = file.toURI().toString() + if (url.startsWith("file:///")) + return url + if (url.startsWith("file:/")) + return url.replace("file:/", "file:///") + return "file:///" + url + } + +} diff --git a/src/test/groovy/io/gofannon/gradle/cots/report/CotsReportPluginTest.groovy b/src/test/groovy/io/gofannon/gradle/cots/report/CotsReportPluginTest.groovy new file mode 100644 index 0000000..5e2f6e7 --- /dev/null +++ b/src/test/groovy/io/gofannon/gradle/cots/report/CotsReportPluginTest.groovy @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.gofannon.gradle.cots.report + +import org.gradle.testfixtures.ProjectBuilder +import spock.lang.Specification + +class CotsReportPluginTest extends Specification { + + def "plugin shall be accessible"() { + given: + def project = ProjectBuilder.builder().build() + project.getPluginManager().apply("io.gofannon.cots-report") + + + expect: + project.getPluginManager().hasPlugin("io.gofannon.cots-report") + project.getTasks().named("cotsReport") != null + } + +}