com.github.fracpete.minify.Meka Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of minify-meka Show documentation
Show all versions of minify-meka Show documentation
Allows minification of a Meka build environment .
/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
/*
* Meka.java
* Copyright (C) 2017 University of Waikato, Hamilton, NZ
*/
package com.github.fracpete.minify;
import com.github.fracpete.deps4j.MinDeps;
import com.github.fracpete.processoutput4j.output.CollectingProcessOutput;
import net.sourceforge.argparse4j.ArgumentParsers;
import net.sourceforge.argparse4j.impl.Arguments;
import net.sourceforge.argparse4j.inf.ArgumentParser;
import net.sourceforge.argparse4j.inf.ArgumentParserException;
import net.sourceforge.argparse4j.inf.Namespace;
import org.apache.commons.io.FileUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
/**
* Minifies a Meka build environment using a specified minimum set of classes.
*
* @author FracPete (fracpete at waikato dot ac dot nz)
*/
public class Meka {
public final static String EXEC_SKIP = "-Dexec.skip=True";
/** the java home directory to use. */
protected File m_JavaHome;
/** the file with classes to determine the minimum dependencies for. */
protected File m_ClassesFile;
/** the file with additional class names to include (optional). */
protected File m_AdditionalFile;
/** the input build env. */
protected File m_Input;
/** the absolute input path. */
protected String m_InputAbs;
/** packages to keep. */
protected List m_Packages;
/** the output build env. */
protected File m_Output;
/** the absolute output path. */
protected String m_OutputAbs;
/** whether to test the build environment. */
protected boolean m_Test;
/** the pom.xml DOM. */
protected Document m_Document;
/** the mindeps classpath. */
protected String m_MinDepsClassPath;
/**
* Initializes the minifier.
*/
public Meka() {
super();
m_JavaHome = null;
m_ClassesFile = null;
m_AdditionalFile = null;
m_Input = null;
m_InputAbs = null;
m_Packages = new ArrayList<>();
m_Output = null;
m_OutputAbs = null;
m_Test = false;
m_Document = null;
m_MinDepsClassPath = null;
}
/**
* Sets the java home directory.
*
* @param value the directory
*/
public void setJavaHome(File value) {
m_JavaHome = value;
}
/**
* Returns the java home directory.
*
* @return the directory
*/
public File getJavaHome() {
return m_JavaHome;
}
/**
* Sets the file with the class files to inspect.
*
* @param value the file
*/
public void setClassesFile(File value) {
m_ClassesFile = value;
}
/**
* Returns the file with the class files to inspect.
*
* @return the file, null if not set
*/
public File getClassesFile() {
return m_ClassesFile;
}
/**
* Sets the file with the additional classnames to include (optional).
*
* @param value the file
*/
public void setAdditionalFile(File value) {
m_AdditionalFile = value;
}
/**
* Returns the file with the additional classnames to include (optional).
*
* @return the file, null if not set
*/
public File getAdditionalFile() {
return m_AdditionalFile;
}
/**
* Sets the directory to use as input build environment.
*
* @param value the directory
*/
public void setInput(File value) {
m_Input = value;
if (m_Input == null)
m_InputAbs = null;
else
m_InputAbs = m_Input.getAbsolutePath();
}
/**
* Returns the directory to use as input build environment.
*
* @return the directory, null if not set
*/
public File getInput() {
return m_Input;
}
/**
* Sets the packages to keep.
*
* @param value the packages
*/
public void setPackages(List value) {
m_Packages.addAll(value);
}
/**
* Returns the packages to keep.
*
* @return the packages
*/
public List getPackages() {
return m_Packages;
}
/**
* Sets the directory for the output build environment.
*
* @param value the directory
*/
public void setOutput(File value) {
m_Output = value;
if (m_Output == null)
m_OutputAbs = null;
else
m_OutputAbs = m_Output.getAbsolutePath();
}
/**
* Returns the directory for output build environment.
*
* @return the directory, null if not set
*/
public File getOutput() {
return m_Output;
}
/**
* Sets whether to test the minified build env.
*
* @param value true if to test
*/
public void setTest(boolean value) {
m_Test = value;
}
/**
* Returns whether to test the minified build env.
*
* @return true if to test
*/
public boolean getTest() {
return m_Test;
}
/**
* Sets the commandline options.
*
* @param options the options to use
* @return true if successful
* @throws Exception in case of an invalid option
*/
public boolean setOptions(String[] options) throws Exception {
ArgumentParser parser;
Namespace ns;
parser = ArgumentParsers.newArgumentParser(Meka.class.getName());
parser.addArgument("--java-home")
.type(Arguments.fileType().verifyExists().verifyIsDirectory())
.dest("javahome")
.required(true)
.help("The java home directory of the JDK that includes the jdeps binary, default is taken from JAVA_HOME environment variable.");
parser.addArgument("--classes")
.type(Arguments.fileType().verifyExists().verifyIsFile().verifyCanRead())
.dest("classes")
.required(true)
.help("The file containing the classes to determine the dependencies for. Empty lines and lines starting with # get ignored.");
parser.addArgument("--additional")
.type(Arguments.fileType())
.setDefault(new File("."))
.required(false)
.dest("additional")
.help("The file with additional class names to just include.");
parser.addArgument("--input")
.type(Arguments.fileType())
.setDefault(new File("."))
.required(true)
.dest("input")
.help("The directory with the pristing build environment in.");
parser.addArgument("--output")
.type(Arguments.fileType().verifyIsDirectory().verifyExists())
.setDefault(new File("."))
.required(true)
.dest("output")
.help("The directory for storing the minified build environment in.");
parser.addArgument("--test")
.action(Arguments.storeTrue())
.required(false)
.dest("test")
.help("Optional testing of the minified build environment.");
parser.addArgument("package")
.dest("packages")
.required(true)
.nargs("+")
.help("The packages to keep, eg 'meka'.");
try {
ns = parser.parseArgs(options);
}
catch (ArgumentParserException e) {
parser.handleError(e);
return false;
}
setJavaHome(ns.get("javahome"));
setClassesFile(ns.get("classes"));
setAdditionalFile(ns.get("additional"));
setInput(ns.get("input"));
setPackages(ns.getList("packages"));
setOutput(ns.get("output"));
setTest( ns.getBoolean("test"));
return true;
}
/**
* Performs some checks.
*
* @return null if successful, otherwise error message
*/
protected String check() {
if (!m_JavaHome.exists())
return "Java home directory does not exist: " + m_JavaHome;
if (!m_JavaHome.isDirectory())
return "Java home does not point to a directory: " + m_JavaHome;
if (!m_ClassesFile.exists())
return "File with class names does not exist: " + m_ClassesFile;
if (m_ClassesFile.isDirectory())
return "File with class names points to directory: " + m_ClassesFile;
if (!m_Input.exists())
return "Input build environment does not exist: " + m_Input;
if (!m_Input.isDirectory())
return "Input build environment points to a file: " + m_Input;
if (m_Output == null)
return "No output directory supplied!";
return null;
}
/**
* Builds the specified Meka environment.
*
* @param dir the build env
* @return null if successful, otherwise error message
*/
protected String build(File dir) {
String error;
String[] cmd;
ProcessBuilder builder;
CollectingProcessOutput output;
cmd = new String[]{
"mvn",
"clean",
"compile",
"package",
"-DskipTests=True",
EXEC_SKIP,
};
builder = new ProcessBuilder();
builder.command(cmd);
builder.directory(dir);
output = new CollectingProcessOutput();
try {
output.monitor(builder);
if (!output.hasSucceeded()) {
error = "\nExit code: " + output.getExitCode();
if (output.getStdErr().length() > 0)
error += "\nStderr:\n" + output.getStdErr();
if (output.getStdOut().length() > 0)
error += "\nStdout:\n" + output.getStdOut();
return error;
}
}
catch (Exception e) {
return "Failed to execute: " + builder.toString() + "\n" + e;
}
return null;
}
/**
* Reads the pom.xml.
*
* @return null if successful, otherwise error message
*/
protected String readPOM() {
File pom;
String input;
DocumentBuilderFactory factory;
DocumentBuilder builder;
pom = new File(m_InputAbs + File.separator + "pom.xml");
try {
input = new String(Files.readAllBytes(pom.toPath()));
factory = DocumentBuilderFactory.newInstance();
factory.setValidating(false);
factory.setNamespaceAware(false);
factory.setXIncludeAware(false);
factory.setExpandEntityReferences(false);
factory.setIgnoringComments(false);
factory.setIgnoringElementContentWhitespace(false);
builder = factory.newDocumentBuilder();
m_Document = builder.parse(new ByteArrayInputStream(input.getBytes()));
}
catch (Exception e) {
m_Document = null;
return "Failed to read/parse: " + pom + "\n" + e;
}
return null;
}
/**
* Analyzes the pom.xml to generate a classpath for MinDeps.
*
* @return null if successful, otherwise error message
*/
protected String assembleMinDepsClassPath() {
StringBuilder cp;
List parts;
javax.xml.xpath.XPath xpath;
NodeList list;
NodeList clist;
int i;
int n;
Node node;
String group;
String artifact;
String version;
String scope;
String part;
File target;
File[] files;
parts = new ArrayList<>();
try {
xpath = XPathFactory.newInstance().newXPath();
list = (NodeList) xpath.evaluate("//dependency", m_Document, XPathConstants.NODESET);
for (i = 0; i < list.getLength(); i++) {
node = list.item(i);
// scope
scope = null;
clist = (NodeList) xpath.evaluate("./scope", node, XPathConstants.NODESET);
for (n = 0; n < clist.getLength(); n++) {
scope = clist.item(n).getTextContent();
break;
}
if ((scope != null) && scope.equals("test"))
continue;
// group
group = null;
clist = (NodeList) xpath.evaluate("./groupId", node, XPathConstants.NODESET);
for (n = 0; n < clist.getLength(); n++) {
group = clist.item(n).getTextContent();
break;
}
// artifact
artifact = null;
clist = (NodeList) xpath.evaluate("./artifactId", node, XPathConstants.NODESET);
for (n = 0; n < clist.getLength(); n++) {
artifact = clist.item(n).getTextContent();
break;
}
// version
version = null;
clist = (NodeList) xpath.evaluate("./version", node, XPathConstants.NODESET);
for (n = 0; n < clist.getLength(); n++) {
version = clist.item(n).getTextContent();
break;
}
if ((group == null) || (artifact == null) || (version == null))
continue;
// assemble part
part = System.getProperty("user.home")
+ File.separator + ".m2"
+ File.separator + "repository"
+ File.separator + group.replace(".", File.separator)
+ File.separator + artifact
+ File.separator + version
+ File.separator + artifact + "-" + version + ".jar";
if (!new File(part).exists())
throw new IllegalStateException("File not found: " + part);
parts.add(part);
}
}
catch (Exception e) {
return "Failed to determine 'dependency' tags to build classpath!\n" + e;
}
// meka jar
target = new File(m_InputAbs + File.separator + "target");
files = target.listFiles((File dir, String name) -> {
return (name.endsWith("-SNAPSHOT.jar"));
});
if (files == null)
return "No jars found in directory: " + target;
if (files.length == 0)
return "Meka jar not found in directory: " + target;
parts.add(0, files[0].getAbsolutePath());
// assemble the classpath
cp = new StringBuilder();
for (i = 0; i < parts.size(); i++) {
if (i > 0)
cp.append(File.pathSeparator);
cp.append(parts.get(i));
}
System.err.println("Classpath:\n" + cp);
m_MinDepsClassPath = cp.toString();
return null;
}
/**
* Determines the classes to keep.
*
* @param classes to fill in the classes
* @return null if successful, otherwise error message
*/
protected String determineClasses(List classes) {
MinDeps min;
String msg;
// determine minimum set of classes
min = new MinDeps();
min.setJavaHome(getJavaHome());
min.setPackages(new ArrayList<>(m_Packages));
min.setClassPath(m_MinDepsClassPath);
min.setClassesFile(m_ClassesFile);
min.setAdditionalFile(m_AdditionalFile);
msg = min.execute();
if (msg != null)
return "Failed to execute " + MinDeps.class.getName() + ": " + msg;
classes.addAll(min.getDependencies());
return null;
}
/**
* Prepares the output directory, either creating or emptying it.
*
* @return null if successful, otherwise error message
*/
protected String prepareOutputDir() {
File[] files;
String msg;
File dir;
File outDir;
files = m_Output.listFiles();
if (files == null) {
System.err.println("Creating output dir...");
if (m_Output.mkdirs())
return "Failed to create output directory: " + m_Output;
}
else {
if (files.length > 0) {
System.err.println("Cleaning output dir...");
for (File file: files) {
if (file.getName().equals("") || file.getName().equals(".."))
continue;
if (file.isDirectory()) {
try {
FileUtils.deleteDirectory(file);
}
catch (Exception e) {
return "Failed to delete directory: " + file + "\n" + e;
}
}
else {
if (!file.delete())
return "Failed to delete file: " + file;
}
}
}
}
// src/main/java
dir = new File(m_OutputAbs + File.separator + "src" + File.separator + "main" + File.separator + "java");
if (!dir.mkdirs())
return "Failed to create directory: " + dir;
// src/main/resources
dir = new File(m_OutputAbs + File.separator + "src" + File.separator + "main" + File.separator + "resources");
if (!dir.mkdirs())
return "Failed to create directory: " + dir;
// src/main/assembly
msg = copyDirectory(new File(m_InputAbs + File.separator + "src" + File.separator + "main" + File.separator + "assembly"));
if (msg != null)
return msg;
// src/main/latex
msg = copyDirectory(new File(m_InputAbs + File.separator + "src" + File.separator + "main" + File.separator + "latex"));
if (msg != null)
return msg;
// src/main/scripts
msg = copyDirectory(new File(m_InputAbs + File.separator + "src" + File.separator + "main" + File.separator + "scripts"));
if (msg != null)
return msg;
// pom.xml
msg = copyFile(new File(m_InputAbs + File.separator + "pom.xml"));
if (msg != null)
return msg;
return null;
}
/**
* Copies the specified input file into the output directory.
*
* @param inputFile the file to copy
* @return null if successful, otherwise error message
*/
protected String copyFile(File inputFile) {
File outputFile;
String subPath;
if (inputFile.exists()) {
subPath = inputFile.getAbsolutePath().substring(m_InputAbs.length());
outputFile = new File(m_OutputAbs + File.separator + subPath);
try {
FileUtils.copyFile(inputFile, outputFile);
}
catch (Exception e) {
return "Failed to copy file: " + inputFile + " -> " + outputFile + "\n" + e;
}
}
else {
System.err.println("Missing file: " + inputFile);
}
return null;
}
/**
* Copies a directory to the output dir.
*
* @param inputDir the directory to copy
* @return null if successful, otherwise error message
*/
protected String copyDirectory(File inputDir) {
File outputDir;
String subPath;
if (inputDir.exists()) {
subPath = inputDir.getAbsolutePath().substring(m_InputAbs.length());
outputDir = new File(m_OutputAbs + File.separator + subPath);
try {
FileUtils.copyDirectory(inputDir, outputDir);
}
catch (Exception e) {
return "Failed to copy directory: " + inputDir + " -> " + outputDir + "\n" + e;
}
}
else {
System.err.println("Missing directory: " + inputDir);
}
return null;
}
/**
* Generates a class file name from the class name.
*
* @param cls the class to convert
* @return the filename
*/
protected File classToFile(String cls) {
return new File(
m_InputAbs
+ File.separator + "src" + File.separator + "main" + File.separator + "java"
+ File.separator + cls.replace(".", File.separator) + ".java");
}
/**
* Copies the classes and resources across.
*
* @param classes the classes to copy
* @return null if successful, otherwise error message
*/
protected String copy(List classes) {
List inputDirs;
File[] files;
File inFile;
File inDir;
String msg;
// classes
inputDirs = new ArrayList<>();
for (String cls: classes) {
inFile = classToFile(cls);
// record directories
inDir = inFile.getParentFile();
if (!inputDirs.contains(inDir))
inputDirs.add(inDir);
// copy
msg = copyFile(inFile);
if (msg != null)
return msg;
}
// other resources
System.err.println("Copying resources...");
for (File inputDir : inputDirs) {
inDir = new File(inputDir.getAbsolutePath().replaceAll("main.java", "main" + File.separator + "resources"));
files = inDir.listFiles((File dir, String name) -> {
return !name.equals(".") && !name.equals("..") && !name.endsWith(".java");
});
if (files != null) {
System.err.println("- " + inDir);
for (File file: files) {
if (file.isDirectory())
continue;
msg = copyFile(file);
if (msg != null)
return msg;
}
}
}
return null;
}
/**
* Minifies the build environment.
*
* @return null if successful, otherwise error message
*/
protected String minify() {
String msg;
List classes;
// minimal set of classes
System.err.println("Determining minimal set of classes...");
classes = new ArrayList<>();
msg = determineClasses(classes);
if (msg != null)
return msg;
// prepare the output directory
msg = prepareOutputDir();
if (msg != null)
return msg;
// copy the classes/resources across
msg = copy(classes);
if (msg != null)
return msg;
return null;
}
/**
* Determines the dependencies.
*
* @return null if successful, otherwise error message
*/
public String execute() {
String result;
result = check();
if (result == null) {
result = build(m_Input);
if (result != null)
result = "Failed to build input build environment: " + result;
}
if (result == null)
result = readPOM();
if (result == null)
result = assembleMinDepsClassPath();
if (result == null)
result = minify();
if (result == null) {
if (m_Test) {
result = build(m_Output);
if (result != null)
result = "Failed to build minified build environment: " + result;
}
}
if (result == null)
System.err.println("Note: Either delete the maven-exec-plugin build tag or use '" + EXEC_SKIP + "'");
return result;
}
public static void main(String[] args) throws Exception {
Meka meka;
String error;
meka = new Meka();
if (meka.setOptions(args)) {
error = meka.execute();
if (error != null) {
System.err.println(error);
System.exit(2);
}
}
else {
System.exit(1);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy