├── .img ├── logo.ai ├── logo.svg └── wasp.svg ├── src ├── test │ ├── resources │ │ ├── checker-qual-3.8.0.jar │ │ ├── auto-value-annotations-1.8.1.jar │ │ └── pom.xml │ ├── resources-its │ │ └── se │ │ │ └── kth │ │ │ └── deptrim │ │ │ └── DepTrimMojoIT │ │ │ ├── ignore_scopes │ │ │ ├── src │ │ │ │ ├── main │ │ │ │ │ └── java │ │ │ │ │ │ └── UseCommonsCodec.java │ │ │ │ └── test │ │ │ │ │ └── java │ │ │ │ │ └── UseJcabiXMLTest.java │ │ │ └── pom.xml │ │ │ ├── ignore_types │ │ │ ├── src │ │ │ │ └── main │ │ │ │ │ └── java │ │ │ │ │ └── org │ │ │ │ │ ├── UseCommonsCodec.java │ │ │ │ │ └── UseJcabiManifest.java │ │ │ └── pom.xml │ │ │ ├── all_pom_specialized │ │ │ ├── src │ │ │ │ └── main │ │ │ │ │ └── java │ │ │ │ │ └── Main.java │ │ │ └── pom.xml │ │ │ ├── ignore_dependencies │ │ │ ├── src │ │ │ │ └── main │ │ │ │ │ └── java │ │ │ │ │ └── Main.java │ │ │ └── pom.xml │ │ │ └── empty_project │ │ │ └── pom.xml │ └── java │ │ └── se │ │ └── kth │ │ └── deptrim │ │ ├── util │ │ ├── TimeUtilsTest.java │ │ ├── JarUtilsTest.java │ │ ├── FileUtilsTest.java │ │ └── PomUtilsTest.java │ │ ├── core │ │ └── TypesExtractorTest.java │ │ └── DepTrimMojoIT.java └── main │ ├── resources │ ├── log4j2-includes │ │ └── console-appender_pattern-layout_colored.xml │ └── log4j2.xml │ └── java │ └── se │ └── kth │ └── deptrim │ ├── util │ ├── TimeUtils.java │ ├── FileUtils.java │ ├── JarUtils.java │ └── PomUtils.java │ ├── io │ └── ConsolePrinter.java │ ├── core │ ├── SpecializedDependency.java │ ├── TypesExtractor.java │ ├── TypesUsageAnalyzer.java │ └── Specializer.java │ ├── DepTrimManager.java │ └── DepTrimMojo.java ├── renovate.json ├── .gitattributes ├── .gitignore ├── LICENSE ├── .github └── workflows │ ├── release.yml │ └── build.yml ├── README.md ├── checkstyle.xml └── pom.xml /.img/logo.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ASSERT-KTH/deptrim/HEAD/.img/logo.ai -------------------------------------------------------------------------------- /src/test/resources/checker-qual-3.8.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ASSERT-KTH/deptrim/HEAD/src/test/resources/checker-qual-3.8.0.jar -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /src/test/resources/auto-value-annotations-1.8.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ASSERT-KTH/deptrim/HEAD/src/test/resources/auto-value-annotations-1.8.1.jar -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Check out all text files in UNIX format, with LF as end of line 2 | # Don't change this file. If you have any ideas about it, please 3 | # submit a separate issue about it and we'll discuss. 4 | 5 | * text=auto eol=lf 6 | *.java ident 7 | *.xml ident 8 | *.png binary -------------------------------------------------------------------------------- /src/test/resources-its/se/kth/deptrim/DepTrimMojoIT/ignore_scopes/src/main/java/UseCommonsCodec.java: -------------------------------------------------------------------------------- 1 | import org.apache.commons.codec.Charsets; 2 | 3 | public class UseCommonsCodec { 4 | 5 | public static void main(String[] args) { 6 | System.out.println(Charsets.UTF_8); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/test/resources-its/se/kth/deptrim/DepTrimMojoIT/ignore_types/src/main/java/org/UseCommonsCodec.java: -------------------------------------------------------------------------------- 1 | package org; 2 | 3 | import org.apache.commons.codec.Charsets; 4 | 5 | public class UseCommonsCodec { 6 | 7 | public static void main(String[] args) { 8 | System.out.println(Charsets.UTF_8); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/test/resources-its/se/kth/deptrim/DepTrimMojoIT/ignore_types/src/main/java/org/UseJcabiManifest.java: -------------------------------------------------------------------------------- 1 | package org; 2 | 3 | import com.jcabi.manifests.Manifests; 4 | 5 | public class UseJcabiManifest { 6 | 7 | public static void main(String[] args) { 8 | Manifests manifests = new Manifests(); 9 | System.out.println(manifests.size()); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/resources/log4j2-includes/console-appender_pattern-layout_colored.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /src/test/java/se/kth/deptrim/util/TimeUtilsTest.java: -------------------------------------------------------------------------------- 1 | package se.kth.deptrim.util; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Test; 6 | 7 | class TimeUtilsTest { 8 | 9 | TimeUtils timeUtils; 10 | 11 | @BeforeEach 12 | void setUp() { 13 | timeUtils = new TimeUtils(); 14 | } 15 | 16 | @Test 17 | void ifGettingTheTime_ThenTimeIsCorrect() { 18 | Assertions.assertEquals("1min 40s", timeUtils.toHumanReadableTime(100000)); 19 | } 20 | } -------------------------------------------------------------------------------- /src/main/java/se/kth/deptrim/util/TimeUtils.java: -------------------------------------------------------------------------------- 1 | package se.kth.deptrim.util; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | /** 6 | * Utility class for handling time. 7 | */ 8 | public class TimeUtils { 9 | 10 | /** 11 | * Convert milliseconds to a human-readable format. 12 | * 13 | * @param millis The milliseconds to be converted. 14 | * @return A string representing the time. 15 | */ 16 | public String toHumanReadableTime(long millis) { 17 | long minutes = TimeUnit.MILLISECONDS.toMinutes(millis); 18 | long seconds = (TimeUnit.MILLISECONDS.toSeconds(millis) % 60); 19 | return String.format("%smin %ss", minutes, seconds); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/test/resources-its/se/kth/deptrim/DepTrimMojoIT/ignore_scopes/src/test/java/UseJcabiXMLTest.java: -------------------------------------------------------------------------------- 1 | import com.jcabi.xml.XML; 2 | import com.jcabi.xml.XMLDocument; 3 | import java.util.List; 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.Test; 6 | 7 | class UseJcabiXMLTest { 8 | 9 | @Test 10 | void test() { 11 | XML xml = new XMLDocument("Coffee to go"); 12 | 13 | String id = xml.xpath("//order/@id").get(0); 14 | List xpath = xml.xpath("//order[@id=4]/text()"); 15 | 16 | Assertions.assertEquals("[Coffee to go]", xpath.toString()); 17 | Assertions.assertEquals("4", id.toString()); 18 | 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /src/test/resources-its/se/kth/deptrim/DepTrimMojoIT/all_pom_specialized/src/main/java/Main.java: -------------------------------------------------------------------------------- 1 | import com.google.common.collect.ImmutableMap; 2 | import java.util.Map; 3 | import org.apache.commons.io.FileUtils; 4 | 5 | public class Main { 6 | 7 | public static void main(String[] args) { 8 | // Use guava 9 | Map salary = ImmutableMap.builder() 10 | .put("John", 1000) 11 | .put("Jane", 1500) 12 | .put("Adam", 2000) 13 | .put("Tom", 2000) 14 | .build(); 15 | salary.forEach((key, value) -> System.out.println(key + " -> " + value)); 16 | 17 | 18 | // Use commons-io 19 | System.out.println(FileUtils.getTempDirectory().getAbsolutePath()); 20 | 21 | } 22 | } -------------------------------------------------------------------------------- /src/test/resources-its/se/kth/deptrim/DepTrimMojoIT/ignore_dependencies/src/main/java/Main.java: -------------------------------------------------------------------------------- 1 | import com.google.common.collect.ImmutableMap; 2 | import java.util.Map; 3 | import org.apache.commons.io.FileUtils; 4 | 5 | public class Main { 6 | 7 | public static void main(String[] args) { 8 | // Use guava 9 | Map salary = ImmutableMap.builder() 10 | .put("John", 1000) 11 | .put("Jane", 1500) 12 | .put("Adam", 2000) 13 | .put("Tom", 2000) 14 | .build(); 15 | salary.forEach((key, value) -> System.out.println(key + " -> " + value)); 16 | 17 | 18 | // Use commons-io 19 | System.out.println(FileUtils.getTempDirectory().getAbsolutePath()); 20 | 21 | } 22 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### IntelliJ IDEA ### 2 | /.idea/ 3 | 4 | ### Eclipse ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### NetBeans ### 14 | /nbproject/private/ 15 | /nbbuild/ 16 | /dist/ 17 | /nbdist/ 18 | /.nb-gradle/ 19 | build/ 20 | !**/src/main/**/build/ 21 | !**/src/test/**/build/ 22 | 23 | ### VS Code ### 24 | .vscode/ 25 | 26 | ### Mac OS ### 27 | .DS_Store 28 | 29 | ### Maven ### 30 | target/ 31 | pom.xml.tag 32 | pom.xml.releaseBackup 33 | pom.xml.versionsBackup 34 | pom.xml.next 35 | release.properties 36 | dependency-reduced-pom.xml 37 | buildNumber.properties 38 | .mvn/timing.properties 39 | # https://github.com/takari/maven-wrapper#usage-without-binary-jar 40 | .mvn/wrapper/maven-wrapper.jar 41 | !.mvn/wrapper/maven-wrapper.jar 42 | !**/src/main/**/target/ 43 | !**/src/test/**/target/ 44 | 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 CASTOR-Software 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/test/resources-its/se/kth/deptrim/DepTrimMojoIT/empty_project/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.foo.bar 7 | foobar 8 | 1.0.0-SNAPSHOT 9 | jar 10 | foobar 11 | 12 | 13 | UTF-8 14 | UTF-8 15 | 11 16 | 11 17 | 18 | 19 | 20 | 21 | 22 | se.kth.castor 23 | deptrim-maven-plugin 24 | 0.0.1 25 | 26 | 27 | 28 | deptrim 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/test/java/se/kth/deptrim/util/JarUtilsTest.java: -------------------------------------------------------------------------------- 1 | package se.kth.deptrim.util; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | import org.junit.jupiter.api.AfterEach; 8 | import org.junit.jupiter.api.Assertions; 9 | import org.junit.jupiter.api.BeforeEach; 10 | import org.junit.jupiter.api.Test; 11 | import org.junit.jupiter.api.io.TempDir; 12 | 13 | class JarUtilsTest { 14 | 15 | @TempDir 16 | Path tempDir; 17 | 18 | File jarFile = new File("test.jar"); 19 | 20 | @BeforeEach 21 | void setUp() { 22 | try { 23 | // add folder to tempDir 24 | Files.createDirectory(tempDir.resolve("META-INF")); 25 | // add file to the older in tempDir 26 | Path tempFile = Files.createFile(tempDir.resolve("META-INF/Manifest.mf")); 27 | Files.writeString(tempFile, "Manifest-Version: 1.0\n" 28 | + "Created-By: 1.7.0_06 (Oracle Corporation)"); 29 | } catch (IOException e) { 30 | System.out.println("Error creating temporary folder for testing."); 31 | } 32 | } 33 | 34 | @Test 35 | void ifCreateJarFromDirectory_ThenJarShouldExist() throws Exception { 36 | JarUtils.createJarFromDirectory(tempDir.toFile(), jarFile); 37 | Assertions.assertTrue(jarFile.exists() && !jarFile.isDirectory()); 38 | } 39 | 40 | @AfterEach 41 | void tearDown() { 42 | jarFile.delete(); 43 | } 44 | } -------------------------------------------------------------------------------- /src/test/java/se/kth/deptrim/util/FileUtilsTest.java: -------------------------------------------------------------------------------- 1 | package se.kth.deptrim.util; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.nio.file.Files; 6 | import java.util.Objects; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.junit.jupiter.api.Assertions; 9 | import org.junit.jupiter.api.Test; 10 | 11 | @Slf4j 12 | class FileUtilsTest { 13 | 14 | @Test 15 | void testDeleteEmptyDirectories() throws IOException { 16 | // Create a temporary directory for testing 17 | File tempDir = Files.createTempDirectory("temp").toFile(); 18 | // Create some empty directories within the temporary directory 19 | File dir1 = new File(tempDir, "dir1"); 20 | dir1.mkdir(); 21 | File dir2 = new File(tempDir, "dir2"); 22 | dir2.mkdir(); 23 | File dir3 = new File(dir1, "dir3"); 24 | dir3.mkdir(); 25 | // Verify that the temporary directory contains the expected number of directories 26 | Assertions.assertEquals(2, Objects.requireNonNull(tempDir.listFiles()).length); 27 | // Call the deleteEmptyDirectories method and verify that it deletes all empty directories 28 | FileUtils fileUtils = new FileUtils(); 29 | fileUtils.deleteEmptyDirectories(tempDir); 30 | Assertions.assertEquals(3, fileUtils.getDeletedDirectories()); 31 | Assertions.assertEquals(0, Objects.requireNonNull(tempDir.listFiles()).length); 32 | // Clean up the temporary directory 33 | tempDir.delete(); 34 | } 35 | } -------------------------------------------------------------------------------- /src/main/java/se/kth/deptrim/util/FileUtils.java: -------------------------------------------------------------------------------- 1 | package se.kth.deptrim.util; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.nio.file.Files; 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | import java.util.Objects; 10 | import lombok.Getter; 11 | import lombok.extern.slf4j.Slf4j; 12 | 13 | /** 14 | * Utility class for handling files. 15 | */ 16 | @Slf4j 17 | @Getter 18 | public class FileUtils { 19 | 20 | private int deletedDirectories = 0; 21 | 22 | /** 23 | * Delete all empty directories in the given directory. 24 | * 25 | * @param directory the directory to delete empty directories from 26 | */ 27 | public int deleteEmptyDirectories(File directory) { 28 | List toSort = new ArrayList<>(); 29 | for (File f : Objects.requireNonNull(directory.listFiles())) { 30 | toSort.add(f); 31 | } 32 | toSort.sort(null); 33 | List toBeDeleted = new ArrayList<>(); 34 | for (File f : toSort) { 35 | if (f.isDirectory()) { 36 | if (f.listFiles().length == deleteEmptyDirectories(f)) { 37 | toBeDeleted.add(f); 38 | } 39 | } 40 | } 41 | int size = toBeDeleted.size(); 42 | toBeDeleted.forEach(t -> { 43 | try { 44 | Files.delete(t.toPath()); 45 | deletedDirectories++; 46 | } catch (IOException e) { 47 | log.error("Error deleting file " + t.getAbsolutePath()); 48 | } 49 | }); 50 | return size; // the number of deleted directories. 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/se/kth/deptrim/io/ConsolePrinter.java: -------------------------------------------------------------------------------- 1 | package se.kth.deptrim.io; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | import se.kth.depclean.core.analysis.model.ProjectDependencyAnalysis; 6 | import se.kth.depclean.core.model.ClassName; 7 | 8 | /** 9 | * A class that prints messages to the console. 10 | */ 11 | public class ConsolePrinter { 12 | 13 | /** 14 | * Prints the results of the DepClean dependency usage analysis. 15 | * 16 | * @param projectDependencyAnalysis the analysis results of each dependency. 17 | */ 18 | public void printDependencyUsageAnalysis(ProjectDependencyAnalysis projectDependencyAnalysis) { 19 | printString("ALL TYPES"); 20 | projectDependencyAnalysis.getDependencyClassesMap().forEach((key, value) -> printString(key.getFile().getName() + " -> " + value.getAllTypes())); 21 | printString("USED TYPES"); 22 | projectDependencyAnalysis.getDependencyClassesMap().forEach((key, value) -> printString(key.getFile().getName() + " -> " + value.getUsedTypes())); 23 | printString("UNUSED TYPES"); 24 | projectDependencyAnalysis.getDependencyClassesMap().forEach((key, value) -> { 25 | Set tmp = new HashSet<>(); 26 | tmp.addAll(value.getAllTypes()); 27 | tmp.removeAll(value.getUsedTypes()); 28 | printString(key.getFile().getName() + " -> " + tmp); 29 | }); 30 | } 31 | 32 | /** 33 | * Prints a string to the console. 34 | * 35 | * @param string the string to print. 36 | */ 37 | private void printString(final String string) { 38 | System.out.println(string); //NOSONAR avoid a warning of non-used logger 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 18 | 19 | 20 | 21 | 23 | 24 | 25 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /.img/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/java/se/kth/deptrim/core/SpecializedDependency.java: -------------------------------------------------------------------------------- 1 | package se.kth.deptrim.core; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * This class represents the original coordinates of a dependency and the coordinates of its trimmed version. 7 | */ 8 | @Getter 9 | public class SpecializedDependency { 10 | 11 | String originalGroupId; 12 | String originalArtifactId; 13 | String originalVersion; 14 | String specializedGroupId; 15 | String specializedArtifactId; 16 | String specializedVersion; 17 | 18 | /** 19 | * Constructor. 20 | * 21 | * @param originalGroupId The original group id of the dependency. 22 | * @param originalArtifactId The original id of the dependency. 23 | * @param originalVersion The original version of the dependency. 24 | * @param specializedGroupId The standard group id of the dependency trimmed by deptrim. 25 | */ 26 | public SpecializedDependency( 27 | String originalGroupId, 28 | String originalArtifactId, 29 | String originalVersion, 30 | String specializedGroupId 31 | ) { 32 | this.originalGroupId = originalGroupId; 33 | this.originalArtifactId = originalArtifactId; 34 | this.originalVersion = originalVersion; 35 | this.specializedGroupId = specializedGroupId; 36 | this.specializedArtifactId = originalArtifactId; 37 | this.specializedVersion = originalVersion; 38 | } 39 | 40 | /** 41 | * toString method. 42 | * 43 | * @return the string representation of the object. 44 | */ 45 | @Override 46 | public String toString() { 47 | return "SpecializedDependency{" 48 | + "originalGroupId='" + originalGroupId + '\'' 49 | + ", originalArtifactId='" + originalArtifactId + '\'' 50 | + ", originalVersion='" + originalVersion + '\'' 51 | + ", specializedGroupId='" + specializedGroupId + '\'' 52 | + ", specializedArtifactId='" + specializedArtifactId + '\'' 53 | + ", specializedVersion='" + specializedVersion + '\'' 54 | + '}'; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/test/resources-its/se/kth/deptrim/DepTrimMojoIT/ignore_types/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.foo.bar 7 | foobar 8 | 1.0.0-SNAPSHOT 9 | jar 10 | foobar 11 | 12 | 13 | UTF-8 14 | UTF-8 15 | 11 16 | 11 17 | 18 | 19 | 20 | 21 | com.jcabi 22 | jcabi-manifests 23 | 1.1 24 | 25 | 26 | com.jcabi 27 | jcabi-log 28 | 29 | 30 | 31 | 32 | commons-io 33 | commons-io 34 | 2.8.0 35 | 36 | 37 | commons-codec 38 | commons-codec 39 | 1.15 40 | 41 | 42 | 43 | 44 | 45 | 46 | se.kth.castor 47 | deptrim-maven-plugin 48 | 0.0.1 49 | 50 | 51 | 52 | deptrim 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/main/java/se/kth/deptrim/core/TypesExtractor.java: -------------------------------------------------------------------------------- 1 | package se.kth.deptrim.core; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import lombok.SneakyThrows; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.apache.commons.io.FileUtils; 8 | import se.kth.depclean.core.model.Dependency; 9 | import se.kth.depclean.core.util.JarUtils; 10 | import se.kth.depclean.core.wrapper.DependencyManagerWrapper; 11 | 12 | /** 13 | * A class that extracts all the types in all the dependencies. 14 | */ 15 | @Slf4j 16 | public class TypesExtractor { 17 | 18 | private static final String DIRECTORY_TO_EXTRACT_DEPENDENCIES = "dependency"; 19 | 20 | private DependencyManagerWrapper dependencyManager; 21 | 22 | /** 23 | * Constructor. 24 | * 25 | * @param dependencyManager The dependency manager. 26 | */ 27 | public TypesExtractor(DependencyManagerWrapper dependencyManager) { 28 | this.dependencyManager = dependencyManager; 29 | } 30 | 31 | /** 32 | * Extracts all the types in all the dependencies. 33 | */ 34 | @SneakyThrows 35 | public void extractAllTypes() { 36 | final File dependencyDirectory = dependencyManager.getBuildDirectory().resolve(DIRECTORY_TO_EXTRACT_DEPENDENCIES).toFile(); 37 | FileUtils.deleteDirectory(dependencyDirectory); 38 | dependencyManager.dependencyGraph().allDependencies() 39 | .forEach(jarFile -> copyDependencies(jarFile, dependencyDirectory)); 40 | // TODO remove this workaround later 41 | if (dependencyManager.getBuildDirectory().resolve("libs").toFile().exists()) { 42 | try { 43 | FileUtils.copyDirectory( 44 | dependencyManager.getBuildDirectory().resolve("libs").toFile(), 45 | dependencyDirectory 46 | ); 47 | } catch (IOException | NullPointerException e) { 48 | log.error("Error copying directory libs to dependency"); 49 | throw new RuntimeException(e); 50 | } 51 | } 52 | if (dependencyDirectory.exists()) { 53 | JarUtils.decompress(dependencyDirectory.getAbsolutePath()); 54 | } 55 | } 56 | 57 | private void copyDependencies(Dependency dependency, File destFolder) { 58 | copyDependencies(dependency.getFile(), destFolder); 59 | } 60 | 61 | @SneakyThrows 62 | private void copyDependencies(File jarFile, File destFolder) { 63 | FileUtils.copyFileToDirectory(jarFile, destFolder); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/test/resources/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.foo.bar 7 | foobar 8 | 1.0.0-SNAPSHOT 9 | jar 10 | foobar 11 | 12 | 13 | UTF-8 14 | UTF-8 15 | 11 16 | 11 17 | 18 | 19 | 20 | 21 | com.fasterxml.jackson.core 22 | jackson-databind 23 | 2.12.2 24 | 25 | 26 | com.google.guava 27 | guava 28 | 17.0 29 | 30 | 31 | commons-io 32 | commons-io 33 | 2.11.0 34 | 35 | 36 | 37 | 38 | 39 | 40 | com.soebes.itf.jupiter.extension 41 | itf-failure-plugin 42 | 0.9.0 43 | 44 | 45 | first_very_simple 46 | initialize 47 | 48 | failure 49 | 50 | 51 | 52 | 53 | 54 | se.kth.castor 55 | deptrim-maven-plugin 56 | 0.0.1 57 | 58 | 59 | 60 | deptrim 61 | 62 | 63 | true 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /src/test/resources-its/se/kth/deptrim/DepTrimMojoIT/ignore_dependencies/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.foo.bar 7 | foobar 8 | 1.0.0-SNAPSHOT 9 | jar 10 | foobar 11 | 12 | 13 | UTF-8 14 | UTF-8 15 | 11 16 | 11 17 | 18 | 19 | 20 | 21 | com.fasterxml.jackson.core 22 | jackson-databind 23 | 2.12.2 24 | 25 | 26 | com.google.guava 27 | guava 28 | 17.0 29 | 30 | 31 | commons-io 32 | commons-io 33 | 2.11.0 34 | 35 | 36 | 37 | 38 | 39 | 40 | com.soebes.itf.jupiter.extension 41 | itf-failure-plugin 42 | 0.9.0 43 | 44 | 45 | first_very_simple 46 | initialize 47 | 48 | failure 49 | 50 | 51 | 52 | 53 | 54 | se.kth.castor 55 | deptrim-maven-plugin 56 | 0.0.1 57 | 58 | 59 | 60 | deptrim 61 | 62 | 63 | true 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /src/test/resources-its/se/kth/deptrim/DepTrimMojoIT/all_pom_specialized/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.foo.bar 7 | foobar 8 | 1.0.0-SNAPSHOT 9 | jar 10 | foobar 11 | 12 | 13 | UTF-8 14 | UTF-8 15 | 11 16 | 11 17 | 18 | 19 | 20 | 21 | com.fasterxml.jackson.core 22 | jackson-databind 23 | 2.12.2 24 | 25 | 26 | com.google.guava 27 | guava 28 | 17.0 29 | 30 | 31 | commons-io 32 | commons-io 33 | 2.11.0 34 | 35 | 36 | 37 | 38 | 39 | 40 | com.soebes.itf.jupiter.extension 41 | itf-failure-plugin 42 | 0.9.0 43 | 44 | 45 | first_very_simple 46 | initialize 47 | 48 | failure 49 | 50 | 51 | 52 | 53 | 54 | se.kth.castor 55 | deptrim-maven-plugin 56 | 0.0.1 57 | 58 | 59 | 60 | deptrim 61 | 62 | 63 | true 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | previousVersion: 6 | description: 'Previous version' 7 | required: true 8 | newVersion: 9 | description: 'New version' 10 | required: true 11 | jobs: 12 | release: 13 | name: Release 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Check out Git repository 17 | uses: actions/checkout@v3 18 | 19 | - name: Install Java and Maven 20 | uses: actions/setup-java@v3 21 | with: 22 | distribution: 'adopt-hotspot' 23 | java-version: 11 24 | server-id: ossrh 25 | server-username: OSSRH_USERNAME # env variable for username in release 26 | server-password: OSSRH_TOKEN # env variable for token in release 27 | gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} # Value of the GPG private key to import 28 | gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase 29 | 30 | - name: Release Maven package 31 | run: mvn deploy -P deploy -Djacoco.skip=true 32 | env: 33 | OSSRH_USERNAME: ${{ secrets.NEXUS_USERNAME }} 34 | OSSRH_TOKEN: ${{ secrets.NEXUS_PASSWORD }} 35 | MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} 36 | 37 | - name: Create and push tag 38 | run: | 39 | git config --global user.email "cesarsotovalero@gmail.com" 40 | git config --global user.name "$GITHUB_ACTOR" 41 | git tag -a $TAG -m "Release v$TAG" 42 | git push origin $TAG 43 | env: 44 | TAG: ${{ github.event.inputs.newVersion }} 45 | 46 | - name: Create Release on GitHub 47 | id: create_release 48 | uses: actions/create-release@v1 49 | env: 50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 51 | with: 52 | tag_name: ${{ github.event.inputs.newVersion }} 53 | release_name: ${{ github.event.inputs.newVersion }} 54 | draft: true 55 | prerelease: false 56 | 57 | - name: Update README 58 | run: | 59 | sed -i 's/${{ github.event.inputs.previousVersion }}/${{ github.event.inputs.newVersion }}/g' README.md 60 | git config --global user.email "cesarsotovalero@gmail.com" 61 | git config --global user.name "$GITHUB_ACTOR" 62 | git commit -am "Replace ${{ github.event.inputs.previousVersion }} with ${{ github.event.inputs.newVersion }} in README" 63 | git push origin main 64 | 65 | -------------------------------------------------------------------------------- /src/main/java/se/kth/deptrim/util/JarUtils.java: -------------------------------------------------------------------------------- 1 | package se.kth.deptrim.util; 2 | 3 | import java.io.BufferedInputStream; 4 | import java.io.File; 5 | import java.io.FileInputStream; 6 | import java.io.FileOutputStream; 7 | import java.util.Objects; 8 | import java.util.jar.JarEntry; 9 | import java.util.jar.JarOutputStream; 10 | 11 | /** 12 | * Utility class for creating jar files. 13 | */ 14 | public class JarUtils { 15 | 16 | private JarUtils() { 17 | throw new IllegalStateException("Utility class."); 18 | } 19 | 20 | /** 21 | * Create a Jar file from a directory. 22 | * 23 | * @param directory The directory to be archived. 24 | * @param jarFile The jar file to be created. 25 | * @throws Exception if an error occurs. 26 | */ 27 | public static void createJarFromDirectory(File directory, File jarFile) throws Exception { 28 | JarOutputStream target = new JarOutputStream(new FileOutputStream(jarFile)); 29 | for (File file : directory.listFiles()) { 30 | addFile("", file, target); 31 | } 32 | target.close(); 33 | } 34 | 35 | /** 36 | * Add a file to the jar file. 37 | * 38 | * @param parents The parent directories of the file. 39 | * @param source The file to be added. 40 | * @param target The jar file. 41 | * @throws Exception if an error occurs. 42 | */ 43 | private static void addFile(String parents, File source, JarOutputStream target) throws Exception { 44 | BufferedInputStream in = null; 45 | try { 46 | String name = (parents + source.getName()).replace("\\", "/"); 47 | if (source.isDirectory()) { 48 | if (!name.isEmpty()) { 49 | if (!name.endsWith("/")) { 50 | name += "/"; 51 | } 52 | JarEntry entry = new JarEntry(name); 53 | entry.setTime(source.lastModified()); 54 | target.putNextEntry(entry); 55 | target.closeEntry(); 56 | } 57 | for (File nestedFile : Objects.requireNonNull(source.listFiles())) { 58 | addFile(name, nestedFile, target); 59 | } 60 | return; 61 | } 62 | JarEntry entry = new JarEntry(name); 63 | entry.setTime(source.lastModified()); 64 | target.putNextEntry(entry); 65 | in = new BufferedInputStream(new FileInputStream(source)); 66 | byte[] buffer = new byte[1024]; 67 | while (true) { 68 | int count = in.read(buffer); 69 | if (count == -1) { 70 | break; 71 | } 72 | target.write(buffer, 0, count); 73 | } 74 | target.closeEntry(); 75 | } finally { 76 | if (in != null) { 77 | in.close(); 78 | } 79 | } 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | jobs: 10 | # For maven project 11 | build: 12 | strategy: 13 | max-parallel: 1 14 | matrix: 15 | os: [ ubuntu-latest, windows-latest, macos-latest ] 16 | java: [ 17 ] 17 | runs-on: ${{ matrix.os }} 18 | name: Maven Build with Java ${{ matrix.java }} on ${{ matrix.os }} 19 | steps: 20 | 21 | - name: "Checkout" 22 | uses: actions/checkout@v3 23 | with: 24 | fetch-depth: 0 25 | 26 | - name: Java ${{ matrix.java }} 27 | uses: actions/setup-java@v3 28 | with: 29 | java-version: ${{ matrix.java }} 30 | distribution: 'adopt-hotspot' 31 | 32 | - name: "Cache Local Maven Repository" 33 | uses: actions/cache@v3 34 | with: 35 | path: ~/.m2/repository 36 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 37 | restore-keys: | 38 | ${{ runner.os }}-maven- 39 | 40 | - name: "CheckStyle" 41 | run: mvn validate --errors 42 | 43 | - name: "Compile and Install" 44 | run: mvn clean install -DskipTests --errors 45 | 46 | - name: "Unit Tests" 47 | run: mvn test --errors --fail-at-end 48 | 49 | - name: "Integration Tests" 50 | run: mvn failsafe:integration-test --errors --fail-at-end 51 | 52 | # The following is only executed on Ubuntu on Java 17 53 | - name: "JaCoCo Coverage Report" 54 | if: matrix.os == 'ubuntu-latest' && matrix.java == 17 && github.repository == 'castor-software/deptrim' 55 | run: mvn jacoco:report 56 | 57 | - name: "Codecov" 58 | if: matrix.os == 'ubuntu-latest' && matrix.java == 17 && github.repository == 'castor-software/deptrim' 59 | uses: codecov/codecov-action@v3 60 | with: 61 | token: ${{ secrets.CODECOV_TOKEN }} 62 | files: ./target/site/jacoco/jacoco.xml 63 | flags: unittests 64 | 65 | - name: "Cache SonarCloud" 66 | if: matrix.os == 'ubuntu-latest' && matrix.java == 17 && github.repository == 'castor-software/deptrim' 67 | uses: actions/cache@v3 68 | with: 69 | path: ~/.sonar/cache 70 | key: ${{ runner.os }}-sonar 71 | restore-keys: ${{ runner.os }}-sonar 72 | 73 | - name: "SonarCloud" 74 | if: matrix.os == 'ubuntu-latest' && matrix.java == 17 && github.repository == 'castor-software/deptrim' 75 | run: mvn sonar:sonar -Dsonar.projectKey=castor-software_deptrim -Dsonar.organization=castor-software -Dsonar.host.url=https://sonarcloud.io -Dsonar.login=${{ secrets.SONAR_TOKEN }} -Dsonar.java.source=17 -Dsonar.java.target=17 76 | env: 77 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 78 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} -------------------------------------------------------------------------------- /src/test/resources-its/se/kth/deptrim/DepTrimMojoIT/ignore_scopes/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.foo.bar 7 | foobar 8 | 1.0.0-SNAPSHOT 9 | jar 10 | foobar 11 | 12 | 13 | UTF-8 14 | UTF-8 15 | 11 16 | 11 17 | 18 | 19 | 20 | 21 | oss.sonatype.org 22 | https://oss.sonatype.org/content/repositories/snapshots/ 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | commons-io 31 | commons-io 32 | 2.8.0 33 | 34 | 35 | 36 | 37 | commons-codec 38 | commons-codec 39 | 1.15 40 | 41 | 42 | 43 | 44 | com.jcabi 45 | jcabi-xml 46 | 0.18.1 47 | test 48 | 49 | 50 | 51 | 52 | org.junit.jupiter 53 | junit-jupiter-api 54 | 5.9.0 55 | test 56 | 57 | 58 | org.junit.jupiter 59 | junit-jupiter-engine 60 | 5.9.0 61 | test 62 | 63 | 64 | org.junit.vintage 65 | junit-vintage-engine 66 | 5.9.0 67 | test 68 | 69 | 70 | org.junit.jupiter 71 | junit-jupiter-params 72 | 5.9.0 73 | test 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | se.kth.castor 82 | deptrim-maven-plugin 83 | 0.0.1 84 | 85 | 86 | 87 | deptrim 88 | 89 | 90 | test 91 | true 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /src/main/java/se/kth/deptrim/core/TypesUsageAnalyzer.java: -------------------------------------------------------------------------------- 1 | package se.kth.deptrim.core; 2 | 3 | import java.util.HashSet; 4 | import java.util.Objects; 5 | import java.util.Set; 6 | import java.util.stream.Collectors; 7 | import lombok.extern.slf4j.Slf4j; 8 | import se.kth.depclean.core.model.ClassName; 9 | import se.kth.depclean.core.model.Dependency; 10 | import se.kth.depclean.core.model.ProjectContext; 11 | import se.kth.depclean.core.model.Scope; 12 | import se.kth.depclean.core.wrapper.DependencyManagerWrapper; 13 | 14 | /** 15 | * A class that analyses the types used by the project in each dependency. 16 | */ 17 | @Slf4j 18 | public class TypesUsageAnalyzer { 19 | 20 | private DependencyManagerWrapper dependencyManager; 21 | 22 | /** 23 | * Constructor. 24 | * 25 | * @param dependencyManager the dependency manager. 26 | */ 27 | public TypesUsageAnalyzer(DependencyManagerWrapper dependencyManager) { 28 | this.dependencyManager = dependencyManager; 29 | } 30 | 31 | /** 32 | * Creates a project dependency analysis. 33 | * 34 | * @return the {@link ProjectContext} with the types used by the project in each dependency. 35 | */ 36 | public ProjectContext buildProjectContext(Set ignoreDependencies, Set ignoreScopes) { 37 | // Consider as used all the classes declared in Maven processors 38 | Set allUsedClasses = new HashSet<>(); 39 | Set usedClassesFromProcessors = dependencyManager 40 | .collectUsedClassesFromProcessors().stream() 41 | .map(ClassName::new) 42 | .collect(Collectors.toSet()); 43 | 44 | // Consider as used all the classes located in the imports of the source code 45 | Set usedClassesFromSource = dependencyManager.collectUsedClassesFromSource( 46 | dependencyManager.getSourceDirectory(), 47 | dependencyManager.getTestDirectory()) 48 | .stream() 49 | .map(ClassName::new) 50 | .collect(Collectors.toSet()); 51 | 52 | allUsedClasses.addAll(usedClassesFromProcessors); 53 | allUsedClasses.addAll(usedClassesFromSource); 54 | 55 | // Ignore the dependencies with the ignored scopes 56 | for (Dependency dependency : dependencyManager.dependencyGraph().allDependencies()) { 57 | if (ignoreScopes.contains(dependency.getScope())) { 58 | log.info("Ignoring dependency " + dependency + " because of scope " + dependency.getScope()); 59 | ignoreDependencies.add(dependency.toString()); 60 | } 61 | } 62 | 63 | return new ProjectContext( 64 | dependencyManager.dependencyGraph(), 65 | dependencyManager.getOutputDirectories(), 66 | dependencyManager.getTestOutputDirectories(), 67 | dependencyManager.getSourceDirectory(), 68 | dependencyManager.getTestDirectory(), 69 | dependencyManager.getDependenciesDirectory(), 70 | ignoreScopes.stream().map(Scope::new).collect(Collectors.toSet()), 71 | toDependency(dependencyManager.dependencyGraph().allDependencies(), ignoreDependencies), 72 | allUsedClasses 73 | ); 74 | } 75 | 76 | /** 77 | * Returns a set of {@code DependencyCoordinate}s that match given string representations. 78 | * 79 | * @param allDependencies all known dependencies 80 | * @param ignoreDependencies string representation of dependencies to return 81 | * @return a set of {@code Dependency} that match given string representations 82 | */ 83 | private Set toDependency(Set allDependencies, Set ignoreDependencies) { 84 | return ignoreDependencies.stream() 85 | .map(dependency -> findDependency(allDependencies, dependency)) 86 | .filter(Objects::nonNull) 87 | .collect(Collectors.toSet()); 88 | } 89 | 90 | private Dependency findDependency(Set allDependencies, String dependency) { 91 | return allDependencies.stream() 92 | .filter(dep -> dep.toString().toLowerCase().contains(dependency.toLowerCase())) 93 | .findFirst() 94 | .orElse(null); 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/test/java/se/kth/deptrim/core/TypesExtractorTest.java: -------------------------------------------------------------------------------- 1 | package se.kth.deptrim.core; 2 | 3 | import static org.junit.Assert.assertTrue; 4 | import static org.mockito.Mockito.mock; 5 | import static org.mockito.Mockito.when; 6 | 7 | import java.io.File; 8 | import java.io.IOException; 9 | import java.nio.file.Paths; 10 | import java.util.Arrays; 11 | import java.util.HashSet; 12 | import org.apache.commons.io.FileUtils; 13 | import org.junit.jupiter.api.AfterEach; 14 | import org.junit.jupiter.api.Test; 15 | import se.kth.depclean.core.analysis.graph.DependencyGraph; 16 | import se.kth.depclean.core.model.Dependency; 17 | import se.kth.depclean.core.wrapper.DependencyManagerWrapper; 18 | 19 | class TypesExtractorTest { 20 | 21 | File fakeProjectRoot = new File("src/test/resources/project"); 22 | File extractedTarget = new File("src/test/resources/target"); 23 | File extractedTypes = new File("src/test/resources/target/dependency"); 24 | 25 | @Test 26 | void testExtractAllTypes() throws Exception { 27 | // Set up mock DependencyManagerWrapper and Dependency 28 | DependencyManagerWrapper dependencyManager = mock(DependencyManagerWrapper.class); 29 | DependencyGraph dependencyGraph = mock(DependencyGraph.class); 30 | Dependency dependency1 = mock(Dependency.class); 31 | Dependency dependency2 = mock(Dependency.class); 32 | HashSet dependencies = new HashSet<>(Arrays.asList(dependency1, dependency2)); 33 | File libsDirectory = new File(fakeProjectRoot.getAbsolutePath() + File.separator + "libs"); 34 | File targetDirectory = new File(fakeProjectRoot.getAbsolutePath() + File.separator + "target"); 35 | File dependencyDirectory = new File( 36 | fakeProjectRoot.getAbsolutePath() + File.separator + "project" + File.separator + "target" + File.separator + "dependency" 37 | ); 38 | targetDirectory.mkdir(); 39 | dependencyDirectory.mkdir(); 40 | libsDirectory.mkdir(); 41 | File jarFile1 = new File("src/test/resources" + File.separator + "auto-value-annotations-1.8.1.jar"); 42 | File jarFile2 = new File("src/test/resources" + File.separator + "checker-qual-3.8.0.jar"); 43 | FileUtils.copyFileToDirectory(jarFile1, dependencyDirectory); 44 | FileUtils.copyFileToDirectory(jarFile2, libsDirectory); 45 | jarFile1 = new File(dependencyDirectory + File.separator + "auto-value-annotations-1.8.1.jar"); 46 | jarFile2 = new File(libsDirectory + File.separator + "checker-qual-3.8.0.jar"); 47 | when(dependency1.getFile()).thenReturn(jarFile1); 48 | when(dependency2.getFile()).thenReturn(jarFile2); 49 | when(dependencyManager.dependencyGraph()).thenReturn(dependencyGraph); 50 | when(dependencyGraph.allDependencies()).thenReturn(dependencies); 51 | when(dependencyManager.getBuildDirectory()).thenReturn(Paths.get("src/test/resources/target/")); 52 | // Create TypesExtractor and extract types 53 | TypesExtractor extractor = new TypesExtractor(dependencyManager); 54 | extractor.extractAllTypes(); 55 | // Verify that the dependency directory was deleted and the jar files were copied 56 | assertTrue(dependencyDirectory.exists()); 57 | assertTrue(new File(dependencyDirectory, "auto-value-annotations-1.8.1.jar").exists()); 58 | assertTrue(new File(libsDirectory, "checker-qual-3.8.0.jar").exists()); 59 | // Verify that the libs directory was copied 60 | assertTrue(new File(fakeProjectRoot, "libs").exists()); 61 | // Verify that the jar files were decompressed 62 | assertTrue( 63 | new File(extractedTypes.getAbsolutePath() + File.separator + "auto-value-annotations-1.8.1/com/google/auto/value/AutoValue$Builder.class") 64 | .exists() 65 | ); 66 | assertTrue( 67 | new File(extractedTypes.getAbsolutePath() + File.separator + "checker-qual-3.8.0/org/checkerframework/framework/qual/DefaultFor.class") 68 | .exists() 69 | ); 70 | } 71 | 72 | @AfterEach 73 | void tearDown() throws IOException { 74 | FileUtils.deleteDirectory(fakeProjectRoot); 75 | FileUtils.deleteDirectory(extractedTarget); 76 | FileUtils.deleteDirectory(extractedTypes); 77 | } 78 | } -------------------------------------------------------------------------------- /src/test/java/se/kth/deptrim/DepTrimMojoIT.java: -------------------------------------------------------------------------------- 1 | package se.kth.deptrim; 2 | 3 | import static com.soebes.itf.extension.assertj.MavenITAssertions.assertThat; 4 | 5 | import com.soebes.itf.jupiter.extension.MavenJupiterExtension; 6 | import com.soebes.itf.jupiter.extension.MavenTest; 7 | import com.soebes.itf.jupiter.maven.MavenExecutionResult; 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.nio.file.Files; 11 | import javax.xml.parsers.DocumentBuilder; 12 | import javax.xml.parsers.DocumentBuilderFactory; 13 | import javax.xml.parsers.ParserConfigurationException; 14 | import org.junit.jupiter.api.Assertions; 15 | import org.junit.jupiter.api.DisplayName; 16 | import org.w3c.dom.Document; 17 | import org.w3c.dom.NodeList; 18 | import org.xml.sax.SAXException; 19 | 20 | /** 21 | *

