├── .github
└── workflows
│ └── nebula.yml
├── .gitignore
├── .java-version
├── CHANGELOG.md
├── LICENSE
├── README.md
├── build.gradle
├── gradle.lockfile
├── gradle.properties
├── gradle
├── gradle-daemon-jvm.properties
├── idea-codestyle.xml
├── idea-copyright.xml
├── idea-inspections.xml
├── idea.gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
├── integTest
├── groovy
│ └── nebula
│ │ └── plugin
│ │ └── resolutionrules
│ │ ├── AbstractAlignAndMigrateSpec.groovy
│ │ ├── AbstractAlignRulesSpec.groovy
│ │ ├── AbstractIntegrationTestKitSpec.groovy
│ │ ├── AbstractRulesWithSpringBootPluginSpec.groovy
│ │ ├── AlignAndLockWithDowngradedTransitiveDependenciesSpec.groovy
│ │ ├── AlignAndMigrateViaReplacementSpec.groovy
│ │ ├── AlignAndMigrateViaSubstitutionSpec.groovy
│ │ ├── AlignAndSubstituteRulesSpec.groovy
│ │ ├── AlignAndSubstituteRulesWithSpringBoot1xPluginSpec.groovy
│ │ ├── AlignAndSubstituteRulesWithSpringBoot2xPluginAndManagedDepsSpec.groovy
│ │ ├── AlignAndSubstituteRulesWithSpringBoot2xPluginWithoutManagedDepsSpec.groovy
│ │ ├── AlignRulesBasicSpec.groovy
│ │ ├── AlignRulesBasicWithCoreSpec.groovy
│ │ ├── AlignRulesDirectDependenciesSpec.groovy
│ │ ├── AlignRulesForceSpec.groovy
│ │ ├── AlignRulesForceStrictlyWithSubstitutionSpec.groovy
│ │ ├── AlignRulesMultiprojectSpec.groovy
│ │ ├── AlignRulesPluginInteractionSpec.groovy
│ │ ├── AlignRulesTransitiveDependenciesSpec.groovy
│ │ ├── AlignRulesVersionMatchSpec.groovy
│ │ ├── AlignRulesVersionSuffixesSpec.groovy
│ │ ├── IgnoredConfigurationsWithRulesSpec.groovy
│ │ ├── ResolutionRulesPluginSpec.groovy
│ │ ├── SubstituteRulesSpec.groovy
│ │ └── SubstituteRulesWithRangesSpec.groovy
└── resources
│ └── logback.xml
├── main
└── kotlin
│ └── nebula
│ └── plugin
│ └── resolutionrules
│ ├── alignRule.kt
│ ├── configurations.kt
│ ├── extensions.kt
│ ├── json.kt
│ ├── plugin.kt
│ └── rules.kt
└── test
└── groovy
└── nebula
└── plugin
└── resolutionrules
├── AlignRuleMatcherTest.groovy
├── NebulaResolutionRulesExtensionTest.groovy
└── RulesTest.groovy
/.github/workflows/nebula.yml:
--------------------------------------------------------------------------------
1 | name: Nebula Build
2 | on:
3 | push:
4 | branches:
5 | - '*'
6 | tags:
7 | - v*.*.*
8 | - v*.*.*-rc.*
9 | pull_request:
10 |
11 | jobs:
12 | validation:
13 | name: "Gradle Wrapper Validation"
14 | runs-on: ubuntu-latest
15 | steps:
16 | - uses: actions/checkout@v2
17 | - uses: gradle/wrapper-validation-action@v1
18 | buildmultijdk:
19 | if: (!startsWith(github.ref, 'refs/tags/v'))
20 | needs: validation
21 | runs-on: ubuntu-latest
22 | strategy:
23 | matrix:
24 | # test against latest update of some major Java version(s), as well as specific LTS version(s)
25 | java: [8, 17, 21]
26 | name: Gradle Build without Publish
27 | steps:
28 | - uses: actions/checkout@v1
29 | - name: Setup git user
30 | run: |
31 | git config --global user.name "Nebula Plugin Maintainers"
32 | git config --global user.email "nebula-plugins-oss@netflix.com"
33 | - name: Set up JDKs
34 | uses: actions/setup-java@v4
35 | with:
36 | distribution: 'zulu'
37 | java-version: |
38 | 8
39 | ${{ matrix.java }}
40 | java-package: jdk
41 | - uses: actions/cache@v4
42 | id: gradle-cache
43 | with:
44 | path: ~/.gradle/caches
45 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/gradle/dependency-locks/*.lockfile') }}
46 | restore-keys: |
47 | - ${{ runner.os }}-gradle-
48 | - uses: actions/cache@v4
49 | id: gradle-wrapper-cache
50 | with:
51 | path: ~/.gradle/wrapper
52 | key: ${{ runner.os }}-gradlewrapper-${{ hashFiles('gradle/wrapper/*') }}
53 | restore-keys: |
54 | - ${{ runner.os }}-gradlewrapper-
55 | - name: Gradle build
56 | run: ./gradlew --info --stacktrace build
57 | env:
58 | JDK_VERSION_FOR_TESTS: ${{ matrix.java }}
59 | validatepluginpublication:
60 | if: startsWith(github.ref, 'refs/tags/v')
61 | needs: validation
62 | runs-on: ubuntu-latest
63 | name: Gradle Plugin Publication Validation
64 | env:
65 | NETFLIX_OSS_SONATYPE_USERNAME: ${{ secrets.ORG_SONATYPE_USERNAME }}
66 | NETFLIX_OSS_SONATYPE_PASSWORD: ${{ secrets.ORG_SONATYPE_PASSWORD }}
67 | steps:
68 | - uses: actions/checkout@v1
69 | - name: Setup git user
70 | run: |
71 | git config --global user.name "Nebula Plugin Maintainers"
72 | git config --global user.email "nebula-plugins-oss@netflix.com"
73 | - name: Set up JDKs
74 | uses: actions/setup-java@v4
75 | with:
76 | distribution: 'zulu'
77 | java-version: |
78 | 8
79 | 21
80 | java-package: jdk
81 | - uses: actions/cache@v4
82 | id: gradle-cache
83 | with:
84 | path: ~/.gradle/caches
85 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/gradle/dependency-locks/*.lockfile') }}
86 | restore-keys: |
87 | - ${{ runner.os }}-gradle-
88 | - uses: actions/cache@v4
89 | id: gradle-wrapper-cache
90 | with:
91 | path: ~/.gradle/wrapper
92 | key: ${{ runner.os }}-gradlewrapper-${{ hashFiles('gradle/wrapper/*') }}
93 | restore-keys: |
94 | - ${{ runner.os }}-gradlewrapper-
95 | - name: Verify plugin publication
96 | if: |
97 | startsWith(github.ref, 'refs/tags/v') &&
98 | (!contains(github.ref, '-rc.'))
99 | run: ./gradlew --stacktrace -Dgradle.publish.key=${{ secrets.gradlePublishKey }} -Dgradle.publish.secret=${{ secrets.gradlePublishSecret }} -Prelease.useLastTag=true final publishPlugin --validate-only -x check -x signPluginMavenPublication
100 | publish:
101 | if: startsWith(github.ref, 'refs/tags/v')
102 | needs: validatepluginpublication
103 | runs-on: ubuntu-latest
104 | name: Gradle Build and Publish
105 | env:
106 | NETFLIX_OSS_SONATYPE_USERNAME: ${{ secrets.ORG_SONATYPE_USERNAME }}
107 | NETFLIX_OSS_SONATYPE_PASSWORD: ${{ secrets.ORG_SONATYPE_PASSWORD }}
108 | NETFLIX_OSS_SIGNING_KEY: ${{ secrets.ORG_SIGNING_KEY }}
109 | NETFLIX_OSS_SIGNING_PASSWORD: ${{ secrets.ORG_SIGNING_PASSWORD }}
110 | NETFLIX_OSS_REPO_USERNAME: ${{ secrets.ORG_NETFLIXOSS_USERNAME }}
111 | NETFLIX_OSS_REPO_PASSWORD: ${{ secrets.ORG_NETFLIXOSS_PASSWORD }}
112 | steps:
113 | - uses: actions/checkout@v1
114 | - name: Setup git user
115 | run: |
116 | git config --global user.name "Nebula Plugin Maintainers"
117 | git config --global user.email "nebula-plugins-oss@netflix.com"
118 | - name: Set up JDKs
119 | uses: actions/setup-java@v4
120 | with:
121 | distribution: 'zulu'
122 | java-version: |
123 | 8
124 | 21
125 | java-package: jdk
126 | - uses: actions/cache@v4
127 | id: gradle-cache
128 | with:
129 | path: ~/.gradle/caches
130 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/gradle/dependency-locks/*.lockfile') }}
131 | restore-keys: |
132 | - ${{ runner.os }}-gradle-
133 | - uses: actions/cache@v4
134 | id: gradle-wrapper-cache
135 | with:
136 | path: ~/.gradle/wrapper
137 | key: ${{ runner.os }}-gradlewrapper-${{ hashFiles('gradle/wrapper/*') }}
138 | restore-keys: |
139 | - ${{ runner.os }}-gradlewrapper-
140 | - name: Publish candidate
141 | if: |
142 | startsWith(github.ref, 'refs/tags/v') &&
143 | contains(github.ref, '-rc.')
144 | run: ./gradlew --info --stacktrace -Prelease.useLastTag=true candidate
145 | - name: Publish release
146 | if: |
147 | startsWith(github.ref, 'refs/tags/v') &&
148 | (!contains(github.ref, '-rc.'))
149 | run: ./gradlew --info --stacktrace -Dgradle.publish.key=${{ secrets.gradlePublishKey }} -Dgradle.publish.secret=${{ secrets.gradlePublishSecret }} -Prelease.useLastTag=true final
150 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 | .DS_Store
3 | generated/
4 | out/
5 | .gradle
6 | .idea
7 | *.ipr
8 | *.iml
9 | *.iws
10 |
--------------------------------------------------------------------------------
/.java-version:
--------------------------------------------------------------------------------
1 | 1.8
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | 2.5.2 / 2017/03/16
2 | ==================
3 | - Fix a memory leak in the gradle daemon caused by putting an object in a static context
4 |
5 | 2.5.1 / 2017/03/15
6 | ==================
7 | - Minor performance optimization to rule matching
8 |
9 | 2.5.0 / 2017/03/15
10 | ==================
11 | - Use `nebula.dependency-base` to add `dependencyInsightEnhanced` task to grant more insight into resolution rule choices
12 |
13 | 2.4.4 / 2017/03/10
14 | ==================
15 | - Use reflection when getting the event register to avoid an upcoming internal API change from a class -> interface causing `IncompatibleClassChangeError`
16 |
17 | 2.4.3 / 2017/03/08
18 | ==================
19 | - Improve performance of alignment by short-circuiting when all dependencies in a configuration have already been aligned either naturally, or via other plugins such as dependency lock
20 |
21 | 2.4.2 / 2017/02/28
22 | ==================
23 | - Fixed an issue that prevented rules from being applied to configurations created after the project was evaluated, causing global dependency locks not to be affected by rules.
24 |
25 | 2.4.1 / 2017/02/26
26 | ==================
27 | - Prevent beforeResolve rules from applying unless afterEvaluate rules have already been applied. Prevents 'already resolved' warnings for alignment configurations
28 |
29 | 2.4.0 / 2017/02/26
30 | ==================
31 | - Support version selectors (dynamic, range, latest.*) for reject rules
32 |
33 | 2.3.3 / 2017/02/24
34 | ==================
35 | - Fix multi-pass alignment where any unexpected resolved version would cause the second pass to be ineffective for all dependencies
36 | - Fix duplicate path separators in root project configuration names
37 |
38 | 2.3.2 / 2017/01/24
39 | ==================
40 | - Improve version selection for multi-pass alignment to ensure that it doesn't affect dependencies that were selected for reasons other than conflict resolution
41 |
42 | 2.3.1 / 2017/01/20
43 | ==================
44 | - No longer applies any rules to configurations with no dependencies, rather than just optimizing align rules
45 |
46 | 2.3.0 / 2017/01/19
47 | ==================
48 | - Improve performance by avoiding alignment where possible:
49 | - Skips configurations with no dependencies
50 | - Skips non-transitive configurations
51 | - Stops alignment after the baseline resolve, if there are no aligned dependencies
52 |
53 | 1.5.1 / 2016/05/12
54 | ==================
55 | - Protect against spring-boot plugin getting us into a stackoverflow situation
56 |
57 | 1.5.0 / 2016/05/12
58 | ==================
59 | - Align rules no longer replace changes made by other rule type (uses useVersion instead of useTarget).
60 |
61 | 1.4.0 / 2016/05/11
62 | ==================
63 | - Make it so we are not eagerly resolving the different configurations. Will only resolve when gradle resolves the configuration.
64 |
65 | 1.3.0 / 2016/04/28
66 | ==================
67 | - BUGFIX: Align rules attempt to align project dependencies, causing them to be resolved as remote artifacts
68 | - Rules files may now be optional, so optionated rules aren't applied without users opting in
69 | - Align rules support regular expressions in the group, includes and excludes
70 | - Empty rules types can be excluded from rules files (required for backwards compatibility with old rules files, but also makes working with them nicer)
71 |
72 | 1.2.2 / 2016/04/25
73 | ==================
74 | - BUGFIX: Handle circularish dependencies B depends on A for compile, A depends on B for testCompile
75 |
76 | 1.2.1 / 2016/04/19
77 | ==================
78 | - BUGFIX: Make sure resolutionRules configuration can be locked by nebula.dependency-lock
79 | - BUGFIX: Allow other changes to configurations.all and associated resolutionStrategy
80 |
81 | 1.2.0 / 2016/04/11
82 | ==================
83 | - Allow opt out of rules for shared company wide rules that apply to your project, e.g. there is a common align rule for a:foo and a:bar and you produce them
84 | - Performance improvement if there are multiple align rules
85 | - BUGFIX for unresolvable dependencies fixed by a resolution rule
86 |
87 | 1.1.5 / 2016/03/31
88 | ==================
89 | - Fix interaction bug with nebula.dependency-recommender (omitted versions causing issues)
90 | - Fix interaction bug with spring-boot plugin (omitted versions causing issues)
91 | - Fix handling of dependency graphs with cycles in them
92 |
93 | 1.1.4 / 2016/03/22
94 | ==================
95 | - Remove dependency on jackson libraries
96 |
97 | 1.1.3 / 2016/03/21
98 | ==================
99 | - Publish nebula.resolution-rules-producer to gradle plugin portal
100 |
101 | 1.1.2 / 2016/03/21
102 | ==================
103 | - Attempt to add nebula.resolution-rules-producer to gradle plugin portal
104 |
105 | 1.1.1 / 2016/03/21
106 | ==================
107 | - Fix publishing to bintray
108 |
109 | 1.1.0 / 2016/03/21
110 | ==================
111 | - Add in align rule
112 |
113 | 1.0.1 / 2015/10/28
114 | ==================
115 | - Re-publish due to initial JCenter sync failure
116 |
117 | 1.0.0 / 2015/10/28
118 | ==================
119 | - Initial Release
120 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Gradle Resolution Rules Plugin
2 |
3 | 
4 | [](https://plugins.gradle.org/plugin/com.netflix.nebula.resolution-rules)
5 | [](https://maven-badges.herokuapp.com/maven-central/com.netflix.nebula/gradle-resolution-rules-plugin)
6 | 
7 | [](http://www.apache.org/licenses/LICENSE-2.0)
8 |
9 |
10 | Gradle resolution strategies and module metadata provide an effective way to solve the most common dependency issues, however sharing these rules between projects is cumbersome, and requires custom plugins or `apply from` calls. This plugin provides general purpose rule types, allowing rules to be published, versioned, shared between projects, and optionally [dependency locked](https://github.com/nebula-plugins/gradle-dependency-lock-plugin).
11 |
12 | These rule types solve the most common cause of dependency issues in projects, including:
13 |
14 | - Duplicate classes caused by changes to group or artifact ids, without renaming packages
15 | - Duplicate classes caused by bundle dependencies, which do not conflict resolve against the normal dependencies for that library
16 | - Lack of version alignment between libraries, where version alignment is needed for compatibility
17 | - Ensuring a minimum version of a library
18 |
19 | # Quick Start
20 |
21 | Refer to the [Gradle Plugin Portal](https://plugins.gradle.org/plugin/nebula.resolution-rules) for instructions on how to apply the plugin.
22 |
23 | ## Open Source Rules
24 |
25 | We produce a rules for dependencies found in Maven Central and other public repositories, to use those rules in your project add the following to your root project:
26 |
27 | ```groovy
28 | allprojects {
29 | apply plugin: 'com.netflix.nebula.resolution-rules'
30 | }
31 |
32 | dependencies {
33 | resolutionRules 'com.netflix.nebula:gradle-resolution-rules:latest.release'
34 | }
35 | ```
36 |
37 | See the [gradle-resolution-rules](https://github.com/nebula-plugins/gradle-resolution-rules) project for details of the rules, and instructions on how to enable optional rule sets.
38 |
39 | # Documentation
40 |
41 | The project wiki contains the [full documentation](https://github.com/nebula-plugins/gradle-resolution-rules-plugin/wiki) for the plugin.
42 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2019 Netflix, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | plugins {
19 | id 'com.netflix.nebula.plugin-plugin' version '21.2.2'
20 | id "org.jetbrains.kotlin.jvm" version "2.1.0"
21 | id 'java-gradle-plugin'
22 | }
23 |
24 | dependencies {
25 | implementation platform("com.fasterxml.jackson:jackson-bom:2.9.10.+")
26 |
27 | implementation 'org.jetbrains.kotlin:kotlin-reflect'
28 | implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7' // Not a direct dependency but ensures alignment when we upgrade Kotlin
29 | implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' // Not a direct dependency but ensures alignment when we upgrade Kotlin
30 | implementation 'com.netflix.nebula:nebula-gradle-interop:latest.release'
31 | implementation 'com.fasterxml.jackson.module:jackson-module-kotlin'
32 | implementation 'com.netflix.nebula:nebula-dependency-recommender:latest.release'
33 |
34 | testImplementation gradleTestKit()
35 | testRuntimeOnly 'com.netflix.nebula:gradle-dependency-lock-plugin:latest.release'
36 | }
37 |
38 | apply from: 'gradle/idea.gradle'
39 |
40 | description 'Gradle resolution rules plugin'
41 |
42 | contacts {
43 | 'nebula-plugins-oss@netflix.com' {
44 | moniker 'Nebula Plugins Maintainers'
45 | github 'nebula-plugins'
46 | }
47 | }
48 |
49 |
50 |
51 | tasks.integrationTest {
52 | maxParallelForks = (int) (Runtime.getRuntime().availableProcessors() / 2 + 1)
53 | }
54 |
55 | gradlePlugin {
56 | plugins {
57 | resolutionRules {
58 | id = 'com.netflix.nebula.resolution-rules'
59 | implementationClass = 'nebula.plugin.resolutionrules.ResolutionRulesPlugin'
60 | displayName = 'Gradle Resolution Rules plugin'
61 | description = project.description
62 | tags.set(['nebula', 'resolve', 'resolution', 'rules'])
63 | }
64 | }
65 | }
66 |
67 | java {
68 | toolchain {
69 | languageVersion = JavaLanguageVersion.of(8)
70 | }
71 | }
72 |
73 | idea {
74 | project {
75 | jdkName = '1.8'
76 | languageLevel = '1.8'
77 | }
78 | }
79 |
80 |
81 | tasks.withType(GenerateModuleMetadata).configureEach {
82 | suppressedValidationErrors.add('enforced-platform')
83 | }
84 |
85 | javaCrossCompile {
86 | disableKotlinSupport = true
87 | }
88 |
--------------------------------------------------------------------------------
/gradle.lockfile:
--------------------------------------------------------------------------------
1 | # This is a Gradle generated file for dependency locking.
2 | # Manual edits can break the build and are not advised.
3 | # This file is expected to be part of source control.
4 | cglib:cglib-nodep:3.2.2=integTestRuntimeClasspath,testRuntimeClasspath
5 | com.fasterxml.jackson.core:jackson-annotations:2.9.10=compileClasspath,integTestCompileClasspath,integTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
6 | com.fasterxml.jackson.core:jackson-core:2.9.10=compileClasspath,integTestCompileClasspath,integTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
7 | com.fasterxml.jackson.core:jackson-databind:2.9.10.8=compileClasspath,integTestCompileClasspath,integTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
8 | com.fasterxml.jackson.module:jackson-module-kotlin:2.9.10=compileClasspath,integTestCompileClasspath,integTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
9 | com.fasterxml.jackson:jackson-bom:2.9.10.20210106=compileClasspath,integTestCompileClasspath,integTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
10 | com.google.guava:guava:20.0=integTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
11 | com.netflix.nebula:gradle-dependency-lock-plugin:15.1.0=integTestRuntimeClasspath,testRuntimeClasspath
12 | com.netflix.nebula:nebula-dependencies-comparison:0.2.1=integTestRuntimeClasspath,testRuntimeClasspath
13 | com.netflix.nebula:nebula-dependency-recommender:12.5.1=compileClasspath,integTestCompileClasspath,integTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
14 | com.netflix.nebula:nebula-gradle-interop:2.3.0=compileClasspath,integTestCompileClasspath,integTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
15 | com.netflix.nebula:nebula-test:10.6.2=integTestCompileClasspath,integTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
16 | com.squareup.moshi:moshi:1.12.0=integTestRuntimeClasspath,testRuntimeClasspath
17 | com.squareup.okio:okio:2.10.0=integTestRuntimeClasspath,testRuntimeClasspath
18 | javax.inject:javax.inject:1=integTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
19 | joda-time:joda-time:2.10=integTestRuntimeClasspath,testRuntimeClasspath
20 | junit:junit:4.13.2=integTestCompileClasspath,integTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
21 | org.apache.commons:commons-lang3:3.12.0=integTestRuntimeClasspath,testRuntimeClasspath
22 | org.apache.commons:commons-lang3:3.8.1=runtimeClasspath
23 | org.apache.maven:maven-artifact:3.8.3=integTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
24 | org.apache.maven:maven-builder-support:3.8.3=integTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
25 | org.apache.maven:maven-model-builder:3.8.3=integTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
26 | org.apache.maven:maven-model:3.8.3=integTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
27 | org.apiguardian:apiguardian-api:1.1.2=integTestCompileClasspath,integTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
28 | org.codehaus.groovy:groovy:3.0.12=integTestCompileClasspath,integTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
29 | org.codehaus.plexus:plexus-interpolation:1.26=integTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
30 | org.codehaus.plexus:plexus-utils:3.3.0=integTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
31 | org.eclipse.sisu:org.eclipse.sisu.inject:0.3.5=integTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
32 | org.hamcrest:hamcrest-core:1.3=integTestCompileClasspath,integTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
33 | org.hamcrest:hamcrest:2.2=integTestCompileClasspath,integTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
34 | org.jetbrains.kotlin:kotlin-reflect:2.1.0=compileClasspath,integTestCompileClasspath,integTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
35 | org.jetbrains.kotlin:kotlin-stdlib-common:1.4.20=integTestRuntimeClasspath,testRuntimeClasspath
36 | org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.1.0=compileClasspath,integTestCompileClasspath,integTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
37 | org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.1.0=compileClasspath,integTestCompileClasspath,integTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
38 | org.jetbrains.kotlin:kotlin-stdlib:2.1.0=compileClasspath,integTestCompileClasspath,integTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
39 | org.jetbrains:annotations:13.0=compileClasspath,integTestCompileClasspath,integTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
40 | org.junit.platform:junit-platform-commons:1.9.0=integTestCompileClasspath,integTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
41 | org.junit.platform:junit-platform-engine:1.9.0=integTestCompileClasspath,integTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
42 | org.objenesis:objenesis:2.4=integTestRuntimeClasspath,testRuntimeClasspath
43 | org.opentest4j:opentest4j:1.2.0=integTestCompileClasspath,integTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
44 | org.spockframework:spock-core:2.3-groovy-3.0=integTestCompileClasspath,integTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
45 | org.spockframework:spock-junit4:2.3-groovy-3.0=integTestCompileClasspath,integTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
46 | empty=annotationProcessor,compile,integTestAnnotationProcessor,integTestCompile,integTestRuntime,runtime,testAnnotationProcessor,testCompile,testRuntime
47 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | systemProp.nebula.features.coreLockingSupport=true
2 |
--------------------------------------------------------------------------------
/gradle/gradle-daemon-jvm.properties:
--------------------------------------------------------------------------------
1 | toolchainVersion=21
--------------------------------------------------------------------------------
/gradle/idea-codestyle.xml:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | GETTERS_AND_SETTERS
38 | KEEP
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 | xmlns:.*
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
--------------------------------------------------------------------------------
/gradle/idea-copyright.xml:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/gradle/idea-inspections.xml:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/gradle/idea.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Netflix, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | idea {
19 | project {
20 | def javaTarget = '1.7'
21 | jdkName = javaTarget
22 | languageLevel = javaTarget
23 |
24 | wildcards += 'logback.groovy'
25 |
26 | ipr {
27 | withXml { provider ->
28 | def node = provider.asNode()
29 |
30 | // Code styles
31 | def codestyle = new XmlParser().parse(file('gradle/idea-codestyle.xml'))
32 | node.append(codestyle)
33 |
34 | // Inspections
35 | def inspections = new XmlParser().parse(file('gradle/idea-inspections.xml'))
36 | node.append(inspections)
37 |
38 | // Copyright
39 | def copyrightManager = node.component.find { it.'@name' == 'CopyrightManager' }
40 | node.remove(copyrightManager)
41 | def copyright = new XmlParser().parse(file('gradle/idea-copyright.xml'))
42 | node.append(copyright)
43 |
44 | // VCS mappings
45 | def vcsDirectoryMappings = node.component.find { it.'@name' == 'VcsDirectoryMappings' }
46 | def mappings = vcsDirectoryMappings.iterator()
47 | while (mappings.hasNext()) {
48 | mappings.next()
49 | mappings.remove()
50 | }
51 |
52 | def gitRoot = file('.git')
53 | if (gitRoot.exists()) {
54 | vcsDirectoryMappings.appendNode('mapping', ['directory': gitRoot.parentFile, 'vcs': 'Git'])
55 | }
56 |
57 | // Annotation processing
58 | node.component.find { it.@name == 'CompilerConfiguration' }['annotationProcessing'][0].replaceNode {
59 | annotationProcessing {
60 | profile(default: true, name: 'Default', useClasspath: 'true', enabled: true) {
61 | outputRelativeToContentRoot(value: true)
62 | processorPath(useClasspath: true)
63 | }
64 | }
65 | }
66 | }
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nebula-plugins/gradle-resolution-rules-plugin/ef67ba983c4364d9a20192506eb124875416f93c/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionSha256Sum=7197a12f450794931532469d4ff21a59ea2c1cd59a3ec3f89c035c3c420a6999
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
5 | networkTimeout=10000
6 | validateDistributionUrl=true
7 | zipStoreBase=GRADLE_USER_HOME
8 | zipStorePath=wrapper/dists
9 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 | # SPDX-License-Identifier: Apache-2.0
19 | #
20 |
21 | ##############################################################################
22 | #
23 | # Gradle start up script for POSIX generated by Gradle.
24 | #
25 | # Important for running:
26 | #
27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
28 | # noncompliant, but you have some other compliant shell such as ksh or
29 | # bash, then to run this script, type that shell name before the whole
30 | # command line, like:
31 | #
32 | # ksh Gradle
33 | #
34 | # Busybox and similar reduced shells will NOT work, because this script
35 | # requires all of these POSIX shell features:
36 | # * functions;
37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
39 | # * compound commands having a testable exit status, especially «case»;
40 | # * various built-in commands including «command», «set», and «ulimit».
41 | #
42 | # Important for patching:
43 | #
44 | # (2) This script targets any POSIX shell, so it avoids extensions provided
45 | # by Bash, Ksh, etc; in particular arrays are avoided.
46 | #
47 | # The "traditional" practice of packing multiple parameters into a
48 | # space-separated string is a well documented source of bugs and security
49 | # problems, so this is (mostly) avoided, by progressively accumulating
50 | # options in "$@", and eventually passing that to Java.
51 | #
52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
54 | # see the in-line comments for details.
55 | #
56 | # There are tweaks for specific operating systems such as AIX, CygWin,
57 | # Darwin, MinGW, and NonStop.
58 | #
59 | # (3) This script is generated from the Groovy template
60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
61 | # within the Gradle project.
62 | #
63 | # You can find Gradle at https://github.com/gradle/gradle/.
64 | #
65 | ##############################################################################
66 |
67 | # Attempt to set APP_HOME
68 |
69 | # Resolve links: $0 may be a link
70 | app_path=$0
71 |
72 | # Need this for daisy-chained symlinks.
73 | while
74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
75 | [ -h "$app_path" ]
76 | do
77 | ls=$( ls -ld "$app_path" )
78 | link=${ls#*' -> '}
79 | case $link in #(
80 | /*) app_path=$link ;; #(
81 | *) app_path=$APP_HOME$link ;;
82 | esac
83 | done
84 |
85 | # This is normally unused
86 | # shellcheck disable=SC2034
87 | APP_BASE_NAME=${0##*/}
88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
90 |
91 | # Use the maximum available, or set MAX_FD != -1 to use that value.
92 | MAX_FD=maximum
93 |
94 | warn () {
95 | echo "$*"
96 | } >&2
97 |
98 | die () {
99 | echo
100 | echo "$*"
101 | echo
102 | exit 1
103 | } >&2
104 |
105 | # OS specific support (must be 'true' or 'false').
106 | cygwin=false
107 | msys=false
108 | darwin=false
109 | nonstop=false
110 | case "$( uname )" in #(
111 | CYGWIN* ) cygwin=true ;; #(
112 | Darwin* ) darwin=true ;; #(
113 | MSYS* | MINGW* ) msys=true ;; #(
114 | NONSTOP* ) nonstop=true ;;
115 | esac
116 |
117 | CLASSPATH="\\\"\\\""
118 |
119 |
120 | # Determine the Java command to use to start the JVM.
121 | if [ -n "$JAVA_HOME" ] ; then
122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
123 | # IBM's JDK on AIX uses strange locations for the executables
124 | JAVACMD=$JAVA_HOME/jre/sh/java
125 | else
126 | JAVACMD=$JAVA_HOME/bin/java
127 | fi
128 | if [ ! -x "$JAVACMD" ] ; then
129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
130 |
131 | Please set the JAVA_HOME variable in your environment to match the
132 | location of your Java installation."
133 | fi
134 | else
135 | JAVACMD=java
136 | if ! command -v java >/dev/null 2>&1
137 | then
138 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
139 |
140 | Please set the JAVA_HOME variable in your environment to match the
141 | location of your Java installation."
142 | fi
143 | fi
144 |
145 | # Increase the maximum file descriptors if we can.
146 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
147 | case $MAX_FD in #(
148 | max*)
149 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
150 | # shellcheck disable=SC2039,SC3045
151 | MAX_FD=$( ulimit -H -n ) ||
152 | warn "Could not query maximum file descriptor limit"
153 | esac
154 | case $MAX_FD in #(
155 | '' | soft) :;; #(
156 | *)
157 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
158 | # shellcheck disable=SC2039,SC3045
159 | ulimit -n "$MAX_FD" ||
160 | warn "Could not set maximum file descriptor limit to $MAX_FD"
161 | esac
162 | fi
163 |
164 | # Collect all arguments for the java command, stacking in reverse order:
165 | # * args from the command line
166 | # * the main class name
167 | # * -classpath
168 | # * -D...appname settings
169 | # * --module-path (only if needed)
170 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
171 |
172 | # For Cygwin or MSYS, switch paths to Windows format before running java
173 | if "$cygwin" || "$msys" ; then
174 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
175 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
176 |
177 | JAVACMD=$( cygpath --unix "$JAVACMD" )
178 |
179 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
180 | for arg do
181 | if
182 | case $arg in #(
183 | -*) false ;; # don't mess with options #(
184 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
185 | [ -e "$t" ] ;; #(
186 | *) false ;;
187 | esac
188 | then
189 | arg=$( cygpath --path --ignore --mixed "$arg" )
190 | fi
191 | # Roll the args list around exactly as many times as the number of
192 | # args, so each arg winds up back in the position where it started, but
193 | # possibly modified.
194 | #
195 | # NB: a `for` loop captures its iteration list before it begins, so
196 | # changing the positional parameters here affects neither the number of
197 | # iterations, nor the values presented in `arg`.
198 | shift # remove old arg
199 | set -- "$@" "$arg" # push replacement arg
200 | done
201 | fi
202 |
203 |
204 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
205 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
206 |
207 | # Collect all arguments for the java command:
208 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
209 | # and any embedded shellness will be escaped.
210 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
211 | # treated as '${Hostname}' itself on the command line.
212 |
213 | set -- \
214 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
215 | -classpath "$CLASSPATH" \
216 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
217 | "$@"
218 |
219 | # Stop when "xargs" is not available.
220 | if ! command -v xargs >/dev/null 2>&1
221 | then
222 | die "xargs is not available"
223 | fi
224 |
225 | # Use "xargs" to parse quoted args.
226 | #
227 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
228 | #
229 | # In Bash we could simply go:
230 | #
231 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
232 | # set -- "${ARGS[@]}" "$@"
233 | #
234 | # but POSIX shell has neither arrays nor command substitution, so instead we
235 | # post-process each arg (as a line of input to sed) to backslash-escape any
236 | # character that might be a shell metacharacter, then use eval to reverse
237 | # that process (while maintaining the separation between arguments), and wrap
238 | # the whole thing up as a single "set" statement.
239 | #
240 | # This will of course break if any of these variables contains a newline or
241 | # an unmatched quote.
242 | #
243 |
244 | eval "set -- $(
245 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
246 | xargs -n1 |
247 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
248 | tr '\n' ' '
249 | )" '"$@"'
250 |
251 | exec "$JAVACMD" "$@"
252 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 | @rem SPDX-License-Identifier: Apache-2.0
17 | @rem
18 |
19 | @if "%DEBUG%"=="" @echo off
20 | @rem ##########################################################################
21 | @rem
22 | @rem Gradle startup script for Windows
23 | @rem
24 | @rem ##########################################################################
25 |
26 | @rem Set local scope for the variables with windows NT shell
27 | if "%OS%"=="Windows_NT" setlocal
28 |
29 | set DIRNAME=%~dp0
30 | if "%DIRNAME%"=="" set DIRNAME=.
31 | @rem This is normally unused
32 | set APP_BASE_NAME=%~n0
33 | set APP_HOME=%DIRNAME%
34 |
35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
37 |
38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
40 |
41 | @rem Find java.exe
42 | if defined JAVA_HOME goto findJavaFromJavaHome
43 |
44 | set JAVA_EXE=java.exe
45 | %JAVA_EXE% -version >NUL 2>&1
46 | if %ERRORLEVEL% equ 0 goto execute
47 |
48 | echo. 1>&2
49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
50 | echo. 1>&2
51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
52 | echo location of your Java installation. 1>&2
53 |
54 | goto fail
55 |
56 | :findJavaFromJavaHome
57 | set JAVA_HOME=%JAVA_HOME:"=%
58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
59 |
60 | if exist "%JAVA_EXE%" goto execute
61 |
62 | echo. 1>&2
63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
64 | echo. 1>&2
65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
66 | echo location of your Java installation. 1>&2
67 |
68 | goto fail
69 |
70 | :execute
71 | @rem Setup the command line
72 |
73 | set CLASSPATH=
74 |
75 |
76 | @rem Execute Gradle
77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
78 |
79 | :end
80 | @rem End local scope for the variables with windows NT shell
81 | if %ERRORLEVEL% equ 0 goto mainEnd
82 |
83 | :fail
84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
85 | rem the _cmd.exe /c_ return code!
86 | set EXIT_CODE=%ERRORLEVEL%
87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
89 | exit /b %EXIT_CODE%
90 |
91 | :mainEnd
92 | if "%OS%"=="Windows_NT" endlocal
93 |
94 | :omega
95 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.gradle.develocity' version '3.19'
3 | }
4 |
5 | develocity {
6 | buildScan {
7 | termsOfUseUrl = "https://gradle.com/help/legal-terms-of-use"
8 | termsOfUseAgree = 'yes'
9 | }
10 | }
11 |
12 | rootProject.name = 'gradle-resolution-rules-plugin'
13 |
--------------------------------------------------------------------------------
/src/integTest/groovy/nebula/plugin/resolutionrules/AbstractAlignAndMigrateSpec.groovy:
--------------------------------------------------------------------------------
1 | package nebula.plugin.resolutionrules
2 |
3 | import nebula.test.dependencies.DependencyGraphBuilder
4 | import nebula.test.dependencies.GradleDependencyGenerator
5 | import nebula.test.dependencies.ModuleBuilder
6 |
7 | /*
8 | Used to verify behavior when a dependency brings in its replacement as its only direct dependency
9 | Example:
10 | foo:bar:1.2.3 is the latest release of foo:bar that brings in its new coordinates at
11 | better-foo:better-bar:2.0.0
12 | We want to make sure that alignment still takes place for all dependencies in better-foo:better-bar
13 | */
14 | class AbstractAlignAndMigrateSpec extends AbstractAlignRulesSpec {
15 | String alignedVersion = '1.0.3'
16 | File mavenrepo
17 |
18 | def setup() {
19 | createTestDependencies()
20 | buildFile << """
21 | repositories {
22 | maven { url = '${projectDir.toPath().relativize(mavenrepo.toPath()).toFile()}' }
23 | }
24 | dependencies {
25 | implementation 'test.nebula:a:1.0.0'
26 | implementation 'test.nebula:b:1.0.3'
27 | implementation 'other:e:4.0.0'
28 | }
29 | """.stripIndent()
30 | }
31 |
32 | private void createTestDependencies() {
33 | def graph = new DependencyGraphBuilder()
34 | .addModule('test.nebula:a:1.0.0')
35 | .addModule('test.nebula:a:1.0.1')
36 | .addModule('test.nebula:a:1.0.2')
37 | .addModule('test.nebula:a:1.0.3')
38 |
39 | .addModule('test.nebula:b:1.0.0')
40 | .addModule('test.nebula:b:1.0.1')
41 | .addModule('test.nebula:b:1.0.2')
42 | .addModule('test.nebula:b:1.0.3')
43 |
44 | .addModule('test.nebula:c:1.0.0')
45 | .addModule('test.nebula:c:1.0.1')
46 | .addModule('test.nebula:c:1.0.2')
47 | .addModule('test.nebula:c:1.0.3')
48 |
49 | .addModule(new ModuleBuilder('other:e:4.0.0').addDependency('test.nebula:c:1.0.1').build()) // this is the most interesting dependency under test
50 |
51 | .build()
52 | mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo()
53 | }
54 |
55 | Collection dependencyInsightTasks() {
56 | return ['dependencyInsight', '--dependency', 'test.nebula']
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/integTest/groovy/nebula/plugin/resolutionrules/AbstractAlignRulesSpec.groovy:
--------------------------------------------------------------------------------
1 | package nebula.plugin.resolutionrules
2 |
3 |
4 | abstract class AbstractAlignRulesSpec extends AbstractIntegrationTestKitSpec {
5 | def rulesJsonFile
6 |
7 | def setup() {
8 | rulesJsonFile = new File(projectDir, "${moduleName}.json")
9 | buildFile << """\
10 | plugins {
11 | id 'com.netflix.nebula.resolution-rules'
12 | id 'java'
13 | }
14 | dependencies {
15 | resolutionRules files('$rulesJsonFile')
16 | }
17 | """.stripIndent()
18 | }
19 |
20 | protected def createAlignAndReplaceRules(Map modulesAndReplacements) {
21 | String reason = "★ custom replacement reason"
22 | rulesJsonFile << """
23 | {
24 | "replace": [
25 | """.stripIndent()
26 |
27 | List replacements = new ArrayList<>()
28 | modulesAndReplacements.each { module, with ->
29 | replacements.add(""" {
30 | "module" : "$module",
31 | "with" : "$with",
32 | "reason" : "$reason",
33 | "author" : "Test user ",
34 | "date" : "2020-02-27T10:31:14.321Z"
35 | }""")
36 |
37 | }
38 | rulesJsonFile << replacements.join(',')
39 |
40 | rulesJsonFile << """
41 | ],
42 | "align": [
43 | {
44 | "group": "(test.nebula|test.nebula.ext)",
45 | "reason": "Align test.nebula dependencies",
46 | "author": "Example Person ",
47 | "date": "2020-02-27T10:31:14.321Z"
48 | }
49 | ]
50 | }
51 | """.stripIndent()
52 | }
53 |
54 | protected def createAlignAndSubstituteRules(Map modulesAndSubstitutions) {
55 | String reason = "★ custom substitution reason"
56 | rulesJsonFile << """
57 | {
58 | "substitute": [
59 | """.stripIndent()
60 |
61 | List substitutions = new ArrayList<>()
62 | modulesAndSubstitutions.each { module, with ->
63 | substitutions.add(""" {
64 | "module" : "$module",
65 | "with" : "$with",
66 | "reason" : "$reason",
67 | "author" : "Test user ",
68 | "date" : "2020-02-27T10:31:14.321Z"
69 | }""")
70 | }
71 |
72 | rulesJsonFile << substitutions.join(',')
73 |
74 | rulesJsonFile << """
75 | ],
76 | "align": [
77 | {
78 | "group": "(test.nebula|test.nebula.ext)",
79 | "reason": "Align test.nebula dependencies",
80 | "author": "Example Person ",
81 | "date": "2020-02-27T10:31:14.321Z"
82 | }
83 | ]
84 | }
85 | """.stripIndent()
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/integTest/groovy/nebula/plugin/resolutionrules/AbstractIntegrationTestKitSpec.groovy:
--------------------------------------------------------------------------------
1 | package nebula.plugin.resolutionrules
2 |
3 | import nebula.test.IntegrationTestKitSpec
4 |
5 | abstract class AbstractIntegrationTestKitSpec extends IntegrationTestKitSpec {
6 | def setup() {
7 | // Enable configuration cache :)
8 | new File(projectDir, 'gradle.properties') << '''org.gradle.configuration-cache=true'''.stripIndent()
9 | }
10 |
11 |
12 | void disableConfigurationCache() {
13 | def propertiesFile = new File(projectDir, 'gradle.properties')
14 | if(propertiesFile.exists()) {
15 | propertiesFile.delete()
16 | }
17 | propertiesFile.createNewFile()
18 | propertiesFile << '''org.gradle.configuration-cache=false'''.stripIndent()
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/integTest/groovy/nebula/plugin/resolutionrules/AbstractRulesWithSpringBootPluginSpec.groovy:
--------------------------------------------------------------------------------
1 | package nebula.plugin.resolutionrules
2 |
3 |
4 | class AbstractRulesWithSpringBootPluginSpec extends AbstractIntegrationTestKitSpec {
5 | File rulesJsonFile
6 |
7 | def setup() {
8 | rulesJsonFile = new File(projectDir, "rules.json")
9 | keepFiles = true
10 | }
11 |
12 | static void dependencyInsightContains(String resultOutput, String groupAndName, String resultingVersion) {
13 | def content = "$groupAndName:.*$resultingVersion\n"
14 | assert resultOutput.findAll(content).size() >= 1
15 | }
16 |
17 | File addSpringDependenciesWhenUsingManagedDependencies(String requestedVersion) {
18 | buildFile << """
19 | dependencies {
20 | implementation "org.springframework:spring-core$requestedVersion"
21 | implementation "org.springframework.boot:spring-boot-starter"
22 | implementation "org.springframework.boot:spring-boot-starter-web"
23 | }
24 | """.stripIndent()
25 | }
26 |
27 | static def tasks(String groupForInsight = 'org.springframework:') {
28 | return [
29 | 'dependencyInsight',
30 | '--dependency',
31 | groupForInsight, '-s'
32 | ]
33 | }
34 |
35 | void setupForDirectDependencyScenario(String extSpringBootVersion, String forcedVersion, String additionalPlugin = '', String additionalExtProperty = '') {
36 | setupBaseSpringBootBasedBuildFileWith(extSpringBootVersion, additionalPlugin, additionalExtProperty)
37 |
38 | if (forcedVersion != '' && forcedVersion != null) {
39 | buildFile << """
40 | configurations.all {
41 | resolutionStrategy {
42 | force "org.springframework:spring-aop:$forcedVersion"
43 | force "org.springframework:spring-beans:$forcedVersion"
44 | force "org.springframework:spring-context:$forcedVersion"
45 | force "org.springframework:spring-core:$forcedVersion"
46 | force "org.springframework:spring-expression:$forcedVersion"
47 | force "org.springframework:spring-web:$forcedVersion"
48 | force "org.springframework:spring-webmvc:$forcedVersion"
49 | }
50 | }
51 | """.stripIndent()
52 | }
53 |
54 | rulesJsonFile << alignSpringRule()
55 | }
56 |
57 | void setupForTransitiveDependencyScenario(String extSpringBootVersion, String forcedVersion, String additionalPlugin = '', String additionalExtProperty = '') {
58 | setupBaseSpringBootBasedBuildFileWith(extSpringBootVersion, additionalPlugin, additionalExtProperty)
59 |
60 | if (forcedVersion != '' && forcedVersion != null) {
61 | buildFile << """
62 | configurations.all {
63 | resolutionStrategy {
64 | force "org.slf4j:slf4j-simple:$forcedVersion"
65 | force "org.slf4j:slf4j-api:$forcedVersion"
66 | }
67 | }
68 | """.stripIndent()
69 | }
70 |
71 | rulesJsonFile << alignSlf4jRule()
72 | }
73 |
74 | private File setupBaseSpringBootBasedBuildFileWith(String extSpringBootVersion, String additionalPlugin = '', String additionalExtProperty = '') {
75 | buildFile << """
76 | buildscript {
77 | dependencies {
78 | classpath("org.springframework.boot:spring-boot-gradle-plugin:$extSpringBootVersion")
79 | classpath "io.spring.gradle:dependency-management-plugin:1.1.0"
80 | }
81 | repositories {
82 | maven {
83 | url = "https://plugins.gradle.org/m2/"
84 | }
85 | }
86 | }
87 | plugins {
88 | id 'java'
89 | id 'com.netflix.nebula.resolution-rules'
90 | }
91 | apply plugin: 'org.springframework.boot'$additionalPlugin
92 | repositories {
93 | mavenCentral()
94 | }
95 | dependencies {
96 | resolutionRules files('$rulesJsonFile')
97 | }
98 | ext {$additionalExtProperty
99 | }
100 | """.stripIndent()
101 |
102 | }
103 |
104 | private static String alignSpringRule() {
105 | """
106 | {
107 | "align": [
108 | {
109 | "group": "org\\\\.springframework",
110 | "includes": ["spring-(tx|aop|instrument|context-support|beans|jms|test|core|oxm|web|context|expression|aspects|websocket|framework-bom|webmvc|webmvc-portlet|jdbc|orm|instrument-tomcat|messaging)"],
111 | "excludes": [],
112 | "match": "[2-9]\\\\.[0-9]+\\\\.[0-9]+.RELEASE",
113 | "reason": "Align Spring",
114 | "author": "User ",
115 | "date": "2016-05-16"
116 | }
117 | ]
118 | }
119 | """.stripIndent()
120 | }
121 |
122 | private static String alignSlf4jRule() {
123 | """
124 | {
125 | "align": [
126 | {
127 | "name": "align slf4j",
128 | "group": "org.slf4j",
129 | "reason": "Align slf4j",
130 | "author": "User ",
131 | "date": "2016-05-16"
132 | }
133 | ]
134 | }
135 | """.stripIndent()
136 | }
137 |
138 | void writeOutputToProjectDir(String output) {
139 | def file = new File(projectDir, "result.txt")
140 | file.createNewFile()
141 | file << output
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/src/integTest/groovy/nebula/plugin/resolutionrules/AlignAndMigrateViaReplacementSpec.groovy:
--------------------------------------------------------------------------------
1 | package nebula.plugin.resolutionrules
2 |
3 |
4 | import spock.lang.Unroll
5 |
6 | class AlignAndMigrateViaReplacementSpec extends AbstractAlignAndMigrateSpec {
7 | @Unroll
8 | def 'align and migrate via replacement'() {
9 | given:
10 | createAlignAndReplaceRules(['other:e': 'test.nebula:c'])
11 |
12 | when:
13 | def tasks = ['dependencyInsight', '--dependency', 'test.nebula']
14 | def results = runTasks(*tasks)
15 | then:
16 | results.output.contains("test.nebula:a:1.0.0 -> $alignedVersion")
17 | results.output.contains("test.nebula:b:$alignedVersion")
18 | results.output.contains("other:e:4.0.0 -> test.nebula:c:$alignedVersion")
19 | results.output.contains("belongs to platform aligned-platform")
20 |
21 | when:
22 | def dependenciesTasks = ['dependencies', '--configuration', 'compileClasspath']
23 | def resultsForDependencies = runTasks(*dependenciesTasks)
24 |
25 | then:
26 | resultsForDependencies.output.contains("other:e:4.0.0 -> test.nebula:c:1.0.3")
27 | }
28 |
29 | @Unroll
30 | def 'align and migrate via replacement with brought in dependency as direct as well'() {
31 | given:
32 | createAlignAndReplaceRules(['other:e': 'test.nebula:c'])
33 | buildFile << """
34 | dependencies {
35 | implementation 'test.nebula:c:1.0.1'
36 | }
37 | """
38 |
39 | when:
40 | def tasks = ['dependencyInsight', '--dependency', 'test.nebula']
41 | def results = runTasks(*tasks)
42 | then:
43 | results.output.contains("test.nebula:a:1.0.0 -> $alignedVersion")
44 | results.output.contains("test.nebula:b:$alignedVersion")
45 | results.output.contains("other:e:4.0.0 -> test.nebula:c:$alignedVersion")
46 | results.output.contains("belongs to platform aligned-platform")
47 |
48 |
49 | when:
50 | def dependenciesTasks = ['dependencies', '--configuration', 'compileClasspath']
51 | def resultsForDependencies = runTasks(*dependenciesTasks)
52 |
53 | then:
54 | resultsForDependencies.output.contains("other:e:4.0.0 -> test.nebula:c:$alignedVersion")
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/integTest/groovy/nebula/plugin/resolutionrules/AlignAndMigrateViaSubstitutionSpec.groovy:
--------------------------------------------------------------------------------
1 | package nebula.plugin.resolutionrules
2 |
3 |
4 | import spock.lang.Unroll
5 |
6 | class AlignAndMigrateViaSubstitutionSpec extends AbstractAlignAndMigrateSpec {
7 | @Unroll
8 | def 'substitution and alignment'() {
9 | given:
10 | createAlignAndSubstituteRules(['other:e:4.0.0': 'test.nebula:c:1.0.1'])
11 |
12 | when:
13 | def results = runTasks(*dependencyInsightTasks())
14 |
15 | then:
16 | results.output.contains("test.nebula:a:1.0.0 -> $alignedVersion")
17 | results.output.contains("test.nebula:b:$alignedVersion")
18 | results.output.contains("other:e:4.0.0 -> test.nebula:c:$alignedVersion")
19 | results.output.contains("substituted other:e:4.0.0 with test.nebula:c:1.0.1 because '★ custom substitution reason'")
20 | results.output.contains("belongs to platform aligned-platform")
21 |
22 |
23 | when:
24 | def dependenciesTasks = ['dependencies', '--configuration', 'compileClasspath']
25 | def resultsForDependencies = runTasks(*dependenciesTasks)
26 |
27 | then:
28 | resultsForDependencies.output.contains("other:e:4.0.0 -> test.nebula:c:$alignedVersion")
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/integTest/groovy/nebula/plugin/resolutionrules/AlignAndSubstituteRulesWithSpringBoot2xPluginWithoutManagedDepsSpec.groovy:
--------------------------------------------------------------------------------
1 | package nebula.plugin.resolutionrules
2 |
3 |
4 | import spock.lang.Unroll
5 |
6 | class AlignAndSubstituteRulesWithSpringBoot2xPluginWithoutManagedDepsSpec extends AbstractRulesWithSpringBootPluginSpec {
7 | File rulesJsonFile
8 |
9 | def setup() {
10 | rulesJsonFile = new File(projectDir, "rules.json")
11 | keepFiles = true
12 | System.setProperty('ignoreDeprecations', 'true')
13 | }
14 |
15 | @Unroll
16 | def 'direct dep | with lower requested version'() {
17 | given:
18 | // in Spring Boot 2.x plugin, the `io.spring.dependency-management` plugin is added for dependency management. We are not including it here.
19 | setupForDirectDependencyScenario(extSpringBootVersion, forcedVersion, '',
20 | "\n\tspringVersion = \"$extSpringVersion\"")
21 | buildFile << """
22 | dependencies {
23 | implementation "org.springframework:spring-core$requestedVersion"
24 | implementation "org.springframework.boot:spring-boot-starter:$extSpringBootVersion"
25 | implementation "org.springframework.boot:spring-boot-starter-web:$extSpringBootVersion"
26 | }
27 | """.stripIndent()
28 |
29 | when:
30 | def result = runTasks(*tasks())
31 | def output = result.output
32 |
33 | then:
34 | writeOutputToProjectDir(output)
35 | dependencyInsightContains(output, 'org.springframework:spring-aop', managedSpringVersion)
36 | dependencyInsightContains(output, 'org.springframework:spring-beans', managedSpringVersion)
37 | dependencyInsightContains(output, 'org.springframework:spring-expression', managedSpringVersion)
38 | dependencyInsightContains(output, 'org.springframework:spring-core', managedSpringVersion)
39 |
40 | where:
41 | extSpringVersion = '4.2.4.RELEASE'
42 | extSpringBootVersion = '2.7.0'
43 | managedSpringVersion = '5.3.20' // from https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-dependencies/5.3.20/spring-boot-dependencies-5.3.20.pom
44 |
45 | requestedVersion = ':\${springVersion}'
46 | forcedVersion = ''
47 | }
48 |
49 | @Unroll
50 | def 'direct dep | with higher requested version'() {
51 | given:
52 | // in Spring Boot 2.x plugin, the `io.spring.dependency-management` plugin is added for dependency management. We are not including it here.
53 | setupForDirectDependencyScenario(extSpringBootVersion, forcedVersion, '',
54 | "\n\tspringVersion = \"$extSpringVersion\"")
55 | buildFile << """
56 | dependencies {
57 | implementation "org.springframework:spring-core$requestedVersion"
58 | implementation "org.springframework.boot:spring-boot-starter:$extSpringBootVersion"
59 | implementation "org.springframework.boot:spring-boot-starter-web:$extSpringBootVersion"
60 | }
61 | """.stripIndent()
62 |
63 | when:
64 | def result = runTasks(*tasks())
65 | def output = result.output
66 |
67 | then:
68 | writeOutputToProjectDir(output)
69 | dependencyInsightContains(output, 'org.springframework:spring-aop', extSpringVersion)
70 | dependencyInsightContains(output, 'org.springframework:spring-beans', extSpringVersion)
71 | dependencyInsightContains(output, 'org.springframework:spring-expression', extSpringVersion)
72 | dependencyInsightContains(output, 'org.springframework:spring-core', extSpringVersion)
73 |
74 | where:
75 | extSpringVersion = '5.3.24'
76 | extSpringBootVersion = '2.7.0'
77 | managedSpringVersion = '5.3.20' // from https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-dependencies/5.3.20/spring-boot-dependencies-5.3.20.pom
78 |
79 | requestedVersion = ':\${springVersion}'
80 | forcedVersion = '' }
81 |
82 | @Unroll
83 | def 'direct dep | with requested version and forced'() {
84 | given:
85 | // in Spring Boot 2.x plugin, the `io.spring.dependency-management` plugin is added for dependency management. We are not including it here.
86 | setupForDirectDependencyScenario(extSpringBootVersion, forcedVersion, '',
87 | "\n\tspringVersion = \"$extSpringVersion\"")
88 | buildFile << """
89 | dependencies {
90 | implementation "org.springframework:spring-core$requestedVersion"
91 | implementation "org.springframework.boot:spring-boot-starter:$extSpringBootVersion"
92 | implementation "org.springframework.boot:spring-boot-starter-web:$extSpringBootVersion"
93 | }
94 | """.stripIndent()
95 |
96 | when:
97 | def result = runTasks(*tasks())
98 | def output = result.output
99 |
100 | then:
101 | writeOutputToProjectDir(output)
102 | dependencyInsightContains(output, 'org.springframework:spring-aop', forcedVersion)
103 | dependencyInsightContains(output, 'org.springframework:spring-beans', forcedVersion)
104 | dependencyInsightContains(output, 'org.springframework:spring-expression', forcedVersion)
105 | dependencyInsightContains(output, 'org.springframework:spring-core', forcedVersion)
106 |
107 | where:
108 | extSpringVersion = '4.2.4.RELEASE'
109 | extSpringBootVersion = '2.7.0'
110 |
111 | requestedVersion = ':\${springVersion}'
112 | forcedVersion = '4.2.4.RELEASE'
113 | }
114 |
115 | @Unroll
116 | def 'transitive dep | with requested version'() {
117 | given:
118 | // in Spring Boot 2.x plugin, the `io.spring.dependency-management` plugin is added for dependency management. We are not including it here.
119 | setupForTransitiveDependencyScenario(extSpringBootVersion, forcedVersion, '',
120 | "\n\tslf4jVersion = \"$extSlf4jVersion\"")
121 | buildFile << """
122 | dependencies {
123 | implementation "org.slf4j:slf4j-simple$requestedVersion"
124 | }
125 | """.stripIndent()
126 |
127 | when:
128 | def result = runTasks(*tasks('org.slf4j'))
129 | def output = result.output
130 |
131 | then:
132 | writeOutputToProjectDir(output)
133 | dependencyInsightContains(output, 'org.slf4j:slf4j-simple', extSlf4jVersion)
134 | dependencyInsightContains(output, 'org.slf4j:slf4j-api', extSlf4jVersion)
135 |
136 | where:
137 | extSpringVersion = '4.2.4.RELEASE'
138 | extSpringBootVersion = '2.7.0'
139 | extSlf4jVersion = '1.6.0'
140 |
141 | requestedVersion = ':\$slf4jVersion'
142 | forcedVersion = ''
143 | }
144 |
145 | @Unroll
146 | def 'transitive dep | without requested version and forced'() {
147 | given:
148 | // in Spring Boot 2.x plugin, the `io.spring.dependency-management` plugin is added for dependency management. We are not including it here.
149 | setupForTransitiveDependencyScenario(extSpringBootVersion, forcedVersion, '',
150 | "\n\tslf4jVersion = \"$extSlf4jVersion\"")
151 | buildFile << """
152 | dependencies {
153 | implementation "org.slf4j:slf4j-simple$requestedVersion"
154 | }
155 | """.stripIndent()
156 |
157 | when:
158 | def result = runTasks(*tasks('org.slf4j'))
159 | def output = result.output
160 |
161 | then:
162 | writeOutputToProjectDir(output)
163 | dependencyInsightContains(output, 'org.slf4j:slf4j-simple', forcedVersion)
164 | dependencyInsightContains(output, 'org.slf4j:slf4j-api', forcedVersion)
165 |
166 | where:
167 | extSpringVersion = '4.2.4.RELEASE'
168 | extSpringBootVersion = '2.7.0'
169 | extSlf4jVersion = '1.6.0'
170 |
171 | requestedVersion = ''
172 | forcedVersion = '1.7.10'
173 | }
174 |
175 | @Unroll
176 | def 'transitive dep | with lower requested version and forced to different version'() {
177 | given:
178 | // in Spring Boot 2.x plugin, the `io.spring.dependency-management` plugin is added for dependency management. We are not including it here.
179 | setupForTransitiveDependencyScenario(extSpringBootVersion, forcedVersion, '',
180 | "\n\tslf4jVersion = \"$extSlf4jVersion\"")
181 | buildFile << """
182 | dependencies {
183 | implementation "org.slf4j:slf4j-simple$requestedVersion"
184 | }
185 | """.stripIndent()
186 |
187 | when:
188 | def result = runTasks(*tasks('org.slf4j'))
189 | def output = result.output
190 |
191 | then:
192 | writeOutputToProjectDir(output)
193 | dependencyInsightContains(output, 'org.slf4j:slf4j-simple', forcedVersion)
194 | dependencyInsightContains(output, 'org.slf4j:slf4j-api', forcedVersion)
195 |
196 | where:
197 | extSpringVersion = '4.2.4.RELEASE'
198 | extSpringBootVersion = '2.7.0'
199 | extSlf4jVersion = '1.6.0'
200 |
201 | requestedVersion = ':\$slf4jVersion'
202 | forcedVersion = '1.7.10'
203 | }
204 |
205 | @Unroll
206 | def 'transitive dep | with higher requested version and forced to different version'() {
207 | given:
208 | // in Spring Boot 2.x plugin, the `io.spring.dependency-management` plugin is added for dependency management. We are not including it here.
209 | setupForTransitiveDependencyScenario(extSpringBootVersion, forcedVersion, '',
210 | "\n\tslf4jVersion = \"$extSlf4jVersion\"")
211 | buildFile << """
212 | dependencies {
213 | implementation "org.slf4j:slf4j-simple$requestedVersion"
214 | }
215 | """.stripIndent()
216 |
217 | when:
218 | def result = runTasks(*tasks('org.slf4j'))
219 | def output = result.output
220 |
221 | then:
222 | writeOutputToProjectDir(output)
223 | dependencyInsightContains(output, 'org.slf4j:slf4j-simple', forcedVersion)
224 | dependencyInsightContains(output, 'org.slf4j:slf4j-api', forcedVersion)
225 |
226 | where:
227 | extSpringVersion = '4.2.4.RELEASE'
228 | extSpringBootVersion = '2.7.0'
229 | extSlf4jVersion = '1.8.0-beta4'
230 |
231 | requestedVersion = ':\$slf4jVersion'
232 | forcedVersion = '1.7.10'
233 | }
234 |
235 | @Unroll
236 | def 'transitive dep | with requested version and forced to same version'() {
237 | given:
238 | // in Spring Boot 2.x plugin, the `io.spring.dependency-management` plugin is added for dependency management. We are not including it here.
239 | setupForTransitiveDependencyScenario(extSpringBootVersion, forcedVersion, '',
240 | "\n\tslf4jVersion = \"$extSlf4jVersion\"")
241 | buildFile << """
242 | dependencies {
243 | implementation "org.slf4j:slf4j-simple$requestedVersion"
244 | }
245 | """.stripIndent()
246 |
247 | when:
248 | def result = runTasks(*tasks('org.slf4j'))
249 | def output = result.output
250 |
251 | then:
252 | writeOutputToProjectDir(output)
253 | dependencyInsightContains(output, 'org.slf4j:slf4j-simple', forcedVersion)
254 | dependencyInsightContains(output, 'org.slf4j:slf4j-api', forcedVersion)
255 |
256 | where:
257 | extSpringVersion = '4.2.4.RELEASE'
258 | extSpringBootVersion = '2.7.0'
259 | extSlf4jVersion = '1.6.0'
260 |
261 | requestedVersion = ':\$slf4jVersion'
262 | forcedVersion = extSlf4jVersion
263 | }
264 |
265 | }
266 |
--------------------------------------------------------------------------------
/src/integTest/groovy/nebula/plugin/resolutionrules/AlignRulesBasicWithCoreSpec.groovy:
--------------------------------------------------------------------------------
1 | package nebula.plugin.resolutionrules
2 |
3 | import nebula.test.dependencies.DependencyGraphBuilder
4 | import nebula.test.dependencies.GradleDependencyGenerator
5 | import nebula.test.dependencies.ModuleBuilder
6 | import org.gradle.api.logging.LogLevel
7 | import org.gradle.util.GradleVersion
8 | import spock.lang.Unroll
9 |
10 | class AlignRulesBasicWithCoreSpec extends AbstractIntegrationTestKitSpec {
11 | private def rulesJsonFile
12 |
13 | def setup() {
14 | keepFiles = true
15 | if (GradleVersion.current().baseVersion < GradleVersion.version("6.0")) {
16 | settingsFile << '''\
17 | enableFeaturePreview("GRADLE_METADATA")
18 | '''.stripIndent()
19 | }
20 |
21 | rulesJsonFile = new File(projectDir, "rules.json")
22 | rulesJsonFile.createNewFile()
23 |
24 | buildFile << """\
25 | plugins {
26 | id 'com.netflix.nebula.resolution-rules'
27 | id 'java'
28 | }
29 | dependencies {
30 | resolutionRules files('$rulesJsonFile')
31 | }
32 | """.stripIndent()
33 |
34 | settingsFile << """\
35 | rootProject.name = '${moduleName}'
36 | """.stripIndent()
37 |
38 | logLevel = LogLevel.INFO
39 | }
40 |
41 | def 'align rules and force to latest.release'() {
42 | def graph = new DependencyGraphBuilder()
43 | .addModule('test.nebula:a:1.0.0')
44 | .addModule('test.nebula:a:1.0.1')
45 | .addModule('test.nebula:a:1.1.0')
46 | .addModule('test.nebula:b:1.0.0')
47 | .addModule('test.nebula:b:1.0.1')
48 | .addModule('test.nebula:b:1.1.0')
49 | .build()
50 | def mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen")
51 | mavenrepo.generateTestMavenRepo()
52 |
53 | rulesJsonFile << alignTestNebulaRule()
54 |
55 | buildFile << """\
56 | repositories {
57 | ${mavenrepo.mavenRepositoryBlock}
58 | }
59 | dependencies {
60 | implementation 'test.nebula:a:1.0.0'
61 | implementation 'test.nebula:b:1.1.0'
62 | }
63 | configurations.all {
64 | resolutionStrategy {
65 | force 'test.nebula:a:latest.release'
66 | }
67 | }
68 | """.stripIndent()
69 |
70 | when:
71 | def result = runTasks('dependencyInsight', '--dependency', 'test.nebula')
72 |
73 | then:
74 | def resultingVersion = "1.1.0"
75 | dependencyInsightContains(result.output, "test.nebula:a", resultingVersion)
76 | dependencyInsightContains(result.output, "test.nebula:b", resultingVersion)
77 |
78 | result.output.contains 'belongs to platform aligned-platform:rules-0-for-test.nebula-or-test.nebula.ext:1.1.0'
79 | }
80 |
81 | def 'align rules and force to latest.release when brought in transitively'() {
82 | def graph = new DependencyGraphBuilder()
83 | .addModule('test.nebula:a:1.0.0')
84 | .addModule('test.nebula:a:1.0.1')
85 | .addModule('test.nebula:a:1.1.0')
86 | .addModule('test.nebula:b:1.0.0')
87 | .addModule('test.nebula:b:1.0.1')
88 | .addModule('test.nebula:b:1.1.0')
89 | .addModule(new ModuleBuilder('test.other:brings-a:1.0.0').addDependency('test.nebula:a:1.0.3').build())
90 | .addModule(new ModuleBuilder('test.other:also-brings-a:1.0.0').addDependency('test.nebula:a:1.1.0').build())
91 | .addModule(new ModuleBuilder('test.other:brings-b:1.0.0').addDependency('test.nebula:b:1.1.0').build())
92 | .build()
93 | def mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen")
94 | mavenrepo.generateTestMavenRepo()
95 |
96 | rulesJsonFile << alignTestNebulaRule()
97 |
98 | buildFile << """\
99 | repositories {
100 | ${mavenrepo.mavenRepositoryBlock}
101 | }
102 | dependencies {
103 | implementation 'test.other:brings-a:latest.release'
104 | implementation 'test.other:also-brings-a:latest.release'
105 | implementation 'test.other:brings-b:latest.release'
106 | }
107 | configurations.all {
108 | resolutionStrategy {
109 | force 'test.nebula:a:latest.release'
110 | }
111 | }
112 | """.stripIndent()
113 |
114 | when:
115 | def result = runTasks('dependencyInsight', '--dependency', 'test.nebula')
116 |
117 | then:
118 | def resultingVersion = "1.1.0"
119 | dependencyInsightContains(result.output, "test.nebula:a", resultingVersion)
120 | dependencyInsightContains(result.output, "test.nebula:b", resultingVersion)
121 | }
122 |
123 | def 'multiple align rules'() {
124 | def graph = new DependencyGraphBuilder()
125 | .addModule('test.nebula:a:1.0.0')
126 | .addModule('test.nebula:a:1.1.0')
127 | .addModule('test.nebula:b:1.0.0')
128 | .addModule('test.nebula:b:1.1.0')
129 | .addModule('test.other:c:0.12.2')
130 | .addModule('test.other:c:1.0.0')
131 | .addModule('test.other:d:0.12.2')
132 | .addModule('test.other:d:1.0.0')
133 | .build()
134 | def mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen")
135 | mavenrepo.generateTestMavenRepo()
136 |
137 | rulesJsonFile << '''\
138 | {
139 | "deny": [], "reject": [], "substitute": [], "replace": [],
140 | "align": [
141 | {
142 | "name": "testNebula",
143 | "group": "test.nebula",
144 | "reason": "Align test.nebula dependencies",
145 | "author": "Example Person ",
146 | "date": "2016-03-17T20:21:20.368Z"
147 | },
148 | {
149 | "name": "testOther",
150 | "group": "test.other",
151 | "reason": "Aligning test",
152 | "author": "Example Tester ",
153 | "date": "2016-04-05T19:19:49.495Z"
154 | }
155 | ]
156 | }
157 | '''.stripIndent()
158 |
159 | buildFile << """\
160 | repositories {
161 | ${mavenrepo.mavenRepositoryBlock}
162 | }
163 | dependencies {
164 | implementation 'test.nebula:a:1.0.0'
165 | implementation 'test.nebula:b:1.1.0'
166 | implementation 'test.other:c:1.0.0'
167 | implementation 'test.other:d:0.12.+'
168 | }
169 | """.stripIndent()
170 |
171 | when:
172 | def result = runTasks('dependencies', '--configuration', 'compileClasspath')
173 |
174 | then:
175 | result.output.contains 'test.nebula:a:1.0.0 -> 1.1.0\n'
176 | result.output.contains 'test.nebula:b:1.1.0\n'
177 | result.output.contains 'test.other:c:1.0.0\n'
178 | result.output.contains 'test.other:d:0.12.+ -> 1.0.0\n'
179 | }
180 |
181 | @Unroll
182 | def 'core alignment uses versions observed during resolution'() {
183 | // test case from https://github.com/nebula-plugins/gradle-nebula-integration/issues/52
184 | // higher version transitive aligning parent dependency
185 | given:
186 | rulesJsonFile << """
187 | {
188 | "align": [
189 | {
190 | "name": "exampleapp-client-align",
191 | "group": "test.nebula",
192 | "includes": [ "exampleapp-.*" ],
193 | "excludes": [],
194 | "reason": "Library all together",
195 | "author": "example@example.com",
196 | "date": "2018-03-01"
197 | }
198 | ],
199 | "deny": [],
200 | "exclude": [],
201 | "reject": [],
202 | "replace": [],
203 | "substitute": []
204 | }
205 | """.stripIndent()
206 |
207 | def mavenrepo = createDependenciesForExampleAppDependencies()
208 |
209 | buildFile << """
210 | repositories {
211 | ${mavenrepo.mavenRepositoryBlock}
212 | }
213 | dependencies {
214 | implementation 'test.nebula:exampleapp-client:80.0.139'
215 | }
216 | """.stripIndent()
217 | when:
218 | def dependenciesResult = runTasks('dependencies')
219 | def result = runTasks(*tasks())
220 |
221 | then:
222 | dependencyInsightContains(result.output, "test.nebula:exampleapp-client", resultingVersion)
223 |
224 | assert dependenciesResult.output.contains("""
225 | \\--- test.nebula:exampleapp-client:80.0.139 -> 80.0.225
226 | +--- test.nebula:exampleapp-common:80.0.249
227 | \\--- test.nebula:exampleapp-smart-client:80.0.10
228 | """.stripIndent())
229 |
230 | where:
231 | resultingVersion << ["80.0.225"]
232 | }
233 |
234 | private static def tasks(Boolean usingCoreBomSupport = false, String groupForInsight = 'test.nebula') {
235 | return [
236 | 'dependencyInsight',
237 | '--dependency',
238 | groupForInsight,
239 | "-Dnebula.features.coreBomSupport=$usingCoreBomSupport"
240 | ]
241 | }
242 |
243 | private static void dependencyInsightContains(String resultOutput, String groupAndName, String resultingVersion) {
244 | def content = "$groupAndName:.*$resultingVersion\n"
245 | assert resultOutput.findAll(content).size() >= 1
246 | }
247 |
248 | private static String alignTestNebulaRule() {
249 | return '''\
250 | {
251 | "deny": [], "reject": [], "substitute": [], "replace": [],
252 | "align": [
253 | {
254 | "name": "testNebula",
255 | "group": "(test.nebula|test.nebula.ext)",
256 | "reason": "Align test.nebula dependencies",
257 | "author": "Example Person ",
258 | "date": "2016-03-17T20:21:20.368Z"
259 | }
260 | ]
261 | }
262 | '''.stripIndent()
263 | }
264 |
265 | private GradleDependencyGenerator createDependenciesForExampleAppDependencies() {
266 | def client = 'test.nebula:exampleapp-client'
267 | def common = 'test.nebula:exampleapp-common'
268 | def model = 'test.nebula:exampleapp-model'
269 | def smartClient = 'test.nebula:exampleapp-smart-client'
270 | def graph = new DependencyGraphBuilder()
271 | .addModule(new ModuleBuilder("$client:80.0.139")
272 | .addDependency("$common:80.0.154")
273 | .build())
274 | .addModule(new ModuleBuilder("$client:80.0.154")
275 | .addDependency("$common:80.0.177")
276 | .build())
277 | .addModule(new ModuleBuilder("$client:80.0.177")
278 | .addDependency("$common:80.0.201")
279 | .build())
280 | .addModule(new ModuleBuilder("$client:80.0.201")
281 | .addDependency("$common:80.0.225")
282 | .build())
283 | .addModule(new ModuleBuilder("$client:80.0.225")
284 | .addDependency("$common:80.0.249")
285 | .addDependency("$smartClient:80.0.10")
286 | .build())
287 | .addModule(new ModuleBuilder("$client:80.0.236")
288 | .addDependency("$common:80.0.260")
289 | .addDependency("$smartClient:80.0.21")
290 | .build())
291 |
292 | .addModule("$common:80.0.154")
293 | .addModule("$common:80.0.177")
294 | .addModule("$common:80.0.201")
295 | .addModule("$common:80.0.225")
296 | .addModule("$common:80.0.249")
297 | .addModule("$common:80.0.260")
298 |
299 | .addModule("$model:80.0.15")
300 |
301 | .addModule("$smartClient:80.0.10")
302 | .addModule(new ModuleBuilder("$smartClient:80.0.21")
303 | .addDependency("$model:80.0.15")
304 | .build())
305 | .build()
306 | def mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen")
307 | mavenrepo.generateTestMavenRepo()
308 | return mavenrepo
309 | }
310 | }
311 |
--------------------------------------------------------------------------------
/src/integTest/groovy/nebula/plugin/resolutionrules/AlignRulesDirectDependenciesSpec.groovy:
--------------------------------------------------------------------------------
1 | package nebula.plugin.resolutionrules
2 |
3 | import nebula.test.dependencies.DependencyGraphBuilder
4 | import nebula.test.dependencies.GradleDependencyGenerator
5 | import spock.lang.Unroll
6 |
7 | class AlignRulesDirectDependenciesSpec extends AbstractAlignRulesSpec {
8 |
9 | @Unroll
10 | def 'can align direct dependencies if necessary'() {
11 | def graph = new DependencyGraphBuilder()
12 | .addModule('test.nebula:a:1.0.0')
13 | .addModule('test.nebula:a:0.15.0')
14 | .addModule('test.nebula:b:1.0.0')
15 | .addModule('test.nebula:b:0.15.0')
16 | .build()
17 | File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo()
18 |
19 | rulesJsonFile << '''\
20 | {
21 | "deny": [], "reject": [], "substitute": [], "replace": [],
22 | "align": [
23 | {
24 | "name": "testNebula",
25 | "group": "test.nebula",
26 | "reason": "Align test.nebula dependencies",
27 | "author": "Example Person ",
28 | "date": "2016-03-17T20:21:20.368Z"
29 | }
30 | ]
31 | }
32 | '''.stripIndent()
33 |
34 | buildFile << """\
35 | repositories {
36 | maven { url = '${mavenrepo.absolutePath}' }
37 | }
38 | dependencies {
39 | implementation 'test.nebula:a:1.0.0'
40 | implementation 'test.nebula:b:0.15.0'
41 | }
42 | """.stripIndent()
43 |
44 | when:
45 | def result = runTasks('dependencies', '--configuration', 'compileClasspath')
46 |
47 | then:
48 | result.output.contains '+--- test.nebula:a:1.0.0\n'
49 | result.output.contains '\\--- test.nebula:b:0.15.0 -> 1.0.0\n'
50 |
51 | }
52 |
53 | @Unroll
54 | def 'can align direct dependencies from ivy repositories'() {
55 | def graph = new DependencyGraphBuilder()
56 | .addModule('test.nebula:a:1.0.0')
57 | .addModule('test.nebula:a:0.15.0')
58 | .addModule('test.nebula:b:1.0.0')
59 | .addModule('test.nebula:b:0.15.0')
60 | .build()
61 | GradleDependencyGenerator ivyrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen")
62 | ivyrepo.generateTestIvyRepo()
63 |
64 | rulesJsonFile << '''\
65 | {
66 | "deny": [], "reject": [], "substitute": [], "replace": [],
67 | "align": [
68 | {
69 | "name": "testNebula",
70 | "group": "test.nebula",
71 | "reason": "Align test.nebula dependencies",
72 | "author": "Example Person ",
73 | "date": "2016-03-17T20:21:20.368Z"
74 | }
75 | ]
76 | }
77 | '''.stripIndent()
78 |
79 | buildFile << """\
80 | repositories {
81 | ${ivyrepo.ivyRepositoryBlock}
82 | }
83 | dependencies {
84 | implementation 'test.nebula:a:1.0.0'
85 | implementation 'test.nebula:b:0.15.0'
86 | }
87 | """.stripIndent()
88 |
89 | when:
90 | def result = runTasks('dependencies', '--configuration', 'compileClasspath')
91 |
92 | then:
93 | result.output.contains '+--- test.nebula:a:1.0.0\n'
94 | result.output.contains '\\--- test.nebula:b:0.15.0 -> 1.0.0\n'
95 | }
96 |
97 | @Unroll
98 | def 'can align dynamic dependencies'() {
99 | def graph = new DependencyGraphBuilder()
100 | .addModule('test.nebula:a:1.0.0')
101 | .addModule('test.nebula:a:1.0.1')
102 | .build()
103 | File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo()
104 |
105 | rulesJsonFile << '''\
106 | {
107 | "deny": [], "reject": [], "substitute": [], "replace": [],
108 | "align": [
109 | {
110 | "name": "testNebula",
111 | "group": "test.nebula",
112 | "reason": "Align test.nebula dependencies",
113 | "author": "Example Person ",
114 | "date": "2016-03-17T20:21:20.368Z"
115 | }
116 | ]
117 | }
118 | '''.stripIndent()
119 |
120 | buildFile << """\
121 | repositories {
122 | maven { url = '${mavenrepo.absolutePath}' }
123 | }
124 | dependencies {
125 | implementation 'test.nebula:a:1.+'
126 | }
127 | """.stripIndent()
128 |
129 | when:
130 | def result = runTasks('dependencies', '--configuration', 'compileClasspath')
131 |
132 | then:
133 | result.output.contains '\\--- test.nebula:a:1.+ -> 1.0.1\n'
134 | }
135 |
136 | @Unroll
137 | def 'can align dynamic range dependencies'() {
138 | def graph = new DependencyGraphBuilder()
139 | .addModule('test.nebula:a:1.0.0')
140 | .addModule('test.nebula:a:1.0.1')
141 | .build()
142 | File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo()
143 |
144 | rulesJsonFile << '''\
145 | {
146 | "deny": [], "reject": [], "substitute": [], "replace": [],
147 | "align": [
148 | {
149 | "name": "testNebula",
150 | "group": "test.nebula",
151 | "reason": "Align test.nebula dependencies",
152 | "author": "Example Person ",
153 | "date": "2016-03-17T20:21:20.368Z"
154 | }
155 | ]
156 | }
157 | '''.stripIndent()
158 |
159 | buildFile << """\
160 | repositories {
161 | maven { url = '${mavenrepo.absolutePath}' }
162 | }
163 | dependencies {
164 | implementation 'test.nebula:a:[1.0.0, 2.0.0)'
165 | }
166 | """.stripIndent()
167 |
168 | when:
169 | def result = runTasks('dependencies', '--configuration', 'compileClasspath')
170 |
171 | then:
172 | result.output.contains '\\--- test.nebula:a:[1.0.0, 2.0.0) -> 1.0.1\n'
173 | }
174 |
175 | @Unroll
176 | def 'unresolvable dependencies cause assemble to fail'() {
177 | rulesJsonFile << '''\
178 | {
179 | "deny": [], "reject": [], "substitute": [], "replace": [],
180 | "align": [
181 | {
182 | "name": "testNebula",
183 | "group": "com.google.guava",
184 | "reason": "Align guava",
185 | "author": "Example Person ",
186 | "date": "2016-03-17T20:21:20.368Z"
187 | }
188 | ]
189 | }
190 | '''.stripIndent()
191 |
192 | buildFile << """\
193 | repositories { mavenCentral() }
194 | dependencies {
195 | implementation 'org.slf4j:slf4j-api:1.7.21'
196 | implementation 'com.google.guava:guava:oops'
197 | }
198 | """
199 |
200 | writeHelloWorld('com.netflix.nebula')
201 |
202 | when:
203 | org.gradle.testkit.runner.BuildResult result = runTasksAndFail('assemble')
204 |
205 | then:
206 | result.output.contains("Could not resolve all files for configuration ':compileClasspath'.")
207 | result.output.contains("Could not find com.google.guava:guava:oops.")
208 |
209 | }
210 |
211 | @Unroll('unresolvable dependencies do not cause #tasks to fail')
212 | def 'unresolvable dependencies do not cause dependencies tasks to fail'() {
213 | buildFile.delete()
214 | buildFile << """\
215 | apply plugin: 'java'
216 |
217 | repositories { mavenCentral() }
218 |
219 | dependencies {
220 | implementation 'org.slf4j:slf4j-api:1.7.21'
221 | implementation 'com.google.guava:guava:oops'
222 | }
223 | """.stripIndent()
224 |
225 | when:
226 | runTasks(*tasks)
227 |
228 | then:
229 | noExceptionThrown()
230 |
231 | where:
232 | tasks | _
233 | ['dependencies', '--configuration', 'compileClasspath'] | _
234 | ['dependencyInsight', '--dependency', 'guava'] | _
235 | }
236 | }
237 |
--------------------------------------------------------------------------------
/src/integTest/groovy/nebula/plugin/resolutionrules/AlignRulesForceSpec.groovy:
--------------------------------------------------------------------------------
1 | package nebula.plugin.resolutionrules
2 |
3 | import nebula.test.dependencies.DependencyGraphBuilder
4 | import nebula.test.dependencies.GradleDependencyGenerator
5 | import spock.lang.Unroll
6 |
7 | class AlignRulesForceSpec extends AbstractAlignRulesSpec {
8 | def setup() {
9 | keepFiles = true
10 | }
11 |
12 | @Unroll
13 | def 'alignment uses #name forced version'() {
14 | def graph = new DependencyGraphBuilder()
15 | .addModule('test.nebula:a:1.0.0')
16 | .addModule('test.nebula:a:0.15.0')
17 | .addModule('test.nebula:b:1.0.0')
18 | .addModule('test.nebula:b:0.15.0')
19 | .addModule('test.nebula:c:1.0.0')
20 | .addModule('test.nebula:c:0.15.0')
21 | .addModule('test.nebula.other:a:1.0.0')
22 | .build()
23 | File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo()
24 |
25 | rulesJsonFile << '''\
26 | {
27 | "deny": [], "reject": [], "substitute": [], "replace": [],
28 | "align": [
29 | {
30 | "name": "testNebula",
31 | "group": "test.nebula",
32 | "reason": "Align test.nebula dependencies",
33 | "author": "Example Person ",
34 | "date": "2016-03-17T20:21:20.368Z"
35 | }
36 | ]
37 | }
38 | '''.stripIndent()
39 |
40 | buildFile << """\
41 | repositories {
42 | maven { url = '${mavenrepo.absolutePath}' }
43 | }
44 | dependencies {
45 | implementation 'test.nebula:a:1.0.0'
46 | implementation 'test.nebula:b:1.0.0'
47 | implementation 'test.nebula:c:0.15.0'
48 | implementation 'test.nebula.other:a:1.0.0'
49 | }
50 | $force
51 | """.stripIndent()
52 |
53 | when:
54 | def tasks = ['dependencies', '--configuration', 'compileClasspath', '--warning-mode', 'none', '-s']
55 | def result = runTasks(*tasks)
56 |
57 |
58 | then:
59 | result.output.contains '+--- test.nebula:a:1.0.0 -> 0.15.0\n'
60 | result.output.contains '+--- test.nebula:b:1.0.0 -> 0.15.0\n'
61 | result.output.contains '+--- test.nebula:c:0.15.0\n'
62 | result.output.contains '--- test.nebula.other:a:1.0.0\n'
63 |
64 | where:
65 | name | force
66 | "all" | "configurations.all { resolutionStrategy { force 'test.nebula:a:0.15.0' } }"
67 | "configuration" | "configurations.compileClasspath { resolutionStrategy { force 'test.nebula:a:0.15.0' } }"
68 | }
69 |
70 | @Unroll
71 | def 'when multiple forces are present then Core alignment fails due to multiple forces'() {
72 | def graph = new DependencyGraphBuilder()
73 | .addModule('test.nebula:a:2.0.0')
74 | .addModule('test.nebula:a:1.0.0')
75 | .addModule('test.nebula:a:0.15.0')
76 | .addModule('test.nebula:b:2.0.0')
77 | .addModule('test.nebula:b:1.0.0')
78 | .addModule('test.nebula:b:0.15.0')
79 | .addModule('test.nebula:c:2.0.0')
80 | .addModule('test.nebula:c:1.0.0')
81 | .addModule('test.nebula:c:0.15.0')
82 | .build()
83 | File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo()
84 |
85 | rulesJsonFile << '''\
86 | {
87 | "deny": [], "reject": [], "substitute": [], "replace": [],
88 | "align": [
89 | {
90 | "name": "testNebula",
91 | "group": "test.nebula",
92 | "reason": "Align test.nebula dependencies",
93 | "author": "Example Person ",
94 | "date": "2016-03-17T20:21:20.368Z"
95 | }
96 | ]
97 | }
98 | '''.stripIndent()
99 |
100 | buildFile << """\
101 | repositories {
102 | maven { url = '${mavenrepo.absolutePath}' }
103 | }
104 | dependencies {
105 | implementation 'test.nebula:a:2.0.0'
106 | implementation 'test.nebula:b:2.0.0'
107 | implementation 'test.nebula:c:1.0.0'
108 | }
109 | configurations.compileClasspath.resolutionStrategy {
110 | force 'test.nebula:a:2.0.0'
111 | force 'test.nebula:b:1.0.0'
112 | force 'test.nebula:c:0.15.0'
113 | }
114 | """.stripIndent()
115 |
116 | when:
117 | def result = runTasks('dependencies', '--configuration', 'compileClasspath', '--warning-mode', 'none')
118 | def dependencyInsightResult = runTasks('dependencyInsight', '--dependency', 'test.nebula', '--warning-mode', 'none')
119 |
120 | then:
121 | assert dependencyInsightResult.output.contains('Multiple forces on different versions for virtual platform ')
122 | assert dependencyInsightResult.output.contains('Could not resolve test.nebula:a:2.0.0')
123 | assert dependencyInsightResult.output.contains('Could not resolve test.nebula:b:1.0.0. (already reported)')
124 | assert dependencyInsightResult.output.contains('Could not resolve test.nebula:c:0.15.0. (already reported)')
125 | }
126 |
127 | @Unroll
128 | def 'when dynamic forces are present then Core alignment fails due to multiple forces'() {
129 | def graph = new DependencyGraphBuilder()
130 | .addModule('test.nebula:a:2.0.0')
131 | .addModule('test.nebula:a:1.0.0')
132 | .addModule('test.nebula:a:0.15.0')
133 | .addModule('test.nebula:b:2.0.0')
134 | .addModule('test.nebula:b:1.00.0')
135 | .addModule('test.nebula:b:0.15.0')
136 | .addModule('test.nebula:c:2.0.0')
137 | .addModule('test.nebula:c:1.0.0')
138 | .addModule('test.nebula:c:0.15.0')
139 | .build()
140 | File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo()
141 |
142 | rulesJsonFile << '''\
143 | {
144 | "deny": [], "reject": [], "substitute": [], "replace": [],
145 | "align": [
146 | {
147 | "name": "testNebula",
148 | "group": "test.nebula",
149 | "reason": "Align test.nebula dependencies",
150 | "author": "Example Person ",
151 | "date": "2016-03-17T20:21:20.368Z"
152 | }
153 | ]
154 | }
155 | '''.stripIndent()
156 |
157 | buildFile << """\
158 | repositories {
159 | maven { url = '${mavenrepo.absolutePath}' }
160 | }
161 | dependencies {
162 | implementation 'test.nebula:a:2.0.0'
163 | implementation 'test.nebula:b:2.0.0'
164 | implementation 'test.nebula:c:1.0.0'
165 | }
166 | configurations.compileClasspath.resolutionStrategy {
167 | force 'test.nebula:a:latest.release'
168 | force 'test.nebula:b:1.+'
169 | force 'test.nebula:c:0.15.0'
170 | }
171 | """.stripIndent()
172 |
173 | when:
174 | def tasks = ['dependencies', '--configuration', 'compileClasspath', '--warning-mode', 'none']
175 | def result = runTasks(*tasks)
176 | def dependencyInsightResult = runTasks('dependencyInsight', '--dependency', 'test.nebula', '--warning-mode', 'none')
177 |
178 | then:
179 | assert dependencyInsightResult.output.contains('Multiple forces on different versions for virtual platform ')
180 | assert dependencyInsightResult.output.contains('Could not resolve test.nebula:a:latest.release')
181 | assert dependencyInsightResult.output.contains('Could not resolve test.nebula:b:1.+. (already reported)')
182 | assert dependencyInsightResult.output.contains('Could not resolve test.nebula:c:0.15.0. (already reported)')
183 |
184 | }
185 |
186 | @Unroll
187 | def 'alignment with latest.release force'() {
188 | def graph = new DependencyGraphBuilder()
189 | .addModule('test.nebula:a:2.0.0')
190 | .addModule('test.nebula:a:1.0.0')
191 | .addModule('test.nebula:a:0.15.0')
192 | .addModule('test.nebula:b:2.0.0')
193 | .addModule('test.nebula:b:1.0.0')
194 | .addModule('test.nebula:b:0.15.0')
195 | .addModule('test.nebula:c:2.0.0')
196 | .addModule('test.nebula:c:1.0.0')
197 | .addModule('test.nebula:c:0.15.0')
198 | .build()
199 | File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo()
200 |
201 | rulesJsonFile << '''\
202 | {
203 | "deny": [], "reject": [], "substitute": [], "replace": [],
204 | "align": [
205 | {
206 | "name": "testNebula",
207 | "group": "test.nebula",
208 | "reason": "Align test.nebula dependencies",
209 | "author": "Example Person ",
210 | "date": "2016-03-17T20:21:20.368Z"
211 | }
212 | ]
213 | }
214 | '''.stripIndent()
215 |
216 | buildFile << """\
217 | repositories {
218 | maven { url = '${mavenrepo.absolutePath}' }
219 | }
220 | dependencies {
221 | implementation 'test.nebula:a:2.0.0'
222 | implementation 'test.nebula:b:1.0.0'
223 | implementation 'test.nebula:c:0.15.0'
224 | }
225 | configurations.compileClasspath.resolutionStrategy {
226 | force 'test.nebula:a:latest.release'
227 | }
228 | """.stripIndent()
229 |
230 | when:
231 | def tasks = ['dependencies', '--configuration', 'compileClasspath', '--warning-mode', 'none']
232 | def result = runTasks(*tasks)
233 |
234 | then:
235 | result.output.contains '+--- test.nebula:a:2.0.0\n'
236 | result.output.contains '+--- test.nebula:b:1.0.0 -> 2.0.0\n'
237 | result.output.contains '\\--- test.nebula:c:0.15.0 -> 2.0.0\n'
238 |
239 | }
240 |
241 | @Unroll
242 | def 'alignment with sub-version force'() {
243 | def graph = new DependencyGraphBuilder()
244 | .addModule('test.nebula:a:2.0.0')
245 | .addModule('test.nebula:a:1.0.0')
246 | .addModule('test.nebula:a:0.15.0')
247 | .addModule('test.nebula:b:2.0.0')
248 | .addModule('test.nebula:b:1.0.0')
249 | .addModule('test.nebula:b:0.15.0')
250 | .addModule('test.nebula:c:2.0.0')
251 | .addModule('test.nebula:c:1.0.0')
252 | .addModule('test.nebula:c:0.15.0')
253 | .build()
254 | File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo()
255 |
256 | rulesJsonFile << '''\
257 | {
258 | "deny": [], "reject": [], "substitute": [], "replace": [],
259 | "align": [
260 | {
261 | "name": "testNebula",
262 | "group": "test.nebula",
263 | "reason": "Align test.nebula dependencies",
264 | "author": "Example Person ",
265 | "date": "2016-03-17T20:21:20.368Z"
266 | }
267 | ]
268 | }
269 | '''.stripIndent()
270 |
271 | buildFile << """\
272 | repositories {
273 | maven { url = '${mavenrepo.absolutePath}' }
274 | }
275 | dependencies {
276 | implementation 'test.nebula:a:2.0.0'
277 | implementation 'test.nebula:b:1.0.0'
278 | implementation 'test.nebula:c:0.15.0'
279 | }
280 | configurations.compileClasspath.resolutionStrategy {
281 | force 'test.nebula:a:1.+'
282 | }
283 | """.stripIndent()
284 |
285 | when:
286 | def tasks = ['dependencies', '--configuration', 'compileClasspath', '--warning-mode', 'none']
287 | def result = runTasks(*tasks)
288 |
289 |
290 | then:
291 |
292 | result.output.contains '+--- test.nebula:a:2.0.0 -> 1.0.0\n'
293 | result.output.contains '+--- test.nebula:b:1.0.0\n'
294 | result.output.contains '\\--- test.nebula:c:0.15.0 -> 1.0.0\n'
295 | }
296 |
297 | @Unroll
298 | def 'with multiple specific dynamic versions then Core alignment fails due to multiple forces'() {
299 | def graph = new DependencyGraphBuilder()
300 | .addModule('test.nebula:a:3.0.0')
301 | .addModule('test.nebula:a:2.0.0')
302 | .addModule('test.nebula:a:1.0.0')
303 | .addModule('test.nebula:a:0.15.0')
304 | .addModule('test.nebula:b:2.0.0')
305 | .addModule('test.nebula:b:1.0.0')
306 | .addModule('test.nebula:b:0.15.0')
307 | .addModule('test.nebula:c:2.0.0')
308 | .addModule('test.nebula:c:1.0.0')
309 | .addModule('test.nebula:c:0.15.0')
310 | .build()
311 | File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo()
312 |
313 | rulesJsonFile << '''\
314 | {
315 | "deny": [], "reject": [], "substitute": [], "replace": [],
316 | "align": [
317 | {
318 | "name": "testNebula",
319 | "group": "test.nebula",
320 | "reason": "Align test.nebula dependencies",
321 | "author": "Example Person ",
322 | "date": "2016-03-17T20:21:20.368Z"
323 | }
324 | ]
325 | }
326 | '''.stripIndent()
327 |
328 | buildFile << """\
329 | repositories {
330 | maven { url = '${mavenrepo.absolutePath}' }
331 | }
332 | dependencies {
333 | implementation 'test.nebula:a:2.0.0'
334 | implementation 'test.nebula:b:1.0.0'
335 | implementation 'test.nebula:c:0.15.0'
336 | }
337 | configurations.compileClasspath.resolutionStrategy {
338 | force 'test.nebula:a:latest.release'
339 | force 'test.nebula:b:1.+'
340 | force 'test.nebula:c:[1.0, 2.0)'
341 | }
342 | """.stripIndent()
343 |
344 | when:
345 | def tasks = ['dependencies', '--configuration', 'compileClasspath', '--warning-mode', 'none']
346 | def result = runTasks(*tasks)
347 | def dependencyInsightResult = runTasks('dependencyInsight', '--dependency', 'test.nebula', '--warning-mode', 'none')
348 |
349 |
350 | then:
351 | assert dependencyInsightResult.output.contains('Multiple forces on different versions for virtual platform ')
352 | assert dependencyInsightResult.output.contains('Could not resolve test.nebula:a:latest.release')
353 | assert dependencyInsightResult.output.contains('Could not resolve test.nebula:b:1.+. (already reported)')
354 | assert dependencyInsightResult.output.contains('Could not resolve test.nebula:c:[1.0, 2.0). (already reported)')
355 | }
356 | }
357 |
--------------------------------------------------------------------------------
/src/integTest/groovy/nebula/plugin/resolutionrules/AlignRulesMultiprojectSpec.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Netflix, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package nebula.plugin.resolutionrules
18 |
19 | import nebula.test.IntegrationSpec
20 | import nebula.test.dependencies.DependencyGraphBuilder
21 | import nebula.test.dependencies.GradleDependencyGenerator
22 | import spock.lang.Unroll
23 |
24 | class AlignRulesMultiprojectSpec extends IntegrationSpec {
25 | def rulesJsonFile
26 | def aDir
27 | def bDir
28 |
29 | def setup() {
30 | // Avoid deprecation warnings during parallel resolution while we look for a solution
31 | System.setProperty('ignoreDeprecations', 'true')
32 | System.setProperty('ignoreMutableProjectStateWarnings', 'true')
33 |
34 | fork = false
35 | rulesJsonFile = new File(projectDir, "${moduleName}.json")
36 | buildFile << """\
37 | allprojects {
38 | ${applyPlugin(ResolutionRulesPlugin)}
39 |
40 |
41 | group = 'test.nebula'
42 | }
43 |
44 | project(':a') {
45 | apply plugin: 'java'
46 | }
47 |
48 | project(':b') {
49 | apply plugin: 'java-library'
50 | }
51 |
52 | dependencies {
53 | resolutionRules files('$rulesJsonFile')
54 | }
55 | """.stripIndent()
56 |
57 | settingsFile << '''\
58 | rootProject.name = 'aligntest'
59 | '''.stripIndent()
60 |
61 | aDir = addSubproject('a')
62 | bDir = addSubproject('b')
63 | }
64 |
65 | @Unroll
66 | def 'align rules do not interfere with a multiproject that produces the jars being aligned (parallel #parallel)'() {
67 | rulesJsonFile << '''\
68 | {
69 | "deny": [], "reject": [], "substitute": [], "replace": [],
70 | "align": [
71 | {
72 | "name": "testNebula",
73 | "group": "test.nebula",
74 | "includes": ["a", "b"],
75 | "reason": "Align test.nebula dependencies",
76 | "author": "Example Person ",
77 | "date": "2016-03-17T20:21:20.368Z"
78 | }
79 | ]
80 | }
81 | '''.stripIndent()
82 |
83 | // project b depends on a
84 | new File(bDir, 'build.gradle') << '''\
85 | dependencies {
86 | implementation project(':a')
87 | }
88 | '''.stripIndent()
89 |
90 | buildFile << '''\
91 | subprojects {
92 | apply plugin: 'maven-publish'
93 |
94 | publishing {
95 | publications {
96 | test(MavenPublication) {
97 | from components.java
98 | }
99 | }
100 | repositories {
101 | maven {
102 | name 'repo'
103 | url = 'build/repo'
104 | }
105 | }
106 | }
107 | }
108 | '''.stripIndent()
109 |
110 | when:
111 | def tasks = [':b:dependencies', '--configuration', 'compileClasspath']
112 | if (parallel) {
113 | tasks += "--parallel"
114 | }
115 | def results = runTasks(*tasks)
116 |
117 | then:
118 | results.standardOutput.contains('\\--- project :a\n')
119 |
120 | where:
121 | parallel << [false, true]
122 | }
123 |
124 | @Unroll
125 | def 'cycle like behavior (parallel #parallel)'() {
126 | rulesJsonFile << '''\
127 | {
128 | "deny": [], "reject": [], "substitute": [], "replace": [],
129 | "align": [
130 | {
131 | "name": "testNebula",
132 | "group": "test",
133 | "reason": "Align test.nebula dependencies",
134 | "author": "Example Person ",
135 | "date": "2016-03-17T20:21:20.368Z"
136 | }
137 | ]
138 | }
139 | '''.stripIndent()
140 |
141 | new File(aDir, 'build.gradle') << '''\
142 | dependencies {
143 | testImplementation project(':b')
144 | }
145 | '''.stripIndent()
146 |
147 | new File(bDir, 'build.gradle') << '''\
148 | dependencies {
149 | implementation project(':a')
150 | }
151 | '''.stripIndent()
152 |
153 | when:
154 | def tasks = [':a:dependencies', ':b:dependencies', 'assemble']
155 | if (parallel) {
156 | tasks += "--parallel"
157 | }
158 | runTasks(*tasks)
159 |
160 | then:
161 | noExceptionThrown()
162 |
163 | where:
164 | parallel << [true, false]
165 | }
166 |
167 | @Unroll
168 | def 'can align project dependencies (parallel #parallel)'() {
169 | def graph = new DependencyGraphBuilder()
170 | .addModule('other.nebula:a:0.42.0')
171 | .addModule('other.nebula:a:1.0.0')
172 | .addModule('other.nebula:a:1.1.0')
173 | .addModule('other.nebula:b:0.42.0')
174 | .addModule('other.nebula:b:1.0.0')
175 | .addModule('other.nebula:b:1.1.0')
176 | .addModule('other.nebula:c:0.42.0')
177 | .addModule('other.nebula:c:1.0.0')
178 | .addModule('other.nebula:c:1.1.0')
179 | .build()
180 | File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo()
181 |
182 | rulesJsonFile << '''\
183 | {
184 | "deny": [], "reject": [], "substitute": [], "replace": [],
185 | "align": [
186 | {
187 | "group": "other.nebula",
188 | "includes": [ "a", "b" ],
189 | "reason": "Align test.nebula dependencies",
190 | "author": "Example Person ",
191 | "date": "2016-03-17T20:21:20.368Z"
192 | }
193 | ]
194 | }
195 | '''.stripIndent()
196 |
197 | buildFile << """\
198 | subprojects {
199 | repositories {
200 | maven { url = '${mavenrepo.absolutePath}' }
201 | }
202 | }
203 |
204 | project(':a') {
205 | dependencies {
206 | implementation project(':b')
207 | }
208 | }
209 |
210 | project(':b') {
211 | dependencies {
212 | api 'other.nebula:a:1.0.0'
213 | api 'other.nebula:b:1.1.0'
214 | api 'other.nebula:c:0.42.0'
215 | }
216 | }
217 | """.stripIndent()
218 |
219 | when:
220 | def tasks = [':a:dependencies', '--configuration', 'compileClasspath']
221 | if (parallel) {
222 | tasks += "--parallel"
223 | }
224 | def result = runTasks(*tasks)
225 |
226 | then:
227 | result.standardOutput.contains '+--- other.nebula:a:1.0.0 -> 1.1.0'
228 | result.standardOutput.contains '+--- other.nebula:b:1.1.0'
229 | result.standardOutput.contains '\\--- other.nebula:c:0.42.0'
230 |
231 | where:
232 | parallel << [true, false]
233 | }
234 |
235 | @Unroll
236 | def 'root project can depend on subprojects (parallel #parallel)'() {
237 | def graph = new DependencyGraphBuilder()
238 | .addModule('other.nebula:a:0.42.0')
239 | .addModule('other.nebula:a:1.0.0')
240 | .addModule('other.nebula:a:1.1.0')
241 | .addModule('other.nebula:b:0.42.0')
242 | .addModule('other.nebula:b:1.0.0')
243 | .addModule('other.nebula:b:1.1.0')
244 | .addModule('other.nebula:c:0.42.0')
245 | .addModule('other.nebula:c:1.0.0')
246 | .addModule('other.nebula:c:1.1.0')
247 | .build()
248 | File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo()
249 |
250 | rulesJsonFile << '''\
251 | {
252 | "deny": [], "reject": [], "substitute": [], "replace": [],
253 | "align": [
254 | {
255 | "group": "other.nebula",
256 | "includes": [ "a", "b" ],
257 | "reason": "Align test.nebula dependencies",
258 | "author": "Example Person ",
259 | "date": "2016-03-17T20:21:20.368Z"
260 | }
261 | ]
262 | }
263 | '''.stripIndent()
264 |
265 | buildFile << """\
266 | apply plugin: 'java'
267 |
268 | subprojects {
269 | repositories {
270 | maven { url = '${mavenrepo.absolutePath}' }
271 | }
272 | }
273 |
274 | dependencies {
275 | implementation project(':a')
276 | implementation project(':b')
277 | }
278 |
279 | project(':a') {
280 | dependencies {
281 | implementation project(':b')
282 | }
283 | }
284 |
285 | project(':b') {
286 | dependencies {
287 | api 'other.nebula:a:1.0.0'
288 | api 'other.nebula:b:1.1.0'
289 | api 'other.nebula:c:0.42.0'
290 | }
291 | }
292 | """.stripIndent()
293 |
294 | when:
295 | def tasks = [':a:dependencies', '--configuration', 'compileClasspath']
296 | if (parallel) {
297 | tasks += "--parallel"
298 | }
299 | def result = runTasks(*tasks)
300 |
301 | then:
302 | result.standardOutput.contains '+--- other.nebula:a:1.0.0 -> 1.1.0'
303 | result.standardOutput.contains '+--- other.nebula:b:1.1.0'
304 | result.standardOutput.contains '\\--- other.nebula:c:0.42.0'
305 |
306 | where:
307 | parallel << [true, false]
308 | }
309 | }
310 |
--------------------------------------------------------------------------------
/src/integTest/groovy/nebula/plugin/resolutionrules/AlignRulesVersionMatchSpec.groovy:
--------------------------------------------------------------------------------
1 | package nebula.plugin.resolutionrules
2 |
3 | import nebula.test.dependencies.DependencyGraphBuilder
4 | import nebula.test.dependencies.GradleDependencyGenerator
5 | import nebula.test.dependencies.ModuleBuilder
6 | import org.junit.Ignore
7 |
8 | @Ignore("we do not currently use VersionMatchers")
9 | class AlignRulesVersionMatchSpec extends AbstractAlignRulesSpec {
10 | def 'match excluding differences in version results in no alignment'() {
11 | def graph = new DependencyGraphBuilder()
12 | .addModule('test.nebula:a:1.0.0')
13 | .addModule(new ModuleBuilder('test.nebula:b:1.0.0-1').addDependency('test.nebula:a:1.0.0').build())
14 | .build()
15 | File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo()
16 |
17 | rulesJsonFile << '''\
18 | {
19 | "deny": [], "reject": [], "substitute": [], "replace": [],
20 | "align": [
21 | {
22 | "name": "testNebula",
23 | "group": "test.nebula",
24 | "match": "EXCLUDE_SUFFIXES",
25 | "reason": "Align test.nebula dependencies",
26 | "author": "Example Person ",
27 | "date": "2016-03-17T20:21:20.368Z"
28 | }
29 | ]
30 | }
31 | '''.stripIndent()
32 |
33 | buildFile << """\
34 | repositories {
35 | maven { url = '${mavenrepo.absolutePath}' }
36 | }
37 | dependencies {
38 | implementation 'test.nebula:b:1.0.0-1'
39 | }
40 | """.stripIndent()
41 |
42 | when:
43 | def output = runTasks('dependencies', '--configuration', 'compileClasspath').output
44 |
45 | then:
46 | output.contains '\\--- test.nebula:b:1.0.0-1\n'
47 | output.contains '\\--- test.nebula:a:1.0.0\n'
48 | }
49 |
50 | def 'match regex version alignment'() {
51 | def graph = new DependencyGraphBuilder()
52 | .addModule('test.nebula:a:1.0.0')
53 | .addModule(new ModuleBuilder('test.nebula:b:1.0.0-1').addDependency('test.nebula:a:1.0.0').build())
54 | .build()
55 | File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo()
56 |
57 | rulesJsonFile << '''\
58 | {
59 | "deny": [], "reject": [], "substitute": [], "replace": [],
60 | "align": [
61 | {
62 | "name": "testNebula",
63 | "group": "test.nebula",
64 | "match": "^(\\\\d+\\\\.)?(\\\\d+\\\\.)?(\\\\*|\\\\d+)",
65 | "reason": "Align test.nebula dependencies",
66 | "author": "Example Person ",
67 | "date": "2016-03-17T20:21:20.368Z"
68 | }
69 | ]
70 | }
71 | '''.stripIndent()
72 |
73 | buildFile << """\
74 | repositories {
75 | maven { url = '${mavenrepo.absolutePath}' }
76 | }
77 | dependencies {
78 | implementation 'test.nebula:b:1.0.0-1'
79 | }
80 | """.stripIndent()
81 |
82 | when:
83 | def output = runTasks('dependencies', '--configuration', 'compileClasspath').output
84 |
85 | then:
86 | output.contains '\\--- test.nebula:b:1.0.0-1\n'
87 | output.contains '\\--- test.nebula:a:1.0.0\n'
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/integTest/groovy/nebula/plugin/resolutionrules/AlignRulesVersionSuffixesSpec.groovy:
--------------------------------------------------------------------------------
1 | package nebula.plugin.resolutionrules
2 |
3 | import nebula.test.dependencies.DependencyGraphBuilder
4 | import nebula.test.dependencies.GradleDependencyGenerator
5 | import nebula.test.dependencies.ModuleBuilder
6 | import spock.lang.Unroll
7 |
8 | class AlignRulesVersionSuffixesSpec extends AbstractAlignRulesSpec {
9 |
10 | @Unroll
11 | def 'requesting a specific version with no release version available'() {
12 | def graph = new DependencyGraphBuilder()
13 | .addModule('test.nebula:a:1.0.0')
14 | .addModule(new ModuleBuilder('test.nebula:b:1.0.0-1').addDependency('test.nebula:a:1.0.0').build())
15 | .addModule(new ModuleBuilder('test.nebula:c:1.0.0-eap-1').addDependency('test.nebula:a:1.0.0').build())
16 | .addModule(new ModuleBuilder('test.nebula:d:1.0.0.pr.1').addDependency('test.nebula:a:1.0.0').build())
17 | .build()
18 | File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo()
19 |
20 | rulesJsonFile << '''\
21 | {
22 | "deny": [], "reject": [], "substitute": [], "replace": [],
23 | "align": [
24 | {
25 | "name": "testNebula",
26 | "group": "test.nebula",
27 | "reason": "Align test.nebula dependencies",
28 | "author": "Example Person ",
29 | "date": "2016-03-17T20:21:20.368Z"
30 | }
31 | ]
32 | }
33 | '''.stripIndent()
34 |
35 | buildFile << """\
36 | repositories {
37 | maven { url = '${mavenrepo.absolutePath}' }
38 | }
39 | dependencies {
40 | implementation 'test.nebula:b:1.0.0-1'
41 | implementation 'test.nebula:c:1.0.0-eap-1'
42 | implementation 'test.nebula:d:1.0.0.pr.1'
43 | }
44 | """.stripIndent()
45 |
46 | when:
47 | def results = runTasks('dependencies', '--configuration', 'compileClasspath')
48 | def insightResults = runTasks('dependencyInsight', '--dependency', 'test.nebula')
49 |
50 | then:
51 | results.output.contains '--- test.nebula:b:1.0.0-1\n'
52 | results.output.contains '--- test.nebula:c:1.0.0-eap-1\n'
53 | results.output.contains '--- test.nebula:d:1.0.0.pr.1\n'
54 | results.output.contains '\\--- test.nebula:a:1.0.0\n'
55 | assert insightResults.output.contains("belongs to platform aligned-platform:$moduleName-0-for-test.nebula:1.0.0")
56 | assert insightResults.output.findAll("belongs to platform aligned-platform:$moduleName-0-for-test.nebula:1.0.0").size() == 4
57 | }
58 |
59 | @Unroll
60 | def 'requesting a specific version with a release version available'() {
61 | def graph = new DependencyGraphBuilder()
62 | .addModule('test.nebula:a:1.0.0')
63 | .addModule(new ModuleBuilder('test.nebula:b:1.0.0-1').addDependency('test.nebula:a:1.0.0').build())
64 | .addModule(new ModuleBuilder('test.nebula:b:1.0.0').addDependency('test.nebula:a:1.0.0').build())
65 | .addModule(new ModuleBuilder('test.nebula:c:1.0.0-eap-1').addDependency('test.nebula:a:1.0.0').build())
66 | .addModule(new ModuleBuilder('test.nebula:c:1.0.0').addDependency('test.nebula:a:1.0.0').build())
67 | .addModule(new ModuleBuilder('test.nebula:d:1.0.0.pr.1').addDependency('test.nebula:a:1.0.0').build())
68 | .addModule(new ModuleBuilder('test.nebula:d:1.0.0').addDependency('test.nebula:a:1.0.0').build())
69 | .build()
70 | File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo()
71 |
72 | rulesJsonFile << '''\
73 | {
74 | "deny": [], "reject": [], "substitute": [], "replace": [],
75 | "align": [
76 | {
77 | "name": "testNebula",
78 | "group": "test.nebula",
79 | "reason": "Align test.nebula dependencies",
80 | "author": "Example Person ",
81 | "date": "2016-03-17T20:21:20.368Z"
82 | }
83 | ]
84 | }
85 | '''.stripIndent()
86 |
87 | buildFile << """\
88 | repositories {
89 | maven { url = '${mavenrepo.absolutePath}' }
90 | }
91 | dependencies {
92 | implementation 'test.nebula:b:1.0.0-1'
93 | implementation 'test.nebula:c:1.0.0-eap-1'
94 | implementation 'test.nebula:d:1.0.0.pr.1'
95 | }
96 | """.stripIndent()
97 |
98 | when:
99 | def results = runTasks('dependencies', '--configuration', 'compileClasspath')
100 | def insightResults = runTasks('dependencyInsight', '--dependency', 'test.nebula')
101 |
102 | then:
103 | results.output.contains '--- test.nebula:b:1.0.0-1\n'
104 | results.output.contains '--- test.nebula:c:1.0.0-eap-1 -> 1.0.0\n'
105 | results.output.contains '--- test.nebula:d:1.0.0.pr.1 -> 1.0.0\n'
106 | results.output.contains '\\--- test.nebula:a:1.0.0\n'
107 | assert insightResults.output.contains("belongs to platform aligned-platform:$moduleName-0-for-test.nebula:1.0.0")
108 | assert insightResults.output.findAll("belongs to platform aligned-platform:$moduleName-0-for-test.nebula:1.0.0").size() == 4
109 |
110 | }
111 |
112 | @Unroll
113 | def 'requesting major.+ with no release version available'() {
114 | def graph = new DependencyGraphBuilder()
115 | .addModule('test.nebula:a:1.0.0')
116 | .addModule(new ModuleBuilder('test.nebula:b:1.0.0-1').addDependency('test.nebula:a:1.0.0').build())
117 | .addModule(new ModuleBuilder('test.nebula:c:1.0.0-eap-1').addDependency('test.nebula:a:1.0.0').build())
118 | .addModule(new ModuleBuilder('test.nebula:d:1.0.0.pr.1').addDependency('test.nebula:a:1.0.0').build())
119 | .build()
120 | File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo()
121 |
122 | rulesJsonFile << '''\
123 | {
124 | "deny": [], "reject": [], "substitute": [], "replace": [],
125 | "align": [
126 | {
127 | "name": "testNebula",
128 | "group": "test.nebula",
129 | "reason": "Align test.nebula dependencies",
130 | "author": "Example Person ",
131 | "date": "2016-03-17T20:21:20.368Z"
132 | }
133 | ]
134 | }
135 | '''.stripIndent()
136 |
137 | buildFile << """\
138 | repositories {
139 | maven { url = '${mavenrepo.absolutePath}' }
140 | }
141 | dependencies {
142 | implementation 'test.nebula:b:1.+'
143 | implementation 'test.nebula:c:1.+'
144 | implementation 'test.nebula:d:1.+'
145 | }
146 | """.stripIndent()
147 |
148 | when:
149 | def results = runTasks('dependencies', '--configuration', 'compileClasspath')
150 | def insightResults = runTasks('dependencyInsight', '--dependency', 'test.nebula')
151 | insightResults.output.contains("belongs to platform aligned-platform:$moduleName-0-for-test.nebula:1.0.0")
152 | insightResults.output.findAll("belongs to platform aligned-platform:$moduleName-0-for-test.nebula:1.0.0").size() == 4
153 |
154 | then:
155 | results.output.contains '--- test.nebula:b:1.+ -> 1.0.0-1\n'
156 | results.output.contains '--- test.nebula:c:1.+ -> 1.0.0-eap-1\n'
157 | results.output.contains '--- test.nebula:d:1.+ -> 1.0.0.pr.1\n'
158 | results.output.contains '\\--- test.nebula:a:1.0.0\n'
159 | }
160 |
161 | @Unroll
162 | def 'requesting major.+ with a release version available'() {
163 | def graph = new DependencyGraphBuilder()
164 | .addModule('test.nebula:a:1.0.0')
165 | .addModule(new ModuleBuilder('test.nebula:b:1.0.0-1').addDependency('test.nebula:a:1.0.0').build())
166 | .addModule(new ModuleBuilder('test.nebula:b:1.0.0').addDependency('test.nebula:a:1.0.0').build())
167 | .addModule(new ModuleBuilder('test.nebula:c:1.0.0-eap-1').addDependency('test.nebula:a:1.0.0').build())
168 | .addModule(new ModuleBuilder('test.nebula:c:1.0.0').addDependency('test.nebula:a:1.0.0').build())
169 | .addModule(new ModuleBuilder('test.nebula:d:1.0.0.pr.1').addDependency('test.nebula:a:1.0.0').build())
170 | .addModule(new ModuleBuilder('test.nebula:d:1.0.0').addDependency('test.nebula:a:1.0.0').build())
171 | .build()
172 | File mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen").generateTestMavenRepo()
173 |
174 | rulesJsonFile << '''\
175 | {
176 | "deny": [], "reject": [], "substitute": [], "replace": [],
177 | "align": [
178 | {
179 | "name": "testNebula",
180 | "group": "test.nebula",
181 | "reason": "Align test.nebula dependencies",
182 | "author": "Example Person ",
183 | "date": "2016-03-17T20:21:20.368Z"
184 | }
185 | ]
186 | }
187 | '''.stripIndent()
188 |
189 | buildFile << """\
190 | repositories {
191 | maven { url = '${mavenrepo.absolutePath}' }
192 | }
193 | dependencies {
194 | implementation 'test.nebula:b:1.+'
195 | implementation 'test.nebula:c:1.+'
196 | implementation 'test.nebula:d:1.+'
197 | }
198 | """.stripIndent()
199 |
200 | when:
201 | def results = runTasks('dependencies', '--configuration', 'compileClasspath')
202 | def insightResults = runTasks('dependencyInsight', '--dependency', 'test.nebula')
203 |
204 | then:
205 | results.output.contains '--- test.nebula:b:1.+ -> 1.0.0-1\n'
206 | results.output.contains '--- test.nebula:c:1.+ -> 1.0.0\n'
207 | results.output.contains '--- test.nebula:d:1.+ -> 1.0.0\n'
208 | results.output.contains '\\--- test.nebula:a:1.0.0\n'
209 | assert insightResults.output.contains("belongs to platform aligned-platform:$moduleName-0-for-test.nebula:1.0.0")
210 | assert insightResults.output.findAll("belongs to platform aligned-platform:$moduleName-0-for-test.nebula:1.0.0").size() == 4
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/src/integTest/groovy/nebula/plugin/resolutionrules/IgnoredConfigurationsWithRulesSpec.groovy:
--------------------------------------------------------------------------------
1 | package nebula.plugin.resolutionrules
2 |
3 | import spock.lang.IgnoreIf
4 |
5 |
6 | class IgnoredConfigurationsWithRulesSpec extends AbstractIntegrationTestKitSpec {
7 | File rulesJsonFile
8 |
9 | def setup() {
10 | rulesJsonFile = new File(projectDir, "${moduleName}.json")
11 | definePluginOutsideOfPluginBlock = true
12 |
13 | buildFile << """
14 | apply plugin: 'java'
15 | apply plugin: 'com.netflix.nebula.resolution-rules'
16 |
17 | repositories {
18 | mavenCentral()
19 | }
20 |
21 | dependencies {
22 | resolutionRules files("$rulesJsonFile")
23 | }
24 | """.stripIndent()
25 |
26 | rulesJsonFile << """
27 | {
28 | "substitute": [
29 | {
30 | "module" : "bouncycastle:bcmail-jdk16",
31 | "with" : "org.bouncycastle:bcmail-jdk16:latest.release",
32 | "reason" : "The latest version of BC is required, using the new coordinate",
33 | "author" : "Danny Thomas ",
34 | "date" : "2015-10-07T20:21:20.368Z"
35 | },
36 | {
37 | "module": "com.google.guava:guava:19.0-rc2",
38 | "with": "com.google.guava:guava:19.0-rc1",
39 | "reason" : "Guava 19.0-rc2 is not permitted, use previous release",
40 | "author" : "Danny Thomas ",
41 | "date" : "2015-10-07T20:21:20.368Z"
42 | }
43 | ]
44 | }
45 | """.stripIndent()
46 | }
47 |
48 |
49 | def 'does not substitute dependency if the configuration is ignored'() {
50 | given:
51 | buildFile << """
52 |
53 | configurations {
54 | myIgnoredConfiguration
55 | myExtraIgnoredConfiguration
56 | }
57 |
58 | dependencies {
59 | myIgnoredConfiguration 'com.google.guava:guava:19.0-rc2'
60 | myExtraIgnoredConfiguration'bouncycastle:bcmail-jdk16:1.40'
61 | }
62 | """.stripIndent()
63 |
64 | when:
65 | def result = runTasks('dependencies', '--configuration', 'compileClasspath', '-PresolutionRulesIgnoredConfigurations=myIgnoredConfiguration,myExtraIgnoredConfiguration')
66 |
67 | then:
68 | !result.output.contains('com.google.guava:guava:19.0-rc2 -> 19.0-rc1')
69 | !result.output.contains('bouncycastle:bcmail-jdk16:1.40 -> org.bouncycastle:bcmail-jdk16:')
70 | }
71 |
72 |
73 | @IgnoreIf({ !jvm.isJava17Compatible() })
74 | def 'does not apply for configurations housing only built artifacts'() {
75 | given:
76 | System.setProperty('ignoreDeprecations', 'true')
77 |
78 | forwardOutput = true
79 | keepFiles = true
80 | def intermediateBuildFileText = buildFile.text
81 | buildFile.delete()
82 | buildFile.createNewFile()
83 | buildFile << """
84 | buildscript {
85 | repositories {
86 | maven {
87 | url = uri("https://plugins.gradle.org/m2/")
88 | }
89 | }
90 | dependencies {
91 | classpath("org.springframework.boot:spring-boot-gradle-plugin:3.+")
92 | }
93 | }""".stripIndent()
94 | buildFile << intermediateBuildFileText
95 | buildFile << """
96 | apply plugin: 'org.springframework.boot'
97 | dependencies {
98 | implementation 'com.google.guava:guava:19.0-rc2'
99 | implementation 'bouncycastle:bcmail-jdk16:1.40'
100 | }
101 | tasks.named("bootJar") {
102 | mainClass = 'com.test.HelloWorldApp'
103 | }
104 | project.tasks.register("viewSpecificConfigurations").configure {
105 | it.dependsOn project.tasks.named('bootJar')
106 | it.dependsOn project.tasks.named('assemble')
107 | doLast {
108 | project.configurations.matching { it.name == 'bootArchives' || it.name == 'archives' }.each {
109 | println "Dependencies for \${it}: " + it.allDependencies
110 | println "Artifacts for \${it}: " + it.allArtifacts
111 | }
112 | }
113 | }
114 | """.stripIndent()
115 | writeJavaSourceFile("""
116 | package com.test;
117 |
118 | class HelloWorldApp {
119 | public static void main(String[] args) {
120 | System.out.println("Hello World");
121 | }
122 | }""".stripIndent())
123 | new File(projectDir, 'gradle.properties').text = '''org.gradle.configuration-cache=false'''.stripIndent()
124 |
125 | when:
126 | def result = runTasks( 'bootJar', 'assemble')
127 | def resolutionResult = runTasks( 'viewSpecificConfigurations')
128 |
129 | then:
130 | !result.output.contains('FAIL')
131 | !resolutionResult.output.contains('FAIL')
132 | resolutionResult.output.contains(':jar')
133 | }
134 |
135 | }
136 |
--------------------------------------------------------------------------------
/src/integTest/groovy/nebula/plugin/resolutionrules/SubstituteRulesSpec.groovy:
--------------------------------------------------------------------------------
1 | package nebula.plugin.resolutionrules
2 |
3 |
4 | class SubstituteRulesSpec extends AbstractIntegrationTestKitSpec {
5 | File rulesJsonFile
6 |
7 | def setup() {
8 | rulesJsonFile = new File(projectDir, "${moduleName}.json")
9 | definePluginOutsideOfPluginBlock = true
10 |
11 | buildFile << """
12 | apply plugin: 'java'
13 | apply plugin: 'com.netflix.nebula.resolution-rules'
14 |
15 | repositories {
16 | mavenCentral()
17 | }
18 |
19 | dependencies {
20 | resolutionRules files("$rulesJsonFile")
21 | }
22 | """.stripIndent()
23 |
24 | rulesJsonFile << """
25 | {
26 | "substitute": [
27 | {
28 | "module" : "bouncycastle:bcmail-jdk16",
29 | "with" : "org.bouncycastle:bcmail-jdk16:latest.release",
30 | "reason" : "The latest version of BC is required, using the new coordinate",
31 | "author" : "Danny Thomas ",
32 | "date" : "2015-10-07T20:21:20.368Z"
33 | },
34 | {
35 | "module": "com.google.guava:guava:19.0-rc2",
36 | "with": "com.google.guava:guava:19.0-rc1",
37 | "reason" : "Guava 19.0-rc2 is not permitted, use previous release",
38 | "author" : "Danny Thomas ",
39 | "date" : "2015-10-07T20:21:20.368Z"
40 | },
41 | {
42 | "module": "com.sun.jersey:jersey-bundle:(,1.18)",
43 | "with": "com.sun.jersey:jersey-bundle:1.18",
44 | "reason" : "Use a minimum version of 1.18",
45 | "author" : "Danny Thomas ",
46 | "date" : "2015-10-07T20:21:20.368Z"
47 | }
48 | ]
49 | }
50 | """.stripIndent()
51 | }
52 |
53 | def 'substitute dependency without version'() {
54 | given:
55 | buildFile << """
56 | dependencies {
57 | implementation'bouncycastle:bcmail-jdk16:1.40'
58 | }
59 | """.stripIndent()
60 |
61 | when:
62 | def result = runTasks('dependencies', '--configuration', 'compileClasspath')
63 |
64 | then:
65 | result.output.contains('bouncycastle:bcmail-jdk16:1.40 -> org.bouncycastle:bcmail-jdk16:')
66 | }
67 |
68 | def 'substitute details are shown by dependencyInsight'() {
69 | given:
70 | buildFile << """\
71 | dependencies {
72 | implementation'bouncycastle:bcmail-jdk16:1.40'
73 | }
74 | """.stripIndent()
75 |
76 | when:
77 | def result = runTasks('dependencyInsight', '--configuration', 'compileClasspath', '--dependency', 'bcmail-jdk16')
78 |
79 | then:
80 | !result.output.contains('org.bouncycastle:bcmail-jdk16:1.40')
81 | result.output.contains('org.bouncycastle:bcmail-jdk16:')
82 | result.output.contains('The latest version of BC is required, using the new coordinate')
83 | }
84 |
85 | def 'substitute dependency with version'() {
86 | given:
87 | buildFile << """
88 | dependencies {
89 | implementation'com.google.guava:guava:19.0-rc2'
90 | }
91 | """.stripIndent()
92 |
93 | when:
94 | def result = runTasks('dependencies', '--configuration', 'compileClasspath')
95 |
96 | then:
97 | result.output.contains('com.google.guava:guava:19.0-rc2 -> 19.0-rc1')
98 | }
99 |
100 | def 'substitute dependency outside allowed range'() {
101 | given:
102 | buildFile << """
103 | dependencies {
104 | implementation'com.sun.jersey:jersey-bundle:1.17'
105 | }
106 | """.stripIndent()
107 |
108 | when:
109 | def result = runTasks('dependencies', '--configuration', 'compileClasspath')
110 |
111 | then:
112 | result.output.contains('om.sun.jersey:jersey-bundle:1.17 -> 1.18')
113 | }
114 |
115 | def 'do not substitute dependency above allowed range'() {
116 | given:
117 | buildFile << """
118 | dependencies {
119 | implementation'com.sun.jersey:jersey-bundle:1.18'
120 | }
121 | """.stripIndent()
122 |
123 | when:
124 | def result = runTasks('dependencies', '--configuration', 'compileClasspath')
125 |
126 | then:
127 | result.output.contains('om.sun.jersey:jersey-bundle:1.18\n')
128 | }
129 |
130 | def 'missing version in substitution rule'() {
131 | given:
132 | rulesJsonFile.delete()
133 | rulesJsonFile << """
134 | {
135 | "substitute": [
136 | {
137 | "module" : "asm:asm",
138 | "with" : "org.ow2.asm:asm",
139 | "reason" : "The asm group id changed for 4.0 and later",
140 | "author" : "Danny Thomas ",
141 | "date" : "2015-10-07T20:21:20.368Z"
142 | }
143 | ]
144 | }
145 | """.stripIndent()
146 |
147 | buildFile << """
148 | dependencies {
149 | implementation'asm:asm:3.3.1'
150 | }
151 | """.stripIndent()
152 |
153 | when:
154 | def result = runTasksAndFail('dependencies', '--configuration', 'compileClasspath')
155 |
156 | then:
157 | result.output.contains("The dependency to be substituted (org.ow2.asm:asm) must have a version. Rule missing-version-in-substitution-rule is invalid")
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/src/integTest/groovy/nebula/plugin/resolutionrules/SubstituteRulesWithRangesSpec.groovy:
--------------------------------------------------------------------------------
1 | package nebula.plugin.resolutionrules
2 |
3 |
4 | import nebula.test.dependencies.DependencyGraphBuilder
5 | import nebula.test.dependencies.GradleDependencyGenerator
6 | import nebula.test.dependencies.ModuleBuilder
7 | import spock.lang.Unroll
8 |
9 | /**
10 | * Substitutions apply to declared dependencies, not resolved ones
11 | * See: https://github.com/nebula-plugins/gradle-nebula-integration/issues/50#issuecomment-433934842
12 | */
13 | class SubstituteRulesWithRangesSpec extends AbstractIntegrationTestKitSpec {
14 | File rulesJsonFile
15 |
16 | def setup() {
17 | definePluginOutsideOfPluginBlock = true
18 | rulesJsonFile = new File(projectDir, "${moduleName}.json")
19 |
20 | def graph = new DependencyGraphBuilder()
21 | .addModule('test.nebula:apricot:1.0')
22 | .addModule('test.nebula:apricot:1.2')
23 | .addModule('test.nebula:apricot:1.4')
24 | .addModule('test.nebula:apricot:1.4.0-dev.1+mybranch.e1c43c7') // version in the form of ..-dev.#+.
25 | .addModule('test.nebula:apricot:1.6')
26 | .addModule('test.nebula:apricot:1.8')
27 |
28 | .addModule('test.nebula:apricot:2.0')
29 | .build()
30 | def mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen")
31 | mavenrepo.generateTestMavenRepo()
32 |
33 | buildFile << """
34 | apply plugin: 'java'
35 | apply plugin: 'com.netflix.nebula.resolution-rules'
36 |
37 | repositories {
38 | mavenCentral()
39 | ${mavenrepo.mavenRepositoryBlock}
40 | }
41 |
42 | dependencies {
43 | resolutionRules files("$rulesJsonFile")
44 | }
45 | """.stripIndent()
46 |
47 | definePluginOutsideOfPluginBlock = true
48 | keepFiles = true
49 | }
50 |
51 | @Unroll
52 | def 'substitutions apply to declared dependencies when #description'() {
53 | given:
54 | createSubstitutionRule(substituteFromRange, substituteToVersion)
55 |
56 | buildFile << """
57 | dependencies {
58 | implementation 'test.nebula:apricot:$definedVersion'
59 | }
60 | """.stripIndent()
61 |
62 | when:
63 | def result = runTasks('dependencyInsight', '--dependency', 'apricot')
64 |
65 | then:
66 | result.output.contains("test.nebula:apricot:$definedVersion -> $substituteToVersion")
67 |
68 | where:
69 | definedVersion | substituteFromRange | substituteToVersion | description
70 | '1.0' | "(,1.4]" | '1.6' | "x is less than or equal to"
71 | '1.0' | "(,1.4)" | '1.6' | "x is less than"
72 | '1.8' | "[1.6,)" | '1.4' | "x is greater than or equal to"
73 | '1.8' | "(1.6,)" | '1.4' | "x is greater than"
74 | '1.4' | "(1.2,1.6)" | '1.8' | "x is less than and greater than"
75 | '1.4' | "[1.2,1.6]" | '1.8' | "x is less than or equal to and greater than or equal to"
76 | '1.4.0-dev.1+mybranch.e1c43c7' | "[1.2,1.6]" | '1.8' | "version string contains a 'plus'"
77 | }
78 |
79 | @Unroll
80 | def 'do not substitute declared dependencies outside of range when #description'() {
81 | given:
82 | createSubstitutionRule(substituteFromRange, '1.0')
83 |
84 | buildFile << """
85 | dependencies {
86 | implementation 'test.nebula:apricot:$definedVersion'
87 | }
88 | """.stripIndent()
89 |
90 | when:
91 | def result = runTasks('dependencyInsight', '--dependency', 'apricot')
92 |
93 | then:
94 | result.output.contains("test.nebula:apricot:$definedVersion\n")
95 |
96 | where:
97 | definedVersion | substituteFromRange | description
98 | '1.8' | "(,1.4]" | "x is not less than or equal to"
99 | '1.8' | "(,1.4)" | "x is not less than"
100 | '1.2' | "[1.6,)" | "x is not greater than or equal to"
101 | '1.2' | "(1.6,)" | "x is not greater than"
102 | '1.8' | "(1.2,1.6)" | "x is not less than and greater than"
103 | '1.8' | "[1.2,1.6]" | "x is not less than or equal to and greater than or equal to"
104 | }
105 |
106 | @Unroll
107 | def 'do not substitute dynamic major.+ dependency when #description'() {
108 | given:
109 | createSubstitutionRule(substituteFromRange, substituteToVersion)
110 |
111 | buildFile << """
112 | dependencies {
113 | implementation 'test.nebula:apricot:$definedVersion'
114 | }
115 | """.stripIndent()
116 |
117 | when:
118 | def result = runTasks('dependencyInsight', '--dependency', 'apricot')
119 |
120 | then:
121 | result.output.contains("test.nebula:apricot:$definedVersion -> 1.8\n")
122 |
123 | where:
124 | definedVersion | substituteFromRange | substituteToVersion | description
125 | '1.+' | "[1.6,)" | '1.4' | "x is greater than or equal to"
126 | '1.+' | "(1.6,)" | '1.4' | "x is greater than"
127 | '1.+' | "(1.2,2.0)" | '1.0' | "x is less than and greater than"
128 | '1.+' | "[1.2,2.0]" | '1.0' | "x is less than or equal to and greater than or equal to"
129 | }
130 |
131 | @Unroll
132 | def 'do not substitute dynamic major.+ dependency outside of range when #description'() {
133 | given:
134 | createSubstitutionRule(substituteFromRange, '1.0')
135 |
136 | buildFile << """
137 | dependencies {
138 | implementation 'test.nebula:apricot:$definedVersion'
139 | }
140 | """.stripIndent()
141 |
142 | when:
143 | def result = runTasks('dependencyInsight', '--dependency', 'apricot')
144 |
145 | then:
146 | result.output.contains("test.nebula:apricot:$definedVersion -> 1.8")
147 |
148 | where:
149 | definedVersion | substituteFromRange | description
150 | '1.+' | "(,1.4]" | "x is not less than or equal to"
151 | '1.+' | "(,1.4)" | "x is not less than"
152 | '1.+' | "[2.0,)" | "x is not greater than or equal to"
153 | '1.+' | "(2.0,)" | "x is not greater than"
154 | '1.+' | "(1.2,1.6)" | "x is not less than and greater than"
155 | '1.+' | "[1.2,1.6]" | "x is not less than or equal to and greater than or equal to"
156 | }
157 |
158 | @Unroll
159 | def 'do not substitute dynamic latest.release dependency when #description'() {
160 | given:
161 | createSubstitutionRule(substituteFromRange, substituteToVersion)
162 |
163 | buildFile << """
164 | dependencies {
165 | implementation 'test.nebula:apricot:$definedVersion'
166 | }
167 | """.stripIndent()
168 |
169 | when:
170 | def result = runTasks('dependencyInsight', '--dependency', 'apricot')
171 |
172 | then:
173 | result.output.contains("test.nebula:apricot:$definedVersion -> 2.0\n")
174 |
175 | where:
176 | definedVersion | substituteFromRange | substituteToVersion | description
177 | 'latest.release' | "[1.6,)" | '1.4' | "x is greater than or equal to"
178 | 'latest.release' | "(1.6,)" | '1.4' | "x is greater than"
179 | }
180 |
181 | @Unroll
182 | def 'do not substitute dynamic latest.release dependency outside of range when #description'() {
183 | given:
184 | createSubstitutionRule(substituteFromRange, '1.0')
185 |
186 | buildFile << """
187 | dependencies {
188 | implementation 'test.nebula:apricot:$definedVersion'
189 | }
190 | """.stripIndent()
191 |
192 | when:
193 | def result = runTasks('dependencyInsight', '--dependency', 'apricot')
194 |
195 | then:
196 | result.output.contains("test.nebula:apricot:$definedVersion -> 2.0\n")
197 |
198 | where:
199 | definedVersion | substituteFromRange | description
200 | 'latest.release' | "(,1.4]" | "x is not less than or equal to"
201 | 'latest.release' | "(,1.4)" | "x is not less than"
202 | 'latest.release' | "(1.2,1.6)" | "x is not less than and greater than"
203 | 'latest.release' | "[1.2,1.6]" | "x is not less than or equal to and greater than or equal to"
204 | }
205 |
206 | @Unroll
207 | def 'substitutions apply to transitive dependencies where #description'() {
208 | given:
209 | def graph = new DependencyGraphBuilder()
210 | .addModule(new ModuleBuilder('test.nebula:blueberry:5.0').addDependency("test.nebula:apricot:$definedVersion").build())
211 | .build()
212 | def mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen")
213 | mavenrepo.generateTestMavenRepo()
214 |
215 | createSubstitutionRule(substituteFromRange, substituteToVersion)
216 |
217 | buildFile << """
218 | dependencies {
219 | implementation 'test.nebula:blueberry:5.0'
220 | }
221 | """.stripIndent()
222 |
223 | when:
224 | def result = runTasks('dependencyInsight', '--dependency', 'apricot')
225 |
226 | then:
227 | result.output.contains("test.nebula:apricot:$definedVersion -> $substituteToVersion")
228 |
229 | where:
230 | definedVersion | substituteFromRange | substituteToVersion | description
231 | '1.0' | "(,1.4]" | '1.6' | "x is less than or equal to"
232 | '1.0' | "(,1.4)" | '1.6' | "x is less than"
233 | '1.8' | "[1.6,)" | '1.4' | "x is greater than or equal to"
234 | '1.8' | "(1.6,)" | '1.4' | "x is greater than"
235 | '1.4' | "(1.2,1.6)" | '1.8' | "x is less than and greater than"
236 | '1.4' | "[1.2,1.6]" | '1.8' | "x is less than or equal to and greater than or equal to"
237 | '1.4.0-dev.1+mybranch.e1c43c7' | "[1.2,1.6]" | '1.8' | "version string contains a 'plus'"
238 | }
239 |
240 | @Unroll
241 | def 'do not substitute transitive declared dependencies outside of range when #description'() {
242 | given:
243 | def graph = new DependencyGraphBuilder()
244 | .addModule(new ModuleBuilder('test.nebula:blueberry:5.0').addDependency("test.nebula:apricot:$definedVersion").build())
245 | .build()
246 | def mavenrepo = new GradleDependencyGenerator(graph, "${projectDir}/testrepogen")
247 | mavenrepo.generateTestMavenRepo()
248 |
249 | createSubstitutionRule(substituteFromRange, '1.0')
250 |
251 | buildFile << """
252 | dependencies {
253 | implementation 'test.nebula:blueberry:5.0'
254 | }
255 | """.stripIndent()
256 |
257 | when:
258 | def result = runTasks('dependencyInsight', '--dependency', 'apricot')
259 |
260 | then:
261 | result.output.contains("test.nebula:apricot:$definedVersion\n")
262 |
263 | where:
264 | definedVersion | substituteFromRange | description
265 | '1.8' | "(,1.4]" | "x is not less than or equal to"
266 | '1.8' | "(,1.4)" | "x is not less than"
267 | '1.2' | "[1.6,)" | "x is not greater than or equal to"
268 | '1.2' | "(1.6,)" | "x is not greater than"
269 | '1.8' | "(1.2,1.6)" | "x is not less than and greater than"
270 | '1.8' | "[1.2,1.6]" | "x is not less than or equal to and greater than or equal to"
271 | }
272 |
273 | private File createSubstitutionRule(String substituteFromRange, substituteToVersion) {
274 | assert substituteFromRange != null
275 |
276 | rulesJsonFile << """
277 | {
278 | "substitute": [
279 | {
280 | "module": "test.nebula:apricot:$substituteFromRange",
281 | "with": "test.nebula:apricot:$substituteToVersion",
282 | "reason" : "this version is better",
283 | "author": "Example Person ",
284 | "date": "2016-03-17T20:21:20.368Z"
285 | }
286 | ]
287 | }
288 | """.stripIndent()
289 | }
290 | }
291 |
--------------------------------------------------------------------------------
/src/integTest/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
22 |
23 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/main/kotlin/nebula/plugin/resolutionrules/alignRule.kt:
--------------------------------------------------------------------------------
1 | package nebula.plugin.resolutionrules
2 |
3 | import org.gradle.api.Project
4 | import org.gradle.api.artifacts.*
5 | import org.gradle.api.artifacts.ModuleVersionIdentifier
6 | import org.gradle.api.logging.Logger
7 | import org.gradle.api.logging.Logging
8 | import java.io.Serializable
9 | import java.util.concurrent.ConcurrentHashMap
10 | import java.util.regex.Matcher
11 | import java.util.regex.Pattern
12 | import javax.inject.Inject
13 |
14 | data class AlignRule(val name: String?,
15 | val group: Regex,
16 | val includes: List = emptyList(),
17 | val excludes: List = emptyList(),
18 | val match: String?,
19 | override var ruleSet: String?,
20 | override val reason: String,
21 | override val author: String,
22 | override val date: String,
23 | var belongsToName: String?) : BasicRule, Serializable {
24 |
25 | private val groupPattern = group.toPattern()
26 | private val includesPatterns = includes.map { it.toPattern() }
27 | private val excludesPatterns = excludes.map { it.toPattern() }
28 | private val alignMatchers = ConcurrentHashMap()
29 |
30 | override fun apply(project: Project,
31 | configuration: Configuration,
32 | resolutionStrategy: ResolutionStrategy,
33 | extension: NebulaResolutionRulesExtension) {
34 | //TODO this rule is applied repeatedly for each configuration. Ideally it should be taken out and
35 | //applied only once per project
36 | if (configuration.name == "compileClasspath") { // This is one way to ensure it'll be run for only one configuration
37 | project.dependencies.components.all(AlignedPlatformMetadataRule::class.java) {
38 | it.params(this)
39 | }
40 | }
41 | }
42 |
43 | fun ruleMatches(dep: ModuleVersionIdentifier) = ruleMatches(dep.group, dep.name)
44 |
45 | fun ruleMatches(group: String, name: String) = alignMatchers.computeIfAbsent(Thread.currentThread()) {
46 | AlignMatcher(this, groupPattern, includesPatterns, excludesPatterns)
47 | }.matches(group, name)
48 | }
49 |
50 | class AlignMatcher(val rule: AlignRule, groupPattern: Pattern, includesPatterns: List, excludesPatterns: List) {
51 | private val groupMatcher = groupPattern.matcher("")
52 | private val includeMatchers = includesPatterns.map { it.matcher("") }
53 | private val excludeMatchers = excludesPatterns.map { it.matcher("") }
54 |
55 | private fun Matcher.matches(input: String, type: String): Boolean {
56 | reset(input)
57 | return try {
58 | matches()
59 | } catch (e: Exception) {
60 | throw java.lang.IllegalArgumentException("Failed to use matcher '$this' from type '$type' to match '$input'\n" +
61 | "Rule: $rule", e)
62 | }
63 | }
64 |
65 | fun matches(group: String, name: String): Boolean {
66 | return groupMatcher.matches(group, "group") &&
67 | (includeMatchers.isEmpty() || includeMatchers.any { it.matches(name, "includes") }) &&
68 | (excludeMatchers.isEmpty() || excludeMatchers.none { it.matches(name, "excludes") })
69 | }
70 | }
71 |
72 | //@CacheableRule
73 | open class AlignedPlatformMetadataRule @Inject constructor(val rule: AlignRule) : ComponentMetadataRule, Serializable {
74 | private val logger: Logger = Logging.getLogger(AlignedPlatformMetadataRule::class.java)
75 |
76 | override fun execute(componentMetadataContext: ComponentMetadataContext?) {
77 | modifyDetails(componentMetadataContext!!.details)
78 | }
79 |
80 | fun modifyDetails(details: ComponentMetadataDetails) {
81 | if (rule.ruleMatches(details.id)) {
82 | details.belongsTo("aligned-platform:${rule.belongsToName}:${details.id.version}")
83 | logger.debug("Aligning platform based on '${details.id.group}:${details.id.name}:${details.id.version}' from align rule with group '${rule.group}'")
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/main/kotlin/nebula/plugin/resolutionrules/configurations.kt:
--------------------------------------------------------------------------------
1 | package nebula.plugin.resolutionrules
2 |
3 | import org.gradle.api.artifacts.Configuration
4 | import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal
5 | import java.lang.reflect.Field
6 | import java.lang.reflect.Modifier
7 |
8 |
9 | /**
10 | * Various reflection hackiness follows due to deficiencies in the Gradle configuration APIs:
11 | *
12 | * - We can't add the configuration to the configuration container to get the addAction handlers, because it causes ConcurrentModificationExceptions
13 | * - We can't set the configuration name on copyRecursive, which makes for confusing logging output when we're resolving our configurations
14 | */
15 | fun Any.setField(name: String, value: Any) {
16 | val field = javaClass.findDeclaredField(name)
17 | field.isAccessible = true
18 |
19 | lateinit var modifiersField: Field
20 | try {
21 | modifiersField = Field::class.java.getDeclaredField("modifiers")
22 | } catch (e: NoSuchFieldException) {
23 | try {
24 | val getDeclaredFields0 = Class::class.java.getDeclaredMethod("getDeclaredFields0", Boolean::class.javaPrimitiveType)
25 | val accessibleBeforeSet: Boolean = getDeclaredFields0.isAccessible
26 | getDeclaredFields0.isAccessible = true
27 | @Suppress("UNCHECKED_CAST") val declaredFields = getDeclaredFields0.invoke(Field::class.java, false) as Array
28 | getDeclaredFields0.isAccessible = accessibleBeforeSet
29 | for (declaredField in declaredFields) {
30 | if ("modifiers" == declaredField.name) {
31 | modifiersField = declaredField
32 | break
33 | }
34 | }
35 | } catch (ex: Exception) {
36 | e.addSuppressed(ex)
37 | throw e
38 | }
39 | }
40 | modifiersField.isAccessible = true
41 | modifiersField.setInt(field, field.modifiers and Modifier.FINAL.inv())
42 |
43 | field.set(this, value)
44 | }
45 |
46 | tailrec fun Class.findDeclaredField(name: String): Field {
47 | val field = declaredFields
48 | .filter { it.name == name }
49 | .singleOrNull()
50 | if (field != null) {
51 | return field
52 | } else if (superclass != null) {
53 | return superclass.findDeclaredField(name)
54 | }
55 | throw IllegalArgumentException("Could not find field $name")
56 | }
57 |
58 | fun Configuration.getObservedState(): Configuration.State {
59 | val f: Field = this::class.java.findDeclaredField("observedState")
60 | f.isAccessible = true
61 | val resolvedState = f.get(this) as ConfigurationInternal.InternalState
62 | return if(resolvedState != ConfigurationInternal.InternalState.UNRESOLVED)
63 | Configuration.State.RESOLVED else Configuration.State.UNRESOLVED
64 | }
65 |
66 |
--------------------------------------------------------------------------------
/src/main/kotlin/nebula/plugin/resolutionrules/extensions.kt:
--------------------------------------------------------------------------------
1 | package nebula.plugin.resolutionrules
2 |
3 | import org.gradle.api.Project
4 |
5 | inline fun Collection.mapToSet(transform: (T) -> R): Set {
6 | return mapTo(LinkedHashSet(size), transform)
7 | }
8 |
9 | fun Project.findStringProperty(name: String): String? = if (hasProperty(name)) property(name) as String? else null
10 |
11 | fun parseRuleNames(ruleNames: String): Set =
12 | ruleNames.split(",").map { it.trim() }.filter { it.isNotEmpty()} .toSet()
--------------------------------------------------------------------------------
/src/main/kotlin/nebula/plugin/resolutionrules/json.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016-2017 Netflix, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package nebula.plugin.resolutionrules
18 |
19 | import com.fasterxml.jackson.databind.*
20 | import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
21 |
22 | fun objectMapper(): ObjectMapper {
23 | return jacksonObjectMapper()
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/kotlin/nebula/plugin/resolutionrules/plugin.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2016 Netflix, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package nebula.plugin.resolutionrules
18 |
19 | import com.fasterxml.jackson.module.kotlin.readValue
20 | import com.netflix.nebula.interop.onExecute
21 | import org.gradle.api.Plugin
22 | import org.gradle.api.Project
23 | import org.gradle.api.artifacts.Configuration
24 | import org.gradle.api.artifacts.ConfigurationContainer
25 | import org.gradle.api.logging.Logger
26 | import org.gradle.api.logging.Logging
27 | import org.gradle.api.provider.Property
28 | import org.gradle.api.provider.Provider
29 | import org.gradle.api.services.BuildService
30 | import org.gradle.api.services.BuildServiceParameters
31 | import java.io.File
32 | import java.io.Serializable
33 | import java.util.*
34 | import java.util.stream.Collectors
35 | import java.util.stream.Stream
36 | import java.util.zip.ZipFile
37 | import javax.inject.Inject
38 | import kotlin.collections.ArrayList
39 | import kotlin.collections.LinkedHashMap
40 |
41 | const val RESOLUTION_RULES_CONFIG_NAME = "resolutionRules"
42 |
43 | class ResolutionRulesPlugin : Plugin {
44 | private lateinit var project: Project
45 | private lateinit var configurations: ConfigurationContainer
46 | private lateinit var extension: NebulaResolutionRulesExtension
47 | private val ignoredConfigurationPrefixes = listOf(
48 | RESOLUTION_RULES_CONFIG_NAME,
49 | SPRING_VERSION_MANAGEMENT_CONFIG_NAME,
50 | NEBULA_RECOMMENDER_BOM_CONFIG_NAME,
51 | SCALA_INCREMENTAL_ANALYSIS_CONFIGURATION_PREFIX,
52 | KTLINT_CONFIGURATION_PREFIX,
53 | REPOSITORY_CONTENT_DESCRIPTOR_CONFIGURATION_PREFIX,
54 | BOOT_ARCHIVES_CONFIGURATION_NAME,
55 | ARCHIVES_CONFIGURATION_NAME,
56 | )
57 | private val ignoredConfigurationSuffixes = listOf(PMD_CONFIGURATION_SUFFIX)
58 |
59 | companion object {
60 | val Logger: Logger = Logging.getLogger(ResolutionRulesPlugin::class.java)
61 |
62 | const val NEBULA_RECOMMENDER_BOM_CONFIG_NAME: String = "nebulaRecommenderBom"
63 | const val SPRING_VERSION_MANAGEMENT_CONFIG_NAME = "versionManagement"
64 | const val KTLINT_CONFIGURATION_PREFIX = "ktlint"
65 | const val PMD_CONFIGURATION_SUFFIX = "PmdAuxClasspath"
66 | const val SCALA_INCREMENTAL_ANALYSIS_CONFIGURATION_PREFIX = "incrementalScalaAnalysis"
67 | const val REPOSITORY_CONTENT_DESCRIPTOR_CONFIGURATION_PREFIX = "repositoryContentDescriptor"
68 | const val BOOT_ARCHIVES_CONFIGURATION_NAME = "bootArchives"
69 | const val ARCHIVES_CONFIGURATION_NAME = "archives"
70 | const val OPTIONAL_PREFIX = "optional-"
71 | const val OPTIONAL_RULES_PROJECT_PROPERTY = "nebulaResolutionRules.optional"
72 | const val INCLUDE_RULES_PROJECT_PROPERTY = "nebulaResolutionRules.include"
73 | const val EXCLUDE_RULES_PROJECT_PROPERTY = "nebulaResolutionRules.exclude"
74 | }
75 |
76 | override fun apply(project: Project) {
77 | this.project = project
78 | configurations = project.configurations
79 | extension =
80 | project.extensions.create("nebulaResolutionRules", NebulaResolutionRulesExtension::class.java, project)
81 | addRulesFromProjectProperties(project, extension)
82 | val rootProject = project.rootProject
83 | val configuration = project.configurations.maybeCreate(RESOLUTION_RULES_CONFIG_NAME)
84 | if (project != rootProject) {
85 | configuration.isCanBeConsumed = false
86 | val rootProjectDependency = project.dependencies.project(
87 | mapOf("path" to rootProject.path, "configuration" to RESOLUTION_RULES_CONFIG_NAME)
88 | )
89 | configuration.withDependencies { dependencies ->
90 | dependencies.add(rootProjectDependency)
91 | }
92 | }
93 | if (rootProject.extensions.findByType(NebulaResolutionRulesExtension::class.java) == null) {
94 | rootProject.extensions.create(
95 | "nebulaResolutionRules",
96 | NebulaResolutionRulesExtension::class.java,
97 | rootProject
98 | )
99 | }
100 |
101 | project.configurations.all { config ->
102 | if (ignoredConfigurationPrefixes.any { config.name.startsWith(it) }) {
103 | return@all
104 | }
105 |
106 | if (ignoredConfigurationSuffixes.any { config.name.endsWith(it) }) {
107 | return@all
108 | }
109 |
110 | var dependencyRulesApplied = false
111 | project.onExecute {
112 | val ruleSet = extension.ruleSet()
113 | when {
114 | config.state != Configuration.State.UNRESOLVED || config.getObservedState() != Configuration.State.UNRESOLVED -> Logger.warn(
115 | "Dependency resolution rules will not be applied to $config, it was resolved before the project was executed"
116 | )
117 | else -> {
118 | ruleSet.dependencyRulesPartOne().forEach { rule ->
119 | rule.apply(project, config, config.resolutionStrategy, extension)
120 | }
121 |
122 | ruleSet.dependencyRulesPartTwo().forEach { rule ->
123 | rule.apply(project, config, config.resolutionStrategy, extension)
124 | }
125 | dependencyRulesApplied = true
126 | }
127 | }
128 | }
129 | }
130 | }
131 |
132 | /**
133 | * Search for optional, include and exclude rules in project properties
134 | * Add them to the extension if found
135 | */
136 | private fun addRulesFromProjectProperties(
137 | project: Project,
138 | extension: NebulaResolutionRulesExtension
139 | ) {
140 | val optionalRules = project.findStringProperty(OPTIONAL_RULES_PROJECT_PROPERTY)
141 | optionalRules?.let { rules -> parseRuleNames(rules).forEach { extension.optional.add(it) } }
142 | val includeRules = project.findStringProperty(INCLUDE_RULES_PROJECT_PROPERTY)
143 | includeRules?.let { rules -> parseRuleNames(rules).forEach { extension.include.add(it) } }
144 | val excludeRules = project.findStringProperty(EXCLUDE_RULES_PROJECT_PROPERTY)
145 | excludeRules?.let { rules -> parseRuleNames(rules).forEach { extension.exclude.add(it) } }
146 | }
147 |
148 | }
149 |
150 | abstract class NebulaResolutionRulesService : BuildService {
151 | companion object {
152 | private val Logger: Logger = Logging.getLogger(NebulaResolutionRulesService::class.java)
153 | private val Mapper = objectMapper()
154 |
155 | fun registerService(project: Project): Provider {
156 | return project.gradle.sharedServices.registerIfAbsent(
157 | "nebulaResolutionRules",
158 | NebulaResolutionRulesService::class.java
159 | ) { spec ->
160 | val resolutionRules = resolveResolutionRules(project)
161 | spec.parameters.getResolutionRules().set(ResolutionRules(resolutionRules))
162 | }
163 | }
164 |
165 | private fun resolveResolutionRules(project: Project): Map {
166 | val configuration = project.configurations.getByName(RESOLUTION_RULES_CONFIG_NAME)
167 | configuration.resolve().stream().use { stream ->
168 | return stream.flatMap { file ->
169 | when (file.extension) {
170 | "json" -> {
171 | Logger.debug("nebula.resolution-rules uses: {}", file.name)
172 | Stream.of(file.absolutePath to file.readBytes())
173 | }
174 | "jar", "zip" -> {
175 | Logger.info("nebula.resolution-rules is using ruleset: {}", file.name)
176 | val zipFile = ZipFile(file)
177 | Collections.list(zipFile.entries()).stream()
178 | .onClose(zipFile::close)
179 | .flatMap { entry ->
180 | val entryFile = File(entry.name)
181 | if (entryFile.extension == "json") {
182 | Stream.of("${file.absolutePath}!${entry.name}" to zipFile.getInputStream(entry).readBytes())
183 | } else Stream.empty()
184 | }
185 | }
186 | else -> {
187 | Logger.debug("Unsupported rules file extension for {}", file)
188 | Stream.empty()
189 | }
190 | }
191 | }.parallel()
192 | .map { (path, bytes) ->
193 | val filePath = if (path.contains("!")) path.substringAfter("!") else path
194 | val file = File(filePath)
195 | val ruleSetName = file.nameWithoutExtension
196 | Logger.debug("Using {} ({}) a dependency rules source", ruleSetName, path)
197 | Mapper.readValue(bytes).withName(ruleSetName)
198 | }.collect(
199 | Collectors.toMap(
200 | { it.name },
201 | { it },
202 | { r1, r2 ->
203 | Logger.info("Found rules with the same name. Overriding existing ruleset {}", r1.name)
204 | r2
205 | },
206 | { LinkedHashMap() })
207 | )
208 | }
209 | }
210 | }
211 |
212 | interface Params : BuildServiceParameters {
213 | fun getResolutionRules(): Property
214 | }
215 |
216 | class ResolutionRules(val byFile: Map) : Serializable
217 | }
218 |
219 | open class NebulaResolutionRulesExtension @Inject constructor(private val project: Project) {
220 | var include = ArrayList()
221 | set(value) {
222 | field.addAll(value)
223 | }
224 |
225 | var optional = ArrayList()
226 | set(value) {
227 | field.addAll(value)
228 | }
229 | var exclude = ArrayList()
230 | // Setter should add to the existing array rather than replacing all values
231 | set(value) {
232 | field.addAll(value)
233 | }
234 |
235 | fun ruleSet(): RuleSet {
236 | val service = NebulaResolutionRulesService.registerService(project).get()
237 | val rulesByFile = service.parameters
238 | .getResolutionRules()
239 | .get()
240 | .byFile
241 | return rulesByFile.filterKeys { ruleSet ->
242 | when {
243 | ruleSet.startsWith(ResolutionRulesPlugin.OPTIONAL_PREFIX) -> {
244 | val ruleSetWithoutPrefix = ruleSet.substring(ResolutionRulesPlugin.OPTIONAL_PREFIX.length)
245 | optional.contains(ruleSetWithoutPrefix)
246 | }
247 | include.isNotEmpty() -> include.contains(ruleSet)
248 | else -> !exclude.contains(ruleSet)
249 | }
250 | }.values.flatten()
251 | }
252 | }
253 |
--------------------------------------------------------------------------------
/src/main/kotlin/nebula/plugin/resolutionrules/rules.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Netflix, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package nebula.plugin.resolutionrules
18 |
19 | import com.netflix.nebula.interop.VersionWithSelector
20 | import org.gradle.api.Action
21 | import org.gradle.api.Project
22 | import org.gradle.api.artifacts.*
23 | import org.gradle.api.artifacts.component.ModuleComponentSelector
24 | import org.gradle.api.internal.artifacts.DefaultModuleIdentifier
25 | import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
26 | import org.gradle.api.internal.artifacts.ivyservice.dependencysubstitution.DefaultDependencySubstitutions
27 | import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.ExactVersionSelector
28 | import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionSelector
29 | import java.io.Serializable
30 |
31 | interface Rule : Serializable {
32 | fun apply(
33 | project: Project,
34 | configuration: Configuration,
35 | resolutionStrategy: ResolutionStrategy,
36 | extension: NebulaResolutionRulesExtension
37 | )
38 | }
39 |
40 | interface BasicRule : Rule {
41 | var ruleSet: String?
42 | val reason: String
43 | val author: String
44 | val date: String
45 | }
46 |
47 | interface ModuleRule : BasicRule {
48 | val module: String
49 | }
50 |
51 | data class RuleSet(
52 | val replace: List = emptyList(),
53 | val substitute: List = emptyList(),
54 | val reject: List = emptyList(),
55 | val deny: List = emptyList(),
56 | val exclude: List = emptyList(),
57 | val align: List = emptyList()
58 | ) : Serializable {
59 |
60 | lateinit var name: String
61 |
62 | fun dependencyRulesPartOne() =
63 | listOf(replace, deny, exclude).flatten() + listOf(SubstituteRules(substitute), RejectRules(reject))
64 |
65 | fun dependencyRulesPartTwo() = listOf(align).flatten()
66 |
67 | fun generateAlignmentBelongsToName() {
68 | align.forEachIndexed { index, alignRule ->
69 | var abbreviatedAlignGroup = alignRule.group.toString()
70 | .replace("|", "-or-")
71 |
72 | val onlyAlphabeticalRegex = Regex("[^A-Za-z.\\-]")
73 | abbreviatedAlignGroup = onlyAlphabeticalRegex.replace(abbreviatedAlignGroup, "")
74 |
75 | alignRule.belongsToName = "$name-$index-for-$abbreviatedAlignGroup"
76 | }
77 | }
78 | }
79 |
80 | fun RuleSet.withName(ruleSetName: String): RuleSet {
81 | name = ruleSetName
82 | listOf(replace, substitute, reject, deny, exclude, align).flatten().forEach { it.ruleSet = ruleSetName }
83 | generateAlignmentBelongsToName()
84 | return this
85 | }
86 |
87 | fun Collection.flatten() = RuleSet(
88 | flatMap { it.replace },
89 | flatMap { it.substitute },
90 | flatMap { it.reject },
91 | flatMap { it.deny },
92 | flatMap { it.exclude },
93 | flatMap { it.align })
94 |
95 | data class ReplaceRule(
96 | override val module: String,
97 | val with: String,
98 | override var ruleSet: String?,
99 | override val reason: String,
100 | override val author: String,
101 | override val date: String
102 | ) : ModuleRule {
103 | private val moduleId = module.toModuleId()
104 | private val withId = with.toModuleId()
105 |
106 | override fun apply(
107 | project: Project,
108 | configuration: Configuration,
109 | resolutionStrategy: ResolutionStrategy,
110 | extension: NebulaResolutionRulesExtension
111 | ) {
112 | project.dependencies.modules.module(moduleId) {
113 | val details = it as ComponentModuleMetadataDetails
114 | val message = "replaced $module -> $with because '$reason' by rule $ruleSet"
115 | details.replacedBy(withId, message)
116 | }
117 | }
118 | }
119 |
120 | data class SubstituteRule(
121 | val module: String, val with: String, override var ruleSet: String?,
122 | override val reason: String, override val author: String, override val date: String
123 | ) : BasicRule, Serializable {
124 | @Transient lateinit var substitutedVersionId: ModuleVersionIdentifier
125 | @Transient lateinit var withComponentSelector: ModuleComponentSelector
126 | @Transient lateinit var versionSelector: VersionSelector
127 |
128 | override fun apply(
129 | project: Project,
130 | configuration: Configuration,
131 | resolutionStrategy: ResolutionStrategy,
132 | extension: NebulaResolutionRulesExtension
133 | ) {
134 | throw UnsupportedOperationException("Substitution rules cannot be applied directly and must be applied via SubstituteRules")
135 | }
136 |
137 | fun isInitialized(): Boolean = this::substitutedVersionId.isInitialized
138 |
139 | fun acceptsVersion(version: String): Boolean {
140 | return if (substitutedVersionId.version.isNotEmpty()) {
141 | when (VersionWithSelector(version).asSelector()) {
142 | is ExactVersionSelector -> versionSelector.accept(version)
143 | else -> false
144 | }
145 | } else true
146 | }
147 | }
148 |
149 | class SubstituteRules(val rules: List) : Rule {
150 | companion object {
151 | private val SUBSTITUTIONS_ADD_RULE = DefaultDependencySubstitutions::class.java.getDeclaredMethod(
152 | "addSubstitution",
153 | Action::class.java,
154 | Boolean::class.java
155 | ).apply { isAccessible = true }
156 | }
157 |
158 | @Transient private lateinit var rulesById: Map>
159 |
160 | override fun apply(
161 | project: Project,
162 | configuration: Configuration,
163 | resolutionStrategy: ResolutionStrategy,
164 | extension: NebulaResolutionRulesExtension
165 | ) {
166 | if (!this::rulesById.isInitialized) {
167 | val substitution = resolutionStrategy.dependencySubstitution
168 | rulesById = rules.map { rule ->
169 | if (!rule.isInitialized()) {
170 | rule.substitutedVersionId = rule.module.toModuleVersionId()
171 | val withModule = substitution.module(rule.with)
172 | if (withModule !is ModuleComponentSelector) {
173 | throw SubstituteRuleMissingVersionException(rule.with, rule)
174 | }
175 | rule.withComponentSelector = withModule
176 | rule.versionSelector = VersionWithSelector(rule.substitutedVersionId.version).asSelector()
177 | }
178 | rule
179 | }.groupBy { it.substitutedVersionId.module }
180 | .mapValues { entry -> entry.value.sortedBy { it.substitutedVersionId.version } }
181 | }
182 |
183 | val substitutionAction = Action { details ->
184 | val requested = details.requested
185 | if (requested is ModuleComponentSelector) {
186 | val rules = rulesById[requested.moduleIdentifier] ?: return@Action
187 | rules.forEach { rule ->
188 | val withComponentSelector = rule.withComponentSelector
189 | if (rule.acceptsVersion(requested.version)) {
190 | val message =
191 | "substituted ${rule.substitutedVersionId} with $withComponentSelector because '${rule.reason}' by rule ${rule.ruleSet}"
192 | details.useTarget(
193 | withComponentSelector,
194 | message
195 | )
196 | return@Action
197 | }
198 | }
199 | }
200 | }
201 |
202 | /*
203 | * Unfortunately impossible to avoid an internal/protected method dependency for now:
204 | *
205 | * - We can't dependencySubstitutions.all because it causes the configuration to be resolved at task graph calculation time due to the possibility of project substitutions there
206 | * - Likewise eachDependency has it's own performance issues - https://github.com/gradle/gradle/issues/16151
207 | *
208 | * There's no alternative to all that only allows module substitution and we only ever substitute modules for modules, so this is completely safe.
209 | */
210 | SUBSTITUTIONS_ADD_RULE.invoke(resolutionStrategy.dependencySubstitution, substitutionAction, false)
211 | }
212 | }
213 |
214 | data class RejectRule(
215 | override val module: String,
216 | override var ruleSet: String?,
217 | override val reason: String,
218 | override val author: String,
219 | override val date: String
220 | ) : ModuleRule {
221 | val moduleVersionId = module.toModuleVersionId()
222 | @Transient lateinit var versionSelector: VersionSelector
223 |
224 | override fun apply(
225 | project: Project,
226 | configuration: Configuration,
227 | resolutionStrategy: ResolutionStrategy,
228 | extension: NebulaResolutionRulesExtension
229 | ) {
230 | throw UnsupportedOperationException("Reject rules cannot be applied directly and must be applied via RejectRules")
231 | }
232 |
233 | fun hasVersionSelector(): Boolean = this::versionSelector.isInitialized
234 | }
235 |
236 | data class RejectRules(val rules: List) : Rule {
237 | private val ruleByModuleIdentifier = rules.groupBy { it.moduleVersionId.module }
238 |
239 | override fun apply(
240 | project: Project,
241 | configuration: Configuration,
242 | resolutionStrategy: ResolutionStrategy,
243 | extension: NebulaResolutionRulesExtension
244 | ) {
245 | resolutionStrategy.componentSelection.all { selection ->
246 | val candidate = selection.candidate
247 | val rules = ruleByModuleIdentifier[candidate.moduleIdentifier] ?: return@all
248 | rules.forEach { rule ->
249 | rule.versionSelector = VersionWithSelector(rule.moduleVersionId.version).asSelector()
250 | if (!rule.hasVersionSelector() || rule.versionSelector.accept(candidate.version)) {
251 | val message = "rejected by rule ${rule.ruleSet} because '${rule.reason}'"
252 | selection.reject(message)
253 | if (!rule.hasVersionSelector()) {
254 | return@forEach
255 | }
256 | }
257 | }
258 | }
259 | }
260 | }
261 |
262 | data class DenyRule(
263 | override val module: String,
264 | override var ruleSet: String?,
265 | override val reason: String,
266 | override val author: String,
267 | override val date: String
268 | ) : ModuleRule {
269 | private val moduleVersionId = module.toModuleVersionId()
270 |
271 | override fun apply(
272 | project: Project,
273 | configuration: Configuration,
274 | resolutionStrategy: ResolutionStrategy,
275 | extension: NebulaResolutionRulesExtension
276 | ) {
277 | val moduleId = moduleVersionId.module
278 | val match = configuration.allDependencies.find {
279 | it is ExternalModuleDependency && it.group == moduleId.group && it.name == moduleId.name
280 | }
281 | if (match != null && (moduleVersionId.version.isEmpty() || match.version == moduleVersionId.version)) {
282 | resolutionStrategy.componentSelection.withModule(moduleId) { selection ->
283 | val message = "denied by rule $ruleSet because '$reason'"
284 | selection.reject(message)
285 | }
286 | throw DependencyDeniedException(moduleVersionId, this)
287 | }
288 | }
289 | }
290 |
291 | data class ExcludeRule(
292 | override val module: String,
293 | override var ruleSet: String?,
294 | override val reason: String,
295 | override val author: String,
296 | override val date: String
297 | ) : ModuleRule {
298 | private val moduleId = module.toModuleId()
299 |
300 | @Override
301 | override fun apply(
302 | project: Project,
303 | configuration: Configuration,
304 | resolutionStrategy: ResolutionStrategy,
305 | extension: NebulaResolutionRulesExtension
306 | ) {
307 | val message =
308 | "excluded $moduleId and transitive dependencies for all dependencies of this configuration by rule $ruleSet"
309 | ResolutionRulesPlugin.Logger.debug(message)
310 | // TODO: would like a core Gradle feature that accepts a reason
311 | configuration.exclude(moduleId.group, moduleId.name)
312 | resolutionStrategy.componentSelection.withModule(moduleId.toString()) { selection ->
313 | selection.reject(message)
314 | }
315 | }
316 | }
317 |
318 | class DependencyDeniedException(moduleVersionId: ModuleVersionIdentifier, rule: DenyRule) :
319 | Exception("Dependency $moduleVersionId denied by rule ${rule.ruleSet}")
320 |
321 | class SubstituteRuleMissingVersionException(moduleId: String, rule: SubstituteRule) :
322 | Exception("The dependency to be substituted ($moduleId) must have a version. Rule ${rule.ruleSet} is invalid")
323 |
324 | fun Configuration.exclude(group: String, module: String) {
325 | exclude(mapOf("group" to group, "module" to module))
326 | }
327 |
328 | fun String.toModuleId(): ModuleIdentifier {
329 | val parts = split(":")
330 | check(parts.size == 2) { "$this is an invalid module identifier" }
331 | return DefaultModuleIdentifier.newId(parts[0], parts[1])
332 | }
333 |
334 | fun String.toModuleVersionId(): ModuleVersionIdentifier {
335 | val parts = split(":")
336 | val id = DefaultModuleIdentifier.newId(parts[0], parts[1])
337 | check((2..3).contains(parts.size)) { "$this is an invalid module identifier" }
338 | return DefaultModuleVersionIdentifier.newId(id, if (parts.size == 3) parts[2] else "")
339 | }
340 |
--------------------------------------------------------------------------------
/src/test/groovy/nebula/plugin/resolutionrules/AlignRuleMatcherTest.groovy:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2014-2019 Netflix, Inc.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | *
17 | */
18 |
19 | package nebula.plugin.resolutionrules
20 |
21 | import kotlin.text.Regex
22 | import org.junit.Test
23 | import spock.lang.Specification
24 |
25 | class AlignRuleMatcherTest extends Specification {
26 | private static final String inputGroup = "test-group"
27 | public static final String ruleName = "test-rule"
28 |
29 | @Test
30 | void groupMatcherMatches() {
31 | given:
32 | def alignRule = createAlignRule()
33 |
34 | when:
35 | def matches = alignRule.ruleMatches(inputGroup, "test-name")
36 |
37 | then:
38 | assert matches
39 | }
40 |
41 | @Test
42 | void includesMatcherMatches() {
43 | given:
44 | def includes = new ArrayList()
45 | includes.add(new Regex("a"))
46 | includes.add(new Regex("b"))
47 |
48 | def excludes = new ArrayList()
49 |
50 | def alignRule = createAlignRule(includes, excludes)
51 |
52 | when:
53 | def matches = alignRule.ruleMatches(inputGroup, "a")
54 |
55 | then:
56 | assert matches
57 | }
58 |
59 | @Test
60 | void excludesMatcherMatches() {
61 | given:
62 | def includes = new ArrayList()
63 |
64 | def excludes = new ArrayList()
65 | excludes.add(new Regex("y"))
66 | excludes.add(new Regex("z"))
67 |
68 | def alignRule = createAlignRule(includes, excludes)
69 |
70 | when:
71 | def matches = alignRule.ruleMatches(inputGroup, "z")
72 |
73 | then:
74 | assert !matches
75 | }
76 |
77 | @Test
78 | void groupDoesNotMatch() {
79 | given:
80 | def alignRule = createAlignRule()
81 |
82 | when:
83 | def matches = alignRule.ruleMatches("other-group", "test-name")
84 |
85 | then:
86 | assert !matches
87 | }
88 |
89 | @Test
90 | void includesDoNotMatch() {
91 | given:
92 | def includes = new ArrayList()
93 | includes.add(new Regex("a"))
94 | includes.add(new Regex("b"))
95 |
96 | def excludes = new ArrayList()
97 |
98 | def alignRule = createAlignRule(includes, excludes)
99 |
100 | when:
101 | def matches = alignRule.ruleMatches(inputGroup, "something-else")
102 |
103 | then:
104 | assert !matches
105 | }
106 |
107 | @Test
108 | void excludesDoNotMatch() {
109 | given:
110 | def includes = new ArrayList()
111 |
112 | def excludes = new ArrayList()
113 | excludes.add(new Regex("y"))
114 | excludes.add(new Regex("z"))
115 |
116 | def alignRule = createAlignRule(includes, excludes)
117 |
118 | when:
119 | def matches = alignRule.ruleMatches(inputGroup, "something-else")
120 |
121 | then:
122 | assert matches
123 | }
124 |
125 | private static AlignRule createAlignRule(ArrayList includes = new ArrayList(), ArrayList excludes = new ArrayList()) {
126 | new AlignRule(
127 | ruleName,
128 | new Regex(inputGroup),
129 | includes,
130 | excludes,
131 | "match...",
132 | "test-rule-set",
133 | "reason",
134 | "author",
135 | "2015-10-07T20:21:20.368Z",
136 | ""
137 | )
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/test/groovy/nebula/plugin/resolutionrules/NebulaResolutionRulesExtensionTest.groovy:
--------------------------------------------------------------------------------
1 | package nebula.plugin.resolutionrules
2 |
3 | import org.gradle.api.Project
4 | import spock.lang.Specification
5 |
6 | class NebulaResolutionRulesExtensionTest extends Specification {
7 |
8 | Project project = Mock(Project)
9 |
10 | def 'can assign values'() {
11 | given:
12 | def extension = new NebulaResolutionRulesExtension(project)
13 |
14 | when:
15 | extension.include = ['something']
16 | extension.optional = ['some-rule']
17 | extension.exclude = ['foo']
18 |
19 | then:
20 | extension.include.contains('something')
21 | extension.optional.contains('some-rule')
22 | extension.exclude.contains('foo')
23 | }
24 |
25 | def 'can assign and append to exclude value'() {
26 | given:
27 | def extension = new NebulaResolutionRulesExtension(project)
28 |
29 | when:
30 | extension.include = ['something']
31 | extension.include.add('else')
32 |
33 | extension.exclude = ['foo']
34 | extension.exclude.add('bar')
35 |
36 | extension.optional = ['some-rule']
37 | extension.optional.add('another-rule')
38 |
39 |
40 | then:
41 | extension.include.contains('something')
42 | extension.include.contains('else')
43 | extension.exclude.contains('foo')
44 | extension.exclude.contains('bar')
45 | extension.optional.contains('some-rule')
46 | extension.optional.contains('another-rule')
47 | }
48 |
49 | def 'can assign and setter does not override existing values'() {
50 | given:
51 | def extension = new NebulaResolutionRulesExtension(project)
52 |
53 | when:
54 |
55 | extension.include = ['something']
56 | extension.include = ['else']
57 |
58 | extension.exclude = ['foo']
59 | extension.exclude = ['bar']
60 |
61 | extension.optional = ['some-rule']
62 | extension.optional = ['another-rule']
63 |
64 | then:
65 | extension.include.contains('something')
66 | extension.include.contains('else')
67 | extension.exclude.contains('foo')
68 | extension.exclude.contains('bar')
69 | extension.optional.contains('some-rule')
70 | extension.optional.contains('another-rule')
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/test/groovy/nebula/plugin/resolutionrules/RulesTest.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Netflix, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package nebula.plugin.resolutionrules
19 |
20 | import spock.lang.Specification
21 |
22 | /**
23 | * Tests for {@link RuleSet}.
24 | */
25 | class RulesTest extends Specification {
26 | def 'json deserialised'() {
27 | when:
28 | String json = """{
29 | "replace" : [
30 | {
31 | "module" : "asm:asm",
32 | "with" : "org.ow2.asm:asm",
33 | "reason" : "The asm group id changed for 4.0 and later",
34 | "author" : "Danny Thomas ",
35 | "date" : "2015-10-07T20:21:20.368Z"
36 | }
37 | ],
38 | "substitute": [],
39 | "reject": [],
40 | "deny": [],
41 | "align": [],
42 | "exclude": []
43 | }"""
44 |
45 |
46 | RuleSet rules = parseJsonText(json)
47 |
48 | then:
49 | !rules.replace.isEmpty()
50 | rules.replace[0].class == ReplaceRule
51 | }
52 |
53 | def 'json deserialised with one category of rules'() {
54 | when:
55 | String json = """{
56 | "replace" : [
57 | {
58 | "module" : "asm:asm",
59 | "with" : "org.ow2.asm:asm",
60 | "reason" : "The asm group id changed for 4.0 and later",
61 | "author" : "Danny Thomas ",
62 | "date" : "2015-10-07T20:21:20.368Z"
63 | }
64 | ]
65 | }"""
66 |
67 |
68 | RuleSet rules = parseJsonText(json)
69 |
70 | then:
71 | !rules.replace.isEmpty()
72 | rules.replace[0].class == ReplaceRule
73 | }
74 |
75 | static RuleSet parseJsonText(String json) {
76 | def ruleSet = JsonKt.objectMapper().readValue(json, RuleSet)
77 | return RulesKt.withName(ruleSet, "dummy")
78 | }
79 | }
80 |
--------------------------------------------------------------------------------