From 0e6bb79e698598eebb2e8d6e4c4ee824a32797d4 Mon Sep 17 00:00:00 2001 From: johnsonlee Date: Tue, 23 Apr 2019 17:28:51 +0800 Subject: [PATCH] init --- .gitignore | 46 +++ CONTRIBUTING.md | 30 ++ LICENSE.txt | 201 +++++++++++ README.md | 56 +++ booster-android-api/build.gradle | 2 + .../src/main/java/android/app/Activity.java | 4 + .../main/java/android/app/ActivityThread.java | 17 + .../main/java/android/app/Application.java | 31 ++ .../android/content/BroadcastReceiver.java | 4 + .../main/java/android/content/Context.java | 28 ++ .../java/android/content/ContextWrapper.java | 63 ++++ .../src/main/java/android/content/Intent.java | 4 + .../java/android/content/IntentFilter.java | 4 + .../android/content/SharedPreferences.java | 12 + .../android/content/res/AssetManager.java | 16 + .../java/android/content/res/Resources.java | 21 ++ .../android/graphics/drawable/Drawable.java | 9 + .../java/android/media/AudioAttributes.java | 6 + .../main/java/android/media/MediaPlayer.java | 37 ++ .../java/android/media/MediaTimeProvider.java | 4 + .../android/media/SubtitleController.java | 17 + .../src/main/java/android/net/Uri.java | 4 + .../src/main/java/android/os/AsyncTask.java | 22 ++ .../src/main/java/android/os/Binder.java | 4 + .../src/main/java/android/os/Build.java | 326 ++++++++++++++++++ .../src/main/java/android/os/Bundle.java | 4 + .../java/android/os/DeadObjectException.java | 13 + .../java/android/os/DeadSystemException.java | 9 + .../src/main/java/android/os/Debug.java | 13 + .../src/main/java/android/os/Handler.java | 37 ++ .../main/java/android/os/HandlerThread.java | 25 ++ .../src/main/java/android/os/IBinder.java | 4 + .../src/main/java/android/os/IInterface.java | 7 + .../src/main/java/android/os/Looper.java | 33 ++ .../src/main/java/android/os/Message.java | 10 + .../main/java/android/os/MessageQueue.java | 17 + .../src/main/java/android/os/Parcelable.java | 4 + .../src/main/java/android/os/Process.java | 15 + .../main/java/android/os/RemoteException.java | 7 + .../src/main/java/android/os/SystemClock.java | 45 +++ .../java/android/os/SystemProperties.java | 29 ++ .../java/android/util/AndroidException.java | 4 + .../android/util/AndroidRuntimeException.java | 21 ++ .../src/main/java/android/util/ArrayMap.java | 75 ++++ .../main/java/android/util/AttributeSet.java | 4 + .../src/main/java/android/util/Log.java | 276 +++++++++++++++ .../main/java/android/view/InputEvent.java | 6 + .../src/main/java/android/view/KeyEvent.java | 10 + .../main/java/android/view/SurfaceHolder.java | 4 + .../src/main/java/android/view/View.java | 7 + .../src/main/java/android/view/ViewDebug.java | 8 + .../src/main/java/android/view/ViewGroup.java | 43 +++ .../main/java/android/view/ViewManager.java | 4 + .../main/java/android/view/ViewParent.java | 4 + .../java/android/view/ViewTreeObserver.java | 145 ++++++++ .../main/java/android/view/WindowManager.java | 15 + .../AccessibilityEventSource.java | 4 + .../java/android/webkit/CookieManager.java | 4 + .../android/webkit/IWebViewUpdateService.java | 9 + .../main/java/android/webkit/WebSettings.java | 11 + .../main/java/android/webkit/WebStorage.java | 4 + .../src/main/java/android/webkit/WebView.java | 96 ++++++ .../java/android/webkit/WebViewDatabase.java | 4 + .../java/android/webkit/WebViewFactory.java | 13 + .../webkit/WebViewFactoryProvider.java | 4 + .../java/android/widget/AbsoluteLayout.java | 4 + .../src/main/java/android/widget/Toast.java | 9 + booster-android-bugfix-toast/build.gradle | 7 + .../booster/android/widget/Toast.java | 54 +++ booster-android-bugfix/build.gradle | 6 + .../android/bugfix/CaughtCallback.java | 23 ++ .../android/bugfix/CaughtRunnable.java | 19 + .../booster/android/bugfix/Constants.java | 7 + .../booster/android/bugfix/Reflection.java | 225 ++++++++++++ booster-android-gradle-api/build.gradle | 8 + .../didiglobal/booster/gradle/VariantScope.kt | 67 ++++ booster-android-gradle-v3_0/build.gradle | 7 + .../booster/gradle/VariantScopeV30.java | 94 +++++ booster-android-gradle-v3_2/build.gradle | 7 + .../booster/gradle/VariantScopeV32.java | 95 +++++ booster-gradle-base/build.gradle | 10 + .../didiglobal/booster/gradle/BaseVariant.kt | 30 ++ .../com/didiglobal/booster/gradle/Project.kt | 13 + .../booster/gradle/ResolvedArtifactResults.kt | 82 +++++ .../booster/gradle/TransformInvocation.kt | 63 ++++ .../booster/util/ComponentHandler.kt | 36 ++ booster-gradle-plugin/build.gradle | 12 + .../booster/gradle/BoosterAppTransform.kt | 15 + .../booster/gradle/BoosterLibTransform.kt | 15 + .../booster/gradle/BoosterPlugin.kt | 46 +++ .../booster/gradle/BoosterTransform.kt | 58 ++++ .../gradle/BoosterTransformInvocation.kt | 158 +++++++++ .../com.didiglobal.booster.properties | 1 + booster-kotlinx/build.gradle | 1 + .../didiglobal/booster/kotlinx/FileTree.kt | 42 +++ .../didiglobal/booster/kotlinx/Wildcard.kt | 119 +++++++ .../com/didiglobal/booster/kotlinx/ansi.kt | 44 +++ .../didiglobal/booster/kotlinx/collections.kt | 30 ++ .../com/didiglobal/booster/kotlinx/io.kt | 188 ++++++++++ .../didiglobal/booster/kotlinx/primitive.kt | 29 ++ .../com/didiglobal/booster/kotlinx/text.kt | 23 ++ .../didiglobal/booster/kotlinx/FileTest.kt | 32 ++ .../booster/kotlinx/WildcardTest.kt | 15 + booster-task-all/build.gradle | 5 + booster-task-artifact/build.gradle | 9 + .../task/artifact/ArtifactVariantProcessor.kt | 22 ++ .../task/artifact/ArtifactsResolver.kt | 23 ++ booster-task-dependency/build.gradle | 9 + .../booster/task/dependency/CheckSnapshot.kt | 29 ++ .../dependency/DependencyVariantProcessor.kt | 23 ++ booster-task-permission/build.gradle | 9 + .../task/permission/PermissionExtractor.kt | 88 +++++ .../task/permission/PermissionUsageHandler.kt | 18 + .../permission/PermissionVariantProcessor.kt | 22 ++ booster-task-spi/build.gradle | 6 + .../booster/task/spi/VariantProcessor.kt | 9 + booster-transform-all/build.gradle | 6 + booster-transform-asm/build.gradle | 14 + .../transform/asm/ClassTransformer.java | 29 ++ .../booster/transform/asm/AsmTransformer.kt | 45 +++ .../booster/transform/asm/ClassNode.kt | 9 + .../booster/transform/asm/InsnList.kt | 27 ++ booster-transform-bugfix-toast/build.gradle | 10 + .../bugfix/toast/ToastBugfixTransformer.kt | 36 ++ .../toast/ToastBugfixVariantProcessor.kt | 18 + booster-transform-lint/build.gradle | 7 + .../booster/transform/lint/EntryPoint.kt | 46 +++ .../booster/transform/lint/LintTransformer.kt | 182 ++++++++++ .../booster/transform/lint/graph/CallGraph.kt | 109 ++++++ .../booster/transform/lint/URITest.kt | 18 + booster-transform-shrink/build.gradle | 7 + .../shrink/MalformedSymbolListException.kt | 5 + .../booster/transform/shrink/RFinder.kt | 25 ++ .../transform/shrink/ShrinkTransformer.kt | 131 +++++++ .../booster/transform/shrink/SymbolList.kt | 174 ++++++++++ .../transform/shrink/SymbolListTest.kt | 18 + .../src/test/resources/R.txt | 186 ++++++++++ booster-transform-spi/build.gradle | 1 + .../booster/transform/TransformListener.java | 18 + .../booster/transform/ArtifactManager.kt | 31 ++ .../booster/transform/TransformContext.kt | 67 ++++ .../booster/transform/Transformer.kt | 21 ++ booster-transform-usage/build.gradle | 7 + .../transform/usage/UsageTransformer.kt | 54 +++ booster-transform-util/build.gradle | 6 + .../booster/transform/util/transform.kt | 71 ++++ .../booster/transform/FileTransformTest.kt | 36 ++ build.gradle | 118 +++++++ buildSrc/build.gradle | 35 ++ .../booster/buildprops/BuildPropsGenerator.kt | 84 +++++ .../booster/buildprops/BuildPropsPlugin.kt | 83 +++++ .../buildprops/BuildPropsGeneratorTest.kt | 14 + gradle/booster.gradle | 39 +++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54329 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 172 +++++++++ gradlew.bat | 84 +++++ license/3rdparty/asm/LICENSE.txt | 27 ++ license/README.md | 27 ++ settings.gradle | 22 ++ 160 files changed, 6105 insertions(+) create mode 100644 .gitignore create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 booster-android-api/build.gradle create mode 100644 booster-android-api/src/main/java/android/app/Activity.java create mode 100644 booster-android-api/src/main/java/android/app/ActivityThread.java create mode 100644 booster-android-api/src/main/java/android/app/Application.java create mode 100644 booster-android-api/src/main/java/android/content/BroadcastReceiver.java create mode 100644 booster-android-api/src/main/java/android/content/Context.java create mode 100644 booster-android-api/src/main/java/android/content/ContextWrapper.java create mode 100644 booster-android-api/src/main/java/android/content/Intent.java create mode 100644 booster-android-api/src/main/java/android/content/IntentFilter.java create mode 100644 booster-android-api/src/main/java/android/content/SharedPreferences.java create mode 100644 booster-android-api/src/main/java/android/content/res/AssetManager.java create mode 100644 booster-android-api/src/main/java/android/content/res/Resources.java create mode 100644 booster-android-api/src/main/java/android/graphics/drawable/Drawable.java create mode 100644 booster-android-api/src/main/java/android/media/AudioAttributes.java create mode 100644 booster-android-api/src/main/java/android/media/MediaPlayer.java create mode 100644 booster-android-api/src/main/java/android/media/MediaTimeProvider.java create mode 100644 booster-android-api/src/main/java/android/media/SubtitleController.java create mode 100644 booster-android-api/src/main/java/android/net/Uri.java create mode 100644 booster-android-api/src/main/java/android/os/AsyncTask.java create mode 100644 booster-android-api/src/main/java/android/os/Binder.java create mode 100644 booster-android-api/src/main/java/android/os/Build.java create mode 100644 booster-android-api/src/main/java/android/os/Bundle.java create mode 100644 booster-android-api/src/main/java/android/os/DeadObjectException.java create mode 100644 booster-android-api/src/main/java/android/os/DeadSystemException.java create mode 100644 booster-android-api/src/main/java/android/os/Debug.java create mode 100644 booster-android-api/src/main/java/android/os/Handler.java create mode 100644 booster-android-api/src/main/java/android/os/HandlerThread.java create mode 100644 booster-android-api/src/main/java/android/os/IBinder.java create mode 100644 booster-android-api/src/main/java/android/os/IInterface.java create mode 100644 booster-android-api/src/main/java/android/os/Looper.java create mode 100644 booster-android-api/src/main/java/android/os/Message.java create mode 100644 booster-android-api/src/main/java/android/os/MessageQueue.java create mode 100644 booster-android-api/src/main/java/android/os/Parcelable.java create mode 100644 booster-android-api/src/main/java/android/os/Process.java create mode 100644 booster-android-api/src/main/java/android/os/RemoteException.java create mode 100644 booster-android-api/src/main/java/android/os/SystemClock.java create mode 100644 booster-android-api/src/main/java/android/os/SystemProperties.java create mode 100644 booster-android-api/src/main/java/android/util/AndroidException.java create mode 100644 booster-android-api/src/main/java/android/util/AndroidRuntimeException.java create mode 100644 booster-android-api/src/main/java/android/util/ArrayMap.java create mode 100644 booster-android-api/src/main/java/android/util/AttributeSet.java create mode 100644 booster-android-api/src/main/java/android/util/Log.java create mode 100644 booster-android-api/src/main/java/android/view/InputEvent.java create mode 100644 booster-android-api/src/main/java/android/view/KeyEvent.java create mode 100644 booster-android-api/src/main/java/android/view/SurfaceHolder.java create mode 100644 booster-android-api/src/main/java/android/view/View.java create mode 100644 booster-android-api/src/main/java/android/view/ViewDebug.java create mode 100644 booster-android-api/src/main/java/android/view/ViewGroup.java create mode 100644 booster-android-api/src/main/java/android/view/ViewManager.java create mode 100644 booster-android-api/src/main/java/android/view/ViewParent.java create mode 100644 booster-android-api/src/main/java/android/view/ViewTreeObserver.java create mode 100644 booster-android-api/src/main/java/android/view/WindowManager.java create mode 100644 booster-android-api/src/main/java/android/view/accessibility/AccessibilityEventSource.java create mode 100644 booster-android-api/src/main/java/android/webkit/CookieManager.java create mode 100644 booster-android-api/src/main/java/android/webkit/IWebViewUpdateService.java create mode 100644 booster-android-api/src/main/java/android/webkit/WebSettings.java create mode 100644 booster-android-api/src/main/java/android/webkit/WebStorage.java create mode 100644 booster-android-api/src/main/java/android/webkit/WebView.java create mode 100644 booster-android-api/src/main/java/android/webkit/WebViewDatabase.java create mode 100644 booster-android-api/src/main/java/android/webkit/WebViewFactory.java create mode 100644 booster-android-api/src/main/java/android/webkit/WebViewFactoryProvider.java create mode 100644 booster-android-api/src/main/java/android/widget/AbsoluteLayout.java create mode 100644 booster-android-api/src/main/java/android/widget/Toast.java create mode 100644 booster-android-bugfix-toast/build.gradle create mode 100644 booster-android-bugfix-toast/src/main/java/com/didiglobal/booster/android/widget/Toast.java create mode 100644 booster-android-bugfix/build.gradle create mode 100644 booster-android-bugfix/src/main/java/com/didiglobal/booster/android/bugfix/CaughtCallback.java create mode 100644 booster-android-bugfix/src/main/java/com/didiglobal/booster/android/bugfix/CaughtRunnable.java create mode 100644 booster-android-bugfix/src/main/java/com/didiglobal/booster/android/bugfix/Constants.java create mode 100644 booster-android-bugfix/src/main/java/com/didiglobal/booster/android/bugfix/Reflection.java create mode 100644 booster-android-gradle-api/build.gradle create mode 100644 booster-android-gradle-api/src/main/kotlin/com/didiglobal/booster/gradle/VariantScope.kt create mode 100644 booster-android-gradle-v3_0/build.gradle create mode 100644 booster-android-gradle-v3_0/src/main/java/com/didiglobal/booster/gradle/VariantScopeV30.java create mode 100644 booster-android-gradle-v3_2/build.gradle create mode 100644 booster-android-gradle-v3_2/src/main/java/com/didiglobal/booster/gradle/VariantScopeV32.java create mode 100644 booster-gradle-base/build.gradle create mode 100644 booster-gradle-base/src/main/kotlin/com/didiglobal/booster/gradle/BaseVariant.kt create mode 100644 booster-gradle-base/src/main/kotlin/com/didiglobal/booster/gradle/Project.kt create mode 100644 booster-gradle-base/src/main/kotlin/com/didiglobal/booster/gradle/ResolvedArtifactResults.kt create mode 100644 booster-gradle-base/src/main/kotlin/com/didiglobal/booster/gradle/TransformInvocation.kt create mode 100644 booster-gradle-base/src/main/kotlin/com/didiglobal/booster/util/ComponentHandler.kt create mode 100644 booster-gradle-plugin/build.gradle create mode 100644 booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterAppTransform.kt create mode 100644 booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterLibTransform.kt create mode 100644 booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterPlugin.kt create mode 100644 booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterTransform.kt create mode 100644 booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterTransformInvocation.kt create mode 100644 booster-gradle-plugin/src/main/resources/META-INF/gradle-plugins/com.didiglobal.booster.properties create mode 100644 booster-kotlinx/build.gradle create mode 100644 booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/FileTree.kt create mode 100644 booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/Wildcard.kt create mode 100644 booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/ansi.kt create mode 100644 booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/collections.kt create mode 100644 booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/io.kt create mode 100644 booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/primitive.kt create mode 100644 booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/text.kt create mode 100644 booster-kotlinx/src/test/kotlin/com/didiglobal/booster/kotlinx/FileTest.kt create mode 100644 booster-kotlinx/src/test/kotlin/com/didiglobal/booster/kotlinx/WildcardTest.kt create mode 100644 booster-task-all/build.gradle create mode 100644 booster-task-artifact/build.gradle create mode 100644 booster-task-artifact/src/main/kotlin/com/didiglobal/booster/task/artifact/ArtifactVariantProcessor.kt create mode 100644 booster-task-artifact/src/main/kotlin/com/didiglobal/booster/task/artifact/ArtifactsResolver.kt create mode 100644 booster-task-dependency/build.gradle create mode 100644 booster-task-dependency/src/main/kotlin/com/didiglobal/booster/task/dependency/CheckSnapshot.kt create mode 100644 booster-task-dependency/src/main/kotlin/com/didiglobal/booster/task/dependency/DependencyVariantProcessor.kt create mode 100644 booster-task-permission/build.gradle create mode 100644 booster-task-permission/src/main/kotlin/com/didiglobal/booster/task/permission/PermissionExtractor.kt create mode 100644 booster-task-permission/src/main/kotlin/com/didiglobal/booster/task/permission/PermissionUsageHandler.kt create mode 100644 booster-task-permission/src/main/kotlin/com/didiglobal/booster/task/permission/PermissionVariantProcessor.kt create mode 100644 booster-task-spi/build.gradle create mode 100644 booster-task-spi/src/main/kotlin/com/didiglobal/booster/task/spi/VariantProcessor.kt create mode 100644 booster-transform-all/build.gradle create mode 100644 booster-transform-asm/build.gradle create mode 100644 booster-transform-asm/src/main/java/com/didiglobal/booster/transform/asm/ClassTransformer.java create mode 100644 booster-transform-asm/src/main/kotlin/com/didiglobal/booster/transform/asm/AsmTransformer.kt create mode 100644 booster-transform-asm/src/main/kotlin/com/didiglobal/booster/transform/asm/ClassNode.kt create mode 100644 booster-transform-asm/src/main/kotlin/com/didiglobal/booster/transform/asm/InsnList.kt create mode 100644 booster-transform-bugfix-toast/build.gradle create mode 100644 booster-transform-bugfix-toast/src/main/kotlin/com/didiglobal/booster/transform/bugfix/toast/ToastBugfixTransformer.kt create mode 100644 booster-transform-bugfix-toast/src/main/kotlin/com/didiglobal/booster/transform/bugfix/toast/ToastBugfixVariantProcessor.kt create mode 100644 booster-transform-lint/build.gradle create mode 100644 booster-transform-lint/src/main/kotlin/com/didiglobal/booster/transform/lint/EntryPoint.kt create mode 100644 booster-transform-lint/src/main/kotlin/com/didiglobal/booster/transform/lint/LintTransformer.kt create mode 100644 booster-transform-lint/src/main/kotlin/com/didiglobal/booster/transform/lint/graph/CallGraph.kt create mode 100644 booster-transform-lint/src/test/kotlin/com/didiglobal/booster/transform/lint/URITest.kt create mode 100644 booster-transform-shrink/build.gradle create mode 100644 booster-transform-shrink/src/main/kotlin/com/didiglobal/booster/transform/shrink/MalformedSymbolListException.kt create mode 100644 booster-transform-shrink/src/main/kotlin/com/didiglobal/booster/transform/shrink/RFinder.kt create mode 100644 booster-transform-shrink/src/main/kotlin/com/didiglobal/booster/transform/shrink/ShrinkTransformer.kt create mode 100644 booster-transform-shrink/src/main/kotlin/com/didiglobal/booster/transform/shrink/SymbolList.kt create mode 100644 booster-transform-shrink/src/test/kotlin/com/didiglobal/booster/transform/shrink/SymbolListTest.kt create mode 100644 booster-transform-shrink/src/test/resources/R.txt create mode 100644 booster-transform-spi/build.gradle create mode 100644 booster-transform-spi/src/main/java/com/didiglobal/booster/transform/TransformListener.java create mode 100644 booster-transform-spi/src/main/kotlin/com/didiglobal/booster/transform/ArtifactManager.kt create mode 100644 booster-transform-spi/src/main/kotlin/com/didiglobal/booster/transform/TransformContext.kt create mode 100644 booster-transform-spi/src/main/kotlin/com/didiglobal/booster/transform/Transformer.kt create mode 100644 booster-transform-usage/build.gradle create mode 100644 booster-transform-usage/src/main/kotlin/com/didiglobal/booster/transform/usage/UsageTransformer.kt create mode 100644 booster-transform-util/build.gradle create mode 100644 booster-transform-util/src/main/kotlin/com/didiglobal/booster/transform/util/transform.kt create mode 100644 booster-transform-util/src/test/kotlin/com/didiglobal/booster/transform/FileTransformTest.kt create mode 100644 build.gradle create mode 100644 buildSrc/build.gradle create mode 100644 buildSrc/src/main/kotlin/com/didiglobal/booster/buildprops/BuildPropsGenerator.kt create mode 100644 buildSrc/src/main/kotlin/com/didiglobal/booster/buildprops/BuildPropsPlugin.kt create mode 100644 buildSrc/src/test/kotlin/com/didiglobal/booster/buildprops/BuildPropsGeneratorTest.kt create mode 100644 gradle/booster.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 license/3rdparty/asm/LICENSE.txt create mode 100644 license/README.md create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..3a1633699 --- /dev/null +++ b/.gitignore @@ -0,0 +1,46 @@ +# Generated by Mac OS X +.DS_Store + +# Generated by VIM +.*.swp + +# Generated by Gradle +.gradle +build/ +out/ + +# Generated by IDEA +.idea +*.iml + +# Generated by Eclipse +.metadata +.settings +.project +.classpath +/bin/ + +# Generated by Maven +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties + +# Generated by IDE +local.properties + +# Node & NPM +node_modules + +# GitBook +_book + +# Misc +*.log +*.bak + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..77ccbc488 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,30 @@ +# Contribution Guideline + +Thanks for considering to contribute this project. All issues and pull requests are highly appreciated. + +## Pull Requests + +Before sending pull request to this project, please read and follow guidelines below. + +1. Branch: We only accept pull request on `master` branch. +2. Coding style: Follow the coding style used in this project. +3. Commit message: Use English and be aware of your spell. +4. Test: Make sure to test your code. + +Add device mode, API version, related log, screenshots and other related information in your pull request if possible. + +NOTE: We assume all your contribution can be licensed under the [Apache License 2.0](https://github.com/didi/booster/blob/master/LICENSE). + +## Issues + +We love clearly described issues. :) + +Following information can help us to resolve the issue faster. + +* Device mode and hardware information. +* Operating System information. +* API version. +* Logs. +* Screenshots. +* Steps to reproduce the issue. + diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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/README.md b/README.md new file mode 100644 index 000000000..ecf91fb1b --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +# Booster + +![GitHub](https://img.shields.io/github/license/didi/booster.svg?style=for-the-badge) +![Travis (.org)](https://img.shields.io/travis/didi/booster.svg?style=for-the-badge) +![GitHub contributors](https://img.shields.io/github/release/didi/booster.svg?style=for-the-badge) + +Booster is an easy-to-use, lightweight, powerful and extensible optimization toolkit designed specially for mobile applications. Using the dynamic discovering and loading mechanism, booster provides the ability for customizing. + +## Prerequisite + +- Gradle version 4.1+ +- Android Gradle Plugin version 3.0+ + +## Getting Started + +The plugin can be added to the buildscript classpath and applied: + +```groovy +buildscript { + ext.booster_version = '0.1.0' + repositories { + google() + mavenCentral() + jcenter() + } + dependencies { + classpath "com.didiglobal.booster:booster-gradle-plugin:$booster_version" + classpath "com.didiglobal.booster:booster-task-all:$booster_version" + classpath "com.didiglobal.booster:booster-transform-all:$booster_version" + } +} + +apply plugin: 'com.android.application' +apply plugin: 'com.didiglobal.booster' +``` + +Booster is a modularized project, and the optimizer consist of gradle plugin and a dozen of transformers. +This means, at least one transformer must be *explicitly* depended to have the desired effect. +Conveniently, the `booster-transform-all` module can be depended to enable all optimization options. + +In addition, Booster provides a collection of [Gradle Task](https://docs.gradle.org/current/userguide/tutorial_using_tasks.html) to help developers be more efficient. +Conveniently, the `booster-task-all` module can be depended to enable all tasks. + +Then build an optimized package by executing the *assemble* task: + +```bash +$ ./gradlew assembleRelease +``` + +# Contributing + +Welcome to contribute by creating issues or sending pull requests. See [Contributing Guideline](./CONTRIBUTING.md) + +# License + +Booster is licensed under the [Apache License 2.0](./LICENSE.txt). diff --git a/booster-android-api/build.gradle b/booster-android-api/build.gradle new file mode 100644 index 000000000..16ddebc6c --- /dev/null +++ b/booster-android-api/build.gradle @@ -0,0 +1,2 @@ +sourceCompatibility = JavaVersion.VERSION_1_7 +targetCompatibility = JavaVersion.VERSION_1_7 diff --git a/booster-android-api/src/main/java/android/app/Activity.java b/booster-android-api/src/main/java/android/app/Activity.java new file mode 100644 index 000000000..78b5b8c90 --- /dev/null +++ b/booster-android-api/src/main/java/android/app/Activity.java @@ -0,0 +1,4 @@ +package android.app; + +public class Activity { +} diff --git a/booster-android-api/src/main/java/android/app/ActivityThread.java b/booster-android-api/src/main/java/android/app/ActivityThread.java new file mode 100644 index 000000000..5c47d1b24 --- /dev/null +++ b/booster-android-api/src/main/java/android/app/ActivityThread.java @@ -0,0 +1,17 @@ +package android.app; + +public class ActivityThread { + + public static ActivityThread currentActivityThread() { + throw new RuntimeException("Stub!"); + } + + public static Application currentApplication() { + throw new RuntimeException("Stub!"); + } + + public static boolean isSystem() { + throw new RuntimeException("Stub!"); + } + +} diff --git a/booster-android-api/src/main/java/android/app/Application.java b/booster-android-api/src/main/java/android/app/Application.java new file mode 100644 index 000000000..4c70bfff6 --- /dev/null +++ b/booster-android-api/src/main/java/android/app/Application.java @@ -0,0 +1,31 @@ +package android.app; + +import android.content.Context; +import android.content.ContextWrapper; +import android.os.Bundle; + +public class Application extends ContextWrapper { + + public interface ActivityLifecycleCallbacks { + void onActivityCreated(Activity activity, Bundle savedInstanceState); + void onActivityDestroyed(Activity activity); + void onActivityPaused(Activity activity); + void onActivityResumed(Activity activity); + void onActivitySaveInstanceState(Activity activity, Bundle outState); + void onActivityStarted(Activity activity); + void onActivityStopped(Activity activity); + } + + public Application() { + super(null); + } + + public void registerActivityLifecycleCallbacks(ActivityLifecycleCallbacks callbacks) { + throw new RuntimeException("Stub!"); + } + + final void attach(final Context context) { + attachBaseContext(context); + } + +} diff --git a/booster-android-api/src/main/java/android/content/BroadcastReceiver.java b/booster-android-api/src/main/java/android/content/BroadcastReceiver.java new file mode 100644 index 000000000..b5e61ebb6 --- /dev/null +++ b/booster-android-api/src/main/java/android/content/BroadcastReceiver.java @@ -0,0 +1,4 @@ +package android.content; + +public abstract class BroadcastReceiver { +} diff --git a/booster-android-api/src/main/java/android/content/Context.java b/booster-android-api/src/main/java/android/content/Context.java new file mode 100644 index 000000000..4a2771c6b --- /dev/null +++ b/booster-android-api/src/main/java/android/content/Context.java @@ -0,0 +1,28 @@ +package android.content; + +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.os.Handler; +import android.os.Looper; + +public abstract class Context { + + public abstract AssetManager getAssets(); + + public abstract Resources getResources(); + + public abstract Looper getMainLooper(); + + public abstract Intent registerReceiver(final BroadcastReceiver receiver, final IntentFilter filter); + + public abstract Intent registerReceiver(final BroadcastReceiver receiver, final IntentFilter filter, final int flags); + + public abstract Intent registerReceiver(final BroadcastReceiver receiver, final IntentFilter filter, final String perm, final Handler handler); + + public abstract Intent registerReceiver(final BroadcastReceiver receiver, final IntentFilter filter, final String perm, final Handler handler, final int flags); + + public abstract void unregisterReceiver(final BroadcastReceiver receiver); + + public abstract Object getSystemService(final String name); + +} diff --git a/booster-android-api/src/main/java/android/content/ContextWrapper.java b/booster-android-api/src/main/java/android/content/ContextWrapper.java new file mode 100644 index 000000000..dc858c826 --- /dev/null +++ b/booster-android-api/src/main/java/android/content/ContextWrapper.java @@ -0,0 +1,63 @@ +package android.content; + +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.os.Handler; +import android.os.Looper; + +public class ContextWrapper extends Context { + + public ContextWrapper(final Context base) { + throw new RuntimeException("Stub!"); + } + + protected void attachBaseContext(final Context base) { + throw new RuntimeException("Stub!"); + } + + @Override + public AssetManager getAssets() { + throw new RuntimeException("Stub!"); + } + + @Override + public Resources getResources() { + throw new RuntimeException("Stub!"); + } + + @Override + public Looper getMainLooper() { + throw new RuntimeException("Stub!"); + } + + @Override + public Intent registerReceiver(final BroadcastReceiver receiver, final IntentFilter filter) { + throw new RuntimeException("Stub!"); + } + + @Override + public Intent registerReceiver(final BroadcastReceiver receiver, final IntentFilter filter, final int flags) { + throw new RuntimeException("Stub!"); + } + + @Override + public Intent registerReceiver(final BroadcastReceiver receiver, final IntentFilter filter, final String perm, final Handler handler) { + throw new RuntimeException("Stub!"); + } + + @Override + public Intent registerReceiver(final BroadcastReceiver receiver, final IntentFilter filter, final String perm, final Handler handler, final int flags) { + throw new RuntimeException("Stub!"); + } + + @Override + public void unregisterReceiver(final BroadcastReceiver receiver) { + throw new RuntimeException("Stub!"); + } + + @Override + public Object getSystemService(final String name) { + throw new RuntimeException("Stub!"); + } + +} diff --git a/booster-android-api/src/main/java/android/content/Intent.java b/booster-android-api/src/main/java/android/content/Intent.java new file mode 100644 index 000000000..459e57566 --- /dev/null +++ b/booster-android-api/src/main/java/android/content/Intent.java @@ -0,0 +1,4 @@ +package android.content; + +public class Intent { +} diff --git a/booster-android-api/src/main/java/android/content/IntentFilter.java b/booster-android-api/src/main/java/android/content/IntentFilter.java new file mode 100644 index 000000000..2b0c01ceb --- /dev/null +++ b/booster-android-api/src/main/java/android/content/IntentFilter.java @@ -0,0 +1,4 @@ +package android.content; + +public class IntentFilter { +} diff --git a/booster-android-api/src/main/java/android/content/SharedPreferences.java b/booster-android-api/src/main/java/android/content/SharedPreferences.java new file mode 100644 index 000000000..a77983d40 --- /dev/null +++ b/booster-android-api/src/main/java/android/content/SharedPreferences.java @@ -0,0 +1,12 @@ +package android.content; + +public interface SharedPreferences { + + public interface Editor { + + void apply(); + + boolean commit(); + } + +} diff --git a/booster-android-api/src/main/java/android/content/res/AssetManager.java b/booster-android-api/src/main/java/android/content/res/AssetManager.java new file mode 100644 index 000000000..d5f2cfcf4 --- /dev/null +++ b/booster-android-api/src/main/java/android/content/res/AssetManager.java @@ -0,0 +1,16 @@ +package android.content.res; + +import java.io.IOException; +import java.io.InputStream; + +public final class AssetManager { + + public final InputStream open(String fileName) throws IOException { + throw new RuntimeException("Stub!"); + } + + public final InputStream open(String fileName, int accessMode) throws IOException { + throw new RuntimeException("Stub!"); + } + +} diff --git a/booster-android-api/src/main/java/android/content/res/Resources.java b/booster-android-api/src/main/java/android/content/res/Resources.java new file mode 100644 index 000000000..f2d324372 --- /dev/null +++ b/booster-android-api/src/main/java/android/content/res/Resources.java @@ -0,0 +1,21 @@ +package android.content.res; + +public class Resources { + + public static class NotFoundException extends RuntimeException { + + public NotFoundException() { + throw new RuntimeException("Stub!"); + } + + public NotFoundException(String name) { + throw new RuntimeException("Stub!"); + } + + public NotFoundException(String name, Exception cause) { + throw new RuntimeException("Stub!"); + } + + } + +} diff --git a/booster-android-api/src/main/java/android/graphics/drawable/Drawable.java b/booster-android-api/src/main/java/android/graphics/drawable/Drawable.java new file mode 100644 index 000000000..4a69e6ae9 --- /dev/null +++ b/booster-android-api/src/main/java/android/graphics/drawable/Drawable.java @@ -0,0 +1,9 @@ +package android.graphics.drawable; + +public abstract class Drawable { + + public interface Callback { + } + + +} diff --git a/booster-android-api/src/main/java/android/media/AudioAttributes.java b/booster-android-api/src/main/java/android/media/AudioAttributes.java new file mode 100644 index 000000000..592d6f805 --- /dev/null +++ b/booster-android-api/src/main/java/android/media/AudioAttributes.java @@ -0,0 +1,6 @@ +package android.media; + +import android.os.Parcelable; + +public final class AudioAttributes implements Parcelable { +} diff --git a/booster-android-api/src/main/java/android/media/MediaPlayer.java b/booster-android-api/src/main/java/android/media/MediaPlayer.java new file mode 100644 index 000000000..de23e77e5 --- /dev/null +++ b/booster-android-api/src/main/java/android/media/MediaPlayer.java @@ -0,0 +1,37 @@ +package android.media; + +import android.content.Context; +import android.net.Uri; +import android.view.SurfaceHolder; + +public class MediaPlayer { + + public MediaPlayer() { + throw new RuntimeException("Stub!"); + } + + public static MediaPlayer create(final Context context, final Uri uri) { + throw new RuntimeException("Stub!"); + } + + public static MediaPlayer create(final Context context, final Uri uri, final SurfaceHolder holder) { + throw new RuntimeException("Stub!"); + } + + public static MediaPlayer create(final Context context, final Uri uri, final SurfaceHolder holder, final AudioAttributes audioAttributes, final int audioSessionId) { + throw new RuntimeException("Stub!"); + } + + public static MediaPlayer create(final Context context, final int resid) { + throw new RuntimeException("Stub!"); + } + + public static MediaPlayer create(final Context context, final int resid, final AudioAttributes audioAttributes, final int audioSessionId) { + throw new RuntimeException("Stub!"); + } + + public void setSubtitleAnchor(final SubtitleController controller, final SubtitleController.Anchor anchor) { + throw new RuntimeException("Stub!"); + } + +} diff --git a/booster-android-api/src/main/java/android/media/MediaTimeProvider.java b/booster-android-api/src/main/java/android/media/MediaTimeProvider.java new file mode 100644 index 000000000..93d84ce97 --- /dev/null +++ b/booster-android-api/src/main/java/android/media/MediaTimeProvider.java @@ -0,0 +1,4 @@ +package android.media; + +public interface MediaTimeProvider { +} diff --git a/booster-android-api/src/main/java/android/media/SubtitleController.java b/booster-android-api/src/main/java/android/media/SubtitleController.java new file mode 100644 index 000000000..1d741c41c --- /dev/null +++ b/booster-android-api/src/main/java/android/media/SubtitleController.java @@ -0,0 +1,17 @@ +package android.media; + +import android.content.Context; + +public class SubtitleController { + + public SubtitleController(final Context context, final MediaTimeProvider timeProvider, final Listener listener) { + throw new RuntimeException("Stub!"); + } + + public interface Anchor { + } + + public interface Listener { + } + +} diff --git a/booster-android-api/src/main/java/android/net/Uri.java b/booster-android-api/src/main/java/android/net/Uri.java new file mode 100644 index 000000000..415eaeda5 --- /dev/null +++ b/booster-android-api/src/main/java/android/net/Uri.java @@ -0,0 +1,4 @@ +package android.net; + +public class Uri { +} diff --git a/booster-android-api/src/main/java/android/os/AsyncTask.java b/booster-android-api/src/main/java/android/os/AsyncTask.java new file mode 100644 index 000000000..a5755d2d8 --- /dev/null +++ b/booster-android-api/src/main/java/android/os/AsyncTask.java @@ -0,0 +1,22 @@ +package android.os; + +import java.util.concurrent.Executor; + +public abstract class AsyncTask { + + private static final Executor STUB = new Executor() { + @Override + public void execute(Runnable command) { + throw new RuntimeException("Stub!"); + } + }; + + public static final Executor THREAD_POOL_EXECUTOR = STUB; + + public static final Executor SERIAL_EXECUTOR = STUB; + + public static void setDefaultExecutor(final Executor executor) { + throw new RuntimeException("Stub!"); + } + +} diff --git a/booster-android-api/src/main/java/android/os/Binder.java b/booster-android-api/src/main/java/android/os/Binder.java new file mode 100644 index 000000000..16a24e385 --- /dev/null +++ b/booster-android-api/src/main/java/android/os/Binder.java @@ -0,0 +1,4 @@ +package android.os; + +public class Binder implements IBinder { +} diff --git a/booster-android-api/src/main/java/android/os/Build.java b/booster-android-api/src/main/java/android/os/Build.java new file mode 100644 index 000000000..4a32e93f0 --- /dev/null +++ b/booster-android-api/src/main/java/android/os/Build.java @@ -0,0 +1,326 @@ +package android.os; + +public class Build { + + public static final String UNKNOWN = "unknown"; + + /** + * Either a changelist number, or a label like "M4-rc20". + */ + public static final String ID = getString("ro.build.id"); + + /** + * A build ID string meant for displaying to the user + */ + public static final String DISPLAY = getString("ro.build.display.id"); + + /** + * The name of the overall product. + */ + public static final String PRODUCT = getString("ro.product.name"); + + /** + * The name of the industrial design. + */ + public static final String DEVICE = getString("ro.product.device"); + + /** + * The name of the underlying board, like "goldfish". + */ + public static final String BOARD = getString("ro.product.board"); + + /** + * The manufacturer of the product/hardware. + */ + public static final String MANUFACTURER = getString("ro.product.manufacturer"); + + /** + * The consumer-visible brand with which the product/hardware will be associated, if any. + */ + public static final String BRAND = getString("ro.product.brand"); + + /** + * The end-user-visible name for the end product. + */ + public static final String MODEL = getString("ro.product.model"); + + /** + * The system bootloader version number. + */ + public static final String BOOTLOADER = getString("ro.bootloader"); + + /** + * The name of the hardware (from the kernel command line or /proc). + */ + public static final String HARDWARE = getString("ro.hardware"); + + /** + * Whether this build was for an emulator device. + */ + public static final boolean IS_EMULATOR = getString("ro.kernel.qemu").equals("1"); + + /** + * An ordered list of ABIs supported by this device. The most preferred ABI is the first + * element in the list. + *

