Skip to content

Commit 63615ea

Browse files
authored
feat: automatic merge for multi-module projects (#82)
1 parent f28c912 commit 63615ea

File tree

2 files changed

+75
-60
lines changed

2 files changed

+75
-60
lines changed

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,12 @@ In order to achieve runtime dependencies introspection with classport, you have
6161
Inside the target project folder.
6262
```bash
6363
mvn compile io.github.project:classport-maven-plugin:0.1.0-SNAPSHOT:embed
64-
mvn package -Dmaven.repo.local=classport-files -DskipTests
64+
mvn package -Dmaven.repo.local=classport-files -DskipTests -Dmaven.main.skip
6565
```
66-
Note: if the project has more than one module, it is required to merge all the `classport-files` folders and use this as a Maven local repo during the packaging phase.
66+
> Note: `-Dmaven.main.skip` is used to skip the main class compilation and packaging.
67+
> This is necessary because packaging phase writes the `MANIFEST.MF` file into `target/classes` directory which triggers a recompilation of the main class *for the next submodule* in the reactor phase.
68+
> For a single module project, this `-Dmaven.main.skip` is not needed.
69+
> If you do need to compile the main class, you can run `mvn compile` again.
6770
6871
2. Introspect
6972
```bash
@@ -83,6 +86,7 @@ mvn clean
8386
# Embed
8487
mvn compile io.github.project:classport-maven-plugin:0.1.0-SNAPSHOT:embed
8588
mvn package -Dmaven.repo.local=classport-files -DskipTests
89+
> single module project so we do not need to skip the main class compilation.
8690

8791
# Introspect
8892
mkdir output

maven-plugin/src/main/java/io/github/project/classport/plugin/EmbeddingMojo.java

Lines changed: 69 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
import java.nio.file.FileVisitResult;
88
import java.nio.file.Files;
99
import java.nio.file.Path;
10+
import java.nio.file.Paths;
1011
import java.nio.file.SimpleFileVisitor;
12+
import java.nio.file.StandardCopyOption;
1113
import java.nio.file.attribute.BasicFileAttributes;
1214
import java.util.Arrays;
1315
import java.util.Set;
@@ -33,7 +35,7 @@
3335
import io.github.project.classport.commons.ClassportInfo;
3436
import io.github.project.classport.commons.Utility;
3537

36-
@Mojo(name = "embed", defaultPhase = LifecyclePhase.GENERATE_RESOURCES, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME)
38+
@Mojo(name = "embed", defaultPhase = LifecyclePhase.COMPILE, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME)
3739
public class EmbeddingMojo
3840
extends AbstractMojo {
3941
/**
@@ -93,10 +95,6 @@ private String getArtifactLongId(Artifact a) {
9395
+ ":" + a.getVersion();
9496
}
9597

96-
private void embedDirectory(Artifact a) throws IOException, MojoExecutionException {
97-
embedDirectory(a, a.getFile());
98-
}
99-
10098
private void embedDirectory(Artifact a, File dirToWalk) throws IOException, MojoExecutionException {
10199
ClassportInfo metadata = getMetadata(a);
102100
AnnotationConstantPool acp = new AnnotationConstantPool(metadata);
@@ -125,45 +123,84 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO
125123
});
126124
}
127125

126+
/**
127+
* Root directory (shared by all modules) where embedded artefacts are written
128+
* in Maven-repository layout.
129+
* We keep the name "classport-files" to maintain backward compatibility with the previous version of the plugin.
130+
*/
131+
private File getAggregatedRepoRoot() {
132+
File topLevelBaseDir = session.getTopLevelProject().getBasedir();
133+
return new File(topLevelBaseDir, "classport-files");
134+
}
135+
136+
/**
137+
* Destination path (jar) inside the aggregated repository for the given
138+
* artifact.
139+
*/
140+
private File getRepoPathForArtifact(Artifact artifact, File repoRoot) {
141+
String groupPath = artifact.getGroupId().replace('.', File.separatorChar);
142+
File baseDir = Paths.get(repoRoot.getAbsolutePath(), groupPath, artifact.getArtifactId(), artifact.getVersion()).toFile();
143+
String classifierPart = artifact.getClassifier() != null ? "-" + artifact.getClassifier() : "";
144+
String extension = artifact.getArtifactHandler().getExtension();
145+
return Paths.get(baseDir.getAbsolutePath(), artifact.getArtifactId() + "-" + artifact.getVersion() + classifierPart + "." + extension).toFile();
146+
}
147+
148+
/**
149+
* Copy an artifact into the aggregated repository and embed metadata into the
150+
* copy, leaving the original (e.g. ~/.m2) untouched.
151+
*/
152+
private void embedArtifactIntoRepo(Artifact artifact, File repoRoot)
153+
throws IOException, MojoExecutionException {
154+
File artifactFile = artifact.getFile();
155+
if (artifactFile == null || !artifactFile.exists()) {
156+
getLog().warn("Artifact file not found for " + artifact + " (file: "
157+
+ (artifactFile != null ? artifactFile.getAbsolutePath() : "null") + ")");
158+
return;
159+
}
160+
161+
if (artifactFile.isDirectory()) {
162+
getLog().info(String.format("%s is a directory (most likely target/classes). We don't embed it again since it was already embedded in reactor.", artifactFile.getAbsolutePath()));
163+
return;
164+
}
165+
166+
File destJar = getRepoPathForArtifact(artifact, repoRoot);
167+
destJar.getParentFile().mkdirs();
168+
169+
Files.copy(artifactFile.toPath(), destJar.toPath(), StandardCopyOption.REPLACE_EXISTING);
170+
171+
ClassportInfo meta = getMetadata(artifact);
172+
File tempJar = new File(destJar.getParent(), destJar.getName() + ".tmp");
173+
JarHelper pkgr = new JarHelper(destJar, tempJar, true);
174+
pkgr.embed(meta);
175+
176+
Files.delete(destJar.toPath());
177+
Files.move(tempJar.toPath(), destJar.toPath(), StandardCopyOption.REPLACE_EXISTING);
178+
179+
getLog().info("Embedded artifact into aggregated repo: " + destJar.getAbsolutePath());
180+
}
181+
182+
@Override
128183
public void execute() throws MojoExecutionException, MojoFailureException {
129184
Set<Artifact> dependencyArtifacts = project.getArtifacts();
130185

131-
// Each module gets its own classport directory
132-
File localrepoRoot = new File(project.getBasedir() + "/classport-files");
133-
localrepoRoot.mkdir();
186+
// Shared repository for all modules
187+
File aggregatedRepoRoot = getAggregatedRepoRoot();
188+
aggregatedRepoRoot.mkdirs();
134189

135-
getLog().info("Processing project class files");
190+
getLog().info("Embedding metadata into compiled classes for module: " + project.getArtifactId());
136191
try {
137192
embedDirectory(project.getArtifact(), classesDirectory);
193+
getLog().info("Successfully embedded metadata in compiled classes");
138194
} catch (IOException e) {
139-
getLog().error("Failed to embed annotations in project class files: " + e);
195+
getLog().error("Failed to embed annotations in project class files: " + e.getMessage(), e);
196+
throw new MojoExecutionException("Failed to embed annotations in project class files", e);
140197
}
141198

142199
getLog().info("Processing dependencies");
143200
for (Artifact artifact : dependencyArtifacts) {
144201
try {
145-
ClassportInfo meta = getMetadata(artifact);
146-
String artefactPath = getArtefactPath(artifact, true);
147-
File artefactFullPath = new File(localrepoRoot + "/" + artefactPath);
148-
getLog().debug("Embedding metadata for " + artifact);
149-
if (artifact.getFile().isFile()) {
150-
JarHelper pkgr = new JarHelper(artifact.getFile(),
151-
artefactFullPath,
152-
/* overwrite target if exists? */ true);
153-
pkgr.embed(meta);
154-
155-
// Also copy POMs to classport dir
156-
File pomFile = new File(artifact.getFile().getAbsolutePath().replace(".jar", ".pom"));
157-
File pomDestFile = new File(artefactFullPath.getAbsolutePath().replace(".jar", ".pom"));
158-
if (pomFile.isFile() && !pomDestFile.exists())
159-
Files.copy(Path.of(pomFile.getAbsolutePath()),
160-
Path.of(pomDestFile.getAbsolutePath()));
161-
} else if (artifact.getFile().isDirectory()) {
162-
embedDirectory(artifact);
163-
} else {
164-
getLog().warn("Skipping " + artifact.getArtifactId()
165-
+ " since it does not seem to reside in either a file nor a directory");
166-
}
202+
System.out.println("Embedding artifact: " + artifact.getArtifactId());
203+
embedArtifactIntoRepo(artifact, aggregatedRepoRoot);
167204
} catch (IOException e) {
168205
getLog().error("Failed to embed metadata for " + artifact + ": " + e);
169206
}
@@ -194,30 +231,4 @@ private ClassportInfo getMetadata(Artifact artifact) throws IOException, MojoExe
194231
.collect(Collectors.toList()).toArray(String[]::new));
195232
}
196233

197-
/**
198-
* Get an artefact's path relative to the repository root.
199-
* If resolveSnapshotVersion is true, we get the specific snapshot
200-
* version instead of just "-SNAPSHOT". This may default to true
201-
* in the future, as this seems to be Maven's default behaviour.
202-
*
203-
* TODO: Get the regular path (~/.m2/repository/...) and remove the
204-
* m2/repo-part instead?
205-
*
206-
* @see <a href="https://maven.apache.org/repositories/layout.html">Maven
207-
* docs on repository structure</a>
208-
*/
209-
private String getArtefactPath(Artifact a, boolean resolveSnapshotVersion) {
210-
String classifier = a.getClassifier();
211-
if (classifier == null)
212-
classifier = "";
213-
else
214-
classifier = "-" + classifier;
215-
216-
return String.format("%s/%s/%s/%s-%s%s.%s",
217-
a.getGroupId().replace('.', '/'),
218-
a.getArtifactId(),
219-
a.getBaseVersion(), // This seems to always be the base version
220-
a.getArtifactId(), (resolveSnapshotVersion ? a.getVersion() : a.getBaseVersion()), classifier,
221-
"jar" /* TODO support more extensions */);
222-
}
223234
}

0 commit comments

Comments
 (0)