Added WAR operation
This commit is contained in:
parent
cae601cf6f
commit
287064dec4
7 changed files with 511 additions and 290 deletions
6
examples/.idea/vcs.xml
generated
Normal file
6
examples/.idea/vcs.xml
generated
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -3,6 +3,7 @@ package com.example.demo;
|
||||||
import rife.bld.BuildCommand;
|
import rife.bld.BuildCommand;
|
||||||
import rife.bld.WebProject;
|
import rife.bld.WebProject;
|
||||||
import rife.bld.extension.BootJarOperation;
|
import rife.bld.extension.BootJarOperation;
|
||||||
|
import rife.bld.extension.BootWarOperation;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.logging.ConsoleHandler;
|
import java.util.logging.ConsoleHandler;
|
||||||
|
@ -49,9 +50,21 @@ public class DemoApplicationBuild extends WebProject {
|
||||||
}
|
}
|
||||||
|
|
||||||
@BuildCommand(summary = "Creates an executable JAR for the project")
|
@BuildCommand(summary = "Creates an executable JAR for the project")
|
||||||
public void jar() throws Exception {
|
public void bootjar() throws Exception {
|
||||||
new BootJarOperation()
|
new BootJarOperation()
|
||||||
.fromProject(this)
|
.fromProject(this)
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@BuildCommand(summary = "Creates an executable JAR for the project")
|
||||||
|
public void uberjar() throws Exception {
|
||||||
|
bootjar();
|
||||||
|
}
|
||||||
|
|
||||||
|
@BuildCommand(summary = "Creates WAR for the project")
|
||||||
|
public void war() throws Exception {
|
||||||
|
new BootWarOperation()
|
||||||
|
.fromProject(this)
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
}
|
}
|
271
src/main/java/rife/bld/extension/AbstractBootOperation.java
Normal file
271
src/main/java/rife/bld/extension/AbstractBootOperation.java
Normal file
|
@ -0,0 +1,271 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package rife.bld.extension;
|
||||||
|
|
||||||
|
import rife.bld.Project;
|
||||||
|
import rife.bld.operations.AbstractOperation;
|
||||||
|
import rife.tools.FileUtils;
|
||||||
|
import rife.tools.exceptions.FileUtilsErrorException;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import java.util.spi.ToolProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements commons methods used by Spring Boot operations, such as {@link BootJarOperation} and
|
||||||
|
* {@link BootWarOperation}.
|
||||||
|
*
|
||||||
|
* @author <a href="https://erik.thauvin.net/">Erik C. Thauvin</a>
|
||||||
|
* @since 1.0
|
||||||
|
*/
|
||||||
|
public abstract class AbstractBootOperation extends AbstractOperation<AbstractBootOperation> {
|
||||||
|
private final List<BootManifestAttribute> manifestAttributes_ = new ArrayList<>();
|
||||||
|
private final List<File> sourceDirectories_ = new ArrayList<>();
|
||||||
|
protected
|
||||||
|
Project project_;
|
||||||
|
private String destinationArchiveFileName;
|
||||||
|
private File destinationDirectory_;
|
||||||
|
private String launcherClass_;
|
||||||
|
|
||||||
|
public void deleteDirectories(File... directory) throws FileUtilsErrorException {
|
||||||
|
for (var d : directory) {
|
||||||
|
if (d.exists()) {
|
||||||
|
FileUtils.deleteDirectory(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the destination file name that will be used for the archive creation.
|
||||||
|
*
|
||||||
|
* @param name the war archive destination file name
|
||||||
|
* @return this operation instance
|
||||||
|
*/
|
||||||
|
public AbstractBootOperation destinationArchiveFileName(String name) {
|
||||||
|
destinationArchiveFileName = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the destination file name that will be used for the JAR creation.
|
||||||
|
*
|
||||||
|
* @return the war Jar's destination file name
|
||||||
|
*/
|
||||||
|
public String destinationArchiveFileName() {
|
||||||
|
return destinationArchiveFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the destination directory in which the JAR will be created.
|
||||||
|
*
|
||||||
|
* @return the JAR's destination directory
|
||||||
|
*/
|
||||||
|
public File destinationDirectory() {
|
||||||
|
return destinationDirectory_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the destination directory in which the archive will be created.
|
||||||
|
*
|
||||||
|
* @param directory the war destination directory
|
||||||
|
* @return this operation instance
|
||||||
|
* @since 1.5
|
||||||
|
*/
|
||||||
|
public AbstractBootOperation destinationDirectory(File directory) throws IOException {
|
||||||
|
destinationDirectory_ = directory;
|
||||||
|
mkDirs(destinationDirectory_);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Part of the {@link #execute} operation, copy the {@code BOOT-INF} classes.
|
||||||
|
*/
|
||||||
|
protected void executeCopyBootInfClassesFiles(File stagingBootInfDirectory) throws IOException {
|
||||||
|
var boot_inf_classes_dir = new File(stagingBootInfDirectory, "classes");
|
||||||
|
mkDirs(boot_inf_classes_dir);
|
||||||
|
|
||||||
|
for (var dir : sourceDirectories_) {
|
||||||
|
FileUtils.copyDirectory(dir, boot_inf_classes_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteDirectories(new File(boot_inf_classes_dir, "resources"), new File(boot_inf_classes_dir, "templates"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Part of the {@link #execute} operation, copy the {@code spring-boot-loader} archive content to the staging directory.
|
||||||
|
*/
|
||||||
|
protected void executeCopyBootLoader(File stagingDirectory) throws FileUtilsErrorException {
|
||||||
|
if (project_.standaloneClasspathJars().isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("ERROR: Spring Boot Loader required.");
|
||||||
|
} else {
|
||||||
|
var meta_inf_dir = new File(stagingDirectory, "META-INF");
|
||||||
|
for (var jar : project_.standaloneClasspathJars()) {
|
||||||
|
FileUtils.unzipFile(jar, stagingDirectory);
|
||||||
|
if (meta_inf_dir.exists()) {
|
||||||
|
FileUtils.deleteDirectory(meta_inf_dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Part of the {@link #execute} operation, create the archive from the staging directory.
|
||||||
|
*/
|
||||||
|
protected void executeCreateArchive(File stagingDirectory, Logger logger)
|
||||||
|
throws IOException {
|
||||||
|
executeCreateManifest(stagingDirectory);
|
||||||
|
if (logger.isLoggable(Level.FINE) && (!silent())) {
|
||||||
|
logger.fine(MessageFormat.format("Staging Directory: {0} (exists:{1})", stagingDirectory,
|
||||||
|
stagingDirectory.exists()));
|
||||||
|
logger.fine(MessageFormat.format("Destination Directory: {0} (exists:{1})", destinationDirectory(),
|
||||||
|
destinationDirectory().exists()));
|
||||||
|
logger.fine(MessageFormat.format("Destination WAR: {0}", destinationArchiveFileName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
var out = new StringWriter();
|
||||||
|
var stdout = new PrintWriter(out);
|
||||||
|
var err = new StringWriter();
|
||||||
|
var stderr = new PrintWriter(err);
|
||||||
|
var jarTool = ToolProvider.findFirst("jar").orElseThrow();
|
||||||
|
|
||||||
|
String args;
|
||||||
|
if (logger.isLoggable(Level.FINER)) {
|
||||||
|
args = "-0cMvf";
|
||||||
|
} else {
|
||||||
|
args = "-0cMf";
|
||||||
|
}
|
||||||
|
|
||||||
|
jarTool.run(stdout, stderr, args,
|
||||||
|
new File(destinationDirectory(), destinationArchiveFileName()).getAbsolutePath(),
|
||||||
|
"-C", stagingDirectory.getAbsolutePath(), ".");
|
||||||
|
|
||||||
|
var errBuff = err.getBuffer();
|
||||||
|
if (!errBuff.isEmpty()) {
|
||||||
|
throw new IOException(errBuff.toString());
|
||||||
|
} else {
|
||||||
|
var outBuff = out.getBuffer();
|
||||||
|
if (!outBuff.isEmpty() && logger.isLoggable(Level.INFO) && !silent()) {
|
||||||
|
logger.info(outBuff.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Part of the {@link #execute} operation, create the manifest for the jar archive.
|
||||||
|
*/
|
||||||
|
protected void executeCreateManifest(File stagingDirectory) throws IOException {
|
||||||
|
var meta_inf_dir = new File(stagingDirectory, "META-INF");
|
||||||
|
mkDirs(meta_inf_dir);
|
||||||
|
|
||||||
|
var manifest = new File(meta_inf_dir, "MANIFEST.MF");
|
||||||
|
|
||||||
|
try (var fileWriter = Files.newBufferedWriter(manifest.toPath())) {
|
||||||
|
for (var manifestAttribute : manifestAttributes()) {
|
||||||
|
fileWriter.write(manifestAttribute.name() + ": " + manifestAttribute.value() + System.lineSeparator());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Part of the {@link #execute} operation, creates the {@code WEB-INF} staging directory.
|
||||||
|
*/
|
||||||
|
protected File executeCreateWebInfDirectory(File stagingDirectory) throws IOException {
|
||||||
|
var boot_inf = new File(stagingDirectory, "WEB-INF");
|
||||||
|
mkDirs(boot_inf);
|
||||||
|
return boot_inf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Part of the {@link #execute} operation, configure the JAR launcher class.
|
||||||
|
*/
|
||||||
|
protected AbstractBootOperation launcherClass(String className) {
|
||||||
|
launcherClass_ = className;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the JAR launcher class fully-qualified name.
|
||||||
|
*/
|
||||||
|
protected String launcherClass() {
|
||||||
|
if (launcherClass_ == null) {
|
||||||
|
throw new IllegalArgumentException("ERROR: Spring boot launcher class required.");
|
||||||
|
}
|
||||||
|
return launcherClass_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides an attribute to put in the JAR manifest.
|
||||||
|
*
|
||||||
|
* @param name the attribute name to put in the manifest
|
||||||
|
* @param value the attribute value to put in the manifest
|
||||||
|
* @return this operation instance
|
||||||
|
*/
|
||||||
|
public AbstractBootOperation manifestAttribute(String name, String value) {
|
||||||
|
manifestAttributes_.add(new BootManifestAttribute(name, value));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the list of attributes that will be put in the jar manifest.
|
||||||
|
*/
|
||||||
|
public List<BootManifestAttribute> manifestAttributes() {
|
||||||
|
return manifestAttributes_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a map of attributes to put in the jar manifest.
|
||||||
|
* <p>
|
||||||
|
* A copy will be created to allow this map to be independently modifiable.
|
||||||
|
*
|
||||||
|
* @param attributes the attributes to put in the manifest
|
||||||
|
* @return this operation instance
|
||||||
|
* \
|
||||||
|
*/
|
||||||
|
public AbstractBootOperation manifestAttributes(Collection<BootManifestAttribute> attributes) {
|
||||||
|
manifestAttributes_.addAll(attributes);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a directory for the given file path, including any necessary but nonexistent parent directories.
|
||||||
|
*/
|
||||||
|
protected void mkDirs(File file) throws IOException {
|
||||||
|
if (!file.exists() && !file.mkdirs()) {
|
||||||
|
throw new IOException("ERROR: unable to create: " + file.getAbsolutePath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides source directories that will be used for the jar archive creation.
|
||||||
|
*
|
||||||
|
* @param directories source directories
|
||||||
|
* @return this operation instance
|
||||||
|
*/
|
||||||
|
public AbstractBootOperation sourceDirectories(File... directories) {
|
||||||
|
sourceDirectories_.addAll(List.of(directories));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,82 +17,25 @@
|
||||||
package rife.bld.extension;
|
package rife.bld.extension;
|
||||||
|
|
||||||
import rife.bld.Project;
|
import rife.bld.Project;
|
||||||
import rife.bld.WebProject;
|
|
||||||
import rife.bld.operations.AbstractOperation;
|
|
||||||
import rife.tools.FileUtils;
|
import rife.tools.FileUtils;
|
||||||
import rife.tools.exceptions.FileUtilsErrorException;
|
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.text.MessageFormat;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import java.util.spi.ToolProvider;
|
|
||||||
import java.util.zip.CRC32;
|
|
||||||
|
|
||||||
public class BootJarOperation extends AbstractOperation<BootJarOperation> {
|
/**
|
||||||
|
* Builds and creates a Sprint Boot executable java archive (JAR).
|
||||||
|
*
|
||||||
|
* @author <a href="https://erik.thauvin.net/">Erik C. Thauvin</a>
|
||||||
|
* @since 1.0
|
||||||
|
*/
|
||||||
|
public class BootJarOperation extends AbstractBootOperation {
|
||||||
private static final Logger LOGGER = Logger.getLogger(BootJarOperation.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(BootJarOperation.class.getName());
|
||||||
private final CRC32 crc32_ = new CRC32();
|
|
||||||
private final List<File> libJars_ = new ArrayList<>();
|
private final List<File> libJars_ = new ArrayList<>();
|
||||||
private final List<ManifestAttribute> manifestAttributes_ = new ArrayList<>();
|
|
||||||
private final List<File> sourceDirectories_ = new ArrayList<>();
|
|
||||||
private File destinationDirectory_;
|
|
||||||
private String destinationJarFileName_;
|
|
||||||
private String jarLauncherClass_ = "org.springframework.boot.loader.JarLauncher";
|
|
||||||
private WebProject project_;
|
|
||||||
|
|
||||||
private long computeCrc32Checksum(Path filePath) throws IOException {
|
|
||||||
crc32_.reset();
|
|
||||||
crc32_.update(Files.readAllBytes(filePath));
|
|
||||||
|
|
||||||
return crc32_.getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the destination directory in which the JAR will be created.
|
|
||||||
*
|
|
||||||
* @return the JAR's destination directory
|
|
||||||
*/
|
|
||||||
public File destinationDirectory() {
|
|
||||||
return destinationDirectory_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides the destination directory in which the JAR will be created.
|
|
||||||
*
|
|
||||||
* @param directory the war destination directory
|
|
||||||
* @return this operation instance
|
|
||||||
* @since 1.5
|
|
||||||
*/
|
|
||||||
public BootJarOperation destinationDirectory(File directory) throws IOException {
|
|
||||||
destinationDirectory_ = directory;
|
|
||||||
mkDirs(destinationDirectory_);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides the destination file name that will be used for the JAR creation.
|
|
||||||
*
|
|
||||||
* @param name the war archive destination file name
|
|
||||||
* @return this operation instance
|
|
||||||
*/
|
|
||||||
public BootJarOperation destinationJarFileName(String name) {
|
|
||||||
destinationJarFileName_ = name;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the destination file name that will be used for the JAR creation.
|
|
||||||
*
|
|
||||||
* @return the war Jar's destination file name
|
|
||||||
*/
|
|
||||||
public String destinationJarFileName() {
|
|
||||||
return destinationJarFileName_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs the BootJar operation.
|
* Performs the BootJar operation.
|
||||||
|
@ -108,15 +51,15 @@ public class BootJarOperation extends AbstractOperation<BootJarOperation> {
|
||||||
var staging_dir = Files.createTempDirectory("bootjar").toFile();
|
var staging_dir = Files.createTempDirectory("bootjar").toFile();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var boot_inf_dir = executeCreateBootInfDirectory(staging_dir);
|
var boot_inf_dir = executeCreateWebInfDirectory(staging_dir);
|
||||||
executeCopyBootInfClassesFiles(boot_inf_dir);
|
executeCopyBootInfClassesFiles(boot_inf_dir);
|
||||||
executeCopyBootInfLibJars(boot_inf_dir);
|
executeCopyBootInfLibs(boot_inf_dir);
|
||||||
executeCopyBootLoader(staging_dir);
|
executeCopyBootLoader(staging_dir);
|
||||||
|
|
||||||
executeCreateJar(staging_dir);
|
executeCreateArchive(staging_dir, LOGGER);
|
||||||
|
|
||||||
if (!silent()) {
|
if (!silent()) {
|
||||||
System.out.printf("The executable JAR (%s) was created in: %s%n", destinationJarFileName(),
|
System.out.printf("The executable JAR (%s) was created in: %s%n", destinationArchiveFileName(),
|
||||||
destinationDirectory());
|
destinationDirectory());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,27 +68,10 @@ public class BootJarOperation extends AbstractOperation<BootJarOperation> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Part of the {@link #execute} operation, copy the {@code BOOT-INF} classes.
|
|
||||||
*/
|
|
||||||
protected void executeCopyBootInfClassesFiles(File stagingBootInfDirectory) throws IOException {
|
|
||||||
var boot_inf_classes_dir = new File(stagingBootInfDirectory, "classes");
|
|
||||||
mkDirs(boot_inf_classes_dir);
|
|
||||||
|
|
||||||
for (var dir : sourceDirectories_) {
|
|
||||||
FileUtils.copyDirectory(dir, boot_inf_classes_dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
var resources_dir = new File(boot_inf_classes_dir, "resources");
|
|
||||||
if (resources_dir.exists()) {
|
|
||||||
FileUtils.deleteDirectory(resources_dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Part of the {@link #execute} operation, copy the {@code BOOT-INF} libs.
|
* Part of the {@link #execute} operation, copy the {@code BOOT-INF} libs.
|
||||||
*/
|
*/
|
||||||
protected void executeCopyBootInfLibJars(File stagingBootInfDirectory) throws IOException {
|
protected void executeCopyBootInfLibs(File stagingBootInfDirectory) throws IOException {
|
||||||
var boot_inf_lib_dir = new File(stagingBootInfDirectory, "lib");
|
var boot_inf_lib_dir = new File(stagingBootInfDirectory, "lib");
|
||||||
mkDirs(boot_inf_lib_dir);
|
mkDirs(boot_inf_lib_dir);
|
||||||
|
|
||||||
|
@ -154,226 +80,44 @@ public class BootJarOperation extends AbstractOperation<BootJarOperation> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Part of the {@link #execute} operation, copy the {@code spring-boot-loader} archive content to the staging directory.
|
|
||||||
*/
|
|
||||||
protected void executeCopyBootLoader(File stagingDirectory) throws FileUtilsErrorException {
|
|
||||||
if (project_.standaloneClasspathJars().isEmpty()) {
|
|
||||||
throw new IllegalArgumentException("ERROR: Spring Boot Loader required.");
|
|
||||||
} else {
|
|
||||||
for (var jar : project_.standaloneClasspathJars()) {
|
|
||||||
FileUtils.unzipFile(jar, stagingDirectory);
|
|
||||||
var meta_inf_dir = new File(stagingDirectory, "META-INF");
|
|
||||||
if (meta_inf_dir.exists()) {
|
|
||||||
FileUtils.deleteDirectory(meta_inf_dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Part of the {@link #execute} operation, creates the {@code BOOT-INF} staging directory.
|
|
||||||
*/
|
|
||||||
protected File executeCreateBootInfDirectory(File stagingDirectory) throws IOException {
|
|
||||||
var boot_inf = new File(stagingDirectory, "BOOT-INF");
|
|
||||||
mkDirs(boot_inf);
|
|
||||||
return boot_inf;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Part of the {@link #execute} operation, create the executable JAR from the staging directory.
|
|
||||||
*/
|
|
||||||
protected void executeCreateJar(File stagingDirectory)
|
|
||||||
throws IOException {
|
|
||||||
executeCreateManifest(stagingDirectory);
|
|
||||||
if (LOGGER.isLoggable(Level.FINE) && (!silent())) {
|
|
||||||
LOGGER.fine(MessageFormat.format("Staging Directory: {0} (exists:{1})", stagingDirectory,
|
|
||||||
stagingDirectory.exists()));
|
|
||||||
LOGGER.fine(MessageFormat.format("Destination Directory: {0} (exists:{1})", destinationDirectory(),
|
|
||||||
destinationDirectory().exists()));
|
|
||||||
LOGGER.fine(MessageFormat.format("Destination JAR: {0}", destinationJarFileName()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// try (var zipOutputStream = new ZipOutputStream(new FileOutputStream(
|
|
||||||
// new File(destinationDirectory(), destinationJarFileName())))) {
|
|
||||||
// zipOutputStream.setLevel(ZipOutputStream.STORED);
|
|
||||||
// zipOutputStream.setMethod(Deflater.NO_COMPRESSION);
|
|
||||||
// var stagingPath = stagingDirectory.toPath();
|
|
||||||
// Files.walkFileTree(stagingPath, new SimpleFileVisitor<>() {
|
|
||||||
// public FileVisitResult visitFile(Path filePath, BasicFileAttributes attrs) throws IOException {
|
|
||||||
// var zipEntry = new ZipEntry(stagingPath.relativize(filePath).toString());
|
|
||||||
// zipEntry.setMethod(ZipEntry.STORED);
|
|
||||||
// zipEntry.setSize(filePath.toFile().length());
|
|
||||||
// zipEntry.setSize(filePath.toFile().length());
|
|
||||||
// zipEntry.setCrc(computeCrc32Checksum(filePath));
|
|
||||||
// zipOutputStream.putNextEntry(zipEntry);
|
|
||||||
// Files.copy(filePath, zipOutputStream);
|
|
||||||
// zipOutputStream.closeEntry();
|
|
||||||
//
|
|
||||||
// if (logger.isLoggable(Level.FINER) && !silent()) {
|
|
||||||
// logger.finer(MessageFormat.format("Added to JAR: {0}", filePath));
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return FileVisitResult.CONTINUE;
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
var out = new StringWriter();
|
|
||||||
var stdout = new PrintWriter(out);
|
|
||||||
var err = new StringWriter();
|
|
||||||
var stderr = new PrintWriter(err);
|
|
||||||
var jarTool = ToolProvider.findFirst("jar").orElseThrow();
|
|
||||||
|
|
||||||
String args;
|
|
||||||
if (LOGGER.isLoggable(Level.FINER)) {
|
|
||||||
args = "-0cMvf";
|
|
||||||
} else {
|
|
||||||
args = "-0cMf";
|
|
||||||
}
|
|
||||||
|
|
||||||
jarTool.run(stdout, stderr, args,
|
|
||||||
new File(destinationDirectory(), destinationJarFileName()).getAbsolutePath(),
|
|
||||||
"-C", stagingDirectory.getAbsolutePath(), ".");
|
|
||||||
|
|
||||||
var errBuff = err.getBuffer();
|
|
||||||
if (!errBuff.isEmpty()) {
|
|
||||||
throw new IOException(errBuff.toString());
|
|
||||||
} else {
|
|
||||||
var outBuff = out.getBuffer();
|
|
||||||
if (!outBuff.isEmpty()) {
|
|
||||||
if (LOGGER.isLoggable(Level.INFO) && !silent()) {
|
|
||||||
LOGGER.info(outBuff.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Part of the {@link #execute} operation, create the manifest for the jar archive.
|
|
||||||
*/
|
|
||||||
protected void executeCreateManifest(File stagingDirectory) throws IOException {
|
|
||||||
var meta_inf_dir = new File(stagingDirectory, "META-INF");
|
|
||||||
mkDirs(meta_inf_dir);
|
|
||||||
|
|
||||||
var manifest = new File(meta_inf_dir, "MANIFEST.MF");
|
|
||||||
|
|
||||||
try (var fileWriter = new FileWriter(manifest)) {
|
|
||||||
for (var manifestAttribute : manifestAttributes()) {
|
|
||||||
fileWriter.write(manifestAttribute.name + ": " + manifestAttribute.value + System.lineSeparator());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures the operation from a {@link Project}.
|
* Configures the operation from a {@link Project}.
|
||||||
*
|
|
||||||
* @param project the project to configure the operation from
|
|
||||||
*/
|
*/
|
||||||
public BootJarOperation fromProject(WebProject project) throws IOException {
|
public AbstractBootOperation fromProject(Project project) throws IOException {
|
||||||
project_ = project;
|
project_ = project;
|
||||||
return manifestAttributes(
|
return bootInfLibs(project_.compileClasspathJars())
|
||||||
List.of(
|
.bootInfLibs(project_.runtimeClasspathJars())
|
||||||
new ManifestAttribute("Manifest-Version", "1.0"),
|
.launcherClass("org.springframework.boot.loader.JarLauncher")
|
||||||
new ManifestAttribute("Main-Class", jarLauncherClass()),
|
.manifestAttributes(
|
||||||
new ManifestAttribute("Start-Class", project_.mainClass())
|
List.of(
|
||||||
))
|
new BootManifestAttribute("Manifest-Version", "1.0"),
|
||||||
|
new BootManifestAttribute("Main-Class", launcherClass()),
|
||||||
|
new BootManifestAttribute("Start-Class", project_.mainClass()))
|
||||||
|
)
|
||||||
.destinationDirectory(project.buildDistDirectory())
|
.destinationDirectory(project.buildDistDirectory())
|
||||||
.destinationJarFileName(project.jarFileName())
|
.destinationArchiveFileName(project_.archiveBaseName() + "-" + project_.version() + "-boot.jar")
|
||||||
.libJars(project_.compileClasspathJars())
|
|
||||||
.libJars(project_.runtimeClasspathJars())
|
|
||||||
.sourceDirectories(project_.buildMainDirectory(), project_.srcMainResourcesDirectory());
|
.sourceDirectories(project_.buildMainDirectory(), project_.srcMainResourcesDirectory());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Part of the {@link #execute} operation, configure the JAR launcher class.
|
* Provides library JARs that will be used for the JAR creation.
|
||||||
*/
|
|
||||||
protected BootJarOperation jarLauncherClass(String className) {
|
|
||||||
jarLauncherClass_ = className;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the JAR launcher class fully-qualified name.
|
|
||||||
*/
|
|
||||||
protected String jarLauncherClass() {
|
|
||||||
return jarLauncherClass_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides library JARs that will be used for the jar archive creation.
|
|
||||||
*
|
|
||||||
* @param jar Java archive file
|
|
||||||
* @return this operation instance
|
|
||||||
*/
|
|
||||||
public BootJarOperation libJars(File... jar) {
|
|
||||||
libJars_.addAll(List.of(jar));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides library JARs that will be used for the jar archive creation.
|
|
||||||
*
|
*
|
||||||
* @param jars Java archive files
|
* @param jars Java archive files
|
||||||
* @return this operation instance
|
* @return this operation instance
|
||||||
*/
|
*/
|
||||||
public BootJarOperation libJars(Collection<File> jars) {
|
public BootJarOperation bootInfLibs(Collection<File> jars) {
|
||||||
libJars_.addAll(jars);
|
libJars_.addAll(jars);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides an attribute to put in the JAR manifest.
|
* Provides library JARs that will be used for the JAR creation.
|
||||||
*
|
*
|
||||||
* @param name the attribute name to put in the manifest
|
* @param jar Java archive file
|
||||||
* @param value the attribute value to put in the manifest
|
|
||||||
* @return this operation instance
|
* @return this operation instance
|
||||||
*/
|
*/
|
||||||
public BootJarOperation manifestAttribute(String name, String value) {
|
public BootJarOperation bootInfLibs(File... jar) {
|
||||||
manifestAttributes_.add(new ManifestAttribute(name, value));
|
libJars_.addAll(List.of(jar));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the list of attributes that will be put in the jar manifest.
|
|
||||||
*/
|
|
||||||
public List<ManifestAttribute> manifestAttributes() {
|
|
||||||
return manifestAttributes_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides a map of attributes to put in the jar manifest.
|
|
||||||
* <p>
|
|
||||||
* A copy will be created to allow this map to be independently modifiable.
|
|
||||||
*
|
|
||||||
* @param attributes the attributes to put in the manifest
|
|
||||||
* @return this operation instance
|
|
||||||
* \
|
|
||||||
*/
|
|
||||||
public BootJarOperation manifestAttributes(Collection<ManifestAttribute> attributes) {
|
|
||||||
manifestAttributes_.addAll(attributes);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void mkDirs(File file) throws IOException {
|
|
||||||
if (!file.exists()) {
|
|
||||||
if (!file.mkdirs()) {
|
|
||||||
throw new IOException("ERROR: unable to create: " + file.getAbsolutePath());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides source directories that will be used for the jar archive creation.
|
|
||||||
*
|
|
||||||
* @param directories source directories
|
|
||||||
* @return this operation instance
|
|
||||||
*/
|
|
||||||
public BootJarOperation sourceDirectories(File... directories) {
|
|
||||||
sourceDirectories_.addAll(List.of(directories));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public record ManifestAttribute(String name, String value) {
|
|
||||||
}
|
|
||||||
}
|
}
|
26
src/main/java/rife/bld/extension/BootManifestAttribute.java
Normal file
26
src/main/java/rife/bld/extension/BootManifestAttribute.java
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package rife.bld.extension;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new manifest attribute.
|
||||||
|
*
|
||||||
|
* @param name The attribute name
|
||||||
|
* @param value The attribute value
|
||||||
|
*/
|
||||||
|
public record BootManifestAttribute(String name, String value) {
|
||||||
|
}
|
163
src/main/java/rife/bld/extension/BootWarOperation.java
Normal file
163
src/main/java/rife/bld/extension/BootWarOperation.java
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package rife.bld.extension;
|
||||||
|
|
||||||
|
import rife.bld.Project;
|
||||||
|
import rife.tools.FileUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds and creates a Sprint Boot web archive (WAR).
|
||||||
|
*
|
||||||
|
* @author <a href="https://erik.thauvin.net/">Erik C. Thauvin</a>
|
||||||
|
* @since 1.0
|
||||||
|
*/
|
||||||
|
public class BootWarOperation extends AbstractBootOperation {
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(BootWarOperation.class.getName());
|
||||||
|
private final List<File> webInfLibs_ = new ArrayList<>();
|
||||||
|
private final List<File> webInfProvidedLibs_ = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the BootJar operation.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void execute() throws Exception {
|
||||||
|
if (project_ == null) {
|
||||||
|
throw new IllegalArgumentException("ERROR: project required.");
|
||||||
|
} else if (project_.mainClass() == null) {
|
||||||
|
throw new IllegalArgumentException("ERROR: project mainClass required.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var staging_dir = Files.createTempDirectory("bootwar").toFile();
|
||||||
|
|
||||||
|
try {
|
||||||
|
var boot_web_inf_dir = executeCreateWebInfDirectory(staging_dir);
|
||||||
|
executeCopyBootInfClassesFiles(boot_web_inf_dir);
|
||||||
|
executeCopyWebInfLib(boot_web_inf_dir);
|
||||||
|
executeCopyWebInfProvidedLib(boot_web_inf_dir);
|
||||||
|
executeCopyBootLoader(staging_dir);
|
||||||
|
|
||||||
|
executeCreateArchive(staging_dir, LOGGER);
|
||||||
|
|
||||||
|
if (!silent()) {
|
||||||
|
System.out.printf("The WAR (%s) was created in: %s%n", destinationArchiveFileName(),
|
||||||
|
destinationDirectory());
|
||||||
|
}
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
FileUtils.deleteDirectory(staging_dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Part of the {@link #execute} operation, copy the {@code BOOT-INF} libs.
|
||||||
|
*/
|
||||||
|
protected void executeCopyWebInfLib(File stagingBootInfDirectory) throws IOException {
|
||||||
|
var boot_inf_lib_dir = new File(stagingBootInfDirectory, "lib");
|
||||||
|
mkDirs(boot_inf_lib_dir);
|
||||||
|
|
||||||
|
for (var jar : webInfLibs_) {
|
||||||
|
Files.copy(jar.toPath(), boot_inf_lib_dir.toPath().resolve(jar.getName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Part of the {@link #execute} operation, copy the {@code BOOT-INF} libs.
|
||||||
|
*/
|
||||||
|
protected void executeCopyWebInfProvidedLib(File stagingBootInfDirectory) throws IOException {
|
||||||
|
var boot_inf_lib_dir = new File(stagingBootInfDirectory, "lib");
|
||||||
|
mkDirs(boot_inf_lib_dir);
|
||||||
|
|
||||||
|
for (var jar : webInfProvidedLibs_) {
|
||||||
|
Files.copy(jar.toPath(), boot_inf_lib_dir.toPath().resolve(jar.getName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the operation from a {@link Project}.
|
||||||
|
*
|
||||||
|
* @param project the project to configure the operation from
|
||||||
|
*/
|
||||||
|
public AbstractBootOperation fromProject(Project project) throws IOException {
|
||||||
|
project_ = project;
|
||||||
|
return webInfLibs(project.compileClasspathJars())
|
||||||
|
.webInfLibs(project.runtimeClasspathJars())
|
||||||
|
.webInfLibs(project.buildDistDirectory())
|
||||||
|
// TODO add provided libs
|
||||||
|
.launcherClass("org.springframework.boot.loader.WarLauncher")
|
||||||
|
.manifestAttributes(
|
||||||
|
List.of(
|
||||||
|
new BootManifestAttribute("Manifest-Version", "1.0"),
|
||||||
|
new BootManifestAttribute("Main-Class", launcherClass()),
|
||||||
|
new BootManifestAttribute("Start-Class", project.mainClass())
|
||||||
|
))
|
||||||
|
.destinationDirectory(project.buildDistDirectory())
|
||||||
|
.destinationArchiveFileName(project.archiveBaseName() + "-" + project.version() + "-boot.war")
|
||||||
|
.sourceDirectories(project.buildMainDirectory(), project.srcMainResourcesDirectory());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides library JARs that will be used for the WAR creation.
|
||||||
|
*
|
||||||
|
* @param jars Java archive files
|
||||||
|
* @return this operation instance
|
||||||
|
*/
|
||||||
|
public BootWarOperation webInfLibs(Collection<File> jars) {
|
||||||
|
webInfLibs_.addAll(jars);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides library JARs that will be used for the WAR creation.
|
||||||
|
*
|
||||||
|
* @param jar Java archive file
|
||||||
|
* @return this operation instance
|
||||||
|
*/
|
||||||
|
public BootWarOperation webInfLibs(File... jar) {
|
||||||
|
webInfLibs_.addAll(List.of(jar));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides library JARs that will be used for the WAR creation in {@code /WEB-INF/lib-provided}.
|
||||||
|
*
|
||||||
|
* @param jars Java archive files
|
||||||
|
* @return this operation instance
|
||||||
|
*/
|
||||||
|
public BootWarOperation webInfProvidedLibs(Collection<File> jars) {
|
||||||
|
webInfProvidedLibs_.addAll(jars);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the library JARs that will be used for the WAR creation in {@code /WEB-INF/lib-provided}.
|
||||||
|
*
|
||||||
|
* @param jar Java archive file
|
||||||
|
* @return this operation instance
|
||||||
|
*/
|
||||||
|
public BootWarOperation webInfProvidedLibs(File... jar) {
|
||||||
|
webInfProvidedLibs_.addAll(List.of(jar));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,7 +16,5 @@
|
||||||
|
|
||||||
package rife.bld.extension;
|
package rife.bld.extension;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
class BootJarOperationTest {
|
class BootJarOperationTest {
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue