From 6c222d8345a09f6481145b2e53441192c4182dea Mon Sep 17 00:00:00 2001 From: "Dmitriy.Panov" Date: Tue, 2 Sep 2025 20:39:08 +0200 Subject: [PATCH] build scripts: Maven Central publication validation code decoupled from Jewel-related code GitOrigin-RevId: 8cd53e83176e74ceffa948122c14707834117971 --- .../intellij/build/IdeaCommunityProperties.kt | 3 + .../intellij/build/JewelMavenArtifacts.kt | 55 +---------------- .../build/JetBrainsProductProperties.kt | 17 ++++-- .../build/MavenArtifactsProperties.kt | 6 ++ .../build/impl/maven/MavenArtifactsBuilder.kt | 61 ++++++++++++++++++- 5 files changed, 83 insertions(+), 59 deletions(-) diff --git a/build/src/org/jetbrains/intellij/build/IdeaCommunityProperties.kt b/build/src/org/jetbrains/intellij/build/IdeaCommunityProperties.kt index 4ec49a58232a..ab2b723cbfd5 100644 --- a/build/src/org/jetbrains/intellij/build/IdeaCommunityProperties.kt +++ b/build/src/org/jetbrains/intellij/build/IdeaCommunityProperties.kt @@ -74,6 +74,9 @@ open class IdeaCommunityProperties(private val communityHomeDir: Path) : BaseIde "intellij.platform.util.base.multiplatform", "intellij.platform.util.zip", )) + mavenArtifacts.validateForMavenCentralPublication = { module -> + JewelMavenArtifacts.isPublishedJewelModule(module) + } mavenArtifacts.patchCoordinates = { module, coordinates -> when { JewelMavenArtifacts.isPublishedJewelModule(module) -> JewelMavenArtifacts.patchCoordinates(module, coordinates) diff --git a/build/src/org/jetbrains/intellij/build/JewelMavenArtifacts.kt b/build/src/org/jetbrains/intellij/build/JewelMavenArtifacts.kt index 4c2363d31674..82d7e4e9d9b4 100644 --- a/build/src/org/jetbrains/intellij/build/JewelMavenArtifacts.kt +++ b/build/src/org/jetbrains/intellij/build/JewelMavenArtifacts.kt @@ -2,30 +2,22 @@ package org.jetbrains.intellij.build import com.intellij.util.text.SemVer -import kotlin.io.path.exists -import kotlin.io.path.name import org.apache.maven.model.Developer import org.apache.maven.model.License import org.apache.maven.model.Model -import org.apache.maven.model.Scm import org.jetbrains.intellij.build.BuildPaths.Companion.COMMUNITY_ROOT import org.jetbrains.intellij.build.dependencies.DependenciesProperties import org.jetbrains.intellij.build.impl.libraries.isLibraryModule import org.jetbrains.intellij.build.impl.maven.DependencyScope import org.jetbrains.intellij.build.impl.maven.GeneratedMavenArtifacts import org.jetbrains.intellij.build.impl.maven.MavenArtifactDependency -import org.jetbrains.intellij.build.impl.maven.MavenCentralPublication import org.jetbrains.intellij.build.impl.maven.MavenCoordinates -import org.jetbrains.jps.model.java.JavaSourceRootType import org.jetbrains.jps.model.java.JpsJavaDependencyScope import org.jetbrains.jps.model.java.JpsJavaExtensionService import org.jetbrains.jps.model.module.JpsDependencyElement import org.jetbrains.jps.model.module.JpsModule import org.jetbrains.jps.model.module.JpsModuleDependency -import kotlin.io.path.ExperimentalPathApi -import kotlin.io.path.copyTo -import kotlin.io.path.createDirectories -import kotlin.io.path.deleteRecursively +import kotlin.io.path.exists internal object JewelMavenArtifacts { private const val GROUP_ID: String = "org.jetbrains.jewel" @@ -155,16 +147,10 @@ internal object JewelMavenArtifacts { check(isPublishedJewelModule(module)) model.name = "Jewel" model.description = "A theme for Compose for Desktop that implements the IntelliJ Platform look and feel." - model.url = "https://github.com/JetBrains/intellij-community" model.addLicense(License().apply { name = "Apache License 2.0" url = "https://www.apache.org/licenses/LICENSE-2.0.txt" }) - model.scm = Scm().apply { - connection = "scm:git:https://github.com/JetBrains/intellij-community.git" - developerConnection = "scm:git:https://github.com/JetBrains/intellij-community.git" - url = "https://github.com/JetBrains/intellij-community" - } model.addDeveloper(Developer().apply { id = "Google" name = "Google Team" @@ -188,7 +174,7 @@ internal object JewelMavenArtifacts { return (scope == JpsJavaDependencyScope.COMPILE || scope == JpsJavaDependencyScope.PROVIDED) } - suspend fun validate(context: BuildContext, mavenArtifacts: Collection) { + fun validate(context: BuildContext, mavenArtifacts: Collection) { ALL_MODULES.asSequence() .map(context::findRequiredModule) .flatMap { it.modulesTree() } @@ -207,7 +193,6 @@ internal object JewelMavenArtifacts { "The module ${module.name} has groupId=${artifact.coordinates.groupId} " + "but it's expected to have groupId=$GROUP_ID because Maven Central publication credentials are issues per namespace/groupId" } - validateForMavenCentralPublication(artifact, context) } } for ((jewelModuleName, artifactId) in ALL) { @@ -221,40 +206,4 @@ internal object JewelMavenArtifacts { } } } - - /** See https://central.sonatype.org/publish/requirements */ - @OptIn(ExperimentalPathApi::class) - private suspend fun validateForMavenCentralPublication(artifacts: GeneratedMavenArtifacts, context: BuildContext) { - if (artifacts.module.getSourceRoots(JavaSourceRootType.SOURCE).any()) { - val sources = artifacts.coordinates.getFileName("sources", "jar") - check(artifacts.files.any { it.name == sources }) { - "No $sources is generated for the module ${artifacts.module.name}" - } - val javadoc = artifacts.coordinates.getFileName("javadoc", "jar") - check(artifacts.files.any { it.name == javadoc }) { - "No $javadoc is generated for the module ${artifacts.module.name}" - } - } - val pom = artifacts.coordinates.getFileName(packaging = "pom") - val pomXml = artifacts.files.singleOrNull { it.name == pom } - check(pomXml != null) { - "No $pom is generated for the module ${artifacts.module.name}" - } - val workDir = context.paths.tempDir.resolve("${artifacts.module.name}-maven-central-publication-test") - workDir.deleteRecursively() - workDir.createDirectories() - artifacts.files.forEach { it.copyTo(workDir.resolve(it.name)) } - val publication = MavenCentralPublication( - context = context, - workDir = workDir, - dryRun = true, - ) - // making sure that a bundle.zip can be built without issues - publication.bundle() - val coordinatesToBePublished = publication.artifacts.map { it.coordinates } - check(coordinatesToBePublished.count() == 1 && coordinatesToBePublished.contains(artifacts.coordinates)) { - "Maven coordinates ${artifacts.coordinates} generated for the module ${artifacts.module.name} " + - "don't match the coordinates $coordinatesToBePublished from $pomXml" - } - } } diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/JetBrainsProductProperties.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/JetBrainsProductProperties.kt index 035252c63ce7..a76fea4c0dce 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/JetBrainsProductProperties.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/JetBrainsProductProperties.kt @@ -20,6 +20,7 @@ import com.jetbrains.plugin.structure.intellij.problems.TemplateWordInPluginName import com.jetbrains.plugin.structure.intellij.verifiers.DEFAULT_ILLEGAL_PREFIXES import com.jetbrains.plugin.structure.intellij.verifiers.PRODUCT_ID_RESTRICTED_WORDS import kotlinx.collections.immutable.plus +import org.jetbrains.annotations.ApiStatus import org.jetbrains.intellij.build.SoftwareBillOfMaterials.Companion.Suppliers import org.jetbrains.intellij.build.impl.PlatformJarNames.PLATFORM_CORE_NIO_FS import org.jetbrains.jps.model.module.JpsModule @@ -31,6 +32,15 @@ import java.util.function.BiPredicate * Describes a distribution of an IntelliJ-based IDE hosted in the IntelliJ repository. */ abstract class JetBrainsProductProperties : ProductProperties() { + companion object { + @ApiStatus.Internal + fun isCommunityModule(module: JpsModule, context: BuildContext): Boolean { + return module.contentRootsList.urls.all { url -> + Path.of(JpsPathUtil.urlToPath(url)).startsWith(context.paths.communityHomeDir) + } + } + } + init { scrambleMainJar = true includeIntoSourcesArchiveFilter = BiPredicate(::isCommunityModule) @@ -40,10 +50,9 @@ abstract class JetBrainsProductProperties : ProductProperties() { productLayout.addPlatformSpec { layout, _ -> layout.withModule(IJENT_BOOT_CLASSPATH_MODULE, PLATFORM_CORE_NIO_FS) } } - protected fun isCommunityModule(module: JpsModule, context: BuildContext): Boolean = - module.contentRootsList.urls.all { url -> - Path.of(JpsPathUtil.urlToPath(url)).startsWith(context.paths.communityHomeDir) - } + protected fun isCommunityModule(module: JpsModule, context: BuildContext): Boolean { + return JetBrainsProductProperties.isCommunityModule(module, context) + } override suspend fun copyAdditionalFiles(context: BuildContext, targetDir: Path) { } diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/MavenArtifactsProperties.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/MavenArtifactsProperties.kt index 80e547ca3432..bbd212e7b706 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/MavenArtifactsProperties.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/MavenArtifactsProperties.kt @@ -53,6 +53,12 @@ class MavenArtifactsProperties { module.contentRootsList.urls.all { Path.of(JpsPathUtil.urlToPath(it)).startsWith(context.paths.communityHomeDir) } } + /** + * A predicate which returns `true` for modules which Maven artifacts should be validated according to https://central.sonatype.org/publish/requirements + */ + @ApiStatus.Internal + var validateForMavenCentralPublication: (JpsModule) -> Boolean = { false } + @ApiStatus.Internal var patchCoordinates: (JpsModule, MavenCoordinates) -> MavenCoordinates = { _, coordinates -> coordinates } diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/maven/MavenArtifactsBuilder.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/maven/MavenArtifactsBuilder.kt index 348c65326f94..8f5642cddad0 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/maven/MavenArtifactsBuilder.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/maven/MavenArtifactsBuilder.kt @@ -13,10 +13,12 @@ import org.apache.maven.model.Developer import org.apache.maven.model.Exclusion import org.apache.maven.model.Model import org.apache.maven.model.Organization +import org.apache.maven.model.Scm import org.apache.maven.model.io.xpp3.MavenXpp3Writer import org.jetbrains.annotations.ApiStatus import org.jetbrains.intellij.build.BuildContext import org.jetbrains.intellij.build.DirSource +import org.jetbrains.intellij.build.JetBrainsProductProperties import org.jetbrains.intellij.build.ZipSource import org.jetbrains.intellij.build.buildJar import org.jetbrains.intellij.build.impl.commonModuleExcludes @@ -41,6 +43,11 @@ import java.nio.file.Files import java.nio.file.Path import java.util.Locale import java.util.function.BiConsumer +import kotlin.io.path.ExperimentalPathApi +import kotlin.io.path.copyTo +import kotlin.io.path.createDirectories +import kotlin.io.path.deleteRecursively +import kotlin.io.path.name /** * Generates Maven artifacts for IDE and plugin modules. Artifacts aren't generated for modules which depend on non-repository libraries. @@ -321,9 +328,51 @@ open class MavenArtifactsBuilder(protected val context: BuildContext) { } internal suspend fun validate(builtArtifacts: Map>) { - context.productProperties.mavenArtifacts.validate(context, builtArtifacts.map { (data, files) -> + val artifacts = builtArtifacts.map { (data, files) -> GeneratedMavenArtifacts(data.module, data.coordinates, files) - }) + } + artifacts.forEach { + if (context.productProperties.mavenArtifacts.validateForMavenCentralPublication(it.module)) { + validateForMavenCentralPublication(it, context) + } + } + context.productProperties.mavenArtifacts.validate(context, artifacts) + } + + /** See https://central.sonatype.org/publish/requirements */ + @OptIn(ExperimentalPathApi::class) + private suspend fun validateForMavenCentralPublication(artifacts: GeneratedMavenArtifacts, context: BuildContext) { + if (artifacts.module.getSourceRoots(JavaSourceRootType.SOURCE).any()) { + val sources = artifacts.coordinates.getFileName("sources", "jar") + check(artifacts.files.any { it.name == sources }) { + "No $sources is generated for the module ${artifacts.module.name}" + } + val javadoc = artifacts.coordinates.getFileName("javadoc", "jar") + check(artifacts.files.any { it.name == javadoc }) { + "No $javadoc is generated for the module ${artifacts.module.name}" + } + } + val pom = artifacts.coordinates.getFileName(packaging = "pom") + val pomXml = artifacts.files.singleOrNull { it.name == pom } + check(pomXml != null) { + "No $pom is generated for the module ${artifacts.module.name}" + } + val workDir = context.paths.tempDir.resolve("${artifacts.module.name}-maven-central-publication-test") + workDir.deleteRecursively() + workDir.createDirectories() + artifacts.files.forEach { it.copyTo(workDir.resolve(it.name)) } + val publication = MavenCentralPublication( + context = context, + workDir = workDir, + dryRun = true, + ) + // making sure that a bundle.zip can be built without issues + publication.bundle() + val coordinatesToBePublished = publication.artifacts.map { it.coordinates } + check(coordinatesToBePublished.count() == 1 && coordinatesToBePublished.contains(artifacts.coordinates)) { + "Maven coordinates ${artifacts.coordinates} generated for the module ${artifacts.module.name} " + + "don't match the coordinates $coordinatesToBePublished from $pomXml" + } } } @@ -381,6 +430,14 @@ private fun generatePomXmlData(artifactData: MavenArtifactData, file: Path, cont organization = "JetBrains" organizationUrl = "https://www.jetbrains.com" }) + if (JetBrainsProductProperties.isCommunityModule(artifactData.module, context)) { + pomModel.url = "https://github.com/JetBrains/intellij-community" + pomModel.scm = Scm().apply { + connection = "scm:git:https://github.com/JetBrains/intellij-community.git" + developerConnection = "scm:git:https://github.com/JetBrains/intellij-community.git" + url = "https://github.com/JetBrains/intellij-community" + } + } context.productProperties.mavenArtifacts.addPomMetadata(artifactData.module, pomModel) pomModel.setOrFailIfAlreadySet("Model version", value = "4.0.0", { modelVersion }, { modelVersion = it }) pomModel.setOrFailIfAlreadySet("GroupId", value = artifactData.coordinates.groupId, { groupId }, { groupId = it })