Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.netgrif</groupId>
<artifactId>nae-module-maven-plugin</artifactId>
<version>1.0.1</version>
<version>1.1.0</version>
<packaging>maven-plugin</packaging>

<name>nae-module-maven-plugin</name>
Expand Down Expand Up @@ -145,7 +145,11 @@
<version>3.6.4</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>io.github.classgraph</groupId>
<artifactId>classgraph</artifactId>
<version>4.8.179</version>
</dependency>
<dependency>
<groupId>org.twdata.maven</groupId>
<artifactId>mojo-executor</artifactId>
Expand Down
223 changes: 206 additions & 17 deletions src/main/java/com/netgrif/maven/plugin/module/BuildModuleMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
import com.netgrif.maven.plugin.module.assembly.AssemblyDescriptorBuilder;
import com.netgrif.maven.plugin.module.assembly.DependencySetBuilder;
import com.netgrif.maven.plugin.module.parameters.SimpleArtifact;
import io.github.classgraph.ClassGraph;
import io.github.classgraph.ClassInfo;
import io.github.classgraph.ScanResult;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Developer;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.BuildPluginManager;
import org.apache.maven.plugin.MojoExecutionException;
Expand All @@ -17,13 +21,19 @@
import org.apache.maven.shared.dependency.graph.DependencyNode;
import org.eclipse.sisu.Nullable;

import java.io.File;
import java.io.IOException;
import java.io.*;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.stream.Collectors;

import static org.twdata.maven.mojoexecutor.MojoExecutor.*;

