Skip to content

Commit b008c2b

Browse files
authored
Merge pull request #134 from nebula-plugins/dannyt/improve-apply-performance
Improve apply performance
2 parents ba7260b + a8642ae commit b008c2b

File tree

3 files changed

+120
-155
lines changed

3 files changed

+120
-155
lines changed

src/main/kotlin/nebula/plugin/resolutionrules/moduleIdentifiers.kt

Lines changed: 0 additions & 52 deletions
This file was deleted.

src/main/kotlin/nebula/plugin/resolutionrules/rules.kt

Lines changed: 120 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@
1717
package nebula.plugin.resolutionrules
1818

1919
import com.netflix.nebula.interop.VersionWithSelector
20-
import org.gradle.api.Action
2120
import org.gradle.api.Project
2221
import org.gradle.api.artifacts.*
2322
import org.gradle.api.artifacts.component.ComponentSelector
2423
import org.gradle.api.artifacts.component.ModuleComponentSelector
24+
import org.gradle.api.internal.artifacts.DefaultModuleIdentifier
2525
import org.gradle.api.internal.artifacts.dsl.ModuleVersionSelectorParsers
26+
import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionSelector
2627
import org.gradle.api.logging.Logger
2728
import org.gradle.api.logging.Logging
2829
import java.io.Serializable
@@ -58,7 +59,7 @@ data class RuleSet(
5859
) {
5960

6061
fun dependencyRulesPartOne() =
61-
listOf(replace, substitute, reject, deny, exclude).flatten()
62+
listOf(replace, deny, exclude).flatten() + listOf(SubstituteRules(substitute), RejectRules(reject))
6263

6364
fun dependencyRulesPartTwo(coreAlignmentEnabled: Boolean) =
6465
if (coreAlignmentEnabled)
@@ -67,10 +68,7 @@ data class RuleSet(
6768
emptyList()
6869

6970
fun resolveRules(coreAlignmentEnabled: Boolean) =
70-
if (coreAlignmentEnabled)
71-
emptyList()
72-
else
73-
listOf(AlignRules(align))
71+
if (coreAlignmentEnabled) emptyList() else listOf(AlignRules(align))
7472

7573
fun generateAlignmentBelongsToName() {
7674
align.forEachIndexed { index, alignRule ->
@@ -109,20 +107,16 @@ data class ReplaceRule(
109107
override val author: String,
110108
override val date: String
111109
) : ModuleRule {
112-
private val moduleId = ModuleIdentifier.valueOf(module)
113-
private val withModuleId = ModuleIdentifier.valueOf(with)
114-
115110
override fun apply(
116111
project: Project,
117112
configuration: Configuration,
118113
resolutionStrategy: ResolutionStrategy,
119114
extension: NebulaResolutionRulesExtension
120115
) {
121-
project.dependencies.modules.module(moduleId.toString()) {
116+
project.dependencies.modules.module(module) {
122117
val details = it as ComponentModuleMetadataDetails
123-
val message =
124-
"replaced ${moduleId.organization}:${moduleId.name} -> ${withModuleId.organization}:${withModuleId.name} because '$reason' by rule $ruleSet"
125-
details.replacedBy(withModuleId.toString(), message)
118+
val message = "replaced $module -> $with because '$reason' by rule $ruleSet"
119+
details.replacedBy(with, message)
126120
}
127121
}
128122
}
@@ -131,10 +125,10 @@ data class SubstituteRule(
131125
val module: String, val with: String, override var ruleSet: String?,
132126
override val reason: String, override val author: String, override val date: String
133127
) : BasicRule, Serializable {
134-
private lateinit var substitutedModule: ComponentSelector
135-
private lateinit var withComponentSelector: ModuleComponentSelector
136-
private lateinit var withVersionSelector: ModuleVersionSelector
137-
private val versionSelector by lazy {
128+
lateinit var substitutedModule: ComponentSelector
129+
lateinit var withComponentSelector: ModuleComponentSelector
130+
lateinit var withVersionSelector: ModuleVersionSelector
131+
val versionSelector by lazy {
138132
val version = (substitutedModule as ModuleComponentSelector).version
139133
VersionWithSelector(version).asSelector()
140134
}
@@ -145,44 +139,71 @@ data class SubstituteRule(
145139
resolutionStrategy: ResolutionStrategy,
146140
extension: NebulaResolutionRulesExtension
147141
) {
148-
val substitution = resolutionStrategy.dependencySubstitution
149-
if (!this::substitutedModule.isInitialized) {
150-
substitutedModule = substitution.module(module)
151-
val withModule = substitution.module(with)
152-
if (withModule !is ModuleComponentSelector) {
153-
throw SubstituteRuleMissingVersionException(with, this)
154-
}
155-
withComponentSelector = substitution.module(with) as ModuleComponentSelector
156-
withVersionSelector = ModuleVersionSelectorParsers.parser().parseNotation(withComponentSelector.displayName)
142+
throw UnsupportedOperationException("Substitution rules cannot be applied directly and must be applied via SubstituteRules")
143+
}
144+
}
145+
146+
class SubstituteRules(val rules: List<SubstituteRule>) : Rule {
147+
private lateinit var versionedRulesById: Map<ModuleIdentifier, List<SubstituteRule>>
148+
private lateinit var unversionedRules: List<SubstituteRule>
149+
150+
override fun apply(
151+
project: Project,
152+
configuration: Configuration,
153+
resolutionStrategy: ResolutionStrategy,
154+
extension: NebulaResolutionRulesExtension
155+
) {
156+
if (!this::versionedRulesById.isInitialized) {
157+
val (versionedRules, unversionedRules) = rules.map { rule ->
158+
val substitution = resolutionStrategy.dependencySubstitution
159+
rule.substitutedModule = substitution.module(rule.module)
160+
val withModule = substitution.module(rule.with)
161+
if (withModule !is ModuleComponentSelector) {
162+
throw SubstituteRuleMissingVersionException(rule.with, rule)
163+
}
164+
rule.withComponentSelector = withModule
165+
rule.withVersionSelector = ModuleVersionSelectorParsers.parser()
166+
.parseNotation(rule.withComponentSelector.displayName)
167+
rule
168+
}.partition { it.substitutedModule is ModuleComponentSelector }
169+
170+
this.unversionedRules = unversionedRules
171+
versionedRulesById =
172+
versionedRules.groupBy { (it.substitutedModule as ModuleComponentSelector).moduleIdentifier }
157173
}
158174

159-
if (substitutedModule is ModuleComponentSelector) {
175+
if (versionedRulesById.isNotEmpty()) {
160176
// We use eachDependency because dependencySubstitutions.all causes configuration task dependencies to resolve at configuration time
161177
resolutionStrategy.eachDependency { details ->
162178
val requested = details.requested
163-
val moduleSelector = substitutedModule as ModuleComponentSelector
164-
if (requested.group == moduleSelector.group && requested.module.name == moduleSelector.module) {
165-
val requestedSelectorVersion = requested.version
166-
if (versionSelector.accept(requestedSelectorVersion)
167-
&& !requested.toString().contains(".+")
168-
&& !requested.toString().contains("latest")
179+
val requestedString = requested.toString()
180+
val rules = versionedRulesById[requested.module] ?: return@eachDependency
181+
val requestedSelectorVersion = requested.version
182+
rules.forEach { rule ->
183+
if (rule.versionSelector.accept(requestedSelectorVersion)
184+
&& !requestedString.contains(".+")
185+
&& !requestedString.contains("latest")
169186
) {
170187
// Note on `useTarget`:
171188
// Forcing modules via ResolutionStrategy.force(Object...) uses this capability.
172189
// from https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/DependencyResolveDetails.html
173-
details.useTarget(withVersionSelector) // We can't pass a ModuleComponentSelector here so we take the conversion hit
174-
details.because("substituted $substitutedModule with $withComponentSelector because '$reason' by rule $ruleSet")
190+
details.useTarget(rule.withVersionSelector) // We can't pass a ModuleComponentSelector here so we take the conversion hit
191+
details.because("substituted ${rule.substitutedModule} with ${rule.withComponentSelector} because '${rule.reason}' by rule ${rule.ruleSet}")
192+
return@eachDependency
175193
}
176194
}
177195
}
178-
} else {
179-
var message = "substituted $withComponentSelector because '$reason' by rule $ruleSet"
196+
}
197+
198+
unversionedRules.forEach { rule ->
199+
val substitutedModule = rule.substitutedModule
200+
val withComponentSelector = rule.withComponentSelector
201+
var message = "substituted $withComponentSelector because '${rule.reason}' by rule ${rule.ruleSet}"
180202

181203
val selectorNameSections = substitutedModule.displayName.split(":")
182204
if (selectorNameSections.size > 2) {
183205
val selectorGroupAndArtifact = "${selectorNameSections[0]}:${selectorNameSections[1]}"
184-
message =
185-
"substituted $selectorGroupAndArtifact with $withComponentSelector because '$reason' by rule $ruleSet"
206+
message = "substituted $selectorGroupAndArtifact with $withComponentSelector because '${rule.reason}' by rule ${rule.ruleSet}"
186207
}
187208

188209
resolutionStrategy.dependencySubstitution {
@@ -191,7 +212,6 @@ data class SubstituteRule(
191212
.with(withComponentSelector)
192213
}
193214
}
194-
195215
}
196216
}
197217

@@ -202,8 +222,31 @@ data class RejectRule(
202222
override val author: String,
203223
override val date: String
204224
) : ModuleRule {
205-
private val moduleId = ModuleVersionIdentifier.valueOf(module)
206-
private val versionSelector = VersionWithSelector(moduleId.version).asSelector()
225+
val moduleIdentifier: ModuleIdentifier
226+
lateinit var versionSelector: VersionSelector
227+
228+
init {
229+
val parts = module.split(":")
230+
moduleIdentifier = DefaultModuleIdentifier.newId(parts[0], parts[1])
231+
if (parts.size == 3) {
232+
versionSelector = VersionWithSelector(parts[2]).asSelector()
233+
}
234+
}
235+
236+
override fun apply(
237+
project: Project,
238+
configuration: Configuration,
239+
resolutionStrategy: ResolutionStrategy,
240+
extension: NebulaResolutionRulesExtension
241+
) {
242+
throw UnsupportedOperationException("Reject rules cannot be applied directly and must be applied via RejectRules")
243+
}
244+
245+
fun hasVersionSelector(): Boolean = this::versionSelector.isInitialized
246+
}
247+
248+
data class RejectRules(val rules: List<RejectRule>) : Rule {
249+
private val ruleByModuleIdentifier = rules.groupBy { it.moduleIdentifier }
207250

208251
override fun apply(
209252
project: Project,
@@ -213,10 +256,14 @@ data class RejectRule(
213256
) {
214257
resolutionStrategy.componentSelection.all { selection ->
215258
val candidate = selection.candidate
216-
if (candidate.group == moduleId.organization && candidate.module == moduleId.name) {
217-
if (!moduleId.hasVersion() || versionSelector.accept(candidate.version)) {
218-
val message = "rejected by rule $ruleSet because '$reason'"
259+
val rules = ruleByModuleIdentifier[candidate.moduleIdentifier] ?: return@all
260+
rules.forEach { rule ->
261+
if (!rule.hasVersionSelector() || rule.versionSelector.accept(candidate.version)) {
262+
val message = "rejected by rule ${rule.ruleSet} because '${rule.reason}'"
219263
selection.reject(message)
264+
if (!rule.hasVersionSelector()) {
265+
return@forEach
266+
}
220267
}
221268
}
222269
}
@@ -230,7 +277,16 @@ data class DenyRule(
230277
override val author: String,
231278
override val date: String
232279
) : ModuleRule {
233-
private val moduleId = ModuleVersionIdentifier.valueOf(module)
280+
private val moduleId: ModuleIdentifier
281+
private lateinit var version: String
282+
283+
init {
284+
val parts = module.split(":")
285+
moduleId = DefaultModuleIdentifier.newId(parts[0], parts[1])
286+
if (parts.size == 3) {
287+
version = parts[2]
288+
}
289+
}
234290

235291
override fun apply(
236292
project: Project,
@@ -239,16 +295,14 @@ data class DenyRule(
239295
extension: NebulaResolutionRulesExtension
240296
) {
241297
val match = configuration.allDependencies.find {
242-
it is ExternalModuleDependency && it.group == moduleId.organization && it.name == moduleId.name
298+
it is ExternalModuleDependency && it.group == moduleId.group && it.name == moduleId.name
243299
}
244-
if (match != null && (!moduleId.hasVersion() || match.version == moduleId.version)) {
245-
resolutionStrategy.componentSelection.withModule(
246-
"${moduleId.organization}:${moduleId.name}",
247-
Action<ComponentSelection> { selection ->
248-
val message = "denied by rule $ruleSet because '$reason'"
249-
selection.reject(message)
250-
})
251-
throw DependencyDeniedException(moduleId, this)
300+
if (match != null && (!this::version.isInitialized || match.version == version)) {
301+
resolutionStrategy.componentSelection.withModule(moduleId) { selection ->
302+
val message = "denied by rule $ruleSet because '$reason'"
303+
selection.reject(message)
304+
}
305+
throw DependencyDeniedException(module, this)
252306
}
253307
}
254308
}
@@ -261,7 +315,12 @@ data class ExcludeRule(
261315
override val date: String
262316
) : ModuleRule {
263317
private val logger: Logger = Logging.getLogger(ExcludeRule::class.java)
264-
private val moduleId = ModuleIdentifier.valueOf(module)
318+
private val moduleId: ModuleIdentifier
319+
320+
init {
321+
val parts = module.split(":")
322+
moduleId = DefaultModuleIdentifier.newId(parts[0], parts[1])
323+
}
265324

266325
@Override
267326
override fun apply(
@@ -271,19 +330,18 @@ data class ExcludeRule(
271330
extension: NebulaResolutionRulesExtension
272331
) {
273332
val message =
274-
"excluded ${moduleId.organization}:${moduleId.name} and transitive dependencies for all dependencies of this configuration by rule $ruleSet"
333+
"excluded $moduleId and transitive dependencies for all dependencies of this configuration by rule $ruleSet"
275334
logger.debug(message)
276335
// TODO: would like a core Gradle feature that accepts a reason
277-
configuration.exclude(moduleId.organization, moduleId.name)
278-
279-
resolutionStrategy.componentSelection.withModule("${moduleId.organization}:${moduleId.name}") { selection ->
336+
configuration.exclude(moduleId.group, moduleId.name)
337+
resolutionStrategy.componentSelection.withModule(moduleId.toString()) { selection ->
280338
selection.reject(message)
281339
}
282340
}
283341
}
284342

285-
class DependencyDeniedException(moduleId: ModuleVersionIdentifier, rule: DenyRule) :
286-
Exception("Dependency $moduleId denied by rule ${rule.ruleSet}")
343+
class DependencyDeniedException(notation: String, rule: DenyRule) :
344+
Exception("Dependency $notation denied by rule ${rule.ruleSet}")
287345

288346
class SubstituteRuleMissingVersionException(moduleId: String, rule: SubstituteRule) :
289347
Exception("The dependency to be substituted ($moduleId) must have a version. Rule ${rule.ruleSet} is invalid")

0 commit comments

Comments
 (0)