pin初步逻辑

This commit is contained in:
yummylau
2019-10-24 19:52:53 +08:00
parent f2169f2468
commit b19fa0aeeb
37 changed files with 920 additions and 901 deletions

View File

@@ -21,12 +21,12 @@ class ComponentPlugin implements Plugin<Project>,BasePlugin{
private BasePlugin sdk = new SdkPlugin()
private BasePlugin debug = new DebugPlugin()
private BasePlugin pins = new PinPlugin()
private ComponentExtension componentExtension
@Override
void apply(Project project) {
if (project == project.rootProject) {
ComponentExtension componentExtension = project.getExtensions().create(Constants.COMPONENT, ComponentExtension, project)
componentExtension = project.getExtensions().create(Constants.COMPONENT, ComponentExtension, project)
initExtension(componentExtension)
evaluateRoot(project)
project.afterEvaluate {
@@ -73,6 +73,7 @@ class ComponentPlugin implements Plugin<Project>,BasePlugin{
@Override
void afterEvaluateRoot(Project root) {
Runtimes.initRuntimeConfiguration(root, componentExtension)
sdk.afterEvaluateRoot(root)
debug.afterEvaluateRoot(root)
pins.afterEvaluateRoot(root)

View File

@@ -1,7 +1,9 @@
package com.plugin.component
import com.plugin.component.extension.ComponentExtension
import com.plugin.component.extension.module.PinInfo
import com.plugin.component.extension.module.ProjectInfo
import com.plugin.component.extension.option.pin.PinConfiguration
import com.plugin.component.extension.option.pin.PinOption
import com.plugin.component.extension.option.sdk.CompileOptions
import com.plugin.component.extension.option.debug.DebugConfiguration
@@ -20,6 +22,8 @@ class Runtimes {
//模块信息
private static Map<String, ProjectInfo> sProjectInfoMap = new HashMap<>()
private static Map<String, PinConfiguration> sPinConfigurations = new HashMap<>()
private static String sAndroidJarPath
public static DebugOption sDebugOption
public static SdkOption sSdkOption
@@ -49,13 +53,35 @@ class Runtimes {
Logger.buildOutput(componentExtension.debugOption.toString())
Logger.buildOutput(" =====> component.gradle配置信息 <===== ")
Logger.buildOutput("")
//初始化pins
root.allprojects.each {
for (PinConfiguration pinConfiguration : sPinOption.configurationList) {
if (ProjectUtil.isProjectSame(it.name, pinConfiguration.name)) {
sPinConfigurations.put(it.name, pinConfiguration)
pinConfiguration.initMainPin(it)
}
}
}
}
static CompileOptions getCompileOption(){
static Map<String, PinConfiguration> getPinConfigurations() {
return sPinConfigurations
}
static boolean hasPinModule() {
return !sPinConfigurations.isEmpty()
}
static PinConfiguration getPinConfiguration(String name) {
return sPinConfigurations.get(name)
}
static CompileOptions getCompileOption() {
return sSdkOption.compileOption
}
static getAndroidJarPath(){
static getAndroidJarPath() {
return sAndroidJarPath
}

View File

@@ -1,8 +1,8 @@
package com.plugin.component.check
import com.plugin.component.extension.module.MicroModule
import com.plugin.component.extension.module.MicroModuleInfo
import com.plugin.component.extension.module.PinInfo
import com.plugin.component.extension.module.ProductFlavorInfo
import com.plugin.component.extension.option.pin.PinConfiguration
import org.gradle.api.GradleScriptException
import org.gradle.api.Project
import org.w3c.dom.Element
@@ -11,7 +11,7 @@ import org.w3c.dom.NodeList
class CodeChecker {
Project project
MicroModuleInfo microModuleInfo
PinConfiguration pinConfiguration
ProductFlavorInfo productFlavorInfo
String buildType
@@ -25,9 +25,9 @@ class CodeChecker {
Map<String, List<String>> microModulePackageNameMap
CodeChecker(Project project, MicroModuleInfo microModuleInfo, ProductFlavorInfo productFlavorInfo, String buildType, String productFlavor) {
CodeChecker(Project project, PinConfiguration pinConfiguration, ProductFlavorInfo productFlavorInfo, String buildType, String productFlavor) {
this.project = project
this.microModuleInfo = microModuleInfo
this.pinConfiguration = pinConfiguration
this.productFlavorInfo = productFlavorInfo
this.buildType = buildType
this.productFlavor = productFlavor
@@ -50,7 +50,7 @@ class CodeChecker {
throw new GradleScriptException(errorMessage, null)
}
def manifest = new File(microModuleInfo.mainMicroModule.microModuleDir, "src/main/AndroidManifest.xml")
def manifest = new File(pinConfiguration.mainPin.pinDir, "src/main/AndroidManifest.xml")
String packageName = Utils.getAndroidManifestPackageName(manifest)
checkManifest.packageName = packageName
saveModuleCheckManifest()
@@ -105,7 +105,7 @@ class CodeChecker {
def find = matcher.group()
def name = find.substring(find.indexOf("/") + 1)
def from = resourcesMap.get(name)
if (from != null && microModuleName != from && !microModuleInfo.hasDependency(microModuleName, from)) {
if (from != null && microModuleName != from && !pinConfiguration.hasDependency(microModuleName, from)) {
List<Number> lines = textLines.findIndexValues { it.contains(find) }
lines.each {
def lineIndex = it.intValue()
@@ -117,7 +117,7 @@ class CodeChecker {
def message = absolutePath + ':' + (lineIndex + 1)
if (!errorMessage.contains(message)) {
message += lineSeparator
message += "- cannot use [" + find + "] which from MicroModule '${from}'."
message += "- cannot use [" + find + "] which from PinInfo '${from}'."
message += lineSeparator
errorMessage += message
}
@@ -157,14 +157,14 @@ class CodeChecker {
List<File> getModifiedClassesList(List<String> sourceFolders) {
Map<String, MicroModuleFile> lastModifiedClassesMap = checkManifest.getClassesMap()
List<File> modifiedClassesList = new ArrayList<>()
microModuleInfo.includeMicroModules.each {
MicroModule microModule = it.value
pinConfiguration.includePins.each {
PinInfo microModule = it.value
sourceFolders.each {
File javaDir = new File(microModule.microModuleDir, "/src/${it}/java")
File javaDir = new File(microModule.pinDir, "/src/${it}/java")
if (javaDir.exists()) {
getModifiedJavaFile(javaDir, modifiedClassesList, lastModifiedClassesMap)
}
File kotlinDir = new File(microModule.microModuleDir, "/src/${it}/kotlin")
File kotlinDir = new File(microModule.pinDir, "/src/${it}/kotlin")
if (kotlinDir.exists()) {
getModifiedJavaFile(kotlinDir, modifiedClassesList, lastModifiedClassesMap)
}
@@ -229,7 +229,7 @@ class CodeChecker {
from = classesMap.get(name)
}
if (from != null && microModuleName != from && !microModuleInfo.hasDependency(microModuleName, from)) {
if (from != null && microModuleName != from && !pinConfiguration.hasDependency(microModuleName, from)) {
List<Number> lines = textLines.findIndexValues { it.contains(find) }
lines.each {
def lineIndex = it.intValue()
@@ -241,7 +241,7 @@ class CodeChecker {
def message = absolutePath + ':' + (lineIndex + 1)
if (!errorMessage.contains(message)) {
message += lineSeparator
message += "- cannot use [" + find + "] which from MicroModule '${from}'."
message += "- cannot use [" + find + "] which from PinInfo '${from}'."
message += lineSeparator
errorMessage += message
}
@@ -307,13 +307,13 @@ class CodeChecker {
private String initMicroModulePackageName() {
microModulePackageNameMap = new HashMap<>()
microModuleInfo.includeMicroModules.each {
MicroModule microModule = it.value
pinConfiguration.includePins.each {
PinInfo microModule = it.value
boolean find = false
List<String> flavorList = productFlavorInfo.combinedProductFlavorsMap.get(productFlavor)
if (flavorList != null && !flavorList.isEmpty()) {
for (String flavor : flavorList) {
File manifest = new File(microModule.microModuleDir, "/src/${flavor}/AndroidManifest.xml")
File manifest = new File(microModule.pinDir, "/src/${flavor}/AndroidManifest.xml")
if (manifest.exists()) {
String packageName = Utils.getAndroidManifestPackageName(manifest)
if (packageName != null && !packageName.isEmpty()) {
@@ -331,7 +331,7 @@ class CodeChecker {
}
if (!find) {
File manifest = new File(microModule.microModuleDir, "/src/${buildType}/AndroidManifest.xml")
File manifest = new File(microModule.pinDir, "/src/${buildType}/AndroidManifest.xml")
if (manifest.exists()) {
String packageName = Utils.getAndroidManifestPackageName(manifest)
if (packageName != null && !packageName.isEmpty()) {
@@ -347,7 +347,7 @@ class CodeChecker {
}
if (!find) {
File manifest = new File(microModule.microModuleDir, "/src/main/AndroidManifest.xml")
File manifest = new File(microModule.pinDir, "/src/main/AndroidManifest.xml")
if (manifest.exists()) {
String packageName = Utils.getAndroidManifestPackageName(manifest)
if (packageName != null && !packageName.isEmpty()) {

View File

@@ -1,6 +1,5 @@
package com.plugin.component.extension
import com.plugin.component.Runtimes
import com.plugin.component.extension.option.pin.PinOption
import com.plugin.component.extension.option.debug.DebugOption
import com.plugin.component.extension.option.sdk.SdkOption

View File

@@ -1,78 +0,0 @@
package com.plugin.component.extension.module
import com.plugin.component.utils.PinUtils
import org.gradle.api.GradleException
import org.gradle.api.Project
class MicroModuleInfo {
Project project
MicroModule mainMicroModule
Map<String, MicroModule> includeMicroModules
Map<String, String> exportMicroModules
Digraph<String> dependencyGraph
MicroModuleInfo(Project project) {
this.project = project
this.includeMicroModules = new HashMap<>()
this.exportMicroModules = new HashMap<>()
dependencyGraph = new Digraph<String>()
MicroModule microModule = PinUtils.buildMicroModule(project, ':main')
if (microModule != null) {
setMainMicroModule(microModule)
}
}
void setMainMicroModule(MicroModule microModule) {
if (microModule == null) {
throw new GradleException("main MicroModule cannot be null.")
}
this.mainMicroModule = microModule
addIncludeMicroModule(microModule)
}
void addIncludeMicroModule(MicroModule microModule) {
includeMicroModules.put(microModule.name, microModule)
}
void addExportMicroModule(String name) {
MicroModule microModule = PinUtils.buildMicroModule(project, name)
if (microModule == null) {
throw new GradleException("MicroModule with path '${name}' could not be found in ${project.getDisplayName()}.")
}
exportMicroModules.put(name, null)
}
MicroModule getMicroModule(String name) {
return includeMicroModules.get(name)
}
void setMicroModuleDependency(String target, String dependency) {
MicroModule dependencyMicroModule = getMicroModule(dependency)
if(dependencyMicroModule == null) {
if(PinUtils.buildMicroModule(project, dependency) != null) {
throw new GradleException("MicroModule '${target}' dependency MicroModle '${dependency}', but its not included.")
} else {
throw new GradleException("MicroModule with path '${path}' could not be found in ${project.getDisplayName()}.")
}
}
dependencyGraph.add(target, dependency)
if(!dependencyGraph.isDag()) {
throw new GradleException("Circular dependency between MicroModule '${target}' and '${dependency}'.")
}
}
boolean hasDependency(String target, String dependency) {
Map<String, Integer> bfsDistance = dependencyGraph.bfsDistance(target)
for(String key: bfsDistance.keySet()) {
if(key == dependency) {
return bfsDistance.get(key) != null
}
}
return false
}
}

View File

@@ -1,11 +1,8 @@
package com.plugin.component.extension.module
class MicroModule {
class PinInfo {
String name
File microModuleDir
File pinDir
boolean appliedScript
}

View File

@@ -4,6 +4,12 @@ import com.android.build.gradle.BaseExtension
import com.plugin.component.utils.PinUtils
import org.gradle.api.Project
/**
* 记录变种信息
* 参考 https://developer.android.com/studio/build/build-variants
* 变体的产品根据 <product-flavor><Build-Type> 做命名方案
* 如果还存在维度,则 <product-flavor><flavor-dimensions><Build-Type>
*/
class ProductFlavorInfo {
List<String> flavorDimensions
@@ -18,27 +24,35 @@ class ProductFlavorInfo {
ProductFlavorInfo(Project project) {
BaseExtension extension = (BaseExtension) project.extensions.getByName("android")
buildTypes = new ArrayList<>()
//获取build类型, 比如 debugrelease
if(extension.buildTypes != null) {
extension.buildTypes.each {
buildTypes.add(it.name)
}
}
//获取维度,比如 api mode
flavorDimensions = extension.flavorDimensionList
if (flavorDimensions == null) {
flavorDimensions = new ArrayList<>()
}
productFlavors = new ArrayList<>()
flavorGroups = new ArrayList<>()
for (int i = 0; i < flavorDimensions.size(); i++) {
flavorGroups.add(new ArrayList<>())
}
//获取变体,比如 minApi21{ dimension "api"} minApi24{ dimension "mode"}
productFlavors = new ArrayList<>()
extension.productFlavors.each {
productFlavors.add(it.name)
def position = flavorDimensions.indexOf(it.dimension)
flavorGroups.get(position).add(it.name)
}
//过滤掉无效的维度
List<List<String>> flavorGroupTemp = new ArrayList<>()
flavorGroups.each {
if (it.size() != 0) {
@@ -47,7 +61,9 @@ class ProductFlavorInfo {
}
flavorGroups = flavorGroupTemp
//计算合并变体
calculateFlavorCombination()
if (combinedProductFlavors.size() == extension.productFlavors.size()) {
singleDimension = true
}
@@ -62,12 +78,12 @@ class ProductFlavorInfo {
}
List<Integer> combination = new ArrayList<Integer>()
int n = flavorGroups.size();
int n = flavorGroups.size()
for (int i = 0; i < n; i++) {
combination.add(0);
combination.add(0)
}
int i = 0;
boolean isContinue = true;
int i = 0
boolean isContinue = true
while (isContinue) {
List<String> items = new ArrayList<>()
String item = flavorGroups.get(0).get(combination.get(0))
@@ -80,21 +96,22 @@ class ProductFlavorInfo {
}
combinedProductFlavors.add(combined)
combinedProductFlavorsMap.put(combined, items)
i++;
combination.set(n - 1, i);
i++
//i赋值给n-1
combination.set(n - 1, i)
for (int j = n - 1; j >= 0; j--) {
if (combination.get(j) >= flavorGroups.get(j).size()) {
combination.set(j, 0);
i = 0;
combination.set(j, 0)
i = 0
if (j - 1 >= 0) {
combination.set(j - 1, combination.get(j - 1) + 1);
combination.set(j - 1, combination.get(j - 1) + 1)
}
}
}
isContinue = false;
isContinue = false
for (Integer integer : combination) {
if (integer != 0) {
isContinue = true;
isContinue = true
}
}
}

View File

@@ -1,55 +0,0 @@
package com.plugin.component.extension.option.pin
import com.plugin.component.extension.module.MicroModule
import org.gradle.api.GradleException
import org.gradle.api.Project
class DefaultMicroModuleExtension implements MicroModuleExtension {
Project project
OnMicroModuleListener onMicroModuleListener
boolean codeCheckEnabled = true
DefaultMicroModuleExtension(Project project) {
this.project = project
}
@Override
void codeCheckEnabled(boolean enabled) {
this.codeCheckEnabled = enabled
}
@Override
void export(String... microModulePaths) {
if(onMicroModuleListener == null) return
onMicroModuleListener.addExportMicroModule(microModulePaths)
}
@Override
void include(String... microModulePaths) {
if(onMicroModuleListener == null) return
int size = microModulePaths.size()
for (int i = 0; i < size; i++) {
MicroModule microModule = Utils.buildMicroModule(project, microModulePaths[i])
if (microModule == null) {
throw new GradleException("MicroModule with path '${microModulePaths[i]}' could not be found in ${project.getDisplayName()}.")
}
onMicroModuleListener.addIncludeMicroModule(microModule, false)
}
}
@Override
void includeMain(String microModulePath) {
if(onMicroModuleListener == null) return
MicroModule microModule = Utils.buildMicroModule(project, microModulePath)
if (microModule == null) {
throw new GradleException("MicroModule with path '${microModulePath}' could not be found in ${project.getDisplayName()}.")
}
onMicroModuleListener.addIncludeMicroModule(microModule, true)
}
}

View File

@@ -1,13 +0,0 @@
package com.plugin.component.extension.option.pin
interface MicroModuleExtension {
void codeCheckEnabled(boolean disable)
void export(String... microModulePaths)
void includeMain(String microModulePath)
void include(String... microModulePaths)
}

View File

@@ -1,12 +0,0 @@
package com.plugin.component.extension.option.pin
import com.plugin.component.extension.module.MicroModule
interface OnMicroModuleListener {
void addIncludeMicroModule(MicroModule microModule, boolean mainMicroModule)
void addExportMicroModule(String... microModulePaths)
}

View File

@@ -1,18 +1,122 @@
package com.plugin.component.extension.option.pin
import com.plugin.component.Logger
import com.plugin.component.extension.module.Digraph
import com.plugin.component.extension.module.PinInfo
import com.plugin.component.extension.module.ProductFlavorInfo
import com.plugin.component.utils.PinUtils
import org.gradle.api.GradleException
import org.gradle.api.Project
class PinConfiguration {
String name
boolean codeCheckEnabled = true
Set<String> includePins = new HashSet<>()
Set<String> include = new HashSet<>()
Set<String> export = new HashSet<>()
String mainPath
Project project
PinInfo mainPin
Map<String, PinInfo> includePins
Map<String, String> exportPins
Digraph<String> dependencyGraph
ProductFlavorInfo productFlavorInfo
PinConfiguration(String name) {
this.name = name
}
void initMainPin(Project project) {
this.project = project
this.includePins = new HashMap<>()
this.exportPins = new HashMap<>()
dependencyGraph = new Digraph<String>()
PinInfo mainPin = PinUtils.buildPin(project, ':main')
if (mainPin != null) {
setMainPin(mainPin)
}
}
void initProductFlavor() {
productFlavorInfo = new ProductFlavorInfo(project)
PinUtils.clearOriginSourceSet(project, productFlavorInfo)
for (String include : include) {
PinInfo pin = PinUtils.buildPin(project, include)
if (microModule == null) {
throw new GradleException("PinInfo with path '${include}' could not be found in ${project.getDisplayName()}.")
}
addIncludePin(pin)
PinUtils.addMicroModuleSourceSet(project, pin, productFlavorInfo)
}
for (String export : export) {
PinInfo pin = PinUtils.buildPin(project, export)
if (pin == null) {
throw new GradleException("PinInfo with path '${export}' could not be found in ${project.getDisplayName()}.")
}
addExportPin(pin)
PinUtils.addMicroModuleSourceSet(project, pin, productFlavorInfo)
}
if (mainPath != null && !mainPath.isEmpty()) {
PinInfo pin = PinUtils.buildPin(project, mainPath)
setMainPin(pin)
PinUtils.addMicroModuleSourceSet(project, pin, productFlavorInfo)
}
}
void setMainPin(PinInfo pin) {
if (pin == null) {
throw new GradleException("main PinInfo cannot be null.")
}
this.mainPin = pin
addIncludePin(pin)
}
void addIncludePin(PinInfo pin) {
includePins.put(pin.name, pin)
}
void addExportPin(String name) {
PinInfo pin = PinUtils.buildPin(project, name)
if (pin == null) {
throw new GradleException("PinInfo with path '${name}' could not be found in ${project.getDisplayName()}.")
}
exportPins.put(name, null)
}
PinInfo getIncludePin(String name) {
return includePins.get(name)
}
void setPinDependency(String target, String name) {
PinInfo pin = getIncludePin(name)
if (pin == null) {
if (PinUtils.buildPin(project, name) != null) {
throw new GradleException("PinInfo '${target}' name MicroModle '${name}', but its not included.")
} else {
throw new GradleException("PinInfo with path '${path}' could not be found in ${project.getDisplayName()}.")
}
}
dependencyGraph.add(target, name)
if (!dependencyGraph.isDag()) {
throw new GradleException("Circular name between PinInfo '${target}' and '${name}'.")
}
}
boolean hasDependency(String target, String name) {
Map<String, Integer> bfsDistance = dependencyGraph.bfsDistance(target)
for (String key : bfsDistance.keySet()) {
if (key == name) {
return bfsDistance.get(key) != null
}
}
return false
}
void codeCheckEnabled(boolean enabled) {
this.codeCheckEnabled = enabled
}
@@ -20,7 +124,7 @@ class PinConfiguration {
void export(String... pinPaths) {
int size = pinPaths.size()
for (int i = 0; i < size; i++) {
includePins.add(pinPaths[i])
include.add(pinPaths[i])
}
}

View File

@@ -1,20 +1,21 @@
package com.plugin.component.extension.option.pin
import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.Project
import org.gradle.util.ConfigureUtil
class PinOption {
Project project
Project root
List<PinConfiguration> configurationList = new ArrayList<>() //配置信息
PinOption(Project project) {
this.project = project
PinOption(Project root) {
this.root = root
}
void configuration(Closure closure) {
NamedDomainObjectContainer<PinConfiguration> configurations = project.container(PinConfiguration)
NamedDomainObjectContainer<PinConfiguration> configurations = root.container(PinConfiguration)
ConfigureUtil.configure(closure, configurations)
configurations.each {
configurationList.add(it)
@@ -25,13 +26,14 @@ class PinOption {
String toString() {
StringBuilder stringBuilder = new StringBuilder("\n")
stringBuilder.append(" ------------------------------------------------------------------" + "\n")
stringBuilder.append(" | configuration = [ " + "\n" )
for(PinConfiguration configuration: configurationList){
stringBuilder.append(" | name = " + configuration.name + ", codeCheckEnabled = " + configuration.codeCheckEnabled + ", mathPath = " + configuration.mainPath
+ ", include = " + configuration.includePins.toString() + ", export = " + configuration.export.toString() + "\n" )
stringBuilder.append(" | configuration = [ " + "\n")
for (PinConfiguration configuration : configurationList) {
stringBuilder.append(" | name = " + configuration.name + ", codeCheckEnabled = " + configuration.codeCheckEnabled + ", mathPath = " + configuration.mainPath
+ ", include = " + configuration.include.toString() + ", export = " + configuration.export.toString() + "\n")
}
stringBuilder.append(" | ] " + "\n" )
stringBuilder.append(" | ] " + "\n")
stringBuilder.append(" ------------------------------------------------------------------" + "\n")
return stringBuilder.toString()
}
}

View File

@@ -1,608 +0,0 @@
package com.plugin.component.plugin
import com.android.build.gradle.*
import com.android.build.gradle.api.BaseVariant
import com.android.builder.model.ProductFlavor
import com.android.manifmerger.ManifestMerger2
import com.android.manifmerger.MergingReport
import com.android.manifmerger.XmlDocument
import com.android.utils.ILogger
import com.plugin.component.check.CodeChecker
import com.plugin.component.extension.module.MicroModule
import com.plugin.component.extension.module.MicroModuleInfo
import com.plugin.component.extension.module.ProductFlavorInfo
import com.plugin.component.utils.PinUtils
import com.plugin.component.extension.option.pin.DefaultMicroModuleExtension
import com.plugin.component.extension.option.pin.OnMicroModuleListener
import org.gradle.BuildListener
import org.gradle.BuildResult
import org.gradle.api.*
import org.gradle.api.artifacts.Configuration
import org.gradle.api.initialization.Settings
import org.gradle.api.invocation.Gradle
class MicroModulePlugin implements Plugin<Project> {
private final static String NORMAL = 'normal'
private final static String ASSEMBLE_OR_GENERATE = 'assemble_or_generate'
private final static String APPLY_NORMAL_MICRO_MODULE_SCRIPT = 'apply_normal_micro_module_script'
private final static String APPLY_INCLUDE_MICRO_MODULE_SCRIPT = 'apply_include_micro_module_script'
private final static String APPLY_EXPORT_MICRO_MODULE_SCRIPT = 'apply_export_micro_module_script'
Project project
String startTaskState = NORMAL
MicroModuleInfo microModuleInfo
ProductFlavorInfo productFlavorInfo
MicroModule currentMicroModule
String applyScriptState
boolean appliedLibraryPlugin
boolean clearedOriginSourceSets
private final static BuildListener buildListener = new BuildListener() {
@Override
void buildStarted(Gradle gradle) {
}
@Override
void settingsEvaluated(Settings settings) {
}
@Override
void projectsLoaded(Gradle gradle) {
}
@Override
void projectsEvaluated(Gradle gradle) {
}
@Override
void buildFinished(BuildResult buildResult) {
// generate microModules.xml for MicroModule IDEA plugin.
def ideaFile = new File(buildResult.gradle.rootProject.rootDir, '.idea')
if (!ideaFile.exists()) return
def microModuleInfo = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<modules>\n'
buildResult.gradle.rootProject.allprojects.each {
MicroModulePlugin microModulePlugin = it.plugins.findPlugin('micro-module')
if (microModulePlugin == null) return
def displayName = it.displayName
microModuleInfo += ' <module name=\"' + displayName.substring(displayName.indexOf("'") + 1, displayName.lastIndexOf("'")) + '\" path=\"' + it.projectDir.getCanonicalPath() + '\">\n'
microModulePlugin.microModuleInfo.includeMicroModules.each {
MicroModule microModule = it.value
microModuleInfo += ' <microModule name=\"' + microModule.name + '\" path=\"' + microModule.microModuleDir.getCanonicalPath() + '\" />\n'
}
microModuleInfo += ' </module>\n'
}
microModuleInfo += '</modules>'
def microModules = new File(ideaFile, 'microModules.xml')
microModules.write(microModuleInfo, 'utf-8')
}
}
void apply(Project project) {
this.project = project
this.microModuleInfo = new MicroModuleInfo(project)
project.gradle.removeListener(buildListener)
project.gradle.addBuildListener(buildListener)
if (project.gradle.getStartParameter().taskNames.size() == 0) {
startTaskState = NORMAL
} else {
startTaskState = ASSEMBLE_OR_GENERATE
}
if (startTaskState != NORMAL) {
project.getConfigurations().whenObjectAdded {
Configuration configuration = it
configuration.dependencies.whenObjectAdded {
if (applyScriptState == APPLY_INCLUDE_MICRO_MODULE_SCRIPT) {
configuration.dependencies.remove(it)
return
} else if (applyScriptState == APPLY_NORMAL_MICRO_MODULE_SCRIPT
|| applyScriptState == APPLY_EXPORT_MICRO_MODULE_SCRIPT) {
return
} else if (currentMicroModule == null && startTaskState == ASSEMBLE_OR_GENERATE) {
return
} else if (it.group != null && it.group.startsWith('com.android.tools')) {
return
}
configuration.dependencies.remove(it)
}
}
}
DefaultMicroModuleExtension microModuleExtension = project.extensions.create(MicroModuleExtension, 'microModule', DefaultMicroModuleExtension, project)
microModuleExtension.onMicroModuleListener = new OnMicroModuleListener() {
@Override
void addIncludeMicroModule(MicroModule microModule, boolean mainMicroModule) {
if (mainMicroModule) {
microModuleInfo.setMainMicroModule(microModule)
} else {
microModuleInfo.addIncludeMicroModule(microModule)
}
if(!clearedOriginSourceSets) {
productFlavorInfo = new ProductFlavorInfo(project)
clearedOriginSourceSets = true
clearOriginSourceSet()
if(microModuleInfo.mainMicroModule != null) {
addMicroModuleSourceSet(microModuleInfo.mainMicroModule)
}
}
addMicroModuleSourceSet(microModule)
}
@Override
void addExportMicroModule(String... microModulePaths) {
microModulePaths.each {
microModuleInfo.addExportMicroModule(it)
}
}
}
project.dependencies.metaClass.microModule { String path ->
if (currentMicroModule == null || applyScriptState == APPLY_NORMAL_MICRO_MODULE_SCRIPT) {
return []
}
if (applyScriptState == APPLY_INCLUDE_MICRO_MODULE_SCRIPT) {
microModuleInfo.setMicroModuleDependency(currentMicroModule.name, path)
return []
}
MicroModule microModule = microModuleInfo.getMicroModule(path)
def result = []
if (startTaskState == ASSEMBLE_OR_GENERATE) {
addMicroModuleSourceSet(microModule)
applyMicroModuleScript(microModule)
microModule.appliedScript = true
}
return result
}
project.plugins.all {
Class extensionClass
if (it instanceof AppPlugin) {
extensionClass = AppExtension
} else if (it instanceof LibraryPlugin) {
extensionClass = LibraryExtension
} else {
return
}
project.extensions.configure(extensionClass, new Action<? extends TestedExtension>() {
@Override
void execute(TestedExtension testedExtension) {
boolean isLibrary
DomainObjectSet<BaseVariant> baseVariants
if (testedExtension instanceof AppExtension) {
AppExtension appExtension = (AppExtension) testedExtension
baseVariants = appExtension.applicationVariants
} else {
LibraryExtension libraryExtension = (LibraryExtension) testedExtension
baseVariants = libraryExtension.libraryVariants
isLibrary = true
}
baseVariants.all { BaseVariant variant ->
if (microModuleExtension.codeCheckEnabled) {
def taskNamePrefix = isLibrary ? 'package' : 'merge'
List<String> sourceFolders = new ArrayList<>()
sourceFolders.add('main')
sourceFolders.add(variant.buildType.name)
if (variant.productFlavors.size() > 0) {
sourceFolders.add(variant.name)
sourceFolders.add(variant.flavorName)
for (ProductFlavor productFlavor : variant.productFlavors) {
sourceFolders.add(productFlavor.name)
}
checkMicroModuleBoundary(taskNamePrefix, variant.buildType.name, variant.flavorName, sourceFolders)
} else {
checkMicroModuleBoundary(taskNamePrefix, variant.buildType.name, null, sourceFolders)
}
}
}
}
})
}
project.afterEvaluate {
microModuleExtension.onMicroModuleListener = null
if (microModuleInfo.mainMicroModule == null) {
throw new GradleException("the main MicroModule could not be found in ${project.getDisplayName()}.")
}
appliedLibraryPlugin = project.pluginManager.hasPlugin('com.android.library')
productFlavorInfo = new ProductFlavorInfo(project)
applyScriptState = APPLY_INCLUDE_MICRO_MODULE_SCRIPT
microModuleInfo.includeMicroModules.each {
MicroModule microModule = it.value
microModuleInfo.dependencyGraph.add(microModule.name)
applyMicroModuleScript(microModule)
}
clearOriginSourceSet()
if (startTaskState == ASSEMBLE_OR_GENERATE) {
applyScriptState = APPLY_EXPORT_MICRO_MODULE_SCRIPT
boolean hasExportMainMicroModule = false
boolean isEmpty = microModuleInfo.exportMicroModules.isEmpty()
List<String> dependencySort = microModuleInfo.dependencyGraph.topSort()
dependencySort.each {
if (isEmpty || microModuleInfo.exportMicroModules.containsKey(it)) {
MicroModule microModule = microModuleInfo.getMicroModule(it)
if (microModule == null) {
throw new GradleException("MicroModule with path '${it}' could not be found in ${project.getDisplayName()}.")
}
if (microModule == microModuleInfo.mainMicroModule) {
hasExportMainMicroModule = true
}
if (microModule.appliedScript) return
addMicroModuleSourceSet(microModule)
applyMicroModuleScript(microModule)
microModule.appliedScript = true
}
}
if (!hasExportMainMicroModule) {
throw new GradleException("the main MicroModule '${microModuleInfo.mainMicroModule.name}' is not in the export list.")
}
} else {
applyScriptState = APPLY_NORMAL_MICRO_MODULE_SCRIPT
microModuleInfo.includeMicroModules.each {
MicroModule microModule = it.value
addMicroModuleSourceSet(microModule)
applyMicroModuleScript(microModule)
}
}
currentMicroModule = null
generateAndroidManifest()
project.tasks.preBuild.doFirst {
clearOriginSourceSet()
if (startTaskState == ASSEMBLE_OR_GENERATE) {
microModuleInfo.includeMicroModules.each {
MicroModule microModule = it.value
if (microModule.appliedScript) {
addMicroModuleSourceSet(microModule)
}
}
} else {
microModuleInfo.includeMicroModules.each {
addMicroModuleSourceSet(it.value)
}
}
generateAndroidManifest()
}
}
}
def generateAndroidManifest() {
if ((startTaskState == ASSEMBLE_OR_GENERATE || !microModuleInfo.exportMicroModules.isEmpty()) && isMainSourceSetEmpty()) {
setMainSourceSetManifest()
return
}
mergeAndroidManifest('main')
productFlavorInfo.buildTypes.each {
mergeAndroidManifest(it)
}
if (!productFlavorInfo.singleDimension) {
productFlavorInfo.productFlavors.each {
mergeAndroidManifest(it)
}
}
productFlavorInfo.combinedProductFlavors.each {
mergeAndroidManifest(it)
def productFlavor = it
productFlavorInfo.buildTypes.each {
mergeAndroidManifest(productFlavor + PinUtils.upperCase(it))
}
}
def androidTest = 'androidTest'
mergeAndroidManifest(androidTest)
mergeAndroidManifest(androidTest + 'Debug')
if (!productFlavorInfo.singleDimension) {
productFlavorInfo.productFlavors.each {
mergeAndroidManifest(androidTest + PinUtils.upperCase(it))
}
}
productFlavorInfo.combinedProductFlavors.each {
mergeAndroidManifest(androidTest + PinUtils.upperCase(it))
mergeAndroidManifest(androidTest + PinUtils.upperCase(it) + 'Debug')
}
}
def mergeAndroidManifest(String variantName) {
File mainManifestFile = new File(microModuleInfo.mainMicroModule.microModuleDir, "/src/${variantName}/AndroidManifest.xml")
if (!mainManifestFile.exists()) return
ManifestMerger2.MergeType mergeType = ManifestMerger2.MergeType.APPLICATION
XmlDocument.Type documentType = XmlDocument.Type.MAIN
def logger = new ILogger() {
@Override
void error(Throwable t, String msgFormat, Object... args) {
println(msgFormat)
}
@Override
void warning(String msgFormat, Object... args) {
}
@Override
void info(String msgFormat, Object... args) {
}
@Override
void verbose(String msgFormat, Object... args) {
}
}
ManifestMerger2.Invoker invoker = new ManifestMerger2.Invoker(mainManifestFile, logger, mergeType, documentType)
invoker.withFeatures(ManifestMerger2.Invoker.Feature.NO_PLACEHOLDER_REPLACEMENT)
microModuleInfo.includeMicroModules.each {
MicroModule microModule = it.value
if (startTaskState == ASSEMBLE_OR_GENERATE && !microModule.appliedScript) return
if (microModule.name == microModuleInfo.mainMicroModule.name) return
def microManifestFile = new File(microModule.microModuleDir, "/src/${variantName}/AndroidManifest.xml")
if (microManifestFile.exists()) {
invoker.addLibraryManifest(microManifestFile)
}
}
def mergingReport = invoker.merge()
if (!mergingReport.result.success) {
mergingReport.log(logger)
throw new GradleException(mergingReport.reportString)
}
def moduleAndroidManifest = mergingReport.getMergedDocument(MergingReport.MergedManifestKind.MERGED)
moduleAndroidManifest = new String(moduleAndroidManifest.getBytes('UTF-8'))
def saveDir = new File(project.projectDir, "build/microModule/merge-manifest/${variantName}")
saveDir.mkdirs()
def AndroidManifestFile = new File(saveDir, 'AndroidManifest.xml')
AndroidManifestFile.createNewFile()
AndroidManifestFile.write(moduleAndroidManifest)
def extensionContainer = project.getExtensions()
BaseExtension android = extensionContainer.getByName('android')
def obj = android.sourceSets.findByName(variantName)
if (obj == null) {
return
}
obj.manifest.srcFile project.projectDir.absolutePath + "/build/microModule/merge-manifest/${variantName}/AndroidManifest.xml"
}
def addMicroModuleSourceSet(MicroModule microModule) {
addVariantSourceSet(microModule, 'main')
productFlavorInfo.buildTypes.each {
addVariantSourceSet(microModule, it)
}
if (!productFlavorInfo.singleDimension) {
productFlavorInfo.productFlavors.each {
addVariantSourceSet(microModule, it)
}
}
productFlavorInfo.combinedProductFlavors.each {
addVariantSourceSet(microModule, it)
def flavorName = it
productFlavorInfo.buildTypes.each {
addVariantSourceSet(microModule, flavorName + PinUtils.upperCase(it))
}
}
def testTypes = ['androidTest', 'test']
testTypes.each {
def testType = it
addVariantSourceSet(microModule, testType)
if (testType == 'test') {
productFlavorInfo.buildTypes.each {
addVariantSourceSet(microModule, testType + PinUtils.upperCase(it))
}
} else {
addVariantSourceSet(microModule, testType + 'Debug')
}
if (!productFlavorInfo.singleDimension) {
productFlavorInfo.productFlavors.each {
addVariantSourceSet(microModule, testType + PinUtils.upperCase(it))
}
}
productFlavorInfo.combinedProductFlavors.each {
def productFlavorName = testType + PinUtils.upperCase(it)
addVariantSourceSet(microModule, productFlavorName)
if (testType == 'test') {
productFlavorInfo.buildTypes.each {
addVariantSourceSet(microModule, productFlavorName + PinUtils.upperCase(it))
}
} else {
addVariantSourceSet(microModule, productFlavorName + 'Debug')
}
}
}
}
def clearOriginSourceSet() {
clearModuleSourceSet('main')
// buildTypes
productFlavorInfo.buildTypes.each {
clearModuleSourceSet(it)
}
if (!productFlavorInfo.singleDimension) {
productFlavorInfo.productFlavors.each {
clearModuleSourceSet(it)
}
}
productFlavorInfo.combinedProductFlavors.each {
clearModuleSourceSet(it)
def flavorName = it
productFlavorInfo.buildTypes.each {
clearModuleSourceSet(flavorName + PinUtils.upperCase(it))
}
}
def testTypes = ['androidTest', 'test']
testTypes.each {
def testType = it
clearModuleSourceSet(testType)
if (testType == 'test') {
productFlavorInfo.buildTypes.each {
clearModuleSourceSet(testType + PinUtils.upperCase(it))
}
} else {
clearModuleSourceSet(testType + 'Debug')
}
if (!productFlavorInfo.singleDimension) {
productFlavorInfo.productFlavors.each {
clearModuleSourceSet(testType + PinUtils.upperCase(it))
}
}
productFlavorInfo.combinedProductFlavors.each {
def productFlavorName = testType + PinUtils.upperCase(it)
clearModuleSourceSet(productFlavorName)
if (testType == 'test') {
productFlavorInfo.buildTypes.each {
clearModuleSourceSet(productFlavorName + PinUtils.upperCase(it))
}
} else {
clearModuleSourceSet(productFlavorName + 'Debug')
}
}
}
}
def isMainSourceSetEmpty() {
BaseExtension android = project.extensions.getByName('android')
def obj = android.sourceSets.findByName('main')
if (obj == null) {
return true
}
return obj.java.srcDirs.size() == 0;
}
def setMainSourceSetManifest() {
BaseExtension android = project.extensions.getByName('android')
def obj = android.sourceSets.findByName('main')
if (obj == null) {
obj = android.sourceSets.create('main')
}
File mainManifestFile = new File(microModuleInfo.mainMicroModule.microModuleDir, '/src/main/AndroidManifest.xml')
obj.manifest.srcFile mainManifestFile
}
def addVariantSourceSet(MicroModule microModule, def type) {
def absolutePath = microModule.microModuleDir.absolutePath
BaseExtension android = project.extensions.getByName('android')
def obj = android.sourceSets.findByName(type)
if (obj == null) {
obj = android.sourceSets.create(type)
}
obj.java.srcDir(absolutePath + "/src/${type}/java")
obj.java.srcDir(absolutePath + "/src/${type}/kotlin")
obj.res.srcDir(absolutePath + "/src/${type}/res")
obj.jni.srcDir(absolutePath + "/src/${type}/jni")
obj.jniLibs.srcDir(absolutePath + "/src/${type}/jniLibs")
obj.aidl.srcDir(absolutePath + "/src/${type}/aidl")
obj.assets.srcDir(absolutePath + "/src/${type}/assets")
obj.shaders.srcDir(absolutePath + "/src/${type}/shaders")
obj.resources.srcDir(absolutePath + "/src/${type}/resources")
obj.renderscript.srcDir(absolutePath + "/src/${type}/rs")
}
def clearModuleSourceSet(def type) {
def srcDirs = []
BaseExtension android = project.extensions.getByName('android')
def obj = android.sourceSets.findByName(type)
if (obj == null) {
return
}
obj.java.srcDirs = srcDirs
obj.res.srcDirs = srcDirs
obj.jni.srcDirs = srcDirs
obj.jniLibs.srcDirs = srcDirs
obj.aidl.srcDirs = srcDirs
obj.assets.srcDirs = srcDirs
obj.shaders.srcDirs = srcDirs
obj.resources.srcDirs = srcDirs
obj.renderscript.srcDirs = srcDirs
}
void applyMicroModuleScript(MicroModule microModule) {
def microModuleBuild = new File(microModule.microModuleDir, 'build.gradle')
if (microModuleBuild.exists()) {
MicroModule tempMicroModule = currentMicroModule
currentMicroModule = microModule
project.apply from: microModuleBuild.absolutePath
currentMicroModule = tempMicroModule
}
}
def checkMicroModuleBoundary(String taskPrefix, String buildType, String flavorName, List<String> sourceFolders) {
CodeChecker codeChecker
def buildTypeFirstUp = PinUtils.upperCase(buildType)
def productFlavorFirstUp = flavorName != null ? PinUtils.upperCase(flavorName) : ""
def mergeResourcesTaskName = taskPrefix + productFlavorFirstUp + buildTypeFirstUp + 'Resources'
def packageResourcesTask = project.tasks.findByName(mergeResourcesTaskName)
if (packageResourcesTask != null) {
codeChecker = new CodeChecker(project, microModuleInfo, productFlavorInfo, buildType, flavorName)
packageResourcesTask.doLast {
codeChecker.checkResources(mergeResourcesTaskName, sourceFolders)
}
}
def compileJavaTaskName = "compile${productFlavorFirstUp}${buildTypeFirstUp}JavaWithJavac"
def compileJavaTask = project.tasks.findByName(compileJavaTaskName)
if (compileJavaTask != null) {
compileJavaTask.doLast {
if (codeChecker == null) {
codeChecker = new CodeChecker(project, microModuleInfo, productFlavorInfo, buildType, flavorName)
}
codeChecker.checkClasses(mergeResourcesTaskName, sourceFolders)
}
}
}
}

View File

@@ -1,7 +1,26 @@
package com.plugin.component.plugin
import com.android.build.gradle.AppExtension
import com.android.build.gradle.AppPlugin
import com.android.build.gradle.LibraryExtension
import com.android.build.gradle.LibraryPlugin
import com.android.build.gradle.TestedExtension
import com.android.build.gradle.api.BaseVariant
import com.android.builder.model.ProductFlavor
import com.plugin.component.Runtimes
import com.plugin.component.extension.ComponentExtension
import com.plugin.component.extension.module.PinInfo
import com.plugin.component.extension.option.pin.PinConfiguration
import com.plugin.component.utils.PinUtils
import org.gradle.BuildListener
import org.gradle.BuildResult
import org.gradle.api.Action
import org.gradle.api.DomainObjectSet
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.initialization.Settings
import org.gradle.api.invocation.Gradle
class PinPlugin implements BasePlugin {
@@ -12,19 +31,211 @@ class PinPlugin implements BasePlugin {
private final static String APPLY_INCLUDE_MICRO_MODULE_SCRIPT = 'apply_include_micro_module_script'
private final static String APPLY_EXPORT_MICRO_MODULE_SCRIPT = 'apply_export_micro_module_script'
String startTaskState = NORMAL
String applyScriptState
boolean appliedLibraryPlugin
ComponentExtension componentExtension
PinConfiguration pinConfiguration
PinInfo currentPin
Project project
@Override
void initExtension(ComponentExtension componentExtension) {
this.componentExtension = componentExtension
}
boolean isSupportPins() {
return pinConfiguration != null
}
@Override
void evaluateChild(Project child) {
this.project = child
pinConfiguration = Runtimes.getPinConfiguration(child.name)
if (!isSupportPins()) {
return
}
//是否是sync
if (project.gradle.getStartParameter().taskNames.size() == 0) {
startTaskState = NORMAL
} else {
startTaskState = ASSEMBLE_OR_GENERATE
}
//非sync情况下在configuration添加的时候
if (startTaskState != NORMAL) {
project.getConfigurations().whenObjectAdded {
Configuration configuration = it
configuration.dependencies.whenObjectAdded {
if (applyScriptState == APPLY_INCLUDE_MICRO_MODULE_SCRIPT) {
configuration.dependencies.remove(it)
return
} else if (applyScriptState == APPLY_NORMAL_MICRO_MODULE_SCRIPT
|| applyScriptState == APPLY_EXPORT_MICRO_MODULE_SCRIPT) {
return
} else if (currentPin == null && startTaskState == ASSEMBLE_OR_GENERATE) {
return
} else if (it.group != null && it.group.startsWith('com.android.tools')) {
return
}
configuration.dependencies.remove(it)
}
}
}
project.dependencies.metaClass.microModule { String path ->
if (currentPin == null || applyScriptState == APPLY_NORMAL_MICRO_MODULE_SCRIPT) {
return []
}
if (applyScriptState == APPLY_INCLUDE_MICRO_MODULE_SCRIPT) {
pinConfiguration.setPinDependency(currentPin.name, path)
return []
}
PinInfo microModule = pinConfiguration.getIncludePin(path)
def result = []
if (startTaskState == ASSEMBLE_OR_GENERATE) {
PinUtils.addMicroModuleSourceSet(project, microModule, pinConfiguration.productFlavorInfo)
PinUtils.applyMicroModuleScript(child, microModule, currentPin)
microModule.appliedScript = true
}
return result
}
project.plugins.all {
Class extensionClass
if (it instanceof AppPlugin) {
extensionClass = AppExtension
} else if (it instanceof LibraryPlugin) {
extensionClass = LibraryExtension
} else {
return
}
project.extensions.configure(extensionClass, new Action<? extends TestedExtension>() {
@Override
void execute(TestedExtension testedExtension) {
boolean isLibrary
DomainObjectSet<BaseVariant> baseVariants
if (testedExtension instanceof AppExtension) {
AppExtension appExtension = (AppExtension) testedExtension
baseVariants = appExtension.applicationVariants
} else {
LibraryExtension libraryExtension = (LibraryExtension) testedExtension
baseVariants = libraryExtension.libraryVariants
isLibrary = true
}
baseVariants.all { BaseVariant variant ->
if (pinConfiguration.codeCheckEnabled) {
def taskNamePrefix = isLibrary ? 'package' : 'merge'
List<String> sourceFolders = new ArrayList<>()
sourceFolders.add('main')
sourceFolders.add(variant.buildType.name)
if (variant.productFlavors.size() > 0) {
sourceFolders.add(variant.name)
sourceFolders.add(variant.flavorName)
for (ProductFlavor productFlavor : variant.productFlavors) {
sourceFolders.add(productFlavor.name)
}
PinUtils.checkMicroModuleBoundary(child, pinConfiguration, taskNamePrefix, variant.buildType.name, variant.flavorName, sourceFolders)
} else {
PinUtils.checkMicroModuleBoundary(child, pinConfiguration, taskNamePrefix, variant.buildType.name, null, sourceFolders)
}
}
}
}
})
}
}
@Override
void afterEvaluateChild(Project child) {
if (!isSupportPins()) {
return
}
pinConfiguration.initProductFlavor()
if (pinConfiguration.mainPin == null) {
throw new GradleException("the main PinInfo could not be found in ${project.getDisplayName()}.")
}
appliedLibraryPlugin = project.pluginManager.hasPlugin('com.android.library')
applyScriptState = APPLY_INCLUDE_MICRO_MODULE_SCRIPT
pinConfiguration.includePins.each {
PinInfo microModule = it.value
pinConfiguration.dependencyGraph.add(microModule.name)
applyMicroModuleScript(child, microModule)
}
//清除所有 srouceSet 信息
PinUtils.clearOriginSourceSet(child, pinConfiguration.productFlavorInfo)
//如果非 sync
if (startTaskState == ASSEMBLE_OR_GENERATE) {
//读取所有 export 配置
applyScriptState = APPLY_EXPORT_MICRO_MODULE_SCRIPT
boolean hasExportMainMicroModule = false
boolean isEmpty = pinConfiguration.exportPins.isEmpty()
List<String> dependencySort = pinConfiguration.dependencyGraph.topSort()
dependencySort.each {
if (isEmpty || pinConfiguration.exportPins.containsKey(it)) {
PinInfo microModule = pinConfiguration.getIncludePin(it)
if (microModule == null) {
throw new GradleException("PinInfo with path '${it}' could not be found in ${project.getDisplayName()}.")
}
if (microModule == pinConfiguration.mainPin) {
hasExportMainMicroModule = true
}
if (microModule.appliedScript) return
PinUtils.addMicroModuleSourceSet(project, microModule, pinConfiguration.productFlavorInfo)
applyMicroModuleScript(child, microModule)
microModule.appliedScript = true
}
}
if (!hasExportMainMicroModule) {
throw new GradleException("the main PinInfo '${pinConfiguration.mainPin.name}' is not in the export list.")
}
} else {
applyScriptState = APPLY_NORMAL_MICRO_MODULE_SCRIPT
pinConfiguration.includePins.each {
PinInfo microModule = it.value
PinUtils.addMicroModuleSourceSet(project, microModule, pinConfiguration.productFlavorInfo)
applyMicroModuleScript(child, microModule)
}
}
currentPin = null
PinUtils.generateAndroidManifest(project, pinConfiguration, startTaskState)
project.tasks.preBuild.doFirst {
clearOriginSourceSet()
if (startTaskState == ASSEMBLE_OR_GENERATE) {
pinConfiguration.includePins.each {
PinInfo pin = it.value
if (pin.appliedScript) {
PinUtils.addMicroModuleSourceSet(project, pin, pinConfiguration.productFlavorInfo)
}
}
} else {
pinConfiguration.includePins.each {
PinUtils.addMicroModuleSourceSet(project, it.value, pinConfiguration.productFlavorInfo)
}
}
PinUtils.generateAndroidManifest(project, pinConfiguration, startTaskState)
}
}
@Override
@@ -34,11 +245,68 @@ class PinPlugin implements BasePlugin {
@Override
void afterEvaluateRoot(Project root) {
if (Runtimes.hasPinModule()) {
root.gradle.addBuildListener(new BuildListener() {
@Override
void buildStarted(Gradle gradle) {
}
@Override
void settingsEvaluated(Settings settings) {
}
@Override
void projectsLoaded(Gradle gradle) {
}
@Override
void projectsEvaluated(Gradle gradle) {
}
@Override
void buildFinished(BuildResult buildResult) {
// generate microModules.xml for PinInfo IDEA plugin.
def ideaFile = new File(buildResult.gradle.rootProject.rootDir, '.idea')
if (!ideaFile.exists()) return
def pininfos = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<modules>\n'
Map<String, PinConfiguration> allPins = Runtimes.getPinConfigurations()
Set<String> moduleNames = allPins.keySet()
for (String moduleName : moduleNames) {
PinConfiguration pins = allPins.get(moduleName)
def displayName = pins.name
pininfos += ' <module name=\"' + displayName.substring(displayName.indexOf("'") + 1, displayName.lastIndexOf("'")) + '\" path=\"' + it.projectDir.getCanonicalPath() + '\">\n'
pins.includePins.each {
PinInfo microModule = it.value
pininfos += ' <microModule name=\"' + microModule.name + '\" path=\"' + microModule.pinDir.getCanonicalPath() + '\" />\n'
}
pininfos += ' </module>\n'
}
pininfos += '</modules>'
def pins = new File(ideaFile, 'microModules.xml')
pins.write(pininfos, 'utf-8')
}
})
}
}
@Override
void afterAllEvaluate() {
}
void applyMicroModuleScript(Project project, PinInfo microModule) {
def pinBuild = new File(microModule.pinDir, 'build.gradle')
if (pinBuild.exists()) {
PinInfo tempMicroModule = currentPin
currentPin = microModule
project.apply from: pinBuild.absolutePath
currentPin = tempMicroModule
}
}
}

View File

@@ -151,8 +151,6 @@ class SdkPlugin implements BasePlugin{
@Override
void afterEvaluateRoot(Project root) {
Runtimes.initRuntimeConfiguration(root, mComponentExtension)
Logger.buildOutput("")
Logger.buildOutput("=====> 处理 sdk/impl jar <=====")
List<String> topSort = PublicationManager.getInstance().dependencyGraph.topSort()

View File

@@ -1,6 +1,16 @@
package com.plugin.component.utils
import com.plugin.component.extension.module.MicroModule
import com.android.build.gradle.BaseExtension
import com.android.manifmerger.ManifestMerger2
import com.android.manifmerger.MergingReport
import com.android.manifmerger.XmlDocument
import com.android.utils.ILogger
import com.plugin.component.check.CodeChecker
import com.plugin.component.extension.module.PinInfo
import com.plugin.component.extension.module.ProductFlavorInfo
import com.plugin.component.extension.option.pin.PinConfiguration
import com.plugin.component.plugin.PinPlugin
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.w3c.dom.Element
@@ -16,6 +26,303 @@ class PinUtils {
return String.valueOf(ch)
}
static void generateAndroidManifest(Project project, PinConfiguration pinConfiguration, String startTaskState) {
if ((startTaskState == PinPlugin.ASSEMBLE_OR_GENERATE || !pinConfiguration.exportPins.isEmpty()) && isMainSourceSetEmpty(project)) {
setMainSourceSetManifest()
return
}
mergeAndroidManifest(project, 'main', pinConfiguration, startTaskState)
pinConfiguration.productFlavorInfo.buildTypes.each {
mergeAndroidManifest(project, it, pinConfiguration, startTaskState)
}
if (!pinConfiguration.productFlavorInfo.singleDimension) {
pinConfiguration.productFlavorInfo.productFlavors.each {
mergeAndroidManifest(project, it, pinConfiguration, startTaskState)
}
}
pinConfiguration.productFlavorInfo.combinedProductFlavors.each {
mergeAndroidManifest(project, it, pinConfiguration, startTaskState)
def productFlavor = it
pinConfiguration.productFlavorInfo.buildTypes.each {
mergeAndroidManifest(project, productFlavor + upperCase(it), pinConfiguration, startTaskState)
}
}
def androidTest = 'androidTest'
mergeAndroidManifest(project, androidTest, pinConfiguration, startTaskState)
mergeAndroidManifest(project, androidTest + 'Debug', pinConfiguration, startTaskState)
if (!pinConfiguration.productFlavorInfo.singleDimension) {
pinConfiguration.productFlavorInfo.productFlavors.each {
mergeAndroidManifest(project, androidTest + upperCase(it), pinConfiguration, startTaskState)
}
}
pinConfiguration.productFlavorInfo.combinedProductFlavors.each {
mergeAndroidManifest(androidTest + Utils.upperCase(it), pinConfiguration, startTaskState)
mergeAndroidManifest(androidTest + Utils.upperCase(it) + 'Debug', pinConfiguration, startTaskState)
}
}
static void mergeAndroidManifest(Project project, String variantName, PinConfiguration pinConfiguration, String startTaskState) {
File mainManifestFile = new File(pinConfiguration.mainPin.pinDir, "/src/${variantName}/AndroidManifest.xml")
if (!mainManifestFile.exists()) return
ManifestMerger2.MergeType mergeType = ManifestMerger2.MergeType.APPLICATION
XmlDocument.Type documentType = XmlDocument.Type.MAIN
def logger = new ILogger() {
@Override
void error(Throwable t, String msgFormat, Object... args) {
println(msgFormat)
}
@Override
void warning(String msgFormat, Object... args) {
}
@Override
void info(String msgFormat, Object... args) {
}
@Override
void verbose(String msgFormat, Object... args) {
}
}
ManifestMerger2.Invoker invoker = new ManifestMerger2.Invoker(mainManifestFile, logger, mergeType, documentType)
invoker.withFeatures(ManifestMerger2.Invoker.Feature.NO_PLACEHOLDER_REPLACEMENT)
pinConfiguration.includePins.each {
PinInfo pin = it.value
if (startTaskState == PinPlugin.ASSEMBLE_OR_GENERATE && !pin.appliedScript) return
if (pin.name == pinConfiguration.mainPin.name) return
def pinManifestFile = new File(pin.pinDir, "/src/${variantName}/AndroidManifest.xml")
if (pinManifestFile.exists()) {
invoker.addLibraryManifest(pinManifestFile)
}
}
def mergingReport = invoker.merge()
if (!mergingReport.result.success) {
mergingReport.log(logger)
throw new GradleException(mergingReport.reportString)
}
def moduleAndroidManifest = mergingReport.getMergedDocument(MergingReport.MergedManifestKind.MERGED)
moduleAndroidManifest = new String(moduleAndroidManifest.getBytes('UTF-8'))
def saveDir = new File(project.projectDir, "build/pin/merge-manifest/${variantName}")
saveDir.mkdirs()
def AndroidManifestFile = new File(saveDir, 'AndroidManifest.xml')
AndroidManifestFile.createNewFile()
AndroidManifestFile.write(moduleAndroidManifest)
def extensionContainer = project.getExtensions()
BaseExtension android = extensionContainer.getByName('android')
def obj = android.sourceSets.findByName(variantName)
if (obj == null) {
return
}
obj.manifest.srcFile project.projectDir.absolutePath + "/build/pin/merge-manifest/${variantName}/AndroidManifest.xml"
}
static void addMicroModuleSourceSet(Project project, PinInfo pin, ProductFlavorInfo productFlavorInfo) {
addVariantSourceSet(project, pin, 'main')
productFlavorInfo.buildTypes.each {
addVariantSourceSet(project, pin, it)
}
if (!productFlavorInfo.singleDimension) {
productFlavorInfo.productFlavors.each {
addVariantSourceSet(project, pin, it)
}
}
productFlavorInfo.combinedProductFlavors.each {
addVariantSourceSet(project, pin, it)
def flavorName = it
productFlavorInfo.buildTypes.each {
addVariantSourceSet(project, pin, flavorName + upperCase(it))
}
}
def testTypes = ['androidTest', 'test']
testTypes.each {
def testType = it
addVariantSourceSet(project, pin, testType)
if (testType == 'test') {
productFlavorInfo.buildTypes.each {
addVariantSourceSet(project, pin, testType + upperCase(it))
}
} else {
addVariantSourceSet(project, pin, testType + 'Debug')
}
if (!productFlavorInfo.singleDimension) {
productFlavorInfo.productFlavors.each {
addVariantSourceSet(project, pin, testType + upperCase(it))
}
}
productFlavorInfo.combinedProductFlavors.each {
def productFlavorName = testType + upperCase(it)
addVariantSourceSet(project, pin, productFlavorName)
if (testType == 'test') {
productFlavorInfo.buildTypes.each {
addVariantSourceSet(project, pin, productFlavorName + upperCase(it))
}
} else {
addVariantSourceSet(project, pin, productFlavorName + 'Debug')
}
}
}
}
static void clearOriginSourceSet(Project project, ProductFlavorInfo productFlavorInfo) {
clearModuleSourceSet(project, 'main')
// buildTypes
productFlavorInfo.buildTypes.each {
clearModuleSourceSet(project, it)
}
if (!productFlavorInfo.singleDimension) {
productFlavorInfo.productFlavors.each {
clearModuleSourceSet(project, it)
}
}
productFlavorInfo.combinedProductFlavors.each {
clearModuleSourceSet(project, it)
def flavorName = it
productFlavorInfo.buildTypes.each {
clearModuleSourceSet(project, flavorName + upperCase(it))
}
}
def testTypes = ['androidTest', 'test']
testTypes.each {
def testType = it
clearModuleSourceSet(project, testType)
if (testType == 'test') {
productFlavorInfo.buildTypes.each {
clearModuleSourceSet(project, testType + upperCase(it))
}
} else {
clearModuleSourceSet(project, testType + 'Debug')
}
if (!productFlavorInfo.singleDimension) {
productFlavorInfo.productFlavors.each {
clearModuleSourceSet(project, testType + upperCase(it))
}
}
productFlavorInfo.combinedProductFlavors.each {
def productFlavorName = testType + upperCase(it)
clearModuleSourceSet(project, productFlavorName)
if (testType == 'test') {
productFlavorInfo.buildTypes.each {
clearModuleSourceSet(project, productFlavorName + upperCase(it))
}
} else {
clearModuleSourceSet(project, productFlavorName + 'Debug')
}
}
}
}
static boolean isMainSourceSetEmpty(Project project) {
BaseExtension android = project.extensions.getByName('android')
def obj = android.sourceSets.findByName('main')
if (obj == null) {
return true
}
return obj.java.srcDirs.size() == 0
}
static setMainSourceSetManifest(Project project, String dir) {
BaseExtension android = project.extensions.getByName('android')
def obj = android.sourceSets.findByName('main')
if (obj == null) {
obj = android.sourceSets.create('main')
}
File mainManifestFile = new File(dir, '/src/main/AndroidManifest.xml')
obj.manifest.srcFile mainManifestFile
}
static clearModuleSourceSet(Project project, def type) {
def srcDirs = []
BaseExtension android = project.extensions.getByName('android')
def obj = android.sourceSets.findByName(type)
if (obj == null) {
return
}
obj.java.srcDirs = srcDirs
obj.res.srcDirs = srcDirs
obj.jni.srcDirs = srcDirs
obj.jniLibs.srcDirs = srcDirs
obj.aidl.srcDirs = srcDirs
obj.assets.srcDirs = srcDirs
obj.shaders.srcDirs = srcDirs
obj.resources.srcDirs = srcDirs
obj.renderscript.srcDirs = srcDirs
}
static addVariantSourceSet(Project project, PinInfo microModule, def type) {
def absolutePath = microModule.pinDir.absolutePath
BaseExtension android = project.extensions.getByName('android')
def obj = android.sourceSets.findByName(type)
if (obj == null) {
obj = android.sourceSets.create(type)
}
obj.java.srcDir(absolutePath + "/src/${type}/java")
obj.java.srcDir(absolutePath + "/src/${type}/kotlin")
obj.res.srcDir(absolutePath + "/src/${type}/res")
obj.jni.srcDir(absolutePath + "/src/${type}/jni")
obj.jniLibs.srcDir(absolutePath + "/src/${type}/jniLibs")
obj.aidl.srcDir(absolutePath + "/src/${type}/aidl")
obj.assets.srcDir(absolutePath + "/src/${type}/assets")
obj.shaders.srcDir(absolutePath + "/src/${type}/shaders")
obj.resources.srcDir(absolutePath + "/src/${type}/resources")
obj.renderscript.srcDir(absolutePath + "/src/${type}/rs")
}
static void checkMicroModuleBoundary(Project project, PinConfiguration pinConfiguration, String taskPrefix, String buildType, String flavorName, List<String> sourceFolders) {
CodeChecker codeChecker
def buildTypeFirstUp = upperCase(buildType)
def productFlavorFirstUp = flavorName != null ? upperCase(flavorName) : ""
def mergeResourcesTaskName = taskPrefix + productFlavorFirstUp + buildTypeFirstUp + 'Resources'
def packageResourcesTask = project.tasks.findByName(mergeResourcesTaskName)
if (packageResourcesTask != null) {
codeChecker = new CodeChecker(project, pinConfiguration, pinConfiguration.productFlavorInfo, buildType, flavorName)
packageResourcesTask.doLast {
codeChecker.checkResources(mergeResourcesTaskName, sourceFolders)
}
}
def compileJavaTaskName = "compile${productFlavorFirstUp}${buildTypeFirstUp}JavaWithJavac"
def compileJavaTask = project.tasks.findByName(compileJavaTaskName)
if (compileJavaTask != null) {
compileJavaTask.doLast {
if (codeChecker == null) {
codeChecker = new CodeChecker(project, pinConfiguration, pinConfiguration.productFlavorInfo, buildType, flavorName)
}
codeChecker.checkClasses(mergeResourcesTaskName, sourceFolders)
}
}
}
static String getAndroidManifestPackageName(File androidManifest) {
def builderFactory = DocumentBuilderFactory.newInstance()
builderFactory.setNamespaceAware(true)
@@ -23,7 +330,13 @@ class PinUtils {
return manifestXml.getAttribute("package")
}
static MicroModule buildMicroModule(Project project, String microModulePath) {
/**
* 构建pin工程
* @param project
* @param microModulePath
* @return
*/
static PinInfo buildPin(Project project, String microModulePath) {
String[] pathElements = removeTrailingColon(microModulePath).split(":")
int pathElementsLen = pathElements.size()
File parentMicroModuleDir = project.projectDir
@@ -40,12 +353,17 @@ class PinUtils {
if (!microModuleDir.exists()) {
return null
}
MicroModule microModule = new MicroModule()
PinInfo microModule = new PinInfo()
microModule.name = microModuleName
microModule.microModuleDir = microModuleDir
microModule.pinDir = microModuleDir
return microModule
}
/**
* 删除冒号
* @param microModulePath
* @return
*/
private static String removeTrailingColon(String microModulePath) {
return microModulePath.startsWith(":") ? microModulePath.substring(1) : microModulePath
}

View File

@@ -1,71 +0,0 @@
package com.plugin.component.utils
import java.util.regex.Pattern
class Utils {
//
// /**
// * 自动添加依赖只在运行assemble任务的才会添加依赖因此在开发期间组件之间是完全感知不到的这是做到完全隔离的关键
// * 支持两种语法module或者groupId:artifactId:version(@aar),前者之间引用module工程后者使用maven中已经发布的aar
// *
// * @param assembleTask
// * @param project
// */
// static void compileComponents(@Nonnull Project project, @Nonnull AssembleTask assembleTask) {
// String components;
// if (assembleTask.isDebug) {
// components = (String) project.getProperties().get("debugCompileComponent");
// } else {
// components = (String) project.getProperties().get("releaseCompileComponent");
// }
//
// if (components == null || components.length() == 0) {
// System.out.println("there is no add dependencies ");
// return;
// }
//
// String[] compileComponents = components.split(",");
// if (compileComponents == null || compileComponents.length == 0) {
// System.out.println("there is no add dependencies ");
// return;
// }
// for (String str : compileComponents) {
// System.out.println("comp is " + str);
// str = str.trim();
// if (str.startsWith(":")) {
// str = str.substring(1);
// }
// if (isMavenArtifact(str)) {
// /**
// * 示例语法:groupId:artifactId:version(@aar)
// * compileComponent=com.luojilab.reader:readercomponent:1.0.0
// * 注意前提是已经将组件aar文件发布到maven上并配置了相应的repositories
// */
// project.getDependencies().add("implementation", str);
// System.out.println("add dependencies lib : " + str);
// } else {
// /**
// * 示例语法:module
// * compileComponent=readercomponent,sharecomponent
// */
// project.getDependencies().add("implementation", project.project(':' + str));
// System.out.println("add dependencies project : " + str);
// }
// }
// }
/**
* 是否是maven 坐标
*
* @return
*/
private static boolean isMavenArtifact(String str) {
if (str == null || str.isEmpty()) {
return false;
}
return Pattern.matches("\\S+(\\.\\S+)+:\\S+(:\\S+)?(@\\S+)?", str);
}
}

View File

@@ -9,7 +9,7 @@ component {
exclude 'libraryWithoutPlugin','component-core'
// 上述 语句等价于下面语句
include ':app','library','libraryKotlin','debugModule'
// include ':app','library','libraryKotlin','debugModule'
sdk {
@@ -46,14 +46,12 @@ component {
}
pin {
configuration {
'library' {
'pins' {
codeCheckEnabled true
include ':p_base'
include ':p_common'
include ':p_home'
export ':main', ':p_home'
export ':p_common'
}

1
pins/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

36
pins/build.gradle Normal file
View File

@@ -0,0 +1,36 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
defaultConfig {
minSdkVersion 15
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles 'consumer-rules.pro'
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

0
pins/consumer-rules.pro Normal file
View File

3
pins/main/build.gradle Normal file
View File

@@ -0,0 +1,3 @@
dependencies {
implementation fileTree(dir: 'main/libs', include: ['*.jar'])
}

View File

@@ -0,0 +1,24 @@
package com.plugin.pin
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.plugin.pin.test", appContext.packageName)
}
}

View File

@@ -0,0 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.plugin.pin" />

View File

@@ -0,0 +1,3 @@
<resources>
<string name="app_name">Pins</string>
</resources>

View File

@@ -0,0 +1,17 @@
package com.plugin.pin
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}

3
pins/p_base/build.gradle Normal file
View File

@@ -0,0 +1,3 @@
dependencies {
}

View File

@@ -0,0 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.plugin.pin.base" />

View File

@@ -0,0 +1,2 @@
class Base {
}

View File

@@ -0,0 +1,3 @@
dependencies {
}

View File

@@ -0,0 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.plugin.pin.common" />

View File

@@ -0,0 +1,2 @@
class Common {
}

3
pins/p_home/build.gradle Normal file
View File

@@ -0,0 +1,3 @@
dependencies {
}

View File

@@ -0,0 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.plugin.pin.home" />

View File

@@ -0,0 +1,2 @@
class Home {
}

21
pins/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -7,6 +7,6 @@
//调试插件
includeBuild './component-plugin'
//
include ':app'
include ':app', ':pins'
include ':debugModule'
include ':library', ':libraryKotlin', ':libraryWithoutPlugin'