[Engine-devel] Improve classpath building

Juan Hernandez jhernand at redhat.com
Tue Jun 5 12:53:08 UTC 2012


Hello all,

I would like to propose an improvement in the way we build class-paths 
in the tools associated to the engine. At the moment we have at least 
two different ways to build classpaths:

1. Hard coded in the scripts, with maybe some variables:

CP=$EAR_LIB/engine-encryptutils.jar:$EAR_LIB/engine-compat.jar:...

This depends a lot on where we place the jar files, and we need to place 
them in different places in different environments if we want to adhere 
to common packaging practices.

2. Use the build-classpath script:

CP=`build-classpath engine-encryptutils engine-compat ...`

This depends less on the place we put them, but it doesn't work in 
development environments where some jars are not installed to the proper 
system locations.

None of these is good for all environments.

I would like to replace this classpath building logic with an script 
that performs the task in an smarter way and that works in all our 
environments (production, development, Fedora, RHEL, etc).

My proposal is to create a "engine-java" script that we should use 
always when invoking java programs. This script will receive the same 
parameters that the "java" launcher receives, but the "-cp" or 
"-classpath" options will contain not the absolute name of the jar 
files, but just a simple jar name instead, something like 
"commons-logging", "commons-codec" or "engineencryptutils". The script 
will extract the "-cp" or "-classpath" options given and use them to do 
a search of the jar files in the locations where they can be in 
different environments:

/usr/share/java
/usr/share/java/ovirt-engine
/usr/share/ovirt-engine/engine.ear
/usr/share/ovirt-engine/engine.ear/lib
<your jboss development installation>/engine.ear
<your jboss development installation>/engine.ear/lib

In addition the script will check that all the give jar files exist and 
will abort the execution if any of them is missing.

Find attached the initial version of the proposed script.

Let me know what you think.

Regards,
Juan Hernandez

-- 
Dirección Comercial: C/Jose Bardasano Baos, 9, Edif. Gorbea 3, planta 
3ºD, 28016 Madrid, Spain
Inscrita en el Reg. Mercantil de Madrid – C.I.F. B82657941 - Red Hat S.L.
-------------- next part --------------
#!/usr/bin/python

# Copyright 2012 Red Hat
#
# 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
#
# http://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.

import logging
import lxml.etree
import os
import traceback
import sys

# Flag to activate debug output (it is activated when the -d or
# -debug command line options are provided):
debugEnabled = False

# In some environments there are several versions of some of the
# most used jar files, so it is good to be explicit and state them
# here:
preferredJars = {

    # In RHEL we prefer to use the commons-codec jar file provided
    # by EAP6, as the version provided by the base operating
    # system is missing some methods that we need:
    "commons-codec": "/usr/share/java/commons-codec-eap6/commons-codec.jar",

    # XXX: I expect this dictionary to grow ...
}

def getSettingsProperty(propertyName, settingsFile):
    # Create the namespace map used when parsing settings files:
    nsmap = {
        "p": "http://maven.apache.org/POM/4.0.0",
    }

    # Parse the settings file:
    settingsDoc = lxml.etree.parse(settingsFile)

    # Get the list of active profile names:
    activeProfilesPath = (
        "/p:settings"
        "/p:activeProfiles"
        "/p:activeProfile"
        "/text()"
    )
    activeProfiles = settingsDoc.xpath(activeProfilesPath, namespaces=nsmap)

    # Try to find the value of the property in each of the active
    # profiles:
    for activeProfile in activeProfiles:
        propertyValuePath = (
            "/p:settings"
            "/p:profiles"
            "/p:profile[p:id/text()='" + activeProfile + "']"
            "/p:properties"
            "/p:" + propertyName + ""
            "/text()"
        )
        propertyValues = settingsDoc.xpath(propertyValuePath, namespaces=nsmap)
        if propertyValues:
            return propertyValues[0]

    # No luck:
    return None

def getProperty(propertyName):
    # First try with the user specific settings file, if it
    # exists:
    homeDir = os.getenv("HOME")
    if homeDir:
        settingsFile = os.path.join(homeDir, ".m2/settings.xml")
        if os.path.exists(settingsFile):
            propertyValue = getSettingsProperty(propertyName, settingsFile)
            if propertyValue:
                return propertyValue

    # Then try with the global setttings file, if it exists:
    settingsFile = "/etc/maven/settings.xml"
    if os.path.exists(settingsFile):
        propertyValue = getSettingsProperty(propertyName, settingsFile)
        if propertyValue:
            return propertyValue

    # No luck:
    return None

