-
Notifications
You must be signed in to change notification settings - Fork 209
Feature/177/task: Introduce compat modules #244
Feature/177/task: Introduce compat modules #244
Conversation
…e with Gradle 4.1
…ule classpath and jar
…nt compiled with different version of Gradle
} | ||
}) | ||
|
||
Task compatClassesTask = taskContainer.create(name: 'releasePluginCompatClasses', type: DefaultTask) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This task is used to 'join' all the work needed to compile and include the compat classes in the plugin/core
module
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the minimum version of Gradle you need to support?
You may want to consider using tasks.register
if you can use the 4.8+ Gradle API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @JLLeitschuh thanks for the comment. These tasks here are not part of the plugin code, but just of its builscript, so we could use a more recent version of Gradle and use the new tasks api, as right now the plugin core module is compiled against Gradle 4.3. I will think about it, but it's definitely something that I wouldn't address as part of this tranche of work (that focuses on fixing the compatibility issue of its internals).
|
||
def moduleName = moduleDir.name | ||
|
||
def cleanModuleTask = taskContainer.create(name: "clean${moduleName.capitalize()}", type: Delete) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we add a custom clean task to ensure that we clean all the outputs of each compat module when we invoke :clean
on the plugin/core
module
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't each module already have a clean
task we could use instead of creating a new one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
they do, but they are also different Gradle projects (see description). The only way to invoke their :clean
task is then to run a gradle build from the core module Gradle build, but it sounded overkill considering that we need to just remove a folder
} | ||
taskContainer.clean.dependsOn cleanModuleTask | ||
|
||
def compileModuleTask = taskContainer.create(name: "compile${moduleName.capitalize()}Classes", type: Exec) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we want to kick-off the compilation of the compat module, but given that is a separate Gradle project we then leverage an ad-hoc Exec
task to run the Gradle build for that project (that will use a different version of Gradle for instance)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here, why do we manually need to create a compile
task for each module?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so, the comment above yours was my attempt at highlighting that each module is a separate Gradle project, that is plugin/compat/xyz
is not in the same Gradle project as plugin/core
.
To invoke the compile task in a separate Gradle project we are leveraging Exec
to run a Gradle build from a Gradle build (buildception!)
commandLine gw, 'clean', 'build' | ||
} | ||
|
||
def copyClassesTask = taskContainer.create(name: "copy${moduleName.capitalize()}Classes", type: Copy) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once the compat classes are compiled we can move them into a folder inside the plugin/core
module. We have picked build/compat/classes
so this folder will be wiped automatically at every :clean
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we not also just change the buildDir
of each module?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, but that feels like tampering with the compat projects, and it will be a little unexpected. After all these are not very expensive operations.
sourceSets { | ||
main { | ||
groovy { | ||
output.dir(project.files(compatClassesDestination)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This single line here allows us to bundle the content of build/compat/classes
in the artifact of the plugin/core
module, so to ship them in the same Jar. This enables the class loader to load them at runtime via reflection without looking for other artifacts.
} | ||
|
||
dependencies { | ||
compileOnly files(compatClassesTask) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This compileOnly
dependency ensures that we trigger the : releasePluginCompatClasses
task before we start compiling the sources in plugin/core
.
Note that compileOnly
dependencies will not pollute the POM descriptor of the plugin.
private static AndroidSoftwareComponent androidComponentFrom(Project project) { | ||
return new AndroidSoftwareComponent(project.objects, project.configurations) | ||
private static SoftwareComponent androidComponentFrom(Project project) { | ||
def clazz = this.classLoader.loadClass(ANDROID_SOFTWARE_COMPONENT_COMPAT_4_1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right now we are not checking the current version of Gradle at runtime, we will once we have more compat implementations. (Follow-up PRs coming against the same feature branch)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's great, just left a couple of questions.
|
||
def moduleName = moduleDir.name | ||
|
||
def cleanModuleTask = taskContainer.create(name: "clean${moduleName.capitalize()}", type: Delete) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't each module already have a clean
task we could use instead of creating a new one?
} | ||
taskContainer.clean.dependsOn cleanModuleTask | ||
|
||
def compileModuleTask = taskContainer.create(name: "compile${moduleName.capitalize()}Classes", type: Exec) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here, why do we manually need to create a compile
task for each module?
commandLine gw, 'clean', 'build' | ||
} | ||
|
||
def copyClassesTask = taskContainer.create(name: "copy${moduleName.capitalize()}Classes", type: Copy) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we not also just change the buildDir
of each module?
The plugin is currently relying on a custom implementation of an internal Gradle API (
SoftwareComponentInternal
) in order to provide full support to Android library projects. Specifically we need to implement this internal API if we want to leverage the internal capabilities of theMavenPublishPlugin
to produce a correct POM descriptor that includes all the dependencies needed by an Android library, exclusion rules, etcUnfortunately coding against an internal Gradle API has proven to be quite challenging, as
SoftwareComponentInternal
has changed its shape few times over time, and our custom implementation is not complying anymore.The solution proposed here is to create 'compat implementations' of that class using separate modules targeting each a different version of Gradle that we want to support. The compiled compat classes can then automatically added into the final jar of the plugin and we will use reflection to load the correct one according to the Gradle version used at runtime.
This first PR shows how we can achieve this just moving the current implementation in its own
plugin/compat/gradle-4_1
module and adding a little of gradle setup to ensure we compile and bundle the classes automatically as needed.