fix(build): isolate model catalog bundle preparation
This commit is contained in:
@@ -0,0 +1,113 @@
|
|||||||
|
package org.egothor.radixor
|
||||||
|
|
||||||
|
import org.gradle.api.DefaultTask
|
||||||
|
import org.gradle.api.GradleException
|
||||||
|
import org.gradle.api.file.DirectoryProperty
|
||||||
|
import org.gradle.api.provider.Property
|
||||||
|
import org.gradle.api.tasks.Input
|
||||||
|
import org.gradle.api.tasks.InputDirectory
|
||||||
|
import org.gradle.api.tasks.OutputDirectory
|
||||||
|
import org.gradle.api.tasks.PathSensitive
|
||||||
|
import org.gradle.api.tasks.PathSensitivity
|
||||||
|
import org.gradle.api.tasks.TaskAction
|
||||||
|
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.StandardCopyOption
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.util.stream.Stream
|
||||||
|
|
||||||
|
/** Prepares the two POM-only catalog publications for a Maven Central bundle. */
|
||||||
|
abstract class PrepareModelCatalogBundleInputTask extends DefaultTask {
|
||||||
|
@InputDirectory
|
||||||
|
@PathSensitive(PathSensitivity.RELATIVE)
|
||||||
|
abstract DirectoryProperty getRawRepositoryDirectory()
|
||||||
|
|
||||||
|
@OutputDirectory
|
||||||
|
abstract DirectoryProperty getPreparedBundleDirectory()
|
||||||
|
|
||||||
|
@Input
|
||||||
|
abstract Property<String> getCatalogVersion()
|
||||||
|
|
||||||
|
/** Copies permitted publication files and creates Central's required legacy checksums. */
|
||||||
|
@TaskAction
|
||||||
|
void prepare() {
|
||||||
|
prepareBundle(rawRepositoryDirectory.get().asFile.toPath(),
|
||||||
|
preparedBundleDirectory.get().asFile.toPath(), catalogVersion.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
static void prepareBundle(final Path rawRepository, final Path preparedDirectory,
|
||||||
|
final String version) {
|
||||||
|
if (!Files.isDirectory(rawRepository)) {
|
||||||
|
throw new GradleException("The raw model catalog staging repository does not exist: ${rawRepository}.")
|
||||||
|
}
|
||||||
|
deleteTree(preparedDirectory)
|
||||||
|
Files.createDirectories(preparedDirectory)
|
||||||
|
|
||||||
|
final Set<String> expectedPoms = [
|
||||||
|
"org/egothor/radixor-models-standard/${version}/radixor-models-standard-${version}.pom",
|
||||||
|
"org/egothor/radixor-models-bom/${version}/radixor-models-bom-${version}.pom"
|
||||||
|
] as Set<String>
|
||||||
|
final List<Path> copied = []
|
||||||
|
Files.walk(rawRepository).withCloseable { Stream<Path> paths ->
|
||||||
|
paths.filter(Files::isRegularFile).sorted().forEach { Path source ->
|
||||||
|
final String relative = rawRepository.relativize(source).toString().replace(File.separatorChar, '/' as char)
|
||||||
|
if (isExcludedPublicationMetadata(relative)) return
|
||||||
|
if (relative.endsWith('.jar') || relative.endsWith('/stemmer.gz')
|
||||||
|
|| relative.contains('benchmark-pack')) {
|
||||||
|
throw new GradleException("Unsupported model catalog publication file: ${relative}.")
|
||||||
|
}
|
||||||
|
final String pom = expectedPoms.find { String candidate ->
|
||||||
|
relative == candidate || relative.startsWith(candidate + '.')
|
||||||
|
}
|
||||||
|
if (pom == null) {
|
||||||
|
throw new GradleException("Unexpected file in the raw model catalog repository: ${relative}.")
|
||||||
|
}
|
||||||
|
if (relative == pom || relative == pom + '.asc') {
|
||||||
|
final Path target = preparedDirectory.resolve(relative)
|
||||||
|
Files.createDirectories(target.parent)
|
||||||
|
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING)
|
||||||
|
copied.add(target)
|
||||||
|
} else if (!(relative ==~ /.*\.pom(?:\.asc)?\.(?:md5|sha1|sha256|sha512)/)) {
|
||||||
|
throw new GradleException("Unsupported model catalog publication file: ${relative}.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<Path> poms = copied.findAll { Path path -> path.fileName.toString().endsWith('.pom') }
|
||||||
|
if (copied.isEmpty()) {
|
||||||
|
throw new GradleException('No model catalog publication files were copied from the raw staging repository.')
|
||||||
|
}
|
||||||
|
if (poms.size() != 2 || !expectedPoms.every { String expected -> Files.isRegularFile(preparedDirectory.resolve(expected)) }) {
|
||||||
|
throw new GradleException("The prepared model catalog must contain exactly the standard and BOM POMs; found ${poms.size()} POM files.")
|
||||||
|
}
|
||||||
|
copied.each { Path artifact ->
|
||||||
|
writeDigest(artifact, 'MD5', artifact.resolveSibling(artifact.fileName.toString() + '.md5'))
|
||||||
|
writeDigest(artifact, 'SHA-1', artifact.resolveSibling(artifact.fileName.toString() + '.sha1'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isExcludedPublicationMetadata(final String relative) {
|
||||||
|
final String name = relative.substring(relative.lastIndexOf('/') + 1)
|
||||||
|
return name ==~ /maven-metadata.*\.xml(?:\..*)?/ || relative ==~ /.*\.module(?:\..*)?/
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeDigest(final Path source, final String algorithm, final Path target) {
|
||||||
|
final MessageDigest digest = MessageDigest.getInstance(algorithm)
|
||||||
|
Files.newInputStream(source).withCloseable { InputStream input ->
|
||||||
|
final byte[] buffer = new byte[16 * 1024]
|
||||||
|
int count
|
||||||
|
while ((count = input.read(buffer)) >= 0) {
|
||||||
|
if (count > 0) digest.update(buffer, 0, count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Files.writeString(target, digest.digest().encodeHex().toString(), java.nio.charset.StandardCharsets.US_ASCII)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void deleteTree(final Path directory) {
|
||||||
|
if (!Files.exists(directory)) return
|
||||||
|
Files.walk(directory).withCloseable { Stream<Path> paths ->
|
||||||
|
paths.sorted(Comparator.reverseOrder()).forEach(Files::delete)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,5 +12,13 @@ final class RadixorBuildSupportPlugin implements Plugin<Project> {
|
|||||||
group = 'verification'
|
group = 'verification'
|
||||||
description = 'Creates an isolated local Maven repository for model dependency-resolution integration tests.'
|
description = 'Creates an isolated local Maven repository for model dependency-resolution integration tests.'
|
||||||
}
|
}
|
||||||
|
project.tasks.register('prepareModelCatalogReleaseCandidate', PrepareModelCatalogBundleInputTask) {
|
||||||
|
group = 'publishing'
|
||||||
|
description = 'Prepares the isolated POM-only model catalog input for Maven Central.'
|
||||||
|
}
|
||||||
|
project.tasks.register('verifyModelCatalogReleaseCandidate', VerifyModelCatalogReleaseCandidateTask) {
|
||||||
|
group = 'verification'
|
||||||
|
description = 'Verifies catalog bundle contents, checksums, coordinates, and dependency semantics.'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,194 @@
|
|||||||
|
package org.egothor.radixor
|
||||||
|
|
||||||
|
import org.gradle.api.DefaultTask
|
||||||
|
import org.gradle.api.GradleException
|
||||||
|
import org.gradle.api.file.RegularFileProperty
|
||||||
|
import org.gradle.api.provider.ListProperty
|
||||||
|
import org.gradle.api.provider.Property
|
||||||
|
import org.gradle.api.tasks.Input
|
||||||
|
import org.gradle.api.tasks.InputFile
|
||||||
|
import org.gradle.api.tasks.OutputFile
|
||||||
|
import org.gradle.api.tasks.PathSensitive
|
||||||
|
import org.gradle.api.tasks.PathSensitivity
|
||||||
|
import org.gradle.api.tasks.TaskAction
|
||||||
|
import org.w3c.dom.Element
|
||||||
|
|
||||||
|
import javax.xml.XMLConstants
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.util.zip.ZipEntry
|
||||||
|
import java.util.zip.ZipFile
|
||||||
|
|
||||||
|
/** Verifies the contents and Maven semantics of the model catalog Central bundle. */
|
||||||
|
abstract class VerifyModelCatalogReleaseCandidateTask extends DefaultTask {
|
||||||
|
@InputFile
|
||||||
|
@PathSensitive(PathSensitivity.RELATIVE)
|
||||||
|
abstract RegularFileProperty getBundleFile()
|
||||||
|
|
||||||
|
@OutputFile
|
||||||
|
abstract RegularFileProperty getReportFile()
|
||||||
|
|
||||||
|
@Input abstract Property<String> getCatalogVersion()
|
||||||
|
@Input abstract Property<String> getModelVersion()
|
||||||
|
@Input abstract ListProperty<String> getDefaultModelIds()
|
||||||
|
@Input abstract ListProperty<String> getAllModelIds()
|
||||||
|
|
||||||
|
/** Performs byte-level archive and semantic POM validation. */
|
||||||
|
@TaskAction
|
||||||
|
void verify() {
|
||||||
|
final List<String> entries = verifyBundle(bundleFile.get().asFile, catalogVersion.get(),
|
||||||
|
modelVersion.get(), defaultModelIds.get(), allModelIds.get())
|
||||||
|
final File report = reportFile.get().asFile
|
||||||
|
Files.createDirectories(report.toPath().parent)
|
||||||
|
Files.writeString(report.toPath(), "Bundle: ${bundleFile.get().asFile.name}\nBytes: ${bundleFile.get().asFile.length()}\n"
|
||||||
|
+ entries.join('\n') + '\n', StandardCharsets.UTF_8)
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<String> verifyBundle(final File bundle, final String catalogVersion,
|
||||||
|
final String modelVersion, final List<String> defaultIds, final List<String> allIds) {
|
||||||
|
if (!bundle.isFile() || bundle.length() == 0L) {
|
||||||
|
throw new GradleException("The model catalog Central bundle is missing or empty: ${bundle}.")
|
||||||
|
}
|
||||||
|
final Map<String, byte[]> content = new TreeMap<>()
|
||||||
|
new ZipFile(bundle).withCloseable { ZipFile archive ->
|
||||||
|
archive.entries().each { ZipEntry entry ->
|
||||||
|
if (!entry.directory) {
|
||||||
|
archive.getInputStream(entry).withCloseable { InputStream input ->
|
||||||
|
content.put(entry.name, input.readAllBytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final List<String> entries = content.keySet().toList()
|
||||||
|
final List<String> poms = entries.findAll { String entry -> entry.endsWith('.pom') }
|
||||||
|
final List<String> unsupported = entries.findAll { String entry ->
|
||||||
|
!(entry ==~ 'org/egothor/radixor-models-(?:standard|bom)/[^/]+/'
|
||||||
|
+ 'radixor-models-(?:standard|bom)-[^/]+\\.pom(?:\\.asc)?(?:\\.(?:md5|sha1))?')
|
||||||
|
}
|
||||||
|
if (!unsupported.isEmpty()) {
|
||||||
|
throw new GradleException("The model catalog bundle contains unsupported files: ${unsupported}.")
|
||||||
|
}
|
||||||
|
if (poms.size() != 2) {
|
||||||
|
throw new GradleException("The model catalog bundle must contain exactly two POM files; found ${poms.size()}.")
|
||||||
|
}
|
||||||
|
if (entries.any { String entry -> entry.endsWith('.jar') || entry.endsWith('/stemmer.gz')
|
||||||
|
|| entry.endsWith('.module') || entry.contains('maven-metadata') || entry.contains('benchmark-pack') }) {
|
||||||
|
throw new GradleException('The model catalog bundle contains forbidden publication content.')
|
||||||
|
}
|
||||||
|
poms.each { String pom -> verifyChecksums(content, pom) }
|
||||||
|
entries.findAll { String entry -> entry.endsWith('.pom.asc') }.each { String signature ->
|
||||||
|
verifyChecksums(content, signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
final String standardPath = expectedPomPath('standard', catalogVersion)
|
||||||
|
final String bomPath = expectedPomPath('bom', catalogVersion)
|
||||||
|
if (!content.containsKey(standardPath) || !content.containsKey(bomPath)) {
|
||||||
|
throw new GradleException('The bundle does not contain the expected standard and BOM coordinates.')
|
||||||
|
}
|
||||||
|
final Element standard = parsePom(content.get(standardPath))
|
||||||
|
final Element bom = parsePom(content.get(bomPath))
|
||||||
|
verifyCoordinates(standard, 'radixor-models-standard', catalogVersion)
|
||||||
|
verifyCoordinates(bom, 'radixor-models-bom', catalogVersion)
|
||||||
|
|
||||||
|
final Map<String, String> standardDependencies = dependencies(standard, false)
|
||||||
|
final Map<String, String> bomConstraints = dependencies(bom, true)
|
||||||
|
final Set<String> expectedDefaults = defaultIds.collect { String id -> "org.egothor:radixor-model-${id}" } as Set<String>
|
||||||
|
final Set<String> expectedAll = allIds.collect { String id -> "org.egothor:radixor-model-${id}" } as Set<String>
|
||||||
|
if (standardDependencies.keySet() != expectedDefaults
|
||||||
|
|| standardDependencies.values().any { String version -> version != modelVersion }
|
||||||
|
|| standardDependencies.containsKey('org.egothor:radixor-model-pl-pl-polimorf')
|
||||||
|
|| dependencyScopes(standard).any { String scope -> scope != 'runtime' }) {
|
||||||
|
throw new GradleException('The standard catalog POM must reference exactly the 20 default model artifacts at the model version.')
|
||||||
|
}
|
||||||
|
if (!dependencies(bom, false).isEmpty()) {
|
||||||
|
throw new GradleException('The model BOM must not introduce runtime dependencies.')
|
||||||
|
}
|
||||||
|
if (bomConstraints.keySet() != expectedAll
|
||||||
|
|| bomConstraints.values().any { String version -> version != modelVersion }) {
|
||||||
|
throw new GradleException('The model BOM must manage exactly all 21 model artifacts at the model version.')
|
||||||
|
}
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String expectedPomPath(final String kind, final String version) {
|
||||||
|
return "org/egothor/radixor-models-${kind}/${version}/radixor-models-${kind}-${version}.pom"
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void verifyChecksums(final Map<String, byte[]> content, final String artifact) {
|
||||||
|
['MD5': 'md5', 'SHA-1': 'sha1'].each { String algorithm, String extension ->
|
||||||
|
final String checksum = artifact + '.' + extension
|
||||||
|
if (!content.containsKey(checksum)) {
|
||||||
|
throw new GradleException("The catalog artifact is missing its ${algorithm} checksum: ${artifact}.")
|
||||||
|
}
|
||||||
|
final String expected = MessageDigest.getInstance(algorithm).digest(content.get(artifact)).encodeHex().toString()
|
||||||
|
final String actual = new String(content.get(checksum), StandardCharsets.US_ASCII).trim()
|
||||||
|
if (actual != expected) {
|
||||||
|
throw new GradleException("The ${algorithm} checksum does not match ${artifact}.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Element parsePom(final byte[] xml) {
|
||||||
|
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance()
|
||||||
|
factory.setNamespaceAware(true)
|
||||||
|
factory.setFeature('http://apache.org/xml/features/disallow-doctype-decl', true)
|
||||||
|
factory.setFeature('http://xml.org/sax/features/external-general-entities', false)
|
||||||
|
factory.setFeature('http://xml.org/sax/features/external-parameter-entities', false)
|
||||||
|
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, '')
|
||||||
|
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, '')
|
||||||
|
return factory.newDocumentBuilder().parse(new ByteArrayInputStream(xml)).documentElement
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void verifyCoordinates(final Element project, final String artifactId, final String version) {
|
||||||
|
if (directText(project, 'groupId') != 'org.egothor'
|
||||||
|
|| directText(project, 'artifactId') != artifactId
|
||||||
|
|| directText(project, 'version') != version) {
|
||||||
|
throw new GradleException("Unexpected Maven coordinates for ${artifactId}.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, String> dependencies(final Element project, final boolean managed) {
|
||||||
|
final Map<String, String> result = new TreeMap<>()
|
||||||
|
final Element parent = managed ? directChild(project, 'dependencyManagement') : project
|
||||||
|
final Element container = parent == null ? null : directChild(parent, 'dependencies')
|
||||||
|
if (container == null) return result
|
||||||
|
childElements(container, 'dependency').each { Element dependency ->
|
||||||
|
final String coordinate = directText(dependency, 'groupId') + ':' + directText(dependency, 'artifactId')
|
||||||
|
if (result.put(coordinate, directText(dependency, 'version')) != null) {
|
||||||
|
throw new GradleException("The catalog POM contains duplicate dependency ${coordinate}.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> dependencyScopes(final Element project) {
|
||||||
|
final Element container = directChild(project, 'dependencies')
|
||||||
|
if (container == null) return []
|
||||||
|
return childElements(container, 'dependency').collect { Element dependency -> directText(dependency, 'scope') }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String directText(final Element parent, final String name) {
|
||||||
|
final Element child = directChild(parent, name)
|
||||||
|
return child == null ? null : child.textContent.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Element directChild(final Element parent, final String name) {
|
||||||
|
if (parent == null) return null
|
||||||
|
for (int index = 0; index < parent.childNodes.length; index++) {
|
||||||
|
if (parent.childNodes.item(index) instanceof Element
|
||||||
|
&& parent.childNodes.item(index).localName == name) return (Element) parent.childNodes.item(index)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Element> childElements(final Element parent, final String name) {
|
||||||
|
final List<Element> result = []
|
||||||
|
for (int index = 0; index < parent.childNodes.length; index++) {
|
||||||
|
if (parent.childNodes.item(index) instanceof Element
|
||||||
|
&& parent.childNodes.item(index).localName == name) result.add((Element) parent.childNodes.item(index))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,259 @@
|
|||||||
|
package org.egothor.radixor
|
||||||
|
|
||||||
|
import org.gradle.api.GradleException
|
||||||
|
import org.gradle.testkit.runner.GradleRunner
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.io.TempDir
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.util.zip.ZipEntry
|
||||||
|
import java.util.zip.ZipOutputStream
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertArrayEquals
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
|
||||||
|
/** Exercises catalog publication filtering, isolation, checksums, and semantic verification. */
|
||||||
|
final class ModelCatalogBundleTaskTest {
|
||||||
|
private static final String CATALOG_VERSION = '2026.1'
|
||||||
|
private static final String MODEL_VERSION = '1.0.0'
|
||||||
|
private static final List<String> DEFAULTS = ['alpha', 'beta']
|
||||||
|
private static final List<String> ALL = ['alpha', 'beta', 'pl-pl-polimorf']
|
||||||
|
|
||||||
|
@TempDir Path temporaryDirectory
|
||||||
|
|
||||||
|
/** Prepares exactly two unsigned POMs and their checksums without changing raw bytes. */
|
||||||
|
@Test
|
||||||
|
void preparesUnsignedPublicationsWithoutMutatingRawInput() {
|
||||||
|
final Path raw = fixture(false)
|
||||||
|
final byte[] before = Files.readAllBytes(standardPom(raw))
|
||||||
|
final Path prepared = temporaryDirectory.resolve('prepared')
|
||||||
|
PrepareModelCatalogBundleInputTask.prepareBundle(raw, prepared, CATALOG_VERSION)
|
||||||
|
assertArrayEquals(before, Files.readAllBytes(standardPom(raw)))
|
||||||
|
assertEquals(6L, regularFiles(prepared))
|
||||||
|
assertTrue(Files.isRegularFile(prepared.resolve(relativeStandardPom() + '.md5')))
|
||||||
|
assertTrue(Files.isRegularFile(prepared.resolve(relativeBomPom() + '.sha1')))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Copies test-only signatures and generates checksums for both signatures. */
|
||||||
|
@Test
|
||||||
|
void preparesSignedPublications() {
|
||||||
|
final Path prepared = temporaryDirectory.resolve('prepared')
|
||||||
|
PrepareModelCatalogBundleInputTask.prepareBundle(fixture(true), prepared, CATALOG_VERSION)
|
||||||
|
assertEquals(12L, regularFiles(prepared))
|
||||||
|
assertTrue(Files.isRegularFile(prepared.resolve(relativeStandardPom() + '.asc.md5')))
|
||||||
|
assertTrue(Files.isRegularFile(prepared.resolve(relativeBomPom() + '.asc.sha1')))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Deletes stale prepared content before copying current publication files. */
|
||||||
|
@Test
|
||||||
|
void removesStalePreparedContent() {
|
||||||
|
final Path prepared = temporaryDirectory.resolve('prepared')
|
||||||
|
Files.createDirectories(prepared)
|
||||||
|
Files.writeString(prepared.resolve('stale.jar'), 'stale')
|
||||||
|
PrepareModelCatalogBundleInputTask.prepareBundle(fixture(false), prepared, CATALOG_VERSION)
|
||||||
|
assertFalse(Files.exists(prepared.resolve('stale.jar')))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Excludes Gradle module metadata, its sidecars, and Maven metadata. */
|
||||||
|
@Test
|
||||||
|
void excludesModuleAndMavenMetadata() {
|
||||||
|
final Path raw = fixture(false)
|
||||||
|
final Path module = standardPom(raw).resolveSibling("radixor-models-standard-${CATALOG_VERSION}.module")
|
||||||
|
Files.writeString(module, 'module')
|
||||||
|
Files.writeString(module.resolveSibling(module.fileName.toString() + '.asc'), 'signature')
|
||||||
|
Files.writeString(module.resolveSibling(module.fileName.toString() + '.sha1'), 'checksum')
|
||||||
|
Files.writeString(module.parent.resolve('maven-metadata-local.xml'), 'metadata')
|
||||||
|
final Path prepared = temporaryDirectory.resolve('prepared')
|
||||||
|
PrepareModelCatalogBundleInputTask.prepareBundle(raw, prepared, CATALOG_VERSION)
|
||||||
|
assertEquals(6L, regularFiles(prepared))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rejects a missing standard publication. */
|
||||||
|
@Test
|
||||||
|
void rejectsMissingStandardPom() {
|
||||||
|
final Path raw = fixture(false)
|
||||||
|
Files.delete(standardPom(raw))
|
||||||
|
assertThrows(GradleException) {
|
||||||
|
PrepareModelCatalogBundleInputTask.prepareBundle(raw, temporaryDirectory.resolve('prepared'), CATALOG_VERSION)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rejects a missing BOM publication. */
|
||||||
|
@Test
|
||||||
|
void rejectsMissingBomPom() {
|
||||||
|
final Path raw = fixture(false)
|
||||||
|
Files.delete(bomPom(raw))
|
||||||
|
assertThrows(GradleException) {
|
||||||
|
PrepareModelCatalogBundleInputTask.prepareBundle(raw, temporaryDirectory.resolve('prepared'), CATALOG_VERSION)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rejects unexpected binary publication content. */
|
||||||
|
@Test
|
||||||
|
void rejectsUnexpectedJar() {
|
||||||
|
final Path raw = fixture(false)
|
||||||
|
Files.writeString(standardPom(raw).resolveSibling('unexpected.jar'), 'binary')
|
||||||
|
assertThrows(GradleException) {
|
||||||
|
PrepareModelCatalogBundleInputTask.prepareBundle(raw, temporaryDirectory.resolve('prepared'), CATALOG_VERSION)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rejects dictionary content in the catalog staging repository. */
|
||||||
|
@Test
|
||||||
|
void rejectsDictionaryContent() {
|
||||||
|
final Path raw = fixture(false)
|
||||||
|
final Path dictionary = raw.resolve('unrelated/stemmer.gz')
|
||||||
|
Files.createDirectories(dictionary.parent)
|
||||||
|
Files.writeString(dictionary, 'dictionary')
|
||||||
|
assertThrows(GradleException) {
|
||||||
|
PrepareModelCatalogBundleInputTask.prepareBundle(raw, temporaryDirectory.resolve('prepared'), CATALOG_VERSION)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Produces and semantically verifies a nonempty ZIP from prepared files. */
|
||||||
|
@Test
|
||||||
|
void verifiesRealPreparedArchive() {
|
||||||
|
final Path prepared = temporaryDirectory.resolve('prepared')
|
||||||
|
PrepareModelCatalogBundleInputTask.prepareBundle(fixture(false), prepared, CATALOG_VERSION)
|
||||||
|
final File archive = zip(prepared, temporaryDirectory.resolve('catalog.zip'))
|
||||||
|
final List<String> entries = VerifyModelCatalogReleaseCandidateTask.verifyBundle(
|
||||||
|
archive, CATALOG_VERSION, MODEL_VERSION, DEFAULTS, ALL)
|
||||||
|
assertEquals(6, entries.size())
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rejects an archived checksum that does not match its POM. */
|
||||||
|
@Test
|
||||||
|
void rejectsIncorrectArchivedChecksum() {
|
||||||
|
final Path prepared = temporaryDirectory.resolve('prepared')
|
||||||
|
PrepareModelCatalogBundleInputTask.prepareBundle(fixture(false), prepared, CATALOG_VERSION)
|
||||||
|
Files.writeString(prepared.resolve(relativeStandardPom() + '.sha1'), 'incorrect')
|
||||||
|
final File archive = zip(prepared, temporaryDirectory.resolve('catalog.zip'))
|
||||||
|
assertThrows(GradleException) {
|
||||||
|
VerifyModelCatalogReleaseCandidateTask.verifyBundle(
|
||||||
|
archive, CATALOG_VERSION, MODEL_VERSION, DEFAULTS, ALL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Repeated preparation replaces restored or stale output deterministically. */
|
||||||
|
@Test
|
||||||
|
void repeatedPreparationRecreatesValidInput() {
|
||||||
|
final Path raw = fixture(false)
|
||||||
|
final Path prepared = temporaryDirectory.resolve('prepared')
|
||||||
|
PrepareModelCatalogBundleInputTask.prepareBundle(raw, prepared, CATALOG_VERSION)
|
||||||
|
final String first = treeDigest(prepared)
|
||||||
|
Files.writeString(prepared.resolve('restored-history-stale.txt'), 'stale')
|
||||||
|
PrepareModelCatalogBundleInputTask.prepareBundle(raw, prepared, CATALOG_VERSION)
|
||||||
|
assertEquals(first, treeDigest(prepared))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates a real Gradle ZIP, rebuilds a missing output, and reuses Configuration Cache. */
|
||||||
|
@Test
|
||||||
|
void gradleZipRebuildsWithConfigurationCacheReuse() {
|
||||||
|
final Path project = temporaryDirectory.resolve('testkit-project')
|
||||||
|
Files.createDirectories(project)
|
||||||
|
Files.writeString(project.resolve('settings.gradle'), "rootProject.name = 'catalog-fixture'\n")
|
||||||
|
Files.writeString(project.resolve('build.gradle'), '''plugins {
|
||||||
|
id 'org.egothor.radixor.build-support'
|
||||||
|
}
|
||||||
|
tasks.named('prepareModelCatalogReleaseCandidate') {
|
||||||
|
rawRepositoryDirectory = layout.projectDirectory.dir('raw')
|
||||||
|
preparedBundleDirectory = layout.buildDirectory.dir('prepared')
|
||||||
|
catalogVersion = '2026.1'
|
||||||
|
}
|
||||||
|
tasks.register('bundle', Zip) {
|
||||||
|
dependsOn(tasks.named('prepareModelCatalogReleaseCandidate'))
|
||||||
|
from(layout.buildDirectory.dir('prepared'))
|
||||||
|
destinationDirectory = layout.buildDirectory.dir('candidate')
|
||||||
|
archiveFileName = 'catalog.zip'
|
||||||
|
}
|
||||||
|
''')
|
||||||
|
final Path raw = project.resolve('raw')
|
||||||
|
write(standardPom(raw), pom('radixor-models-standard', false))
|
||||||
|
write(bomPom(raw), pom('radixor-models-bom', true))
|
||||||
|
|
||||||
|
final List<String> arguments = ['bundle', '--configuration-cache',
|
||||||
|
'--configuration-cache-problems=fail', '--warning-mode=fail']
|
||||||
|
final String first = GradleRunner.create().withProjectDir(project.toFile())
|
||||||
|
.withPluginClasspath().withArguments(arguments).build().output
|
||||||
|
final Path archive = project.resolve('build/candidate/catalog.zip')
|
||||||
|
assertTrue(Files.size(archive) > 0L)
|
||||||
|
Files.delete(archive)
|
||||||
|
final String second = GradleRunner.create().withProjectDir(project.toFile())
|
||||||
|
.withPluginClasspath().withArguments(arguments).build().output
|
||||||
|
assertTrue(Files.size(archive) > 0L)
|
||||||
|
assertTrue(first.contains('Configuration cache entry stored.'))
|
||||||
|
assertTrue(second.contains('Configuration cache entry reused.'))
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path fixture(final boolean signed) {
|
||||||
|
final Path raw = temporaryDirectory.resolve('raw')
|
||||||
|
write(standardPom(raw), pom('radixor-models-standard', false))
|
||||||
|
write(bomPom(raw), pom('radixor-models-bom', true))
|
||||||
|
if (signed) {
|
||||||
|
Files.writeString(standardPom(raw).resolveSibling(standardPom(raw).fileName.toString() + '.asc'), 'test signature')
|
||||||
|
Files.writeString(bomPom(raw).resolveSibling(bomPom(raw).fileName.toString() + '.asc'), 'test signature')
|
||||||
|
}
|
||||||
|
return raw
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String pom(final String artifact, final boolean managed) {
|
||||||
|
final List<String> ids = managed ? ALL : DEFAULTS
|
||||||
|
final String dependencies = ids.collect { String id ->
|
||||||
|
"<dependency><groupId>org.egothor</groupId><artifactId>radixor-model-${id}</artifactId>" +
|
||||||
|
"<version>${MODEL_VERSION}</version>${managed ? '' : '<scope>runtime</scope>'}</dependency>"
|
||||||
|
}.join()
|
||||||
|
final String body = managed ? "<dependencyManagement><dependencies>${dependencies}</dependencies></dependencyManagement>"
|
||||||
|
: "<dependencies>${dependencies}</dependencies>"
|
||||||
|
return "<?xml version=\"1.0\"?><project xmlns=\"http://maven.apache.org/POM/4.0.0\">" +
|
||||||
|
"<modelVersion>4.0.0</modelVersion><groupId>org.egothor</groupId>" +
|
||||||
|
"<artifactId>${artifact}</artifactId><version>${CATALOG_VERSION}</version>${body}</project>"
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Path standardPom(final Path raw) { raw.resolve(relativeStandardPom()) }
|
||||||
|
private static Path bomPom(final Path raw) { raw.resolve(relativeBomPom()) }
|
||||||
|
private static String relativeStandardPom() {
|
||||||
|
"org/egothor/radixor-models-standard/${CATALOG_VERSION}/radixor-models-standard-${CATALOG_VERSION}.pom"
|
||||||
|
}
|
||||||
|
private static String relativeBomPom() {
|
||||||
|
"org/egothor/radixor-models-bom/${CATALOG_VERSION}/radixor-models-bom-${CATALOG_VERSION}.pom"
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void write(final Path path, final String value) {
|
||||||
|
Files.createDirectories(path.parent)
|
||||||
|
Files.writeString(path, value, StandardCharsets.UTF_8)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long regularFiles(final Path root) {
|
||||||
|
Files.walk(root).withCloseable { paths -> paths.filter(Files::isRegularFile).count() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static File zip(final Path root, final Path target) {
|
||||||
|
new ZipOutputStream(Files.newOutputStream(target)).withCloseable { ZipOutputStream output ->
|
||||||
|
Files.walk(root).withCloseable { paths ->
|
||||||
|
paths.filter(Files::isRegularFile).sorted().forEach { Path file ->
|
||||||
|
output.putNextEntry(new ZipEntry(root.relativize(file).toString().replace(File.separatorChar, '/' as char)))
|
||||||
|
Files.copy(file, output)
|
||||||
|
output.closeEntry()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return target.toFile()
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String treeDigest(final Path root) {
|
||||||
|
final MessageDigest digest = MessageDigest.getInstance('SHA-256')
|
||||||
|
Files.walk(root).withCloseable { paths ->
|
||||||
|
paths.filter(Files::isRegularFile).sorted().forEach { Path path ->
|
||||||
|
digest.update(root.relativize(path).toString().getBytes(StandardCharsets.UTF_8))
|
||||||
|
digest.update(Files.readAllBytes(path))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return digest.digest().encodeHex().toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
94
build.gradle
94
build.gradle
@@ -603,12 +603,13 @@ tasks.named('prepareModelConsumerTestRepository') {
|
|||||||
repositoryDirectory = layout.buildDirectory.dir('model-consumer-repository')
|
repositoryDirectory = layout.buildDirectory.dir('model-consumer-repository')
|
||||||
}
|
}
|
||||||
|
|
||||||
def cleanModelCatalogStaging = tasks.register('cleanModelCatalogStaging') {
|
def rawModelCatalogRepository = layout.buildDirectory.dir('model-catalog-staging-repository')
|
||||||
|
def preparedModelCatalogBundleInput = layout.buildDirectory.dir('model-catalog-bundle-input')
|
||||||
|
|
||||||
|
def cleanModelCatalogStaging = tasks.register('cleanModelCatalogStaging', Delete) {
|
||||||
group = 'publishing'
|
group = 'publishing'
|
||||||
description = 'Cleans the isolated model catalog Maven staging repository.'
|
description = 'Cleans the isolated model catalog Maven staging repository.'
|
||||||
doLast {
|
delete(rawModelCatalogRepository)
|
||||||
project.delete(layout.buildDirectory.dir('model-catalog-staging-repository'))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gradle.projectsEvaluated {
|
gradle.projectsEvaluated {
|
||||||
@@ -620,40 +621,28 @@ gradle.projectsEvaluated {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register('prepareModelCatalogReleaseCandidate') {
|
def prepareModelCatalogReleaseCandidate = tasks.named('prepareModelCatalogReleaseCandidate') {
|
||||||
group = 'publishing'
|
|
||||||
description = 'Stages the POM-only standard aggregate and model BOM with Central checksums.'
|
|
||||||
dependsOn(project(':models:standard').tasks.named('check'))
|
dependsOn(project(':models:standard').tasks.named('check'))
|
||||||
dependsOn(project(':models:bom').tasks.named('check'))
|
dependsOn(project(':models:bom').tasks.named('check'))
|
||||||
dependsOn(':models:standard:publishStandardPublicationToCatalogStagingRepository')
|
dependsOn(':models:standard:publishStandardPublicationToCatalogStagingRepository')
|
||||||
dependsOn(':models:bom:publishBomPublicationToCatalogStagingRepository')
|
dependsOn(':models:bom:publishBomPublicationToCatalogStagingRepository')
|
||||||
outputs.dir(layout.buildDirectory.dir('model-catalog-staging-repository'))
|
rawRepositoryDirectory = rawModelCatalogRepository
|
||||||
doLast {
|
preparedBundleDirectory = preparedModelCatalogBundleInput
|
||||||
File repository = layout.buildDirectory.dir('model-catalog-staging-repository').get().asFile
|
catalogVersion = project(':models:standard').version.toString()
|
||||||
repository.eachFileRecurse { File artifact ->
|
|
||||||
if (artifact.isFile() && !['.md5', '.sha1', '.sha256', '.sha512'].any {
|
|
||||||
String extension -> artifact.name.endsWith(extension)
|
|
||||||
}) {
|
|
||||||
['MD5': 'md5', 'SHA-1': 'sha1'].each { String algorithm, String extension ->
|
|
||||||
String digest = java.security.MessageDigest.getInstance(algorithm)
|
|
||||||
.digest(artifact.bytes).encodeHex().toString()
|
|
||||||
new File(artifact.absolutePath + ".${extension}").setText(digest, 'US-ASCII')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register('modelCatalogCentralBundle', Zip) {
|
def modelCatalogCentralBundle = tasks.register('modelCatalogCentralBundle', Zip) {
|
||||||
group = 'publishing'
|
group = 'publishing'
|
||||||
description = 'Builds the local POM-only model catalog bundle without remote publication.'
|
description = 'Builds the local POM-only model catalog bundle without remote publication.'
|
||||||
dependsOn(tasks.named('prepareModelCatalogReleaseCandidate'))
|
dependsOn(prepareModelCatalogReleaseCandidate)
|
||||||
from(layout.buildDirectory.dir('model-catalog-staging-repository')) {
|
from(preparedModelCatalogBundleInput)
|
||||||
exclude('**/maven-metadata*.xml*', '**/*.module*')
|
|
||||||
}
|
|
||||||
destinationDirectory = layout.buildDirectory.dir('model-catalog-release-candidate')
|
destinationDirectory = layout.buildDirectory.dir('model-catalog-release-candidate')
|
||||||
archiveFileName = "radixor-models-catalog-${project(':models:standard').version}-central-bundle.zip"
|
archiveFileName = "radixor-models-catalog-${project(':models:standard').version}-central-bundle.zip"
|
||||||
doFirst {
|
doFirst {
|
||||||
|
File preparedInput = preparedModelCatalogBundleInput.get().asFile
|
||||||
|
if (!preparedInput.isDirectory() || preparedInput.listFiles() == null || preparedInput.listFiles().length == 0) {
|
||||||
|
throw new GradleException("The prepared model catalog bundle input is missing or empty: ${preparedInput}.")
|
||||||
|
}
|
||||||
if (providers.environmentVariable('GITHUB_REF_TYPE').orNull == 'tag'
|
if (providers.environmentVariable('GITHUB_REF_TYPE').orNull == 'tag'
|
||||||
&& (providers.environmentVariable('SIGNING_KEY').orNull?.isBlank() != false
|
&& (providers.environmentVariable('SIGNING_KEY').orNull?.isBlank() != false
|
||||||
|| providers.environmentVariable('SIGNING_PASSWORD').orNull?.isBlank() != false)) {
|
|| providers.environmentVariable('SIGNING_PASSWORD').orNull?.isBlank() != false)) {
|
||||||
@@ -662,45 +651,20 @@ tasks.register('modelCatalogCentralBundle', Zip) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register('verifyModelCatalogReleaseCandidate') {
|
def publishedModelVersions = modelProjects().collect { Project modelProject ->
|
||||||
group = 'verification'
|
modelProject.file('model-version.txt').getText('UTF-8').trim()
|
||||||
description = 'Verifies that the local catalog bundle contains only two POM publications, signatures when configured, and checksums.'
|
}.toSet()
|
||||||
dependsOn(tasks.named('modelCatalogCentralBundle'))
|
if (publishedModelVersions.size() != 1) {
|
||||||
outputs.file(layout.buildDirectory.file('reports/models/catalog-release-candidate.txt'))
|
throw new GradleException("The catalog verifier requires one common model version; found ${publishedModelVersions}.")
|
||||||
doLast {
|
|
||||||
File bundle = tasks.named('modelCatalogCentralBundle', Zip).get().archiveFile.get().asFile
|
|
||||||
List<String> entries = []
|
|
||||||
new java.util.zip.ZipFile(bundle).withCloseable { java.util.zip.ZipFile archive ->
|
|
||||||
archive.entries().each { java.util.zip.ZipEntry entry ->
|
|
||||||
if (!entry.directory) entries.add(entry.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
entries.sort()
|
|
||||||
List<String> poms = entries.findAll { String entry -> entry.endsWith('.pom') }
|
|
||||||
if (poms.size() != 2 || entries.any { String entry ->
|
|
||||||
entry.endsWith('.jar') || entry.endsWith('/stemmer.gz') || entry.contains('benchmark-pack')
|
|
||||||
}) {
|
|
||||||
throw new GradleException('The model catalog bundle must contain only the standard and BOM POM publications.')
|
|
||||||
}
|
|
||||||
List<String> unsupported = entries.findAll { String entry ->
|
|
||||||
!(entry.endsWith('.pom') || entry.endsWith('.pom.md5') || entry.endsWith('.pom.sha1')
|
|
||||||
|| entry.endsWith('.pom.sha256') || entry.endsWith('.pom.sha512')
|
|
||||||
|| entry.endsWith('.pom.asc') || entry.endsWith('.pom.asc.md5')
|
|
||||||
|| entry.endsWith('.pom.asc.sha1') || entry.endsWith('.pom.asc.sha256')
|
|
||||||
|| entry.endsWith('.pom.asc.sha512'))
|
|
||||||
}
|
|
||||||
if (!unsupported.isEmpty()) {
|
|
||||||
throw new GradleException("The model catalog bundle contains unsupported files: ${unsupported}.")
|
|
||||||
}
|
|
||||||
poms.each { String pom ->
|
|
||||||
if (!entries.contains(pom + '.md5') || !entries.contains(pom + '.sha1')) {
|
|
||||||
throw new GradleException("The model catalog POM is missing Central checksums: ${pom}.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File report = layout.buildDirectory.file('reports/models/catalog-release-candidate.txt').get().asFile
|
|
||||||
report.parentFile.mkdirs()
|
|
||||||
report.setText("Bundle: ${bundle.name}\nBytes: ${bundle.length()}\n" + entries.join('\n') + '\n', 'UTF-8')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.named('verifyModelCatalogReleaseCandidate') {
|
||||||
|
bundleFile = modelCatalogCentralBundle.flatMap { Zip archive -> archive.archiveFile }
|
||||||
|
reportFile = layout.buildDirectory.file('reports/models/catalog-release-candidate.txt')
|
||||||
|
catalogVersion = project(':models:standard').version.toString()
|
||||||
|
modelVersion = publishedModelVersions.first()
|
||||||
|
defaultModelIds = defaultModelProjects().collect { Project modelProject -> modelProject.name }
|
||||||
|
allModelIds = modelProjects().collect { Project modelProject -> modelProject.name }
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register('verifyArtifactSizes') {
|
tasks.register('verifyArtifactSizes') {
|
||||||
|
|||||||
@@ -74,23 +74,26 @@ signing {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def bomPublicationPom = layout.buildDirectory.file('publications/bom/pom-default.xml')
|
||||||
|
def expectedBomArtifactIds = modelIds.collect { String modelId -> "radixor-model-${modelId}" }
|
||||||
|
def bomHasJarTasks = !tasks.withType(Jar).isEmpty()
|
||||||
tasks.register('verifyPomOnlyPlatform') {
|
tasks.register('verifyPomOnlyPlatform') {
|
||||||
group = 'verification'
|
group = 'verification'
|
||||||
description = 'Verifies the POM-only model dependency-management platform.'
|
description = 'Verifies the POM-only model dependency-management platform.'
|
||||||
dependsOn(tasks.named('generatePomFileForBomPublication'))
|
dependsOn(tasks.named('generatePomFileForBomPublication'))
|
||||||
|
inputs.file(bomPublicationPom)
|
||||||
doLast {
|
doLast {
|
||||||
File pomFile = layout.buildDirectory.file('publications/bom/pom-default.xml').get().asFile
|
File pomFile = bomPublicationPom.get().asFile
|
||||||
Node pom = new XmlParser().parse(pomFile)
|
Node pom = new XmlParser().parse(pomFile)
|
||||||
List<Node> constraints = pom.dependencyManagement.dependencies.dependency as List<Node>
|
List<Node> constraints = pom.dependencyManagement.dependencies.dependency as List<Node>
|
||||||
List<String> artifactIds = constraints.collect { Node dependency -> dependency.artifactId.text() }
|
List<String> artifactIds = constraints.collect { Node dependency -> dependency.artifactId.text() }
|
||||||
List<String> expected = modelIds.collect { String modelId -> "radixor-model-${modelId}" }
|
if (pom.packaging.text() != 'pom' || artifactIds != expectedBomArtifactIds) {
|
||||||
if (pom.packaging.text() != 'pom' || artifactIds != expected) {
|
|
||||||
throw new GradleException('radixor-models-bom must publish exactly the ordered model constraints as Maven packaging pom.')
|
throw new GradleException('radixor-models-bom must publish exactly the ordered model constraints as Maven packaging pom.')
|
||||||
}
|
}
|
||||||
if (!pom.dependencies.isEmpty()) {
|
if (!pom.dependencies.isEmpty()) {
|
||||||
throw new GradleException('radixor-models-bom must not introduce runtime model dependencies.')
|
throw new GradleException('radixor-models-bom must not introduce runtime model dependencies.')
|
||||||
}
|
}
|
||||||
if (!tasks.withType(Jar).isEmpty()) {
|
if (bomHasJarTasks) {
|
||||||
throw new GradleException('radixor-models-bom must not create binary, sources, or Javadoc JARs.')
|
throw new GradleException('radixor-models-bom must not create binary, sources, or Javadoc JARs.')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,28 +85,31 @@ signing {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def standardPublicationPom = layout.buildDirectory.file('publications/standard/pom-default.xml')
|
||||||
|
def expectedStandardArtifactIds = defaultModelIds.collect { String modelId -> "radixor-model-${modelId}" }
|
||||||
|
def standardHasJarTasks = !tasks.withType(Jar).isEmpty()
|
||||||
tasks.register('verifyPomOnlyAggregate') {
|
tasks.register('verifyPomOnlyAggregate') {
|
||||||
group = 'verification'
|
group = 'verification'
|
||||||
description = 'Verifies the standard POM-only aggregate and its runtime model dependencies.'
|
description = 'Verifies the standard POM-only aggregate and its runtime model dependencies.'
|
||||||
dependsOn(tasks.named('generatePomFileForStandardPublication'))
|
dependsOn(tasks.named('generatePomFileForStandardPublication'))
|
||||||
|
inputs.file(standardPublicationPom)
|
||||||
doLast {
|
doLast {
|
||||||
File pomFile = layout.buildDirectory.file('publications/standard/pom-default.xml').get().asFile
|
File pomFile = standardPublicationPom.get().asFile
|
||||||
Node pom = new XmlParser().parse(pomFile)
|
Node pom = new XmlParser().parse(pomFile)
|
||||||
List<Node> dependencies = pom.dependencies.dependency as List<Node>
|
List<Node> dependencies = pom.dependencies.dependency as List<Node>
|
||||||
List<String> artifactIds = dependencies.collect { Node dependency ->
|
List<String> artifactIds = dependencies.collect { Node dependency ->
|
||||||
dependency.artifactId.text()
|
dependency.artifactId.text()
|
||||||
}
|
}
|
||||||
List<String> expected = defaultModelIds.collect { String modelId -> "radixor-model-${modelId}" }
|
|
||||||
if (pom.packaging.text() != 'pom') {
|
if (pom.packaging.text() != 'pom') {
|
||||||
throw new GradleException('radixor-models-standard must publish Maven packaging pom.')
|
throw new GradleException('radixor-models-standard must publish Maven packaging pom.')
|
||||||
}
|
}
|
||||||
if (artifactIds != expected || dependencies.any { Node dependency -> dependency.scope.text() != 'runtime' }) {
|
if (artifactIds != expectedStandardArtifactIds || dependencies.any { Node dependency -> dependency.scope.text() != 'runtime' }) {
|
||||||
throw new GradleException('radixor-models-standard must contain exactly the ordered default model runtime dependencies.')
|
throw new GradleException('radixor-models-standard must contain exactly the ordered default model runtime dependencies.')
|
||||||
}
|
}
|
||||||
if (artifactIds.contains('radixor-model-pl-pl-polimorf')) {
|
if (artifactIds.contains('radixor-model-pl-pl-polimorf')) {
|
||||||
throw new GradleException('radixor-models-standard must exclude the optional PoliMorf model.')
|
throw new GradleException('radixor-models-standard must exclude the optional PoliMorf model.')
|
||||||
}
|
}
|
||||||
if (!tasks.withType(Jar).isEmpty()) {
|
if (standardHasJarTasks) {
|
||||||
throw new GradleException('radixor-models-standard must not create binary, sources, or Javadoc JARs.')
|
throw new GradleException('radixor-models-standard must not create binary, sources, or Javadoc JARs.')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user