From 49e19e3a93904f8c5c6fed1c4c92a81731bae5c9 Mon Sep 17 00:00:00 2001 From: ABHAY SINGH <75938293+ABHAY0O7@users.noreply.github.com> Date: Wed, 28 Jul 2021 19:47:01 +0530 Subject: [PATCH 1/7] Completed addition of optional parameter --- depclean-gradle-plugin/build.gradle | 31 ++++++++++++------- .../se/kth/depclean/DepCleanGradlePlugin.java | 6 ++++ .../se.kth.castor.depCleanGradlePlugin | 1 + 3 files changed, 27 insertions(+), 11 deletions(-) create mode 100644 depclean-gradle-plugin/src/main/resources/META-INF/gradle-plugins/se.kth.castor.depCleanGradlePlugin diff --git a/depclean-gradle-plugin/build.gradle b/depclean-gradle-plugin/build.gradle index e246e6e3..ee3d6b29 100644 --- a/depclean-gradle-plugin/build.gradle +++ b/depclean-gradle-plugin/build.gradle @@ -1,25 +1,34 @@ plugins { - id 'java' - id 'java-gradle-plugin' + id 'java' + id 'java-gradle-plugin' } group 'se.kth.castor' version '1.0-SNAPSHOT' repositories { - mavenLocal() - mavenCentral() + mavenLocal() + mavenCentral() } dependencies { - implementation(gradleApi()) - implementation('se.kth.castor:depclean-core:2.0.2-SNAPSHOT') - implementation('se.kth.castor:depclean-maven-plugin:2.0.2-SNAPSHOT') - compileOnly('org.projectlombok:lombok:1.18.20') - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0' + implementation(gradleApi()) + implementation('se.kth.castor:depclean-core:2.0.2-SNAPSHOT') + implementation('se.kth.castor:depclean-maven-plugin:2.0.2-SNAPSHOT') + compileOnly('org.projectlombok:lombok:1.18.20') + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0' } test { - useJUnitPlatform() + useJUnitPlatform() +} + +gradlePlugin { + plugins { + demoPlugin { + id = 'se.kth.castor.depclean-gradle-plugin' + implementationClass = 'se.kth.depclean.DepCleanGradlePlugin' + } + } } \ No newline at end of file diff --git a/depclean-gradle-plugin/src/main/java/se/kth/depclean/DepCleanGradlePlugin.java b/depclean-gradle-plugin/src/main/java/se/kth/depclean/DepCleanGradlePlugin.java index dcec0901..53687311 100644 --- a/depclean-gradle-plugin/src/main/java/se/kth/depclean/DepCleanGradlePlugin.java +++ b/depclean-gradle-plugin/src/main/java/se/kth/depclean/DepCleanGradlePlugin.java @@ -16,6 +16,12 @@ public class DepCleanGradlePlugin implements Plugin { @Override public void apply(@NotNull Project project) { + + final String depCleanConfigurationName = "depclean"; + + // Creating extra configurations for the plugin to provide more flexibility. + project.getExtensions().create(depCleanConfigurationName, DepCleanGradlePluginExtension.class); + // Creating the default task. createTask(project); } diff --git a/depclean-gradle-plugin/src/main/resources/META-INF/gradle-plugins/se.kth.castor.depCleanGradlePlugin b/depclean-gradle-plugin/src/main/resources/META-INF/gradle-plugins/se.kth.castor.depCleanGradlePlugin new file mode 100644 index 00000000..07712bad --- /dev/null +++ b/depclean-gradle-plugin/src/main/resources/META-INF/gradle-plugins/se.kth.castor.depCleanGradlePlugin @@ -0,0 +1 @@ +implementation-class = se.kth.depclean.DepCleanGradlePlugin \ No newline at end of file From 25d28442baca1fb120e3392bfb9b6840a5923599 Mon Sep 17 00:00:00 2001 From: ABHAY SINGH <75938293+ABHAY0O7@users.noreply.github.com> Date: Thu, 29 Jul 2021 00:22:20 +0530 Subject: [PATCH 2/7] Added test for an empty project This will ensure that plugin will not fail on an empty project --- .gitignore | 5 +-- depclean-gradle-plugin/.gitignore | 3 -- depclean-gradle-plugin/build.gradle | 5 ++- .../se/kth/depclean/DepCleanGradleFT.groovy | 34 +++++++++++++++++ .../resources-fts/empty_project/build.gradle | 37 +++++++++++++++++++ .../empty_project/settings.gradle | 5 +++ 6 files changed, 82 insertions(+), 7 deletions(-) delete mode 100644 depclean-gradle-plugin/.gitignore create mode 100644 depclean-gradle-plugin/src/test/groovy/se/kth/depclean/DepCleanGradleFT.groovy create mode 100644 depclean-gradle-plugin/src/test/resources-fts/empty_project/build.gradle create mode 100644 depclean-gradle-plugin/src/test/resources-fts/empty_project/settings.gradle diff --git a/.gitignore b/.gitignore index 68d638f9..cc1f7848 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ Gemfile.lock *.gem *.idea *.iml +*.gradle/ target/ @@ -15,13 +16,11 @@ depclean-maven-plugin/target depclean-core/depclean-core.iml depclean-core/target/generated-sources/ -depclean-gradle-plugin/.gradle/4.10.2/vcsMetadata-1/ -depclean-gradle-plugin/build/tmp/compileJava/ +depclean-gradle-plugin/build/ depclean-gradle-plugin/depclean-gradle-plugin.iml depclean-gradle-plugin/example/.gradle/4.10.2/vcsMetadata-1/ depclean-gradle-plugin/example/build/dependencies/ depclean-gradle-plugin/example/build/tmp/compileJava/ -depclean-gradle-plugin/target/generated-sources/ depclean-maven-plugin/depclean-maven-plugin.iml depclean-maven-plugin/target/generated-sources/ depclean-maven-plugin/target/generated-test-sources/ diff --git a/depclean-gradle-plugin/.gitignore b/depclean-gradle-plugin/.gitignore deleted file mode 100644 index 91c89aaa..00000000 --- a/depclean-gradle-plugin/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -.gradle/ -build/ - diff --git a/depclean-gradle-plugin/build.gradle b/depclean-gradle-plugin/build.gradle index ee3d6b29..64730edb 100644 --- a/depclean-gradle-plugin/build.gradle +++ b/depclean-gradle-plugin/build.gradle @@ -1,6 +1,8 @@ plugins { id 'java' + id 'groovy' id 'java-gradle-plugin' + id 'maven-publish' } group 'se.kth.castor' @@ -13,11 +15,12 @@ repositories { dependencies { implementation(gradleApi()) + implementation(gradleTestKit()) implementation('se.kth.castor:depclean-core:2.0.2-SNAPSHOT') implementation('se.kth.castor:depclean-maven-plugin:2.0.2-SNAPSHOT') compileOnly('org.projectlombok:lombok:1.18.20') + testImplementation('org.spockframework:spock-core:2.0-groovy-3.0') testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0' } test { diff --git a/depclean-gradle-plugin/src/test/groovy/se/kth/depclean/DepCleanGradleFT.groovy b/depclean-gradle-plugin/src/test/groovy/se/kth/depclean/DepCleanGradleFT.groovy new file mode 100644 index 00000000..3439f3fc --- /dev/null +++ b/depclean-gradle-plugin/src/test/groovy/se/kth/depclean/DepCleanGradleFT.groovy @@ -0,0 +1,34 @@ +package se.kth.depclean + +import org.apache.maven.BuildFailureException +import org.gradle.testfixtures.ProjectBuilder +import org.gradle.testkit.runner.BuildResult +import org.gradle.testkit.runner.GradleRunner +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import spock.lang.Specification +import static org.junit.jupiter.api.Assertions.assertEquals + +class DepCleanGradleFT extends Specification { + + File emptyProjectFile = new File("src/test/resources-fts/empty_project") + @Test + @DisplayName("Test that depclean gradle plugin runs on an empty project.") + def "pluginRunsOnEmptyProject"() { + given: + def project = ProjectBuilder.builder().withProjectDir(emptyProjectFile).build() + + when: + project.plugins.apply("se.kth.castor.depclean-gradle-plugin") + + then: + try { + BuildResult result = GradleRunner.create() + .withProjectDir(emptyProjectFile) + .withArguments("debloat") + .buildAndFail() + } catch (Exception e) { + assertEquals(e, BuildFailureException) + } + } +} \ No newline at end of file diff --git a/depclean-gradle-plugin/src/test/resources-fts/empty_project/build.gradle b/depclean-gradle-plugin/src/test/resources-fts/empty_project/build.gradle new file mode 100644 index 00000000..b0e2c7fa --- /dev/null +++ b/depclean-gradle-plugin/src/test/resources-fts/empty_project/build.gradle @@ -0,0 +1,37 @@ +buildscript{ + repositories { + mavenLocal() + + dependencies{ + classpath 'se.kth.castor:depclean-gradle-plugin:1.0-SNAPSHOT' + } + } +} +plugins { + id 'java' + id 'maven-publish' +} +apply plugin: 'se.kth.castor.depclean-gradle-plugin' +repositories { + mavenLocal() + maven { + url = uri('https://repo.maven.apache.org/maven2/') + } +} + +group = 'org.foo.bar' +version = '1.0.0-SNAPSHOT' +description = 'foobar' +java.sourceCompatibility = JavaVersion.VERSION_1_8 + +publishing { + publications { + maven(MavenPublication) { + from(components.java) + } + } +} + +tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' +} \ No newline at end of file diff --git a/depclean-gradle-plugin/src/test/resources-fts/empty_project/settings.gradle b/depclean-gradle-plugin/src/test/resources-fts/empty_project/settings.gradle new file mode 100644 index 00000000..fb50a0e9 --- /dev/null +++ b/depclean-gradle-plugin/src/test/resources-fts/empty_project/settings.gradle @@ -0,0 +1,5 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +rootProject.name = 'foobar' From e94255c196864c4a9f524f152ade6980b7f8d54d Mon Sep 17 00:00:00 2001 From: ABHAY SINGH <75938293+ABHAY0O7@users.noreply.github.com> Date: Thu, 29 Jul 2021 00:36:43 +0530 Subject: [PATCH 3/7] Added test for the correct working of plugin This test will ensure that the plugin returns the desired output only for unused dependences --- .gitignore | 2 +- .../se/kth/depclean/DepCleanGradleFT.groovy | 59 +++++++++++++++++++ .../all_dependencies_unused/build.gradle | 42 +++++++++++++ .../expectedOutputFile.txt | 18 ++++++ .../originalOutputFile.txt | 24 ++++++++ .../all_dependencies_unused/settings.gradle | 5 ++ .../src/main/java/Main.java | 9 +++ 7 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 depclean-gradle-plugin/src/test/resources-fts/all_dependencies_unused/build.gradle create mode 100644 depclean-gradle-plugin/src/test/resources-fts/all_dependencies_unused/expectedOutputFile.txt create mode 100644 depclean-gradle-plugin/src/test/resources-fts/all_dependencies_unused/originalOutputFile.txt create mode 100644 depclean-gradle-plugin/src/test/resources-fts/all_dependencies_unused/settings.gradle create mode 100644 depclean-gradle-plugin/src/test/resources-fts/all_dependencies_unused/src/main/java/Main.java diff --git a/.gitignore b/.gitignore index cc1f7848..a0ecaf88 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ Gemfile.lock *.idea *.iml *.gradle/ +build/ target/ @@ -16,7 +17,6 @@ depclean-maven-plugin/target depclean-core/depclean-core.iml depclean-core/target/generated-sources/ -depclean-gradle-plugin/build/ depclean-gradle-plugin/depclean-gradle-plugin.iml depclean-gradle-plugin/example/.gradle/4.10.2/vcsMetadata-1/ depclean-gradle-plugin/example/build/dependencies/ diff --git a/depclean-gradle-plugin/src/test/groovy/se/kth/depclean/DepCleanGradleFT.groovy b/depclean-gradle-plugin/src/test/groovy/se/kth/depclean/DepCleanGradleFT.groovy index 3439f3fc..61137cdc 100644 --- a/depclean-gradle-plugin/src/test/groovy/se/kth/depclean/DepCleanGradleFT.groovy +++ b/depclean-gradle-plugin/src/test/groovy/se/kth/depclean/DepCleanGradleFT.groovy @@ -6,8 +6,11 @@ import org.gradle.testkit.runner.BuildResult import org.gradle.testkit.runner.GradleRunner import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test +import se.kth.depclean.util.FileUtils import spock.lang.Specification import static org.junit.jupiter.api.Assertions.assertEquals +import static org.gradle.testkit.runner.TaskOutcome.* +import static org.junit.jupiter.api.Assertions.assertTrue class DepCleanGradleFT extends Specification { @@ -31,4 +34,60 @@ class DepCleanGradleFT extends Specification { assertEquals(e, BuildFailureException) } } + + String projectPath1 = "src/Test/resources-fts/all_dependencies_unused" + File allDependenciesUnused = new File(projectPath1) + File originalOutputFile1 = new File(projectPath1 + "/originalOutputFile.txt") + File expectedOutputFile1 = new File(projectPath1 + "/expectedOutputFile.txt") + @Test + @DisplayName("Test that depclean gradle plugin runs on a project which has only unused dependencies.") + def "all_dependencies_unused"() { + given: + def project = ProjectBuilder.builder().withProjectDir(allDependenciesUnused).build() + + when: + project.plugins.apply("se.kth.castor.depclean-gradle-plugin") + BuildResult buildResult = createRunner(allDependenciesUnused, "build") + BuildResult debloatResult = createRunner(allDependenciesUnused, "debloat") + + then: + assertEquals(SUCCESS, buildResult.task(":build").getOutcome()) + assertEquals(SUCCESS, debloatResult.task(":debloat").getOutcome()) + + originalOutputFile1.write(debloatResult.getOutput()) + assertTrue(compareOutputs(expectedOutputFile1, originalOutputFile1)) + FileUtils.forceDelete(new File(projectPath1 + "/build")) + } + + private static BuildResult createRunner(File project, String argument) { + BuildResult result = GradleRunner.create() + .withProjectDir(project) + .withArguments(argument) + .build() + return result + } + + private static boolean compareOutputs(File expectedOutputFile, File originalOutputFile) { + + FileReader fileReader1 = new FileReader(expectedOutputFile) + FileReader fileReader2 = new FileReader(originalOutputFile) + BufferedReader reader1 = new BufferedReader(fileReader1) + BufferedReader reader2 = new BufferedReader(fileReader2) + + String line1, line2 + while (true) { + // Continue while there are equal lines + line1 = reader1.readLine() + line2 = reader2.readLine() + + if (line1 == null) { + // End of file 1 + return 1 + } + if (!line1.trim().equalsIgnoreCase(line2.trim())) { + // Different lines, or end of file 2 + return 0 + } + } + } } \ No newline at end of file diff --git a/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_unused/build.gradle b/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_unused/build.gradle new file mode 100644 index 00000000..b589e9e0 --- /dev/null +++ b/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_unused/build.gradle @@ -0,0 +1,42 @@ +buildscript{ + repositories { + mavenLocal() + + dependencies{ + classpath 'se.kth.castor:depclean-gradle-plugin:1.0-SNAPSHOT' + } + } +} +plugins { + id 'java' + id 'maven-publish' +} + +apply plugin: ('se.kth.castor.depclean-gradle-plugin') +repositories { + mavenLocal() + maven { + url = uri('https://repo.maven.apache.org/maven2/') + } +} + +dependencies { + implementation 'com.fasterxml.jackson.core:jackson-databind:2.12.2' +} + +group = 'org.foo.bar' +version = '1.0.0-SNAPSHOT' +description = 'foobar' +java.sourceCompatibility = JavaVersion.VERSION_1_8 + +publishing { + publications { + maven(MavenPublication) { + from(components.java) + } + } +} + +tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' +} diff --git a/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_unused/expectedOutputFile.txt b/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_unused/expectedOutputFile.txt new file mode 100644 index 00000000..91bb1bbe --- /dev/null +++ b/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_unused/expectedOutputFile.txt @@ -0,0 +1,18 @@ + +> Task :debloat +Starting DepClean dependency analysis +------------------------------------------------------- + D E P C L E A N A N A L Y S I S R E S U L T S +------------------------------------------------------- +------------------------------------------------------- +USED DIRECT DEPENDENCIES [0]: +USED INHERITED DEPENDENCIES [0]: +USED TRANSITIVE DEPENDENCIES [0]: +POTENTIALLY UNUSED DIRECT DEPENDENCIES [1]: + com.fasterxml.jackson.core:jackson-databind:2.12.2:compile (1 MB) +POTENTIALLY UNUSED INHERITED DEPENDENCIES [0]: +POTENTIALLY UNUSED TRANSITIVE DEPENDENCIES [2]: + com.fasterxml.jackson.core:jackson-core:2.12.2:compile (356 KB) + com.fasterxml.jackson.core:jackson-annotations:2.12.2:compile (73 KB) +------------------------------------------------------- + diff --git a/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_unused/originalOutputFile.txt b/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_unused/originalOutputFile.txt new file mode 100644 index 00000000..feb57f64 --- /dev/null +++ b/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_unused/originalOutputFile.txt @@ -0,0 +1,24 @@ + +> Task :debloat +Starting DepClean dependency analysis +------------------------------------------------------- + D E P C L E A N A N A L Y S I S R E S U L T S +------------------------------------------------------- +------------------------------------------------------- +USED DIRECT DEPENDENCIES [0]: +USED INHERITED DEPENDENCIES [0]: +USED TRANSITIVE DEPENDENCIES [0]: +POTENTIALLY UNUSED DIRECT DEPENDENCIES [1]: + com.fasterxml.jackson.core:jackson-databind:2.12.2:compile (1 MB) +POTENTIALLY UNUSED INHERITED DEPENDENCIES [0]: +POTENTIALLY UNUSED TRANSITIVE DEPENDENCIES [2]: + com.fasterxml.jackson.core:jackson-core:2.12.2:compile (356 KB) + com.fasterxml.jackson.core:jackson-annotations:2.12.2:compile (73 KB) +------------------------------------------------------- + +Deprecated Gradle features were used in this build, making it incompatible with Gradle 8.0. +Use '--warning-mode all' to show the individual deprecation warnings. +See https://docs.gradle.org/7.0.2/userguide/command_line_interface.html#sec:command_line_warnings + +BUILD SUCCESSFUL in 1s +1 actionable task: 1 executed diff --git a/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_unused/settings.gradle b/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_unused/settings.gradle new file mode 100644 index 00000000..fb50a0e9 --- /dev/null +++ b/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_unused/settings.gradle @@ -0,0 +1,5 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +rootProject.name = 'foobar' diff --git a/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_unused/src/main/java/Main.java b/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_unused/src/main/java/Main.java new file mode 100644 index 00000000..be2ecc8b --- /dev/null +++ b/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_unused/src/main/java/Main.java @@ -0,0 +1,9 @@ +// import com.fasterxml.jackson.databind.ObjectMapper; +//import com.fasterxml.jackson.core.util.Separators; + + +public class Main { + // private static final ObjectMapper converter = new ObjectMapper(); +// Separators separators = new Separators(); + int field = 42; +} \ No newline at end of file From ba0534c9c4f845ada6cc147b9f8c609573868dcf Mon Sep 17 00:00:00 2001 From: ABHAY SINGH <75938293+ABHAY0O7@users.noreply.github.com> Date: Thu, 29 Jul 2021 00:39:13 +0530 Subject: [PATCH 4/7] Added test for the correct working of plugin This test will ensure that the plugin returns the desired output only for used dependences --- .../se/kth/depclean/DepCleanGradleFT.groovy | 24 ++++++++++ .../all_dependencies_used/build.gradle | 44 +++++++++++++++++++ .../expectedOutputFile.txt | 18 ++++++++ .../originalOutputFile.txt | 24 ++++++++++ .../all_dependencies_used/settings.gradle | 5 +++ .../src/main/java/Main.java | 15 +++++++ 6 files changed, 130 insertions(+) create mode 100644 depclean-gradle-plugin/src/test/resources-fts/all_dependencies_used/build.gradle create mode 100644 depclean-gradle-plugin/src/test/resources-fts/all_dependencies_used/expectedOutputFile.txt create mode 100644 depclean-gradle-plugin/src/test/resources-fts/all_dependencies_used/originalOutputFile.txt create mode 100644 depclean-gradle-plugin/src/test/resources-fts/all_dependencies_used/settings.gradle create mode 100644 depclean-gradle-plugin/src/test/resources-fts/all_dependencies_used/src/main/java/Main.java diff --git a/depclean-gradle-plugin/src/test/groovy/se/kth/depclean/DepCleanGradleFT.groovy b/depclean-gradle-plugin/src/test/groovy/se/kth/depclean/DepCleanGradleFT.groovy index 61137cdc..fb9cb7ca 100644 --- a/depclean-gradle-plugin/src/test/groovy/se/kth/depclean/DepCleanGradleFT.groovy +++ b/depclean-gradle-plugin/src/test/groovy/se/kth/depclean/DepCleanGradleFT.groovy @@ -59,6 +59,30 @@ class DepCleanGradleFT extends Specification { FileUtils.forceDelete(new File(projectPath1 + "/build")) } + String projectPath2 = "src/Test/resources-fts/all_dependencies_used" + File allDependenciesUsed = new File(projectPath2) + File originalOutputFile2 = new File(projectPath2 + "/originalOutputFile.txt") + File expectedOutputFile2 = new File(projectPath2 + "/expectedOutputFile.txt") + @Test + @DisplayName("Test that depclean gradle plugin runs on a project which has only used dependencies.") + def "all_dependencies_used"() { + given: + def project = ProjectBuilder.builder().withProjectDir(allDependenciesUsed).build() + + when: + project.plugins.apply("se.kth.castor.depclean-gradle-plugin") + BuildResult buildResult = createRunner(allDependenciesUsed, "build") + BuildResult debloatResult = createRunner(allDependenciesUsed, "debloat") + + then: + assertEquals(SUCCESS, buildResult.task(":build").getOutcome()) + assertEquals(SUCCESS, debloatResult.task(":debloat").getOutcome()) + + originalOutputFile2.write(debloatResult.getOutput()) + assertTrue(compareOutputs(expectedOutputFile2, originalOutputFile2)) + FileUtils.forceDelete(new File(projectPath2 + "/build")) + } + private static BuildResult createRunner(File project, String argument) { BuildResult result = GradleRunner.create() .withProjectDir(project) diff --git a/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_used/build.gradle b/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_used/build.gradle new file mode 100644 index 00000000..ab2a4e33 --- /dev/null +++ b/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_used/build.gradle @@ -0,0 +1,44 @@ +buildscript{ + repositories { + mavenLocal() + + dependencies{ + classpath 'se.kth.castor:depclean-gradle-plugin:1.0-SNAPSHOT' + } + } +} + +plugins { + id 'java' + id 'maven-publish' +} + +apply plugin: 'se.kth.castor.depclean-gradle-plugin' + +repositories { + mavenLocal() + maven { + url = uri('https://repo.maven.apache.org/maven2/') + } +} + +dependencies { + implementation 'com.fasterxml.jackson.core:jackson-databind:2.12.2' +} + +group = 'org.foo.bar' +version = '1.0.0-SNAPSHOT' +description = 'foobar' +java.sourceCompatibility = JavaVersion.VERSION_1_8 + +publishing { + publications { + maven(MavenPublication) { + from(components.java) + } + } +} + +tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' +} \ No newline at end of file diff --git a/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_used/expectedOutputFile.txt b/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_used/expectedOutputFile.txt new file mode 100644 index 00000000..6a4000ae --- /dev/null +++ b/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_used/expectedOutputFile.txt @@ -0,0 +1,18 @@ + +> Task :debloat +Starting DepClean dependency analysis +------------------------------------------------------- + D E P C L E A N A N A L Y S I S R E S U L T S +------------------------------------------------------- +------------------------------------------------------- +USED DIRECT DEPENDENCIES [1]: + com.fasterxml.jackson.core:jackson-databind:2.12.2:compile (1 MB) +USED INHERITED DEPENDENCIES [0]: +USED TRANSITIVE DEPENDENCIES [2]: + com.fasterxml.jackson.core:jackson-core:2.12.2:compile (356 KB) + com.fasterxml.jackson.core:jackson-annotations:2.12.2:compile (73 KB) +POTENTIALLY UNUSED DIRECT DEPENDENCIES [0]: +POTENTIALLY UNUSED INHERITED DEPENDENCIES [0]: +POTENTIALLY UNUSED TRANSITIVE DEPENDENCIES [0]: +------------------------------------------------------- + diff --git a/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_used/originalOutputFile.txt b/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_used/originalOutputFile.txt new file mode 100644 index 00000000..f7c61c7e --- /dev/null +++ b/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_used/originalOutputFile.txt @@ -0,0 +1,24 @@ + +> Task :debloat +Starting DepClean dependency analysis +------------------------------------------------------- + D E P C L E A N A N A L Y S I S R E S U L T S +------------------------------------------------------- +------------------------------------------------------- +USED DIRECT DEPENDENCIES [1]: + com.fasterxml.jackson.core:jackson-databind:2.12.2:compile (1 MB) +USED INHERITED DEPENDENCIES [0]: +USED TRANSITIVE DEPENDENCIES [2]: + com.fasterxml.jackson.core:jackson-core:2.12.2:compile (356 KB) + com.fasterxml.jackson.core:jackson-annotations:2.12.2:compile (73 KB) +POTENTIALLY UNUSED DIRECT DEPENDENCIES [0]: +POTENTIALLY UNUSED INHERITED DEPENDENCIES [0]: +POTENTIALLY UNUSED TRANSITIVE DEPENDENCIES [0]: +------------------------------------------------------- + +Deprecated Gradle features were used in this build, making it incompatible with Gradle 8.0. +Use '--warning-mode all' to show the individual deprecation warnings. +See https://docs.gradle.org/7.0.2/userguide/command_line_interface.html#sec:command_line_warnings + +BUILD SUCCESSFUL in 3s +1 actionable task: 1 executed diff --git a/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_used/settings.gradle b/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_used/settings.gradle new file mode 100644 index 00000000..fb50a0e9 --- /dev/null +++ b/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_used/settings.gradle @@ -0,0 +1,5 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +rootProject.name = 'foobar' diff --git a/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_used/src/main/java/Main.java b/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_used/src/main/java/Main.java new file mode 100644 index 00000000..7db23676 --- /dev/null +++ b/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_used/src/main/java/Main.java @@ -0,0 +1,15 @@ + import java.util.Map; + + import com.fasterxml.jackson.annotation.JsonAnyGetter; + import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.core.util.Separators; + +public class Main { + private static final ObjectMapper converter = new ObjectMapper(); + Separators separators = new Separators(); + private Map properties; + @JsonAnyGetter + public Map getProperties() { + return properties; + } +} \ No newline at end of file From a6f8f9bf5974bff28d5efec157045994feae3667 Mon Sep 17 00:00:00 2001 From: ABHAY SINGH <75938293+ABHAY0O7@users.noreply.github.com> Date: Thu, 29 Jul 2021 00:43:23 +0530 Subject: [PATCH 5/7] Added test for the correct working of plugin with optional parameter This test will ensure that the plugin runs successfully with 'createBuildDebloated' parameter. --- .../se/kth/depclean/DepCleanGradleFT.groovy | 22 +++++++++ .../originalOutputFile.txt | 2 +- .../build.gradle | 47 +++++++++++++++++++ .../debloated-dependencies.gradle | 6 +++ .../settings.gradle | 5 ++ .../src/main/java/Main.java | 9 ++++ 6 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 depclean-gradle-plugin/src/test/resources-fts/debloated_dependencies.gradle_is_correct/build.gradle create mode 100644 depclean-gradle-plugin/src/test/resources-fts/debloated_dependencies.gradle_is_correct/debloated-dependencies.gradle create mode 100644 depclean-gradle-plugin/src/test/resources-fts/debloated_dependencies.gradle_is_correct/settings.gradle create mode 100644 depclean-gradle-plugin/src/test/resources-fts/debloated_dependencies.gradle_is_correct/src/main/java/Main.java diff --git a/depclean-gradle-plugin/src/test/groovy/se/kth/depclean/DepCleanGradleFT.groovy b/depclean-gradle-plugin/src/test/groovy/se/kth/depclean/DepCleanGradleFT.groovy index fb9cb7ca..1b1c91ff 100644 --- a/depclean-gradle-plugin/src/test/groovy/se/kth/depclean/DepCleanGradleFT.groovy +++ b/depclean-gradle-plugin/src/test/groovy/se/kth/depclean/DepCleanGradleFT.groovy @@ -83,6 +83,28 @@ class DepCleanGradleFT extends Specification { FileUtils.forceDelete(new File(projectPath2 + "/build")) } + String projectPath3 = "src/test/resources-fts/debloated_dependencies.gradle_is_correct" + File debloatedDependenciesIsCorrect = new File(projectPath3) + File generatedDebloatedDependenciesDotGradle = new File(projectPath3 + "/debloated-dependencies.gradle"); + @Test + @DisplayName("Test that the depclean creates a proper debloated-dependencies.gradle file.") + def "debloated_dependencies.gradle_is_correct"() { + given: + def project = ProjectBuilder.builder().withProjectDir(debloatedDependenciesIsCorrect).build() + + when: + project.plugins.apply("se.kth.castor.depclean-gradle-plugin") + BuildResult buildResult = createRunner(debloatedDependenciesIsCorrect, "build") + BuildResult debloatResult = createRunner(debloatedDependenciesIsCorrect, "debloat") + + then: + assertEquals(SUCCESS, buildResult.task(":build").getOutcome()) + assertEquals(SUCCESS, debloatResult.task(":debloat").getOutcome()) + + assertTrue(generatedDebloatedDependenciesDotGradle.exists()) + FileUtils.forceDelete(new File(projectPath3 + "/build")) + } + private static BuildResult createRunner(File project, String argument) { BuildResult result = GradleRunner.create() .withProjectDir(project) diff --git a/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_used/originalOutputFile.txt b/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_used/originalOutputFile.txt index f7c61c7e..8abae736 100644 --- a/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_used/originalOutputFile.txt +++ b/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_used/originalOutputFile.txt @@ -20,5 +20,5 @@ Deprecated Gradle features were used in this build, making it incompatible with Use '--warning-mode all' to show the individual deprecation warnings. See https://docs.gradle.org/7.0.2/userguide/command_line_interface.html#sec:command_line_warnings -BUILD SUCCESSFUL in 3s +BUILD SUCCESSFUL in 2s 1 actionable task: 1 executed diff --git a/depclean-gradle-plugin/src/test/resources-fts/debloated_dependencies.gradle_is_correct/build.gradle b/depclean-gradle-plugin/src/test/resources-fts/debloated_dependencies.gradle_is_correct/build.gradle new file mode 100644 index 00000000..aba2f15c --- /dev/null +++ b/depclean-gradle-plugin/src/test/resources-fts/debloated_dependencies.gradle_is_correct/build.gradle @@ -0,0 +1,47 @@ +buildscript{ + repositories { + mavenLocal() + + dependencies{ + classpath 'se.kth.castor:depclean-gradle-plugin:1.0-SNAPSHOT' + } + } +} + +plugins { + id 'java' + id 'maven-publish' +} + +apply plugin: ('se.kth.castor.depclean-gradle-plugin') +repositories { + mavenLocal() + maven { + url = uri('https://repo.maven.apache.org/maven2/') + } +} + +dependencies { + implementation 'com.fasterxml.jackson.core:jackson-databind:2.12.2' + compileOnly 'org.mapstruct:mapstruct-processor:1.4.2.Final' +} + +group = 'org.foo.bar' +version = '1.0.0-SNAPSHOT' +description = 'foobar' +java.sourceCompatibility = JavaVersion.VERSION_1_8 + +publishing { + publications { + maven(MavenPublication) { + from(components.java) + } + } +} + +tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' +} +depclean { + createBuildDebloated=true +} diff --git a/depclean-gradle-plugin/src/test/resources-fts/debloated_dependencies.gradle_is_correct/debloated-dependencies.gradle b/depclean-gradle-plugin/src/test/resources-fts/debloated_dependencies.gradle_is_correct/debloated-dependencies.gradle new file mode 100644 index 00000000..cf7b0335 --- /dev/null +++ b/depclean-gradle-plugin/src/test/resources-fts/debloated_dependencies.gradle_is_correct/debloated-dependencies.gradle @@ -0,0 +1,6 @@ +dependencies { + implementation 'com.fasterxml.jackson.core:jackson-core:2.12.2', + 'com.fasterxml.jackson.core:jackson-databind:2.12.2', + 'com.fasterxml.jackson.core:jackson-annotations:2.12.2' + +} diff --git a/depclean-gradle-plugin/src/test/resources-fts/debloated_dependencies.gradle_is_correct/settings.gradle b/depclean-gradle-plugin/src/test/resources-fts/debloated_dependencies.gradle_is_correct/settings.gradle new file mode 100644 index 00000000..fb50a0e9 --- /dev/null +++ b/depclean-gradle-plugin/src/test/resources-fts/debloated_dependencies.gradle_is_correct/settings.gradle @@ -0,0 +1,5 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +rootProject.name = 'foobar' diff --git a/depclean-gradle-plugin/src/test/resources-fts/debloated_dependencies.gradle_is_correct/src/main/java/Main.java b/depclean-gradle-plugin/src/test/resources-fts/debloated_dependencies.gradle_is_correct/src/main/java/Main.java new file mode 100644 index 00000000..9105e3c6 --- /dev/null +++ b/depclean-gradle-plugin/src/test/resources-fts/debloated_dependencies.gradle_is_correct/src/main/java/Main.java @@ -0,0 +1,9 @@ +// import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.core.util.Separators; + + +public class Main { + // private static final ObjectMapper converter = new ObjectMapper(); + Separators separators = new Separators(); + int field = 42; +} \ No newline at end of file From a2609a7ea01ed2532c4d80c8c3984d0be6822294 Mon Sep 17 00:00:00 2001 From: ABHAY SINGH <75938293+ABHAY0O7@users.noreply.github.com> Date: Thu, 29 Jul 2021 01:26:21 +0530 Subject: [PATCH 6/7] Updated .gitignore Ignored original output files from resources --- .gitignore | 2 ++ .../originalOutputFile.txt | 24 ------------------- .../originalOutputFile.txt | 24 ------------------- .../debloated-dependencies.gradle | 6 ----- 4 files changed, 2 insertions(+), 54 deletions(-) delete mode 100644 depclean-gradle-plugin/src/test/resources-fts/all_dependencies_unused/originalOutputFile.txt delete mode 100644 depclean-gradle-plugin/src/test/resources-fts/all_dependencies_used/originalOutputFile.txt delete mode 100644 depclean-gradle-plugin/src/test/resources-fts/debloated_dependencies.gradle_is_correct/debloated-dependencies.gradle diff --git a/.gitignore b/.gitignore index a0ecaf88..a6dfd05c 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,8 @@ depclean-gradle-plugin/depclean-gradle-plugin.iml depclean-gradle-plugin/example/.gradle/4.10.2/vcsMetadata-1/ depclean-gradle-plugin/example/build/dependencies/ depclean-gradle-plugin/example/build/tmp/compileJava/ +depclean-gradle-plugin/src/test/resources-fts/*/originalOutputFile.txt +depclean-gradle-plugin/src/test/resources-fts/*/debloated-dependencies.gradle depclean-maven-plugin/depclean-maven-plugin.iml depclean-maven-plugin/target/generated-sources/ depclean-maven-plugin/target/generated-test-sources/ diff --git a/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_unused/originalOutputFile.txt b/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_unused/originalOutputFile.txt deleted file mode 100644 index feb57f64..00000000 --- a/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_unused/originalOutputFile.txt +++ /dev/null @@ -1,24 +0,0 @@ - -> Task :debloat -Starting DepClean dependency analysis -------------------------------------------------------- - D E P C L E A N A N A L Y S I S R E S U L T S -------------------------------------------------------- -------------------------------------------------------- -USED DIRECT DEPENDENCIES [0]: -USED INHERITED DEPENDENCIES [0]: -USED TRANSITIVE DEPENDENCIES [0]: -POTENTIALLY UNUSED DIRECT DEPENDENCIES [1]: - com.fasterxml.jackson.core:jackson-databind:2.12.2:compile (1 MB) -POTENTIALLY UNUSED INHERITED DEPENDENCIES [0]: -POTENTIALLY UNUSED TRANSITIVE DEPENDENCIES [2]: - com.fasterxml.jackson.core:jackson-core:2.12.2:compile (356 KB) - com.fasterxml.jackson.core:jackson-annotations:2.12.2:compile (73 KB) -------------------------------------------------------- - -Deprecated Gradle features were used in this build, making it incompatible with Gradle 8.0. -Use '--warning-mode all' to show the individual deprecation warnings. -See https://docs.gradle.org/7.0.2/userguide/command_line_interface.html#sec:command_line_warnings - -BUILD SUCCESSFUL in 1s -1 actionable task: 1 executed diff --git a/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_used/originalOutputFile.txt b/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_used/originalOutputFile.txt deleted file mode 100644 index 8abae736..00000000 --- a/depclean-gradle-plugin/src/test/resources-fts/all_dependencies_used/originalOutputFile.txt +++ /dev/null @@ -1,24 +0,0 @@ - -> Task :debloat -Starting DepClean dependency analysis -------------------------------------------------------- - D E P C L E A N A N A L Y S I S R E S U L T S -------------------------------------------------------- -------------------------------------------------------- -USED DIRECT DEPENDENCIES [1]: - com.fasterxml.jackson.core:jackson-databind:2.12.2:compile (1 MB) -USED INHERITED DEPENDENCIES [0]: -USED TRANSITIVE DEPENDENCIES [2]: - com.fasterxml.jackson.core:jackson-core:2.12.2:compile (356 KB) - com.fasterxml.jackson.core:jackson-annotations:2.12.2:compile (73 KB) -POTENTIALLY UNUSED DIRECT DEPENDENCIES [0]: -POTENTIALLY UNUSED INHERITED DEPENDENCIES [0]: -POTENTIALLY UNUSED TRANSITIVE DEPENDENCIES [0]: -------------------------------------------------------- - -Deprecated Gradle features were used in this build, making it incompatible with Gradle 8.0. -Use '--warning-mode all' to show the individual deprecation warnings. -See https://docs.gradle.org/7.0.2/userguide/command_line_interface.html#sec:command_line_warnings - -BUILD SUCCESSFUL in 2s -1 actionable task: 1 executed diff --git a/depclean-gradle-plugin/src/test/resources-fts/debloated_dependencies.gradle_is_correct/debloated-dependencies.gradle b/depclean-gradle-plugin/src/test/resources-fts/debloated_dependencies.gradle_is_correct/debloated-dependencies.gradle deleted file mode 100644 index cf7b0335..00000000 --- a/depclean-gradle-plugin/src/test/resources-fts/debloated_dependencies.gradle_is_correct/debloated-dependencies.gradle +++ /dev/null @@ -1,6 +0,0 @@ -dependencies { - implementation 'com.fasterxml.jackson.core:jackson-core:2.12.2', - 'com.fasterxml.jackson.core:jackson-databind:2.12.2', - 'com.fasterxml.jackson.core:jackson-annotations:2.12.2' - -} From f5ebc813097cb22692a36785931d89e52191b609 Mon Sep 17 00:00:00 2001 From: ABHAY SINGH <75938293+ABHAY0O7@users.noreply.github.com> Date: Wed, 4 Aug 2021 23:05:02 +0530 Subject: [PATCH 7/7] Implemented createResultJson and createClassUsageCsv parameters These parameters will create a .json and .csv file respectively of the debloated result of dependencies generated by depclean-gradle-plugin which can be used in large data analysis --- .gitignore | 1 + depclean-gradle-plugin/build.gradle | 2 +- .../se/kth/depclean/DepCleanGradleAction.java | 53 +++- ...efaultGradleProjectDependencyAnalyzer.java | 27 +- .../depclean/utils/json/writeJsonResult.java | 261 ++++++++++++++++++ .../se/kth/depclean/DepCleanGradleFT.groovy | 38 ++- .../json_should_be_correct/build.gradle | 46 +++ .../json_should_be_correct/settings.gradle | 5 + .../src/main/java/org/UseCommonsCodec.java | 11 + .../src/main/java/org/UseJcabiManifest.java | 11 + 10 files changed, 442 insertions(+), 13 deletions(-) create mode 100644 depclean-gradle-plugin/src/main/java/se/kth/depclean/utils/json/writeJsonResult.java create mode 100644 depclean-gradle-plugin/src/test/resources-fts/json_should_be_correct/build.gradle create mode 100644 depclean-gradle-plugin/src/test/resources-fts/json_should_be_correct/settings.gradle create mode 100644 depclean-gradle-plugin/src/test/resources-fts/json_should_be_correct/src/main/java/org/UseCommonsCodec.java create mode 100644 depclean-gradle-plugin/src/test/resources-fts/json_should_be_correct/src/main/java/org/UseJcabiManifest.java diff --git a/.gitignore b/.gitignore index a6dfd05c..411feec7 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ Gemfile.lock *.iml *.gradle/ build/ +userHome/ target/ diff --git a/depclean-gradle-plugin/build.gradle b/depclean-gradle-plugin/build.gradle index 64730edb..fea0608a 100644 --- a/depclean-gradle-plugin/build.gradle +++ b/depclean-gradle-plugin/build.gradle @@ -34,4 +34,4 @@ gradlePlugin { implementationClass = 'se.kth.depclean.DepCleanGradlePlugin' } } -} \ No newline at end of file +} diff --git a/depclean-gradle-plugin/src/main/java/se/kth/depclean/DepCleanGradleAction.java b/depclean-gradle-plugin/src/main/java/se/kth/depclean/DepCleanGradleAction.java index db0904d8..76ba4c94 100644 --- a/depclean-gradle-plugin/src/main/java/se/kth/depclean/DepCleanGradleAction.java +++ b/depclean-gradle-plugin/src/main/java/se/kth/depclean/DepCleanGradleAction.java @@ -3,7 +3,9 @@ import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import java.io.File; +import java.io.FileWriter; import java.io.IOException; +import java.nio.charset.Charset; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Comparator; @@ -32,6 +34,7 @@ import se.kth.depclean.analysis.GradleProjectDependencyAnalysis; import se.kth.depclean.core.analysis.ProjectDependencyAnalyzerException; import se.kth.depclean.util.JarUtils; +import se.kth.depclean.utils.json.writeJsonResult; /** * Depclean default and only action. @@ -60,7 +63,6 @@ public class DepCleanGradleAction implements Action { private boolean failIfUnusedTransitive; private boolean failIfUnusedInherited; private boolean createBuildDebloated; - // TODO : The implementation of next two parameters will be done later. private boolean createResultJson; private boolean createClassUsageCsv; private Set ignoreConfiguration; @@ -402,6 +404,50 @@ public void execute(@NotNull Project project) { logger.lifecycle("debloated-dependencies.gradle file created in: " + pathToDebloatedDependencies); } + + /* Writing the JSON file with the debloat results */ + if (createResultJson) { + printString("Creating depclean-results.json, please wait..."); + final File jsonFile = projectDirPath.resolve("build" + File.separator + "depclean-results.json").toFile(); + final File classUsageFile = projectDirPath.resolve("build" + File.separator + "class-usage.csv").toFile(); + if (createClassUsageCsv) { + printString("Creating class-usage.csv, please wait..."); + try { + FileUtils.write(classUsageFile, "OriginClass,TargetClass,Dependency\n", Charset.defaultCharset()); + } catch (IOException e) { + logger.error("Error writing the CSV header."); + } + } + writeJsonResult writeJsonResult = new writeJsonResult( + project, + classUsageFile, + dependencyAnalyzer, + SizeOfDependencies, + createClassUsageCsv, + declaredDependencies, + usedDirectArtifactsCoordinates, + usedInheritedArtifactsCoordinates, + usedTransitiveArtifactsCoordinates, + unusedDirectArtifactsCoordinates, + unusedInheritedArtifactsCoordinates, + unusedTransitiveArtifactsCoordinates + ); + try { + FileWriter fw = new FileWriter(jsonFile, Charset.defaultCharset()); + writeJsonResult.write(fw); + fw.flush(); + fw.close(); + } catch (IOException e) { + logger.error("Unable to generate JSON file."); + } + if (jsonFile.exists()) { + logger.lifecycle("depclean-results.json file created in: " + jsonFile.getAbsolutePath()); + } + if (classUsageFile.exists()) { + logger.lifecycle("class-usage.csv file created in: " + classUsageFile.getAbsolutePath()); + } + } + } /** @@ -561,9 +607,7 @@ private String getSize(final String dependency) { * @return Name of artifact */ public static String getName(final ResolvedArtifact artifact) { - String[] artifactGroupArtifactIds = artifact.toString().split(" \\("); - String[] artifactGroupArtifactId = artifactGroupArtifactIds[1].split("\\)"); - return artifactGroupArtifactId[0] + ":" + ArtifactConfigurationMap.get(artifact); + return artifact.getModuleVersion() + ":" + ArtifactConfigurationMap.get(artifact); } /** @@ -624,5 +668,4 @@ public Set getAllChildren(final Set allD } return allChildren; } - } diff --git a/depclean-gradle-plugin/src/main/java/se/kth/depclean/analysis/DefaultGradleProjectDependencyAnalyzer.java b/depclean-gradle-plugin/src/main/java/se/kth/depclean/analysis/DefaultGradleProjectDependencyAnalyzer.java index c2ca5b4e..80ca972a 100644 --- a/depclean-gradle-plugin/src/main/java/se/kth/depclean/analysis/DefaultGradleProjectDependencyAnalyzer.java +++ b/depclean-gradle-plugin/src/main/java/se/kth/depclean/analysis/DefaultGradleProjectDependencyAnalyzer.java @@ -22,8 +22,8 @@ import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.artifacts.ResolvedArtifact; import org.gradle.api.artifacts.ResolvedDependency; -import se.kth.depclean.utils.DependencyUtils; import se.kth.depclean.core.analysis.ArtifactTypes; +import se.kth.depclean.utils.DependencyUtils; import se.kth.depclean.core.analysis.ClassAnalyzer; import se.kth.depclean.core.analysis.DefaultClassAnalyzer; import se.kth.depclean.core.analysis.DependencyAnalyzer; @@ -311,4 +311,29 @@ private Set removeAll( return results; } + /** + * Computes a map of [artifact] -> [allTypes, usedTypes]. + * + * @return A map of [artifact] -> [allTypes, usedTypes] + */ + public Map getArtifactClassesMap() { + Map output = new HashMap<>(); + for (Map.Entry> entry : artifactClassesMap.entrySet()) { + ResolvedArtifact key = entry.getKey(); + if (artifactUsedClassesMap.containsKey(key)) { + output.put(key.getModuleVersion().toString(), + new ArtifactTypes( + artifactClassesMap.get(key), // get all the types + artifactUsedClassesMap.get(key) // get used types + )); + } else { + output.put(key.getModuleVersion().toString(), + new ArtifactTypes( + artifactClassesMap.get(key), // get all the types + new HashSet<>() // get used types + )); + } + } + return output; + } } diff --git a/depclean-gradle-plugin/src/main/java/se/kth/depclean/utils/json/writeJsonResult.java b/depclean-gradle-plugin/src/main/java/se/kth/depclean/utils/json/writeJsonResult.java new file mode 100644 index 00000000..d8e463a2 --- /dev/null +++ b/depclean-gradle-plugin/src/main/java/se/kth/depclean/utils/json/writeJsonResult.java @@ -0,0 +1,261 @@ +package se.kth.depclean.utils.json; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.Map; +import java.util.Set; +import org.apache.commons.io.FileUtils; +import org.gradle.api.Project; +import org.gradle.api.artifacts.ResolvedDependency; +import org.jetbrains.annotations.NotNull; +import com.google.gson.stream.JsonWriter; +import lombok.extern.slf4j.Slf4j; +import se.kth.depclean.analysis.DefaultGradleProjectDependencyAnalyzer; +import se.kth.depclean.core.analysis.graph.DefaultCallGraph; + +/** + * Uses the DepClean analysis results and the declared dependencies of the project + * to produce a JSON file. This file represent the structure of the dependency + * tree enriched with metadata of the usage or not of each dependency. + */ +@Slf4j +public class writeJsonResult { + + private final Project project; + private final Set allDependencies; + private final Map sizeOfDependencies; + private final DefaultGradleProjectDependencyAnalyzer dependencyAnalyzer; + private final Set usedDirectArtifactsCoordinates; + private final Set usedInheritedArtifactsCoordinates; + private final Set usedTransitiveArtifactsCoordinates; + private final Set unusedDirectArtifactsCoordinates; + private final Set unusedInheritedArtifactsCoordinates; + private final Set unusedTransitiveArtifactsCoordinates; + private final File classUsageFile; + private final boolean createClassUsageCsv; + + /** + * Ctor. + */ + public writeJsonResult(Project project, + File classUsageFile, + DefaultGradleProjectDependencyAnalyzer dependencyAnalyzer, + Map sizeOfDependencies, + boolean createClassUsageCsv, + Set declaredDependencies, + Set usedDirectArtifactsCoordinates, + Set usedInheritedArtifactsCoordinates, + Set usedTransitiveArtifactsCoordinates, + Set unusedDirectArtifactsCoordinates, + Set unusedInheritedArtifactsCoordinates, + Set unusedTransitiveArtifactsCoordinates) { + this.project = project; + this.classUsageFile = classUsageFile; + this.allDependencies = declaredDependencies; + this.dependencyAnalyzer = dependencyAnalyzer; + this.sizeOfDependencies = sizeOfDependencies; + this.createClassUsageCsv = createClassUsageCsv; + this.usedDirectArtifactsCoordinates = usedDirectArtifactsCoordinates; + this.usedInheritedArtifactsCoordinates = usedInheritedArtifactsCoordinates; + this.usedTransitiveArtifactsCoordinates = usedTransitiveArtifactsCoordinates; + this.unusedDirectArtifactsCoordinates = unusedDirectArtifactsCoordinates; + this.unusedInheritedArtifactsCoordinates = unusedInheritedArtifactsCoordinates; + this.unusedTransitiveArtifactsCoordinates = unusedTransitiveArtifactsCoordinates; + } + + /** + * Write the result.json file of debloated result. + * + * @param fw File to be generated. + * @throws IOException If the JSON file cannot be written. + */ + public void write(FileWriter fw) throws IOException { + + BufferedWriter bw = new BufferedWriter(fw, 512000); + JsonWriter jsonWriter = new JsonWriter(bw); + + /* First adding the project artifact as the json parent. */ + String projectGroupId = project.getGroup().toString(); + String projectArtifactId = project.getName(); + String projectVersion = project.getVersion().toString(); + String projectId = projectGroupId + ":" + projectArtifactId + ":" + projectVersion; + String projectCoordinates = projectId + ":" + null; + String projectJar = projectArtifactId + "-" + projectVersion + ".jar"; + + if (createClassUsageCsv) { + writeClassUsageCsv(projectId); + } + + jsonWriter.setIndent(" "); + JsonWriter localWriter = jsonWriter.beginObject() + + .name("coordinates") + .value(projectCoordinates) + + .name("id") + .value(projectId) + + .name("groupId") + .value(projectGroupId) + + .name("artifactId") + .value(projectArtifactId) + + .name("version") + .value(projectVersion) + + .name("size") + .value(sizeOfDependencies.get(projectJar)) + + .name("type") + .value(getType(projectCoordinates)) + + .name("status") + .value(getStatus(projectCoordinates)); + + writeAllTypes(projectId, localWriter); + writeUsedTypes(projectId, localWriter); + writeUsageRatio(projectId, localWriter); + + /* Now writing the project's dependencies as children of project. */ + writeChild(jsonWriter, allDependencies); + jsonWriter.endArray().endObject(); + bw.flush(); + bw.close(); + } + + private void writeChild(JsonWriter jsonWriter, Set allDependencies) throws IOException { + for (ResolvedDependency dependency : allDependencies) { + String dependencyId = dependency.getName(); + String configuration = dependency.getConfiguration(); + String coordinates = dependencyId + ":" + configuration; + String groupId = dependency.getModuleGroup(); + String artifactId = coordinates.split(":")[1]; + String version = dependency.getModuleVersion(); + String dependencyJar = artifactId + "-" + version + ".jar"; + + if (createClassUsageCsv) { + writeClassUsageCsv(dependencyId); + } + + JsonWriter childWriter = jsonWriter.beginObject() + + .name("coordinates") + .value(coordinates) + + .name("id") + .value(dependencyId) + + .name("groupId") + .value(groupId) + + .name("artifactId") + .value(artifactId) + + .name("version") + .value(version) + + .name("configuration") + .value(configuration) + + .name("size") + .value(sizeOfDependencies.get(dependencyJar)) + + .name("type") + .value(getType(coordinates)) + + .name("status") + .value(getStatus(coordinates)); + + writeParent(dependency, childWriter); + writeAllTypes(dependencyId, childWriter); + writeUsedTypes(dependencyId, childWriter); + writeUsageRatio(dependencyId, childWriter); + + if (!dependency.getChildren().isEmpty()) { + this.writeChild(childWriter, dependency.getChildren()); + } + jsonWriter.endArray() + .endObject(); + } + } + + private void writeParent(ResolvedDependency dependency, JsonWriter childWriter) throws IOException { + JsonWriter localWriter = childWriter.name("parent(s)").beginArray(); + if (!dependency.getParents().isEmpty()) { + for (ResolvedDependency parent : dependency.getParents()) { + localWriter.value(parent.toString()); + } + } + localWriter.endArray(); + } + + @NotNull + private String getStatus(String coordinates) { + return (usedDirectArtifactsCoordinates.contains(coordinates) || usedInheritedArtifactsCoordinates + .contains(coordinates) || usedTransitiveArtifactsCoordinates.contains(coordinates)) + ? "used" : + (unusedDirectArtifactsCoordinates.contains(coordinates) || unusedInheritedArtifactsCoordinates + .contains(coordinates) || unusedTransitiveArtifactsCoordinates.contains(coordinates)) + ? "bloated" : "unknown"; + } + + @NotNull + private String getType(String coordinates) { + return (usedDirectArtifactsCoordinates.contains(coordinates) || unusedDirectArtifactsCoordinates + .contains(coordinates)) ? "direct" : + (usedInheritedArtifactsCoordinates.contains(coordinates) || unusedInheritedArtifactsCoordinates + .contains(coordinates)) ? "inherited" : + (usedTransitiveArtifactsCoordinates.contains(coordinates) || unusedTransitiveArtifactsCoordinates + .contains(coordinates)) ? "transitive" : "unknown"; + } + + private void writeUsageRatio(String dependencyId, JsonWriter localWriter) throws IOException { + localWriter.name("usageRatio") + .value(dependencyAnalyzer.getArtifactClassesMap().containsKey(dependencyId) + ? dependencyAnalyzer.getArtifactClassesMap().get(dependencyId).getAllTypes().isEmpty() + ? 0 : // handle division by zero + ((double) dependencyAnalyzer.getArtifactClassesMap().get(dependencyId).getUsedTypes().size() + / dependencyAnalyzer.getArtifactClassesMap().get(dependencyId).getAllTypes().size()) : -1) + .name("children(s)") + .beginArray(); + } + + private void writeUsedTypes(String dependencyId, JsonWriter localWriter) throws IOException { + JsonWriter usedTypes = localWriter.name("usedTypes").beginArray(); + if (dependencyAnalyzer.getArtifactClassesMap().containsKey(dependencyId)) { + for (String usedType : dependencyAnalyzer.getArtifactClassesMap().get(dependencyId).getUsedTypes()) { + usedTypes.value(usedType); + } + } + usedTypes.endArray(); + } + + private void writeAllTypes(String dependencyId, JsonWriter localWriter) throws IOException { + JsonWriter allTypes = localWriter.name("allTypes").beginArray(); + if (dependencyAnalyzer.getArtifactClassesMap().containsKey(dependencyId)) { + for (String allType : dependencyAnalyzer.getArtifactClassesMap().get(dependencyId).getAllTypes()) { + allTypes.value(allType); + } + } + allTypes.endArray(); + } + + private void writeClassUsageCsv(String dependencyId) throws IOException { + DefaultCallGraph defaultCallGraph = new DefaultCallGraph(); + for (Map.Entry> usagePerClassMap : defaultCallGraph.getUsagesPerClass().entrySet()) { + String key = usagePerClassMap.getKey(); + Set value = usagePerClassMap.getValue(); + for (String s : value) { + if (dependencyAnalyzer.getArtifactClassesMap().containsKey(dependencyId) && dependencyAnalyzer + .getArtifactClassesMap().get(dependencyId).getAllTypes().contains(s)) { + String triplet = key + "," + s + "," + dependencyId + "\n"; + FileUtils.write(classUsageFile, triplet, Charset.defaultCharset(), true); + } + } + } + } +} diff --git a/depclean-gradle-plugin/src/test/groovy/se/kth/depclean/DepCleanGradleFT.groovy b/depclean-gradle-plugin/src/test/groovy/se/kth/depclean/DepCleanGradleFT.groovy index 1b1c91ff..dee3631b 100644 --- a/depclean-gradle-plugin/src/test/groovy/se/kth/depclean/DepCleanGradleFT.groovy +++ b/depclean-gradle-plugin/src/test/groovy/se/kth/depclean/DepCleanGradleFT.groovy @@ -4,6 +4,7 @@ import org.apache.maven.BuildFailureException import org.gradle.testfixtures.ProjectBuilder import org.gradle.testkit.runner.BuildResult import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import se.kth.depclean.util.FileUtils @@ -51,8 +52,8 @@ class DepCleanGradleFT extends Specification { BuildResult debloatResult = createRunner(allDependenciesUnused, "debloat") then: - assertEquals(SUCCESS, buildResult.task(":build").getOutcome()) - assertEquals(SUCCESS, debloatResult.task(":debloat").getOutcome()) + assertTrue(checkTaskOutcome(buildResult.task(":build").getOutcome())); + assertTrue(checkTaskOutcome(debloatResult.task(":debloat").getOutcome())); originalOutputFile1.write(debloatResult.getOutput()) assertTrue(compareOutputs(expectedOutputFile1, originalOutputFile1)) @@ -75,8 +76,8 @@ class DepCleanGradleFT extends Specification { BuildResult debloatResult = createRunner(allDependenciesUsed, "debloat") then: - assertEquals(SUCCESS, buildResult.task(":build").getOutcome()) - assertEquals(SUCCESS, debloatResult.task(":debloat").getOutcome()) + assertTrue(checkTaskOutcome(buildResult.task(":build").getOutcome())); + assertTrue(checkTaskOutcome(debloatResult.task(":debloat").getOutcome())); originalOutputFile2.write(debloatResult.getOutput()) assertTrue(compareOutputs(expectedOutputFile2, originalOutputFile2)) @@ -98,13 +99,34 @@ class DepCleanGradleFT extends Specification { BuildResult debloatResult = createRunner(debloatedDependenciesIsCorrect, "debloat") then: - assertEquals(SUCCESS, buildResult.task(":build").getOutcome()) - assertEquals(SUCCESS, debloatResult.task(":debloat").getOutcome()) + assertTrue(checkTaskOutcome(buildResult.task(":build").getOutcome())); + assertTrue(checkTaskOutcome(debloatResult.task(":debloat").getOutcome())); assertTrue(generatedDebloatedDependenciesDotGradle.exists()) FileUtils.forceDelete(new File(projectPath3 + "/build")) } + String projectPath4 = "src/test/resources-fts/json_should_be_correct" + File json_should_be_correct = new File(projectPath4) + File generatedResultDotJson = new File(projectPath4 + "/build/depclean-results.json"); + @Test + @DisplayName("Test that the depclean creates a proper results.json file.") + def "json_should_be_correct"() { + given: + def project = ProjectBuilder.builder().withProjectDir(json_should_be_correct).build() + + when: + project.plugins.apply("se.kth.castor.depclean-gradle-plugin") + BuildResult buildResult = createRunner(json_should_be_correct, "build") + BuildResult debloatResult = createRunner(json_should_be_correct, "debloat") + + then: + assertTrue(checkTaskOutcome(buildResult.task(":build").getOutcome())); + assertTrue(checkTaskOutcome(debloatResult.task(":debloat").getOutcome())); + + assertTrue(generatedResultDotJson.exists()) + } + private static BuildResult createRunner(File project, String argument) { BuildResult result = GradleRunner.create() .withProjectDir(project) @@ -136,4 +158,8 @@ class DepCleanGradleFT extends Specification { } } } + + private static boolean checkTaskOutcome(TaskOutcome outcome) { + return outcome == SUCCESS || outcome == UP_TO_DATE; + } } \ No newline at end of file diff --git a/depclean-gradle-plugin/src/test/resources-fts/json_should_be_correct/build.gradle b/depclean-gradle-plugin/src/test/resources-fts/json_should_be_correct/build.gradle new file mode 100644 index 00000000..3907c881 --- /dev/null +++ b/depclean-gradle-plugin/src/test/resources-fts/json_should_be_correct/build.gradle @@ -0,0 +1,46 @@ +buildscript{ + repositories { + mavenLocal() + + dependencies{ + classpath 'se.kth.castor:depclean-gradle-plugin:1.0-SNAPSHOT' + } + } +} +plugins { + id 'java' + id 'maven-publish' +} +apply plugin: ('se.kth.castor.depclean-gradle-plugin') +repositories { + mavenLocal() + maven { + url = uri('https://repo.maven.apache.org/maven2/') + } +} + +dependencies { + implementation 'com.jcabi:jcabi-manifests:1.1' + implementation 'commons-io:commons-io:2.8.0' + implementation 'commons-codec:commons-codec:1.15' +} + +group = 'org.foo.bar' +version = '1.0.0-SNAPSHOT' +description = 'foobar' +java.sourceCompatibility = JavaVersion.VERSION_1_8 + +publishing { + publications { + maven(MavenPublication) { + from(components.java) + } + } +} + +tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' +} +depclean { + createResultJson=true +} diff --git a/depclean-gradle-plugin/src/test/resources-fts/json_should_be_correct/settings.gradle b/depclean-gradle-plugin/src/test/resources-fts/json_should_be_correct/settings.gradle new file mode 100644 index 00000000..fb50a0e9 --- /dev/null +++ b/depclean-gradle-plugin/src/test/resources-fts/json_should_be_correct/settings.gradle @@ -0,0 +1,5 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +rootProject.name = 'foobar' diff --git a/depclean-gradle-plugin/src/test/resources-fts/json_should_be_correct/src/main/java/org/UseCommonsCodec.java b/depclean-gradle-plugin/src/test/resources-fts/json_should_be_correct/src/main/java/org/UseCommonsCodec.java new file mode 100644 index 00000000..3dd41d30 --- /dev/null +++ b/depclean-gradle-plugin/src/test/resources-fts/json_should_be_correct/src/main/java/org/UseCommonsCodec.java @@ -0,0 +1,11 @@ +package org; + +import org.apache.commons.codec.Charsets; + +public class UseCommonsCodec { + + public static void main(String[] args) { + System.out.println(Charsets.UTF_8); + } + +} diff --git a/depclean-gradle-plugin/src/test/resources-fts/json_should_be_correct/src/main/java/org/UseJcabiManifest.java b/depclean-gradle-plugin/src/test/resources-fts/json_should_be_correct/src/main/java/org/UseJcabiManifest.java new file mode 100644 index 00000000..4d16e10c --- /dev/null +++ b/depclean-gradle-plugin/src/test/resources-fts/json_should_be_correct/src/main/java/org/UseJcabiManifest.java @@ -0,0 +1,11 @@ +package org; + +import com.jcabi.manifests.Manifests; + +public class UseJcabiManifest { + + public static void main(String[] args) { + Manifests manifests = new Manifests(); + System.out.println(manifests.size()); + } +}