/*
* ALMA - Atacama Large Millimiter Array
* (c) European Southern Observatory, 2004
* Copyright by ESO (in the framework of the ALMA collaboration),
* All rights reserved
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
package alma.acs.eclipse.utils.pluginbuilder;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import alma.acs.classloading.AcsSystemClassLoader;
import alma.acs.eclipse.utils.jar.AcsFolders;
import alma.acs.eclipse.utils.jar.FileHelper;
import alma.acs.util.CmdLineArgs;
import alma.acs.util.CmdLineRegisteredOption;
/**
* Build an eclipse plugin from one or more jar files.
* <P>
* The required ACS jars are read from $INTROOT, $ACSROOT and so on.
* <BR>
* The purpose of <code>PluginBuilder</code> is to build an eclipse plugin to import ACS jars
* while developing RCP applications for ACS.
* The computation requires:
* <UL>
* <LI>the list of jars to include in the plugin
* <LI>the name of the plugin to build
* <LI>the eclipse plugin of ACS jars is built in the target location
* </UL>
* <P>
* The structure of a plugin is:
* <code>
* plugin_folder
* META-INF
* MANIFEST.MF
* plugin.xml
* jarFile1.jar
* jarFile2.jar
* ...
* jarFile3.jar
* </code>
*
* @author acaproni
* @since ACS-8.0.1
*/
public class PluginBuilder {
/**
* The jacorb folder of jar files.
* <P>
* It comes from the $JAVA_HOME environment variable.
*/
public final String jacorbFolderPropertyName="acs.system.classpath.jacorb.jardirs";
/**
* The endorsed folders of jars
*/
public final String endorsedFoldersPropertyName="java.endorsed.dirs";
/**
* The folder where JacORB stores the jar files
*/
private String jacorbFileOfJars;
/**
* The location in the file system where the generated plugin will
* be created.
* <P>
* <code>target</code> is a writable folder for example could be the
* target platform like <code>~/eclipse_target/eclipse/plugins/</code>.
*
*/
private String target;
/**
* The name of the plugin
*/
private String name;
/**
* The folders to look for jars
*/
private final Vector<String> jarDirs= new Vector<String>();
/**
* A set of folder to look for jars provided by the user
* <P>
* This folders are not standard ACS folders.
*/
private String[] userDirs=new String[0];
/**
* The plugins this plugin depends on (i.e. the dependencies)
*/
private String[] requiredPlugins;
/**
* The bundle version currently gets derived from the bundle name ('_' suffix).
* It may be null if the bundle name does not contain an underscore. Then the ManifestWriter will use 1.0.0 as default.
*/
private String bundleVersion;
/**
* The logger to print info and debug messages
*/
private static final Logger logger=Logger.getLogger(PluginBuilder.class.getName());
//private static final Logger logger = ClientLogManager.getAcsLogManager().getLoggerForApplication(PluginBuilder.class.getSimpleName(), false);
/**
* The handler getting logs
*/
private static final LogHandler logHandler= new LogHandler();
/**
* If <code>true</code> the jars are included in the plugin folder (i.e. they
* are copied from the ACS INTROOT, or ACSROOT into the plugin folder).
* If <code>wrapJars</code> is <code>true</code> the process bundles by wrapping.
*
* If it is <code>false</code>, a link to the jars is created into the plugin folder.
* If <code>wrapJars</code> is <code>false</code> the process bundles by reference.
*/
private boolean wrapJars;
/**
* The level of log
*/
private boolean verbose;
/**
* If <code>true</code> look for jars in endorsed dirs (passed as java properties)
*/
private boolean endorsed;
/**
* The folder of the plugin where the jars are copied
* <P>
* It is the main folder of the plugin and its name usually has a form like
* <code>alma.acs.eclipse.test_3.0.2</code>.
*/
private File pluginRootFolder=null;
/**
* The <code>META-INF</code> folder inside the plugin root folder
*/
private File metaFolder=null;
/**
* The jars to include in the created plugin.
*
*/
private String[] jars;
/**
* The jars to include in the created plugin.
*
*/
private String[] finalJarsLocations;
/**
* Constructor
*
* @param args Command line arguments
*/
public PluginBuilder(String[] args) throws Exception {
if (parseCmdLineArgs(args)) {
PluginBuilder.printUsage(System.err);
System.exit(-1);
}
if (verbose) {
logger.setLevel(Level.FINEST);
}
logger.addHandler(logHandler);
if (verbose) {
logHandler.setLevel(Level.FINE);
} else {
logHandler.setLevel(Level.WARNING);
}
logger.fine("Plugin name: "+name);
logger.fine("Plugin folder: "+target);
logger.fine("bundle by wrapping: "+wrapJars);
logger.fine("bundle by reference: " + !wrapJars);
for (String dep: requiredPlugins) {
logger.fine("Dependency: "+dep);
}
logger.fine("ACSROOT: " + System.getenv("ACSROOT"));
logger.fine("INTROOT: " + System.getenv("INTROOT"));
if (endorsed) {
logger.fine("Use endorsed folders");
} else {
logger.fine("Do NOT use endorsed folders");
}
for (String tmp: userDirs) {
logger.fine("UserDir: "+tmp);
}
logger.fine("Tot. num. of plugins to wrap: "+jars.length);
for (String jar: jars) {
logger.fine("Plugin to wrap: "+jar);
}
// Check if the target is a readable folder containing build.properties
File targetF = new File(target);
if (!targetF.isDirectory() || !targetF.canRead() || !targetF.canWrite()) {
throw new IllegalArgumentException("Wrong permissions or invalid folder "+target);
}
// Check if the target is a readable folder
File pluginF = new File(target);
if (!pluginF.isDirectory() || !pluginF.canRead()) {
throw new IllegalArgumentException("Wrong permissions or invalid folder "+target);
}
// Check if the name is a valid plugin name
//
// The check is done by checking if the name contains dots and underscore
// The version is derived from the suffix after the underscore. Perhaps a separate option would be better than underscore in the name.
if (name.indexOf('.')<0 || name.indexOf('_')<0) {
logger.warning("Plugin name '" + name + "' does not follow eclipse rules (it should be something like eso.alma.ecs_3.0.0).");
}
else {
bundleVersion = name.substring(name.indexOf('_') + 1);
name = name.substring(0, name.indexOf('_'));
}
this.finalJarsLocations = new String[jars.length];
if (this.jars==null || this.jars.length==0) {
throw new IllegalArgumentException("There must be at least one jar to wrap in the plugin");
}
// Init the folder of jars
initdFolders();
}
/**
* Get the folder to look for jars to wrap in the plugin
*
* @throws Exception If at least one of the folder is not valid
*/
private void initdFolders() throws Exception {
// Get the endorse folders
String endorsedProperty = System.getProperty(endorsedFoldersPropertyName);
String[] endorsedFolders;
if (endorsed) {
if (endorsedProperty==null) {
endorsedFolders=new String[0];
} else {
endorsedFolders=endorsedProperty.split(":");
}
} else {
endorsedFolders=new String[0];
}
// Get jacorb folder
jacorbFileOfJars=System.getProperty(jacorbFolderPropertyName);
// Get the folder of jars
String jarFolders = System.getProperty(AcsSystemClassLoader.PROPERTY_JARDIRS);
String[] temp=jarFolders.split(File.pathSeparator);
jarDirs.add(jacorbFileOfJars+"/lib");
for (String str: temp) {
jarDirs.add(str);
}
for (String str: endorsedFolders) {
jarDirs.add(str);
}
for (String str: userDirs) {
jarDirs.add(str);
}
// Check if the folders are readable
for (String folder: jarDirs) {
File f = new File(folder);
if (!f.isDirectory() || !f.canRead()) {
throw new Exception(folder+" unreadable or not a directory");
}
logger.fine("Jar file folder: "+folder);
}
}
/**
* Create the folder structure of the plugin
*
*/
private void initPluginFolders() {
pluginRootFolder = new File(target+File.separator+name);
if (!pluginRootFolder.exists()) {
pluginRootFolder.mkdir();
logger.fine("Creating folder "+pluginRootFolder.getAbsolutePath());
}
metaFolder = new File(target+File.separator+name+File.separator+"META-INF");
if (!metaFolder.exists()) {
metaFolder.mkdir();
logger.fine("Creating folder "+metaFolder.getAbsolutePath());
}
}
/**
* Build the plugin
*
* @throws Exception In case of error building the plugin
*/
public void build() throws Exception {
logger.info("Creating directory structure");
// Create the folder structure
initPluginFolders();
// Copy/link the jars into the plugin folder
addJars();
// Write the plugin XML
addPluginXML();
// Write the MANIFEST
addManifest();
}
/**
* Add the <code>plugin.xml</code>
*
* @throws IOException In case of erro writing the file
* @see PluginXmlWriter
*/
private void addPluginXML() throws IOException {
PluginXmlWriter xmlWriter = new PluginXmlWriter(pluginRootFolder);
xmlWriter.write();
}
/**
* Add the <code>MANIFEST.MF</code> to the <code>META-INF</code> folder.
*
* @throws Exception In case of error writing the manifest.
*/
private void addManifest() throws Exception {
ManifestWriter manifestWriter = new ManifestWriter(
metaFolder,
pluginRootFolder,
!wrapJars,
finalJarsLocations,
requiredPlugins,
bundleVersion,
logger);
manifestWriter.write();
}
/**
* Add (or link) the jars into the plugin folder.
* <P>
* jar files are found following ACS rules (i.e. first <code>../lib</code>, then <code>$INTROOT/lib</code> and so on).
*
* @throws Exception in case of error
*/
private void addJars() throws Exception {
if (pluginRootFolder==null || metaFolder==null) {
throw new IllegalStateException("Directory struct not initialized");
}
AcsFolders jarFolders = new AcsFolders(jarDirs);
int i = 0;
for (String jar: jars) {
logger.fine("Adding "+jar);
File jarFile = jarFolders.getJar(jar);
if (jarFile==null) {
throw new Exception(jar+" NOT found");
}
finalJarsLocations[i++] = jarFile.getAbsolutePath();
File dest = new File(pluginRootFolder+File.separator+jar);
if (wrapJars) {
FileHelper.copy(jarFile, dest);
} else {
//FileHelper.link(jarFile, dest);
}
}
}
/**
* Parse the command line
*
* @param args The command line params
* @return <code>true</code> if the user asked for help
*/
public boolean parseCmdLineArgs(String[] args) {
CmdLineArgs cmdLineArgs = new CmdLineArgs();
CmdLineRegisteredOption helpOpt = new CmdLineRegisteredOption("-h","--help",0);
cmdLineArgs.registerOption(helpOpt);
CmdLineRegisteredOption verboseOpt = new CmdLineRegisteredOption("-v","--verbose",0);
cmdLineArgs.registerOption(verboseOpt);
CmdLineRegisteredOption includeOpt = new CmdLineRegisteredOption("-i","--include",0);
cmdLineArgs.registerOption(includeOpt);
CmdLineRegisteredOption linkOpt = new CmdLineRegisteredOption("-l","--link",0);
cmdLineArgs.registerOption(linkOpt);
CmdLineRegisteredOption dirsOpt = new CmdLineRegisteredOption("-d","--userDirs",0);
cmdLineArgs.registerOption(dirsOpt);
CmdLineRegisteredOption endorsedOpt = new CmdLineRegisteredOption("-e","--endorsedDirs",0);
cmdLineArgs.registerOption(endorsedOpt);
CmdLineRegisteredOption requiredOpt = new CmdLineRegisteredOption("-r","--requiredPlugins",0);
cmdLineArgs.registerOption(requiredOpt);
cmdLineArgs.parseArgs(args);
// Help
if (cmdLineArgs.isSpecified(helpOpt)) {
return true;
}
// Verbose mode
verbose=cmdLineArgs.isSpecified(verboseOpt);
// Endorsed
endorsed=cmdLineArgs.isSpecified(endorsedOpt);
if (cmdLineArgs.isSpecified(requiredOpt)) {
String temp=cmdLineArgs.getValues(requiredOpt)[0];
requiredPlugins=temp.split(":");
} else {
requiredPlugins=new String[0];
}
// Set the wrap mode
String[] mainArgs; // what follows -i|-l
if (cmdLineArgs.isSpecified(includeOpt) && cmdLineArgs.isSpecified(linkOpt)) {
System.out.println("Set only one between -i and -l");
return true;
}
if (!cmdLineArgs.isSpecified(includeOpt) && !cmdLineArgs.isSpecified(linkOpt)) {
System.out.println("Set one between -i and -l");
return true;
}
if (cmdLineArgs.isSpecified(includeOpt)) {
wrapJars=true;
mainArgs=cmdLineArgs.getValues(includeOpt);
} else {
wrapJars=false;
mainArgs=cmdLineArgs.getValues(linkOpt);
}
if (mainArgs.length<3) {
System.err.println("Wrong command line args!");
return true;
}
name=mainArgs[0];
target=mainArgs[1];
jars=new String[mainArgs.length-2];
for (int t=2; t<mainArgs.length; t++) {
jars[t-2]=mainArgs[t];
}
// User defined folders
if (cmdLineArgs.isSpecified(dirsOpt)) {
String udirs=cmdLineArgs.getValues(dirsOpt)[0];
userDirs=udirs.split(":");
} else {
userDirs=new String[0];
}
return false;
}
/**
* The main
*
* @param args
*/
public static void main(String[] args) {
System.out.println();
PluginBuilder pluginBuilder=null;
try {
pluginBuilder= new PluginBuilder(args);
} catch (Exception e) {
logger.log(Level.SEVERE,"Error: "+e.getMessage(),e);
System.exit(-1);
}
logger.fine("Creating plugin");
try {
pluginBuilder.build();
logger.fine("Done");
} catch (Throwable t) {
logger.log(Level.SEVERE,"Error building plugin: "+t.getMessage(),t);
System.exit(-1);
}
}
/**
* Print the usage string in the passed stream
*
* @param stream The stream to print the string
*/
public static void printUsage(OutputStream stream) {
StringBuilder ret = new StringBuilder("\nacsPluginBuilder build an eclipse plugin from a list ACS jars.\n");
ret.append("USAGE: \n");
ret.append("acsPluginBuilder [OPTION] -l|-i pluginName destFolder jar...\n");
ret.append("\t-i: the jars files are included in the plugin\n");
ret.append("\t-l: the jars files are linked in the plugin\n");
ret.append("\tpluginName: the name of the plugin\n");
ret.append("\tdestFolder: the destination folder of the created plugin of jars\n");
ret.append("\tjars...: the list of jars to include in the plugin\n");
ret.append("Options:\n");
ret.append("\t-h, --help: print this help and exit\n");
ret.append("\t-v, --verbose: verbose output\n");
ret.append("\t-d, --userDirs <dirs>: supply a comma separated list of folder for searching jars\n");
ret.append("\t-e, --endorsedDirs: looks for jars in endosed folders\n");
ret.append("\t-r, --requiredPlugins: a comma separated list of plugin names required by this plugin\n");
ret.append("Required plugin must be inserted in the following way: org.eclipse.swt_3.6.0:org.aparche.xerces\n");
ret.append(" means to add version 3.6.0 of org.eclipse.swt and org.aparche.xerces\n");
try {
stream.write(ret.toString().getBytes());
} catch (IOException e) {
System.err.println(ret.toString());
}
}
}