background
Our engineering structure is a single bunker, and then the entire single bunker mode is completed through the mechanism of composit building provided by Gradle.
Single warehouse means that all code is compiled in one repository, which ensures the stability of the code, especially if the compiled product is not particularly trustworthy
A brief introduction of the composite building has been made before. Although this is a good thing, it has a natural problem that some of the general properties of the main project cannot be reused in a project that conforms to the build.
Coroutine routing componentization 1+1+1>3 article is this interested can have a look
Is there a way to share ext-like properties across all composite projects?
Strange knowledge
Then analyze each point slowly and let everyone know what you have done.
The demo project address is here
initscript
This is a method that gradle hides deeply. Normally it will be placed in the. Gradle directory. The official demo is as follows.
Init. Gradle initscript {repositories {mavenCentral ()} dependencies {classpath 'org.apache.com mons: Commons - math: 2.0' }}Copy the code
In the gradle life cycle, this is the first method to execute, we can define the global properties to be placed in this file, and then in the.gradle directory.
On the other hand, initscript can be executed before settings.gradle is executed, so we can do something bad.
In short, it works with gradle projects globally, including composite build projects.
Plugin
We write gradle Plugin as Plugin
The other Plugin is Settings :Plugin
. This is usually used for large projects. A large Project has only one settings.gradle.
This time we will introduce Plugin
, which is specific to Gradle. You may not understand what this is.
class RepoSettingsPlugin : Plugin<Settings> {
override fun apply(settings: Settings) {
settings.gradle.addProjectEvaluationListener(object : ProjectEvaluationListener {
})
}
}
Copy the code
Settings. gradle is what we call gradle. There are Gradle instances within the Project instance.
Gradle doesn’t have a lot of apis, but it can get Settings and project execution ahead of time, which means we can insert what we want earlier.
PluginManagement
Add classpath to build builder.gralde to add classpath to build builder.gralde.
PluginManagement gradle PluginManagement gradle PluginManagement gradle PluginManagement gradle PluginManagement gradle PluginManagement gradle PluginManagement You can get the corresponding plug-in from PluginManagement.
Of course, we need to add some general configuration under Settings.
pluginManagement {
repositories {
mavenLocal()
gradlePluginPortal()
google()
maven {
url "https://dl.bintray.com/kotlin/kotlin-eap"
}
}
}
Copy the code
This is basically the same functionality we used with mavenCentral, but some of our other plugins are now available in plugins as pluginName+version without being declared in the classpath.
PluginManagement And there are preconditions for using this. This is actually a new poM-like file. So our plugin needs to define the gradlePlugin DSL after java-gradle-plugin when published, so that the corresponding file will be uploaded when mavenPublish.
GradlePlugin {plugins {settingsPlugin {// In the app module needs to reference the plugin id = 'kronos.settings' // implement the plugin class path implementationClass = 'com.kronos.plugin.repo.RepoSettingsPlugin' } } }Copy the code
There are also extreme cases, such as plug-ins that have not been iterated for several versions and do not support the feature itself, where there is an alternative.
New features are enforced through the resolutionStrategy in pluginManagement, followed by a name mapping to a specific download address.
resolutionStrategy {
eachPlugin {
if (requested.id.id == 'xxxxx') {
useModule('xxx:xxx:version')
}
if (requested.id.id == 'xxxxxx') {
useModule("xxx:xxx:version")
}
}
}
Copy the code
If it cannot be found in the warehouse, the plugin of the specified version will be downloaded to the specified address according to the ID of the plug-in.
String together
What we need now is to add pluginManagement to all projects under one Project to ensure that their Settings remain the same as before.
This way we can add the same logic to all of the project Settings globally, and then our pointcut is just a plugin for the root settings.gralde.
First SettingsPlugin let’s see what I did there.
class PluginsVersionPlugin : Plugin<Settings> { override fun apply(target: Settings) {/ / access to the outermost transformation file FileUtils. GetRootProjectDir (target. Gradle)? .let { IncludeBuildInsertScript().execute(target, it) } target.gradle.plugins.apply(PluginVersionGradlePlugin::class.java) } } class IncludeBuildInsertScript { fun execute(target: Settings, root: File) { val initFile = getBuildTemp(root, "global.settings.pluginManagement.gradle") if (initFile.exists()) { initFile.delete() } initFile.appendText(PLUGIN_MANAGEMENT_SCRIPT) initFile.appendText("gradle.apply plugin: com.kronos.plugin.version.PluginVersionGradlePlugin.class") val fileList = mutableListOf<File>().apply { addAll(target.gradle.startParameter.initScripts) } fileList.add(initFile) target.gradle.startParameter.initScripts = fileList } fun getBuildTemp(root: File, path: String): File { val result = File(root.canonicalPath + File.separator + "build" + File.separator + path) touch(result) return result } private fun touch(file: File) { if (! file.parentFile.exists()) { file.parentFile.mkdirs() } } }Copy the code
. This time we very opportunistic, through gradle startParameter. InitScripts will we manually generated in the build initscript inserted into the global gradle. InitScripts. So we can insert in all conform to the building engineering the PluginVersionGradlePlugin.
class PluginVersionGradlePlugin : Plugin<Gradle> { override fun apply(target: Gradle) { target.settingsEvaluated { pluginManagement(DefaultPluginManagementAction(this)) GradlePluginsVersion().execute(this) } target.addBuildListener(object : BuildAdapter() { override fun projectsEvaluated(gradle: Gradle) { super.projectsEvaluated(gradle) val rootProject = gradle.rootProject rootProject.configurations.all { resolutionStrategy { dependencySubstitution { all { if (requested is ModuleComponentSelector) { val selector = requested as ModuleComponentSelector val group = selector.group val module = selector.module val p = rootProject.allprojects.find { p -> p.group.toString() == group && p.name == module } if (p ! = null) { Logger.debug("select $requested local project") useTarget(project(p.path), "selected local project") } } } } } } } }) } }Copy the code
Add a projectsEvaluated listener via target.addBuildListener, and then insert the global PluginManagement and corresponding policies into the Settings.
conclusion
To tell you the truth, I still learned a lot of interesting operations from my boss, recently transferred to the compilation group, the content is actually quite interesting, this part is also from the boss’s code out.
I personally think the composite build pattern is still better than a single-project include, and not just because simple configurations share these. There are also natural build separations, project-level ones, such as cross-references.