+ * See {@link #SUPPORTED_32_BIT_ABIS} and {@link #SUPPORTED_64_BIT_ABIS}. + */ + public static final String[] SUPPORTED_ABIS = getStringList("ro.product.cpu.abilist", ","); + + /** + * An ordered list of 32 bit ABIs supported by this device. The most preferred ABI + * is the first element in the list. + *

+ * See {@link #SUPPORTED_ABIS} and {@link #SUPPORTED_64_BIT_ABIS}. + */ + public static final String[] SUPPORTED_32_BIT_ABIS = getStringList("ro.product.cpu.abilist32", ","); + + /** + * An ordered list of 64 bit ABIs supported by this device. The most preferred ABI + * is the first element in the list. + *

+ * See {@link #SUPPORTED_ABIS} and {@link #SUPPORTED_32_BIT_ABIS}. + */ + public static final String[] SUPPORTED_64_BIT_ABIS = getStringList("ro.product.cpu.abilist64", ","); + + public static class VERSION { + + /** + * The internal value used by the underlying source control to + * represent this build. E.g., a perforce changelist number + * or a git hash. + */ + public static final String INCREMENTAL = getString("ro.build.version.incremental"); + + /** + * The user-visible version string. E.g., "1.0" or "3.4b5". + */ + public static final String RELEASE = getString("ro.build.version.release"); + + /** + * The base OS build the product is based on. + */ + public static final String BASE_OS = SystemProperties.get("ro.build.version.base_os", ""); + + /** + * The user-visible security patch level. + */ + public static final String SECURITY_PATCH = SystemProperties.get("ro.build.version.security_patch", ""); + + /** + * The user-visible SDK version of the framework in its raw String + * representation; use {@link #SDK_INT} instead. + * + * @deprecated Use {@link #SDK_INT} to easily get this as an integer. + */ + @Deprecated + public static final String SDK = getString("ro.build.version.sdk"); + + /** + * The user-visible SDK version of the framework; its possible + * values are defined in {@link Build.VERSION_CODES}. + */ + public static final int SDK_INT = SystemProperties.getInt("ro.build.version.sdk", 0); + + /** + * The developer preview revision of a prerelease SDK. This value will always + * be 0 on production platform builds/devices. + * + *

When this value is nonzero, any new API added since the last + * officially published {@link #SDK_INT API level} is only guaranteed to be present + * on that specific preview revision. For example, an API Activity.fooBar() + * might be present in preview revision 1 but renamed or removed entirely in + * preview revision 2, which may cause an app attempting to call it to crash + * at runtime.

+ * + *

Experimental apps targeting preview APIs should check this value for + * equality (==) with the preview SDK revision they were built for + * before using any prerelease platform APIs. Apps that detect a preview SDK revision + * other than the specific one they expect should fall back to using APIs from + * the previously published API level only to avoid unwanted runtime exceptions. + *

+ */ + public static final int PREVIEW_SDK_INT = SystemProperties.getInt("ro.build.version.preview_sdk", 0); + + /** + * The current development codename, or the string "REL" if this is + * a release build. + */ + public static final String CODENAME = getString("ro.build.version.codename"); + + private static final String[] ALL_CODENAMES = getStringList("ro.build.version.all_codenames", ","); + + /** + * @hide + */ + public static final String[] ACTIVE_CODENAMES = "REL".equals(ALL_CODENAMES[0]) + ? new String[0] : ALL_CODENAMES; + + /** + * The SDK version to use when accessing resources. + * Use the current SDK version code. For every active development codename + * we are operating under, we bump the assumed resource platform version by 1. + * + * @hide + */ + public static final int RESOURCES_SDK_INT = SDK_INT + ACTIVE_CODENAMES.length; + + } + + /** + * Enumeration of the currently known SDK version codes. These are the + * values that can be found in {@link VERSION#SDK}. Version numbers + * increment monotonically with each official platform release. + */ + public static class VERSION_CODES { + + /** + * October 2008: The original, first, version of Android. Yay! + */ + public static final int BASE = 1; + + /** + * February 2009: First Android update, officially called 1.1. + */ + public static final int BASE_1_1 = 2; + + /** + * May 2009: Android 1.5. + */ + public static final int CUPCAKE = 3; + + /** + * September 2009: Android 1.6. + */ + public static final int DONUT = 4; + + /** + * November 2009: Android 2.0 + */ + public static final int ECLAIR = 5; + + /** + * December 2009: Android 2.0.1 + */ + public static final int ECLAIR_0_1 = 6; + + /** + * January 2010: Android 2.1 + */ + public static final int ECLAIR_MR1 = 7; + + /** + * June 2010: Android 2.2 + */ + public static final int FROYO = 8; + + /** + * November 2010: Android 2.3 + */ + public static final int GINGERBREAD = 9; + + /** + * February 2011: Android 2.3.3. + */ + public static final int GINGERBREAD_MR1 = 10; + + /** + * February 2011: Android 3.0. + */ + public static final int HONEYCOMB = 11; + + /** + * May 2011: Android 3.1. + */ + public static final int HONEYCOMB_MR1 = 12; + + /** + * June 2011: Android 3.2. + */ + public static final int HONEYCOMB_MR2 = 13; + + /** + * October 2011: Android 4.0. + */ + public static final int ICE_CREAM_SANDWICH = 14; + + /** + * December 2011: Android 4.0.3. + */ + public static final int ICE_CREAM_SANDWICH_MR1 = 15; + + /** + * June 2012: Android 4.1. + */ + public static final int JELLY_BEAN = 16; + + /** + * November 2012: Android 4.2, Moar jelly beans! + */ + public static final int JELLY_BEAN_MR1 = 17; + + /** + * July 2013: Android 4.3, the revenge of the beans. + */ + public static final int JELLY_BEAN_MR2 = 18; + + /** + * October 2013: Android 4.4, KitKat, another tasty treat. + */ + public static final int KITKAT = 19; + + /** + * June 2014: Android 4.4W. KitKat for watches, snacks on the run. + */ + public static final int KITKAT_WATCH = 20; + + /** + * Temporary until we completely switch to {@link #LOLLIPOP}. + */ + public static final int L = 21; + + /** + * November 2014: Lollipop. A flat one with beautiful shadows. But still tasty. + */ + public static final int LOLLIPOP = 21; + + /** + * March 2015: Lollipop with an extra sugar coating on the outside! + */ + public static final int LOLLIPOP_MR1 = 22; + + /** + * M is for Marshmallow! + */ + public static final int M = 23; + + /** + * N is for Nougat. + */ + public static final int N = 24; + + /** + * N MR1: Nougat++. + */ + public static final int N_MR1 = 25; + + /** + * O. + */ + public static final int O = 26; + } + + private static String getString(String property) { + return SystemProperties.get(property, UNKNOWN); + } + + private static String[] getStringList(String property, String separator) { + String value = SystemProperties.get(property); + if (value.isEmpty()) { + return new String[0]; + } else { + return value.split(separator); + } + } + +} diff --git a/booster-android-api/src/main/java/android/os/Bundle.java b/booster-android-api/src/main/java/android/os/Bundle.java new file mode 100644 index 000000000..49ff9ee20 --- /dev/null +++ b/booster-android-api/src/main/java/android/os/Bundle.java @@ -0,0 +1,4 @@ +package android.os; + +public class Bundle { +} diff --git a/booster-android-api/src/main/java/android/os/DeadObjectException.java b/booster-android-api/src/main/java/android/os/DeadObjectException.java new file mode 100644 index 000000000..72a86a94e --- /dev/null +++ b/booster-android-api/src/main/java/android/os/DeadObjectException.java @@ -0,0 +1,13 @@ +package android.os; + +public class DeadObjectException extends RemoteException { + + public DeadObjectException() { + throw new RuntimeException("Stub!"); + } + + public DeadObjectException(String message) { + throw new RuntimeException("Stub!"); + } + +} diff --git a/booster-android-api/src/main/java/android/os/DeadSystemException.java b/booster-android-api/src/main/java/android/os/DeadSystemException.java new file mode 100644 index 000000000..1cc6d667c --- /dev/null +++ b/booster-android-api/src/main/java/android/os/DeadSystemException.java @@ -0,0 +1,9 @@ +package android.os; + +public class DeadSystemException extends DeadObjectException { + + public DeadSystemException() { + throw new RuntimeException("Stub!"); + } + +} diff --git a/booster-android-api/src/main/java/android/os/Debug.java b/booster-android-api/src/main/java/android/os/Debug.java new file mode 100644 index 000000000..72a002a1b --- /dev/null +++ b/booster-android-api/src/main/java/android/os/Debug.java @@ -0,0 +1,13 @@ +package android.os; + +public class Debug { + + public static void startMethodTracing(String path, int bufferSize) { + throw new RuntimeException("Stub!"); + } + + public static void stopMethodTracing() { + throw new RuntimeException("Stub!"); + } + +} diff --git a/booster-android-api/src/main/java/android/os/Handler.java b/booster-android-api/src/main/java/android/os/Handler.java new file mode 100644 index 000000000..1d93f0dc8 --- /dev/null +++ b/booster-android-api/src/main/java/android/os/Handler.java @@ -0,0 +1,37 @@ +package android.os; + +public class Handler { + + public Handler() { + throw new RuntimeException("Stub!"); + } + + public Handler(final Looper looper) { + throw new RuntimeException("Stub!"); + } + + public final void removeCallbacks(final Runnable runnable) { + throw new RuntimeException("Stub!"); + } + + public final boolean post(final Runnable runnable) { + throw new RuntimeException("Stub!"); + } + + public final boolean postDelayed(final Runnable runnable, final long delayInMillis) { + throw new RuntimeException("Stub!"); + } + + public void handleMessage(final Message msg) { + throw new RuntimeException("Stub!"); + } + + public final Looper getLooper() { + throw new RuntimeException("Stub!"); + } + + public interface Callback { + public boolean handleMessage(final Message msg); + } + +} diff --git a/booster-android-api/src/main/java/android/os/HandlerThread.java b/booster-android-api/src/main/java/android/os/HandlerThread.java new file mode 100644 index 000000000..345e92ff4 --- /dev/null +++ b/booster-android-api/src/main/java/android/os/HandlerThread.java @@ -0,0 +1,25 @@ +package android.os; + +public class HandlerThread extends Thread { + + public HandlerThread(String name) { + throw new RuntimeException("Stub!"); + } + + public HandlerThread(String name, int priority) { + throw new RuntimeException("Stub!"); + } + + public Looper getLooper() { + throw new RuntimeException("Stub!"); + } + + public boolean quit() { + throw new RuntimeException("Stub!"); + } + + public boolean quitSafely() { + throw new RuntimeException("Stub!"); + } + +} diff --git a/booster-android-api/src/main/java/android/os/IBinder.java b/booster-android-api/src/main/java/android/os/IBinder.java new file mode 100644 index 000000000..03ebbfaca --- /dev/null +++ b/booster-android-api/src/main/java/android/os/IBinder.java @@ -0,0 +1,4 @@ +package android.os; + +public interface IBinder { +} diff --git a/booster-android-api/src/main/java/android/os/IInterface.java b/booster-android-api/src/main/java/android/os/IInterface.java new file mode 100644 index 000000000..46e4301b9 --- /dev/null +++ b/booster-android-api/src/main/java/android/os/IInterface.java @@ -0,0 +1,7 @@ +package android.os; + +public interface IInterface { + + public IBinder asBinder(); + +} diff --git a/booster-android-api/src/main/java/android/os/Looper.java b/booster-android-api/src/main/java/android/os/Looper.java new file mode 100644 index 000000000..a0dacaaea --- /dev/null +++ b/booster-android-api/src/main/java/android/os/Looper.java @@ -0,0 +1,33 @@ +package android.os; + +public final class Looper { + + public Thread getThread() { + throw new RuntimeException("Stub!"); + } + + public static Looper getMainLooper() { + throw new RuntimeException("Stub!"); + } + + public static Looper myLooper() { + throw new RuntimeException("Stub!"); + } + + public static void prepare() { + throw new RuntimeException("Stub!"); + } + + public void quit() { + throw new RuntimeException("Stub!"); + } + + public void quitSafely() { + throw new RuntimeException("Stub!"); + } + + public MessageQueue getQueue() { + throw new RuntimeException("Stub!"); + } + +} diff --git a/booster-android-api/src/main/java/android/os/Message.java b/booster-android-api/src/main/java/android/os/Message.java new file mode 100644 index 000000000..e825062df --- /dev/null +++ b/booster-android-api/src/main/java/android/os/Message.java @@ -0,0 +1,10 @@ +package android.os; + +public final class Message implements Parcelable { + + public int what; + public int arg1; + public int arg2; + public Object obj; + +} diff --git a/booster-android-api/src/main/java/android/os/MessageQueue.java b/booster-android-api/src/main/java/android/os/MessageQueue.java new file mode 100644 index 000000000..fc0da7fb4 --- /dev/null +++ b/booster-android-api/src/main/java/android/os/MessageQueue.java @@ -0,0 +1,17 @@ +package android.os; + +public final class MessageQueue { + + public static interface IdleHandler { + boolean queueIdle(); + } + + public void addIdleHandler(final IdleHandler handler) { + throw new RuntimeException("Stub!"); + } + + public void removeIdleHandler(final IdleHandler handler) { + throw new RuntimeException("Stub!"); + } + +} diff --git a/booster-android-api/src/main/java/android/os/Parcelable.java b/booster-android-api/src/main/java/android/os/Parcelable.java new file mode 100644 index 000000000..3ac3685e4 --- /dev/null +++ b/booster-android-api/src/main/java/android/os/Parcelable.java @@ -0,0 +1,4 @@ +package android.os; + +public interface Parcelable { +} diff --git a/booster-android-api/src/main/java/android/os/Process.java b/booster-android-api/src/main/java/android/os/Process.java new file mode 100644 index 000000000..7d6179a89 --- /dev/null +++ b/booster-android-api/src/main/java/android/os/Process.java @@ -0,0 +1,15 @@ +package android.os; + +public class Process { + + public static int THREAD_PRIORITY_DEFAULT = 0; + + public static final int myPid() { + throw new RuntimeException("Stub!"); + } + + public static final void killProcess(final int pid) { + throw new RuntimeException("Stub!"); + } + +} diff --git a/booster-android-api/src/main/java/android/os/RemoteException.java b/booster-android-api/src/main/java/android/os/RemoteException.java new file mode 100644 index 000000000..0bbb98b75 --- /dev/null +++ b/booster-android-api/src/main/java/android/os/RemoteException.java @@ -0,0 +1,7 @@ +package android.os; + +import android.util.AndroidException; + +public class RemoteException extends AndroidException { + +} diff --git a/booster-android-api/src/main/java/android/os/SystemClock.java b/booster-android-api/src/main/java/android/os/SystemClock.java new file mode 100644 index 000000000..2fc47239f --- /dev/null +++ b/booster-android-api/src/main/java/android/os/SystemClock.java @@ -0,0 +1,45 @@ +package android.os; + +public final class SystemClock { + + public static void sleep(long ms) { + throw new RuntimeException("Stub!"); + } + + /** + * Sets the current wall time, in milliseconds. Requires the calling + * process to have appropriate permissions. + * + * @return if the clock was successfully set to the specified time. + */ + native public static boolean setCurrentTimeMillis(long millis); + + /** + * Returns milliseconds since boot, not counting time spent in deep sleep. + * + * @return milliseconds of non-sleep uptime since boot. + */ + native public static long uptimeMillis(); + + /** + * Returns milliseconds since boot, including time spent in sleep. + * + * @return elapsed milliseconds since boot. + */ + native public static long elapsedRealtime(); + + /** + * Returns nanoseconds since boot, including time spent in sleep. + * + * @return elapsed nanoseconds since boot. + */ + native public static long elapsedRealtimeNanos(); + + /** + * Returns milliseconds running in the current thread. + * + * @return elapsed milliseconds in the thread + */ + native public static long currentThreadTimeMillis(); + +} diff --git a/booster-android-api/src/main/java/android/os/SystemProperties.java b/booster-android-api/src/main/java/android/os/SystemProperties.java new file mode 100644 index 000000000..7ee94512c --- /dev/null +++ b/booster-android-api/src/main/java/android/os/SystemProperties.java @@ -0,0 +1,29 @@ +package android.os; + +public class SystemProperties { + + public static String get(String key) { + throw new RuntimeException("Stub!"); + } + + public static String get(String key, String def) { + throw new RuntimeException("Stub!"); + } + + public static int getInt(String key, int def) { + throw new RuntimeException("Stub!"); + } + + public static long getLong(String key, long def) { + throw new RuntimeException("Stub!"); + } + + public static boolean getBoolean(String key, boolean def) { + throw new RuntimeException("Stub!"); + } + + public static void set(String key, String val) { + throw new RuntimeException("Stub!"); + } + +} diff --git a/booster-android-api/src/main/java/android/util/AndroidException.java b/booster-android-api/src/main/java/android/util/AndroidException.java new file mode 100644 index 000000000..c27a48252 --- /dev/null +++ b/booster-android-api/src/main/java/android/util/AndroidException.java @@ -0,0 +1,4 @@ +package android.util; + +public class AndroidException extends Exception { +} diff --git a/booster-android-api/src/main/java/android/util/AndroidRuntimeException.java b/booster-android-api/src/main/java/android/util/AndroidRuntimeException.java new file mode 100644 index 000000000..6d44b80b6 --- /dev/null +++ b/booster-android-api/src/main/java/android/util/AndroidRuntimeException.java @@ -0,0 +1,21 @@ +package android.util; + +public class AndroidRuntimeException extends RuntimeException { + + public AndroidRuntimeException() { + throw new RuntimeException("Stub!"); + } + + public AndroidRuntimeException(String name) { + throw new RuntimeException("Stub!"); + } + + public AndroidRuntimeException(String name, Throwable cause) { + throw new RuntimeException("Stub!"); + } + + public AndroidRuntimeException(Exception cause) { + throw new RuntimeException("Stub!"); + } + +} diff --git a/booster-android-api/src/main/java/android/util/ArrayMap.java b/booster-android-api/src/main/java/android/util/ArrayMap.java new file mode 100644 index 000000000..b6c97b260 --- /dev/null +++ b/booster-android-api/src/main/java/android/util/ArrayMap.java @@ -0,0 +1,75 @@ +package android.util; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +public final class ArrayMap implements Map { + public ArrayMap() { + throw new RuntimeException("Stub!"); + } + + public ArrayMap(int capacity) { + throw new RuntimeException("Stub!"); + } + + @Override + public int size() { + throw new RuntimeException("Stub!"); + } + + @Override + public boolean isEmpty() { + throw new RuntimeException("Stub!"); + } + + @Override + public boolean containsKey(Object key) { + throw new RuntimeException("Stub!"); + } + + @Override + public boolean containsValue(Object value) { + throw new RuntimeException("Stub!"); + } + + @Override + public V get(Object key) { + throw new RuntimeException("Stub!"); + } + + @Override + public V put(K key, V value) { + throw new RuntimeException("Stub!"); + } + + @Override + public V remove(Object key) { + throw new RuntimeException("Stub!"); + } + + @Override + public void putAll(Map m) { + throw new RuntimeException("Stub!"); + } + + @Override + public void clear() { + throw new RuntimeException("Stub!"); + } + + @Override + public Set keySet() { + throw new RuntimeException("Stub!"); + } + + @Override + public Collection values() { + throw new RuntimeException("Stub!"); + } + + @Override + public Set> entrySet() { + throw new RuntimeException("Stub!"); + } +} diff --git a/booster-android-api/src/main/java/android/util/AttributeSet.java b/booster-android-api/src/main/java/android/util/AttributeSet.java new file mode 100644 index 000000000..95cbe4c7a --- /dev/null +++ b/booster-android-api/src/main/java/android/util/AttributeSet.java @@ -0,0 +1,4 @@ +package android.util; + +public interface AttributeSet { +} diff --git a/booster-android-api/src/main/java/android/util/Log.java b/booster-android-api/src/main/java/android/util/Log.java new file mode 100644 index 000000000..84f1be48e --- /dev/null +++ b/booster-android-api/src/main/java/android/util/Log.java @@ -0,0 +1,276 @@ +package android.util; + + +/** + * API for sending log output. + * + *

Generally, use the Log.v() Log.d() Log.i() Log.w() and Log.e() + * methods. + * + *

The order in terms of verbosity, from least to most is + * ERROR, WARN, INFO, DEBUG, VERBOSE. Verbose should never be compiled + * into an application except during development. Debug logs are compiled + * in but stripped at runtime. Error, warning and info logs are always kept. + * + *

Tip: A good convention is to declare a TAG constant + * in your class: + * + *

private static final String TAG = "MyActivity";
+ * + * and use that in subsequent calls to the log methods. + *

+ * + *

Tip: Don't forget that when you make a call like + *

Log.v(TAG, "index=" + i);
+ * that when you're building the string to pass into Log.d, the compiler uses a + * StringBuilder and at least three allocations occur: the StringBuilder + * itself, the buffer, and the String object. Realistically, there is also + * another buffer allocation and copy, and even more pressure on the gc. + * That means that if your log message is filtered out, you might be doing + * significant work and incurring significant overhead. + */ +public final class Log { + + /** + * Priority constant for the println method; use Log.v. + */ + public static final int VERBOSE = 2; + + /** + * Priority constant for the println method; use Log.d. + */ + public static final int DEBUG = 3; + + /** + * Priority constant for the println method; use Log.i. + */ + public static final int INFO = 4; + + /** + * Priority constant for the println method; use Log.w. + */ + public static final int WARN = 5; + + /** + * Priority constant for the println method; use Log.e. + */ + public static final int ERROR = 6; + + /** + * Priority constant for the println method. + */ + public static final int ASSERT = 7; + + private Log() { + } + + /** + * Send a {@link #VERBOSE} log message. + * @param tag Used to identify the source of a log message. It usually identifies + * the class or activity where the log call occurs. + * @param msg The message you would like logged. + */ + public static int v(String tag, String msg) { + throw new RuntimeException("Stub!"); + } + + /** + * Send a {@link #VERBOSE} log message and log the exception. + * @param tag Used to identify the source of a log message. It usually identifies + * the class or activity where the log call occurs. + * @param msg The message you would like logged. + * @param tr An exception to log + */ + public static int v(String tag, String msg, Throwable tr) { + throw new RuntimeException("Stub!"); + } + + /** + * Send a {@link #DEBUG} log message. + * @param tag Used to identify the source of a log message. It usually identifies + * the class or activity where the log call occurs. + * @param msg The message you would like logged. + */ + public static int d(String tag, String msg) { + throw new RuntimeException("Stub!"); + } + + /** + * Send a {@link #DEBUG} log message and log the exception. + * @param tag Used to identify the source of a log message. It usually identifies + * the class or activity where the log call occurs. + * @param msg The message you would like logged. + * @param tr An exception to log + */ + public static int d(String tag, String msg, Throwable tr) { + throw new RuntimeException("Stub!"); + } + + /** + * Send an {@link #INFO} log message. + * @param tag Used to identify the source of a log message. It usually identifies + * the class or activity where the log call occurs. + * @param msg The message you would like logged. + */ + public static int i(String tag, String msg) { + throw new RuntimeException("Stub!"); + } + + /** + * Send a {@link #INFO} log message and log the exception. + * @param tag Used to identify the source of a log message. It usually identifies + * the class or activity where the log call occurs. + * @param msg The message you would like logged. + * @param tr An exception to log + */ + public static int i(String tag, String msg, Throwable tr) { + throw new RuntimeException("Stub!"); + } + + /** + * Send a {@link #WARN} log message. + * @param tag Used to identify the source of a log message. It usually identifies + * the class or activity where the log call occurs. + * @param msg The message you would like logged. + */ + public static int w(String tag, String msg) { + throw new RuntimeException("Stub!"); + } + + /** + * Send a {@link #WARN} log message and log the exception. + * @param tag Used to identify the source of a log message. It usually identifies + * the class or activity where the log call occurs. + * @param msg The message you would like logged. + * @param tr An exception to log + */ + public static int w(String tag, String msg, Throwable tr) { + throw new RuntimeException("Stub!"); + } + + /** + * Checks to see whether or not a log for the specified tag is loggable at the specified level. + * + * The default level of any tag is set to INFO. This means that any level above and including + * INFO will be logged. Before you make any calls to a logging method you should check to see + * if your tag should be logged. You can change the default level by setting a system property: + * 'setprop log.tag.<YOUR_LOG_TAG> <LEVEL>' + * Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPPRESS will + * turn off all logging for your tag. You can also create a local.prop file that with the + * following in it: + * 'log.tag.<YOUR_LOG_TAG>=<LEVEL>' + * and place that in /data/local.prop. + * + * @param tag The tag to check. + * @param level The level to check. + * @return Whether or not that this is allowed to be logged. + * @throws IllegalArgumentException is thrown if the tag.length() > 23 + * for Nougat (7.0) releases (API <= 23) and prior, there is no + * tag limit of concern after this API level. + */ + public static native boolean isLoggable(String tag, int level); + + /* + * Send a {@link #WARN} log message and log the exception. + * @param tag Used to identify the source of a log message. It usually identifies + * the class or activity where the log call occurs. + * @param tr An exception to log + */ + public static int w(String tag, Throwable tr) { + throw new RuntimeException("Stub!"); + } + + /** + * Send an {@link #ERROR} log message. + * @param tag Used to identify the source of a log message. It usually identifies + * the class or activity where the log call occurs. + * @param msg The message you would like logged. + */ + public static int e(String tag, String msg) { + throw new RuntimeException("Stub!"); + } + + /** + * Send a {@link #ERROR} log message and log the exception. + * @param tag Used to identify the source of a log message. It usually identifies + * the class or activity where the log call occurs. + * @param msg The message you would like logged. + * @param tr An exception to log + */ + public static int e(String tag, String msg, Throwable tr) { + throw new RuntimeException("Stub!"); + } + + /** + * What a Terrible Failure: Report a condition that should never happen. + * The error will always be logged at level ASSERT with the call stack. + * Depending on system configuration, a report may be added to the + * {@code android.os.DropBoxManager} and/or the process may be terminated + * immediately with an error dialog. + * @param tag Used to identify the source of a log message. + * @param msg The message you would like logged. + */ + public static int wtf(String tag, String msg) { + throw new RuntimeException("Stub!"); + } + + /** + * Like {@link #wtf(String, String)}, but also writes to the log the full + * call stack. + * @hide + */ + public static int wtfStack(String tag, String msg) { + throw new RuntimeException("Stub!"); + } + + /** + * What a Terrible Failure: Report an exception that should never happen. + * Similar to {@link #wtf(String, String)}, with an exception to log. + * @param tag Used to identify the source of a log message. + * @param tr An exception to log. + */ + public static int wtf(String tag, Throwable tr) { + throw new RuntimeException("Stub!"); + } + + /** + * What a Terrible Failure: Report an exception that should never happen. + * Similar to {@link #wtf(String, Throwable)}, with a message as well. + * @param tag Used to identify the source of a log message. + * @param msg The message you would like logged. + * @param tr An exception to log. May be null. + */ + public static int wtf(String tag, String msg, Throwable tr) { + throw new RuntimeException("Stub!"); + } + + /** + * Handy function to get a loggable stack trace from a Throwable + * @param tr An exception to log + */ + public static String getStackTraceString(Throwable tr) { + throw new RuntimeException("Stub!"); + } + + /** + * Low-level logging call. + * @param priority The priority/type of this log message + * @param tag Used to identify the source of a log message. It usually identifies + * the class or activity where the log call occurs. + * @param msg The message you would like logged. + * @return The number of bytes written. + */ + public static int println(int priority, String tag, String msg) { + throw new RuntimeException("Stub!"); + } + + /** + * Helper function for long messages. Uses the LineBreakBufferedWriter to break + * up long messages and stacktraces along newlines, but tries to write in large + * chunks. This is to avoid truncation. + */ + public static int printlns(int bufID, int priority, String tag, String msg, Throwable tr) { + throw new RuntimeException("Stub!"); + } + +} diff --git a/booster-android-api/src/main/java/android/view/InputEvent.java b/booster-android-api/src/main/java/android/view/InputEvent.java new file mode 100644 index 000000000..d478adfb6 --- /dev/null +++ b/booster-android-api/src/main/java/android/view/InputEvent.java @@ -0,0 +1,6 @@ +package android.view; + +import android.os.Parcelable; + +public abstract class InputEvent implements Parcelable { +} diff --git a/booster-android-api/src/main/java/android/view/KeyEvent.java b/booster-android-api/src/main/java/android/view/KeyEvent.java new file mode 100644 index 000000000..01a2a0588 --- /dev/null +++ b/booster-android-api/src/main/java/android/view/KeyEvent.java @@ -0,0 +1,10 @@ +package android.view; + +import android.os.Parcelable; + +public class KeyEvent extends InputEvent implements Parcelable { + + public interface Callback { + } + +} diff --git a/booster-android-api/src/main/java/android/view/SurfaceHolder.java b/booster-android-api/src/main/java/android/view/SurfaceHolder.java new file mode 100644 index 000000000..f44552da0 --- /dev/null +++ b/booster-android-api/src/main/java/android/view/SurfaceHolder.java @@ -0,0 +1,4 @@ +package android.view; + +public interface SurfaceHolder { +} diff --git a/booster-android-api/src/main/java/android/view/View.java b/booster-android-api/src/main/java/android/view/View.java new file mode 100644 index 000000000..a8be8f898 --- /dev/null +++ b/booster-android-api/src/main/java/android/view/View.java @@ -0,0 +1,7 @@ +package android.view; + +import android.graphics.drawable.Drawable; +import android.view.accessibility.AccessibilityEventSource; + +public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { +} diff --git a/booster-android-api/src/main/java/android/view/ViewDebug.java b/booster-android-api/src/main/java/android/view/ViewDebug.java new file mode 100644 index 000000000..4603c6de4 --- /dev/null +++ b/booster-android-api/src/main/java/android/view/ViewDebug.java @@ -0,0 +1,8 @@ +package android.view; + +public class ViewDebug { + + public interface HierarchyHandler { + } + +} diff --git a/booster-android-api/src/main/java/android/view/ViewGroup.java b/booster-android-api/src/main/java/android/view/ViewGroup.java new file mode 100644 index 000000000..22b6b8efe --- /dev/null +++ b/booster-android-api/src/main/java/android/view/ViewGroup.java @@ -0,0 +1,43 @@ +package android.view; + +public abstract class ViewGroup extends View implements ViewParent, ViewManager { + + /** + * Interface definition for a callback to be invoked when the hierarchy + * within this view changed. The hierarchy changes whenever a child is added + * to or removed from this view. + */ + public interface OnHierarchyChangeListener { + /** + * Called when a new child is added to a parent view. + * + * @param parent + * the view in which a child was added + * @param child + * the new child view added in the hierarchy + */ + void onChildViewAdded(View parent, View child); + + /** + * Called when a child is removed from a parent view. + * + * @param parent + * the view from which the child was removed + * @param child + * the child removed from the hierarchy + */ + void onChildViewRemoved(View parent, View child); + } + + /** + * Register a callback to be invoked when a child is added to or removed + * from this view. + * + * @param listener + * the callback to invoke on hierarchy change + */ + public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) { + throw new RuntimeException("Stub!"); + } + +} diff --git a/booster-android-api/src/main/java/android/view/ViewManager.java b/booster-android-api/src/main/java/android/view/ViewManager.java new file mode 100644 index 000000000..bf9fc12b1 --- /dev/null +++ b/booster-android-api/src/main/java/android/view/ViewManager.java @@ -0,0 +1,4 @@ +package android.view; + +public interface ViewManager { +} diff --git a/booster-android-api/src/main/java/android/view/ViewParent.java b/booster-android-api/src/main/java/android/view/ViewParent.java new file mode 100644 index 000000000..18025da13 --- /dev/null +++ b/booster-android-api/src/main/java/android/view/ViewParent.java @@ -0,0 +1,4 @@ +package android.view; + +public interface ViewParent { +} diff --git a/booster-android-api/src/main/java/android/view/ViewTreeObserver.java b/booster-android-api/src/main/java/android/view/ViewTreeObserver.java new file mode 100644 index 000000000..eb56482c1 --- /dev/null +++ b/booster-android-api/src/main/java/android/view/ViewTreeObserver.java @@ -0,0 +1,145 @@ +package android.view; + +public final class ViewTreeObserver { + + /** + * Interface definition for a callback to be invoked when the view hierarchy is + * attached to and detached from its window. + */ + public interface OnWindowAttachListener { + /** + * Callback method to be invoked when the view hierarchy is attached to a window + */ + public void onWindowAttached(); + + /** + * Callback method to be invoked when the view hierarchy is detached from a window + */ + public void onWindowDetached(); + } + + /** + * Interface definition for a callback to be invoked when the view hierarchy's window + * focus state changes. + */ + public interface OnWindowFocusChangeListener { + /** + * Callback method to be invoked when the window focus changes in the view tree. + * + * @param hasFocus + * Set to true if the window is gaining focus, false if it is + * losing focus. + */ + public void onWindowFocusChanged(boolean hasFocus); + } + + /** + * Interface definition for a callback to be invoked when the focus state within + * the view tree changes. + */ + public interface OnGlobalFocusChangeListener { + /** + * Callback method to be invoked when the focus changes in the view tree. When + * the view tree transitions from touch mode to non-touch mode, oldFocus is null. + * When the view tree transitions from non-touch mode to touch mode, newFocus is + * null. When focus changes in non-touch mode (without transition from or to + * touch mode) either oldFocus or newFocus can be null. + * + * @param oldFocus + * The previously focused view, if any. + * @param newFocus + * The newly focused View, if any. + */ + public void onGlobalFocusChanged(View oldFocus, View newFocus); + } + + /** + * Interface definition for a callback to be invoked when the global layout state + * or the visibility of views within the view tree changes. + */ + public interface OnGlobalLayoutListener { + /** + * Callback method to be invoked when the global layout state or the visibility of views + * within the view tree changes + */ + public void onGlobalLayout(); + } + + /** + * Interface definition for a callback to be invoked when the view tree is about to be drawn. + */ + public interface OnPreDrawListener { + /** + * Callback method to be invoked when the view tree is about to be drawn. At this point, all + * views in the tree have been measured and given a frame. Clients can use this to adjust + * their scroll bounds or even to request a new layout before drawing occurs. + * + * @return Return true to proceed with the current drawing pass, or false to cancel. + * @see android.view.View#onMeasure + * @see android.view.View#onLayout + * @see android.view.View#onDraw + */ + public boolean onPreDraw(); + } + + /** + * Interface definition for a callback to be invoked when the view tree is about to be drawn. + */ + public interface OnDrawListener { + /** + *

Callback method to be invoked when the view tree is about to be drawn. At this point, + * views cannot be modified in any way.

+ * + *

Unlike with {@link OnPreDrawListener}, this method cannot be used to cancel the + * current drawing pass.

+ * + *

An {@link OnDrawListener} listener cannot be added or removed + * from this method.

+ * + * @see android.view.View#onMeasure + * @see android.view.View#onLayout + * @see android.view.View#onDraw + */ + public void onDraw(); + } + + /** + * Interface definition for a callback to be invoked when the touch mode changes. + */ + public interface OnTouchModeChangeListener { + /** + * Callback method to be invoked when the touch mode changes. + * + * @param isInTouchMode + * True if the view hierarchy is now in touch mode, false otherwise. + */ + public void onTouchModeChanged(boolean isInTouchMode); + } + + /** + * Interface definition for a callback to be invoked when + * something in the view tree has been scrolled. + */ + public interface OnScrollChangedListener { + /** + * Callback method to be invoked when something in the view tree + * has been scrolled. + */ + public void onScrollChanged(); + } + + /** + * Interface definition for a callback noting when a system window has been displayed. + * This is only used for non-Activity windows. Activity windows can use + * Activity.onEnterAnimationComplete() to get the same signal. + * + * @hide + */ + public interface OnWindowShownListener { + /** + * Callback method to be invoked when a non-activity window is fully shown. + */ + void onWindowShown(); + } + +} diff --git a/booster-android-api/src/main/java/android/view/WindowManager.java b/booster-android-api/src/main/java/android/view/WindowManager.java new file mode 100644 index 000000000..eccdf56d1 --- /dev/null +++ b/booster-android-api/src/main/java/android/view/WindowManager.java @@ -0,0 +1,15 @@ +package android.view; + +public interface WindowManager extends ViewManager { + + public static class BadTokenException extends RuntimeException { + public BadTokenException() { + throw new RuntimeException("Stub!"); + } + + public BadTokenException(String name) { + throw new RuntimeException("Stub!"); + } + } + +} diff --git a/booster-android-api/src/main/java/android/view/accessibility/AccessibilityEventSource.java b/booster-android-api/src/main/java/android/view/accessibility/AccessibilityEventSource.java new file mode 100644 index 000000000..60a6cff22 --- /dev/null +++ b/booster-android-api/src/main/java/android/view/accessibility/AccessibilityEventSource.java @@ -0,0 +1,4 @@ +package android.view.accessibility; + +public interface AccessibilityEventSource { +} diff --git a/booster-android-api/src/main/java/android/webkit/CookieManager.java b/booster-android-api/src/main/java/android/webkit/CookieManager.java new file mode 100644 index 000000000..8683e7404 --- /dev/null +++ b/booster-android-api/src/main/java/android/webkit/CookieManager.java @@ -0,0 +1,4 @@ +package android.webkit; + +public abstract class CookieManager { +} diff --git a/booster-android-api/src/main/java/android/webkit/IWebViewUpdateService.java b/booster-android-api/src/main/java/android/webkit/IWebViewUpdateService.java new file mode 100644 index 000000000..85674cae6 --- /dev/null +++ b/booster-android-api/src/main/java/android/webkit/IWebViewUpdateService.java @@ -0,0 +1,9 @@ +package android.webkit; + +import android.os.IInterface; + +public interface IWebViewUpdateService extends IInterface { + + String getCurrentWebViewPackageName(); + +} diff --git a/booster-android-api/src/main/java/android/webkit/WebSettings.java b/booster-android-api/src/main/java/android/webkit/WebSettings.java new file mode 100644 index 000000000..39a7b98e2 --- /dev/null +++ b/booster-android-api/src/main/java/android/webkit/WebSettings.java @@ -0,0 +1,11 @@ +package android.webkit; + +import android.content.Context; + +public abstract class WebSettings { + + public static String getDefaultUserAgent(Context context) { + throw new RuntimeException("Stub!"); + } + +} diff --git a/booster-android-api/src/main/java/android/webkit/WebStorage.java b/booster-android-api/src/main/java/android/webkit/WebStorage.java new file mode 100644 index 000000000..ac374409c --- /dev/null +++ b/booster-android-api/src/main/java/android/webkit/WebStorage.java @@ -0,0 +1,4 @@ +package android.webkit; + +public class WebStorage { +} diff --git a/booster-android-api/src/main/java/android/webkit/WebView.java b/booster-android-api/src/main/java/android/webkit/WebView.java new file mode 100644 index 000000000..9628e3f06 --- /dev/null +++ b/booster-android-api/src/main/java/android/webkit/WebView.java @@ -0,0 +1,96 @@ +package android.webkit; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewDebug; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.widget.AbsoluteLayout; + +public class WebView extends AbsoluteLayout implements ViewTreeObserver.OnGlobalFocusChangeListener, ViewGroup.OnHierarchyChangeListener, ViewDebug.HierarchyHandler { + + /** + * Constructs a new WebView with a Context object. + * + * @param context a Context object used to access application assets + */ + public WebView(Context context) { + this(context, null); + } + + /** + * Constructs a new WebView with layout parameters. + * + * @param context a Context object used to access application assets + * @param attrs an AttributeSet passed to our parent + */ + public WebView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + /** + * Constructs a new WebView with layout parameters and a default style. + * + * @param context a Context object used to access application assets + * @param attrs an AttributeSet passed to our parent + * @param defStyleAttr an attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. + */ + public WebView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + /** + * Constructs a new WebView with layout parameters and a default style. + * + * @param context a Context object used to access application assets + * @param attrs an AttributeSet passed to our parent + * @param defStyleAttr an attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. + * @param defStyleRes a resource identifier of a style resource that + * supplies default values for the view, used only if + * defStyleAttr is 0 or can not be found in the theme. Can be 0 + * to not look for defaults. + */ + public WebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + throw new RuntimeException("Stub!"); + } + + /** + * Constructs a new WebView with layout parameters and a default style. + * + * @param context a Context object used to access application assets + * @param attrs an AttributeSet passed to our parent + * @param defStyleAttr an attribute in the current theme that contains a + * reference to a style resource that supplies default values for + * the view. Can be 0 to not look for defaults. + * @param privateBrowsing whether this WebView will be initialized in + * private mode + * + * @deprecated Private browsing is no longer supported directly via + * WebView and will be removed in a future release. Prefer using + * {@link WebSettings}, {@link WebViewDatabase}, {@link CookieManager} + * and {@link WebStorage} for fine-grained control of privacy data. + */ + @Deprecated + public WebView(Context context, AttributeSet attrs, int defStyleAttr, boolean privateBrowsing) { + throw new RuntimeException("Stub!"); + } + + + @Override + public void onChildViewAdded(View parent, View child) { + } + + @Override + public void onChildViewRemoved(View parent, View child) { + } + + @Override + public void onGlobalFocusChanged(View oldFocus, View newFocus) { + } + +} diff --git a/booster-android-api/src/main/java/android/webkit/WebViewDatabase.java b/booster-android-api/src/main/java/android/webkit/WebViewDatabase.java new file mode 100644 index 000000000..69c5b93a4 --- /dev/null +++ b/booster-android-api/src/main/java/android/webkit/WebViewDatabase.java @@ -0,0 +1,4 @@ +package android.webkit; + +public abstract class WebViewDatabase { +} diff --git a/booster-android-api/src/main/java/android/webkit/WebViewFactory.java b/booster-android-api/src/main/java/android/webkit/WebViewFactory.java new file mode 100644 index 000000000..f65f41c99 --- /dev/null +++ b/booster-android-api/src/main/java/android/webkit/WebViewFactory.java @@ -0,0 +1,13 @@ +package android.webkit; + +public final class WebViewFactory { + + public static Class getWebViewProviderClass(ClassLoader clazzLoader) { + throw new RuntimeException("Stub!"); + } + + public static IWebViewUpdateService getUpdateService() { + throw new RuntimeException("Stub!"); + } + +} diff --git a/booster-android-api/src/main/java/android/webkit/WebViewFactoryProvider.java b/booster-android-api/src/main/java/android/webkit/WebViewFactoryProvider.java new file mode 100644 index 000000000..f1dedef72 --- /dev/null +++ b/booster-android-api/src/main/java/android/webkit/WebViewFactoryProvider.java @@ -0,0 +1,4 @@ +package android.webkit; + +public interface WebViewFactoryProvider { +} diff --git a/booster-android-api/src/main/java/android/widget/AbsoluteLayout.java b/booster-android-api/src/main/java/android/widget/AbsoluteLayout.java new file mode 100644 index 000000000..0eea63d2c --- /dev/null +++ b/booster-android-api/src/main/java/android/widget/AbsoluteLayout.java @@ -0,0 +1,4 @@ +package android.widget; + +public class AbsoluteLayout { +} diff --git a/booster-android-api/src/main/java/android/widget/Toast.java b/booster-android-api/src/main/java/android/widget/Toast.java new file mode 100644 index 000000000..f8c37531c --- /dev/null +++ b/booster-android-api/src/main/java/android/widget/Toast.java @@ -0,0 +1,9 @@ +package android.widget; + +public class Toast { + + public void show() { + throw new RuntimeException("Stub!"); + } + +} diff --git a/booster-android-bugfix-toast/build.gradle b/booster-android-bugfix-toast/build.gradle new file mode 100644 index 000000000..6eb6d389c --- /dev/null +++ b/booster-android-bugfix-toast/build.gradle @@ -0,0 +1,7 @@ +sourceCompatibility = JavaVersion.VERSION_1_7 +targetCompatibility = JavaVersion.VERSION_1_7 + +dependencies { + compileOnly project(':booster-android-api') + compile project(':booster-android-bugfix') +} diff --git a/booster-android-bugfix-toast/src/main/java/com/didiglobal/booster/android/widget/Toast.java b/booster-android-bugfix-toast/src/main/java/com/didiglobal/booster/android/widget/Toast.java new file mode 100644 index 000000000..dcc0a45e7 --- /dev/null +++ b/booster-android-bugfix-toast/src/main/java/com/didiglobal/booster/android/widget/Toast.java @@ -0,0 +1,54 @@ +package com.didiglobal.booster.android.widget; + +import android.os.Build; +import android.os.Handler; +import android.util.Log; +import com.didiglobal.booster.android.bugfix.CaughtCallback; +import com.didiglobal.booster.android.bugfix.CaughtRunnable; + +import static com.didiglobal.booster.android.bugfix.Constants.TAG; +import static com.didiglobal.booster.android.bugfix.Reflection.getFieldValue; +import static com.didiglobal.booster.android.bugfix.Reflection.setFieldValue; + +public class Toast { + + /** + * Fix {@code WindowManager$BadTokenException} for Android N + * + * @param toast + * The original toast + */ + public static void show(final android.widget.Toast toast) { + if (Build.VERSION.SDK_INT == 25) { + workaround(toast).show(); + } else { + toast.show(); + } + } + + private static android.widget.Toast workaround(final android.widget.Toast toast) { + final Object tn = getFieldValue(toast, "mTN"); + if (null == tn) { + Log.w(TAG, "Field mTN of " + toast + " is null"); + return toast; + } + + final Object handler = getFieldValue(tn, "mHandler"); + if (handler instanceof Handler) { + if (setFieldValue(handler, "mCallback", new CaughtCallback((Handler) handler))) { + return toast; + } + } + + final Object show = getFieldValue(tn, "mShow"); + if (show instanceof Runnable) { + if (setFieldValue(tn, "mShow", new CaughtRunnable((Runnable) show))) { + return toast; + } + } + + Log.w(TAG, "Neither field mHandler nor mShow of " + tn + " is accessible"); + return toast; + } + +} diff --git a/booster-android-bugfix/build.gradle b/booster-android-bugfix/build.gradle new file mode 100644 index 000000000..22a4e3818 --- /dev/null +++ b/booster-android-bugfix/build.gradle @@ -0,0 +1,6 @@ +sourceCompatibility = JavaVersion.VERSION_1_7 +targetCompatibility = JavaVersion.VERSION_1_7 + +dependencies { + compileOnly project(':booster-android-api') +} diff --git a/booster-android-bugfix/src/main/java/com/didiglobal/booster/android/bugfix/CaughtCallback.java b/booster-android-bugfix/src/main/java/com/didiglobal/booster/android/bugfix/CaughtCallback.java new file mode 100644 index 000000000..1d899ba8f --- /dev/null +++ b/booster-android-bugfix/src/main/java/com/didiglobal/booster/android/bugfix/CaughtCallback.java @@ -0,0 +1,23 @@ +package com.didiglobal.booster.android.bugfix; + +import android.os.Handler; +import android.os.Message; + +public class CaughtCallback implements Handler.Callback { + + private final Handler mHandler; + + public CaughtCallback(final Handler handler) { + this.mHandler = handler; + } + + @Override + public boolean handleMessage(final Message msg) { + try { + this.mHandler.handleMessage(msg); + } catch (final RuntimeException e) { + // ignore + } + return true; + } +} diff --git a/booster-android-bugfix/src/main/java/com/didiglobal/booster/android/bugfix/CaughtRunnable.java b/booster-android-bugfix/src/main/java/com/didiglobal/booster/android/bugfix/CaughtRunnable.java new file mode 100644 index 000000000..2d8375f81 --- /dev/null +++ b/booster-android-bugfix/src/main/java/com/didiglobal/booster/android/bugfix/CaughtRunnable.java @@ -0,0 +1,19 @@ +package com.didiglobal.booster.android.bugfix; + +public class CaughtRunnable implements Runnable { + + private final Runnable mRunnable; + + public CaughtRunnable(final Runnable runnable) { + this.mRunnable = runnable; + } + + @Override + public void run() { + try { + this.mRunnable.run(); + } catch (final RuntimeException e) { + // ignore + } + } +} diff --git a/booster-android-bugfix/src/main/java/com/didiglobal/booster/android/bugfix/Constants.java b/booster-android-bugfix/src/main/java/com/didiglobal/booster/android/bugfix/Constants.java new file mode 100644 index 000000000..7acdbdfef --- /dev/null +++ b/booster-android-bugfix/src/main/java/com/didiglobal/booster/android/bugfix/Constants.java @@ -0,0 +1,7 @@ +package com.didiglobal.booster.android.bugfix; + +public interface Constants { + + String TAG = "booster"; + +} diff --git a/booster-android-bugfix/src/main/java/com/didiglobal/booster/android/bugfix/Reflection.java b/booster-android-bugfix/src/main/java/com/didiglobal/booster/android/bugfix/Reflection.java new file mode 100644 index 000000000..cf3ed8a9e --- /dev/null +++ b/booster-android-bugfix/src/main/java/com/didiglobal/booster/android/bugfix/Reflection.java @@ -0,0 +1,225 @@ +package com.didiglobal.booster.android.bugfix; + +import android.util.Log; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; + +public abstract class Reflection { + + @SuppressWarnings("unchecked") + public static T getStaticFieldValue(final Class klass, final String name) { + if (null != klass && null != name) { + try { + final Field field = getField(klass, name); + if (null != field) { + field.setAccessible(true); + return (T) field.get(klass); + } + } catch (final Throwable t) { + Log.w(Constants.TAG, "get field " + name + " of " + klass + " error", t); + } + } + + return null; + } + + public static boolean setStaticFieldValue(final Class klass, final String name, final Object value) { + if (null != klass && null != name) { + try { + final Field field = getField(klass, name); + if (null != field) { + field.setAccessible(true); + field.set(klass, value); + return true; + } + } catch (final Throwable t) { + Log.w(Constants.TAG, "set field " + name + " of " + klass + " error", t); + } + } + + return false; + } + + @SuppressWarnings("unchecked") + public static T getFieldValue(final Object obj, final String name) { + if (null != obj && null != name) { + try { + final Field field = getField(obj.getClass(), name); + if (null != field) { + field.setAccessible(true); + return (T) field.get(obj); + } + } catch (final Throwable t) { + Log.w(Constants.TAG, "get field " + name + " of " + obj + " error", t); + } + } + + return null; + } + + @SuppressWarnings("unchecked") + public static T getFieldValue(final Object obj, final Class type) { + if (null != obj && null != type) { + try { + final Field field = getField(obj.getClass(), type); + if (null != field) { + field.setAccessible(true); + return (T) field.get(obj); + } + } catch (final Throwable t) { + Log.w(Constants.TAG, "get field with type " + type + " of " + obj + " error", t); + } + } + + return null; + } + + public static boolean setFieldValue(final Object obj, final String name, final Object value) { + if (null != obj && null != name) { + try { + final Field field = getField(obj.getClass(), name); + if (null != field) { + field.setAccessible(true); + field.set(obj, value); + return true; + } + } catch (final Throwable t) { + Log.w(Constants.TAG, "set field " + name + " of " + obj + " error", t); + } + } + + return false; + } + + public static T newInstance(final String className, final Object... args) { + try { + return newInstance(Class.forName(className), args); + } catch (final ClassNotFoundException e) { + Log.w(Constants.TAG, "new instance of " + className + " error", e); + return null; + } + } + + @SuppressWarnings("unchecked") + public static T newInstance(final Class clazz, Object... args) { + final Constructor[] ctors = clazz.getDeclaredConstructors(); + + loop: + for (final Constructor ctor : ctors) { + final Class[] types = ctor.getParameterTypes(); + if (types.length == args.length) { + for (int i = 0; i < types.length; i++) { + if (null != args[i] && !types[i].isAssignableFrom(args[i].getClass())) { + continue loop; + } + } + + try { + ctor.setAccessible(true); + return (T) ctor.newInstance(args); + } catch (final Throwable t) { + Log.w(Constants.TAG, "Invoke constructor " + ctor + " error", t); + return null; + } + } + } + + return null; + } + + @SuppressWarnings("unchecked") + public static T invokeStaticMethod(final Class klass, final String name) { + return invokeStaticMethod(klass, name, new Class[0], new Object[0]); + } + + @SuppressWarnings("unchecked") + public static T invokeStaticMethod(final Class klass, final String name, final Class[] types, final Object[] args) { + if (null != klass && null != name && null != types && null != args && types.length == args.length) { + try { + final Method method = getMethod(klass, name, types); + if (null != method) { + method.setAccessible(true); + return (T) method.invoke(klass, args); + } + } catch (final Throwable e) { + Log.w(Constants.TAG, "Invoke " + name + "(" + Arrays.toString(types) + ") of " + klass + " error", e); + } + } + + return null; + } + + + @SuppressWarnings("unchecked") + public static T invokeMethod(final Object obj, final String name) { + return invokeMethod(obj, name, new Class[0], new Object[0]); + } + + @SuppressWarnings("unchecked") + public static T invokeMethod(final Object obj, final String name, final Class[] types, final Object[] args) { + if (null != obj && null != name && null != types && null != args && types.length == args.length) { + try { + final Method method = getMethod(obj.getClass(), name, types); + if (null != method) { + method.setAccessible(true); + return (T) method.invoke(obj, args); + } + } catch (final Throwable e) { + Log.w(Constants.TAG, "Invoke " + name + "(" + Arrays.toString(types) + ") of " + obj + " error", e); + } + } + + return null; + } + + public static Field getField(final Class klass, final String name) { + try { + return klass.getDeclaredField(name); + } catch (final NoSuchFieldException e) { + final Class parent = klass.getSuperclass(); + if (null == parent) { + return null; + } + return getField(parent, name); + } + } + + public static Field getField(final Class klass, final Class type) { + final Field[] fields = klass.getDeclaredFields(); + if (fields.length <= 0) { + final Class parent = klass.getSuperclass(); + if (null == parent) { + return null; + } + return getField(parent, type); + } + + for (final Field field : fields) { + if (field.getType() == type) { + return field; + } + } + + return null; + } + + private static Method getMethod(final Class klass, final String name, final Class[] types) { + try { + return klass.getDeclaredMethod(name, types); + } catch (final NoSuchMethodException e) { + final Class parent = klass.getSuperclass(); + if (null == parent) { + return null; + } + return getMethod(parent, name, types); + } + } + + private Reflection() { + } + +} + diff --git a/booster-android-gradle-api/build.gradle b/booster-android-gradle-api/build.gradle new file mode 100644 index 000000000..585d4fd42 --- /dev/null +++ b/booster-android-gradle-api/build.gradle @@ -0,0 +1,8 @@ +apply from: '../gradle/booster.gradle' + +dependencies { + compile project(':booster-android-gradle-v3_0') + compile project(':booster-android-gradle-v3_2') + compileOnly 'com.android.tools.build:gradle:3.0.0' + testCompileOnly 'com.android.tools.build:gradle:3.0.0' +} diff --git a/booster-android-gradle-api/src/main/kotlin/com/didiglobal/booster/gradle/VariantScope.kt b/booster-android-gradle-api/src/main/kotlin/com/didiglobal/booster/gradle/VariantScope.kt new file mode 100644 index 000000000..32bf9c7c7 --- /dev/null +++ b/booster-android-gradle-api/src/main/kotlin/com/didiglobal/booster/gradle/VariantScope.kt @@ -0,0 +1,67 @@ +package com.didiglobal.booster.gradle + +import com.android.build.gradle.internal.scope.VariantScope +import com.android.builder.model.Version +import com.android.repository.Revision +import java.io.File + +private val ANDROID_GRADLE_PLUGIN_VERSION = Revision.parseRevision(Version.ANDROID_GRADLE_PLUGIN_VERSION) +private val GTE_V32 = ANDROID_GRADLE_PLUGIN_VERSION.major >= 3 && ANDROID_GRADLE_PLUGIN_VERSION.minor >= 2 +private val ALL_ARTIFACTS_GETTER = if (GTE_V32) VariantScopeV32::getAllArtifacts else VariantScopeV30::getAllArtifacts +private val ALL_CLASSES_GETTER = if (GTE_V32) VariantScopeV32::getAllClasses else VariantScopeV30::getAllClasses +private val APK_GETTER = if (GTE_V32) VariantScopeV32::getApk else VariantScopeV30::getApk +private val JAVAC_GETTER = if (GTE_V32) VariantScopeV32::getJavac else VariantScopeV30::getJavac +private val MERGED_ASSETS_GETTER = if (GTE_V32) VariantScopeV32::getMergedAssets else VariantScopeV30::getMergedAssets +private val MERGED_MANIFESTS_GETTER = if (GTE_V32) VariantScopeV32::getMergedManifests else VariantScopeV30::getMergedManifests +private val MERGED_RESOURCE_GETTER = if (GTE_V32) VariantScopeV32::getMergedRes else VariantScopeV30::getMergedRes +private val PROCESSED_RES_GETTER = if (GTE_V32) VariantScopeV32::getProcessedRes else VariantScopeV30::getProcessedRes +private val SYMBOL_LIST_GETTER = if (GTE_V32) VariantScopeV32::getSymbolList else VariantScopeV30::getSymbolList +private val SYMBOL_LIST_WITH_PACKAGE_NAME_GETTER = if (GTE_V32) VariantScopeV32::getSymbolListWithPackageName else VariantScopeV30::getSymbolListWithPackageName + +/** + * The output directory of APK files + */ +val VariantScope.apk: Collection + get() = APK_GETTER(this) + +val VariantScope.javac: Collection + get() = JAVAC_GETTER(this) + +/** + * The output directory of merged [AndroidManifest.xml](https://developer.android.com/guide/topics/manifest/manifest-intro) + */ +val VariantScope.mergedManifests: Collection + get() = MERGED_MANIFESTS_GETTER(this) + +/** + * The output directory of merged resources + */ +val VariantScope.mergedRes: Collection + get() = MERGED_RESOURCE_GETTER(this) + +/** + * The output directory of merged assets + */ +val VariantScope.mergedAssets: Collection + get() = MERGED_ASSETS_GETTER(this) + +/** + * The output directory of processed resources: *resources-**variant**.ap\_* + */ +val VariantScope.processedRes: Collection + get() = PROCESSED_RES_GETTER(this) + +/** + * All of classes + */ +val VariantScope.allClasses: Collection + get() = ALL_CLASSES_GETTER(this) + +val VariantScope.symbolList: Collection + get() = SYMBOL_LIST_GETTER(this) + +val VariantScope.symbolListWithPackageName: Collection + get() = SYMBOL_LIST_WITH_PACKAGE_NAME_GETTER(this) + +val VariantScope.allArtifacts: Map> + get() = ALL_ARTIFACTS_GETTER(this) diff --git a/booster-android-gradle-v3_0/build.gradle b/booster-android-gradle-v3_0/build.gradle new file mode 100644 index 000000000..f09cbc96d --- /dev/null +++ b/booster-android-gradle-v3_0/build.gradle @@ -0,0 +1,7 @@ +apply from: '../gradle/booster.gradle' + +dependencies { + compile gradleApi() + compileOnly 'com.android.tools.build:gradle:3.0.0' + testCompileOnly 'com.android.tools.build:gradle:3.0.0' +} diff --git a/booster-android-gradle-v3_0/src/main/java/com/didiglobal/booster/gradle/VariantScopeV30.java b/booster-android-gradle-v3_0/src/main/java/com/didiglobal/booster/gradle/VariantScopeV30.java new file mode 100644 index 000000000..a083b0524 --- /dev/null +++ b/booster-android-gradle-v3_0/src/main/java/com/didiglobal/booster/gradle/VariantScopeV30.java @@ -0,0 +1,94 @@ +package com.didiglobal.booster.gradle; + +import com.android.build.gradle.internal.scope.TaskOutputHolder.AnchorOutputType; +import com.android.build.gradle.internal.scope.TaskOutputHolder.OutputType; +import com.android.build.gradle.internal.scope.TaskOutputHolder.TaskOutputType; +import com.android.build.gradle.internal.scope.VariantScope; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +class VariantScopeV30 { + + /** + * The merged AndroidManifest.xml + */ + @NotNull + static Collection getMergedManifests(@NotNull final VariantScope scope) { + return scope.getOutput(TaskOutputType.MERGED_MANIFESTS).getFiles(); + } + + /** + * The merged resources + */ + @NotNull + static Collection getMergedRes(@NotNull final VariantScope scope) { + return scope.getOutput(TaskOutputType.MERGED_RES).getFiles(); + } + + /** + * The merged assets + */ + @NotNull + static Collection getMergedAssets(@NotNull final VariantScope scope) { + return scope.getOutput(TaskOutputType.MERGED_ASSETS).getFiles(); + } + + /** + * The processed resources + */ + @NotNull + static Collection getProcessedRes(@NotNull final VariantScope scope) { + return scope.getOutput(TaskOutputType.PROCESSED_RES).getFiles(); + } + + /** + * All of classes + */ + @NotNull + static Collection getAllClasses(@NotNull final VariantScope scope) { + return scope.getOutput(AnchorOutputType.ALL_CLASSES).getFiles(); + } + + @NotNull + static Collection getSymbolList(@NotNull final VariantScope scope) { + return getOutput(scope, TaskOutputType.SYMBOL_LIST); + } + + @NotNull + static Collection getSymbolListWithPackageName(@NotNull final VariantScope scope) { + return getOutput(scope, TaskOutputType.SYMBOL_LIST_WITH_PACKAGE_NAME); + } + + @NotNull + static Collection getApk(@NotNull final VariantScope scope) { + return getOutput(scope, TaskOutputType.APK); + } + + @NotNull + static Collection getJavac(@NotNull final VariantScope scope) { + return getOutput(scope, TaskOutputType.JAVAC); + } + + @NotNull + static Map> getAllArtifacts(@NotNull final VariantScope scope) { + return Stream.concat(Arrays.stream(TaskOutputType.values()), Arrays.stream(AnchorOutputType.values())) + .collect(Collectors.toMap(OutputType::name, v -> getOutput(scope, v))); + } + + @NotNull + static Collection getOutput(@NotNull final VariantScope scope, @NotNull final OutputType type) { + try { + return scope.getOutput(type).getFiles(); + } catch (final RuntimeException e) { + return Collections.emptyList(); + } + } + +} diff --git a/booster-android-gradle-v3_2/build.gradle b/booster-android-gradle-v3_2/build.gradle new file mode 100644 index 000000000..2dbab4af6 --- /dev/null +++ b/booster-android-gradle-v3_2/build.gradle @@ -0,0 +1,7 @@ +apply from: '../gradle/booster.gradle' + +dependencies { + compile gradleApi() + compileOnly 'com.android.tools.build:gradle:3.2.0' + testCompileOnly 'com.android.tools.build:gradle:3.2.0' +} diff --git a/booster-android-gradle-v3_2/src/main/java/com/didiglobal/booster/gradle/VariantScopeV32.java b/booster-android-gradle-v3_2/src/main/java/com/didiglobal/booster/gradle/VariantScopeV32.java new file mode 100644 index 000000000..f021f1399 --- /dev/null +++ b/booster-android-gradle-v3_2/src/main/java/com/didiglobal/booster/gradle/VariantScopeV32.java @@ -0,0 +1,95 @@ +package com.didiglobal.booster.gradle; + +import com.android.build.api.artifact.ArtifactType; +import com.android.build.gradle.internal.scope.AnchorOutputType; +import com.android.build.gradle.internal.scope.InternalArtifactType; +import com.android.build.gradle.internal.scope.VariantScope; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + + +class VariantScopeV32 { + + /** + * The merged AndroidManifest.xml + */ + @NotNull + static Collection getMergedManifests(@NotNull final VariantScope scope) { + return getFinalArtifactFiles(scope, InternalArtifactType.MERGED_MANIFESTS); + } + + /** + * The merged resources + */ + @NotNull + static Collection getMergedRes(@NotNull final VariantScope scope) { + return getFinalArtifactFiles(scope, InternalArtifactType.MERGED_RES); + } + + /** + * The merged assets + */ + @NotNull + static Collection getMergedAssets(@NotNull final VariantScope scope) { + return getFinalArtifactFiles(scope, InternalArtifactType.MERGED_ASSETS); + } + + /** + * The processed resources + */ + @NotNull + static Collection getProcessedRes(@NotNull final VariantScope scope) { + return getFinalArtifactFiles(scope, InternalArtifactType.PROCESSED_RES); + } + + /** + * All of classes + */ + @NotNull + static Collection getAllClasses(@NotNull final VariantScope scope) { + return getFinalArtifactFiles(scope, AnchorOutputType.ALL_CLASSES); + } + + @NotNull + static Collection getSymbolList(@NotNull final VariantScope scope) { + return getFinalArtifactFiles(scope, InternalArtifactType.SYMBOL_LIST); + } + + @NotNull + static Collection getSymbolListWithPackageName(@NotNull final VariantScope scope) { + return getFinalArtifactFiles(scope, InternalArtifactType.SYMBOL_LIST_WITH_PACKAGE_NAME); + } + + @NotNull + static Collection getApk(@NotNull final VariantScope scope) { + return getFinalArtifactFiles(scope, InternalArtifactType.APK); + } + + @NotNull + static Collection getJavac(@NotNull final VariantScope scope) { + return getFinalArtifactFiles(scope, InternalArtifactType.JAVAC); + } + + @NotNull + static Map> getAllArtifacts(@NotNull final VariantScope scope) { + return Stream.concat(Arrays.stream(InternalArtifactType.values()), Arrays.stream(AnchorOutputType.values())) + .collect(Collectors.toMap(Enum::name, v -> getFinalArtifactFiles(scope, v))); + } + + @NotNull + static Collection getFinalArtifactFiles(@NotNull final VariantScope scope, @NotNull final ArtifactType type) { + try { + return scope.getArtifacts().getFinalArtifactFiles(type).getFiles(); + } catch (final RuntimeException e) { + return Collections.emptyList(); + } + } + +} diff --git a/booster-gradle-base/build.gradle b/booster-gradle-base/build.gradle new file mode 100644 index 000000000..faa2ed56b --- /dev/null +++ b/booster-gradle-base/build.gradle @@ -0,0 +1,10 @@ +apply from: '../gradle/booster.gradle' + +dependencies { + compile gradleApi() + compile project(':booster-kotlinx') + compile project(':booster-android-gradle-api') + compile 'com.google.auto.service:auto-service:1.0-rc4' + compileOnly 'com.android.tools.build:gradle:3.0.0' + testCompileOnly 'com.android.tools.build:gradle:3.0.0' +} diff --git a/booster-gradle-base/src/main/kotlin/com/didiglobal/booster/gradle/BaseVariant.kt b/booster-gradle-base/src/main/kotlin/com/didiglobal/booster/gradle/BaseVariant.kt new file mode 100644 index 000000000..f563e51f3 --- /dev/null +++ b/booster-gradle-base/src/main/kotlin/com/didiglobal/booster/gradle/BaseVariant.kt @@ -0,0 +1,30 @@ +package com.didiglobal.booster.gradle + +import com.android.build.gradle.api.BaseVariant +import com.android.build.gradle.internal.api.InstallableVariantImpl +import com.android.build.gradle.internal.scope.VariantScope +import com.android.build.gradle.internal.variant.BaseVariantData + +/** + * The variant dependencies + * + * @author johnsonlee + */ +val BaseVariant.dependencies: ResolvedArtifactResults + get() = ResolvedArtifactResults(this) + +/** + * The variant scope + * + * @author johnsonlee + */ +val BaseVariant.scope: VariantScope + get() = variantData.scope + +/** + * The variant data + * + * @author johnsonlee + */ +val BaseVariant.variantData: BaseVariantData + get() = if (this is InstallableVariantImpl) this.variantData else javaClass.getDeclaredMethod("getVariantData").invoke(this) as BaseVariantData diff --git a/booster-gradle-base/src/main/kotlin/com/didiglobal/booster/gradle/Project.kt b/booster-gradle-base/src/main/kotlin/com/didiglobal/booster/gradle/Project.kt new file mode 100644 index 000000000..7e56a2988 --- /dev/null +++ b/booster-gradle-base/src/main/kotlin/com/didiglobal/booster/gradle/Project.kt @@ -0,0 +1,13 @@ +package com.didiglobal.booster.gradle + +import com.android.build.gradle.BaseExtension +import org.gradle.api.Project + +/** + * Returns android extension + * + * @author johnsonlee + */ +inline fun Project.getAndroid(): T { + return extensions.getByName("android") as T +} diff --git a/booster-gradle-base/src/main/kotlin/com/didiglobal/booster/gradle/ResolvedArtifactResults.kt b/booster-gradle-base/src/main/kotlin/com/didiglobal/booster/gradle/ResolvedArtifactResults.kt new file mode 100644 index 000000000..d5a5dd9c5 --- /dev/null +++ b/booster-gradle-base/src/main/kotlin/com/didiglobal/booster/gradle/ResolvedArtifactResults.kt @@ -0,0 +1,82 @@ +package com.didiglobal.booster.gradle + +import com.android.build.gradle.api.BaseVariant +import com.android.build.gradle.internal.publishing.AndroidArtifacts +import com.didiglobal.booster.kotlinx.Formatter +import com.didiglobal.booster.kotlinx.file +import com.didiglobal.booster.kotlinx.int +import com.didiglobal.booster.kotlinx.separatorsToSystem +import com.didiglobal.booster.kotlinx.touch +import org.gradle.api.artifacts.component.ProjectComponentIdentifier +import org.gradle.api.artifacts.result.ResolvedArtifactResult +import java.io.File +import java.io.PrintWriter + +/** + * Represents the dependencies of the specified variant + */ +class ResolvedArtifactResults(private val variant: BaseVariant) : Collection { + + private val results: Iterable + private val maxNameWidth: Int + private val maxFileWidth: Int + + init { + results = listOf(AndroidArtifacts.ArtifactType.AAR, AndroidArtifacts.ArtifactType.JAR) + .asSequence() + .map { variant.variantData.scope.getArtifactCollection(AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH, AndroidArtifacts.ArtifactScope.ALL, it) } + .map { it.artifacts } + .flatten() + .filter { it.id.componentIdentifier !is ProjectComponentIdentifier } + .toList() + .distinctBy { it.id.componentIdentifier.displayName } + .sortedBy { it.id.componentIdentifier.displayName } + .toList() + maxNameWidth = int(map { it.id.componentIdentifier.displayName.length }.max()).value + maxFileWidth = int(map { it.file.path.length }.max()).value + } + + override val size: Int + get() = results.count() + + override fun contains(element: ResolvedArtifactResult) = results.contains(element) + + override fun containsAll(elements: Collection) = results.intersect(elements).size == elements.size + + override fun isEmpty() = results.iterator().hasNext() + + override fun iterator(): Iterator = results.iterator() + + /** + * Default output location: $buildDir/intermediates/dependencies/${variantDirName}/dependencies.txt + */ + private val output = variant.variantData.scope.globalScope.intermediatesDir.file("dependencies").file(variant.dirName.separatorsToSystem()).file("dependencies.txt") + + /** + * Default dependency formatter + */ + private val formatter: Formatter = { result -> + result.id.componentIdentifier.displayName + " ".repeat(maxNameWidth + 1 - result.id.componentIdentifier.displayName.length) + result.file + " ".repeat(maxFileWidth + 1 - result.file.path.length) + result.file.length() + } + + /** + * Dump it to file with specific formatter + */ + fun dump(file: File = output, formatter: Formatter = this.formatter) { + file.touch().printWriter().use { + print(it, formatter) + } + } + + /** + * Print all component artifacts + */ + fun print(printer: PrintWriter = PrintWriter(System.out, true), formatter: Formatter = this.formatter) { + forEach { result -> + printer.apply { + println(formatter(result)) + }.flush() + } + } + +} diff --git a/booster-gradle-base/src/main/kotlin/com/didiglobal/booster/gradle/TransformInvocation.kt b/booster-gradle-base/src/main/kotlin/com/didiglobal/booster/gradle/TransformInvocation.kt new file mode 100644 index 000000000..d0339ffec --- /dev/null +++ b/booster-gradle-base/src/main/kotlin/com/didiglobal/booster/gradle/TransformInvocation.kt @@ -0,0 +1,63 @@ +package com.didiglobal.booster.gradle + +import com.android.build.api.transform.TransformInvocation +import com.android.build.gradle.AppExtension +import com.android.build.gradle.BaseExtension +import com.android.build.gradle.LibraryExtension +import com.android.build.gradle.api.BaseVariant +import org.gradle.api.Project +import org.gradle.api.internal.AbstractTask +import java.io.File +import java.net.URLClassLoader + +/** + * Represents the booster transform for + * + * @author johnsonlee + */ +val TransformInvocation.project: Project + get() = (this.context as AbstractTask).project + +/** + * Returns the corresponding variant of this transform invocation + * + * @author johnsonlee + */ +val TransformInvocation.variant: BaseVariant + get() = project.getAndroid().let { android -> + when (android) { + is AppExtension -> return android.applicationVariants.single { it.name == this.context.variantName } + is LibraryExtension -> return android.libraryVariants.single { it.name == this.context.variantName } + } + TODO("variant not found") + } + +/** + * Returns the compile classpath of this transform invocation + * + * @author johnsonlee + */ +val TransformInvocation.compileClasspath: Collection + get() = listOf(inputs, referencedInputs).flatten().map { + it.jarInputs + it.directoryInputs + }.flatten().map { + it.file + } + +/** + * Returns the runtime classpath of this transform invocation + * + * @author johnsonlee + */ +val TransformInvocation.runtimeClasspath: Collection + get() = compileClasspath + project.getAndroid().bootClasspath + +/** + * Returns the classloader of the specified invocation + * + * @author johnsonlee + */ +val TransformInvocation.classLoader: ClassLoader + get() = URLClassLoader(runtimeClasspath.map { + it.toURI().toURL() + }.toTypedArray()) diff --git a/booster-gradle-base/src/main/kotlin/com/didiglobal/booster/util/ComponentHandler.kt b/booster-gradle-base/src/main/kotlin/com/didiglobal/booster/util/ComponentHandler.kt new file mode 100644 index 000000000..9d9caffb3 --- /dev/null +++ b/booster-gradle-base/src/main/kotlin/com/didiglobal/booster/util/ComponentHandler.kt @@ -0,0 +1,36 @@ +package com.didiglobal.booster.util + +import org.xml.sax.Attributes +import org.xml.sax.helpers.DefaultHandler + +const val ATTR_NAME = "android:name" + +class ComponentHandler : DefaultHandler() { + + val applications = mutableSetOf() + val activities = mutableSetOf() + val services = mutableSetOf() + val providers = mutableSetOf() + val receivers = mutableSetOf() + + override fun startElement(uri: String, localName: String, qName: String, attributes: Attributes) { + when (qName) { + "application" -> { + applications.add(attributes.getValue(ATTR_NAME)) + } + "activity" -> { + activities.add(attributes.getValue(ATTR_NAME)) + } + "service" -> { + services.add(attributes.getValue(ATTR_NAME)) + } + "provider" -> { + providers.add(attributes.getValue(ATTR_NAME)) + } + "receiver" -> { + receivers.add(attributes.getValue(ATTR_NAME)) + } + } + } + +} diff --git a/booster-gradle-plugin/build.gradle b/booster-gradle-plugin/build.gradle new file mode 100644 index 000000000..5d29755b5 --- /dev/null +++ b/booster-gradle-plugin/build.gradle @@ -0,0 +1,12 @@ +apply from: '../gradle/booster.gradle' + +dependencies { + kapt 'com.google.auto.service:auto-service:1.0-rc4' + compile project(':booster-gradle-base') + compile project(':booster-task-spi') + compile project(':booster-transform-spi') + compile project(':booster-transform-util') + compile 'com.google.auto.service:auto-service:1.0-rc4' + compileOnly 'com.android.tools.build:gradle:3.0.0' + testCompileOnly 'com.android.tools.build:gradle:3.0.0' +} diff --git a/booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterAppTransform.kt b/booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterAppTransform.kt new file mode 100644 index 000000000..a6d17d9e3 --- /dev/null +++ b/booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterAppTransform.kt @@ -0,0 +1,15 @@ +package com.didiglobal.booster.gradle + +import com.android.build.api.transform.QualifiedContent +import com.android.build.gradle.internal.pipeline.TransformManager + +/** + * Represents android transform for application project + * + * @author johnsonlee + */ +class BoosterAppTransform : BoosterTransform() { + + override fun getScopes(): MutableSet = TransformManager.SCOPE_FULL_PROJECT + +} diff --git a/booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterLibTransform.kt b/booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterLibTransform.kt new file mode 100644 index 000000000..280e24752 --- /dev/null +++ b/booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterLibTransform.kt @@ -0,0 +1,15 @@ +package com.didiglobal.booster.gradle + +import com.android.build.api.transform.QualifiedContent +import com.android.build.gradle.internal.pipeline.TransformManager + +/** + * Represents android transform for library project + * + * @author johnsonlee + */ +class BoosterLibTransform : BoosterTransform() { + + override fun getScopes(): MutableSet = TransformManager.PROJECT_ONLY + +} diff --git a/booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterPlugin.kt b/booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterPlugin.kt new file mode 100644 index 000000000..cb54ddbb8 --- /dev/null +++ b/booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterPlugin.kt @@ -0,0 +1,46 @@ +package com.didiglobal.booster.gradle + +import com.android.build.gradle.AppExtension +import com.android.build.gradle.LibraryExtension +import com.didiglobal.booster.task.spi.VariantProcessor +import org.gradle.api.Plugin +import org.gradle.api.Project +import java.util.ServiceLoader + +/** + * Represents the booster gradle plugin + * + * @author johnsonlee + */ +class BoosterPlugin : Plugin { + + override fun apply(project: Project) { + when { + project.plugins.hasPlugin("com.android.application") -> project.getAndroid().let { android -> + android.registerTransform(BoosterAppTransform()) + project.afterEvaluate { + ServiceLoader.load(VariantProcessor::class.java, javaClass.classLoader).toList().let { processors -> + android.applicationVariants.forEach { variant -> + processors.forEach { processor -> + processor.process(variant) + } + } + } + } + } + project.plugins.hasPlugin("com.android.library") -> project.getAndroid().let { android -> + android.registerTransform(BoosterLibTransform()) + project.afterEvaluate { + ServiceLoader.load(VariantProcessor::class.java, javaClass.classLoader).toList().let { processors -> + android.libraryVariants.forEach { variant -> + processors.forEach { processor -> + processor.process(variant) + } + } + } + } + } + } + } + +} diff --git a/booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterTransform.kt b/booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterTransform.kt new file mode 100644 index 000000000..cd6182798 --- /dev/null +++ b/booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterTransform.kt @@ -0,0 +1,58 @@ +package com.didiglobal.booster.gradle + +import com.android.build.api.transform.QualifiedContent +import com.android.build.api.transform.Transform +import com.android.build.api.transform.TransformInvocation +import com.android.build.gradle.internal.pipeline.TransformManager +import com.didiglobal.booster.kotlinx.file +import com.didiglobal.booster.kotlinx.touch +import java.util.concurrent.TimeUnit + +/** + * Represents the transform base + * + * @author johnsonlee + */ +abstract class BoosterTransform : Transform() { + + override fun getName() = "booster" + + override fun isIncremental() = true + + override fun getInputTypes(): MutableSet = TransformManager.CONTENT_CLASS + + final override fun transform(invocation: TransformInvocation?) { + if (invocation == null) { + return + } + + BoosterTransformInvocation(invocation).apply { + dumpInputs(this) + + if (isIncremental) { + onPreTransform(this) + doIncrementalTransform() + } else { + outputProvider.deleteAll() + onPreTransform(this) + doFullTransform() + } + + this.onPostTransform(this) + }.executor.apply { + shutdown() + awaitTermination(1, TimeUnit.MINUTES) + } + } + + private fun dumpInputs(invocation: BoosterTransformInvocation) { + invocation.context.temporaryDir.file("inputs.txt").touch().printWriter().use { printer -> + invocation.inputs.flatMap { + it.jarInputs + }.map { + it.file + }.forEach(printer::println) + } + } + +} diff --git a/booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterTransformInvocation.kt b/booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterTransformInvocation.kt new file mode 100644 index 000000000..987168e0b --- /dev/null +++ b/booster-gradle-plugin/src/main/kotlin/com/didiglobal/booster/gradle/BoosterTransformInvocation.kt @@ -0,0 +1,158 @@ +package com.didiglobal.booster.gradle + +import com.android.build.api.transform.Context +import com.android.build.api.transform.Format +import com.android.build.api.transform.SecondaryInput +import com.android.build.api.transform.Status +import com.android.build.api.transform.TransformInput +import com.android.build.api.transform.TransformInvocation +import com.android.build.api.transform.TransformOutputProvider +import com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactScope.ALL +import com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactType.AAR +import com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactType.JAR +import com.android.build.gradle.internal.publishing.AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH +import com.didiglobal.booster.kotlinx.ifNotEmpty +import com.didiglobal.booster.transform.ArtifactManager +import com.didiglobal.booster.transform.TransformContext +import com.didiglobal.booster.transform.TransformListener +import com.didiglobal.booster.transform.Transformer +import com.didiglobal.booster.transform.util.transform +import java.io.File +import java.util.ServiceLoader +import java.util.concurrent.ForkJoinPool + +/** + * Represents a delegate of TransformInvocation + * + * @author johnsonlee + */ +internal class BoosterTransformInvocation(private val delegate: TransformInvocation) : TransformInvocation, TransformContext, TransformListener, ArtifactManager { + + /* + * Preload transformers as List to fix NoSuchElementException caused by ServiceLoader in parallel mode + */ + private val transformers = ServiceLoader.load(Transformer::class.java, javaClass.classLoader).toList() + + override val name: String = delegate.context.variantName + + override val projectDir: File = delegate.project.projectDir + + override val buildDir: File = delegate.project.buildDir + + override val temporaryDir: File = delegate.context.temporaryDir + + override val executor = ForkJoinPool(Runtime.getRuntime().availableProcessors(), ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, true) + + override val compileClasspath = delegate.compileClasspath + + override val runtimeClasspath = delegate.runtimeClasspath + + override val artifacts = this + + override fun hasProperty(name: String): Boolean { + return project.hasProperty(name) + } + + override fun getProperty(name: String): String? { + return project.properties[name]?.toString() + } + + override fun getInputs(): MutableCollection = delegate.inputs + + override fun getSecondaryInputs(): MutableCollection = delegate.secondaryInputs + + override fun getReferencedInputs(): MutableCollection = delegate.referencedInputs + + override fun isIncremental() = delegate.isIncremental + + override fun getOutputProvider(): TransformOutputProvider = delegate.outputProvider + + override fun getContext(): Context = delegate.context + + override fun onPreTransform(context: TransformContext) { + transformers.forEach { + it.onPreTransform(this) + } + } + + override fun onPostTransform(context: TransformContext) { + transformers.forEach { + it.onPostTransform(this) + } + } + + override fun get(type: String): Collection { + when (type) { + ArtifactManager.AAR -> return variant.scope.getArtifactCollection(RUNTIME_CLASSPATH, ALL, AAR).artifactFiles.files + ArtifactManager.ALL_CLASSES -> return variant.scope.allClasses + ArtifactManager.APK -> return variant.scope.apk + ArtifactManager.JAR -> return variant.scope.getArtifactCollection(RUNTIME_CLASSPATH, ALL, JAR).artifactFiles.files + ArtifactManager.JAVAC -> return variant.scope.javac + ArtifactManager.MERGED_ASSETS -> return variant.scope.mergedAssets + ArtifactManager.MERGED_RES -> return variant.scope.mergedRes + ArtifactManager.MERGED_MANIFESTS -> return variant.scope.mergedManifests + ArtifactManager.PROCESSED_RES -> return variant.scope.processedRes + ArtifactManager.SYMBOL_LIST -> return variant.scope.symbolList + ArtifactManager.SYMBOL_LIST_WITH_PACKAGE_NAME -> return variant.scope.symbolListWithPackageName + } + TODO("Unexpected type: $type") + } + + internal fun doFullTransform() { + this.inputs.parallelStream().forEach { input -> + input.directoryInputs.parallelStream().forEach { + it.file.transform(outputProvider.getContentLocation(it.file.name, it.contentTypes, it.scopes, Format.DIRECTORY)) { bytecode -> + bytecode.transform(this) + } + } + input.jarInputs.parallelStream().forEach { + it.file.transform(outputProvider.getContentLocation(it.name, it.contentTypes, it.scopes, Format.JAR)) { bytecode -> + bytecode.transform(this) + } + } + } + } + + internal fun doIncrementalTransform() { + this.inputs.parallelStream().forEach { input -> + input.jarInputs.parallelStream().filter { it.status != Status.NOTCHANGED }.forEach { jarInput -> + if (Status.REMOVED == jarInput.status || Status.CHANGED == jarInput.status) { + jarInput.file.delete() + } + + if (Status.ADDED == jarInput.status || Status.CHANGED == jarInput.status) { + val root = outputProvider.getContentLocation(jarInput.name, jarInput.contentTypes, jarInput.scopes, Format.JAR) + jarInput.file.transform(root) { bytecode -> + bytecode.transform(this) + } + } + } + + input.directoryInputs.parallelStream().forEach { dirInput -> + dirInput.changedFiles.ifNotEmpty { + it.forEach { file, status -> + if (Status.REMOVED == status || Status.CHANGED == status) { + file.delete() + } + + if (Status.ADDED == status || Status.CHANGED == status) { + val root = outputProvider.getContentLocation(dirInput.name, dirInput.contentTypes, dirInput.scopes, Format.DIRECTORY) + val path = file.absolutePath.substring(dirInput.file.absolutePath.length + File.separator.length) + file.transform(File(root, path)) { bytecode -> + bytecode.transform(this) + } + } + } + + } + } + } + } + + private fun ByteArray.transform(invocation: BoosterTransformInvocation): ByteArray { + return transformers.fold(this) { bytes, transformer -> + transformer.transform(invocation, bytes) + } + } + +} diff --git a/booster-gradle-plugin/src/main/resources/META-INF/gradle-plugins/com.didiglobal.booster.properties b/booster-gradle-plugin/src/main/resources/META-INF/gradle-plugins/com.didiglobal.booster.properties new file mode 100644 index 000000000..9a04a4e74 --- /dev/null +++ b/booster-gradle-plugin/src/main/resources/META-INF/gradle-plugins/com.didiglobal.booster.properties @@ -0,0 +1 @@ +implementation-class=com.didiglobal.booster.gradle.BoosterPlugin diff --git a/booster-kotlinx/build.gradle b/booster-kotlinx/build.gradle new file mode 100644 index 000000000..345bf9ee5 --- /dev/null +++ b/booster-kotlinx/build.gradle @@ -0,0 +1 @@ +apply from: '../gradle/booster.gradle' diff --git a/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/FileTree.kt b/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/FileTree.kt new file mode 100644 index 000000000..9186fde11 --- /dev/null +++ b/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/FileTree.kt @@ -0,0 +1,42 @@ +package com.didiglobal.booster.kotlinx + +import java.io.File +import java.util.concurrent.ForkJoinPool +import java.util.concurrent.RecursiveTask + +private val forkJoinPool = ForkJoinPool() + +/** + * Represents a file tree which walk in parallel + * + * @author johnsonlee + */ +internal class FileTree(private val root: File) : Iterable { + + override fun iterator() = walk() + + private fun walk(): Iterator { + return forkJoinPool.invoke(FileWalker(root)).iterator() + } + + class FileWalker(private val root: File) : RecursiveTask>() { + + override fun compute(): List { + val tasks = mutableListOf>>() + val result = mutableListOf() + + root.listFiles()?.forEach { file -> + result.add(file) + if (file.isDirectory) { + FileWalker(file).also { task -> + tasks.add(task) + }.fork() + } + } + + return result + tasks.flatMap { it.join() } + } + + } + +} diff --git a/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/Wildcard.kt b/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/Wildcard.kt new file mode 100644 index 000000000..768223bbd --- /dev/null +++ b/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/Wildcard.kt @@ -0,0 +1,119 @@ +package com.didiglobal.booster.kotlinx + +import java.util.ArrayList +import java.util.Stack + +class Wildcard(private val pattern: String, private val ignoreCase: Boolean = false) { + + fun matches(text: String): Boolean { + val wcs = splitOnTokens(pattern) + var anyChars = false + var textIdx = 0 + var wcsIdx = 0 + val backtrack = Stack() + + do { + if (backtrack.size > 0) { + val array = backtrack.pop() + wcsIdx = array[0] + textIdx = array[1] + anyChars = true + } + + while (wcsIdx < wcs.size) { + + if (wcs[wcsIdx] == "?") { + textIdx++ + if (textIdx > text.length) { + break + } + anyChars = false + + } else if (wcs[wcsIdx] == "*") { + anyChars = true + if (wcsIdx == wcs.size - 1) { + textIdx = text.length + } + + } else { + if (anyChars) { + textIdx = checkIndexOf(text, textIdx, wcs[wcsIdx]) + if (textIdx == -1) { + break + } + val repeat = checkIndexOf(text, textIdx + 1, wcs[wcsIdx]) + if (repeat >= 0) { + backtrack.push(intArrayOf(wcsIdx, repeat)) + } + } else { + if (!checkRegionMatches(text, textIdx, wcs[wcsIdx])) { + break + } + } + + textIdx += wcs[wcsIdx].length + anyChars = false + } + + wcsIdx++ + } + + if (wcsIdx == wcs.size && textIdx == text.length) { + return true + } + + } while (backtrack.size > 0) + + return false + } + + private fun splitOnTokens(text: String): Array { + // used by wildcardMatch + // package level so a unit test may run on this + + if (text.indexOf('?') == -1 && text.indexOf('*') == -1) { + return arrayOf(text) + } + + val array = text.toCharArray() + val list = ArrayList() + val buffer = StringBuilder() + for (i in array.indices) { + if (array[i] == '?' || array[i] == '*') { + if (buffer.isNotEmpty()) { + list.add(buffer.toString()) + buffer.setLength(0) + } + if (array[i] == '?') { + list.add("?") + } else if (list.isEmpty() || i > 0 && list[list.size - 1] != "*") { + list.add("*") + } + } else { + buffer.append(array[i]) + } + } + if (buffer.isNotEmpty()) { + list.add(buffer.toString()) + } + + return list.toTypedArray() + } + + private fun checkIndexOf(str: String, strStartIndex: Int, search: String): Int { + val endIndex = str.length - search.length + if (endIndex >= strStartIndex) { + for (i in strStartIndex..endIndex) { + if (checkRegionMatches(str, i, search)) { + return i + } + } + } + return -1 + } + + private fun checkRegionMatches(str: String, strStartIndex: Int, search: String): Boolean { + return str.regionMatches(strStartIndex, search, 0, search.length, ignoreCase = ignoreCase) + } + +} diff --git a/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/ansi.kt b/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/ansi.kt new file mode 100644 index 000000000..f06ae57e8 --- /dev/null +++ b/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/ansi.kt @@ -0,0 +1,44 @@ +package com.didiglobal.booster.kotlinx + +const val ESC = '\u001B' + +const val RESET = "$ESC[0m" + +const val BOLD = "$ESC[1m" +const val UNDERLINE = "$ESC[4m" +const val BLINK = "$ESC[5m" +const val REVERSED = "$ESC[7m" + +const val BLACK = "$ESC[30m" +const val RED = "$ESC[31m" +const val GREEN = "$ESC[32m" +const val YELLOW = "$ESC[33m" +const val BLUE = "$ESC[34m" +const val MAGENTA = "$ESC[35m" +const val CYAN = "$ESC[36m" +const val WHITE = "$ESC[37m" +const val BRIGHT_BLACK = "$ESC[30,1m" +const val BRIGHT_RED = "$ESC[31,1m" +const val BRIGHT_GREEN = "$ESC[32,1m" +const val BRIGHT_YELLOW = "$ESC[33,1m" +const val BRIGHT_BLUE = "$ESC[34,1m" +const val BRIGHT_MAGENTA = "$ESC[35,1m" +const val BRIGHT_CYAN = "$ESC[36,1m" +const val BRIGHT_WHITE = "$ESC[37,1m" + +const val BACKGROUND_BLACK = "$ESC[40m" +const val BACKGROUND_RED = "$ESC[41m" +const val BACKGROUND_GREEN = "$ESC[42m" +const val BACKGROUND_YELLOW = "$ESC[43m" +const val BACKGROUND_BLUE = "$ESC[44m" +const val BACKGROUND_MAGENTA = "$ESC[45m" +const val BACKGROUND_CYAN = "$ESC[46m" +const val BACKGROUND_WHITE = "$ESC[47m" +const val BACKGROUND_BRIGHT_BLACK = "$ESC[40,1m" +const val BACKGROUND_BRIGHT_RED = "$ESC[41,1m" +const val BACKGROUND_BRIGHT_GREEN = "$ESC[42,1m" +const val BACKGROUND_BRIGHT_YELLOW = "$ESC[43,1m" +const val BACKGROUND_BRIGHT_BLUE = "$ESC[44,1m" +const val BACKGROUND_BRIGHT_MAGENTA = "$ESC[45,1m" +const val BACKGROUND_BRIGHT_CYAN = "$ESC[46,1m" +const val BACKGROUND_BRIGHT_WHITE = "$ESC[47,1m" diff --git a/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/collections.kt b/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/collections.kt new file mode 100644 index 000000000..9b763b78c --- /dev/null +++ b/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/collections.kt @@ -0,0 +1,30 @@ +package com.didiglobal.booster.kotlinx + +import java.util.stream.Stream +import java.util.stream.StreamSupport + +fun Iterable.isEmpty() = iterator().hasNext() + +inline fun Map.ifNotEmpty(action: (Map) -> Unit): Map { + if (isNotEmpty()) { + action(this) + } + return this +} + +inline fun Collection.ifNotEmpty(action: (Collection) -> Unit): Collection { + if (isNotEmpty()) { + action(this) + } + return this +} + +fun Iterator.asIterable(): Iterable = Iterable { this } + +fun Iterator.stream(): Stream = asIterable().stream() + +fun Iterator.parallelStream(): Stream = asIterable().parallelStream() + +fun Iterable.stream(): Stream = StreamSupport.stream(spliterator(), false) + +fun Iterable.parallelStream(): Stream = StreamSupport.stream(spliterator(), true) diff --git a/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/io.kt b/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/io.kt new file mode 100644 index 000000000..be36b7a59 --- /dev/null +++ b/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/io.kt @@ -0,0 +1,188 @@ +package com.didiglobal.booster.kotlinx + +import java.io.BufferedReader +import java.io.File +import java.io.InputStream +import java.io.InputStreamReader +import java.io.OutputStream +import java.io.Reader +import java.util.jar.JarEntry +import java.util.jar.JarFile +import java.util.jar.JarOutputStream +import java.util.zip.ZipEntry +import java.util.zip.ZipFile +import java.util.zip.ZipOutputStream + +fun File.file(vararg path: String) = File(this, path.joinToString(File.separator)) + +/** + * Create a new file if not exists + * + * @author johnsonlee + */ +fun File.touch(): File { + if (!this.exists()) { + this.parentFile?.mkdirs() + this.createNewFile() + } + return this +} + +fun File.ifExists(block: (File) -> Unit) { + if (this.exists()) { + block(this) + } +} + +/** + * Archive this file as a JAR + * + * @author johnsonlee + */ +fun File.jar(): JarFile { + return jar(File.createTempFile("tmp-", ".jar")) +} + +/** + * Archive this file as the specified JAR + * + * @author johnsonlee + */ +fun File.jar(dest: File): JarFile { + JarOutputStream(dest.outputStream()).use { output -> + if (this.isFile) { + output.putNextEntry(JarEntry(this.name)) + this.inputStream().use { input -> + input.copyTo(output) + } + } else { + this.parallelWalk().filter { it != this && it.isFile }.forEach { file -> + val path = file.absolutePath.substring(this.absolutePath.length + File.separator.length) + output.putNextEntry(JarEntry(path.separatorsToUnix())) + file.inputStream().use { input -> + input.copyTo(output) + } + } + } + } + return JarFile(dest) +} + +fun File.unjar(out: File): List { + val files = mutableListOf() + + JarFile(this).use { jar -> + jar.entries().asSequence().filter { !it.isDirectory }.forEach { entry -> + val path = entry.name.separatorsToSystem() + jar.getInputStream(entry).use { input -> + val file = File(out, path) + input.redirect(file) + files.add(file) + } + } + } + + return files +} + +fun File.zip(): ZipFile { + return this.zip(File.createTempFile("tmp-", ".zip")) +} + +fun File.zip(dest: File): ZipFile { + ZipOutputStream(dest.outputStream()).use { output -> + if (this.isFile) { + output.putNextEntry(ZipEntry(this.name)) + this.inputStream().use { input -> + input.copyTo(output) + } + } else { + this.parallelWalk().filter { it != this && it.isFile }.forEach { file -> + val path = file.absolutePath.substring(this.absolutePath.length + File.separator.length) + output.putNextEntry(ZipEntry(path.separatorsToUnix())) + file.inputStream().use { input -> + input.copyTo(output) + } + } + } + } + return ZipFile(dest) +} + +fun File.unzip(out: File): List { + var files = mutableListOf() + + ZipFile(this).use { zip -> + zip.entries().asSequence().filter { !it.isDirectory }.forEach { entry -> + val path = entry.name.separatorsToSystem() + zip.getInputStream(entry).use { input -> + val file = File(out, path) + input.redirect(file) + files.add(file) + } + } + } + + return files +} + +/** + * Walk file in parallel + */ +fun File.parallelWalk(): Iterable { + return FileTree(this) +} + +/** + * Return the first line of file + */ +fun File.head(): String? { + return inputStream().use { + it.head() + } +} + +/** + * Returns the first line of input stream + */ +fun InputStream.head(): String? { + return BufferedReader(InputStreamReader(this)).head() +} + +/** + * Returns the first line of reader + */ +fun Reader.head(): String? { + return BufferedReader(this).readLine() +} + +/** + * Redirect this input stream to the specified file + * + * @author johnsonlee + */ +fun InputStream.redirect(file: File): Long { + file.touch().outputStream().use { + return this.copyTo(it) + } +} + +/** + * Redirect this byte data to the specified file + * + * @author johnsonlee + */ +fun ByteArray.redirect(file: File): Long { + this.inputStream().use { + return it.redirect(file) + } +} + +/** + * Redirect this byte data to the specified output stream + * + * @author johnsonlee + */ +fun ByteArray.redirect(output: OutputStream): Long { + return this.inputStream().copyTo(output) +} diff --git a/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/primitive.kt b/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/primitive.kt new file mode 100644 index 000000000..054d03701 --- /dev/null +++ b/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/primitive.kt @@ -0,0 +1,29 @@ +package com.didiglobal.booster.kotlinx + +data class byte(val value: Byte = 0) { + constructor(value: Byte? = null) : this(value ?: 0) +} + +data class char(val value: Char = '\u0000') { + constructor(value: Char? = null) : this(value ?: '\u0000') +} + +data class short(val value: Short = 0) { + constructor(value: Short? = null) : this(value ?: 0) +} + +data class int(val value: Int = 0) { + constructor(value: Int? = null) : this(value ?: 0) +} + +data class float(val value: Float = 0f) { + constructor(value: Float? = null) : this(value ?: 0f) +} + +data class double(val value: Double = 0.0) { + constructor(value: Double? = null) : this(value ?: 0.0) +} + +data class boolean(val value: Boolean = false) { + constructor(value: Boolean? = null) : this(value ?: false) +} diff --git a/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/text.kt b/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/text.kt new file mode 100644 index 000000000..327c13419 --- /dev/null +++ b/booster-kotlinx/src/main/kotlin/com/didiglobal/booster/kotlinx/text.kt @@ -0,0 +1,23 @@ +package com.didiglobal.booster.kotlinx + +import java.io.File +import java.math.BigInteger +import java.security.MessageDigest + +fun String.separatorsToUnix(): String { + return if ('/' == File.separatorChar) this else this.replace(File.separatorChar, '/') +} + +fun String.separatorsToSystem(): String { + return if ('/' != File.separatorChar && this.contains('/')) this.replace('/', File.separatorChar) else this +} + +fun String.md5(): String { + return BigInteger(1, MessageDigest.getInstance("MD5").digest(toByteArray())).toString(16).padStart(32, '0') +} + +fun String.matches(wildcard: Wildcard): Boolean { + return wildcard.matches(this) +} + +typealias Formatter = (value: T) -> CharSequence diff --git a/booster-kotlinx/src/test/kotlin/com/didiglobal/booster/kotlinx/FileTest.kt b/booster-kotlinx/src/test/kotlin/com/didiglobal/booster/kotlinx/FileTest.kt new file mode 100644 index 000000000..5c17e7396 --- /dev/null +++ b/booster-kotlinx/src/test/kotlin/com/didiglobal/booster/kotlinx/FileTest.kt @@ -0,0 +1,32 @@ +package com.didiglobal.booster.kotlinx + +import java.io.File +import kotlin.test.Test +import kotlin.test.assertEquals + +/** + * Unit test for File extension + * + * @author johnsonlee + */ +class FileTest { + + @Test + fun `archive current dir as a jar`() { + val pwd = File(System.getProperty("user.dir")) + val count = pwd.walk().filter { it != pwd && it.isFile }.count() + assertEquals(count, pwd.jar().entries().asSequence().count()) + } + + @Test + fun `read the 1st line of file`() { + val head = "Hello, booster!" + val file = File.createTempFile("FileTest-", ".txt") + + file.printWriter().use { + it.println(head) + } + assertEquals(head, file.head()) + } + +} diff --git a/booster-kotlinx/src/test/kotlin/com/didiglobal/booster/kotlinx/WildcardTest.kt b/booster-kotlinx/src/test/kotlin/com/didiglobal/booster/kotlinx/WildcardTest.kt new file mode 100644 index 000000000..42d34721f --- /dev/null +++ b/booster-kotlinx/src/test/kotlin/com/didiglobal/booster/kotlinx/WildcardTest.kt @@ -0,0 +1,15 @@ +package com.didiglobal.booster.kotlinx + +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class WildcardTest { + + @Test + fun `wildcard matching`() { + assertFalse("java/io/File.exists()B".matches(Wildcard("java/x/**"))) + assertTrue("java/io/File.exists()B".matches(Wildcard("java/io/*"))) + } + +} diff --git a/booster-task-all/build.gradle b/booster-task-all/build.gradle new file mode 100644 index 000000000..2b52ad3d2 --- /dev/null +++ b/booster-task-all/build.gradle @@ -0,0 +1,5 @@ +dependencies { + compile project(':booster-task-artifact') + compile project(':booster-task-dependency') + compile project(':booster-task-permission') +} diff --git a/booster-task-artifact/build.gradle b/booster-task-artifact/build.gradle new file mode 100644 index 000000000..c01d0d476 --- /dev/null +++ b/booster-task-artifact/build.gradle @@ -0,0 +1,9 @@ +apply from: '../gradle/booster.gradle' + +dependencies { + kapt "com.google.auto.service:auto-service:1.0-rc4" + implementation project(':booster-gradle-base') + implementation project(':booster-task-spi') + compileOnly 'com.android.tools.build:gradle:3.0.0' + testCompileOnly 'com.android.tools.build:gradle:3.0.0' +} diff --git a/booster-task-artifact/src/main/kotlin/com/didiglobal/booster/task/artifact/ArtifactVariantProcessor.kt b/booster-task-artifact/src/main/kotlin/com/didiglobal/booster/task/artifact/ArtifactVariantProcessor.kt new file mode 100644 index 000000000..afd10400f --- /dev/null +++ b/booster-task-artifact/src/main/kotlin/com/didiglobal/booster/task/artifact/ArtifactVariantProcessor.kt @@ -0,0 +1,22 @@ +package com.didiglobal.booster.task.artifact + +import com.android.build.gradle.api.BaseVariant +import com.didiglobal.booster.gradle.scope +import com.didiglobal.booster.task.spi.VariantProcessor +import com.google.auto.service.AutoService + +@AutoService(VariantProcessor::class) +class ArtifactVariantProcessor : VariantProcessor { + + override fun process(variant: BaseVariant) { + val tasks = variant.scope.globalScope.project.tasks + val artifacts = tasks.findByName("showArtifacts") ?: tasks.create("showArtifacts") + tasks.create("show${variant.name.capitalize()}Artifacts", ArtifactsResolver::class.java) { + it.variant = variant + it.outputs.upToDateWhen { false } + }.also { + artifacts.dependsOn(it) + } + } + +} diff --git a/booster-task-artifact/src/main/kotlin/com/didiglobal/booster/task/artifact/ArtifactsResolver.kt b/booster-task-artifact/src/main/kotlin/com/didiglobal/booster/task/artifact/ArtifactsResolver.kt new file mode 100644 index 000000000..f7cdf13a4 --- /dev/null +++ b/booster-task-artifact/src/main/kotlin/com/didiglobal/booster/task/artifact/ArtifactsResolver.kt @@ -0,0 +1,23 @@ +package com.didiglobal.booster.task.artifact + +import com.android.build.gradle.api.BaseVariant +import com.didiglobal.booster.gradle.allArtifacts +import com.didiglobal.booster.gradle.scope +import org.gradle.api.DefaultTask +import org.gradle.api.tasks.TaskAction + +internal open class ArtifactsResolver : DefaultTask() { + + lateinit var variant: BaseVariant + + @TaskAction + fun run() { + val artifacts = this.variant.scope.allArtifacts + val maxTypeWidth: Int = artifacts.keys.map { it.length }.max()!! + + artifacts.forEach { type, files -> + println("${".".repeat(maxTypeWidth - type.length + 1)}$type : $files") + } + } + +} diff --git a/booster-task-dependency/build.gradle b/booster-task-dependency/build.gradle new file mode 100644 index 000000000..c01d0d476 --- /dev/null +++ b/booster-task-dependency/build.gradle @@ -0,0 +1,9 @@ +apply from: '../gradle/booster.gradle' + +dependencies { + kapt "com.google.auto.service:auto-service:1.0-rc4" + implementation project(':booster-gradle-base') + implementation project(':booster-task-spi') + compileOnly 'com.android.tools.build:gradle:3.0.0' + testCompileOnly 'com.android.tools.build:gradle:3.0.0' +} diff --git a/booster-task-dependency/src/main/kotlin/com/didiglobal/booster/task/dependency/CheckSnapshot.kt b/booster-task-dependency/src/main/kotlin/com/didiglobal/booster/task/dependency/CheckSnapshot.kt new file mode 100644 index 000000000..88b842813 --- /dev/null +++ b/booster-task-dependency/src/main/kotlin/com/didiglobal/booster/task/dependency/CheckSnapshot.kt @@ -0,0 +1,29 @@ +package com.didiglobal.booster.task.dependency + +import com.android.build.gradle.api.BaseVariant +import com.didiglobal.booster.gradle.dependencies +import com.didiglobal.booster.kotlinx.RESET +import com.didiglobal.booster.kotlinx.YELLOW +import com.didiglobal.booster.kotlinx.ifNotEmpty +import org.gradle.api.DefaultTask +import org.gradle.api.internal.artifacts.repositories.resolver.MavenUniqueSnapshotComponentIdentifier +import org.gradle.api.tasks.TaskAction + +internal open class CheckSnapshot : DefaultTask() { + + lateinit var variant: BaseVariant + + @TaskAction + fun run() { + if (!variant.buildType.isDebuggable) { + variant.dependencies.filter { + it.id.componentIdentifier is MavenUniqueSnapshotComponentIdentifier + }.map { + it.id.componentIdentifier as MavenUniqueSnapshotComponentIdentifier + }.ifNotEmpty { snapshots -> + println("$YELLOW ⚠️ ${snapshots.size} SNAPSHOT artifacts found in ${variant.name} variant:$RESET\n${snapshots.joinToString("\n") { snapshot -> "$YELLOW→ ${snapshot.displayName}$RESET" }}") + } + } + } + +} diff --git a/booster-task-dependency/src/main/kotlin/com/didiglobal/booster/task/dependency/DependencyVariantProcessor.kt b/booster-task-dependency/src/main/kotlin/com/didiglobal/booster/task/dependency/DependencyVariantProcessor.kt new file mode 100644 index 000000000..a40bf2383 --- /dev/null +++ b/booster-task-dependency/src/main/kotlin/com/didiglobal/booster/task/dependency/DependencyVariantProcessor.kt @@ -0,0 +1,23 @@ +package com.didiglobal.booster.task.dependency + +import com.android.build.gradle.api.BaseVariant +import com.didiglobal.booster.gradle.scope +import com.didiglobal.booster.task.spi.VariantProcessor +import com.google.auto.service.AutoService + +@AutoService(VariantProcessor::class) +class DependencyVariantProcessor : VariantProcessor { + + override fun process(variant: BaseVariant) { + val tasks = variant.scope.globalScope.project.tasks + val checkSnapshot = tasks.findByName("checkSnapshot") ?: tasks.create("checkSnapshot") + tasks.create("check${variant.name.capitalize()}Snapshot", CheckSnapshot::class.java) { + it.variant = variant + it.outputs.upToDateWhen { false } + }.also { + variant.javaCompiler.dependsOn(it) + checkSnapshot.dependsOn(it) + } + } + +} diff --git a/booster-task-permission/build.gradle b/booster-task-permission/build.gradle new file mode 100644 index 000000000..c01d0d476 --- /dev/null +++ b/booster-task-permission/build.gradle @@ -0,0 +1,9 @@ +apply from: '../gradle/booster.gradle' + +dependencies { + kapt "com.google.auto.service:auto-service:1.0-rc4" + implementation project(':booster-gradle-base') + implementation project(':booster-task-spi') + compileOnly 'com.android.tools.build:gradle:3.0.0' + testCompileOnly 'com.android.tools.build:gradle:3.0.0' +} diff --git a/booster-task-permission/src/main/kotlin/com/didiglobal/booster/task/permission/PermissionExtractor.kt b/booster-task-permission/src/main/kotlin/com/didiglobal/booster/task/permission/PermissionExtractor.kt new file mode 100644 index 000000000..b174e61ba --- /dev/null +++ b/booster-task-permission/src/main/kotlin/com/didiglobal/booster/task/permission/PermissionExtractor.kt @@ -0,0 +1,88 @@ +package com.didiglobal.booster.task.permission + +import com.android.build.gradle.api.BaseVariant +import com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactScope.ALL +import com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactType.AAR +import com.android.build.gradle.internal.publishing.AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH +import com.didiglobal.booster.gradle.scope +import com.didiglobal.booster.kotlinx.RESET +import com.didiglobal.booster.kotlinx.YELLOW +import com.didiglobal.booster.kotlinx.ifNotEmpty +import org.gradle.api.DefaultTask +import org.gradle.api.tasks.TaskAction +import java.io.File +import java.util.zip.ZipFile +import javax.xml.parsers.SAXParserFactory + +internal open class PermissionExtractor : DefaultTask() { + + private val factory = SAXParserFactory.newInstance() + + lateinit var variant: BaseVariant + + init { + factory.isXIncludeAware = false + factory.isNamespaceAware = true + factory.setFeature("http://xml.org/sax/features/namespace-prefixes", true) + factory.setFeature("http://xml.org/sax/features/xmlns-uris", true) + factory.isValidating = false + } + + @TaskAction + fun run() { + variant.scope.getArtifactFileCollection(RUNTIME_CLASSPATH, ALL, AAR).files.forEach { aar -> + ZipFile(aar).use { zip -> + zip.getEntry("AndroidManifest.xml")?.let { entry -> + zip.getInputStream(entry).use { source -> + PermissionUsageHandler().also { handler -> + factory.newSAXParser().parse(source, handler) + }.permissions.sorted().ifNotEmpty { permissions -> + println("${aar.componentId} [$YELLOW${variant.name}$RESET]") + permissions.forEach { permission -> + println(" - $permission") + } + } + } + } + } + } + } + +} + +private val HEX = "[a-zA-Z0-9]+".toRegex() + +private val EXTRA_ANDROID_M2REPOSITORY = "${File.separator}extras${File.separator}android${File.separator}m2repository${File.separator}" +private val LEN_EXTRA_ANDROID_M2REPOSITORY = EXTRA_ANDROID_M2REPOSITORY.length + +private val EXTRA_GOOGLE_M2REPOSITORY = "${File.separator}extras${File.separator}google${File.separator}m2repository${File.separator}" +private val LEN_EXTRA_GOOGLE_M2REPOSITORY = EXTRA_GOOGLE_M2REPOSITORY.length + +internal val File.componentId: String + get() { + val parent = this.parentFile + if (parent.name.matches(HEX)) { + val version = parent.parentFile + val artifact = version.parentFile + val group = artifact.parentFile + return "${group.name}:${artifact.name}:${version.name}" + } + + this.absolutePath.let { + val idxAndroidM2 = it.indexOf(EXTRA_ANDROID_M2REPOSITORY) + if (idxAndroidM2 > -1) { + val artifact = parent.parentFile + val group = artifact.parentFile.absolutePath.substring(idxAndroidM2 + LEN_EXTRA_ANDROID_M2REPOSITORY).replace(File.separatorChar, '.') + return "$group:${artifact.name}:${parent.name}" + } + + val idxGoogleM2 = it.indexOf(EXTRA_GOOGLE_M2REPOSITORY) + if (idxGoogleM2 > -1) { + val artifact = parent.parentFile + val group = artifact.parentFile.absolutePath.substring(idxGoogleM2 + LEN_EXTRA_GOOGLE_M2REPOSITORY).replace(File.separatorChar, '.') + return "$group:${artifact.name}:${parent.name}" + } + + TODO("Unrecognizable AAR: $absolutePath") + } + } diff --git a/booster-task-permission/src/main/kotlin/com/didiglobal/booster/task/permission/PermissionUsageHandler.kt b/booster-task-permission/src/main/kotlin/com/didiglobal/booster/task/permission/PermissionUsageHandler.kt new file mode 100644 index 000000000..80b9a96b9 --- /dev/null +++ b/booster-task-permission/src/main/kotlin/com/didiglobal/booster/task/permission/PermissionUsageHandler.kt @@ -0,0 +1,18 @@ +package com.didiglobal.booster.task.permission + +import org.xml.sax.Attributes +import org.xml.sax.helpers.DefaultHandler + +internal class PermissionUsageHandler : DefaultHandler() { + + internal val permissions = mutableSetOf() + + override fun startElement(uri: String, localName: String, qName: String, attributes: Attributes) { + when (localName) { + "uses-permission" -> permissions.add(attributes.getValue("android:name")) + "uses-permission-sdk-23" -> permissions.add(attributes.getValue("android:name")) + "uses-permission-sdk-m" -> permissions.add(attributes.getValue("android:name")) + } + } + +} diff --git a/booster-task-permission/src/main/kotlin/com/didiglobal/booster/task/permission/PermissionVariantProcessor.kt b/booster-task-permission/src/main/kotlin/com/didiglobal/booster/task/permission/PermissionVariantProcessor.kt new file mode 100644 index 000000000..801659d6b --- /dev/null +++ b/booster-task-permission/src/main/kotlin/com/didiglobal/booster/task/permission/PermissionVariantProcessor.kt @@ -0,0 +1,22 @@ +package com.didiglobal.booster.task.permission + +import com.android.build.gradle.api.BaseVariant +import com.didiglobal.booster.gradle.scope +import com.didiglobal.booster.task.spi.VariantProcessor +import com.google.auto.service.AutoService + +@AutoService(VariantProcessor::class) +class PermissionVariantProcessor : VariantProcessor { + + override fun process(variant: BaseVariant) { + val tasks = variant.scope.globalScope.project.tasks + val showPermission = tasks.findByName("showPermissions") ?: tasks.create("showPermissions") + tasks.create("show${variant.name.capitalize()}Permissions", PermissionExtractor::class.java) { + it.variant = variant + it.outputs.upToDateWhen { false } + }.also { + showPermission.dependsOn(it) + } + } + +} diff --git a/booster-task-spi/build.gradle b/booster-task-spi/build.gradle new file mode 100644 index 000000000..84969aa0c --- /dev/null +++ b/booster-task-spi/build.gradle @@ -0,0 +1,6 @@ +apply from: '../gradle/booster.gradle' + +dependencies { + compileOnly 'com.android.tools.build:gradle:3.0.0' + testCompileOnly 'com.android.tools.build:gradle:3.0.0' +} diff --git a/booster-task-spi/src/main/kotlin/com/didiglobal/booster/task/spi/VariantProcessor.kt b/booster-task-spi/src/main/kotlin/com/didiglobal/booster/task/spi/VariantProcessor.kt new file mode 100644 index 000000000..c120853e4 --- /dev/null +++ b/booster-task-spi/src/main/kotlin/com/didiglobal/booster/task/spi/VariantProcessor.kt @@ -0,0 +1,9 @@ +package com.didiglobal.booster.task.spi + +import com.android.build.gradle.api.BaseVariant + +interface VariantProcessor { + + fun process(variant: BaseVariant) + +} diff --git a/booster-transform-all/build.gradle b/booster-transform-all/build.gradle new file mode 100644 index 000000000..ea65c0f44 --- /dev/null +++ b/booster-transform-all/build.gradle @@ -0,0 +1,6 @@ +dependencies { + compile project(':booster-transform-bugfix-toast') + compile project(':booster-transform-lint') + compile project(':booster-transform-shrink') + compile project(':booster-transform-usage') +} diff --git a/booster-transform-asm/build.gradle b/booster-transform-asm/build.gradle new file mode 100644 index 000000000..f69f06806 --- /dev/null +++ b/booster-transform-asm/build.gradle @@ -0,0 +1,14 @@ +apply from: '../gradle/booster.gradle' + +dependencies { + kapt "com.google.auto.service:auto-service:1.0-rc4" + compile gradleApi() + compile project(':booster-kotlinx') + compile project(':booster-transform-spi') + compile 'org.ow2.asm:asm:7.1' + compile 'org.ow2.asm:asm-analysis:7.1' + compile 'org.ow2.asm:asm-commons:7.1' + compile 'org.ow2.asm:asm-tree:7.1' + compile 'org.ow2.asm:asm-util:7.1' + compile 'com.google.auto.service:auto-service:1.0-rc4' +} diff --git a/booster-transform-asm/src/main/java/com/didiglobal/booster/transform/asm/ClassTransformer.java b/booster-transform-asm/src/main/java/com/didiglobal/booster/transform/asm/ClassTransformer.java new file mode 100644 index 000000000..904506aae --- /dev/null +++ b/booster-transform-asm/src/main/java/com/didiglobal/booster/transform/asm/ClassTransformer.java @@ -0,0 +1,29 @@ +package com.didiglobal.booster.transform.asm; + +import com.didiglobal.booster.transform.TransformContext; +import com.didiglobal.booster.transform.TransformListener; +import org.jetbrains.annotations.NotNull; +import org.objectweb.asm.tree.ClassNode; + +/** + * Represents class transformer + * + * @author johnsonlee + */ +public interface ClassTransformer extends TransformListener { + + /** + * Transform the specified class node + * + * @param context + * The transform context + * @param klass + * The class node to be transformed + * @return The transformed class node + */ + @NotNull + default ClassNode transform(@NotNull final TransformContext context, @NotNull final ClassNode klass) { + return klass; + } + +} diff --git a/booster-transform-asm/src/main/kotlin/com/didiglobal/booster/transform/asm/AsmTransformer.kt b/booster-transform-asm/src/main/kotlin/com/didiglobal/booster/transform/asm/AsmTransformer.kt new file mode 100644 index 000000000..c7edf0e38 --- /dev/null +++ b/booster-transform-asm/src/main/kotlin/com/didiglobal/booster/transform/asm/AsmTransformer.kt @@ -0,0 +1,45 @@ +package com.didiglobal.booster.transform.asm + +import com.didiglobal.booster.transform.TransformContext +import com.didiglobal.booster.transform.Transformer +import com.google.auto.service.AutoService +import org.objectweb.asm.ClassReader +import org.objectweb.asm.ClassWriter +import org.objectweb.asm.tree.ClassNode +import java.util.ServiceLoader + +/** + * Represents bytecode transformer using ASM + * + * @author johnsonlee + */ +@AutoService(Transformer::class) +class AsmTransformer : Transformer { + + /* + * Preload transformers as List to fix NoSuchElementException caused by ServiceLoader in parallel mode + */ + private val transformers = ServiceLoader.load(ClassTransformer::class.java, javaClass.classLoader).toList() + + override fun transform(context: TransformContext, bytecode: ByteArray): ByteArray { + return ClassWriter(ClassWriter.COMPUTE_MAXS).also { writer -> + transformers.fold(ClassNode().also { klass -> + ClassReader(bytecode).accept(klass, 0) + }) { klass, transformer -> + transformer.transform(context, klass) + }.accept(writer) + }.toByteArray() + } + + override fun onPreTransform(context: TransformContext) { + transformers.forEach { + it.onPreTransform(context) + } + } + + override fun onPostTransform(context: TransformContext) { + transformers.forEach { + it.onPostTransform(context) + } + } +} diff --git a/booster-transform-asm/src/main/kotlin/com/didiglobal/booster/transform/asm/ClassNode.kt b/booster-transform-asm/src/main/kotlin/com/didiglobal/booster/transform/asm/ClassNode.kt new file mode 100644 index 000000000..042744a64 --- /dev/null +++ b/booster-transform-asm/src/main/kotlin/com/didiglobal/booster/transform/asm/ClassNode.kt @@ -0,0 +1,9 @@ +package com.didiglobal.booster.transform.asm + +import org.objectweb.asm.tree.ClassNode + +/** + * The simple name of class + */ +val ClassNode.simpleName: String + get() = this.name.substring(this.name.lastIndexOf('/') + 1) diff --git a/booster-transform-asm/src/main/kotlin/com/didiglobal/booster/transform/asm/InsnList.kt b/booster-transform-asm/src/main/kotlin/com/didiglobal/booster/transform/asm/InsnList.kt new file mode 100644 index 000000000..43b984281 --- /dev/null +++ b/booster-transform-asm/src/main/kotlin/com/didiglobal/booster/transform/asm/InsnList.kt @@ -0,0 +1,27 @@ +package com.didiglobal.booster.transform.asm + +import com.didiglobal.booster.kotlinx.asIterable +import org.objectweb.asm.tree.AbstractInsnNode +import org.objectweb.asm.tree.InsnList + +/** + * Find the first instruction node with the specified opcode + * + * @param opcode The opcode to search + */ +fun InsnList.find(opcode: Int): AbstractInsnNode? { + return this.iterator().asIterable().find { + it.opcode == opcode + } +} + +/** + * Find all of instruction nodes with the specified opcode + * + * @param opcodes The opcode to search + */ +fun InsnList.findAll(vararg opcodes: Int): Collection { + return this.iterator().asIterable().filter { + it.opcode in opcodes + }.toList() +} diff --git a/booster-transform-bugfix-toast/build.gradle b/booster-transform-bugfix-toast/build.gradle new file mode 100644 index 000000000..b02db7d31 --- /dev/null +++ b/booster-transform-bugfix-toast/build.gradle @@ -0,0 +1,10 @@ +apply from: '../gradle/booster.gradle' + +dependencies { + kapt "com.google.auto.service:auto-service:1.0-rc4" + implementation project(':booster-gradle-base') + implementation project(':booster-task-spi') + implementation project(':booster-transform-asm') + compileOnly 'com.android.tools.build:gradle:3.0.0' + testCompileOnly 'com.android.tools.build:gradle:3.0.0' +} diff --git a/booster-transform-bugfix-toast/src/main/kotlin/com/didiglobal/booster/transform/bugfix/toast/ToastBugfixTransformer.kt b/booster-transform-bugfix-toast/src/main/kotlin/com/didiglobal/booster/transform/bugfix/toast/ToastBugfixTransformer.kt new file mode 100644 index 000000000..29cd963ed --- /dev/null +++ b/booster-transform-bugfix-toast/src/main/kotlin/com/didiglobal/booster/transform/bugfix/toast/ToastBugfixTransformer.kt @@ -0,0 +1,36 @@ +package com.didiglobal.booster.transform.bugfix.toast + +import com.didiglobal.booster.kotlinx.asIterable +import com.didiglobal.booster.transform.TransformContext +import com.didiglobal.booster.transform.asm.ClassTransformer +import com.google.auto.service.AutoService +import org.objectweb.asm.Opcodes +import org.objectweb.asm.tree.ClassNode +import org.objectweb.asm.tree.MethodInsnNode + +/** + * Represents a class node transformer used to fix [bug: 30150688](https://android.googlesource.com/platform/frameworks/base/+/dc24f93) + * + * @author johnsonlee + */ +@AutoService(ClassTransformer::class) +class ToastBugfixTransformer : ClassTransformer { + + override fun transform(context: TransformContext, klass: ClassNode): ClassNode { + klass.methods.forEach { method -> + method.instructions?.iterator()?.asIterable()?.filterIsInstance(MethodInsnNode::class.java)?.filter { + it.owner == TOAST && it.name == "show" && it.desc == "()V" + }?.forEach { + it.owner = `TOAST'` + it.desc = "(L$TOAST;)V" + it.opcode = Opcodes.INVOKESTATIC + } + } + return klass + } + +} + +private const val TOAST = "android/widget/Toast" + +private const val `TOAST'` = "com/didiglobal/booster/$TOAST" diff --git a/booster-transform-bugfix-toast/src/main/kotlin/com/didiglobal/booster/transform/bugfix/toast/ToastBugfixVariantProcessor.kt b/booster-transform-bugfix-toast/src/main/kotlin/com/didiglobal/booster/transform/bugfix/toast/ToastBugfixVariantProcessor.kt new file mode 100644 index 000000000..f11502095 --- /dev/null +++ b/booster-transform-bugfix-toast/src/main/kotlin/com/didiglobal/booster/transform/bugfix/toast/ToastBugfixVariantProcessor.kt @@ -0,0 +1,18 @@ +package com.didiglobal.booster.transform.bugfix.toast + +import com.android.build.gradle.api.BaseVariant +import com.android.build.gradle.api.LibraryVariant +import com.didiglobal.booster.gradle.scope +import com.didiglobal.booster.task.spi.VariantProcessor +import com.google.auto.service.AutoService + +@AutoService(VariantProcessor::class) +class ToastBugfixVariantProcessor : VariantProcessor { + + override fun process(variant: BaseVariant) { + if (variant !is LibraryVariant) { + variant.scope.globalScope.project.dependencies.add("implementation", "${Build.GROUP}:booster-android-bugfix-toast:${Build.VERSION}") + } + } + +} diff --git a/booster-transform-lint/build.gradle b/booster-transform-lint/build.gradle new file mode 100644 index 000000000..b4ab5659f --- /dev/null +++ b/booster-transform-lint/build.gradle @@ -0,0 +1,7 @@ +apply from: '../gradle/booster.gradle' + +dependencies { + kapt "com.google.auto.service:auto-service:1.0-rc4" + implementation project(':booster-gradle-base') + implementation project(':booster-transform-asm') +} diff --git a/booster-transform-lint/src/main/kotlin/com/didiglobal/booster/transform/lint/EntryPoint.kt b/booster-transform-lint/src/main/kotlin/com/didiglobal/booster/transform/lint/EntryPoint.kt new file mode 100644 index 000000000..af39098fc --- /dev/null +++ b/booster-transform-lint/src/main/kotlin/com/didiglobal/booster/transform/lint/EntryPoint.kt @@ -0,0 +1,46 @@ +package com.didiglobal.booster.transform.lint + +import java.util.Objects + +/** + * Represents the entry point of main thread / UI thread + * + * @author johnsonlee + */ +internal class EntryPoint(val name: String, val desc: String) { + + override fun hashCode(): Int { + return Objects.hash(name, desc) + } + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + if (other !is EntryPoint) { + return false + } + + return name == other.name && desc == other.desc + } + + override fun toString(): String { + return "$name$desc" + } +} + +internal val APPLICATION_ENTRY_POINTS = setOf(EntryPoint("onCreate", "()V")) + +internal val ACTIVITY_ENTRY_POINTS = setOf( + EntryPoint("onCreate", "(Landroid/os/Bundle;)V"), + EntryPoint("onStart", "()V"), + EntryPoint("onResume", "()V"), + EntryPoint("onPause", "()V"), + EntryPoint("onStop", "()V"), + EntryPoint("onDestroy", "()") +) + +internal val SERVICE_ENTRY_POINTS = setOf( + EntryPoint("onCreate", "()V"), + EntryPoint("onStartCommand", "(Landroid/content/Intent;II)I") +) diff --git a/booster-transform-lint/src/main/kotlin/com/didiglobal/booster/transform/lint/LintTransformer.kt b/booster-transform-lint/src/main/kotlin/com/didiglobal/booster/transform/lint/LintTransformer.kt new file mode 100644 index 000000000..3202fda79 --- /dev/null +++ b/booster-transform-lint/src/main/kotlin/com/didiglobal/booster/transform/lint/LintTransformer.kt @@ -0,0 +1,182 @@ +package com.didiglobal.booster.transform.lint + +import com.didiglobal.booster.kotlinx.MAGENTA +import com.didiglobal.booster.kotlinx.RESET +import com.didiglobal.booster.kotlinx.asIterable +import com.didiglobal.booster.kotlinx.file +import com.didiglobal.booster.transform.ArtifactManager +import com.didiglobal.booster.transform.TransformContext +import com.didiglobal.booster.transform.asm.ClassTransformer +import com.didiglobal.booster.transform.lint.graph.CallGraph +import com.didiglobal.booster.transform.lint.graph.CallGraph.Node +import com.didiglobal.booster.util.ComponentHandler +import com.google.auto.service.AutoService +import org.objectweb.asm.tree.ClassNode +import org.objectweb.asm.tree.MethodInsnNode +import java.io.File +import java.net.URI +import java.util.stream.Collectors +import javax.xml.parsers.SAXParserFactory + +/** + * Represents a class node transformer for static analysis + * + * @author johnsonlee + */ +@AutoService(ClassTransformer::class) +class LintTransformer : ClassTransformer { + + private val builder = CallGraph.Builder() + + override fun transform(context: TransformContext, klass: ClassNode): ClassNode { + klass.methods.forEach { method -> + method.instructions.iterator().asIterable().filterIsInstance(MethodInsnNode::class.java).forEach { invoke -> + val from = CallGraph.Node(klass.name, method.name, method.desc) + val to = CallGraph.Node(invoke.owner, invoke.name, invoke.desc) + // break recursive invocation + if (!builder.hasEdge(to, from)) { + builder.addEdge(from, to) + } + } + } + return klass + } + + override fun onPostTransform(context: TransformContext) { + val factory = SAXParserFactory.newInstance() + val parser = factory.newSAXParser() + val manifest = context.artifacts.get(ArtifactManager.MERGED_MANIFESTS).single().file("AndroidManifest.xml") + val graph = builder.build() + val apis = if (context.hasProperty(PROPERTY_LINT_APIS)) context.lintApis else LINT_APIS + + ComponentHandler().let { handler -> + parser.parse(manifest, handler) + graph.analyse(handler.applications, APPLICATION_ENTRY_POINTS, apis) + graph.analyse(handler.activities, ACTIVITY_ENTRY_POINTS, apis) + graph.analyse(handler.services, SERVICE_ENTRY_POINTS, apis) + + } + } + +} + +private val TransformContext.lintApis: Set + get() { + val uri = URI(this.getProperty(PROPERTY_LINT_APIS)) + val url = if (uri.isAbsolute) uri.toURL() else File(uri).toURI().toURL() + + url.openStream().bufferedReader().use { + return it.lines().filter(String::isNotBlank).map { line -> + Node.from(line.trim()) + }.collect(Collectors.toSet()) + } + } + +private fun CallGraph.analyse(components: Set, entryPoints: Set, apis: Set) { + components.map { + it.replace('.', '/') + }.forEach { component -> + entryPoints.map { + Node(component, it.name, it.desc) + }.forEach { + analyse(it, listOf(it), apis) + } + } +} + +private fun CallGraph.analyse(node: Node, parent: List, apis: Set) { + parent.last() + this.edges[node]?.forEach { + if (parent.contains(it)) { + return + } + + val paths = parent.plus(it) + if (apis.contains(it)) { + println(" ⚠️ $MAGENTA${paths.joinToString("$RESET -> $MAGENTA")} $RESET") + return + } + analyse(it, paths, apis) + } +} + +private val PROPERTY_LINT_APIS = "${Build.ARTIFACT.replace('-', '.')}.apis" + +internal val LINT_APIS = setOf( + Node("java/lang/Object", "wait", "()V"), + Node("java/lang/Object", "wait", "(J)V"), + Node("java/lang/Object", "wait", "(JI)V"), + Node("java/lang/ClassLoader", "getResource", "(Ljava/lang/String;)Ljava/net/URL;"), + Node("java/lang/ClassLoader", "getResources", "(Ljava/lang/String;)Ljava/util/Enumeration;"), + Node("java/lang/ClassLoader", "getResourceAsStream", "(Ljava/lang/String;)Ljava/io/InputStream;"), + Node("java/lang/ClassLoader", "getSystemResource", "(Ljava/lang/String;)Ljava/net/URL;"), + Node("java/lang/ClassLoader", "getSystemResources", "(Ljava/lang/String;)Ljava/util/Enumeration;"), + Node("java/lang/ClassLoader", "getSystemResourceAsStream", "(Ljava/lang/String;)Ljava/io/InputStream;"), + Node("java/io/InputStream", "read", "()I"), + Node("java/io/InputStream", "read", "([B)I"), + Node("java/io/InputStream", "read", "([BII)I"), + Node("java/io/BufferedInputStream", "read", "()I"), + Node("java/io/BufferedInputStream", "read", "([BII)I"), + Node("java/io/OutputStream", "write", "(I)V"), + Node("java/io/OutputStream", "write", "([B)V"), + Node("java/io/OutputStream", "write", "([BII)V"), + Node("java/io/OutputStream", "flush", "()V"), + Node("java/io/BufferedOutputStream", "write", "(I)V"), + Node("java/io/BufferedOutputStream", "write", "([BII)V"), + Node("java/io/BufferedOutputStream", "flush", "()V"), + Node("java/io/Reader", "read", "()I"), + Node("java/io/Reader", "read", "([C)I"), + Node("java/io/Reader", "read", "([CII)I"), + Node("java/io/Reader", "read", "(Ljava/nio/CharBuffer;)I"), + Node("java/io/Writer", "append", "(C)Ljava/io/Writer;"), + Node("java/io/Writer", "append", "(Ljava/lang/CharSequence;)Ljava/io/Writer;"), + Node("java/io/Writer", "append", "(Ljava/lang/CharSequence;II)Ljava/io/Writer;"), + Node("java/io/Writer", "flush", "()V"), + Node("java/io/Writer", "write", "(I)V"), + Node("java/io/Writer", "write", "(Ljava/lang/String;)V"), + Node("java/io/Writer", "write", "(Ljava/lang/String;II)V"), + Node("java/io/Writer", "write", "([C)V"), + Node("java/io/Writer", "write", "([CII)V"), + Node("java/util/ServiceLoader", "load", "(Ljava/lang/Class;)Ljava/util/ServiceLoader;"), + Node("java/util/ServiceLoader", "load", "(Ljava/lang/Class;Ljava/lang/ClassLoader;)Ljava/util/ServiceLoader;"), + Node("java/util/zip/ZipFile", "", "(Ljava/lang/String;)"), + Node("java/util/zip/ZipFile", "getInputStream", "(Ljava/util/zip/ZipEntry;)"), + Node("java/util/jar/JarFile", "", "(Ljava/lang/String;)"), + Node("java/util/jar/JarFile", "getInputStream", "(Ljava/util/jar/JarEntry;)"), + Node("android/content/Context", "getSharedPreferences", "Ljava/lang/String;I)Landroid/content/SharedPreferences;"), + Node("android/content/SharedPreferences\$Editor", "apply", "()V"), + Node("android/content/SharedPreferences\$Editor", "commit", "()B"), + Node("android/content/res/AssetManager", "list", "(Ljava/lang/String;)[Ljava/lang/String;"), + Node("android/content/res/AssetManager", "open", "(Ljava/lang/String;)Ljava/io/InputStream;"), + Node("android/content/res/AssetManager", "open", "(Ljava/lang/String;I)Ljava/io/InputStream;"), + Node("android/content/res/AssetManager", "openFd", "(Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;"), + Node("android/content/res/AssetManager", "openNonAssetFd", "(Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;"), + Node("android/content/res/AssetManager", "openNonAssetFd", "(ILjava/lang/String;)Landroid/content/res/AssetFileDescriptor;"), + Node("android/content/res/AssetManager", "openXmlResourceParser", "(Ljava/lang/String;)Landroid/content/res/XmlResourceParser;"), + Node("android/content/res/AssetManager", "openXmlResourceParser", "(ILjava/lang/String;)Landroid/content/res/XmlResourceParser;"), + Node("android/database/sqlite/SQLiteDatabase", "beginTransaction", "()V"), + Node("android/database/sqlite/SQLiteDatabase", "beginTransactionNonExclusive", "()V"), + Node("android/database/sqlite/SQLiteDatabase", "beginTransactionWithListener", "(Landroid/database/sqlite/SQLiteTransactionListener;)V"), + Node("android/database/sqlite/SQLiteDatabase", "delete", "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V"), + Node("android/database/sqlite/SQLiteDatabase", "deleteDatabase", "(Ljava/io/File;)Z"), + Node("android/database/sqlite/SQLiteDatabase", "endTransaction", "()V"), + Node("android/database/sqlite/SQLiteDatabase", "execSQL", "(Ljava/lang/String;)V"), + Node("android/database/sqlite/SQLiteDatabase", "execSQL", "(Ljava/lang/String;[Ljava/lang/Object;)V"), + Node("android/database/sqlite/SQLiteDatabase", "insert", "(Ljava/lang/String;Ljava/lang/String;Landroid/content/ContentValues;)J"), + Node("android/database/sqlite/SQLiteDatabase", "insertOrThrow", "(Ljava/lang/String;Ljava/lang/String;Landroid/content/ContentValues;)J"), + Node("android/database/sqlite/SQLiteDatabase", "insertWithOnConflict", "(Ljava/lang/String;Ljava/lang/String;Landroid/content/ContentValues;I)J"), + Node("android/database/sqlite/SQLiteDatabase", "query", "(ZLjava/lang/String;[java/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;"), + Node("android/database/sqlite/SQLiteDatabase", "query", "(Ljava/lang/String;[java/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;"), + Node("android/database/sqlite/SQLiteDatabase", "query", "(ZLjava/lang/String;[java/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Landroid/os/CancellationSignal;)Landroid/database/Cursor;"), + Node("android/database/sqlite/SQLiteDatabase", "query", "(Ljava/lang/String;[java/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;"), + Node("android/database/sqlite/SQLiteDatabase", "queryWithFactory", "(Landroid/database/sqlite/SQLiteDatabase\$CursorFactory;ZLjava/lang/String;[java/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Landroid/os/CancellationSignal;)Landroid/database/Cursor;"), + Node("android/database/sqlite/SQLiteDatabase", "queryWithFactory", "(Landroid/database/sqlite/SQLiteDatabase\$CursorFactory;ZLjava/lang/String;[java/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;"), + Node("android/database/sqlite/SQLiteDatabase", "rawQuery", "(Ljava/lang/String;[Ljava/lang/String;Landroid/os/CancellationSignal;)Landroid/database/Cursor;"), + Node("android/database/sqlite/SQLiteDatabase", "rawQuery", "(Ljava/lang/String;[Ljava/lang/String;)Landroid/database/Cursor;"), + Node("android/database/sqlite/SQLiteDatabase", "rawQueryWithFactory", "(Landroid/database/sqlite/SQLiteDatabase\$CursorFactory;Ljava/lang/String;[java/lang/String;Ljava/lang/String;Landroid/os/CancellationSignal;)Landroid/database/Cursor;"), + Node("android/database/sqlite/SQLiteDatabase", "rawQueryWithFactory", "(Landroid/database/sqlite/SQLiteDatabase\$CursorFactory;Ljava/lang/String;[java/lang/String;Ljava/lang/String;)Landroid/database/Cursor;"), + Node("android/database/sqlite/SQLiteDatabase", "replace", "(Ljava/lang/String;Ljava/lang/String;Landroid/content/ContentValues;)J"), + Node("android/database/sqlite/SQLiteDatabase", "replaceOrThrow", "(Ljava/lang/String;Ljava/lang/String;Landroid/content/ContentValues;)J"), + Node("android/database/sqlite/SQLiteDatabase", "update", "(Ljava/lang/String;Ljava/lang/String;Landroid/content/ContentValues;)J"), + Node("android/database/sqlite/SQLiteDatabase", "updateWithOnConflict", "(Ljava/lang/String;Landroid/content/ContentValues;Ljava/lang/String;[Ljava/lang/String;I)J") +) diff --git a/booster-transform-lint/src/main/kotlin/com/didiglobal/booster/transform/lint/graph/CallGraph.kt b/booster-transform-lint/src/main/kotlin/com/didiglobal/booster/transform/lint/graph/CallGraph.kt new file mode 100644 index 000000000..03200658d --- /dev/null +++ b/booster-transform-lint/src/main/kotlin/com/didiglobal/booster/transform/lint/graph/CallGraph.kt @@ -0,0 +1,109 @@ +package com.didiglobal.booster.transform.lint.graph + +import java.util.Objects + +/** + * Represents the call graph + * + * @author johnsonlee + */ +class CallGraph private constructor(val edges: Map>) : Iterable { + + override fun iterator(): Iterator { + return edges.map { pair -> + pair.value.map { + Edge(pair.key, it) + }.toSet() + }.flatten().iterator() + } + + class Node(val type: String, val name: String, val desc: String) { + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + if (other !is Node) { + return false + } + + return type == other.type && name == other.name && desc == other.desc + } + + override fun hashCode(): Int { + return Objects.hash(type, name, desc) + } + + override fun toString(): String { + return "$type.$name$desc" + } + + companion object { + fun from(s: String): Node { + val lbrace = s.lastIndexOf('(') + val dot = s.lastIndexOf('.', lbrace) + if (lbrace < 0 || dot < 0) { + throw IllegalArgumentException(s) + } + return Node(s.substring(0, dot), s.substring(dot + 1, lbrace), s.substring(lbrace)) + } + } + } + + class Edge(val from: Node, val to: Node) { + + override fun hashCode(): Int { + return Objects.hash(from, to) + } + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + if (other !is Edge) { + return false + } + + return from == other.from && to == other.to + } + + override fun toString(): String { + return "$from -> $to" + } + } + + class Builder { + + private val edges = mutableMapOf>() + + fun addEdge(edge: Edge): Builder { + this.edges.getOrPut(edge.from) { + mutableSetOf() + }.add(edge.to) + return this + } + + fun hasEdge(edge: Edge): Boolean { + return this.hasEdge(edge.from, edge.to) + } + + fun hasEdge(from: Node, to: Node): Boolean { + return this.edges.containsKey(from) && this.edges[from]?.contains(to) == true + } + + fun addEdge(from: Node, to: Node): Builder { + this.edges.getOrPut(from) { + mutableSetOf() + }.add(to) + return this + } + + fun build(): CallGraph { + return CallGraph(this.edges) + } + + } + +} diff --git a/booster-transform-lint/src/test/kotlin/com/didiglobal/booster/transform/lint/URITest.kt b/booster-transform-lint/src/test/kotlin/com/didiglobal/booster/transform/lint/URITest.kt new file mode 100644 index 000000000..a65c3d088 --- /dev/null +++ b/booster-transform-lint/src/test/kotlin/com/didiglobal/booster/transform/lint/URITest.kt @@ -0,0 +1,18 @@ +package com.didiglobal.booster.transform.lint + +import java.net.URI +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class URITest { + + @Test + fun `parse string as uri`() { + val home = URI(System.getProperty("user.home")) + assertFalse(home.isAbsolute) + val github = URI("http://github.com") + assertTrue(github.isAbsolute) + } + +} diff --git a/booster-transform-shrink/build.gradle b/booster-transform-shrink/build.gradle new file mode 100644 index 000000000..b4ab5659f --- /dev/null +++ b/booster-transform-shrink/build.gradle @@ -0,0 +1,7 @@ +apply from: '../gradle/booster.gradle' + +dependencies { + kapt "com.google.auto.service:auto-service:1.0-rc4" + implementation project(':booster-gradle-base') + implementation project(':booster-transform-asm') +} diff --git a/booster-transform-shrink/src/main/kotlin/com/didiglobal/booster/transform/shrink/MalformedSymbolListException.kt b/booster-transform-shrink/src/main/kotlin/com/didiglobal/booster/transform/shrink/MalformedSymbolListException.kt new file mode 100644 index 000000000..0c589ac82 --- /dev/null +++ b/booster-transform-shrink/src/main/kotlin/com/didiglobal/booster/transform/shrink/MalformedSymbolListException.kt @@ -0,0 +1,5 @@ +package com.didiglobal.booster.transform.shrink + +import java.lang.Exception + +class MalformedSymbolListException(msg: String?) : Exception(msg) diff --git a/booster-transform-shrink/src/main/kotlin/com/didiglobal/booster/transform/shrink/RFinder.kt b/booster-transform-shrink/src/main/kotlin/com/didiglobal/booster/transform/shrink/RFinder.kt new file mode 100644 index 000000000..1acf26c1f --- /dev/null +++ b/booster-transform-shrink/src/main/kotlin/com/didiglobal/booster/transform/shrink/RFinder.kt @@ -0,0 +1,25 @@ +package com.didiglobal.booster.transform.shrink + +import java.io.File +import java.util.concurrent.RecursiveTask + +internal class RFinder(private val root: File) : RecursiveTask>() { + + override fun compute(): Collection { + val tasks = mutableListOf>>() + val result = mutableListOf() + + root.listFiles()?.forEach { file -> + if (file.isDirectory) { + RFinder(file).also { task -> + tasks.add(task) + }.fork() + } else if ("R.class" == file.name || (file.name.startsWith("R$") && file.name.endsWith(".class"))) { + result.add(file) + } + } + + return result + tasks.flatMap { it.join() } + } + +} diff --git a/booster-transform-shrink/src/main/kotlin/com/didiglobal/booster/transform/shrink/ShrinkTransformer.kt b/booster-transform-shrink/src/main/kotlin/com/didiglobal/booster/transform/shrink/ShrinkTransformer.kt new file mode 100644 index 000000000..707bd3ab6 --- /dev/null +++ b/booster-transform-shrink/src/main/kotlin/com/didiglobal/booster/transform/shrink/ShrinkTransformer.kt @@ -0,0 +1,131 @@ +package com.didiglobal.booster.transform.shrink + +import com.didiglobal.booster.kotlinx.BLUE +import com.didiglobal.booster.kotlinx.RED +import com.didiglobal.booster.kotlinx.RESET +import com.didiglobal.booster.kotlinx.YELLOW +import com.didiglobal.booster.kotlinx.asIterable +import com.didiglobal.booster.kotlinx.head +import com.didiglobal.booster.kotlinx.separatorsToSystem +import com.didiglobal.booster.transform.ArtifactManager.Companion.JAVAC +import com.didiglobal.booster.transform.ArtifactManager.Companion.SYMBOL_LIST +import com.didiglobal.booster.transform.ArtifactManager.Companion.SYMBOL_LIST_WITH_PACKAGE_NAME +import com.didiglobal.booster.transform.TransformContext +import com.didiglobal.booster.transform.asm.ClassTransformer +import com.didiglobal.booster.transform.asm.simpleName +import com.google.auto.service.AutoService +import org.objectweb.asm.Opcodes +import org.objectweb.asm.tree.ClassNode +import org.objectweb.asm.tree.FieldInsnNode +import org.objectweb.asm.tree.FieldNode +import org.objectweb.asm.tree.LdcInsnNode +import java.util.concurrent.ForkJoinPool + +internal const val R_STYLEABLE = "R\$styleable" +internal const val R_STYLEABLE_CLASS = "$R_STYLEABLE.class" +internal const val ANDROID_R = "android/R$" +internal const val COM_ANDROID_INTERNAL_R = "com/android/internal/R$" + +internal val CONST_TYPE_SIGNATURES = setOf("Z", "B", "C", "S", "I", "J", "F", "D", "L/java/lang/String;") + +/** + * Represents a class node transformer for constants shrinking + * + * @author johnsonlee + */ +@AutoService(ClassTransformer::class) +class ShrinkTransformer : ClassTransformer { + + lateinit var pkg: String + lateinit var pkgRStyleable: String + lateinit var symbols: SymbolList + + override fun onPreTransform(context: TransformContext) { + this.pkg = context.artifacts.get(SYMBOL_LIST_WITH_PACKAGE_NAME).single().head()!!.replace('.', '/') + this.symbols = SymbolList.from(context.artifacts.get(SYMBOL_LIST).single()) + this.pkgRStyleable = "$pkg/$R_STYLEABLE" + + ForkJoinPool().also { pool -> + context.artifacts.get(JAVAC).forEach { root -> + pool.invoke(RFinder(root)).filter { + // only keep application's R$styleable.class + !it.parent.endsWith(pkg.separatorsToSystem()) || it.name != R_STYLEABLE_CLASS + }.forEach { + // delete library's R + if (it.delete()) { + println("$YELLOW x ${it.absolutePath}$RESET") + } else { + println("$BLUE ? ${it.absolutePath}$RESET") + } + } + } + }.shutdown() + } + + override fun transform(context: TransformContext, klass: ClassNode): ClassNode { + when { + klass.name == this.pkgRStyleable -> removeIntFields(klass) + klass.simpleName == "BuildConfig" -> removeConstantFields(klass) + else -> replaceSymbolReferenceWithConstant(klass) + } + return klass + } + + private fun replaceSymbolReferenceWithConstant(klass: ClassNode) { + klass.methods.forEach { method -> + val insns = method.instructions.iterator().asIterable().filter { + it.opcode == Opcodes.GETSTATIC + }.map { + it as FieldInsnNode + }.filter { + ("I" == it.desc || "[I" == it.desc) + && it.owner.substring(it.owner.lastIndexOf('/') + 1).startsWith("R$") + && !(it.owner.startsWith(COM_ANDROID_INTERNAL_R) || it.owner.startsWith(ANDROID_R)) + } + + val intFields = insns.filter { "I" == it.desc } + val intArrayFields = insns.filter { "[I" == it.desc } + + // Replace int field with constant + intFields.forEach { field -> + val type = field.owner.substring(field.owner.lastIndexOf("/R$") + 3) + try { + method.instructions.insertBefore(field, LdcInsnNode(symbols.getInt(type, field.name))) + method.instructions.remove(field) + } catch (e: NullPointerException) { + println("$RED ! Unresolvable symbol R.$type.${field.name} : ${klass.name}.${method.name}${method.desc} $RESET") + } + } + + // Replace library's R fields with application's R fields + intArrayFields.forEach { field -> + field.owner = "$pkg/${field.owner.substring(field.owner.lastIndexOf('/') + 1)}" + } + } + } + +} + +private fun removeIntFields(klass: ClassNode) { + klass.fields.map { + it as FieldNode + }.filter { + it.desc == "I" + }.forEach { + klass.fields.remove(it) + println("$YELLOW x ${klass.name}.${it.name} : ${it.desc}$RESET") + } +} + +private fun removeConstantFields(klass: ClassNode) { + klass.fields.map { + it as FieldNode + }.filter { + 0 != (Opcodes.ACC_STATIC and it.access) + && 0 != (Opcodes.ACC_FINAL and it.access) + && CONST_TYPE_SIGNATURES.contains(it.desc) + }.forEach { + klass.fields.remove(it) + println("$YELLOW x ${klass.name}.${it.name} : ${it.desc}$RESET") + } +} diff --git a/booster-transform-shrink/src/main/kotlin/com/didiglobal/booster/transform/shrink/SymbolList.kt b/booster-transform-shrink/src/main/kotlin/com/didiglobal/booster/transform/shrink/SymbolList.kt new file mode 100644 index 000000000..7582f1b6f --- /dev/null +++ b/booster-transform-shrink/src/main/kotlin/com/didiglobal/booster/transform/shrink/SymbolList.kt @@ -0,0 +1,174 @@ +package com.didiglobal.booster.transform.shrink + +import java.io.File +import java.util.Objects + +/** + * Represents the symbol list of resources + * + * @author johnsonlee + */ +class SymbolList private constructor(builder: Builder) : Iterable> { + + private val symbols = builder.symbols + + override fun iterator(): Iterator> = this.symbols.iterator() + + fun getInt(type: String, name: String): Int { + return (this.symbols.find { it.type == type && it.name == name }?.value as? Int)!! + } + + fun getIntArray(type: String = "styleable", name: String): IntArray { + return (this.symbols.find { it.type == type && it.name == name && it is IntArraySymbol }?.value as? IntArray)!! + } + + abstract class Symbol(val type: String, val name: String, val value: T) { + + override fun hashCode(): Int { + return Objects.hash(type, name, value) + } + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + if (other !is Symbol<*>) { + return false + } + + return type == other.type && name == other.name && value == other.value + } + } + + class IntSymbol(type: String, name: String, value: Int) : Symbol(type, name, value) { + override fun toString(): String = "int $type $name $value" + } + + class IntArraySymbol(type: String, name: String, value: IntArray) : Symbol(type, name, value) { + override fun toString(): String = "int[] $type $name ${value.joinToString(", ", "{ ", " }") { "0x${it.toString(16)}" }}" + } + + class Builder { + + val symbols = mutableListOf>() + + fun build(): SymbolList { + return SymbolList(this) + } + + fun addSymbol(symbol: Symbol<*>): Builder { + this.symbols.add(symbol) + return this + } + + } + + companion object { + + /** + * Parses symbols from the specified file + * + * @param file symbol list file + */ + fun from(file: File): SymbolList { + return SymbolList.Builder().also { builder -> + file.forEachLine { line -> + val sp1 = line.nextColumnIndex(' ') + val dataType = line.substring(0, sp1) + when (dataType) { + "int" -> { + val sp2 = line.nextColumnIndex(' ', sp1 + 1) + val type = line.substring(sp1 + 1, sp2) + val sp3 = line.nextColumnIndex(' ', sp2 + 1) + val name = line.substring(sp2 + 1, sp3) + val value: Int = line.substring(sp3 + 1).toInt() + builder.addSymbol(IntSymbol(type, name, value)) + } + "int[]" -> { + val sp2 = line.nextColumnIndex(' ', sp1 + 1) + val type = line.substring(sp1 + 1, sp2) + val sp3 = line.nextColumnIndex(' ', sp2 + 1) + val name = line.substring(sp2 + 1, sp3) + val leftBrace = line.nextColumnIndex('{', sp3) + val rightBrace = line.prevColumnIndex('}') + val vStart = line.skipWhitespaceForward(leftBrace + 1) + val vEnd = line.skipWhitespaceBackward(rightBrace - 1) + 1 + val values = mutableListOf() + var i = vStart + + while (i < vEnd) { + val comma = line.nextColumnIndex(',', i, true) + i = if (comma > -1) { + values.add(line.substring(line.skipWhitespaceForward(i), comma).toInt()) + line.skipWhitespaceForward(comma + 1) + } else { + values.add(line.substring(i, vEnd).toInt()) + vEnd + } + } + + builder.addSymbol(IntArraySymbol(type, name, values.toIntArray())) + } + else -> throw MalformedSymbolListException(file.absolutePath) + } + } + }.build() + } + + } + +} + + +private fun String.toInt(): Int { + return if (startsWith("0x")) { + substring(2).toInt(16) + } else { + toInt(10) + } +} + +private fun String.nextColumnIndex(delimiter: Char = ' ', startIndex: Int = 0, optional: Boolean = false): Int { + val index = this.indexOf(delimiter, startIndex) + if (!optional && index < 0) { + throw MalformedSymbolListException(this) + } + return index +} + +private fun String.prevColumnIndex(delimiter: Char = ' ', startIndex: Int = lastIndex, optional: Boolean = false): Int { + val index = this.lastIndexOf(delimiter, startIndex) + if (!optional && index < 0) { + throw MalformedSymbolListException(this) + } + return index +} + +private fun String.skipWhitespaceForward(startIndex: Int = 0): Int { + var i = startIndex + val n = length + + while (i < n) { + if (!this[i].isWhitespace()) { + break + } + i++ + } + + return i +} + +private fun String.skipWhitespaceBackward(startIndex: Int = lastIndex): Int { + var i = startIndex + + while (i >= 0) { + if (!this[i].isWhitespace()) { + break + } + i-- + } + + return i +} + diff --git a/booster-transform-shrink/src/test/kotlin/com/didiglobal/booster/transform/shrink/SymbolListTest.kt b/booster-transform-shrink/src/test/kotlin/com/didiglobal/booster/transform/shrink/SymbolListTest.kt new file mode 100644 index 000000000..2118d8b80 --- /dev/null +++ b/booster-transform-shrink/src/test/kotlin/com/didiglobal/booster/transform/shrink/SymbolListTest.kt @@ -0,0 +1,18 @@ +package com.didiglobal.booster.transform.shrink + +import com.didiglobal.booster.kotlinx.file +import java.io.File +import kotlin.test.Test + +val PWD = File(System.getProperty("user.dir")) + +class SymbolListTest { + + @Test + fun `parse symbol list from R txt`() { + SymbolList.from(PWD.file("src", "test", "resources", "R.txt")).forEach { symbol -> + println(symbol) + } + } + +} diff --git a/booster-transform-shrink/src/test/resources/R.txt b/booster-transform-shrink/src/test/resources/R.txt new file mode 100644 index 000000000..424aaad6e --- /dev/null +++ b/booster-transform-shrink/src/test/resources/R.txt @@ -0,0 +1,186 @@ +int attr alpha 0x7f040001 +int attr coordinatorLayoutStyle 0x7f040002 +int attr font 0x7f040003 +int attr fontProviderAuthority 0x7f040004 +int attr fontProviderCerts 0x7f040005 +int attr fontProviderFetchStrategy 0x7f040006 +int attr fontProviderFetchTimeout 0x7f040007 +int attr fontProviderPackage 0x7f040008 +int attr fontProviderQuery 0x7f040009 +int attr fontStyle 0x7f04000a +int attr fontVariationSettings 0x7f04000b +int attr fontWeight 0x7f04000c +int attr keylines 0x7f04000d +int attr layout_anchor 0x7f04000e +int attr layout_anchorGravity 0x7f04000f +int attr layout_behavior 0x7f040010 +int attr layout_dodgeInsetEdges 0x7f040011 +int attr layout_insetEdge 0x7f040012 +int attr layout_keyline 0x7f040013 +int attr statusBarBackground 0x7f040014 +int attr ttcIndex 0x7f040015 +int color notification_action_color_filter 0x7f060001 +int color notification_icon_bg_color 0x7f060002 +int color notification_material_background_media_default_color 0x7f060003 +int color primary_text_default_material_dark 0x7f060004 +int color ripple_material_light 0x7f060005 +int color secondary_text_default_material_dark 0x7f060006 +int color secondary_text_default_material_light 0x7f060007 +int dimen compat_button_inset_horizontal_material 0x7f080001 +int dimen compat_button_inset_vertical_material 0x7f080002 +int dimen compat_button_padding_horizontal_material 0x7f080003 +int dimen compat_button_padding_vertical_material 0x7f080004 +int dimen compat_control_corner_material 0x7f080005 +int dimen compat_notification_large_icon_max_height 0x7f080006 +int dimen compat_notification_large_icon_max_width 0x7f080007 +int dimen notification_action_icon_size 0x7f080008 +int dimen notification_action_text_size 0x7f080009 +int dimen notification_big_circle_margin 0x7f08000a +int dimen notification_content_margin_start 0x7f08000b +int dimen notification_large_icon_height 0x7f08000c +int dimen notification_large_icon_width 0x7f08000d +int dimen notification_main_column_padding_top 0x7f08000e +int dimen notification_media_narrow_margin 0x7f08000f +int dimen notification_right_icon_size 0x7f080010 +int dimen notification_right_side_padding_top 0x7f080011 +int dimen notification_small_icon_background_padding 0x7f080012 +int dimen notification_small_icon_size_as_large 0x7f080013 +int dimen notification_subtext_size 0x7f080014 +int dimen notification_top_pad 0x7f080015 +int dimen notification_top_pad_large_text 0x7f080016 +int dimen subtitle_corner_radius 0x7f080017 +int dimen subtitle_outline_width 0x7f080018 +int dimen subtitle_shadow_offset 0x7f080019 +int dimen subtitle_shadow_radius 0x7f08001a +int drawable notification_action_background 0x7f090001 +int drawable notification_bg 0x7f090002 +int drawable notification_bg_low 0x7f090003 +int drawable notification_bg_low_normal 0x7f090004 +int drawable notification_bg_low_pressed 0x7f090005 +int drawable notification_bg_normal 0x7f090006 +int drawable notification_bg_normal_pressed 0x7f090007 +int drawable notification_icon_background 0x7f090008 +int drawable notification_template_icon_bg 0x7f090009 +int drawable notification_template_icon_low_bg 0x7f09000a +int drawable notification_tile_bg 0x7f09000b +int drawable notify_panel_notification_icon_bg 0x7f09000c +int id action0 0x7f0c0001 +int id action_container 0x7f0c0002 +int id action_divider 0x7f0c0003 +int id action_image 0x7f0c0004 +int id action_text 0x7f0c0005 +int id actions 0x7f0c0006 +int id async 0x7f0c0007 +int id blocking 0x7f0c0008 +int id bottom 0x7f0c0009 +int id cancel_action 0x7f0c000a +int id chronometer 0x7f0c000b +int id end 0x7f0c000c +int id end_padder 0x7f0c000d +int id forever 0x7f0c000e +int id icon 0x7f0c000f +int id icon_group 0x7f0c0010 +int id info 0x7f0c0011 +int id italic 0x7f0c0012 +int id left 0x7f0c0013 +int id line1 0x7f0c0014 +int id line3 0x7f0c0015 +int id media_actions 0x7f0c0016 +int id none 0x7f0c0017 +int id normal 0x7f0c0018 +int id notification_background 0x7f0c0019 +int id notification_main_column 0x7f0c001a +int id notification_main_column_container 0x7f0c001b +int id right 0x7f0c001c +int id right_icon 0x7f0c001d +int id right_side 0x7f0c001e +int id start 0x7f0c001f +int id status_bar_latest_event_content 0x7f0c0020 +int id tag_transition_group 0x7f0c0021 +int id tag_unhandled_key_event_manager 0x7f0c0022 +int id tag_unhandled_key_listeners 0x7f0c0023 +int id text 0x7f0c0024 +int id text2 0x7f0c0025 +int id time 0x7f0c0026 +int id title 0x7f0c0027 +int id top 0x7f0c0028 +int integer cancel_button_image_alpha 0x7f0d0001 +int integer status_bar_notification_info_maxnum 0x7f0d0002 +int layout notification_action 0x7f0f0001 +int layout notification_action_tombstone 0x7f0f0002 +int layout notification_media_action 0x7f0f0003 +int layout notification_media_cancel_action 0x7f0f0004 +int layout notification_template_big_media 0x7f0f0005 +int layout notification_template_big_media_custom 0x7f0f0006 +int layout notification_template_big_media_narrow 0x7f0f0007 +int layout notification_template_big_media_narrow_custom 0x7f0f0008 +int layout notification_template_custom_big 0x7f0f0009 +int layout notification_template_icon_group 0x7f0f000a +int layout notification_template_lines_media 0x7f0f000b +int layout notification_template_media 0x7f0f000c +int layout notification_template_media_custom 0x7f0f000d +int layout notification_template_part_chronometer 0x7f0f000e +int layout notification_template_part_time 0x7f0f000f +int string status_bar_notification_info_overflow 0x7f150001 +int style TextAppearance_Compat_Notification 0x7f160001 +int style TextAppearance_Compat_Notification_Info 0x7f160002 +int style TextAppearance_Compat_Notification_Info_Media 0x7f160003 +int style TextAppearance_Compat_Notification_Line2 0x7f160004 +int style TextAppearance_Compat_Notification_Line2_Media 0x7f160005 +int style TextAppearance_Compat_Notification_Media 0x7f160006 +int style TextAppearance_Compat_Notification_Time 0x7f160007 +int style TextAppearance_Compat_Notification_Time_Media 0x7f160008 +int style TextAppearance_Compat_Notification_Title 0x7f160009 +int style TextAppearance_Compat_Notification_Title_Media 0x7f16000a +int style Widget_Compat_NotificationActionContainer 0x7f16000b +int style Widget_Compat_NotificationActionText 0x7f16000c +int style Widget_Support_CoordinatorLayout 0x7f16000d +int[] styleable ColorStateListItem { 0x7f040001, 0x101031f, 0x10101a5 } +int styleable ColorStateListItem_alpha 0 +int styleable ColorStateListItem_android_alpha 1 +int styleable ColorStateListItem_android_color 2 +int[] styleable CoordinatorLayout { 0x7f04000d, 0x7f040014 } +int styleable CoordinatorLayout_keylines 0 +int styleable CoordinatorLayout_statusBarBackground 1 +int[] styleable CoordinatorLayout_Layout { 0x10100b3, 0x7f04000e, 0x7f04000f, 0x7f040010, 0x7f040011, 0x7f040012, 0x7f040013 } +int styleable CoordinatorLayout_Layout_android_layout_gravity 0 +int styleable CoordinatorLayout_Layout_layout_anchor 1 +int styleable CoordinatorLayout_Layout_layout_anchorGravity 2 +int styleable CoordinatorLayout_Layout_layout_behavior 3 +int styleable CoordinatorLayout_Layout_layout_dodgeInsetEdges 4 +int styleable CoordinatorLayout_Layout_layout_insetEdge 5 +int styleable CoordinatorLayout_Layout_layout_keyline 6 +int[] styleable FontFamily { 0x7f040004, 0x7f040005, 0x7f040006, 0x7f040007, 0x7f040008, 0x7f040009 } +int styleable FontFamily_fontProviderAuthority 0 +int styleable FontFamily_fontProviderCerts 1 +int styleable FontFamily_fontProviderFetchStrategy 2 +int styleable FontFamily_fontProviderFetchTimeout 3 +int styleable FontFamily_fontProviderPackage 4 +int styleable FontFamily_fontProviderQuery 5 +int[] styleable FontFamilyFont { 0x1010532, 0x101053f, 0x1010570, 0x1010533, 0x101056f, 0x7f040003, 0x7f04000a, 0x7f04000b, 0x7f04000c, 0x7f040015 } +int styleable FontFamilyFont_android_font 0 +int styleable FontFamilyFont_android_fontStyle 1 +int styleable FontFamilyFont_android_fontVariationSettings 2 +int styleable FontFamilyFont_android_fontWeight 3 +int styleable FontFamilyFont_android_ttcIndex 4 +int styleable FontFamilyFont_font 5 +int styleable FontFamilyFont_fontStyle 6 +int styleable FontFamilyFont_fontVariationSettings 7 +int styleable FontFamilyFont_fontWeight 8 +int styleable FontFamilyFont_ttcIndex 9 +int[] styleable GradientColor { 0x101020b, 0x10101a2, 0x10101a3, 0x101019e, 0x1010512, 0x1010513, 0x10101a4, 0x101019d, 0x1010510, 0x1010511, 0x1010201, 0x10101a1 } +int styleable GradientColor_android_centerColor 0 +int styleable GradientColor_android_centerX 1 +int styleable GradientColor_android_centerY 2 +int styleable GradientColor_android_endColor 3 +int styleable GradientColor_android_endX 4 +int styleable GradientColor_android_endY 5 +int styleable GradientColor_android_gradientRadius 6 +int styleable GradientColor_android_startColor 7 +int styleable GradientColor_android_startX 8 +int styleable GradientColor_android_startY 9 +int styleable GradientColor_android_tileMode 10 +int styleable GradientColor_android_type 11 +int[] styleable GradientColorItem { 0x10101a5, 0x1010514 } +int styleable GradientColorItem_android_color 0 +int styleable GradientColorItem_android_offset 1 diff --git a/booster-transform-spi/build.gradle b/booster-transform-spi/build.gradle new file mode 100644 index 000000000..345bf9ee5 --- /dev/null +++ b/booster-transform-spi/build.gradle @@ -0,0 +1 @@ +apply from: '../gradle/booster.gradle' diff --git a/booster-transform-spi/src/main/java/com/didiglobal/booster/transform/TransformListener.java b/booster-transform-spi/src/main/java/com/didiglobal/booster/transform/TransformListener.java new file mode 100644 index 000000000..8f6368b36 --- /dev/null +++ b/booster-transform-spi/src/main/java/com/didiglobal/booster/transform/TransformListener.java @@ -0,0 +1,18 @@ +package com.didiglobal.booster.transform; + +import org.jetbrains.annotations.NotNull; + +/** + * Represents the transform lifecycle listener + * + * @author johnsonlee + */ +public interface TransformListener { + + default void onPreTransform(@NotNull final TransformContext context) { + } + + default void onPostTransform(@NotNull final TransformContext context) { + } + +} diff --git a/booster-transform-spi/src/main/kotlin/com/didiglobal/booster/transform/ArtifactManager.kt b/booster-transform-spi/src/main/kotlin/com/didiglobal/booster/transform/ArtifactManager.kt new file mode 100644 index 000000000..c306c30a5 --- /dev/null +++ b/booster-transform-spi/src/main/kotlin/com/didiglobal/booster/transform/ArtifactManager.kt @@ -0,0 +1,31 @@ +package com.didiglobal.booster.transform + +import java.io.File + +/** + * Represents a manager of artifacts + */ +interface ArtifactManager { + + companion object { + const val AAR = "AAR" + const val ALL_CLASSES = "ALL_CLASSES" + const val APK = "APK" + const val JAR = "JAR" + const val JAVAC = "JAVAC" + const val MERGED_ASSETS = "MERGED_ASSETS" + const val MERGED_RES = "MERGED_RES" + const val MERGED_MANIFESTS = "MERGED_MANIFESTS" + const val PROCESSED_RES = "PROCESSED_RES" + const val SYMBOL_LIST = "SYMBOL_LIST" + const val SYMBOL_LIST_WITH_PACKAGE_NAME = "SYMBOL_LIST_WITH_PACKAGE_NAME" + } + + /** + * Returns the specified type of artifacts + * + * @param type The type of artifacts + */ + fun get(type: String): Collection = emptyList() + +} diff --git a/booster-transform-spi/src/main/kotlin/com/didiglobal/booster/transform/TransformContext.kt b/booster-transform-spi/src/main/kotlin/com/didiglobal/booster/transform/TransformContext.kt new file mode 100644 index 000000000..fd620db9a --- /dev/null +++ b/booster-transform-spi/src/main/kotlin/com/didiglobal/booster/transform/TransformContext.kt @@ -0,0 +1,67 @@ +package com.didiglobal.booster.transform + +import java.io.File +import java.util.concurrent.ExecutorService + +/** + * Represent the transform context + * + * @author johnsonlee + */ +interface TransformContext { + + /** + * The name of transform + */ + val name: String + + /** + * The project directory + */ + val projectDir: File + + /** + * The build directory + */ + val buildDir: File + + /** + * The temporary directory + */ + val temporaryDir: File + + /** + * The executor service + */ + val executor: ExecutorService + + /** + * The compile classpath + */ + val compileClasspath: Collection + + /** + * The runtime classpath + */ + val runtimeClasspath: Collection + + /** + * The artifact manager + */ + val artifacts: ArtifactManager + + /** + * Check if has the specified property. Generally, the property is equivalent to project property + * + * @param name the name of property + */ + fun hasProperty(name: String): Boolean + + /** + * Returns the value of the specified property. Generally, the property is equivalent to project property + * + * @param name the name of property + */ + fun getProperty(name: String): String? + +} diff --git a/booster-transform-spi/src/main/kotlin/com/didiglobal/booster/transform/Transformer.kt b/booster-transform-spi/src/main/kotlin/com/didiglobal/booster/transform/Transformer.kt new file mode 100644 index 000000000..172ada870 --- /dev/null +++ b/booster-transform-spi/src/main/kotlin/com/didiglobal/booster/transform/Transformer.kt @@ -0,0 +1,21 @@ +package com.didiglobal.booster.transform + +/** + * Represents bytecode transformer + * + * @author johnsonlee + */ +interface Transformer : TransformListener { + + /** + * Returns the transformed bytecode + * + * @param context + * The transforming context + * @param bytecode + * The bytecode to be transformed + * @return the transformed bytecode + */ + fun transform(context: TransformContext, bytecode: ByteArray): ByteArray + +} diff --git a/booster-transform-usage/build.gradle b/booster-transform-usage/build.gradle new file mode 100644 index 000000000..dfdcbdc4b --- /dev/null +++ b/booster-transform-usage/build.gradle @@ -0,0 +1,7 @@ +apply from: '../gradle/booster.gradle' + +dependencies { + kapt "com.google.auto.service:auto-service:1.0-rc4" + compile project(':booster-gradle-base') + compile project(':booster-transform-asm') +} diff --git a/booster-transform-usage/src/main/kotlin/com/didiglobal/booster/transform/usage/UsageTransformer.kt b/booster-transform-usage/src/main/kotlin/com/didiglobal/booster/transform/usage/UsageTransformer.kt new file mode 100644 index 000000000..a08f6c5e9 --- /dev/null +++ b/booster-transform-usage/src/main/kotlin/com/didiglobal/booster/transform/usage/UsageTransformer.kt @@ -0,0 +1,54 @@ +package com.didiglobal.booster.transform.usage + +import com.didiglobal.booster.kotlinx.RESET +import com.didiglobal.booster.kotlinx.YELLOW +import com.didiglobal.booster.transform.TransformContext +import com.didiglobal.booster.transform.asm.ClassTransformer +import com.google.auto.service.AutoService +import org.objectweb.asm.tree.ClassNode +import org.objectweb.asm.tree.MethodInsnNode +import java.io.File +import java.net.URI +import java.util.stream.Collectors + +/** + * Represents a class node transformer for type/method/field usage analysis + * + * @author johnsonlee + */ +@AutoService(ClassTransformer::class) +class UsageTransformer : ClassTransformer { + + override fun transform(context: TransformContext, klass: ClassNode): ClassNode { + if (context.hasProperty(PROPERTY_USED_APIS)) { + val apis = context.usedApis + + klass.methods.forEach { method -> + method.instructions.iterator().asSequence().filterIsInstance(MethodInsnNode::class.java).map { + "${it.owner}.${it.name}${it.desc}" + }.filter { + apis.contains(it) + }.forEach { + println("$YELLOW ! ${klass.name}.${method.name}${method.desc}: $RESET$it") + } + } + } + + return klass + } + +} + +private val TransformContext.usedApis: Set + get() { + val uri = URI(this.getProperty(PROPERTY_USED_APIS)) + val url = if (uri.isAbsolute) uri.toURL() else File(uri).toURI().toURL() + + url.openStream().bufferedReader().use { + return it.lines().filter(String::isNotBlank).map { line -> + line.trim() + }.collect(Collectors.toSet()) + } + } + +private val PROPERTY_USED_APIS = "${Build.ARTIFACT.replace('-', '.')}.apis" diff --git a/booster-transform-util/build.gradle b/booster-transform-util/build.gradle new file mode 100644 index 000000000..d9d38ea87 --- /dev/null +++ b/booster-transform-util/build.gradle @@ -0,0 +1,6 @@ +apply from: '../gradle/booster.gradle' + +dependencies { + compile project(':booster-transform-spi') + compile project(':booster-kotlinx') +} diff --git a/booster-transform-util/src/main/kotlin/com/didiglobal/booster/transform/util/transform.kt b/booster-transform-util/src/main/kotlin/com/didiglobal/booster/transform/util/transform.kt new file mode 100644 index 000000000..44247dfbf --- /dev/null +++ b/booster-transform-util/src/main/kotlin/com/didiglobal/booster/transform/util/transform.kt @@ -0,0 +1,71 @@ +package com.didiglobal.booster.transform.util + +import com.didiglobal.booster.kotlinx.parallelWalk +import com.didiglobal.booster.kotlinx.redirect +import com.didiglobal.booster.kotlinx.touch +import java.io.File +import java.io.InputStream +import java.util.jar.JarEntry +import java.util.jar.JarFile +import java.util.jar.JarOutputStream + +fun nop(data: ByteArray): ByteArray = data + +/** + * Transform this file or directory to the output by the specified transformer + * + * @param output The output location + * @param transformer The byte data transformer + */ +fun File.transform(output: File, transformer: (ByteArray) -> ByteArray = ::nop) { + when { + isDirectory -> { + this.parallelWalk().filter { it != this && it.isFile }.forEach { file -> + val path = file.absolutePath.substring(this.absolutePath.length + java.io.File.separator.length) + file.transform(File(output, path), transformer) + } + } + isFile -> { + when (output.extension.toLowerCase()) { + "jar" -> { + JarOutputStream(output.touch().outputStream()).use { dest -> + JarFile(this).use { jar -> + jar.entries().asSequence().forEach { entry -> + dest.putNextEntry(JarEntry(entry.name)) + if (!entry.isDirectory) { + when (entry.name.substringAfterLast('.', "")) { + "class" -> { + jar.getInputStream(entry).use { src -> + src.transform(transformer).redirect(dest) + } + } + else -> { + jar.getInputStream(entry).use { src -> + src.copyTo(dest) + } + } + } + } + } + } + } + } + "class" -> { + inputStream().use { + it.transform(transformer).redirect(output) + } + } + else -> { + this.copyTo(output, true) + } + } + } + else -> { + TODO("Unexpected file: ${this.absolutePath}") + } + } +} + +fun InputStream.transform(transformer: (ByteArray) -> ByteArray): ByteArray { + return transformer(this.readBytes()) +} diff --git a/booster-transform-util/src/test/kotlin/com/didiglobal/booster/transform/FileTransformTest.kt b/booster-transform-util/src/test/kotlin/com/didiglobal/booster/transform/FileTransformTest.kt new file mode 100644 index 000000000..863c069e6 --- /dev/null +++ b/booster-transform-util/src/test/kotlin/com/didiglobal/booster/transform/FileTransformTest.kt @@ -0,0 +1,36 @@ +package com.didiglobal.booster.transform + +import com.didiglobal.booster.kotlinx.file +import com.didiglobal.booster.transform.util.Build +import com.didiglobal.booster.transform.util.transform +import java.io.File +import java.nio.file.Files +import java.util.jar.JarFile +import kotlin.test.Test +import kotlin.test.assertEquals + +val PWD: String = System.getProperty("user.dir") +val BUILD_DIR = File(PWD, "build") + +class FileTransformTest { + + @Test + fun `transform jar`() { + val jar = "${Build.ARTIFACT}-${Build.VERSION}.jar" + val src = BUILD_DIR.file("libs", jar) + val dest = Files.createTempDirectory(Build.GROUP).toFile().file(jar) + + src.transform(dest) { it } + + JarFile(src).use { a -> + JarFile(dest).use { b -> + val aEntries = a.entries().asSequence().sortedBy { it.name }.toList() + val bEntries = b.entries().asSequence().sortedBy { it.name }.toList() + assertEquals(aEntries.size, bEntries.size) + assertEquals(aEntries.map { it.name }, bEntries.map { it.name }) + assertEquals(aEntries.map { it.size }, bEntries.map { it.size }) + } + } + } + +} diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..908889ad5 --- /dev/null +++ b/build.gradle @@ -0,0 +1,118 @@ +buildscript { + ext.kotlin_version = org.gradle.kotlin.dsl.KotlinDependencyExtensionsKt.embeddedKotlinVersion + + repositories { + mavenLocal() + google() + mavenCentral() + jcenter() + maven { url 'https://plugins.gradle.org/m2/' } + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { project -> + apply plugin: 'java' + apply plugin: 'maven' + apply plugin: 'signing' + + group = 'com.didiglobal.booster' + version = '0.1.0' + + repositories { + mavenLocal() + google() + mavenCentral() + jcenter() + } + + javadoc { + failOnError = false + } + + task packageJavadoc(type: Jar) { + classifier = 'javadoc' + from javadoc + } + + task packageSources(type: Jar) { + classifier = 'sources' + from sourceSets.main.allSource + } + + artifacts { + archives packageJavadoc, packageSources + } + + signing { + sign configurations.archives + } + + uploadArchives { + repositories { + mavenDeployer { + beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } + pom.project { + groupId project.group + artifactId project.name + version project.version + description project.description ?: '' + + scm { + connection 'scm:git:git://github.com/didi/booster.git' + developerConnection 'scm:git:git@github.com:didi/booster.git' + url 'https://github.com/didi/booster' + } + + licenses { + license { + name 'Apache License' + url 'https://www.apache.org/licenses/LICENSE-2.0' + } + } + } + pom.withXml { xml -> + (['git', 'log', '--format=%aN %aE'].execute() | ['sort', '-u'].execute()).with { + waitForOrKill(5000) + if (0 == exitValue()) { + def developers = xml.asNode().appendNode('developers') + text.eachLine { line -> + def author = line.split('\\s+') + if (author.length > 1) { + developers.appendNode('developer').with { + appendNode('id', author[0]) + appendNode('email', author[1]) + } + } + } + } + } + } + repository(url: 'https://oss.sonatype.org/service/local/staging/deploy/maven2/') { + authentication(userName: OSSRH_USERNAME, password: OSSRH_PASSWORD) + } + snapshotRepository(url: 'https://oss.sonatype.org/content/repositories/snapshots/') { + authentication(userName: OSSRH_USERNAME, password: OSSRH_PASSWORD) + } + } + } + } + + test { + testLogging { + events "passed", "skipped", "failed", "standardOut", "standardError" + } + } +} + +subprojects { project -> + rootProject.clean.dependsOn project.clean +} + +dependencies { + implementation project(':booster-gradle-plugin') +} + + diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle new file mode 100644 index 000000000..dfacaa314 --- /dev/null +++ b/buildSrc/build.gradle @@ -0,0 +1,35 @@ +import static org.gradle.kotlin.dsl.KotlinDependencyExtensionsKt.embeddedKotlinVersion + +buildscript { + ext.kotlin_version = embeddedKotlinVersion + repositories { + mavenLocal() + google() + mavenCentral() + jcenter() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +apply plugin: 'java' +apply plugin: 'maven' +apply plugin: 'kotlin' +apply plugin: 'kotlin-kapt' + +repositories { + mavenLocal() + google() + mavenCentral() + jcenter() +} + +dependencies { + implementation gradleApi() + implementation localGroovy() + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" + testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" + testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" +} diff --git a/buildSrc/src/main/kotlin/com/didiglobal/booster/buildprops/BuildPropsGenerator.kt b/buildSrc/src/main/kotlin/com/didiglobal/booster/buildprops/BuildPropsGenerator.kt new file mode 100644 index 000000000..5df728d58 --- /dev/null +++ b/buildSrc/src/main/kotlin/com/didiglobal/booster/buildprops/BuildPropsGenerator.kt @@ -0,0 +1,84 @@ +package com.didiglobal.booster.buildprops + +import org.gradle.api.internal.AbstractTask +import org.gradle.api.plugins.JavaPluginConvention +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.SourceSet +import org.gradle.api.tasks.TaskAction +import java.io.File +import java.io.IOException +import java.util.StringTokenizer + +/** + * The task for `Build.java` source file generating + * + * @author johnsonlee + */ +open class BuildPropsGenerator : AbstractTask() { + + val output: File + @OutputDirectory + get() = project.getGeneratedSourceDir(project.convention.getPlugin(JavaPluginConvention::class.java).sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME)) + + @TaskAction + @Throws(IOException::class) + fun run() { + val pkg = mkpkg("${project.group}.${project.name}") + val path = "${pkg.replace(".", File.separator)}${File.separator}Build.java" + val revision = File(project.rootProject.projectDir, ".git${File.separator}logs${File.separator}HEAD").readText().let { + StringTokenizer(it).nextToken(1) + } + + File(output, path).also { + it.parentFile.mkdirs() + it.createNewFile() + }.printWriter().use { + it.apply { + it.println(""" + /** + * DO NOT MODIFY! This file is generated automatically. + */ + package $pkg; + + public interface Build { + String GROUP = "${project.group}"; + String ARTIFACT = "${project.name}"; + String VERSION = "${project.version}"; + String REVISION = "$revision"; + } + """.trimIndent()) + } + } + } + +} + +/** + * Make a safe package name + */ +internal fun mkpkg(s: String): String { + return StringBuilder(s.length).apply { + s.forEachIndexed { i, c -> + if (0 == i) { + append(if ('.' != c && !c.isJavaIdentifierStart()) '.' else c) + } else { + append(if ('.' != c && !c.isJavaIdentifierPart()) '.' else c) + } + } + }.toString().split('.').let { + it.filterIndexed { i, s -> !(i > 0 && s == it[i - 1]) } + }.joinToString(".") +} + +internal fun StringTokenizer.nextToken(nth: Int): String? { + var i = 0 + + while (hasMoreTokens()) { + val token = nextToken() + if (nth == i++) { + return token + } + } + + return null +} diff --git a/buildSrc/src/main/kotlin/com/didiglobal/booster/buildprops/BuildPropsPlugin.kt b/buildSrc/src/main/kotlin/com/didiglobal/booster/buildprops/BuildPropsPlugin.kt new file mode 100644 index 000000000..7ec0173fa --- /dev/null +++ b/buildSrc/src/main/kotlin/com/didiglobal/booster/buildprops/BuildPropsPlugin.kt @@ -0,0 +1,83 @@ +package com.didiglobal.booster.buildprops + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.plugins.JavaPluginConvention +import org.gradle.api.tasks.SourceSet +import org.gradle.api.tasks.SourceSetContainer +import org.gradle.api.tasks.SourceTask +import org.gradle.plugins.ide.idea.model.IdeaModel +import org.gradle.plugins.ide.idea.model.IdeaModule +import java.io.File +import java.util.LinkedHashSet + +/** + * Gradle plugin for `Build.java` source code generating. + * + * @author johnsonlee + */ +class BuildPropsPlugin : Plugin { + + override fun apply(project: Project) { + if (!project.plugins.hasPlugin("idea")) { + project.plugins.apply("idea") + } + + val convention = project.convention + val javaPlugin = convention.getPlugin(JavaPluginConvention::class.java) + val sourceSets = javaPlugin.sourceSets + val buildProps = project.tasks.create("generateBuildProps", BuildPropsGenerator::class.java) { + it.outputs.upToDateWhen { false } + } + + project.configureIdeaModule(sourceSets) + sourceSets.filter { it.name == SourceSet.MAIN_SOURCE_SET_NAME }.forEach { sourceSet -> + (project.tasks.findByName(sourceSet.getCompileTaskName("java"))?.dependsOn(buildProps) as? SourceTask)?.source(buildProps.output) + (project.tasks.findByName(sourceSet.getCompileTaskName("groovy"))?.dependsOn(buildProps) as? SourceTask)?.source(buildProps.output) + (project.tasks.findByName(sourceSet.getCompileTaskName("kotlin"))?.dependsOn(buildProps) as? SourceTask)?.source(buildProps.output) + } + } + +} + +internal const val PLUGIN_ID = "buildprops" + +internal val GENERATED_SOURCE_ROOT = "generated${File.separator}source${File.separator}$PLUGIN_ID" + +internal fun Project.getGeneratedSourceDir(sourceSet: SourceSet) = File(this.buildDir, GENERATED_SOURCE_ROOT + File.separator + sourceSet.name + File.separator + "java") + +private fun Project.configureIdeaModule(sourceSets: SourceSetContainer) { + val mainSourceSet = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME) + val testSourceSet = sourceSets.getByName(SourceSet.TEST_SOURCE_SET_NAME) + val mainGeneratedSourcesDir = getGeneratedSourceDir(mainSourceSet) + val testGeneratedSourcesDir = getGeneratedSourceDir(testSourceSet) + val ideaModule = extensions.getByType(IdeaModel::class.java).module + ideaModule.excludeDirs = getIdeaExcludeDirs(getGeneratedSourceDirs(sourceSets), ideaModule) + ideaModule.sourceDirs = files(ideaModule.sourceDirs, mainGeneratedSourcesDir).files + ideaModule.testSourceDirs = files(ideaModule.testSourceDirs, testGeneratedSourcesDir).files + ideaModule.generatedSourceDirs = files(ideaModule.generatedSourceDirs, mainGeneratedSourcesDir, testGeneratedSourcesDir).files +} + +private fun Project.getGeneratedSourceDirs(sourceSets: SourceSetContainer): Set = LinkedHashSet().also { excludes -> + sourceSets.forEach { sourceSet -> + var f = getGeneratedSourceDir(sourceSet) + + while (f != this.projectDir) { + excludes.add(f) + f = f.parentFile + } + } +} + +private fun Project.getIdeaExcludeDirs(excludes: Set, ideaModule: IdeaModule): Set = LinkedHashSet(ideaModule.excludeDirs).also { excludeDirs -> + if (excludes.contains(buildDir) && excludeDirs.contains(buildDir)) { + excludeDirs.remove(buildDir) + buildDir.listFiles()?.filter { + it.isDirectory + }?.forEach { + excludeDirs.add(it) + } + } + + excludeDirs.removeAll(excludes) +} diff --git a/buildSrc/src/test/kotlin/com/didiglobal/booster/buildprops/BuildPropsGeneratorTest.kt b/buildSrc/src/test/kotlin/com/didiglobal/booster/buildprops/BuildPropsGeneratorTest.kt new file mode 100644 index 000000000..426598463 --- /dev/null +++ b/buildSrc/src/test/kotlin/com/didiglobal/booster/buildprops/BuildPropsGeneratorTest.kt @@ -0,0 +1,14 @@ +package com.didiglobal.booster.buildprops + +import kotlin.test.Test +import kotlin.test.assertEquals + +class BuildPropsGeneratorTest { + + @Test + fun test_mkpkg() { + val s = mkpkg("booster.booster-gradle-plugin") + assertEquals("booster.gradle.plugin", s) + } + +} diff --git a/gradle/booster.gradle b/gradle/booster.gradle new file mode 100644 index 000000000..32653d8c9 --- /dev/null +++ b/gradle/booster.gradle @@ -0,0 +1,39 @@ +import com.didiglobal.booster.buildprops.BuildPropsPlugin + +apply plugin: 'kotlin' +apply plugin: 'kotlin-kapt' +apply plugin: BuildPropsPlugin + +sourceSets { + main { + java { + srcDirs += [] + } + kotlin { + srcDirs += ['src/main/kotlin', 'src/main/java'] + } + } + test { + java { + srcDirs += [] + } + kotlin { + srcDirs += ['src/main/kotlin', 'src/main/java'] + } + } +} + +compileKotlin { + kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8 +} + +compileTestKotlin { + kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8 +} + +dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" + testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version" + testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..f6b961fd5a86aa5fbfe90f707c3138408be7c718 GIT binary patch literal 54329 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

<5G2M1WZP5 zzqSuO91lJod(SBDDw<*sX(+F6Uq~YAeYV#2A;XQu_p=N5X+#cmu19Qk>QAnV=k!?wbk5I;tDWgFc}0NkvC*G=V+Yh1cyeJVq~9czZiDXe+S=VfL2g`LWo8om z$Y~FQc6MFjV-t1Y`^D9XMwY*U_re2R?&(O~68T&D4S{X`6JYU-pz=}ew-)V0AOUT1 zVOkHAB-8uBcRjLvz<9HS#a@X*Kc@|W)nyiSgi|u5$Md|P()%2(?olGg@ypoJwp6>m z*dnfjjWC>?_1p;%1brqZyDRR;8EntVA92EJ3ByOxj6a+bhPl z;a?m4rQAV1@QU^#M1HX)0+}A<7TCO`ZR_RzF}X9-M>cRLyN4C+lCk2)kT^3gN^`IT zNP~fAm(wyIoR+l^lQDA(e1Yv}&$I!n?&*p6?lZcQ+vGLLd~fM)qt}wsbf3r=tmVYe zl)ntf#E!P7wlakP9MXS7m0nsAmqxZ*)#j;M&0De`oNmFgi$ov#!`6^4)iQyxg5Iuj zjLAhzQ)r`^hf7`*1`Rh`X;LVBtDSz@0T?kkT1o!ijeyTGt5vc^Cd*tmNgiNo^EaWvaC8$e+nb_{W01j3%=1Y&92YacjCi>eNbwk%-gPQ@H-+4xskQ}f_c=jg^S-# zYFBDf)2?@5cy@^@FHK5$YdAK9cI;!?Jgd}25lOW%xbCJ>By3=HiK@1EM+I46A)Lsd zeT|ZH;KlCml=@;5+hfYf>QNOr^XNH%J-lvev)$Omy8MZ`!{`j>(J5cG&ZXXgv)TaF zg;cz99i$4CX_@3MIb?GL0s*8J=3`#P(jXF(_(6DXZjc@(@h&=M&JG)9&Te1?(^XMW zjjC_70|b=9hB6pKQi`S^Ls7JyJw^@P>Ko^&q8F&?>6i;#CbxUiLz1ZH4lNyd@QACd zu>{!sqjB!2Dg}pbAXD>d!3jW}=5aN0b;rw*W>*PAxm7D)aw(c*RX2@bTGEI|RRp}vw7;NR2wa;rXN{L{Q#=Fa z$x@ms6pqb>!8AuV(prv>|aU8oWV={C&$c zMa=p=CDNOC2tISZcd8~18GN5oTbKY+Vrq;3_obJlfSKRMk;Hdp1`y`&LNSOqeauR_ z^j*Ojl3Ohzb5-a49A8s|UnM*NM8tg}BJXdci5%h&;$afbmRpN0&~9rCnBA`#lG!p zc{(9Y?A0Y9yo?wSYn>iigf~KP$0*@bGZ>*YM4&D;@{<%Gg5^uUJGRrV4 z(aZOGB&{_0f*O=Oi0k{@8vN^BU>s3jJRS&CJOl3o|BE{FAA&a#2YYiX3pZz@|Go-F z|Fly;7eX2OTs>R}<`4RwpHFs9nwh)B28*o5qK1Ge=_^w0m`uJOv!=&!tzt#Save(C zgKU=Bsgql|`ui(e1KVxR`?>Dx>(rD1$iWp&m`v)3A!j5(6vBm*z|aKm*T*)mo(W;R zNGo2`KM!^SS7+*9YxTm6YMm_oSrLceqN*nDOAtagULuZl5Q<7mOnB@Hq&P|#9y{5B z!2x+2s<%Cv2Aa0+u{bjZXS);#IFPk(Ph-K7K?3i|4ro> zRbqJoiOEYo(Im^((r}U4b8nvo_>4<`)ut`24?ILnglT;Pd&U}$lV3U$F9#PD(O=yV zgNNA=GW|(E=&m_1;uaNmipQe?pon4{T=zK!N!2_CJL0E*R^XXIKf*wi!>@l}3_P9Z zF~JyMbW!+n-+>!u=A1ESxzkJy$DRuG+$oioG7(@Et|xVbJ#BCt;J43Nvj@MKvTxzy zMmjNuc#LXBxFAwIGZJk~^!q$*`FME}yKE8d1f5Mp}KHNq(@=Z8YxV}0@;YS~|SpGg$_jG7>_8WWYcVx#4SxpzlV9N4aO>K{c z$P?a_fyDzGX$Of3@ykvedGd<@-R;M^Shlj*SswJLD+j@hi_&_>6WZ}#AYLR0iWMK|A zH_NBeu(tMyG=6VO-=Pb>-Q#$F*or}KmEGg*-n?vWQREURdB#+6AvOj*I%!R-4E_2$ zU5n9m>RWs|Wr;h2DaO&mFBdDb-Z{APGQx$(L`if?C|njd*fC=rTS%{o69U|meRvu?N;Z|Y zbT|ojL>j;q*?xXmnHH#3R4O-59NV1j=uapkK7}6@Wo*^Nd#(;$iuGsb;H315xh3pl zHaJ>h-_$hdNl{+|Zb%DZH%ES;*P*v0#}g|vrKm9;j-9e1M4qX@zkl&5OiwnCz=tb6 zz<6HXD+rGIVpGtkb{Q^LIgExOm zz?I|oO9)!BOLW#krLmWvX5(k!h{i>ots*EhpvAE;06K|u_c~y{#b|UxQ*O@Ks=bca z^_F0a@61j3I(Ziv{xLb8AXQj3;R{f_l6a#H5ukg5rxwF9A$?Qp-Mo54`N-SKc}fWp z0T)-L@V$$&my;l#Ha{O@!fK4-FSA)L&3<${Hcwa7ue`=f&YsXY(NgeDU#sRlT3+9J z6;(^(sjSK@3?oMo$%L-nqy*E;3pb0nZLx6 z;h5)T$y8GXK1DS-F@bGun8|J(v-9o=42&nLJy#}M5D0T^5VWBNn$RpC zZzG6Bt66VY4_?W=PX$DMpKAI!d`INr) zkMB{XPQ<52rvWVQqgI0OL_NWxoe`xxw&X8yVftdODPj5|t}S6*VMqN$-h9)1MBe0N zYq?g0+e8fJCoAksr0af1)FYtz?Me!Cxn`gUx&|T;)695GG6HF7!Kg1zzRf_{VWv^bo81v4$?F6u2g|wxHc6eJQAg&V z#%0DnWm2Rmu71rPJ8#xFUNFC*V{+N_qqFH@gYRLZ6C?GAcVRi>^n3zQxORPG)$-B~ z%_oB?-%Zf7d*Fe;cf%tQwcGv2S?rD$Z&>QC2X^vwYjnr5pa5u#38cHCt4G3|efuci z@3z=#A13`+ztmp;%zjXwPY_aq-;isu*hecWWX_=Z8paSqq7;XYnUjK*T>c4~PR4W7 z#C*%_H&tfGx`Y$w7`dXvVhmovDnT>btmy~SLf>>~84jkoQ%cv=MMb+a{JV&t0+1`I z32g_Y@yDhKe|K^PevP~MiiVl{Ou7^Mt9{lOnXEQ`xY^6L8D$705GON{!1?1&YJEl#fTf5Z)da=yiEQ zGgtC-soFGOEBEB~ZF_{7b(76En>d}mI~XIwNw{e>=Fv)sgcw@qOsykWr?+qAOZSVrQfg}TNI ztKNG)1SRrAt6#Q?(me%)>&A_^DM`pL>J{2xu>xa$3d@90xR61TQDl@fu%_85DuUUA za9tn64?At;{`BAW6oykwntxHeDpXsV#{tmt5RqdN7LtcF4vR~_kZNT|wqyR#z^Xcd zFdymVRZvyLfTpBT>w9<)Ozv@;Yk@dOSVWbbtm^y@@C>?flP^EgQPAwsy75bveo=}T zFxl(f)s)j(0#N_>Or(xEuV(n$M+`#;Pc$1@OjXEJZumkaekVqgP_i}p`oTx;terTx zZpT+0dpUya2hqlf`SpXN{}>PfhajNk_J0`H|2<5E;U5Vh4F8er z;RxLSFgpGhkU>W?IwdW~NZTyOBrQ84H7_?gviIf71l`EETodG9a1!8e{jW?DpwjL? zGEM&eCzwoZt^P*8KHZ$B<%{I}>46IT%jJ3AnnB5P%D2E2Z_ z1M!vr#8r}1|KTqWA4%67ZdbMW2YJ81b(KF&SQ2L1Qn(y-=J${p?xLMx3W7*MK;LFQ z6Z`aU;;mTL4XrrE;HY*Rkh6N%?qviUGNAKiCB~!P}Z->IpO6E(gGd7I#eDuT7j|?nZ zK}I(EJ>$Kb&@338M~O+em9(L!+=0zBR;JAQesx|3?Ok90)D1aS9P?yTh6Poh8Cr4X zk3zc=f2rE7jj+aP7nUsr@~?^EGP>Q>h#NHS?F{Cn`g-gD<8F&dqOh-0sa%pfL`b+1 zUsF*4a~)KGb4te&K0}bE>z3yb8% zibb5Q%Sfiv7feb1r0tfmiMv z@^4XYwg@KZI=;`wC)`1jUA9Kv{HKe2t$WmRcR4y8)VAFjRi zaz&O7Y2tDmc5+SX(bj6yGHYk$dBkWc96u3u&F)2yEE~*i0F%t9Kg^L6MJSb&?wrXi zGSc;_rln$!^ybwYBeacEFRsVGq-&4uC{F)*Y;<0y7~USXswMo>j4?~5%Zm!m@i@-> zXzi82sa-vpU{6MFRktJy+E0j#w`f`>Lbog{zP|9~hg(r{RCa!uGe>Yl536cn$;ouH za#@8XMvS-kddc1`!1LVq;h57~zV`7IYR}pp3u!JtE6Q67 zq3H9ZUcWPm2V4IukS}MCHSdF0qg2@~ufNx9+VMjQP&exiG_u9TZAeAEj*jw($G)zL zq9%#v{wVyOAC4A~AF=dPX|M}MZV)s(qI9@aIK?Pe+~ch|>QYb+78lDF*Nxz2-vpRbtQ*F4$0fDbvNM#CCatgQ@z1+EZWrt z2dZfywXkiW=no5jus-92>gXn5rFQ-COvKyegmL=4+NPzw6o@a?wGE-1Bt;pCHe;34K%Z z-FnOb%!nH;)gX+!a3nCk?5(f1HaWZBMmmC@lc({dUah+E;NOros{?ui1zPC-Q0);w zEbJmdE$oU$AVGQPdm{?xxI_0CKNG$LbY*i?YRQ$(&;NiA#h@DCxC(U@AJ$Yt}}^xt-EC_ z4!;QlLkjvSOhdx!bR~W|Ezmuf6A#@T`2tsjkr>TvW*lFCMY>Na_v8+{Y|=MCu1P8y z89vPiH5+CKcG-5lzk0oY>~aJC_0+4rS@c@ZVKLAp`G-sJB$$)^4*A!B zmcf}lIw|VxV9NSoJ8Ag3CwN&d7`|@>&B|l9G8tXT^BDHOUPrtC70NgwN4${$k~d_4 zJ@eo6%YQnOgq$th?0{h`KnqYa$Nz@vlHw<%!C5du6<*j1nwquk=uY}B8r7f|lY+v7 zm|JU$US08ugor8E$h3wH$c&i~;guC|3-tqJy#T;v(g( zBZtPMSyv%jzf->435yM(-UfyHq_D=6;ouL4!ZoD+xI5uCM5ay2m)RPmm$I}h>()hS zO!0gzMxc`BPkUZ)WXaXam%1;)gedA7SM8~8yIy@6TPg!hR0=T>4$Zxd)j&P-pXeSF z9W`lg6@~YDhd19B9ETv(%er^Xp8Yj@AuFVR_8t*KS;6VHkEDKI#!@l!l3v6`W1`1~ zP{C@keuV4Q`Rjc08lx?zmT$e$!3esc9&$XZf4nRL(Z*@keUbk!GZi(2Bmyq*saOD? z3Q$V<*P-X1p2}aQmuMw9nSMbOzuASsxten7DKd6A@ftZ=NhJ(0IM|Jr<91uAul4JR zADqY^AOVT3a(NIxg|U;fyc#ZnSzw2cr}#a5lZ38>nP{05D)7~ad7JPhw!LqOwATXtRhK!w0X4HgS1i<%AxbFmGJx9?sEURV+S{k~g zGYF$IWSlQonq6}e;B(X(sIH|;52+(LYW}v_gBcp|x%rEAVB`5LXg_d5{Q5tMDu0_2 z|LOm$@K2?lrLNF=mr%YP|U-t)~9bqd+wHb4KuPmNK<}PK6e@aosGZK57=Zt+kcszVOSbe;`E^dN! ze7`ha3WUUU7(nS0{?@!}{0+-VO4A{7+nL~UOPW9_P(6^GL0h${SLtqG!} zKl~Ng5#@Sy?65wk9z*3SA`Dpd4b4T^@C8Fhd8O)k_4%0RZL5?#b~jmgU+0|DB%0Z) zql-cPC>A9HPjdOTpPC` zQwvF}uB5kG$Xr4XnaH#ruSjM*xG?_hT7y3G+8Ox`flzU^QIgb_>2&-f+XB6MDr-na zSi#S+c!ToK84<&m6sCiGTd^8pNdXo+$3^l3FL_E`0 z>8it5YIDxtTp2Tm(?}FX^w{fbfgh7>^8mtvN>9fWgFN_*a1P`Gz*dyOZF{OV7BC#j zQV=FQM5m>47xXgapI$WbPM5V`V<7J9tD)oz@d~MDoM`R^Y6-Na(lO~uvZlpu?;zw6 zVO1faor3dg#JEb5Q*gz4<W8tgC3nE2BG2jeIQs1)<{In&7hJ39x=;ih;CJDy)>0S1at*7n?Wr0ahYCpFjZ|@u91Zl7( zv;CSBRC65-6f+*JPf4p1UZ)k=XivKTX6_bWT~7V#rq0Xjas6hMO!HJN8GdpBKg_$B zwDHJF6;z?h<;GXFZan8W{XFNPpOj!(&I1`&kWO86p?Xz`a$`7qV7Xqev|7nn_lQuX ziGpU1MMYt&5dE2A62iX3;*0WzNB9*nSTzI%62A+N?f?;S>N@8M=|ef3gtQTIA*=yq zQAAjOqa!CkHOQo4?TsqrrsJLclXcP?dlAVv?v`}YUjo1Htt;6djP@NPFH+&p1I+f_ z)Y279{7OWomY8baT(4TAOlz1OyD{4P?(DGv3XyJTA2IXe=kqD)^h(@*E3{I~w;ws8 z)ZWv7E)pbEM zd3MOXRH3mQhks9 zv6{s;k0y5vrcjXaVfw8^>YyPo=oIqd5IGI{)+TZq5Z5O&hXAw%ZlL}^6FugH;-%vP zAaKFtt3i^ag226=f0YjzdPn6|4(C2sC5wHFX{7QF!tG1E-JFA`>eZ`}$ymcRJK?0c zN363o{&ir)QySOFY0vcu6)kX#;l??|7o{HBDVJN+17rt|w3;(C_1b>d;g9Gp=8YVl zYTtA52@!7AUEkTm@P&h#eg+F*lR zQ7iotZTcMR1frJ0*V@Hw__~CL>_~2H2cCtuzYIUD24=Cv!1j6s{QS!v=PzwQ(a0HS zBKx04KA}-Ue+%9d`?PG*hIij@54RDSQpA7|>qYVIrK_G6%6;#ZkR}NjUgmGju)2F`>|WJoljo)DJgZr4eo1k1i1+o z1D{>^RlpIY8OUaOEf5EBu%a&~c5aWnqM zxBpJq98f=%M^{4mm~5`CWl%)nFR64U{(chmST&2jp+-r z3675V<;Qi-kJud%oWnCLdaU-)xTnMM%rx%Jw6v@=J|Ir=4n-1Z23r-EVf91CGMGNz zb~wyv4V{H-hkr3j3WbGnComiqmS0vn?n?5v2`Vi>{Ip3OZUEPN7N8XeUtF)Ry6>y> zvn0BTLCiqGroFu|m2zG-;Xb6;W`UyLw)@v}H&(M}XCEVXZQoWF=Ykr5lX3XWwyNyF z#jHv)A*L~2BZ4lX?AlN3X#axMwOC)PoVy^6lCGse9bkGjb=qz%kDa6}MOmSwK`cVO zt(e*MW-x}XtU?GY5}9{MKhRhYOlLhJE5=ca+-RmO04^ z66z{40J=s=ey9OCdc(RCzy zd7Zr1%!y3}MG(D=wM_ebhXnJ@MLi7cImDkhm0y{d-Vm81j`0mbi4lF=eirlr)oW~a zCd?26&j^m4AeXEsIUXiTal)+SPM4)HX%%YWF1?(FV47BaA`h9m67S9x>hWMVHx~Hg z1meUYoLL(p@b3?x|9DgWeI|AJ`Ia84*P{Mb%H$ZRROouR4wZhOPX15=KiBMHl!^JnCt$Az`KiH^_d>cev&f zaG2>cWf$=A@&GP~DubsgYb|L~o)cn5h%2`i^!2)bzOTw2UR!>q5^r&2Vy}JaWFUQE04v>2;Z@ZPwXr?y&G(B^@&y zsd6kC=hHdKV>!NDLIj+3rgZJ|dF`%N$DNd;B)9BbiT9Ju^Wt%%u}SvfM^=|q-nxDG zuWCQG9e#~Q5cyf8@y76#kkR^}{c<_KnZ0QsZcAT|YLRo~&tU|N@BjxOuy`#>`X~Q< z?R?-Gsk$$!oo(BveQLlUrcL#eirhgBLh`qHEMg`+sR1`A=1QX7)ZLMRT+GBy?&mM8 zQG^z-!Oa&J-k7I(3_2#Q6Bg=NX<|@X&+YMIOzfEO2$6Mnh}YV!m!e^__{W@-CTprr zbdh3f=BeCD$gHwCrmwgM3LAv3!Mh$wM)~KWzp^w)Cu6roO7uUG5z*}i0_0j47}pK; ztN530`ScGatLOL06~zO)Qmuv`h!gq5l#wx(EliKe&rz-5qH(hb1*fB#B+q`9=jLp@ zOa2)>JTl7ovxMbrif`Xe9;+fqB1K#l=Dv!iT;xF zdkCvS>C5q|O;}ns3AgoE({Ua-zNT-9_5|P0iANmC6O76Sq_(AN?UeEQJ>#b54fi3k zFmh+P%b1x3^)0M;QxXLP!BZ^h|AhOde*{9A=f3|Xq*JAs^Y{eViF|=EBfS6L%k4ip zk+7M$gEKI3?bQg?H3zaE@;cyv9kv;cqK$VxQbFEsy^iM{XXW0@2|DOu$!-k zSFl}Y=jt-VaT>Cx*KQnHTyXt}f9XswFB9ibYh+k2J!ofO+nD?1iw@mwtrqI4_i?nE zhLkPp41ED62me}J<`3RN80#vjW;wt`pP?%oQ!oqy7`miL>d-35a=qotK$p{IzeSk# ze_$CFYp_zIkrPFVaW^s#U4xT1lI^A0IBe~Y<4uS%zSV=wcuLr%gQT=&5$&K*bwqx| zWzCMiz>7t^Et@9CRUm9E+@hy~sBpm9fri$sE1zgLU((1?Yg{N1Sars=DiW&~Zw=3I zi7y)&oTC?UWD2w97xQ&5vx zRXEBGeJ(I?Y}eR0_O{$~)bMJRTsNUPIfR!xU9PE7A>AMNr_wbrFK>&vVw=Y;RH zO$mlpmMsQ}-FQ2cSj7s7GpC+~^Q~dC?y>M}%!-3kq(F3hGWo9B-Gn02AwUgJ>Z-pKOaj zysJBQx{1>Va=*e@sLb2z&RmQ7ira;aBijM-xQ&cpR>X3wP^foXM~u1>sv9xOjzZpX z0K;EGouSYD~oQ&lAafj3~EaXfFShC+>VsRlEMa9cg9i zFxhCKO}K0ax6g4@DEA?dg{mo>s+~RPI^ybb^u--^nTF>**0l5R9pocwB?_K)BG_)S zyLb&k%XZhBVr7U$wlhMqwL)_r&&n%*N$}~qijbkfM|dIWP{MyLx}X&}ES?}7i;9bW zmTVK@zR)7kE2+L42Q`n4m0VVg5l5(W`SC9HsfrLZ=v%lpef=Gj)W59VTLe+Z$8T8i z4V%5+T0t8LnM&H>Rsm5C%qpWBFqgTwL{=_4mE{S3EnBXknM&u8n}A^IIM4$s3m(Rd z>zq=CP-!9p9es2C*)_hoL@tDYABn+o#*l;6@7;knWIyDrt5EuakO99S$}n((Fj4y} zD!VvuRzghcE{!s;jC*<_H$y6!6QpePo2A3ZbX*ZzRnQq*b%KK^NF^z96CHaWmzU@f z#j;y?X=UP&+YS3kZx7;{ zDA{9(wfz7GF`1A6iB6fnXu0?&d|^p|6)%3$aG0Uor~8o? z*e}u#qz7Ri?8Uxp4m_u{a@%bztvz-BzewR6bh*1Xp+G=tQGpcy|4V_&*aOqu|32CM zz3r*E8o8SNea2hYJpLQ-_}R&M9^%@AMx&`1H8aDx4j%-gE+baf2+9zI*+Pmt+v{39 zDZ3Ix_vPYSc;Y;yn68kW4CG>PE5RoaV0n@#eVmk?p$u&Fy&KDTy!f^Hy6&^-H*)#u zdrSCTJPJw?(hLf56%2;_3n|ujUSJOU8VPOTlDULwt0jS@j^t1WS z!n7dZIoT+|O9hFUUMbID4Ec$!cc($DuQWkocVRcYSikFeM&RZ=?BW)mG4?fh#)KVG zcJ!<=-8{&MdE)+}?C8s{k@l49I|Zwswy^ZN3;E!FKyglY~Aq?4m74P-0)sMTGXqd5(S<-(DjjM z&7dL-Mr8jhUCAG$5^mI<|%`;JI5FVUnNj!VO2?Jiqa|c2;4^n!R z`5KK0hyB*F4w%cJ@Un6GC{mY&r%g`OX|1w2$B7wxu97%<@~9>NlXYd9RMF2UM>(z0 zouu4*+u+1*k;+nFPk%ly!nuMBgH4sL5Z`@Rok&?Ef=JrTmvBAS1h?C0)ty5+yEFRz zY$G=coQtNmT@1O5uk#_MQM1&bPPnspy5#>=_7%WcEL*n$;sSAZcXxMpcXxLe;_mLA z5F_paad+bGZV*oh@8h0(|D2P!q# zTHjmiphJ=AazSeKQPkGOR-D8``LjzToyx{lfK-1CDD6M7?pMZOdLKFtjZaZMPk4}k zW)97Fh(Z+_Fqv(Q_CMH-YYi?fR5fBnz7KOt0*t^cxmDoIokc=+`o# zrud|^h_?KW=Gv%byo~(Ln@({?3gnd?DUf-j2J}|$Mk>mOB+1{ZQ8HgY#SA8END(Zw z3T+W)a&;OO54~m}ffemh^oZ!Vv;!O&yhL0~hs(p^(Yv=(3c+PzPXlS5W79Er8B1o* z`c`NyS{Zj_mKChj+q=w)B}K za*zzPhs?c^`EQ;keH{-OXdXJet1EsQ)7;{3eF!-t^4_Srg4(Ot7M*E~91gwnfhqaM zNR7dFaWm7MlDYWS*m}CH${o?+YgHiPC|4?X?`vV+ws&Hf1ZO-w@OGG^o4|`b{bLZj z&9l=aA-Y(L11!EvRjc3Zpxk7lc@yH1e$a}8$_-r$)5++`_eUr1+dTb@ zU~2P1HM#W8qiNN3b*=f+FfG1!rFxnNlGx{15}BTIHgxO>Cq4 z;#9H9YjH%>Z2frJDJ8=xq>Z@H%GxXosS@Z>cY9ppF+)e~t_hWXYlrO6)0p7NBMa`+ z^L>-#GTh;k_XnE)Cgy|0Dw;(c0* zSzW14ZXozu)|I@5mRFF1eO%JM=f~R1dkNpZM+Jh(?&Zje3NgM{2ezg1N`AQg5%+3Y z64PZ0rPq6;_)Pj-hyIOgH_Gh`1$j1!jhml7ksHA1`CH3FDKiHLz+~=^u@kUM{ilI5 z^FPiJ7mSrzBs9{HXi2{sFhl5AyqwUnU{sPcUD{3+l-ZHAQ)C;c$=g1bdoxeG(5N01 zZy=t8i{*w9m?Y>V;uE&Uy~iY{pY4AV3_N;RL_jT_QtLFx^KjcUy~q9KcLE3$QJ{!)@$@En{UGG7&}lc*5Kuc^780;7Bj;)X?1CSy*^^ zPP^M)Pr5R>mvp3_hmCtS?5;W^e@5BjE>Cs<`lHDxj<|gtOK4De?Sf0YuK5GX9G93i zMYB{8X|hw|T6HqCf7Cv&r8A$S@AcgG1cF&iJ5=%+x;3yB`!lQ}2Hr(DE8=LuNb~Vs z=FO&2pdc16nD$1QL7j+!U^XWTI?2qQKt3H8=beVTdHHa9=MiJ&tM1RRQ-=+vy!~iz zj3O{pyRhCQ+b(>jC*H)J)%Wq}p>;?@W*Eut@P&?VU+Sdw^4kE8lvX|6czf{l*~L;J zFm*V~UC;3oQY(ytD|D*%*uVrBB}BbAfjK&%S;z;7$w68(8PV_whC~yvkZmX)xD^s6 z{$1Q}q;99W?*YkD2*;)tRCS{q2s@JzlO~<8x9}X<0?hCD5vpydvOw#Z$2;$@cZkYrp83J0PsS~!CFtY%BP=yxG?<@#{7%2sy zOc&^FJxsUYN36kSY)d7W=*1-{7ghPAQAXwT7z+NlESlkUH&8ODlpc8iC*iQ^MAe(B z?*xO4i{zFz^G=^G#9MsLKIN64rRJykiuIVX5~0#vAyDWc9-=6BDNT_aggS2G{B>dD ze-B%d3b6iCfc5{@yz$>=@1kdK^tX9qh0=ocv@9$ai``a_ofxT=>X7_Y0`X}a^M?d# z%EG)4@`^Ej_=%0_J-{ga!gFtji_byY&Vk@T1c|ucNAr(JNr@)nCWj?QnCyvXg&?FW;S-VOmNL6^km_dqiVjJuIASVGSFEos@EVF7St$WE&Z%)`Q##+0 zjaZ=JI1G@0!?l|^+-ZrNd$WrHBi)DA0-Eke>dp=_XpV<%CO_Wf5kQx}5e<90dt>8k zAi00d0rQ821nA>B4JHN7U8Zz=0;9&U6LOTKOaC1FC8GgO&kc=_wHIOGycL@c*$`ce703t%>S}mvxEnD-V!;6c`2(p74V7D0No1Xxt`urE66$0(ThaAZ1YVG#QP$ zy~NN%kB*zhZ2Y!kjn826pw4bh)75*e!dse+2Db(;bN34Uq7bLpr47XTX{8UEeC?2i z*{$`3dP}32${8pF$!$2Vq^gY|#w+VA_|o(oWmQX8^iw#n_crb(K3{69*iU?<%C-%H zuKi)3M1BhJ@3VW>JA`M>L~5*_bxH@Euy@niFrI$82C1}fwR$p2E&ZYnu?jlS}u7W9AyfdXh2pM>78bIt3 z)JBh&XE@zA!kyCDfvZ1qN^np20c1u#%P6;6tU&dx0phT1l=(mw7`u!-0e=PxEjDds z9E}{E!7f9>jaCQhw)&2TtG-qiD)lD(4jQ!q{`x|8l&nmtHkdul# zy+CIF8lKbp9_w{;oR+jSLtTfE+B@tOd6h=QePP>rh4@~!8c;Hlg9m%%&?e`*Z?qz5-zLEWfi>`ord5uHF-s{^bexKAoMEV@9nU z^5nA{f{dW&g$)BAGfkq@r5D)jr%!Ven~Q58c!Kr;*Li#`4Bu_?BU0`Y`nVQGhNZk@ z!>Yr$+nB=`z#o2nR0)V3M7-eVLuY`z@6CT#OTUXKnxZn$fNLPv7w1y7eGE=Qv@Hey`n;`U=xEl|q@CCV^#l)s0ZfT+mUf z^(j5r4)L5i2jnHW4+!6Si3q_LdOLQi<^fu?6WdohIkn79=jf%Fs3JkeXwF(?_tcF? z?z#j6iXEd(wJy4|p6v?xNk-)iIf2oX5^^Y3q3ziw16p9C6B;{COXul%)`>nuUoM*q zzmr|NJ5n)+sF$!yH5zwp=iM1#ZR`O%L83tyog-qh1I z0%dcj{NUs?{myT~33H^(%0QOM>-$hGFeP;U$puxoJ>>o-%Lk*8X^rx1>j|LtH$*)>1C!Pv&gd16%`qw5LdOIUbkNhaBBTo}5iuE%K&ZV^ zAr_)kkeNKNYJRgjsR%vexa~&8qMrQYY}+RbZ)egRg9_$vkoyV|Nc&MH@8L)`&rpqd zXnVaI@~A;Z^c3+{x=xgdhnocA&OP6^rr@rTvCnhG6^tMox$ulw2U7NgUtW%|-5VeH z_qyd47}1?IbuKtqNbNx$HR`*+9o=8`%vM8&SIKbkX9&%TS++x z5|&6P<%=F$C?owUI`%uvUq^yW0>`>yz!|WjzsoB9dT;2Dx8iSuK%%_XPgy0dTD4kd zDXF@&O_vBVVKQq(9YTClUPM30Sk7B!v7nOyV`XC!BA;BIVwphh+c)?5VJ^(C;GoQ$ zvBxr7_p*k$T%I1ke}`U&)$uf}I_T~#3XTi53OX)PoXVgxEcLJgZG^i47U&>LY(l%_ z;9vVDEtuMCyu2fqZeez|RbbIE7@)UtJvgAcVwVZNLccswxm+*L&w`&t=ttT=sv6Aq z!HouSc-24Y9;0q$>jX<1DnnGmAsP))- z^F~o99gHZw`S&Aw7e4id6Lg7kMk-e)B~=tZ!kE7sGTOJ)8@q}np@j7&7Sy{2`D^FH zI7aX%06vKsfJ168QnCM2=l|i>{I{%@gcr>ExM0Dw{PX6ozEuqFYEt z087%MKC;wVsMV}kIiuu9Zz9~H!21d!;Cu#b;hMDIP7nw3xSX~#?5#SSjyyg+Y@xh| z%(~fv3`0j#5CA2D8!M2TrG=8{%>YFr(j)I0DYlcz(2~92?G*?DeuoadkcjmZszH5& zKI@Lis%;RPJ8mNsbrxH@?J8Y2LaVjUIhRUiO-oqjy<&{2X~*f|)YxnUc6OU&5iac= z*^0qwD~L%FKiPmlzi&~a*9sk2$u<7Al=_`Ox^o2*kEv?p`#G(p(&i|ot8}T;8KLk- zPVf_4A9R`5^e`Om2LV*cK59EshYXse&IoByj}4WZaBomoHAPKqxRKbPcD`lMBI)g- zeMRY{gFaUuecSD6q!+b5(?vAnf>c`Z(8@RJy%Ulf?W~xB1dFAjw?CjSn$ph>st5bc zUac1aD_m6{l|$#g_v6;=32(mwpveQDWhmjR7{|B=$oBhz`7_g7qNp)n20|^^op3 zSfTdWV#Q>cb{CMKlWk91^;mHap{mk)o?udk$^Q^^u@&jd zfZ;)saW6{e*yoL6#0}oVPb2!}r{pAUYtn4{P~ES9tTfC5hXZnM{HrC8^=Pof{G4%Bh#8 ze~?C9m*|fd8MK;{L^!+wMy>=f^8b&y?yr6KnTq28$pFMBW9Oy7!oV5z|VM$s-cZ{I|Xf@}-)1=$V&x7e;9v81eiTi4O5-vs?^5pCKy2l>q);!MA zS!}M48l$scB~+Umz}7NbwyTn=rqt@`YtuwiQSMvCMFk2$83k50Q>OK5&fe*xCddIm)3D0I6vBU<+!3=6?(OhkO|b4fE_-j zimOzyfBB_*7*p8AmZi~X2bgVhyPy>KyGLAnOpou~sx9)S9%r)5dE%ADs4v%fFybDa_w*0?+>PsEHTbhKK^G=pFz z@IxLTCROWiKy*)cV3y%0FwrDvf53Ob_XuA1#tHbyn%Ko!1D#sdhBo`;VC*e1YlhrC z?*y3rp86m#qI|qeo8)_xH*G4q@70aXN|SP+6MQ!fJQqo1kwO_v7zqvUfU=Gwx`CR@ zRFb*O8+54%_8tS(ADh}-hUJzE`s*8wLI>1c4b@$al)l}^%GuIXjzBK!EWFO8W`>F^ ze7y#qPS0NI7*aU)g$_ziF(1ft;2<}6Hfz10cR8P}67FD=+}MfhrpOkF3hFhQu;Q1y zu%=jJHTr;0;oC94Hi@LAF5quAQ(rJG(uo%BiRQ@8U;nhX)j0i?0SL2g-A*YeAqF>RVCBOTrn{0R27vu}_S zS>tX4!#&U4W;ikTE!eFH+PKw%p+B(MR2I%n#+m0{#?qRP_tR@zpgCb=4rcrL!F=;A zh%EIF8m6%JG+qb&mEfuFTLHSxUAZEvC-+kvZKyX~SA3Umt`k}}c!5dy?-sLIM{h@> z!2=C)@nx>`;c9DdwZ&zeUc(7t<21D7qBj!|1^Mp1eZ6)PuvHx+poKSDCSBMFF{bKy z;9*&EyKitD99N}%mK8431rvbT+^%|O|HV23{;RhmS{$5tf!bIPoH9RKps`-EtoW5h zo6H_!s)Dl}2gCeGF6>aZtah9iLuGd19^z0*OryPNt{70RvJSM<#Ox9?HxGg04}b^f zrVEPceD%)#0)v5$YDE?f`73bQ6TA6wV;b^x*u2Ofe|S}+q{s5gr&m~4qGd!wOu|cZ||#h_u=k*fB;R6&k?FoM+c&J;ISg70h!J7*xGus)ta4veTdW)S^@sU@ z4$OBS=a~@F*V0ECic;ht4@?Jw<9kpjBgHfr2FDPykCCz|v2)`JxTH55?b3IM={@DU z!^|9nVO-R#s{`VHypWyH0%cs;0GO3E;It6W@0gX6wZ%W|Dzz&O%m17pa19db(er}C zUId1a4#I+Ou8E1MU$g=zo%g7K(=0Pn$)Rk z<4T2u<0rD)*j+tcy2XvY+0 z0d2pqm4)4lDewsAGThQi{2Kc3&C=|OQF!vOd#WB_`4gG3@inh-4>BoL!&#ij8bw7? zqjFRDaQz!J-YGitV4}$*$hg`vv%N)@#UdzHFI2E<&_@0Uw@h_ZHf}7)G;_NUD3@18 zH5;EtugNT0*RXVK*by>WS>jaDDfe!A61Da=VpIK?mcp^W?!1S2oah^wowRnrYjl~`lgP-mv$?yb6{{S55CCu{R z$9;`dyf0Y>uM1=XSl_$01Lc1Iy68IosWN8Q9Op=~I(F<0+_kKfgC*JggjxNgK6 z-3gQm6;sm?J&;bYe&(dx4BEjvq}b`OT^RqF$J4enP1YkeBK#>l1@-K`ajbn05`0J?0daOtnzh@l3^=BkedW1EahZlRp;`j*CaT;-21&f2wU z+Nh-gc4I36Cw+;3UAc<%ySb`#+c@5y ze~en&bYV|kn?Cn|@fqmGxgfz}U!98$=drjAkMi`43I4R%&H0GKEgx-=7PF}y`+j>r zg&JF`jomnu2G{%QV~Gf_-1gx<3Ky=Md9Q3VnK=;;u0lyTBCuf^aUi?+1+`4lLE6ZK zT#(Bf`5rmr(tgTbIt?yA@y`(Ar=f>-aZ}T~>G32EM%XyFvhn&@PWCm#-<&ApLDCXT zD#(9m|V(OOo7PmE@`vD4$S5;+9IQm19dd zvMEU`)E1_F+0o0-z>YCWqg0u8ciIknU#{q02{~YX)gc_u;8;i233D66pf(IkTDxeN zL=4z2)?S$TV9=ORVr&AkZMl<4tTh(v;Ix1{`pPVqI3n2ci&4Dg+W|N8TBUfZ*WeLF zqCH_1Q0W&f9T$lx3CFJ$o@Lz$99 zW!G&@zFHxTaP!o#z^~xgF|(vrHz8R_r9eo;TX9}2ZyjslrtH=%6O)?1?cL&BT(Amp zTGFU1%%#xl&6sH-UIJk_PGk_McFn7=%yd6tAjm|lnmr8bE2le3I~L{0(ffo}TQjyo zHZZI{-}{E4ohYTlZaS$blB!h$Jq^Rf#(ch}@S+Ww&$b);8+>g84IJcLU%B-W?+IY& zslcZIR>+U4v3O9RFEW;8NpCM0w1ROG84=WpKxQ^R`{=0MZCubg3st z48AyJNEvyxn-jCPTlTwp4EKvyEwD3e%kpdY?^BH0!3n6Eb57_L%J1=a*3>|k68A}v zaW`*4YitylfD}ua8V)vb79)N_Ixw_mpp}yJGbNu+5YYOP9K-7nf*jA1#<^rb4#AcS zKg%zCI)7cotx}L&J8Bqo8O1b0q;B1J#B5N5Z$Zq=wX~nQFgUfAE{@u0+EnmK{1hg> zC{vMfFLD;L8b4L+B51&LCm|scVLPe6h02rws@kGv@R+#IqE8>Xn8i|vRq_Z`V;x6F zNeot$1Zsu`lLS92QlLWF54za6vOEKGYQMdX($0JN*cjG7HP&qZ#3+bEN$8O_PfeAb z0R5;=zXac2IZ?fxu59?Nka;1lKm|;0)6|#RxkD05P5qz;*AL@ig!+f=lW5^Jbag%2 z%9@iM0ph$WFlxS!`p31t92z~TB}P-*CS+1Oo_g;7`6k(Jyj8m8U|Q3Sh7o-Icp4kV zK}%qri5>?%IPfamXIZ8pXbm-#{ytiam<{a5A+3dVP^xz!Pvirsq7Btv?*d7eYgx7q zWFxrzb3-%^lDgMc=Vl7^={=VDEKabTG?VWqOngE`Kt7hs236QKidsoeeUQ_^FzsXjprCDd@pW25rNx#6x&L6ZEpoX9Ffzv@olnH3rGOSW( zG-D|cV0Q~qJ>-L}NIyT?T-+x+wU%;+_GY{>t(l9dI%Ximm+Kmwhee;FK$%{dnF;C% zFjM2&$W68Sz#d*wtfX?*WIOXwT;P6NUw}IHdk|)fw*YnGa0rHx#paG!m=Y6GkS4VX zX`T$4eW9k1W!=q8!(#8A9h67fw))k_G)Q9~Q1e3f`aV@kbcSv7!priDUN}gX(iXTy zr$|kU0Vn%*ylmyDCO&G0Z3g>%JeEPFAW!5*H2Ydl>39w3W+gEUjL&vrRs(xGP{(ze zy7EMWF14@Qh>X>st8_029||TP0>7SG9on_xxeR2Iam3G~Em$}aGsNt$iES9zFa<3W zxtOF*!G@=PhfHO!=9pVPXMUVi30WmkPoy$02w}&6A7mF)G6-`~EVq5CwD2`9Zu`kd)52``#V zNSb`9dG~8(dooi1*-aSMf!fun7Sc`-C$-E(3BoSC$2kKrVcI!&yC*+ff2+C-@!AT_ zsvlAIV+%bRDfd{R*TMF><1&_a%@yZ0G0lg2K;F>7b+7A6pv3-S7qWIgx+Z?dt8}|S z>Qbb6x(+^aoV7FQ!Ph8|RUA6vXWQH*1$GJC+wXLXizNIc9p2yLzw9 z0=MdQ!{NnOwIICJc8!+Jp!zG}**r#E!<}&Te&}|B4q;U57$+pQI^}{qj669zMMe_I z&z0uUCqG%YwtUc8HVN7?0GHpu=bL7&{C>hcd5d(iFV{I5c~jpX&!(a{yS*4MEoYXh z*X4|Y@RVfn;piRm-C%b@{0R;aXrjBtvx^HO;6(>i*RnoG0Rtcd25BT6edxTNOgUAOjn zJ2)l{ipj8IP$KID2}*#F=M%^n&=bA0tY98@+2I+7~A&T-tw%W#3GV>GTmkHaqftl)#+E zMU*P(Rjo>8%P@_@#UNq(_L{}j(&-@1iY0TRizhiATJrnvwSH0v>lYfCI2ex^><3$q znzZgpW0JlQx?JB#0^^s-Js1}}wKh6f>(e%NrMwS`Q(FhazkZb|uyB@d%_9)_xb$6T zS*#-Bn)9gmobhAtvBmL+9H-+0_0US?g6^TOvE8f3v=z3o%NcPjOaf{5EMRnn(_z8- z$|m0D$FTU zDy;21v-#0i)9%_bZ7eo6B9@Q@&XprR&oKl4m>zIj-fiRy4Dqy@VVVs?rscG| zmzaDQ%>AQTi<^vYCmv#KOTd@l7#2VIpsj?nm_WfRZzJako`^uU%Nt3e;cU*y*|$7W zLm%fX#i_*HoUXu!NI$ey>BA<5HQB=|nRAwK!$L#n-Qz;~`zACig0PhAq#^5QS<8L2 zS3A+8%vbVMa7LOtTEM?55apt(DcWh#L}R^P2AY*c8B}Cx=6OFAdMPj1f>k3#^#+Hk z6uW1WJW&RlBRh*1DLb7mJ+KO>!t^t8hX1#_Wk`gjDio9)9IGbyCAGI4DJ~orK+YRv znjxRMtshZQHc$#Y-<-JOV6g^Cr@odj&Xw5B(FmI)*qJ9NHmIz_r{t)TxyB`L-%q5l ztzHgD;S6cw?7Atg*6E1!c6*gPRCb%t7D%z<(xm+K{%EJNiI2N0l8ud0Ch@_av_RW? zIr!nO4dL5466WslE6MsfMss7<)-S!e)2@r2o=7_W)OO`~CwklRWzHTfpB)_HYwgz=BzLhgZ9S<{nLBOwOIgJU=94uj6r!m>Xyn9>&xP+=5!zG_*yEoRgM0`aYts z^)&8(>z5C-QQ*o_s(8E4*?AX#S^0)aqB)OTyX>4BMy8h(cHjA8ji1PRlox@jB*1n? zDIfyDjzeg91Ao(;Q;KE@zei$}>EnrF6I}q&Xd=~&$WdDsyH0H7fJX|E+O~%LS*7^Q zYzZ4`pBdY{b7u72gZm6^5~O-57HwzwAz{)NvVaowo`X02tL3PpgLjwA`^i9F^vSpN zAqH3mRjG8VeJNHZ(1{%!XqC+)Z%D}58Qel{_weSEHoygT9pN@i zi=G;!Vj6XQk2tuJC>lza%ywz|`f7TIz*EN2Gdt!s199Dr4Tfd_%~fu8gXo~|ogt5Q zlEy_CXEe^BgsYM^o@L?s33WM14}7^T(kqohOX_iN@U?u;$l|rAvn{rwy>!yfZw13U zB@X9)qt&4;(C6dP?yRsoTMI!j-f1KC!<%~i1}u7yLXYn)(#a;Z6~r>hp~kfP));mi zcG%kdaB9H)z9M=H!f>kM->fTjRVOELNwh1amgKQT=I8J66kI)u_?0@$$~5f`u%;zl zC?pkr^p2Fe=J~WK%4ItSzKA+QHqJ@~m|Cduv=Q&-P8I5rQ-#G@bYH}YJr zUS(~(w|vKyU(T(*py}jTUp%I%{2!W!K(i$uvotcPjVddW z8_5HKY!oBCwGZcs-q`4Yt`Zk~>K?mcxg51wkZlX5e#B08I75F7#dgn5yf&Hrp`*%$ zQ;_Qg>TYRzBe$x=T(@WI9SC!ReSas9vDm(yslQjBJZde5z8GDU``r|N(MHcxNopGr z_}u39W_zwWDL*XYYt>#Xo!9kL#97|EAGyGBcRXtLTd59x%m=3i zL^9joWYA)HfL15l9%H?q`$mY27!<9$7GH(kxb%MV>`}hR4a?+*LH6aR{dzrX@?6X4 z3e`9L;cjqYb`cJmophbm(OX0b)!AFG?5`c#zLagzMW~o)?-!@e80lvk!p#&CD8u5_r&wp4O0zQ>y!k5U$h_K;rWGk=U)zX!#@Q%|9g*A zWx)qS1?fq6X<$mQTB$#3g;;5tHOYuAh;YKSBz%il3Ui6fPRv#v62SsrCdMRTav)Sg zTq1WOu&@v$Ey;@^+_!)cf|w_X<@RC>!=~+A1-65O0bOFYiH-)abINwZvFB;hJjL_$ z(9iScmUdMp2O$WW!520Hd0Q^Yj?DK%YgJD^ez$Z^?@9@Ab-=KgW@n8nC&88)TDC+E zlJM)L3r+ZJfZW_T$;Imq*#2<(j+FIk8ls7)WJ6CjUu#r5PoXxQs4b)mZza<8=v{o)VlLRM<9yw^0En#tXAj`Sylxvki{<1DPe^ zhjHwx^;c8tb?Vr$6ZB;$Ff$+3(*oinbwpN-#F)bTsXq@Sm?43MC#jQ~`F|twI=7oC zH4TJtu#;ngRA|Y~w5N=UfMZi?s0%ZmKUFTAye&6Y*y-%c1oD3yQ%IF2q2385Zl+=> zfz=o`Bedy|U;oxbyb^rB9ixG{Gb-{h$U0hVe`J;{ql!s_OJ_>>eoQn(G6h7+b^P48 zG<=Wg2;xGD-+d@UMZ!c;0>#3nws$9kIDkK13IfloGT@s14AY>&>>^#>`PT7GV$2Hp zN<{bN*ztlZu_%W=&3+=#3bE(mka6VoHEs~0BjZ$+=0`a@R$iaW)6>wp2w)=v2@|2d z%?34!+iOc5S@;AAC4hELWLH56RGxo4jw8MDMU0Wk2k_G}=Vo(>eRFo(g3@HjG|`H3 zm8b*dK=moM*oB<)*A$M9!!5o~4U``e)wxavm@O_R(`P|u%9^LGi(_%IF<6o;NLp*0 zKsfZ0#24GT8(G`i4UvoMh$^;kOhl?`0yNiyrC#HJH=tqOH^T_d<2Z+ zeN>Y9Zn!X4*DMCK^o75Zk2621bdmV7Rx@AX^alBG4%~;G_vUoxhfhFRlR&+3WwF^T zaL)8xPq|wCZoNT^>3J0K?e{J-kl+hu2rZI>CUv#-z&u@`hjeb+bBZ>bcciQVZ{SbW zez04s9oFEgc8Z+Kp{XFX`MVf-s&w9*dx7wLen(_@y34}Qz@&`$2+osqfxz4&d}{Ql z*g1ag00Gu+$C`0avds{Q65BfGsu9`_`dML*rX~hyWIe$T>CsPRoLIr%MTk3pJ^2zH1qub1MBzPG}PO;Wmav9w%F7?%l=xIf#LlP`! z_Nw;xBQY9anH5-c8A4mME}?{iewjz(Sq-29r{fV;Fc>fv%0!W@(+{={Xl-sJ6aMoc z)9Q+$bchoTGTyWU_oI19!)bD=IG&OImfy;VxNXoIO2hYEfO~MkE#IXTK(~?Z&!ae! zl8z{D&2PC$Q*OBC(rS~-*-GHNJ6AC$@eve>LB@Iq;jbBZj`wk4|LGogE||Ie=M5g= z9d`uYQ1^Sr_q2wmZE>w2WG)!F%^KiqyaDtIAct?}D~JP4shTJy5Bg+-(EA8aXaxbd~BKMtTf2iQ69jD1o* zZF9*S3!v-TdqwK$%&?91Sh2=e63;X0Lci@n7y3XOu2ofyL9^-I767eHESAq{m+@*r zbVDx!FQ|AjT;!bYsXv8ilQjy~Chiu&HNhFXt3R_6kMC8~ChEFqG@MWu#1Q1#=~#ix zrkHpJre_?#r=N0wv`-7cHHqU`phJX2M_^{H0~{VP79Dv{6YP)oA1&TSfKPEPZn2)G z9o{U1huZBLL;Tp_0OYw@+9z(jkrwIGdUrOhKJUbwy?WBt zlIK)*K0lQCY0qZ!$%1?3A#-S70F#YyUnmJF*`xx?aH5;gE5pe-15w)EB#nuf6B*c~ z8Z25NtY%6Wlb)bUA$w%HKs5$!Z*W?YKV-lE0@w^{4vw;J>=rn?u!rv$&eM+rpU6rc=j9>N2Op+C{D^mospMCjF2ZGhe4eADA#skp2EA26%p3Ex9wHW8l&Y@HX z$Qv)mHM}4*@M*#*ll5^hE9M^=q~eyWEai*P;4z<9ZYy!SlNE5nlc7gm;M&Q zKhKE4d*%A>^m0R?{N}y|i6i^k>^n4(wzKvlQeHq{l&JuFD~sTsdhs`(?lFK@Q{pU~ zb!M3c@*3IwN1RUOVjY5>uT+s-2QLWY z4T2>fiSn>>Fob+%B868-v9D@AfWr#M8eM6w#eAlhc#zk6jkLxGBGk`E3$!A@*am!R zy>29&ptYK6>cvP`b!syNp)Q$0UOW|-O@)8!?94GOYF_}+zlW%fCEl|Tep_zx05g6q z>tp47e-&R*hSNe{6{H!mL?+j$c^TXT{C&@T-xIaesNCl05 z9SLb@q&mSb)I{VXMaiWa3PWj=Ed!>*GwUe;^|uk=Pz$njNnfFY^MM>E?zqhf6^{}0 zx&~~dA5#}1ig~7HvOQ#;d9JZBeEQ+}-~v$at`m!(ai z$w(H&mWCC~;PQ1$%iuz3`>dWeb3_p}X>L2LK%2l59Tyc}4m0>9A!8rhoU3m>i2+hl zx?*qs*c^j}+WPs>&v1%1Ko8_ivAGIn@QK7A`hDz-Emkcgv2@wTbYhkiwX2l=xz*XG zaiNg+j4F-I>9v+LjosI-QECrtKjp&0T@xIMKVr+&)gyb4@b3y?2CA?=ooN zT#;rU86WLh(e@#mF*rk(NV-qSIZyr z$6!ZUmzD)%yO-ot`rw3rp6?*_l*@Z*IB0xn4|BGPWHNc-1ZUnNSMWmDh=EzWJRP`) zl%d%J613oXzh5;VY^XWJi{lB`f#u+ThvtP7 zq(HK<4>tw(=yzSBWtYO}XI`S1pMBe3!jFxBHIuwJ(@%zdQFi1Q_hU2eDuHqXte7Ki zOV55H2D6u#4oTfr7|u*3p75KF&jaLEDpxk!4*bhPc%mpfj)Us3XIG3 zIKMX^s^1wt8YK7Ky^UOG=w!o5e7W-<&c|fw2{;Q11vm@J{)@N3-p1U>!0~sKWHaL= zWV(0}1IIyt1p%=_-Fe5Kfzc71wg}`RDDntVZv;4!=&XXF-$48jS0Sc;eDy@Sg;+{A zFStc{dXT}kcIjMXb4F7MbX~2%i;UrBxm%qmLKb|2=?uPr00-$MEUIGR5+JG2l2Nq` zkM{{1RO_R)+8oQ6x&-^kCj)W8Z}TJjS*Wm4>hf+4#VJP)OBaDF%3pms7DclusBUw} z{ND#!*I6h85g6DzNvdAmnwWY{&+!KZM4DGzeHI?MR@+~|su0{y-5-nICz_MIT_#FE zm<5f3zlaKq!XyvY3H`9s&T};z!cK}G%;~!rpzk9-6L}4Rg7vXtKFsl}@sT#U#7)x- z7UWue5sa$R>N&b{J61&gvKcKlozH*;OjoDR+elkh|4bJ!_3AZNMOu?n9&|L>OTD78 z^i->ah_Mqc|Ev)KNDzfu1P3grBIM#%`QZqj5W{qu(HocQhjyS;UINoP`{J+DvV?|1 z_sw6Yr3z6%e7JKVDY<$P=M)dbk@~Yw9|2!Cw!io3%j92wTD!c^e9Vj+7VqXo3>u#= zv#M{HHJ=e$X5vQ>>ML?E8#UlmvJgTnb73{PSPTf*0)mcj6C z{KsfUbDK|F$E(k;ER%8HMdDi`=BfpZzP3cl5yJHu;v^o2FkHNk;cXc17tL8T!CsYI zfeZ6sw@;8ia|mY_AXjCS?kUfxdjDB28)~Tz1dGE|{VfBS9`0m2!m1yG?hR})er^pl4c@9Aq+|}ZlDaHL)K$O| z%9Jp-imI-Id0|(d5{v~w6mx)tUKfbuVD`xNt04Mry%M+jXzE>4(TBsx#&=@wT2Vh) z1yeEY&~17>0%P(eHP0HB^|7C+WJxQBTG$uyOWY@iDloRIb-Cf!p<{WQHR!422#F34 zG`v|#CJ^G}y9U*7jgTlD{D&y$Iv{6&PYG>{Ixg$pGk?lWrE#PJ8KunQC@}^6OP!|< zS;}p3to{S|uZz%kKe|;A0bL0XxPB&Q{J(9PyX`+Kr`k~r2}yP^ND{8!v7Q1&vtk& z2Y}l@J@{|2`oA%sxvM9i0V+8IXrZ4;tey)d;LZI70Kbim<4=WoTPZy=Yd|34v#$Kh zx|#YJ8s`J>W&jt#GcMpx84w2Z3ur-rK7gf-p5cE)=w1R2*|0mj12hvapuUWM0b~dG zMg9p8FmAZI@i{q~0@QuY44&mMUNXd7z>U58shA3o`p5eVLpq>+{(<3->DWuSFVZwC zxd50Uz(w~LxC4}bgag#q#NNokK@yNc+Q|Ap!u>Ddy+df>v;j@I12CDNN9do+0^n8p zMQs7X#+FVF0C5muGfN{r0|Nkql%BQT|K(DDNdR2pzM=_ea5+GO|J67`05AV92t@4l z0Qno0078PIHdaQGHZ~Scw!dzgqjK~3B7kf>BcP__&lLyU(cu3B^uLo%{j|Mb0NR)tkeT7Hcwp4O# z)yzu>cvG(d9~0a^)eZ;;%3ksk@F&1eEBje~ zW+-_s)&RgiweQc!otF>4%vbXKaOU41{!hw?|2`Ld3I8$&#WOsq>EG)1ANb!{N4z9@ zsU!bPG-~-bqCeIDzo^Q;gnucB{tRzm{ZH^Orphm2U+REA!*<*J6YQV83@&xoDl%#wnl5qcBqCcAF-vX5{30}(oJrnSH z{RY85hylK2dMOh2%oO1J8%)0?8TOL%rS8)+CsDv}aQ>4D)Jv+DLK)9gI^n-T^$)Tc zFPUD75qJm!Y-KBqj;JP4dV4 z`X{lGmn<)1IGz330}s}Jrjtf{(lnuuNHe5(ezA(pYa=1|Ff-LhPFK8 zyJh_b{yzu0yll6ZkpRzRjezyYivjyjW7QwO;@6X`m;2Apn2EK2!~7S}-*=;5*7K$B z`x(=!^?zgj(-`&ApZJXI09aDLXaT@<;CH=?fBOY5d|b~wBA@@p^K#nxr`)?i?SqTupI_PJ(A3cx`z~9mX_*)>L F{|7XC?P&l2 literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..bf3de2183 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 000000000..cccdd3d51 --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# 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 () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +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 + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +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" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000..e95643d6a --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +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 + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/license/3rdparty/asm/LICENSE.txt b/license/3rdparty/asm/LICENSE.txt new file mode 100644 index 000000000..b3f31f95f --- /dev/null +++ b/license/3rdparty/asm/LICENSE.txt @@ -0,0 +1,27 @@ + ASM: a very small and fast Java bytecode manipulation framework + Copyright (c) 2000-2005 INRIA, France Telecom + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holders nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + THE POSSIBILITY OF SUCH DAMAGE. diff --git a/license/README.md b/license/README.md new file mode 100644 index 000000000..c902074bd --- /dev/null +++ b/license/README.md @@ -0,0 +1,27 @@ +The Apache 2 license (given in full in LICENSE.txt) applies to all code in this repository. +The following sections of the repository contain third-party code, to which difference license may apply: + +## ASM-based Transformer + +- Path: booster-transform-asm/src/main/kotlin/com/didiglobal/booster/transform/asm/AsmTransformer.kt + - License: BSD ([3rdparty/asm/LICENSE.txt](./3rdparty/asm/LICENSE.txt)) + - Origin: Derived from ASM: a very small and fast Java bytecode manipulation framework, Copyright (c) 2000-2011 INRIA, France Telecom +- booster-transform-asm/src/main/kotlin/com/didiglobal/booster/transform/asm/ClassNode.kt + - License: BSD ([3rdparty/asm/LICENSE.txt](./3rdparty/asm/LICENSE.txt)) + - Origin: Derived from ASM: a very small and fast Java bytecode manipulation framework, Copyright (c) 2000-2011 INRIA, France Telecom +- booster-transform-asm/src/main/kotlin/com/didiglobal/booster/transform/asm/InsnList.kt + - License: BSD ([3rdparty/asm/LICENSE.txt](./3rdparty/asm/LICENSE.txt)) + - Origin: Derived from ASM: a very small and fast Java bytecode manipulation framework, Copyright (c) 2000-2011 INRIA, France Telecom +- Path: booster-transform-bugfix-toast/src/main/kotlin/com/didiglobal/booster/transform/bugfix/toast/ToastBugfixTransformer.kt + - License: BSD ([3rdparty/asm/LICENSE.txt](./3rdparty/asm/LICENSE.txt)) + - Origin: Derived from ASM: a very small and fast Java bytecode manipulation framework, Copyright (c) 2000-2011 INRIA, France Telecom +- Path: booster-transform-lint/src/main/kotlin/com/didiglobal/booster/transform/lint/LintTransformer.kt + - License: BSD ([3rdparty/asm/LICENSE.txt](./3rdparty/asm/LICENSE.txt)) + - Origin: Derived from ASM: a very small and fast Java bytecode manipulation framework, Copyright (c) 2000-2011 INRIA, France Telecom +- Path: booster-transform-shrink/src/main/kotlin/com/didiglobal/booster/transform/shrink/ShrinkTransformer.kt + - License: BSD ([3rdparty/asm/LICENSE.txt](./3rdparty/asm/LICENSE.txt)) + - Origin: Derived from ASM: a very small and fast Java bytecode manipulation framework, Copyright (c) 2000-2011 INRIA, France Telecom +- Path: booster-transform-usage/src/main/kotlin/com/didiglobal/booster/transform/usage/UsageTransformer.kt + - License: BSD ([3rdparty/asm/LICENSE.txt](./3rdparty/asm/LICENSE.txt)) + - Origin: Derived from ASM: a very small and fast Java bytecode manipulation framework, Copyright (c) 2000-2011 INRIA, France Telecom + diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 000000000..fd2383dc0 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,22 @@ +include ':booster-android-api' +include ':booster-android-bugfix' +include ':booster-android-bugfix-toast' +include ':booster-android-gradle-api' +include ':booster-android-gradle-v3_0' +include ':booster-android-gradle-v3_2' +include ':booster-gradle-base' +include ':booster-gradle-plugin' +include ':booster-task-all' +include ':booster-task-artifact' +include ':booster-task-dependency' +include ':booster-task-permission' +include ':booster-task-spi' +include ':booster-transform-all' +include ':booster-transform-asm' +include ':booster-transform-bugfix-toast' +include ':booster-transform-lint' +include ':booster-transform-spi' +include ':booster-transform-shrink' +include ':booster-transform-usage' +include ':booster-transform-util' +include ':booster-kotlinx'