Skip to content

Commit

Permalink
WIP - add robust way to resolve main artifact directory for shaded jars
Browse files Browse the repository at this point in the history
  • Loading branch information
rudsberg committed Sep 5, 2024
1 parent 11aa828 commit 489c1f6
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,9 @@ public void execute() throws MojoExecutionException {
maybeSetMainClassFromPlugin(this::consumeConfigurationNodeValue, "org.apache.maven.plugins:maven-jar-plugin", "archive", "manifest", "mainClass");
maybeAddGeneratedResourcesConfig(buildArgs);

// TODO: use flag
if (true) {
var generator = new SBOMGenerator(mavenProject, mavenSession, pluginManager, repositorySystem);
var generator = new SBOMGenerator(mavenProject, mavenSession, pluginManager, repositorySystem, mainClass);
generator.generate();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,12 @@ class ArtifactToPackageNameResolver {
private final List<RemoteRepository> remoteRepositories;
private final ShadedPackageNameResolver shadedPackageNameResolver;

ArtifactToPackageNameResolver(MavenProject mavenProject, RepositorySystem repositorySystem, RepositorySystemSession repositorySystemSession) {
ArtifactToPackageNameResolver(MavenProject mavenProject, RepositorySystem repositorySystem, RepositorySystemSession repositorySystemSession, String mainClass) {
this.mavenProject = mavenProject;
this.repositorySystem = repositorySystem;
this.repositorySystemSession = repositorySystemSession;
this.remoteRepositories = mavenProject.getRemoteProjectRepositories();
this.shadedPackageNameResolver = new ShadedPackageNameResolver(mavenProject);
this.shadedPackageNameResolver = new ShadedPackageNameResolver(mavenProject, mainClass);
}

Set<ArtifactAdapter> getArtifactPackageMappings() throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,12 @@ static Optional<String> extractPackageName(Path filePath, Path basePath) {
packageName = packageName.replace(File.separatorChar, '.');
return Optional.of(packageName);
}

static boolean containsClassFiles(Path directory) {
try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory, "*.class")) {
return stream.iterator().hasNext();
} catch (Exception e) {
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,17 @@ public class SBOMGenerator {
private final MavenSession mavenSession;
private final BuildPluginManager pluginManager;
private final RepositorySystem repositorySystem;
private final String mainClass;

private static final String SBOM_NAME = "WIP_SBOM";
private static final String FILE_FORMAT = "json";

public SBOMGenerator(MavenProject mavenProject, MavenSession mavenSession, BuildPluginManager pluginManager, RepositorySystem repositorySystem) {
public SBOMGenerator(MavenProject mavenProject, MavenSession mavenSession, BuildPluginManager pluginManager, RepositorySystem repositorySystem, String mainClass) {
this.mavenProject = mavenProject;
this.mavenSession = mavenSession;
this.pluginManager = pluginManager;
this.repositorySystem = repositorySystem;
this.mainClass = mainClass;
}

public void generate() throws MojoExecutionException {
Expand Down Expand Up @@ -109,7 +111,7 @@ public void generate() throws MojoExecutionException {
Files.copy(sbomPath, Paths.get(outputDirectory, "SBOM_UNMODIFIED" + "." + FILE_FORMAT), REPLACE_EXISTING);
System.out.println("✅ CycloneDX SBOM generated successfully: " + sbomPath);

var resolver = new ArtifactToPackageNameResolver(mavenProject, repositorySystem, mavenSession.getRepositorySession());
var resolver = new ArtifactToPackageNameResolver(mavenProject, repositorySystem, mavenSession.getRepositorySession(), mainClass);
Set<ArtifactAdapter> artifactsWithPackageNames = resolver.getArtifactPackageMappings();
augmentSBOMWithPackageNames(sbomPath, artifactsWithPackageNames);
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.utils.xml.Xpp3Dom;

import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
Expand All @@ -25,12 +26,14 @@ class ShadedPackageNameResolver {
*/
private final Set<Path> pathToClassFilesDirectories;
private final Set<Path> visitedPathToClassFileDirectories;
private final String mainClass;

ShadedPackageNameResolver(MavenProject mavenProject) {
ShadedPackageNameResolver(MavenProject mavenProject, String mainClass) {
this.mavenProject = mavenProject;
this.optionalShadePlugin = getShadePluginIfUsed(mavenProject);
this.pathToClassFilesDirectories = new HashSet<>();
this.visitedPathToClassFileDirectories = new HashSet<>();
this.mainClass = mainClass;
}

/**
Expand All @@ -54,13 +57,6 @@ Optional<ArtifactAdapter> resolvePackageNamesFromPossiblyShadedJar(Path jarPath,
return Optional.empty();
}

// TODO: PROBLEM eg in /io/micronaut/http, which is one artifact, contains directories which are packages
// TODO: for itself like /io/micronaut/http/bind, but also directories for other aritfacts like /io/micronaut/http/netty.
// TODO: currenlty have no way of differentiating.
// - use all directories with class files as possible targets? Downside is that this visited thing to recollect
// the main artifact's classes will not work.
// - are there meta files in the original jar we can recover and extract useful information from?

/* If the shade plugin is not used, then we are not dealing with a fat or shaded jar. */
if (optionalShadePlugin.isEmpty()) {
return handleNonShadedCase(artifact, jarPath);
Expand Down Expand Up @@ -199,8 +195,15 @@ private Optional<Set<Path>> resolveDirectoriesWithClasses(FileSystem jarFileSyst

boolean isMainArtifact = artifact.equals(mavenProject.getArtifact());
if (isMainArtifact) {
Optional<Path> resolvedPath = tryResolveUsingGAVCoordinates(jarFileSystem.getPath("/"), artifact);
Optional<Path> resolvedPath = findTopClassDirectory(jarFileSystem.getPath("/"), mainClass);
if (resolvedPath.isPresent()) {
System.out.printf("👍 mainClass backwards walk %s.%s -> %s\n", artifact.groupId, artifact.artifactId, resolvedPath.get());
return Optional.of(Set.of(resolvedPath.get()));
}

resolvedPath = tryResolveUsingGAVCoordinates(jarFileSystem.getPath("/"), artifact);
if (resolvedPath.isPresent()) return Optional.of(Set.of(resolvedPath.get()));

System.out.printf("⚠️ The main artifact %s:%s has been relocated and there are more than one remaining unresolved path.\n", artifact.groupId, artifact.artifactId);
return Optional.empty();
}
Expand Down Expand Up @@ -228,6 +231,27 @@ private Optional<Set<Path>> resolveDirectoriesWithClasses(FileSystem jarFileSyst
return Optional.empty();
}


/**
* Resolves the top directory containing class files by traversing backwards from the main class location.
*
* @param qualifiedName the qualified name of the class to start the search from.
* @param rootPath the root of the file system.
* @return a path of the top directory containing class files.
*/
private Optional<Path> findTopClassDirectory(Path rootPath, String qualifiedName) throws IOException {
String mainClassPath = qualifiedName.replace('.', File.separatorChar) + ".class";
Path classFilePath = rootPath.resolve(mainClassPath);
Path currentPath = classFilePath.getParent();
while (currentPath != null && !Files.isSameFile(currentPath, rootPath)) {
if (FileWalkerUtility.containsClassFiles(currentPath)) {
return Optional.of(currentPath);
}
currentPath = currentPath.getParent();
}
return Optional.empty();
}

/**
* Helper method to resolve GAV coordinates and check if they exist in the class files directory.
* Tries both with and without using the artifactId.
Expand Down

0 comments on commit 489c1f6

Please sign in to comment.