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
2 |
3 | [](https://search.maven.org/search?q=g:se.kth.castor%20AND%20a:deptrim*)
4 | [](https://github.com/castor-software/deptrim/actions/workflows/build.yml)
5 | [](https://sonarcloud.io/dashboard?id=castor-software_deptrim)
6 | [](https://codecov.io/gh/ASSERT-KTH/deptrim)
7 | [](https://sonarcloud.io/dashboard?id=castor-software_deptrim)
8 | [](https://sonarcloud.io/dashboard?id=castor-software_deptrim)
9 | [](https://sonarcloud.io/dashboard?id=castor-software_deptrim)
10 | [](https://sonarcloud.io/dashboard?id=castor-software_deptrim)
11 | [](https://sonarcloud.io/dashboard?id=castor-software_deptrim)
12 | [](https://sonarcloud.io/dashboard?id=castor-software_deptrim)
13 | [](https://sonarcloud.io/dashboard?id=castor-software_deptrim)
14 | [](https://sonarcloud.io/dashboard?id=castor-software_deptrim)
15 | [](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 |
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 |
--------------------------------------------------------------------------------