22 | * This class executes integration tests against the {@link se.kth.deptrim.DepTrimMojo}. The projects used for testing are in src/test/resources-its/se/kth/deptrim/DepTrimMojoIT. The results of the 23 | * DepTrim executions for each project are in target/maven-it/se/kth/deptrim/DepTrimMojoIT. 24 | *

25 | * 26 | * @see 27 | */ 28 | @MavenJupiterExtension 29 | public class DepTrimMojoIT { 30 | 31 | @MavenTest 32 | @DisplayName("Test that DepTrim runs in an empty Maven project") 33 | void empty_project(MavenExecutionResult result) { 34 | System.out.println("Testing that DepTrim runs in an empty Maven project"); 35 | assertThat(result).isSuccessful(); // should pass 36 | } 37 | 38 | @MavenTest 39 | @DisplayName("Test that DepTrim creates specialized poms") 40 | void all_pom_specialized(MavenExecutionResult result) { 41 | System.out.println("Testing that DepTrim pushes specialized dependencies to the local repository."); 42 | String LocalRepositoryAbsolutePath = result.getMavenCacheResult().getStdout().toFile().getAbsolutePath(); 43 | String pathToSpecializedCommonsIO = "/se/kth/castor/deptrim/spl/commons-io/2.11.0/commons-io-2.11.0.jar"; 44 | String pathToSpecializedGuava = "/se/kth/castor/deptrim/spl/guava/17.0/guava-17.0.jar"; 45 | Assertions.assertTrue(Files.exists(new File(LocalRepositoryAbsolutePath + pathToSpecializedCommonsIO).toPath())); 46 | Assertions.assertTrue(Files.exists(new File(LocalRepositoryAbsolutePath + pathToSpecializedGuava).toPath())); 47 | 48 | System.out.println("Testing that DepTrim pushes specialized dependencies to /libs-specialized."); 49 | String pathToProject = result.getMavenProjectResult().getTargetBaseDirectory().getAbsolutePath(); 50 | String pathToSpecializedCommonsIOInProject = "/project/libs-specialized/commons-io-2.11.0.jar"; 51 | String pathToSpecializedGuavaInProject = "/project/libs-specialized/commons-io-2.11.0.jar"; 52 | Assertions.assertTrue(Files.exists(new File(pathToProject + pathToSpecializedCommonsIOInProject).toPath())); 53 | Assertions.assertTrue(Files.exists(new File(pathToProject + pathToSpecializedGuavaInProject).toPath())); 54 | 55 | System.out.println("Testing that DepTrim produces four specialized POM files in the root of the project."); 56 | File pathToProjectDirectory = new File(result.getMavenProjectResult().getTargetBaseDirectory().getAbsolutePath() + "/project"); 57 | File[] specializedPomFiles = pathToProjectDirectory.listFiles((dirFiles, filename) -> filename.startsWith("pom-specialized_") && filename.endsWith(".xml")); 58 | assert specializedPomFiles != null; 59 | Assertions.assertEquals(4, specializedPomFiles.length); 60 | 61 | System.out.println("Testing that the number of dependencies in the specialized POM files are correct."); 62 | for (File specializedPomFile : specializedPomFiles) { 63 | try { 64 | DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); 65 | documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); 66 | DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); 67 | Document document = documentBuilder.parse(specializedPomFile); 68 | document.getDocumentElement().normalize(); 69 | NodeList dependencies = document.getDocumentElement().getElementsByTagName("dependency"); 70 | Assertions.assertEquals(2, dependencies.getLength()); 71 | } catch (IOException | ParserConfigurationException | SAXException e) { 72 | System.out.println("Error parsing pom file: " + specializedPomFile.getAbsolutePath()); 73 | } 74 | } 75 | } 76 | 77 | } 78 | 79 | -------------------------------------------------------------------------------- /src/main/java/se/kth/deptrim/DepTrimManager.java: -------------------------------------------------------------------------------- 1 | package se.kth.deptrim; 2 | 3 | import java.io.File; 4 | import java.util.Set; 5 | import lombok.AllArgsConstructor; 6 | import lombok.SneakyThrows; 7 | import org.apache.maven.execution.MavenSession; 8 | import org.apache.maven.project.MavenProject; 9 | import se.kth.depclean.core.analysis.DefaultProjectDependencyAnalyzer; 10 | import se.kth.depclean.core.analysis.model.ProjectDependencyAnalysis; 11 | import se.kth.depclean.core.model.ProjectContext; 12 | import se.kth.depclean.core.wrapper.DependencyManagerWrapper; 13 | import se.kth.depclean.core.wrapper.LogWrapper; 14 | import se.kth.deptrim.core.SpecializedDependency; 15 | import se.kth.deptrim.core.Specializer; 16 | import se.kth.deptrim.core.TypesExtractor; 17 | import se.kth.deptrim.core.TypesUsageAnalyzer; 18 | import se.kth.deptrim.io.ConsolePrinter; 19 | import se.kth.deptrim.util.PomUtils; 20 | import se.kth.deptrim.util.TimeUtils; 21 | 22 | /** 23 | * Runs the DepTrim process, regardless of a specific dependency manager. 24 | */ 25 | @AllArgsConstructor 26 | public class DepTrimManager { 27 | 28 | private static final String SEPARATOR = "-------------------------------------------------------"; 29 | private static final String DEBLOATED_POM_NAME = "pom-debloated.xml"; 30 | private final DependencyManagerWrapper dependencyManager; 31 | private final MavenProject project; 32 | private final MavenSession session; 33 | private final boolean verboseMode; 34 | private final boolean skipDepTrim; 35 | private final Set ignoreScopes; 36 | private final Set ignoreDependencies; 37 | private final Set specializeDependencies; 38 | private final boolean createSinglePomSpecialized; 39 | private final boolean createDependencySpecializedPerPom; 40 | private final boolean createAllPomSpecialized; 41 | 42 | /** 43 | * Execute the DepTrim manager. 44 | */ 45 | @SneakyThrows 46 | public ProjectDependencyAnalysis execute() { 47 | final long startTime = System.currentTimeMillis(); 48 | 49 | // Skip DepTrim if the user has specified so. 50 | if (skipDepTrim) { 51 | getLog().info("Skipping DepTrim plugin execution."); 52 | return null; 53 | } 54 | // Skip the execution if the packaging is not a JAR or WAR. 55 | if (dependencyManager.isMaven() && dependencyManager.isPackagingPom()) { 56 | getLog().info("Skipping DepTrim because the packaging type is pom."); 57 | return null; 58 | } 59 | 60 | getLog().info(SEPARATOR); 61 | getLog().info("DEPTRIM IS ANALYZING DEPENDENCIES"); 62 | getLog().info(SEPARATOR); 63 | // Extract all the dependencies in target/dependencies/. 64 | TypesExtractor typesExtractor = new TypesExtractor(dependencyManager); 65 | typesExtractor.extractAllTypes(); 66 | // Analyze the dependencies extracted. 67 | DefaultProjectDependencyAnalyzer projectDependencyAnalyzer = new DefaultProjectDependencyAnalyzer(); 68 | TypesUsageAnalyzer typesUsageAnalyzer = new TypesUsageAnalyzer(dependencyManager); 69 | ProjectContext projectContext = typesUsageAnalyzer.buildProjectContext(ignoreDependencies, ignoreScopes); 70 | ProjectDependencyAnalysis analysis = projectDependencyAnalyzer.analyze(projectContext); 71 | 72 | if (verboseMode) { 73 | ConsolePrinter consolePrinter = new ConsolePrinter(); 74 | consolePrinter.printDependencyUsageAnalysis(analysis); 75 | } 76 | 77 | // Specializing dependencies. 78 | getLog().info(SEPARATOR); 79 | getLog().info("DEPTRIM IS SPECIALIZING DEPENDENCIES"); 80 | getLog().info(SEPARATOR); 81 | String projectCoordinates = project.getGroupId() + ":" + project.getArtifactId() + ":" + project.getVersion(); 82 | String mavenLocalRepoUrl = session.getLocalRepository().getUrl(); 83 | Specializer specializer = new Specializer(projectCoordinates, mavenLocalRepoUrl, dependencyManager, ignoreScopes); 84 | Set specializedDependencies = specializer.specialize(analysis, specializeDependencies); 85 | getLog().info("Number of specialized dependencies: " + specializedDependencies.size()); 86 | getLog().info(SEPARATOR); 87 | getLog().info("DEPTRIM IS CREATING SPECIALIZED POMS"); 88 | getLog().info(SEPARATOR); 89 | 90 | if (createSinglePomSpecialized || createAllPomSpecialized || createDependencySpecializedPerPom) { 91 | // The following code creates a pom-debloated.xml 92 | dependencyManager.getDebloater(analysis).write(); 93 | String debloatedPomPath = project.getBasedir().getAbsolutePath() 94 | + File.separator 95 | + DEBLOATED_POM_NAME; 96 | // The following code creates a pom-specialized-*.xml from pom-debloated.xml 97 | PomUtils pomUtils = new PomUtils(specializedDependencies, debloatedPomPath, createSinglePomSpecialized, createDependencySpecializedPerPom, createAllPomSpecialized); 98 | pomUtils.createPoms(); 99 | } 100 | 101 | // Print execution time. 102 | final long stopTime = System.currentTimeMillis(); 103 | TimeUtils timeUtils = new TimeUtils(); 104 | getLog().info("DepTrim execution done in " + timeUtils.toHumanReadableTime(stopTime - startTime)); 105 | 106 | return analysis; 107 | } 108 | 109 | private LogWrapper getLog() { 110 | return dependencyManager.getLog(); 111 | } 112 | } -------------------------------------------------------------------------------- /src/main/java/se/kth/deptrim/DepTrimMojo.java: -------------------------------------------------------------------------------- 1 | package se.kth.deptrim; 2 | 3 | import java.util.Set; 4 | import lombok.SneakyThrows; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.apache.maven.execution.MavenSession; 7 | import org.apache.maven.plugin.AbstractMojo; 8 | import org.apache.maven.plugin.MojoExecutionException; 9 | import org.apache.maven.plugins.annotations.Component; 10 | import org.apache.maven.plugins.annotations.LifecyclePhase; 11 | import org.apache.maven.plugins.annotations.Mojo; 12 | import org.apache.maven.plugins.annotations.Parameter; 13 | import org.apache.maven.plugins.annotations.ResolutionScope; 14 | import org.apache.maven.project.MavenProject; 15 | import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder; 16 | import se.kth.depclean.wrapper.MavenDependencyManager; 17 | 18 | /** 19 | * This Maven mojo is the main class of DepTrim. DepTrim automatically removes unused types in the project's dependencies. 20 | */ 21 | @Mojo(name = "deptrim", 22 | defaultPhase = LifecyclePhase.PACKAGE, 23 | requiresDependencyCollection = ResolutionScope.TEST, 24 | requiresDependencyResolution = ResolutionScope.TEST, 25 | threadSafe = true) 26 | @Slf4j 27 | public class DepTrimMojo extends AbstractMojo { 28 | 29 | /** 30 | * The Maven project to analyze. 31 | */ 32 | @Parameter(defaultValue = "${project}", readonly = true) 33 | private MavenProject project; 34 | 35 | /** 36 | * The Maven session to analyze. 37 | */ 38 | @Parameter(defaultValue = "${session}", readonly = true) 39 | private MavenSession session; 40 | 41 | /** 42 | * Add a list of dependencies, identified by their coordinates, to be specialized by DepTrim during the execution. The format of each dependency is 43 | * groupId:artifactId:version. 44 | */ 45 | @Parameter(property = "specializeDependencies") 46 | private Set specializeDependencies; 47 | 48 | /** 49 | * If this is true, DepTrim creates aversion of the pom, named "pom-specialized.xml", in the root of the project. 50 | */ 51 | @Parameter(property = "createSinglePomSpecialized", defaultValue = "false") 52 | private boolean createSinglePomSpecialized; 53 | /** 54 | * If this is true, DepTrim creates a version of the POM for each specialized dependency in the root of the project. 55 | */ 56 | @Parameter(property = "createDependencySpecializedPerPom", defaultValue = "false") 57 | private boolean createDependencySpecializedPerPom; 58 | /** 59 | * If this is true, DepTrim creates all the combinations of the specialized poms in the root of the project. 60 | */ 61 | @Parameter(property = "createAllPomSpecialized", defaultValue = "false") 62 | private boolean createAllPomSpecialized; 63 | 64 | /** 65 | * If this is true, DepTrim creates a JSON file with the result of the analysis. The file is called "deptrim-result.json" and it is located in /target. 66 | */ 67 | @Parameter(property = "createResultJson", defaultValue = "false") 68 | private boolean createResultJson; 69 | 70 | /** 71 | * If this is true, DepTrim creates a CSV file with the result of the analysis with the columns: OriginClass,TargetClass,OriginDependency,TargetDependency. 72 | * The file is called deptrim-callgraph.csv" and it is located in /target. 73 | */ 74 | @Parameter(property = "createCallGraphCsv", defaultValue = "false") 75 | private boolean createCallGraphCsv; 76 | 77 | /** 78 | * Add a list of dependencies, identified by their coordinates, to be ignored by DepTrim during the analysis and considered as fully used dependencies. Useful 79 | * to override incomplete result caused by bytecode-level analysis. Dependency format is groupId:artifactId:version. 80 | */ 81 | @Parameter(property = "ignoreDependencies") 82 | private Set ignoreDependencies; 83 | 84 | /** 85 | * Ignore dependencies with specific scopes. 86 | */ 87 | @Parameter(property = "ignoreScopes") 88 | private Set ignoreScopes; 89 | 90 | /** 91 | * Print plugin execution details to the console. 92 | */ 93 | @Parameter(property = "verboseMode", defaultValue = "false") 94 | private boolean verboseMode; 95 | 96 | /** 97 | * Skip plugin execution completely. 98 | */ 99 | @Parameter(property = "skipDepTrim", defaultValue = "false") 100 | private boolean skipDepTrim; 101 | 102 | /** 103 | * To build the dependency graph. 104 | */ 105 | @Component(hint = "default") 106 | private DependencyGraphBuilder dependencyGraphBuilder; 107 | 108 | @SneakyThrows 109 | @Override 110 | public final void execute() { 111 | try { 112 | new DepTrimManager( 113 | new MavenDependencyManager( 114 | getLog(), 115 | project, 116 | session, 117 | dependencyGraphBuilder 118 | ), 119 | project, 120 | session, 121 | verboseMode, 122 | skipDepTrim, 123 | ignoreScopes, 124 | ignoreDependencies, 125 | specializeDependencies, 126 | createSinglePomSpecialized, 127 | createDependencySpecializedPerPom, 128 | createAllPomSpecialized 129 | ).execute(); 130 | } catch (Exception e) { 131 | throw new MojoExecutionException(e.getMessage(), e); 132 | } 133 | } 134 | } -------------------------------------------------------------------------------- /src/test/java/se/kth/deptrim/util/PomUtilsTest.java: -------------------------------------------------------------------------------- 1 | package se.kth.deptrim.util; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.HashSet; 6 | import java.util.Set; 7 | import org.apache.commons.io.FileUtils; 8 | import org.junit.jupiter.api.Assertions; 9 | import org.junit.jupiter.api.Test; 10 | import se.kth.deptrim.core.SpecializedDependency; 11 | 12 | class PomUtilsTest { 13 | 14 | @Test 15 | void testCreateSinglePomSpecialized() throws IOException { 16 | Set specializedDependencies = new HashSet<>(); 17 | specializedDependencies.add(new SpecializedDependency("com.fasterxml.jackson.core", "jackson-databind", "2.12.2", "se.kth.castor")); 18 | specializedDependencies.add(new SpecializedDependency("com.google.guava", "guava", "17.0", "se.kth.castor")); 19 | specializedDependencies.add(new SpecializedDependency("commons-io", "commons-io", "2.11.0", "se.kth.castor")); 20 | File pomPath = new File("src/test/resources/pom.xml"); 21 | File debloatedPomPath = new File("src/test/resources/pom-debloated.xml"); 22 | FileUtils.copyFile(pomPath, debloatedPomPath); 23 | boolean createSinglePomSpecialized = true; 24 | boolean createDependencySpecializedPerPom = false; 25 | boolean createAllPomSpecialized = false; 26 | PomUtils pomUtils = new PomUtils( 27 | specializedDependencies, 28 | debloatedPomPath.getAbsolutePath(), 29 | createSinglePomSpecialized, 30 | createDependencySpecializedPerPom, 31 | createAllPomSpecialized 32 | ); 33 | pomUtils.createPoms(); 34 | File specializedPomFile = new File(debloatedPomPath.getAbsolutePath().replace("-debloated.xml", "-specialized.xml")); 35 | Assertions.assertTrue(specializedPomFile.exists()); 36 | specializedPomFile.delete(); 37 | debloatedPomPath.delete(); 38 | } 39 | 40 | @Test 41 | void testCreateDependencySpecializedPerPom() throws IOException { 42 | Set specializedDependencies = new HashSet<>(); 43 | specializedDependencies.add(new SpecializedDependency("com.fasterxml.jackson.core", "jackson-databind", "2.12.2", "se.kth.castor")); 44 | specializedDependencies.add(new SpecializedDependency("com.google.guava", "guava", "17.0", "se.kth.castor")); 45 | specializedDependencies.add(new SpecializedDependency("commons-io", "commons-io", "2.11.0", "se.kth.castor")); 46 | File pomPath = new File("src/test/resources/pom.xml"); 47 | File debloatedPomPath = new File("src/test/resources/pom-debloated.xml"); 48 | FileUtils.copyFile(pomPath, debloatedPomPath); 49 | boolean createSinglePomSpecialized = false; 50 | boolean createDependencySpecializedPerPom = true; 51 | boolean createAllPomSpecialized = false; 52 | PomUtils pomUtils = new PomUtils( 53 | specializedDependencies, 54 | debloatedPomPath.getAbsolutePath(), 55 | createSinglePomSpecialized, 56 | createDependencySpecializedPerPom, 57 | createAllPomSpecialized 58 | ); 59 | pomUtils.createPoms(); 60 | File specializedPomFile1 = new File(debloatedPomPath.getAbsolutePath().replace("-debloated.xml", "-specialized_1_3.xml")); 61 | File specializedPomFile2 = new File(debloatedPomPath.getAbsolutePath().replace("-debloated.xml", "-specialized_2_3.xml")); 62 | File specializedPomFile3 = new File(debloatedPomPath.getAbsolutePath().replace("-debloated.xml", "-specialized_3_3.xml")); 63 | Assertions.assertTrue(specializedPomFile1.exists()); 64 | Assertions.assertTrue(specializedPomFile2.exists()); 65 | Assertions.assertTrue(specializedPomFile3.exists()); 66 | debloatedPomPath.delete(); 67 | specializedPomFile1.delete(); 68 | specializedPomFile2.delete(); 69 | specializedPomFile3.delete(); 70 | } 71 | 72 | @Test 73 | void testCreateAllCombinationsOfSpecializedPoms() throws IOException { 74 | Set specializedDependencies = new HashSet<>(); 75 | specializedDependencies.add(new SpecializedDependency("com.fasterxml.jackson.core", "jackson-databind", "2.12.2", "se.kth.castor")); 76 | specializedDependencies.add(new SpecializedDependency("com.google.guava", "guava", "17.0", "se.kth.castor")); 77 | specializedDependencies.add(new SpecializedDependency("commons-io", "commons-io", "2.11.0", "se.kth.castor")); 78 | File pomPath = new File("src/test/resources/pom.xml"); 79 | File debloatedPomPath = new File("src/test/resources/pom-debloated.xml"); 80 | FileUtils.copyFile(pomPath, debloatedPomPath); 81 | boolean createSinglePomSpecialized = false; 82 | boolean createDependencySpecializedPerPom = false; 83 | boolean createAllPomSpecialized = true; 84 | PomUtils pomUtils = new PomUtils( 85 | specializedDependencies, 86 | debloatedPomPath.getAbsolutePath(), 87 | createSinglePomSpecialized, 88 | createDependencySpecializedPerPom, 89 | createAllPomSpecialized 90 | ); 91 | pomUtils.createPoms(); 92 | File resources = new File("src/test/resources/"); 93 | // count the number of files in the directory 94 | int count = 0; 95 | for (File file : resources.listFiles()) { 96 | if (file.isFile() && file.getName().contains("specialized")) { 97 | count++; 98 | file.delete(); 99 | } 100 | } 101 | Assertions.assertEquals(8, count); 102 | debloatedPomPath.delete(); 103 | } 104 | 105 | } -------------------------------------------------------------------------------- /src/main/java/se/kth/deptrim/core/Specializer.java: -------------------------------------------------------------------------------- 1 | package se.kth.deptrim.core; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | import java.nio.file.Paths; 8 | import java.util.HashSet; 9 | import java.util.LinkedHashSet; 10 | import java.util.Set; 11 | import lombok.SneakyThrows; 12 | import lombok.extern.slf4j.Slf4j; 13 | import org.apache.commons.io.FileUtils; 14 | import se.kth.depclean.core.analysis.model.ProjectDependencyAnalysis; 15 | import se.kth.depclean.core.model.ClassName; 16 | import se.kth.depclean.core.wrapper.DependencyManagerWrapper; 17 | import se.kth.depclean.util.MavenInvoker; 18 | 19 | /** 20 | * A class that trims the dependencies of a project. 21 | */ 22 | @Slf4j 23 | public class Specializer { 24 | 25 | private static final String DIRECTORY_TO_EXTRACT_DEPENDENCIES = "dependency"; 26 | private static final String DIRECTORY_TO_LOCATE_THE_DEBLOATED_DEPENDENCIES = "dependency-debloated"; 27 | private static final String GROUP_ID_OF_SPECIALIZED_JAR = "se.kth.castor.deptrim.spl"; 28 | private DependencyManagerWrapper dependencyManager; 29 | /** 30 | * The coordinates of the project being analyzed, so that deptrim does not throw an error when copying files. 31 | */ 32 | private String projectCoordinates; 33 | /** 34 | * The local Maven repository for deployment of specialized jars. 35 | */ 36 | private String mavenLocalRepoUrl; 37 | private Set ignoreScopes; 38 | 39 | /** 40 | * Constructor. 41 | * 42 | * @param dependencyManager The dependency manager wrapper. 43 | * @param projectCoordinates The coordinates of the project being analyzed, so that deptrim does not throw an error when copying files. 44 | * @param mavenLocalRepoUrl The local Maven repository for deployment of specialized jars. 45 | * @param ignoreScopes The scopes to ignore. 46 | */ 47 | public Specializer(String projectCoordinates, String mavenLocalRepoUrl, DependencyManagerWrapper dependencyManager, Set ignoreScopes) { 48 | this.projectCoordinates = projectCoordinates; 49 | this.mavenLocalRepoUrl = mavenLocalRepoUrl; 50 | this.dependencyManager = dependencyManager; 51 | this.ignoreScopes = ignoreScopes; 52 | } 53 | 54 | /** 55 | * Get all the dependencies in the project if the trimDependencies flag is not set. 56 | * 57 | * @param analysis The dependency usage analysis results 58 | * @return The set of all dependencies 59 | */ 60 | public Set getAllDependencies(ProjectDependencyAnalysis analysis) { 61 | Set dependenciesToSpecialize = new LinkedHashSet<>(); 62 | analysis.getDependencyClassesMap() 63 | .forEach((dependency, types) -> { 64 | String dependencyCoordinates = dependency.getGroupId() + ":" + dependency.getDependencyId() + ":" + dependency.getVersion(); 65 | dependenciesToSpecialize.add(dependencyCoordinates); 66 | }); 67 | return dependenciesToSpecialize; 68 | } 69 | 70 | /** 71 | * Trim the unused classes from the dependencies specified by the user based on the usage analysis results. 72 | * 73 | * @param analysis The dependency usage analysis results 74 | * @param specializeDependencies The dependencies to be trimmed, if empty then trims all the dependencies 75 | */ 76 | @SneakyThrows 77 | public Set specialize(ProjectDependencyAnalysis analysis, Set specializeDependencies) { 78 | Set deployedSpecializedDependencies = new LinkedHashSet<>(); 79 | if (specializeDependencies.isEmpty()) { 80 | log.info("No dependencies specified, specializing all dependencies except the ignored dependencies."); 81 | specializeDependencies = getAllDependencies(analysis); 82 | } 83 | Set finalSpecializedDependencies = specializeDependencies; 84 | analysis 85 | .getDependencyClassesMap() 86 | .forEach((dependency, types) -> { 87 | String dependencyCoordinates = dependency.getGroupId() + ":" + dependency.getDependencyId() + ":" + dependency.getVersion(); 88 | // specializing only the dependencies provided by the user and if the scope is not ignored 89 | if (!types.getUsedTypes().isEmpty() 90 | && finalSpecializedDependencies.contains(dependencyCoordinates) 91 | && !ignoreScopes.contains(dependency.getScope()) 92 | && !dependencyCoordinates.equals(projectCoordinates) 93 | ) { 94 | log.info("Specializing dependency: {}, with file {}", dependencyCoordinates, dependency.getFile().getName()); 95 | Set unusedTypes = new HashSet<>(types.getAllTypes()); 96 | unusedTypes.removeAll(types.getUsedTypes()); 97 | String dependencyDirName = dependency.getFile().getName().substring(0, dependency.getFile().getName().length() - 4); 98 | File srcDir = dependencyManager.getBuildDirectory().resolve(DIRECTORY_TO_EXTRACT_DEPENDENCIES + File.separator + dependencyDirName).toFile(); 99 | File destDir = dependencyManager.getBuildDirectory().resolve(DIRECTORY_TO_LOCATE_THE_DEBLOATED_DEPENDENCIES + File.separator + dependencyDirName) 100 | .toFile(); 101 | 102 | // Copy all files from srcDir to destDir 103 | try { 104 | FileUtils.copyDirectory(srcDir, destDir); 105 | } catch (IOException e) { 106 | log.error("Error copying files from " + srcDir + " to " + destDir); 107 | } 108 | 109 | // Remove files in destDir. 110 | log.info( 111 | "Specializing dependency " + dependencyCoordinates + ", removing " + unusedTypes.size() + "/" + types.getAllTypes().size() + " unused types."); 112 | if (unusedTypes.isEmpty()) { 113 | log.info("Skipping specializing dependency " + dependencyCoordinates + " because all its types are used."); 114 | } 115 | for (ClassName className : unusedTypes) { 116 | String fileName = className.toString().replace(".", File.separator) + ".class"; 117 | File file = new File(destDir.getAbsolutePath() + File.separator + fileName); 118 | try { 119 | Files.delete(file.toPath()); 120 | } catch (IOException e) { 121 | log.error("Error deleting file " + file.getPath()); 122 | } 123 | } 124 | 125 | // Delete all empty directories in destDir. 126 | se.kth.deptrim.util.FileUtils fileUtils = new se.kth.deptrim.util.FileUtils(); 127 | fileUtils.deleteEmptyDirectories(destDir); 128 | 129 | // Create a new jar file with the debloated classes and move it to libs-specialized. 130 | Path libSpecializedPath = Paths.get("libs-specialized"); 131 | String jarName = destDir.getName() + ".jar"; 132 | File jarFile = libSpecializedPath.resolve(jarName).toFile(); 133 | try { 134 | Files.createDirectories(libSpecializedPath); // create libs-deptrim directory if it does not exist 135 | se.kth.deptrim.util.JarUtils.createJarFromDirectory(destDir, jarFile); 136 | } catch (Exception e) { 137 | log.error("Error creating specialized jar for " + destDir.getName()); 138 | } 139 | 140 | // Deploy specialized jars to the local Maven repository. 141 | try { 142 | String mavenDeployCommand = "mvn deploy:deploy-file -Durl=" 143 | + mavenLocalRepoUrl 144 | + " -Dpackaging=jar" 145 | + " -Dfile=" + jarFile.getAbsolutePath() 146 | + " -DgroupId=" + GROUP_ID_OF_SPECIALIZED_JAR 147 | + " -DartifactId=" + dependency.getDependencyId() 148 | + " -Dversion=" + dependency.getVersion(); 149 | log.info(mavenDeployCommand); 150 | MavenInvoker.runCommand(mavenDeployCommand, null); 151 | // If successfully deployed 152 | SpecializedDependency specializedDependency = new SpecializedDependency( 153 | dependency.getGroupId(), 154 | dependency.getDependencyId(), 155 | dependency.getVersion(), 156 | GROUP_ID_OF_SPECIALIZED_JAR 157 | ); 158 | deployedSpecializedDependencies.add(specializedDependency); 159 | } catch (IOException | InterruptedException e) { 160 | log.error("Error installing the specialized dependency JAR in the local repository."); 161 | Thread.currentThread().interrupt(); 162 | } 163 | } 164 | }); 165 | return deployedSpecializedDependencies; 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/main/java/se/kth/deptrim/util/PomUtils.java: -------------------------------------------------------------------------------- 1 | package se.kth.deptrim.util; 2 | 3 | import com.google.common.collect.Sets; 4 | import java.io.File; 5 | import java.io.IOException; 6 | import java.util.Set; 7 | import javax.xml.parsers.DocumentBuilder; 8 | import javax.xml.parsers.DocumentBuilderFactory; 9 | import javax.xml.parsers.ParserConfigurationException; 10 | import javax.xml.transform.Transformer; 11 | import javax.xml.transform.TransformerException; 12 | import javax.xml.transform.TransformerFactory; 13 | import javax.xml.transform.dom.DOMSource; 14 | import javax.xml.transform.stream.StreamResult; 15 | import lombok.extern.slf4j.Slf4j; 16 | import org.w3c.dom.Document; 17 | import org.w3c.dom.Element; 18 | import org.w3c.dom.Node; 19 | import org.w3c.dom.NodeList; 20 | import org.xml.sax.SAXException; 21 | import se.kth.deptrim.core.SpecializedDependency; 22 | 23 | /** 24 | * Utility class for manipulating Maven pom.xml files. 25 | */ 26 | @Slf4j 27 | public class PomUtils { 28 | 29 | Set specializedDependencies; 30 | String debloatedPomPath; 31 | 32 | boolean createSinglePomSpecialized; 33 | boolean createDependencySpecializedPerPom; 34 | boolean createAllPomSpecialized; 35 | 36 | /** 37 | * Constructor. 38 | * 39 | * @param specializedDependencies The set of trimmed dependencies. 40 | * @param debloatedPomPath The path to the debloated pom.xml file. 41 | * @param createAllPomSpecialized Whether to create all pom.xml files trimmed. 42 | */ 43 | public PomUtils(Set specializedDependencies, String debloatedPomPath, boolean createSinglePomSpecialized, 44 | boolean createDependencySpecializedPerPom, boolean createAllPomSpecialized) { 45 | this.specializedDependencies = specializedDependencies; 46 | this.debloatedPomPath = debloatedPomPath; 47 | this.createSinglePomSpecialized = createSinglePomSpecialized; 48 | this.createDependencySpecializedPerPom = createDependencySpecializedPerPom; 49 | this.createAllPomSpecialized = createAllPomSpecialized; 50 | } 51 | 52 | /** 53 | * This method produces a new pom file for each combination of trimmed dependencies. 54 | */ 55 | public void createPoms() { 56 | if (createAllPomSpecialized) { 57 | createAllCombinationsOfSpecializedPoms(); 58 | } 59 | if (createSinglePomSpecialized) { 60 | createSinglePomSpecialized(); 61 | } 62 | if (createDependencySpecializedPerPom) { 63 | log.info("Creating specialized pom files for each dependency."); 64 | createDependencySpecializedPerPom(); 65 | } 66 | } 67 | 68 | private void createDependencySpecializedPerPom() { 69 | log.info("Number of specialized poms: " + specializedDependencies.size()); 70 | try { 71 | createDependencySpecializedPerPom(specializedDependencies); 72 | } catch (Exception e) { 73 | throw new RuntimeException(e); 74 | } 75 | } 76 | 77 | /** 78 | * Creates a pom-specialized.xml from the pom-debloated.xml produced by DepClean. 79 | * 80 | * @param specializedDependencies A combination of trimmed dependencies 81 | * @return The path of the generated pom-debloated-spl.xml file 82 | * @throws Exception when any of this goes wrong :) 83 | */ 84 | private void createDependencySpecializedPerPom(Set specializedDependencies) 85 | throws TransformerException, ParserConfigurationException, IOException, SAXException { 86 | int nbSpecializedDependencies = specializedDependencies.size(); 87 | int combinationNumber = 1; 88 | for (SpecializedDependency thisDependency : specializedDependencies) { 89 | DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); 90 | documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); 91 | DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); 92 | Document document = documentBuilder.parse(new File(debloatedPomPath)); 93 | document.getDocumentElement().normalize(); 94 | NodeList dependencies = document.getDocumentElement().getElementsByTagName("dependency"); 95 | replaceDependencyInPom(thisDependency, dependencies); 96 | String debloatedAndSpecializedPom = debloatedPomPath.replace("-debloated.xml", "-specialized.xml"); 97 | debloatedAndSpecializedPom = debloatedAndSpecializedPom.replace( 98 | "-specialized.xml", 99 | "-specialized_" + combinationNumber + "_" + nbSpecializedDependencies + ".xml" 100 | ); 101 | log.info("Created " + new File(debloatedAndSpecializedPom).getName()); 102 | saveUpdatedDomInANewPom(document, debloatedAndSpecializedPom); 103 | combinationNumber++; 104 | } 105 | } 106 | 107 | private void replaceDependencyInPom(SpecializedDependency thisDependency, NodeList dependencies) { 108 | for (int i = 0; i < dependencies.getLength(); i++) { 109 | Element dependencyNode = (Element) dependencies.item(i); 110 | Node groupIdNode = dependencyNode.getElementsByTagName("groupId").item(0); 111 | Node artifactIdNode = dependencyNode.getElementsByTagName("artifactId").item(0); 112 | // When original groupId and artifactId are found in debloated pom, 113 | // replace with new coordinates 114 | if (groupIdNode.getTextContent().equals(thisDependency.getOriginalGroupId()) 115 | && artifactIdNode.getTextContent().equals(thisDependency.getOriginalArtifactId()) 116 | ) { 117 | // Found original dependency in debloated POM. 118 | // Replacing with specialized dependency. 119 | Node versionNode = dependencyNode.getElementsByTagName("version").item(0); 120 | groupIdNode.setTextContent(thisDependency.getSpecializedGroupId()); 121 | artifactIdNode.setTextContent(thisDependency.getSpecializedArtifactId()); 122 | versionNode.setTextContent(thisDependency.getSpecializedVersion()); 123 | } 124 | } 125 | } 126 | 127 | private String createSinglePomSpecialized() { 128 | String generatedPomFile = ""; 129 | try { 130 | generatedPomFile = createSpecializedPomFromDebloatedPom(specializedDependencies, 1); 131 | log.info("Created " + new File(generatedPomFile).getName()); 132 | } catch (Exception e) { 133 | log.error("Error creating specialized pom file: " + e.getMessage()); 134 | } 135 | return generatedPomFile; 136 | } 137 | 138 | private void createAllCombinationsOfSpecializedPoms() { 139 | Set> allCombinationsOfSpecializedDependencies = Sets.powerSet(specializedDependencies); 140 | log.info("Number of specialized poms: " + allCombinationsOfSpecializedDependencies.size()); 141 | int combinationNumber = 1; 142 | for (Set oneCombinationOfSpecializedDependencies : allCombinationsOfSpecializedDependencies) { 143 | // Producing POM for combination. 144 | // oneCombinationOfSpecializedDependencies.forEach(c -> log.info(c.toString())); 145 | try { 146 | String generatedPomFile = createSpecializedPomFromDebloatedPom(oneCombinationOfSpecializedDependencies, combinationNumber); 147 | log.info("Created " + new File(generatedPomFile).getName()); 148 | combinationNumber++; 149 | } catch (Exception e) { 150 | log.error("Error creating specialized POM."); 151 | } 152 | } 153 | } 154 | 155 | /** 156 | * Creates a pom-specialized.xml from the pom-debloated.xml produced by DepClean. 157 | * 158 | * @param oneCombinationOfSpecializedDependencies A combination of trimmed dependencies 159 | * @return The path of the generated pom-debloated-spl.xml file 160 | * @throws Exception when any of this goes wrong :) 161 | */ 162 | private String createSpecializedPomFromDebloatedPom(Set oneCombinationOfSpecializedDependencies, Integer combinationNumber) 163 | throws TransformerException, ParserConfigurationException, IOException, SAXException { 164 | DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); 165 | documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); 166 | DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); 167 | Document document = documentBuilder.parse(new File(debloatedPomPath)); 168 | document.getDocumentElement().normalize(); 169 | int nbSpecializedDependencies = oneCombinationOfSpecializedDependencies.size(); 170 | NodeList dependencies = document.getDocumentElement().getElementsByTagName("dependency"); 171 | for (SpecializedDependency thisDependency : oneCombinationOfSpecializedDependencies) { 172 | replaceDependencyInPom(thisDependency, dependencies); 173 | } 174 | String debloatedAndSpecializedPom = debloatedPomPath.replace("-debloated.xml", "-specialized.xml"); 175 | if (createAllPomSpecialized) { 176 | debloatedAndSpecializedPom = debloatedAndSpecializedPom.replace( 177 | "-specialized.xml", 178 | "-specialized_" + combinationNumber + "_" + nbSpecializedDependencies + "_" + specializedDependencies.size() + ".xml" 179 | ); 180 | log.debug( 181 | "POM number: " + " " + debloatedAndSpecializedPom + " " + combinationNumber + " " + nbSpecializedDependencies + " " + specializedDependencies.size() 182 | ); 183 | } 184 | saveUpdatedDomInANewPom(document, debloatedAndSpecializedPom); 185 | return debloatedAndSpecializedPom; 186 | } 187 | 188 | /** 189 | * Generates a new XML file based with the changes to the XML document. 190 | * 191 | * @param document The XML document structure to save to file 192 | * @param debloatedSpecializedPom The path to the pom-debloated-spl.xml file to generate 193 | * @throws TransformerException If XML document cannot be saved to file 194 | */ 195 | private void saveUpdatedDomInANewPom(Document document, String debloatedSpecializedPom) throws TransformerException { 196 | DOMSource dom = new DOMSource(document); 197 | TransformerFactory transformerFactory = TransformerFactory.newInstance(); 198 | transformerFactory.setAttribute("http://javax.xml.XMLConstants/property/accessExternalDTD", ""); 199 | transformerFactory.setAttribute("http://javax.xml.XMLConstants/property/accessExternalStylesheet", ""); 200 | Transformer transformer = transformerFactory.newTransformer(); 201 | StreamResult result = new StreamResult(new File(debloatedSpecializedPom)); 202 | transformer.transform(dom, result); 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DepTrim DepTrim logo 2 | 3 | [![Maven Central](https://img.shields.io/maven-central/v/se.kth.castor/deptrim-maven-plugin.svg)](https://search.maven.org/search?q=g:se.kth.castor%20AND%20a:deptrim*) 4 | [![build](https://github.com/castor-software/deptrim/actions/workflows/build.yml/badge.svg)](https://github.com/castor-software/deptrim/actions/workflows/build.yml) 5 | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=castor-software_deptrim&metric=alert_status)](https://sonarcloud.io/dashboard?id=castor-software_deptrim) 6 | [![codecov](https://codecov.io/gh/castor-software/deptrim/branch/main/graph/badge.svg?token=L70YMFGJ4D)](https://codecov.io/gh/ASSERT-KTH/deptrim) 7 | [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=castor-software_deptrim&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=castor-software_deptrim) 8 | [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=castor-software_deptrim&metric=reliability_rating)](https://sonarcloud.io/dashboard?id=castor-software_deptrim) 9 | [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=castor-software_deptrim&metric=security_rating)](https://sonarcloud.io/dashboard?id=castor-software_deptrim) 10 | [![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=castor-software_deptrim&metric=vulnerabilities)](https://sonarcloud.io/dashboard?id=castor-software_deptrim) 11 | [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=castor-software_deptrim&metric=bugs)](https://sonarcloud.io/dashboard?id=castor-software_deptrim) 12 | [![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=castor-software_deptrim&metric=code_smells)](https://sonarcloud.io/dashboard?id=castor-software_deptrim) 13 | [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=castor-software_deptrim&metric=ncloc)](https://sonarcloud.io/dashboard?id=castor-software_deptrim) 14 | [![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=castor-software_deptrim&metric=duplicated_lines_density)](https://sonarcloud.io/dashboard?id=castor-software_deptrim) 15 | [![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=castor-software_deptrim&metric=sqale_index)](https://sonarcloud.io/dashboard?id=castor-software_deptrim) 16 | 17 | ## What is DepTrim? 18 | 19 | DepTrim is a Maven plugin that automatically specializes the dependencies of a project. 20 | The objective is hardening the [software supply chain](https://www.cesarsotovalero.net/blog/the-software-supply-chain.html) of third-party dependencies of a project by using dependencies that only contain the classes and interfaces that are **actually necessary** to build the project. 21 | Relying on specialized variants of dependencies is **good for security**, as it reduces the attack surface of the project, and **good for performance**, as it reduces the size of the final artifact. 22 | 23 | After running DepTrim, a directory named `libs-specialized` is created in the root of the project. 24 | This directory contains the specialized variants of all the dependencies necessary to build the project (inc. direct and transitive dependencies). 25 | DepTrim can also create a specialized POM file, named `pom-specialized.xml`. 26 | This specialized POM uses the specialized variants of the dependencies instead of the original dependencies. 27 | DepTrim deploys the specialized variants of the dependencies in the local Maven repository. 28 | 29 | **NOTE:** DepTrim does not modify the original source code of the project nor its original `pom.xml`. 30 | 31 | ## Usage 32 | 33 | Run DepTrim directly from the command line as follows: 34 | 35 | ```bash 36 | cd {PATH_TO_MAVEN_PROJECT} 37 | # First, compile source and test files of the project. 38 | mvn compile 39 | mvn compiler:testCompile 40 | # Then, run the latest version of DepTrim. 41 | mvn se.kth.castor:deptrim-maven-plugin:0.1.1:deptrim -DcreateSinglePomSpecialized=true 42 | ``` 43 | 44 | Alternatively, configure the original `pom.xml` file of the project to run DepTrim as part of the build as follows: 45 | 46 | ```xml 47 | 48 | se.kth.castor 49 | deptrim-maven-plugin 50 | 0.1.1 51 | 52 | 53 | 54 | deptrim 55 | 56 | 57 | true 58 | 59 | 60 | 61 | 62 | ``` 63 | 64 | In both cases, a directory name `libs-specialized` will be created in the root of the project, together with a file named `pom-specialized.xml`, which uses the specialized variants of the dependencies. 65 | 66 | ## Optional parameters 67 | 68 | The `deptrim-maven-plugin` accepts the following additional parameters. 69 | 70 | | Name | Type | Description | 71 | |:----------------------------|:-------------:|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 72 | | `` | `Set` | Add a list of dependencies, identified by their coordinates, to be specialized by DepTrim. **Dependency format is:** `groupId:artifactId:version:scope`. An empty string indicates that all the dependencies in the dependency tree of the project will be specialized (`default`). | 73 | | `` | `Set` | Add a list of dependencies, identified by their coordinates, to be ignored by DepTrim during the analysis. This is useful to override incomplete result caused by bytecode-level static analysis. **Dependency format is:** `groupId:artifactId:version:scope`. | 74 | | `` | `Set` | Add a list of scopes, to be ignored by DepTrim during the analysis. Useful to not analyze dependencies with scopes that are not needed at runtime. **Valid scopes are:** `compile`, `provided`, `test`, `runtime`, `system`, `import`. An empty string indicates no scopes (`default`). | 75 | | `` | `boolean` | If this is `true`, DepTrim creates a specialized version of the POM file in the root of the project, called `pom-specialized.xml`, which points to the variant of the specialized the dependencies. **Default value is:** `false`. | 76 | | `` | `boolean` | If this is `true`, DepTrim creates one specialized version of the POM file per specialized dependency, called `pom-specialized-x-y.xml`, where `x` is an integer identifying a specialized dependency, and `y` is the total number of specialized dependencies. **Default value is:** `false`. | 77 | | `` | `boolean` | If this is `true`, DepTrim creates all the combinations of specialized version of the original POM in the root of the project (i.e., $2^y$ POM files will be created). Name format is `pom-specialized-n-x-y.xml`, where `n` is the combination number, `x` is the number of specialized dependencies in this combination, and `y` is the total number of specialized dependencies. **Default value is:** `false`. | 78 | | `` | `boolean` | Run DepTrim in verbose mode. **Default value is:** `false`. | 79 | | `` | `boolean` | Skip plugin execution completely. **Default value is:** `false`. | 80 | 81 | ## How does DepTrim works? 82 | 83 | DepTrim runs before executing during the [`pre-package`](https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html#some-phases-are-not-usually-called-from-the-command-line) phase of the Maven build lifecycle. 84 | DepTrim relies on [depclean-core](https://github.com/castor-software/depclean) to statically collects all the types used by the project under analysis, as well as in its dependencies. 85 | With this information, DepTrim removes all the types in the dependencies that are not used by the project. 86 | DepTrim also creates a directory named `libs-specialized` in the root of the project, which contains the specialized versions of the dependencies. 87 | DepTrim creates a new `pom-specialized.xml` file that contains only the specialized versions of the dependencies. 88 | 89 | The `pom-specialized.xml` is created following these steps: 90 | 91 | 1. Identify all used dependencies and add them as direct dependencies. 92 | 2. For the used dependencies, remove the types (i.e., compiled classes and interfaces) that are not used by the project. 93 | 3. Deploy the modified dependencies in the local Maven repository. 94 | 4. Create a `pom-specialized.xml` so that it uses the specialized variants of the dependencies located in the local Maven repository. 95 | 96 | ### Known limitations 97 | 98 | DepTrim needs to know all the types used by the project under analysis, as well as in its dependencies. 99 | This is a challenging task, as it requires "seeing" all the project's codebase. 100 | In particular, it is not possible to detect the usage of [dynamic Java features](https://www.graalvm.org/latest/reference-manual/native-image/dynamic-features/), such as reflection, dynamic proxies, or custom class loaders, in Java. 101 | This necessitates both a thorough understanding of Java's dynamic features and a careful examination of the project's codebase. 102 | To detect the utilization of dynamic features within a Java application, we recommend the use of the [GraalVM Tracing Agent](https://www.graalvm.org/22.0/reference-manual/native-image/Agent/). 103 | 104 | ```bash 105 | java -agentlib:native-image-agent=config-output-dir=/path/to/config-dir/ -jar yourApp.jar 106 | ``` 107 | By running your application with the agent, it will generate a configuration directory (`/path/to/config-dir/`) containing the files that describe the observed dynamic behavior. 108 | This useful for specialization tasks, e.g., when specializing dependencies that could be accessed dynamically and lack complete a priori knowledge about all possible dynamic behaviors. 109 | 110 | While DepTrim aims to streamline the dependency-trimming process, understanding its limitations and employing additional tools like the GraalVM Tracing Agent can help enhance the process. 111 | However, note that certain dynamic behaviors, such as the implications of multi-threading or just-in-time (JIT) compilation, may be too subtle or intricate to be detected readily. 112 | 113 | ## Installing and building from source 114 | 115 | Prerequisites: 116 | 117 | - [Java OpenJDK 17](https://openjdk.java.net) or above 118 | - [Apache Maven](https://maven.apache.org/) 119 | 120 | In a terminal, clone the repository and switch to the cloned folder: 121 | 122 | ```bash 123 | git clone https://github.com/castor-software/deptrim.git 124 | cd deptrim 125 | ``` 126 | 127 | Then run the following Maven command to build the application and install the plugin locally: 128 | 129 | ```bash 130 | mvn clean install 131 | ``` 132 | 133 | ## License 134 | 135 | Distributed under the MIT License. See [LICENSE](https://github.com/castor-software/depclean/blob/master/LICENSE.md) for more information. 136 | 137 | ## Funding 138 | 139 | DepTrim is partially funded by the [Wallenberg Autonomous Systems and Software Program (WASP)](https://wasp-sweden.org). 140 | 141 | Wallenberg Autonomous Systems and Software Program (WASP) 142 | -------------------------------------------------------------------------------- /checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 47 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 64 | 65 | 66 | 68 | 69 | 70 | 76 | 77 | 78 | 79 | 82 | 83 | 84 | 85 | 86 | 90 | 91 | 92 | 93 | 94 | 96 | 97 | 98 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 117 | 119 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 167 | 168 | 169 | 171 | 173 | 174 | 175 | 176 | 178 | 179 | 180 | 181 | 183 | 184 | 185 | 186 | 188 | 189 | 190 | 191 | 193 | 194 | 195 | 196 | 198 | 199 | 200 | 201 | 203 | 204 | 205 | 206 | 208 | 209 | 210 | 211 | 213 | 214 | 215 | 216 | 218 | 219 | 220 | 221 | 223 | 224 | 225 | 226 | 228 | 229 | 230 | 231 | 233 | 235 | 237 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 267 | 268 | 269 | 272 | 273 | 274 | 275 | 281 | 282 | 283 | 284 | 287 | 288 | 289 | 290 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 305 | 306 | 307 | 308 | 309 | 310 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 326 | 327 | 328 | 329 | 332 | 333 | 334 | 335 | 336 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 350 | 351 | 352 | 353 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | 6 | se.kth.castor 7 | deptrim-maven-plugin 8 | 0.1.2 9 | maven-plugin 10 | 11 | 12 | DepTrim 13 | DepTrim automatically specializes dependencies in Maven projects. 14 | https://github.com/castor-software/deptrim 15 | 16 | 17 | 18 | GitHub Issues 19 | https://github.com/castor-software/deptrim/issues 20 | 21 | 22 | 23 | 24 | https://github.com/castor-software/deptrim/ 25 | scm:git:git:github.com/castor-software/deptrim.git 26 | scm:git:git@github.com:castor-software/deptrim.git 27 | 28 | 29 | 30 | 31 | 32 | MIT License 33 | https://www.opensource.org/licenses/mit-license.php 34 | repo 35 | 36 | 37 | 38 | 39 | 40 | 41 | cesarsotovalero 42 | César Soto Valero 43 | cesarsotovalero@gmail.com 44 | Castor Software Research Centre 45 | https://www.castor.kth.se/ 46 | 47 | 48 | 49 | 50 | 51 | 52 | UTF-8 53 | UTF-8 54 | 55 | 11 56 | 11 57 | 11 58 | 11 59 | 60 | 0.8.10 61 | 4.3.0 62 | 3.0.0-M7 63 | 3.11.0 64 | 3.9.1.2184 65 | 4.0.0-M3 66 | 3.4.5 67 | 3.3.0 68 | 69 | 1.18.28 70 | 2.0.7 71 | 2.0.5 72 | 5.9.3 73 | 4.11.0 74 | 75 | 76 | 77 | 78 | 79 | 80 | se.kth.castor 81 | depclean-core 82 | 2.0.6 83 | 84 | 85 | se.kth.castor 86 | depclean-maven-plugin 87 | 2.0.6 88 | 89 | 90 | 91 | org.projectlombok 92 | lombok 93 | ${lombok.version} 94 | provided 95 | 96 | 97 | 98 | org.slf4j 99 | slf4j-api 100 | ${slf4j-api.version} 101 | 102 | 103 | 104 | org.junit.jupiter 105 | junit-jupiter-api 106 | ${junit5.version} 107 | test 108 | 109 | 110 | org.junit.jupiter 111 | junit-jupiter-engine 112 | ${junit5.version} 113 | test 114 | 115 | 116 | org.junit.vintage 117 | junit-vintage-engine 118 | ${junit5.version} 119 | test 120 | 121 | 122 | org.junit.jupiter 123 | junit-jupiter-params 124 | ${junit5.version} 125 | test 126 | 127 | 128 | org.mockito 129 | mockito-core 130 | ${mockito.core.version} 131 | test 132 | 133 | 134 | 135 | 136 | com.soebes.itf.jupiter.extension 137 | itf-extension-maven 138 | 0.11.0 139 | test 140 | 141 | 142 | com.soebes.itf.jupiter.extension 143 | itf-assertj 144 | 0.12.0 145 | test 146 | 147 | 148 | com.soebes.itf.jupiter.extension 149 | itf-jupiter-extension 150 | 0.12.0 151 | test 152 | 153 | 154 | org.assertj 155 | assertj-core 156 | 3.23.1 157 | test 158 | 159 | 160 | 161 | org.apache.maven 162 | maven-core 163 | 3.8.5 164 | provided 165 | 166 | 167 | org.apache.maven 168 | maven-plugin-api 169 | 3.8.5 170 | provided 171 | 172 | 173 | org.apache.maven 174 | maven-project 175 | 3.0-alpha-2 176 | provided 177 | 178 | 179 | org.apache.maven.plugin-tools 180 | maven-plugin-annotations 181 | 3.9.0 182 | provided 183 | 184 | 185 | org.apache.maven.shared 186 | maven-dependency-tree 187 | 3.2.1 188 | provided 189 | 190 | 191 | 192 | 193 | 194 | ossrh 195 | https://oss.sonatype.org/content/repositories/snapshots 196 | 197 | 198 | ossrh 199 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | src/test/resources 209 | false 210 | 211 | 212 | src/test/resources-its 213 | true 214 | 215 | 216 | 217 | 218 | com.soebes.itf.jupiter.extension 219 | itf-maven-plugin 220 | 0.11.0 221 | 222 | 223 | installing 224 | pre-integration-test 225 | 226 | install 227 | 228 | 229 | 230 | 231 | 232 | org.apache.maven.plugins 233 | maven-failsafe-plugin 234 | 3.1.2 235 | 236 | 237 | **/*IT.java 238 | 239 | 240 | 241 | 242 | 243 | integration-test 244 | 245 | 246 | 247 | 248 | 249 | 250 | org.apache.maven.plugins 251 | maven-plugin-plugin 252 | 3.9.0 253 | 254 | 255 | default-descriptor 256 | process-classes 257 | 258 | 259 | 260 | 261 | 262 | org.jacoco 263 | jacoco-maven-plugin 264 | ${jacoco.maven.plugin} 265 | 266 | 267 | 268 | prepare-agent 269 | 270 | 271 | 272 | report 273 | prepare-package 274 | 275 | report 276 | 277 | 278 | 279 | 280 | 281 | 282 | org.eluder.coveralls 283 | coveralls-maven-plugin 284 | ${coveralls.maven.plugin} 285 | 286 | 287 | javax.xml.bind 288 | jaxb-api 289 | 2.3.1 290 | 291 | 292 | 293 | 294 | 295 | ${project.basedir}/target/site/jacoco/jacoco.xml 296 | 297 | 298 | ${project.basedir}/target/site/jacoco/jacoco.xml 299 | 300 | 301 | false 302 | 303 | 304 | 305 | 306 | org.sonarsource.scanner.maven 307 | sonar-maven-plugin 308 | ${sonar.maven.plugin} 309 | 310 | 311 | 312 | org.apache.maven.plugins 313 | maven-checkstyle-plugin 314 | ${maven.checkstyle.plugin} 315 | 316 | 317 | com.puppycrawl.tools 318 | checkstyle 319 | 8.41 320 | 321 | 322 | 323 | 324 | check for errors 325 | 326 | 327 | error 328 | true 329 | 330 | checkstyle.xml 331 | false 332 | true 333 | 334 | validate 335 | 336 | check 337 | 338 | 339 | 340 | checkstyle report 341 | 342 | true 343 | 344 | checkstyle.xml 345 | true 346 | 347 | verify 348 | 349 | 350 | checkstyle 351 | 352 | 353 | 354 | 355 | 356 | 357 | org.apache.maven.plugins 358 | maven-compiler-plugin 359 | ${maven.compiler.plugin} 360 | 361 | 11 362 | 363 | 364 | 365 | 366 | org.apache.maven.plugins 367 | maven-site-plugin 368 | ${maven.site.plugin} 369 | 370 | 371 | 372 | org.apache.maven.plugins 373 | maven-project-info-reports-plugin 374 | ${maven.project.info.reports.plugin} 375 | 376 | 377 | 378 | org.apache.maven.plugins 379 | maven-surefire-plugin 380 | ${surefire.plugin.version} 381 | 382 | 383 | **/resources/**/*.java 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | org.apache.maven.plugins 394 | maven-jxr-plugin 395 | 3.3.0 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | deploy 404 | 405 | 406 | 407 | 408 | org.apache.maven.plugins 409 | maven-compiler-plugin 410 | 3.11.0 411 | 412 | 11 413 | 414 | 415 | 416 | 417 | org.apache.maven.plugins 418 | maven-source-plugin 419 | 3.2.0 420 | 421 | 422 | attach-sources 423 | 424 | jar-no-fork 425 | 426 | 427 | 428 | 429 | 430 | 431 | org.apache.maven.plugins 432 | maven-javadoc-plugin 433 | 3.5.0 434 | 435 | ${javadoc.source} 436 | none 437 | 438 | 439 | 440 | attach-javadocs 441 | 442 | jar 443 | 444 | 445 | 446 | 447 | 448 | 449 | org.apache.maven.plugins 450 | maven-gpg-plugin 451 | 3.1.0 452 | 453 | 454 | sign-artifacts 455 | verify 456 | 457 | sign 458 | 459 | 460 | 461 | 462 | 463 | 464 | org.sonatype.plugins 465 | nexus-staging-maven-plugin 466 | 1.6.13 467 | true 468 | 469 | ossrh 470 | https://oss.sonatype.org/ 471 | true 472 | 473 | 474 | 475 | 476 | 477 | org.apache.maven.plugins 478 | maven-release-plugin 479 | 3.0.1 480 | 481 | true 482 | false 483 | release 484 | deploy 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | -------------------------------------------------------------------------------- /.img/wasp.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 16 | 18 | image/svg+xml 19 | 21 | 22 | 23 | 24 | 25 | 27 | 29 | 32 | 36 | 37 | 40 | 44 | 45 | 48 | 52 | 53 | 56 | 60 | 61 | 64 | 68 | 69 | 72 | 76 | 77 | 80 | 84 | 85 | 88 | 92 | 93 | 96 | 100 | 101 | 104 | 108 | 109 | 112 | 116 | 117 | 120 | 124 | 125 | 128 | 132 | 133 | 136 | 140 | 141 | 144 | 148 | 149 | 152 | 156 | 157 | 160 | 164 | 165 | 168 | 172 | 173 | 176 | 180 | 181 | 184 | 188 | 189 | 192 | 196 | 197 | 198 | 199 | 201 | 205 | 209 | 213 | 217 | 221 | 224 | 229 | 230 | 233 | 238 | 243 | 248 | 249 | 252 | 257 | 262 | 263 | 266 | 271 | 276 | 281 | 286 | 291 | 296 | 301 | 302 | 305 | 310 | 315 | 316 | 319 | 324 | 329 | 334 | 335 | 338 | 343 | 344 | 347 | 352 | 357 | 358 | 361 | 366 | 371 | 376 | 377 | 380 | 385 | 390 | 395 | 400 | 405 | 410 | 411 | 414 | 419 | 420 | 423 | 428 | 429 | 432 | 437 | 438 | 441 | 446 | 451 | 452 | 455 | 460 | 465 | 470 | 475 | 476 | 479 | 484 | 485 | 488 | 493 | 494 | 497 | 502 | 503 | 506 | 511 | 516 | 521 | 526 | 531 | 536 | 541 | 546 | 547 | 550 | 555 | 560 | 565 | 566 | 567 | 568 | --------------------------------------------------------------------------------