Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Gradle and nested non-transitive dependencies

Here is a test project: click

I have a test Gradle Android project with three modules: app, library_a, library_b. app depends on library_a, then library_a depends on library_b:

build.gradle (app)

dependencies {
    ...
    compile (project(":library_a")){
        transitive = false;
    }
}

build.gradle (library_a)

dependencies {
    ...
    compile (project(":library_b")){
        transitive = false;
    }
}

Note that I set transitive = false because I don't want classes from library_b to be accessed from app

Every module has just one class, code is pretty simple:

app:

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //...
        ClassA classA = new ClassA();
        classA.doSomething();
    }
}

library_a:

public class ClassA
{
    public void doSomething(){
        Log.i("Test", "Done A!");

        ClassB classB = new ClassB();
        classB.doSomething();
    }
}

library_b:

public class ClassB
{
    public void doSomething(){
        Log.i("Test", "Done B!");
    }
}

Well, here is the problem: I'm building my project with gradlew. Apk is compiling successfully, but when I run it I get NoClassDefFoundError.

I/Test﹕ Done A!
E/AndroidRuntime﹕ FATAL EXCEPTION: main
    java.lang.NoClassDefFoundError: ru.pvolan.library_b.ClassB
            at ru.pvolan.somelibrary.ClassA.doSomething(ClassA.java:12)
            ...

If I set transitive = true in both .gradle files, it runs ok, but, as I noted above, I don't want dependency to be transitive, as far as I don't want ClassB can be accessed from MainActivity - only ClassA.

What am I doing wrong?

like image 210
PVoLan Avatar asked Nov 01 '22 12:11

PVoLan


1 Answers

This is a problem that Gradle has simplified in Gradle v3.4.

If you convert library A to use v3.4 there is a simple fix.

Gradle 3.4 changes the "compile" configuration to a set of configurations "api" and "implementation".

First you should upgrade gradle to 3.4 and use the java-library plugin in lieu of the java plugin.

You should use the "api" configuration on any jar that is explicitly used in the API method calls (return type, input parameters, etc).

For all other jars that you want to "hide" (like Library B) you should use the "implementation" configuration. As Library B is only used within the body of implementation methods there is no need to expose it to any other jars at compile time; however it still needs to be available at runtime so Library A can use it.

To implement this your Library A script should replace

apply plugin: 'java' 

dependencies {
    ...
    compile (project(":library_b")){
        transitive = false;
    }
}

with

apply plugin: 'java-library'

dependencies {
    implementation project(":library_b")
}

This change will tell Gradle to include Library B as a runtime dependency of app, so that app cannot compile against it, but Library B still will be available at runtime for Library A to use. If for some reason app ends up needing Library B in the future, it would be forced to explicitly include Library B in it's dependency list to ensure it gets the desired version.

See this description from Gradle itself for more details and examples: https://blog.gradle.org/incremental-compiler-avoidance

like image 149
Justin Rhoades Avatar answered Nov 09 '22 09:11

Justin Rhoades