Is there a way to specify the location of a local jsch.jar from within build.xml?
Asked Answered
T

7

9

build.xml contains <scp> and <sshexec> tasks, so I provide jsch.jar and other libraries in the same directory together with build.xml.

The following taskdef:

<taskdef name="scp"
    classname="org.apache.tools.ant.taskdefs.optional.ssh.Scp"
    classpath="WebContent/WEB-INF/lib/jsch-0.1.43.jar" />

throws an error

A class needed by class org.apache.tools.ant.taskdefs.optional.ssh.Scp
cannot be found: com/jcraft/jsch/UserInfo

I cannot modify the standard Ant installation (e.g. put jsch.jar in ant lib directory, or remove ant-jsch.jar), or add command-line flags, or modify system environment variables, etc.: the script has to run with default Ant on different systems.

I'm actually reposting the question originally asked here: http://ant.1045680.n5.nabble.com/specifying-location-of-an-external-library-within-build-xml-td1344969.html

but could not get the answer about classloader to work.

Tropo answered 16/9, 2010 at 12:26 Comment(0)
F
17

Finally I found a working solution (for Ant 1.7.1 at least). First you have to remove ant-jsch.jar from ANT_HOME/lib as Ant complains about it and gets confused. Then load libraries from the project itself:

<available property="ant-jsch.present" file="${ant.home}/lib/ant-jsch.jar"/>
<fail if="ant-jsch.present" message="Please remove ant-jsch.jar from ANT_HOME/lib see [http://ant.apache.org/faq.html#delegating-classloader]"/>

<path id="jsch.path">
    <pathelement location="lib/ant-jsch.jar" />
    <pathelement location="lib/jsch-0.1.44.jar" />
</path>

<taskdef name="scp" classname="org.apache.tools.ant.taskdefs.optional.ssh.Scp" classpathref="jsch.path" />
<taskdef name="sshexec" classname="org.apache.tools.ant.taskdefs.optional.ssh.SSHExec" classpathref="jsch.path" />
Firn answered 7/12, 2010 at 13:48 Comment(3)
It seems this is a common problem and I think this is the best solution, except instead of failing, I'm going to just go ahead and delete the /lib/ant-jsch.jar. In case it helps others, Paulo gives an explanation of the core issue in his SO answer to a related SCP question and the same problem occurs with junit and is explained hereDenisdenise
But this requires modification of the standard Ant installation.Moving
I've been grappling with this issue for days and this solution is the only one that I could get to work!Krute
S
3

So, this question is old, but I devised another approach which may help others. We can spawn Ant from a <java> task with the proper classpath to run <scp>. This avoid the classpath leaking problem, and doesn't requires changing Ant install in any way:

<target name="sendfile">
    <!-- file: local file to send -->
    <!-- todir: remote directory -->
    <java classname="org.apache.tools.ant.launch.Launcher"
        fork="true" dir="${basedir}" taskname="ant+scp">
        <classpath>
            <pathelement location="/where/is/jsch-0.1.49.jar"/>
            <pathelement location="${ant.home}/lib/ant-launcher.jar"/>
        </classpath>
        <arg value="-buildfile"/>
        <arg file="${ant.file}"/>
        <arg value="-Dfile=${file}"/>
        <arg value="-Dtodir=${todir}"/>
        <arg value="sendfile.scp"/>
    </java>
</target>

<target name="sendfile.scp">
    <echo message="Sending ${file} to ${todir}"/>
    <property file="/tmp/passwordfile"/>
    <scp file="${file}" todir="[email protected]:${todir}"
        trust="true" port="22" password="${PASSWORD}"/>
</target>

The port parameter isn't needed, but it's here as a reminder for custom SSH ports. The password is a property stored on /tmp/passwordfile, like PASSWORD=mysecretpassword. Change these to suit your needs. Here follows an usage example:

<ant target="sendfile">
    <!-- Example: send /etc/os-release file to remote dir /home/myself -->
    <property name="file" value="/etc/os-release"/>
    <property name="todir" value="/home/myself"/>
</ant>
Scorpaenid answered 2/2, 2013 at 21:7 Comment(0)
P
2

For reference, an approach that I find useful is to repackage the jars, so they don't conflict - you can do this in Ant using JarJar like this:

<taskdef name="jarjar" classname="com.tonicsystems.jarjar.JarJarTask" classpath="${basedir}/lib/build/jar/jarjar-1.4.jar"/>

