org.smallmind.spark.singularity.maven.GenerateSingularityMojo Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) 2007 through 2024 David Berkman
*
* This file is part of the SmallMind Code Project.
*
* The SmallMind Code Project is free software, you can redistribute
* it and/or modify it under either, at your discretion...
*
* 1) The terms of GNU Affero General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* ...or...
*
* 2) The terms of the Apache License, Version 2.0.
*
* The SmallMind Code Project is distributed in the hope that it will
* be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License or Apache License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and the Apache License along with the SmallMind Code Project. If not, see
* or .
*
* Additional permission under the GNU Affero GPL version 3 section 7
* ------------------------------------------------------------------
* If you modify this Program, or any covered work, by linking or
* combining it with other code, such other code is not for that reason
* alone subject to any of the requirements of the GNU Affero GPL
* version 3.
*/
package org.smallmind.spark.singularity.maven;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.smallmind.nutsnbolts.zip.CompressionType;
import org.smallmind.spark.singularity.boot.SingularityEntryPoint;
import org.smallmind.spark.singularity.boot.SingularityIndex;
// Generates Singularity based one jar applications
@Mojo(name = "generate-singularity", defaultPhase = LifecyclePhase.PACKAGE, requiresDependencyResolution = ResolutionScope.RUNTIME, threadSafe = true)
public class GenerateSingularityMojo extends AbstractMojo {
@Parameter(readonly = true, property = "project")
private MavenProject project;
@Parameter
private Exclusion[] exclusions;
@Parameter(defaultValue = "singularity")
private String singularityBuildDir;
@Parameter
private String mainClass;
@Parameter(defaultValue = "false")
private boolean verbose;
@Parameter(defaultValue = "false")
private boolean skip;
@Component
ArtifactFactory artifactFactory;
@Parameter(readonly = true, property = "plugin.artifacts")
protected List pluginArtifacts;
public void execute ()
throws MojoExecutionException {
if (!skip) {
Artifact applicationArtifact;
SingularityIndex singularityIndex = new SingularityIndex();
Path buildPath;
Path libraryPath;
Path indexPath;
Path classesPath;
Path compressedFile;
boolean bootClassesFound = false;
try {
Files.createDirectories(buildPath = Paths.get(project.getBuild().getDirectory(), singularityBuildDir));
Files.createDirectories(libraryPath = buildPath.resolve("META-INF").resolve("singularity").resolve("lib"));
Files.createDirectories(indexPath = buildPath.resolve("META-INF").resolve("singularity").resolve("index"));
} catch (IOException ioException) {
throw new MojoExecutionException("Unable to create a build directory", ioException);
}
for (Artifact pluginArtifact : pluginArtifacts) {
if (pluginArtifact.getGroupId().equals("org.smallmind") && pluginArtifact.getArtifactId().equals("spark-singularity-boot")) {
try {
copyBootClasses(singularityIndex, pluginArtifact.getFile(), buildPath);
} catch (IOException ioException) {
throw new MojoExecutionException("Problem in copying boot classes into the build directory", ioException);
}
bootClassesFound = true;
break;
}
}
if (!bootClassesFound) {
throw new MojoExecutionException("Unable to locate the boot class dependencies");
}
for (Artifact artifact : project.getRuntimeArtifacts()) {
boolean excluded = false;
if ((exclusions != null) && (exclusions.length > 0)) {
for (Exclusion exclusion : exclusions) {
if (exclusion.matchesArtifact(artifact)) {
excluded = true;
break;
}
}
}
if (excluded) {
if (verbose) {
getLog().info(String.format("Excluded dependency(%s)...", artifact.getFile().getName()));
}
} else {
if (verbose) {
getLog().info(String.format("Copying dependency(%s)...", artifact.getFile().getName()));
}
try {
JarFile jarFile = new JarFile(artifact.getFile());
Enumeration jarEntryEnum = jarFile.entries();
while (jarEntryEnum.hasMoreElements()) {
singularityIndex.addInverseJarEntry(jarEntryEnum.nextElement().getName(), artifact.getFile().getName());
}
copyToDestination(artifact.getFile(), libraryPath.resolve(artifact.getFile().getName()));
} catch (IOException ioException) {
throw new MojoExecutionException(String.format("Problem in copying a dependency(%s) into the build directory", artifact), ioException);
}
}
}
if (Files.exists(classesPath = Paths.get(project.getBuild().getDirectory(), "classes"))) {
if (verbose) {
getLog().info("Copying classes directory...");
}
try {
Files.walkFileTree(classesPath, new CopyFileVisitor(singularityIndex, buildPath));
} catch (IOException ioException) {
throw new MojoExecutionException("Unable to copy the classes directory into the build path", ioException);
}
}
if (verbose) {
getLog().info("Creating singularity index...");
}
try {
try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(indexPath.resolve("singularity.idx").toFile()))) {
objectOutputStream.writeObject(singularityIndex);
}
} catch (IOException ioException) {
throw new MojoExecutionException("Unable to write the singularity index", ioException);
}
try {
Manifest manifest = new Manifest();
Attributes attributes = manifest.getMainAttributes();
attributes.put(Attributes.Name.MAIN_CLASS, SingularityEntryPoint.class.getName());
attributes.put(new Attributes.Name("Singularity-Class"), mainClass);
attributes.put(Attributes.Name.SPECIFICATION_TITLE, System.getProperty("java.vm.specification.name"));
attributes.put(Attributes.Name.SPECIFICATION_VERSION, System.getProperty("java.vm.specification.version"));
attributes.put(Attributes.Name.SPECIFICATION_VENDOR, System.getProperty("java.vm.specification.vendor"));
attributes.put(Attributes.Name.IMPLEMENTATION_TITLE, System.getProperty("java.specification.name"));
attributes.put(Attributes.Name.IMPLEMENTATION_VERSION, System.getProperty("java.specification.version"));
attributes.put(Attributes.Name.IMPLEMENTATION_VENDOR, System.getProperty("java.specification.vendor"));
attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
if (verbose) {
getLog().info("Compressing output jar...");
}
CompressionType.JAR.compress(buildPath, compressedFile = Paths.get(project.getBuild().getDirectory(), constructArtifactName()), manifest);
} catch (IOException ioException) {
throw new MojoExecutionException("Problem constructing the executable jar", ioException);
}
applicationArtifact = artifactFactory.createArtifact(project.getGroupId(), project.getArtifactId(), project.getVersion(), "compile", "jar");
applicationArtifact.setFile(compressedFile.toFile());
project.addAttachedArtifact(applicationArtifact);
}
}
private String constructArtifactName () {
StringBuilder nameBuilder = new StringBuilder(project.getArtifactId()).append('-').append(project.getVersion());
if (project.getArtifact().getClassifier() != null) {
nameBuilder.append('-').append(project.getArtifact().getClassifier());
}
return nameBuilder.append(".jar").toString();
}
private void copyToDestination (File file, Path destinationPath)
throws IOException {
FileInputStream inputStream;
FileOutputStream outputStream;
FileChannel readChannel;
FileChannel writeChannel;
long bytesTransferred;
long currentPosition = 0;
readChannel = (inputStream = new FileInputStream(file)).getChannel();
writeChannel = (outputStream = new FileOutputStream(destinationPath.toFile())).getChannel();
while ((currentPosition < readChannel.size()) && (bytesTransferred = readChannel.transferTo(currentPosition, 8192, writeChannel)) >= 0) {
currentPosition += bytesTransferred;
}
outputStream.close();
inputStream.close();
}
private void copyBootClasses (SingularityIndex singularityIndex, File jarFile, Path destinationPath)
throws IOException {
byte[] buffer = new byte[1024];
try (JarInputStream jarInputStream = new JarInputStream(new FileInputStream(jarFile))) {
JarEntry jarEntry;
while ((jarEntry = jarInputStream.getNextJarEntry()) != null) {
if ((!jarEntry.isDirectory()) && jarEntry.getName().startsWith("org/smallmind/spark/singularity/boot/")) {
if (verbose) {
getLog().info(String.format("Copying boot class(%s)...", jarEntry.getName()));
}
Files.createDirectories(destinationPath.resolve(jarEntry.getName().substring(0, jarEntry.getName().lastIndexOf('/'))));
try (OutputStream outputStream = Files.newOutputStream(destinationPath.resolve(jarEntry.getName()), StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
long totalBytesToRead = jarEntry.getSize();
int totalBytesRead = 0;
int bytesRead;
do {
bytesRead = jarInputStream.read(buffer, 0, (int)Math.min(buffer.length, totalBytesToRead - totalBytesRead));
outputStream.write(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
} while (totalBytesRead < totalBytesToRead);
}
singularityIndex.addFileName(jarEntry.getName());
}
}
}
}
}