Skip to content

Commit

Permalink
Compute entrypoint for a standard jar in exploded mode. (#2862)
Browse files Browse the repository at this point in the history
* Add method to compute entrypoint for standard jar in exploded mode
  • Loading branch information
mpeddada1 authored Oct 27, 2020
1 parent 1e1b2d2 commit dbba0c6
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.google.cloud.tools.jib.filesystem.DirectoryWalker;
import com.google.cloud.tools.jib.plugins.common.ZipUtil;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
Expand All @@ -37,6 +38,10 @@
public class JarProcessor {

private static final AbsoluteUnixPath APP_ROOT = AbsoluteUnixPath.get("/app");
private static final String CLASSES = "classes";
private static final String RESOURCES = "resources";
private static final String DEPENDENCIES = "dependencies";
private static final String SNAPSHOT_DEPENDENCIES = "snapshot dependencies";

/**
* Jar Type.
Expand Down Expand Up @@ -76,20 +81,19 @@ public static JarType determineJarType(Path jarPath) throws IOException {
* @throws IOException if I/O error occurs when opening the jar file or if temporary directory
* provided doesn't exist
*/
public static List<FileEntriesLayer> explodeStandardJar(Path jarPath, Path tempDirPath)
static List<FileEntriesLayer> explodeStandardJar(Path jarPath, Path tempDirPath)
throws IOException {
Path localExplodedJarRoot = tempDirPath;
ZipUtil.unzip(jarPath, localExplodedJarRoot);
List<FileEntriesLayer> layers = new ArrayList<>();

// Get dependencies from Class-Path in the jar's manifest and add a layer each for non-snapshot
// and snapshot dependencies. If Class-Path is not present in the jar's manifest then skip
// adding a dependencies layer.
// adding the dependencies layers.
String classPath = null;
try (JarFile jarFile = new JarFile(jarPath.toFile())) {
classPath = jarFile.getManifest().getMainAttributes().getValue(Attributes.Name.CLASS_PATH);
}

if (classPath != null) {
Predicate<String> isSnapshot = name -> name.contains("SNAPSHOT");
List<String> allDependencies = Splitter.onPattern("\\s+").splitToList(classPath.trim());
Expand All @@ -103,7 +107,7 @@ public static List<FileEntriesLayer> explodeStandardJar(Path jarPath, Path tempD
allDependencies.stream().filter(isSnapshot).map(Paths::get).collect(Collectors.toList());
if (!nonSnapshotDependencies.isEmpty()) {
FileEntriesLayer.Builder nonSnapshotDependenciesLayerBuilder =
FileEntriesLayer.builder().setName("dependencies");
FileEntriesLayer.builder().setName(DEPENDENCIES);
nonSnapshotDependencies.forEach(
path ->
nonSnapshotDependenciesLayerBuilder.addEntry(
Expand All @@ -112,7 +116,7 @@ public static List<FileEntriesLayer> explodeStandardJar(Path jarPath, Path tempD
}
if (!snapshotDependencies.isEmpty()) {
FileEntriesLayer.Builder snapshotDependenciesLayerBuilder =
FileEntriesLayer.builder().setName("snapshot dependencies");
FileEntriesLayer.builder().setName(SNAPSHOT_DEPENDENCIES);
snapshotDependencies.forEach(
path ->
snapshotDependenciesLayerBuilder.addEntry(
Expand All @@ -121,21 +125,20 @@ public static List<FileEntriesLayer> explodeStandardJar(Path jarPath, Path tempD
}
}

Predicate<Path> isClassFile = path -> path.getFileName().toString().endsWith(".class");
Predicate<Path> isResourceFile = isClassFile.negate();

// Determine class and resource files in the directory containing jar contents and create
// FileEntriesLayer for each type of layer (classes or resources), while maintaining the
// file's original project structure.
Predicate<Path> isClassFile = path -> path.getFileName().toString().endsWith(".class");
Predicate<Path> isResourceFile = isClassFile.negate();
FileEntriesLayer classesLayer =
addDirectoryContentsToLayer(
"classes",
CLASSES,
localExplodedJarRoot,
isClassFile,
APP_ROOT.resolve(RelativeUnixPath.get("explodedJar")));
FileEntriesLayer resourcesLayer =
addDirectoryContentsToLayer(
"resources",
RESOURCES,
localExplodedJarRoot,
isResourceFile,
APP_ROOT.resolve(RelativeUnixPath.get("explodedJar")));
Expand All @@ -145,6 +148,27 @@ public static List<FileEntriesLayer> explodeStandardJar(Path jarPath, Path tempD
return layers;
}

/**
* Computes the entrypoint for a standard jar in exploded mode.
*
* @param jarPath path to jar file
* @return list of {@link String} representing entrypoint
* @throws IOException if I/O error occurs when opening the jar file
*/
static ImmutableList<String> computeEntrypointForExplodedStandard(Path jarPath)
throws IOException {
try (JarFile jarFile = new JarFile(jarPath.toFile())) {
String mainClass =
jarFile.getManifest().getMainAttributes().getValue(Attributes.Name.MAIN_CLASS);
if (mainClass == null) {
throw new IllegalArgumentException(
"`Main-Class:` attribute for an application main class not defined in the input Jar's manifest (`META-INF/MANIFEST.MF` in the Jar).");
}
String classpath = APP_ROOT + "/explodedJar:" + APP_ROOT + "/dependencies/*";
return ImmutableList.of("java", "-cp", classpath, mainClass);
}
}

private static FileEntriesLayer addDirectoryContentsToLayer(
String layerName,
Path sourceRoot,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.google.cloud.tools.jib.cli.jar;

import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;

import com.google.cloud.tools.jib.api.buildplan.AbsoluteUnixPath;
import com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;
Expand Down Expand Up @@ -281,4 +282,44 @@ public void testExplodeMode_standard_withoutClassPathInManifest_containsOnlyClas
AbsoluteUnixPath.get("/app/explodedJar/class1.class"),
AbsoluteUnixPath.get("/app/explodedJar/class2.class"));
}

@Test
public void testExplodeMode_standard_computeEntrypoint_allLayersPresent()
throws IOException, URISyntaxException {
Path standardJar =
Paths.get(Resources.getResource(STANDARD_JAR_WITH_CLASS_PATH_MANIFEST).toURI());
ImmutableList<String> actualEntrypoint =
JarProcessor.computeEntrypointForExplodedStandard(standardJar);

assertThat(actualEntrypoint)
.isEqualTo(
ImmutableList.of("java", "-cp", "/app/explodedJar:/app/dependencies/*", "HelloWorld"));
}

@Test
public void testExplodedMode_standard_computeEntrypoint_noDependenciesLayers()
throws IOException, URISyntaxException {
Path standardJar =
Paths.get(Resources.getResource(STANDARD_JAR_WITHOUT_CLASS_PATH_MANIFEST).toURI());
ImmutableList<String> actualEntrypoint =
JarProcessor.computeEntrypointForExplodedStandard(standardJar);

assertThat(actualEntrypoint)
.isEqualTo(
ImmutableList.of("java", "-cp", "/app/explodedJar:/app/dependencies/*", "HelloWorld"));
}

@Test
public void testExplodedMode_standard_computeEntrypoint_noMainClass() throws URISyntaxException {
Path standardJar = Paths.get(Resources.getResource(STANDARD_JAR_EMPTY).toURI());
IllegalArgumentException ex =
assertThrows(
IllegalArgumentException.class,
() -> JarProcessor.computeEntrypointForExplodedStandard(standardJar));

assertThat(ex)
.hasMessageThat()
.isEqualTo(
"`Main-Class:` attribute for an application main class not defined in the input Jar's manifest (`META-INF/MANIFEST.MF` in the Jar).");
}
}

0 comments on commit dbba0c6

Please sign in to comment.