IJI-2358 artifacts may be supplied in both the flat and the regular Maven layout

(cherry picked from commit 915a4c59cd6e5ef1f9d8c111876bad07e0b3ea97)

IJ-MR-159792

GitOrigin-RevId: dff1f3cc50c3ce95f4ef858853c23b1d098c7a25
This commit is contained in:
Dmitriy.Panov
2025-04-04 18:27:07 +02:00
committed by intellij-monorepo-bot
parent ccf7641949
commit 6cc9f277fc
2 changed files with 112 additions and 33 deletions

View File

@@ -28,7 +28,18 @@ import org.jetbrains.intellij.build.telemetry.use
import java.nio.file.Path
import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.io.path.*
import kotlin.io.path.ExperimentalPathApi
import kotlin.io.path.PathWalkOption
import kotlin.io.path.deleteIfExists
import kotlin.io.path.exists
import kotlin.io.path.inputStream
import kotlin.io.path.isDirectory
import kotlin.io.path.listDirectoryEntries
import kotlin.io.path.name
import kotlin.io.path.readLines
import kotlin.io.path.relativeTo
import kotlin.io.path.walk
import kotlin.io.path.writeText
import kotlin.time.Duration.Companion.minutes
/**
@@ -41,6 +52,7 @@ import kotlin.time.Duration.Companion.minutes
* See https://youtrack.jetbrains.com/articles/IJPL-A-611 internal article for more details
*/
@ApiStatus.Internal
@OptIn(ExperimentalPathApi::class)
class MavenCentralPublication(
private val context: BuildContext,
private val workDir: Path,
@@ -75,24 +87,53 @@ class MavenCentralPublication(
val distributionFiles: List<Path> = listOf(jar, pom, javadoc, sources)
val signatures: List<Path>
get() = if (sign) files(extension = "asc") else emptyList()
get() = distributionFiles.asSequence()
.map { it.resolveSibling("${it.fileName}.asc") }
.onEach {
check(sign || dryRun || it.exists()) {
"Signature file $it is expected to present"
}
}.filter {
it.exists()
}.toList()
val checksums: List<Path>
get() = files("md5") +
files("sha1") +
files("sha256") +
files("sha512")
get() = distributionFiles.asSequence().flatMap {
sequenceOf(
it.resolveSibling("${it.fileName}.md5"),
it.resolveSibling("${it.fileName}.sha1"),
it.resolveSibling("${it.fileName}.sha256"),
it.resolveSibling("${it.fileName}.sha512"),
)
}.onEach {
check(it.exists()) {
"Checksum file $it is expected to present"
}
}.toList()
}
private fun Path.listDirectoryEntriesRecursively(glob: String): List<Path> {
val matchingFiles = walk(PathWalkOption.INCLUDE_DIRECTORIES)
.filter { it.isDirectory() }
.flatMap { it.listDirectoryEntries(glob = glob) }
.toList()
check(matchingFiles.size == matchingFiles.distinctBy { it.name }.size) {
matchingFiles.joinToString(prefix = "Duplicate files found in $this:\n", separator = "\n") {
it.relativeTo(this).toString()
}
}
return matchingFiles
}
private fun file(name: String): Path {
val matchingFiles = workDir.listDirectoryEntries(glob = name)
val matchingFiles = workDir.listDirectoryEntriesRecursively(glob = name)
return requireNotNull(matchingFiles.singleOrNull()) {
"A single $name file is expected to be present in $workDir but found: $matchingFiles"
}
}
private fun files(extension: String): List<Path> {
val matchingFiles = workDir.listDirectoryEntries(glob = "*.$extension")
val matchingFiles = workDir.listDirectoryEntriesRecursively(glob = "*.$extension")
require(matchingFiles.any()) {
"No *.$extension files in $workDir"
}
@@ -178,6 +219,7 @@ class MavenCentralPublication(
private suspend fun bundle(): Path {
return spanBuilder("creating a bundle").use {
val bundle = workDir.resolve("bundle.zip")
bundle.deleteIfExists()
Compressor.Zip(bundle).use { zip ->
for (artifact in artifacts) {
artifact.distributionFiles.asSequence()

View File

@@ -1,6 +1,7 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.intellij.build.impl.maven
import com.intellij.testFramework.assertions.Assertions.assertThat
import com.intellij.testFramework.utils.io.createFile
import kotlinx.coroutines.runBlocking
import org.jetbrains.intellij.build.BuildContext
@@ -15,7 +16,6 @@ import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.api.io.TempDir
import java.nio.file.Path
import kotlin.io.path.exists
import kotlin.io.path.name
import kotlin.io.path.writeText
class MavenCentralPublicationTest {
@@ -34,26 +34,35 @@ class MavenCentralPublicationTest {
@TempDir
lateinit var workDir: Path
val publication: MavenCentralPublication by lazy { MavenCentralPublication(context, workDir, dryRun = true) }
val coordinates = MavenCoordinates("foo", "bar", "1.0")
fun createDistributionFiles(): List<Path> {
class Result(val workDirPath: Path, val zipPath: String)
fun createDistributionFiles(flatLayout: Boolean = false): List<Result> {
return sequenceOf(
"pom" to "",
"jar" to "",
"jar" to "sources",
"jar" to "javadoc",
).map { (packaging, classifier) ->
workDir.resolve(coordinates.getFileName(classifier = classifier, packaging = packaging)).createFile().apply {
MavenCoordinates("org.jetbrains", "bar", "1.0"),
MavenCoordinates("org.jetbrains", "foo", "2.0"),
).flatMap { coordinates ->
sequenceOf(
"pom" to "",
"jar" to "",
"jar" to "sources",
"jar" to "javadoc",
).map { (packaging, classifier) ->
val name = coordinates.getFileName(classifier = classifier, packaging = packaging)
val zipPath = "${coordinates.directoryPath}/$name"
val file = workDir.resolve(if (flatLayout) name else zipPath).createFile()
if (packaging == "pom") {
writeText(
file.writeText(
"""
<project>
<groupId>${coordinates.groupId}</groupId>
<artifactId>${coordinates.artifactId}</artifactId>
<version>${coordinates.version}</version>
</project>
""".trimIndent()
<project>
<groupId>${coordinates.groupId}</groupId>
<artifactId>${coordinates.artifactId}</artifactId>
<version>${coordinates.version}</version>
</project>
""".trimIndent()
)
}
Result(file, zipPath = zipPath)
}
}.toList()
}
@@ -68,9 +77,33 @@ class MavenCentralPublicationTest {
}
@Test
fun `should generate a bundle zip`() {
fun `should generate a bundle zip for artifacts`() {
runBlocking {
val files = createDistributionFiles().map { "${coordinates.directoryPath}/${it.name}" }
val files = createDistributionFiles().map { it.zipPath }
publication.execute()
val bundle = workDir.resolve("bundle.zip")
assertThat(bundle).exists()
val entries = buildList {
suspendAwareReadZipFile(bundle) { entry, _ ->
add(entry)
}
}.sorted()
Assertions.assertEquals(
files.asSequence()
.plus(files.asSequence().map { "$it.sha1" })
.plus(files.asSequence().map { "$it.sha256" })
.plus(files.asSequence().map { "$it.sha512" })
.plus(files.asSequence().map { "$it.md5" })
.sorted().toList(),
entries,
)
}
}
@Test
fun `should generate a bundle zip for flat artifacts layout`() {
runBlocking {
val files = createDistributionFiles(flatLayout = true).map { it.zipPath }
publication.execute()
val bundle = workDir.resolve("bundle.zip")
assert(bundle.exists())
@@ -78,19 +111,23 @@ class MavenCentralPublicationTest {
suspendAwareReadZipFile(bundle) { entry, _ ->
add(entry)
}
}
assert(entries.containsAll(files))
assert(entries.containsAll(files.map { "$it.sha1" }))
assert(entries.containsAll(files.map { "$it.sha256" }))
assert(entries.containsAll(files.map { "$it.sha512" }))
assert(entries.containsAll(files.map { "$it.md5" }))
}.sorted()
Assertions.assertEquals(
files.asSequence()
.plus(files.asSequence().map { "$it.sha1" })
.plus(files.asSequence().map { "$it.sha256" })
.plus(files.asSequence().map { "$it.sha512" })
.plus(files.asSequence().map { "$it.md5" })
.sorted().toList(),
entries,
)
}
}
@Test
fun `should fail upon an invalid input checksum`() {
runBlocking {
val files = createDistributionFiles()
val files = createDistributionFiles().map { it.workDirPath }
val malformedChecksum = files.first()
.resolveSibling("${files.first().fileName}.sha1")
.createFile()