Dynamically add buildscript dependencies to some subprojects

I want to dynamically go and add specific plugins to some subprojects. Therefore I’m using this syntax:

rootProject.ext {
    crashlytics = 'io.fabric.tools:gradle:1.+'
}

rootProject.subprojects { project ->
    if (project.name.contains('client-') && !project.name.contains('core')) {
        project.buildscript.dependencies.add('classpath', rootProject.ext.crashlytics)
        project.plugins.apply('io.fabric')
    }
}

However I do get this error:

Plugin with id io.fabric cannot be found.

How can I fix that?

If I manually add it to the specific subproject build.gradle file with

buildscript {
    dependencies {
        classpath rootProject.ext.crashlytics
    }
}

apply plugin: 'io.fabric'

It compiles without any complaints / errors.

1 Like

When referencing an ext property you do not use the ext prefix.

It should suffice to use rootProject.crashlytics for the direct buildscript references and the injected references.

When declaring the property you have to do so via the ext namespace as you already do.

Even if I do

ext {
    crashlytics = 'io.fabric.tools:gradle:1.+'
}

subprojects { project ->
        project.buildscript.dependencies.add('classpath', crashlytics)
        project.plugins.apply('io.fabric')
}

I still get the same error

Plugin with id ‘io.fabric’ not found

The subprojects iteration causes the buildscript dependencies for the subprojects to be resolved. Injecting the classpath dependency in this closure will never work. What you might be able to do is just use the buildscript at the root project alone. The buildscript classpath for the root project is the parent clasloader for the subprojects and the application of the plugin will work then.

I use all of classpath plugins this way in my multiproject configurations. There was at least one occasion that I encountered a classloading issue with this approach that caused the subproject to require a buildscript closure directly but the majority of plugins will work this way in my experience.

This pattern works in my multiproject configuration.

buildscript {
    ext.crashlytics = 'io.fabric.tools:gradle:1.+'
    dependencies {
        classpath crashlytics
    }
}

subprojects {
        apply plugin: 'io.fabric'
}

Okay fair enough. Will the dependencies be loaded for every subproject or only when you actually do apply the plugin? I’m talking about having around 25 subprojects and I only do need the fabric plugin in 5. So the overhead would be quite big if the dependency would be loaded for every subproject.

The dependencies are loaded only once in the root project classloader by the buildscript reference. You only need the buildscript in the root project. They are inherited by subprojects via the parent relationship but are not reloaded. So, you can safely use a condition as in your original post to make the plugin apply only where is needed.

subprojects { project ->
    if (project.name.contains('client-') && !project.name.contains('core')) {
        project.plugins.apply('io.fabric')
    }
}

Okay awesome. The reason I asked is because if you generate a new android project it explicitly says in the root/build.gradle file that dependencies should not be declared in the root location.

The rule you mention is intended for compile/test dependencies and not build script dependencies.

Plugin dependencies are different from your own compile/test dependencies. What you are doing is simply providing access to the plugin by the build script classpath. That is completely separate from the compile classpath.

Glad I could help solve this for you.

@Alex_Vol, is there any way how the per-project dependencies this could be done?

I have a lot of sub projects (~500) and I would like to do slow rollout of a new version of Kotlin plugin.
we are on 1.3 and I would like to slowly move one project at a time to 1.4. Would you know a reasonable solution for this?

thx,
Jan

I have same problem~