Expand All @@ -38,7 +48,9 @@
requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME)
public class BuildModuleMojo extends AbstractMojo {

private final Log log = getLog();
private Log log() {
return getLog();
}

@Component
private MavenProject project;
Expand All @@ -64,6 +76,9 @@ public class BuildModuleMojo extends AbstractMojo {
@Parameter(property = "singleOutput")
private boolean singleOutput = true;

@Parameter(property = "customManifestOutputJar", defaultValue = "false")
private boolean customManifestOutputJar;

@Override
public void execute() throws MojoExecutionException {
ProjectBuildingRequest buildingRequest = new DefaultProjectBuildingRequest(session.getProjectBuildingRequest());
Expand All @@ -76,21 +91,30 @@ public void execute() throws MojoExecutionException {
SimpleArtifact hostAppArtifact = getHostAppArtifact();

if (hostAppArtifact == null) {
log.warn("Packaging module without host application");
log().warn("Packaging module without host application");
} else {
hostDep = findDependency(hostAppArtifact.getGroupId(), hostAppArtifact.getArtifactId(), hostAppArtifact.getVersion(), rootNode);
if (hostDep == null || hostDep.getArtifact() == null)
throw new HostApplicationNotFoundException("Cannot find host app artifact as dependency: " + hostAppArtifact);
if (log.isDebugEnabled())
log.debug("Host app dependency: " + hostDep.getArtifact());
if (log().isDebugEnabled())
log().debug("Host app dependency: " + hostDep.getArtifact());
hostAppDependencies = flattenDependencies(hostDep);
log.info("Dependencies aggregated " + hostAppDependencies.size() + " from host app");
log().info("Dependencies aggregated " + hostAppDependencies.size() + " from host app");
hostAppDependencies.forEach(d -> {
if (log.isDebugEnabled())
log.debug(d.getArtifact().toString());
if (log().isDebugEnabled())
log().debug(d.getArtifact().toString());
});
}

try {
createJarWithCustomManifest();

generateSpringFactoriesToOutputDir();

} catch (IOException ioEx) {
log().error("Failed to create JAR with custom manifest", ioEx);
throw new MojoExecutionException("Failed to create JAR with custom manifest", ioEx);
}

// Build assembly descriptor
AssemblyDescriptorBuilder assembly = new AssemblyDescriptorBuilder();
Expand All @@ -103,16 +127,16 @@ public void execute() throws MojoExecutionException {
File targetDir = new File(project.getBuild().getDirectory() + File.separator + "assembly");
try {
Files.createDirectories(targetDir.toPath());
log.info("Successfully ensured target directory exists: " + targetDir.getAbsolutePath());
log().info("Successfully ensured target directory exists: " + targetDir.getAbsolutePath());
} catch (IOException e) {
throw new RuntimeException("Could not create target directory for package assembly: " + targetDir, e);
}
if (!targetDir.getParentFile().canWrite()) {
throw new RuntimeException("Cannot write to parent directory: " + targetDir.getParent());
}
File descriptor = assembly.build(targetDir.getPath());
if (log.isDebugEnabled())
log.debug("maven-assembly-plugin descriptor saved on path: " + descriptor.getAbsolutePath());
if (log().isDebugEnabled())
log().debug("maven-assembly-plugin descriptor saved on path: " + descriptor.getAbsolutePath());

// Executes maven-assembly-plugin to build the resulting package
executeMojo(
Expand All @@ -134,10 +158,139 @@ public void execute() throws MojoExecutionException {
}
}

private void createJarWithCustomManifest() throws IOException {
File sourceJar = new File(project.getBuild().getDirectory(), project.getBuild().getFinalName() + ".jar");
if (!sourceJar.exists()) {
log().warn("Original JAR file not found: " + sourceJar.getAbsolutePath());
return;
}

Manifest manifest = buildCustomManifest();

if (!customManifestOutputJar) {
File tempJar = new File(sourceJar.getParent(), sourceJar.getName() + ".tmp");
copyJarExcludingManifest(sourceJar, tempJar, manifest);

if (!sourceJar.delete()) {
throw new IOException("Could not delete original JAR before renaming temp JAR!");
}
if (!tempJar.renameTo(sourceJar)) {
throw new IOException("Could not rename temp JAR to original name!");
}
} else {
File outJar = new File(project.getBuild().getDirectory(), project.getBuild().getFinalName() + "-with-manifest.jar");
copyJarExcludingManifest(sourceJar, outJar, manifest);
log().info("Created JAR with custom manifest: " + outJar.getAbsolutePath());
}
}

private void copyJarExcludingManifest(File inputJar, File outputJar, Manifest manifest) throws IOException {
try (
JarInputStream jarIn = new JarInputStream(new FileInputStream(inputJar));
JarOutputStream jarOut = new JarOutputStream(new FileOutputStream(outputJar), manifest)
) {
JarEntry entry;
byte[] buffer = new byte[8192];
while ((entry = jarIn.getNextJarEntry()) != null) {
String name = entry.getName();
if ("META-INF/MANIFEST.MF".equalsIgnoreCase(name)) {
continue;
}
jarOut.putNextEntry(new JarEntry(name));
int bytesRead;
while ((bytesRead = jarIn.read(buffer)) != -1) {
jarOut.write(buffer, 0, bytesRead);
}
jarOut.closeEntry();
}
}
}

private Manifest buildCustomManifest() throws IOException {
File sourceJar = new File(project.getBuild().getDirectory(), project.getBuild().getFinalName() + ".jar");
Manifest manifest;
try (JarInputStream jarIn = new JarInputStream(new FileInputStream(sourceJar))) {
manifest = jarIn.getManifest();
if (manifest == null) {
manifest = new Manifest();
}
}
Attributes mainAttrs = manifest.getMainAttributes();
if (mainAttrs.getValue(Attributes.Name.MANIFEST_VERSION) == null) {
mainAttrs.put(Attributes.Name.MANIFEST_VERSION, "1.0");
}

List<Developer> developers = project.getDevelopers() != null ? project.getDevelopers() : Collections.emptyList();

Map<String, String> customAttributes = new LinkedHashMap<>();
putIfNotNull(customAttributes, "Netgrif-Name", project.getName());
putIfNotNull(customAttributes, "Netgrif-Version", project.getVersion());
putIfNotNull(customAttributes, "Netgrif-Url", project.getUrl());
putIfNotNull(customAttributes, "Netgrif-Description", project.getDescription());
putIfNotNull(customAttributes, "Netgrif-GroupId", project.getGroupId());
putIfNotNull(customAttributes, "Netgrif-ArtifactId", project.getArtifactId());

if (project.getScm() != null) {
putIfNotNull(customAttributes, "Netgrif-SCM-Connection", project.getScm().getConnection());
putIfNotNull(customAttributes, "Netgrif-SCM-URL", project.getScm().getUrl());
}

if (project.getLicenses() != null && !project.getLicenses().isEmpty()) {
putIfNotNull(customAttributes, "Netgrif-License", project.getLicenses().getFirst());
}

if (project.getOrganization() != null) {
putIfNotNull(customAttributes, "Netgrif-Organization", project.getOrganization().getName());
putIfNotNull(customAttributes, "Netgrif-OrganizationUrl", project.getOrganization().getUrl());
}

if (project.getIssueManagement() != null) {
putIfNotNull(customAttributes, "Netgrif-IssueSystem", project.getIssueManagement().getSystem());
putIfNotNull(customAttributes, "Netgrif-IssueUrl", project.getIssueManagement().getUrl());
}

putIfNotNull(customAttributes, "Netgrif-BuildJdk", System.getProperty("java.version"));

customAttributes.put("Netgrif-BuildTime", java.time.ZonedDateTime.now().toString());

String authors = developers.stream()
.map(dev -> {
String name = dev.getName();
String email = dev.getEmail();
String org = dev.getOrganization();
if (name == null || name.isBlank()) {
return null;
}
StringBuilder sb = new StringBuilder(name);
if (org != null && !org.isBlank()) {
sb.append(" (").append(org).append(")");
}
if (email != null && !email.isBlank()) {
sb.append(" <").append(email).append(">");
}
return sb.toString();
})
.filter(Objects::nonNull)
.collect(Collectors.joining("\n"));

if (!authors.isBlank()) {
putIfNotNull(customAttributes, "Netgrif-Author", authors);
}
customAttributes.forEach(mainAttrs::putValue);

return manifest;
}

private void putIfNotNull(Map<String, String> map, String key, Object value) {
if (value != null && !String.valueOf(value).isBlank()) {
map.put(key, String.valueOf(value));
}
}

private SimpleArtifact getHostAppArtifact() {
SimpleArtifact hostAppArtifact = null;
if (hostApp == null && (naeVersion == null || naeVersion.isEmpty())) {
log.warn("Host application was not found. Packaging module without host application");
log().warn("Host application was not found. Packaging module without host application");
return null;
}
if (naeVersion != null && !naeVersion.isEmpty()) {
Expand Down Expand Up @@ -191,8 +344,8 @@ public void separateDescriptor(AssemblyDescriptorBuilder assembly, @Nullable Dep
if (hostAppDependencies != null) {
hostAppDependencies.forEach(depSet::exclude);
}
if (log.isDebugEnabled())
log.debug("Excluding extra dependencies: " + excludes);
if (log().isDebugEnabled())
log().debug("Excluding extra dependencies: " + excludes);
excludes.forEach(depSet::exclude);
}

Expand Down Expand Up @@ -225,9 +378,45 @@ public void singleDescriptor(AssemblyDescriptorBuilder assembly, @Nullable Depen
if (hostAppDependencies != null) {
hostAppDependencies.forEach(depSet::exclude);
}
if (log.isDebugEnabled())
log.debug("Excluding extra dependencies: " + excludes);
if (log().isDebugEnabled())
log().debug("Excluding extra dependencies: " + excludes);
excludes.forEach(depSet::exclude);
}

private void generateSpringFactoriesToOutputDir() throws IOException {
String outputDir = project.getBuild().getOutputDirectory();
File springMetaInf = new File(outputDir, "META-INF/spring");
if (!springMetaInf.exists()) {
springMetaInf.mkdirs();
}
List<String> configClasses = new ArrayList<>();
try (ScanResult scanResult = new ClassGraph()
.overrideClasspath(outputDir)
.enableAnnotationInfo()
.enableClassInfo()
.scan()) {
for (ClassInfo ci : scanResult.getAllClasses()) {
if (ci.hasAnnotation("org.springframework.context.annotation.Configuration")
|| ci.hasAnnotation("org.springframework.boot.autoconfigure.AutoConfiguration")) {
configClasses.add(ci.getName());
}
}
}

if (configClasses.isEmpty()) {
return;
}
Collections.sort(configClasses);

File importsFile = new File(springMetaInf, "org.springframework.boot.autoconfigure.AutoConfiguration.imports");
try (PrintWriter writer = new PrintWriter(new FileWriter(importsFile))) {
for (String className : configClasses) {
log().info("Adding to imports: " + className);
writer.println(className);
}
}
log().info("Generated " + importsFile.getAbsolutePath() + " with auto-configuration: " + configClasses.size() + " entries");
}


}
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
package com.netgrif.maven.plugin.module.parameters;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.maven.artifact.Artifact;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class SimpleArtifact {

public static final String SEPARATOR = ":";
Expand All @@ -28,6 +24,15 @@ public SimpleArtifact(String artifact) {
}
}

public SimpleArtifact() {
}

public SimpleArtifact(String groupId, String artifactId, String version) {
this.groupId = groupId;
this.artifactId = artifactId;
this.version = version;
}

public boolean equalsToArtifact(Artifact artifact) {
if (artifact == null) return false;
return this.groupId.equals(artifact.getGroupId()) && this.artifactId.equals(artifact.getArtifactId()) && this.version.equals(artifact.getVersion());
Expand All @@ -39,6 +44,18 @@ public boolean isValid() {
version != null && !version.isBlank();
}

public String getGroupId() {
return groupId;
}

public String getArtifactId() {
return artifactId;
}

public String getVersion() {
return version;
}

@Override
public String toString() {
return groupId + SEPARATOR + artifactId + SEPARATOR + version;
Expand Down