/* * omero * * Copyright 2006 University of Dundee. All rights reserved. * Use is subject to license terms supplied in LICENSE.txt */ import java.io.File; import java.io.FileFilter; import java.lang.reflect.Method; import java.net.URI; import java.net.URL; import java.net.URLClassLoader; import java.util.Locale; import java.util.Properties; /** * The Build Tool. * This class is in charge of launching Ant with a suitable environment. * <p>The workflow is (in principle) identical to the Ant Launcher class (see * <code>org.apache.tools.ant.launch.Launcher</code>): set up the required * system properties, locate all the required libraries (including the JDK * tools), and launch Ant with a suitable classpath and with whatever arguments * were supplied on the command line.</p> * <p>The implementation however is quite different. In fact, on one hand we * know exactly how the tool is deployed and run, so some aspects of the * launch procedure can be simplified. On the other hand we tailor this * procedure to the needs of our build system.</p> * <p>The launch procedure assumes the following:</p> * <ul> * <li>This class is compiled in the <i>build</i> directory under the * Shoola CVS root. Its file name is <i>"omero.class"</i> and it * belongs to the default namespace (unnamed package).</li> * <li>The <i>build</i> directory contains a sub-directory named <i>tools</i>. * The latter contains the Ant core jars, the Ant jars for the optional * tasks that we use, and all the external libraries required for those * optional tasks. Optionally, it may contain the Sun JDK tools jar. * (Because of licensing issues, we can't add the <i>tools.jar</i> to CVS; * however developers may want to drop that file into this directory after * downloading from CVS.)</li> * <li>The Sun JDK tools can be accessed because the classes: * <ul> * <li>Are in the default classpath (boot, ext) of the JVM that is used to * invoke the Build Tool, or</li> * <li>Are contained in a jar file named <i>"tools.jar"</i> under * <i>{java.home}/lib</i> (JDK installed; if {java.home} points to the * <i>jre</i> directory within the JDK, then the jar is expected to be * in the <i>lib</i> sibling directory), or</li> * <li>Are contained in a jar file under the <i>build/tools</i> directory. * In this case, the version of the JDK tools is expected to be the * same as the one of the JVM that is used to invoke the Build Tool. * (Failure to comply may result in compilation errors due to class * file version incompatibilities.)</li> * </ul> * </li> * <li>The Build Tool is invoked with the following syntax: <code>java build * [options] [target1 [target2 [target3] ...]]</code>, where * <code>options</code> are any of the Ant options and <code>targetN</code> * is any of the available targets. Note that we assume no classpath is * ever specified to <code>java</code>.</li> * </ul> * <p>A final important remark. Before dropping the JDK tools into * <i>build/tools</i>, you should make sure they're not available to the JVM * in use. (Just run the Build Tool to compile, if it fails the JDK tools are * not available.) This is important because if the JDK tools are already * available, adding a copy under <i>build/tools</i> would result in having * two sets of JDK tools classes on the classpath. As you can imagine, some * nasty runtime behavior could originate from that if the JDK tools have * different versions.</p> * * @author Jean-Marie Burel      * <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a> * @author <br>Andrea Falconi      * <a href="mailto:a.falconi@dundee.ac.uk"> * a.falconi@dundee.ac.uk</a> * @since OME2.2 */ public class omero implements FileFilter { /** * The <i>build</i> directory under the Shoola CVS root. * This is the directory where this class is compiled. */ private File buildDir; /** * The <i>tools</i> directory under {@link #buildDir}. * This directory contains all the libraries required by the Build Tool, * except the JDK tools which can be added after downloading from CVS. */ private File toolsDir; /** * Points to the Sun JDK tools jar file, if any, in the java installation * directory. * This is the installation directory of the JVM used to launch the build * tool and is given by the <i>java.home</i> system property. * Note that this file may not exist. (For example if the Sun JDK is not * installed or the JVM used to run the Build Tool is not part of the JDK.) */ private File jdkTools; /** * Locates the needed filesystem entries and initializes the corresponding * fields. * We throw an exception if the <i>build</i> and <i>tools</i> directories * can't be located from the position of the <code>build</code> class on * the filesystem. We try and guess the position of the Sun JDK tools, but * we don't check whether this file actually exists. In fact, the JDK tools * classes may turn out to be available to the current JVM by some other * means. (For example, they are already in the bootclasspath or the tools * jar file has been dropped into our <i>tools</i> directory.) * * @throws Exception If <i>build</i> and <i>tools</i> directories can't be * located. */ private omero() throws Exception { //First locate the build directory. //We assume this class always belongs to the default package and //is compiled in the build directory. URL url = omero.class.getClassLoader().getResource("omero.class"); if (url == null) throw new Exception("Can't locate omero class."); File build = new File(new URI(url.toExternalForm())); buildDir = build.getParentFile(); if (!buildDir.exists() || !buildDir.isDirectory()) throw new Exception("Invalid location: "+buildDir.getAbsolutePath()+ " is not a directory."); //Now locate the tools sub-directory. toolsDir = new File(buildDir, "lib"); if (!toolsDir.exists() || !toolsDir.isDirectory()) throw new Exception("Invalid location: "+toolsDir.getAbsolutePath()+ " is not a directory."); //Finally guess the location of the JDK tools.jar file. String javaHome = System.getProperty("java.home"); if (javaHome.toLowerCase(Locale.US).endsWith("jre")) javaHome = javaHome.substring(0, javaHome.length()-4); jdkTools = new File(javaHome+"/lib/tools.jar"); } /** * Tests for availability of the JDK tools in the JVM default classpath. * (Recall that the Build Tool is invoked like: java build, so no -cp * is ever specified.) * * @return <code>true</code> if the JDK tools are found, <code>false</code> * otherwise. */ private boolean isJDKToolsAvailable() { String[] main = {"com.sun.tools.javac.Main", "sun.tools.javac.Main"}; for (int i = 0; i < main.length; ++i) try { Class.forName(main[i]); return true; } catch (ClassNotFoundException cnfe) {} return false; } /** * Locates all the libraries required by the Build Tool. * We consider <i>any jar</i> file in <i>build/tools</i> under the * Shoola CVS root to be a library required by the Build Tool. * Moreover, if the JDK tools are not already in the JVM default classpath, * we try and grab the <i>tools.jar</i> file and then add it to the * returned array. This could potentially be a problem if a jar file * containing the JDK tools is also present in <i>build/tools</i>. In this * case we would end up with two sets of JDK tools classes on the classpath, * which could originate some nasty runtime behavior if the JDK tools have * different versions. * * @return A file object for each jar file found in <i>build/tools</i>. * @throws Exception If no libraries were found in <i>build/tools</i>. */ private File[] locateBuildJars() throws Exception { File[] jars = new File[8]; jars[0] = new File("lib/repository/junit-3.8.1.jar"); jars[1] = new File("lib/repository/ant-1.8.0.jar"); jars[2] = new File("lib/repository/ant-launcher-1.8.0.jar"); jars[3] = new File("lib/repository/ant-junit-1.8.0.jar"); jars[4] = new File("lib/repository/ant-trax-1.8.0.jar"); jars[5] = new File("lib/repository/ant-nodeps-1.8.0.jar"); jars[6] = new File("lib/repository/ant-contrib-1.0b3.jar"); jars[7] = new File("lib/repository/bsh-2.0b4.jar"); if (!isJDKToolsAvailable()) { //Try and grab tools.jar. if (jdkTools.exists()) { //Add it to the other jars. File[] tmp = new File[jars.length+1]; System.arraycopy(jars, 0, tmp, 0, jars.length); tmp[jars.length] = jdkTools; jars = tmp; } //Else we assume the JDK tools are in a jar under build/tools. } //Else the jdk tools are either in the bootclasspath or in the ext. //In fact, the Build Tool is never invoked w/ -cp. return jars; } String mavenPath(String grp, String art, String ver){ return "lib/repository/"+grp+"/"+art+"/"+ver+"/"+art+"-"+ver+".jar"; } /** * Sets the classpath to the libraries required by the Build Tool. * * @param buildJars The list of the required libraries. */ private void setClasspath(File[] buildJars) { StringBuffer cp = new StringBuffer(); for (int i = 0; i < buildJars.length; ++i) { cp.append(buildJars[i].getAbsolutePath()); cp.append(File.pathSeparator); } System.setProperty("java.class.path", cp.toString()); //The above wipes out the current classpath. This is not a problem //b/c the Build Tool is never invoked w/ -cp and . contains no other //classes besides this one. } private void setSystemProperties() { //Set Ant properties. System.setProperty("ant.home", toolsDir.getAbsolutePath()); System.setProperty("ant.library.dir", toolsDir.getAbsolutePath()); System.setProperty("build.sysclasspath","last"); // or first, ignore, only // This solves the problem of users having conflicting // versions on their CLASSPATH } /** * Sets the environment and then starts Ant with the specified command line. * * @param args Ant command line. * @throws Exception If the Build Tool can't be started. */ private void startAnt(String[] args) throws Exception { try { //Set system properties, then set classpath and replace classloader //with one that is aware of the new classpath. setSystemProperties(); File[] buildJars = locateBuildJars(); setClasspath(buildJars); URL[] buildJarsURLs = new URL[buildJars.length]; for (int i = 0; i < buildJarsURLs.length; i++) buildJarsURLs[i] = buildJars[i].toURL(); URLClassLoader loader = new URLClassLoader(buildJarsURLs); Thread.currentThread().setContextClassLoader(loader); //Emacs logging String[] tmp = new String[args.length+3]; System.arraycopy(args, 0, tmp, 0, args.length); tmp[args.length] = "-emacs"; tmp[args.length+1] = "-logger"; tmp[args.length+2] = "org.apache.tools.ant.NoBannerLogger"; args = tmp; //Load and start Ant. Class mainClass = loader.loadClass("org.apache.tools.ant.Main"); Method entry = mainClass.getMethod("startAnt", new Class[] {String[].class, Properties.class, ClassLoader.class}); Object antMain = mainClass.newInstance(); entry.invoke(antMain, new Object[] {args, null, null}); } catch (Throwable t) { throw new Exception("Couldn't start the Build Tool. \n"+ t.getMessage(),t); } } /** * Grabs every file with a jar extension. * @see java.io.FileFilter#accept(java.io.File) */ public boolean accept(File pathname) { String name = pathname.getName(); if (name.toLowerCase().endsWith(".jar")) return true; return false; } /** * Build Tool entry point. * * @param args Any valid Ant options and targets. * @throws Exception If the Build Tool can't be started. */ public static void main(String[] args) throws Exception { omero launcher = new omero(); launcher.startAnt(args); } }