Skip to content

Commit b954a8e

Browse files
authored
Update dependency resolution (#4)
* sort filetree entries * refactor Configurations/Attributes * add `exclusiveContent` filter for test projects * update test for project aggregation * wip * gradle 8.9 * make testFixture util public * minor build config tweaks * Update Configurations - Fix extendsFrom. - Add Category attribute (apparently it's better than Usage? Although there's no explanation for this gradle/gradle#27991 (comment)) - Experiment with filtering via artifact view... (but it doesn't work because Gradle sucks gradle/gradle#27991) - Create Configurations rather than register them, because Configurations get eagerly created anyway. * rename `named` to `name` (initially done to avoid `val name`'s getting clashing with `Named#getNamed()`, but the property is private so it doesn't have a getter so there's no clash) * rename DevPublishService to match class name * mark task constructors as internal * sort FileTree elements, because the order isn't stable, even on the same machine * refactoring to make simple, mark TaskActions as internal * make Configurations non-visible by default * tidy kdoc * change DevPublishTasksContainer to actual class, not an abstract Gradle managed type * update kdoc * add lazy logging utils * fix ApiPropagationTest * misc tidying * update api dump * fix/tidy MultiProjectTest * fix/tidy ApiPropagationTest * update config cache property names in gradle.properties * add more logger utils (they're not used, but they're nice to have) * add Task onlyIf/doLast/doFirst typesafe utils * update file checksums - Hide checksum type, and rename function so 'md5' isn't implied. - Use SHA-256 (md5 vs sha256 doesn't really matter, but why not) - Base64 encode (smaller log output) * big ol' refactor to support checksums per publication * disable Kotest autoscan * prettier printing of checksums debug string * add Kotest-GradleTestKit assertions * add more tags to Gradle Plugin * update Gradle plugin test to test up-to-date checks * de-dupe createPublicationData * remove unused functions * tidy configurations * fix repos being nested * use shouldContain as infix fun * test a single project * move checksum utils to internal package
1 parent b51c108 commit b954a8e

21 files changed

+1142
-284
lines changed

api/dev-publish-plugin.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ public final class dev/adamko/gradle/dev_publish/DevPublishPlugin : org/gradle/a
33
public static final field DEV_PUB__EXTENSION_NAME Ljava/lang/String;
44
public static final field DEV_PUB__MAVEN_REPO_DIR Ljava/lang/String;
55
public static final field DEV_PUB__MAVEN_REPO_NAME Ljava/lang/String;
6+
public static final field DEV_PUB__PUBLICATION_API_DEPENDENCIES Ljava/lang/String;
67
public static final field DEV_PUB__PUBLICATION_DEPENDENCIES Ljava/lang/String;
78
public static final field DEV_PUB__PUBLICATION_INCOMING Ljava/lang/String;
89
public static final field DEV_PUB__PUBLICATION_OUTGOING Ljava/lang/String;

build.gradle.kts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,9 @@ project.group = "dev.adamko.gradle"
1414

1515
dependencies {
1616
testFixturesApi(gradleTestKit())
17-
18-
testImplementation(platform(libs.kotest.bom))
19-
testImplementation(libs.kotest.runnerJUnit5)
20-
testImplementation(libs.kotest.assertionsCore)
17+
testFixturesApi(platform(libs.kotest.bom))
18+
testFixturesApi(libs.kotest.runnerJUnit5)
19+
testFixturesApi(libs.kotest.assertionsCore)
2120
}
2221

2322

src/main/kotlin/DevPublishPlugin.kt

Lines changed: 72 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,21 @@ package dev.adamko.gradle.dev_publish
22

33
import dev.adamko.gradle.dev_publish.data.DevPubAttributes
44
import dev.adamko.gradle.dev_publish.data.DevPubConfigurationsContainer
5+
import dev.adamko.gradle.dev_publish.data.PublicationData
56
import dev.adamko.gradle.dev_publish.internal.DevPublishInternalApi
7+
import dev.adamko.gradle.dev_publish.internal.checksums.CreatePublicationChecksum.Companion.createPublicationChecksum
8+
import dev.adamko.gradle.dev_publish.internal.checksums.LoadPublicationChecksum.Companion.loadPublicationChecksum
9+
import dev.adamko.gradle.dev_publish.internal.checksums.checksumsToDebugString
610
import dev.adamko.gradle.dev_publish.services.DevPublishService
711
import dev.adamko.gradle.dev_publish.services.DevPublishService.Companion.SERVICE_NAME
812
import dev.adamko.gradle.dev_publish.tasks.DevPublishTasksContainer
9-
import dev.adamko.gradle.dev_publish.utils.checksumsToDebugString
10-
import java.io.File
13+
import dev.adamko.gradle.dev_publish.utils.*
1114
import javax.inject.Inject
1215
import org.gradle.api.Plugin
1316
import org.gradle.api.Project
1417
import org.gradle.api.file.FileSystemOperations
1518
import org.gradle.api.file.ProjectLayout
19+
import org.gradle.api.logging.Logging
1620
import org.gradle.api.model.ObjectFactory
1721
import org.gradle.api.plugins.ExtensionContainer
1822
import org.gradle.api.provider.Provider
@@ -22,6 +26,7 @@ import org.gradle.api.publish.maven.MavenPublication
2226
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin
2327
import org.gradle.api.publish.maven.tasks.PublishToMavenRepository
2428
import org.gradle.api.services.BuildServiceRegistry
29+
import org.gradle.api.tasks.PathSensitivity.RELATIVE
2530
import org.gradle.kotlin.dsl.*
2631
import org.gradle.language.base.plugins.LifecycleBasePlugin
2732
import org.gradle.language.base.plugins.LifecycleBasePlugin.CHECK_TASK_NAME
@@ -66,24 +71,20 @@ constructor(
6671

6772
devPubTasks.updateDevRepo.configure {
6873
// update this project's maven-test-repo with files from other subprojects
69-
from(devPubConfigurations.testMavenPublicationConsumer.map { conf ->
70-
conf.incoming.artifacts.artifactFiles
71-
})
74+
repositoryContents.from(devPubConfigurations.devMavenPublicationResolver)
7275
}
7376

74-
devPubConfigurations.testMavenPublicationProvider.configure {
75-
outgoing {
76-
artifact(devPubExtension.devMavenRepo) {
77-
builtBy(devPubTasks.updateDevRepo)
78-
}
77+
devPubConfigurations.devMavenPublicationApiElements.outgoing {
78+
// Only share repos from _this_ subproject, not from the aggregated repo
79+
artifact(devPubExtension.publicationsStore) {
80+
builtBy(devPubTasks.publishAllToDevRepo)
7981
}
8082
}
8183

8284
configureMavenPublishingPlugin(
8385
project = project,
8486
devPubExtension = devPubExtension,
8587
devPubTasks = devPubTasks,
86-
devPublishService = devPubService,
8788
)
8889

8990
configureBasePlugin(
@@ -108,21 +109,16 @@ constructor(
108109
}
109110
}
110111

111-
private fun BuildServiceRegistry.registerDevPubService(): Provider<DevPublishService> {
112-
return registerIfAbsent(
113-
SERVICE_NAME,
114-
DevPublishService::class
115-
) {
112+
private fun BuildServiceRegistry.registerDevPubService(): Provider<DevPublishService> =
113+
registerIfAbsent(SERVICE_NAME, DevPublishService::class) {
116114
maxParallelUsages.set(1)
117115
}
118-
}
119116

120-
/** React to [MavenPublishPlugin], and configure the appropriate DevPublish tasks */
117+
/** React to [MavenPublishPlugin], and configure the appropriate DevPublish tasks. */
121118
private fun configureMavenPublishingPlugin(
122119
project: Project,
123120
devPubExtension: DevPublishPluginExtension,
124121
devPubTasks: DevPublishTasksContainer,
125-
devPublishService: Provider<DevPublishService>,
126122
) {
127123
project.plugins.withType<MavenPublishPlugin>().configureEach {
128124
project.extensions.configure<PublishingExtension> {
@@ -131,10 +127,10 @@ constructor(
131127
}
132128

133129
devPubTasks.generatePublicationChecksum.configure {
134-
publicationData.addAllLater(devPublishService.map { service ->
130+
publicationData.addAllLater(providers.provider {
135131
publications
136132
.withType<MavenPublication>()
137-
.mapNotNull { service.createPublicationData(it) }
133+
.mapNotNull { createPublicationData(it) }
138134
})
139135
}
140136
}
@@ -157,53 +153,56 @@ constructor(
157153
inputs.property("repoIsDevPub", repoIsDevPub)
158154

159155
inputs
160-
// must convert to FileTree, because the directory might not exist, and
156+
// Must convert to FileTree, because the directory might not exist, and
161157
// Gradle won't accept directories that don't exist as inputs.
162-
.files(checksumsStore.asFileTree)
158+
.files(checksumsStore.asFileTree.sortedElements())
163159
.withPropertyName("devPubChecksumsStoreFiles")
160+
.withPathSensitivity(RELATIVE)
164161

165162
outputs
166-
.files(publicationStore.map { it.asFileTree })
163+
.files(publicationStore.map { it.asFileTree.sortedElements() })
167164
.withPropertyName("devPubPublicationStore")
168165

169-
val publicationData = devPubService.flatMap { service ->
170-
providers.provider { service.createPublicationData(publication) }
171-
}
172-
val currentChecksum: Provider<String> = publicationData.map { data ->
173-
data.createChecksumContent()
174-
}
166+
val currentProjectDir = layout.projectDirectory
175167

176-
val storedChecksum: Provider<String> = providers.zip(
177-
publicationData,
178-
checksumsStore,
179-
) { data, checksumStore ->
180-
checksumStore.asFile
181-
.resolve(data?.checksumFilename ?: "unknown")
182-
.takeIf(File::exists)
183-
?.readText()
168+
val publicationData = providers.provider { createPublicationData(publication) }
169+
170+
val currentChecksum = providers.createPublicationChecksum {
171+
this.projectDir.set(currentProjectDir)
172+
this.artifacts.from(publicationData.map { it.artifacts })
173+
this.identifier.set(publicationData.flatMap { it.identifier })
184174
}
185175

186-
onlyIf("current checksums don't match stored checksum") {
187-
if (!repoIsDevPub.get()) return@onlyIf true
176+
val storedChecksum = providers.loadPublicationChecksum {
177+
this.checksumFilename.set(publicationData.map { "${it.name}.txt" })
178+
this.checksumsStore.set(checksumsStore)
179+
}
188180

189-
if (logger.isInfoEnabled) {
190-
logger.info(checksumsToDebugString(currentChecksum, storedChecksum))
181+
onlyIf_("current checksums don't match stored checksum") {
182+
if (!repoIsDevPub.get()) {
183+
true
184+
} else {
185+
val enabled = currentChecksum.orNull != storedChecksum.orNull
186+
logger.info {
187+
val checksums = checksumsToDebugString(currentChecksum, storedChecksum).prependIndent(" ")
188+
val match = if (!enabled) "match" else "do not match"
189+
"[$path] currentChecksum and storedChecksum $match\n${checksums}"
190+
}
191+
enabled
191192
}
192-
193-
currentChecksum.orNull != storedChecksum.orNull
194193
}
195194

196-
doFirst("clear staging repo") {
195+
doFirst_("clear staging repo") {
197196
if (repoIsDevPub.get()) {
198197
// clear the staging repo so that we can only sync this publication's files in the doLast {} below
199198
fs.delete { delete(stagingDevMavenRepo) }
200199
stagingDevMavenRepo.get().asFile.mkdirs()
201200
}
202201
}
203202

204-
doLast("sync staging repo to publication store") {
203+
doLast_("sync staging repo to publication store") {
205204
if (repoIsDevPub.get()) {
206-
logger.info("[$path] Syncing staging-dev-maven-repo to publication store ${publicationStore.get().asFile.invariantSeparatorsPath}")
205+
logger.info { ("[$path] Syncing staging-dev-maven-repo to publication store ${publicationStore.get().asFile.invariantSeparatorsPath}") }
207206
fs.sync {
208207
from(stagingDevMavenRepo)
209208
into(publicationStore)
@@ -231,6 +230,29 @@ constructor(
231230
}
232231
}
233232

233+
/** Create an instance of [PublicationData] from [publication]. */
234+
private fun createPublicationData(
235+
publication: MavenPublication?,
236+
): PublicationData? {
237+
if (publication == null) {
238+
logger.warn("cannot create PublicationData - MavenPublication is null")
239+
return null
240+
}
241+
242+
val artifacts = providers.provider { publication.artifacts }
243+
.map { artifacts ->
244+
objects.fileCollection()
245+
.from(artifacts.map { it.file })
246+
.builtBy(artifacts)
247+
}
248+
val identifier = providers.provider { publication.run { "$groupId:$artifactId:$version" } }
249+
250+
return objects.newInstance<PublicationData>(publication.name).apply {
251+
this.identifier.set(identifier)
252+
this.artifacts.from(artifacts)
253+
}
254+
}
255+
234256
companion object {
235257
const val DEV_PUB__EXTENSION_NAME = "devPublish"
236258

@@ -243,7 +265,10 @@ constructor(
243265
const val DEV_PUB__MAVEN_REPO_DIR = "maven-dev"
244266

245267
const val DEV_PUB__PUBLICATION_DEPENDENCIES = "devPublication"
268+
const val DEV_PUB__PUBLICATION_API_DEPENDENCIES = "devPublicationApi"
246269
const val DEV_PUB__PUBLICATION_INCOMING = "devPublicationResolvableElements"
247270
const val DEV_PUB__PUBLICATION_OUTGOING = "devPublicationConsumableElements"
271+
272+
private val logger = Logging.getLogger(DevPublishService::class.java)
248273
}
249274
}

src/main/kotlin/data/DevPubConfigurationsContainer.kt

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package dev.adamko.gradle.dev_publish.data
22

3+
import dev.adamko.gradle.dev_publish.DevPublishPlugin
34
import dev.adamko.gradle.dev_publish.DevPublishPlugin.Companion.DEV_PUB__PUBLICATION_DEPENDENCIES
45
import dev.adamko.gradle.dev_publish.DevPublishPlugin.Companion.DEV_PUB__PUBLICATION_INCOMING
56
import dev.adamko.gradle.dev_publish.DevPublishPlugin.Companion.DEV_PUB__PUBLICATION_OUTGOING
@@ -8,7 +9,6 @@ import dev.adamko.gradle.dev_publish.internal.DevPublishInternalApi
89
import dev.adamko.gradle.dev_publish.utils.consumable
910
import dev.adamko.gradle.dev_publish.utils.declarable
1011
import dev.adamko.gradle.dev_publish.utils.resolvable
11-
import org.gradle.api.NamedDomainObjectProvider
1212
import org.gradle.api.artifacts.Configuration
1313
import org.gradle.api.artifacts.ConfigurationContainer
1414
import org.gradle.api.artifacts.dsl.DependencyHandler
@@ -30,38 +30,43 @@ class DevPubConfigurationsContainer(
3030
dependencies.attributesSchema.attribute(DevPublishTypeAttribute)
3131
}
3232

33-
private val testMavenPublicationDependencies = configurations.registerPublicationsDependencies()
34-
val testMavenPublicationConsumer = configurations.registerPublicationsConsumer()
35-
val testMavenPublicationProvider = configurations.registerPublicationsProvider()
33+
private val devPublicationApiDependencies: Configuration =
34+
configurations.create(DevPublishPlugin.DEV_PUB__PUBLICATION_API_DEPENDENCIES) {
35+
description =
36+
"Declare dependencies on test Maven Publications." +
37+
"The publications will also be shared with consumers of this subproject."
38+
declarable()
39+
}
3640

37-
private fun ConfigurationContainer.registerPublicationsDependencies(): NamedDomainObjectProvider<Configuration> =
38-
register(DEV_PUB__PUBLICATION_DEPENDENCIES) {
39-
description = "Declare dependencies on test Maven Publications"
41+
private val devPublicationDependencies: Configuration =
42+
configurations.create(DEV_PUB__PUBLICATION_DEPENDENCIES) {
43+
description = "Declare dependencies on test Maven Publications."
4044
declarable()
4145
}
4246

43-
private fun ConfigurationContainer.registerPublicationsConsumer(): NamedDomainObjectProvider<Configuration> =
44-
register(DEV_PUB__PUBLICATION_INCOMING) {
45-
description = "Resolve test Maven Publications"
47+
val devMavenPublicationResolver: Configuration =
48+
configurations.create(DEV_PUB__PUBLICATION_INCOMING) {
49+
description = "Resolve dev Maven Publications."
4650
resolvable()
51+
extendsFrom(devPublicationApiDependencies)
52+
extendsFrom(devPublicationDependencies)
4753
attributes {
4854
attribute(USAGE_ATTRIBUTE, devPubAttributes.devPublishUsage)
4955
attribute(CATEGORY_ATTRIBUTE, devPubAttributes.devPublishCategory)
5056
attribute(DevPublishTypeAttribute, devPubAttributes.mavenRepositoryType)
5157
}
52-
extendsFrom(testMavenPublicationDependencies.get())
5358
}
5459

55-
private fun ConfigurationContainer.registerPublicationsProvider(): NamedDomainObjectProvider<Configuration> =
56-
register(DEV_PUB__PUBLICATION_OUTGOING) {
57-
description = "Provide test Maven Publications"
60+
val devMavenPublicationApiElements: Configuration =
61+
configurations.create(DEV_PUB__PUBLICATION_OUTGOING) {
62+
description = "Provide dev Maven Publications."
5863
consumable()
5964
attributes {
6065
attribute(USAGE_ATTRIBUTE, devPubAttributes.devPublishUsage)
6166
attribute(CATEGORY_ATTRIBUTE, devPubAttributes.devPublishCategory)
6267
attribute(DevPublishTypeAttribute, devPubAttributes.mavenRepositoryType)
6368
}
64-
extendsFrom(testMavenPublicationDependencies.get())
69+
extendsFrom(devPublicationApiDependencies)
6570
}
6671

6772
@DevPublishInternalApi
Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,30 @@
11
package dev.adamko.gradle.dev_publish.data
22

33
import dev.adamko.gradle.dev_publish.internal.DevPublishInternalApi
4-
import dev.adamko.gradle.dev_publish.utils.FileChecksumSeparator
5-
import dev.adamko.gradle.dev_publish.utils.checksum
64
import javax.inject.Inject
75
import org.gradle.api.Named
86
import org.gradle.api.file.ConfigurableFileCollection
97
import org.gradle.api.provider.Property
108
import org.gradle.api.publish.maven.MavenPublication
119
import org.gradle.api.tasks.Input
1210
import org.gradle.api.tasks.InputFiles
13-
import org.gradle.api.tasks.Internal
1411
import org.gradle.api.tasks.PathSensitive
1512
import org.gradle.api.tasks.PathSensitivity.RELATIVE
1613

1714
/**
1815
* Specific information about a [MavenPublication] that will be used to create a checksum file.
1916
*
20-
* @param[named] must match the publication name, [MavenPublication.getName]
17+
* @param[name] must match the publication name, [MavenPublication.getName]
2118
*/
2219
abstract class PublicationData
2320
@Inject
2421
@DevPublishInternalApi
2522
constructor(
26-
private val named: String
23+
private val name: String,
2724
) : Named {
2825

2926
/**
30-
* The artifacts inside a [MavenPublication]
27+
* The artifacts inside a [MavenPublication].
3128
*
3229
* @see MavenPublication.getArtifacts
3330
*/
@@ -46,23 +43,5 @@ constructor(
4643
abstract val identifier: Property<String>
4744

4845
@Input
49-
override fun getName(): String = named
50-
51-
@get:Internal
52-
internal val checksumFilename = "$named.txt"
53-
54-
internal fun createChecksumContent(): String {
55-
val md5 = artifacts
56-
.map { "${it.invariantSeparatorsPath}${FileChecksumSeparator}${it.checksum()}" }
57-
.sorted()
58-
.joinToString("\n")
59-
60-
val identifier = identifier.get()
61-
62-
return /* language=TEXT */ """
63-
|$identifier
64-
|---
65-
|$md5
66-
""".trimMargin()
67-
}
46+
override fun getName(): String = name
6847
}

0 commit comments

Comments
 (0)