def resolveJar(jarName, searchPath):
    # Don't try to search the jar if it is given as an absolute
    # path, as this means that the application wants to use that
    # particular file (or maybe the application is broken):
    if os.path.isabs(jarName):
        if os.path.exists(jarName):
            return jarName
        else:
            return None

    # If we have a preferred jar for this name then try it before
    # anything else:
    preferredJar = preferredJars.get(jarName)
    if preferredJar and os.path.exists(preferredJar):
        logging.debug("Jar \"%s\" has been replaced by preferred jar \"%s\"." % (jarName, preferredJar))
        return preferredJar

    # Add the jar extension to the name if it has not been
    # provided:
    if not jarName.endswith(".jar"):
        jarName = jarName + ".jar"

    # Try to find the jar file in each of the jar directories, in
    # the order they are specified in the search path:
    for jarDirectory in searchPath:
        jarFile = os.path.join(jarDirectory, jarName)
        if os.path.exists(jarFile):
            logging.debug("Jar \"%s\" has been resolved as \"%s\"." % (jarName, jarFile))
            return jarFile

    # No luck:
    return None

def main():
    # Configure logging:
    logHandler = logging.StreamHandler(sys.stdout)
    logHandler.setLevel(logging.INFO)
    logFormatter = logging.Formatter('%(message)s')
    logHandler.setFormatter(logFormatter) 
    logging.root.addHandler(logHandler)
    logging.root.setLevel(logging.DEBUG)

    # Initially the classpath is empty:
    unresolvedJars = []

    # Parse the command line looking for the classpath options,
    # and for each of them remove it from the list of arguments
    # and add its content to the list of unresolved jar files:
    args = []
    index = 1
    while index < len(sys.argv):
        arg = sys.argv[index]
        if arg in ["-d", "-debug"]:
            logHandler.setLevel(logging.DEBUG)
        elif arg in ["-cp", "-classpath"]:
            index += 1
            if index < len(sys.argv):
                arg = sys.argv[index]
                unresolvedJars.extend(arg.split(":"))
        else:
            args.append(arg)
        index += 1

    # Build the search path for jar files that will be later used
    # to resolve them:
    searchPath = []

    # Add the system wide jars directory to the search path:
    systemJars = "/usr/share/java"
    if os.path.exists(systemJars):
        searchPath.append(systemJars)

    # Add the engine jars directory to the search path:
    engineJars = os.path.join(systemJars, "ovirt-engine")
    if os.path.exists(engineJars):
        searchPath.append(engineJars)

    # Add the ear and lib directory of the deployed engine to the
    # search path:
    engineEar = "/usr/share/ovirt-engine/engine.ear"
    if os.path.exists(engineEar):
        searchPath.append(engineEar)
        engineLib = os.path.join(engineEar, "lib")
        if os.path.exists(engineLib):
            searchPath.append(engineLib)

    # Try to find the application server installation used in
    # development environments and add the lib directory of the
    # deployed engine to the jar path:
    jbossHome = getProperty("jbossHome")
    if jbossHome:
        logging.debug("Application server home is \"%s\"." % jbossHome)
        engineEar = os.path.join(jbossHome, "standalone/deployments/engine.ear")
        if os.path.exists(engineEar):
            searchPath.append(engineEar)
            engineLib = os.path.join(engineEar, "lib")
            if os.path.exists(engineLib):
                searchPath.append(engineLib)

    # Try to find the actual path for each unresolved jar:
    resolvedJars = []
    missingJars = []
    for unresolvedJar in unresolvedJars:
        resolvedJar = resolveJar(unresolvedJar, searchPath)
        if resolvedJar:
            resolvedJars.append(resolvedJar)
        else:
            missingJars.append(unresolvedJar)

    # Don't proceed if there are unresolved paths:
    if missingJars:
        for missingJar in missingJars:
            sys.stderr.write("Can't find the actual location of jar \"%s\".\n" % missingJar)
        sys.exit(1)
    
    # Compute the class path used to invoke the java virtual
    # machine:
    classPath = ":".join(resolvedJars)
    logging.debug("Class path is \"%s\"." % classPath)

    # Execute tha java virtual machine with the given classpath
    # and arguments:
    javaArgs = ["java", "-classpath", classPath]
    javaArgs.extend(args)
    logging.debug("Running command \"%s\"." % " ".join(javaArgs))
    os.execvp("java", javaArgs)


if __name__ == "__main__":
    try:
        main()
    except SystemExit:
        raise
    except BaseException as exception:
        sys.stderr.write("%s\n" % traceback.format_exc())
        sys.exit(1)


More information about the Engine-devel mailing list