0

I would like to distribute a jar of a library I created with all my dependencies bundled inside. However I would like to avoid version conflicts of dependencies with the adopting project.

I think maven shade can do this but I could not find a way to do this with Scala / SBT. I found OneJar however from my experiments with it seems to work only for executables.

How could I achieve this?

Thanks!

Jason
  • 4,034
  • 4
  • 40
  • 62

3 Answers3

3

You can do this with your own classloader.

The classLoader:
Write a class loader which loads class files from diferent classloader using a rewrite.

For example you could add library as a prefix to the classpath when fetching the resource.

I have created a classloader using this teqnuiqe. https://github.com/espenbrekke/dependent/blob/master/src/main/java/no/dependent/hacks/PathRewritingClassLoader.java

It replaces the method findClass in URLClassLoader with one adding a prefix.

protected Class<?> findClass(final String name) throws ClassNotFoundException {
    Class result;
    try {
        result = (Class)AccessController.doPrivileged(new PrivilegedExceptionAction() {
            public Class<?> run() throws ClassNotFoundException {

// This is where the prefix is added:
                String path = PathRewritingClassLoader.this.prefix + name.replace('.', '/').concat(".class");
                Resource res = PathRewritingClassLoader.this._ucp.getResource(path, false);
                if(res != null) {
                    try {
                        return PathRewritingClassLoader.this._defineClass(name, res);
                    } catch (IOException var4) {
                        throw new ClassNotFoundException(name, var4);
                    }
                } else {
                    return null;
                }
            }
        }, this._acc);
    } catch (PrivilegedActionException var4) {
        throw (ClassNotFoundException)var4.getException();
    }

    if(result == null) {
        throw new ClassNotFoundException(name);
    } else {
        return result;
    }
}

We also have to rewrite resource loading

@Override
public URL getResource(String name){
    return super.getResource(prefix+name);
}

Here is how it is used:

_dependentClassLoader = new PathRewritingClassLoader("private", (URLClassLoader)DependentFactory.class.getClassLoader());
Class myImplementationClass=_dependentClassLoader.loadClass("my.hidden.Implementation");

Building your jar:
In your build you place all the library and private classes under your selected prefix. In my gradle build I have a simple loop collecting all the dependencies.

task packageImplementation {
dependsOn cleanImplementationClasses

doLast {
    def paths = project.configurations.runtime.asPath
    paths.split(':').each { dependencyJar ->
        println "unpacking" + dependencyJar

        ant.unzip(src: dependencyJar,
                dest: "build/classes/main/private/",
                overwrite: "true")
        }
    }
}
Espen Brekke
  • 404
  • 3
  • 7
  • Sounds ok, hoped for a packaging system which does this but it seems there isn't. Thanks. Will be glad to see you solution when you're done. – Jason Aug 13 '15 at 15:37
  • Just updated with some source examples and a link to my current implementation. Hope it helps:) – Espen Brekke Aug 14 '15 at 11:38
  • Sweet, I will have a look at this when I get a chance. Thanks. Also if you happen to know of the correct way to do the build with sbt as opposed to gradle I could use that :) Thanks Espen – Jason Aug 14 '15 at 16:55
  • I have added rewriting of resource loading, since it broke some libraries I use. Seems to work now. I don't have any experience with sbt, so I can't help you there. – Espen Brekke Aug 18 '15 at 09:58
2

Proguard can rename packages inside jar and obfuscate code. It is a bit complicated but you can achieve you goal with it. sbt-proguard plugin is actively maintained

Also you can check answers from similar thread:

maven-shade like plugin for SBT

UPDATE:

from version 0.14.0 sbt-assembly plugin seemed to have shading ability

Community
  • 1
  • 1
  • While this seems like it might work. It seems like a big hack if I have to I'll give it a try someday. Thanks – Jason Aug 14 '15 at 16:56
0

Have you tried sbt-assembly plugin? It has set of merging strategies in case of conflicts and has pretty much nice start guide.

tkachuko
  • 1,956
  • 1
  • 13
  • 20
  • The sbt assembly is great, and I use it, but the merge stratagy does not help, because it's not under my control how the ussr of my jar will merge things, and also because even if resolved via explicitly defining merge rules then the version used at rubtime will not match my compile time version. Additionaly sbt assembly has an option to exclude the dependencies from the jar, but I want them included, just hidden. – Jason Aug 07 '15 at 05:50