<taskdef name="scp" classname="repackaged.scp.org.apache.tools.ant.taskdefs.optional.ssh.Scp" classpath="${basedir}/lib/build/jar/repackaged-scp.jar"/>

<target name="repackage.scp" description="Repackages Ant's optional SCP task and the JSch implementation to avoid conflicting with one on Ant's classpath">
    <delete file="${basedir}/lib/build/jar/repackaged-scp.jar" failonerror="false"/>
    <jarjar basedir="." jarfile="${basedir}/lib/build/jar/repackaged-scp.jar" includes="nothing">
        <zipfileset src="${basedir}/lib/build/jar/ant-jsch-1.9.1.jar"/>
        <zipfileset src="${basedir}/lib/build/jar/jsch-0.1.50.jar"/>
        <rule pattern="com.jcraft.jsch.**" result="repackaged.scp.com.jcraft.jsch.@1"/>
        <rule pattern="org.apache.tools.ant.taskdefs.optional.ssh.**" result="repackaged.scp.org.apache.tools.ant.taskdefs.optional.ssh.@1"/>
    </jarjar>
</target>
Poohpooh answered 12/6, 2013 at 10:24 Comment(0)
S
1

I was able to solve this issue following post from here https://mcmap.net/q/728640/-how-to-load-an-optional-task-into-ant-without-lib-or-global-installation and then

<taskdef resource="net/jtools/classloadertask/antlib.xml" classpath="${basedir}/ant-lib/ant-classloadertask.jar" />
<classloader loader="system" classpath="${basedir}/ant-lib/jsch-0.1.54.jar"/>
Squabble answered 11/10, 2016 at 11:18 Comment(0)
B
0

Create a path reference and then use it in your task definition:

<path id="ssh.path">
   <pathelement location="${lib1.dir}/helloworld.jar"/>
   <fileset dir="${lib2.dir}">
       <include name="*.jar"/>
   </fileset>
</path>

<taskdef name="mytask" classname="org.mytask" classpathref="ssh.path" />
Brynhild answered 16/9, 2010 at 22:25 Comment(0)
M
0

Create ~/.ant/lib and copy jsch.jar in there as part of the build initialisation.

<target name="init">
  <property name="user.ant.lib" location="${user.home}/.ant/lib"/>
  <mkdir dir="${user.ant.lib}"/>
  <copy todir="${user.ant.lib}">
    <fileset dir="${basedir}/build/tools" includes="jsch-*.jar"/>
  </copy>
</target>
Moving answered 4/8, 2016 at 8:36 Comment(0)
K
0

There's a well-known trick with URLClassLoader. By using it we can make jsch accessible to ant-jsch.

I wonder how classloadertask from the answer by @user3499805 works.

<target name="injectJsch" description="inject jsch jar">
    <makeurl file="${acdc.java.tools}/lib/jsch-0.1.50.jar" property="jsch.jar.url"/>
    <taskdef name="injectJsch"
        classname="tools.deployments.ant.InjectJsch"
        classpath="${basedir}/jars/ajwf_deploytools.jar"
    />
    <injectJsch jarLocation="${jsch.jar.url}"/>
</target>

_

package tools.deployments.ant;

import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.taskdefs.optional.ssh.LogListener;

public class InjectJsch extends Task {

    public void setJarLocation(final String jarLocation) {
        this.jarLocation = jarLocation;
    }

    @Override
    public void execute() throws BuildException {
        try {
            injectJsch(new URL(jarLocation));
        } catch (final Exception e) {
            throw new BuildException(e);
        }
    }

    public static void injectJsch(final URL jarLocation) throws Exception {
        ClassLoader parent = LogListener.class.getClassLoader();
        try {
            parent.loadClass(TESTCLASS);
        } catch (final ClassNotFoundException e) {
            final Method addURLmethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
            addURLmethod.setAccessible(true);
            ClassLoader cl;
            do {
                cl = parent;
                if (cl instanceof URLClassLoader) {
                    addURLmethod.invoke(cl, jarLocation);
                    break;
                }
                parent = cl.getParent();
            } while (parent != cl && parent != null);
            LogListener.class.getClassLoader().loadClass(TESTCLASS);
        }

    }

    private String jarLocation;

    private static final String TESTCLASS = "com.jcraft.jsch.UserInfo";
}
Kohima answered 22/9, 2017 at 18:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.