Skip to content

Commit aeb2a7a

Browse files
wip
1 parent 9463f7e commit aeb2a7a

File tree

6 files changed

+211
-46
lines changed

6 files changed

+211
-46
lines changed

src/main/kotlin/com/autonomousapps/internal/Bundles.kt

Lines changed: 116 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@
22
// SPDX-License-Identifier: Apache-2.0
33
package com.autonomousapps.internal
44

5+
import com.autonomousapps.ProjectType
56
import com.autonomousapps.extension.DependenciesHandler.SerializableBundles
67
import com.autonomousapps.graph.Graphs.children
78
import com.autonomousapps.graph.Graphs.reachableNodes
9+
import com.autonomousapps.internal.utils.filterToOrderedSet
810
import com.autonomousapps.model.*
911
import com.autonomousapps.model.Coordinates.Companion.copy
1012
import com.autonomousapps.model.internal.DependencyGraphView
1113
import com.autonomousapps.model.internal.declaration.Bucket
14+
import com.autonomousapps.model.internal.declaration.ConfigurationNames
15+
import com.autonomousapps.model.internal.declaration.Declaration
1216
import com.autonomousapps.model.internal.intermediates.Usage
1317

1418
/**
@@ -19,7 +23,11 @@ import com.autonomousapps.model.internal.intermediates.Usage
1923
* C -> used as API, part of bundle with B. Should not be declared!
2024
*/
2125
@Suppress("UnstableApiUsage")
22-
internal class Bundles private constructor(private val dependencyUsages: Map<Coordinates, Set<Usage>>) {
26+
internal class Bundles private constructor(
27+
private val dependencyUsages: Map<Coordinates, Set<Usage>>,
28+
private val declarations: Set<Declaration>,
29+
private val configurationNames: ConfigurationNames,
30+
) {
2331

2432
// a sort of adjacency-list structure
2533
private val parentKeyedBundle = mutableMapOf<Coordinates, MutableSet<Coordinates>>()
@@ -44,8 +52,103 @@ internal class Bundles private constructor(private val dependencyUsages: Map<Coo
4452
}
4553

4654
fun hasParentInBundle(coordinates: Coordinates): Boolean = parentPointers[coordinates] != null
55+
4756
fun findParentInBundle(coordinates: Coordinates): Coordinates? = parentPointers[coordinates]
4857

58+
/**
59+
* Requirements for calling this method:
60+
* 1. [hasParentInBundle] has already been called and it returns true. Otherwise this method will throw.
61+
* 2. [addAdvice] is add-advice.
62+
*
63+
* Can return either [addAdvice] exactly, or a change-advice. Will do the latter when the following is true:
64+
* 1. [originalCoordinates] is in a bundle with a declared parent.
65+
* 2. That declared parent is declared on another source set (currently either `commonMain` or `commonTest`) in a
66+
* Kotlin Multiplatform (KMP) project.
67+
* 3. The parent declaration is implementation-scoped and [addAdvice] is api-scoped (that is, we'd like to upgrade).
68+
*
69+
* In this case, we suggest changing the parent declaration from `commonMainImplementation` or
70+
* `commonTestImplementation` to `commonMainApi` or `commonTestApi`, respectively.
71+
*
72+
* We do this because KMP is a special case where we know a priori that the commonX source sets are upstream from
73+
* target-specific source sets. We're being very conservative by only targeting the `implementation` -> `api` upgrade;
74+
* that is, the goal is to provide _safe_ advice even more than maximally-correct advice. Part of the problem here is
75+
* we're in an intermediate state where we only support JVM targets: we can't know how the other targets use the
76+
* common dependencies.
77+
*
78+
* @see [maybePrimary]
79+
*/
80+
fun maybeParent(addAdvice: Advice, originalCoordinates: Coordinates): Advice {
81+
check(addAdvice.isAdd()) { "Must be add-advice" }
82+
83+
val parent = findParentInBundle(originalCoordinates)
84+
?: error("No parent for $originalCoordinates. Check 'hasParentInBundle()' before calling this method.")
85+
val preferredCoordinatesNotation = preferredCoordinates(parent, addAdvice)
86+
87+
val preferredBucket = Bucket.of(addAdvice.toConfiguration!!, configurationNames)
88+
89+
// Get the source set name for the addAdvice. E.g., "jvmMain".
90+
val adviceSourceSetName = DependencyScope.sourceSetName(addAdvice.toConfiguration)
91+
92+
// Find all declarations for this dependency. E.g., ["commonMainImplementation", "jvmMainImplementation"].
93+
val parentDeclarations = declarations
94+
.filterToOrderedSet { decl -> decl.identifier == preferredCoordinatesNotation.identifier }
95+
96+
// Find all declarations for the advice source set. E.g., ["jvmMainImplementation", "jvmMainApi"]. It's been known
97+
// to happen.
98+
var parentDeclaration = parentDeclarations
99+
.filter { declaration -> DependencyScope.sourceSetName(declaration.configurationName) == adviceSourceSetName }
100+
// Pick the "highest" one (api > implementation > everything else)
101+
.maxByOrNull { declaration ->
102+
when (declaration.bucket(configurationNames)) {
103+
Bucket.API -> 10
104+
Bucket.IMPL -> 1
105+
else -> -1
106+
}
107+
}
108+
109+
// If there are no declarations within the same source set
110+
if (parentDeclaration == null) {
111+
parentDeclaration = parentDeclarations
112+
.filter { declaration -> DependencyScope.sourceSetName(declaration.configurationName) != adviceSourceSetName }
113+
// Pick the "highest" one (api > implementation > everything else)
114+
.maxBy { declaration ->
115+
when (declaration.bucket(configurationNames)) {
116+
Bucket.API -> 10
117+
Bucket.IMPL -> 1
118+
else -> -1
119+
}
120+
}
121+
}
122+
123+
val parentBucket = parentDeclaration.bucket(configurationNames)
124+
125+
// Only change the advice if it's from implementation -> api. We don't change compileOnly or runtimeOnly advice.
126+
return if (preferredBucket == Bucket.API && parentBucket == Bucket.IMPL) {
127+
// TODO(tsr): there's a bug that probably impacts all project types, but I've only just noticed while working on KMP.
128+
val toConfiguration = if (configurationNames.projectType == ProjectType.KMP) {
129+
when (parentDeclaration.configurationName) {
130+
// Handle the `commonX` cases
131+
"commonMainImplementation" -> "commonMainApi"
132+
"commonTestImplementation" -> "commonTestApi"
133+
134+
// This means the parent is probably in the same source set?
135+
else -> addAdvice.toConfiguration
136+
}
137+
} else {
138+
// This means the parent is probably in the same source set?
139+
addAdvice.toConfiguration
140+
}
141+
142+
Advice.ofChange(
143+
coordinates = preferredCoordinatesNotation.withoutDefaultCapability(),
144+
fromConfiguration = parentDeclaration.configurationName,
145+
toConfiguration = toConfiguration,
146+
)
147+
} else {
148+
addAdvice
149+
}
150+
}
151+
49152
fun hasUsedChild(coordinates: Coordinates): Boolean {
50153
val children = parentKeyedBundle[coordinates] ?: return false
51154

@@ -65,25 +168,30 @@ internal class Bundles private constructor(private val dependencyUsages: Map<Coo
65168
fun maybePrimary(addAdvice: Advice, originalCoordinates: Coordinates): Advice {
66169
check(addAdvice.isAdd()) { "Must be add-advice" }
67170
return primaryPointers[originalCoordinates]?.let { primary ->
68-
val preferredCoordinatesNotation =
69-
if (primary is IncludedBuildCoordinates && addAdvice.coordinates is ProjectCoordinates) {
70-
primary.resolvedProject
71-
} else {
72-
primary
73-
}
171+
val preferredCoordinatesNotation = preferredCoordinates(primary, addAdvice)
74172
addAdvice.copy(coordinates = preferredCoordinatesNotation.withoutDefaultCapability())
75173
} ?: addAdvice
76174
}
77175

176+
private fun preferredCoordinates(coordinates: Coordinates, advice: Advice): Coordinates {
177+
return if (coordinates is IncludedBuildCoordinates && advice.coordinates is ProjectCoordinates) {
178+
coordinates.resolvedProject
179+
} else {
180+
coordinates
181+
}
182+
}
183+
78184
companion object {
79185
fun of(
80186
projectPath: String,
81187
dependencyGraph: Map<String, DependencyGraphView>,
82188
bundleRules: SerializableBundles,
83189
dependencyUsages: Map<Coordinates, Set<Usage>>,
190+
declarations: Set<Declaration>,
191+
configurationNames: ConfigurationNames,
84192
ignoreKtx: Boolean,
85193
): Bundles {
86-
val bundles = Bundles(dependencyUsages)
194+
val bundles = Bundles(dependencyUsages, declarations, configurationNames)
87195

88196
// Handle bundles with primary entry points
89197
bundleRules.primaries.forEach { (name, primaryId) ->

src/main/kotlin/com/autonomousapps/internal/transform/StandardTransform.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,13 @@ internal class StandardTransform(
3030
private val coordinates: Coordinates,
3131
private val declarations: Set<Declaration>,
3232
private val dependencyGraph: Map<String, DependencyGraphView>,
33-
private val supportedSourceSets: Set<String>,
3433
private val buildPath: String,
3534
private val explicitSourceSets: Set<String> = emptySet(),
3635
private val projectType: ProjectType,
36+
private val configurationNames: ConfigurationNames,
3737
private val isKaptApplied: Boolean = false,
3838
) : Usage.Transform {
3939

40-
private val configurationNames = ConfigurationNames(projectType, supportedSourceSets)
41-
4240
private val mapper = UsageToConfigurationMapper(
4341
isKaptApplied = isKaptApplied,
4442
projectType = projectType,

src/main/kotlin/com/autonomousapps/model/internal/declaration/ConfigurationNames.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import com.autonomousapps.model.source.SourceKind
1212

1313
/** Utility for mapping configuration names to related types. */
1414
internal class ConfigurationNames(
15-
private val projectType: ProjectType,
15+
val projectType: ProjectType,
1616
private val supportedSourceSetNames: Set<String>,
1717
) {
1818

src/main/kotlin/com/autonomousapps/model/source/SourceKind.kt

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -334,27 +334,8 @@ public data class KmpSourceKind(
334334

335335
// TODO(tsr): double-check this is correct.
336336
internal companion object {
337-
// kotlin source set: commonMain
338-
// - api : commonMainApi
339-
// - implementation : commonMainImplementation
340-
// - compile-only : commonMainCompileOnly
341-
// - runtime-only : commonMainRuntimeOnly
342-
// kotlin source set: commonTest
343-
// - (and so on)
344-
// kotlin source set: iosArm64Main
345-
// kotlin source set: iosArm64Test
346-
// kotlin source set: iosSimulatorArm64Main
347-
// kotlin source set: iosSimulatorArm64Test
348-
// kotlin source set: jvmMain
349-
// kotlin source set: jvmTest
350-
//
351-
// classpaths:
352-
// - jvmRuntimeClasspath
353-
// - jvmMainRuntimeClasspath
354-
// - jvmTestRuntimeClasspath
355-
// - jvmCompileClasspath
356-
// - jvmMainCompileClasspath
357-
// - jvmTestCompileClasspath
337+
const val COMMON_MAIN_NAME = "commonMain"
338+
const val COMMON_TEST_NAME = "commonTest"
358339
const val JVM_MAIN_NAME = "jvmMain"
359340
const val JVM_TEST_NAME = "jvmTest"
360341

src/main/kotlin/com/autonomousapps/tasks/ComputeAdviceTask.kt

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ public abstract class ComputeAdviceTask @Inject constructor(
161161

162162
val projectPath = parameters.projectPath.get()
163163
val buildPath = parameters.buildPath.get()
164-
val declarations = parameters.declarations.fromJsonSet<Declaration>()
164+
val declarations = parameters.declarations.fromJsonSet<Declaration>().toSortedSet()
165165
val dependencyGraph = DependencyGraphView.asMap(parameters.dependencyGraphViews)
166166
val androidScore = parameters.androidScoreReports.get()
167167
.map { it.fromJson<AndroidScoreVariant>() }
@@ -181,14 +181,16 @@ public abstract class ComputeAdviceTask @Inject constructor(
181181
val explicitSourceSets = parameters.explicitSourceSets.get()
182182
val projectType = parameters.projectType.get()
183183
val isKaptApplied = parameters.kapt.get()
184-
185184
val ignoreKtx = parameters.ignoreKtx.get()
185+
val configurationNames = ConfigurationNames(projectType, supportedSourceSets)
186186

187187
val bundles = Bundles.of(
188188
projectPath = projectPath,
189189
dependencyGraph = dependencyGraph,
190190
bundleRules = bundleRules,
191191
dependencyUsages = dependencyUsages,
192+
declarations = declarations,
193+
configurationNames = configurationNames,
192194
ignoreKtx = ignoreKtx,
193195
)
194196

@@ -203,6 +205,7 @@ public abstract class ComputeAdviceTask @Inject constructor(
203205
supportedSourceSets = supportedSourceSets,
204206
explicitSourceSets = explicitSourceSets,
205207
projectType = projectType,
208+
configurationNames = configurationNames,
206209
isKaptApplied = isKaptApplied,
207210
)
208211

@@ -275,11 +278,10 @@ internal class DependencyAdviceBuilder(
275278
private val supportedSourceSets: Set<String>,
276279
private val explicitSourceSets: Set<String>,
277280
private val projectType: ProjectType,
281+
private val configurationNames: ConfigurationNames,
278282
private val isKaptApplied: Boolean,
279283
) {
280284

281-
private val configurationNames = ConfigurationNames(projectType, supportedSourceSets)
282-
283285
/** The unfiltered advice. */
284286
val advice: Set<Advice>
285287

@@ -293,7 +295,8 @@ internal class DependencyAdviceBuilder(
293295
}
294296

295297
private fun computeDependencyAdvice(projectPath: String): Sequence<Advice> {
296-
val declarations = declarations.filterToSet { configurationNames.isForRegularDependency(it.configurationName) }
298+
val declarations =
299+
declarations.filterToOrderedSet { configurationNames.isForRegularDependency(it.configurationName) }
297300

298301
fun Advice.isRemoveTestDependencyOnSelf(): Boolean {
299302
return coordinates.identifier == projectPath
@@ -314,7 +317,7 @@ internal class DependencyAdviceBuilder(
314317
coordinates = coordinates,
315318
declarations = declarations,
316319
dependencyGraph = dependencyGraph,
317-
supportedSourceSets = supportedSourceSets,
320+
configurationNames = configurationNames,
318321
buildPath = buildPath,
319322
explicitSourceSets = explicitSourceSets,
320323
projectType = projectType,
@@ -336,7 +339,14 @@ internal class DependencyAdviceBuilder(
336339
advice.isAdd() && bundles.hasParentInBundle(originalCoordinates) -> {
337340
val parent = bundles.findParentInBundle(originalCoordinates)!!
338341
bundledTraces.add(BundleTrace.DeclaredParent(parent = parent, child = originalCoordinates))
339-
null
342+
343+
// TODO(tsr): need to update bundledTraces and Reason
344+
val parentAdvice = bundles.maybeParent(advice, originalCoordinates)
345+
if (parentAdvice != advice) {
346+
parentAdvice
347+
} else {
348+
null
349+
}
340350
}
341351

342352
// Optionally map given advice to "primary" advice, if bundle has a primary
@@ -368,17 +378,18 @@ internal class DependencyAdviceBuilder(
368378

369379
// nb: no bundle support for annotation processors
370380
private fun computeAnnotationProcessorAdvice(): Sequence<Advice> {
371-
val declarations = declarations.filterToSet { configurationNames.isForAnnotationProcessor(it.configurationName) }
381+
val declarations = declarations
382+
.filterToOrderedSet { configurationNames.isForAnnotationProcessor(it.configurationName) }
372383
return annotationProcessorUsages.asSequence()
373384
.flatMap { (coordinates, usages) ->
374385
StandardTransform(
375386
coordinates = coordinates,
376387
declarations = declarations,
377388
dependencyGraph = emptyMap(),
378-
supportedSourceSets = supportedSourceSets,
379389
buildPath = buildPath,
380390
explicitSourceSets = explicitSourceSets,
381391
projectType = projectType,
392+
configurationNames = configurationNames,
382393
isKaptApplied = isKaptApplied,
383394
).reduce(usages)
384395
}

0 commit comments

Comments
 (0)