diff --git a/.github/issue_template.md b/.github/issue_template.md new file mode 100644 index 00000000..ad637d72 --- /dev/null +++ b/.github/issue_template.md @@ -0,0 +1,8 @@ +**Describe the issue** +A clear and detailed description of what the issue is. + +**Build Environment** + - Operating System: [e.g. MacOS] + - Gradle Plugin Version: [e.g. 4.1.0] + - Gradle Version: [e.g. 6.5] + - Fat-aar Version: [e.g. 1.3.1] diff --git a/LICENSE b/LICENSE index 3e140558..7d43ecfe 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,21 @@ - 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 2018-2019 kezong - - 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. +MIT License + +Copyright kezong + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index c0d714c4..99f98e71 100644 --- a/README.md +++ b/README.md @@ -1,80 +1,92 @@ # fat-aar-android -[![license](http://img.shields.io/badge/license-Apache2.0-brightgreen.svg?style=flat)](https://github.com/kezong/fat-aar-android/blob/master/LICENSE) -[![Download](https://api.bintray.com/packages/kezong/maven/fat-aar/images/download.svg)](https://bintray.com/kezong/maven/fat-aar/_latestVersion) +[![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/kezong/fat-aar-android/blob/master/LICENSE) +[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.kezong/fat-aar/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.kezong/fat-aar) - [中文文档](./README_CN.md) -The solution of merging aar works with [the android gradle plugin][3], the android plugin's version of the development is `3.0.1` and higher. (Tested in gradle plugin 3.0.1 - 3.6.2, and gradle 4.6 - 6.0.1) +>**I am no longer engaged in research and development, so the project will not be updated and maintained.
** +>**You can try to use the following steps to reference the remote plugin. If it doesn't work on the new version of gradle, you can fork or download this project to modify it, the code of this project is not very complex.** +> +>**P.S. Hope Google can support this damn feature as soon as possible.** +The solution of merging aar works with [AGP][3] `3.0` and higher. (Tested in AGP 3.0 - 7.1.0, and Gradle 4.9 - 7.3) ## Getting Started -### Step 1: Apply plugin - -Add snippet below to your root build script file: - -```gradle +### Step 1: Add classpath +#### Add snippet below to your root build script file: +For Maven Central (The lastest release is available on [Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.kezong/fat-aar)): +```groovy buildscript { repositories { - jcenter() + mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:xxx' - classpath 'com.kezong:fat-aar:1.2.10' + classpath 'com.github.kezong:fat-aar:1.3.8' } } ``` -Add snippet below to the `build.gradle` of your android library: - -```gradle +### Step 2: Add plugin +Add snippet below to the `build.gradle` of your main android library: +```groovy apply plugin: 'com.kezong.fat-aar' ``` -### Step 2: Embed dependencies +### Step 3: Embed dependencies + +Declare `embed` for the dependencies you want to merge in `build.gradle`. -change `implementation` or `api` to `embed` while you want to embed the dependency in the library. Like this: +The usage is similar to `implementation`, like this: -```gradle +```groovy dependencies { implementation fileTree(dir: 'libs', include: '*.jar') // java dependency - embed project(path: ':lib-java', configuration:'default') + embed project(path: ':lib-java', configuration: 'default') // aar dependency - embed project(path: ':lib-aar', configuration:'default') + embed project(path: ':lib-aar', configuration: 'default') // aar dependency - embed project(path: ':lib-aar2', configuration:'default') - // local full aar dependency - embed project(path: ':lib-aar-local', configuration:'default') - // local full aar dependency - embed (name:'lib-aar-local2',ext:'aar') + embed project(path: ':lib-aar2', configuration: 'default') + // local full aar dependency, just build in flavor1 + flavor1Embed project(path: ':lib-aar-local', configuration: 'default') + // local full aar dependency, just build in debug + debugEmbed(name: 'lib-aar-local2', ext: 'aar') // remote jar dependency embed 'com.google.guava:guava:20.0' // remote aar dependency - embed 'com.facebook.fresco:fresco:1.11.0' + embed 'com.facebook.fresco:fresco:1.12.0' // don't want to embed in - // implementation is not recommended because the dependency may be different with the version in application, resulting in the R class not found. - compileOnly 'com.android.support:appcompat-v7:27.1.1' + implementation('androidx.appcompat:appcompat:1.2.0') } ``` ### Transitive #### Local Dependency -If you want to including local transitive dependencies in final artifact, you must add `embed` for transitive dependencies in your main library. +If you want to include local transitive dependencies in final artifact, you must add `embed` for transitive dependencies in your main library. -For example, mainLib depend on subLib1, subLib1 depend on subLib2, If you want including all dependencies in final artifact, you must add `embed` for subLib1 and subLib2 in mainLib `build.gradle` +For example, mainLib depend on subLib1, subLib1 depend on subLib2, If you want include all dependencies in the final artifact, you must add `embed` for subLib1 and subLib2 in mainLib `build.gradle` #### Remote Dependency -If you want to including all remote transitive dependencies which in pom file, you need change the `embed`'s transitive value to true in your `build.gradle`, like this: -```gradle -// the default value is false -// invalid for local aar dependency -configurations.embed.transitive = true +If you want to inlcude all of the remote transitive dependencies which are in POM file, you need change the `transitive` value to true in your `build.gradle`, like this: +```groovy +fataar { + /** + * If transitive is true, local jar module and remote library's dependencies will be embed. (local aar module does not support) + * If transitive is false, just embed first level dependency + * Default value is false + * @since 1.3.0 + */ + transitive = true +} ``` If you change the transitive value to true,and want to ignore a dependency in its POM file, you can add exclude keywords, like this: -```gradle +```groovy embed('com.facebook.fresco:fresco:1.11.0') { + // exclude all dependencies + transitive = false + // exclude any group or module exclude(group:'com.facebook.soloader', module:'soloader') } ``` @@ -89,33 +101,94 @@ See [anatomy of an aar file here][2]. **support list for now:** -- [x] productFlavors -- [x] manifest merge -- [x] classes jar and external jars merge -- [x] res merge -- [x] assets merge -- [x] jni libs merge -- [x] proguard.txt merge +- [x] Flavors +- [x] AndroidManifest merge +- [x] Classes merge +- [x] Jar merge +- [x] Res merge +- [x] Assets merge +- [x] Jni libs merge - [x] R.txt merge - [x] R.class merge +- [x] DataBinding merge +- [x] Proguard merge +- [x] Kotlin module merge + ## Gradle Version Support | Version | Gradle Plugin | Gradle | | :--------: | :--------:|:-------:| -| 1.0.1 | 3.1.0 - 3.2.1 | 4.4-6.0 | -| 1.1.6 | 3.1.0 - 3.4.1 | 4.4-6.0 | -| 1.1.10| 3.0.1 - 3.4.1 | 4.1-6.0 | -| 1.2.6 | 3.0.1 - 3.5.0 | 4.1-6.0 | -| 1.2.8 | 3.0.1+ | 4.1+ | -| 1.2.10| 3.6.0+ | 5.4.1+ | +| 1.0.1 | 3.1.0 - 3.2.1 | 4.4 - 6.0 | +| 1.1.6 | 3.1.0 - 3.4.1 | 4.4 - 6.0 | +| 1.1.10| 3.0.0 - 3.4.1 | 4.1 - 6.0 | +| 1.2.6 | 3.0.0 - 3.5.0 | 4.1 - 6.0 | +| 1.2.8 | 3.0.0 - 3.5.9 | 4.1 - 6.8 | +| 1.2.11 - 1.2.14 | 3.0.0 - 3.6.9 | 4.1 - 6.8 | +| 1.2.15 - 1.2.16 | 3.0.0 - 4.0.2 | 4.1 - 6.8 | +| 1.2.17 | 3.0.0 - 4.0.2 | 4.9 - 6.8 | +| 1.2.18+ | 3.0.0 - 4.1.0 | 4.9 - 6.8 | +| 1.3.+ | 3.0.0 - 4.1.0 | 4.9 - 6.8 | +| 1.3.4 - 1.3.5 | 3.0.0 - 4.1.0 | 4.9+ | +| 1.3.6 | 3.0.0 - 4.2.0 | 4.9+ | +| 1.3.8 | 3.0.0+ | 4.9+ | The following link which version of Gradle is required for each version of the Android Gradle plugin. For the best performance, you should use the latest possible version of both Gradle and the plugin. [Plugin version and Required Gradle version](https://developer.android.google.cn/studio/releases/gradle-plugin.html) ## Version Log -- [1.2.10]() +- [1.3.8]() + - Fix the issue that plugin cannot be used in jdk 1.8 [#371](https://github.com/kezong/fat-aar-android/issues/371) +- [1.3.7]() + - Fix productFlavor detection in embed submodules [#348](https://github.com/kezong/fat-aar-android/issues/348) + - Support missingDimensionStrategy without productFlavors in current project. [#343](https://github.com/kezong/fat-aar-android/issues/343) +- [1.3.6]() + - Support AGP 4.2.0 [#290](https://github.com/kezong/fat-aar-android/issues/290) [#304](https://github.com/kezong/fat-aar-android/issues/304) + - Copy 'navigation' along with other R.$ classes. [#296](https://github.com/kezong/fat-aar-android/issues/296) +- [1.3.5]() + - Fix the bug that jar cannot be merged in some case. [#255](https://github.com/kezong/fat-aar-android/issues/255) [#288](https://github.com/kezong/fat-aar-android/issues/288) + - Fix build error when use gradle 6.0-6.8. [#277](https://github.com/kezong/fat-aar-android/issues/277) +- [1.3.4]() + - Support Gradle 6.8 [#274](https://github.com/kezong/fat-aar-android/issues/274) +- [1.3.3]() + - Fix bug that "Can not find task bundleDebugAar". [#84](https://github.com/kezong/fat-aar-android/issues/84) + - Fix bug that crash when module can not resolve. + - Throw a runtime exception when manifest merge fail. +- [1.3.1]() + - Implement bytecode patching to process R class + - Support merge consumerProguardFiles + - Support merge *.kotlin_module, support kotlin top-level + - Support flavor missingDimensionStrategy + - Fix build error when flavor artifact renamed + - Fix Jar merge error when use AGP 3.0 - 3.1 + - Fix AGP version not found in some cases +- [1.2.20]() + - Fix error that getName() in a null object. [#214](https://github.com/kezong/fat-aar-android/issues/214) + - Rename r-classes.jar with applicationId. +- [1.2.19]() + - Support embed aar that has no classes.jar [#157](https://github.com/kezong/fat-aar-android/issues/158) + - Support embed aar that has no AndroidManifest.xml [#206](https://github.com/kezong/fat-aar-android/issues/206) + - Fix bug that R.class not embed when publish to maven [#200](https://github.com/kezong/fat-aar-android/issues/200) +- [1.2.18]() + - Adapt gradle plugin 4.1.0 [#201](https://github.com/kezong/fat-aar-android/issues/201) +- [1.2.17]() + - Support databing merge [#25](https://github.com/kezong/fat-aar-android/issues/25) [#67](https://github.com/kezong/fat-aar-android/issues/67) [#142](https://github.com/kezong/fat-aar-android/issues/142) + - Use Gradle's configuration avoidance APIs [#195](https://github.com/kezong/fat-aar-android/issues/195) + - Support incremental build [#199](https://github.com/kezong/fat-aar-android/issues/199) [#185](https://github.com/kezong/fat-aar-android/issues/185) + - Fix wrong directory for aar's jar libs [#154](https://github.com/kezong/fat-aar-android/issues/154) +- [1.2.16]() + - Search for android build plugin version in full classpath [#172](https://github.com/kezong/fat-aar-android/issues/172) + - Fixed a bug where resources might not be found when build in gradle version 4.0 [#163](https://github.com/kezong/fat-aar-android/issues/163) +- [1.2.15]() + - adapt gradle plugin 4.0.0 [#147](https://github.com/kezong/fat-aar-android/issues/147) + - support that the module can be indexed in AS 4.0.0 [#148](https://github.com/kezong/fat-aar-android/issues/148) + - fix lint error [#152](https://github.com/kezong/fat-aar-android/issues/152) +- [1.2.12]() + - Added support for specific build type and product flavor dependencies, like debugEmbed or flavorEmbed. [#135](https://github.com/kezong/fat-aar-android/issues/135) [#137](https://github.com/kezong/fat-aar-android/issues/137) + - Fix some build warning +- [1.2.11]() - Fix build variants error in gradle plugin 3.6.+ [#126](https://github.com/kezong/fat-aar-android/issues/126) + - Fix bug that remote recources symbol can not found in R.class when build with gradle plugin 3.6.0+ - [1.2.9]() - adapt gradle plugin 3.6.1 [#120](https://github.com/kezong/fat-aar-android/issues/120) - [1.2.8]() @@ -157,13 +230,17 @@ The following link which version of Gradle is required for each version of the A - Support R class file merge ## Known Defects or Issues +- **Application cannot directly rely on embedded project:** application cannot directly rely on your embedded project. It must rely on the AAR file compiled by your embedded project + - For debugging convenience, you can use `embed` in the main library project when you choose to package aar. When you need to run the app directly, you can use `implementation` or `api` -- **Proguard note.** Produce lots of(maybe) `Note: duplicate definition of library class`, while proguard is on. A workaround is to add `-dontnote` in `proguard-rules.pro`. -- **The overlay order of res merge is changed:** Embedded dependency has higher priority than other dependencies. - **Res merge conflicts.** If the library res folder and embedded dependencies res have the same res Id(mostly `string/app_name`). A duplicate resources build exception will be thrown. To avoid res conflicts: - consider using a prefix to each res Id, both in library res and aar dependencies if possible. - - Adding "android.disableResourceValidation=true" to "gradle.properties" can do a trick to skip the exception, but is not recommended. + - Adding `android.disableResourceValidation=true` to `gradle.properties` can do a trick to skip the exception. +- **Proguard** + - If `minifyEnabled` is set to true, classes not referenced in the project will be filtered according to Proguard rules during compile, resulting in ClassNotFound during app compile. + Most AAR is SDK that provide interfaces. It is recommended that you carefully comb Proguard files and add keep rules. + ## Thanks - [android-fat-aar][1] diff --git a/README_CN.md b/README_CN.md index 0d498691..16614ea2 100644 --- a/README_CN.md +++ b/README_CN.md @@ -1,60 +1,77 @@ # fat-aar-android -[![license](http://img.shields.io/badge/license-Apache2.0-brightgreen.svg?style=flat)](https://github.com/kezong/fat-aar-android/blob/master/LICENSE) -[![Download](https://api.bintray.com/packages/kezong/maven/fat-aar/images/download.svg)](https://bintray.com/kezong/maven/fat-aar/_latestVersion) +[![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/kezong/fat-aar-android/blob/master/LICENSE) +[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.kezong/fat-aar/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.kezong/fat-aar) -该插件提供了将library以及它依赖的module一起打包成一个完整aar的解决方案,支持gradle plugin 3.0.1及以上。(目前测试的版本范围是gradle plugin 3.0.1 - 3.6.2,gradle 4.6 - 6.0.1) +>**因为我不再从事研发工作,所以该仓库将不再进行维护和更新。
** +>**你可以尝试按底下的步骤引入该插件,如果它在新的gradle版本中无法工作,你可以fork或者下载该仓库并且修改它,该项目的代码并不是很复杂。** +> +>**P.S. 希望Google官方能够尽快支持该功能** -## 如何使用 - -#### 第一步: Apply plugin +该插件提供了将library以及它依赖的library一起打包成一个完整aar的解决方案,支持AGP 3.0及以上。(目前测试的版本范围是AGP 3.0 - 7.1.0,Gradle 4.9 - 7.3) -添加以下代码到你工程根目录下的`build.gradle`文件中: +## 如何使用 -```gradle +#### 第一步: Apply classpath +##### 添加以下代码到你工程根目录下的`build.gradle`文件中: +For Maven Central (The lastest release is available on [Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.kezong/fat-aar)): +```groovy buildscript { repositories { - jcenter() + mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:xxx' - classpath 'com.kezong:fat-aar:1.2.10' + classpath 'com.github.kezong:fat-aar:1.3.8' } } ``` +#### 第二步: Add plugin 添加以下代码到你的主library的`build.gradle`中: - -```gradle +```groovy apply plugin: 'com.kezong.fat-aar' ``` -#### 第二步: Embed dependencies -- 将`implementation`或者`api`改成`embed` +#### 第三步: Embed dependencies +- `embed`你所需要的工程, 用法类似`implementation` 代码所示: -```gradle +```groovy dependencies { implementation fileTree(dir: 'libs', include: '*.jar') // java dependency - embed project(path: ':lib-java', configuration:'default') + embed project(path: ':lib-java', configuration: 'default') // aar dependency - embed project(path: ':lib-aar', configuration:'default') + embed project(path: ':lib-aar', configuration: 'default') // aar dependency - embed project(path: ':lib-aar2', configuration:'default') - // local full aar dependency - embed project(path: ':lib-aar-local', configuration:'default') - // local full aar dependency - embed (name:'lib-aar-local2',ext:'aar') + embed project(path: ':lib-aar2', configuration: 'default') + // local full aar dependency, just build in flavor1 + flavor1Embed project(path: ':lib-aar-local', configuration: 'default') + // local full aar dependency, just build in debug + debugEmbed(name: 'lib-aar-local2', ext: 'aar') // remote jar dependency embed 'com.google.guava:guava:20.0' // remote aar dependency - embed 'com.facebook.fresco:fresco:1.11.0' + embed 'com.facebook.fresco:fresco:1.12.0' // don't want to embed in - // 不建议使用implementation,因为该依赖可能与application的依赖版本不一致,使用implementation可能会导致R类找不到的问题 - compileOnly 'com.android.support:appcompat-v7:27.1.1' + implementation('androidx.appcompat:appcompat:1.2.0') } ``` +### 第四步: 执行assemble命令 + +- 在你的工程目录下执行assemble指令,其中lib-main为你主library的工程名称,你可以根据不同的flavor以及不同的buildType来决定执行具体的assemble指令 +```shell script +# assemble all +./gradlew :lib-main:assemble + +# assemble debug +./gradlew :lib-main:assembleDebug + +# assemble flavor +./gradlew :lib-main:assembleFlavor1Debug +``` +最终合并产物会覆盖原有aar,同时路径会打印在log信息中. + ### 多级依赖 #### 本地依赖 @@ -65,17 +82,27 @@ dependencies { #### 远程依赖 -如果你想将所有远程依赖在pom中声明的依赖项同时打入在最终产物里的话,你需要在`build.gradle`中将`embed`的transitive值改为true,例如: -```gradle -// the default value is false -// invalid for local aar dependency -configurations.embed.transitive = true +如果你想将所有远程依赖在pom中声明的依赖项同时打入在最终产物里的话,你需要在`build.gradle`中将transitive值改为true,例如: +```groovy +fataar { + /** + * If transitive is true, local jar module and remote library's dependencies will be embed. + * If transitive is false, just embed first level dependency + * Local aar project does not support transitive, always embed first level + * Default value is false + * @since 1.3.0 + */ + transitive = true +} ``` 如果你将transitive的值改成了true,并且想忽略pom文件中的某一个依赖项,你可以添加`exclude`关键字,例如: -```gradle +```groovy embed('com.facebook.fresco:fresco:1.11.0') { + // exclude any group or module exclude(group:'com.facebook.soloader', module:'soloader') + // exclude all dependencies + transitive = false } ``` @@ -88,32 +115,89 @@ AAR是Android提供的一种官方文件形式; **支持功能列表:** -- [x] 支持library以及module中含有flavor +- [x] 支持flavor配置 - [x] AndroidManifest合并 -- [x] classes以及jar合并 -- [x] res合并 -- [x] assets合并 -- [x] jni合并 +- [x] Classes合并 +- [x] Jar合并 +- [x] Res合并 +- [x] Assets合并 +- [x] Jni合并 - [x] R.txt合并 - [x] R.class合并 -- [ ] proguard合并(混淆合并现在看来有些问题,建议将所有混淆文件都写在主Library中) +- [x] DataBinding合并 +- [x] Proguard合并 +- [x] Kotlin module合并 ## Gradle版本支持 | Version | Gradle Plugin | Gradle | | :--------: | :--------:|:-------:| -| 1.0.1 | 3.1.0 - 3.2.1 | 4.4-6.0 | -| 1.1.6 | 3.1.0 - 3.4.1 | 4.4-6.0 | -| 1.1.10| 3.0.1 - 3.4.1 | 4.1-6.0 | -| 1.2.6 | 3.0.1 - 3.5.0 | 4.1-6.0 | -| 1.2.8 | 3.0.1+ | 4.1+ | -| 1.2.10| 3.6.0+ | 5.4.1+ | +| 1.0.1 | 3.1.0 - 3.2.1 | 4.4 - 6.0 | +| 1.1.6 | 3.1.0 - 3.4.1 | 4.4 - 6.0 | +| 1.1.10| 3.0.0 - 3.4.1 | 4.1 - 6.0 | +| 1.2.6 | 3.0.0 - 3.5.0 | 4.1 - 6.0 | +| 1.2.8 | 3.0.0 - 3.5.9 | 4.1 - 6.8 | +| 1.2.11 - 1.2.14 | 3.0.0 - 3.6.9 | 4.1 - 6.8 | +| 1.2.15 - 1.2.16 | 3.0.0 - 4.0.2 | 4.1 - 6.8 | +| 1.2.17 | 3.0.0 - 4.0.2 | 4.9 - 6.8 | +| 1.2.18+ | 3.0.0 - 4.1.0 | 4.9 - 6.8 | +| 1.3.+ | 3.0.0 - 4.1.0 | 4.9 - 6.8 | +| 1.3.4 | 3.0.0 - 4.1.0 | 4.9+ | +| 1.3.6 | 3.0.0 - 4.2.0 | 4.9+ | +| 1.3.8 | 3.0.0+ | 4.9+ | [Gradle Plugin和所需求的Gradle版本官方文档](https://developer.android.google.cn/studio/releases/gradle-plugin.html) ## 更新日志 -- [1.2.10]() +- [1.3.8]() + - Fix the issue that plugin cannot be used in jdk 1.8 [#371](https://github.com/kezong/fat-aar-android/issues/371) +- [1.3.7]() + - Fix productFlavor detection in embed submodules [#348](https://github.com/kezong/fat-aar-android/issues/348) + - Support missingDimensionStrategy without productFlavors in current project. [#343](https://github.com/kezong/fat-aar-android/issues/343) +- [1.3.5]() + - 修复在仅有jar工程时jar无法合并的问题. [#255](https://github.com/kezong/fat-aar-android/issues/255) [#288](https://github.com/kezong/fat-aar-android/issues/288) + - 修复在使用Gradle 6.0-6.8时的编译错误. [#277](https://github.com/kezong/fat-aar-android/issues/277) +- [1.3.4]() + - 支持Gradle 6.8 [#274](https://github.com/kezong/fat-aar-android/issues/274) +- [1.3.3]() + - 修复异常"Can not find task bundleDebugAar". [#84](https://github.com/kezong/fat-aar-android/issues/84) + - 修复当工程解析失败时产生的异常. + - 当AndroidManifest合并时抛出异常. +- [1.3.1]() + - R.class合并采用Transform,解决大部分R class找不到的问题. + - 支持consumerProguardFiles合并 + - 支持kotlin_module合并,支持top-level机制 + - 支持flavor中missingDimensionStrategy + - 修复依赖的flavor产物更名后无法找到的问题 + - 修复AGP 3.0 - 3.1 Jar包无法合并的问题 + - 修复某些情况下AGP版本获取不到的问题 +- [1.2.20]() + - 修复获取产物名时的空指针异常. [#214](https://github.com/kezong/fat-aar-android/issues/214) + - r-classes.jar重命名,加上包名作为前缀. +- [1.2.19]() + - 支持embed没有class.jar的aar [#157](https://github.com/kezong/fat-aar-android/issues/158) + - 支持embed没有AndroidManifest.xml的aar [#206](https://github.com/kezong/fat-aar-android/issues/206) + - 修复上传至maven时,R.class未包含进aar的BUG [#200](https://github.com/kezong/fat-aar-android/issues/200) +- [1.2.18]() + - 适配gradle plugin 4.1.0 [#201](https://github.com/kezong/fat-aar-android/issues/201) +- [1.2.17]() + - 支持databinding合并 [#25](https://github.com/kezong/fat-aar-android/issues/25) [#67](https://github.com/kezong/fat-aar-android/issues/67) [#142](https://github.com/kezong/fat-aar-android/issues/142) + - Use Gradle's configuration avoidance APIs [#195](https://github.com/kezong/fat-aar-android/issues/195) + - Support incremental build [#199](https://github.com/kezong/fat-aar-android/issues/199) [#185](https://github.com/kezong/fat-aar-android/issues/185) + - Fix wrong directory for aar's jar libs [#154](https://github.com/kezong/fat-aar-android/issues/154) +- [1.2.16]() + - 修复gradle plugin版本不在根目录下就找不到的问题 [#172](https://github.com/kezong/fat-aar-android/issues/172) + - 修复在gradle plugin 4.0构建的产物中有可能styleable资源找不到的问题 [#163](https://github.com/kezong/fat-aar-android/issues/163) +- [1.2.15]() + - 支持gradle plugin 4.0.0 [#147](https://github.com/kezong/fat-aar-android/issues/147) + - 修复在Android Studio 4.0.0上embed的库无法直接索引源码的问题 [#148](https://github.com/kezong/fat-aar-android/issues/148) + - 修复lint编译错误 [#152](https://github.com/kezong/fat-aar-android/issues/152) +- [1.2.12]() + - 添加对buildType以及flavor的支持,例如debugEmbed以及flavorEmbed. [#135](https://github.com/kezong/fat-aar-android/issues/135) [#137](https://github.com/kezong/fat-aar-android/issues/137) + - 修复一些编译时的warning. +- [1.2.11]() - 修复在gradle plugin 3.6.0下编译variants会error的情况 [#126](https://github.com/kezong/fat-aar-android/issues/126) + - 修复在gradle plugin 3.6.0下编译出来的aar,在编译apk时会出现资源符号对象找不到的问题 - [1.2.9]() - 适配gradle plugin 3.6.1 [#120](https://github.com/kezong/fat-aar-android/issues/120) - [1.2.8]() @@ -157,11 +241,16 @@ AAR是Android提供的一种官方文件形式; ## 常见问题 -* **混淆日志:** 当开启proguard时,可能会产生大量的`Note: duplicate definition of library class`日志,如果你想忽略这些日志,你可以在`proguard-rules.pro`中加上`-dontnote`关键字; -* **资源冲突:** 如果library和module中含有同名的资源(比如 `string/app_name`),编译将会报`duplication resources`的相关错误,有两种方法可以解决这个问题: - * 考虑将library以及module中的资源都加一个前缀来避免资源冲突; - * 在`gradle.properties`中添加`android.disableResourceValidation=true`可以忽略资源冲突的编译错误,程序会采用第一个找到的同名资源作为实际资源,不建议这样做,如果资源同名但实际资源不一样会造成不可预期的问题。 - +- **Application无法直接依赖embed工程:** application无法直接依赖你的embed工程,必须依赖你embed工程所编译生成的aar文件 + - 为了调试方便,你可以在选择在打包aar时,在主library工程中使用`embed`,需要直接运行app时,采用`implementation`或者`api` + +- **资源冲突:** 如果library和module中含有同名的资源(比如 `string/app_name`),编译将会报`duplication resources`的相关错误,有两种方法可以解决这个问题: + - 考虑将library以及module中的资源都加一个前缀来避免资源冲突; + - 在`gradle.properties`中添加`android.disableResourceValidation=true`可以忽略资源冲突的编译错误,程序会采用第一个找到的同名资源作为实际资源. + +- **关于混淆** + - 如果`minifyEnabled`设置为true,编译时会根据proguard规则过滤工程中没有引用到的类,导致App集成时找不到对象,因为大多数AAR都是提供接口的SDK,建议大家仔细梳理proguard文件。 + ## 致谢 * [android-fat-aar][1] * [fat-aar-plugin][4] diff --git a/example/app/.gitignore b/example/app/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/example/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/example/app/build.gradle b/example/app/build.gradle new file mode 100644 index 00000000..ecbecdf1 --- /dev/null +++ b/example/app/build.gradle @@ -0,0 +1,53 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + compileSdkVersion 29 + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + defaultConfig { + applicationId "com.fataar.demo" + minSdkVersion 19 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + + externalNativeBuild { + ndk { + abiFilters "armeabi-v7a" + } + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + buildFeatures { + dataBinding = true + } + + kotlinOptions { + jvmTarget = "1.8" + } +} + +repositories { + flatDir { + dirs 'libs/' + } +} + +dependencies { + implementation fileTree(dir: "libs", include: ["*.jar"]) + implementation('androidx.appcompat:appcompat:1.2.0') + implementation(name: 'fat-aar-final', ext: 'aar') +} \ No newline at end of file diff --git a/example/app/libs/fat-aar-final.aar b/example/app/libs/fat-aar-final.aar new file mode 100644 index 00000000..b23a3154 Binary files /dev/null and b/example/app/libs/fat-aar-final.aar differ diff --git a/example/app/proguard-rules.pro b/example/app/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/example/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/example/app/src/main/AndroidManifest.xml b/example/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..05598c81 --- /dev/null +++ b/example/app/src/main/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/example/app/src/main/java/com/fataar/demo/MainActivity.java b/example/app/src/main/java/com/fataar/demo/MainActivity.java new file mode 100644 index 00000000..a1591236 --- /dev/null +++ b/example/app/src/main/java/com/fataar/demo/MainActivity.java @@ -0,0 +1,218 @@ +package com.fataar.demo; + +import android.content.Intent; +import android.content.res.AssetManager; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.fragment.app.FragmentActivity; + +import com.facebook.common.executors.CallerThreadExecutor; +import com.facebook.common.references.CloseableReference; +import com.facebook.datasource.DataSource; +import com.facebook.drawee.backends.pipeline.Fresco; +import com.facebook.imagepipeline.core.ImagePipeline; +import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber; +import com.facebook.imagepipeline.image.CloseableImage; +import com.facebook.imagepipeline.request.ImageRequest; +import com.facebook.imagepipeline.request.ImageRequestBuilder; +import com.kezong.demo.javalib.JavaLib1; +import com.kezong.demo.lib.MainLibClass; +import com.kezong.demo.lib.KotlinInMain; +import com.kezong.demo.libaar.AarFlavor; +import com.kezong.demo.libaar.AarLibClass; +import com.kezong.demo.libaar.KotlinTest2; +import com.kezong.demo.libaar.TestActivity; +import com.kezong.demo.libaar2.Aar2LibClass; +import com.kezong.demo.libaarlocal.AarLocalLibClass; +import com.kezong.demo.libaarlocal2.AarLocal2LibClass; + +import java.io.IOException; +import java.io.InputStream; + +/** + * @author kezong on 2020/12/11. + */ +public class MainActivity extends FragmentActivity { + + private ViewGroup mRoot; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main_layout); + mRoot = findViewById(R.id.root_layout); + testActivity(); + testMainLibClassMerge(); + testClassMerge(); + testClassMerge2(); + testJarMerge(); + testResourceMerge(); + testResourceMerge2(); + testKotlinTopLevel(); + testKotlinTopLevel2(); + testLocalAar1(); + testLocalAar2(); + testSoMerge(); + testAssetsMerge(); + testRemoteAar(); + testFlavor(); + } + + private void testFlavor() { + addTestView("flavor", AarFlavor.TAG, true); + } + + private void testRemoteAar() { + try { + Fresco.initialize(this); + ImageRequest imageRequest = ImageRequestBuilder + .newBuilderWithSource(Uri.parse("https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3096503764,1460949822&fm=26&gp=0.jpg")) + .setProgressiveRenderingEnabled(true) + .build(); + ImagePipeline imagePipeline = Fresco.getImagePipeline(); + DataSource> + dataSource = imagePipeline.fetchDecodedImage(imageRequest, getApplicationContext()); + dataSource.subscribe(new BaseBitmapDataSubscriber() { + + @Override + public void onNewResultImpl(Bitmap bitmap) { + mRoot.post(() -> { + ImageView imageView = new ImageView(MainActivity.this); + imageView.setImageBitmap(bitmap); + LinearLayout.LayoutParams ll = new LinearLayout.LayoutParams(100, 100); + mRoot.addView(imageView, ll); + addTestView("remote aar merge", "yes", true); + }); + } + + @Override + public void onFailureImpl(DataSource dataSource) { + } + }, CallerThreadExecutor.getInstance()); + } catch (Throwable e) { + addTestView("remote aar merge", e.getMessage(), false); + } + } + + private void testSoMerge() { + try { + System.loadLibrary("gnustl_shared"); + } catch (Throwable e) { + addTestView("so", e.getMessage(), false); + return; + } + + addTestView("so", "✔️", true); + } + + private void testAssetsMerge() { + Bitmap bitmap; + AssetManager assetManager = this.getAssets(); + try { + InputStream inputStream = assetManager.open("cat.jpg"); + bitmap = BitmapFactory.decodeStream(inputStream); + } catch (IOException e) { + addTestView("assets", e.getMessage(), false); + return; + } + + ImageView imageView = new ImageView(this); + imageView.setImageBitmap(bitmap); + LinearLayout.LayoutParams ll = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + mRoot.addView(imageView, ll); + addTestView("assets", "look cat!", true); + } + + private void testJarMerge() { + String text = String.valueOf(JavaLib1.class.getSimpleName()); + addTestView("external jars", text, true); + } + + + private void testActivity() { + Button button = new Button(this); + button.setText("Activity Test"); + button.setOnClickListener(v -> { + Intent intent = new Intent(MainActivity.this, TestActivity.class); + startActivity(intent); + }); + LinearLayout.LayoutParams ll = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + mRoot.addView(button, ll); + } + + public void testKotlinTopLevel() { + String text = String.valueOf(KotlinInMain.test()); + addTestView("kotlin", text, TextUtils.equals(text, "120")); + } + + public void testKotlinTopLevel2() { + String text = String.valueOf(KotlinTest2.test2()); + addTestView("kotlin2", text, TextUtils.equals(text, "130")); + } + + public void testResourceMerge() { + String text = new AarLibClass().getLibName(this); + addTestView("resource", text, TextUtils.equals(text, "lib-aar")); + } + + public void testResourceMerge2() { + String text = this.getResources().getString(R.string.app_name_aar2); + addTestView("resource2", text, TextUtils.equals(text, "lib-aar2")); + } + + public void testClassMerge2() { + String text = Aar2LibClass.TAG; + addTestView("lib class2", text, TextUtils.equals(text, Aar2LibClass.class.getSimpleName())); + } + + public void testMainLibClassMerge() { + String text = String.valueOf(MainLibClass.test()); + addTestView("main class", text, TextUtils.equals(text, "200")); + } + + public void testClassMerge() { + String text = AarLibClass.TAG; + addTestView("lib class", text, TextUtils.equals(text, AarLibClass.class.getSimpleName())); + } + + public void addTestView(String title, String text, boolean success) { + TextView textView = new TextView(this); + String s; + if (success) { + s = "[Success] " + "[" + title + "] " + text; + } else { + s = "[Fail]" + "[" + title + "] " + text; + } + textView.setText(s); + mRoot.addView(textView); + } + + private void testLocalAar1() { + try { + String text = AarLocalLibClass.TAG; + addTestView("local aar1", text, TextUtils.equals(text, AarLocalLibClass.class.getSimpleName())); + } catch (Exception e) { + addTestView("local aar1", "flavor 2???", false); + } + } + + private void testLocalAar2() { + try { + String text = AarLocal2LibClass.TAG; + addTestView("local aar2", text, TextUtils.equals(text, AarLocal2LibClass.class.getSimpleName())); + } catch (Exception e) { + addTestView("local aar2", "release???", false); + } + } +} diff --git a/example/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/example/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..2b068d11 --- /dev/null +++ b/example/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/example/app/src/main/res/drawable/ic_launcher_background.xml b/example/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..07d5da9c --- /dev/null +++ b/example/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/app/src/main/res/layout/main_layout.xml b/example/app/src/main/res/layout/main_layout.xml new file mode 100644 index 00000000..b64d2f02 --- /dev/null +++ b/example/app/src/main/res/layout/main_layout.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/example/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/example/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..eca70cfe --- /dev/null +++ b/example/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/example/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/example/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..eca70cfe --- /dev/null +++ b/example/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/example/app/src/main/res/mipmap-hdpi/ic_launcher.png b/example/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..a571e600 Binary files /dev/null and b/example/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/example/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/example/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 00000000..61da551c Binary files /dev/null and b/example/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/example/app/src/main/res/mipmap-mdpi/ic_launcher.png b/example/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..c41dd285 Binary files /dev/null and b/example/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/example/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/example/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 00000000..db5080a7 Binary files /dev/null and b/example/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/example/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/example/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..6dba46da Binary files /dev/null and b/example/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/example/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/example/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 00000000..da31a871 Binary files /dev/null and b/example/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/example/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/example/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..15ac6817 Binary files /dev/null and b/example/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/example/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/example/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..b216f2d3 Binary files /dev/null and b/example/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/example/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/example/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..f25a4197 Binary files /dev/null and b/example/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/example/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/example/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..e96783cc Binary files /dev/null and b/example/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/example/app/src/main/res/values/colors.xml b/example/app/src/main/res/values/colors.xml new file mode 100644 index 00000000..4faecfa8 --- /dev/null +++ b/example/app/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #6200EE + #3700B3 + #03DAC5 + \ No newline at end of file diff --git a/example/app/src/main/res/values/strings.xml b/example/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..efbb3ee4 --- /dev/null +++ b/example/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Fat-aar-test + \ No newline at end of file diff --git a/example/app/src/main/res/values/styles.xml b/example/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..bcfb562c --- /dev/null +++ b/example/app/src/main/res/values/styles.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/example/build.gradle b/example/build.gradle index 3af53d3d..751c24a4 100644 --- a/example/build.gradle +++ b/example/build.gradle @@ -1,20 +1,20 @@ buildscript { repositories { -// maven { -// url "http://dl.bintray.com/kezong/maven" -// } - jcenter() + mavenCentral() google() + jcenter() maven { url "https://plugins.gradle.org/m2/" } } dependencies { - classpath 'com.android.tools.build:gradle:3.6.1' - classpath 'com.kezong:fat-aar:1.2.9' + classpath 'com.android.tools.build:gradle:7.0.2' + classpath 'com.github.kezong:fat-aar:1.3.8' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72" } } allprojects { repositories { + mavenCentral() google() jcenter() } diff --git a/example/gradle.properties b/example/gradle.properties new file mode 100644 index 00000000..d015431a --- /dev/null +++ b/example/gradle.properties @@ -0,0 +1,2 @@ +android.useAndroidX=true +android.enableJetifier=true \ No newline at end of file diff --git a/example/gradle/wrapper/gradle-wrapper.properties b/example/gradle/wrapper/gradle-wrapper.properties index eb891f28..210b22c8 100644 --- a/example/gradle/wrapper/gradle-wrapper.properties +++ b/example/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip diff --git a/example/lib-aar/build.gradle b/example/lib-aar/build.gradle index 355c82c9..ef462983 100644 --- a/example/lib-aar/build.gradle +++ b/example/lib-aar/build.gradle @@ -1,13 +1,16 @@ apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' android { - compileSdkVersion 27 + compileSdkVersion 29 defaultConfig { minSdkVersion 16 - targetSdkVersion 27 + targetSdkVersion 29 versionCode 1 versionName "1.0" + consumerProguardFiles 'proguard-rules.pro' } buildTypes { @@ -16,10 +19,31 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + + buildFeatures { + dataBinding = true + } + + flavorDimensions "default" + + productFlavors { + + flavor1 { + + } + + flavor2 { + + } + } +} + +android.libraryVariants.all { variant -> + variant.outputs.all { output -> + outputFileName = "change-name-lib-aar-${variant.name.capitalize()}.aar" + } } dependencies { implementation fileTree(dir: 'libs', include: '*.jar') - - implementation 'com.android.support:appcompat-v7:27.1.1' } diff --git a/example/lib-aar/proguard-rules.pro b/example/lib-aar/proguard-rules.pro index f1b42451..66056eb4 100644 --- a/example/lib-aar/proguard-rules.pro +++ b/example/lib-aar/proguard-rules.pro @@ -19,3 +19,4 @@ # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile +-keep class com.kezong.demo.libaar.AarLibClass {*;} \ No newline at end of file diff --git a/example/lib-aar/src/flavor1/java/com/kezong/demo/libaar/AarFlavor.java b/example/lib-aar/src/flavor1/java/com/kezong/demo/libaar/AarFlavor.java new file mode 100644 index 00000000..1210d012 --- /dev/null +++ b/example/lib-aar/src/flavor1/java/com/kezong/demo/libaar/AarFlavor.java @@ -0,0 +1,6 @@ +package com.kezong.demo.libaar; + +public class AarFlavor { + + public static final String TAG = "flavor1"; +} diff --git a/example/lib-aar/src/flavor2/java/com/kezong/demo/libaar/AarFlavor.java b/example/lib-aar/src/flavor2/java/com/kezong/demo/libaar/AarFlavor.java new file mode 100644 index 00000000..998c3c45 --- /dev/null +++ b/example/lib-aar/src/flavor2/java/com/kezong/demo/libaar/AarFlavor.java @@ -0,0 +1,6 @@ +package com.kezong.demo.libaar; + +public class AarFlavor { + + private static final String TAG = "flavor2"; +} diff --git a/example/lib-aar/src/main/AndroidManifest.xml b/example/lib-aar/src/main/AndroidManifest.xml index 41926c26..52c10332 100644 --- a/example/lib-aar/src/main/AndroidManifest.xml +++ b/example/lib-aar/src/main/AndroidManifest.xml @@ -1,6 +1,12 @@ + package="com.kezong.demo.libaar"> - - + + + + + + diff --git a/example/lib-aar/src/main/assets/cat.jpg b/example/lib-aar/src/main/assets/cat.jpg new file mode 100644 index 00000000..1dc32061 Binary files /dev/null and b/example/lib-aar/src/main/assets/cat.jpg differ diff --git a/example/lib-aar/src/main/java/com/kezong/demo/libaar/KotlinTest.kt b/example/lib-aar/src/main/java/com/kezong/demo/libaar/KotlinTest.kt new file mode 100644 index 00000000..fb54ac89 --- /dev/null +++ b/example/lib-aar/src/main/java/com/kezong/demo/libaar/KotlinTest.kt @@ -0,0 +1,8 @@ +@file:JvmName("KotlinTest2") +package com.kezong.demo.libaar + +fun test2() : Int { + return 130 +} + +val param = 110 diff --git a/example/lib-aar/src/main/java/com/kezong/demo/libaar/TestActivity.java b/example/lib-aar/src/main/java/com/kezong/demo/libaar/TestActivity.java new file mode 100644 index 00000000..0618496e --- /dev/null +++ b/example/lib-aar/src/main/java/com/kezong/demo/libaar/TestActivity.java @@ -0,0 +1,23 @@ +package com.kezong.demo.libaar; + +import android.app.Activity; +import androidx.databinding.DataBindingUtil; + +import android.os.Bundle; + +import com.kezong.demo.libaar.databinding.DatabindingBinding; + +public class TestActivity extends Activity { + + private DatabindingBinding binding; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + binding = DataBindingUtil.setContentView(this, R.layout.databinding); + User user = new User(); + user.setName("Hello World"); + user.setSex("[success][dataBinding] male"); + binding.setUser(user); + } +} diff --git a/example/lib-aar/src/main/java/com/kezong/demo/libaar/User.java b/example/lib-aar/src/main/java/com/kezong/demo/libaar/User.java new file mode 100644 index 00000000..fa1323d1 --- /dev/null +++ b/example/lib-aar/src/main/java/com/kezong/demo/libaar/User.java @@ -0,0 +1,28 @@ +package com.kezong.demo.libaar; + +import androidx.databinding.BaseObservable; +import androidx.databinding.Bindable; + +public class User extends BaseObservable { + private String name; + private String sex; + + @Bindable + + public String getName() { + return name; + } + + @Bindable + public String getSex() { + return sex; + } + + public void setName(String name) { + this.name = name; + } + + public void setSex(String sex) { + this.sex = sex; + } +} diff --git a/example/lib-aar/src/main/jniLibs/armeabi/libgnustl_shared.so b/example/lib-aar/src/main/jniLibs/armeabi-v7a/libgnustl_shared.so similarity index 100% rename from example/lib-aar/src/main/jniLibs/armeabi/libgnustl_shared.so rename to example/lib-aar/src/main/jniLibs/armeabi-v7a/libgnustl_shared.so diff --git a/example/lib-aar/src/main/res/layout/databinding.xml b/example/lib-aar/src/main/res/layout/databinding.xml new file mode 100644 index 00000000..f4c534c9 --- /dev/null +++ b/example/lib-aar/src/main/res/layout/databinding.xml @@ -0,0 +1,19 @@ + + + + + + + + + + diff --git a/example/lib-aar2/build.gradle b/example/lib-aar2/build.gradle index c4e5b63d..a5f23c65 100644 --- a/example/lib-aar2/build.gradle +++ b/example/lib-aar2/build.gradle @@ -1,11 +1,11 @@ apply plugin: 'com.android.library' android { - compileSdkVersion 26 + compileSdkVersion 29 defaultConfig { minSdkVersion 16 - targetSdkVersion 26 + targetSdkVersion 29 versionCode 1 versionName "1.0" } @@ -16,6 +16,20 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + + buildFeatures { + dataBinding = true + } + + flavorDimensions "default2" + + productFlavors { + flavor4 { + } + + flavor3 { + } + } } dependencies { diff --git a/example/lib-main/build.gradle b/example/lib-main/build.gradle index fb7e89e3..28ba820b 100644 --- a/example/lib-main/build.gradle +++ b/example/lib-main/build.gradle @@ -1,5 +1,7 @@ apply plugin: 'com.android.library' apply plugin: 'com.kezong.fat-aar' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' repositories { flatDir { @@ -8,7 +10,7 @@ repositories { } android { - compileSdkVersion 27 + compileSdkVersion 29 defaultConfig { minSdkVersion 16 @@ -19,33 +21,84 @@ android { buildTypes { release { - minifyEnabled false + minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + + flavorDimensions "default" + + productFlavors { + flavor1 { + missingDimensionStrategy 'default2', 'flavor3' + } + + flavor2 { + missingDimensionStrategy 'default2', 'flavor3' + } + } + + packagingOptions { + exclude 'lib/x86/*.so' + exclude 'lib/x86_64/*.so' + exclude 'lib/armeabi/*.so' + exclude 'lib/arm64-v8a/*.so' + } } -// If the value is changed to true, the dependencies of the remote dependency will also be embedded in the final aar. -// the default value of transitive is false -configurations.embed.transitive = true +afterEvaluate { + // for app test, copy the final aar to application libs + android.libraryVariants.all { variant -> + File outputFile = variant.outputs.first().outputFile + tasks.named("assemble${variant.name.capitalize()}").configure { + doLast { + copy { + from outputFile + into "../app/libs" + rename outputFile.name, "fat-aar-final.aar" + } + } + } + } +} + +fataar { + /** + * If transitive is true, local jar module and remote library's dependencies will be embed. + * If transitive is false, just embed first level dependency + * Local aar project does not support transitive, always embed first level + * Default value is false + * @since 1.3.0 + */ + transitive = true +} dependencies { implementation fileTree(dir: 'libs', include: '*.jar') // java dependency - embed project(path: ':lib-java', configuration:'default') + embed project(path: ':lib-java', configuration: 'default') // aar dependency - embed project(path: ':lib-aar', configuration:'default') + embed project(path: ':lib-aar', configuration: 'default') // aar dependency - embed project(path: ':lib-aar2', configuration:'default') - // local full aar dependency - embed project(path: ':lib-aar-local', configuration:'default') - // local full aar dependency - embed (name:'lib-aar-local2',ext:'aar') - // remote jar dependency + embed project(path: ':lib-aar2', configuration: 'default') + // local full aar dependency, just build in flavor1 + flavor1Embed project(path: ':lib-aar-local', configuration: 'default') + // local full aar dependency, just build in debug + debugEmbed (name:'lib-aar-local2', ext:'aar') + // remote dependency embed 'com.google.guava:guava:20.0' // remote aar dependency - embed 'com.facebook.fresco:fresco:1.11.0' + embed ('com.facebook.fresco:fresco:1.12.0') { + // exclude any group or module + // exclude(group:'com.facebook.soloader', module:'soloader') + } + // remote aar dependency + embed ('com.github.bumptech.glide:glide:4.11.0') { + // If transitive = false, it will not embed dependencies declared in pom.xml + // If fataar.transitive = false, There is no need to declare. + transitive = false + } // don't want to embed in - compileOnly 'com.android.support:appcompat-v7:27.1.1' + implementation('androidx.appcompat:appcompat:1.2.0') } diff --git a/example/lib-main/libs/lib2.jar b/example/lib-main/libs/lib2.jar deleted file mode 100644 index 3d69593a..00000000 Binary files a/example/lib-main/libs/lib2.jar and /dev/null differ diff --git a/example/lib-main/proguard-rules.pro b/example/lib-main/proguard-rules.pro index f1b42451..d3e5bb2d 100644 --- a/example/lib-main/proguard-rules.pro +++ b/example/lib-main/proguard-rules.pro @@ -19,3 +19,8 @@ # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile +-keepparameternames +-dontoptimize +-dontshrink +-dontobfuscate +-dontpreverify diff --git a/example/lib-main/src/main/java/com/kezong/demo/lib/KotlinInMain.kt b/example/lib-main/src/main/java/com/kezong/demo/lib/KotlinInMain.kt new file mode 100644 index 00000000..4a41aa96 --- /dev/null +++ b/example/lib-main/src/main/java/com/kezong/demo/lib/KotlinInMain.kt @@ -0,0 +1,7 @@ +@file:JvmName("KotlinInMain") +package com.kezong.demo.lib + +fun test() : Int { + return 120 +} + diff --git a/example/lib-main/src/main/java/com/kezong/demo/lib/MainLibClass.java b/example/lib-main/src/main/java/com/kezong/demo/lib/MainLibClass.java index 4ecca6ad..ec7995cc 100644 --- a/example/lib-main/src/main/java/com/kezong/demo/lib/MainLibClass.java +++ b/example/lib-main/src/main/java/com/kezong/demo/lib/MainLibClass.java @@ -6,4 +6,8 @@ public class MainLibClass { public static final String TAG = MainLibClass.class.getSimpleName(); + + public static int test() { + return 200; + } } diff --git a/example/settings.gradle b/example/settings.gradle index e010a0c2..e407688f 100644 --- a/example/settings.gradle +++ b/example/settings.gradle @@ -3,3 +3,10 @@ include ':lib-java' include ':lib-aar' include ':lib-aar2' include ':lib-aar-local' +include ':app' + +includeBuild('../source') { + dependencySubstitution { + substitute module('com.github.kezong:fat-aar') using project(':') + } +} diff --git a/source/build.gradle b/source/build.gradle index edc8f73d..913630b5 100644 --- a/source/build.gradle +++ b/source/build.gradle @@ -1,17 +1,14 @@ apply plugin: 'groovy' -apply plugin: 'com.novoda.bintray-release' +apply from: "./upload.gradle" buildscript { repositories { - jcenter() - } - dependencies { - classpath 'com.novoda:bintray-release:0.9.1' + mavenCentral() } } repositories { - jcenter() + mavenCentral() google() maven { url "https://plugins.gradle.org/m2/" } } @@ -20,25 +17,6 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation gradleApi() implementation localGroovy() - - implementation 'com.android.tools.build:gradle:3.4.1' -} - -//afterEvaluate { -// tasks.all { task -> -// if (task.name == "generatePomFileForMavenPublication") { -// task.setEnabled(false) -// } else { -// } -// } -//} - -publish { - userOrg = 'kezong' - groupId = 'com.kezong' - artifactId = 'fat-aar' - publishVersion = '1.2.5' - desc = 'Gradle plugin for merging dependencies applies to gradle plugin version 3.+' - website = 'https://github.com/kezong/fat-aar-android' + implementation "org.javassist:javassist:3.27.0-GA" + implementation 'com.android.tools.build:gradle:4.2.0' } - diff --git a/source/gradle.properties b/source/gradle.properties new file mode 100644 index 00000000..7c863c1a --- /dev/null +++ b/source/gradle.properties @@ -0,0 +1,3 @@ +PUBLISH_GROUP_ID=com.github.kezong +PUBLISH_ARTIFACT_ID=fat-aar +PUBLISH_VERSION=1.3.8 diff --git a/source/gradle/wrapper/gradle-wrapper.properties b/source/gradle/wrapper/gradle-wrapper.properties index be297714..69a3256f 100644 --- a/source/gradle/wrapper/gradle-wrapper.properties +++ b/source/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip diff --git a/source/src/main/groovy/com/kezong/fataar/DirectoryManager.groovy b/source/src/main/groovy/com/kezong/fataar/DirectoryManager.groovy new file mode 100644 index 00000000..4892d2ff --- /dev/null +++ b/source/src/main/groovy/com/kezong/fataar/DirectoryManager.groovy @@ -0,0 +1,45 @@ +package com.kezong.fataar; + +import com.android.build.gradle.api.LibraryVariant; + +import org.gradle.api.Project; + +/** + * Temp directory used by fat-aar + */ +class DirectoryManager { + + private static final String RE_BUNDLE_FOLDER = "aar_rebundle"; + + private static final String INTERMEDIATES_TEMP_FOLDER = "fat-aar"; + + private static Project sProject; + + static void attach(Project project) { + sProject = project; + } + + static File getReBundleDirectory(LibraryVariant variant) { + return sProject.file("${sProject.getBuildDir()}/outputs/${RE_BUNDLE_FOLDER}/${variant.name}") + } + + static File getRJavaDirectory(LibraryVariant variant) { + return sProject.file("${sProject.getBuildDir()}/intermediates/${INTERMEDIATES_TEMP_FOLDER}/r/${variant.name}") + } + + static File getRClassDirectory(LibraryVariant variant) { + return sProject.file("${sProject.getBuildDir()}/intermediates/${INTERMEDIATES_TEMP_FOLDER}/r-class/${variant.name}") + } + + static File getRJarDirectory(LibraryVariant variant) { + return sProject.file("${sProject.getBuildDir()}/outputs/${RE_BUNDLE_FOLDER}/${variant.name}/libs") + } + + static File getMergeClassDirectory(LibraryVariant variant) { + return sProject.file("${sProject.getBuildDir()}/intermediates/${INTERMEDIATES_TEMP_FOLDER}/merge_classes/${variant.name}") + } + + static File getKotlinMetaDirectory(LibraryVariant variant) { + return sProject.file("${sProject.getBuildDir()}/tmp/kotlin-classes/${variant.name}/META-INF") + } +} diff --git a/source/src/main/groovy/com/kezong/fataar/EmbedResolutionListener.groovy b/source/src/main/groovy/com/kezong/fataar/EmbedResolutionListener.groovy new file mode 100644 index 00000000..40a656e6 --- /dev/null +++ b/source/src/main/groovy/com/kezong/fataar/EmbedResolutionListener.groovy @@ -0,0 +1,59 @@ +package com.kezong.fataar + +import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration +import org.gradle.api.artifacts.DependencyResolutionListener +import org.gradle.api.artifacts.ResolvableDependencies +import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependency + +class EmbedResolutionListener implements DependencyResolutionListener { + + private final Project project + + private final Configuration configuration + + private final String compileOnlyConfigName; + + EmbedResolutionListener(Project project, Configuration configuration) { + this.project = project + this.configuration = configuration + String prefix = getConfigNamePrefix(configuration.name) + if (prefix != null) { + this.compileOnlyConfigName = prefix + "CompileOnly" + } else { + this.compileOnlyConfigName = "compileOnly" + } + } + + private String getConfigNamePrefix(String configurationName) { + if (configurationName.endsWith(FatAarPlugin.CONFIG_SUFFIX)) { + return configurationName.substring(0, configuration.name.length() - FatAarPlugin.CONFIG_SUFFIX.length()) + } else { + return null + } + } + + @Override + void beforeResolve(ResolvableDependencies resolvableDependencies) { + configuration.dependencies.each { dependency -> + if (dependency instanceof DefaultProjectDependency) { + if (dependency.targetConfiguration == null) { + dependency.targetConfiguration = "default" + } + // support that the module can be indexed in Android Studio 4.0.0 + DefaultProjectDependency dependencyClone = dependency.copy() + dependencyClone.targetConfiguration = null; + // The purpose is to support the code hints + project.dependencies.add(compileOnlyConfigName, dependencyClone) + } else { + // The purpose is to support the code hints + project.dependencies.add(compileOnlyConfigName, dependency) + } + } + project.gradle.removeListener(this) + } + + @Override + void afterResolve(ResolvableDependencies resolvableDependencies) { + } +} \ No newline at end of file diff --git a/source/src/main/groovy/com/kezong/fataar/ExplodedHelper.groovy b/source/src/main/groovy/com/kezong/fataar/ExplodedHelper.groovy index 12b31b8c..276e69e4 100644 --- a/source/src/main/groovy/com/kezong/fataar/ExplodedHelper.groovy +++ b/source/src/main/groovy/com/kezong/fataar/ExplodedHelper.groovy @@ -4,41 +4,37 @@ import org.gradle.api.Project /** * process jars and classes - * Created by Vigi on 2017/1/20. - * Modified by kezong on 2018/12/18 */ class ExplodedHelper { static void processLibsIntoLibs(Project project, - Collection androidLibraries, Collection jarFiles, + Collection androidLibraries, + Collection jarFiles, File folderOut) { for (androidLibrary in androidLibraries) { if (!androidLibrary.rootFolder.exists()) { - Utils.logInfo('[warning]' + androidLibrary.rootFolder + ' not found!') + FatUtils.logInfo('[warning]' + androidLibrary.rootFolder + ' not found!') continue } if (androidLibrary.localJars.isEmpty()) { - Utils.logInfo("Not found jar file, Library:${androidLibrary.name}") + FatUtils.logInfo("Not found jar file, Library: ${androidLibrary.name}") } else { - Utils.logInfo("Merge ${androidLibrary.name} jar file, Library:${androidLibrary.name}") - } - androidLibrary.localJars.each { - Utils.logInfo(it.path) - } - project.copy { - from(androidLibrary.localJars) - into folderOut + FatUtils.logInfo("Merge ${androidLibrary.name} local jar files") + project.copy { + from(androidLibrary.localJars) + into(folderOut) + } } } for (jarFile in jarFiles) { - if (!jarFile.exists()) { - Utils.logInfo('[warning]' + jarFile + ' not found!') - continue - } - Utils.logInfo('copy jar from: ' + jarFile + " to " + folderOut.absolutePath) - project.copy { - from(jarFile) - into folderOut + if (jarFile.exists()) { + FatUtils.logInfo("Copy jar from: $jarFile to $folderOut.absolutePath") + project.copy { + from(jarFile) + into(folderOut) + } + } else { + FatUtils.logInfo('[warning]' + jarFile + ' not found!') } } } @@ -46,42 +42,44 @@ class ExplodedHelper { static void processClassesJarInfoClasses(Project project, Collection androidLibraries, File folderOut) { - Utils.logInfo('Merge ClassesJar') + FatUtils.logInfo('Merge ClassesJar') Collection allJarFiles = new ArrayList<>() for (androidLibrary in androidLibraries) { if (!androidLibrary.rootFolder.exists()) { - Utils.logInfo('[warning]' + androidLibrary.rootFolder + ' not found!') + FatUtils.logInfo('[warning]' + androidLibrary.rootFolder + ' not found!') continue } allJarFiles.add(androidLibrary.classesJarFile) } for (jarFile in allJarFiles) { + if (!jarFile.exists()) { + continue; + } project.copy { from project.zipTree(jarFile) into folderOut - exclude 'META-INF/' } } } static void processLibsIntoClasses(Project project, - Collection androidLibraries, Collection jarFiles, - File folderOut) { - Utils.logInfo('Merge Libs') + Collection androidLibraries, + Collection jarFiles, + File folderOut) { + FatUtils.logInfo('Merge Libs') Collection allJarFiles = new ArrayList<>() for (androidLibrary in androidLibraries) { if (!androidLibrary.rootFolder.exists()) { - Utils.logInfo('[warning]' + androidLibrary.rootFolder + ' not found!') + FatUtils.logInfo('[warning]' + androidLibrary.rootFolder + ' not found!') continue } - Utils.logInfo('[androidLibrary]' + androidLibrary.getName()) + FatUtils.logInfo('[androidLibrary]' + androidLibrary.getName()) allJarFiles.addAll(androidLibrary.localJars) } for (jarFile in jarFiles) { - if (!jarFile.exists()) { - continue + if (jarFile.exists()) { + allJarFiles.add(jarFile) } - allJarFiles.add(jarFile) } for (jarFile in allJarFiles) { project.copy { diff --git a/source/src/main/groovy/com/kezong/fataar/FatAarExtension.groovy b/source/src/main/groovy/com/kezong/fataar/FatAarExtension.groovy new file mode 100644 index 00000000..00e25716 --- /dev/null +++ b/source/src/main/groovy/com/kezong/fataar/FatAarExtension.groovy @@ -0,0 +1,28 @@ +package com.kezong.fataar; + +class FatAarExtension { + + /** + * Used in RClassesTransform.java by reflection, don't change the name. + */ + static final String NAME = "fataar" + + /** + * Plan A: using bytecode patching to process the merging problem of R files + * Plan B: generate sub module's R class to process the merging problem of R files + * if transformR is true, use Plan A, else use Plan B. + * In the future, Plan B maybe deprecated. + * + * Used in RClassesTransform.java by reflection, don't change the field name. + * @since 1.3.0 + */ + boolean transformR = true + + /** + * If transitive is true, local jar module and remote library's dependencies will be embed. (local aar module does not support) + * If transitive is false, just embed first level dependency + * Default value is false + * @since 1.3.0 + */ + boolean transitive = false +} diff --git a/source/src/main/groovy/com/kezong/fataar/FatAarPlugin.groovy b/source/src/main/groovy/com/kezong/fataar/FatAarPlugin.groovy new file mode 100644 index 00000000..9de20f25 --- /dev/null +++ b/source/src/main/groovy/com/kezong/fataar/FatAarPlugin.groovy @@ -0,0 +1,151 @@ +package com.kezong.fataar + +import com.android.build.gradle.api.LibraryVariant +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.ProjectConfigurationException +import org.gradle.api.artifacts.Configuration +import org.gradle.api.artifacts.ResolvedArtifact +import org.gradle.api.artifacts.ResolvedDependency + +/** + * plugin entry + */ +class FatAarPlugin implements Plugin { + + public static final String ARTIFACT_TYPE_AAR = 'aar' + + public static final String ARTIFACT_TYPE_JAR = 'jar' + + private static final String CONFIG_NAME = "embed" + + public static final String CONFIG_SUFFIX = 'Embed' + + private Project project + + private RClassesTransform transform + + private final Collection embedConfigurations = new ArrayList<>() + + @Override + void apply(Project project) { + this.project = project + checkAndroidPlugin() + FatUtils.attach(project) + DirectoryManager.attach(project) + project.extensions.create(FatAarExtension.NAME, FatAarExtension) + createConfigurations() + registerTransform() + project.afterEvaluate { + doAfterEvaluate() + } + } + + private registerTransform() { + transform = new RClassesTransform(project) + // register in project.afterEvaluate is invalid. + project.android.registerTransform(transform) + } + + private void doAfterEvaluate() { + embedConfigurations.each { + if (project.fataar.transitive) { + it.transitive = true + } + } + + project.android.libraryVariants.all { variant -> + Collection artifacts = new ArrayList() + Collection firstLevelDependencies = new ArrayList<>() + embedConfigurations.each { configuration -> + if (configuration.name == CONFIG_NAME + || configuration.name == variant.getBuildType().name + CONFIG_SUFFIX + || configuration.name == variant.getFlavorName() + CONFIG_SUFFIX + || configuration.name == variant.name + CONFIG_SUFFIX) { + Collection resolvedArtifacts = resolveArtifacts(configuration) + artifacts.addAll(resolvedArtifacts) + artifacts.addAll(dealUnResolveArtifacts(configuration, variant as LibraryVariant, resolvedArtifacts)) + firstLevelDependencies.addAll(configuration.resolvedConfiguration.firstLevelModuleDependencies) + } + } + + if (!artifacts.isEmpty()) { + def processor = new VariantProcessor(project, variant) + processor.processVariant(artifacts, firstLevelDependencies, transform) + } + } + } + + private void createConfigurations() { + Configuration embedConf = project.configurations.create(CONFIG_NAME) + createConfiguration(embedConf) + FatUtils.logInfo("Creating configuration embed") + + project.android.buildTypes.all { buildType -> + String configName = buildType.name + CONFIG_SUFFIX + Configuration configuration = project.configurations.create(configName) + createConfiguration(configuration) + FatUtils.logInfo("Creating configuration " + configName) + } + + project.android.productFlavors.all { flavor -> + String configName = flavor.name + CONFIG_SUFFIX + Configuration configuration = project.configurations.create(configName) + createConfiguration(configuration) + FatUtils.logInfo("Creating configuration " + configName) + project.android.buildTypes.all { buildType -> + String variantName = flavor.name + buildType.name.capitalize() + String variantConfigName = variantName + CONFIG_SUFFIX + Configuration variantConfiguration = project.configurations.create(variantConfigName) + createConfiguration(variantConfiguration) + FatUtils.logInfo("Creating configuration " + variantConfigName) + } + } + } + + private void checkAndroidPlugin() { + if (!project.plugins.hasPlugin('com.android.library')) { + throw new ProjectConfigurationException('fat-aar-plugin must be applied in project that' + + ' has android library plugin!', null) + } + } + + private void createConfiguration(Configuration embedConf) { + embedConf.visible = false + embedConf.transitive = false + project.gradle.addListener(new EmbedResolutionListener(project, embedConf)) + embedConfigurations.add(embedConf) + } + + private Collection resolveArtifacts(Configuration configuration) { + def set = new ArrayList() + if (configuration != null) { + configuration.resolvedConfiguration.resolvedArtifacts.each { artifact -> + if (ARTIFACT_TYPE_AAR == artifact.type || ARTIFACT_TYPE_JAR == artifact.type) { + // + } else { + throw new ProjectConfigurationException('Only support embed aar and jar dependencies!', null) + } + set.add(artifact) + } + } + return set + } + + private Collection dealUnResolveArtifacts(Configuration configuration, LibraryVariant variant, Collection artifacts) { + def artifactList = new ArrayList() + configuration.resolvedConfiguration.firstLevelModuleDependencies.each { dependency -> + def match = artifacts.any { artifact -> + dependency.moduleName == artifact.moduleVersion.id.name + } + + if (!match) { + def flavorArtifact = FlavorArtifact.createFlavorArtifact(project, variant, dependency) + if (flavorArtifact != null) { + artifactList.add(flavorArtifact) + } + } + } + return artifactList + } +} diff --git a/source/src/main/groovy/com/kezong/fataar/FatLibraryPlugin.groovy b/source/src/main/groovy/com/kezong/fataar/FatLibraryPlugin.groovy deleted file mode 100644 index ed4d56bf..00000000 --- a/source/src/main/groovy/com/kezong/fataar/FatLibraryPlugin.groovy +++ /dev/null @@ -1,114 +0,0 @@ -package com.kezong.fataar - -import com.android.build.gradle.api.LibraryVariant -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.ProjectConfigurationException -import org.gradle.api.artifacts.Configuration -import org.gradle.api.artifacts.DependencyResolutionListener -import org.gradle.api.artifacts.ResolvableDependencies -import org.gradle.api.artifacts.ResolvedArtifact -import org.gradle.api.artifacts.ResolvedDependency - -/** - * plugin entry - * - * Created by Vigi on 2017/1/14. - * Modified by kezong on 2018/12/18 - */ -class FatLibraryPlugin implements Plugin { - - public static final String ARTIFACT_TYPE_AAR = 'aar' - - public static final String ARTIFACT_TYPE_JAR = 'jar' - - private Project project - - private Configuration embedConf - - private Set artifacts - - private Set unResolveArtifact - - @Override - void apply(Project project) { - this.project = project - Utils.setProject(project) - checkAndroidPlugin() - createConfiguration() - project.afterEvaluate { - resolveArtifacts() - dealUnResolveArtifacts() - project.android.libraryVariants.all { variant -> - processVariant(variant) - } - } - - } - - private void checkAndroidPlugin() { - if (!project.plugins.hasPlugin('com.android.library')) { - throw new ProjectConfigurationException('fat-aar-plugin must be applied in project that' + - ' has android library plugin!', null) - } - } - - private void createConfiguration() { - embedConf = project.configurations.create('embed') - embedConf.visible = false - embedConf.transitive = false - - project.gradle.addListener(new DependencyResolutionListener() { - @Override - void beforeResolve(ResolvableDependencies resolvableDependencies) { - embedConf.dependencies.each { dependency -> - project.dependencies.add('compileOnly', dependency) - } - project.gradle.removeListener(this) - } - - @Override - void afterResolve(ResolvableDependencies resolvableDependencies) { - } - }) - } - - private void resolveArtifacts() { - def set = new HashSet<>() - embedConf.resolvedConfiguration.resolvedArtifacts.each { artifact -> - // jar file wouldn't be here - if (ARTIFACT_TYPE_AAR == artifact.type || ARTIFACT_TYPE_JAR == artifact.type) { - Utils.logAnytime('[embed detected][' + artifact.type + ']' + artifact.moduleVersion.id) - } else { - throw new ProjectConfigurationException('Only support embed aar and jar dependencies!', null) - } - set.add(artifact) - } - artifacts = Collections.unmodifiableSet(set) - } - - private void processVariant(LibraryVariant variant) { - def processor = new VariantProcessor(project, variant) - processor.addArtifacts(artifacts) - processor.addUnResolveArtifact(unResolveArtifact) - processor.processVariant() - } - - private void dealUnResolveArtifacts() { - def dependencies = Collections.unmodifiableSet(embedConf.resolvedConfiguration.firstLevelModuleDependencies) - def dependencySet = new HashSet() - dependencies.each { dependency -> - boolean match = false - artifacts.each { artifact -> - if (dependency.moduleName == artifact.name) { - match = true - } - } - if (!match) { - Utils.logAnytime('[unResolve dependency detected][' + dependency.name + ']') - dependencySet.add(dependency) - } - } - unResolveArtifact = Collections.unmodifiableSet(dependencySet) - } -} diff --git a/source/src/main/groovy/com/kezong/fataar/FatUtils.groovy b/source/src/main/groovy/com/kezong/fataar/FatUtils.groovy new file mode 100644 index 00000000..fb8dbea6 --- /dev/null +++ b/source/src/main/groovy/com/kezong/fataar/FatUtils.groovy @@ -0,0 +1,147 @@ +package com.kezong.fataar + +import org.gradle.api.Project + +class FatUtils { + + private static Project sProject + + def static attach(Project p) { + sProject = p + } + + def static logError(def msg) { + sProject.logger.error("[fat-aar]${msg}") + } + + def static logInfo(def msg) { + sProject.logger.info("[fat-aar]${msg}") + } + + def static logAnytime(def msg) { + sProject.println("[fat-aar]${msg}") + } + + def static showDir(int indent, File file) throws IOException { + for (int i = 0; i < indent; i++) + System.out.print('-') + println(file.getName() + " " + file.size()) + if (file.isDirectory()) { + File[] files = file.listFiles() + for (int i = 0; i < files.length; i++) + showDir(indent + 4, files[i]) + } + } + + static void deleteEmptyDir(final File file) { + file.listFiles().each { x -> + if (x.isDirectory()) { + if (x.listFiles().size() == 0) { + x.delete() + } else { + deleteEmptyDir(x) + if (x.listFiles().size() == 0) { + x.delete() + } + } + } + } + } + + static int compareVersion(String v1, String v2) { + if (v1.equals(v2)) { + return 0 + } + + String[] version1 = v1.split("-") + String[] version2 = v2.split("-") + String[] version1Array = version1[0].split("[._]") + String[] version2Array = version2[0].split("[._]") + + String preRelease1 = new String() + String preRelease2 = new String() + if (version1.length > 1) { + preRelease1 = version1[1] + } + if (version2.length > 1) { + preRelease2 = version2[1] + } + + int index = 0 + int minLen = Math.min(version1Array.length, version2Array.length) + long diff = 0 + + while (index < minLen + && (diff = Long.parseLong(version1Array[index]) + - Long.parseLong(version2Array[index])) == 0) { + index++ + } + if (diff == 0) { + for (int i = index; i < version1Array.length; i++) { + if (Long.parseLong(version1Array[i]) > 0) { + return 1 + } + } + + for (int i = index; i < version2Array.length; i++) { + if (Long.parseLong(version2Array[i]) > 0) { + return -1 + } + } + //compare pre-release + if (!preRelease1.isEmpty() && preRelease2.isEmpty()) { + return -1 + } else if (preRelease1.isEmpty() && !preRelease2.isEmpty()) { + return 1 + } else if (!preRelease1.isEmpty() && !preRelease2.isEmpty()) { + int preReleaseDiff = preRelease1.compareTo(preRelease2); + if (preReleaseDiff > 0) { + return 1 + } else if (preReleaseDiff < 0) { + return -1 + } + } + return 0 + } else { + return diff > 0 ? 1 : -1 + } + } + + static String formatDataSize(long size) { + String result + if (size < 1024) { + result = size + "Byte" + } else if (size < (1024 * 1024)) { + result = String.format("%.0fK", size / 1024) + } else if (size < 1024 * 1024 * 1024) { + result = String.format("%.2fM", size / (1024 * 1024.0)) + } else { + result = String.format("%.2fG", size / (1024 * 1024 * 1024.0)) + } + return result + } + + def static mergeFiles(Collection inputFiles, File output) { + if (inputFiles == null) { + return + } + // filter out any non-existent files + Collection existingFiles = inputFiles.findAll { it -> + it.exists() + } + + // no input? done. + if (existingFiles.isEmpty()) { + return + } + + if (!output.exists()) { + output.createNewFile() + } + + // otherwise put all the files together append to output file + for (File file in existingFiles) { + output.append("\n${file.getText("UTF-8")}\n") + } + } +} \ No newline at end of file diff --git a/source/src/main/groovy/com/kezong/fataar/FlavorArtifact.groovy b/source/src/main/groovy/com/kezong/fataar/FlavorArtifact.groovy old mode 100644 new mode 100755 index f894acab..cdae3c10 --- a/source/src/main/groovy/com/kezong/fataar/FlavorArtifact.groovy +++ b/source/src/main/groovy/com/kezong/fataar/FlavorArtifact.groovy @@ -1,17 +1,20 @@ package com.kezong.fataar import com.android.build.gradle.api.LibraryVariant +import com.android.builder.model.ProductFlavor import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.artifacts.ModuleVersionIdentifier +import org.gradle.api.artifacts.ResolvedArtifact import org.gradle.api.artifacts.ResolvedDependency import org.gradle.api.artifacts.component.ComponentArtifactIdentifier import org.gradle.api.artifacts.component.ComponentIdentifier import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier -import org.gradle.api.internal.artifacts.DefaultResolvedArtifact import org.gradle.api.internal.tasks.TaskDependencyContainer import org.gradle.api.internal.tasks.TaskDependencyResolveContext import org.gradle.api.tasks.TaskDependency +import org.gradle.api.tasks.TaskProvider +import org.gradle.internal.DisplayName import org.gradle.internal.Factory import org.gradle.internal.component.model.DefaultIvyArtifactName @@ -19,15 +22,33 @@ import javax.annotation.Nullable /** * FlavorArtifact - * @author kezong on 2019/4/25. */ class FlavorArtifact { - static DefaultResolvedArtifact createFlavorArtifact(Project project, LibraryVariant variant, ResolvedDependency unResolvedArtifact, String version) { - ModuleVersionIdentifier identifier = createModuleVersionIdentifier(unResolvedArtifact) - DefaultIvyArtifactName artifactName = createArtifactName(unResolvedArtifact) + // since 6.8.0 + private static final String CLASS_PreResolvedResolvableArtifact = "org.gradle.api.internal.artifacts.PreResolvedResolvableArtifact"; + // since 6.8.0 + private static final String CLASS_CalculatedValueContainer = "org.gradle.internal.model.CalculatedValueContainer" + + private static final String CLASS_DefaultResolvedArtifact = "org.gradle.api.internal.artifacts.DefaultResolvedArtifact" + + static ResolvedArtifact createFlavorArtifact(Project project, LibraryVariant variant, ResolvedDependency unResolvedArtifact) { Project artifactProject = getArtifactProject(project, unResolvedArtifact) - File artifactFile = createArtifactFile(artifactProject, variant, unResolvedArtifact, version) + TaskProvider bundleProvider = null; + try { + bundleProvider = getBundleTask(artifactProject, variant) + } catch (Exception ignore) { + FatUtils.logError("[$variant.name]Can not resolve :$unResolvedArtifact.moduleName") + return null + } + + if (bundleProvider == null) { + return null + } + + ModuleVersionIdentifier identifier = createModuleVersionIdentifier(unResolvedArtifact) + File artifactFile = createArtifactFile(artifactProject, bundleProvider.get()) + DefaultIvyArtifactName artifactName = createArtifactName(artifactFile) Factory fileFactory = new Factory() { @Override File create() { @@ -35,17 +56,41 @@ class FlavorArtifact { } } ComponentArtifactIdentifier artifactIdentifier = createComponentIdentifier(artifactFile) - if (Utils.compareVersion(project.gradle.gradleVersion, "6.0.0") >= 0) { + if (FatUtils.compareVersion(project.gradle.gradleVersion, "6.0.0") >= 0) { TaskDependencyContainer taskDependencyContainer = new TaskDependencyContainer() { @Override void visitDependencies(TaskDependencyResolveContext taskDependencyResolveContext) { - taskDependencyResolveContext.add(createTaskDependency(artifactProject, variant)) + taskDependencyResolveContext.add(createTaskDependency(bundleProvider.get())) } } - return new DefaultResolvedArtifact(identifier, artifactName, artifactIdentifier, taskDependencyContainer, fileFactory) + if (FatUtils.compareVersion(project.gradle.gradleVersion, "6.8.0") >= 0) { + Object fileCalculatedValue = Class.forName(CLASS_CalculatedValueContainer).newInstance(new DisplayName(){ + @Override + String getCapitalizedDisplayName() { + return artifactFile.name + } + + @Override + String getDisplayName() { + return artifactFile.name + } + }, artifactFile) + return Class.forName(CLASS_PreResolvedResolvableArtifact).newInstance( + identifier, + artifactName, + artifactIdentifier, + fileCalculatedValue, + taskDependencyContainer, + null + ) + } else { + return Class.forName(CLASS_DefaultResolvedArtifact) + .newInstance(identifier, artifactName, artifactIdentifier, taskDependencyContainer, fileFactory) + } } else { - TaskDependency taskDependency = createTaskDependency(artifactProject, variant) - return new DefaultResolvedArtifact(identifier, artifactName, artifactIdentifier, taskDependency, fileFactory) + TaskDependency taskDependency = createTaskDependency(bundleProvider.get()) + return Class.forName(CLASS_DefaultResolvedArtifact) + .newInstance(identifier, artifactName, artifactIdentifier, taskDependency, fileFactory) } } @@ -57,8 +102,8 @@ class FlavorArtifact { ) } - private static DefaultIvyArtifactName createArtifactName(ResolvedDependency unResolvedArtifact) { - return new DefaultIvyArtifactName(unResolvedArtifact.getModuleName(), "aar", "") + private static DefaultIvyArtifactName createArtifactName(File artifactFile) { + return new DefaultIvyArtifactName(artifactFile.getName(), "aar", "") } private static ComponentArtifactIdentifier createComponentIdentifier(final File artifactFile) { @@ -84,28 +129,66 @@ class FlavorArtifact { return null } - private static File createArtifactFile(Project project, LibraryVariant variant, ResolvedDependency unResolvedArtifact, String version) { - def buildPath = project.buildDir.path - def outputName - if (Utils.compareVersion(project.gradle.gradleVersion, "5.1.0") >= 0 && Utils.compareVersion(version, "3.4") < 0) { - outputName = "$buildPath/outputs/aar/${unResolvedArtifact.moduleName}.aar" + private static File createArtifactFile(Project project, Task bundle) { + File output + if (FatUtils.compareVersion(project.gradle.gradleVersion, "5.1") >= 0) { + output = new File(bundle.getDestinationDirectory().getAsFile().get(), bundle.getArchiveFileName().get()) } else { - outputName = "$buildPath/outputs/aar/$unResolvedArtifact.moduleName-$variant.flavorName-${variant.buildType.name}.aar" + output = new File(bundle.destinationDir, bundle.archiveName) } - return new File(outputName) + return output } - private static TaskDependency createTaskDependency(Project project, LibraryVariant variant) { - def taskPath = 'bundle' + variant.name.capitalize() - Task bundleTask = project.tasks.findByPath(taskPath) - if (bundleTask == null) { - taskPath = 'bundle' + variant.name.capitalize() + "Aar" - bundleTask = project.tasks.findByPath(taskPath) - } - if (bundleTask == null) { - throw new RuntimeException("Can not find task ${taskPath}!") + private static TaskProvider getBundleTask(Project project, LibraryVariant variant) { + TaskProvider bundleTaskProvider = null + project.android.libraryVariants.find { subVariant -> + // 1. find same flavor + if (variant.name == subVariant.name) { + try { + bundleTaskProvider = VersionAdapter.getBundleTaskProvider(project, subVariant.name as String) + return true + } catch (Exception ignore) { + } + } + + // 2. find buildType + ProductFlavor flavor = variant.productFlavors.isEmpty() ? variant.mergedFlavor : variant.productFlavors.first() + if (subVariant.name == variant.buildType.name) { + try { + bundleTaskProvider = VersionAdapter.getBundleTaskProvider(project, subVariant.name as String) + return true + } catch (Exception ignore) { + } + } + + // 3. find missingStrategies + try { + flavor.missingDimensionStrategies.find { entry -> + String toDimension = entry.getKey() + String toFlavor = entry.getValue().getFallbacks().first() + ProductFlavor subFlavor = subVariant.productFlavors.isEmpty() ? + subVariant.mergedFlavor : subVariant.productFlavors.first() + if (toDimension == subFlavor.dimension + && toFlavor == subFlavor.name + && variant.buildType.name == subVariant.buildType.name) { + try { + bundleTaskProvider = VersionAdapter.getBundleTaskProvider(project, subVariant.name as String) + return true + } catch (Exception ignore) { + } + } + } + } catch (Exception ignore) { + + } + + return bundleTaskProvider != null } + return bundleTaskProvider + } + + private static TaskDependency createTaskDependency(Task bundleTask) { return new TaskDependency() { @Override Set getDependencies(@Nullable Task task) { diff --git a/source/src/main/groovy/com/kezong/fataar/RClassesGenerate.groovy b/source/src/main/groovy/com/kezong/fataar/RClassesGenerate.groovy new file mode 100644 index 00000000..e679da45 --- /dev/null +++ b/source/src/main/groovy/com/kezong/fataar/RClassesGenerate.groovy @@ -0,0 +1,191 @@ +package com.kezong.fataar + +import com.android.build.gradle.api.LibraryVariant +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.tasks.PathSensitivity +import org.gradle.api.tasks.TaskProvider +import org.gradle.api.tasks.compile.JavaCompile +import org.gradle.jvm.tasks.Jar + +/** + * Used to generate R classes + * generate R File -> R Class -> R Jar -> unzip aar -> reBundle with R.jar + * @deprecated Prefer {@code RClassesTransform} + */ +class RClassesGenerate { + + private final Project mProject + private final LibraryVariant mVariant + + private VersionAdapter mVersionAdapter + private final Collection mLibraries + + RClassesGenerate(Project project, LibraryVariant variant, Collection libraries) { + mProject = project + mVariant = variant + mLibraries = libraries + mVersionAdapter = new VersionAdapter(project, variant) + } + + TaskProvider configure(TaskProvider reBundleTask) { + File rJavaDir = DirectoryManager.getRJavaDirectory(mVariant) + File rClassDir = DirectoryManager.getRClassDirectory(mVariant) + File rJarDir = DirectoryManager.getRJarDirectory(mVariant) + def RJarTask = configureRJarTask(rClassDir, rJarDir, reBundleTask) + def RClassTask = configureRClassTask(rJavaDir, rClassDir, RJarTask) + def RFileTask = configureRFileTask(rJavaDir, RClassTask) + + return RFileTask + } + + private def createRFile(AndroidArchiveLibrary library, def rFolder, ConfigObject symbolsMap) { + def libPackageName = mVariant.getApplicationId() + def aarPackageName = library.getPackageName() + + String packagePath = aarPackageName.replace('.', '/') + + def rTxt = library.getSymbolFile() + def rMap = new ConfigObject() + + if (rTxt.exists()) { + rTxt.eachLine { line -> + def (type, subclass, name, value) = line.tokenize(' ') + if (symbolsMap.containsKey(subclass) && symbolsMap.get(subclass).containsKey(name)) { + rMap[subclass].putAt(name, type) + } + } + } + + def sb = "package $aarPackageName;" << '\n' << '\n' + sb << 'public final class R {' << '\n' + rMap.each { subclass, values -> + sb << " public static final class $subclass {" << '\n' + values.each { name, type -> + sb << " public static final $type $name = ${libPackageName}.R.${subclass}.${name};" << '\n' + } + + sb << " }" << '\n' + } + + sb << '}' << '\n' + + new File("${rFolder.path}/$packagePath").mkdirs() + FileOutputStream outputStream = new FileOutputStream("${rFolder.path}/$packagePath/R.java") + outputStream.write(sb.toString().getBytes()) + outputStream.close() + } + + private def getSymbolsMap() { + def file = mVersionAdapter.getLocalSymbolFile() + if (!file.exists()) { + throw IllegalAccessException("{$file.absolutePath} not found") + } + + def map = new ConfigObject() + + if (file.name == "R-def.txt") { + // R-def.txt is a local symbol file that format is different of R.txt + file.eachLine { line -> + List splits = line.tokenize(' ') + if (splits == null || splits.size() < 2) { + return + } + def subclass = splits.get(0) + def name = splits.get(1).replace(".", "_") + if (subclass == "attr?") { + //styleable attributes + subclass = "attr" + } + map[subclass].putAt(name, 1) + if (subclass == "styleable" && splits.size() > 2) { + for (int i = 2; i < splits.size(); ++i) { + String subStyle = splits.get(i).replace(':', "_") + map[subclass].putAt("${name}_$subStyle", 1) + } + } + } + } else { + file.eachLine { line -> + def (type, subclass, name, value) = line.tokenize(' ') + map[subclass].putAt(name, type) + } + } + return map + } + + private TaskProvider configureRFileTask(final File destFolder, final TaskProvider RClassTask) { + def task = mProject.tasks.register("createRsFile${mVariant.name}") { + finalizedBy(RClassTask) + + inputs.files(mLibraries.stream().map { it.symbolFile }.collect()) + .withPathSensitivity(PathSensitivity.RELATIVE) + outputs.dir(destFolder) + + doLast { + if (destFolder.exists()) { + destFolder.deleteDir() + } + if (mLibraries != null && mLibraries.size() > 0) { + def symbolsMap = getSymbolsMap() + mLibraries.each { + FatUtils.logInfo("Generate R File, Library:${it.name}") + createRFile(it, destFolder, symbolsMap) + } + } + } + } + return task + } + + private TaskProvider configureRClassTask(final File sourceDir, final File destinationDir, final TaskProvider RJarTask) { + mProject.mkdir(destinationDir) + + def classpath = mVersionAdapter.getRClassPath() + String taskName = "compileRs${mVariant.name.capitalize()}" + TaskProvider task = mProject.getTasks().register(taskName, JavaCompile.class) { + finalizedBy(RJarTask) + + it.source = sourceDir.path + it.sourceCompatibility = mProject.android.compileOptions.sourceCompatibility + it.targetCompatibility = mProject.android.compileOptions.targetCompatibility + it.classpath = classpath + it.destinationDir = destinationDir + + doFirst { + FatUtils.logInfo("Compile R.class, Dir:${sourceDir.path}") + FatUtils.logInfo("Compile R.class, classpath:${classpath.first().absolutePath}") + + if (FatUtils.compareVersion(VersionAdapter.AGPVersion, "3.3.0") >= 0) { + mProject.copy { + from mProject.zipTree(mVersionAdapter.getRClassPath().first().absolutePath + "/R.jar") + into mVersionAdapter.getRClassPath().first().absolutePath + } + } + } + } + return task + } + + private TaskProvider configureRJarTask(final File fromDir, final File desFile, final TaskProvider reBundleAarTask) { + String taskName = "createRsJar${mVariant.name.capitalize()}" + TaskProvider task = mProject.getTasks().register(taskName, Jar) { + finalizedBy(reBundleAarTask) + + it.from fromDir.path + // The destinationDir property has been deprecated. + // This is scheduled to be removed in Gradle 7.0. Please use the destinationDirectory property instead. + if (FatUtils.compareVersion(mProject.gradle.gradleVersion, "5.1") >= 0) { + it.getArchiveFileName().set("${mVariant.getApplicationId()}-r-classes.jar") + it.getDestinationDirectory().set(desFile) + } else { + it.archiveName = "${mVariant.getApplicationId()}-r-classes.jar" + it.destinationDir = desFile + } + doFirst { + FatUtils.logInfo("Generate R.jar, Dir:$fromDir") + } + } + return task + } +} \ No newline at end of file diff --git a/source/src/main/groovy/com/kezong/fataar/RProcessor.groovy b/source/src/main/groovy/com/kezong/fataar/RProcessor.groovy deleted file mode 100644 index 10d9ed13..00000000 --- a/source/src/main/groovy/com/kezong/fataar/RProcessor.groovy +++ /dev/null @@ -1,247 +0,0 @@ -package com.kezong.fataar - -import com.android.build.gradle.api.LibraryVariant - -import org.gradle.api.Project -import org.gradle.api.Task -import org.gradle.api.tasks.bundling.Zip -import org.gradle.api.tasks.compile.JavaCompile -import org.gradle.jvm.tasks.Jar - - -/** - * R file processor - * @author kezong on 2019/7/16. - */ -class RProcessor { - - private final Project mProject - private final LibraryVariant mVariant - - private final File mJavaDir - private final File mClassDir - private final File mJarDir - private final File mAarUnZipDir - private final File mAarOutputDir - private final String mGradlePluginVersion - private String mAarOutputPath - private VersionAdapter mVersionAdapter - private final Collection mLibraries - - RProcessor(Project project, LibraryVariant variant, Collection libraries, String version) { - mProject = project - mVariant = variant - mLibraries = libraries - mGradlePluginVersion = version - mVersionAdapter = new VersionAdapter(project, variant, version) - // R.java dir - mJavaDir = mProject.file("${mProject.getBuildDir()}/intermediates/fat-R/r/${mVariant.dirName}") - // R.class compile dir - mClassDir = mProject.file("${mProject.getBuildDir()}/intermediates/fat-R/r-class/${mVariant.dirName}") - // R.jar dir - mJarDir = mProject.file("${mProject.getBuildDir()}/outputs/aar-R/${mVariant.dirName}/libs") - // aar zip file - mAarUnZipDir = mJarDir.getParentFile() - // aar output dir - mAarOutputDir = mProject.file("${mProject.getBuildDir()}/outputs/aar/") - mAarOutputPath = mVariant.outputs.first().outputFile.absolutePath - } - - void inject(Task bundleTask) { - def RFileTask = createRFileTask(mJavaDir) - def RClassTask = createRClassTask(mJavaDir, mClassDir) - def RJarTask = createRJarTask(mClassDir, mJarDir) - def reBundleAar = createBundleAarTask(mAarUnZipDir, mAarOutputDir, mAarOutputPath) - - reBundleAar.doFirst { - mProject.copy { - from mProject.zipTree(mAarOutputPath) - into mAarUnZipDir - } - deleteEmptyDir(mAarUnZipDir) - } - - reBundleAar.doLast { - Utils.logAnytime("target: $mAarOutputPath") - } - - bundleTask.doFirst { - File f = new File(mAarOutputPath) - if (f.exists()) { - f.delete() - } - mJarDir.getParentFile().deleteDir() - mJarDir.mkdirs() - } - - bundleTask.doLast { - // support gradle 5.1 && gradle plugin 3.4 before, the outputName is changed - File file = new File(mAarOutputPath) - if (!file.exists()) { - mAarOutputPath = mAarOutputDir.absolutePath + "/" + mProject.name + ".aar" - reBundleAar.archiveName = new File(mAarOutputPath).name - } - } - - bundleTask.finalizedBy(RFileTask) - RFileTask.finalizedBy(RClassTask) - RClassTask.finalizedBy(RJarTask) - RJarTask.finalizedBy(reBundleAar) - } - - private def createRFile(AndroidArchiveLibrary library, def rFolder, ConfigObject symbolsMap) { - def libPackageName = mVariant.getApplicationId() - def aarPackageName = library.getPackageName() - - String packagePath = aarPackageName.replace('.', '/') - - def rTxt = library.getSymbolFile() - def rMap = new ConfigObject() - - if (rTxt.exists()) { - rTxt.eachLine { line -> - def (type, subclass, name, value) = line.tokenize(' ') - if (symbolsMap.containsKey(subclass) && symbolsMap.get(subclass).containsKey(name)) { - rMap[subclass].putAt(name, type) - } - } - } - - def sb = "package $aarPackageName;" << '\n' << '\n' - sb << 'public final class R {' << '\n' - rMap.each { subclass, values -> - sb << " public static final class $subclass {" << '\n' - values.each { name, type -> - sb << " public static final $type $name = ${libPackageName}.R.${subclass}.${name};" << '\n' - } - - sb << " }" << '\n' - } - - sb << '}' << '\n' - - new File("${rFolder.path}/$packagePath").mkdirs() - FileOutputStream outputStream = new FileOutputStream("${rFolder.path}/$packagePath/R.java") - outputStream.write(sb.toString().getBytes()) - outputStream.close() - } - - private def getSymbolsMap() { - def file = mVersionAdapter.getLocalSymbolFile() - if (!file.exists()) { - throw IllegalAccessException("{$file.absolutePath} not found") - } - - def map = new ConfigObject() - - if (file.name == "R-def.txt") { - // R-def.txt is a local symbol file that format is different of R.txt - file.eachLine { line -> - List splits = line.tokenize(' ') - if (splits == null || splits.size() < 2) { - return - } - def subclass = splits.get(0) - def name = splits.get(1) - map[subclass].putAt(name, 1) - if (subclass == "styleable" && splits.size() > 2) { - for (int i = 2; i < splits.size(); ++i) { - String subStyle = splits.get(i).replace(':', "_") - map[subclass].putAt("${name}_$subStyle", 1) - } - } - } - } else { - file.eachLine { line -> - def (type, subclass, name, value) = line.tokenize(' ') - map[subclass].putAt(name, type) - } - } - return map - } - - private Task createRFileTask(final File destFolder) { - def task = mProject.tasks.create(name: 'createRsFile' + mVariant.name) - task.doLast { - if (destFolder.exists()) { - destFolder.deleteDir() - } - if (mLibraries != null && mLibraries.size() > 0) { - def symbolsMap = getSymbolsMap() - mLibraries.each { - Utils.logInfo("Generate R File, Library:${it.name}") - createRFile(it, destFolder, symbolsMap) - } - } - } - - return task - } - - private Task createRClassTask(final def sourceDir, final def destinationDir) { - mProject.mkdir(destinationDir) - - def classpath = mVersionAdapter.getRClassPath() - String taskName = "compileRs${mVariant.name.capitalize()}" - Task task = mProject.getTasks().create(taskName, JavaCompile.class, { - it.source = sourceDir.path - it.sourceCompatibility = mProject.android.compileOptions.sourceCompatibility - it.targetCompatibility = mProject.android.compileOptions.targetCompatibility - it.classpath = classpath - it.destinationDir destinationDir - }) - - task.doFirst { - Utils.logInfo("Compile R.class, Dir:${sourceDir.path}") - Utils.logInfo("Compile R.class, classpath:${classpath.first().absolutePath}") - - if (mGradlePluginVersion != null && Utils.compareVersion(mGradlePluginVersion, "3.3.0") >= 0) { - mProject.copy { - from mProject.zipTree(mVersionAdapter.getRClassPath().first().absolutePath + "/R.jar") - into mVersionAdapter.getRClassPath().first().absolutePath - } - } - } - return task - } - - private Task createRJarTask(final def fromDir, final def desFile) { - String taskName = "createRsJar${mVariant.name.capitalize()}" - Task task = mProject.getTasks().create(taskName, Jar.class, { - it.from fromDir.path - it.archiveName = "r-classes.jar" - it.destinationDir desFile - }) - task.doFirst { - Utils.logInfo("Generate R.jar, Dir:$fromDir") - } - return task - } - - private Task createBundleAarTask(final File from, final File destDir, final String filePath) { - String taskName = "reBundleAar${mVariant.name.capitalize()}" - Task task = mProject.getTasks().create(taskName, Zip.class, { - it.from from - it.include "**" - it.archiveName = new File(filePath).name - it.destinationDir(destDir) - }) - - return task - } - - def deleteEmptyDir = { file -> - file.listFiles().each { x -> - if (x.isDirectory()) { - if (x.listFiles().size() == 0) { - x.delete() - } else { - deleteEmptyDir(x) - if (x.listFiles().size() == 0) { - x.delete() - } - } - } - } - } -} diff --git a/source/src/main/groovy/com/kezong/fataar/Utils.groovy b/source/src/main/groovy/com/kezong/fataar/Utils.groovy deleted file mode 100644 index b905327f..00000000 --- a/source/src/main/groovy/com/kezong/fataar/Utils.groovy +++ /dev/null @@ -1,83 +0,0 @@ -package com.kezong.fataar - -import org.gradle.api.Project - -import java.lang.ref.WeakReference - -/** - * Utils - * @author kezong @since 2018-12-10 17:28 - */ -class Utils { - - private static WeakReference mProjectRef - - def static setProject(Project p) { - mProjectRef = new WeakReference<>(p) - } - - def static logError(def msg) { - Project p = mProjectRef.get() - if (p != null) { - p.logger.error("[fat-aar]${msg}") - } - } - - def static logInfo(def msg) { - Project p = mProjectRef.get() - if (p != null) { - p.logger.info("[fat-aar]${msg}") - } - } - - def static logAnytime(def msg) { - Project p = mProjectRef.get() - if (p != null) { - p.println("[fat-aar]${msg}") - } - } - - def static showDir(int indent, File file) throws IOException { - for (int i = 0; i < indent; i++) - System.out.print('-') - println(file.getName() + " " + file.size()) - if (file.isDirectory()) { - File[] files = file.listFiles() - for (int i = 0; i < files.length; i++) - showDir(indent + 4, files[i]) - } - } - - static int compareVersion(String v1, String v2) { - if (v1.equals(v2)) { - return 0 - } - String[] version1Array = v1.split("[._]") - String[] version2Array = v2.split("[._]") - int index = 0 - int minLen = Math.min(version1Array.length, version2Array.length) - long diff = 0 - - while (index < minLen - && (diff = Long.parseLong(version1Array[index]) - - Long.parseLong(version2Array[index])) == 0) { - index++ - } - if (diff == 0) { - for (int i = index; i < version1Array.length; i++) { - if (Long.parseLong(version1Array[i]) > 0) { - return 1 - } - } - - for (int i = index; i < version2Array.length; i++) { - if (Long.parseLong(version2Array[i]) > 0) { - return -1 - } - } - return 0 - } else { - return diff > 0 ? 1 : -1 - } - } -} \ No newline at end of file diff --git a/source/src/main/groovy/com/kezong/fataar/VariantProcessor.groovy b/source/src/main/groovy/com/kezong/fataar/VariantProcessor.groovy old mode 100644 new mode 100755 index 2e1eb518..63d71eda --- a/source/src/main/groovy/com/kezong/fataar/VariantProcessor.groovy +++ b/source/src/main/groovy/com/kezong/fataar/VariantProcessor.groovy @@ -1,21 +1,27 @@ package com.kezong.fataar import com.android.build.gradle.api.LibraryVariant -import com.android.build.gradle.tasks.InvokeManifestMerger +import com.android.build.gradle.internal.api.DefaultAndroidSourceSet +import com.android.build.gradle.tasks.ManifestProcessorTask import org.gradle.api.Project import org.gradle.api.Task -import org.gradle.api.artifacts.Dependency import org.gradle.api.artifacts.ResolvedArtifact import org.gradle.api.artifacts.ResolvedDependency -import org.gradle.api.internal.artifacts.DefaultResolvedArtifact +import org.gradle.api.internal.artifacts.ResolvableDependency import org.gradle.api.internal.tasks.CachingTaskDependencyResolveContext import org.gradle.api.tasks.Copy +import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.TaskDependency +import org.gradle.api.tasks.TaskProvider +import org.gradle.api.tasks.bundling.Zip + +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths /** + * Core * Processor for variant - * Created by Vigi on 2017/2/24. - * Modified by kezong on 2019/05/29 */ class VariantProcessor { @@ -23,122 +29,273 @@ class VariantProcessor { private final LibraryVariant mVariant - private Set mResolvedArtifacts = new ArrayList<>() - private Collection mAndroidArchiveLibraries = new ArrayList<>() private Collection mJarFiles = new ArrayList<>() private Collection mExplodeTasks = new ArrayList<>() - private String mGradlePluginVersion - private VersionAdapter mVersionAdapter + private TaskProvider mMergeClassTask + VariantProcessor(Project project, LibraryVariant variant) { mProject = project mVariant = variant - // gradle version - mProject.rootProject.buildscript.getConfigurations().getByName("classpath").getDependencies().each { Dependency dep -> - if (dep.group == "com.android.tools.build" && dep.name == "gradle") { - mGradlePluginVersion = dep.version - } - } - if (mGradlePluginVersion == null) { - throw new IllegalStateException("com.android.tools.build:gradle is no set in the root build.gradle file") - } - mVersionAdapter = new VersionAdapter(project, variant, mGradlePluginVersion) - } - - void addArtifacts(Set resolvedArtifacts) { - mResolvedArtifacts.addAll(resolvedArtifacts) + mVersionAdapter = new VersionAdapter(project, variant) } void addAndroidArchiveLibrary(AndroidArchiveLibrary library) { mAndroidArchiveLibraries.add(library) } - void addUnResolveArtifact(Set dependencies) { - if (dependencies != null) { - dependencies.each { - def artifact = FlavorArtifact.createFlavorArtifact(mProject, mVariant, it, mGradlePluginVersion) - mResolvedArtifacts.add(artifact) - } - } - } - void addJarFile(File jar) { mJarFiles.add(jar) } - void processVariant() { + void processVariant(Collection artifacts, + Collection dependencies, + RClassesTransform transform) { String taskPath = 'pre' + mVariant.name.capitalize() + 'Build' - Task prepareTask = mProject.tasks.findByPath(taskPath) + TaskProvider prepareTask = mProject.tasks.named(taskPath) if (prepareTask == null) { throw new RuntimeException("Can not find task ${taskPath}!") } - taskPath = 'bundle' + mVariant.name.capitalize() - Task bundleTask = mProject.tasks.findByPath(taskPath) - if (bundleTask == null) { - taskPath = 'bundle' + mVariant.name.capitalize() + "Aar" - bundleTask = mProject.tasks.findByPath(taskPath) - } - if (bundleTask == null) { - throw new RuntimeException("Can not find task ${taskPath}!") - } - processCache() - processArtifacts(prepareTask, bundleTask) + TaskProvider bundleTask = VersionAdapter.getBundleTaskProvider(mProject, mVariant.name) + preEmbed(artifacts, dependencies, prepareTask) + processArtifacts(artifacts, prepareTask, bundleTask) processClassesAndJars(bundleTask) if (mAndroidArchiveLibraries.isEmpty()) { return } processManifest() - processResourcesAndR() + processResources() processAssets() processJniLibs() - processProguardTxt(prepareTask) - RProcessor rProcessor = new RProcessor(mProject, mVariant, mAndroidArchiveLibraries, mGradlePluginVersion) - rProcessor.inject(bundleTask) + processConsumerProguard() + processGenerateProguard() + processDataBinding(bundleTask) + processRClasses(transform, bundleTask) + } + + private static void printEmbedArtifacts(Collection artifacts, + Collection dependencies) { + Collection moduleNames = artifacts.stream().map { it.moduleVersion.id.name }.collect() + dependencies.each { dependency -> + if (!moduleNames.contains(dependency.moduleName)) { + return + } + + ResolvedArtifact self = dependency.allModuleArtifacts.find { module -> + module.moduleVersion.id.name == dependency.moduleName + } + + if (self == null) { + return + } + + FatUtils.logAnytime("[embed detected][$self.type]${self.moduleVersion.id}") + moduleNames.remove(self.moduleVersion.id.name) + + dependency.allModuleArtifacts.each { artifact -> + if (!moduleNames.contains(artifact.moduleVersion.id.name)) { + return + } + if (artifact != self) { + FatUtils.logAnytime(" - [embed detected][transitive][$artifact.type]${artifact.moduleVersion.id}") + moduleNames.remove(artifact.moduleVersion.id.name) + } + } + } + + moduleNames.each { name -> + ResolvedArtifact artifact = artifacts.find { it.moduleVersion.id.name == name } + if (artifact != null) { + FatUtils.logAnytime("[embed detected][$artifact.type]${artifact.moduleVersion.id}") + } + } } - private void processCache() { - if (Utils.compareVersion(mGradlePluginVersion, "3.5.0") >= 0) { - mVersionAdapter.getLibsDirFile().deleteDir() - mVersionAdapter.getClassPathDirFiles().first().deleteDir() + private void preEmbed(Collection artifacts, + Collection dependencies, + TaskProvider prepareTask) { + TaskProvider embedTask = mProject.tasks.register("pre${mVariant.name.capitalize()}Embed") { + doFirst { + printEmbedArtifacts(artifacts, dependencies) + } + } + + prepareTask.configure { + dependsOn embedTask + } + } + + private TaskProvider configureReBundleAarTask(TaskProvider bundleTask) { + File aarOutputFile + File reBundleDir = DirectoryManager.getReBundleDirectory(mVariant) + bundleTask.configure { it -> + if (FatUtils.compareVersion(mProject.gradle.gradleVersion, "5.1") >= 0) { + aarOutputFile = new File(it.getDestinationDirectory().getAsFile().get(), it.getArchiveFileName().get()) + } else { + aarOutputFile = new File(it.destinationDir, it.archiveName) + } + + doFirst { + // Delete previously unzipped data. + reBundleDir.deleteDir() + } + + doLast { + mProject.copy { + from mProject.zipTree(aarOutputFile) + into reBundleDir + } + FatUtils.deleteEmptyDir(reBundleDir) + } + } + + String taskName = "reBundleAar${mVariant.name.capitalize()}" + TaskProvider task = mProject.getTasks().register(taskName, Zip.class) { + it.from reBundleDir + it.include "**" + if (aarOutputFile == null) { + aarOutputFile = mVersionAdapter.getOutputFile() + } + if (FatUtils.compareVersion(mProject.gradle.gradleVersion, "5.1") >= 0) { + it.getArchiveFileName().set(aarOutputFile.getName()) + it.getDestinationDirectory().set(aarOutputFile.getParentFile()) + } else { + it.archiveName = aarOutputFile.getName() + it.destinationDir = aarOutputFile.getParentFile() + } + + doLast { + FatUtils.logAnytime(" target: ${aarOutputFile.absolutePath} [${FatUtils.formatDataSize(aarOutputFile.size())}]") + } + } + + return task + } + + private void processRClasses(RClassesTransform transform, TaskProvider bundleTask) { + TaskProvider reBundleTask = configureReBundleAarTask(bundleTask) + TaskProvider transformTask = mProject.tasks.named("transformClassesWith${transform.name.capitalize()}For${mVariant.name.capitalize()}") + transformTask.configure { + it.dependsOn(mMergeClassTask) + } + if (mProject.fataar.transformR) { + transformRClasses(transform, transformTask, bundleTask, reBundleTask) + } else { + generateRClasses(bundleTask, reBundleTask) + } + } + + private void transformRClasses(RClassesTransform transform, TaskProvider transformTask, TaskProvider bundleTask, TaskProvider reBundleTask) { + transform.putTargetPackage(mVariant.name, mVariant.getApplicationId()) + transformTask.configure { + doFirst { + // library package name parsed by aar's AndroidManifest.xml + // so must put after explode tasks perform. + Collection libraryPackages = mAndroidArchiveLibraries + .stream() + .map { it.packageName } + .collect() + transform.putLibraryPackages(mVariant.name, libraryPackages); + } + } + bundleTask.configure { + finalizedBy(reBundleTask) + } + } + + private void generateRClasses(TaskProvider bundleTask, TaskProvider reBundleTask) { + RClassesGenerate rClassesGenerate = new RClassesGenerate(mProject, mVariant, mAndroidArchiveLibraries) + TaskProvider RTask = rClassesGenerate.configure(reBundleTask) + bundleTask.configure { + finalizedBy(RTask) + } + } + + /** + * copy data binding file must be do last in BundleTask, and reBundleTask will be package it. + * @param bundleTask + */ + private void processDataBinding(TaskProvider bundleTask) { + bundleTask.configure { + doLast { + for (archiveLibrary in mAndroidArchiveLibraries) { + if (archiveLibrary.dataBindingFolder != null && archiveLibrary.dataBindingFolder.exists()) { + String filePath = "${DirectoryManager.getReBundleDirectory(mVariant).path}/${archiveLibrary.dataBindingFolder.name}" + new File(filePath).mkdirs() + mProject.copy { + from archiveLibrary.dataBindingFolder + into filePath + } + } + + if (archiveLibrary.dataBindingLogFolder != null && archiveLibrary.dataBindingLogFolder.exists()) { + String filePath = "${DirectoryManager.getReBundleDirectory(mVariant).path}/${archiveLibrary.dataBindingLogFolder.name}" + new File(filePath).mkdirs() + mProject.copy { + from archiveLibrary.dataBindingLogFolder + into filePath + } + } + } + } + } + } + + // gradle < 6, return TaskDependency + // gradle >= 6, return TaskDependencyContainer + static def getTaskDependency(ResolvedArtifact artifact) { + try { + return artifact.buildDependencies + } catch(MissingPropertyException ignore) { + // since gradle 6.8.0, property is changed; + return artifact.builtBy } } /** * exploded artifact files */ - private void processArtifacts(Task prepareTask, Task bundleTask) { - for (final DefaultResolvedArtifact artifact in mResolvedArtifacts) { - if (FatLibraryPlugin.ARTIFACT_TYPE_JAR == artifact.type) { + private void processArtifacts(Collection artifacts, TaskProvider prepareTask, TaskProvider bundleTask) { + if (artifacts == null) { + return + } + for (final ResolvedArtifact artifact in artifacts) { + if (FatAarPlugin.ARTIFACT_TYPE_JAR == artifact.type) { addJarFile(artifact.file) - } else if (FatLibraryPlugin.ARTIFACT_TYPE_AAR == artifact.type) { + } else if (FatAarPlugin.ARTIFACT_TYPE_AAR == artifact.type) { AndroidArchiveLibrary archiveLibrary = new AndroidArchiveLibrary(mProject, artifact, mVariant.name) addAndroidArchiveLibrary(archiveLibrary) Set dependencies - if (artifact.buildDependencies instanceof TaskDependency) { + + if (getTaskDependency(artifact) instanceof TaskDependency) { dependencies = artifact.buildDependencies.getDependencies() } else { CachingTaskDependencyResolveContext context = new CachingTaskDependencyResolveContext() - artifact.buildDependencies.visitDependencies(context) + getTaskDependency(artifact).visitDependencies(context) if (context.queue.size() == 0) { dependencies = new HashSet<>() } else { dependencies = context.queue.getFirst().getDependencies() } } - archiveLibrary.getRootFolder().deleteDir() final def zipFolder = archiveLibrary.getRootFolder() zipFolder.mkdirs() def group = artifact.getModuleVersion().id.group.capitalize() def name = artifact.name.capitalize() String taskName = "explode${group}${name}${mVariant.name.capitalize()}" - Task explodeTask = mProject.tasks.create(name: taskName, type: Copy) { + Task explodeTask = mProject.tasks.create(taskName, Copy) { from mProject.zipTree(artifact.file.absolutePath) into zipFolder + + doFirst { + // Delete previously extracted data. + zipFolder.deleteDir() + } } if (dependencies.size() == 0) { @@ -148,7 +305,9 @@ class VariantProcessor { } Task javacTask = mVersionAdapter.getJavaCompileTask() javacTask.dependsOn(explodeTask) - bundleTask.dependsOn(explodeTask) + bundleTask.configure { + dependsOn(explodeTask) + } mExplodeTasks.add(explodeTask) } } @@ -158,62 +317,113 @@ class VariantProcessor { * merge manifest */ private void processManifest() { - Task processManifestTask = mVersionAdapter.getProcessManifest() - File manifestOutputBackup - if (mGradlePluginVersion != null && Utils.compareVersion(mGradlePluginVersion, "3.3.0") >= 0) { - manifestOutputBackup = mProject.file("${mProject.buildDir.path}/intermediates/library_manifest/${mVariant.name}/AndroidManifest.xml") + ManifestProcessorTask processManifestTask = mVersionAdapter.getProcessManifest() + + File manifestOutput + if (FatUtils.compareVersion(VersionAdapter.AGPVersion, "4.2.0-alpha07") >= 0) { + manifestOutput = mProject.file("${mProject.buildDir.path}/intermediates/merged_manifest/${mVariant.name}/AndroidManifest.xml") + } else if (FatUtils.compareVersion(VersionAdapter.AGPVersion, "3.3.0") >= 0) { + manifestOutput = mProject.file("${mProject.buildDir.path}/intermediates/library_manifest/${mVariant.name}/AndroidManifest.xml") } else { - manifestOutputBackup = mProject.file(processManifestTask.getManifestOutputDirectory().absolutePath + '/AndroidManifest.xml') - } - InvokeManifestMerger manifestsMergeTask = mProject.tasks.create("merge${mVariant.name.capitalize()}Manifest", LibraryManifestMerger.class) - manifestsMergeTask.setGradleVersion(mProject.getGradle().getGradleVersion()) - manifestsMergeTask.setGradlePluginVersion(mGradlePluginVersion) - manifestsMergeTask.setVariantName(mVariant.name) - manifestsMergeTask.setMainManifestFile(manifestOutputBackup) - List list = new ArrayList<>() + manifestOutput = mProject.file(processManifestTask.getManifestOutputDirectory().absolutePath + "/AndroidManifest.xml") + } + + final List inputManifests = new ArrayList<>() for (archiveLibrary in mAndroidArchiveLibraries) { - list.add(archiveLibrary.getManifest()) - } - manifestsMergeTask.setSecondaryManifestFiles(list) - manifestsMergeTask.setOutputFile(manifestOutputBackup) - manifestsMergeTask.dependsOn processManifestTask - manifestsMergeTask.doFirst { - List existFiles = new ArrayList<>() - manifestsMergeTask.getSecondaryManifestFiles().each { - if (it.exists()) { - existFiles.add(it) - } - } - manifestsMergeTask.setSecondaryManifestFiles(existFiles) + inputManifests.add(archiveLibrary.getManifest()) } - mExplodeTasks.each { it -> - manifestsMergeTask.dependsOn it + TaskProvider manifestsMergeTask = mProject.tasks.register("merge${mVariant.name.capitalize()}Manifest", LibraryManifestMerger) { + setGradleVersion(mProject.getGradle().getGradleVersion()) + setGradlePluginVersion(VersionAdapter.AGPVersion) + setMainManifestFile(manifestOutput) + setSecondaryManifestFiles(inputManifests) + setOutputFile(manifestOutput) } - processManifestTask.finalizedBy manifestsMergeTask + processManifestTask.dependsOn(mExplodeTasks) + processManifestTask.inputs.files(inputManifests) + processManifestTask.doLast { + // Merge manifests + manifestsMergeTask.get().doTaskAction() + } } - private Task handleClassesMergeTask(final boolean isMinifyEnabled) { - final Task task = mProject.tasks.create(name: 'mergeClasses' - + mVariant.name.capitalize()) - task.doFirst { - def dustDir = mVersionAdapter.getClassPathDirFiles().first() + private TaskProvider handleClassesMergeTask(final boolean isMinifyEnabled) { + final TaskProvider task = mProject.tasks.register("mergeClasses" + mVariant.name.capitalize()) { + dependsOn(mExplodeTasks) + dependsOn(mVersionAdapter.getJavaCompileTask()) + try { + // main lib maybe not use kotlin + TaskProvider kotlinCompile = mProject.tasks.named("compile${mVariant.name.capitalize()}Kotlin") + if (kotlinCompile != null) { + dependsOn(kotlinCompile) + } + } catch(Exception ignore) { + + } + + inputs.files(mAndroidArchiveLibraries.stream().map { it.classesJarFile }.collect()) + .withPathSensitivity(PathSensitivity.RELATIVE) if (isMinifyEnabled) { - ExplodedHelper.processClassesJarInfoClasses(mProject, mAndroidArchiveLibraries, dustDir) - ExplodedHelper.processLibsIntoClasses(mProject, mAndroidArchiveLibraries, mJarFiles, dustDir) - } else { - ExplodedHelper.processClassesJarInfoClasses(mProject, mAndroidArchiveLibraries, dustDir) + inputs.files(mAndroidArchiveLibraries.stream().map { it.localJars }.collect()) + .withPathSensitivity(PathSensitivity.RELATIVE) + inputs.files(mJarFiles).withPathSensitivity(PathSensitivity.RELATIVE) + } + File outputDir = DirectoryManager.getMergeClassDirectory(mVariant) + File javacDir = mVersionAdapter.getClassPathDirFiles().first() + outputs.dir(outputDir) + + doFirst { + // Extract relative paths and delete previous output. + def pathsToDelete = new ArrayList() + mProject.fileTree(outputDir).forEach { + pathsToDelete.add(Paths.get(outputDir.absolutePath).relativize(Paths.get(it.absolutePath))) + } + outputDir.deleteDir() + // Delete output files from javac dir. + pathsToDelete.forEach { + Files.deleteIfExists(Paths.get("$javacDir.absolutePath/${it.toString()}")) + } + } + + doLast { + ExplodedHelper.processClassesJarInfoClasses(mProject, mAndroidArchiveLibraries, outputDir) + if (isMinifyEnabled) { + ExplodedHelper.processLibsIntoClasses(mProject, mAndroidArchiveLibraries, mJarFiles, outputDir) + } + + mProject.copy { + from outputDir + into javacDir + exclude 'META-INF/' + } + + mProject.copy { + from outputDir.absolutePath + "/META-INF" + into DirectoryManager.getKotlinMetaDirectory(mVariant) + include '*.kotlin_module' + } } } return task } - private Task handleJarMergeTask() { - final Task task = mProject.tasks.create(name: 'mergeJars' - + mVariant.name.capitalize()) - task.doFirst { - ExplodedHelper.processLibsIntoLibs(mProject, mAndroidArchiveLibraries, mJarFiles, mVersionAdapter.getLibsDirFile()) + private TaskProvider handleJarMergeTask(final TaskProvider syncLibTask) { + final TaskProvider task = mProject.tasks.register("mergeJars" + mVariant.name.capitalize()) { + dependsOn(mExplodeTasks) + dependsOn(mVersionAdapter.getJavaCompileTask()) + mustRunAfter(syncLibTask) + + inputs.files(mAndroidArchiveLibraries.stream().map { it.libsFolder }.collect()) + .withPathSensitivity(PathSensitivity.RELATIVE) + inputs.files(mJarFiles).withPathSensitivity(PathSensitivity.RELATIVE) + def outputDir = mVersionAdapter.getLibsDirFile() + outputs.dir(outputDir) + + doFirst { + ExplodedHelper.processLibsIntoLibs(mProject, mAndroidArchiveLibraries, mJarFiles, outputDir) + } } return task } @@ -221,76 +431,56 @@ class VariantProcessor { /** * merge classes and jars */ - private void processClassesAndJars(Task bundleTask) { + private void processClassesAndJars(TaskProvider bundleTask) { boolean isMinifyEnabled = mVariant.getBuildType().isMinifyEnabled() - if (isMinifyEnabled) { - //merge proguard file - for (archiveLibrary in mAndroidArchiveLibraries) { - List thirdProguardFiles = archiveLibrary.proguardRules - for (File file : thirdProguardFiles) { - if (file.exists()) { - Utils.logInfo('add proguard file: ' + file.absolutePath) - mProject.android.getDefaultConfig().proguardFile(file) - } - } - } - } - String taskPath = mVersionAdapter.getSyncLibJarsTaskPath() - Task syncLibTask = mProject.tasks.findByPath(taskPath) - if (syncLibTask == null) { - throw new RuntimeException("Can not find task ${taskPath}!") - } + TaskProvider syncLibTask = mProject.tasks.named(mVersionAdapter.getSyncLibJarsTaskPath()) + TaskProvider extractAnnotationsTask = mProject.tasks.named("extract${mVariant.name.capitalize()}Annotations") - Task javacTask = mVersionAdapter.getJavaCompileTask() - Task mergeClasses = handleClassesMergeTask(isMinifyEnabled) - syncLibTask.dependsOn(mergeClasses) - mExplodeTasks.each { it -> - mergeClasses.dependsOn it + mMergeClassTask = handleClassesMergeTask(isMinifyEnabled) + syncLibTask.configure { + dependsOn(mMergeClassTask) + inputs.files(mAndroidArchiveLibraries.stream().map { it.libsFolder }.collect()) + .withPathSensitivity(PathSensitivity.RELATIVE) + inputs.files(mJarFiles).withPathSensitivity(PathSensitivity.RELATIVE) + } + extractAnnotationsTask.configure { + mustRunAfter(mMergeClassTask) } - mergeClasses.dependsOn(javacTask) if (!isMinifyEnabled) { - Task mergeJars = handleJarMergeTask() - mergeJars.mustRunAfter(syncLibTask) - bundleTask.dependsOn(mergeJars) - mExplodeTasks.each { it -> - mergeJars.dependsOn it + TaskProvider mergeJars = handleJarMergeTask(syncLibTask) + bundleTask.configure { + dependsOn(mergeJars) } - mergeJars.dependsOn(javacTask) } } /** - * merge R.txt(actually is to fix issue caused by provided configuration) and res - * - * Here I have to inject res into "main" instead of "variant.name". - * To avoid the res from embed dependencies being used, once they have the same res Id with main res. + * merge R.txt (actually is to fix issue caused by provided configuration) and res * * Now the same res Id will cause a build exception: Duplicate resources, to encourage you to change res Id. * Adding "android.disableResourceValidation=true" to "gradle.properties" can do a trick to skip the exception, but is not recommended. */ - private void processResourcesAndR() { - String taskPath = 'generate' + mVariant.name.capitalize() + 'Resources' - Task resourceGenTask = mProject.tasks.findByPath(taskPath) + private void processResources() { + String taskPath = "generate" + mVariant.name.capitalize() + "Resources" + TaskProvider resourceGenTask = mProject.tasks.named(taskPath) if (resourceGenTask == null) { throw new RuntimeException("Can not find task ${taskPath}!") } - resourceGenTask.doFirst { - for (archiveLibrary in mAndroidArchiveLibraries) { - mProject.android.sourceSets.each { - if (it.name == mVariant.name) { - Utils.logInfo("Merge resource,Library res:${archiveLibrary.resFolder}") - it.res.srcDir(archiveLibrary.resFolder) + resourceGenTask.configure { + dependsOn(mExplodeTasks) + + mProject.android.sourceSets.each { DefaultAndroidSourceSet sourceSet -> + if (sourceSet.name == mVariant.name) { + for (archiveLibrary in mAndroidArchiveLibraries) { + FatUtils.logInfo("Merge resource,Library res:${archiveLibrary.resFolder}") + sourceSet.res.srcDir(archiveLibrary.resFolder) } } } } - - mExplodeTasks.each { it -> - resourceGenTask.dependsOn(it) - } } /** @@ -304,21 +494,19 @@ class VariantProcessor { throw new RuntimeException("Can not find task in variant.getMergeAssets()!") } + assetsTask.dependsOn(mExplodeTasks) assetsTask.doFirst { - for (archiveLibrary in mAndroidArchiveLibraries) { - if (archiveLibrary.assetsFolder != null && archiveLibrary.assetsFolder.exists()) { - mProject.android.sourceSets.each { - if (it.name == mVariant.name) { + mProject.android.sourceSets.each { + if (it.name == mVariant.name) { + for (archiveLibrary in mAndroidArchiveLibraries) { + if (archiveLibrary.assetsFolder != null && archiveLibrary.assetsFolder.exists()) { + FatUtils.logInfo("Merge assets,Library assets folder:${archiveLibrary.assetsFolder}") it.assets.srcDir(archiveLibrary.assetsFolder) } } } } } - - mExplodeTasks.each { it -> - assetsTask.dependsOn it - } } /** @@ -326,59 +514,94 @@ class VariantProcessor { */ private void processJniLibs() { String taskPath = 'merge' + mVariant.name.capitalize() + 'JniLibFolders' - Task mergeJniLibsTask = mProject.tasks.findByPath(taskPath) + TaskProvider mergeJniLibsTask = mProject.tasks.named(taskPath) if (mergeJniLibsTask == null) { throw new RuntimeException("Can not find task ${taskPath}!") } - mergeJniLibsTask.doFirst { - for (archiveLibrary in mAndroidArchiveLibraries) { - if (archiveLibrary.jniFolder != null && archiveLibrary.jniFolder.exists()) { - mProject.android.sourceSets.each { - if (it.name == mVariant.name) { - it.jniLibs.srcDir(archiveLibrary.jniFolder) + mergeJniLibsTask.configure { + dependsOn(mExplodeTasks) + + doFirst { + for (archiveLibrary in mAndroidArchiveLibraries) { + if (archiveLibrary.jniFolder != null && archiveLibrary.jniFolder.exists()) { + mProject.android.sourceSets.each { + if (it.name == mVariant.name) { + it.jniLibs.srcDir(archiveLibrary.jniFolder) + } } } } } } - - mExplodeTasks.each { it -> - mergeJniLibsTask.dependsOn it - } } /** - * fixme * merge proguard.txt */ - private void processProguardTxt(Task prepareTask) { - String taskPath = 'merge' + mVariant.name.capitalize() + 'ConsumerProguardFiles' - Task mergeFileTask = mProject.tasks.findByPath(taskPath) + private void processConsumerProguard() { + String mergeTaskName = 'merge' + mVariant.name.capitalize() + 'ConsumerProguardFiles' + TaskProvider mergeFileTask = mProject.tasks.named(mergeTaskName) if (mergeFileTask == null) { - throw new RuntimeException("Can not find task ${taskPath}!") + throw new RuntimeException("Can not find task ${mergeTaskName}!") } - for (archiveLibrary in mAndroidArchiveLibraries) { - List thirdProguardFiles = archiveLibrary.proguardRules - for (File file : thirdProguardFiles) { - if (file.exists()) { - Utils.logInfo('add proguard file: ' + file.absolutePath) - mergeFileTask.getInputs().file(file) + + mergeFileTask.configure { + dependsOn(mExplodeTasks) + doLast { + try { + Collection files = mAndroidArchiveLibraries.stream().map { it.proguardRules }.collect() + File of + if (outputFile instanceof File) { + of = outputFile + } else { + // RegularFileProperty.class + of = outputFile.get().asFile + } + FatUtils.mergeFiles(files, of) + } catch (Exception e) { + FatUtils.logAnytime(("If you see this error message, please submit issue to " + + "https://github.com/kezong/fat-aar-android/issues with version of AGP and Gradle. Thank you.") + ) + e.printStackTrace() } } } - mergeFileTask.doFirst { - def proguardFiles = mergeFileTask.getInputFiles() - for (archiveLibrary in mAndroidArchiveLibraries) { - List thirdProguardFiles = archiveLibrary.proguardRules - for (File file : thirdProguardFiles) { - if (file.exists()) { - Utils.logInfo('add proguard file: ' + file.absolutePath) - proguardFiles.add(file) + } + + /** + * merge consumer proguard to generate proguard + * @since AGP 3.6 + */ + private void processGenerateProguard() { + TaskProvider mergeGenerateProguardTask + try { + String mergeName = 'merge' + mVariant.name.capitalize() + 'GeneratedProguardFiles' + mergeGenerateProguardTask = mProject.tasks.named(mergeName) + } catch(Exception ignore) { + return + } + + mergeGenerateProguardTask.configure { + dependsOn(mExplodeTasks) + doLast { + try { + Collection files = mAndroidArchiveLibraries.stream().map { it.proguardRules }.collect() + File of + if (outputFile instanceof File) { + of = outputFile + } else { + // RegularFileProperty.class + of = outputFile.get().asFile } + FatUtils.mergeFiles(files, of) + } catch (Exception e) { + FatUtils.logAnytime(("If you see this error message, please submit issue to " + + "https://github.com/kezong/fat-aar-android/issues with version of AGP and Gradle. Thank you.") + ) + e.printStackTrace() } } } - mergeFileTask.dependsOn prepareTask } } diff --git a/source/src/main/groovy/com/kezong/fataar/VersionAdapter.groovy b/source/src/main/groovy/com/kezong/fataar/VersionAdapter.groovy index 5dd9bf05..afbf87ef 100644 --- a/source/src/main/groovy/com/kezong/fataar/VersionAdapter.groovy +++ b/source/src/main/groovy/com/kezong/fataar/VersionAdapter.groovy @@ -1,33 +1,32 @@ package com.kezong.fataar import com.android.build.gradle.api.LibraryVariant +import com.android.build.gradle.tasks.ManifestProcessorTask import org.gradle.api.Project import org.gradle.api.Task +import org.gradle.api.UnknownTaskException import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.tasks.TaskProvider + +import java.lang.reflect.Field -/** - * @author kezong on 2019/7/16. - */ class VersionAdapter { private Project mProject private LibraryVariant mVariant - private String mGradlePluginVersion - - VersionAdapter(Project project, LibraryVariant variant, String version) { + VersionAdapter(Project project, LibraryVariant variant) { mProject = project mVariant = variant - mGradlePluginVersion = version } ConfigurableFileCollection getClassPathDirFiles() { ConfigurableFileCollection classpath - if (Utils.compareVersion(mGradlePluginVersion, "3.5.0") >= 0) { + if (FatUtils.compareVersion(AGPVersion, "3.5.0") >= 0) { classpath = mProject.files("${mProject.buildDir.path}/intermediates/" + "javac/${mVariant.name}/classes") - } else if (Utils.compareVersion(mGradlePluginVersion, "3.2.0") >= 0) { // >= Versions 3.2.X + } else if (FatUtils.compareVersion(AGPVersion, "3.2.0") >= 0) { // >= Versions 3.2.X classpath = mProject.files("${mProject.buildDir.path}/intermediates/" + "javac/${mVariant.name}/compile${mVariant.name.capitalize()}JavaWithJavac/classes") } else { // Versions 3.0.x and 3.1.x @@ -37,10 +36,13 @@ class VersionAdapter { } ConfigurableFileCollection getRClassPath() { - if (Utils.compareVersion(mGradlePluginVersion, "3.5.0") >= 0) { + if (FatUtils.compareVersion(AGPVersion, "4.1.0") >= 0) { + return mProject.files("${mProject.buildDir.path}/intermediates/" + "compile_r_class_jar/" + + "${mVariant.name}") + } else if (FatUtils.compareVersion(AGPVersion, "3.5.0") >= 0) { return mProject.files("${mProject.buildDir.path}/intermediates/" + "compile_only_not_namespaced_r_class_jar/" + "${mVariant.name}") - } else if (Utils.compareVersion(mGradlePluginVersion, "3.3.0") >= 0) { + } else if (FatUtils.compareVersion(AGPVersion, "3.3.0") >= 0) { return mProject.files("${mProject.buildDir.path}/intermediates/" + "compile_only_not_namespaced_r_class_jar/" + "${mVariant.name}/generate${mVariant.name.capitalize()}RFile") } else { @@ -49,25 +51,25 @@ class VersionAdapter { } File getLibsDirFile() { - if (Utils.compareVersion(mGradlePluginVersion, '3.6.0') >= 0) { - return mProject.file("${mProject.buildDir.path}/intermediates/aar_libs_directory/${mVariant.dirName}/libs") - } else if (Utils.compareVersion(mGradlePluginVersion, '3.1.0') >= 0) { + if (FatUtils.compareVersion(AGPVersion, '3.6.0') >= 0) { + return mProject.file("${mProject.buildDir.path}/intermediates/aar_libs_directory/${mVariant.name}/libs") + } else if (FatUtils.compareVersion(AGPVersion, '3.1.0') >= 0) { return mProject.file(mProject.buildDir.path + '/intermediates/packaged-classes/' + mVariant.dirName + "/libs") } else { - return mProject.file(mProject.buildDir.path + '/intermediates/bundles/' + mVariant.dirName + "/libs") + return mProject.file(mProject.buildDir.path + '/intermediates/bundles/' + mVariant.name + "/libs") } } Task getJavaCompileTask() { - if (Utils.compareVersion(mGradlePluginVersion, "3.3.0") >= 0) { + if (FatUtils.compareVersion(AGPVersion, "3.3.0") >= 0) { return mVariant.getJavaCompileProvider().get() } else { return mVariant.getJavaCompiler() } } - Task getProcessManifest() { - if (Utils.compareVersion(mGradlePluginVersion, "3.3.0") >= 0) { + ManifestProcessorTask getProcessManifest() { + if (FatUtils.compareVersion(AGPVersion, "3.3.0") >= 0) { return mVariant.getOutputs().first().getProcessManifestProvider().get() } else { return mVariant.getOutputs().first().getProcessManifest() @@ -75,7 +77,7 @@ class VersionAdapter { } Task getMergeAssets() { - if (Utils.compareVersion(mGradlePluginVersion, "3.3.0") >= 0) { + if (FatUtils.compareVersion(AGPVersion, "3.3.0") >= 0) { return mVariant.getMergeAssetsProvider().get() } else { return mVariant.getMergeAssets() @@ -88,9 +90,9 @@ class VersionAdapter { */ File getLocalSymbolFile() { // > 3.6.0, R.txt contains remote resources, so we use R-def.txt - if (Utils.compareVersion(mGradlePluginVersion, "3.6.0") >= 0) { + if (FatUtils.compareVersion(AGPVersion, "3.6.0") >= 0) { return mProject.file(mProject.buildDir.path + '/intermediates/local_only_symbol_list/' + mVariant.name + "/R-def.txt") - } else if (Utils.compareVersion(mGradlePluginVersion, "3.1.0") >= 0) { + } else if (FatUtils.compareVersion(AGPVersion, "3.1.0") >= 0) { return mProject.file(mProject.buildDir.path + '/intermediates/symbols/' + mVariant.dirName + "/R.txt") } else { return mProject.file(mProject.buildDir.path + '/intermediates/bundles/' + mVariant.name + "/R.txt") @@ -98,10 +100,52 @@ class VersionAdapter { } String getSyncLibJarsTaskPath() { - if (Utils.compareVersion(mGradlePluginVersion, '3.6.0') >= 0) { + if (FatUtils.compareVersion(AGPVersion, '3.6.0') >= 0) { return "sync${mVariant.name.capitalize()}LibJars" } else { return "transformClassesAndResourcesWithSyncLibJarsFor${mVariant.name.capitalize()}" } } + + File getOutputFile() { + return outputFile(mProject, mVariant, AGPVersion) + } + + static File getOutputFile(Project project, LibraryVariant variant) { + if (FatUtils.compareVersion(AGPVersion, "3.3.0") >= 0) { + String fileName = variant.outputs.first().outputFileName + if (FatUtils.compareVersion(project.gradle.gradleVersion, "5.1") >= 0) { + return new File(variant.getPackageLibraryProvider().get().getDestinationDirectory().getAsFile().get(), fileName) + } else { + return new File(variant.getPackageLibraryProvider().get().getDestinationDir(), fileName) + } + } else { + return variant.outputs.first().outputFile + } + } + + static TaskProvider getBundleTaskProvider(Project project, String variantName) throws UnknownTaskException { + def taskPath = "bundle" + variantName.capitalize() + TaskProvider bundleTask + try { + bundleTask = project.tasks.named(taskPath) + } catch (UnknownTaskException ignored) { + taskPath += "Aar" + bundleTask = project.tasks.named(taskPath) + } + return bundleTask + } + + static String getAGPVersion() { + // AGP 3.6+ + try { + Class aClass = Class.forName("com.android.Version") + Field version = aClass.getDeclaredField("ANDROID_GRADLE_PLUGIN_VERSION") + return version.get(aClass) + } catch (Throwable ignore) { + Class aClass = Class.forName("com.android.builder.model.Version") + Field version = aClass.getDeclaredField("ANDROID_GRADLE_PLUGIN_VERSION") + return version.get(aClass) + } + } } diff --git a/source/src/main/java/com/kezong/fataar/AndroidArchiveLibrary.java b/source/src/main/java/com/kezong/fataar/AndroidArchiveLibrary.java index f0006912..6950634a 100644 --- a/source/src/main/java/com/kezong/fataar/AndroidArchiveLibrary.java +++ b/source/src/main/java/com/kezong/fataar/AndroidArchiveLibrary.java @@ -13,10 +13,6 @@ import javax.xml.parsers.DocumentBuilderFactory; -/** - * Created by Vigi on 2017/2/16. - * Modify by kezong on 2019/7/16. - */ public class AndroidArchiveLibrary { private final Project mProject; @@ -25,6 +21,8 @@ public class AndroidArchiveLibrary { private final String mVariantName; + private String mPackageName; + public AndroidArchiveLibrary(Project project, ResolvedArtifact artifact, String variantName) { if (!"aar".equals(artifact.getType())) { throw new IllegalArgumentException("artifact must be aar type!"); @@ -65,13 +63,17 @@ public File getAssetsFolder() { return new File(getRootFolder(), "assets"); } + public File getLibsFolder() { + return new File(getRootFolder(), "libs"); + } + public File getClassesJarFile() { return new File(getRootFolder(), "classes.jar"); } public Collection getLocalJars() { List localJars = new ArrayList<>(); - File[] jarList = new File(getRootFolder(), "libs").listFiles(); + File[] jarList = getLibsFolder().listFiles(); if (jarList != null) { for (File jars : jarList) { if (jars.isFile() && jars.getName().endsWith(".jar")) { @@ -99,32 +101,38 @@ public File getLintJar() { return new File(getRootFolder(), "lint.jar"); } - public List getProguardRules() { - List list = new ArrayList<>(); - list.add(new File(getRootFolder(), "proguard-rules.pro")); - list.add(new File(getRootFolder(), "proguard-project.txt")); - return list; + public File getProguardRules() { + return new File(getRootFolder(), "proguard.txt"); } public File getSymbolFile() { return new File(getRootFolder(), "R.txt"); } - public String getPackageName() { - String packageName = null; - File manifestFile = getManifest(); - if (manifestFile.exists()) { - try { - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - Document doc = dbf.newDocumentBuilder().parse(manifestFile); - Element element = doc.getDocumentElement(); - packageName = element.getAttribute("package"); - } catch (Exception e) { - e.printStackTrace(); + public synchronized String getPackageName() { + if (mPackageName == null) { + File manifestFile = getManifest(); + if (manifestFile.exists()) { + try { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + Document doc = dbf.newDocumentBuilder().parse(manifestFile); + Element element = doc.getDocumentElement(); + mPackageName = element.getAttribute("package"); + } catch (Exception e) { + e.printStackTrace(); + } + } else { + throw new RuntimeException(getName() + " module's AndroidManifest not found"); } - } else { - throw new RuntimeException(getName() + " module's AndroidManifest not found"); } - return packageName; + return mPackageName; + } + + public File getDataBindingFolder() { + return new File(getRootFolder(), "data-binding"); + } + + public File getDataBindingLogFolder() { + return new File(getRootFolder(), "data-binding-base-class-log"); } } diff --git a/source/src/main/java/com/kezong/fataar/LibraryManifestMerger.java b/source/src/main/java/com/kezong/fataar/LibraryManifestMerger.java index 78471a5a..009b6b58 100644 --- a/source/src/main/java/com/kezong/fataar/LibraryManifestMerger.java +++ b/source/src/main/java/com/kezong/fataar/LibraryManifestMerger.java @@ -1,15 +1,14 @@ package com.kezong.fataar; import com.android.build.gradle.internal.LoggerWrapper; -import com.android.build.gradle.tasks.InvokeManifestMerger; import com.android.manifmerger.ManifestMerger2; import com.android.manifmerger.ManifestProvider; import com.android.manifmerger.MergingReport; import com.android.utils.ILogger; import org.apache.tools.ant.BuildException; +import org.gradle.api.DefaultTask; import org.gradle.api.tasks.TaskAction; -import org.gradle.workers.WorkerExecutor; import java.io.BufferedWriter; import java.io.File; @@ -21,14 +20,19 @@ /** * ManifestMerger for Library - * @author kezong on 2019/7/8. */ -public class LibraryManifestMerger extends InvokeManifestMerger { +public class LibraryManifestMerger extends DefaultTask { private String mGradlePluginVersion; private String mGradleVersion; + private File mMainManifestFile; + + private List mSecondaryManifestFiles; + + private File mOutputFile; + public void setGradlePluginVersion(String gradlePluginVersion) { mGradlePluginVersion = gradlePluginVersion; } @@ -41,23 +45,24 @@ protected void doTaskAction() { try { doFullTaskAction(); } catch (Exception e) { - e.printStackTrace(); System.out.println("Gradle Plugin Version:" + mGradlePluginVersion); System.out.println("Gradle Version:" + mGradleVersion); - System.out.println("If you see this error message, please submit issue to " + - "https://github.com/kezong/fat-aar-android/issues with Gradle version. Thank you."); + throw new RuntimeException(e.getMessage()); } } @TaskAction protected void doFullTaskAction() throws ManifestMerger2.MergeFailureException, IOException { ILogger iLogger = new LoggerWrapper(getLogger()); - ManifestMerger2.Invoker mergerInvoker = ManifestMerger2. + ManifestMerger2.Invoker mergerInvoker = ManifestMerger2. newMerger(getMainManifestFile(), iLogger, ManifestMerger2.MergeType.LIBRARY); List secondaryManifestFiles = getSecondaryManifestFiles(); List manifestProviders = new ArrayList<>(); if (secondaryManifestFiles != null) { for (final File file : secondaryManifestFiles) { + if (!file.exists()) { + continue; + } manifestProviders.add(new ManifestProvider() { @Override public File getManifest() { @@ -89,7 +94,27 @@ public String getName() { writer.close(); } - public WorkerExecutor getWorkerExecutor() { - return null; + public File getMainManifestFile() { + return mMainManifestFile; + } + + public void setMainManifestFile(File mainManifestFile) { + this.mMainManifestFile = mainManifestFile; + } + + public List getSecondaryManifestFiles() { + return mSecondaryManifestFiles; + } + + public void setSecondaryManifestFiles(List secondaryManifestFiles) { + this.mSecondaryManifestFiles = secondaryManifestFiles; + } + + public File getOutputFile() { + return mOutputFile; + } + + public void setOutputFile(File outputFile) { + this.mOutputFile = outputFile; } } diff --git a/source/src/main/java/com/kezong/fataar/RClassesTransform.java b/source/src/main/java/com/kezong/fataar/RClassesTransform.java new file mode 100644 index 00000000..c21a72d0 --- /dev/null +++ b/source/src/main/java/com/kezong/fataar/RClassesTransform.java @@ -0,0 +1,222 @@ +package com.kezong.fataar; + +import com.android.build.api.transform.DirectoryInput; +import com.android.build.api.transform.Format; +import com.android.build.api.transform.QualifiedContent; +import com.android.build.api.transform.Status; +import com.android.build.api.transform.Transform; +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.pipeline.TransformManager; +import com.google.common.collect.ImmutableSet; + +import org.gradle.api.Project; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.stream.Collectors; + +import javassist.CannotCompileException; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.NotFoundException; +import javassist.bytecode.ClassFile; +import javassist.bytecode.ConstPool; +import kotlin.io.FilesKt; + +/** + * com.sdk.R + * |-- com.lib1.R + * |-- com.lib2.R + *

