Skip to content
This repository was archived by the owner on Feb 11, 2022. It is now read-only.

Conversation

mr-archano
Copy link
Contributor

@mr-archano mr-archano commented Oct 28, 2018

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 the MavenPublishPlugin to produce a correct POM descriptor that includes all the dependencies needed by an Android library, exclusion rules, etc

Unfortunately 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.

}
})

Task compatClassesTask = taskContainer.create(name: 'releasePluginCompatClasses', type: DefaultTask) {
Copy link
Contributor Author

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

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.

Copy link
Contributor Author

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) {
Copy link
Contributor Author

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

Copy link
Contributor

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?

Copy link
Contributor Author

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) {
Copy link
Contributor Author

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)

Copy link
Contributor

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?

Copy link
Contributor Author

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) {
Copy link
Contributor Author

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

Copy link
Contributor

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?

Copy link
Contributor Author

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))
Copy link
Contributor Author

@mr-archano mr-archano Oct 28, 2018

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)
Copy link
Contributor Author

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)
Copy link
Contributor Author

@mr-archano mr-archano Oct 28, 2018

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)

Copy link
Contributor

@tobiasheine tobiasheine left a 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) {
Copy link
Contributor

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) {
Copy link
Contributor

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) {
Copy link
Contributor

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?

@mr-archano mr-archano merged commit a875fbd into feature/177/support_new_gradle_versions Nov 3, 2018
@mr-archano mr-archano deleted the feature/177/task/introduce_compat_module_4_1 branch November 3, 2018 09:46
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants