commit 416f05460e0ebeaff6010bc33b7ba2f46b47200e Author: fankesyooni Date: Sun Sep 3 01:11:35 2023 +0800 Initial commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7101007 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +# noinspection EditorConfigKeyCorrectness +[{*.kt,*.kts}] +ktlint_standard_annotation = disabled +ktlint_standard_filename = disabled +ktlint_standard_wrapping = disabled +ktlint_standard_import-ordering = enabled +ktlint_standard_max-line-length = disabled +ktlint_standard_multiline-if-else = disabled +ktlint_standard_argument-list-wrapping = disabled +ktlint_standard_parameter-list-wrapping = disabled +ktlint_standard_trailing-comma-on-declaration-site = disabled +ktlint_function_signature_body_expression_wrapping = multiline +ij_continuation_indent_size = 2 +indent_size = 4 +indent_style = space +insert_final_newline = false +max_line_length = 150 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6f8ad16 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +*.iml +.gradle +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..d649463 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +/gradle.xml +/misc.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..07fefc3 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/icon.png b/.idea/icon.png new file mode 100644 index 0000000..d8f73c5 Binary files /dev/null and b/.idea/icon.png differ diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..543b440 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,14 @@ + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..fdc392f --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..f8467b4 --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/ktlint.xml b/.idea/ktlint.xml new file mode 100644 index 0000000..92c4441 --- /dev/null +++ b/.idea/ktlint.xml @@ -0,0 +1,6 @@ + + + + false + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..2b63946 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..80ba5d5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + 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 HighCapable [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README-zh-CN.md b/README-zh-CN.md new file mode 100644 index 0000000..8c02071 --- /dev/null +++ b/README-zh-CN.md @@ -0,0 +1,93 @@ +# Sweet Dependency + +[![Blank](https://img.shields.io/badge/license-Apache2.0-blue)](https://github.com/HighCapable/SweetDependency/blob/master/LICENSE) +![Blank](https://img.shields.io/badge/version-v1.0.0-green) +[![Telegram](https://img.shields.io/badge/Discussion-Telegram-blue.svg?logo=telegram)](https://t.me/HighCapable_Dev) + +LOGO + +一个轻松自动装配和管理依赖的 Gradle 插件。 + +[English](https://github.com/HighCapable/SweetDependency/blob/master/README.md) | 简体中文 + +## 这是什么 + +这是一个用来管理 Gradle 依赖的 Gradle 插件,所有依赖集中管理并自动更新,解决了每个子项目都需要手动更新到相同版本依赖的问题。 + +虽然 Gradle 后期推出了 Version Catalogs 来管理依赖,但是它的方式依然不够自由和人性化,且存在限制。 + +不同于传统的依赖管理方式,`SweetDependency` 采用 YAML 进行动态配置,可读性相对较高,配置过程简单且易用。 + +## 兼容性 + +不仅仅是 Android 项目,任何使用 Gradle 作为构建工具的项目都可以使用。 + +目前暂时只有 Gradle 插件,IDEA 的相关插件还在计划开发中,预计将会支持配置文件的语法检查和将配置文件显示在 Android 项目的 Gradle 文件列表中。 + +支持 Gradle `7.x.x` 和 `8.x.x`,其它版本未做测试并不推荐使用。 + +> 构建脚本语言 + +- Kotlin DSL + +推荐优先使用此语言作为构建脚本语言,这也是目前 Gradle 推荐的语言。 + +- Groovy DSL + +部分功能可能无法兼容,在后期会逐渐放弃支持,且部分功能会无法使用。 + +> 相关功能列表 + +部分功能将跟随后期用户需求逐渐完善。 + +- [x] 支持 Kotlin Multiplatform + +- [x] 管理 Gradle 插件依赖 + +- [x] 管理 Gradle 库依赖 + +- [x] 自动装配 Maven 依赖 (POM、BOM) + +- [ ] 自动装配 Ivy 依赖 + +## 开始使用 + +- [点击这里](https://github.com/HighCapable/SweetDependency/blob/master/docs/guide-zh-CN.md) 查看使用文档 + +## 更新日志 + +- [点击这里](https://github.com/HighCapable/SweetDependency/blob/master/docs/changelog-zh-CN.md) 查看历史更新日志 + +## 项目推广 + +如果你正在寻找一个可以自动生成属性键值的 Gradle 插件,你可以了解一下 [SweetProperty](https://github.com/HighCapable/SweetProperty) 项目。 + +本项目同样使用了 **SweetProperty**。 + +## Star History + +![Star History Chart](https://api.star-history.com/svg?repos=HighCapable/SweetDependency&type=Date) + +## 许可证 + +- [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) + +``` +Apache License Version 2.0 + +Copyright (C) 2019-2023 HighCapable + +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 + + https://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. +``` + +版权所有 © 2019-2023 HighCapable \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..5e36f12 --- /dev/null +++ b/README.md @@ -0,0 +1,96 @@ +# Sweet Dependency + +[![Blank](https://img.shields.io/badge/license-Apache2.0-blue)](https://github.com/HighCapable/SweetDependency/blob/master/LICENSE) +![Blank](https://img.shields.io/badge/version-v1.0.0-green) +[![Telegram](https://img.shields.io/badge/Discussion-Telegram-blue.svg?logo=telegram)](https://t.me/HighCapable_Dev) + +LOGO + +An easy autowire and manage dependencies Gradle plugin. + +English | [简体中文](https://github.com/HighCapable/SweetDependency/blob/master/README-zh-CN.md) + +## What's this + +This is a Gradle plugin for managing Gradle dependencies. + +Although Gradle later launched the Version Catalogs to manage dependencies, its method is still not free and user-friendly, and has limitations. + +Different from traditional dependency management methods, `SweetDependency` uses YAML for dynamic configuration, +which is relatively readable, and the configuration process is simple and easy to use. + +## Compatibility + +Not just Android projects, any project that uses Gradle as a build tool will work. + +Currently there is only Gradle plugin, IDEA related plugins are still under development, +support for syntax checking of configuration file and displaying configuration file in the Android project's Gradle files list is expected. + +Gradle `7.x.x` and `8.x.x` are supported, other versions have not been tested and are not recommended. + +> Build Script Language + +- Kotlin DSL + +It is recommended to use this language as the build script language first, which is also the language currently recommended by Gradle. + +- Groovy DSL + +Some functions may be incompatible, support will be gradually dropped in the future, and some functions may become unavailable. + +> Related Feature List + +Some functions will be gradually improved following the needs of users. + +- [x] Support Kotlin Multiplatform + +- [x] Manage Gradle dependencies (plugins) + +- [x] Manage Gradle dependencies (libraries) + +- [x] Autowire Maven dependencies (POM, BOM) + +- [ ] Autowire Ivy dependencies + +## Get Started + +- [Click here](https://github.com/HighCapable/SweetDependency/blob/master/docs/guide.md) to view the documentation + +## Changelog + +- [Click here](https://github.com/HighCapable/SweetDependency/blob/master/docs/changelog.md) to view the historical changelog + +## Promotion + +If you are looking for a Gradle plugin that can automatically generate properties key-values, +you can check out the [SweetProperty](https://github.com/HighCapable/SweetProperty) project. + +This project also uses **SweetProperty**. + +## Star History + +![Star History Chart](https://api.star-history.com/svg?repos=HighCapable/SweetDependency&type=Date) + +## License + +- [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) + +``` +Apache License Version 2.0 + +Copyright (C) 2019-2023 HighCapable + +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 + + https://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. +``` + +Copyright © 2019-2023 HighCapable \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..64ba158 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,16 @@ +plugins { + autowire(libs.plugins.kotlin.jvm) apply false +} + +allprojects { + tasks.withType().configureEach { + kotlinOptions { + jvmTarget = "17" + freeCompilerArgs = listOf( + "-Xno-param-assertions", + "-Xno-call-assertions", + "-Xno-receiver-assertions" + ) + } + } +} \ No newline at end of file diff --git a/docs/changelog-zh-CN.md b/docs/changelog-zh-CN.md new file mode 100644 index 0000000..12f05bb --- /dev/null +++ b/docs/changelog-zh-CN.md @@ -0,0 +1,5 @@ +# 更新日志 + +## 1.0.0 | 2023.09.03 + +- 首个版本提交至 Maven \ No newline at end of file diff --git a/docs/changelog.md b/docs/changelog.md new file mode 100644 index 0000000..04e947b --- /dev/null +++ b/docs/changelog.md @@ -0,0 +1,5 @@ +# Changelog + +## 1.0.0 | 2023.09.03 + +- The first version is submitted to Maven \ No newline at end of file diff --git a/docs/guide-zh-CN.md b/docs/guide-zh-CN.md new file mode 100644 index 0000000..b924862 --- /dev/null +++ b/docs/guide-zh-CN.md @@ -0,0 +1,1156 @@ +# Sweet Dependency 使用文档 + +在开始使用之前,建议你仔细阅读此文档,以便你能更好地了解它的作用方式与功能。 + +你可以在项目的根目录找到 examples 中的 Demo,并参考此文档食用效果更佳。 + +## 工作原理 + +`SweetDependency` 通过配置文件定义的前置存储库作用于自身的同时应用到 Gradle 默认的项目存储库中。 + +它仅会将需要部署的依赖的名称和版本告知 Gradle,并不参与依赖的最终部署工作。 + +在完成上述工作后,Gradle 将使用 `SweetDependency` 设置的自定义存储库和依赖进行最终的部署。 + +> 工作流程示例 + +``` +--- Sweet Dependency --- +⬇️ 读取配置文件 +⬇️ 将存储库设置到 Gradle 当前项目 +⬇️ 通过存储库自动装配当前定义的依赖 +--- Gradle --- +⬇️ 得到项目存储库 +⬇️ 得到待部署的依赖 +✅ 通过存储库搜索所有依赖并部署 +``` + +## 前提条件 + +请注意 `SweetDependency` 最低支持 Gradle `7.x.x`,并且使用 `pluginManagement` 和 `dependencyResolutionManagement` 新方式进行管理。 + +如果你的项目依然在使用 `buildscript` 的方式进行管理,请迁移到新方式,否则会发生错误。 + +## 快速开始 + +首先,打开你根项目的 `settings.gradle` 或 `settings.gradle.kts`。 + +删除整个 `dependencyResolutionManagement` 方法 (如果有)。 + +然后在你根项目的 `settings.gradle` 或 `settings.gradle.kts` 中加入如下代码。 + +如果已经存在 `pluginManagement` 则不需要重复添加。 + +你需要在 `pluginManagement.repositories` 添加所需的存储库 `mavenCentral` 以便使 Gradle 能够找到 `SweetDependency` 插件。 + +同时你需要保持其它存储库存在以便使 Gradle 能够完成自身插件的初始化。 + +> Kotlin DSL + +```kotlin +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } +} +plugins { + id("com.highcapable.sweetdependency") version "" +} +``` + +> Groovy DSL + +```groovy +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } +} +plugins { + id 'com.highcapable.sweetdependency' version '' +} +``` + +请将上述代码中的 `` 替换为 +[Release](https://github.com/fankes/SweetDependency/releases) 中的最新版本, 请注意**不要**在后方加入 `apply false`。 + +上述配置完成后,运行一次 Gradle Sync。 + +**特别注意** + +`SweetDependency` 会替换 `pluginManagement` 和 `dependencyResolutionManagement` 中设置的存储库,如果你手动在这些方法块中配置了存储库,它们都将会无效。 + +如果你想继续配置 `dependencyResolutionManagement` 中的其它内容,例如 `versionCatalogs`,它只能出现在 `plugins` 的下方。 + +我们不建议继续配置 `versionCatalogs`,因为 `SweetDependency` 借用了来自它的部分功能,可能会引发冲突。 + +不出意外的情况下,`SweetDependency` 会自动为你创建 YAML 配置文件,你将会得到如下项目结构 (以 Android 项目举例)。 + +``` +MyApplication + ├─ gradle + │ └─ sweet-dependency + │ └─ sweet-dependency-config.yaml <-- SweetDependency 配置文件 + ├─ build.gradle / build.gradle.kts + ├─ settings.gradle / settings.gradle.kts + ├─ app + │ └─ build.gradle / build.gradle.kts + └─ mylibrary + └─ build.gradle / build.gradle.kts +``` + +然后,请打开 `sweet-dependency-config.yaml` 配置文件,对 `SweetDependency` 进行基础配置。 + +默认配置已经帮你自动生成,你可以按照你的需求进行配置。 + +如果你觉得手动配置很麻烦,没问题,你可以直接跳转到 [迁移依赖到 Sweet Dependency](#迁移依赖到-sweet-dependency) 进行阅读。 + +> 示例如下 + +```yaml +# 配置偏好设置 +preferences: + # Gradle Sync 时自动装配、更新依赖模式 + # 此选项决定了 Gradle Sync 时的行为 + # - UPDATE_OPTIONAL_DEPENDENCIES + # ↑ 默认模式,自动装配和更新可选依赖 + # - UPDATE_ALL_DEPENDENCIES + # ↑ 自动装配和更新所有依赖 + # - ONLY_AUTOWIRE_DEPENDENCIES + # ↑ 仅自动装配使用 "+" 填充版本的依赖 + # - UPDATE_OPTIONAL_PLUGINS + # ↑ 自动装配和更新可选依赖 (插件依赖) + # - UPDATE_ALL_PLUGINS + # ↑ 自动装配和更新所有依赖 (插件依赖) + # - ONLY_AUTOWIRE_PLUGINS + # ↑ 仅自动装配使用 "+" 填充版本的依赖 (插件依赖) + # - UPDATE_OPTIONAL_LIBRARIES + # ↑ 自动装配和更新可选依赖 (库依赖) + # - UPDATE_ALL_LIBRARIES + # ↑ 自动装配和更新所有依赖 (库依赖) + # - ONLY_AUTOWIRE_LIBRARIES + # ↑ 仅自动装配使用 "+" 填充版本的依赖 (库依赖) + # - OFF + # ↑ 什么也不做,关闭所有功能 + # 注意:不建议完全关闭此功能,如果存在未被自动装配的依赖将无法继续部署依赖 + autowire-on-sync-mode: UPDATE_OPTIONAL_DEPENDENCIES + # 存储库装载模式 + # 目前 Gradle 提供了如下 3 种模式,具体模式可参考官方文档 + # - PREFER_PROJECT + # - PREFER_SETTINGS + # - FAIL_ON_PROJECT_REPOS + repositories-mode: FAIL_ON_PROJECT_REPOS + # 依赖命名空间 + # 设置后在部署依赖时需要加入命名空间作为前缀 + # 只允许 26 个英文字母 (大小写) 以及 '.'、'_'、'-' 且必须以字母开头 (长度至少为 3 位) + # 例如我们有库依赖 "com.mydemo.test:test" 以 "implementation" 部署方式举例 + # 没有命名空间:implementation(com.mydemo.test.test) + # 存在命名空间:implementation(libs.com.mydemo.test.test) + dependencies-namespace: + # 插件依赖必须存在命名空间,如果不设置,其默认为 "libs" + plugins: libs + # 库依赖的命名空间可选 + # 如果你不需要库依赖的命名空间,请删除此节点 + libraries: libs + # 依赖版本过滤器 + # 如果你需要排除一些不希望被更新到的依赖版本 (例如测试版本) 可以手动进行配置 + # 默认情况下过滤器已帮你自动排除测试版本,无需对此项进行配置 + version-filter: + # 使用内置过滤器 + # 默认为启用状态,内置过滤器包含了所有测试版本中可能出现的关键词 + # 其中包含:"-beta"、"-alpha"、"-dev"、"-canary"、"-pre"、"-rc"、"-ga"、"-snapshot" + # 可被匹配的版本例如:"1.2.0-alpha01" 或 "1.1.2-beta01" + # 如果禁用此选项,将仅使用 "exclusion-list" 中定义的关键词,如果 "exclusion-list" 为空,将禁用过滤器 + use-internal: true + # 排除列表 + # 你可以在排除列表中填写需要排除的自定义关键词 (不区分大小写) + # 推荐在开头添加 "-" 防止发生误判,例如:"bar" 匹配 "1.0.0-bar01" 也会匹配 "1.0.0-foobar01" + exclusion-list: + -foo + -bar + +# 配置依赖使用的存储库 +repositories: + # 以下内容仅供示例,你只需要添加用到的存储库 + # 一般情况下只需要添加 google 和 maven-central,默认配置文件中将会自动帮你添加 + # 每个存储库都可以配置 url 和 path,这取决于存储库是否支持这种配置方式 + # 目前 SweetDependency 无法兼容 Maven 以外的自定义存储库 + # 下面这些节点名称为内置存储库,你不可以用这些名称作为自定义存储库 + google: # Google 存储库 + maven-central: # 中央存储库 + maven-local: # 本地存储库 + # 一般情况下不需要配置本地存储库的路径 + # 默认情况下会自动按照以下操作系统的默认路径获取 + # Windows: C:\Users\\.m2 + # Linux: /home//.m2 + # Mac: /Users//.m2 + # 详情请参考 https://www.baeldung.com/maven-local-repository + # 如果你修改了存储库的路径,请在这里重新指定 + # 如果你想保持默认配置,请删除此节点 + path: /path/to/repository + gradle-plugin-portal: # Gradle 插件存储库 + # 以下列出目前 SweetDependency 内置的常见存储库别名 + # 中央存储库 (分流) + maven-central-branch: + # JitPack + jit-pack: + # 阿里云 Google 存储库镜像 + aliyun-google-mirror: + # 阿里云中央存储库镜像 + aliyun-maven-central-mirror: + # 阿里云公共存储库镜像 + aliyun-maven-public-mirror: + # 阿里云 JCenter 镜像 + # 注意:JCenter 已经终止服务,不再建议使用 + aliyun-jcenter-mirror: + # OSS 存储库 + sonatype-oss-releases: + # 快照存储库 + sonatype-oss-snapshots: + # 自定义 Maven 存储库 + # 自定义的存储库节点名称除内置存储库外可随意填写 + your-custom-repo: + # 所有存储库添加即启用,如果你想禁用只需要添加此配置并设置为 false + enable: true + # 设置作用域 + # 此选项决定了此存储库将被作用于什么类型的依赖 + # - ALL + # ↑ 默认模式,作用于所有类型依赖 + # - PLUGINS + # ↑ 作用于插件依赖 + # - LIBRARIES + # ↑ 作用于库依赖 + scope: ALL + # 自定义内容过滤器 + # 此功能可以加快 Gradle 搜索依赖的速度 + # 如果已知此存储库仅包含某些依赖,就可以使用此功能 + content: + # 指定需要包含的内容 + # 你可以以不同形式指定一个或一组内容 + include: + # 此功能接受 1 个参数 + group: + androidx.appcompat + com.android + # 此功能接受 1 个参数 + group-and-subgroups: + androidx.appcompat + # 此功能接受 1 个参数 + group-by-regex: + androidx.* + com.android.* + # 此功能接受 2 个参数,使用 ":" 进行分割 + # 必须为 2 个参数,缺少参数会发生错误 + module: + androidx.core:core + # 此功能接受 2 个参数,使用 ":" 进行分割 + # 必须为 2 个参数,缺少参数会发生错误 + module-by-regex: + androidx.core:* + # 此功能接受 3 个参数,使用 ":" 进行分割 + # 必须为 3 个参数,缺少参数会发生错误 + version: + androidx.core:core:1.9.0 + # 此功能接受 3 个参数,使用 ":" 进行分割 + # 必须为 3 个参数,缺少参数会发生错误 + version-by-regex: + androidx.core:*:1.9.0 + # 指定需要排除的内容 + # 你可以以不同形式指定一个或一组内容 + exclude: + group-by-regex: + androidx.* + com.android.* + group-and-subgroups: + androidx.appcompat + module: + androidx.core:core + module-by-regex: + androidx.core:* + version: + androidx.core:core:1.9.0 + version-by-regex: + androidx.core:*:1.9.0 + # 自定义身份验证 + # 如果你的存储库需要身份验证才能访问,你可以添加此节点 + # 你可以使用 ${...} 的方式来引用当前项目或系统的键值内容 + # 具体用法你可以继续阅读文档最后的 "配置字符串插值" 部分 + credentials: + # 你的用户名 + username: ${your-repo.username} + # 你的密码 + password: ${your-repo.password} + # 自定义存储库 URL + url: https://to.your.custom/repo + # 自定义存储库本地路径 + # 注意:"url" 与 "path" 参数只能存在一个 + path: /path/to/repository + +# 配置需要使用的插件依赖 +plugins: + # 注意:我们只推荐在这里定义一些外部存储库的插件依赖,一些内置于 Gradle 的插件不应该被定义在这里 + # 注意:插件依赖需要声明一个版本,不声明版本的依赖会发生问题且不被推荐,也不会生成自动装配代码 + # 插件 ID + com.android.application: + # 自定义依赖别名 (可选,在部署依赖时会用到) + # 只允许 26 个英文字母 (大小写) 以及 '.'、'_'、'-' 且必须以字母开头 (长度至少为 3 位) + # 别名可被 '.'、'_'、'-' 至少分割为 2 份,例如 "com-mytest" + alias: android-application + # 依赖版本 (如果你不确定版本,可以填写 "+" ,将会自动装配) + version: 7.4.1 + # 自动装配、更新过程是否自动更新此依赖 (在版本为 "+" 的情况下设为 false 不会生效) + auto-update: true + # 依赖版本过滤器,默认情况下跟随全局配置 + version-filter: + use-internal: true + exclusion-list: + -foo + -bar + # 指定当前依赖使用的存储库名称 (可以同时指定多个) + # 在不设置此参数时,当前依赖将使用全部已定义的存储库按顺序依次进行搜索 + # 如果此依赖所在的存储库是已知的,推荐为其设置存储库以提升效率 + # 例如:google,你可以直接填写 google + # 例如:maven-central,你可以直接填写 maven-central + # 或是你指定的名称,例如上方的 jit-pack 节点,你需要填写 jit-pack + # 注意:如果你对目标存储库设置了作用域且不匹配当前依赖类型,它将自动被排除 + # 注意:如果你设置的存储库中没有一个可以被使用,当前依赖将被认为不存在存储库 + repositories: + google + maven-central + com.android.library: + # 如果存在相同版本的依赖,可以使用版本引用来避免重复填写相同版本 + # 版本引用内容支持匹配依赖全称和依赖别名 + # 不可引用已经存在版本引用的依赖 (递归调用) + # 注意:"version" 和 "version-ref" 节点在一个依赖中只能出现一次 + # 注意:如果你声明了 "version-ref",此依赖将在自动装配和更新中被排除 + # 注意:如果你声明了 "version-ref","auto-update"、"repositories"、"version-filter" 将无效 + version-ref: android-application # 或 "com.android.application" + org.jetbrains.kotlin.android: + alias: kotlin-android + version: 1.8.10 + +# 配置需要使用的库依赖 +libraries: + # Group ID + androidx.core: + # Artifact ID + core: + # 自定义依赖别名 (可选,在部署依赖时会用到) + # 只允许 26 个英文字母 (大小写) 以及 '.'、'_'、'-' 且必须以字母开头 (长度至少为 3 位) + # 别名可被 '.'、'_'、'-' 至少分割为 2 份,例如 "com-mytest" + alias: androidx-core + # 依赖版本 (如果你不确定版本,可以填写 "+" ,将会自动装配) + version: 1.9.0 + # 自动装配、更新过程是否自动更新此依赖 (在版本为 "+" 的情况下设为 false 不会生效) + auto-update: true + # 依赖版本过滤器,默认情况下跟随全局配置 + version-filter: + use-internal: true + exclusion-list: + -foo + -bar + # 指定当前依赖使用的存储库名称 (可以同时指定多个) + # 在不设置此参数时,当前依赖将使用全部已定义的存储库按顺序依次进行搜索 + # 如果此依赖所在的存储库是已知的,推荐为其设置存储库以提升效率 + # 例如:google,你可以直接填写 google + # 例如:maven-central,你可以直接填写 maven-central + # 或是你指定的名称,例如上方的 jit-pack 节点,你需要填写 jit-pack + # 注意:如果你对目标存储库设置了作用域且不匹配当前依赖类型,它将自动被排除 + # 注意:如果你设置的存储库中没有一个可以被使用,当前依赖将被认为不存在存储库 + repositories: + google + maven-central + core-ktx: + alias: androidx-core-ktx + # 如果存在相同版本的依赖,可以使用版本引用来避免重复填写相同版本 + # 版本引用内容支持匹配依赖全称和依赖别名 + # 如果当前被引用的版本在当前 "Group ID" 中,可以直接填写 "::Artifact ID" + # 例如当前为 "androidx.core",引用 "core" 的版本只需要填写为 "version-ref: ::core" + # 不可引用已经存在版本引用的依赖 (递归调用) + # 注意:"version" 和 "version-ref" 节点在一个依赖中只能出现一次 + # 注意:如果你声明了 "version-ref",此依赖将在自动装配和更新中被排除 + # 注意:如果你声明了 "version-ref","auto-update"、"repositories"、"version-filter" 将无效 + version-ref: ::core # 或 "androidx.core:core" 以及 "androidx-core" (别名) + com.squareup.okhttp3: + okhttp: + # 如果你在版本中声明了一个 "version-filter" 中存在的版本 (内置过滤器或排除列表) + # 例如版本 "5.0.0-alpha.7" 包含 "-alpha" + # 此时你不需要配置 "version-filter" 并设置 "use-internal: false" + # 运行自动装配和更新依赖时,它会自动更新到当前包含 "-alpha" 的最新版本 + version: 5.0.0-alpha.7 + com.google.android.material: + material: + alias: google-material-android + version: 1.8.0 + junit: + junit: + alias: junit + version: 4.13.2 + # 如果你正在使用一个 BOM 依赖,你可以像大多数依赖一样直接定义它 + org.springframework.boot: + spring-boot-dependencies: + alias: spring-boot-dependencies + version: 1.5.8.RELEASE + dom4j: + dom4j: + # 你可以使用 "" 声明此依赖不需要定义版本 + # 如果声明其不需要定义版本,它将自动使用 BOM 中定义的版本 + # 注意:如果你声明了 "",此依赖将在自动装配和更新中被排除 + # 注意:如果你声明了 "","versions"、"version-ref" 将不能再使用 + version: +``` + +`SweetDependency` 接管了 Gradle 的依赖存储库,在配置文件中定义的存储库会同时被 `SweetDependency` 和 Gradle 使用。 + +上述配置完成后,运行一次 Gradle Sync。 + +然后,你可以前往你每个项目的 `build.gradle` 或 `build.gradle.kts`,将依赖的部署方式迁移到 `SweetDependency`。 + +`SweetDependency` 会将依赖命名空间、依赖名称、依赖别名等自动进行分割。 + +注意:如果你在 `pluginManagement` 的 `plugins` 方法块中设置了插件的版本,请将其移除。 + +> Kotlin DSL + +首先,在根项目部署插件依赖,但是不应用 (与 Gradle 官方推荐做法一致)。 + +```kotlin +plugins { + // 推荐使用 autowire 方法进行部署 (你也可以使用官方提供的 alias 方法,其行为一致) + // 由于插件部分的自定义性限制,这里的代码生成借助了 Gradle 自身的 version catalogs + // 由于 version catalogs 的要求,插件必须以 "命名空间.plugins" 开头 + autowire(libs.plugins.com.android.application) apply false + autowire(libs.plugins.org.jetbrains.kotlin.android) apply false + // 使用别名 + autowire(libs.plugins.android.application) apply false + autowire(libs.plugins.kotlin.android) apply false +} +``` + +接下来,在子项目部署需要使用的插件依赖和库依赖。 + +```kotlin +plugins { + autowire(libs.plugins.com.android.application) + autowire(libs.plugins.org.jetbrains.kotlin.android) + // 使用别名 + autowire(libs.plugins.android.application) + autowire(libs.plugins.kotlin.android) +} + +dependencies { + // 直接部署 + implementation(androidx.core.core.ktx) + implementation(com.google.android.material.material) + // 使用别名 + implementation(androidx.core.ktx) + implementation(google.material.android) + // 如果你设置了依赖命名空间,请将命名空间作为前缀进行部署 + implementation(libs.androidx.core.core.ktx) + // 使用依赖命名空间的情况下,依赖别名的用法依然相同 + implementation(libs.androidx.core.ktx) +} +``` + +> Groovy DSL + +首先,在根项目部署插件依赖,但是不应用 (与 Gradle 官方推荐做法一致)。 + +```groovy +plugins { + // Groovy 不支持使用 autowire 方法进行部署,你只能使用官方提供的 alias 方法 + // 由于插件部分的自定义性限制,这里的代码生成借助了 Gradle 自身的 version catalogs + // 由于 version catalogs 的要求,插件必须以 "命名空间.plugins" 开头 + alias libs.plugins.com.android.application apply false + alias libs.plugins.org.jetbrains.kotlin.android apply false + // 使用别名 + alias libs.plugins.android.application apply false + alias libs.plugins.kotlin.android apply false +} +``` + +接下来,在子项目部署需要使用的插件依赖和库依赖。 + +```groovy +plugins { + alias libs.plugins.com.android.application + alias libs.plugins.org.jetbrains.kotlin.android + // 使用别名 + alias libs.plugins.android.application + alias libs.plugins.kotlin.android +} + +dependencies { + // 直接部署 + implementation androidx.core.core.ktx + implementation com.google.android.material.material + // 使用别名 + implementation androidx.core.ktx + implementation google.material.android + // 如果你设置了依赖命名空间,请将命名空间作为前缀进行部署 + implementation libs.androidx.core.core.ktx + // 使用依赖命名空间的情况下,依赖别名的用法依然相同 + implementation libs.androidx.core.ktx +} +``` + +**特别注意** + +形如 `ext`、`extra`、`extraProperties`、`extensions` 的名称为 Gradle 在创建扩展方法时自带的默认扩展方法。 + +`SweetDependency` 在生成首位依赖扩展方法时如果遇到这些名称将不能正常生成,解决方案是在名称的结尾添加 `s`。 + +如果你一定要使用这些名称作为依赖的名称或别名,你可以考虑设置一个依赖命名空间。 + +目前在 Maven 存储库中尚未收集到以这些名称作为开头的依赖。 + +当然,你也不可以直接使用这些自带的默认扩展方法名称来设置依赖命名空间、依赖别名等。 + +**可能遇到的问题** + +如果你的项目仅存在一个根项目,且没有导入任何子项目,此时如果 `dependencies` +方法体中的扩展方法不能正常生成,你可以将你的根项目迁移至子项目并在 `settings.gradle` 或 `settings.gradle.kts` 中导入这个子项目,这样即可解决此问题。 + +我们一般推荐将项目的功能进行分类,根项目仅用来管理插件和一些配置。 + +**局限性说明** + +`SweetDependency` 无法管理 `settings.gradle` 或 `settings.gradle.kts` 中的 `plugins` 方法块,因为这属于 `SweetDependency` 的上游,这种情况请使用通常做法进行管理。 + +### Kotlin Multiplatform 支持 + +在 Kotlin Multiplatform 中与一般依赖部署方式一致。 + +> Kotlin DSL + +```kotlin +sourceSets { + val androidMain by getting { + dependencies { + implementation(androidx.core.core.ktx) + implementation(com.google.android.material.material) + } + } +} +``` + +> Groovy DSL + +```groovy +sourceSets { + androidMain { + dependencies { + implementation androidx.core.core.ktx + implementation com.google.android.material.material + } + } +} +``` + +## 迁移依赖到 Sweet Dependency + +如果你是第一次开始使用 `SweetDependency`,你可以手动运行创建依赖迁移模板 Task。 + +你可以在根项目的 Task 分组 `sweet-dependency` 中找到 `createDependenciesMigrationTemplate` Task,手动运行它。 + +此操作将会自动分析所有项目中的外部存储库依赖,并在根项目的 `gradle/sweet-dependency` 生成以配置文件名为前缀的 `*.template.yaml` 文件。 + +如果你未修改过配置文件的名称,它将默认为 `sweet-dependency-config.template.yaml`。 + +如果生成的模版文件已经存在,则会被自动覆盖。 + +模板文件中会提供当前项目使用的依赖节点,请手动将生成的节点全部内容复制到配置文件中,并删除模板文件。 + +> 示例如下 + +```yaml +plugins: + ... +libraries: + ... +``` + +请注意,`SweetDependency` 不会在模版文件中创建依赖使用的存储库,请手动添加依赖使用的存储库或使用首次配置文件中自动生成的存储库。 + +然后,请手动将每个项目的 `build.gradle` 或 `build.gradle.kts` 的 `plugins` 和 `dependencies` 方法体中的外部存储库依赖迁移到 `SweetDependency`。 + +下面是一个示例,可供参考。 + +> Kotlin DSL + +```kotlin +plugins { + // 原始部署写法 + id("org.jetbrains.kotlin.android") version "1.8.10" + // 迁移后的写法 + autowire(libs.plugins.org.jetbrains.kotlin.android) +} + +dependencies { + // 原始部署写法 + implementation("androidx.core:core-ktx:1.9.0") + // 迁移后的写法 + implementation(androidx.core.core.ktx) +} +``` + +> Groovy DSL + +```groovy +plugins { + // 原始部署写法 + id 'org.jetbrains.kotlin.android' version '1.8.10' + // 迁移后的写法 + alias libs.plugins.org.jetbrains.kotlin.android +} + +dependencies { + // 原始部署写法 + implementation 'androidx.core:core-ktx:1.9.0' + // 迁移后的写法 + implementation androidx.core.core.ktx +} +``` + +如果你使用了 `versionCatalogs`,请将在 `settings.gradle` 或 `settings.gradle.kts` 中的定义也一并删除。 + +如果你使用 TOML 的方式定义了 `versionCatalogs`,例如 `libs.versions.toml` 文件,现在你可以不需要它了,你可以在迁移依赖后将其删除。 + +请注意,模版文件仅用于迁移依赖使用,它不应该出现在版本控制系统中,建议是使用后将其删除。 + +## 配置依赖自动装配 + +默认情况下,运行 Gradle Sync 即会执行搜索并自动装配、更新依赖。 + +依赖自动装配日志将会写入根项目的 `.gradle/sweet-dependency/dependencies-autowire.log` 中。 + +你可以在此文档的最下方找到配置是否启用依赖自动装配日志的方法。 + +你可以在 `sweet-dependency-config.yaml` 中配置 `preferences.autowire-on-sync-mode` 的模式。 + +你也可以在你需要的时候手动运行以下 Gradle Task,你可以在根项目的 Task 分组 `sweet-dependency` 中找到这些 Task。 + +- updateOptionalDependencies +- updateOptionalPlugins +- updateOptionalLibraries + +自动装配和更新可选依赖。 + +其中以 "Plugins" 结尾的代表仅管理插件依赖,以 "Libraries" 结尾的代表仅管理库依赖。 + +你可以在配置文件中使用 `^` 标识可选更新的依赖。 + +> 示例如下 + +```yaml +plugins: + # 插件依赖的可选更新行为与库依赖保持一致,请参考下方库依赖的示例 + ... + +libraries: + com.google.android.material: + material: + # 使用 "^" 作为开头标识当前版本,它将在有更新的时候被替换为最新版本 + # 使用 "^" 作为开头的版本将在下一次成功更新后移除此符号 (单次可选更新) + # 如果你希望持续保留此符号 (常驻可选更新),请双写它,形如 "^^" + version: ^1.8.0 +``` + +- updateAllDependencies +- updateAllPlugins +- updateAllLibraries + +自动装配和更新所有依赖。 + +其中以 "Plugins" 结尾的代表仅管理插件依赖,以 "Libraries" 结尾的代表仅管理库依赖。 + +将会检查所有依赖的更新并更新到最新版本,依赖过多时可能会非常耗时。 + +- autowireDependencies +- autowirePlugins +- autowireLibraries + +仅自动装配使用 "+" 填充版本的依赖。 + +其中以 "Plugins" 结尾的代表仅管理插件依赖,以 "Libraries" 结尾的代表仅管理库依赖。 + +**特别注意** + +依赖自动装配或更新后,你需要手动运行一次 Gradle Sync 使更改生效,如果你不进行此操作,则更改将在下一次编译或任何 Gradle 活动时生效。 + +## 配置依赖扩展功能 + +你可以使用 `autowire(...)` 方法来自动装配任意依赖。 + +注意:一些特性可能不会适用于 Groovy DSL,如有需要,请开始使用或转移到 Kotlin DSL。 + +下面是一个简单的示例。 + +> Kotlin DSL + +```kotlin +plugins { + // 部署 "org.jetbrains.kotlin.android" + autowire("org.jetbrains.kotlin.android") + // 使用别名部署 + autowire("kotlin-android") +} + +dependencies { + // 部署 "androidx.core:core-ktx" + implementation(autowire("androidx.core:core-ktx")) + // 使用别名部署 + implementation(autowire("androidx-core-ktx")) +} +``` + +> Groovy DSL + +```groovy +plugins { + // 很遗憾,Gradle 不允许使用常规方案自定义 plugins 方法块 + // 这是 Gradle 对自定义插件的限制,插件无法干预 + // 所以,autowire 方法将不会支持 Groovy DSL + // 如有需要,推荐开始使用或转换到 Kotlin DSL +} + +dependencies { + // 部署 "androidx.core:core-ktx" + implementation sweet.autowire('androidx.core:core-ktx') + // 使用别名部署 + implementation sweet.autowire('androidx-core-ktx') +} +``` + +除了自动装配插件依赖和外部存储库的依赖之外,你还可以用它来导入本地文件依赖。 + +> Kotlin DSL + +```kotlin +dependencies { + // 导入当前项目 libs 目录下的所有 jar 依赖 + implementation(autowire("libs/*.jar")) + // 导入 mylibrary 项目 libs 目录下的所有 jar 依赖 + implementation(autowire("../mylibrary/libs/*.jar")) + // 导入一个绝对路径目录下的所有 jar 依赖 + implementation(autowire("/home/test/someDepends/*.jar")) + // 导入当前项目 libs 目录下的所有依赖,不区分文件扩展名 + implementation(autowire("libs/*")) + // 你也可以一个一个或一组一组文件地导入 + implementation( + autowire( + "libs/*.jar", + "libs/*.aar", + "/home/test/someDepends/mylibrary-1.jar", + "/home/test/someDepends/mylibrary-2.jar" + ) + ) + // 以下是一个特殊情况 + // 如果你直接导入一个没有目录层次并相对于当前项目路径的文件,可能无法直接识别 + // 例如我们直接导入当前项目路径下的 "mylibrary.jar" + // 以下情况会识别 "mylibrary.jar" 为一个外部存储库依赖 + implementation(autowire("mylibrary.jar")) + // 要强调这个依赖是一个文件,请使用小括号将文件路径包起来 + implementation(autowire("(mylibrary.jar)")) +} +``` + +> Groovy DSL + +```groovy +dependencies { + // 导入当前项目 libs 目录下的所有 jar 依赖 + implementation sweet.autowire('libs/*.jar') + // 导入 mylibrary 项目 libs 目录下的所有 jar 依赖 + implementation sweet.autowire('../mylibrary/libs/*.jar') + // 导入一个绝对路径目录下的所有 jar 依赖 + implementation sweet.autowire('/home/test/someDepends/*.jar') + // 导入当前项目 libs 目录下的所有依赖,不区分文件扩展名 + implementation sweet.autowire('libs/*') + // 你也可以一个一个或一组一组文件地导入 + implementation sweet.autowire( + 'libs/*.jar', + 'libs/*.aar', + '/home/test/someDepends/mylibrary-1.jar', + '/home/test/someDepends/mylibrary-2.jar' + ) + // 以下是一个特殊情况 + // 如果你直接导入一个没有目录层次并相对于当前项目路径的文件,可能无法直接识别 + // 例如我们直接导入当前项目路径下的 "mylibrary.jar" + // 以下情况会识别 "mylibrary.jar" 为一个外部存储库依赖 + implementation sweet.autowire('mylibrary.jar') + // 要强调这个依赖是一个文件,请使用小括号将文件路径包起来 + implementation sweet.autowire('(mylibrary.jar)') +} +``` + +### Kotlin Multiplatform 支持 + +在 Kotlin Multiplatform 中与一般依赖部署方式一致。 + +> Kotlin DSL + +```kotlin +sourceSets { + val androidMain by getting { + dependencies { + implementation(autowire("androidx.core:core-ktx")) + implementation(autowire("libs/*.jar")) + } + } +} +``` + +> Groovy DSL + +```groovy +sourceSets { + androidMain { + dependencies { + implementation sweet.autowire('androidx.core:core-ktx') + implementation sweet.autowire('libs/*.jar') + } + } +} +``` + +## 配置依赖版本定义 + +有一些依赖的版本我们希望在项目中固定它们,并不轻易被更新或改变。 + +针对这种情况,你可以使用版本定义在配置文件中来预先定义这些版本。 + +> 示例如下 + +```yaml +# 定义一些需要使用的版本 +versions: + # 节点名称只允许 26 个英文字母 (大小写) 以及 '.'、'_'、'-' 且必须以字母开头 (长度至少为 3 位) + mydemo-test: 1.0.0 + +# 在插件依赖定义中引用这些版本 +plugins: + # 插件依赖的依赖版本定义行为与库依赖保持一致,请参考下方库依赖的示例 + ... + +# 在库依赖定义中引用这些版本 +libraries: + com.mydemo.test: + test: + # 你可以直接在此节点引用被定义的版本 + # 版本定义名称的优先级高于依赖名称、别名,如果存在相同的名称则会优先使用前者 + version-ref: mydemo-test +``` + +## 配置依赖版本别名 + +默认情况下,在配置文件中声明的依赖版本是固定的,部署的依赖版本跟随定义中的版本。 + +如果你有这样的需求:相同依赖的版本需要和其它子项目或主项目中依赖的版本保持不一样。 + +> A 项目示例如下 + +```kotlin +plugins { + id("com.mydemo.myplugin") version "1.0.1" +} + +dependencies { + implementation("com.mydemo.test:test:1.0.1") +} +``` + +> B 项目示例如下 + +```kotlin +plugins { + id("com.mydemo.myplugin") version "1.0.2" +} + +dependencies { + implementation("com.mydemo.test:test:1.0.2") +} +``` + +针对这种情况,你可以使用版本别名在配置文件中来声明多个不同的版本。 + +> 示例如下 + +```yaml +plugins: + com.mydemo.myplugin: + alias: demo-myplugin + # 依赖版本 (当前主版本,必须存在) + # 你也可以使用 "version-ref" + version: 1.0.2 + # 自定义版本别名 + # 只允许 26 个英文字母 (大小写) 以及 '.'、'_'、'-' 且必须以字母开头 (长度至少为 3 位) + # 版本不可以填写 "+",因为版本别名定义的版本不会被自动装配 + versions: + a-version: 1.0.1 + # 如果你想跟随主版本,可以填写 "" + b-version: + +libraries: + com.mydemo.test: + test: + alias: demo-test + # 依赖版本 (当前主版本,必须存在) + # 你也可以使用 "version-ref" + version: 1.0.2 + # 自定义版本别名 + # 只允许 26 个英文字母 (大小写) 以及 '.'、'_'、'-' 且必须以字母开头 (长度至少为 3 位) + # 版本不可以填写 "+",因为版本别名定义的版本不会被自动装配 + versions: + a-version: 1.0.1 + # 如果你想跟随主版本,可以填写 "" + b-version: +``` + +然后你可以直接在当前依赖后使用 `.` 来使用它的版本别名。 + +版本别名会自动被转换为小驼峰形式,建议别名全部使用小写字母表示。 + +> A 项目示例如下 + +```kotlin +plugins { + autowire(libs.plugins.com.mydemo.myplugin.aVersion) + // 你也可以直接使用依赖别名 + autowire(libs.plugins.demo.myplugin.aVersion) + // 或者使用 autowire 方法部署 + // 注意,你需要在 autowire 第二个方法参数中填写版本别名 + autowire("com.mydemo.myplugin", "a-version") + autowire("demo-myplugin", "a-version") +} + +dependencies { + implementation(com.mydemo.test.test.aVersion) + // 你也可以直接使用依赖别名 + implementation(demo.test.aVersion) + // 或者使用 autowire 方法部署 + // 注意,你需要在 autowire 第二个方法参数中填写版本别名 + implementation(autowire("com.mydemo.test:test", "a-version")) + implementation(autowire("demo-test", "a-version")) +} + +``` + +> B 项目示例如下 + +```kotlin +plugins { + autowire(libs.plugins.com.mydemo.myplugin.bVersion) + // 你也可以直接使用依赖别名 + autowire(libs.plugins.demo.myplugin.bVersion) + // 或者使用 autowire 方法部署 + // 注意,你需要在 autowire 第二个方法参数中填写版本别名 + autowire("com.mydemo.myplugin", "b-version") + autowire("demo-myplugin", "b-version") +} + +dependencies { + implementation(com.mydemo.test.test.bVersion) + // 你也可以直接使用依赖别名 + implementation(demo.test.bVersion) + // 或者使用 autowire 方法部署 + // 注意,你需要在 autowire 第二个方法参数中填写版本别名 + implementation(autowire("com.mydemo.test:test", "b-version")) + implementation(autowire("demo-test", "b-version")) +} +``` + +注意:一些特性在 Groovy DSL 中的 `plugins` 中可能会无法使用。 + +如果你不指定版本别名,部署的依赖默认会使用当前依赖的主版本 (即 "version" 定义的版本)。 + +**特别注意** + +如果上述示例中 A 项目与 B 项目存在依赖关系,它将优先使用二者中的较新版本 (插件依赖除外)。 + +这是 Gradle 的依赖继承规则,不受版本别名控制。 + +不指定版本的依赖不能使用依赖版本别名功能。 + +## 配置字符串插值 + +你可以使用 ${...} 来动态向 `SweetDependency` 的配置文件中插入内容,这样你就可以从你的配置文件中导出一些敏感信息。 + +其中 `...` 代表当前使用的 KEY (键值名称)。 + +`SweetDependency` 会从以下位置按优先级依次查找需要插入的内容: + +- 当前项目 (Root Project) 的 `gradle.properties` +- 当前用户的 `gradle.properties` +- 系统的 `System.getProperties()` +- 系统的 `System.getenv(...)` + +> 示例如下 + +```yaml +# 配置依赖使用的存储库 +repositories: + your-custom-repo: + credentials: + username: ${your-repo.username} + password: ${your-repo.password} + url: ${your-repo.url} + +# 配置需要使用的插件依赖 +plugins: + com.android.application: + version: + + +# 配置需要使用的库依赖 +libraries: + androidx.core: + # 它还能被设置到节点上 + ${depends.androidx.core.name}: + version: + + versions: + # 或是具体的内容上 + a-version: ${depends.androidx.core.core.a-version} +``` + +在读取配置文件时 `SweetDependency` 会优先将这些内容替换到实际的字符串再进行解析。 + +如果找不到当前使用的 KEY (键值名称) 对应的内容,将返回空字符串。 + +## 自动装配优化建议 + +现在,你已经了解了有关 `SweetDependency` 的基本功能,下面是针对现有项目存储库和依赖的一些优化建议。 + +### 存储库部分 + +根据 Gradle 的依赖搜索规则,存储库添加的顺序是有意义的,搜索顺序将按照你添加的顺序依次进行。 + +你可以适当地调整存储库的顺序,这将有助于提升依赖搜索效率。 + +> 示例如下 + +```yaml +repositories: + google: + maven-central: +``` + +针对中国大陆的用户,你可以采用 `SweetDependency` 为你预置的镜像服务器地址来加快依赖的搜索速度。 + +> 示例如下 + +```yaml +repositories: + aliyun-google-mirror: + aliyun-maven-central-mirror: + aliyun-maven-public-mirror: + aliyun-jcenter-mirror: +``` + +你还可以对 `google` 存储库设置 `content` 参数来提升其使用效率,因为它目前只包含以下开头的依赖。 + +- androidx.* +- com.google.* +- com.android.* + +> 示例如下 + +```yaml +repositories: + google: + content: + include: + group-by-regex: + androidx.* + com.google.* + com.android.* +``` + +你还可以把 `gradle-plugin-portal` 存储库的 `scope` 参数设置为 `PLUGINS` 来提升搜索效率,因为它只会被作用于插件依赖。 + +> 示例如下 + +```yaml +repositories: + gradle-plugin-portal: + scope: PLUGINS +``` + +推荐将 `gradle-plugin-portal` 排在所有存储库的第一位,插件依赖优先使用它进行搜索。 + +### 依赖部分 + +你可以给指定的依赖设置其使用的存储库以减少自动装配搜索的耗时。 + +你可以在适当的时候使用 `version-ref`,这可以减少重复搜索相同版本依赖的耗时。 + +> 示例如下 + +```yaml +plugins: + com.android.application: + alias: android-application + version: 7.4.1 + repositories: + google + com.android.library: + version-ref: android-application + +libraries: + androidx.core: + core-ktx: + version: 1.9.0 + repositories: + google +``` + +## 写出调试信息 + +你可以手动运行 `sweetDependencyDebug` Task 来写出调试信息,你可以在根项目的 Task 分组 `sweet-dependency` 中找到它。 + +此操作将写出当前 `SweetDependency` 在内存中的数据结构到控制台,你可以参照配置文件检查此数据是否正确。 + +如果你认为 `SweetDependency` 不能按预期正常工作,你也可以将此数据提供给我们以供我们调试和修复。 + +## 自定义选项 + +你可以在根项目的 `settings.gradle` 或 `settings.gradle.kts` 中使用 `sweetDependency` lambda 方法来配置 `SweetDependency`。 + +> Kotlin DSL + +```kotlin +sweetDependency { + + // 启用 SweetDependency,设置为 false 将禁用所有功能 + isEnable = true + + // SweetDependency 配置文件名称 + configFileName = "sweet-dependency-config.yaml" + + // 是否启用依赖自动装配日志 + // 此功能默认启用,会在当前根项目 (Root Project) 的 ".gradle/sweet-dependency" 目录下创建日志文件 + isEnableDependenciesAutowireLog = true + + // 是否启用详细模式 + // 此功能默认启用,关闭后 SweetDependency 将会在非必要情况下保持安静 (省略非必要日志) + isEnableVerboseMode = true +} +``` + +> Groovy DSL + +```groovy +sweetDependency { + enable true + configFileName 'sweet-dependency-config.yaml' + enableDependenciesAutowireLog true + enableVerboseMode true +} +``` + +## 问题反馈 + +如果你在使用 `SweetDependency` 的过程中遇到了任何问题,你都可以随时在 GitHub 开启一个 `issues` 向我们反馈。 \ No newline at end of file diff --git a/docs/guide.md b/docs/guide.md new file mode 100644 index 0000000..603c71c --- /dev/null +++ b/docs/guide.md @@ -0,0 +1,1223 @@ +# Sweet Dependency Documentation + +Before you start using it, it is recommended that you read this document carefully so that you can better understand how it works and its functions. + +You can find the demo in examples in the root directory of the project, and refer to this document for better use. + +## Working Principle + +`SweetDependency` acts on itself through the pre-repositories declared in the configuration file and applies to Gradle's default project repositories. + +It only informs Gradle of the name and version of the dependencies that need to be deployed, and does not participate in the final deployment of +dependencies. + +After the above work is done, Gradle will use the custom repositories and dependencies set by `SweetDependency` for the final deployment. + +> Workflow following example + +``` +--- Sweet Dependency --- +⬇️ Read configuration file +⬇️ Set repositories to Gradle current project +⬇️ Autowire currently declared dependencies via repositories +--- Gradle --- +⬇️ Get project repositories +⬇️ Get the dependencies to be deployed +✅ Search all dependencies through the repositories and deploy +``` + +## Prerequisites + +Note that `SweetDependency` supports at least Gradle `7.x.x` and is managed using the new `pluginManagement` and `dependencyResolutionManagement`. + +If your project is still managed using the `buildscript` method, please migrate to the new method, otherwise errors will occur. + +## Quick Start + +First, open `settings.gradle` or `settings.gradle.kts` of your root project. + +Remove the entire `dependencyResolutionManagement` method (if any). + +Then add the following code in `settings.gradle` or `settings.gradle.kts` of your root project. + +If `pluginManagement` already exists, there is no need to add it repeatedly. + +You need to add the required repository `mavenCentral` in `pluginManagement.repositories` for Gradle to be able to find the `SweetDependency` plugin. + +At the same time you need to keep other repositories exist so that Gradle can complete the initialization of its own plugins. + +> Kotlin DSL + +```kotlin +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } +} +plugins { + id("com.highcapable.sweetdependency") version "" +} +``` + +> Groovy DSL + +```groovy +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } +} +plugins { + id 'com.highcapable.sweetdependency' version '' +} +``` + +Please replace `` in the above code with the latest version in +[Release](https://github.com/fankes/SweetDependency/releases), please note that **DO NOT** add `apply false` after it. + +After the above configuration is complete, run Gradle Sync once. + +**Pay Attention** + +`SweetDependency` will replace the repositories set in `pluginManagement` and `dependencyResolutionManagement`, +if you manually configure repositories in these method blocks, they will have no effect. + +If you want to continue to configure other content in `dependencyResolutionManagement`, such as `versionCatalogs`, it can only appear under `plugins`. + +We do not recommend continuing to configure `versionCatalogs`, because `SweetDependency` used some functions from it, +which may cause conflicts. + +Without exception, `SweetDependency` will automatically create a YAML configuration file for you, and you will get the following project structure +(take Android project as an example). + +``` +MyApplication + ├─ gradle + │ └─ sweet-dependency + │ └─ sweet-dependency-config.yaml <-- SweetDependency config file + ├─ build.gradle / build.gradle.kts + ├─ settings.gradle / settings.gradle.kts + ├─ app + │ └─ build.gradle / build.gradle.kts + └─ mylibrary + └─ build.gradle / build.gradle.kts +``` + +Then, please open the `sweet-dependency-config.yaml` configuration file to configure the basic configuration of `SweetDependency`. + +The default configuration has been automatically generated for you, and you can configure it according to your needs. + +If you think manual configuration is cumbersome, no problem, you can jump directly +to [Migrating Dependencies to Sweet Dependency](#migrating-dependencies-to-sweet-dependency) for reading. + +> The following example + +```yaml +# Configure preferences +preferences: + # Autowire and update dependency mode when Gradle Sync + # This option determines the behavior of Gradle Sync + # - UPDATE_OPTIONAL_DEPENDENCIES + # ↑ Default mode, autowire and update optional dependencies + # - UPDATE_ALL_DEPENDENCIES + # ↑ Autowire and update all dependencies + # - ONLY_AUTOWIRE_DEPENDENCIES + # ↑ Autowire only dependencies fill in "+" versions + # - UPDATE_OPTIONAL_PLUGINS + # ↑ Autowire and update optional dependencies (plugin dependencies) + # - UPDATE_ALL_PLUGINS + # ↑ Autowire and update all dependencies (plugin dependencies) + # - ONLY_AUTOWIRE_PLUGINS + # ↑ Autowire only dependencies fill in "+" versions (plugin dependencies) + # - UPDATE_OPTIONAL_LIBRARIES + # ↑ Autowire and update optional dependencies (library dependencies) + # - UPDATE_ALL_LIBRARIES + # ↑ Autowire and update all dependencies (library dependencies) + # - ONLY_AUTOWIRE_LIBRARIES + # ↑ Autowire only dependencies fill in "+" versions (library dependencies) + # - OFF + # ↑ Do nothing, turn off all functions + # Note: It is not recommended to turn off this function completely + # If there are dependencies that are not autowired, you will not be able to continue to deploy dependencies + autowire-on-sync-mode: UPDATE_OPTIONAL_DEPENDENCIES + # Repositories setup mode + # At present, Gradle provides the following 3 modes, the specific mode can refer to the official document + # - PREFER_PROJECT + # - PREFER_SETTINGS + # - FAIL_ON_PROJECT_REPOS + repositories-mode: FAIL_ON_PROJECT_REPOS + # Dependencies namespace + # After setting, you need to add the namespace as a prefix when deploying dependencies + # Only allow 26 letters (upper and lower case) and '.', '_', '-' and must start with a letter + # For example, we have a library dependency "com.mydemo.test:test" and use the "implementation" deployment method as an example + # No namespace: implementation(com.mydemo.test.test) + # Has namespace: implementation(libs.com.mydemo.test.test) + dependencies-namespace: + # Plugin dependencies namespace must exist, if not set, the default is "libs" + plugins: libs + # Library dependencies namespace is optional + # If you don't need the namespace of library dependencies, delete this node + libraries: libs + # Dependencies version filter + # If you need to exclude some dependency versions that you don't want to be updated to (such as test versions), + # you can configure them manually + # By default, the filter has automatically excluded the test version for you, no need to configure this + version-filter: + # Use built-in filters + # The default is enabled, and the internal filter contains keywords that may appear in all test versions + # Contains: "-beta", "-alpha", "-dev", "-canary", "-pre", "-rc", "-ga", "-snapshot" + # Versions that can be matched eg: "1.2.0-alpha01" or "1.1.2-beta01" + # If this option is disabled, only the keywords declared in "exclusion-list" will be used, + # if "exclusion-list" is empty, the filter will be disabled + use-internal: true + # Exclusion list + # You can fill in the custom keywords that need to be excluded in the exclusion list (case-insensitive) + # It is recommended to add "-" at the beginning to prevent misjudgment, + # for example: "bar" matches "1.0.0-bar01" and also matches "1.0.0-foobar01" + exclusion-list: + -foo + -bar + +# Configure repositories used by dependencies +repositories: + # The following content is just for example, you only need to add the repositories used + # Generally, you only need to add google and maven-central, + # and the default configuration file will automatically add them for you + # Each repository can configure url and path, depending on whether the repository supports this configuration method + # Currently SweetDependency is not compatible with custom repositories other than Maven + # The following node names are built-in repositories, you cannot use these names as custom repositories + google: # Google repository + maven-central: # Maven Central repository + maven-local: # Maven Local repository + # Generally, there is no need to configure the path of the local repository + # By default, it will be automatically obtained according to the default path of the following operating systems + # Windows: C:\Users\\.m2 + # Linux: /home//.m2 + # Mac: /Users//.m2 + # For details, please refer to https://www.baeldung.com/maven-local-repository + # If you modified the path of the repository, please re-specify here + # If you want to keep the default configuration, please delete this node + path: /path/to/repository + gradle-plugin-portal: # Gradle plugin repository + # The following lists common repository aliases currently built into SweetDependency + # Maven Central repository (branch) + maven-central-branch: + # JitPack + jit-pack: + # Alibaba Cloud Google repository mirror + # For Mainland China + aliyun-google-mirror: + # Alibaba Cloud Maven Central repository mirror + # For Mainland China + aliyun-maven-central-mirror: + # Alibaba Cloud public repository mirror + # For Mainland China + aliyun-maven-public-mirror: + # Alibaba Cloud JCenter mirror + # Note: JCenter has shut down and is no longer recommended + aliyun-jcenter-mirror: + # OSS repository + sonatype-oss-releases: + # Snapshot repository + sonatype-oss-snapshots: + # Custom Maven repository + # The custom repository node name can be filled in freely except for the built-in repository + your-custom-repo: + # All repositories are added and enabled, if you want to disable just add this configuration and set it to false + enable: true + # Set scope + # This option determines what type of dependencies this repository will be used for + # - ALL + # ↑ Default mode, which applies to all types of dependencies + # - PLUGINS + # ↑ Act on plugins + # - LIBRARIES + # ↑ Act on libraries + scope: ALL + # Custom content filter + # This feature can speed up Gradle's search for dependencies + # This can be used if this repository is known to contain only certain dependencies + content: + # Specify what needs to be included + # You can specify one or a group of content in different forms + include: + # This function accepts 1 argument + group: + androidx.appcompat + com.android + # This function accepts 1 argument + group-and-subgroups: + androidx.appcompat + # This function accepts 1 argument + group-by-regex: + androidx.* + com.android.* + # This function accepts 2 arguments, separated by ":" + # Must be 2 parameters, missing arguments will cause an error + module: + androidx.core:core + # This function accepts 2 arguments, separated by ":" + # Must be 2 parameters, missing arguments will cause an error + module-by-regex: + androidx.core:* + # This function accepts 3 arguments, separated by ":" + # Must be 3 parameters, missing arguments will cause an error + version: + androidx.core:core:1.9.0 + # This function accepts 3 arguments, separated by ":" + # Must be 3 parameters, missing arguments will cause an error + version-by-regex: + androidx.core:*:1.9.0 + # Specify what needs to be excluded + # You can specify one or a group of content in different forms + exclude: + group-by-regex: + androidx.* + com.android.* + group-and-subgroups: + androidx.appcompat + module: + androidx.core:core + module-by-regex: + androidx.core:* + version: + androidx.core:core:1.9.0 + version-by-regex: + androidx.core:*:1.9.0 + # Custom authentication + # If your repository requires authentication to access, you can add this node + # You can use ${...} to refer to the key-value content of the current project or system + # For specific usage, you can continue to read the "Configure String Interpolation" section at the end of the document + credentials: + # Your username + username: ${your-repo.username} + # Your password + password: ${your-repo.password} + # Custom repository URL + url: https://to.your.custom/repo + # Custom repository local path + # Note: There can only be one "url" and "path" parameter + path: /path/to/repository + +# Configure plugins that need to be used +plugins: + # Note: We only recommend declared some external repositories plugins here, some built-in Gradle plugins should not be declared here + # Note: Plugins need to declare a version, dependencies that do not declare versions will cause problems and are not recommended, + # and will not generate autowire code + # Plugin ID + com.android.application: + # Custom dependency alias (optional, will be used when deploying dependencies) + # Only allow 26 letters (upper and lower case) and '.', '_', '-' and must start with a letter + # (minimum 3 digits in length) + # The alias can be split into at least 2 parts by '.', '_', '-', for example "com-mytest" + alias: android-application + # Dependency version (if you are not sure about the version, you can fill in "+" and it will be autowiring) + version: 7.4.1 + # Whether the autowiring, update process will automatically update this dependency (fill in false when version is "+" has no effect) + auto-update: true + # Dependencies version filter, follow the global configuration by default + version-filter: + use-internal: true + exclusion-list: + -foo + -bar + # Specify the name of the repositories currently used by dependencies (multiple can be specified at the same time) + # When this parameter is not set, the current dependency will use all declared repositories to search in order + # If repositories where this dependency is located is known, it is recommended to set the repositories for it to improve efficiency + # For example: google, you can directly fill in google + # For example: maven-central, you can directly fill in maven-central + # Or the name you specified, such as the jit-pack node above, you need to fill in jit-pack + # Note: If you set a scope on the target repository and it does not match the current dependency type, it will be automatically excluded + # Note: If none of the repositories you set are available, the current dependency will be considered as non-existing repositories + repositories: + google + maven-central + com.android.library: + # If there are dependencies of the same version, + # you can use the version reference to avoid filling in the same version repeatedly + # The version reference content supports matching the full name of the dependency + # and the alias of the dependency + # Cannot reference dependencies that already exist version references (recursive call) + # Note: "version" and "version-ref" nodes can only appear once in a dependency + # Note: If you declare "version-ref", this dependency will be excluded from autowiring and updating + # Note: If you declare "version-ref", "auto-update", "repositories", "version-filter" will not work + version-ref: android-application # Or "com.android.application" + org.jetbrains.kotlin.android: + alias: kotlin-android + version: 1.8.10 + +# Configure libraries that need to be used +libraries: + # Group ID + androidx.core: + # Artifact ID + core: + # Custom dependency alias (optional, will be used when deploying dependencies) + # Only allow 26 letters (upper and lower case) and '.', '_', '-' and must start with a letter + # (minimum 3 digits in length) + # The alias can be split into at least 2 parts by '.', '_', '-', for example "com-mytest" + alias: androidx-core + # Dependency version (if you are not sure about the version, you can fill in "+" and it will be autowiring) + version: 1.9.0 + # Whether the autowiring, update process will automatically update this dependency (fill in false when version is "+" has no effect) + auto-update: true + # Dependencies version filter, follow the global configuration by default + version-filter: + use-internal: true + exclusion-list: + -foo + -bar + # Specify the name of the repositories currently used by dependencies (multiple can be specified at the same time) + # When this parameter is not set, the current dependency will use all declared repositories to search in order + # If repositories where this dependency is located is known, it is recommended to set the repositories for it to improve efficiency + # For example: google, you can directly fill in google + # For example: maven-central, you can directly fill in maven-central + # Or the name you specified, such as the jit-pack node above, you need to fill in jit-pack + # Note: If you set a scope on the target repository and it does not match the current dependency type, it will be automatically excluded + # Note: If none of the repositories you set are available, the current dependency will be considered as non-existing repositories + repositories: + google + maven-central + core-ktx: + alias: androidx-core-ktx + # If there are dependencies of the same version, + # you can use the version reference to avoid filling in the same version repeatedly + # The version reference content supports matching the full name of the dependency + # and the alias of the dependency + # If the currently referenced version is in the current "Group ID", + # you can directly fill in the "::Artifact ID" + # For example, it is currently "androidx.core", + # and the version that refers to "core" only needs to be filled in as "version-ref: ::core" + # Cannot reference dependencies that already exist version references (recursive call) + # Note: "version" and "version-ref" nodes can only appear once in a dependency + # Note: If you declare "version-ref", this dependency will be excluded from autowiring and updating + # Note: If you declare "version-ref", "auto-update", "repositories", "version-filter" will not work + version-ref: ::core # Or "androidx.core:core" and "androidx-core" (alias) + com.squareup.okhttp3: + okhttp: + # If you declare a version in the version that exists in "version-filter" (internal filter or exclude list) + # For example version "5.0.0-alpha.7" contains "-alpha" + # At this point you don't need to configure "version-filter" and set "use-internal: false" + # When running autowiring and updating dependencies, + # it will automatically update to the latest version that currently contains "-alpha" + version: 5.0.0-alpha.7 + com.google.android.material: + material: + alias: google-material-android + version: 1.8.0 + junit: + junit: + alias: junit + version: 4.13.2 + # If you are using a BOM dependency, you can declare it directly like most dependencies + org.springframework.boot: + spring-boot-dependencies: + alias: spring-boot-dependencies + version: 1.5.8.RELEASE + dom4j: + dom4j: + # You can use "" to declare that this dependency does not need to declare version + # If it is declared that it does not need to declare a version, + # it will automatically use the version declared in the BOM + # Note: If you declare "", this dependency will be excluded from autowiring and updating + # Note: If you declare "", "versions", "version-ref" will no longer be used + version: +``` + +`SweetDependency` takes over Gradle's dependency repositories, and the repositories declared in the config file will be used by both `SweetDependency` +and Gradle. + +After the above configuration is complete, run Gradle Sync once. + +Then, you can go to your project's `build.gradle` or `build.gradle.kts`, and migrate the dependency deployment method to `SweetDependency`. + +`SweetDependency` will automatically split the dependencies namespace, dependencies name, dependencies alias, etc. + +Note: If you set the plugins version in the `plugins` method block of `pluginManagement`, please remove it. + +> Kotlin DSL + +First, deploy plugins in root project, but not applied (consistent with the official Gradle recommendation). + +```kotlin +plugins { + // It is recommended to use the autowire method for deployment + // (you can also use the official alias method, which behaves the same) + // Due to the customization limitations of the plugins, + // the code generation here uses Gradle's own version catalogs + // Due to version catalogs, plugins must start with "namespace.plugins" + autowire(libs.plugins.com.android.application) apply false + autowire(libs.plugins.org.jetbrains.kotlin.android) apply false + // Use an alias + autowire(libs.plugins.android.application) apply false + autowire(libs.plugins.kotlin.android) apply false +} +``` + +Next, deploy plugins and libraries that need to be used in the sub-projects. + +```kotlin +plugins { + autowire(libs.plugins.com.android.application) + autowire(libs.plugins.org.jetbrains.kotlin.android) + // Use an alias + autowire(libs.plugins.android.application) + autowire(libs.plugins.kotlin.android) +} + +dependencies { + // Direct deployment + implementation(androidx.core.core.ktx) + implementation(com.google.android.material.material) + // Use an alias + implementation(androidx.core.ktx) + implementation(google.material.android) + // If you set up a dependenies namespace, please deploy the namespace as a prefix + implementation(libs.androidx.core.core.ktx) + // In the case of using dependencies namespace, the usage of dependencies aliase is still the same + implementation(libs.androidx.core.ktx) +} +``` + +> Groovy DSL + +First, deploy plugins in root project, but not applied (consistent with the official Gradle recommendation). + +```groovy +plugins { + // Groovy does not support deployment using the autowire method, + // you can only use the official alias method + // Due to the customization limitations of the plugins, + // the code generation here uses Gradle's own version catalogs + // Due to version catalogs, plugins must start with "namespace.plugins" + alias libs.plugins.com.android.application apply false + alias libs.plugins.org.jetbrains.kotlin.android apply false + // Use an alias + alias libs.plugins.android.application apply false + alias libs.plugins.kotlin.android apply false +} +``` + +Next, deploy plugins and libraries that need to be used in the sub-projects. + +```groovy +plugins { + alias libs.plugins.com.android.application + alias libs.plugins.org.jetbrains.kotlin.android + // Use an alias + alias libs.plugins.android.application + alias libs.plugins.kotlin.android +} + +dependencies { + // Direct deployment + implementation androidx.core.core.ktx + implementation com.google.android.material.material + // Use an alias + implementation androidx.core.ktx + implementation google.material.android + // If you set up a dependenies namespace, please deploy the namespace as a prefix + implementation libs.androidx.core.core.ktx + // In the case of using dependencies namespace, the usage of dependencies aliase is still the same + implementation libs.androidx.core.ktx +} +``` + +**Pay Attention** + +Names such as `ext`, `extra`, `extraProperties`, and `extensions` are the default extension methods that come with Gradle when creating extension +methods. + +When `SweetDependency` encounters these names when generating the first dependency extension method, it will not be generated normally. + +The solution is to add `s` at the end of the name. + +If you must use these names as dependencies name or alias, you may consider set a dependencies namespace. + +Dependencies starting with these names are not currently collected in the Maven repository. + +Certainly, you cannot directly use these built-in default extension method names to set dependencies namespace, dependencies aliase, etc. + +**Possible Problems** + +If your project only has one root project and does not import any sub-projects, +if extension methods in `dependencies` are not generated properly, +you can solve this problem by migrating your root project to a sub-projects and importing this sub-projects in `settings.gradle` +or `settings.gradle.kts`. + +We generally recommend classifying the functions of the project, and the root project is only used to manage plugins and some configurations. + +**Limitations Note** + +`SweetDependency` cannot manage the `plugins` method block in `settings.gradle` or `settings.gradle.kts`, +because this belongs to the upstream of `SweetDependency`, please use the usual way to manage this situation. + +### Kotlin Multiplatform Support + +In Kotlin Multiplatform, it is consistent with the general dependency deployment method. + +> Kotlin DSL + +```kotlin +sourceSets { + val androidMain by getting { + dependencies { + implementation(androidx.core.core.ktx) + implementation(com.google.android.material.material) + } + } +} +``` + +> Groovy DSL + +```groovy +sourceSets { + androidMain { + dependencies { + implementation androidx.core.core.ktx + implementation com.google.android.material.material + } + } +} +``` + +## Migrating Dependencies to Sweet Dependency + +If you are starting to use `SweetDependency` for the first time, you can manually run the create dependencies migration template task. + +You can find the `createDependenciesMigrationTemplate` task in the task group `sweet-dependency` of the root project, and run it manually. + +This operation will automatically analyze all external repository dependencies in the projects and generate the `*.template.yaml` file prefixed with +the configuration file name in the root project's `gradle/sweet-dependency`. + +If you haven't changed the name of the configuration file, it will default to `sweet-dependency-config.template.yaml`. + +If the generated template file already exists, it will be overwritten automatically. + +The template file will provide the dependencies nodes used by the current projects. + +Please manually copy all the content of the generated nodes to the configuration file and delete the template file. + +> The following example + +```yaml +plugins: + ... +libraries: + ... +``` + +Please note that `SweetDependency` will not create a dependency repositories in the template file, please add the dependency repositories manually or +use the automatically generated repositories in the first configuration file. + +Then please manually migrate external repository dependencies to `SweetDependency` in the method body of `plugins` and `dependencies` of each +project's `build.gradle` or `build.gradle.kts`. + +Below is an example for reference. + +> Kotlin DSL + +```kotlin +plugins { + // Original deployment writing method + id("org.jetbrains.kotlin.android") version "1.8.10" + // Writing after migration + autowire(libs.plugins.org.jetbrains.kotlin.android) +} + +dependencies { + // Original deployment writing method + implementation("androidx.core:core-ktx:1.9.0") + // Writing after migration + implementation(androidx.core.core.ktx) +} +``` + +> Groovy DSL + +```groovy +plugins { + // Original deployment writing method + id 'org.jetbrains.kotlin.android' version '1.8.10' + // Writing after migration + alias libs.plugins.org.jetbrains.kotlin.android +} + +dependencies { + // Original deployment writing method + implementation 'androidx.core:core-ktx:1.9.0' + // Writing after migration + implementation androidx.core.core.ktx +} +``` + +If you are using `versionCatalogs`, please delete declared them in `settings.gradle` or `settings.gradle.kts` too. + +If you declared `versionCatalogs` using TOML, such as a `libs.versions.toml` file, you don't need it now, +and you can delete it after migrating dependencies. + +Please note that the template file is only used for migration dependencies, it should not appear in the version control system, it is recommended to +delete it after use. + +## Configure Dependencies Autowiring + +By default, running Gradle Sync will perform a search and autowire and update dependencies. + +Dependencies autowire logs will be written to `.gradle/sweet-dependency/dependencies-autowire.log` of the root project. + +You can find the method to configure whether to enable dependency autowiring logging at the bottom of this document. + +You can configure the mode of `preferences.autowire-on-sync-mode` in `sweet-dependency-config.yaml`. + +You can also manually run the following Gradle tasks when you need, you can find these tasks in the root project's task group `sweet-dependency`. + +- updateOptionalDependencies +- updateOptionalPlugins +- updateOptionalLibraries + +Autowire and update optional dependencies. + +The ones ending with "Plugins" only manage plugins, and the ones ending with "Libraries" only manage libraries. + +You can use `^` in the configuration file to identify optional update dependencies. + +> The following example + +```yaml +plugins: + # The optional update behavior of plugins is consistent with libraries, + # please refer to the example of libraries below + ... + +libraries: + com.google.android.material: + material: + # Use "^" as the beginning to identify the current version, + # it will be replaced with the latest version when there is an update + # Use "^" as the beginning to identify the current version will remove this symbol + # after the next successful update (single optional update) + # If you want to keep this symbol persistent (permanent optional update), + # please double write it, like "^^" + version: ^1.8.0 +``` + +- updateAllDependencies +- updateAllPlugins +- updateAllLibraries + +Autowire and update all dependencies. + +The ones ending with "Plugins" only manage plugins, and the ones ending with "Libraries" only manage libraries. + +All dependencies will be checked for updates and updated to the latest version, +which can be very time-consuming when there are too many dependencies. + +- autowireDependencies +- autowirePlugins +- autowireLibraries + +Only autowire fill in "+" version dependencies. + +The ones ending with "Plugins" only manage plugins, and the ones ending with "Libraries" only manage libraries. + +**Pay Attention** + +After dependencies are autowired or updated, you need to manually run Gradle Sync for the changes to take effect, +if you don't do this, the changes will take effect on the next compile or any Gradle activity. + +## Configure Dependencies Extensions + +You can autowire arbitrary dependencies using the `autowire(...)` method. + +Note: Some features may not work with Groovy DSL, please start using or move to Kotlin DSL if needed. + +Below is a simple example. + +> Kotlin DSL + +```kotlin +plugins { + // Deployment "org.jetbrains.kotlin.android" + autowire("org.jetbrains.kotlin.android") + // Deploy with an alias + autowire("kotlin-android") +} + +dependencies { + // Deployment "androidx.core:core-ktx" + implementation(autowire("androidx.core:core-ktx")) + // Deploy with an alias + implementation(autowire("androidx-core-ktx")) +} +``` + +> Groovy DSL + +```groovy +plugins { + // Unfortunately, Gradle does not allow custom plugins method blocks using the usual scheme + // This is Gradle's restriction on custom plugins, and plugins cannot intervene + // Therefore, the autowire method will not support Groovy DSL + // Recommended to start using or switch to Kotlin DSL if needed +} + +dependencies { + // Deployment "androidx.core:core-ktx" + implementation sweet.autowire('androidx.core:core-ktx') + // Deploy with an alias + implementation sweet.autowire('androidx-core-ktx') +} +``` + +In addition to autowiring dependencies from plugins and external repositories, you can also use it to import file collection dependencies. + +> Kotlin DSL + +```kotlin +dependencies { + // Import all jar dependencies in the libs directory of the current project + implementation(autowire("libs/*.jar")) + // Import all jar dependencies in the libs directory of the mylibrary project + implementation(autowire("../mylibrary/libs/*.jar")) + // Import all jar dependencies in an absolute path directory + implementation(autowire("/home/test/someDepends/*.jar")) + // Import all dependencies in the libs directory of the current project, regardless of file extension + implementation(autowire("libs/*")) + // You can also import one by one or a group of files + implementation( + autowire( + "libs/*.jar", + "libs/*.aar", + "/home/test/someDepends/mylibrary-1.jar", + "/home/test/someDepends/mylibrary-2.jar" + ) + ) + // The following is a special case + // If you directly import a file that has no directory hierarchy and is relative to the current project path, + // it may not be recognized directly + // For example, we directly import "mylibrary.jar" under the current project path + // The following will recognize "mylibrary.jar" as an external repository dependency + implementation(autowire("mylibrary.jar")) + // To emphasize that this dependency is a file, use parentheses around the file path + implementation(autowire("(mylibrary.jar)")) +} +``` + +> Groovy DSL + +```groovy +dependencies { + // Import all jar dependencies in the libs directory of the current project + implementation sweet.autowire('libs/*.jar') + // Import all jar dependencies in the libs directory of the mylibrary project + implementation sweet.autowire('../mylibrary/libs/*.jar') + // Import all jar dependencies in an absolute path directory + implementation sweet.autowire('/home/test/someDepends/*.jar') + // Import all dependencies in the libs directory of the current project, regardless of file extension + implementation sweet.autowire('libs/*') + // You can also import one by one or a group of files + implementation sweet.autowire( + 'libs/*.jar', + 'libs/*.aar', + '/home/test/someDepends/mylibrary-1.jar', + '/home/test/someDepends/mylibrary-2.jar' + ) + // The following is a special case + // If you directly import a file that has no directory hierarchy and is relative to the current project path, + // it may not be recognized directly + // For example, we directly import "mylibrary.jar" under the current project path + // The following will recognize "mylibrary.jar" as an external repository dependency + implementation sweet.autowire('mylibrary.jar') + // To emphasize that this dependency is a file, use parentheses around the file path + implementation sweet.autowire('(mylibrary.jar)') +} +``` + +### Kotlin Multiplatform Support + +In Kotlin Multiplatform, it is consistent with the general dependency deployment method. + +> Kotlin DSL + +```kotlin +sourceSets { + val androidMain by getting { + dependencies { + implementation(autowire("androidx.core:core-ktx")) + implementation(autowire("libs/*.jar")) + } + } +} +``` + +> Groovy DSL + +```groovy +sourceSets { + androidMain { + dependencies { + implementation sweet.autowire('androidx.core:core-ktx') + implementation sweet.autowire('libs/*.jar') + } + } +} +``` + +## Configure Dependencies Version Declares + +There are some dependencies versions that we want to keep fixed in the project and not easily updated or changed. + +For this case, you can use version declares to predeclare these versions in the configuration file. + +> The following example + +```yaml +# Declare some versions to use +versions: + # Node name only allow 26 letters (upper and lower case) and '.', '_', '-' and must start with a letter + mydemo-test: 1.0.0 + +# Reference these versions in plugins declare +plugins: + # The dependencies version declares of plugins is consistent with libraries, + # please refer to the example of libraries below + ... + +# Reference these versions in libraries declare +libraries: + com.mydemo.test: + test: + # You can reference the declared version directly at this node + # The node of the version declared name is higher than that of the dependencies name and alias + # If the same name exists, the former will be used first + version-ref: mydemo-test +``` + +## Configure Dependencies Version Aliases + +By default, the version of dependencies declared in the configuration file is fixed, and the version of the deployed dependencies follows the version +in the definition. + +If you have such a requirement: the version of the same dependency needs to be different from the version of the dependency in other sub-projects or +the main project. + +> The "A" project following example + +```kotlin +plugins { + id("com.mydemo.myplugin") version "1.0.1" +} + +dependencies { + implementation("com.mydemo.test:test:1.0.1") +} +``` + +> The "B" project following example + +```kotlin +plugins { + id("com.mydemo.myplugin") version "1.0.2" +} + +dependencies { + implementation("com.mydemo.test:test:1.0.2") +} +``` + +For this situation, you can use version aliases to declare multiple different versions in the configuration file. + +> The following example + +```yaml +plugins: + com.mydemo.myplugin: + alias: demo-myplugin + # Dependency version (current major version, must exist) + # You can also use "version-ref" + version: 1.0.2 + # Custom version alias + # Only allow 26 letters (upper and lower case) and '.', '_', '-' and must start with a letter + # (minimum 3 digits in length) + # Version cannot fill in "+", because the version declared by the version alias will not be autowiring + versions: + a-version: 1.0.1 + # If you want to follow the main version, you can fill in "" + b-version: + +libraries: + com.mydemo.test: + test: + alias: demo-test + # Dependency version (current major version, must exist) + # You can also use "version-ref" + version: 1.0.2 + # Custom version alias + # Only allow 26 letters (upper and lower case) and '.', '_', '-' and must start with a letter + # (minimum 3 digits in length) + # Version cannot fill in "+", because the version declared by the version alias will not be autowiring + versions: + a-version: 1.0.1 + # If you want to follow the main version, you can fill in "" + b-version: +``` + +Then you can use `.` directly after the current dependency to use its version alias. + +The version aliases will be automatically converted to lower camel case. + +It is recommended that all aliases be expressed in lowercase letters. + +> The "A" project following example + +```kotlin +plugins { + autowire(libs.plugins.com.mydemo.myplugin.aVersion) + // You can also use dependencies alias directly + autowire(libs.plugins.demo.myplugin.aVersion) + // Or use the autowire method to deploy + // Note that you need to fill in the version alias in the second method parameter of autowire + autowire("com.mydemo.myplugin", "a-version") + autowire("demo-myplugin", "a-version") +} + +dependencies { + implementation(com.mydemo.test.test.aVersion) + // You can also use dependencies alias directly + implementation(demo.test.aVersion) + // Or use the autowire method to deploy + // Note that you need to fill in the version alias in the second method parameter of autowire + implementation(autowire("com.mydemo.test:test", "a-version")) + implementation(autowire("demo-test", "a-version")) +} +``` + +> The "B" project following example + +```kotlin +plugins { + autowire(libs.plugins.com.mydemo.myplugin.bVersion) + // You can also use dependencies alias directly + autowire(libs.plugins.demo.myplugin.bVersion) + // Or use the autowire method to deploy + // Note that you need to fill in the version alias in the second method parameter of autowire + autowire("com.mydemo.myplugin", "b-version") + autowire("demo-myplugin", "b-version") +} + +dependencies { + implementation(com.mydemo.test.test.bVersion) + // You can also use dependencies alias directly + implementation(demo.test.bVersion) + // Or use the autowire method to deploy + // Note that you need to fill in the version alias in the second method parameter of autowire + implementation(autowire("com.mydemo.test:test", "b-version")) + implementation(autowire("demo-test", "b-version")) +} +``` + +Note: Some features may not be available in `plugins` in the Groovy DSL. + +If you don't specify a version alias, the deployed dependency defaults to using the current major version of the dependency (that is the version +declared by "version"). + +**Pay Attention** + +If there is a dependency relationship between project A and project B in the above example, +it will use the newer version of the two first (except plugins). + +This is Gradle's dependency inheritance rule and is not controlled by version aliases. + +No specific version dependencies cannot use the dependencies version aliases function. + +## Configure String Interpolation + +You can use ${...} to dynamically insert content into `SweetDependency` config file, +so you can export some sensitive information from your config file. + +Where `...` represents the currently used KEY (key value name). + +`SweetDependency` will look for content to insert from the following locations in order of priority: + +- The current project (Root Project)'s `gradle.properties` +- The current user's `gradle.properties` +- System's `System.getProperties()` +- System's `System.getenv(...)` + +> The following example + +```yaml +# Configure the repository used by dependencies +repositories: + your-custom-repo: + credentials: + username: ${your-repo.username} + password: ${your-repo.password} + url: ${your-repo.url} + +# Configure plugins that need to be used +plugins: + com.android.application: + version: + + +# Configure libraries that need to be used +libraries: + androidx.core: + # It can also be set on the node + ${depends.androidx.core.name}: + version: + + versions: + # Or specific content + a-version: ${depends.androidx.core.core.a-version} +``` + +When reading config file, `SweetDependency` will first replace these contents with actual strings before parsing. + +If no corresponding content is found for the currently used KEY (key value name), an empty string will be returned. + +## Autowire Optimization Suggestions + +Now that you understand the basic functionality of `SweetDependency`, here are some optimization suggestions for existing project repositories and +dependencies. + +### Repositories Section + +According to Gradle's dependencies search rules, the order in which the repositories are added makes sense, +and the search order will be in the order you added them. + +You can adjust the order of the repositories appropriately, which will help improve dependencies search efficiency. + +> The following example + +```yaml +repositories: + google: + maven-central: +``` + +For users in mainland China, you can use the mirror server address preset by `SweetDependency` to speed up the dependencies search. + +> The following example + +```yaml +repositories: + aliyun-google-mirror: + aliyun-maven-central-mirror: + aliyun-maven-public-mirror: + aliyun-jcenter-mirror: +``` + +You can also set the `content` parameter to the `google` repository to improve its efficiency, because it currently only contains the dependencies +starting with the following. + +- androidx.* +- com.google.* +- com.android.* + +> The following example + +```yaml +repositories: + google: + content: + include: + group-by-regex: + androidx.* + com.google.* + com.android.* +``` + +You can also set the `scope` parameter of the `gradle-plugin-portal` repository to `PLUGINS` to improve search efficiency, +since it will only be applied to plugins. + +> The following example + +```yaml +repositories: + gradle-plugin-portal: + scope: PLUGINS +``` + +It is recommended to rank `gradle-plugin-portal` first among all repositories, and plugins will be searched using it first. + +### Dependencies Section + +You can set repositories used by a given dependency to reduce the time spent on autowiring searches. + +You can use `version-ref` when appropriate, which can reduce the time-consuming of repeatedly searching for the same version of dependencies. + +> The following example + +```yaml +plugins: + com.android.application: + alias: android-application + version: 7.4.1 + repositories: + google + com.android.library: + version-ref: android-application + +libraries: + androidx.core: + core-ktx: + version: 1.9.0 + repositories: + google +``` + +## Dump Debug Information + +You can manually run the `sweetDependencyDebug` task to dump debug information, which you can find in the root project's task +group `sweet-dependency`. + +This operation will dump the data structure of the current `SweetDependency` in memory to the console, you can refer to the configuration file to +check whether the data is correct. + +If you think `SweetDependency` is not working as expected, you can also provide us with this data so we can debug and fix it. + +## Custom Preferences + +You can configure `SweetDependency` using `sweetDependency` lambda method in `settings.gradle` or `settings.gradle.kts` of the root project. + +> Kotlin DSL + +```kotlin +sweetDependency { + + // Enable SweetDependency, set to false will disable all functions + isEnable = true + + // SweetDependency configuration file name + configFileName = "sweet-dependency-config.yaml" + + // Whether to enable dependency autowiring logging + // This function is enabled by default and will create a log file in the ".gradle/sweet-dependency" directory of the current root project + isEnableDependenciesAutowireLog = true + + // Whether to enable verbose mode + // This function is enabled by default, and when disabled, + // SweetDependency will be silent when not necessary (omit unnecessary logs) + isEnableVerboseMode = true +} +``` + +> Groovy DSL + +```groovy +sweetDependency { + enable true + configFileName 'sweet-dependency-config.yaml' + enableDependenciesAutowireLog true + enableVerboseMode true +} +``` + +## Feedback + +If you encounter any problems while using `SweetDependency`, you can always open an `issues` on GitHub to give us feedback. \ No newline at end of file diff --git a/examples/sample-android/.gitignore b/examples/sample-android/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/examples/sample-android/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/examples/sample-android/build.gradle.kts b/examples/sample-android/build.gradle.kts new file mode 100644 index 0000000..9877187 --- /dev/null +++ b/examples/sample-android/build.gradle.kts @@ -0,0 +1,5 @@ +plugins { + autowire(libs.plugins.com.android.application) apply false + autowire(libs.plugins.com.android.library) apply false + autowire(libs.plugins.org.jetbrains.kotlin.android) apply false +} \ No newline at end of file diff --git a/examples/sample-android/demo-app/.gitignore b/examples/sample-android/demo-app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/examples/sample-android/demo-app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/examples/sample-android/demo-app/build.gradle.kts b/examples/sample-android/demo-app/build.gradle.kts new file mode 100644 index 0000000..99587cb --- /dev/null +++ b/examples/sample-android/demo-app/build.gradle.kts @@ -0,0 +1,42 @@ +plugins { + autowire(libs.plugins.com.android.application) + autowire(libs.plugins.org.jetbrains.kotlin.android) +} + +android { + namespace = "com.highcapable.sweetdependency.demo_app" + compileSdk = 33 + + defaultConfig { + applicationId = "com.highcapable.sweetdependency.demo_app" + minSdk = 24 + targetSdk = 33 + versionCode = 1 + versionName = "1.0" + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + kotlinOptions { + jvmTarget = "17" + } +} + +dependencies { + implementation(androidx.core.core.ktx) + implementation(androidx.appcompat.appcompat) + implementation(com.google.android.material.material) + implementation(androidx.constraintlayout.constraintlayout) + testImplementation(junit.junit) + androidTestImplementation(androidx.test.ext.junit) + androidTestImplementation(androidx.test.espresso.espresso.core) +} \ No newline at end of file diff --git a/examples/sample-android/demo-app/proguard-rules.pro b/examples/sample-android/demo-app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/examples/sample-android/demo-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/examples/sample-android/demo-app/src/androidTest/java/com/highcapable/sweetdependency/demo_app/ExampleInstrumentedTest.kt b/examples/sample-android/demo-app/src/androidTest/java/com/highcapable/sweetdependency/demo_app/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..b0982fe --- /dev/null +++ b/examples/sample-android/demo-app/src/androidTest/java/com/highcapable/sweetdependency/demo_app/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.highcapable.sweetdependency.demo_app + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.highcapable.sweetdependency.demo_app", appContext.packageName) + } +} \ No newline at end of file diff --git a/examples/sample-android/demo-app/src/main/AndroidManifest.xml b/examples/sample-android/demo-app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..aceeb8b --- /dev/null +++ b/examples/sample-android/demo-app/src/main/AndroidManifest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/sample-android/demo-app/src/main/java/com/highcapable/sweetdependency/demo_app/MainActivity.kt b/examples/sample-android/demo-app/src/main/java/com/highcapable/sweetdependency/demo_app/MainActivity.kt new file mode 100644 index 0000000..bd3cbb5 --- /dev/null +++ b/examples/sample-android/demo-app/src/main/java/com/highcapable/sweetdependency/demo_app/MainActivity.kt @@ -0,0 +1,12 @@ +package com.highcapable.sweetdependency.demo_app + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity + +class MainActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + } +} \ No newline at end of file diff --git a/examples/sample-android/demo-app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/examples/sample-android/demo-app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/examples/sample-android/demo-app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/examples/sample-android/demo-app/src/main/res/drawable/ic_launcher_background.xml b/examples/sample-android/demo-app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/examples/sample-android/demo-app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/sample-android/demo-app/src/main/res/layout/activity_main.xml b/examples/sample-android/demo-app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..0c068ae --- /dev/null +++ b/examples/sample-android/demo-app/src/main/res/layout/activity_main.xml @@ -0,0 +1,17 @@ + + + + + \ No newline at end of file diff --git a/examples/sample-android/demo-app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/examples/sample-android/demo-app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/examples/sample-android/demo-app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/examples/sample-android/demo-app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/examples/sample-android/demo-app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/examples/sample-android/demo-app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/examples/sample-android/demo-app/src/main/res/mipmap-anydpi-v33/ic_launcher.xml b/examples/sample-android/demo-app/src/main/res/mipmap-anydpi-v33/ic_launcher.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/examples/sample-android/demo-app/src/main/res/mipmap-anydpi-v33/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/examples/sample-android/demo-app/src/main/res/mipmap-hdpi/ic_launcher.webp b/examples/sample-android/demo-app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..e69de29 diff --git a/examples/sample-android/demo-app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/examples/sample-android/demo-app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..e69de29 diff --git a/examples/sample-android/demo-app/src/main/res/mipmap-mdpi/ic_launcher.webp b/examples/sample-android/demo-app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..e69de29 diff --git a/examples/sample-android/demo-app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/examples/sample-android/demo-app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..e69de29 diff --git a/examples/sample-android/demo-app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/examples/sample-android/demo-app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..e69de29 diff --git a/examples/sample-android/demo-app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/examples/sample-android/demo-app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..e69de29 diff --git a/examples/sample-android/demo-app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/examples/sample-android/demo-app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..e69de29 diff --git a/examples/sample-android/demo-app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/examples/sample-android/demo-app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..e69de29 diff --git a/examples/sample-android/demo-app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/examples/sample-android/demo-app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..e69de29 diff --git a/examples/sample-android/demo-app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/examples/sample-android/demo-app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..e69de29 diff --git a/examples/sample-android/demo-app/src/main/res/values-night/themes.xml b/examples/sample-android/demo-app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..34f316e --- /dev/null +++ b/examples/sample-android/demo-app/src/main/res/values-night/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/examples/sample-android/demo-app/src/main/res/values/colors.xml b/examples/sample-android/demo-app/src/main/res/values/colors.xml new file mode 100644 index 0000000..f8c6127 --- /dev/null +++ b/examples/sample-android/demo-app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/examples/sample-android/demo-app/src/main/res/values/strings.xml b/examples/sample-android/demo-app/src/main/res/values/strings.xml new file mode 100644 index 0000000..8436732 --- /dev/null +++ b/examples/sample-android/demo-app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + SweetDependency + \ No newline at end of file diff --git a/examples/sample-android/demo-app/src/main/res/values/themes.xml b/examples/sample-android/demo-app/src/main/res/values/themes.xml new file mode 100644 index 0000000..9653717 --- /dev/null +++ b/examples/sample-android/demo-app/src/main/res/values/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/examples/sample-android/demo-app/src/main/res/xml/backup_rules.xml b/examples/sample-android/demo-app/src/main/res/xml/backup_rules.xml new file mode 100644 index 0000000..fa0f996 --- /dev/null +++ b/examples/sample-android/demo-app/src/main/res/xml/backup_rules.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/examples/sample-android/demo-app/src/main/res/xml/data_extraction_rules.xml b/examples/sample-android/demo-app/src/main/res/xml/data_extraction_rules.xml new file mode 100644 index 0000000..9ee9997 --- /dev/null +++ b/examples/sample-android/demo-app/src/main/res/xml/data_extraction_rules.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/examples/sample-android/demo-app/src/test/java/com/highcapable/sweetdependency/demo_app/ExampleUnitTest.kt b/examples/sample-android/demo-app/src/test/java/com/highcapable/sweetdependency/demo_app/ExampleUnitTest.kt new file mode 100644 index 0000000..d7de63f --- /dev/null +++ b/examples/sample-android/demo-app/src/test/java/com/highcapable/sweetdependency/demo_app/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.highcapable.sweetdependency.demo_app + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/examples/sample-android/demo-library/.gitignore b/examples/sample-android/demo-library/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/examples/sample-android/demo-library/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/examples/sample-android/demo-library/build.gradle.kts b/examples/sample-android/demo-library/build.gradle.kts new file mode 100644 index 0000000..5a32d1f --- /dev/null +++ b/examples/sample-android/demo-library/build.gradle.kts @@ -0,0 +1,40 @@ +plugins { + autowire(libs.plugins.com.android.library) + autowire(libs.plugins.org.jetbrains.kotlin.android) +} + +android { + namespace = "com.highcapable.sweetdependency.demo_library" + compileSdk = 33 + + defaultConfig { + minSdk = 24 + targetSdk = 33 + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + kotlinOptions { + jvmTarget = "17" + } +} + +dependencies { + implementation(androidx.core.core.ktx) + implementation(androidx.appcompat.appcompat) + implementation(com.google.android.material.material) + implementation(androidx.constraintlayout.constraintlayout) + testImplementation(junit.junit) + androidTestImplementation(androidx.test.ext.junit) + androidTestImplementation(androidx.test.espresso.espresso.core) +} \ No newline at end of file diff --git a/examples/sample-android/demo-library/consumer-rules.pro b/examples/sample-android/demo-library/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/examples/sample-android/demo-library/proguard-rules.pro b/examples/sample-android/demo-library/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/examples/sample-android/demo-library/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/examples/sample-android/demo-library/src/androidTest/java/com/highcapable/sweetdependency/demo_library/ExampleInstrumentedTest.kt b/examples/sample-android/demo-library/src/androidTest/java/com/highcapable/sweetdependency/demo_library/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..1a86cda --- /dev/null +++ b/examples/sample-android/demo-library/src/androidTest/java/com/highcapable/sweetdependency/demo_library/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.highcapable.sweetdependency.demo_library + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.highcapable.sweetdependency.demo_library.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/examples/sample-android/demo-library/src/main/AndroidManifest.xml b/examples/sample-android/demo-library/src/main/AndroidManifest.xml new file mode 100644 index 0000000..568741e --- /dev/null +++ b/examples/sample-android/demo-library/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/examples/sample-android/demo-library/src/main/java/com/highcapable/sweetdependency/demo_library/DemoLibrary.kt b/examples/sample-android/demo-library/src/main/java/com/highcapable/sweetdependency/demo_library/DemoLibrary.kt new file mode 100644 index 0000000..7f48e5b --- /dev/null +++ b/examples/sample-android/demo-library/src/main/java/com/highcapable/sweetdependency/demo_library/DemoLibrary.kt @@ -0,0 +1,3 @@ +package com.highcapable.sweetdependency.demo_library + +class DemoLibrary \ No newline at end of file diff --git a/examples/sample-android/demo-library/src/test/java/com/highcapable/sweetdependency/demo_library/ExampleUnitTest.kt b/examples/sample-android/demo-library/src/test/java/com/highcapable/sweetdependency/demo_library/ExampleUnitTest.kt new file mode 100644 index 0000000..f65954f --- /dev/null +++ b/examples/sample-android/demo-library/src/test/java/com/highcapable/sweetdependency/demo_library/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.highcapable.sweetdependency.demo_library + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/examples/sample-android/gradle.properties b/examples/sample-android/gradle.properties new file mode 100644 index 0000000..3c5031e --- /dev/null +++ b/examples/sample-android/gradle.properties @@ -0,0 +1,23 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true \ No newline at end of file diff --git a/examples/sample-android/gradle/sweet-dependency/sweet-dependency-config.yaml b/examples/sample-android/gradle/sweet-dependency/sweet-dependency-config.yaml new file mode 100644 index 0000000..45ba3f1 --- /dev/null +++ b/examples/sample-android/gradle/sweet-dependency/sweet-dependency-config.yaml @@ -0,0 +1,42 @@ +preferences: + autowire-on-sync-mode: UPDATE_OPTIONAL_DEPENDENCIES + repositories-mode: FAIL_ON_PROJECT_REPOS + +repositories: + gradle-plugin-portal: + scope: PLUGINS + google: + maven-central: + +plugins: + com.android.application: + version: + + com.android.library: + version-ref: com.android.application + org.jetbrains.kotlin.android: + version: + + +libraries: + com.google.android.material: + material: + version: + + androidx.constraintlayout: + constraintlayout: + version: + + junit: + junit: + version: + + androidx.appcompat: + appcompat: + version: + + androidx.core: + core: + version: + + core-ktx: + version-ref: ::core + androidx.test.ext: + junit: + version: + + androidx.test.espresso: + espresso-core: + version: + \ No newline at end of file diff --git a/examples/sample-android/gradle/wrapper/gradle-wrapper.jar b/examples/sample-android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e69de29 diff --git a/examples/sample-android/gradle/wrapper/gradle-wrapper.properties b/examples/sample-android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..545c4c0 --- /dev/null +++ b/examples/sample-android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME \ No newline at end of file diff --git a/examples/sample-android/gradlew b/examples/sample-android/gradlew new file mode 100644 index 0000000..4f906e0 --- /dev/null +++ b/examples/sample-android/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# 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 +# +# https://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. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/examples/sample-android/gradlew.bat b/examples/sample-android/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/examples/sample-android/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/examples/sample-android/settings.gradle.kts b/examples/sample-android/settings.gradle.kts new file mode 100644 index 0000000..623ab33 --- /dev/null +++ b/examples/sample-android/settings.gradle.kts @@ -0,0 +1,20 @@ +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } +} +plugins { + // Import the SweetDependency plugin here + // 在这里引入 SweetDependency 插件 + id("com.highcapable.sweetdependency") version "1.0.0" +} +sweetDependency { + configFileName = "sweet-dependency-config.yaml" + isEnableDependenciesAutowireLog = true + isEnableVerboseMode = true +} +rootProject.name = "SweetDependency-Sample-Android" +include(":demo-app") +include(":demo-library") \ No newline at end of file diff --git a/examples/sample-jvm/.gitignore b/examples/sample-jvm/.gitignore new file mode 100644 index 0000000..b63da45 --- /dev/null +++ b/examples/sample-jvm/.gitignore @@ -0,0 +1,42 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/examples/sample-jvm/build.gradle.kts b/examples/sample-jvm/build.gradle.kts new file mode 100644 index 0000000..d2cbf0a --- /dev/null +++ b/examples/sample-jvm/build.gradle.kts @@ -0,0 +1,3 @@ +plugins { + autowire(libs.plugins.org.jetbrains.kotlin.jvm) apply false +} \ No newline at end of file diff --git a/examples/sample-jvm/gradle.properties b/examples/sample-jvm/gradle.properties new file mode 100644 index 0000000..29e08e8 --- /dev/null +++ b/examples/sample-jvm/gradle.properties @@ -0,0 +1 @@ +kotlin.code.style=official \ No newline at end of file diff --git a/examples/sample-jvm/gradle/sweet-dependency/sweet-dependency-config.yaml b/examples/sample-jvm/gradle/sweet-dependency/sweet-dependency-config.yaml new file mode 100644 index 0000000..0700ab1 --- /dev/null +++ b/examples/sample-jvm/gradle/sweet-dependency/sweet-dependency-config.yaml @@ -0,0 +1,18 @@ +preferences: + autowire-on-sync-mode: UPDATE_OPTIONAL_DEPENDENCIES + repositories-mode: FAIL_ON_PROJECT_REPOS + +repositories: + gradle-plugin-portal: + scope: PLUGINS + google: + maven-central: + +plugins: + org.jetbrains.kotlin.jvm: + version: + + +libraries: + org.jetbrains.kotlin: + kotlin-test: + version: + \ No newline at end of file diff --git a/examples/sample-jvm/gradle/wrapper/gradle-wrapper.jar b/examples/sample-jvm/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e69de29 diff --git a/examples/sample-jvm/gradle/wrapper/gradle-wrapper.properties b/examples/sample-jvm/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..8f5ef1a --- /dev/null +++ b/examples/sample-jvm/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists \ No newline at end of file diff --git a/examples/sample-jvm/gradlew b/examples/sample-jvm/gradlew new file mode 100644 index 0000000..1b6c787 --- /dev/null +++ b/examples/sample-jvm/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# 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 +# +# https://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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/examples/sample-jvm/gradlew.bat b/examples/sample-jvm/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/examples/sample-jvm/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/examples/sample-jvm/sample-jvm/build.gradle.kts b/examples/sample-jvm/sample-jvm/build.gradle.kts new file mode 100644 index 0000000..f7fb09a --- /dev/null +++ b/examples/sample-jvm/sample-jvm/build.gradle.kts @@ -0,0 +1,23 @@ +plugins { + autowire(libs.plugins.org.jetbrains.kotlin.jvm) + application +} + +group = "com.highcapable.sweetdependency.demo_jvm" +version = "1.0-SNAPSHOT" + +tasks.test { + useJUnitPlatform() +} + +kotlin { + jvmToolchain(17) +} + +application { + mainClass.set("com.highcapable.sweetdependency.demo_jvm.MainKt") +} + +dependencies { + testImplementation(org.jetbrains.kotlin.kotlin.test) +} \ No newline at end of file diff --git a/examples/sample-jvm/sample-jvm/src/main/kotlin/com/highcapable/sweetdependency/demo_jvm/Main.kt b/examples/sample-jvm/sample-jvm/src/main/kotlin/com/highcapable/sweetdependency/demo_jvm/Main.kt new file mode 100644 index 0000000..107745b --- /dev/null +++ b/examples/sample-jvm/sample-jvm/src/main/kotlin/com/highcapable/sweetdependency/demo_jvm/Main.kt @@ -0,0 +1,9 @@ +package com.highcapable.sweetdependency.demo_jvm + +fun main(args: Array) { + println("Hello World!") + + // Try adding program arguments via Run/Debug configuration. + // Learn more about running applications: https://www.jetbrains.com/help/idea/running-applications.html. + println("Program arguments: ${args.joinToString()}") +} \ No newline at end of file diff --git a/examples/sample-jvm/settings.gradle.kts b/examples/sample-jvm/settings.gradle.kts new file mode 100644 index 0000000..0f12eea --- /dev/null +++ b/examples/sample-jvm/settings.gradle.kts @@ -0,0 +1,19 @@ +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } +} +plugins { + // Import the SweetDependency plugin here + // 在这里引入 SweetDependency 插件 + id("com.highcapable.sweetdependency") version "1.0.0" +} +sweetDependency { + configFileName = "sweet-dependency-config.yaml" + isEnableDependenciesAutowireLog = true + isEnableVerboseMode = true +} +rootProject.name = "SweetDependency-Sample-Jvm" +include(":sample-jvm") \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..df81cba --- /dev/null +++ b/gradle.properties @@ -0,0 +1,19 @@ +# Project Configuration +project.name=SweetDependency +project.description=An easy autowire and manage dependencies Gradle plugin +project.url=https://github.com/fankes/HighCapable/SweetDependency +project.groupName=com.highcapable.sweetdependency +project.moduleName=sweet-dependency +project.version=1.0.0 +project.licence.name=Apache License 2.0 +project.licence.url=https://github.com/fankes/HighCapable/SweetDependency/blob/master/LICENSE +project.developer.id=0 +project.developer.name=fankes +project.developer.email=qzmmcn@163.com +# Gradle Plugin Configuration +gradle.plugin.moduleName=${project.groupName}.gradle.plugin +gradle.plugin.implementationClass=${project.groupName}.plugin.SweetDependencyPlugin +# Maven Publish Configuration +maven.publish.scm.connection=scm:git:git://github.com/HighCapable/SweetDependency.git +maven.publish.scm.developerConnection=scm:git:ssh://github.com/HighCapable/SweetDependency.git +maven.publish.scm.url=https://github.com/HighCapable/SweetDependency \ No newline at end of file diff --git a/gradle/sweet-dependency/sweet-dependency-config.yaml b/gradle/sweet-dependency/sweet-dependency-config.yaml new file mode 100644 index 0000000..ca286b0 --- /dev/null +++ b/gradle/sweet-dependency/sweet-dependency-config.yaml @@ -0,0 +1,38 @@ +preferences: + autowire-on-sync-mode: UPDATE_OPTIONAL_DEPENDENCIES + repositories-mode: FAIL_ON_PROJECT_REPOS + +repositories: + gradle-plugin-portal: + scope: PLUGINS + google: + maven-central: + maven-local: + +plugins: + org.jetbrains.kotlin.jvm: + alias: kotlin-jvm + version: 1.9.10 + org.jetbrains.kotlin.plugin.serialization: + alias: kotlin-serialization + version-ref: kotlin-jvm + com.vanniktech.maven.publish: + alias: maven-publish + version: 0.25.3 + +libraries: + org.jetbrains.kotlin: + kotlin-gradle-plugin-api: + version: 1.9.10 + org.snakeyaml: + snakeyaml-engine: + version: 2.7 + com.charleskorn.kaml: + kaml: + version: 0.55.0 + com.squareup.okhttp3: + okhttp: + version: 4.11.0 + com.squareup: + javapoet: + version: 1.13.0 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..692fc5c --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME \ No newline at end of file diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..4f906e0 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# 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 +# +# https://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. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/img-src/icon.png b/img-src/icon.png new file mode 100644 index 0000000..d8f73c5 Binary files /dev/null and b/img-src/icon.png differ diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..180750a --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,30 @@ +enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + mavenLocal() + } +} +plugins { + id("com.highcapable.sweetdependency") version "1.0.0" + id("com.highcapable.sweetproperty") version "1.0.0" +} +sweetDependency { + isEnableVerboseMode = false +} +sweetProperty { + global { + sourcesCode { + className = rootProject.name + isEnableRestrictedAccess = true + } + } + rootProject { sourcesCode { isEnable = false } } + project("sweetdependency-gradle-plugin") { + buildScript { isEnableTypeAutoConversion = false } + } +} +rootProject.name = "SweetDependency" +include(":sweetdependency-gradle-plugin") \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/.gitignore b/sweetdependency-gradle-plugin/.gitignore new file mode 100644 index 0000000..606cfde --- /dev/null +++ b/sweetdependency-gradle-plugin/.gitignore @@ -0,0 +1,2 @@ +.gradle +build/ \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/build.gradle.kts b/sweetdependency-gradle-plugin/build.gradle.kts new file mode 100644 index 0000000..19fa02f --- /dev/null +++ b/sweetdependency-gradle-plugin/build.gradle.kts @@ -0,0 +1,69 @@ +plugins { + `kotlin-dsl` + autowire(libs.plugins.kotlin.jvm) + autowire(libs.plugins.kotlin.serialization) + autowire(libs.plugins.maven.publish) +} + +allprojects { + group = property.project.groupName + version = property.project.version +} + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + withSourcesJar() +} + +kotlin { + jvmToolchain(17) + sourceSets.all { languageSettings { languageVersion = "2.0" } } +} + +dependencies { + implementation(org.jetbrains.kotlin.kotlin.gradle.plugin.api) + implementation(org.snakeyaml.snakeyaml.engine) + implementation(com.charleskorn.kaml.kaml) + implementation(com.squareup.okhttp3.okhttp) + implementation(com.squareup.javapoet) +} + +gradlePlugin { + plugins { + create(property.project.moduleName) { + id = property.project.groupName + implementationClass = property.gradle.plugin.implementationClass + } + } +} + +mavenPublishing { + coordinates(property.project.groupName, property.project.moduleName, property.project.version) + pom { + name = property.project.name + description = property.project.description + url = property.project.url + licenses { + license { + name = property.project.licence.name + url = property.project.licence.url + distribution = property.project.licence.url + } + } + developers { + developer { + id = property.project.developer.id + name = property.project.developer.name + email = property.project.developer.email + } + } + scm { + url = property.maven.publish.scm.url + connection = property.maven.publish.scm.connection + developerConnection = property.maven.publish.scm.developerConnection + } + } + publishToMavenCentral(com.vanniktech.maven.publish.SonatypeHost.S01) + signAllPublications() +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/SweetDependency.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/SweetDependency.kt new file mode 100644 index 0000000..3c72e1a --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/SweetDependency.kt @@ -0,0 +1,54 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/5/17. + */ +package com.highcapable.sweetdependency + +import com.highcapable.sweetdependency.generated.SweetDependencyProperties + +/** + * [SweetDependency] 的装载调用类 + */ +object SweetDependency { + + /** Banner 内容 */ + private const val BANNER_CONTENT = """ + _____ _ _____ _ + / ____| | | | __ \ | | + | (_____ _____ ___| |_ | | | | ___ _ __ ___ _ __ __| | ___ _ __ ___ _ _ + \___ \ \ /\ / / _ \/ _ \ __| | | | |/ _ \ '_ \ / _ \ '_ \ / _` |/ _ \ '_ \ / __| | | | + ____) \ V V / __/ __/ |_ | |__| | __/ |_) | __/ | | | (_| | __/ | | | (__| |_| | + |_____/ \_/\_/ \___|\___|\__| |_____/ \___| .__/ \___|_| |_|\__,_|\___|_| |_|\___|\__, | + | | __/ | + |_| |___/ + """ + + /** Banner 内容 */ + val bannerContent = BANNER_CONTENT.trimIndent() + + /** 标签名称 */ + const val TAG = SweetDependencyProperties.PROJECT_NAME + + /** 版本 */ + const val VERSION = SweetDependencyProperties.PROJECT_VERSION + + /** 项目地址 */ + const val PROJECT_URL = SweetDependencyProperties.PROJECT_URL +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/document/DependencyDocument.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/document/DependencyDocument.kt new file mode 100644 index 0000000..cf29cb8 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/document/DependencyDocument.kt @@ -0,0 +1,101 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/5/18. + */ +@file:Suppress("unused") + +package com.highcapable.sweetdependency.document + +import com.highcapable.sweetdependency.gradle.entity.DependencyVersion +import com.highcapable.sweetdependency.manager.content.Repositories +import com.highcapable.sweetdependency.utils.debug.SError +import com.highcapable.sweetdependency.utils.toSpaceList +import com.highcapable.sweetdependency.utils.yaml.proxy.IYamlDocument +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * 依赖每项文档实体 + * @param alias 别名 + * @param version 版本 + * @param versionRef 版本引用 + * @param versions 版本别名数组 + * @param isAutoUpdate 是否自动更新 + * @param versionFilter 版本过滤器文档实体 + * @param repositories 指定使用的存储库名称 + */ +@Serializable +internal data class DependencyDocument( + @SerialName("alias") + internal var alias: String = "", + @SerialName("version") + internal var version: String = "", + @SerialName("version-ref") + internal var versionRef: String = "", + @SerialName("versions") + internal var versions: MutableMap = mutableMapOf(), + @SerialName("auto-update") + internal var isAutoUpdate: Boolean = true, + @SerialName("version-filter") + internal var versionFilter: VersionFilterDocument? = null, + @SerialName("repositories") + internal var repositories: String = "" +) : IYamlDocument { + + /** + * 获取版本 + * @return [DependencyVersion] + */ + internal fun version() = DependencyVersion(version) + + /** + * 获取版本别名数组 + * @return <[MutableMap]><[String], [DependencyVersion]> + */ + internal fun versions() = mutableMapOf().also { + versions.forEach { (key, value) -> it[key] = DependencyVersion(value.replace(DependencyVersion.LATEST_VERSION_NAME, version)) } + } + + /** + * 更新版本 + * @param newVersion 新版本 + */ + internal fun updateVersion(newVersion: DependencyVersion) { + version = newVersion.current + } + + /** + * 更新版本 + * @param document 当前文档实例 + */ + internal fun updateVersion(document: DependencyDocument) { + version = document.version + } + + /** + * 获取指定使用的存储库数组 + * @return [MutableList]<[RepositoryDocument]> + */ + internal fun repositories() = mutableListOf().apply { + repositories.toSpaceList().forEach { + add(Repositories.all().firstOrNull { e -> e.nodeName == it } ?: SError.make("Could not found repository with name \"$it\"")) + } + }.distinctBy { it.url.ifBlank { it.path } }.toMutableList() +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/document/PreferencesDocument.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/document/PreferencesDocument.kt new file mode 100644 index 0000000..23f861f --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/document/PreferencesDocument.kt @@ -0,0 +1,125 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/5/17. + */ +package com.highcapable.sweetdependency.document + +import com.highcapable.sweetdependency.document.factory.checkingName +import com.highcapable.sweetdependency.utils.camelcase +import com.highcapable.sweetdependency.utils.debug.SError +import com.highcapable.sweetdependency.utils.yaml.proxy.IYamlDocument +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import org.gradle.api.initialization.resolve.RepositoriesMode as GradleRepositoriesMode + +/** + * 偏好配置项文档实体 + * @param autowireOnSyncMode Gradle Sync 自动装配、更新依赖模式 + * @param repositoriesMode 存储库装载模式 + * @param dependenciesNamespace 依赖命名空间 + * @param versionFilter 版本过滤器文档实体 + */ +@Serializable +internal data class PreferencesDocument( + @SerialName("autowire-on-sync-mode") + internal var autowireOnSyncMode: AutowireOnSyncMode = AutowireOnSyncMode.UPDATE_OPTIONAL_DEPENDENCIES, + @SerialName("repositories-mode") + internal var repositoriesMode: RepositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS, + @SerialName("dependencies-namespace") + internal var dependenciesNamespace: DependenciesNamespaceDocument = DependenciesNamespaceDocument(), + @SerialName("version-filter") + internal var versionFilter: VersionFilterDocument = VersionFilterDocument() +) : IYamlDocument { + + @Serializable + internal data class DependenciesNamespaceDocument( + @SerialName("plugins") + var plugins: String = "libs", + @SerialName("libraries") + var libraries: String = "" + ) : IYamlDocument { + + init { + if (plugins.isNotBlank() && libraries.isNotBlank() && plugins == libraries) + SError.make("Duplicated dependencies namespace \"$plugins\"") + } + + /** + * 获取插件依赖命名空间 + * @return [String] + */ + internal fun plugins() = plugins.apply { checkingName("plugins namespace", isCheckExtName = true) }.camelcase() + + /** + * 获取库依赖命名空间 + * @return [String] + */ + internal fun libraries() = libraries.apply { checkingName("libraries namespace", isCheckExtName = true) }.camelcase() + } + + /** + * Gradle Sync 自动装配、更新依赖模式定义类 + */ + internal enum class AutowireOnSyncMode { + /** 自动装配和更新可选依赖 (插件依赖 + 库依赖) */ + UPDATE_OPTIONAL_DEPENDENCIES, + + /** 自动装配和更新所有依赖 (插件依赖 + 库依赖) */ + UPDATE_ALL_DEPENDENCIES, + + /** 仅自动装配使用“+”填充版本的依赖 (插件依赖 + 库依赖) */ + ONLY_AUTOWIRE_DEPENDENCIES, + + /** 自动装配和更新可选依赖 (插件依赖) */ + UPDATE_OPTIONAL_PLUGINS, + + /** 自动装配和更新所有依赖 (插件依赖) */ + UPDATE_ALL_PLUGINS, + + /** 仅自动装配使用“+”填充版本的依赖 (插件依赖) */ + ONLY_AUTOWIRE_PLUGINS, + + /** 自动装配和更新可选依赖 (库依赖) */ + UPDATE_OPTIONAL_LIBRARIES, + + /** 自动装配和更新所有依赖 (库依赖) */ + UPDATE_ALL_LIBRARIES, + + /** 仅自动装配使用“+”填充版本的依赖 (库依赖) */ + ONLY_AUTOWIRE_LIBRARIES, + + /** 什么也不做 - 关闭所有功能 */ + OFF + } + + /** + * 存储库装载模式定义类 (跟随 Gradle 进行配置调整) + */ + internal enum class RepositoriesMode { + /** 参考 [GradleRepositoriesMode.PREFER_PROJECT] */ + PREFER_PROJECT, + + /** 参考 [GradleRepositoriesMode.PREFER_SETTINGS] */ + PREFER_SETTINGS, + + /** 参考 [GradleRepositoriesMode.FAIL_ON_PROJECT_REPOS] */ + FAIL_ON_PROJECT_REPOS + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/document/RepositoryDocument.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/document/RepositoryDocument.kt new file mode 100644 index 0000000..cd611c6 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/document/RepositoryDocument.kt @@ -0,0 +1,273 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/5/17. + */ +package com.highcapable.sweetdependency.document + +import com.highcapable.sweetdependency.SweetDependency +import com.highcapable.sweetdependency.manager.const.InternalRepositories +import com.highcapable.sweetdependency.manager.content.Repositories +import com.highcapable.sweetdependency.utils.debug.SError +import com.highcapable.sweetdependency.utils.parseUnixFileSeparator +import com.highcapable.sweetdependency.utils.toSpaceList +import com.highcapable.sweetdependency.utils.yaml.proxy.IYamlDocument +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import org.gradle.api.artifacts.repositories.PasswordCredentials +import org.gradle.api.artifacts.repositories.RepositoryContentDescriptor + +/** + * 存储库配置项文档实体 + * @param isEnable 是否启用 + * @param scope 作用域 + * @param content 内容过滤器 + * @param credentials 身份验证配置项文档实体 + * @param url URL 地址 + * @param path 文件路径 + */ +@Serializable +internal data class RepositoryDocument( + @SerialName("enable") + internal var isEnable: Boolean = true, + @SerialName("scope") + internal var scope: RepositoryScope = RepositoryScope.ALL, + @SerialName("content") + internal var content: ContentDocument = ContentDocument(), + @SerialName("credentials") + internal var credentials: CredentialsDocument = CredentialsDocument(), + @SerialName("url") + internal var url: String = "", + @SerialName("path") + internal var path: String = "", +) : IYamlDocument { + + /** + * 身份验证配置项文档实体 + * + * 这些内容来自 [PasswordCredentials] + * @param username 用户名 + * @param password 密码 + */ + @Serializable + internal data class CredentialsDocument( + @SerialName("username") + internal var username: String = "", + @SerialName("password") + internal var password: String = "" + ) : IYamlDocument + + /** + * 内容配置文档实体 + * + * 这些内容来自 [RepositoryContentDescriptor] + * @param exclude 排除配置文档实体 + * @param include 包含配置文档实体 + */ + @Serializable + internal data class ContentDocument( + @SerialName("exclude") + internal var exclude: FilterDocument = FilterDocument(), + @SerialName("include") + internal var include: FilterDocument = FilterDocument() + ) : IYamlDocument { + + /** + * 内容过滤器配置文档实体 + * + * 这些内容来自 [RepositoryContentDescriptor] + * @param group 过滤器条件内容 + * @param groupAndSubgroups 过滤器条件内容 + * @param groupByRegex 过滤器条件内容 + * @param module 过滤器条件内容 + * @param moduleByRegex 过滤器条件内容 + * @param version 过滤器条件内容 + * @param versionByRegex 过滤器条件内容 + */ + @Serializable + internal data class FilterDocument( + @SerialName("group") + internal var group: String = "", + @SerialName("group-and-subgroups") + internal var groupAndSubgroups: String = "", + @SerialName("group-by-regex") + internal var groupByRegex: String = "", + @SerialName("module") + internal var module: String = "", + @SerialName("module-by-regex") + internal var moduleByRegex: String = "", + @SerialName("version") + internal var version: String = "", + @SerialName("version-by-regex") + internal var versionByRegex: String = "" + ) : IYamlDocument { + + /** + * 获取过滤器条件内容 + * @return [List]<[String]> + */ + internal fun group() = group.toSpaceList() + + /** + * 获取过滤器条件内容 + * @return [List]<[String]> + */ + internal fun groupAndSubgroups() = groupAndSubgroups.toSpaceList() + + /** + * 获取过滤器条件内容 + * @return [List]<[String]> + */ + internal fun groupByRegex() = groupByRegex.toSpaceList() + + /** + * 获取过滤器条件内容 + * @return [List]<[String]> + */ + internal fun module() = module.toSpaceList() + + /** + * 获取过滤器条件内容 + * @return [List]<[String]> + */ + internal fun moduleByRegex() = moduleByRegex.toSpaceList() + + /** + * 获取过滤器条件内容 + * @return [List]<[String]> + */ + internal fun version() = version.toSpaceList() + + /** + * 获取过滤器条件内容 + * @return [List]<[String]> + */ + internal fun versionByRegex() = versionByRegex.toSpaceList() + + /** + * 当前规则是否为空 + * @return [Boolean] + */ + internal fun isEmpty() = + group.isBlank() && groupAndSubgroups.isBlank() && groupByRegex.isBlank() && + module.isBlank() && moduleByRegex.isBlank() && + version.isBlank() && versionByRegex.isBlank() + } + + /** + * 当前规则是否为空 + * @return [Boolean] + */ + internal fun isEmpty() = exclude.isEmpty() && include.isEmpty() + } + + /** 节点名称 */ + @Transient + internal var nodeName = "" + + /** 节点类型 */ + @Transient + internal var nodeType = RepositoryType.UNSPECIFIED + + /** + * 存储库作用域定义类 + */ + internal enum class RepositoryScope { + /** 作用于所有类型依赖 */ + ALL, + + /** 作用于插件依赖 */ + PLUGINS, + + /** 作用于库依赖 */ + LIBRARIES + } + + /** + * 存储库已知类型定义类 + */ + internal enum class RepositoryType { + /** 未指定 */ + UNSPECIFIED, + + /** Google Maven */ + GOOGLE, + + /** 中央存储库 */ + MAVEN_CENTRAL, + + /** 本地存储库 */ + MAVEN_LOCAL, + + /** 自定义存储库 */ + MAVEN, + + /** Gradle Plugin 存储库 */ + GRADLE_PLUGIN_PORTAL + } + + /** + * 获取是否包含在作用域内 + * @param isPlugins 当前类型是否为插件依赖 + * @return [Boolean] + */ + internal fun isIncludeScope(isPlugins: Boolean) = + if (isPlugins) scope == RepositoryScope.ALL || scope == RepositoryScope.PLUGINS + else scope == RepositoryScope.ALL || scope == RepositoryScope.LIBRARIES + + /** + * 创建当前实体 + * @param name 键值名称 + * @return [RepositoryDocument] + */ + internal fun build(name: String) = apply { + when (name) { + InternalRepositories.Name.GOOGLE -> { + url = url.ifBlank { InternalRepositories.GOOGLE } + nodeType = RepositoryType.GOOGLE + } + InternalRepositories.Name.MAVEN_CENTRAL -> { + url = url.ifBlank { InternalRepositories.MAVEN_CENTRAL } + nodeType = RepositoryType.MAVEN_CENTRAL + } + InternalRepositories.Name.GRADLE_PLUGIN_PORTAL -> { + url = url.ifBlank { InternalRepositories.GRADLE_PLUGIN_PORTAL } + nodeType = RepositoryType.GRADLE_PLUGIN_PORTAL + } + InternalRepositories.Name.MAVEN_LOCAL -> { + path = path.ifBlank { Repositories.defaultMavenLocalPath } + nodeType = RepositoryType.MAVEN_LOCAL + } + InternalRepositories.Name.MAVEN -> SError.make("Use \"maven\" as a repository name is an error, please choose another name") + InternalRepositories.Name.IVY -> SError.make("Ivy is not support on ${SweetDependency.TAG} ${SweetDependency.VERSION}") + else -> { + url = url.ifBlank { + Repositories.findAdditional(name).ifBlank { + SError.make("Could not found internal or additional repository URL by repository name \"$name\", you must specify a URL") + } + }; nodeType = RepositoryType.MAVEN + } + }; nodeName = name + if (url.isNotBlank() && path.isNotBlank()) SError.make("There can only be one \"url\" and \"path\" parameter of \"$name\"") + if (path.isNotBlank() && (path.startsWith("https://") || path.startsWith("http://"))) SError.make("Invalid repository path: $path") + if (url.isNotBlank() && url.startsWith("https://").not() && url.startsWith("http://").not()) SError.make("Invalid repository URL: $url") + if (path.isNotBlank()) path = path.parseUnixFileSeparator() + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/document/RootConfigDocument.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/document/RootConfigDocument.kt new file mode 100644 index 0000000..e1ac84b --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/document/RootConfigDocument.kt @@ -0,0 +1,253 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/5/17. + */ +@file:Suppress("MemberVisibilityCanBePrivate") + +package com.highcapable.sweetdependency.document + +import com.highcapable.sweetdependency.SweetDependency +import com.highcapable.sweetdependency.document.factory.DependencyMap +import com.highcapable.sweetdependency.document.factory.RepositoryList +import com.highcapable.sweetdependency.document.factory.checkingName +import com.highcapable.sweetdependency.document.factory.convertToDependencyAmbiguousName +import com.highcapable.sweetdependency.gradle.entity.DependencyName +import com.highcapable.sweetdependency.gradle.entity.DependencyVersion +import com.highcapable.sweetdependency.utils.capitalize +import com.highcapable.sweetdependency.utils.debug.SError +import com.highcapable.sweetdependency.utils.findDuplicates +import com.highcapable.sweetdependency.utils.hasDuplicate +import com.highcapable.sweetdependency.utils.yaml.proxy.IYamlDocument +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * 项目 (根节点) 配置文档实体 + * @param preferences 偏好配置项文档实体 + * @param repositories 每项存储库配置项文档实体 + * @param plugins 每项插件依赖文档实体 + * @param libraries 每项库依赖文档实体 + * @param versions 每项版本定义数组 + */ +@Serializable +internal data class RootConfigDocument( + @SerialName("preferences") + internal var preferences: PreferencesDocument? = PreferencesDocument(), + @SerialName("repositories") + internal var repositories: MutableMap? = null, + @SerialName("plugins") + internal var plugins: MutableMap? = null, + @SerialName("libraries") + internal var libraries: MutableMap>? = null, + @SerialName("versions") + internal var versions: MutableMap? = null +) : IYamlDocument { + + internal companion object { + + /** 默认文档内容 */ + private const val DEFAULT_CONTENT = """ + # SweetDependency project configuration file + # You can adjust your custom configuration to your liking here + # You can visit ${SweetDependency.PROJECT_URL} for more help + # + # SweetDependency 项目配置文件 + # 你可以在这里调整你喜欢的自定义配置 + # 你可以前往 ${SweetDependency.PROJECT_URL} 以获得更多帮助 + + # Configure preferences + # 配置偏好设置 + preferences: + autowire-on-sync-mode: UPDATE_OPTIONAL_DEPENDENCIES + repositories-mode: FAIL_ON_PROJECT_REPOS + + # Configure repositories used by dependencies + # 配置依赖使用的存储库 + repositories: + gradle-plugin-portal: + scope: PLUGINS + google: + maven-central: + + # Configure plugins that need to be used + # For example: + # plugins: + # org.jetbrains.kotlin.jvm: + # version: + + # + # 配置需要使用的插件依赖 + # 例如: + # plugins: + # org.jetbrains.kotlin.jvm: + # version: + + plugins: + + # Configure libraries that need to be used + # For example: + # libraries: + # com.google.code.gson: + # gson: + # version: + + # + # 配置需要使用的库依赖 + # 例如: + # libraries: + # com.google.code.gson: + # gson: + # version: + + libraries: + + """ + + /** 默认文档内容 */ + internal val defaultContent = DEFAULT_CONTENT.trimIndent() + } + + /** + * 获取当前偏好配置项文档实体 + * @return [PreferencesDocument] + */ + internal fun preferences() = preferences ?: PreferencesDocument() + + /** + * 获取当前存储库配置项文档实体 + * @return [RepositoryList] + */ + internal fun repositories() = repositories?.let { + mutableListOf().apply { + it.forEach { (name, repository) -> (repository ?: RepositoryDocument()).build(name).also { if (it.isEnable) add(it) } } + } + } ?: mutableListOf() + + /** + * 获取当前插件依赖数组 + * @param duplicate 允许重复 - 忽略处理后版本重复的异常 - 默认否 + * @return [DependencyMap] + */ + internal fun plugins(duplicate: Boolean = false) = createPlugins().resolveDependencies(typeName = "plugin", duplicate) + + /** + * 获取当前库依赖数组 + * @param duplicate 允许重复 - 忽略处理后版本重复的异常 - 默认否 + * @return [DependencyMap] + */ + internal fun libraries(duplicate: Boolean = false) = createLibraries().resolveDependencies(typeName = "library", duplicate) + + /** + * 处理依赖数组 + * @param typeName 依赖类型名称 + * @param duplicate 允许重复 - 忽略处理后版本重复的异常 - 默认否 + */ + private fun DependencyMap.resolveDependencies(typeName: String, duplicate: Boolean = false) = apply { + val firstTypeName = typeName.capitalize() + val checkDuplicateAlias = mutableMapOf() + val refLibraries = mutableListOf>() + val ambiguousNames = mutableListOf() + eachDependencies { dependencyName, artifact -> + artifact.alias.checkingName("$typeName \"$dependencyName\" alias", isCheckMultiName = true) + artifact.versions().forEach { (name, _) -> name.checkingName("$typeName \"$dependencyName\" version alias") } + if (artifact.alias.isNotBlank()) + if (checkDuplicateAlias.contains(artifact.alias).not()) + checkDuplicateAlias[artifact.alias] = dependencyName.current + else SError.make( + "Duplicated alias \"${artifact.alias}\", " + + "already declared in $typeName \"${checkDuplicateAlias[artifact.alias]}\"" + ) + if (artifact.version().isNoSpecific && (artifact.versions().isNotEmpty() || artifact.versionRef.isNotBlank())) + SError.make( + "$firstTypeName \"$dependencyName\" has declared that it does not specify a version, " + + "so it cannot use \"versions\" or \"version-ref\"" + ) + if (artifact.versionRef.isNotBlank() && artifact.versionRef.startsWith("::")) + artifact.versionRef = artifact.versionRef.replace(":", dependencyName.groupId) + refLibraries.add(Triple(dependencyName, artifact.alias, artifact.version())) + } + eachDependencies { dependencyName, artifact -> + /** 处理版本引用 */ + fun resolveVersionRef() { + refLibraries.firstOrNull { artifact.versionRef.let { e -> e == it.first.current || e == it.second } }?.also { + if (dependencyName == it.first || dependencyName.current == it.second) + SError.make("$firstTypeName \"$dependencyName\" declared \"version-ref\" from itself (recursive call found)") + when { + it.third.isNoSpecific -> SError.make( + "$firstTypeName \"${it.first}\" does not specify a version, so it can no longer be " + + "declared as \"version-ref\" by $typeName \"$dependencyName\"" + ) + it.third.isBlank -> SError.make( + "$firstTypeName \"${it.first}\" already has \"version-ref\" declared, so it can no longer" + + " be declared as \"version-ref\" by $typeName \"$dependencyName\" (recursive call found)" + ) + }; artifact.updateVersion(it.third) + } ?: SError.make( + "Could not found any versions or dependencies associated with " + + "version-ref \"${artifact.versionRef}\" of $typeName \"$dependencyName\"" + ) + } + if (artifact.version().isNoSpecific) return@eachDependencies + if (artifact.version().isBlank) + if (artifact.versionRef.isNotBlank()) + versions()[artifact.versionRef]?.also { artifact.version = it } ?: resolveVersionRef() + else SError.make("Missing declared version when configuring $typeName \"$dependencyName\"") + else if (artifact.version().isBlank.not() && artifact.versionRef.isNotBlank() && duplicate.not()) + SError.make("$firstTypeName \"$dependencyName\" can only have one \"version\" or \"version-ref\" node, please delete one") + } + eachDependencies { dependencyName, artifact -> + ambiguousNames.add(dependencyName.ambiguousName()) + if (artifact.alias.isNotBlank()) { + artifact.alias.checkingName("$typeName \"$dependencyName\" alias", isCheckMultiName = true) + ambiguousNames.add(artifact.alias.convertToDependencyAmbiguousName()) + }; this[dependencyName] = artifact + } + if (ambiguousNames.hasDuplicate()) ambiguousNames.findDuplicates().forEach { + SError.make("Found ambiguous name \"$it\" in declared dependencies, please checking your $typeName aliases that your declared") + } else ambiguousNames.clear() + } + + /** + * 获取当前版本定义数组 + * @return [MutableMap]<[String], [String]> + */ + internal fun versions() = versions?.onEach { (name, _) -> name.checkingName("versions name") } ?: mutableMapOf() + + /** + * 重新创建 [plugins] + * @return [DependencyMap] + */ + private fun createPlugins() = mutableMapOf().apply { + plugins?.forEach { (notation, artifact) -> this[DependencyName.plugin(notation)] = artifact } + } + + /** + * 重新创建 [libraries] + * @return [DependencyMap] + */ + private fun createLibraries() = mutableMapOf().apply { + libraries?.forEach { (groupId, libraries) -> + libraries.forEach { (artifactId, artifact) -> this[DependencyName.library(groupId, artifactId)] = artifact } + } + } + + /** + * 循环每项 [plugins]、[libraries] + * @param result 回调每项结果 + */ + private inline fun DependencyMap.eachDependencies(result: (dependencyName: DependencyName, artifact: DependencyDocument) -> Unit) = + forEach { (dependencyName, artifact) -> result(dependencyName, artifact) } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/document/VersionFilterDocument.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/document/VersionFilterDocument.kt new file mode 100644 index 0000000..99f9168 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/document/VersionFilterDocument.kt @@ -0,0 +1,105 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/6/9. + */ +package com.highcapable.sweetdependency.document + +import com.highcapable.sweetdependency.gradle.entity.DependencyVersion +import com.highcapable.sweetdependency.manager.content.Repositories +import com.highcapable.sweetdependency.utils.filter +import com.highcapable.sweetdependency.utils.toSpaceList +import com.highcapable.sweetdependency.utils.yaml.proxy.IYamlDocument +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * 版本过滤器文档实体 + * @param isUseInternal 使用内置过滤器 + * @param exclusionList 排除列表 + */ +@Serializable +internal data class VersionFilterDocument( + @SerialName("use-internal") + internal var isUseInternal: Boolean = true, + @SerialName("exclusion-list") + internal var exclusionList: String = "" +) : IYamlDocument { + + /** + * 版本排除列表实体 + * @param list 当前排除列表数组 + */ + internal class ExclusionList internal constructor(private val list: MutableList) { + + /** + * 获取当前排除列表数组 + * @return [MutableList]<[String]> + */ + internal fun all() = list + + /** + * 当前是否存在排除列表 + * @return [Boolean] + */ + internal fun isEmpty() = all().isEmpty() + + /** + * 当前是否不存在排除列表 + * @return [Boolean] + */ + internal fun isNotEmpty() = isEmpty().not() + + /** + * 依赖于当前 [version] 提供的版本并在 [all] 中排除 (不区分大小写) + * + * 此操作会调用 [clone] 创建一个新实例并返回 + * @param version 当前版本 + * @return [ExclusionList] + */ + internal fun depends(version: DependencyVersion) = clone().apply { + if (version.isAutowire.not() && version.isBlank.not()) all().removeAll { version.deployed.lowercase().contains(it.lowercase()) } + } + + /** + * 使用 [all] 过滤当前版本字符串 (不区分大小写) + * @param versions 当前版本字符串数组 + * @return [MutableList]<[DependencyVersion]> + */ + internal fun filter(versions: MutableList) = + if (all().isEmpty()) versions else versions.filter { version -> all().none { version.current.lowercase().contains(it.lowercase()) } } + + /** + * 克隆并创建一个新实例 + * @return [ExclusionList] + */ + private fun clone() = ExclusionList(mutableListOf().apply { addAll(all()) }) + + override fun toString() = all().toString() + } + + /** + * 获取排除列表 + * @return [ExclusionList] + */ + internal fun exclusionList() = ExclusionList(mutableListOf().apply { + if (isUseInternal) addAll(Repositories.defaultVersionFilterExclusionList) + addAll(exclusionList.toSpaceList()) + }) +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/document/factory/DocumentFactory.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/document/factory/DocumentFactory.kt new file mode 100644 index 0000000..631f693 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/document/factory/DocumentFactory.kt @@ -0,0 +1,160 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/5/31. + */ +package com.highcapable.sweetdependency.document.factory + +import com.highcapable.sweetdependency.document.DependencyDocument +import com.highcapable.sweetdependency.document.PreferencesDocument +import com.highcapable.sweetdependency.document.RepositoryDocument +import com.highcapable.sweetdependency.gradle.entity.DependencyName +import com.highcapable.sweetdependency.gradle.entity.DependencyUpdateMode +import com.highcapable.sweetdependency.gradle.entity.DependencyVersion +import com.highcapable.sweetdependency.gradle.factory.isUnSafeExtName +import com.highcapable.sweetdependency.utils.debug.SError +import com.highcapable.sweetdependency.utils.firstNumberToLetter + +/** 存储库文档实体类型定义 */ +internal typealias RepositoryList = MutableList + +/** 依赖文档实体类型定义 */ +internal typealias DependencyMap = MutableMap + +/** 依赖文档更新实体类型定义 */ +internal typealias DependencyUpdateMap = MutableMap> + +/** 依赖文档查找条件类型定义 */ +internal typealias DependenciesCondition = (dependencyName: DependencyName, artifact: DependencyDocument) -> Boolean + +/** + * 转换 [PreferencesDocument.AutowireOnSyncMode] 到 [DependencyUpdateMode] + * + * 如果为 [PreferencesDocument.AutowireOnSyncMode.OFF] 则会返回 null + * @return [DependencyUpdateMode] or null + */ +internal fun PreferencesDocument.AutowireOnSyncMode.toUpdateMode() = when (this) { + PreferencesDocument.AutowireOnSyncMode.UPDATE_OPTIONAL_DEPENDENCIES -> + DependencyUpdateMode(DependencyUpdateMode.DependencyType.ALL, DependencyUpdateMode.UpdateType.UPDATE_OPTIONAL) + PreferencesDocument.AutowireOnSyncMode.UPDATE_ALL_DEPENDENCIES -> + DependencyUpdateMode(DependencyUpdateMode.DependencyType.ALL, DependencyUpdateMode.UpdateType.UPDATE_ALL) + PreferencesDocument.AutowireOnSyncMode.ONLY_AUTOWIRE_DEPENDENCIES -> + DependencyUpdateMode(DependencyUpdateMode.DependencyType.ALL, DependencyUpdateMode.UpdateType.ONLY_AUTOWIRE) + PreferencesDocument.AutowireOnSyncMode.UPDATE_OPTIONAL_PLUGINS -> + DependencyUpdateMode(DependencyUpdateMode.DependencyType.PLUGINS, DependencyUpdateMode.UpdateType.UPDATE_OPTIONAL) + PreferencesDocument.AutowireOnSyncMode.UPDATE_ALL_PLUGINS -> + DependencyUpdateMode(DependencyUpdateMode.DependencyType.PLUGINS, DependencyUpdateMode.UpdateType.UPDATE_ALL) + PreferencesDocument.AutowireOnSyncMode.ONLY_AUTOWIRE_PLUGINS -> + DependencyUpdateMode(DependencyUpdateMode.DependencyType.PLUGINS, DependencyUpdateMode.UpdateType.ONLY_AUTOWIRE) + PreferencesDocument.AutowireOnSyncMode.UPDATE_OPTIONAL_LIBRARIES -> + DependencyUpdateMode(DependencyUpdateMode.DependencyType.LIBRARIES, DependencyUpdateMode.UpdateType.UPDATE_OPTIONAL) + PreferencesDocument.AutowireOnSyncMode.UPDATE_ALL_LIBRARIES -> + DependencyUpdateMode(DependencyUpdateMode.DependencyType.LIBRARIES, DependencyUpdateMode.UpdateType.UPDATE_ALL) + PreferencesDocument.AutowireOnSyncMode.ONLY_AUTOWIRE_LIBRARIES -> + DependencyUpdateMode(DependencyUpdateMode.DependencyType.LIBRARIES, DependencyUpdateMode.UpdateType.ONLY_AUTOWIRE) + PreferencesDocument.AutowireOnSyncMode.OFF -> null +} + +/** + * 合并到依赖名称 + * @param groupId Group ID + * @param artifactId Artifact ID + * @return [String] + */ +internal fun spliceToDependencyNotation(groupId: String, artifactId: String) = "$groupId:$artifactId" + +/** + * 分离到依赖名称数组 + * + * "com.mylibrary:library-core" → "com.mylibrary" | "library-core" + * @return [List]<[String]> + */ +internal fun String.splitToDependencyNames() = trim().split(":").apply { if (size != 2) SError.make("Invalid dependency name \"$this\"") } + +/** + * 分离到依赖生成名称数组 + * + * "com.mylibrary:library-core" → "com" | "mylibrary" | "library" | "core" + * @return [List]<[String]> + */ +internal fun String.splitToDependencyGenerateNames() = + trim().replace("_", "|").replace(".", "|").replace(":", "|").replace("-", "|").split("|").filter { it.isNotBlank() } + +/** + * 转换到依赖 URL 名称 + * + * "com.mylibrary:library-core" → "com/mylibrary/library-core" + * @return [String] + */ +internal fun String.convertToDependencyUrlName() = splitToDependencyNames().let { "${it[0].replace(".", "/")}/${it[1]}" } + +/** + * 转换到依赖模糊分离名称 (使用 [symbol] 进行分离) + * + * "com.mylibrary:library-core" → "com[symbol]mylibrary[symbol]library[symbol]core" + * @param symbol 分隔符 - 默认 "." + * @param isReplaceFirstChar 是否使用 [firstNumberToLetter] 替换每一段第一个字符 - 默认否 + * @param isLowerCase 是否全部转换为小写 - 默认是 + * @return [String] + */ +internal fun String.convertToDependencyAmbiguousName(symbol: String = ".", isReplaceFirstChar: Boolean = false, isLowerCase: Boolean = true) = + mutableListOf().apply { + trim().replace(".", "|").replace("_", "|").replace(":", "|").replace("-", "|").split("|").forEach { + add(if (isReplaceFirstChar) it.firstNumberToLetter() else it) + } + }.joinToString(symbol).let { if (isLowerCase) it.lowercase() else it } + +/** + * 检查名称、别名是否合法 + * + * - 只能包含:'0-9'、'A-Z'、'a-z'、'.'、'_'、'-' 且必须以字母开头 (长度至少为 3 位) + * - 不能是 [isUnSafeExtName] + * @param content 内容 + * @param isCheckExtName 是否同时检查是否为 Gradle 使用的关键字名称 - 默认否 + * @param isCheckMultiName 是否同时检查是否可被 [splitToDependencyGenerateNames] 分割为两位及以上名称 - 默认否 + * @throws IllegalArgumentException 如果名称、别名不合法 + */ +internal fun String.checkingName(content: String, isCheckExtName: Boolean = false, isCheckMultiName: Boolean = false) { + if (isBlank()) return + if (length < 3) SError.make("Illegal $content \"$this\", the length of $content must be >= 3") + /** + * 检查是否为 Gradle 使用的关键字名称 + * @param isEnable 默认跟随 [isCheckExtName] + * @throws IllegalArgumentException 如果名称、别名不合法 + */ + fun String.checkUnSafeExtName(isEnable: Boolean = isCheckExtName) { + if (isEnable && isUnSafeExtName()) SError.make("This $content \"$this\" of \"${this@checkingName}\" is a Gradle built-in extension") + } + checkUnSafeExtName() + if (isCheckMultiName) splitToDependencyGenerateNames().also { splitedNames -> + if (splitedNames.isEmpty()) SError.make("This $content \"$this\" cannot be split, please check and try again") + if (splitedNames.size < 2) SError.make("This $content \"$this\" must be able to be split into at least 2 parts") + splitedNames[0].checkUnSafeExtName(isEnable = true) + splitedNames.forEach { + if (it.first() !in 'A'..'Z' && it.first() !in 'a'..'z') + SError.make("Illegal $content \"$it\" of \"$this\", it must start with a letter") + } + } + forEachIndexed { index, char -> + if ((char !in 'A'..'Z' && char !in 'a'..'z' && index == 0) || + (char !in 'A'..'Z' && char !in 'a'..'z' && + char !in '0'..'9' && char != '_' && char != '-' && char != '.') + ) SError.make("Illegal $content \"$this\", it only allow 26 letters (upper and lower case) and '.', '_', '-' and must start with a letter") + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/document/mapping/RootConfigDocumentMapping.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/document/mapping/RootConfigDocumentMapping.kt new file mode 100644 index 0000000..2a38661 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/document/mapping/RootConfigDocumentMapping.kt @@ -0,0 +1,197 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/6/1. + */ +package com.highcapable.sweetdependency.document.mapping + +import com.highcapable.sweetdependency.document.RootConfigDocument +import com.highcapable.sweetdependency.document.factory.DependencyUpdateMap +import com.highcapable.sweetdependency.document.factory.spliceToDependencyNotation +import com.highcapable.sweetdependency.document.mapping.entity.DependencyMapping +import com.highcapable.sweetdependency.exception.SweetDependencyUnresolvedException +import com.highcapable.sweetdependency.gradle.entity.DependencyName +import com.highcapable.sweetdependency.plugin.config.proxy.ISweetDependencyConfigs +import com.highcapable.sweetdependency.utils.debug.SError +import com.highcapable.sweetdependency.utils.debug.SLog +import com.highcapable.sweetdependency.utils.filter +import com.highcapable.sweetdependency.utils.joinToContent +import com.highcapable.sweetdependency.utils.toFile +import com.highcapable.sweetdependency.utils.yaml.Yaml +import com.highcapable.sweetdependency.utils.yaml.factory.asMap +import com.highcapable.sweetdependency.utils.yaml.factory.isKeyExist +import java.io.File + +/** + * [RootConfigDocument] 的测绘实例实现类 + * @param configs 当前配置 + */ +internal class RootConfigDocumentMapping internal constructor(private val configs: ISweetDependencyConfigs) { + + private companion object { + + /** 偏好设置节点名称 */ + private const val PREFERENCES_NODE_NAME = "preferences:" + + /** 存储库节点名称 */ + private const val REPOSITORIES_NODE_NAME = "repositories:" + + /** 版本节点名称 */ + private const val VERSIONS_NODE_NAME = "versions:" + + /** 插件依赖节点名称 */ + private const val PLUGINS_NODE_NAME = "plugins:" + + /** 库依赖节点名称 */ + private const val LIBRARIES_NODE_NAME = "libraries:" + + /** 依赖版本起始内容 */ + private const val VERSION_NODE_CONTENT = "version:" + + /** 依赖版本引用节点名称 (不包括结尾的“:”) */ + private const val VERSION_REF_NODE_NAME = "version-ref" + + /** 4 个空格 (缩进) 内容 */ + private const val SPACE_OF_4 = " " + + /** 6 个空格 (缩进) 内容 */ + private const val SPACE_OF_6 = " " + } + + /** 当前配置文件 */ + private var configFile: File? = null + + /** 配置文件行内容数组 */ + private val configFileContents = mutableListOf() + + /** 插件依赖测绘实体数组 */ + private val pluginsMapping = mutableListOf() + + /** 库依赖测绘实体数组 */ + private val librariesMapping = mutableListOf() + + init { + runCatching { createMapping() }.onFailure { + when (it) { + is SweetDependencyUnresolvedException -> throw it + else -> SLog.error("Failed to create config file's mapping, this will cause problem") + } + } + } + + /** 创建测绘数据 */ + private fun createMapping() { + configFileContents.clear() + configFileContents.addAll(configs.configFilePath.toFile().apply { configFile = this }.readText().split("\n")) + var isFoundPluginsStartLine = false + var pluginsStartLine = -1 + var pluginsLine = 0 + var isFoundLibrariesStartLine = false + var librariesStartLine = -1 + var librariesLine = 0 + val pluginsContents = mutableListOf() + val librariesContents = mutableListOf() + configFileContents.forEachIndexed { index, content -> + if (content.contains("\"\"")) SError.make("Character declared like -> \"\" <- are not allowed, detected at line ${index + 1}") + } + configFileContents.forEachIndexed { index, content -> + if (content.startsWith(PREFERENCES_NODE_NAME) || + content.startsWith(REPOSITORIES_NODE_NAME) || + content.startsWith(VERSIONS_NODE_NAME) || + content.startsWith(LIBRARIES_NODE_NAME) + ) { + isFoundPluginsStartLine = false + return@forEachIndexed + } + if (content.startsWith(PLUGINS_NODE_NAME)) { + isFoundPluginsStartLine = true + pluginsStartLine = index + } + if (isFoundPluginsStartLine) pluginsContents.add(content) + } + configFileContents.forEachIndexed { index, content -> + if (content.startsWith(PREFERENCES_NODE_NAME) || + content.startsWith(REPOSITORIES_NODE_NAME) || + content.startsWith(VERSIONS_NODE_NAME) || + content.startsWith(PLUGINS_NODE_NAME) + ) { + isFoundLibrariesStartLine = false + return@forEachIndexed + } + if (content.startsWith(LIBRARIES_NODE_NAME)) { + isFoundLibrariesStartLine = true + librariesStartLine = index + } + if (isFoundLibrariesStartLine) librariesContents.add(content) + } + if (pluginsContents.isNotEmpty()) + Yaml.loadFromStringAsNode(pluginsContents.joinToContent()).forEach { (_, rootNode) -> + rootNode.asMap()?.forEach { (notation, artifactNode) -> + if (artifactNode.asMap()?.isKeyExist(VERSION_REF_NODE_NAME) == false) + pluginsMapping.add(DependencyMapping(notation.content)) + } + } + if (librariesContents.isNotEmpty()) + Yaml.loadFromStringAsNode(librariesContents.joinToContent()).forEach { (_, rootNode) -> + rootNode.asMap()?.forEach { (groupId, libraryNode) -> + libraryNode.asMap()?.forEach { (artifactId, artifactNode) -> + val notation = spliceToDependencyNotation(groupId.content, artifactId.content) + if (artifactNode.asMap()?.isKeyExist(VERSION_REF_NODE_NAME) == false) + librariesMapping.add(DependencyMapping(notation)) + } + } + } + pluginsContents.onEachIndexed { index, content -> + if ((content.trim().startsWith(VERSION_NODE_CONTENT) && content.startsWith(SPACE_OF_4)).not()) return@onEachIndexed + pluginsMapping[pluginsLine].versionLine = index + pluginsStartLine + pluginsLine++ + }.clear() + librariesContents.onEachIndexed { index, content -> + if ((content.trim().startsWith(VERSION_NODE_CONTENT) && content.startsWith(SPACE_OF_6)).not()) return@onEachIndexed + librariesMapping[librariesLine].versionLine = index + librariesStartLine + librariesLine++ + }.clear() + } + + /** + * 使用测绘数据更新依赖版本内容 + * @param dependencies 需要更新的依赖名称和版本数组 + */ + internal fun updateDependencies(dependencies: DependencyUpdateMap) { + /** + * 写入更新的依赖数据到文件内容 + * @param dependencies 依赖数组 + * @param spaceContent 空格内容 + */ + fun List.dumpToContents(dependencies: DependencyUpdateMap, spaceContent: String) = + filter { dependencies.containsKey(it.notation) }.forEach { + var codeNote = "" + val originContent = configFileContents[it.versionLine] + if (originContent.contains("#")) originContent.indexOf("#") + .also { e -> if (e > 0) codeNote = originContent.substring(e - 1..originContent.lastIndex) } + configFileContents[it.versionLine] = "$spaceContent$VERSION_NODE_CONTENT ${dependencies[it.notation]?.second?.mapped}$codeNote" + } + + val plugins = dependencies.filter { it.value.first.type == DependencyName.Type.PLUGIN } + val libraries = dependencies.filter { it.value.first.type == DependencyName.Type.LIBRARY } + if (plugins.isNotEmpty()) pluginsMapping.dumpToContents(plugins, SPACE_OF_4) + if (libraries.isNotEmpty()) librariesMapping.dumpToContents(libraries, SPACE_OF_6) + if (configFileContents.isNotEmpty()) configFile?.writeText(buildString { configFileContents.forEach { append("$it\n") } }.trim()) + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/document/mapping/entity/DependencyMapping.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/document/mapping/entity/DependencyMapping.kt new file mode 100644 index 0000000..c226215 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/document/mapping/entity/DependencyMapping.kt @@ -0,0 +1,29 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/6/1. + */ +package com.highcapable.sweetdependency.document.mapping.entity + +/** + * 每项依赖测绘实体 + * @param notation 依赖名称或 ID + * @param versionLine 版本所处行号 + */ +internal data class DependencyMapping(internal var notation: String = "", internal var versionLine: Int = -1) \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/environment/Environment.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/environment/Environment.kt new file mode 100644 index 0000000..dd314fe --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/environment/Environment.kt @@ -0,0 +1,95 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/6/28. + */ +package com.highcapable.sweetdependency.environment + +import com.highcapable.sweetdependency.SweetDependency +import com.highcapable.sweetdependency.generated.SweetDependencyProperties +import com.highcapable.sweetdependency.gradle.helper.GradleHelper +import com.highcapable.sweetdependency.utils.toFile +import java.io.File + +/** + * [SweetDependency] 环境工具类 + */ +internal object Environment { + + /** [SweetDependency] 缓存存放目录 */ + private const val MEMORY_DIR_PATH = ".gradle/${SweetDependencyProperties.PROJECT_MODULE_NAME}" + + /** [SweetDependency] 功能存放目录 */ + private const val RESOURCES_DIR_PATH = "gradle/${SweetDependencyProperties.PROJECT_MODULE_NAME}" + + /** + * 获取 [SweetDependency] 缓存存放目录 + * @return [File] + */ + private val memoryDir get() = "${GradleHelper.rootDir.absolutePath}/$MEMORY_DIR_PATH".toFile().also { if (it.exists().not()) it.mkdirs() } + + /** + * 获取 [SweetDependency] 功能存放目录 + * @return [File] + */ + private val resourcesDir get() = "${GradleHelper.rootDir.absolutePath}/$RESOURCES_DIR_PATH".toFile().also { if (it.exists().not()) it.mkdirs() } + + /** + * 获取系统信息 + * @return [String] + */ + internal val systemInfo get() = "${System.getProperty("os.name")} ${System.getProperty("os.version")}" + + /** + * 获取字符集名称 + * @return [String] + */ + internal val characterEncoding get() = System.getProperty("file.encoding") + + /** + * 获取 Java 版本 + * @return [String] + */ + internal val javaVersion get() = System.getProperty("java.version") + + /** + * 获取 [SweetDependency] 缓存存放目录 + * @param dirOrFileName 子路径目录、文件名称数组 + * @return [File] + */ + internal fun memoryDir(vararg dirOrFileName: String) = memoryDir.parseDir(*dirOrFileName) + + /** + * 获取 [SweetDependency] 功能存放目录 + * @param dirOrFileName 子路径目录、文件名称数组 + * @return [File] + */ + internal fun resourcesDir(vararg dirOrFileName: String) = resourcesDir.parseDir(*dirOrFileName) + + /** + * 解析 [SweetDependency] 存放目录 + * @param dirOrFileName 子路径目录、文件名称数组 + * @return [File] + */ + private fun File.parseDir(vararg dirOrFileName: String): File { + var splitPath = "" + dirOrFileName.forEach { splitPath += "$it/" } + return "$absolutePath/${splitPath.ifBlank { "/" }.dropLast(1)}".toFile() + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/exception/SweetDependencyUnresolvedException.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/exception/SweetDependencyUnresolvedException.kt new file mode 100644 index 0000000..05dae63 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/exception/SweetDependencyUnresolvedException.kt @@ -0,0 +1,42 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/5/31. + */ +package com.highcapable.sweetdependency.exception + +import com.highcapable.sweetdependency.SweetDependency +import com.highcapable.sweetdependency.utils.dumpToString + +/** + * [SweetDependency] 异常定义类 + * @param msg 异常内容 + * @param parent 父级异常 - 默认空 + */ +internal class SweetDependencyUnresolvedException internal constructor(private val msg: String, parent: Throwable? = null) : Exception( + ("[${SweetDependency.TAG}] The project initialization could not be completed, please check the following for errors\n" + + "If you need help, visit ${SweetDependency.PROJECT_URL}\n" + + "* What went wrong:\n" + + "$msg\n${if (parent != null) (when (parent) { + is SweetDependencyUnresolvedException -> "* Caused by:" + else -> "* Exception is:" + } + "\n${parent.dumpToString()}") else ""}").trim() +) { + override fun toString() = "${javaClass.simpleName}: $msg" +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/delegate/GradleDelegate.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/delegate/GradleDelegate.kt new file mode 100644 index 0000000..7ac680e --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/delegate/GradleDelegate.kt @@ -0,0 +1,94 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/5/26. + */ +package com.highcapable.sweetdependency.gradle.delegate + +import com.highcapable.sweetdependency.gradle.helper.GradleHelper +import com.highcapable.sweetdependency.gradle.proxy.IGradleLifecycle +import com.highcapable.sweetdependency.utils.debug.SError +import org.gradle.api.Project +import org.gradle.api.initialization.Settings + +/** + * Gradle 代理工具类 + */ +internal object GradleDelegate { + + /** 当前 Gradle 生命周期接口实例 */ + private var lifecycle: IGradleLifecycle? = null + + /** + * 创建 Gradle 生命周期 (插件) [T] + * @param settings 当前设置 + */ + internal inline fun create(settings: Settings) { + runCatching { + lifecycle = T::class.java.getConstructor().newInstance() + }.onFailure { SError.make("Failed to create Gradle lifecycle of \"${T::class.java}\"") } + GradleHelper.attach(settings) + callOnSettingsLoaded(settings) + settings.gradle.settingsEvaluated { callOnSettingsEvaluate(settings = this) } + settings.gradle.projectsLoaded { + callOnProjectLoaded(rootProject, isRoot = true) + rootProject.afterEvaluate { callOnProjectEvaluate(project = this, isRoot = true) } + rootProject.subprojects.forEach { + callOnProjectLoaded(it, isRoot = false) + it.afterEvaluate { callOnProjectEvaluate(project = this, isRoot = false) } + } + } + } + + /** + * 调用 Gradle 开始装载事件 + * @param settings 当前实例 + */ + private fun callOnSettingsLoaded(settings: Settings) { + lifecycle?.onSettingsLoaded(settings) + } + + /** + * 调用 Gradle 装载完成事件 + * @param settings 当前实例 + */ + private fun callOnSettingsEvaluate(settings: Settings) { + lifecycle?.onSettingsEvaluate(settings) + } + + /** + * 调用 Gradle 开始装载项目事件 + * @param project 当前项目 + * @param isRoot 是否为根项目 + */ + private fun callOnProjectLoaded(project: Project, isRoot: Boolean) { + if (isRoot) GradleHelper.cachingProjectList(project) + lifecycle?.onProjectLoaded(project, isRoot) + } + + /** + * 调用 Gradle 项目装载完成事件 + * @param project 当前项目 + * @param isRoot 是否为根项目 + */ + private fun callOnProjectEvaluate(project: Project, isRoot: Boolean) { + GradleHelper.cachingDependencyList(project, isRoot) + lifecycle?.onProjectEvaluate(project, isRoot) + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/delegate/ProjectTransaction.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/delegate/ProjectTransaction.kt new file mode 100644 index 0000000..7e96e27 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/delegate/ProjectTransaction.kt @@ -0,0 +1,63 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/5/29. + */ +package com.highcapable.sweetdependency.gradle.delegate + +import org.gradle.api.Project +import kotlin.properties.Delegates + +/** + * 项目事务实例实现类 + */ +internal class ProjectTransaction { + + internal companion object { + + /** 当前项目 (当前生命周期静态) */ + internal var current by Delegates.notNull() + + /** 是否为根项目 (当前生命周期静态) */ + internal var isRoot by Delegates.notNull() + } + + /** 当前装载实例方法体数组 */ + internal val evaluateCallbacks = mutableSetOf<((Project, Boolean) -> Unit)>() + + /** + * 获取当前项目 + * @return [Project] + */ + internal val current get() = Companion.current + + /** + * 获取是否为根项目 + * @return [Boolean] + */ + internal val isRoot get() = Companion.isRoot + + /** + * 创建装载实例监听 + * @param evaluate 回调装载监听 - ([Project] 当前项目,[Boolean] 师傅为根项目) + */ + internal fun evaluation(evaluate: (project: Project, isRoot: Boolean) -> Unit) { + evaluateCallbacks.add(evaluate) + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/delegate/entity/ExternalDependencyDelegate.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/delegate/entity/ExternalDependencyDelegate.kt new file mode 100644 index 0000000..1b64f2f --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/delegate/entity/ExternalDependencyDelegate.kt @@ -0,0 +1,67 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/6/25. + */ +@file:Suppress("USELESS_ELVIS", "KotlinRedundantDiagnosticSuppress") + +package com.highcapable.sweetdependency.gradle.delegate.entity + +import com.highcapable.sweetdependency.document.factory.spliceToDependencyNotation +import com.highcapable.sweetdependency.gradle.entity.DependencyVersion +import com.highcapable.sweetdependency.manager.GradleTaskManager +import com.highcapable.sweetdependency.utils.debug.SError +import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency + +/** + * 外部存储库依赖实体代理类 + * + * 代理 [DefaultExternalModuleDependency] + * @param groupId Group ID + * @param artifactId Artifact ID + * @param version 版本 + */ +internal open class ExternalDependencyDelegate internal constructor( + @get:JvmName("getDelegateGroupId") + @set:JvmName("setDelegateGroupId") + var groupId: String, + @get:JvmName("getDelegateArtifactId") + @set:JvmName("setDelegateArtifactId") + var artifactId: String, + @get:JvmName("getDelegateVersion") + @set:JvmName("setDelegateVersion") + var version: String +) : DefaultExternalModuleDependency(groupId, artifactId, version) { + + override fun getVersion(): String { + val notation = spliceToDependencyNotation(groupId, artifactId) + if (version == DependencyVersion.AUTOWIRE_VERSION_NAME && GradleTaskManager.isInternalRunningTask.not()) SError.make( + """ + This library "$notation" is not autowired and cannot be deployed + You can try the following solutions to resolve this problem: + 1. Manually re-run Gradle Sync (make sure "autowire-on-sync-mode" not be "OFF") + 2. Manually run "${GradleTaskManager.AUTOWIRE_LIBRARIES_TASK_NAME}" task and re-run Gradle Sync + 3. Fill an existing version for dependency "$notation" and re-run Gradle Sync + If you get this error again after doing the above, maybe the currently set repositories cannot find this library + """.trimIndent() + ); return super.getVersion() ?: version + } + + override fun toString() = "ExternalDependencyDelegate(groupId = $groupId, artifactId = $artifactId, version = $version)" +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/entity/DependencyName.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/entity/DependencyName.kt new file mode 100644 index 0000000..d7bb14a --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/entity/DependencyName.kt @@ -0,0 +1,127 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/8/16. + */ +@file:Suppress("unused", "MemberVisibilityCanBePrivate") + +package com.highcapable.sweetdependency.gradle.entity + +import com.highcapable.sweetdependency.document.factory.convertToDependencyAmbiguousName +import com.highcapable.sweetdependency.document.factory.convertToDependencyUrlName +import com.highcapable.sweetdependency.document.factory.spliceToDependencyNotation +import com.highcapable.sweetdependency.document.factory.splitToDependencyNames +import com.highcapable.sweetdependency.utils.firstNumberToLetter + +/** + * 依赖名称实体类 + * @param type 名称类型 + * @param groupId Group ID + * @param artifactId Artifact ID + */ +internal class DependencyName private constructor(internal val type: Type, internal val groupId: String, internal val artifactId: String) { + + internal companion object { + + /** 标识 Gradle 插件后缀名称 */ + private const val GRADLE_PLUGIN_SUFFIX = "gradle.plugin" + + /** + * 创建为插件依赖名称 + * @param notation 完整名称 + */ + internal fun plugin(notation: String) = DependencyName(Type.PLUGIN, notation, "$notation.$GRADLE_PLUGIN_SUFFIX") + + /** + * 创建为库依赖名称 + * @param notation 完整名称 + */ + internal fun library(notation: String) = notation.splitToDependencyNames().let { names -> DependencyName(Type.LIBRARY, names[0], names[1]) } + + /** + * 创建为库依赖名称 + * @param groupId Group ID + * @param artifactId Artifact ID + */ + internal fun library(groupId: String, artifactId: String) = DependencyName(Type.LIBRARY, groupId, artifactId) + } + + /** + * 获取当前模糊分离名称 (使用 [symbol] 进行分离) + * @param symbol 分隔符 - 默认 "." + * @param isReplaceFirstChar 是否使用 [firstNumberToLetter] 替换每一段第一个字符 - 默认否 + * @param isLowerCase 是否全部转换为小写 - 默认是 + * @return [String] + */ + internal fun ambiguousName(symbol: String = ".", isReplaceFirstChar: Boolean = false, isLowerCase: Boolean = true) = + current.convertToDependencyAmbiguousName(symbol, isReplaceFirstChar, isLowerCase) + + /** + * 获取当前 URL 名称 + * @return [String] + */ + internal val urlName get() = notation.convertToDependencyUrlName() + + /** + * 获取当前描述内容 + * @return [String] + */ + internal val description get() = "$typeName \"$current\"" + + /** + * 获取当前类型名称 + * @return [String] + */ + internal val typeName get() = when (type) { + Type.PLUGIN -> "Plugin" + Type.LIBRARY -> "Library" + } + + /** + * 获取当前名称 + * @return [String] + */ + internal val current get() = when (type) { + Type.PLUGIN -> groupId + Type.LIBRARY -> notation + } + + /** + * 获取当前完整名称 + * @return [String] + */ + internal val notation get() = spliceToDependencyNotation(groupId, artifactId) + + override fun equals(other: Any?) = other.toString() == toString() + + override fun hashCode() = toString().hashCode() + + override fun toString() = current + + /** + * 名称类型定义类 + */ + internal enum class Type { + /** 插件依赖 */ + PLUGIN, + + /** 库依赖 */ + LIBRARY + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/entity/DependencyUpdateMode.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/entity/DependencyUpdateMode.kt new file mode 100644 index 0000000..b93d361 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/entity/DependencyUpdateMode.kt @@ -0,0 +1,58 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/8/18. + */ +package com.highcapable.sweetdependency.gradle.entity + +/** + * 依赖更新模式实体类 + * @param dependencyType 依赖类型 + * @param updateType 依赖更新模式 + */ +internal data class DependencyUpdateMode(internal var dependencyType: DependencyType, internal var updateType: UpdateType) { + + /** + * 依赖类型定义类 + */ + internal enum class DependencyType { + /** 全部类型 */ + ALL, + + /** 插件依赖 */ + PLUGINS, + + /** 库依赖 */ + LIBRARIES, + } + + /** + * 依赖更新模式类型定义类 + */ + internal enum class UpdateType { + /** 可选更新 */ + UPDATE_OPTIONAL, + + /** 全部更新 */ + UPDATE_ALL, + + /** 仅自动装配 */ + ONLY_AUTOWIRE, + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/entity/DependencyVersion.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/entity/DependencyVersion.kt new file mode 100644 index 0000000..e57a2b1 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/entity/DependencyVersion.kt @@ -0,0 +1,157 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/6/16. + */ +@file:Suppress("MemberVisibilityCanBePrivate") + +package com.highcapable.sweetdependency.gradle.entity + +import com.highcapable.sweetdependency.utils.debug.SError + +/** + * 依赖版本实体类 + * @param actual 当前实际版本 - 如果为 [DependencyVersion.isOptional] 则会显示在第一位 + * @param optionalType 可选更新类型 - 默认为 [DependencyVersion.OptionalUpdateType.NONE] + */ +internal class DependencyVersion(internal var actual: String, optionalType: OptionalUpdateType = OptionalUpdateType.NONE) { + + internal companion object { + + /** 标识自动装配版本的名称 */ + const val AUTOWIRE_VERSION_NAME = "+" + + /** 标识不指定版本的名称 */ + const val NO_SPECIFIC_VERSION_NAME = "" + + /** 标识当前最新版本的名称 */ + const val LATEST_VERSION_NAME = "" + + /** 标识常规可选更新版本前缀名称 */ + private const val OPTIONAL_VERSION_NORMAL_PREFIX = "^" + + /** 标识常驻可选更新版本前缀名称 */ + private const val OPTIONAL_VERSION_PERMANENT_PREFIX = "^^" + } + + init { + if (current.startsWith("<") && current.endsWith(">")) + if (current != NO_SPECIFIC_VERSION_NAME && current != LATEST_VERSION_NAME) + SError.make("The parameter \"$current\" is not recognized as any available function") + if (isOptional.not()) when (optionalType) { + OptionalUpdateType.NONE -> {} + OptionalUpdateType.NORMAL -> actual = "$OPTIONAL_VERSION_NORMAL_PREFIX$actual" + OptionalUpdateType.PERMANENT -> actual = "$OPTIONAL_VERSION_PERMANENT_PREFIX$actual" + } + } + + /** + * 获取当前版本 + * @return [String] + */ + internal val current get() = actual.replace(OPTIONAL_VERSION_PERMANENT_PREFIX, "").replace(OPTIONAL_VERSION_NORMAL_PREFIX, "") + + /** + * 获取当前测绘数据使用的版本 + * + * 它会自动识别 [optionalType] 决定是否继续保留可选更新的符号 + * @return [String] + */ + internal val mapped get() = when (optionalType) { + OptionalUpdateType.PERMANENT -> actual + else -> current + } + + /** + * 获取部署版本 + * + * 如果为 [isNoSpecific] 则会返回空 + * @return [String] + */ + internal val deployed get() = current.takeIf { isNoSpecific.not() } ?: "" + + /** + * 获取存在版本 + * + * 如果为空则会返回 [NO_SPECIFIC_VERSION_NAME] + * @return [String] + */ + internal val existed get() = current.ifBlank { NO_SPECIFIC_VERSION_NAME } + + /** + * 是否为空白 + * @return [Boolean] + */ + internal val isBlank get() = current.isBlank() + + /** + * 是否为自动装配版本 + * @return [Boolean] + */ + internal val isAutowire get() = current == AUTOWIRE_VERSION_NAME + + /** + * 是否为不指定版本 + * @return [Boolean] + */ + internal val isNoSpecific get() = current == NO_SPECIFIC_VERSION_NAME + + /** + * 是否为可选更新版本 + * @return [Boolean] + */ + internal val isOptional get() = optionalType != OptionalUpdateType.NONE + + /** + * 获取当前可选更新类型 + * @return [OptionalUpdateType] + */ + internal val optionalType get() = when { + actual.startsWith(OPTIONAL_VERSION_PERMANENT_PREFIX) -> OptionalUpdateType.PERMANENT + actual.startsWith(OPTIONAL_VERSION_NORMAL_PREFIX) -> OptionalUpdateType.NORMAL + else -> OptionalUpdateType.NONE + } + + /** + * 克隆为新的 [DependencyVersion] 实体 + * @param version 当前版本 + * @return [DependencyVersion] + */ + internal fun clone(version: String) = DependencyVersion(version, optionalType) + + override fun equals(other: Any?) = other.toString() == toString() + + override fun hashCode() = toString().hashCode() + + override fun toString() = current + + /** + * 可选更新类型定义类 + */ + internal enum class OptionalUpdateType { + /** 无 */ + NONE, + + /** 常规 */ + NORMAL, + + /** 常驻 */ + PERMANENT, + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/entity/ExternalDependency.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/entity/ExternalDependency.kt new file mode 100644 index 0000000..7432a19 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/entity/ExternalDependency.kt @@ -0,0 +1,44 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/6/23. + */ +package com.highcapable.sweetdependency.gradle.entity + +/** + * 外部存储库依赖实体类 + * @param dependencyName 依赖名称 + * @param version 版本 + */ +internal data class ExternalDependency(private val dependencyName: DependencyName, internal val version: DependencyVersion) { + + /** + * 获取 Group ID + * @return [String] + */ + internal val groupId get() = dependencyName.groupId + + /** + * 获取 Artifact ID + * @return [String] + */ + internal val artifactId get() = dependencyName.artifactId + + override fun toString() = dependencyName.current +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/factory/ExtensionAwareFactory.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/factory/ExtensionAwareFactory.kt new file mode 100644 index 0000000..fa08a78 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/factory/ExtensionAwareFactory.kt @@ -0,0 +1,117 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/5/27. + */ +@file:Suppress("unused", "USELESS_CAST", "KotlinRedundantDiagnosticSuppress") + +package com.highcapable.sweetdependency.gradle.factory + +import com.highcapable.sweetdependency.exception.SweetDependencyUnresolvedException +import com.highcapable.sweetdependency.utils.camelcase +import com.highcapable.sweetdependency.utils.debug.SError +import org.gradle.api.Action +import org.gradle.api.plugins.ExtensionAware + +/** + * 创建、获取扩展方法 + * @param name 方法名称 - 自动调用 [toSafeExtName] + * @param clazz 目标对象 [Class] + * @param args 方法参数 + * @return [ExtensionAware] + */ +internal fun ExtensionAware.getOrCreate(name: String, clazz: Class<*>, vararg args: Any?) = name.toSafeExtName().let { sName -> + runCatching { extensions.create(sName, clazz, *args).asExtension() }.getOrElse { + if ((it is IllegalArgumentException && it.message?.startsWith("Cannot add extension with name") == true).not()) throw it + runCatching { extensions.getByName(sName).asExtension() }.getOrNull() ?: SError.make("Create or get extension failed with name \"$sName\"") + } +} + +/** + * 创建、获取扩展方法 - 目标对象 [T] + * @param name 方法名称 - 自动调用 [toSafeExtName] + * @param args 方法参数 + * @return [T] + */ +internal inline fun ExtensionAware.getOrCreate(name: String, vararg args: Any?) = name.toSafeExtName().let { sName -> + runCatching { extensions.create(sName, T::class.java, *args) as T }.getOrElse { + if ((it is IllegalArgumentException && it.message?.startsWith("Cannot add extension with name") == true).not()) throw it + runCatching { extensions.getByName(sName) as? T? }.getOrNull() ?: SError.make("Create or get extension failed with name \"$sName\"") + } +} + +/** + * 获取扩展方法 + * @param name 方法名称 + * @return [ExtensionAware] + */ +internal fun ExtensionAware.get(name: String) = + runCatching { extensions.getByName(name).asExtension() }.getOrNull() ?: SError.make("Could not get extension with name \"$name\"") + +/** + * 获取扩展方法 - 目标对象 [T] + * @param name 方法名称 + * @return [T] + */ +internal inline fun ExtensionAware.get(name: String) = + runCatching { extensions.getByName(name) as T }.getOrNull() ?: SError.make("Could not get extension with name \"$name\"") + +/** + * 获取扩展方法 - 目标对象 [T] + * @return [T] + */ +internal inline fun ExtensionAware.get() = + runCatching { extensions.getByType(T::class.java) as T }.getOrNull() ?: SError.make("Could not get extension with type ${T::class.java}") + +/** + * 配置扩展方法 - 目标对象 [T] + * @param name 方法名称 + * @param configure 配置方法体 + */ +internal inline fun ExtensionAware.configure(name: String, configure: Action) = extensions.configure(name, configure) + +/** + * 是否存在扩展方法 + * @param name 方法名称 + * @return [Boolean] + */ +internal fun ExtensionAware.hasExtension(name: String) = runCatching { extensions.getByName(name); true }.getOrNull() ?: false + +/** + * 转换到扩展方法类型 [ExtensionAware] + * @return [ExtensionAware] + * @throws SweetDependencyUnresolvedException 如果类型不是 [ExtensionAware] + */ +internal fun Any.asExtension() = this as? ExtensionAware? ?: SError.make("This instance \"$this\" is not a valid Extension") + +/** + * 由于 Gradle 存在一个 [ExtensionAware] 的扩展 + * + * 此功能用于检测当前字符串是否为 Gradle 使用的关键字名称 + * @return [Boolean] + */ +internal fun String.isUnSafeExtName() = camelcase().let { it == "ext" || it == "extra" || it == "extraProperties" || it == "extensions" } + +/** + * 由于 Gradle 存在一个 [ExtensionAware] 的扩展 + * + * 此功能用于转换不符合规定的字符串到 "{字符串}s" + * @return [String] + */ +internal fun String.toSafeExtName() = if (isUnSafeExtName()) "${this}s" else this \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/factory/GradleProjectFactory.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/factory/GradleProjectFactory.kt new file mode 100644 index 0000000..d0f82e8 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/factory/GradleProjectFactory.kt @@ -0,0 +1,190 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/6/2. + */ +@file:Suppress("USELESS_ELVIS", "KotlinRedundantDiagnosticSuppress", "UselessCallOnNotNull") + +package com.highcapable.sweetdependency.gradle.factory + +import com.highcapable.sweetdependency.gradle.helper.GradleHelper +import com.highcapable.sweetdependency.gradle.wrapper.LibraryDependencyWrapper +import com.highcapable.sweetdependency.gradle.wrapper.PluginDependencyWrapper +import com.highcapable.sweetdependency.plugin.task.base.BaseTask +import com.highcapable.sweetdependency.utils.code.entity.MavenPomData +import com.highcapable.sweetdependency.utils.debug.SError +import com.highcapable.sweetdependency.utils.noBlank +import com.highcapable.sweetdependency.utils.orEmpty +import com.highcapable.sweetdependency.utils.toFile +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.artifacts.Dependency +import org.gradle.api.artifacts.ExternalDependency +import org.gradle.api.artifacts.FileCollectionDependency +import org.gradle.api.internal.GeneratedSubclasses +import org.gradle.api.internal.plugins.PluginManagerInternal +import org.gradle.api.plugins.PluginManager +import org.gradle.api.provider.Provider +import org.gradle.api.provider.ProviderConvertible +import org.gradle.kotlin.dsl.buildscript +import org.gradle.kotlin.dsl.repositories +import org.gradle.plugin.use.PluginDependenciesSpec +import org.gradle.plugin.use.PluginDependency + +/** + * 获取指定项目的完整名称 + * @return [String] + */ +internal val Project.fullName + get(): String { + val baseNames = mutableListOf() + + /** + * 递归子项目 + * @param project 当前项目 + */ + fun fetchChild(project: Project) { + project.parent?.also { if (it != it.rootProject) fetchChild(it) } + baseNames.add(project.name) + } + fetchChild(project = this) + return buildString { baseNames.onEach { append(":$it") }.clear() }.drop(1) + } + +/** + * 向构建脚本添加自定义依赖 + * @param repositoryPath 存储库路径 + * @param pomData Maven POM 实体 + */ +internal fun Project.addDependencyToBuildScript(repositoryPath: String, pomData: MavenPomData) = + buildscript { + repositories { + maven { + url = repositoryPath.toFile().toURI() + mavenContent { includeGroup(pomData.groupId) } + } + }; dependencies { classpath("${pomData.groupId}:${pomData.artifactId}:${pomData.version}") } + } + +/** + * 装载构建脚本的 [Class] + * @param name [Class] 完整名称 + * @return [Class] + */ +internal fun Project.loadBuildScriptClass(name: String) = buildscript.classLoader.loadClass(name) + +/** + * 获取指定项目部署的插件依赖数组 (实时) + * + * @param isUseCache 是否使用缓存 - 默认启用 - 启用后将使用 [GradleHelper.projectPlugins] 进行获取 + * @return [MutableList]<[PluginDependencyWrapper]> + */ +internal fun Project.plugins(isUseCache: Boolean = true) = + if (isUseCache) GradleHelper.projectPlugins[this].orEmpty() else mutableListOf().apply { + plugins.configureEach { + pluginManager.findPluginId(this).noBlank()?.also { add(PluginDependencyWrapper(instance = this, it)) } + } + } + +/** + * 获取指定项目部署的库依赖数组 (实时) + * + * @param isUseCache 是否使用缓存 - 默认启用 - 启用后将使用 [GradleHelper.projectLibraries] 进行获取 + * @return [MutableList]<[LibraryDependencyWrapper]> + */ +internal fun Project.libraries(isUseCache: Boolean = true) = + if (isUseCache) GradleHelper.projectLibraries[this].orEmpty() else mutableListOf().apply { + /** + * 检查依赖是否有效 + * @return [Boolean] + */ + fun Dependency.checkingValid() = when (this) { + is ExternalDependency -> group.isNullOrBlank().not() && name.isNullOrBlank().not() + is FileCollectionDependency -> runCatching { files.files.isNotEmpty() }.getOrNull() ?: false + else -> true + } + /** 在一些项目 (例如 Kotlin Multiplatform 中会发生异常 [java.util.ConcurrentModificationException] - 这里直接做拦截处理 */ + runCatching { + configurations.forEach { config -> + config.dependencies.forEach { if (it.checkingValid()) add(LibraryDependencyWrapper(it, config.name ?: "")) } + } + } + } + +/** + * 等待并监听当指定插件被添加时回调 + * @param id 插件 ID + * @param action 回调插件实例 + */ +internal fun Project.waitForPluginAdded(id: String, action: (Plugin<*>) -> Unit) { + plugins.whenPluginAdded { if (pluginManager.findPluginId(this) == id) action(this) } +} + +/** + * 创建 Gradle Task [T] + * @param group Task 分组 + * @param name Task 名称 + * @return [T] + */ +internal inline fun Project.createTask(group: String, name: String) = runCatching { + T::class.java.getConstructor().newInstance().also { instance -> + task(name) { + this.group = group + outputs.upToDateWhen { false } + doFirst { instance.onTransaction() } + } + } +}.getOrNull() ?: SError.make("Gradle task \"$name\" with group \"$group\" create failed") + +/** + * 应用插件 + * @param id 插件 ID + * @param version 版本 + */ +internal fun PluginDependenciesSpec.applyPlugin(id: String, version: String) = + id(id).apply { if (version.isNotBlank()) version(version) } ?: SError.make("Plugin \"$id\" not apply") + +/** + * 应用插件 + * @param alias 别名实例 + */ +internal fun PluginDependenciesSpec.applyPlugin(alias: Any) = when (alias) { + is Provider<*> -> + @Suppress("UNCHECKED_CAST") + alias(alias as? Provider ?: SError.make("The $alias is not a valid plugin")) + ?: SError.make("Plugin $alias not apply") + is ProviderConvertible<*> -> + @Suppress("UNCHECKED_CAST") + alias(alias as? ProviderConvertible ?: SError.make("The $alias is not a valid plugin")) + ?: SError.make("Plugin $alias not apply") + else -> SError.make("The $alias is not a valid plugin (unknown type)") +} + +/** + * 通过 [PluginManager] 查找 [Plugin] 真实 ID + * + * 如果找不到会返回空字符串 + * @param plugin 当前实例 + * @return [String] + */ +private fun PluginManager.findPluginId(plugin: Plugin<*>) = runCatching { + @Suppress("UNCHECKED_CAST") + val pluginIds = (this as PluginManagerInternal).findPluginIdForClass(GeneratedSubclasses.unpackType(plugin) as Class>) + if (pluginIds.isEmpty.not()) pluginIds.get() else null +}.getOrNull()?.id ?: "" \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/helper/GradleHelper.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/helper/GradleHelper.kt new file mode 100644 index 0000000..57b3f83 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/helper/GradleHelper.kt @@ -0,0 +1,155 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/5/19. + */ +@file:Suppress("MemberVisibilityCanBePrivate") + +package com.highcapable.sweetdependency.gradle.helper + +import com.highcapable.sweetdependency.gradle.factory.libraries +import com.highcapable.sweetdependency.gradle.factory.plugins +import com.highcapable.sweetdependency.gradle.wrapper.LibraryDependencyWrapper +import com.highcapable.sweetdependency.gradle.wrapper.PluginDependencyWrapper +import com.highcapable.sweetdependency.utils.debug.SError +import com.highcapable.sweetdependency.utils.parseFileSeparator +import org.gradle.api.Project +import org.gradle.api.initialization.Settings +import org.gradle.api.invocation.Gradle +import java.io.File +import java.io.FileReader +import java.util.* + +/** + * Gradle 工具类 + */ +internal object GradleHelper { + + /** Gradle 配置文件名称 */ + private const val GRADLE_PROPERTIES_FILE_NAME = "gradle.properties" + + /** 当前 [Gradle] 静态实例 */ + private var instance: Gradle? = null + + /** 当前 [Settings] 静态实例 */ + private var settings: Settings? = null + + /** 当前装载的所有项目 */ + internal val allProjects = mutableSetOf() + + /** 当前装载的项目插件依赖数组 */ + internal val projectPlugins = mutableMapOf>() + + /** 当前装载的项目库依赖数组 */ + internal val projectLibraries = mutableMapOf>() + + /** + * 绑定当前使用的 [Settings] 静态实例 + * @param settings 当前设置 + */ + internal fun attach(settings: Settings) { + instance = settings.also { this.settings = it }.gradle + } + + /** + * 缓存所有项目列表 + * @param rootProject 当前根项目 + */ + internal fun cachingProjectList(rootProject: Project) { + allProjects.clear() + allProjects.addAll(rootProject.allprojects) + } + + /** + * 缓存所有依赖列表 + * @param project 当前项目 + * @param isRoot 是否为根项目 + */ + internal fun cachingDependencyList(project: Project, isRoot: Boolean) { + if (isRoot) { + projectPlugins.clear() + projectLibraries.clear() + } + project.plugins(isUseCache = false).forEach { + if (projectPlugins[project] == null) projectPlugins[project] = mutableListOf() + projectPlugins[project]?.add(it) + } + project.libraries(isUseCache = false).forEach { + if (projectLibraries[project] == null) projectLibraries[project] = mutableListOf() + projectLibraries[project]?.add(it) + } + } + + /** + * 获取用户目录的 [Properties] + * @return [Properties] or null + */ + internal val userProperties get() = createProperties(instance?.gradleUserHomeDir) + + /** + * 获取当前项目的 [Properties] + * @return [Properties] or null + */ + internal val projectProperties get() = createProperties(settings?.rootDir) + + /** + * 获取当前 Gradle 项目的根目录 + * @return [File] + */ + internal val rootDir get() = rootProject?.projectDir ?: settings?.rootDir ?: SError.make("Gradle is unavailable") + + /** + * 获取当前 Gradle 项目的根项目 (Root Project) + * @return [Project] or null + */ + internal val rootProject get() = runCatching { instance?.rootProject }.getOrNull() + + /** + * 获取当前 Gradle 版本 + * @return [String] + */ + internal val version get() = instance?.gradle?.gradleVersion ?: "" + + /** + * 获取当前 Gradle 是否处于离线模式 + * @return [Boolean] + */ + internal val isOfflineMode get() = instance?.startParameter?.isOffline == true + + /** + * 获取当前 Gradle 是否处于同步模式 + * @return [Boolean] + */ + internal val isSyncMode get() = runningTaskNames.isNullOrEmpty() + + /** + * 获取当前正在运行的 Task 名称数组 + * @return [MutableList]<[String]> or null + */ + internal val runningTaskNames get() = instance?.startParameter?.taskRequests?.getOrNull(0)?.args + + /** + * 创建新的 [Properties] + * @param dir 当前目录 + * @return [Properties] or null + */ + private fun createProperties(dir: File?) = runCatching { + Properties().apply { load(FileReader(dir?.resolve(GRADLE_PROPERTIES_FILE_NAME)?.absolutePath?.parseFileSeparator() ?: "")) } + }.getOrNull() +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/proxy/IGradleLifecycle.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/proxy/IGradleLifecycle.kt new file mode 100644 index 0000000..d101885 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/proxy/IGradleLifecycle.kt @@ -0,0 +1,57 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/5/26. + */ +package com.highcapable.sweetdependency.gradle.proxy + +import org.gradle.api.Project +import org.gradle.api.initialization.Settings + +/** + * Gradle 生命周期接口 + */ +internal interface IGradleLifecycle { + + /** + * 当 Gradle 开始装载时回调 + * @param settings 当前设置 + */ + fun onSettingsLoaded(settings: Settings) + + /** + * 当 Gradle 装载完成时回调 + * @param settings 当前设置 + */ + fun onSettingsEvaluate(settings: Settings) + + /** + * 当 Gradle 开始装载项目时回调 + * @param project 当前项目 + * @param isRoot 是否为根项目 + */ + fun onProjectLoaded(project: Project, isRoot: Boolean) + + /** + * 当 Gradle 项目装载完成时回调 + * @param project 当前项目 + * @param isRoot 是否为根项目 + */ + fun onProjectEvaluate(project: Project, isRoot: Boolean) +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/wrapper/LibraryDependencyWrapper.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/wrapper/LibraryDependencyWrapper.kt new file mode 100644 index 0000000..6c19cb4 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/wrapper/LibraryDependencyWrapper.kt @@ -0,0 +1,92 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/5/28. + */ +@file:Suppress("USELESS_ELVIS", "KotlinRedundantDiagnosticSuppress") + +package com.highcapable.sweetdependency.gradle.wrapper + +import com.highcapable.sweetdependency.document.factory.spliceToDependencyNotation +import com.highcapable.sweetdependency.gradle.delegate.entity.ExternalDependencyDelegate +import com.highcapable.sweetdependency.gradle.entity.DependencyVersion +import com.highcapable.sweetdependency.gradle.wrapper.type.LibraryDependencyType +import org.gradle.api.Project +import org.gradle.api.artifacts.Dependency +import org.gradle.api.artifacts.ExternalDependency +import org.gradle.api.artifacts.FileCollectionDependency +import org.gradle.api.artifacts.ProjectDependency +import java.io.File + +/** + * 库依赖包装实例实现类 + * @param instance 当前实例 + * @param configurationName 配置名称 + */ +internal data class LibraryDependencyWrapper internal constructor(private val instance: Dependency, internal val configurationName: String) { + + /** + * 获取当前依赖类型 + * @return [LibraryDependencyType] + */ + val type + get() = when (instance) { + is ExternalDependencyDelegate -> LibraryDependencyType.EXTERNAL_DELEGATE + is ExternalDependency -> LibraryDependencyType.EXTERNAL + is ProjectDependency -> LibraryDependencyType.PROJECT + is FileCollectionDependency -> LibraryDependencyType.FILES + else -> LibraryDependencyType.OTHERS + } + + /** + * 依赖的文件数组 + * + * - [type] 需要为 [LibraryDependencyType.FILES] 否则始终为 null + * @return [MutableSet]<[File]> or null + */ + val files get() = runCatching { (instance as? FileCollectionDependency?)?.files?.files?.toMutableSet() }.getOrNull() + + /** + * 依赖的项目 + * + * - [type] 需要为 [LibraryDependencyType.PROJECT] 否则始终为 null + * @return [Project] or null + */ + val project get() = runCatching { (instance as? ProjectDependency?)?.dependencyProject }.getOrNull() + + /** + * Group ID + * @return [String] + */ + val groupId get() = instance.group ?: "" + + /** + * Artifact ID + * @return [String] + */ + val artifactId get() = instance.name ?: "" + + /** + * 版本 + * @return [DependencyVersion] + */ + val version get() = DependencyVersion(instance.version ?: "") + + override fun toString() = spliceToDependencyNotation(groupId, artifactId) +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/wrapper/PluginDependencyWrapper.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/wrapper/PluginDependencyWrapper.kt new file mode 100644 index 0000000..53a32f8 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/wrapper/PluginDependencyWrapper.kt @@ -0,0 +1,34 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/8/17. + */ +package com.highcapable.sweetdependency.gradle.wrapper + +import org.gradle.api.Plugin + +/** + * 插件依赖包装实例实现类 + * @param instance 当前实例 + * @param id 插件 ID + */ +internal data class PluginDependencyWrapper internal constructor(private val instance: Plugin<*>, internal val id: String) { + + override fun toString() = id +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/wrapper/type/LibraryDependencyType.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/wrapper/type/LibraryDependencyType.kt new file mode 100644 index 0000000..68a9bc8 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/gradle/wrapper/type/LibraryDependencyType.kt @@ -0,0 +1,42 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/6/10. + */ +package com.highcapable.sweetdependency.gradle.wrapper.type + +/** + * 库依赖类型定义类 + */ +internal enum class LibraryDependencyType { + /** 其它类型 */ + OTHERS, + + /** 外部存储库 */ + EXTERNAL, + + /** 外部存储库 (代理) */ + EXTERNAL_DELEGATE, + + /** 项目 */ + PROJECT, + + /** 文件 */ + FILES +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/DependencyManager.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/DependencyManager.kt new file mode 100644 index 0000000..8a9ea4e --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/DependencyManager.kt @@ -0,0 +1,335 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/5/16. + */ +package com.highcapable.sweetdependency.manager + +import com.highcapable.sweetdependency.SweetDependency +import com.highcapable.sweetdependency.document.PreferencesDocument +import com.highcapable.sweetdependency.document.VersionFilterDocument +import com.highcapable.sweetdependency.document.factory.DependenciesCondition +import com.highcapable.sweetdependency.document.factory.DependencyMap +import com.highcapable.sweetdependency.document.factory.RepositoryList +import com.highcapable.sweetdependency.gradle.entity.DependencyName +import com.highcapable.sweetdependency.gradle.entity.DependencyUpdateMode +import com.highcapable.sweetdependency.gradle.entity.DependencyVersion +import com.highcapable.sweetdependency.gradle.factory.get +import com.highcapable.sweetdependency.gradle.factory.getOrCreate +import com.highcapable.sweetdependency.gradle.factory.waitForPluginAdded +import com.highcapable.sweetdependency.gradle.helper.GradleHelper +import com.highcapable.sweetdependency.manager.content.Dependencies +import com.highcapable.sweetdependency.manager.content.Repositories +import com.highcapable.sweetdependency.manager.helper.DependencyAutowireLogHelper +import com.highcapable.sweetdependency.manager.helper.DependencyDeployHelper +import com.highcapable.sweetdependency.manager.maven.MavenParser +import com.highcapable.sweetdependency.manager.maven.entity.MavenMetadata +import com.highcapable.sweetdependency.plugin.config.content.SweetDependencyConfigs +import com.highcapable.sweetdependency.plugin.extension.dsl.manager.SweetDependencyAutowireExtension +import com.highcapable.sweetdependency.utils.debug.SLog +import com.highcapable.sweetdependency.utils.noEmpty +import com.highcapable.sweetdependency.utils.single +import org.gradle.api.Project +import org.gradle.api.initialization.Settings +import org.gradle.api.plugins.ExtensionAware + +/** + * 依赖部署、自动装配、更新管理类 + */ +internal object DependencyManager { + + /** 生成并应用依赖数组 */ + internal fun generateAndApply() { + Dependencies.generate(SweetDependencyConfigs.document.plugins(), SweetDependencyConfigs.document.libraries()) + val pluginsSize = Dependencies.plugins().size + val librariesSize = Dependencies.libraries().size + if (Dependencies.isNotEmpty()) SLog.verbose( + "${SweetDependency.TAG} help you autowired ${pluginsSize + librariesSize} dependencies" + + " (plugins: $pluginsSize, libraries: $librariesSize)", SLog.STRNG + ) + } + + /** + * 生成并应用依赖数组 + * @param settings 当前设置 + */ + internal fun generateAndApply(settings: Settings) { + val isDisableOnSync = SweetDependencyConfigs.document.preferences().autowireOnSyncMode == PreferencesDocument.AutowireOnSyncMode.OFF + var requiresAutowiringLibrariesSize = 0 + Dependencies.all().forEach { (_, artifact) -> if (artifact.version().isAutowire) requiresAutowiringLibrariesSize++ } + if (requiresAutowiringLibrariesSize > 0 && GradleHelper.isSyncMode && isDisableOnSync) SLog.warn( + "Found $requiresAutowiringLibrariesSize dependencies need to be autowired, " + + "please manually run \"${GradleTaskManager.AUTOWIRE_DEPENDENCIES_TASK_NAME}\" task and re-run Gradle Sync" + ) + DependencyDeployHelper.generateVersionCatalogs(settings) + } + + /** + * 初始化库依赖可访问类 + * @param rootProject 当前根项目 + */ + internal fun resolve(rootProject: Project) = DependencyDeployHelper.resolveAccessors(rootProject) + + /** + * 部署依赖 + * @param rootProject 当前根项目 + */ + internal fun deploy(rootProject: Project) { + /** + * 为 Groovy 创建扩展方法 + * @param extension 当前扩展实例 + */ + fun Project.deployForGroovy(extension: ExtensionAware) { + if (buildFile.name.endsWith(".gradle")) + extension.getOrCreate(SweetDependencyAutowireExtension.NAME, this) + } + + /** + * 部署到当前项目 + * @param extension 当前扩展实例 + */ + fun Project.deployEach(extension: ExtensionAware) = DependencyDeployHelper.deployAccessors(project = this, extension) + + /** 适配 Kotlin Multiplatform */ + fun Project.deployForKotlinMultiplatform() = + waitForPluginAdded("org.jetbrains.kotlin.multiplatform") { + get("kotlin").also { extension -> + deployForGroovy(extension) + deployEach(extension) + } + } + + /** 部署到当前项目 */ + fun Project.deploy() { + deployForGroovy(dependencies) + deployEach(dependencies) + deployForKotlinMultiplatform() + } + rootProject.deploy() + rootProject.subprojects.forEach { it.deploy() } + } + + /** + * 自动装配、更新依赖 + * @param updateMode 更新模式 + * @param isRunningOnSync 是否在 Gradle Sync 时运行 - 默认是 + */ + internal fun autowireAndUpdate(updateMode: DependencyUpdateMode, isRunningOnSync: Boolean = true) { + /** + * 在适当时候打印 Log + * @param msg 消息内容 + * @param symbol 前缀符号 + */ + fun logIfNeeded(msg: String, symbol: String) = if (isRunningOnSync) SLog.verbose(msg, symbol) else SLog.info(msg, symbol) + + /** + * 通过指定类型和条件查找依赖数组 + * @param condition 条件方法体 + * @return [DependencyMap] + */ + fun findByType(condition: DependenciesCondition) = when (updateMode.dependencyType) { + DependencyUpdateMode.DependencyType.ALL -> Dependencies.findAll(condition) + DependencyUpdateMode.DependencyType.PLUGINS -> Dependencies.findPlugins(condition) + DependencyUpdateMode.DependencyType.LIBRARIES -> Dependencies.findLibraries(condition) + } + if (Repositories.isEmpty()) return SLog.warn( + """ + Repositories is empty, ${SweetDependency.TAG} will stop auto update dependencies + This will cause Gradle fail to load dependencies, you must add some repositories and enable them + """.trimIndent() + ) + if (GradleHelper.isOfflineMode) SLog.warn("Gradle is in offline mode, some dependencies on online repositories will be ignored") + val isOnlyAutowireMode = updateMode.updateType == DependencyUpdateMode.UpdateType.ONLY_AUTOWIRE + var currentIndex = 0 + var updPluginsCount = 0 + var updLbrariesCount = 0 + val needUpdateDependencies = mutableMapOf>() + findByType { _, artifact -> + artifact.version().isNoSpecific.not() && (artifact.versionRef.isBlank() && + ((updateMode.updateType == DependencyUpdateMode.UpdateType.UPDATE_ALL || + (updateMode.updateType == DependencyUpdateMode.UpdateType.UPDATE_OPTIONAL && + (artifact.version().isOptional || artifact.version().isAutowire)) || + (updateMode.updateType == DependencyUpdateMode.UpdateType.ONLY_AUTOWIRE && artifact.version().isAutowire)))) + }.apply { + if (isNotEmpty()) SLog.info("Starting ${when (updateMode.updateType) { + DependencyUpdateMode.UpdateType.UPDATE_OPTIONAL -> "autowire and update $size optional dependencies" + DependencyUpdateMode.UpdateType.UPDATE_ALL -> "autowire and update all $size dependencies" + DependencyUpdateMode.UpdateType.ONLY_AUTOWIRE -> "autowire $size dependencies" + }}", SLog.ANLZE) + forEach { (dependencyName, artifact) -> + val versionFilterExclusionList = artifact.versionFilter?.exclusionList() + ?: SweetDependencyConfigs.document.preferences().versionFilter.exclusionList() + fetchUpdate( + positionTagName = "${++currentIndex}/$size", + dependencyName = dependencyName, + currentVersion = artifact.version(), + alternateVersions = artifact.versions().values.toMutableList(), + isAutoUpdate = artifact.isAutoUpdate, + versionFilterExclusionList = versionFilterExclusionList, + repositories = artifact.repositories() + ) { newVersion -> + DependencyAutowireLogHelper.record(dependencyName, artifact.version(), newVersion) + artifact.updateVersion(newVersion) + needUpdateDependencies[dependencyName.current] = dependencyName to newVersion + when (dependencyName.type) { + DependencyName.Type.PLUGIN -> updPluginsCount++ + DependencyName.Type.LIBRARY -> updLbrariesCount++ + } + } + } + } + /** 找出所有版本引用依赖 - 为其设置版本 */ + needUpdateDependencies.forEach { (notation, dependencyData) -> + val alias = findByType { key, _ -> dependencyData.first == key }.single()?.value?.alias ?: "" + findByType { _, artifact -> + artifact.versionRef.isNotBlank() && (artifact.versionRef == notation || artifact.versionRef == alias) + }.forEach { (dependencyName, artifact) -> + artifact.updateVersion(dependencyData.second) + SLog.info("Link ${dependencyName.description} to ${dependencyData.first.description} version ${dependencyData.second}", SLog.LINK) + } + } + if (needUpdateDependencies.isNotEmpty()) { + SLog.info( + (if (isOnlyAutowireMode) "Autowired" else "Autowired and updated") + + " ${needUpdateDependencies.size} dependencies (plugins: $updPluginsCount, libraries: $updLbrariesCount)", SLog.DONE + ) + if (SweetDependencyConfigs.configs.isEnableDependenciesAutowireLog) + logIfNeeded(msg = "Autowiring logs have been automatically written to: ${DependencyAutowireLogHelper.logFile}", SLog.LINK) + SweetDependencyConfigs.documentMapping.updateDependencies(needUpdateDependencies) + if (isRunningOnSync.not()) SLog.warn( + """ + **************************** NOTICE **************************** + ${needUpdateDependencies.size} dependencies (plugins: $updPluginsCount, libraries: $updLbrariesCount) has been changed + You must to manually re-run Gradle Sync to apply those changes + **************************** NOTICE **************************** + """.trimIndent(), noTag = true + ) + Dependencies.refreshState(isOutdate = true) + } else logIfNeeded(msg = "No dependencies need to ${if (isOnlyAutowireMode) "autowire" else "autowire and update"}", SLog.DONE) + } + + /** + * 自动装配或更新当前依赖 + * @param positionTagName 当前位置标签名称 + * @param dependencyName 依赖名称 + * @param currentVersion 当前依赖版本 + * @param alternateVersions 备选依赖版本数组 + * @param isAutoUpdate 是否自动更新 + * @param versionFilterExclusionList 版本管理器排除列表 + * @param repositories 使用的存储库数组 + * @param result 回调新版本 + */ + private inline fun fetchUpdate( + positionTagName: String, + dependencyName: DependencyName, + currentVersion: DependencyVersion, + alternateVersions: MutableList, + isAutoUpdate: Boolean, + versionFilterExclusionList: VersionFilterDocument.ExclusionList, + repositories: RepositoryList, + result: (newVersion: DependencyVersion) -> Unit + ) { + val poms = mutableListOf() + val headerInfo = if (GradleHelper.isOfflineMode) "$positionTagName > OFFLINE" else "$positionTagName > NOT-FOUND" + val displayInfo = "${dependencyName.description} ${currentVersion.let{ if (it.isAutowire) "" else "version $it" }}" + (repositories.noEmpty() ?: Repositories.all()).apply { + forEachIndexed { index, entry -> + val currentVersionFilterExclusionList = versionFilterExclusionList.depends(currentVersion) + val availableVersions = mutableListOf() + poms.add(MavenParser.acquire(dependencyName, entry, currentVersion)) + if (index == lastIndex) poms.noEmpty() + ?.sortedByDescending { it.lastUpdated } + ?.let { if (it.all { e -> e.lastUpdated <= 0L }) it.sortedByDescending { e -> e.versions.size } else it } + ?.filter { it.versions.isNotEmpty() } + ?.let { linkedSetOf().apply { it.forEach { entity -> addAll(entity.versions) } }.toMutableList() } + ?.let { availableVersions.addAll(it); currentVersionFilterExclusionList.filter(it) } + ?.also { + if (currentVersionFilterExclusionList.isNotEmpty() && availableVersions.isNotEmpty() && it.isEmpty()) SLog.warn( + """ + ${dependencyName.description} available versions exclusion to nothing by version filter + All available versions have been filtered, if this is wrong, please reconfigure your version filter + You can disable internal version filter like following: + """.trimIndent() + "\n" + when (dependencyName.type) { + DependencyName.Type.PLUGIN -> """ + ${dependencyName.groupId}: + version-filter: + use-internal: false + ... + """.trimIndent() + DependencyName.Type.LIBRARY -> """ + ${dependencyName.groupId}: + ${dependencyName.artifactId}: + version-filter: + use-internal: false + ... + """.trimIndent() + } + "\n" + """ + Available versions: $availableVersions + Version filter: $currentVersionFilterExclusionList + """.trimIndent() + ) + }?.noEmpty()?.also { versions -> + resolveEachUpdate( + positionTagName, dependencyName, versions, currentVersion, + versions.first(), alternateVersions, isAutoUpdate, result + ) + } ?: if (GradleHelper.isOfflineMode) SLog.warn("$headerInfo $displayInfo") else SLog.error("$headerInfo $displayInfo") + } + } + } + + /** + * 自动装配或更新每项依赖 + * @param positionTagName 当前位置标签名称 + * @param dependencyName 依赖名称 + * @param versions 全部可用依赖版本数组 + * @param currentVersion 当前依赖版本 + * @param latestVersion 最新依赖版本 + * @param alternateVersions 备选依赖版本数组 + * @param isAutoUpdate 是否自动更新 + * @param result 回调新版本 + */ + private inline fun resolveEachUpdate( + positionTagName: String, + dependencyName: DependencyName, + versions: MutableList, + currentVersion: DependencyVersion, + latestVersion: DependencyVersion, + alternateVersions: MutableList, + isAutoUpdate: Boolean, + result: (newVersion: DependencyVersion) -> Unit + ) = when { + currentVersion.isAutowire.not() && versions.contains(currentVersion).not() -> + SLog.warn("$positionTagName > MISSING ${dependencyName.description} version $currentVersion, available are $versions") + currentVersion.isAutowire.not() && alternateVersions.isNotEmpty() && alternateVersions.all { versions.contains(it) }.not() -> + SLog.warn("$positionTagName > MISSING ${dependencyName.description} version alias $alternateVersions, available are $versions") + latestVersion != currentVersion -> when { + currentVersion.isAutowire -> { + SLog.info("$positionTagName > AUTOWIRE ${dependencyName.description} version $latestVersion", SLog.WIRE) + result(latestVersion) + } + isAutoUpdate -> { + SLog.info("$positionTagName > AUTO-UPDATE ${dependencyName.description} version $currentVersion -> $latestVersion", SLog.UP) + result(latestVersion) + } + else -> SLog.note("$positionTagName > UPDATE-AVAILABLE ${dependencyName.description} version $currentVersion -> $latestVersion", SLog.ROTATE) + } + else -> SLog.info("$positionTagName > UP-TO-DATE ${dependencyName.description} version $currentVersion", SLog.DONE) + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/GradleTaskManager.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/GradleTaskManager.kt new file mode 100644 index 0000000..8af11bc --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/GradleTaskManager.kt @@ -0,0 +1,120 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/6/29. + */ +@file:Suppress("MemberVisibilityCanBePrivate") + +package com.highcapable.sweetdependency.manager + +import com.highcapable.sweetdependency.SweetDependency +import com.highcapable.sweetdependency.generated.SweetDependencyProperties +import com.highcapable.sweetdependency.gradle.factory.createTask +import com.highcapable.sweetdependency.gradle.helper.GradleHelper +import com.highcapable.sweetdependency.plugin.task.AutowireDependenciesTask +import com.highcapable.sweetdependency.plugin.task.AutowireLibrariesTask +import com.highcapable.sweetdependency.plugin.task.AutowirePluginsTask +import com.highcapable.sweetdependency.plugin.task.CreateDependenciesMigrationTemplateTask +import com.highcapable.sweetdependency.plugin.task.SweetDependencyDebugTask +import com.highcapable.sweetdependency.plugin.task.UpdateAllDependenciesTask +import com.highcapable.sweetdependency.plugin.task.UpdateAllLibrariesTask +import com.highcapable.sweetdependency.plugin.task.UpdateAllPluginsTask +import com.highcapable.sweetdependency.plugin.task.UpdateOptionalDependenciesTask +import com.highcapable.sweetdependency.plugin.task.UpdateOptionalLibrariesTask +import com.highcapable.sweetdependency.plugin.task.UpdateOptionalPluginsTask +import org.gradle.api.Project + +/** + * Gradle Task 管理类 + */ +internal object GradleTaskManager { + + /** Gradle Task 分组名称 */ + internal const val TASK_GROUP_NAME = SweetDependencyProperties.PROJECT_MODULE_NAME + + /** 创建依赖迁移模板 Gradle Task 名称 */ + internal const val CREATE_DEPENDENCIES_MIGRATION_TEMPLATE_TASK_NAME = "createDependenciesMigrationTemplate" + + /** 依赖自动装配、更新 (可选) (插件依赖 + 库依赖) Gradle Task 名称 */ + internal const val UPDATE_OPTIONAL_DEPENDENCIES_TASK_NAME = "updateOptionalDependencies" + + /** 依赖自动装配、更新 (全部) (插件依赖 + 库依赖) Gradle Task 名称 */ + internal const val UPDATE_ALL_DEPENDENCIES_TASK_NAME = "updateAllDependencies" + + /** 依赖自动装配 (插件依赖 + 库依赖) Gradle Task 名称 */ + internal const val AUTOWIRE_DEPENDENCIES_TASK_NAME = "autowireDependencies" + + /** 依赖自动装配、更新 (可选) (插件依赖) Gradle Task 名称 */ + internal const val UPDATE_OPTIONAL_PLUGINS_TASK_NAME = "updateOptionalPlugins" + + /** 依赖自动装配、更新 (全部) (插件依赖) Gradle Task 名称 */ + internal const val UPDATE_ALL_PLUGINS_TASK_NAME = "updateAllPlugins" + + /** 插件依赖自动装配 (插件依赖) Gradle Task 名称 */ + internal const val AUTOWIRE_PLUGINS_TASK_NAME = "autowirePlugins" + + /** 依赖自动装配、更新 (可选) (库依赖) Gradle Task 名称 */ + internal const val UPDATE_OPTIONAL_LIBRARIES_TASK_NAME = "updateOptionalLibraries" + + /** 依赖自动装配、更新 (全部) (库依赖) Gradle Task 名称 */ + internal const val UPDATE_ALL_LIBRARIES_TASK_NAME = "updateAllLibraries" + + /** 依赖自动装配 (库依赖) Gradle Task 名称 */ + internal const val AUTOWIRE_LIBRARIES_TASK_NAME = "autowireLibraries" + + /** 调试 Gradle Task 名称 */ + internal const val SWEET_DEPENDENCY_DEBUG_TASK_NAME = "sweetDependencyDebug" + + /** + * 当前正在运行的是否为 [SweetDependency] 内部 Gradle Task + * @return [Boolean] + */ + internal val isInternalRunningTask + get() = GradleHelper.runningTaskNames.orEmpty().any { + it == CREATE_DEPENDENCIES_MIGRATION_TEMPLATE_TASK_NAME || + it == UPDATE_OPTIONAL_DEPENDENCIES_TASK_NAME || + it == UPDATE_ALL_DEPENDENCIES_TASK_NAME || + it == AUTOWIRE_DEPENDENCIES_TASK_NAME || + it == UPDATE_OPTIONAL_PLUGINS_TASK_NAME || + it == UPDATE_ALL_PLUGINS_TASK_NAME || + it == AUTOWIRE_PLUGINS_TASK_NAME || + it == UPDATE_OPTIONAL_LIBRARIES_TASK_NAME || + it == UPDATE_ALL_LIBRARIES_TASK_NAME || + it == AUTOWIRE_LIBRARIES_TASK_NAME || + it == SWEET_DEPENDENCY_DEBUG_TASK_NAME + } + + /** + * 注册 [SweetDependency] 全部 Gradle Task + * @param rootProject 根项目 + */ + internal fun register(rootProject: Project) { + rootProject.createTask(TASK_GROUP_NAME, CREATE_DEPENDENCIES_MIGRATION_TEMPLATE_TASK_NAME) + rootProject.createTask(TASK_GROUP_NAME, UPDATE_OPTIONAL_DEPENDENCIES_TASK_NAME) + rootProject.createTask(TASK_GROUP_NAME, UPDATE_ALL_DEPENDENCIES_TASK_NAME) + rootProject.createTask(TASK_GROUP_NAME, AUTOWIRE_DEPENDENCIES_TASK_NAME) + rootProject.createTask(TASK_GROUP_NAME, UPDATE_OPTIONAL_PLUGINS_TASK_NAME) + rootProject.createTask(TASK_GROUP_NAME, UPDATE_ALL_PLUGINS_TASK_NAME) + rootProject.createTask(TASK_GROUP_NAME, AUTOWIRE_PLUGINS_TASK_NAME) + rootProject.createTask(TASK_GROUP_NAME, UPDATE_OPTIONAL_LIBRARIES_TASK_NAME) + rootProject.createTask(TASK_GROUP_NAME, UPDATE_ALL_LIBRARIES_TASK_NAME) + rootProject.createTask(TASK_GROUP_NAME, AUTOWIRE_LIBRARIES_TASK_NAME) + rootProject.createTask(TASK_GROUP_NAME, SWEET_DEPENDENCY_DEBUG_TASK_NAME) + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/RepositoryManager.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/RepositoryManager.kt new file mode 100644 index 0000000..4de2125 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/RepositoryManager.kt @@ -0,0 +1,131 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/6/6. + */ +package com.highcapable.sweetdependency.manager + +import com.highcapable.sweetdependency.document.PreferencesDocument +import com.highcapable.sweetdependency.document.RepositoryDocument +import com.highcapable.sweetdependency.exception.SweetDependencyUnresolvedException +import com.highcapable.sweetdependency.manager.const.AdditionalRepositories +import com.highcapable.sweetdependency.manager.content.Repositories +import com.highcapable.sweetdependency.plugin.config.content.SweetDependencyConfigs +import com.highcapable.sweetdependency.utils.debug.SError +import com.highcapable.sweetdependency.utils.debug.SLog +import com.highcapable.sweetdependency.utils.noBlank +import com.highcapable.sweetdependency.utils.noEmpty +import com.highcapable.sweetdependency.utils.toFile +import org.gradle.api.artifacts.dsl.RepositoryHandler +import org.gradle.api.artifacts.repositories.ArtifactRepository +import org.gradle.api.artifacts.repositories.AuthenticationSupported +import org.gradle.api.artifacts.repositories.UrlArtifactRepository +import org.gradle.api.initialization.Settings +import java.net.URI +import org.gradle.api.initialization.resolve.RepositoriesMode as GradleRepositoriesMode + +/** + * 存储库装配管理类 + */ +internal object RepositoryManager { + + /** + * 生成并应用存储库数组 + * @param settings 当前设置 + */ + internal fun generateAndApply(settings: Settings) { + val repositories = SweetDependencyConfigs.document.repositories() + Repositories.generate(repositories) + /** + * 应用存储库数组到 Gradle + * @param isPlugins 当前应用类型是否为插件依赖 + */ + fun RepositoryHandler.apply(isPlugins: Boolean) = repositories.forEach { + if (it.isIncludeScope(isPlugins)) when (it.nodeType) { + RepositoryDocument.RepositoryType.GOOGLE -> google { applyToArtifact(it) } + RepositoryDocument.RepositoryType.MAVEN_CENTRAL -> mavenCentral { applyToArtifact(it) } + RepositoryDocument.RepositoryType.MAVEN_LOCAL -> mavenLocal { applyToArtifact(it) } + RepositoryDocument.RepositoryType.MAVEN -> maven { applyToArtifact(it) } + RepositoryDocument.RepositoryType.GRADLE_PLUGIN_PORTAL -> gradlePluginPortal { applyToArtifact(it) } + else -> {} + } + } + settings.pluginManagement { + this.repositories.clear() + this.repositories.apply(isPlugins = true) + } + settings.dependencyResolutionManagement { + this.repositoriesMode.set(when (SweetDependencyConfigs.document.preferences().repositoriesMode) { + PreferencesDocument.RepositoriesMode.PREFER_PROJECT -> GradleRepositoriesMode.PREFER_PROJECT + PreferencesDocument.RepositoriesMode.PREFER_SETTINGS -> GradleRepositoriesMode.PREFER_SETTINGS + PreferencesDocument.RepositoriesMode.FAIL_ON_PROJECT_REPOS -> GradleRepositoriesMode.FAIL_ON_PROJECT_REPOS + }) + this.repositories.clear() + this.repositories.apply(isPlugins = false) + } + } + + /** + * 应用存储库到 [ArtifactRepository] + * @param document 存储库配置项文档实体 + */ + private fun ArtifactRepository.applyToArtifact(document: RepositoryDocument) { + document.nodeName.noBlank()?.also { docName -> this.name = docName } + if (this is AuthenticationSupported && document.credentials.let { it.username.isNotBlank() || it.password.isNotBlank() }) + credentials { this.username = document.credentials.username; this.password = document.credentials.password } + if (document.content.isEmpty().not()) content { + /** + * 使用 ":" 分割字符串 + * @param size 期望的个数 + * @param result 回调每项 + * @return [List]<[String]> + * @throws SweetDependencyUnresolvedException 如果 [size] 不是期望的个数 + */ + fun List.forEachParams(size: Int, result: (List) -> Unit) = forEach { + result(it.split(":").also { e -> if (e.size != size) SError.make("Missing argument in content configuration") }) + } + document.content.exclude.also { + it.group().noEmpty()?.forEach { e -> excludeGroup(e) } + it.groupAndSubgroups().noEmpty()?.forEach { e -> excludeGroupAndSubgroups(e) } + it.groupByRegex().noEmpty()?.forEach { e -> excludeGroupByRegex(e) } + it.module().noEmpty()?.forEachParams(size = 2) { e -> excludeModule(e[0], e[1]) } + it.moduleByRegex().noEmpty()?.forEachParams(size = 2) { e -> excludeModuleByRegex(e[0], e[1]) } + it.version().noEmpty()?.forEachParams(size = 3) { e -> excludeVersion(e[0], e[1], e[2]) } + it.versionByRegex().noEmpty()?.forEachParams(size = 3) { e -> excludeVersionByRegex(e[0], e[1], e[2]) } + } + document.content.include.also { + it.group().noEmpty()?.forEach { e -> includeGroup(e) } + it.groupAndSubgroups().noEmpty()?.forEach { e -> includeGroupAndSubgroups(e) } + it.groupByRegex().noEmpty()?.forEach { e -> includeGroupByRegex(e) } + it.module().noEmpty()?.forEachParams(size = 2) { e -> includeModule(e[0], e[1]) } + it.moduleByRegex().noEmpty()?.forEachParams(size = 2) { e -> includeModuleByRegex(e[0], e[1]) } + it.version().noEmpty()?.forEachParams(size = 3) { e -> includeVersion(e[0], e[1], e[2]) } + it.versionByRegex().noEmpty()?.forEachParams(size = 3) { e -> includeVersionByRegex(e[0], e[1], e[2]) } + } + } + if (document.nodeType != RepositoryDocument.RepositoryType.MAVEN_LOCAL) + (document.url.noBlank()?.let { + /** JCenter 已经终止服务 - 不确定其镜像源是否也会同时关闭 */ + if (it == AdditionalRepositories.ALIYUN_JCENTER_MIRROR) + SLog.warn("JCenter has shut down, and its mirror server may stop soon, please transfer to other repositories") + URI.create(it) + } ?: document.path.noBlank()?.toFile()?.toURI()) + ?.also { uri -> if (this is UrlArtifactRepository) this.url = uri } + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/const/AdditionalRepositories.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/const/AdditionalRepositories.kt new file mode 100644 index 0000000..7d04de6 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/const/AdditionalRepositories.kt @@ -0,0 +1,82 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/5/16. + */ +package com.highcapable.sweetdependency.manager.const + +/** + * 附加常用第三方存储库 + */ +internal object AdditionalRepositories { + + /** 中央存储库 (分流) */ + const val MAVEN_CENTRAL_BRANCH = "https://repo1.maven.org/maven2" + + /** JitPack */ + const val JITPACK = "https://www.jitpack.io" + + /** OSS Release */ + const val SONATYPE_OSS_RELEASES = "https://s01.oss.sonatype.org/content/repositories/releases" + + /** OSS Snapshots */ + const val SONATYPE_OSS_SNAPSHOTS = "https://s01.oss.sonatype.org/content/repositories/snapshots" + + /** 阿里云 Google 存储库镜像 */ + const val ALIYUN_GOOGLE_MIRROR = "https://maven.aliyun.com/repository/google" + + /** 阿里云中央存储库镜像 */ + const val ALIYUN_MAVEN_CENTRAL_MIRROR = "https://maven.aliyun.com/repository/central" + + /** 阿里云公共存储库镜像 */ + const val ALIYUN_MAVEN_PUBLIC_MIRROR = "https://maven.aliyun.com/repository/public" + + /** 阿里云 JCenter 镜像 */ + const val ALIYUN_JCENTER_MIRROR = "https://maven.aliyun.com/nexus/content/repositories/jcenter" + + /** + * 存储库简洁名称定义类 + */ + internal object Name { + + /** 中央存储库 (分流) */ + const val MAVEN_CENTRAL_BRANCH = "maven-central-branch" + + /** JitPack */ + const val JITPACK = "jit-pack" + + /** OSS Release */ + const val SONATYPE_OSS_RELEASES = "sonatype-oss-releases" + + /** OSS Snapshots */ + const val SONATYPE_OSS_SNAPSHOTS = "sonatype-oss-snapshots" + + /** 阿里云 Google 存储库镜像 */ + const val ALIYUN_GOOGLE_MIRROR = "aliyun-google-mirror" + + /** 阿里云中央存储库镜像 */ + const val ALIYUN_MAVEN_CENTRAL_MIRROR = "aliyun-maven-central-mirror" + + /** 阿里云公共存储库镜像 */ + const val ALIYUN_MAVEN_PUBLIC_MIRROR = "aliyun-maven-public-mirror" + + /** 阿里云 JCenter 镜像 */ + const val ALIYUN_JCENTER_MIRROR = "aliyun-jcenter-mirror" + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/const/InternalRepositories.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/const/InternalRepositories.kt new file mode 100644 index 0000000..37f501b --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/const/InternalRepositories.kt @@ -0,0 +1,64 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/5/18. + */ +package com.highcapable.sweetdependency.manager.const + +/** + * 内置存储库 + */ +internal object InternalRepositories { + + /** 本地 Maven 存储库相对路径 */ + const val MAVEN_LOCAL_RELATIVE_PATH = ".m2/repository" + + /** Google Maven */ + const val GOOGLE = "https://dl.google.com/dl/android/maven2" + + /** 中央存储库 */ + const val MAVEN_CENTRAL = "https://repo.maven.apache.org/maven2" + + /** Gradle Plugin 存储库 */ + const val GRADLE_PLUGIN_PORTAL = "https://plugins.gradle.org/m2" + + /** + * 存储库简洁名称定义类 + */ + internal object Name { + + /** Google Maven */ + const val GOOGLE = "google" + + /** 中央存储库 */ + const val MAVEN_CENTRAL = "maven-central" + + /** 本地 Maven 存储库 */ + const val MAVEN_LOCAL = "maven-local" + + /** Maven 存储库 */ + const val MAVEN = "maven" + + /** Ivy 存储库 */ + const val IVY = "ivy" + + /** Gradle Plugin 存储库 */ + const val GRADLE_PLUGIN_PORTAL = "gradle-plugin-portal" + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/content/Dependencies.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/content/Dependencies.kt new file mode 100644 index 0000000..d4576c8 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/content/Dependencies.kt @@ -0,0 +1,150 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/6/25. + */ +@file:Suppress("unused", "MemberVisibilityCanBePrivate") + +package com.highcapable.sweetdependency.manager.content + +import com.highcapable.sweetdependency.document.DependencyDocument +import com.highcapable.sweetdependency.document.factory.DependenciesCondition +import com.highcapable.sweetdependency.document.factory.DependencyMap +import com.highcapable.sweetdependency.gradle.entity.DependencyName +import com.highcapable.sweetdependency.utils.filter + +/** + * 已添加的依赖管理类 + */ +internal object Dependencies { + + /** 当前已添加的全部插件依赖数组 */ + private val pluginEntries = mutableMapOf() + + /** 当前已添加的全部库依赖数组 */ + private val libraryEntries = mutableMapOf() + + /** 标识当前是否为已过期状态 */ + private var isMarkedOutdate = true + + /** + * 获取当前过期状态 + * @return [Boolean] + */ + internal val isOutdate get() = isMarkedOutdate + + /** + * 刷新当前缓存数据状态 + * @param isOutdate 是否标识为已过期 + */ + internal fun refreshState(isOutdate: Boolean) { + isMarkedOutdate = isOutdate + } + + /** + * 获取当前全部数组 + * @return [DependencyMap] + */ + internal fun all() = (pluginEntries + libraryEntries).toMutableMap() + + /** + * 获取当前插件依赖数组 + * @return [DependencyMap] + */ + internal fun plugins() = pluginEntries + + /** + * 获取当前库依赖数组 + * @return [DependencyMap] + */ + internal fun libraries() = libraryEntries + + /** + * 当前是否存在依赖 + * @return [Boolean] + */ + internal fun isEmpty() = all().isEmpty() + + /** + * 当前是否不存在依赖 + * @return [Boolean] + */ + internal fun isNotEmpty() = isEmpty().not() + + /** + * 查找是否存在指定的依赖 + * @param condition 条件方法体 + * @return [Boolean] + */ + internal inline fun hasAll(condition: DependenciesCondition) = findAll { key, value -> condition(key, value) }.isNotEmpty() + + /** + * 查找是否存在指定的插件依赖 + * @param condition 条件方法体 + * @return [Boolean] + */ + internal inline fun hasPlugin(condition: DependenciesCondition) = findPlugins { key, value -> condition(key, value) }.isNotEmpty() + + /** + * 查找是否存在指定的库依赖 + * @param condition 条件方法体 + * @return [Boolean] + */ + internal inline fun hasLibrary(condition: DependenciesCondition) = findLibraries { key, value -> condition(key, value) }.isNotEmpty() + + /** + * 查找指定条件的依赖数组 + * @param condition 条件方法体 + * @return [DependencyMap] + */ + internal inline fun findAll(condition: DependenciesCondition) = all().filter { condition(it.key, it.value) } + + /** + * 查找指定条件的插件依赖数组 + * @param condition 条件方法体 + * @return [DependencyMap] + */ + internal inline fun findPlugins(condition: DependenciesCondition) = plugins().filter { condition(it.key, it.value) } + + /** + * 查找指定条件的库依赖数组 + * @param condition 条件方法体 + * @return [DependencyMap] + */ + internal inline fun findLibraries(condition: DependenciesCondition) = libraries().filter { condition(it.key, it.value) } + + /** + * 生成依赖数组 + * @param plugins 插件依赖数组 + * @param libraries 依赖数组 + */ + internal fun generate(plugins: DependencyMap, libraries: DependencyMap) { + if (plugins == plugins() && libraries == libraries()) return refreshState(isOutdate = false) + resetData() + plugins().putAll(plugins) + libraries().putAll(libraries) + } + + /** 重置 (清空) 当前依赖数组 */ + private fun resetData() { + pluginEntries.clear() + libraryEntries.clear() + refreshState(isOutdate = true) + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/content/Repositories.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/content/Repositories.kt new file mode 100644 index 0000000..4e53c1f --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/content/Repositories.kt @@ -0,0 +1,93 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/5/16. + */ +package com.highcapable.sweetdependency.manager.content + +import com.highcapable.sweetdependency.document.RepositoryDocument +import com.highcapable.sweetdependency.document.factory.RepositoryList +import com.highcapable.sweetdependency.manager.const.AdditionalRepositories +import com.highcapable.sweetdependency.manager.const.InternalRepositories +import com.highcapable.sweetdependency.utils.parseFileSeparator + +/** + * 已添加的存储库管理类 + */ +internal object Repositories { + + /** 默认本地 Maven 存储库路径 */ + internal val defaultMavenLocalPath by lazy { + "${System.getProperty("user.home")}/${InternalRepositories.MAVEN_LOCAL_RELATIVE_PATH}".parseFileSeparator() + } + + /** 默认版本过滤器排除列表数组 */ + internal val defaultVersionFilterExclusionList = arrayOf("-beta", "-alpha", "-dev", "-canary", "-pre", "-rc", "-ga", "-snapshot") + + /** 当前已添加的全部存储库数组 */ + private val entries = mutableListOf() + + /** + * 获取当前存储库数组 + * @return [MutableList]<[RepositoryDocument]> + */ + internal fun all() = entries + + /** + * 当前是否存在存储库 + * @return [Boolean] + */ + internal fun isEmpty() = all().isEmpty() + + /** + * 当前是否不存在存储库 + * @return [Boolean] + */ + internal fun isNotEmpty() = isEmpty().not() + + /** + * 生成存储库数组 + * @param repositories 存储库数组 + */ + internal fun generate(repositories: RepositoryList) { + if (repositories == all()) return + resetData() + all().addAll(repositories) + } + + /** + * 查找可用的存储库名 URL 地址 + * @param name 存储库名 + * @return [String] + */ + internal fun findAdditional(name: String) = when (name) { + AdditionalRepositories.Name.MAVEN_CENTRAL_BRANCH -> AdditionalRepositories.MAVEN_CENTRAL_BRANCH + AdditionalRepositories.Name.JITPACK -> AdditionalRepositories.JITPACK + AdditionalRepositories.Name.SONATYPE_OSS_RELEASES -> AdditionalRepositories.SONATYPE_OSS_RELEASES + AdditionalRepositories.Name.SONATYPE_OSS_SNAPSHOTS -> AdditionalRepositories.SONATYPE_OSS_SNAPSHOTS + AdditionalRepositories.Name.ALIYUN_GOOGLE_MIRROR -> AdditionalRepositories.ALIYUN_GOOGLE_MIRROR + AdditionalRepositories.Name.ALIYUN_MAVEN_CENTRAL_MIRROR -> AdditionalRepositories.ALIYUN_MAVEN_CENTRAL_MIRROR + AdditionalRepositories.Name.ALIYUN_MAVEN_PUBLIC_MIRROR -> AdditionalRepositories.ALIYUN_MAVEN_PUBLIC_MIRROR + AdditionalRepositories.Name.ALIYUN_JCENTER_MIRROR -> AdditionalRepositories.ALIYUN_JCENTER_MIRROR + else -> "" + } + + /** 重置 (清空) 当前存储库数组 */ + private fun resetData() = entries.clear() +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/helper/DependencyAutowireLogHelper.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/helper/DependencyAutowireLogHelper.kt new file mode 100644 index 0000000..03435e3 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/helper/DependencyAutowireLogHelper.kt @@ -0,0 +1,61 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/5/21. + */ +package com.highcapable.sweetdependency.manager.helper + +import com.highcapable.sweetdependency.environment.Environment +import com.highcapable.sweetdependency.gradle.entity.DependencyName +import com.highcapable.sweetdependency.gradle.entity.DependencyVersion +import com.highcapable.sweetdependency.plugin.config.content.SweetDependencyConfigs +import com.highcapable.sweetdependency.utils.debug.SLog +import java.text.SimpleDateFormat +import java.util.* + +/** + * 依赖自动装配日志工具类 + */ +internal object DependencyAutowireLogHelper { + + /** 当前日志文件名 */ + private const val LOG_FILE_NAME = "dependencies-autowire.log" + + /** + * 当前日志文件 + * @return [String] + */ + internal val logFile get() = Environment.memoryDir(LOG_FILE_NAME) + + /** + * 记录当前依赖改变 + * @param dependencyName 依赖名称 + * @param fromVersion 起始版本 + * @param toVersion 最终版本 + */ + internal fun record(dependencyName: DependencyName, fromVersion: DependencyVersion, toVersion: DependencyVersion) { + if (SweetDependencyConfigs.configs.isEnableDependenciesAutowireLog.not()) return + val versionInfo = if (fromVersion.isAutowire) + "autowire version \"$toVersion\"" + else "update version \"$fromVersion\" -> \"$toVersion\"" + logFile.runCatching { + appendText("[${SimpleDateFormat.getDateTimeInstance().format(Date())}] ${dependencyName.description} $versionInfo\n") + }.onFailure { SLog.error("Failed to written log file \"$logFile\"\n$it") } + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/helper/DependencyDeployHelper.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/helper/DependencyDeployHelper.kt new file mode 100644 index 0000000..803bb4d --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/helper/DependencyDeployHelper.kt @@ -0,0 +1,190 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/6/13. + */ +package com.highcapable.sweetdependency.manager.helper + +import com.highcapable.sweetdependency.SweetDependency +import com.highcapable.sweetdependency.environment.Environment +import com.highcapable.sweetdependency.generated.SweetDependencyProperties +import com.highcapable.sweetdependency.gradle.delegate.ProjectTransaction +import com.highcapable.sweetdependency.gradle.delegate.entity.ExternalDependencyDelegate +import com.highcapable.sweetdependency.gradle.factory.addDependencyToBuildScript +import com.highcapable.sweetdependency.gradle.factory.applyPlugin +import com.highcapable.sweetdependency.gradle.factory.getOrCreate +import com.highcapable.sweetdependency.gradle.factory.loadBuildScriptClass +import com.highcapable.sweetdependency.manager.GradleTaskManager +import com.highcapable.sweetdependency.manager.content.Dependencies +import com.highcapable.sweetdependency.plugin.config.content.SweetDependencyConfigs +import com.highcapable.sweetdependency.plugin.generator.LibrariesAccessorsGenerator +import com.highcapable.sweetdependency.utils.camelcase +import com.highcapable.sweetdependency.utils.code.entity.MavenPomData +import com.highcapable.sweetdependency.utils.code.factory.compile +import com.highcapable.sweetdependency.utils.debug.SError +import com.highcapable.sweetdependency.utils.debug.SLog +import com.highcapable.sweetdependency.utils.isEmpty +import com.highcapable.sweetdependency.utils.isValidZip +import com.highcapable.sweetdependency.utils.single +import com.highcapable.sweetdependency.utils.toAbsoluteFilePaths +import com.highcapable.sweetdependency.utils.toFile +import org.gradle.api.InvalidUserDataException +import org.gradle.api.Project +import org.gradle.api.initialization.Settings +import org.gradle.api.plugins.ExtensionAware +import org.gradle.plugin.use.PluginDependenciesSpec +import org.gradle.plugin.use.PluginDependencySpec + +/** + * 依赖部署工具类 + */ +internal object DependencyDeployHelper { + + /** 库依赖可访问 [Class] 标识名称 */ + private const val ACCESSORS_NAME = "dependencies-accessors" + + /** 库依赖可访问 [Class] 生成目录 */ + private val accessorsDir = Environment.memoryDir(ACCESSORS_NAME) + + /** 库依赖可访问 [Class] 虚拟依赖数据 */ + private val accessorsPomData = MavenPomData(SweetDependencyProperties.PROJECT_GROUP_NAME, ACCESSORS_NAME, SweetDependency.VERSION) + + /** 库依赖可访问 [Class] 生成实例 */ + private val accessorsGenerator = LibrariesAccessorsGenerator() + + /** + * 生成 Version Catalogs + * + * 由于 Gradle API 限制 - 无法针对插件依赖进行自定义 - 所以间接使用 Version Catalogs 生成 + * @param settings 当前设置 + */ + internal fun generateVersionCatalogs(settings: Settings) { + val pluginsNamespace = SweetDependencyConfigs.document.preferences().dependenciesNamespace.plugins() + runCatching { + settings.dependencyResolutionManagement.versionCatalogs.create(pluginsNamespace) { + Dependencies.plugins().forEach { (dependencyName, artifact) -> + if (artifact.version().isNoSpecific) return@forEach SLog.warn( + """ + You must specific a version for plugin "$dependencyName" or use Gradle's internal plugin function instead it + This problem came from the version catalogs rules, so will not generate "$dependencyName" + You can also use "autowire("$dependencyName")" to solve this problem + You will see this warning every time, because we don't recommend declaring plugins without version + """.trimIndent() + ) + if (artifact.version().isAutowire) SError.make( + """ + This plugin "$dependencyName" is not autowired and cannot be generate + You can try the following solutions to resolve this problem: + 1. Manually re-run Gradle Sync (make sure "autowire-on-sync-mode" not be "OFF") + 2. Manually run "${GradleTaskManager.AUTOWIRE_PLUGINS_TASK_NAME}" task and re-run Gradle Sync + 3. Fill an existing version for plugin "$dependencyName" and re-run Gradle Sync + If you get this error again after doing the above, maybe the currently set repositories cannot find this plugin + """.trimIndent() + ) + val deployedName = dependencyName.ambiguousName(symbol = "-", isReplaceFirstChar = true, isLowerCase = false) + plugin(deployedName, dependencyName.current).version(artifact.version().deployed) + artifact.versions().forEach { (name, version) -> + plugin("$deployedName-${name.camelcase()}", dependencyName.current).version(version.deployed) + if (artifact.alias.isNotBlank()) + plugin("${artifact.alias}-${name.camelcase()}", dependencyName.current).version(version.deployed) + } + if (artifact.alias.isNotBlank()) plugin(artifact.alias, dependencyName.current).version(artifact.version().deployed) + } + } + }.onFailure { + when (it) { + is InvalidUserDataException -> SError.make("Illegal name called in Gradle version catalogs", it) + else -> throw it + } + } + } + + /** + * 处理库依赖可访问 [Class] 装载 + * @param rootProject 当前根项目 + */ + internal fun resolveAccessors(rootProject: Project) { + if (Dependencies.isOutdate || accessorsDir.isEmpty()) + accessorsGenerator.build().compile(accessorsPomData, accessorsDir.absolutePath, accessorsGenerator.compileStubFiles) + rootProject.addDependencyToBuildScript(accessorsDir.absolutePath, accessorsPomData) + } + + /** + * 部署库依赖可访问 [Class] + * @param project 当前项目 + * @param extension 当前扩展实例 + */ + internal fun deployAccessors(project: Project, extension: ExtensionAware) = + accessorsGenerator.librariesClasses.forEach { (name, className) -> + extension.getOrCreate(name, project.loadBuildScriptClass(className)) + } + + /** + * 处理自动装配的插件依赖 + * @param spec 当前插件依赖声明对象 + * @param params 当前参数数组 + * @return [PluginDependencySpec] + */ + internal fun resolveAutowire(spec: PluginDependenciesSpec, params: Array): PluginDependencySpec { + if (params.isEmpty()) SError.make("The autowire function need a param to resolve plugin") + if (params.size > 2) SError.make("The autowire function currently does not support more than 2 params of plugin") + return when (params[0]) { + is String -> { + val entry = Dependencies.findPlugins { key, value -> params[0] == key.current || params[0] == value.alias }.single() + ?: SError.make("Failed to resolve plugin \"${params[0]}\", also tried alias") + val version = if (params.size == 2) + entry.value.versions()[params[1]] ?: SError.make("Failed to resolve plugin \"${params[0]}\" with version alias \"${params[1]}\"") + else entry.value.version() + spec.applyPlugin(entry.key.current, version.deployed) + } + else -> spec.applyPlugin(params[0]) + } + } + + /** + * 处理自动装配的依赖 + * @param project 当前项目 - 默认为 [ProjectTransaction.current] + * @param params 当前参数数组 + * @return [Any] + */ + internal fun resolveAutowire(project: Project = ProjectTransaction.current, params: Array): Any { + if (params.isEmpty()) SError.make("The autowire function need a param to resolve library") + return if (params[0].let { it.contains("/").not() && it.contains("\\").not() && it.startsWith("(").not() && it.endsWith(")").not() }) { + if (params.size > 2) SError.make("The autowire function currently does not support more than 2 params of external dependency") + val entry = Dependencies.findLibraries { key, value -> params[0] == key.current || params[0] == value.alias }.single() + ?: SError.make("Failed to resolve library \"${params[0]}\", also tried alias") + val version = if (params.size == 2) + entry.value.versions()[params[1]] ?: SError.make("Failed to resolve library \"${params[0]}\" with version alias \"${params[1]}\"") + else entry.value.version() + ExternalDependencyDelegate(entry.key.groupId, entry.key.artifactId, version.deployed) + } else mutableListOf().let { + params.forEach { param -> + val relativePath = if (param.startsWith("(") && param.endsWith(")")) param.replace("(", "").replace(")", "") else param + it.addAll(relativePath.toAbsoluteFilePaths(project.projectDir.absolutePath).onEach { path -> + if (path.toFile().isValidZip().not()) SError.make( + """ + Invalid library at file path $path + The file collection dependency needs to be a valid zip package + """.trimIndent() + ) + }) + }; project.files(it.toTypedArray()) + } + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/maven/MavenParser.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/maven/MavenParser.kt new file mode 100644 index 0000000..2b797fa --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/maven/MavenParser.kt @@ -0,0 +1,82 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/6/6. + */ +package com.highcapable.sweetdependency.manager.maven + +import com.highcapable.sweetdependency.document.RepositoryDocument +import com.highcapable.sweetdependency.gradle.entity.DependencyName +import com.highcapable.sweetdependency.gradle.entity.DependencyVersion +import com.highcapable.sweetdependency.gradle.helper.GradleHelper +import com.highcapable.sweetdependency.manager.maven.entity.MavenMetadata +import com.highcapable.sweetdependency.utils.debug.SError +import com.highcapable.sweetdependency.utils.executeFileBody +import com.highcapable.sweetdependency.utils.executeUrlBody +import com.highcapable.sweetdependency.utils.noEmpty +import org.xml.sax.InputSource +import java.io.StringReader +import javax.xml.parsers.DocumentBuilderFactory + +/** + * Maven 解析器工具类 + */ +internal object MavenParser { + + /** 依赖配置文件名 */ + private const val METADATA_FILE_NAME = "maven-metadata.xml" + + /** 依赖配置文件名 (本地) */ + private const val METADATA_LOCAL_FILE_NAME = "maven-metadata-local.xml" + + /** + * 通过依赖全称使用指定存储库得到 [MavenMetadata] 实体 + * @param dependencyName 依赖名称 + * @param repo 当前存储库实体 + * @param currentVersion 当前依赖版本 + * @return [MavenMetadata] + */ + internal fun acquire(dependencyName: DependencyName, repo: RepositoryDocument, currentVersion: DependencyVersion): MavenMetadata { + val headerUrlOrPath = "${repo.url.ifBlank { repo.path }}/${dependencyName.urlName}/" + val isIncludeScope = repo.isIncludeScope(dependencyName.type == DependencyName.Type.PLUGIN) + /** 离线模式下不会自动装配、更新在线依赖 */ + if (isIncludeScope && GradleHelper.isOfflineMode) return MavenMetadata() + return when { + repo.url.isNotBlank() -> "$headerUrlOrPath$METADATA_FILE_NAME".executeUrlBody(repo.credentials.username, repo.credentials.password) + repo.path.isNotBlank() -> "$headerUrlOrPath$METADATA_LOCAL_FILE_NAME".executeFileBody() + else -> SError.make("Could not resolve this repository \"${repo.nodeName}\"") + }.trim().toMetadata(currentVersion) + } + + /** + * 解析 [METADATA_FILE_NAME]、[METADATA_LOCAL_FILE_NAME] 内容到 [MavenMetadata] 实体 + * @param currentVersion 当前依赖版本 + * @return [MavenMetadata] + */ + private fun String.toMetadata(currentVersion: DependencyVersion) = runCatching { + if ((contains("")).not() || endsWith("").not()) return@runCatching MavenMetadata() + DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(InputSource(StringReader(this))).let { document -> + val lastUpdated = document.getElementsByTagName("lastUpdated").item(0)?.textContent?.toLongOrNull() ?: 0L + val versionNodeList = document.getElementsByTagName("version") + val versions = mutableListOf() + for (i in 0..versionNodeList.length) versionNodeList.item(i)?.textContent?.also { versions.add(currentVersion.clone(it)) } + MavenMetadata(versions.noEmpty()?.reversed()?.toMutableList() ?: mutableListOf(), lastUpdated) + } + }.getOrNull() ?: MavenMetadata() +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/maven/entity/MavenMetadata.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/maven/entity/MavenMetadata.kt new file mode 100644 index 0000000..4ebb496 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/maven/entity/MavenMetadata.kt @@ -0,0 +1,34 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/6/6. + */ +package com.highcapable.sweetdependency.manager.maven.entity + +import com.highcapable.sweetdependency.gradle.entity.DependencyVersion + +/** + * Maven Metadata 实体 + * @param versions 版本数组 + * @param lastUpdated 最后更新时间戳 + */ +internal data class MavenMetadata( + internal var versions: MutableList = mutableListOf(), + internal var lastUpdated: Long = 0L +) \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/transaction/DependencyMigrationTemplateTransaction.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/transaction/DependencyMigrationTemplateTransaction.kt new file mode 100644 index 0000000..08334d1 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/transaction/DependencyMigrationTemplateTransaction.kt @@ -0,0 +1,110 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/6/1. + */ +package com.highcapable.sweetdependency.manager.transaction + +import com.highcapable.sweetdependency.SweetDependency +import com.highcapable.sweetdependency.document.DependencyDocument +import com.highcapable.sweetdependency.document.RootConfigDocument +import com.highcapable.sweetdependency.gradle.entity.DependencyVersion +import com.highcapable.sweetdependency.gradle.factory.fullName +import com.highcapable.sweetdependency.gradle.factory.libraries +import com.highcapable.sweetdependency.gradle.factory.plugins +import com.highcapable.sweetdependency.gradle.helper.GradleHelper +import com.highcapable.sweetdependency.gradle.wrapper.type.LibraryDependencyType +import com.highcapable.sweetdependency.manager.GradleTaskManager +import com.highcapable.sweetdependency.manager.content.Dependencies +import com.highcapable.sweetdependency.plugin.config.content.SweetDependencyConfigs +import com.highcapable.sweetdependency.utils.debug.SLog +import com.highcapable.sweetdependency.utils.parseFileSeparator +import com.highcapable.sweetdependency.utils.toFile +import com.highcapable.sweetdependency.utils.yaml.Yaml + +/** + * 依赖迁移模版管理类 + */ +internal object DependencyMigrationTemplateTransaction { + + /** 模板文件头部内容 */ + private const val TEMPLATE_FILE_HEADER_CONTENT = """ + # SweetDependency project configuration template file + # Template files are automatically generated using Gradle task "${GradleTaskManager.CREATE_DEPENDENCIES_MIGRATION_TEMPLATE_TASK_NAME}" + # The automatically generated configuration is determined according to your project + # Please adjust these contents at any time in actual use, the generated content is for reference only, and its availability is unknown + # You can copy the content of the corresponding node in the template file to the project configuration file, and then delete this file + # You can visit ${SweetDependency.PROJECT_URL} for more help + # + # SweetDependency 项目配置模板文件 + # 模版文件是使用 Gradle Task "${GradleTaskManager.CREATE_DEPENDENCIES_MIGRATION_TEMPLATE_TASK_NAME}" 自动生成的 + # 自动生成的配置根据你的项目决定,请在实际使用中随时调整这些内容,生成的内容仅供参考,其可用性未知 + # 你可以复制模板文件中对应节点的内容到项目配置文件,然后删除此文件 + # 你可以前往 ${SweetDependency.PROJECT_URL} 以获得更多帮助 + """ + + /** 模板文件扩展名 */ + private const val TEMPLATE_FILE_EXT_NAME = "template.yaml" + + /** 模板文件头部内容 */ + private val templateFileHeaderContent = TEMPLATE_FILE_HEADER_CONTENT.trimIndent() + + /** 排除的部分内置插件名称前缀数组 */ + private val exclusionPluginPrefixs = arrayOf("org.gradle", "com.android.internal") + + /** 生成模板使用的文档实例 */ + private val document = RootConfigDocument() + + /** 创建模版 */ + internal fun createTemplate() { + SLog.info("Starting analyze projects dependencies structure", SLog.ANLZE) + GradleHelper.allProjects.forEach { subProject -> + subProject.plugins().onEach { + if (exclusionPluginPrefixs.any { prefix -> it.id.startsWith(prefix) }) return@onEach + if (Dependencies.hasPlugin { key, _ -> key.current == it.id }) return@onEach + if (document.plugins == null) document.plugins = mutableMapOf() + val declareDocument = DependencyDocument(version = DependencyVersion.AUTOWIRE_VERSION_NAME) + document.plugins?.set(it.id, declareDocument) + }.apply { if (isNotEmpty()) SLog.info("Found $size plugins in project \"${subProject.fullName}\"", SLog.LINK) } + subProject.libraries().onEach { + if (Dependencies.hasLibrary { key, _ -> key.current == it.toString() }) return@onEach + if (document.libraries == null) document.libraries = mutableMapOf() + if (it.type == LibraryDependencyType.EXTERNAL) document.libraries?.also { entities -> + if (entities[it.groupId] == null) entities[it.groupId] = mutableMapOf() + val declareDocument = DependencyDocument(version = it.version.existed) + entities[it.groupId]?.set(it.artifactId, declareDocument) + } + }.apply { if (isNotEmpty()) SLog.info("Found $size libraries in project \"${subProject.fullName}\"", SLog.LINK) } + }; saveTemplateFile() + } + + /** 保存模版到文件 */ + private fun saveTemplateFile() { + if (document.plugins?.isEmpty() == true) document.plugins = null + if (document.libraries?.isEmpty() == true) document.libraries = null + if (document.plugins?.isNotEmpty() == true || document.libraries?.isNotEmpty() == true) { + val templateFilePath = SweetDependencyConfigs.configs.configFilePath + .let { it.toFile().let { e -> "${e.parent}/${e.name.split(".")[0]}.$TEMPLATE_FILE_EXT_NAME" } }.parseFileSeparator() + Yaml.parseToFile(document, templateFilePath) { "$templateFileHeaderContent\n\n${replace("\"", "")}" } + SLog.info("Template file is created at $templateFilePath", SLog.DONE) + document.plugins?.clear() + document.libraries?.clear() + } else SLog.info("No suitable dependencies can be found in all projects to create template file, nothing to do", SLog.IGNORE) + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/transaction/RuntimeDebugTransaction.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/transaction/RuntimeDebugTransaction.kt new file mode 100644 index 0000000..c6b701e --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/manager/transaction/RuntimeDebugTransaction.kt @@ -0,0 +1,261 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/6/8. + */ +package com.highcapable.sweetdependency.manager.transaction + +import com.highcapable.sweetdependency.SweetDependency +import com.highcapable.sweetdependency.document.factory.DependencyMap +import com.highcapable.sweetdependency.environment.Environment +import com.highcapable.sweetdependency.gradle.factory.fullName +import com.highcapable.sweetdependency.gradle.factory.libraries +import com.highcapable.sweetdependency.gradle.factory.plugins +import com.highcapable.sweetdependency.gradle.helper.GradleHelper +import com.highcapable.sweetdependency.gradle.wrapper.type.LibraryDependencyType +import com.highcapable.sweetdependency.plugin.config.content.SweetDependencyConfigs +import com.highcapable.sweetdependency.utils.debug.SLog +import com.highcapable.sweetdependency.utils.noBlank +import com.highcapable.sweetdependency.utils.noEmpty + +/** + * 运行时调试管理类 + */ +internal object RuntimeDebugTransaction { + + private val configs get() = SweetDependencyConfigs.configs + private val preferences get() = SweetDependencyConfigs.document.preferences() + private val repositories get() = SweetDependencyConfigs.document.repositories() + private val plugins get() = SweetDependencyConfigs.document.plugins(duplicate = true) + private val libraries get() = SweetDependencyConfigs.document.libraries(duplicate = true) + private val vfExclusionList get() = preferences.versionFilter.exclusionList().all() + + /** 存储库内存数组 */ + private val repositoriesMap = mutableMapOf() + + /** 插件依赖内存数组 */ + private val pluginsMap = mutableMapOf() + + /** 依赖内存数组 */ + private val librariesMap = mutableMapOf() + + /** 版本过滤器内存数组 */ + private val versionFilterMap = mutableMapOf() + + /** 项目插件依赖内存数组 */ + private val projectPluginsMap = mutableMapOf() + + /** 项目库依赖内存数组 */ + private val projectLibrariesMap = mutableMapOf() + + /** 写出调试信息 */ + internal fun dump() { + configureMemoryData() + log( + """ + + +--------------------------------------+ + | SWEET DEPENDENCY MEMORY DATA DUMP | + +--------------------------------------+ + + """ + ) + log( + "System Environment" value Environment.systemInfo, + "Character Encoding" value Environment.characterEncoding, + "Java Version" value Environment.javaVersion, + "Gradle Version" value GradleHelper.version, + "Plugin Version" value SweetDependency.VERSION, + "Plugin Configuration" to mapOf( + "configFileName" with "(path) ${configs.configFilePath}" to mapOf( + "preferences" to mapOf( + "autowire-on-sync-mode" value preferences.autowireOnSyncMode, + "repositories-mode" value preferences.repositoriesMode, + "dependencies-namespace" to mapOf( + "plugins" value preferences.dependenciesNamespace.plugins().ifBlank { NONE }, + "libraries" value preferences.dependenciesNamespace.libraries().ifBlank { NONE } + ), + "version-filter" with (if (vfExclusionList.isEmpty()) "(disabled)" else "") to versionFilterMap + ), + "repositories" to repositoriesMap, + "plugins" to pluginsMap, + "libraries" to librariesMap + ), + "isEnableDependenciesAutowireLog" value configs.isEnableDependenciesAutowireLog, + "isEnableVerboseMode" value configs.isEnableVerboseMode + ), + "Project Dependencies" to mapOf( + "Plugins" with (if (projectPluginsMap.isEmpty()) "(load failed)" else "") to projectPluginsMap, + "Libraries" with (if (projectLibrariesMap.isEmpty()) "(load failed)" else "") to projectLibrariesMap + ) + ) + log( + """ + + All debug information has been dumped, if your project is not working properly, please give us feedback on this report + For details, please visit: ${SweetDependency.PROJECT_URL} + + """ + ) + } + + /** 配置内存数据 */ + private fun configureMemoryData() { + if (repositoriesMap.isNotEmpty() && + pluginsMap.isNotEmpty() && + librariesMap.isNotEmpty() && + versionFilterMap.isNotEmpty() && + projectLibrariesMap.isNotEmpty() + ) return + repositoriesMap.clear() + pluginsMap.clear() + librariesMap.clear() + versionFilterMap.clear() + projectLibrariesMap.clear() + repositories.forEach { repo -> + val hasCredentials = repo.credentials.let { it.username.isNotBlank() || it.password.isNotBlank() } + val repoMap = mutableMapOf( + "enable" value repo.isEnable, + "url" value repo.url.ifBlank { NONE }, + "path" value repo.path.ifBlank { NONE } + ) + if (hasCredentials) repoMap["credentials"] = mapOf( + "username" value repo.credentials.username, + "password" value repo.credentials.password + ) + repositoriesMap[repo.nodeName] = repoMap + } + plugins.resolveDependencies(pluginsMap) + libraries.resolveDependencies(librariesMap) + if (vfExclusionList.isNotEmpty()) versionFilterMap["exclusionList"] = mutableMapOf() + vfExclusionList.forEach { versionFilterMap["exclusionList"]?.addAsMap(it) } + GradleHelper.allProjects.forEach { subProject -> + projectPluginsMap[subProject.fullName] = mutableMapOf() + projectLibrariesMap[subProject.fullName] = mutableMapOf() + subProject.plugins().forEach { projectPluginsMap[subProject.fullName]?.addAsMap(it.id) } + subProject.libraries().forEach { + val prefix = "(${it.configurationName})" + when (it.type) { + LibraryDependencyType.EXTERNAL, LibraryDependencyType.EXTERNAL_DELEGATE -> { + val suffix = it.version.deployed.noBlank()?.let { e -> ":$e" } ?: "" + projectLibrariesMap[subProject.fullName]?.addAsMap("$prefix ${it.groupId}:${it.artifactId}$suffix") + } + LibraryDependencyType.PROJECT -> projectLibrariesMap[subProject.fullName]?.addAsMap("$prefix (project) ${it.project?.fullName}") + LibraryDependencyType.FILES -> { + val filesMap = mutableMapOf() + it.files?.noEmpty()?.forEach { e -> filesMap.addAsMap(e.absolutePath) }?.also { + projectLibrariesMap[subProject.fullName] = mapOf("$prefix (files)" to filesMap) + } ?: projectLibrariesMap[subProject.fullName]?.addAsMap("$prefix (files) not found or empty folder") + } + LibraryDependencyType.OTHERS -> projectLibrariesMap[subProject.fullName]?.addAsMap("$prefix unknown type dependency") + } + } + } + } + + /** + * 处理依赖数组 + * @param dependenciesMap 依赖内存数组 + */ + private fun DependencyMap.resolveDependencies(dependenciesMap: MutableMap) = + forEach { (dependencyName, artifact) -> + val repoMap = mutableMapOf() + val childVersionFilterMap = mutableMapOf() + val childVfExclusionList = artifact.versionFilter?.exclusionList()?.all() + if (childVfExclusionList?.isNotEmpty() == true) childVersionFilterMap["exclusionList"] = mutableMapOf() + childVfExclusionList?.forEach { childVersionFilterMap["exclusionList"]?.addAsMap(it) } + artifact.repositories().forEach { repoMap.addAsMap(it.nodeName) } + dependenciesMap[dependencyName.current] = mapOf( + "alias" value artifact.alias.ifBlank { NONE }, + "version" value artifact.version().let { if (it.isNoSpecific) "(no specific)" else it.current }, + "auto-update" value artifact.isAutoUpdate, + "version-filter" with (if (childVfExclusionList?.isEmpty() == true) "(disabled)" else "") to childVersionFilterMap, + "repositories" to repoMap, + ) + } + + /** + * 生成单边 [Pair] + * @param value 键值内容 + * @return [Pair]<[String], [String]> + */ + private infix fun String.value(value: Any) = Pair(with(value), "") + + /** + * 生成冒号键值对字符串 + * @param value 键值内容 + * @return [String] + */ + private infix fun String.with(value: Any) = if (value != NONE) "$this${if (value.toString().isBlank()) "" else ": $value"}" else "" + + /** + * 任意类型转换为 [MutableMap] 并设置空键值内容 + * @param key 键值名称 + */ + private fun Any.addAsMap(key: String) { + @Suppress("UNCHECKED_CAST") + (this as MutableMap)[key] = "" + } + + /** + * 创建 [MutableMap] + * @param pairs 键值对数组 + * @return [MutableMap]<[String], [Any]> + */ + private fun mapOf(vararg pairs: Pair) = mutableMapOf(*pairs) + + /** + * 根据 [Map] 生成键值对树图形字符串 + * @return [String] + */ + private fun Map<*, *>.genMapTree(): String { + /** + * 生成子项目 + * @param prefix 前缀 + * @return [String] + */ + fun Map<*, *>.genChild(prefix: String = ""): String { + val currentMap = filterKeys { it.toString().isNotBlank() }.filterValues { it !is Map<*, *> || it.isNotEmpty() } + val builder = StringBuilder() + currentMap.keys.forEachIndexed { index, key -> + val value = currentMap[key] + val isLast = index == currentMap.keys.size - 1 + val branch = if (isLast) "└─ " else "├─ " + val newPrefix = if (isLast) "$prefix " else "$prefix│ " + builder.append("$prefix$branch$key\n") + if (value is Map<*, *>) builder.append(value.genChild(newPrefix)) + }; return builder.toString() + }; return "${SweetDependency.TAG}\n${genChild()}" + } + + /** + * 打印日志 + * @param pairs 键值对数组 + */ + private fun log(vararg pairs: Pair) = log(mapOf(*pairs).genMapTree()) + + /** + * 打印日志 + * @param any 任意内容 + */ + private fun log(any: Any) = SLog.info(any.toString().trimIndent(), noTag = true) + + /** 标识当前值为空 */ + private const val NONE = "/*-none-*/" +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/SweetDependencyExtension.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/SweetDependencyExtension.kt new file mode 100644 index 0000000..ef05c60 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/SweetDependencyExtension.kt @@ -0,0 +1,69 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/5/19. + */ +package com.highcapable.sweetdependency.plugin + +import com.highcapable.sweetdependency.SweetDependency +import com.highcapable.sweetdependency.gradle.delegate.ProjectTransaction +import com.highcapable.sweetdependency.gradle.factory.getOrCreate +import com.highcapable.sweetdependency.gradle.proxy.IGradleLifecycle +import com.highcapable.sweetdependency.manager.GradleTaskManager +import com.highcapable.sweetdependency.plugin.extension.dsl.configure.SweetDependencyConfigureExtension +import com.highcapable.sweetdependency.plugin.impl.SweetDependencyExtensionImpl +import com.highcapable.sweetdependency.utils.debug.SError +import org.gradle.api.Project +import org.gradle.api.initialization.Settings + +/** + * [SweetDependency] 插件扩展类 + */ +internal class SweetDependencyExtension internal constructor() : IGradleLifecycle { + + /** 当前配置方法体实例 */ + private var configure: SweetDependencyConfigureExtension? = null + + /** 当前扩展实现实例 */ + private var impl: SweetDependencyExtensionImpl? = null + + /** 当前项目事务实例 */ + private var transaction: ProjectTransaction? = null + + override fun onSettingsLoaded(settings: Settings) { + configure = settings.getOrCreate(SweetDependencyConfigureExtension.NAME) + } + + override fun onSettingsEvaluate(settings: Settings) { + impl = SweetDependencyExtensionImpl() + impl?.onInitialization(settings, configure?.build() ?: SError.make("Settings lifecycle is broken")) + } + + override fun onProjectLoaded(project: Project, isRoot: Boolean) { + ProjectTransaction.current = project + ProjectTransaction.isRoot = isRoot + if (transaction == null) transaction = ProjectTransaction() + if (isRoot) GradleTaskManager.register(project) + transaction?.also { impl?.onTransaction(it) } + } + + override fun onProjectEvaluate(project: Project, isRoot: Boolean) { + transaction?.evaluateCallbacks?.forEach { it(project, isRoot) } + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/SweetDependencyPlugin.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/SweetDependencyPlugin.kt new file mode 100644 index 0000000..23496d9 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/SweetDependencyPlugin.kt @@ -0,0 +1,42 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/5/16. + */ +@file:Suppress("unused") + +package com.highcapable.sweetdependency.plugin + +import com.highcapable.sweetdependency.SweetDependency +import com.highcapable.sweetdependency.gradle.delegate.GradleDelegate +import com.highcapable.sweetdependency.utils.debug.SError +import org.gradle.api.Plugin +import org.gradle.api.initialization.Settings +import org.gradle.api.plugins.ExtensionAware + +/** + * [SweetDependency] 插件定义类 + */ +class SweetDependencyPlugin internal constructor() : Plugin { + + override fun apply(target: T) = when (target) { + is Settings -> GradleDelegate.create(target) + else -> SError.make("${SweetDependency.TAG} can only applied in settings.gradle/settings.gradle.kts, but current is $target") + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/config/content/SweetDependencyConfigs.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/config/content/SweetDependencyConfigs.kt new file mode 100644 index 0000000..ba93d05 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/config/content/SweetDependencyConfigs.kt @@ -0,0 +1,78 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/6/29. + */ +package com.highcapable.sweetdependency.plugin.config.content + +import com.highcapable.sweetdependency.SweetDependency +import com.highcapable.sweetdependency.document.RootConfigDocument +import com.highcapable.sweetdependency.document.mapping.RootConfigDocumentMapping +import com.highcapable.sweetdependency.exception.SweetDependencyUnresolvedException +import com.highcapable.sweetdependency.plugin.config.factory.build +import com.highcapable.sweetdependency.plugin.config.proxy.ISweetDependencyConfigs +import com.highcapable.sweetdependency.utils.debug.SError +import com.highcapable.sweetdependency.utils.debug.SLog +import com.highcapable.sweetdependency.utils.yaml.factory.YamlException +import kotlin.properties.Delegates + +/** + * [SweetDependency] 配置类实现类 + */ +internal object SweetDependencyConfigs { + + /** 当前配置 */ + internal var configs by Delegates.notNull() + + /** 当前文档实体 */ + internal var document by Delegates.notNull() + + /** 当前文档测绘实例 */ + internal var documentMapping by Delegates.notNull() + + /** + * 插件启用后执行 + * @param block 方法体 + */ + internal inline fun withPluginEnable(block: () -> Unit) { + if (configs.isEnable) block() else SLog.warn("${SweetDependency.TAG} is disabled (won't do anything)", noRepeat = true) + } + + /** + * 初始化配置 (从文件) + * @param configs 当前配置 + * @param isThrowOnError 是否在发生错误的时候抛出异常 - 默认是 + * @throws SweetDependencyUnresolvedException 如果设置了 [isThrowOnError] 且发生错误 + */ + internal fun initialize(configs: ISweetDependencyConfigs, isThrowOnError: Boolean = true) { + this.configs = configs + runCatching { + configs.build().also { + document = it.first + documentMapping = it.second + } + }.onFailure { + if (isThrowOnError) when (it) { + is YamlException -> SError.make("Failed to parse config file: ${configs.configFilePath}\nPlease check if there are syntax errors", it) + is SweetDependencyUnresolvedException -> throw it + else -> SError.make("Failed to load config file: ${configs.configFilePath}", it) + } + } + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/config/factory/SweetDependencyConfigsFactory.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/config/factory/SweetDependencyConfigsFactory.kt new file mode 100644 index 0000000..8de3053 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/config/factory/SweetDependencyConfigsFactory.kt @@ -0,0 +1,67 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/5/21. + */ +package com.highcapable.sweetdependency.plugin.config.factory + +import com.highcapable.sweetdependency.document.RootConfigDocument +import com.highcapable.sweetdependency.document.mapping.RootConfigDocumentMapping +import com.highcapable.sweetdependency.plugin.config.proxy.ISweetDependencyConfigs +import com.highcapable.sweetdependency.utils.debug.SError +import com.highcapable.sweetdependency.utils.debug.SLog +import com.highcapable.sweetdependency.utils.toFile +import com.highcapable.sweetdependency.utils.yaml.Yaml +import java.io.File + +/** + * 获取并解析配置文件 [RootConfigDocument] 实体和 [RootConfigDocumentMapping] + * @return [Pair]<[RootConfigDocument], [RootConfigDocumentMapping]> + */ +internal fun ISweetDependencyConfigs.build() = configFilePath.loadOrCreateEmpty() to RootConfigDocumentMapping(this) + +/** + * 通过字符串路径获取或创建配置文件 [RootConfigDocument] 实体 + * @return [RootConfigDocument] + */ +private fun String.loadOrCreateEmpty(): RootConfigDocument { + toFile().apply { + if (name.endsWith(".yaml").not() && name.endsWith(".yml").not()) + SError.make("Config file name must be end with \".yaml\" or \".yml\"") + }.createTemplateFileOrNot() + return Yaml.loadFromFile(path = this) +} + +/** 自动创建模版配置文件 */ +private fun File.createTemplateFileOrNot() { + fun createTemplateFile() { + writeText(RootConfigDocument.defaultContent) + SLog.info("Automatically created config file: $absolutePath") + } + runCatching { + when { + exists().not() && parentFile.exists().not() -> { + parentFile.mkdirs() + createTemplateFile() + } + exists().not() -> createTemplateFile() + exists() && isDirectory -> SError.make("Tries to create file path is a directory") + } + }.onFailure { SError.make("Could not automatically created config file: $absolutePath", it) } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/config/proxy/ISweetDependencyConfigs.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/config/proxy/ISweetDependencyConfigs.kt new file mode 100644 index 0000000..f8fd522 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/config/proxy/ISweetDependencyConfigs.kt @@ -0,0 +1,52 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/5/18. + */ +package com.highcapable.sweetdependency.plugin.config.proxy + +import com.highcapable.sweetdependency.SweetDependency + +/** + * [SweetDependency] 配置类接口类 + */ +internal interface ISweetDependencyConfigs { + + companion object { + + /** + * 默认的配置文件名称 + * + * "sweet-dependency-config.yaml" + */ + internal const val DEFAULT_CONFIG_FILE_NAME = "sweet-dependency-config.yaml" + } + + /** 是否启用插件 */ + val isEnable: Boolean + + /** [SweetDependency] 的配置文件路径 */ + val configFilePath: String + + /** 是否启用依赖自动装配日志 */ + val isEnableDependenciesAutowireLog: Boolean + + /** 是否启用详细模式 */ + val isEnableVerboseMode: Boolean +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/extension/accessors/proxy/IExtensionAccessors.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/extension/accessors/proxy/IExtensionAccessors.kt new file mode 100644 index 0000000..63b6b0e --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/extension/accessors/proxy/IExtensionAccessors.kt @@ -0,0 +1,27 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/6/19. + */ +package com.highcapable.sweetdependency.plugin.extension.accessors.proxy + +/** + * 扩展可访问 [Class] 定义空间接口 + */ +internal interface IExtensionAccessors \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/extension/dsl/configure/SweetDependencyConfigureExtension.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/extension/dsl/configure/SweetDependencyConfigureExtension.kt new file mode 100644 index 0000000..15aa1b4 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/extension/dsl/configure/SweetDependencyConfigureExtension.kt @@ -0,0 +1,89 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/6/26. + */ +@file:Suppress("MemberVisibilityCanBePrivate") + +package com.highcapable.sweetdependency.plugin.extension.dsl.configure + +import com.highcapable.sweetdependency.SweetDependency +import com.highcapable.sweetdependency.environment.Environment +import com.highcapable.sweetdependency.plugin.config.proxy.ISweetDependencyConfigs + +/** + * [SweetDependency] 配置方法体实现类 + */ +open class SweetDependencyConfigureExtension internal constructor() { + + internal companion object { + + /** [SweetDependencyConfigureExtension] 扩展名称 */ + internal const val NAME = "sweetDependency" + } + + /** + * 是否启用插件 + * + * 默认启用 - 如果你想关闭插件 - 在这里设置就可以了 + */ + var isEnable = true + @JvmName("enable") set + + /** + * [SweetDependency] 配置文件名称 + * + * 默认为 [ISweetDependencyConfigs.DEFAULT_CONFIG_FILE_NAME] + */ + var configFileName = ISweetDependencyConfigs.DEFAULT_CONFIG_FILE_NAME + @JvmName("configFileName") set + + /** + * 是否启用依赖自动装配日志 + * + * 此功能默认启用 - 会在当前根项目 (Root Project) 的 build 目录下创建日志文件 + */ + var isEnableDependenciesAutowireLog = true + @JvmName("enableDependenciesAutowireLog") set + + /** + * 是否启用详细模式 + * + * 此功能默认启用 - 关闭后 [SweetDependency] 将会在非必要情况下保持安静 (省略非必要日志) + */ + var isEnableVerboseMode = true + @JvmName("enableVerboseMode") set + + /** + * 构造 [ISweetDependencyConfigs] + * @return [ISweetDependencyConfigs] + */ + internal fun build(): ISweetDependencyConfigs { + val currentEnable = isEnable + val currentConfigFilePath = Environment.resourcesDir(configFileName).absolutePath + val currentEnableDependenciesAutowireLog = isEnableDependenciesAutowireLog + val currentEnableVerboseMode = isEnableVerboseMode + return object : ISweetDependencyConfigs { + override val isEnable get() = currentEnable + override val configFilePath get() = currentConfigFilePath + override val isEnableDependenciesAutowireLog get() = currentEnableDependenciesAutowireLog + override val isEnableVerboseMode get() = currentEnableVerboseMode + } + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/extension/dsl/manager/SweetDependencyAutowireExtension.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/extension/dsl/manager/SweetDependencyAutowireExtension.kt new file mode 100644 index 0000000..5dafcb3 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/extension/dsl/manager/SweetDependencyAutowireExtension.kt @@ -0,0 +1,45 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/6/26. + */ +package com.highcapable.sweetdependency.plugin.extension.dsl.manager + +import com.highcapable.sweetdependency.manager.helper.DependencyDeployHelper +import org.gradle.api.Project + +/** + * 依赖扩展功能配置方法体实现类 + * @param project 当前项目 + */ +open class SweetDependencyAutowireExtension internal constructor(private val project: Project) { + + internal companion object { + + /** [SweetDependencyAutowireExtension] 扩展名称 */ + internal const val NAME = "sweet" + } + + /** + * 自动装配依赖 + * @param params 参数数组 + * @return [Any] + */ + fun autowire(vararg params: String) = DependencyDeployHelper.resolveAutowire(project, params) +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/generator/LibrariesAccessorsGenerator.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/generator/LibrariesAccessorsGenerator.kt new file mode 100644 index 0000000..e9a67a1 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/generator/LibrariesAccessorsGenerator.kt @@ -0,0 +1,433 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/8/13. + */ +package com.highcapable.sweetdependency.plugin.generator + +import com.highcapable.sweetdependency.SweetDependency +import com.highcapable.sweetdependency.document.DependencyDocument +import com.highcapable.sweetdependency.document.factory.splitToDependencyGenerateNames +import com.highcapable.sweetdependency.exception.SweetDependencyUnresolvedException +import com.highcapable.sweetdependency.generated.SweetDependencyProperties +import com.highcapable.sweetdependency.gradle.delegate.entity.ExternalDependencyDelegate +import com.highcapable.sweetdependency.gradle.entity.ExternalDependency +import com.highcapable.sweetdependency.manager.content.Dependencies +import com.highcapable.sweetdependency.plugin.config.content.SweetDependencyConfigs +import com.highcapable.sweetdependency.plugin.extension.accessors.proxy.IExtensionAccessors +import com.highcapable.sweetdependency.utils.camelcase +import com.highcapable.sweetdependency.utils.capitalize +import com.highcapable.sweetdependency.utils.debug.SError +import com.highcapable.sweetdependency.utils.firstNumberToLetter +import com.highcapable.sweetdependency.utils.toNonJavaName +import com.highcapable.sweetdependency.utils.uncapitalize +import com.highcapable.sweetdependency.utils.uppercamelcase +import com.squareup.javapoet.ClassName +import com.squareup.javapoet.FieldSpec +import com.squareup.javapoet.JavaFile +import com.squareup.javapoet.MethodSpec +import com.squareup.javapoet.TypeSpec +import java.text.SimpleDateFormat +import java.util.* +import javax.lang.model.element.Modifier + +/** + * 库依赖可访问 [Class] 生成实现类 + */ +internal class LibrariesAccessorsGenerator { + + private companion object { + + /** 生成的 [Class] 所在包名 */ + private const val ACCESSORS_PACKAGE_NAME = "${SweetDependencyProperties.PROJECT_GROUP_NAME}.plugin.extension.accessors.generated" + + /** 生成的 [Class] 后缀名 */ + private const val CLASS_SUFFIX_NAME = "Accessors" + + /** 生成的首位 [Class] 名称 */ + private const val TOP_CLASS_NAME = "Libraries$CLASS_SUFFIX_NAME" + + /** 标识首位生成的 [Class] TAG */ + private const val TOP_SUCCESSIVE_NAME = "_top_successive_name" + } + + /** 生成的依赖库 [Class] 构建器数组 */ + private val classSpecs = mutableMapOf() + + /** 生成的依赖库构造方法构建器数组 */ + private val constructorSpecs = mutableMapOf() + + /** 生成的依赖库预添加的构造方法名称数组 */ + private val preAddConstructorSpecNames = mutableListOf>() + + /** 生成的依赖库 [Class] 扩展类名数组 */ + private val memoryExtensionClasses = mutableListOf>() + + /** 生成的依赖库连续名称记录数组 */ + private val grandSuccessiveNames = mutableListOf() + + /** 生成的依赖库连续名称重复次数数组 */ + private val grandSuccessiveDuplicateIndexs = mutableMapOf() + + /** 生成的依赖库不重复 TAG 数组 */ + private val usedSuccessiveTags = mutableSetOf() + + /** + * 不重复调用 + * @param tags 当前 TAG 数组 + * @param block 执行的方法块 + */ + private inline fun noRepeated(vararg tags: String, block: () -> Unit) { + val allTag = tags.joinToString("-") + if (usedSuccessiveTags.contains(allTag).not()) block() + usedSuccessiveTags.add(allTag) + } + + /** + * 字符串首字母大写并添加 [CLASS_SUFFIX_NAME] 后缀 + * @return [String] + */ + private fun String.capitalized() = "${capitalize()}$CLASS_SUFFIX_NAME" + + /** + * 字符串首字母小写并添加 [CLASS_SUFFIX_NAME] 后缀 + * @return [String] + */ + private fun String.uncapitalized() = "${uncapitalize()}$CLASS_SUFFIX_NAME" + + /** + * 字符串类名转换为 [ClassName] + * @param packageName 包名 - 默认空 + * @return [ClassName] + */ + private fun String.asClassType(packageName: String = "") = ClassName.get(packageName, this) + + /** + * 通过 [TypeSpec] 创建 [JavaFile] + * @return [JavaFile] + */ + private fun TypeSpec.createJavaFile(packageName: String) = JavaFile.builder(packageName, this).build() + + /** + * 创建扩展类完整名称 (含包名) + * @param name 子类名 - 默认空 + * @return [String] + */ + private fun createAccessorsName(name: String = "") = + "$ACCESSORS_PACKAGE_NAME.$TOP_CLASS_NAME${if (name.isNotBlank()) "\$${name.capitalized()}" else ""}" + + /** + * 创建通用构建器描述类 + * @param name 名称 + * @param accessors 接续对象 - 没有默认值 + * @param isInner 是否为内部类 - 默认是 + * @return [TypeSpec.Builder] + */ + private fun createClassSpec(name: String, accessors: Any = Any(), isInner: Boolean = true) = + TypeSpec.classBuilder(if (isInner) name.capitalized() else name).apply { + if (isInner) { + val actual = when (accessors) { + is ExternalDependency -> "\"$accessors\" library" + else -> "\"$accessors\" accessors" + }; addJavadoc("The $actual") + addSuperinterface(IExtensionAccessors::class.java) + addModifiers(Modifier.PUBLIC, Modifier.STATIC) + } else { + addJavadoc( + """ + This class is generated by ${SweetDependency.TAG} at ${SimpleDateFormat.getDateTimeInstance().format(Date())} +
+ The content here is automatically generated according to the dependencies of your projects +
+ You can visit here for more help + """.trimIndent() + ) + addModifiers(Modifier.PUBLIC) + } + } + + /** + * 创建通用构造方法构建器描述类 + * @return [MethodSpec.Builder] + */ + private fun createConstructorSpec() = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC) + + /** + * 向通用构建器描述类添加变量 + * @param accessors 接续对象 + * @param className 类名 + * @return [TypeSpec.Builder] + */ + private fun TypeSpec.Builder.addSuccessiveField(accessors: Any, className: String) = addField( + FieldSpec.builder(className.capitalized().asClassType(), className.uncapitalized(), Modifier.PRIVATE, Modifier.FINAL) + .apply { + val actual = when (accessors) { + is ExternalDependency -> "\"$accessors\" library" + else -> "\"$accessors\" accessors" + }; addJavadoc("Create the $actual") + }.build() + ) + + /** + * 向通用构建器描述类添加方法 + * @param accessors 接续对象 + * @param methodName 方法名 + * @param className 类名 + * @return [TypeSpec.Builder] + */ + private fun TypeSpec.Builder.addSuccessiveMethod(accessors: Any, methodName: String, className: String) = + addMethod( + MethodSpec.methodBuilder("get${methodName.capitalize()}") + .apply { + val actual = when (accessors) { + is ExternalDependency -> "\"$accessors\" library" + else -> "\"$accessors\" accessors" + }; addJavadoc("Resolve the $actual") + }.addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .returns(className.capitalized().asClassType()) + .addStatement("return ${className.uncapitalized()}") + .build() + ) + + /** + * 向通用构建器描述类添加依赖描述器 + * @param dependency 外部存储库依赖实体 + * @param artifact 依赖文档实体 + * @return [TypeSpec.Builder] + */ + private fun TypeSpec.Builder.addLibraryClass(dependency: ExternalDependency, artifact: DependencyDocument) = apply { + superclass(ExternalDependencyDelegate::class.java) + artifact.versions().forEach { (alias, _) -> + addField( + FieldSpec.builder(ExternalDependencyDelegate::class.java, alias.camelcase(), Modifier.PRIVATE, Modifier.FINAL) + .addJavadoc("Create the \"$dependency\" version alias \"$alias\"") + .build() + ) + addMethod( + MethodSpec.methodBuilder("get${alias.uppercamelcase()}") + .addJavadoc("Resolve the \"$dependency\" version alias \"$alias\"") + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .returns(ExternalDependencyDelegate::class.java) + .addStatement("return ${alias.camelcase()}") + .build() + ) + } + } + + /** + * 向通用构造方法构建器描述类添加变量实例化语句 + * @param className 类名 + * @return [MethodSpec.Builder] + */ + private fun MethodSpec.Builder.addSuccessiveStatement(className: String) = + addStatement("${className.uncapitalized()} = new ${className.capitalized()}()") + + /** + * 向通用构造方法构建器描述类添加依赖描述器 + * @param dependency 外部存储库依赖实体 + * @param artifact 依赖文档实体 + * @return [MethodSpec.Builder] + */ + private fun MethodSpec.Builder.addLibraryStatement(dependency: ExternalDependency, artifact: DependencyDocument) = apply { + addStatement("super(\"${dependency.groupId}\", \"${dependency.artifactId}\", \"${dependency.version.deployed}\")") + artifact.versions().forEach { (alias, version) -> + addStatement( + "${alias.camelcase()} = new ${ExternalDependencyDelegate::class.java.simpleName}" + + "(\"${dependency.groupId}\", \"${dependency.artifactId}\", \"$version\")" + ) + } + } + + /** + * 获取、创建通用构建器描述类 + * @param name 名称 + * @param accessors 接续对象 + * @return [TypeSpec.Builder] + */ + private fun getOrCreateClassSpec(name: String, accessors: Any) = + classSpecs[name] ?: createClassSpec(name, accessors).also { classSpecs[name] = it } + + /** + * 获取、创建通用构造方法构建器描述类 + * @param name 名称 + * @return [MethodSpec.Builder] + */ + private fun getOrCreateConstructorSpec(name: String) = constructorSpecs[name] ?: createConstructorSpec().also { constructorSpecs[name] = it } + + /** + * 解析并生成所有类的构建器 (核心方法) + * + * 解析开始前需要确保已调用 [createTopClassSpec] 并调用一次 [clearGeneratedData] 防止数据混淆 + * + * 解析完成后需要调用 [releaseParseTypeSpec] 完成解析 + * @param successiveName 连续的名称 + * @param dependency 外部存储库依赖实体 + * @param artifact 依赖文档实体 + */ + private fun parseTypeSpec(successiveName: String, dependency: ExternalDependency, artifact: DependencyDocument) { + /** + * 获取生成的依赖库连续名称重复次数 + * @return [Int] + */ + fun String.duplicateGrandSuccessiveIndex() = lowercase().let { name -> + if (grandSuccessiveDuplicateIndexs.contains(name)) { + grandSuccessiveDuplicateIndexs[name] = (grandSuccessiveDuplicateIndexs[name] ?: 1) + 1 + grandSuccessiveDuplicateIndexs[name] ?: 2 + } else 2.also { grandSuccessiveDuplicateIndexs[name] = it } + } + + /** + * 解析 (拆分) 名称到数组 + * + * 形如 "com.mytest" → "ComMytest" → "mytest" + * @return [List]<[Triple]<[String], [String], [String]>> + */ + fun String.parseSuccessiveNames(): List> { + var grandAcccessorsName = "" + var grandSuccessiveName = "" + val successiveNames = mutableListOf>() + val splitNames = splitToDependencyGenerateNames() + splitNames.forEach { eachName -> + val name = eachName.capitalize().toNonJavaName().firstNumberToLetter() + grandAcccessorsName += if (grandAcccessorsName.isNotBlank()) ".$eachName" else eachName + grandSuccessiveName += name + if (grandSuccessiveNames.any { it != grandSuccessiveName && it.lowercase() == grandSuccessiveName.lowercase() }) + grandSuccessiveName += duplicateGrandSuccessiveIndex().toString() + grandSuccessiveNames.add(grandSuccessiveName) + successiveNames.add(Triple(grandAcccessorsName, grandSuccessiveName, name)) + }; return successiveNames + } + val successiveNames = successiveName.parseSuccessiveNames() + successiveNames.forEachIndexed { index, (accessorsName, className, methodName) -> + val nextItem = successiveNames.getOrNull(index + 1) + val nextAccessorsName = nextItem?.first ?: "" + val nextClassName = nextItem?.second ?: "" + val nextMethodName = nextItem?.third ?: "" + val isPreLastIndex = index == successiveNames.lastIndex - 1 + val nextAccessors: Any = if (isPreLastIndex) dependency else nextAccessorsName + if (index == successiveNames.lastIndex) { + getOrCreateClassSpec(className, dependency)?.addLibraryClass(dependency, artifact) + getOrCreateConstructorSpec(className)?.addLibraryStatement(dependency, artifact) + } else { + if (index == 0) noRepeated(TOP_SUCCESSIVE_NAME, methodName, className) { + getOrCreateClassSpec(TOP_SUCCESSIVE_NAME, accessorsName) + .addSuccessiveField(accessorsName, className) + .addSuccessiveMethod(accessorsName, methodName, className) + getOrCreateConstructorSpec(TOP_SUCCESSIVE_NAME) + .addSuccessiveStatement(className) + memoryExtensionClasses.add(methodName.uncapitalize() to createAccessorsName(className)) + } + noRepeated(className, nextMethodName, nextClassName) { + getOrCreateClassSpec(className, accessorsName) + .addSuccessiveField(nextAccessors, nextClassName) + .addSuccessiveMethod(nextAccessors, nextMethodName, nextClassName) + preAddConstructorSpecNames.add(className to nextClassName) + } + } + } + } + + /** 完成生成所有类的构建器 (释放) */ + private fun releaseParseTypeSpec() = + preAddConstructorSpecNames.onEach { (topClassName, innerClassName) -> + getOrCreateConstructorSpec(topClassName)?.addSuccessiveStatement(innerClassName) + }.clear() + + /** + * 解析并生成所有类的构建器 + * @return [TypeSpec] + */ + private fun buildTypeSpec(): TypeSpec { + classSpecs.forEach { (name, typeSpec) -> + constructorSpecs[name]?.build()?.let { typeSpec.addMethod(it) } + if (name != TOP_SUCCESSIVE_NAME) classSpecs[TOP_SUCCESSIVE_NAME]?.addType(typeSpec.build()) + }; return classSpecs[TOP_SUCCESSIVE_NAME]?.build() ?: SError.make("Merge accessors classes failed") + } + + /** 创建首位构建器 */ + private fun createTopClassSpec() { + classSpecs[TOP_SUCCESSIVE_NAME] = createClassSpec(TOP_CLASS_NAME, isInner = false) + constructorSpecs[TOP_SUCCESSIVE_NAME] = createConstructorSpec() + } + + /** 清空所有已生成的数据 */ + private fun clearGeneratedData() { + classSpecs.clear() + constructorSpecs.clear() + preAddConstructorSpecNames.clear() + memoryExtensionClasses.clear() + grandSuccessiveNames.clear() + grandSuccessiveDuplicateIndexs.clear() + usedSuccessiveTags.clear() + } + + /** + * 生成 [JavaFile] + * @return [JavaFile] + * @throws SweetDependencyUnresolvedException 如果生成失败 + */ + internal fun build() = runCatching { + clearGeneratedData() + createTopClassSpec() + Dependencies.libraries().forEach { (dependencyName, artifact) -> + val dependency = ExternalDependency(dependencyName, artifact.version()) + parseTypeSpec(dependencyName.current, dependency, artifact) + if (artifact.alias.isNotBlank()) parseTypeSpec(artifact.alias, dependency, artifact) + releaseParseTypeSpec() + }; buildTypeSpec().createJavaFile(ACCESSORS_PACKAGE_NAME) + }.getOrElse { SError.make("Failed to generated accessors classes, please checking your config file", it) } + + /** + * 获取参与编译的 Stub [JavaFile] 数组 + * @return [List]<[JavaFile]> + */ + internal val compileStubFiles get(): List { + val stubFiles = mutableListOf() + val iExtensionAccessorsFile = + TypeSpec.interfaceBuilder(IExtensionAccessors::class.java.simpleName) + .addModifiers(Modifier.PUBLIC) + .build().createJavaFile(IExtensionAccessors::class.java.packageName) + val externalDependencyDelegateFile = + TypeSpec.classBuilder(ExternalDependencyDelegate::class.java.simpleName) + .addModifiers(Modifier.PUBLIC) + .addMethod( + MethodSpec.constructorBuilder() + .addModifiers(Modifier.PUBLIC) + .addParameter(String::class.java, "groupId") + .addParameter(String::class.java, "artifactId") + .addParameter(String::class.java, "version") + .build() + ).build().createJavaFile(ExternalDependencyDelegate::class.java.packageName) + stubFiles.add(iExtensionAccessorsFile) + stubFiles.add(externalDependencyDelegateFile) + return stubFiles + } + + /** + * 获取扩展功能预置 [Class] 数组 (依赖) + * + * 需要调用 [build] 生成后才可以使用 - 否则可能会返回空数组 + * @return [List]<[Pair]<[String], [String]>> + */ + internal val librariesClasses get() = + SweetDependencyConfigs.document.preferences().dependenciesNamespace.libraries().let { namespace -> + if (namespace.isNotBlank()) listOf(namespace to createAccessorsName()) + else memoryExtensionClasses + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/helper/PluginUpdateHelper.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/helper/PluginUpdateHelper.kt new file mode 100644 index 0000000..0378d3f --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/helper/PluginUpdateHelper.kt @@ -0,0 +1,65 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/7/30. + */ +package com.highcapable.sweetdependency.plugin.helper + +import com.highcapable.sweetdependency.SweetDependency +import com.highcapable.sweetdependency.generated.SweetDependencyProperties +import com.highcapable.sweetdependency.gradle.helper.GradleHelper +import com.highcapable.sweetdependency.utils.debug.SLog +import com.highcapable.sweetdependency.utils.executeUrlBody + +/** + * 插件自身检查更新工具类 + */ +internal object PluginUpdateHelper { + + private const val REPO_NAME = SweetDependencyProperties.PROJECT_NAME + private const val AUTHOR_NAME = SweetDependencyProperties.PROJECT_DEVELOPER_NAME + + /** 检查更新 URL 地址 */ + private const val RELEASE_URL = "https://github.com/$AUTHOR_NAME/$REPO_NAME" + + /** 检查更新 */ + internal fun checkingForUpdate() { + if (GradleHelper.isOfflineMode) return + val latestVersion = RELEASE_URL.executeUrlBody(isShowFailure = false).findVersionName() + if (latestVersion.isNotBlank() && latestVersion != SweetDependency.VERSION) + SLog.note( + """ + Plugin update is available, the current version is ${SweetDependency.VERSION}, please update to $latestVersion + You can modify your plugin version in your project's settings.gradle / settings.gradle.kts + plugins { + id("${SweetDependencyProperties.PROJECT_GROUP_NAME}") version "$latestVersion" + ... + } + For more information, you can visit ${SweetDependency.PROJECT_URL} + """.trimIndent(), SLog.UP + ) + } + + /** + * 解析 JSON 并查找字符串版本 "name" + * @return [String] + */ + private fun String.findVersionName() = + runCatching { trim().split("href=\"/$AUTHOR_NAME/$REPO_NAME/releases/tag/")[1].split("\"")[0] }.getOrNull() ?: "" +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/impl/SweetDependencyExtensionImpl.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/impl/SweetDependencyExtensionImpl.kt new file mode 100644 index 0000000..22e8d87 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/impl/SweetDependencyExtensionImpl.kt @@ -0,0 +1,111 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/5/27. + */ +package com.highcapable.sweetdependency.plugin.impl + +import com.highcapable.sweetdependency.SweetDependency +import com.highcapable.sweetdependency.document.PreferencesDocument +import com.highcapable.sweetdependency.document.factory.toUpdateMode +import com.highcapable.sweetdependency.environment.Environment +import com.highcapable.sweetdependency.gradle.delegate.ProjectTransaction +import com.highcapable.sweetdependency.gradle.helper.GradleHelper +import com.highcapable.sweetdependency.manager.DependencyManager +import com.highcapable.sweetdependency.manager.RepositoryManager +import com.highcapable.sweetdependency.plugin.config.content.SweetDependencyConfigs +import com.highcapable.sweetdependency.plugin.config.proxy.ISweetDependencyConfigs +import com.highcapable.sweetdependency.plugin.helper.PluginUpdateHelper +import com.highcapable.sweetdependency.plugin.impl.base.BaseExtensionImpl +import com.highcapable.sweetdependency.utils.debug.SError +import com.highcapable.sweetdependency.utils.debug.SLog +import org.gradle.api.initialization.Settings + +/** + * [SweetDependency] 扩展实现类 + */ +internal class SweetDependencyExtensionImpl : BaseExtensionImpl() { + + private companion object { + + /** 插件是否已经装载 - 在 Gradle 守护程序启动后进程不会被结束 */ + private var isPluginLoaded = false + } + + /** + * 检查兼容性 + * + * 目前仅支持 Gradle 7.x.x and 8.x.x 版本 + */ + private fun checkingCompatibility() { + Environment.characterEncoding?.also { + if (it.lowercase() != "utf-8") SLog.warn( + """ + !!! WARNING !!! + The current character encoding is not UTF-8, it is currently $it + This will cause some characters cannot be displayed, please change the console character encoding + """.trimIndent(), noTag = true + ) + } + if (GradleHelper.version.let { it.startsWith("7.") || it.startsWith("8.") }.not()) SError.make( + "${SweetDependency.TAG} ${SweetDependency.VERSION} " + + "does not support Gradle ${GradleHelper.version}, please update Gradle or plugin version" + ) + } + + override fun onInitialization(settings: Settings, configs: ISweetDependencyConfigs) { + checkingCompatibility() + configureProject(configs) + SweetDependencyConfigs.initialize(configs) + SweetDependencyConfigs.withPluginEnable { + configureRepositoriesAndDependencies(settings) { autowireOnSyncMode -> + autowireOnSyncMode.toUpdateMode()?.also { DependencyManager.autowireAndUpdate(it) } + }; PluginUpdateHelper.checkingForUpdate() + } + } + + override fun onTransaction(transaction: ProjectTransaction) { + if (transaction.isRoot) DependencyManager.resolve(transaction.current) + transaction.evaluation { project, isRoot -> if (isRoot) DependencyManager.deploy(project) } + } + + /** + * 配置项目 + * @param configs 当前配置 + */ + private fun configureProject(configs: ISweetDependencyConfigs) { + SLog.isVerboseMode = configs.isEnableVerboseMode + if (isPluginLoaded.not() || GradleHelper.isSyncMode.not()) SLog.verbose(SweetDependency.bannerContent, noTag = true) + if (isPluginLoaded) return + isPluginLoaded = true + SLog.verbose("Welcome to ${SweetDependency.TAG} ${SweetDependency.VERSION}! Using Gradle ${GradleHelper.version}") + } + + /** + * 配置存储库和依赖 + * @param settings 当前设置 + * @param autowire 自回调动装配方法体 + */ + private inline fun configureRepositoriesAndDependencies(settings: Settings, autowire: (PreferencesDocument.AutowireOnSyncMode) -> Unit) { + RepositoryManager.generateAndApply(settings) + DependencyManager.generateAndApply() + if (GradleHelper.isSyncMode) autowire(SweetDependencyConfigs.document.preferences().autowireOnSyncMode) + DependencyManager.generateAndApply(settings) + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/impl/base/BaseExtensionImpl.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/impl/base/BaseExtensionImpl.kt new file mode 100644 index 0000000..6868c4a --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/impl/base/BaseExtensionImpl.kt @@ -0,0 +1,45 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/5/27. + */ +package com.highcapable.sweetdependency.plugin.impl.base + +import com.highcapable.sweetdependency.gradle.delegate.ProjectTransaction +import com.highcapable.sweetdependency.plugin.config.proxy.ISweetDependencyConfigs +import org.gradle.api.initialization.Settings + +/** + * 扩展父类实现类 + */ +internal abstract class BaseExtensionImpl internal constructor() { + + /** + * 当初始化时回调 + * @param settings 当前设置 + * @param configs 当前配置 + */ + internal abstract fun onInitialization(settings: Settings, configs: ISweetDependencyConfigs) + + /** + * 当开始事务时回调 + * @param transaction 当前实例 + */ + internal abstract fun onTransaction(transaction: ProjectTransaction) +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/AutowireDependenciesTask.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/AutowireDependenciesTask.kt new file mode 100644 index 0000000..5866aeb --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/AutowireDependenciesTask.kt @@ -0,0 +1,42 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/6/29. + */ +package com.highcapable.sweetdependency.plugin.task + +import com.highcapable.sweetdependency.gradle.entity.DependencyUpdateMode +import com.highcapable.sweetdependency.manager.DependencyManager +import com.highcapable.sweetdependency.plugin.config.content.SweetDependencyConfigs +import com.highcapable.sweetdependency.plugin.task.base.BaseTask + +/** + * 依赖自动装配 Gradle Task + */ +internal class AutowireDependenciesTask : BaseTask() { + + override fun onTransaction() = SweetDependencyConfigs.withPluginEnable { + DependencyManager.autowireAndUpdate( + DependencyUpdateMode( + DependencyUpdateMode.DependencyType.ALL, + DependencyUpdateMode.UpdateType.ONLY_AUTOWIRE + ), isRunningOnSync = false + ) + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/AutowireLibrariesTask.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/AutowireLibrariesTask.kt new file mode 100644 index 0000000..e9b2d3e --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/AutowireLibrariesTask.kt @@ -0,0 +1,42 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/8/16. + */ +package com.highcapable.sweetdependency.plugin.task + +import com.highcapable.sweetdependency.gradle.entity.DependencyUpdateMode +import com.highcapable.sweetdependency.manager.DependencyManager +import com.highcapable.sweetdependency.plugin.config.content.SweetDependencyConfigs +import com.highcapable.sweetdependency.plugin.task.base.BaseTask + +/** + * 依赖自动装配 Gradle Task + */ +internal class AutowireLibrariesTask : BaseTask() { + + override fun onTransaction() = SweetDependencyConfigs.withPluginEnable { + DependencyManager.autowireAndUpdate( + DependencyUpdateMode( + DependencyUpdateMode.DependencyType.LIBRARIES, + DependencyUpdateMode.UpdateType.ONLY_AUTOWIRE + ), isRunningOnSync = false + ) + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/AutowirePluginsTask.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/AutowirePluginsTask.kt new file mode 100644 index 0000000..56e7477 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/AutowirePluginsTask.kt @@ -0,0 +1,42 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/8/16. + */ +package com.highcapable.sweetdependency.plugin.task + +import com.highcapable.sweetdependency.gradle.entity.DependencyUpdateMode +import com.highcapable.sweetdependency.manager.DependencyManager +import com.highcapable.sweetdependency.plugin.config.content.SweetDependencyConfigs +import com.highcapable.sweetdependency.plugin.task.base.BaseTask + +/** + * 依赖自动装配 Gradle Task + */ +internal class AutowirePluginsTask : BaseTask() { + + override fun onTransaction() = SweetDependencyConfigs.withPluginEnable { + DependencyManager.autowireAndUpdate( + DependencyUpdateMode( + DependencyUpdateMode.DependencyType.PLUGINS, + DependencyUpdateMode.UpdateType.ONLY_AUTOWIRE + ), isRunningOnSync = false + ) + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/CreateDependenciesMigrationTemplateTask.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/CreateDependenciesMigrationTemplateTask.kt new file mode 100644 index 0000000..c677a6b --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/CreateDependenciesMigrationTemplateTask.kt @@ -0,0 +1,34 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/6/29. + */ +package com.highcapable.sweetdependency.plugin.task + +import com.highcapable.sweetdependency.manager.transaction.DependencyMigrationTemplateTransaction +import com.highcapable.sweetdependency.plugin.config.content.SweetDependencyConfigs +import com.highcapable.sweetdependency.plugin.task.base.BaseTask + +/** + * 创建依赖迁移模板 Gradle Task + */ +internal class CreateDependenciesMigrationTemplateTask : BaseTask() { + + override fun onTransaction() = SweetDependencyConfigs.withPluginEnable { DependencyMigrationTemplateTransaction.createTemplate() } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/SweetDependencyDebugTask.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/SweetDependencyDebugTask.kt new file mode 100644 index 0000000..c05f773 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/SweetDependencyDebugTask.kt @@ -0,0 +1,34 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/6/29. + */ +package com.highcapable.sweetdependency.plugin.task + +import com.highcapable.sweetdependency.manager.transaction.RuntimeDebugTransaction +import com.highcapable.sweetdependency.plugin.config.content.SweetDependencyConfigs +import com.highcapable.sweetdependency.plugin.task.base.BaseTask + +/** + * 调试 Gradle Task + */ +internal class SweetDependencyDebugTask : BaseTask() { + + override fun onTransaction() = SweetDependencyConfigs.withPluginEnable { RuntimeDebugTransaction.dump() } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/UpdateAllDependenciesTask.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/UpdateAllDependenciesTask.kt new file mode 100644 index 0000000..9d8c606 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/UpdateAllDependenciesTask.kt @@ -0,0 +1,42 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/8/14. + */ +package com.highcapable.sweetdependency.plugin.task + +import com.highcapable.sweetdependency.gradle.entity.DependencyUpdateMode +import com.highcapable.sweetdependency.manager.DependencyManager +import com.highcapable.sweetdependency.plugin.config.content.SweetDependencyConfigs +import com.highcapable.sweetdependency.plugin.task.base.BaseTask + +/** + * 依赖自动装配、更新 (全部) Gradle Task + */ +internal class UpdateAllDependenciesTask : BaseTask() { + + override fun onTransaction() = SweetDependencyConfigs.withPluginEnable { + DependencyManager.autowireAndUpdate( + DependencyUpdateMode( + DependencyUpdateMode.DependencyType.ALL, + DependencyUpdateMode.UpdateType.UPDATE_ALL + ), isRunningOnSync = false + ) + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/UpdateAllLibrariesTask.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/UpdateAllLibrariesTask.kt new file mode 100644 index 0000000..cf41be9 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/UpdateAllLibrariesTask.kt @@ -0,0 +1,42 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/8/16. + */ +package com.highcapable.sweetdependency.plugin.task + +import com.highcapable.sweetdependency.gradle.entity.DependencyUpdateMode +import com.highcapable.sweetdependency.manager.DependencyManager +import com.highcapable.sweetdependency.plugin.config.content.SweetDependencyConfigs +import com.highcapable.sweetdependency.plugin.task.base.BaseTask + +/** + * 依赖自动装配、更新 (全部) Gradle Task + */ +internal class UpdateAllLibrariesTask : BaseTask() { + + override fun onTransaction() = SweetDependencyConfigs.withPluginEnable { + DependencyManager.autowireAndUpdate( + DependencyUpdateMode( + DependencyUpdateMode.DependencyType.LIBRARIES, + DependencyUpdateMode.UpdateType.UPDATE_ALL + ), isRunningOnSync = false + ) + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/UpdateAllPluginsTask.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/UpdateAllPluginsTask.kt new file mode 100644 index 0000000..1bd712f --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/UpdateAllPluginsTask.kt @@ -0,0 +1,42 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/8/16. + */ +package com.highcapable.sweetdependency.plugin.task + +import com.highcapable.sweetdependency.gradle.entity.DependencyUpdateMode +import com.highcapable.sweetdependency.manager.DependencyManager +import com.highcapable.sweetdependency.plugin.config.content.SweetDependencyConfigs +import com.highcapable.sweetdependency.plugin.task.base.BaseTask + +/** + * 依赖自动装配、更新 (全部) Gradle Task + */ +internal class UpdateAllPluginsTask : BaseTask() { + + override fun onTransaction() = SweetDependencyConfigs.withPluginEnable { + DependencyManager.autowireAndUpdate( + DependencyUpdateMode( + DependencyUpdateMode.DependencyType.PLUGINS, + DependencyUpdateMode.UpdateType.UPDATE_ALL + ), isRunningOnSync = false + ) + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/UpdateOptionalDependenciesTask.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/UpdateOptionalDependenciesTask.kt new file mode 100644 index 0000000..87a56a2 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/UpdateOptionalDependenciesTask.kt @@ -0,0 +1,42 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/6/29. + */ +package com.highcapable.sweetdependency.plugin.task + +import com.highcapable.sweetdependency.gradle.entity.DependencyUpdateMode +import com.highcapable.sweetdependency.manager.DependencyManager +import com.highcapable.sweetdependency.plugin.config.content.SweetDependencyConfigs +import com.highcapable.sweetdependency.plugin.task.base.BaseTask + +/** + * 依赖自动装配、更新 (可选) Gradle Task + */ +internal class UpdateOptionalDependenciesTask : BaseTask() { + + override fun onTransaction() = SweetDependencyConfigs.withPluginEnable { + DependencyManager.autowireAndUpdate( + DependencyUpdateMode( + DependencyUpdateMode.DependencyType.ALL, + DependencyUpdateMode.UpdateType.UPDATE_OPTIONAL + ), isRunningOnSync = false + ) + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/UpdateOptionalLibrariesTask.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/UpdateOptionalLibrariesTask.kt new file mode 100644 index 0000000..4a3561b --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/UpdateOptionalLibrariesTask.kt @@ -0,0 +1,42 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/8/16. + */ +package com.highcapable.sweetdependency.plugin.task + +import com.highcapable.sweetdependency.gradle.entity.DependencyUpdateMode +import com.highcapable.sweetdependency.manager.DependencyManager +import com.highcapable.sweetdependency.plugin.config.content.SweetDependencyConfigs +import com.highcapable.sweetdependency.plugin.task.base.BaseTask + +/** + * 依赖自动装配、更新 (可选) Gradle Task + */ +internal class UpdateOptionalLibrariesTask : BaseTask() { + + override fun onTransaction() = SweetDependencyConfigs.withPluginEnable { + DependencyManager.autowireAndUpdate( + DependencyUpdateMode( + DependencyUpdateMode.DependencyType.LIBRARIES, + DependencyUpdateMode.UpdateType.UPDATE_OPTIONAL + ), isRunningOnSync = false + ) + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/UpdateOptionalPluginsTask.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/UpdateOptionalPluginsTask.kt new file mode 100644 index 0000000..4e2132f --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/UpdateOptionalPluginsTask.kt @@ -0,0 +1,42 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/8/16. + */ +package com.highcapable.sweetdependency.plugin.task + +import com.highcapable.sweetdependency.gradle.entity.DependencyUpdateMode +import com.highcapable.sweetdependency.manager.DependencyManager +import com.highcapable.sweetdependency.plugin.config.content.SweetDependencyConfigs +import com.highcapable.sweetdependency.plugin.task.base.BaseTask + +/** + * 依赖自动装配、更新 (可选) Gradle Task + */ +internal class UpdateOptionalPluginsTask : BaseTask() { + + override fun onTransaction() = SweetDependencyConfigs.withPluginEnable { + DependencyManager.autowireAndUpdate( + DependencyUpdateMode( + DependencyUpdateMode.DependencyType.PLUGINS, + DependencyUpdateMode.UpdateType.UPDATE_OPTIONAL + ), isRunningOnSync = false + ) + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/base/BaseTask.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/base/BaseTask.kt new file mode 100644 index 0000000..3a9f274 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/plugin/task/base/BaseTask.kt @@ -0,0 +1,31 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/6/29. + */ +package com.highcapable.sweetdependency.plugin.task.base + +/** + * Gradle Task 父类实现类 + */ +internal abstract class BaseTask internal constructor() { + + /** 当开始事务时回调 */ + internal abstract fun onTransaction() +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/FileFactory.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/FileFactory.kt new file mode 100644 index 0000000..1a459e1 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/FileFactory.kt @@ -0,0 +1,136 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/5/17. + */ +@file:Suppress("unused") + +package com.highcapable.sweetdependency.utils + +import com.highcapable.sweetdependency.utils.debug.SError +import java.io.File +import java.nio.file.Paths +import java.util.zip.ZipFile + +/** + * 字符串路径转换为文件 + * + * 自动调用 [parseFileSeparator] + * @return [File] + */ +internal fun String.toFile() = File(parseFileSeparator()) + +/** + * 格式化到当前操作系统的文件分隔符 + * @return [String] + */ +internal fun String.parseFileSeparator() = replace("/", File.separator).replace("\\", File.separator) + +/** + * 格式化到 Unix 操作系统的文件分隔符 + * @return [String] + */ +internal fun String.parseUnixFileSeparator() = replace("\\", "/") + +/** + * 字符串文件路径转换到相对文件路径 + * @param basePath 基于路径 + * @param rootPath 根路径 - 不填将不校验完整路径 + * @return [String] + */ +internal fun String.toRelativeFilePath(basePath: String, rootPath: String = "") = + parseFileSeparator().runCatching { + if (rootPath.isNotBlank() && contains(rootPath).not()) return this + return Paths.get(basePath).relativize(Paths.get(this)).toString() + }.getOrNull() ?: parseFileSeparator() + +/** + * 字符串文件路径转换到绝对文件路径 + * @param basePath 基于路径 + * @return [String] + */ +internal fun String.toAbsoluteFilePath(basePath: String) = + parseFileSeparator().runCatching { + if (Paths.get(this).isAbsolute) return this + Paths.get(basePath).resolve(Paths.get(this)).normalize().toString() + }.getOrNull() ?: parseFileSeparator() + +/** + * 字符串文件路径转换到绝对文件路径数组 + * @param basePath 基于路径 + * @return [MutableList]<[String]> + */ +internal fun String.toAbsoluteFilePaths(basePath: String) = + toAbsoluteFilePath(basePath).let { path -> + mutableListOf().apply { + when { + path.toFile().let { it.exists() && it.isFile } -> add(path) + path.toFile().let { it.exists() && it.isDirectory } -> SError.make("The file path $path is a directory") + else -> { + /** + * 是否匹配文件扩展名 + * @param condition 条件 + * @return [Boolean] + */ + fun String.isMatch(condition: String) = + condition.let { if (it == "*") "*.*" else it }.replace(".", "\\.").replace("*", ".*").toRegex().matches(this) + val condition = path.split(File.separator) + if (path.contains(File.separator) && condition[condition.lastIndex].contains("*")) + path.toFile().parentFile?.listFiles()?.forEach { if (it.name.isMatch(condition[condition.lastIndex])) add(it.absolutePath) } + else SError.make("Could not resolve file path $path") + } + } + } + } + +/** + * 检查文件是否为合法的压缩包文件 + * + * - 如果不是文件 (可能是目录) - 返回 true + * - 如果文件不存在 - 返回 false + * @return [Boolean] + */ +internal fun File.isValidZip(): Boolean { + if (isFile.not()) return true + if (exists().not()) return false + return runCatching { ZipFile(this).use {}; true }.getOrNull() ?: false +} + +/** + * 检查目录是否为空 + * + * - 如果不是目录 (可能是文件) - 返回 true + * - 如果文件不存在 - 返回 true + * @return [Boolean] + */ +internal fun File.isEmpty() = exists().not() || isDirectory.not() || listFiles().isNullOrEmpty() + +/** 删除目录下的空子目录 */ +internal fun File.deleteEmptyRecursively() { + listFiles { file -> file.isDirectory }?.forEach { subDir -> + subDir.deleteEmptyRecursively() + if (subDir.listFiles()?.isEmpty() == true) subDir.delete() + } +} + +/** + * 获取当前文件内容的字符串内容 (同步) + * @return [String] + */ +internal fun String.executeFileBody() = runCatching { toFile().readText() }.getOrNull() ?: "" \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/HttpFactory.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/HttpFactory.kt new file mode 100644 index 0000000..b335680 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/HttpFactory.kt @@ -0,0 +1,58 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/6/15. + */ +package com.highcapable.sweetdependency.utils + +import com.highcapable.sweetdependency.utils.debug.SError +import com.highcapable.sweetdependency.utils.debug.SLog +import okhttp3.Credentials +import okhttp3.OkHttpClient +import okhttp3.Request +import java.util.concurrent.TimeUnit + +/** + * 获取当前 URL 地址的请求体字符串内容 (GET) (同步) + * @param username 用户名 + * @param password 密码 + * @param isShowFailure 是否显示错误 - 默认是 + * @return [String] + */ +internal fun String.executeUrlBody(username: String = "", password: String = "", isShowFailure: Boolean = true) = runCatching { + OkHttpClient() + .newBuilder() + .connectTimeout(10000, TimeUnit.MILLISECONDS) + .authenticator { _, response -> + if (response.code == 400 || response.code == 401) + response.request.newBuilder() + .header("Authorization", Credentials.basic(username, password)) + .build() + else null + }.build().newCall( + Request.Builder().url(when { + startsWith("https://") -> "https://" + replace("https://", "").replace("//", "/") + startsWith("http://") -> "http://" + replace("http://", "").replace("//", "/") + else -> SError.make("Invalid URL: $this") + }).get().build() + ).execute().let { + if (it.code == 200 || it.code == 404) it.body?.string() ?: "" + else SError.make("Request failed with code ${it.code}") + } +}.onFailure { if (isShowFailure) SLog.error("Failed to connect to $this\n$it") }.getOrNull() ?: "" \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/ThreadPoolFactory.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/ThreadPoolFactory.kt new file mode 100644 index 0000000..2fe9050 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/ThreadPoolFactory.kt @@ -0,0 +1,47 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/7/16. + */ +package com.highcapable.sweetdependency.utils + +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + +/** + * 创建当前线程池服务 + * @return [ExecutorService] + */ +private val currentThreadPool get() = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()) + +/** + * 启动 [Thread] 延迟等待 [block] 的结果 [T] + * @param delayMs 延迟毫秒 - 默认 1 ms + * @param block 方法块 + * @return [T] + */ +internal inline fun T.await(delayMs: Long = 1, crossinline block: (T) -> Unit): T { + currentThreadPool.apply { + execute { + if (delayMs > 0) Thread.sleep(delayMs) + block(this@await) + shutdown() + } + }; return this +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/ThrowableFactory.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/ThrowableFactory.kt new file mode 100644 index 0000000..f8a5e71 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/ThrowableFactory.kt @@ -0,0 +1,31 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/6/19. + */ +package com.highcapable.sweetdependency.utils + +import java.io.PrintWriter +import java.io.StringWriter + +/** + * 写出异常堆栈到字符串 + * @return [String] + */ +internal fun Throwable.dumpToString() = StringWriter().apply { printStackTrace(PrintWriter(this).apply { flush() }) }.toString() \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/VariableFactory.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/VariableFactory.kt new file mode 100644 index 0000000..c634cce --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/VariableFactory.kt @@ -0,0 +1,150 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/6/14. + */ +package com.highcapable.sweetdependency.utils + +/** + * 允许 [MutableList] 进行 [orEmpty] 操作后返回 [MutableList] + * @return [MutableList]<[T]> + */ +internal fun MutableList?.orEmpty() = this ?: emptyList().toMutableList() + +/** + * 允许 [MutableList] 进行 [filter] 操作后返回 [MutableList] + * @param predicate 方法体 + * @return [MutableList]<[T]> + */ +internal inline fun MutableList.filter(predicate: (T) -> Boolean) = filterTo(mutableListOf(), predicate).toMutableList() + +/** + * 允许 [MutableMap] 进行 [filter] 操作后返回 [MutableMap] + * @param predicate 方法体 + * @return [MutableMap]<[K], [V]> + */ +internal inline fun MutableMap.filter(predicate: (Map.Entry) -> Boolean) = filterTo(mutableMapOf(), predicate).toMutableMap() + +/** + * 获取 [MutableMap] 第一位元素 (数组为空返回 null) + * @return [MutableMap.MutableEntry]<[K], [V]> or null + */ +internal fun MutableMap.single() = entries.firstOrNull() + +/** + * 当数组不为空时返回非空 + * @return [T] or null + */ +internal inline fun > T.noEmpty() = takeIf { it.isNotEmpty() } + +/** + * 当字符串不为空白时返回非空 + * @return [T] or null + */ +internal inline fun T.noBlank() = takeIf { it.isNotBlank() } + +/** + * 判断数组中是否存在重复元素 + * @return [Boolean] + */ +internal fun List<*>.hasDuplicate() = distinct().size != size + +/** + * 查找数组中的重复元素 + * @return [List]<[T]> + */ +internal inline fun List.findDuplicates() = distinct().filter { e -> count { it == e } > 1 }.distinct() + +/** + * 字符串数组转换为内容字符串 + * @return [String] + */ +internal fun List.joinToContent() = joinToString("\n").trim() + +/** + * 空格字符串数组转换为 [MutableList] + * @return [MutableList]<[String]> + */ +internal fun String.toSpaceList() = when { + contains(" ") -> replace("\\s+".toRegex(), " ").split(" ").toMutableList() + isNotBlank() -> mutableListOf(this) + else -> mutableListOf() +} + +/** + * 下划线、分隔线、点、空格命名字符串转小驼峰命名字符串 + * @return [String] + */ +internal fun String.camelcase() = runCatching { + split("_", ".", "-", " ").map { it.replaceFirstChar { e -> e.titlecase() } }.let { words -> + words.first().replaceFirstChar { it.lowercase() } + words.drop(1).joinToString("") + } +}.getOrNull() ?: this + +/** + * 下划线、分隔线、点、空格命名字符串转大驼峰命名字符串 + * @return [String] + */ +internal fun String.uppercamelcase() = camelcase().capitalize() + +/** + * 字符串首字母大写 + * @return [String] + */ +internal fun String.capitalize() = replaceFirstChar { it.uppercaseChar() } + +/** + * 字符串首字母小写 + * @return [String] + */ +internal fun String.uncapitalize() = replaceFirstChar { it.lowercaseChar() } + +/** + * 转换字符串第一位数字到外观近似大写字母 + * @return [String] + */ +internal fun String.firstNumberToLetter() = + if (isNotBlank()) (mapOf( + '0' to 'O', '1' to 'I', + '2' to 'Z', '3' to 'E', + '4' to 'A', '5' to 'S', + '6' to 'G', '7' to 'T', + '8' to 'B', '9' to 'P' + )[first()] ?: first()) + substring(1) + else this + +/** + * 转换字符串为非 Java 关键方法引用名称 + * @return [String] + */ +internal fun String.toNonJavaName() = if (lowercase() == "class") replace("lass", "lazz") else this + +/** + * 字符串中是否存在插值符号 ${...} + * @return [Boolean] + */ +internal fun String.hasInterpolation() = contains("\${") && contains("}") + +/** + * 替换字符串中的插值符号 ${...} + * @param result 回调结果 + * @return [String] + */ +internal fun String.replaceInterpolation(result: (groupValue: String) -> CharSequence) = + "\\$\\{(.+?)}".toRegex().replace(this) { result(it.groupValues[1]) } \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/code/CodeCompiler.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/code/CodeCompiler.kt new file mode 100644 index 0000000..ef8a4cc --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/code/CodeCompiler.kt @@ -0,0 +1,193 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/8/6. + */ +package com.highcapable.sweetdependency.utils.code + +import com.highcapable.sweetdependency.plugin.SweetDependencyExtension +import com.highcapable.sweetdependency.utils.code.entity.MavenPomData +import com.highcapable.sweetdependency.utils.debug.SError +import com.highcapable.sweetdependency.utils.deleteEmptyRecursively +import com.highcapable.sweetdependency.utils.parseFileSeparator +import com.highcapable.sweetdependency.utils.toFile +import java.io.File +import java.util.jar.JarEntry +import java.util.jar.JarOutputStream +import javax.tools.DiagnosticCollector +import javax.tools.JavaFileObject +import javax.tools.StandardLocation +import javax.tools.ToolProvider + +/** + * 代码编译处理类 + */ +internal object CodeCompiler { + + /** Maven 模型版本 */ + private const val MAVEN_MODEL_VERSION = "4.0.0" + + /** + * 编译 [JavaFileObject] 为 Maven 依赖 + * @param pomData Maven POM 实体 + * @param outputDirPath 编译输出目录路径 + * @param files [JavaFileObject] 数组 + * @param compileOnlyFiles [JavaFileObject] 仅编译数组 - 默认空 + * @throws SweetDependencyExtension 如果编译失败 + */ + internal fun compile( + pomData: MavenPomData, + outputDirPath: String, + files: List, + compileOnlyFiles: List = mutableListOf() + ) { + val outputDir = outputDirPath.toFile() + if (files.isEmpty()) { + if (outputDir.exists()) outputDir.deleteRecursively() + return + } else outputDir.also { if (it.exists().not()) it.mkdirs() } + val outputBuildDir = "$outputDirPath/build".toFile().also { if (it.exists()) it.deleteRecursively(); it.mkdirs() } + val outputClassesDir = "${outputBuildDir.absolutePath}/classes".toFile().apply { mkdirs() } + val outputSourcesDir = "${outputBuildDir.absolutePath}/sources".toFile().apply { mkdirs() } + val compiler = ToolProvider.getSystemJavaCompiler() + val diagnostics = DiagnosticCollector() + val fileManager = compiler.getStandardFileManager(diagnostics, null, null) + fileManager.setLocation(StandardLocation.CLASS_OUTPUT, listOf(outputClassesDir)) + val task = compiler.getTask(null, fileManager, diagnostics, null, null, compileOnlyFiles + files) + val result = task.call() + var diagnosticsMessage = "" + diagnostics.diagnostics?.forEach { diagnostic -> + diagnosticsMessage += " > Error on line ${diagnostic.lineNumber} in ${diagnostic.source?.toUri()}\n" + diagnosticsMessage += " ${diagnostic.getMessage(null)}\n" + } + runCatching { fileManager.close() } + if (result) { + compileOnlyFiles.forEach { "${outputClassesDir.absolutePath}/${it.name}".replace(".java", ".class").toFile().delete() } + files.forEach { + it.toFiles(outputSourcesDir).also { (sourceDir, sourceFile) -> + sourceDir.mkdirs() + sourceFile.writeText(it.getCharContent(true).toString()) + } + }; outputClassesDir.deleteEmptyRecursively() + writeMetaInf(outputClassesDir.absolutePath) + writeMetaInf(outputSourcesDir.absolutePath) + createJarAndPom(pomData, outputDir, outputBuildDir, outputClassesDir, outputSourcesDir) + } else SError.make("Failed to compile java files into path: $outputDirPath\n$diagnosticsMessage") + } + + /** + * 打包 JAR 并写入 POM + * @param pomData Maven POM 实体 + * @param outputDir 编译输出目录 + * @param buildDir 编译目录 + * @param classesDir 编译二进制目录 + * @param sourcesDir 编译源码目录 + */ + private fun createJarAndPom(pomData: MavenPomData, outputDir: File, buildDir: File, classesDir: File, sourcesDir: File) { + val pomPath = "${pomData.groupId.toPomPathName()}/${pomData.artifactId}/${pomData.version}" + val pomDir = "${outputDir.absolutePath}/$pomPath".toFile().also { if (it.exists().not()) it.mkdirs() } + packageToJar(classesDir, pomDir, pomData, isSourcesJar = false) + packageToJar(sourcesDir, pomDir, pomData, isSourcesJar = true) + writePom(pomDir.absolutePath, pomData) + buildDir.deleteRecursively() + } + + /** + * 写入 META-INF/MANIFEST.MF + * @param dirPath 当前目录路径 + */ + private fun writeMetaInf(dirPath: String) { + val metaInfFile = "$dirPath/META-INF".toFile().apply { mkdirs() } + "${metaInfFile.absolutePath}/MANIFEST.MF".toFile().writeText("Manifest-Version: 1.0") + } + + /** + * 写入 POM + * @param dirPath 当前目录路径 + * @param pomData Maven POM 实体 + */ + private fun writePom(dirPath: String, pomData: MavenPomData) = + "$dirPath/${pomData.artifactId}-${pomData.version}.pom".toFile().writeText( + """ + + + $MAVEN_MODEL_VERSION + ${pomData.groupId} + ${pomData.artifactId} + ${pomData.version} + + """.trimIndent() + ) + + /** + * 转换到 [MavenPomData] 目录名称 + * @return [String] + */ + private fun String.toPomPathName() = trim().replace(".", "/").replace("_", "/").replace(":", "/").replace("-", "/") + + /** + * 转换到文件 + * @param outputDir 输出目录 + * @return [Pair]<[File], [File]> + */ + private fun JavaFileObject.toFiles(outputDir: File): Pair { + val outputDirPath = outputDir.absolutePath + val separator = if (name.contains("/")) "/" else "\\" + val names = name.split(separator) + val fileName = names[names.lastIndex] + val folderName = name.replace(fileName, "") + return "$outputDirPath/$folderName".toFile() to "$outputDirPath/$name".toFile() + } + + /** + * 打包编译输出目录到 JAR + * @param buildDir 编译目录 + * @param outputDir 输出目录 + * @param pomData Maven POM 实体 + * @param isSourcesJar 是否为源码 JAR + * @throws SweetDependencyExtension 如果编译输出目录不存在 + */ + private fun packageToJar(buildDir: File, outputDir: File, pomData: MavenPomData, isSourcesJar: Boolean) { + if (buildDir.exists().not()) SError.make("Jar file output path not found: ${buildDir.absolutePath}") + /** + * 添加文件到 JAR + * @param jos 当前输出流 + * @param parentPath 父级路径 + */ + fun File.addToJar(jos: JarOutputStream, parentPath: String = "") { + val currentPath = "$parentPath$name".replace("${buildDir.name}|", "").replace("|", "/").parseFileSeparator() + if (isFile) { + if (name.startsWith(".")) return + jos.putNextEntry(JarEntry(currentPath)) + inputStream().use { fis -> + val buffer = ByteArray(4096) + var bytesRead: Int + while (fis.read(buffer).also { bytesRead = it } != -1) jos.write(buffer, 0, bytesRead) + } + jos.closeEntry() + } else listFiles()?.forEach { it.addToJar(jos, parentPath = "$currentPath|") } + } + val jarFile = "${outputDir.absolutePath}/${pomData.artifactId}-${pomData.version}${if (isSourcesJar) "-sources" else ""}.jar".toFile() + if (jarFile.exists()) jarFile.delete() + jarFile.outputStream().use { fos -> JarOutputStream(fos).use { jos -> buildDir.addToJar(jos) } } + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/code/entity/MavenPomData.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/code/entity/MavenPomData.kt new file mode 100644 index 0000000..50be7e1 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/code/entity/MavenPomData.kt @@ -0,0 +1,30 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/8/24. + */ +package com.highcapable.sweetdependency.utils.code.entity + +/** + * Maven POM 实体 + * @param groupId Group ID + * @param artifactId Artifact Id + * @param version 版本 + */ +internal data class MavenPomData(internal val groupId: String, internal val artifactId: String, internal val version: String) \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/code/factory/CodeCompilerFactory.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/code/factory/CodeCompilerFactory.kt new file mode 100644 index 0000000..96eaa93 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/code/factory/CodeCompilerFactory.kt @@ -0,0 +1,84 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/8/6. + */ +@file:Suppress("unused") + +package com.highcapable.sweetdependency.utils.code.factory + +import com.highcapable.sweetdependency.plugin.SweetDependencyExtension +import com.highcapable.sweetdependency.utils.code.CodeCompiler +import com.highcapable.sweetdependency.utils.code.entity.MavenPomData +import com.squareup.javapoet.JavaFile +import javax.tools.JavaFileObject + +/** + * 编译 [JavaFile] 为 Maven 依赖 + * @param pomData Maven POM 实体 + * @param outputDirPath 编译输出目录路径 + * @param compileOnlyFiles [JavaFile] 仅编译数组 - 默认空 + * @throws SweetDependencyExtension 如果编译失败 + */ +@JvmName("compileWithJavaFile") +internal fun JavaFile.compile(pomData: MavenPomData, outputDirPath: String, compileOnlyFiles: List = mutableListOf()) = + CodeCompiler.compile( + pomData = pomData, + outputDirPath = outputDirPath, + files = listOf(toJavaFileObject()), + compileOnlyFiles = mutableListOf().also { compileOnlyFiles.forEach { e -> it.add(e.toJavaFileObject()) } } + ) + +/** + * 编译 [JavaFile] 为 Maven 依赖 + * @param pomData Maven POM 实体 + * @param outputDirPath 编译输出目录路径 + * @param compileOnlyFiles [JavaFile] 仅编译数组 - 默认空 + * @throws SweetDependencyExtension 如果编译失败 + */ +@JvmName("compileWithJavaFile") +internal fun List.compile(pomData: MavenPomData, outputDirPath: String, compileOnlyFiles: List = mutableListOf()) = + CodeCompiler.compile( + pomData = pomData, + outputDirPath = outputDirPath, + files = mutableListOf().also { forEach { e -> it.add(e.toJavaFileObject()) } }, + compileOnlyFiles = mutableListOf().also { compileOnlyFiles.forEach { e -> it.add(e.toJavaFileObject()) } } + ) + +/** + * 编译 [JavaFileObject] 为 Maven 依赖 + * @param pomData Maven POM 实体 + * @param outputDirPath 编译输出目录路径 + * @param compileOnlyFiles [JavaFileObject] 仅编译数组 - 默认空 + * @throws SweetDependencyExtension 如果编译失败 + */ +@JvmName("compileWithJavaFileObject") +internal fun JavaFileObject.compile(pomData: MavenPomData, outputDirPath: String, compileOnlyFiles: List = mutableListOf()) = + CodeCompiler.compile(pomData, outputDirPath, listOf(this), compileOnlyFiles) + +/** + * 编译 [JavaFileObject] 为 Maven 依赖 + * @param pomData Maven POM 实体 + * @param outputDirPath 编译输出目录路径 + * @param compileOnlyFiles [JavaFileObject] 仅编译数组 - 默认空 + * @throws SweetDependencyExtension 如果编译失败 + */ +@JvmName("compileWithJavaFileObject") +internal fun List.compile(pomData: MavenPomData, outputDirPath: String, compileOnlyFiles: List = mutableListOf()) = + CodeCompiler.compile(pomData, outputDirPath, files = this, compileOnlyFiles) \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/debug/SError.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/debug/SError.kt new file mode 100644 index 0000000..2a65d5a --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/debug/SError.kt @@ -0,0 +1,38 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/5/31. + */ +package com.highcapable.sweetdependency.utils.debug + +import com.highcapable.sweetdependency.exception.SweetDependencyUnresolvedException + +/** + * 全局异常管理类 + */ +internal object SError { + + /** + * 抛出异常 + * @param msg 消息内容 + * @throws e 异常内容 - 默认空 + * @throws SweetDependencyUnresolvedException + */ + internal fun make(msg: String, e: Throwable? = null): Nothing = throw SweetDependencyUnresolvedException(msg, e) +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/debug/SLog.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/debug/SLog.kt new file mode 100644 index 0000000..a9f93fa --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/debug/SLog.kt @@ -0,0 +1,131 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/5/25. + */ +@file:Suppress("MemberVisibilityCanBePrivate") + +package com.highcapable.sweetdependency.utils.debug + +import com.highcapable.sweetdependency.SweetDependency +import com.highcapable.sweetdependency.utils.await +import org.apache.log4j.Logger + +/** + * 全局 Log 管理类 + */ +internal object SLog { + + internal const val DONE = "✅" + internal const val IGNORE = "❎" + internal const val ERROR = "❌" + internal const val WARN = "⚠️" + internal const val LINK = "➡️" + internal const val WIRE = "⚙️" + internal const val UP = "⬆️" + internal const val ROTATE = "\uD83D\uDD04" + internal const val ANLZE = "\uD83D\uDD0D" + internal const val STRNG = "\uD83D\uDCAA" + + /** 当前日志输出对象 */ + private val logger = Logger.getLogger(SLog::class.java) + + /** 短时间内不重复的日志内容 */ + private var noRepeatInThisTimeContent = "" + + /** 是否启用详细模式 */ + internal var isVerboseMode = true + + /** + * 打印 Verbose 级别 Log (无颜色) + * @param msg 消息内容 + * @param symbol 前缀符号 - 仅限非 [noTag] - 默认无 + * @param noTag 无标签 - 默认否 + * @param noRepeat 短时间内不重复输出 - 默认否 + */ + internal fun verbose(msg: Any, symbol: String = "", noTag: Boolean = false, noRepeat: Boolean = false) { + if (isVerboseMode) info(msg, symbol, noTag, noRepeat) + } + + /** + * 打印 Info (提醒) 级别 Log (绿色) + * @param msg 消息内容 + * @param symbol 前缀符号 - 仅限非 [noTag] - 默认无 + * @param noTag 无标签 - 默认否 + * @param noRepeat 短时间内不重复输出 - 默认否 + */ + internal fun note(msg: Any, symbol: String = "", noTag: Boolean = false, noRepeat: Boolean = false) = + log(if (noTag) msg else msg.createSymbolMsg(symbol), color = "38;5;10", noRepeat = noRepeat) + + /** + * 打印 Info 级别 Log (无颜色) + * @param msg 消息内容 + * @param symbol 前缀符号 - 仅限非 [noTag] - 默认无 + * @param noTag 无标签 - 默认否 + * @param noRepeat 短时间内不重复输出 - 默认否 + */ + internal fun info(msg: Any, symbol: String = "", noTag: Boolean = false, noRepeat: Boolean = false) = + log(if (noTag) msg else msg.createSymbolMsg(symbol), noRepeat = noRepeat) + + /** + * 打印 Warn 级别 Log (黄色) + * @param msg 消息内容 + * @param symbol 前缀符号 - 仅限非 [noTag] - 默认 [WARN] + * @param noTag 无标签 - 默认否 + * @param noRepeat 短时间内不重复输出 - 默认否 + */ + internal fun warn(msg: Any, symbol: String = WARN, noTag: Boolean = false, noRepeat: Boolean = false) = + log(if (noTag) msg else msg.createSymbolMsg(symbol), color = "33", noRepeat = noRepeat) + + /** + * 打印 Error 级别 Log (红色) + * @param msg 消息内容 + * @param symbol 前缀符号 - 仅限非 [noTag] - 默认 [ERROR] + * @param noTag 无标签 - 默认否 + * @param noRepeat 短时间内不重复输出 - 默认否 + */ + internal fun error(msg: Any, symbol: String = ERROR, noTag: Boolean = false, noRepeat: Boolean = false) = + log(if (noTag) msg else msg.createSymbolMsg(symbol), isError = true, noRepeat = noRepeat) + + /** + * 创建符号消息内容 + * @param symbol 前缀符号 + * @return [String] + */ + private fun Any.createSymbolMsg(symbol: String) = + if (symbol.isNotBlank()) "[${SweetDependency.TAG}] $symbol $this" else "[${SweetDependency.TAG}] $this" + + /** + * 打印 Log + * @param msg 消息内容 + * @param color 颜色代码 - 默认无颜色 + * @param isError 是否强制为错误日志 - 默认否 + * @param noRepeat 短时间内不重复输出 - 默认否 + */ + private fun log(msg: Any, color: String = "0", isError: Boolean = false, noRepeat: Boolean = false) { + if (noRepeat && noRepeatInThisTimeContent == msg.toString()) return + noRepeatInThisTimeContent = msg.toString() + when { + isError -> logger.error(msg) + color != "0" -> println("\u001B[${color}m$msg\u001B[0m") + else -> println(msg) + } + if (noRepeat) await(1500) { noRepeatInThisTimeContent = "" } else noRepeatInThisTimeContent = "" + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/yaml/Yaml.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/yaml/Yaml.kt new file mode 100644 index 0000000..edffa37 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/yaml/Yaml.kt @@ -0,0 +1,117 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/5/17. + */ +@file:Suppress("unused") + +package com.highcapable.sweetdependency.utils.yaml + +import com.charleskorn.kaml.YamlConfiguration +import com.charleskorn.kaml.yamlMap +import com.highcapable.sweetdependency.gradle.helper.GradleHelper +import com.highcapable.sweetdependency.utils.hasInterpolation +import com.highcapable.sweetdependency.utils.replaceInterpolation +import com.highcapable.sweetdependency.utils.toFile +import com.highcapable.sweetdependency.utils.yaml.factory.YamlMapEntries +import com.highcapable.sweetdependency.utils.yaml.proxy.IYamlDocument +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.serializer +import com.charleskorn.kaml.Yaml as Kaml + +/** + * YAML 文档处理类 + */ +internal object Yaml { + + /** + * 获取 [Kaml] 对象 + * @return [Kaml] + */ + private val kaml by lazy { Kaml(configuration = YamlConfiguration(encodeDefaults = false)) } + + /** + * 从文件解析到 [IYamlDocument] + * @param path 文件路径 + * @return [T] + */ + internal inline fun loadFromFile(path: String) = loadFromString(path.toFile().readText()) + + /** + * 从字符串解析到 [IYamlDocument] + * @param string 字符串 + * @return [T] + */ + internal inline fun loadFromString(string: String) = kaml.decodeFromString(string.flattened()) + + /** + * 从文件解析到 [YamlMapEntries] + * @param path 文件路径 + * @return [YamlMapEntries] + */ + internal fun loadFromFileAsNode(path: String) = loadFromStringAsNode(path.toFile().readText()) + + /** + * 从字符串解析到 [YamlMapEntries] + * @param string 字符串 + * @return [YamlMapEntries] + */ + internal fun loadFromStringAsNode(string: String) = kaml.parseToYamlNode(string.flattened()).yamlMap.entries + + /** + * 序列化 [IYamlDocument] 到文件 + * @param path 文件路径 + * @param formatter 回调字符串格式化方式 + */ + internal inline fun parseToFile(doc: T, path: String, formatter: String.() -> String = { this }) = + path.toFile().writeText(kaml.encodeToString(serializer(), doc).let(formatter)) + + /** + * 字符串平坦化处理 + * + * - 去除字符串中以 # 开头的注释行并去除空行 + * - 调用 [interpFromEnv] 解析可被插值的字符串 + * @return [String] + */ + private fun String.flattened() = trimIndent() + .replace("(^|\\s)#.*".toRegex(), "") + .replace("(?m)^\\s*$(\\n|\\r\\n?)".toRegex(), "") + .let { if (it.hasInterpolation()) it.interpFromEnv() else it } + .trim() + + /** + * 将系统属性资源值插入到当前字符串中 + * + * 形如:${...} + * + * 会按照以下顺序进行查找 ↓ + * + * - 项目 properties + * - 用户 properties + * - 系统 properties + * - 系统环境变量 + * @return [String] + */ + private fun String.interpFromEnv() = replaceInterpolation { key -> + GradleHelper.projectProperties?.get(key)?.toString() + ?: GradleHelper.userProperties?.get(key)?.toString() + ?: runCatching { System.getProperties()[key]?.toString() }.getOrNull() + ?: runCatching { System.getenv(key) }.getOrNull() ?: "" + } +} \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/yaml/factory/YamlFactory.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/yaml/factory/YamlFactory.kt new file mode 100644 index 0000000..0da51cf --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/yaml/factory/YamlFactory.kt @@ -0,0 +1,46 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/5/19. + */ +package com.highcapable.sweetdependency.utils.yaml.factory + +import com.charleskorn.kaml.YamlException +import com.charleskorn.kaml.YamlMap +import com.charleskorn.kaml.YamlNode +import com.charleskorn.kaml.YamlScalar + +/** YAML 异常类型定义 */ +internal typealias YamlException = YamlException + +/** YAML 节点数组类型定义 */ +internal typealias YamlMapEntries = Map + +/** + * 转换为 YAML 节点数组 + * @return [YamlMapEntries] or null + */ +internal fun YamlNode.asMap() = (this as? YamlMap)?.entries + +/** + * 获取 YAML 节点是否存在 + * @param key 节点名称 + * @return [Boolean] + */ +internal fun YamlMapEntries.isKeyExist(key: String) = keys.singleOrNull { it.content == key } != null \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/yaml/proxy/IYamlDocument.kt b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/yaml/proxy/IYamlDocument.kt new file mode 100644 index 0000000..c3b5f3c --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/com/highcapable/sweetdependency/utils/yaml/proxy/IYamlDocument.kt @@ -0,0 +1,29 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/5/20. + */ +package com.highcapable.sweetdependency.utils.yaml.proxy + +import java.io.Serializable + +/** + * YAML 文档接口 + */ +internal interface IYamlDocument : Serializable \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/org/gradle/kotlin/dsl/SweetDependencyProjectExtensionFactory.kt b/sweetdependency-gradle-plugin/src/main/java/org/gradle/kotlin/dsl/SweetDependencyProjectExtensionFactory.kt new file mode 100644 index 0000000..23e138a --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/org/gradle/kotlin/dsl/SweetDependencyProjectExtensionFactory.kt @@ -0,0 +1,51 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/6/25. + */ +@file:Suppress("unused", "UnusedReceiverParameter") + +package org.gradle.kotlin.dsl + +import com.highcapable.sweetdependency.manager.helper.DependencyDeployHelper +import org.gradle.api.artifacts.dsl.DependencyHandler +import org.gradle.plugin.use.PluginDependenciesSpec +import org.gradle.plugin.use.PluginDependencySpec +import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler + +/** + * 自动装配插件依赖 + * @param params 参数数组 + * @return [PluginDependencySpec] + */ +fun PluginDependenciesSpec.autowire(vararg params: Any) = DependencyDeployHelper.resolveAutowire(spec = this, params) + +/** + * 自动装配依赖 + * @param params 参数数组 + * @return [Any] + */ +fun DependencyHandler.autowire(vararg params: String) = DependencyDeployHelper.resolveAutowire(params = params) + +/** + * 自动装配依赖 + * @param params 参数数组 + * @return [Any] + */ +fun KotlinDependencyHandler.autowire(vararg params: String) = DependencyDeployHelper.resolveAutowire(project, params) \ No newline at end of file diff --git a/sweetdependency-gradle-plugin/src/main/java/org/gradle/kotlin/dsl/SweetDependencySettingsExtensionFactory.kt b/sweetdependency-gradle-plugin/src/main/java/org/gradle/kotlin/dsl/SweetDependencySettingsExtensionFactory.kt new file mode 100644 index 0000000..5504d19 --- /dev/null +++ b/sweetdependency-gradle-plugin/src/main/java/org/gradle/kotlin/dsl/SweetDependencySettingsExtensionFactory.kt @@ -0,0 +1,51 @@ +/* + * SweetDependency - An easy autowire and manage dependencies Gradle plugin + * Copyright (C) 2019-2023 HighCapable + * https://github.com/HighCapable/SweetDependency + * + * Apache License Version 2.0 + * + * 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 + * + * https://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. + * + * This file is Created by fankes on 2023/6/3. + */ +@file:Suppress("unused") + +package org.gradle.kotlin.dsl + +import com.highcapable.sweetdependency.gradle.factory.configure +import com.highcapable.sweetdependency.gradle.factory.get +import com.highcapable.sweetdependency.plugin.extension.dsl.configure.SweetDependencyConfigureExtension +import org.gradle.api.Action +import org.gradle.api.initialization.Settings + +/** + * WORKAROUND: for some reason a type-safe accessor is not generated for the extension, + * even though it is present in the extension container where the plugin is applied. + * This seems to work fine, and the extension methods are only available when the plugin + * is actually applied. + * + * See related link [here](https://stackoverflow.com/questions/72627792/gradle-settings-plugin-extension) + */ + +/** + * Retrieves the [SweetDependencyConfigureExtension] extension. + * @return [SweetDependencyConfigureExtension] + */ +val Settings.sweetDependency get() = get(SweetDependencyConfigureExtension.NAME) + +/** + * Configures the [SweetDependencyConfigureExtension] extension. + * @param configure + */ +fun Settings.sweetDependency(configure: Action) = configure(SweetDependencyConfigureExtension.NAME, configure) \ No newline at end of file