+ * rename com.lib1.R and com.lib2.R to com.sdk.R + */ +public class RClassesTransform extends Transform { + + private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); + + private final ExecutorService executor = Executors.newFixedThreadPool(CPU_COUNT + 1); + + private final List> futures = new ArrayList<>(); + + private final Project project; + + private final Map targetPackageMap = new HashMap<>(); + + private final Map> libraryPackageMap = new HashMap<>(); + + public RClassesTransform(final Project project) { + this.project = project; + } + + /** + * Different variants have different package names. + * So targetPackageName must set after evaluate + * @param variantName variant name + * @param targetPackage main module's package name + */ + public void putTargetPackage(String variantName, String targetPackage) { + targetPackageMap.put(variantName, targetPackage); + } + + /** + * library packages name must set after exploded task perform + * @param variantName variant name + * @param libraryPackages sub module's package name, read from AndroidManifest.xml + */ + public void putLibraryPackages(String variantName, Collection libraryPackages) { + libraryPackageMap.put(variantName, libraryPackages); + } + + @Override + public String getName() { + return "transformR"; + } + + @Override + public Set getInputTypes() { + return TransformManager.CONTENT_CLASS; + } + + @Override + public Set getScopes() { + return ImmutableSet.of(QualifiedContent.Scope.PROJECT); + } + + @Override + public boolean isIncremental() { + return true; + } + + @Override + public void transform(TransformInvocation transformInvocation) throws InterruptedException, IOException { + long startTime = System.currentTimeMillis(); + Map transformTable = buildTransformTable(transformInvocation.getContext().getVariantName()); + final boolean isIncremental = transformInvocation.isIncremental() && this.isIncremental(); + final TransformOutputProvider outputProvider = transformInvocation.getOutputProvider(); + + if (!isIncremental) { + outputProvider.deleteAll(); + } + + final File outputDir = outputProvider.getContentLocation("classes", getOutputTypes(), getScopes(), Format.DIRECTORY); + + try { + for (final TransformInput input : transformInvocation.getInputs()) { + for (final DirectoryInput directoryInput : input.getDirectoryInputs()) { + final File directoryFile = directoryInput.getFile(); + final ClassPool classPool = new ClassPool(); + classPool.insertClassPath(directoryFile.getAbsolutePath()); + + for (final File originalClassFile : getChangedClassesList(directoryInput)) { + if (!originalClassFile.getPath().endsWith(".class")) { + continue; // ignore anything that is not class file + } + + Future submit = executor.submit(() -> { + try { + File relative = FilesKt.relativeTo(originalClassFile, directoryFile); + String className = filePathToClassname(relative); + final CtClass ctClass = classPool.get(className); + if (transformTable != null) { + ClassFile classFile = ctClass.getClassFile(); + ConstPool constPool = classFile.getConstPool(); + constPool.renameClass(transformTable); + } + ctClass.writeFile(outputDir.getAbsolutePath()); + } catch (CannotCompileException | NotFoundException | IOException e) { + e.printStackTrace(); + } + }); + + futures.add(submit); + } + } + } + } catch (NotFoundException e) { + throw new RuntimeException(e); + } + + + for (Future future : futures) { + try { + future.get(); + } catch (ExecutionException e) { + e.printStackTrace(); + } + } + + futures.clear(); + + long endTime = System.currentTimeMillis(); + project.getLogger().info("the task cost " + + (endTime - startTime) + + "ms"); + } + + private Map buildTransformTable(String variantName) { + String targetPackage = targetPackageMap.get(variantName); + Collection libraryPackages = libraryPackageMap.get(variantName); + if (targetPackage == null || libraryPackages == null) { + return null; + } + + final List resourceTypes = Arrays.asList("anim", "animator", "array", "attr", "bool", "color", "dimen", + "drawable", "font", "fraction", "id", "integer", "interpolator", "layout", "menu", "mipmap", "navigation", + "plurals", "raw", "string", "style", "styleable", "transition", "xml"); + + HashMap map = new HashMap<>(); + for (String resource : resourceTypes) { + String targetClass = targetPackageMap.get(variantName).replace(".", "/") + "/R$" + resource; + for (String libraryPackage : libraryPackageMap.get(variantName)) { + String fromClass = libraryPackage.replace(".", "/") + "/R$" + resource; + map.put(fromClass, targetClass); + } + } + + return map; + } + + private List getChangedClassesList(final DirectoryInput directoryInput) throws IOException { + final Map changedFiles = directoryInput.getChangedFiles(); + if (changedFiles.isEmpty()) { + // we're in non incremental mode + return Files.walk(directoryInput.getFile().toPath()) + .filter(Files::isRegularFile) + .map(Path::toFile) + .collect(Collectors.toList()); + } else { + changedFiles.entrySet().stream() + .filter(it -> it.getValue() == Status.REMOVED) + .forEach(it -> it.getKey().delete()); + + return changedFiles.entrySet().stream() + .filter(it -> it.getValue() == Status.ADDED || it.getValue() == Status.CHANGED) + .map(Map.Entry::getKey) + .filter(File::isFile) + .collect(Collectors.toList()); + } + } + + private String filePathToClassname(File file) { + // com/classify/module/a.class -> com.classify.module.a.class -> comify.module.a is not expected + // so must be replace .class first + return file.getPath().replace(".class", "") + .replace("/", ".") + .replace("\\", "."); + } +} diff --git a/source/src/main/resources/META-INF/gradle-plugins/com.kezong.fat-aar.properties b/source/src/main/resources/META-INF/gradle-plugins/com.kezong.fat-aar.properties index f1cfe9af..5131a2a4 100644 --- a/source/src/main/resources/META-INF/gradle-plugins/com.kezong.fat-aar.properties +++ b/source/src/main/resources/META-INF/gradle-plugins/com.kezong.fat-aar.properties @@ -1 +1 @@ -implementation-class=com.kezong.fataar.FatLibraryPlugin \ No newline at end of file +implementation-class=com.kezong.fataar.FatAarPlugin \ No newline at end of file diff --git a/source/upload.gradle b/source/upload.gradle new file mode 100644 index 00000000..03cbfd67 --- /dev/null +++ b/source/upload.gradle @@ -0,0 +1,110 @@ +apply plugin: 'maven-publish' +apply plugin: 'java' +apply plugin: 'signing' + +task sourcesJar(type: Jar) { + from sourceSets.main.allJava + classifier = 'sources' +} + +task javadocJar(type: Jar) { + from javadoc + classifier = 'javadoc' +} + + +File secretPropsFile = project.rootProject.file('local.properties') +if (secretPropsFile.exists()) { + println "Found secret props file, loading props" + Properties p = new Properties() + p.load(new FileInputStream(secretPropsFile)) + p.each { name, value -> + ext[name] = value + } +} else { + println "No props file, loading env vars" +} + +publishing { + publications { + release(MavenPublication) { + // The coordinates of the library, being set from variables that + // we'll set up in a moment + groupId PUBLISH_GROUP_ID + artifactId PUBLISH_ARTIFACT_ID + version PUBLISH_VERSION + + artifact jar + artifact sourcesJar + artifact javadocJar + + // Self-explanatory metadata for the most part + pom { + name = PUBLISH_ARTIFACT_ID + description = 'A gradle plugin that merge dependencies into the final aar file works with AGP 3.+' + // If your project has a dedicated site, use its URL here + url = 'https://github.com/kezong/fat-aar-android' + licenses { + license { + name = 'The MIT License' + url = 'https://opensource.org/licenses/MIT' + } + } + developers { + developer { + id = 'kezong' + name = 'kezong' + email = 'kezong1811@gmail.com' + } + } + // Version control info, if you're using GitHub, follow the format as seen here + scm { + connection = 'scm:git:github.com/kezong/fat-aar-android.git' + developerConnection = 'scm:git:ssh://github.com/kezong/fat-aar-android.git' + url = 'https://github.com/kezong/fat-aar-android/tree/master' + } + // A slightly hacky fix so that your POM will include any transitive dependencies + // that your library builds upon + withXml { + def dependenciesNode = asNode().appendNode('dependencies') + + project.configurations.implementation.allDependencies.each { + if (it.group == "com.android.tools.build" && it.name == "gradle") { + return + } + if (it.group == null) { + return + } + def dependencyNode = dependenciesNode.appendNode('dependency') + dependencyNode.appendNode('groupId', it.group) + dependencyNode.appendNode('artifactId', it.name) + dependencyNode.appendNode('version', it.version) + } + } + } + } + } + repositories { + // The repository to publish to, Sonatype/MavenCentral + maven { + // This is an arbitrary name, you may also use "mavencentral" or + // any other name that's descriptive for you + name = "fat-aar" + + def releasesRepoUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2/" + def snapshotsRepoUrl = "https://oss.sonatype.org/content/repositories/snapshots/" + // You only need this if you want to publish snapshots, otherwise just set the URL + // to the release repo directly + url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl + + // The username and password we've fetched earlier + credentials { + username ossrhUsername + password ossrhPassword + } + } + } +} +signing { + sign publishing.publications +} \ No newline at end of file