diff --git a/.gitignore b/.gitignore index 21b81e0..508886f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,3 @@ -<<<<<<< HEAD -*.iml -.gradle -/local.properties -/.idea/workspace.xml -/.idea/libraries -.DS_Store -/build -/captures -.externalNativeBuild -======= # Built application files *.apk *.ap_ @@ -31,37 +20,16 @@ build/ # Local configuration file (sdk path, etc) local.properties -# Proguard folder generated by Eclipse -proguard/ - -# Log Files -*.log - # Android Studio Navigation editor temp files .navigation/ +.externalNativeBuild # Android Studio captures folder captures/ # Intellij *.iml -.idea/workspace.xml -.idea/tasks.xml -.idea/gradle.xml -.idea/dictionaries -.idea/libraries +.idea/ # Keystore files *.jks - -# External native build folder generated in Android Studio 2.2 and later -.externalNativeBuild - -# Google Services (e.g. APIs or Firebase) -google-services.json - -# Freeline -freeline.py -freeline/ -freeline_project_description.json ->>>>>>> 853a1936d46ab75165caadb5be45deae38a12237 diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index 96cc43e..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml deleted file mode 100644 index e7bedf3..0000000 --- a/.idea/copyright/profiles_settings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index 3227a2e..0000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index cfec270..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 2d063cd..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index 7f68460..0000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/LICENSE b/LICENSE index a98354c..a57fd94 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ - Copyright EastWood Yang {name of copyright owner} + Copyright 2018 EastWood Yang Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index cffafef..df73c33 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,160 @@ # MicroModule -Restrict code boundary, divide module file structure into multi-parts +重新定义Android模块结构,在模块内部可以创建多个和模块结构一致的微模块(MicroModule)。每一个MicroModule的结构和Android模块结构保持一致,也会有自己的`build.gradle`。另外,你可以很方便的配置哪些MicroModule参与APK的编译。 -## More Detail -[http://www.jianshu.com/p/7f31cd67d513](http://www.jianshu.com/p/7f31cd67d513) -(Chinese/中文) +## Usage + +### 在根项目`build.gradle`中添加MicroModule插件依赖: + +``` +buildscript { + dependencies { + ... + classpath 'com.eastwood.tools.plugins:micro-module:1.4.0' + } +} +``` + +### 在`application`或`library`类型的模块`build.gradle`中添加MicroModule插件: + +``` +apply plugin: 'micro-module' +apply plugin: 'com.android.library' // or 'com.android.application' + +android {} + +microModule { + ... +} + +dependencies {} +``` + +注意:MicroModule插件需要添加在android相关插件之前,相关配置`microModule {}` 需要添加在 `android {}` 和 `dependencies {}`之间。 + +### microModule属性说明 + +* **`include`** + + 声明一个或多个MicroModule,类似于`setting.gradle`中的`include`,MicroModule目录名即为MicroModule的名称。 + + ``` + microModule { + include 'p_base', 'p_common' + + // 可以根据条件动态声明 + if(debug) { + include 'debug' + } else { + include 'debug' + } + } + ``` + +* **`export`** + + 配置参与APK编译的MicroModule。如果未配置`export`,则所有`include`的MicroModule都会参与APK编译。 + + ``` + microModule { + include 'feature_A', 'feature_B', 'feature_C' + + export 'feature_A', 'feature_B' + } + ``` + +* **`includeMain`** + + 指定主MicroModule。 + 当前模块的其他MicroModule的`AndroidManifest.xml`,将会合入主MicroModule的`AndroidManifest.xml`,并存放在`build/microModule/merge-manifest/`下。另外,当前模块的R类包名也将由主模块`AndroidManifest.xml`的`package`决定。 + + 默认主MicroModule为目录名为`main`的MicroModule。通过[MicroModule Android Studio插件](#jump)的转换功能,将模块转换成MicroModule格式时,无需指定主模块。转换功能工作只是创建一个`main`目录,并将原先`src`移动到`main`目录下,以及其他操作。 + + +* **`codeCheckEnabled`** + + 是否开启MicroModule代码边界检查,默认不开启检查。 + + 有些场景下可能想使MicroModule在模块中保持独立,其类或资源不被该模块的其他MicroModule引用。代码边界检查在`sync&build`的时候进行,检测到没有依赖而存在引用时,会报错以及停止`sync&build`,并输出相应日志提示。 + + 开启代码边界检查后,一个模块内的MicroModule之间,需要声明依赖关系。例如: + ``` + // Module build.gradle + + microModule { + codeCheckEnabled true + + include 'p_base', 'p_common' + include 'feature_A', 'feature_B' + + export 'feature_A' + } + + // MicroModule feature_A build.gradle + + dependencies { + implementation microModule(':p_base') + implementation microModule(':p_common') + } + + // MicroModule feature_B build.gradle + + dependencies { + implementation microModule(':p_base') + implementation microModule(':p_common') + } + ``` + + 另外MicroModule所需的依赖,也可以在各自的`build.gradle` `dependencies {}`中声明(此处的依赖不在代码边界检查范围之内)。 + + ``` + dependencies { + implementation fileTree(dir: 'main/libs', include: ['*.jar']) + implementation 'com.android.support:appcompat-v7:27.1.1' + implementation 'com.android.support.constraint:constraint-layout:1.1.0' + + implementation microModule(':p_base') + implementation microModule(':p_common') + } + ``` + +## MicroModule Android Studio Plugin +Provides an action which allow you quickly create MicroModule or convert module to MicroModule. +* Right click at module dir, in [New] group, you will find "MicroModule" action. +* Right click at module dir, in [Refactor] group, you will find "Convert to MicroModule" action. + + + + + +**Install Step**: +1. open [File] -> [Settings...] -> [plugins] -> [Browse repositories...] +2. and search name **MicroModule** + + + +**Plugin detail**: + +[https://plugins.jetbrains.com/plugin/10785-micromodule](https://plugins.jetbrains.com/plugin/10785-micromodule) + +## Question or Idea +有问题或想法可以直接加我微信: EastWoodYang + +## License + +``` + Copyright 2018 EastWood Yang + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +``` diff --git a/app/build.gradle b/app/build.gradle deleted file mode 100644 index 3cd00d5..0000000 --- a/app/build.gradle +++ /dev/null @@ -1,22 +0,0 @@ -apply plugin: 'com.android.application' - -//apply from: '../micro.gradle' -apply from: '../micro_reference.gradle' - -android { - compileSdkVersion 25 - buildToolsVersion "26.0.0" - defaultConfig { - applicationId "com.ycdyng.module.micro" - minSdkVersion 15 - targetSdkVersion 25 - versionCode 1 - versionName "1.0" - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } -} \ No newline at end of file diff --git a/app/main/micro.properties b/app/main/micro.properties deleted file mode 100644 index 57d3050..0000000 --- a/app/main/micro.properties +++ /dev/null @@ -1,13 +0,0 @@ -# java.srcDir= -# res.srcDir= -# manifest.srcFile= -# ... - -# use micro.gradle -# enable=false or true - -# use micro_reference.gradle -# android.library.reference.1=./*** - -# e.g. -android.library.reference.3=./p_home \ No newline at end of file diff --git a/app/main/src/main/java/com/ycdyng/module/micro/MainActivity.java b/app/main/src/main/java/com/ycdyng/module/micro/MainActivity.java deleted file mode 100644 index 3f17c41..0000000 --- a/app/main/src/main/java/com/ycdyng/module/micro/MainActivity.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.ycdyng.module.micro; - -import android.app.Activity; -import android.os.Bundle; - -public class MainActivity extends Activity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - } -} diff --git a/app/main/src/main/res/layout/activity_main.xml b/app/main/src/main/res/layout/activity_main.xml deleted file mode 100644 index d829e29..0000000 --- a/app/main/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/main/src/main/res/values/strings.xml b/app/main/src/main/res/values/strings.xml deleted file mode 100644 index 0d20034..0000000 --- a/app/main/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - Product - diff --git a/app/p_base/micro.properties b/app/p_base/micro.properties deleted file mode 100644 index a131e74..0000000 --- a/app/p_base/micro.properties +++ /dev/null @@ -1,10 +0,0 @@ -# java.srcDir= -# res.srcDir= -# manifest.srcFile= -# ... - -# use micro.gradle -# enable=false or true - -# use micro_reference.gradle -# android.library.reference.1=./*** \ No newline at end of file diff --git a/app/p_base/src/AndroidManifest.xml b/app/p_base/src/AndroidManifest.xml deleted file mode 100644 index e6b28e4..0000000 --- a/app/p_base/src/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/p_base/src/main/java/com.ycdyng.module.micro/a.java b/app/p_base/src/main/java/com.ycdyng.module.micro/a.java deleted file mode 100644 index e69de29..0000000 diff --git a/app/p_common/micro.properties b/app/p_common/micro.properties deleted file mode 100644 index 358d2d1..0000000 --- a/app/p_common/micro.properties +++ /dev/null @@ -1,13 +0,0 @@ -# java.srcDir= -# res.srcDir= -# manifest.srcFile= -# ... - -# use micro.gradle -# enable=false or true - -# use micro_reference.gradle -# android.library.reference.1=./*** - -# e.g. -android.library.reference.1=./p_base \ No newline at end of file diff --git a/app/p_common/src/main/java/com.ycdyng.module.micro/a.java b/app/p_common/src/main/java/com.ycdyng.module.micro/a.java deleted file mode 100644 index e69de29..0000000 diff --git a/app/p_home/micro.properties b/app/p_home/micro.properties deleted file mode 100644 index ef8d0e7..0000000 --- a/app/p_home/micro.properties +++ /dev/null @@ -1,13 +0,0 @@ -# java.srcDir= -# res.srcDir= -# manifest.srcFile= -# ... - -# use micro.gradle -# enable=false or true - -# use micro_reference.gradle -# android.library.reference.1=./*** - -# e.g. -android.library.reference.1=./p_common \ No newline at end of file diff --git a/app/p_home/src/AndroidManifest.xml b/app/p_home/src/AndroidManifest.xml deleted file mode 100644 index d110f47..0000000 --- a/app/p_home/src/AndroidManifest.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/p_home/src/main/java/com.ycdyng.module.micro/HomeActivity.java b/app/p_home/src/main/java/com.ycdyng.module.micro/HomeActivity.java deleted file mode 100644 index 57c3738..0000000 --- a/app/p_home/src/main/java/com.ycdyng.module.micro/HomeActivity.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.ycdyng.module.micro; - -import android.app.Activity; - -public class HomeActivity extends Activity { -} diff --git a/app/.gitignore b/application/.gitignore similarity index 100% rename from app/.gitignore rename to application/.gitignore diff --git a/application/build.gradle b/application/build.gradle new file mode 100644 index 0000000..9bc2388 --- /dev/null +++ b/application/build.gradle @@ -0,0 +1,82 @@ +apply plugin: 'micro-module' +apply plugin: 'com.android.application' + +android { + compileSdkVersion 27 + + defaultConfig { + applicationId "com.eastwood.demo" + minSdkVersion 14 + targetSdkVersion 27 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + + } + +// flavorDimensions "api", "mode" +// +// productFlavors { +// +// minApi24 { +// dimension "api" +// minSdkVersion 24 +// versionCode 30000 + android.defaultConfig.versionCode +// versionNameSuffix "-minApi24" +// } +// +// minApi23 { +// dimension "api" +// minSdkVersion 23 +// versionCode 20000 + android.defaultConfig.versionCode +// versionNameSuffix "-minApi23" +// } +// +// minApi21 { +// dimension "api" +// minSdkVersion 21 +// versionCode 10001 + android.defaultConfig.versionCode +// versionNameSuffix "-minApi21" +// } +// +// demo { +// dimension "mode" +// } +// +// full { +// dimension "mode" +// } +// } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + +} + +microModule { + codeCheckEnabled true + + include ':p_base' + include ':p_common' + include ':p_home' + + export ':main', ':p_home' +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + + implementation project(':library') + implementation project(':kotlin') + +} + diff --git a/application/main/build.gradle b/application/main/build.gradle new file mode 100644 index 0000000..2a25276 --- /dev/null +++ b/application/main/build.gradle @@ -0,0 +1,7 @@ +dependencies { + implementation fileTree(dir: 'main/libs', include: ['*.jar']) + implementation 'com.android.support:appcompat-v7:27.1.1' + implementation 'com.android.support.constraint:constraint-layout:1.1.0' + + implementation microModule(':p_common') +} diff --git a/application/main/src/androidTest/java/com/eastwood/demo/application/ExampleInstrumentedTest.java b/application/main/src/androidTest/java/com/eastwood/demo/application/ExampleInstrumentedTest.java new file mode 100644 index 0000000..64e8eb4 --- /dev/null +++ b/application/main/src/androidTest/java/com/eastwood/demo/application/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.eastwood.demo.application; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.eastwood.demo.application", appContext.getPackageName()); + } +} diff --git a/application/main/src/demo/AndroidManifest.xml b/application/main/src/demo/AndroidManifest.xml new file mode 100644 index 0000000..39442db --- /dev/null +++ b/application/main/src/demo/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/application/main/src/demo/java/com/eastwood/demo/application/TestDemo.java b/application/main/src/demo/java/com/eastwood/demo/application/TestDemo.java new file mode 100644 index 0000000..0073099 --- /dev/null +++ b/application/main/src/demo/java/com/eastwood/demo/application/TestDemo.java @@ -0,0 +1,14 @@ +package com.eastwood.demo.application; + +import android.content.Context; + +/** + * @author eastwood + * createDate: 2018-10-15 + */ +public class TestDemo { + + void test(Context context) { +// context.getResources().getString(R.string.home); + } +} diff --git a/application/main/src/demo/res/layout/activity_demo.xml b/application/main/src/demo/res/layout/activity_demo.xml new file mode 100644 index 0000000..9bd6ad9 --- /dev/null +++ b/application/main/src/demo/res/layout/activity_demo.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/main/src/full/AndroidManifest.xml b/application/main/src/full/AndroidManifest.xml new file mode 100644 index 0000000..39442db --- /dev/null +++ b/application/main/src/full/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/application/main/src/full/java/com/eastwood/demo/application/TestFull.java b/application/main/src/full/java/com/eastwood/demo/application/TestFull.java new file mode 100644 index 0000000..0500cee --- /dev/null +++ b/application/main/src/full/java/com/eastwood/demo/application/TestFull.java @@ -0,0 +1,14 @@ +package com.eastwood.demo.application; + +import android.content.Context; + +/** + * @author eastwood + * createDate: 2018-10-15 + */ +public class TestFull { + + void test(Context context) { +// context.getResources().getString(R.string.home); + } +} diff --git a/application/main/src/full/res/layout/activity_full.xml b/application/main/src/full/res/layout/activity_full.xml new file mode 100644 index 0000000..2e3e002 --- /dev/null +++ b/application/main/src/full/res/layout/activity_full.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/main/src/AndroidManifest.xml b/application/main/src/main/AndroidManifest.xml similarity index 57% rename from app/main/src/AndroidManifest.xml rename to application/main/src/main/AndroidManifest.xml index 9342ca4..23a7e7a 100644 --- a/app/main/src/AndroidManifest.xml +++ b/application/main/src/main/AndroidManifest.xml @@ -1,19 +1,21 @@ + + package="com.eastwood.demo.application" > - - + android:roundIcon="@mipmap/ic_launcher_round" + android:supportsRtl="true" + android:theme="@style/AppTheme" > + - \ No newline at end of file diff --git a/application/main/src/main/java/com/eastwood/demo/application/MainActivity.java b/application/main/src/main/java/com/eastwood/demo/application/MainActivity.java new file mode 100644 index 0000000..17380b4 --- /dev/null +++ b/application/main/src/main/java/com/eastwood/demo/application/MainActivity.java @@ -0,0 +1,17 @@ +package com.eastwood.demo.application; + +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; + +public class MainActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + // can't use [R.string.home] which from microModule ':p_home'. +// getString(R.string.home); + + } +} diff --git a/application/main/src/main/res/drawable-v24/ic_launcher_foreground.xml b/application/main/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..c7bd21d --- /dev/null +++ b/application/main/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/application/main/src/main/res/drawable/ic_launcher_background.xml b/application/main/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..01f0af0 --- /dev/null +++ b/application/main/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/application/main/src/main/res/layout/activity_main.xml b/application/main/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..59a0b5a --- /dev/null +++ b/application/main/src/main/res/layout/activity_main.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file diff --git a/application/main/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/application/main/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..bbd3e02 --- /dev/null +++ b/application/main/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/application/main/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/application/main/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..bbd3e02 --- /dev/null +++ b/application/main/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/application/main/src/main/res/mipmap-hdpi/ic_launcher.png b/application/main/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..a2f5908 Binary files /dev/null and b/application/main/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/application/main/src/main/res/mipmap-hdpi/ic_launcher_round.png b/application/main/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..1b52399 Binary files /dev/null and b/application/main/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/application/main/src/main/res/mipmap-mdpi/ic_launcher.png b/application/main/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..ff10afd Binary files /dev/null and b/application/main/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/application/main/src/main/res/mipmap-mdpi/ic_launcher_round.png b/application/main/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..115a4c7 Binary files /dev/null and b/application/main/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/application/main/src/main/res/mipmap-xhdpi/ic_launcher.png b/application/main/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..dcd3cd8 Binary files /dev/null and b/application/main/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/application/main/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/application/main/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..459ca60 Binary files /dev/null and b/application/main/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/application/main/src/main/res/mipmap-xxhdpi/ic_launcher.png b/application/main/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..8ca12fe Binary files /dev/null and b/application/main/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/application/main/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/application/main/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..8e19b41 Binary files /dev/null and b/application/main/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/application/main/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/application/main/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..b824ebd Binary files /dev/null and b/application/main/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/application/main/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/application/main/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..4c19a13 Binary files /dev/null and b/application/main/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/application/main/src/main/res/values/colors.xml b/application/main/src/main/res/values/colors.xml new file mode 100644 index 0000000..3ab3e9c --- /dev/null +++ b/application/main/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #3F51B5 + #303F9F + #FF4081 + diff --git a/application/main/src/main/res/values/strings.xml b/application/main/src/main/res/values/strings.xml new file mode 100644 index 0000000..ceea8af --- /dev/null +++ b/application/main/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Application + diff --git a/application/main/src/main/res/values/styles.xml b/application/main/src/main/res/values/styles.xml new file mode 100644 index 0000000..5885930 --- /dev/null +++ b/application/main/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/application/main/src/minApi21/AndroidManifest.xml b/application/main/src/minApi21/AndroidManifest.xml new file mode 100644 index 0000000..39442db --- /dev/null +++ b/application/main/src/minApi21/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/application/main/src/minApi21/java/com/eastwood/demo/application/TestMinApi21.java b/application/main/src/minApi21/java/com/eastwood/demo/application/TestMinApi21.java new file mode 100644 index 0000000..1cd4b10 --- /dev/null +++ b/application/main/src/minApi21/java/com/eastwood/demo/application/TestMinApi21.java @@ -0,0 +1,14 @@ +package com.eastwood.demo.application; + +import android.content.Context; + +/** + * @author eastwood + * createDate: 2018-10-15 + */ +public class TestMinApi21 { + + void test(Context context) { +// context.getResources().getString(R.string.home); + } +} diff --git a/application/main/src/minApi21/res/layout/activity_minapi21.xml b/application/main/src/minApi21/res/layout/activity_minapi21.xml new file mode 100644 index 0000000..2e3e002 --- /dev/null +++ b/application/main/src/minApi21/res/layout/activity_minapi21.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/main/src/minApi23/AndroidManifest.xml b/application/main/src/minApi23/AndroidManifest.xml new file mode 100644 index 0000000..39442db --- /dev/null +++ b/application/main/src/minApi23/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/application/main/src/minApi23/java/com/eastwood/demo/application/TestMinApi23.java b/application/main/src/minApi23/java/com/eastwood/demo/application/TestMinApi23.java new file mode 100644 index 0000000..c328140 --- /dev/null +++ b/application/main/src/minApi23/java/com/eastwood/demo/application/TestMinApi23.java @@ -0,0 +1,14 @@ +package com.eastwood.demo.application; + +import android.content.Context; + +/** + * @author eastwood + * createDate: 2018-10-15 + */ +public class TestMinApi23 { + + void test(Context context) { +// context.getResources().getString(R.string.home); + } +} diff --git a/application/main/src/minApi23/res/layout/activity_minapi23.xml b/application/main/src/minApi23/res/layout/activity_minapi23.xml new file mode 100644 index 0000000..2e3e002 --- /dev/null +++ b/application/main/src/minApi23/res/layout/activity_minapi23.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/main/src/minApi24/AndroidManifest.xml b/application/main/src/minApi24/AndroidManifest.xml new file mode 100644 index 0000000..39442db --- /dev/null +++ b/application/main/src/minApi24/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/application/main/src/minApi24/java/com/eastwood/demo/application/TestMinApi24.java b/application/main/src/minApi24/java/com/eastwood/demo/application/TestMinApi24.java new file mode 100644 index 0000000..7ea46b8 --- /dev/null +++ b/application/main/src/minApi24/java/com/eastwood/demo/application/TestMinApi24.java @@ -0,0 +1,14 @@ +package com.eastwood.demo.application; + +import android.content.Context; + +/** + * @author eastwood + * createDate: 2018-10-15 + */ +public class TestMinApi24 { + + void test(Context context) { +// context.getResources().getString(R.string.home); + } +} diff --git a/application/main/src/minApi24/res/layout/activity_minapi24.xml b/application/main/src/minApi24/res/layout/activity_minapi24.xml new file mode 100644 index 0000000..2e3e002 --- /dev/null +++ b/application/main/src/minApi24/res/layout/activity_minapi24.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/main/src/test/java/com/eastwood/demo/application/ExampleUnitTest.java b/application/main/src/test/java/com/eastwood/demo/application/ExampleUnitTest.java new file mode 100644 index 0000000..bc542e6 --- /dev/null +++ b/application/main/src/test/java/com/eastwood/demo/application/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.eastwood.demo.application; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/application/p_base/build.gradle b/application/p_base/build.gradle new file mode 100644 index 0000000..2e3ca6a --- /dev/null +++ b/application/p_base/build.gradle @@ -0,0 +1,3 @@ +dependencies { + +} diff --git a/application/p_base/src/main/AndroidManifest.xml b/application/p_base/src/main/AndroidManifest.xml new file mode 100644 index 0000000..78a70ae --- /dev/null +++ b/application/p_base/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/application/p_base/src/main/java/com/eastwood/demo/application/base/Base.java b/application/p_base/src/main/java/com/eastwood/demo/application/base/Base.java new file mode 100644 index 0000000..937e76c --- /dev/null +++ b/application/p_base/src/main/java/com/eastwood/demo/application/base/Base.java @@ -0,0 +1,15 @@ +package com.eastwood.demo.application.base; + +import com.eastwood.demo.application.R; + +/** + * @author eastwood + * createDate: 2018-05-29 + */ +public class Base { + + void test() { + int i = R.string.base; + } + +} diff --git a/application/p_base/src/main/res/values/strings.xml b/application/p_base/src/main/res/values/strings.xml new file mode 100644 index 0000000..0c489c8 --- /dev/null +++ b/application/p_base/src/main/res/values/strings.xml @@ -0,0 +1,6 @@ + + + + base + + \ No newline at end of file diff --git a/application/p_common/build.gradle b/application/p_common/build.gradle new file mode 100644 index 0000000..a2faa28 --- /dev/null +++ b/application/p_common/build.gradle @@ -0,0 +1,3 @@ +dependencies { + implementation microModule(':p_base') +} diff --git a/application/p_common/src/main/AndroidManifest.xml b/application/p_common/src/main/AndroidManifest.xml new file mode 100644 index 0000000..b97d437 --- /dev/null +++ b/application/p_common/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/application/p_common/src/main/java/com/eastwood/demo/application/common/Common.java b/application/p_common/src/main/java/com/eastwood/demo/application/common/Common.java new file mode 100644 index 0000000..db55e08 --- /dev/null +++ b/application/p_common/src/main/java/com/eastwood/demo/application/common/Common.java @@ -0,0 +1,14 @@ +package com.eastwood.demo.application.common; + +import com.eastwood.demo.application.R; + +/** + * @author eastwood + * createDate: 2018-11-26 + */ +public class Common { + + void test() { + int i = R.string.common; + } +} diff --git a/application/p_common/src/main/res/values/strings.xml b/application/p_common/src/main/res/values/strings.xml new file mode 100644 index 0000000..ecdf4f9 --- /dev/null +++ b/application/p_common/src/main/res/values/strings.xml @@ -0,0 +1,6 @@ + + + + common + + \ No newline at end of file diff --git a/application/p_home/build.gradle b/application/p_home/build.gradle new file mode 100644 index 0000000..d3c20b6 --- /dev/null +++ b/application/p_home/build.gradle @@ -0,0 +1,3 @@ +dependencies { + implementation microModule(':p_common') +} \ No newline at end of file diff --git a/application/p_home/src/main/AndroidManifest.xml b/application/p_home/src/main/AndroidManifest.xml new file mode 100644 index 0000000..3ceae98 --- /dev/null +++ b/application/p_home/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/application/p_home/src/main/java/com/eastwood/demo/application/home/Home.java b/application/p_home/src/main/java/com/eastwood/demo/application/home/Home.java new file mode 100644 index 0000000..c198f64 --- /dev/null +++ b/application/p_home/src/main/java/com/eastwood/demo/application/home/Home.java @@ -0,0 +1,14 @@ +package com.eastwood.demo.application.home; + +import com.eastwood.demo.application.R; + +/** + * @author eastwood + * createDate: 2018-11-26 + */ +public class Home { + + void test() { + int i = R.string.home; + } +} diff --git a/application/p_home/src/main/res/values/strings.xml b/application/p_home/src/main/res/values/strings.xml new file mode 100644 index 0000000..e61efd0 --- /dev/null +++ b/application/p_home/src/main/res/values/strings.xml @@ -0,0 +1,6 @@ + + + + home + + \ No newline at end of file diff --git a/app/proguard-rules.pro b/application/proguard-rules.pro similarity index 70% rename from app/proguard-rules.pro rename to application/proguard-rules.pro index 77de25c..f1b4245 100644 --- a/app/proguard-rules.pro +++ b/application/proguard-rules.pro @@ -1,14 +1,10 @@ # Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in F:\Android\sdk/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. +# 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 -# Add any project specific keep options here: - # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: diff --git a/build.gradle b/build.gradle index c2eea8e..fa1eea0 100644 --- a/build.gradle +++ b/build.gradle @@ -1,23 +1,25 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { + ext.kotlin_version = '1.3.61' repositories { + google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.3' - - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files + classpath 'com.android.tools.build:gradle:3.2.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath 'com.eastwood.tools.plugins:micro-module:1.4.0' } } allprojects { repositories { + google() jcenter() } } task clean(type: Delete) { delete rootProject.buildDir -} +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index f1c49f3..87bc2cc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,17 +1,17 @@ -# Project-wide Gradle settings. - -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. - +## Project-wide Gradle settings. +# # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html - +# # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx1536m - +# Default value: -Xmx1024m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 +# # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true \ No newline at end of file +# org.gradle.parallel=true +#Fri Nov 10 18:24:18 CST 2017 +org.gradle.jvmargs=-Xmx2048m +# org.gradle.debug=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 13372ae..7a3265e 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index bfc67ee..85dd5ba 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Jul 24 10:26:08 CST 2017 +#Wed Jun 17 11:54:04 CST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip diff --git a/gradlew b/gradlew index 9d82f78..cccdd3d 100644 --- a/gradlew +++ b/gradlew @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh ############################################################################## ## @@ -6,20 +6,38 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,26 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -85,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -150,11 +154,19 @@ if $cygwin ; then esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 8a0b282..f955316 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -8,14 +8,14 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,10 +46,9 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,11 +59,6 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line diff --git a/kotlin/.gitignore b/kotlin/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/kotlin/.gitignore @@ -0,0 +1 @@ +/build diff --git a/kotlin/build.gradle b/kotlin/build.gradle new file mode 100644 index 0000000..cd3b8bf --- /dev/null +++ b/kotlin/build.gradle @@ -0,0 +1,52 @@ +apply plugin: 'micro-module' +apply plugin: 'com.android.library' + +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + compileSdkVersion 27 + + defaultConfig { + minSdkVersion 14 + targetSdkVersion 27 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + + preview { + + } + } + + androidExtensions { + experimental = true + } +} + +microModule { + include ':p_common' + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + implementation 'com.android.support:appcompat-v7:27.1.1' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} +repositories { + mavenCentral() +} diff --git a/kotlin/main/build.gradle b/kotlin/main/build.gradle new file mode 100644 index 0000000..bade2d6 --- /dev/null +++ b/kotlin/main/build.gradle @@ -0,0 +1,7 @@ +// MicroModule build file where you can add configuration options to publish MicroModule(aar) to Maven +// and declare MicroModule dependencies. + +dependencies { + implementation fileTree(dir: 'main/libs', include: ['*.jar']) +// implementation microModule(':p_common') +} diff --git a/kotlin/main/src/androidTest/java/com/eastwood/demo/kotlin/ExampleInstrumentedTest.java b/kotlin/main/src/androidTest/java/com/eastwood/demo/kotlin/ExampleInstrumentedTest.java new file mode 100644 index 0000000..c1d9baa --- /dev/null +++ b/kotlin/main/src/androidTest/java/com/eastwood/demo/kotlin/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.eastwood.demo.kotlin; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.eastwood.demo.kotlin", appContext.getPackageName()); + } +} diff --git a/kotlin/main/src/main/AndroidManifest.xml b/kotlin/main/src/main/AndroidManifest.xml new file mode 100644 index 0000000..6d9e8cf --- /dev/null +++ b/kotlin/main/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/kotlin/main/src/main/java/com/eastwood/demo/kotlin/Test.kt b/kotlin/main/src/main/java/com/eastwood/demo/kotlin/Test.kt new file mode 100644 index 0000000..27aeeea --- /dev/null +++ b/kotlin/main/src/main/java/com/eastwood/demo/kotlin/Test.kt @@ -0,0 +1,15 @@ +package com.eastwood.demo.kotlin + +/** + * + * @author eastwood + * createDate: 2019-01-18 + */ +class Test { + + fun test() { + // can't use [R.string.kotlin_common] which from microModule ':p_common'. +// var i = R.string.kotlin_common + } + +} \ No newline at end of file diff --git a/kotlin/main/src/main/kotlin/com/eastwood/demo/kotlin/Test1.kt b/kotlin/main/src/main/kotlin/com/eastwood/demo/kotlin/Test1.kt new file mode 100644 index 0000000..5b8cec8 --- /dev/null +++ b/kotlin/main/src/main/kotlin/com/eastwood/demo/kotlin/Test1.kt @@ -0,0 +1,15 @@ +package com.eastwood.demo.kotlin + +/** + * + * @author eastwood + * createDate: 2019-01-18 + */ +class Test1 { + + fun test() { + // can't use [R.string.kotlin_common] which from microModule ':p_common'. +// var i = R.string.kotlin_common + } + +} \ No newline at end of file diff --git a/kotlin/main/src/main/res/values/strings.xml b/kotlin/main/src/main/res/values/strings.xml new file mode 100644 index 0000000..f11f745 --- /dev/null +++ b/kotlin/main/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + + diff --git a/kotlin/main/src/test/java/com/eastwood/demo/kotlin/ExampleUnitTest.java b/kotlin/main/src/test/java/com/eastwood/demo/kotlin/ExampleUnitTest.java new file mode 100644 index 0000000..38b653c --- /dev/null +++ b/kotlin/main/src/test/java/com/eastwood/demo/kotlin/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.eastwood.demo.kotlin; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/kotlin/p_common/build.gradle b/kotlin/p_common/build.gradle new file mode 100644 index 0000000..dd5852c --- /dev/null +++ b/kotlin/p_common/build.gradle @@ -0,0 +1,6 @@ +// MicroModule build file where you can add configuration options to publish MicroModule(aar) to Maven +// and declare MicroModule dependencies. + +dependencies { + implementation fileTree(dir: 'p_common/libs', include: ['*.jar']) +} diff --git a/kotlin/p_common/src/main/AndroidManifest.xml b/kotlin/p_common/src/main/AndroidManifest.xml new file mode 100644 index 0000000..c204b27 --- /dev/null +++ b/kotlin/p_common/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/kotlin/p_common/src/main/java/com/eastwood/demo/kotlin/common/Common.kt b/kotlin/p_common/src/main/java/com/eastwood/demo/kotlin/common/Common.kt new file mode 100644 index 0000000..6ecf4a1 --- /dev/null +++ b/kotlin/p_common/src/main/java/com/eastwood/demo/kotlin/common/Common.kt @@ -0,0 +1,17 @@ +package com.eastwood.demo.kotlin.common + +import com.eastwood.demo.kotlin.R + +/** + * + * @author eastwood + * createDate: 2019-01-18 + */ +class Common { + + fun test() { + + var i = R.string.kotlin_common + } + +} \ No newline at end of file diff --git a/kotlin/p_common/src/main/kotlin/com/eastwood/demo/kotlin/common/Common1.kt b/kotlin/p_common/src/main/kotlin/com/eastwood/demo/kotlin/common/Common1.kt new file mode 100644 index 0000000..d44519b --- /dev/null +++ b/kotlin/p_common/src/main/kotlin/com/eastwood/demo/kotlin/common/Common1.kt @@ -0,0 +1,16 @@ +package com.eastwood.demo.kotlin.common + +import com.eastwood.demo.kotlin.R + +/** + * + * @author eastwood + * createDate: 2019-01-18 + */ +class Common1 { + + fun test() { + var i = R.string.kotlin_common + } + +} \ No newline at end of file diff --git a/kotlin/p_common/src/main/res/values/strings.xml b/kotlin/p_common/src/main/res/values/strings.xml new file mode 100644 index 0000000..fda18d5 --- /dev/null +++ b/kotlin/p_common/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + kotlin_common + diff --git a/kotlin/proguard-rules.pro b/kotlin/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/kotlin/proguard-rules.pro @@ -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 diff --git a/library/.gitignore b/library/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/library/.gitignore @@ -0,0 +1 @@ +/build diff --git a/library/build.gradle b/library/build.gradle new file mode 100644 index 0000000..6079478 --- /dev/null +++ b/library/build.gradle @@ -0,0 +1,79 @@ +apply plugin: 'micro-module' +apply plugin: 'com.android.library' + +android { + compileSdkVersion 27 + + defaultConfig { + minSdkVersion 14 + targetSdkVersion 27 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + +// flavorDimensions "api", "mode" +// +// productFlavors { +// +// minApi24 { +// dimension "api" +// minSdkVersion 24 +// versionCode 30000 + android.defaultConfig.versionCode +// versionNameSuffix "-minApi24" +// } +// +// minApi23 { +// dimension "api" +// minSdkVersion 23 +// versionCode 20000 + android.defaultConfig.versionCode +// versionNameSuffix "-minApi23" +// } +// +// minApi21 { +// dimension "api" +// minSdkVersion 21 +// versionCode 10000 + android.defaultConfig.versionCode +// versionNameSuffix "-minApi21" +// } +// +// demo { +// dimension "mode" +// } +// +// full { +// dimension "mode" +// } +// +// } + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + implementation 'com.android.support:appcompat-v7:27.1.1' + + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' +} + +microModule { + codeCheckEnabled true + + include ':p_base' + include ':p_common' + include ':p_utils' + + export ':main' +} diff --git a/library/main/build.gradle b/library/main/build.gradle new file mode 100644 index 0000000..39c415e --- /dev/null +++ b/library/main/build.gradle @@ -0,0 +1,4 @@ + +dependencies { + implementation microModule(':p_common') +} \ No newline at end of file diff --git a/library/main/src/androidTest/java/com/eastwood/demo/library/ExampleInstrumentedTest.java b/library/main/src/androidTest/java/com/eastwood/demo/library/ExampleInstrumentedTest.java new file mode 100644 index 0000000..92672ac --- /dev/null +++ b/library/main/src/androidTest/java/com/eastwood/demo/library/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.eastwood.demo.library; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.eastwood.demo.library.test", appContext.getPackageName()); + } +} diff --git a/library/main/src/demo/AndroidManifest.xml b/library/main/src/demo/AndroidManifest.xml new file mode 100644 index 0000000..d6c4bd1 --- /dev/null +++ b/library/main/src/demo/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/library/main/src/demo/java/com/eastwood/demo/library/TestDemo.java b/library/main/src/demo/java/com/eastwood/demo/library/TestDemo.java new file mode 100644 index 0000000..6944e7b --- /dev/null +++ b/library/main/src/demo/java/com/eastwood/demo/library/TestDemo.java @@ -0,0 +1,8 @@ +package com.eastwood.demo.library; + +/** + * @author eastwood + * createDate: 2018-11-24 + */ +public class TestDemo { +} diff --git a/library/main/src/full/AndroidManifest.xml b/library/main/src/full/AndroidManifest.xml new file mode 100644 index 0000000..d6c4bd1 --- /dev/null +++ b/library/main/src/full/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/library/main/src/full/java/com/eastwood/demo/library/Full.java b/library/main/src/full/java/com/eastwood/demo/library/Full.java new file mode 100644 index 0000000..b7b7a8a --- /dev/null +++ b/library/main/src/full/java/com/eastwood/demo/library/Full.java @@ -0,0 +1,8 @@ +package com.eastwood.demo.library; + +/** + * @author eastwood + * createDate: 2018-11-24 + */ +public class Full { +} diff --git a/library/main/src/main/AndroidManifest.xml b/library/main/src/main/AndroidManifest.xml new file mode 100644 index 0000000..d6119aa --- /dev/null +++ b/library/main/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/library/main/src/main/java/com/eastwood/demo/library/Test.java b/library/main/src/main/java/com/eastwood/demo/library/Test.java new file mode 100644 index 0000000..8aa62ba --- /dev/null +++ b/library/main/src/main/java/com/eastwood/demo/library/Test.java @@ -0,0 +1,15 @@ +package com.eastwood.demo.library; + +import android.content.Context; + +/** + * @author eastwood + * createDate: 2018-05-30 + */ +public class Test { + + public void get(Context context) { + // can't use [R.string.test_code_check] which from microModule ':p_utils'. +// context.getString(R.string.test_code_check_utils); + } +} diff --git a/library/main/src/main/res/values/strings.xml b/library/main/src/main/res/values/strings.xml new file mode 100644 index 0000000..9d7a64b --- /dev/null +++ b/library/main/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + + diff --git a/library/main/src/minApi21/AndroidManifest.xml b/library/main/src/minApi21/AndroidManifest.xml new file mode 100644 index 0000000..d6c4bd1 --- /dev/null +++ b/library/main/src/minApi21/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/library/main/src/minApi21/java/com/eastwood/demo/library/TestMinApi21.java b/library/main/src/minApi21/java/com/eastwood/demo/library/TestMinApi21.java new file mode 100644 index 0000000..a8e5561 --- /dev/null +++ b/library/main/src/minApi21/java/com/eastwood/demo/library/TestMinApi21.java @@ -0,0 +1,8 @@ +package com.eastwood.demo.library; + +/** + * @author eastwood + * createDate: 2018-11-24 + */ +public class TestMinApi21 { +} diff --git a/library/main/src/minApi23/AndroidManifest.xml b/library/main/src/minApi23/AndroidManifest.xml new file mode 100644 index 0000000..d6c4bd1 --- /dev/null +++ b/library/main/src/minApi23/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/library/main/src/minApi23/java/com/eastwood/demo/library/TestMinApi23.java b/library/main/src/minApi23/java/com/eastwood/demo/library/TestMinApi23.java new file mode 100644 index 0000000..5a6080f --- /dev/null +++ b/library/main/src/minApi23/java/com/eastwood/demo/library/TestMinApi23.java @@ -0,0 +1,8 @@ +package com.eastwood.demo.library; + +/** + * @author eastwood + * createDate: 2018-11-24 + */ +public class TestMinApi23 { +} diff --git a/library/main/src/minApi24/AndroidManifest.xml b/library/main/src/minApi24/AndroidManifest.xml new file mode 100644 index 0000000..d6c4bd1 --- /dev/null +++ b/library/main/src/minApi24/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/library/main/src/minApi24/java/com/eastwood/demo/library/TestMinApi24.java b/library/main/src/minApi24/java/com/eastwood/demo/library/TestMinApi24.java new file mode 100644 index 0000000..e2067af --- /dev/null +++ b/library/main/src/minApi24/java/com/eastwood/demo/library/TestMinApi24.java @@ -0,0 +1,8 @@ +package com.eastwood.demo.library; + +/** + * @author eastwood + * createDate: 2018-11-24 + */ +public class TestMinApi24 { +} diff --git a/library/main/src/test/java/com/eastwood/demo/library/ExampleUnitTest.java b/library/main/src/test/java/com/eastwood/demo/library/ExampleUnitTest.java new file mode 100644 index 0000000..3ea3a0b --- /dev/null +++ b/library/main/src/test/java/com/eastwood/demo/library/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.eastwood.demo.library; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/library/p_base/build.gradle b/library/p_base/build.gradle new file mode 100644 index 0000000..5fbab79 --- /dev/null +++ b/library/p_base/build.gradle @@ -0,0 +1,4 @@ + +dependencies { + +} \ No newline at end of file diff --git a/library/p_base/src/main/AndroidManifest.xml b/library/p_base/src/main/AndroidManifest.xml new file mode 100644 index 0000000..2969872 --- /dev/null +++ b/library/p_base/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/library/p_base/src/main/java/com/eastwood/demo/library/base/Base.java b/library/p_base/src/main/java/com/eastwood/demo/library/base/Base.java new file mode 100644 index 0000000..f6d973e --- /dev/null +++ b/library/p_base/src/main/java/com/eastwood/demo/library/base/Base.java @@ -0,0 +1,15 @@ +package com.eastwood.demo.library.base; + +import com.eastwood.demo.library.R; + +/** + * @author eastwood + * createDate: 2018-11-09 + */ +public class Base { + + void test() { + int i = R.string.test_code_check_base; + } + +} diff --git a/library/p_base/src/main/res/values/strings.xml b/library/p_base/src/main/res/values/strings.xml new file mode 100644 index 0000000..a63156f --- /dev/null +++ b/library/p_base/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + base + \ No newline at end of file diff --git a/library/p_common/build.gradle b/library/p_common/build.gradle new file mode 100644 index 0000000..e9f1107 --- /dev/null +++ b/library/p_common/build.gradle @@ -0,0 +1,4 @@ + +dependencies { + implementation microModule(':p_base') +} \ No newline at end of file diff --git a/library/p_common/src/main/AndroidManifest.xml b/library/p_common/src/main/AndroidManifest.xml new file mode 100644 index 0000000..3d672d3 --- /dev/null +++ b/library/p_common/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/library/p_common/src/main/java/com/eastwood/demo/library/common/Common.java b/library/p_common/src/main/java/com/eastwood/demo/library/common/Common.java new file mode 100644 index 0000000..a62be15 --- /dev/null +++ b/library/p_common/src/main/java/com/eastwood/demo/library/common/Common.java @@ -0,0 +1,15 @@ +package com.eastwood.demo.library.common; + +import com.eastwood.demo.library.R; + +/** + * @author eastwood + * createDate: 2018-11-09 + */ +public class Common { + + void test() { + int i = R.string.test_code_check_common; + } + +} diff --git a/library/p_common/src/main/res/values/strings.xml b/library/p_common/src/main/res/values/strings.xml new file mode 100644 index 0000000..04c8039 --- /dev/null +++ b/library/p_common/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + common + \ No newline at end of file diff --git a/library/p_utils/build.gradle b/library/p_utils/build.gradle new file mode 100644 index 0000000..5fbab79 --- /dev/null +++ b/library/p_utils/build.gradle @@ -0,0 +1,4 @@ + +dependencies { + +} \ No newline at end of file diff --git a/library/p_utils/src/demo/AndroidManifest.xml b/library/p_utils/src/demo/AndroidManifest.xml new file mode 100644 index 0000000..d6c4bd1 --- /dev/null +++ b/library/p_utils/src/demo/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/library/p_utils/src/full/AndroidManifest.xml b/library/p_utils/src/full/AndroidManifest.xml new file mode 100644 index 0000000..6335e63 --- /dev/null +++ b/library/p_utils/src/full/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/library/p_utils/src/main/AndroidManifest.xml b/library/p_utils/src/main/AndroidManifest.xml new file mode 100644 index 0000000..5affc42 --- /dev/null +++ b/library/p_utils/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/library/p_utils/src/main/java/com/eastwood/demo/library/utils/Utils.java b/library/p_utils/src/main/java/com/eastwood/demo/library/utils/Utils.java new file mode 100644 index 0000000..04cf3ab --- /dev/null +++ b/library/p_utils/src/main/java/com/eastwood/demo/library/utils/Utils.java @@ -0,0 +1,14 @@ +package com.eastwood.demo.library.utils; + +import com.eastwood.demo.library.R; + +/** + * @author eastwood + * createDate: 2018-05-29 + */ +public class Utils { + + void test() { + int i = R.string.test_code_check_utils; + } +} diff --git a/library/p_utils/src/main/res/values/strings.xml b/library/p_utils/src/main/res/values/strings.xml new file mode 100644 index 0000000..e7de997 --- /dev/null +++ b/library/p_utils/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + utils + \ No newline at end of file diff --git a/library/p_utils/src/minApi21/AndroidManifest.xml b/library/p_utils/src/minApi21/AndroidManifest.xml new file mode 100644 index 0000000..23f7038 --- /dev/null +++ b/library/p_utils/src/minApi21/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/library/p_utils/src/minApi23/AndroidManifest.xml b/library/p_utils/src/minApi23/AndroidManifest.xml new file mode 100644 index 0000000..23f7038 --- /dev/null +++ b/library/p_utils/src/minApi23/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/library/p_utils/src/minApi24/AndroidManifest.xml b/library/p_utils/src/minApi24/AndroidManifest.xml new file mode 100644 index 0000000..23f7038 --- /dev/null +++ b/library/p_utils/src/minApi24/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/library/proguard-rules.pro b/library/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/library/proguard-rules.pro @@ -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 diff --git a/micro-module/.gitignore b/micro-module/.gitignore new file mode 100644 index 0000000..eaeb95b --- /dev/null +++ b/micro-module/.gitignore @@ -0,0 +1,35 @@ +# Built application files +*.apk +*.ap_ + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Android Studio Navigation editor temp files +.navigation/ +.externalNativeBuild + +# Android Studio captures folder +captures/ + +# Intellij +*.iml +.idea + +# Keystore files +*.jks diff --git a/micro-module/build.gradle b/micro-module/build.gradle new file mode 100644 index 0000000..48668a4 --- /dev/null +++ b/micro-module/build.gradle @@ -0,0 +1,53 @@ +apply plugin: 'groovy' +apply plugin: 'java' + +group 'com.eastwood.tools.plugins' +sourceCompatibility = 1.8 + +repositories { + google() + jcenter() +} + +dependencies { + compile gradleApi() + compile localGroovy() + implementation 'com.android.tools.build:gradle:3.1.0' +} + +apply plugin: 'maven' + +def groupId = 'com.eastwood.tools.plugins' +def artifactId = 'micro-module' +def version = '1.4.0' + +def localReleaseDest = "${buildDir}/release/${version}" + +uploadArchives { + repositories { + mavenDeployer { + pom.groupId = groupId + pom.artifactId = artifactId + pom.version = version + // Add other pom properties here if you want (developer details / licenses) + repository(url: "file://${localReleaseDest}") + } + } +} + + +task zipRelease(type: Zip) { + from localReleaseDest + destinationDir buildDir + archiveName "release-${version}.zip" +} + +task generateRelease { + doLast { + println "Release ${version} can be found at ${localReleaseDest}/" + println "Release ${version} zipped can be found ${buildDir}/release-${version}.zip" + } +} + +generateRelease.dependsOn(uploadArchives) +generateRelease.dependsOn(zipRelease) \ No newline at end of file diff --git a/micro-module/settings.gradle b/micro-module/settings.gradle new file mode 100644 index 0000000..6811b02 --- /dev/null +++ b/micro-module/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'micro-module' + diff --git a/micro-module/src/main/groovy/com/eastwood/tools/plugins/MicroModulePlugin.groovy b/micro-module/src/main/groovy/com/eastwood/tools/plugins/MicroModulePlugin.groovy new file mode 100644 index 0000000..5e94f2b --- /dev/null +++ b/micro-module/src/main/groovy/com/eastwood/tools/plugins/MicroModulePlugin.groovy @@ -0,0 +1,613 @@ +package com.eastwood.tools.plugins + +import com.android.build.gradle.* +import com.android.build.gradle.api.BaseVariant +import com.android.build.gradle.api.BaseVariantOutput +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.eastwood.tools.plugins.core.MicroModule +import com.eastwood.tools.plugins.core.MicroModuleInfo +import com.eastwood.tools.plugins.core.ProductFlavorInfo +import com.eastwood.tools.plugins.core.Utils +import com.eastwood.tools.plugins.core.check.CodeChecker +import com.eastwood.tools.plugins.core.extension.* +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 +import org.gradle.api.tasks.compile.JavaCompile + +class MicroModulePlugin implements Plugin { + + 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' + + 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 = '\n\n' + buildResult.gradle.rootProject.allprojects.each { + MicroModulePlugin microModulePlugin = it.plugins.findPlugin('micro-module') + if (microModulePlugin == null) return + + def displayName = it.displayName + microModuleInfo += ' \n' + microModulePlugin.microModuleInfo.includeMicroModules.each { + MicroModule microModule = it.value + microModuleInfo += ' \n' + } + microModuleInfo += ' \n' + } + microModuleInfo += '' + + def microModules = new File(ideaFile, 'microModules.xml') + microModules.write(microModuleInfo, 'utf-8') + } + } + + Project project + + String startTaskState = NORMAL + + MicroModuleInfo microModuleInfo + ProductFlavorInfo productFlavorInfo + + MicroModule currentMicroModule + String applyScriptState + + boolean appliedLibraryPlugin + + boolean clearedOriginSourceSets + + 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() { + @Override + void execute(TestedExtension testedExtension) { + boolean isLibrary + DomainObjectSet 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 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 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 + Utils.upperCase(it)) + } + } + + def androidTest = 'androidTest' + mergeAndroidManifest(androidTest) + mergeAndroidManifest(androidTest + 'Debug') + if (!productFlavorInfo.singleDimension) { + productFlavorInfo.productFlavors.each { + mergeAndroidManifest(androidTest + Utils.upperCase(it)) + } + } + productFlavorInfo.combinedProductFlavors.each { + mergeAndroidManifest(androidTest + Utils.upperCase(it)) + mergeAndroidManifest(androidTest + Utils.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 + Utils.upperCase(it)) + } + } + + def testTypes = ['androidTest', 'test'] + testTypes.each { + def testType = it + addVariantSourceSet(microModule, testType) + + if (testType == 'test') { + productFlavorInfo.buildTypes.each { + addVariantSourceSet(microModule, testType + Utils.upperCase(it)) + } + } else { + addVariantSourceSet(microModule, testType + 'Debug') + } + + if (!productFlavorInfo.singleDimension) { + productFlavorInfo.productFlavors.each { + addVariantSourceSet(microModule, testType + Utils.upperCase(it)) + } + } + + productFlavorInfo.combinedProductFlavors.each { + def productFlavorName = testType + Utils.upperCase(it) + addVariantSourceSet(microModule, productFlavorName) + + if (testType == 'test') { + productFlavorInfo.buildTypes.each { + addVariantSourceSet(microModule, productFlavorName + Utils.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 + Utils.upperCase(it)) + } + } + + def testTypes = ['androidTest', 'test'] + testTypes.each { + def testType = it + clearModuleSourceSet(testType) + + if (testType == 'test') { + productFlavorInfo.buildTypes.each { + clearModuleSourceSet(testType + Utils.upperCase(it)) + } + } else { + clearModuleSourceSet(testType + 'Debug') + } + + if (!productFlavorInfo.singleDimension) { + productFlavorInfo.productFlavors.each { + clearModuleSourceSet(testType + Utils.upperCase(it)) + } + } + + productFlavorInfo.combinedProductFlavors.each { + def productFlavorName = testType + Utils.upperCase(it) + clearModuleSourceSet(productFlavorName) + + if (testType == 'test') { + productFlavorInfo.buildTypes.each { + clearModuleSourceSet(productFlavorName + Utils.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 sourceFolders) { + CodeChecker codeChecker + + def buildTypeFirstUp = Utils.upperCase(buildType) + def productFlavorFirstUp = flavorName != null ? Utils.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) + } + } + } + +} \ No newline at end of file diff --git a/micro-module/src/main/groovy/com/eastwood/tools/plugins/core/Digraph.java b/micro-module/src/main/groovy/com/eastwood/tools/plugins/core/Digraph.java new file mode 100644 index 0000000..5230853 --- /dev/null +++ b/micro-module/src/main/groovy/com/eastwood/tools/plugins/core/Digraph.java @@ -0,0 +1,180 @@ +package com.eastwood.tools.plugins.core; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Stack; + +/** + * An example class for directed graphs. The vertex type can be specified. + * There are no edge costs/weights. + * + * Written for CS211, Nov 2006. + * + * @author Paul Chew + */ +public class Digraph { + + /** + * The implementation here is basically an adjacency list, but instead + * of an array of lists, a Map is used to map each vertex to its list of + * adjacent vertices. + */ + private Map> neighbors = new HashMap>(); + + /** + * String representation of dependencyGraph. + */ + public String toString () { + StringBuffer s = new StringBuffer(); + for (V v: neighbors.keySet()) s.append("\n " + v + " -> " + neighbors.get(v)); + return s.toString(); + } + + /** + * Add a vertex to the dependencyGraph. Nothing happens if vertex is already in dependencyGraph. + */ + public void add (V vertex) { + if (neighbors.containsKey(vertex)) return; + neighbors.put(vertex, new ArrayList()); + } + + /** + * True iff dependencyGraph contains vertex. + */ + public boolean contains (V vertex) { + return neighbors.containsKey(vertex); + } + + /** + * Add an edge to the dependencyGraph; if either vertex does not exist, it's added. + * This implementation allows the creation of multi-edges and self-loops. + */ + public void add (V from, V to) { + this.add(from); this.add(to); + neighbors.get(from).add(to); + } + + /** + * Remove an edge from the dependencyGraph. Nothing happens if no such edge. + * @throws IllegalArgumentException if either vertex doesn't exist. + */ + public void remove (V from, V to) { + if (!(this.contains(from) && this.contains(to))) + throw new IllegalArgumentException("Nonexistent vertex"); + neighbors.get(from).remove(to); + } + + /** + * Report (as a Map) the out-degree of each vertex. + */ + public Map outDegree () { + Map result = new HashMap(); + for (V v: neighbors.keySet()) result.put(v, neighbors.get(v).size()); + return result; + } + + /** + * Report (as a Map) the in-degree of each vertex. + */ + public Map inDegree () { + Map result = new HashMap(); + for (V v: neighbors.keySet()) result.put(v, 0); // All in-degrees are 0 + for (V from: neighbors.keySet()) { + for (V to: neighbors.get(from)) { + result.put(to, result.get(to) + 1); // Increment in-degree + } + } + return result; + } + + /** + * Report (as a List) the topological sort of the vertices; null for no such sort. + */ + public List topSort () { + Map degree = inDegree(); + // Determine all vertices with zero in-degree + Stack zeroVerts = new Stack(); // Stack as good as any here + for (V v: degree.keySet()) { + if (degree.get(v) == 0) zeroVerts.push(v); + } + // Determine the topological order + List result = new ArrayList(); + while (!zeroVerts.isEmpty()) { + V v = zeroVerts.pop(); // Choose a vertex with zero in-degree + result.add(v); // Vertex v is next in topol order + // "Remove" vertex v by updating its neighbors + for (V neighbor: neighbors.get(v)) { + degree.put(neighbor, degree.get(neighbor) - 1); + // Remember any vertices that now have zero in-degree + if (degree.get(neighbor) == 0) zeroVerts.push(neighbor); + } + } + // Check that we have used the entire dependencyGraph (if not, there was a cycle) + if (result.size() != neighbors.size()) return null; + return result; + } + + /** + * True iff dependencyGraph is a dag (directed acyclic dependencyGraph). + */ + public boolean isDag () { + return topSort() != null; + } + + /** + * Report (as a Map) the bfs distance to each vertex from the start vertex. + * The distance is an Integer; the value null is used to represent infinity + * (implying that the corresponding node cannot be reached). + */ + public Map bfsDistance (V start) { + Map distance = new HashMap(); + // Initially, all distance are infinity, except start node + for (V v: neighbors.keySet()) distance.put(v, null); + distance.put(start, 0); + // Process nodes in queue order + Queue queue = new LinkedList(); + queue.offer(start); // Place start node in queue + while (!queue.isEmpty()) { + V v = queue.remove(); + int vDist = distance.get(v); + // Update neighbors + for (V neighbor: neighbors.get(v)) { + if (distance.get(neighbor) != null) continue; // Ignore if already done + distance.put(neighbor, vDist + 1); + queue.offer(neighbor); + } + } + return distance; + } + +// /** +// * Main program (for testing). +// */ +// public static void main (String[] args) { +// // Create a Graph with Integer nodes +// Digraph graph = new Digraph(); +// graph.add(0, 1); graph.add(0, 2); graph.add(0, 3); +// graph.add(1, 2); graph.add(1, 3); graph.add(2, 3); +// graph.add(2, 4); graph.add(4, 5); graph.add(5, 6); // Tetrahedron with tail +// System.out.println("The current dependencyGraph: " + graph); +// System.out.println("In-degrees: " + graph.inDegree()); +// System.out.println("Out-degrees: " + graph.outDegree()); +// System.out.println("A topological sort of the vertices: " + graph.topSort()); +// System.out.println("The dependencyGraph " + (graph.isDag()?"is":"is not") + " a dag"); +// System.out.println("BFS distances starting from " + 0 + ": " + graph.bfsDistance(0)); +// System.out.println("BFS distances starting from " + 1 + ": " + graph.bfsDistance(1)); +// System.out.println("BFS distances starting from " + 2 + ": " + graph.bfsDistance(2)); +// graph.add(4, 1); // Create a cycle +// System.out.println("Cycle created"); +// System.out.println("The current dependencyGraph: " + graph); +// System.out.println("In-degrees: " + graph.inDegree()); +// System.out.println("Out-degrees: " + graph.outDegree()); +// System.out.println("A topological sort of the vertices: " + graph.topSort()); +// System.out.println("The dependencyGraph " + (graph.isDag()?"is":"is not") + " a dag"); +// System.out.println("BFS distances starting from " + 2 + ": " + graph.bfsDistance(2)); +// } +} \ No newline at end of file diff --git a/micro-module/src/main/groovy/com/eastwood/tools/plugins/core/MicroModule.groovy b/micro-module/src/main/groovy/com/eastwood/tools/plugins/core/MicroModule.groovy new file mode 100644 index 0000000..508a5d2 --- /dev/null +++ b/micro-module/src/main/groovy/com/eastwood/tools/plugins/core/MicroModule.groovy @@ -0,0 +1,11 @@ +package com.eastwood.tools.plugins.core + + +class MicroModule { + + String name + File microModuleDir + + boolean appliedScript + +} \ No newline at end of file diff --git a/micro-module/src/main/groovy/com/eastwood/tools/plugins/core/MicroModuleInfo.groovy b/micro-module/src/main/groovy/com/eastwood/tools/plugins/core/MicroModuleInfo.groovy new file mode 100644 index 0000000..50b0e10 --- /dev/null +++ b/micro-module/src/main/groovy/com/eastwood/tools/plugins/core/MicroModuleInfo.groovy @@ -0,0 +1,77 @@ +package com.eastwood.tools.plugins.core + +import org.gradle.api.GradleException +import org.gradle.api.Project + +class MicroModuleInfo { + + Project project + MicroModule mainMicroModule + Map includeMicroModules + Map exportMicroModules + + Digraph dependencyGraph + + MicroModuleInfo(Project project) { + this.project = project + this.includeMicroModules = new HashMap<>() + this.exportMicroModules = new HashMap<>() + dependencyGraph = new Digraph() + + MicroModule microModule = Utils.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 = Utils.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(Utils.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 bfsDistance = dependencyGraph.bfsDistance(target) + for(String key: bfsDistance.keySet()) { + if(key == dependency) { + return bfsDistance.get(key) != null + } + } + return false + } + +} \ No newline at end of file diff --git a/micro-module/src/main/groovy/com/eastwood/tools/plugins/core/ProductFlavorInfo.groovy b/micro-module/src/main/groovy/com/eastwood/tools/plugins/core/ProductFlavorInfo.groovy new file mode 100644 index 0000000..83fd825 --- /dev/null +++ b/micro-module/src/main/groovy/com/eastwood/tools/plugins/core/ProductFlavorInfo.groovy @@ -0,0 +1,102 @@ +package com.eastwood.tools.plugins.core + +import com.android.build.gradle.BaseExtension +import org.gradle.api.Project + +class ProductFlavorInfo { + + List flavorDimensions + List productFlavors + List buildTypes + List combinedProductFlavors + Map> combinedProductFlavorsMap + boolean singleDimension + + private List> flavorGroups + + ProductFlavorInfo(Project project) { + BaseExtension extension = (BaseExtension) project.extensions.getByName("android") + buildTypes = new ArrayList<>() + if(extension.buildTypes != null) { + extension.buildTypes.each { + buildTypes.add(it.name) + } + } + + 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<>()) + } + extension.productFlavors.each { + productFlavors.add(it.name) + def position = flavorDimensions.indexOf(it.dimension) + flavorGroups.get(position).add(it.name) + } + List> flavorGroupTemp = new ArrayList<>() + flavorGroups.each { + if (it.size() != 0) { + flavorGroupTemp.add(it) + } + } + flavorGroups = flavorGroupTemp + + calculateFlavorCombination() + if (combinedProductFlavors.size() == extension.productFlavors.size()) { + singleDimension = true + } + } + + private void calculateFlavorCombination() { + combinedProductFlavors = new ArrayList<>() + combinedProductFlavorsMap = new HashMap<>() + + if (flavorGroups.size() == 0) { + return + } + + List combination = new ArrayList() + int n = flavorGroups.size(); + for (int i = 0; i < n; i++) { + combination.add(0); + } + int i = 0; + boolean isContinue = true; + while (isContinue) { + List items = new ArrayList<>() + String item = flavorGroups.get(0).get(combination.get(0)) + items.add(item) + String combined = item + for (int j = 1; j < n; j++) { + item = flavorGroups.get(j).get(combination.get(j)) + combined += Utils.upperCase(item) + items.add(item) + } + combinedProductFlavors.add(combined) + combinedProductFlavorsMap.put(combined, items) + i++; + 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; + if (j - 1 >= 0) { + combination.set(j - 1, combination.get(j - 1) + 1); + } + } + } + isContinue = false; + for (Integer integer : combination) { + if (integer != 0) { + isContinue = true; + } + } + } + } + +} \ No newline at end of file diff --git a/micro-module/src/main/groovy/com/eastwood/tools/plugins/core/Utils.groovy b/micro-module/src/main/groovy/com/eastwood/tools/plugins/core/Utils.groovy new file mode 100644 index 0000000..afb8217 --- /dev/null +++ b/micro-module/src/main/groovy/com/eastwood/tools/plugins/core/Utils.groovy @@ -0,0 +1,57 @@ +package com.eastwood.tools.plugins.core + +import org.gradle.api.Project +import org.w3c.dom.Element + +import javax.xml.parsers.DocumentBuilderFactory + +class Utils { + + static String upperCase(String str) { + char[] ch = str.toCharArray() + if (ch[0] >= 'a' && ch[0] <= 'z') { + ch[0] -= 32 + } + return String.valueOf(ch) + } + + static String getAndroidManifestPackageName(File androidManifest) { + def builderFactory = DocumentBuilderFactory.newInstance() + builderFactory.setNamespaceAware(true) + Element manifestXml = builderFactory.newDocumentBuilder().parse(androidManifest).documentElement + return manifestXml.getAttribute("package") + } + + static MicroModule buildMicroModule(Project project, String microModulePath) { + String[] pathElements = removeTrailingColon(microModulePath).split(":") + int pathElementsLen = pathElements.size() + File parentMicroModuleDir = project.projectDir + for (int j = 0; j < pathElementsLen; j++) { + parentMicroModuleDir = new File(parentMicroModuleDir, pathElements[j]) + } + File microModuleDir = parentMicroModuleDir.canonicalFile + String microModuleName = microModuleDir.absolutePath.replace(project.projectDir.absolutePath, "") + if (File.separator == "\\") { + microModuleName = microModuleName.replaceAll("\\\\", ":") + } else { + microModuleName = microModuleName.replaceAll("/", ":") + } + // in windows and mac system, the file name is not case sensitive, micro-module name may not match file name but run correctly + // in linux system(as ci server), this case is wrong + // add this condition to find this error in windows + if (!microModuleDir.exists() || microModuleName != microModulePath) { + System.err.println("microModuleDir:" + microModuleDir.getAbsolutePath() + " not exit, or module name and file name case sensitivity wrong") + return null + } + MicroModule microModule = new MicroModule() + microModule.name = microModuleName + microModule.microModuleDir = microModuleDir + return microModule + } + + private static String removeTrailingColon(String microModulePath) { + return microModulePath.startsWith(":") ? microModulePath.substring(1) : microModulePath + } + + +} diff --git a/micro-module/src/main/groovy/com/eastwood/tools/plugins/core/check/CheckManifest.groovy b/micro-module/src/main/groovy/com/eastwood/tools/plugins/core/check/CheckManifest.groovy new file mode 100644 index 0000000..baf523c --- /dev/null +++ b/micro-module/src/main/groovy/com/eastwood/tools/plugins/core/check/CheckManifest.groovy @@ -0,0 +1,132 @@ +package com.eastwood.tools.plugins.core.check + +import org.w3c.dom.Document +import org.w3c.dom.Element +import org.w3c.dom.NodeList + +import javax.xml.parsers.DocumentBuilderFactory +import javax.xml.transform.OutputKeys +import javax.xml.transform.Transformer +import javax.xml.transform.TransformerFactory +import javax.xml.transform.dom.DOMSource +import javax.xml.transform.stream.StreamResult + +class CheckManifest { + + Document document + Element rootElement + + String packageName + Map lastModifiedResourcesMap + Map lastModifiedClassesMap + + void load(File sourceFile) { + if (!sourceFile.exists()) return + DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance() + document = builderFactory.newDocumentBuilder().parse(sourceFile) + rootElement = document.documentElement + packageName = rootElement.getAttribute("package") + } + + void setResourcesLastModified(long lastModified) { + resourcesLastModified = lastModified + } + + Map getResourcesMap() { + if (lastModifiedResourcesMap != null) return lastModifiedResourcesMap + + lastModifiedResourcesMap = new HashMap<>() + if (rootElement == null) return lastModifiedResourcesMap + + NodeList resourcesNodeList = rootElement.getElementsByTagName("resources") + if (resourcesNodeList.length == 0) { + return lastModifiedResourcesMap + } + Element resourcesElement = (Element) resourcesNodeList.item(0) + NodeList fileNodeList = resourcesElement.getElementsByTagName("file") + for (int i = 0; i < fileNodeList.getLength(); i++) { + Element fileElement = (Element) fileNodeList.item(i) + MicroModuleFile microModuleFile = new MicroModuleFile() + microModuleFile.name = fileElement.getAttribute("name") + microModuleFile.microModuleName = fileElement.getAttribute("microModuleName") + microModuleFile.path = fileElement.getAttribute("path") + microModuleFile.lastModified = fileElement.getAttribute("lastModified").toLong() + lastModifiedResourcesMap.put(microModuleFile.path, microModuleFile) + } + return lastModifiedResourcesMap + } + + Map getClassesMap() { + if (lastModifiedClassesMap != null) return lastModifiedClassesMap + + lastModifiedClassesMap = new HashMap<>() + if (rootElement == null) return lastModifiedClassesMap + + NodeList classesNodeList = rootElement.getElementsByTagName("classes") + if (classesNodeList.length == 0) { + return lastModifiedClassesMap + } + Element classesElement = (Element) classesNodeList.item(0) + NodeList fileNodeList = classesElement.getElementsByTagName("file") + for (int i = 0; i < fileNodeList.getLength(); i++) { + Element fileElement = (Element) fileNodeList.item(i) + MicroModuleFile microModuleFile = new MicroModuleFile() + microModuleFile.name = fileElement.getAttribute("name") + microModuleFile.microModuleName = fileElement.getAttribute("microModuleName") + microModuleFile.path = fileElement.getAttribute("path") + microModuleFile.lastModified = fileElement.getAttribute("lastModified").toLong() + lastModifiedClassesMap.put(microModuleFile.path, microModuleFile) + } + return lastModifiedClassesMap + } + + void save(File destFile) { + DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance() + Document documentTemp = builderFactory.newDocumentBuilder().newDocument() + Element microModuleXmlTemp = documentTemp.createElement("micro-module") + microModuleXmlTemp.setAttribute("package", packageName) + // resources + Element resourcesElement = documentTemp.createElement("resources") + microModuleXmlTemp.appendChild(resourcesElement) + if (lastModifiedResourcesMap != null) { + lastModifiedResourcesMap.each { + MicroModuleFile resourceFile = it.value + Element fileElement = documentTemp.createElement("file") + fileElement.setAttribute("name", resourceFile.name) + fileElement.setAttribute("path", resourceFile.path) + fileElement.setAttribute("lastModified", resourceFile.lastModified.toString()) + fileElement.setAttribute("microModuleName", resourceFile.microModuleName) + resourcesElement.appendChild(fileElement) + } + } + + // classes + if (lastModifiedClassesMap != null) { + Element classesElement = documentTemp.createElement("classes") + microModuleXmlTemp.appendChild(classesElement) + lastModifiedClassesMap.each { + MicroModuleFile resourceFile = it.value + Element fileElement = documentTemp.createElement("file") + fileElement.setAttribute("name", resourceFile.name) + fileElement.setAttribute("path", resourceFile.path) + fileElement.setAttribute("lastModified", resourceFile.lastModified.toString()) + fileElement.setAttribute("microModuleName", resourceFile.microModuleName) + classesElement.appendChild(fileElement) + } + microModuleXmlTemp.appendChild(classesElement) + } else if (rootElement != null) { + NodeList classesNodeList = rootElement.getElementsByTagName("classes") + if (classesNodeList.length == 1) { + Element classesElement = (Element) classesNodeList.item(0) + microModuleXmlTemp.appendChild(documentTemp.importNode(classesElement, true)) + } + } + // save + Transformer transformer = TransformerFactory.newInstance().newTransformer() + transformer.setOutputProperty(OutputKeys.INDENT, "yes") + transformer.setOutputProperty(OutputKeys.CDATA_SECTION_ELEMENTS, "yes") + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2") + transformer.transform(new DOMSource(microModuleXmlTemp), new StreamResult(destFile)) + } + +} \ No newline at end of file diff --git a/micro-module/src/main/groovy/com/eastwood/tools/plugins/core/check/CodeChecker.groovy b/micro-module/src/main/groovy/com/eastwood/tools/plugins/core/check/CodeChecker.groovy new file mode 100644 index 0000000..b07c65e --- /dev/null +++ b/micro-module/src/main/groovy/com/eastwood/tools/plugins/core/check/CodeChecker.groovy @@ -0,0 +1,367 @@ +package com.eastwood.tools.plugins.core.check + +import com.eastwood.tools.plugins.core.MicroModule +import com.eastwood.tools.plugins.core.MicroModuleInfo +import com.eastwood.tools.plugins.core.ProductFlavorInfo +import com.eastwood.tools.plugins.core.Utils +import org.gradle.api.GradleScriptException +import org.gradle.api.Project +import org.w3c.dom.Element +import org.w3c.dom.NodeList + +class CodeChecker { + + Project project + MicroModuleInfo microModuleInfo + ProductFlavorInfo productFlavorInfo + + String buildType + String productFlavor + + CheckManifest checkManifest + ResourceMerged resourceMerged + + String errorMessage = "" + String lineSeparator = System.getProperty("line.separator") + + Map> microModulePackageNameMap + + CodeChecker(Project project, MicroModuleInfo microModuleInfo, ProductFlavorInfo productFlavorInfo, String buildType, String productFlavor) { + this.project = project + this.microModuleInfo = microModuleInfo + this.productFlavorInfo = productFlavorInfo + this.buildType = buildType + this.productFlavor = productFlavor + this.checkManifest = getModuleCheckManifest() + } + + void checkResources(String mergeResourcesTaskName, List sourceFolders) { + resourceMerged = new ResourceMerged() + if (!resourceMerged.load(project.projectDir, mergeResourcesTaskName)) { + return + } + + List resourceNodeLists = resourceMerged.getResourcesNodeList(sourceFolders) + List modifiedResourcesList = getModifiedResourcesList(resourceNodeLists) + if (modifiedResourcesList.size() == 0) { + return + } + handleModifiedResources(modifiedResourcesList) + if (errorMessage != "") { + throw new GradleScriptException(errorMessage, null) + } + + def manifest = new File(microModuleInfo.mainMicroModule.microModuleDir, "src/main/AndroidManifest.xml") + String packageName = Utils.getAndroidManifestPackageName(manifest) + checkManifest.packageName = packageName + saveModuleCheckManifest() + } + + List getModifiedResourcesList(List resourcesNodeList) { + Map lastModifiedResourcesMap = checkManifest.getResourcesMap() + List modifiedResourcesList = new ArrayList<>() + if (resourcesNodeList == null || resourcesNodeList.length == 0) return modifiedResourcesList + + resourcesNodeList.each { + for (int i = 0; i < it.getLength(); i++) { + Element resourcesElement = (Element) it.item(i) + NodeList fileNodeList = resourcesElement.getElementsByTagName("file") + for (int j = 0; j < fileNodeList.getLength(); j++) { + Element fileElement = (Element) fileNodeList.item(j) + String filePath = fileElement.getAttribute("path") + if (filePath != null && filePath.endsWith(".xml")) { + File file = project.file(filePath) + MicroModuleFile resourceFile = lastModifiedResourcesMap.get(filePath) + def currentModified = file.lastModified() + if (resourceFile == null || resourceFile.lastModified.longValue() < currentModified) { + modifiedResourcesList.add(file) + + if (resourceFile == null) { + resourceFile = new MicroModuleFile() + resourceFile.name = file.name + resourceFile.path = filePath + resourceFile.microModuleName = getMicroModuleName(filePath) + lastModifiedResourcesMap.put(filePath, resourceFile) + } + resourceFile.lastModified = currentModified + } + } + } + } + } + + return modifiedResourcesList + } + + void handleModifiedResources(List modifiedResourcesList) { + Map resourcesMap = resourceMerged.getResourcesMap() + def resourcesPattern = /@(dimen|drawable|color|string|style|id|mipmap|layout)\/[A-Za-z0-9_]+/ + modifiedResourcesList.each { + String text = it.text + List textLines = text.readLines() + def matcher = (text =~ resourcesPattern) + def absolutePath = it.absolutePath + def microModuleName = getMicroModuleName(absolutePath) + while (matcher.find()) { + 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)) { + List lines = textLines.findIndexValues { it.contains(find) } + lines.each { + def lineIndex = it.intValue() + def lineContext = textLines.get(lineIndex).trim() + if (lineContext.startsWith("