diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/BuildContextImpl.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/BuildContextImpl.kt index 380c5204b955..0de4339cc41f 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/BuildContextImpl.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/BuildContextImpl.kt @@ -254,8 +254,8 @@ class BuildContextImpl internal constructor( val rootModule = productProperties.embeddedFrontendRootModule if (rootModule != null && options.enableEmbeddedFrontend) { val moduleRepository = getOriginalModuleRepository() - val productModules = moduleRepository.loadProductModules(rootModule, ProductMode.FRONTEND) - FrontendModuleFilterImpl(moduleRepository.repository, productModules) + val productModules = moduleRepository.loadRawProductModules(rootModule, ProductMode.FRONTEND) + FrontendModuleFilterImpl.create(project, productModules, jarPackagerDependencyHelper) } else { EmptyFrontendModuleFilter @@ -276,7 +276,7 @@ class BuildContextImpl internal constructor( return asyncLazy("Content Modules Filter") { val bundledPluginModules = getBundledPluginModules() - ContentModuleByProductModeFilter(getOriginalModuleRepository().repository, bundledPluginModules, productProperties.productMode) + ContentModuleByProductModeFilter(project, bundledPluginModules, productProperties.productMode) } } diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/ContentModuleFilterImpl.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/ContentModuleFilterImpl.kt index 045cbd2d933f..e7b17f5bc46c 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/ContentModuleFilterImpl.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/ContentModuleFilterImpl.kt @@ -2,30 +2,29 @@ package org.jetbrains.intellij.build.impl import com.intellij.platform.runtime.product.ProductMode -import com.intellij.platform.runtime.product.impl.ProductModeMatcher -import com.intellij.platform.runtime.repository.RuntimeModuleId -import com.intellij.platform.runtime.repository.RuntimeModuleRepository import org.jetbrains.intellij.build.BuildContext import org.jetbrains.intellij.build.ContentModuleFilter +import org.jetbrains.intellij.build.impl.moduleBased.JpsProductModeMatcher +import org.jetbrains.jps.model.JpsProject /** * An instance of [ContentModuleFilter] which excludes modules not compatible with the given [ProductMode] from the platform part and bundled plugins. */ internal class ContentModuleByProductModeFilter( - private val moduleRepository: RuntimeModuleRepository, - bundledPluginModules: List, + private val project: JpsProject, + bundledPluginModules: List, private val productMode: ProductMode ) : ContentModuleFilter { - private val productModeMatcher = ProductModeMatcher(productMode) + private val productModeMatcher = JpsProductModeMatcher(productMode) private val bundledPluginMainModules = bundledPluginModules.toSet() override fun isOptionalModuleIncluded(moduleName: String, pluginMainModuleName: String?): Boolean { if (pluginMainModuleName != null && pluginMainModuleName !in bundledPluginMainModules) { return true } - val moduleDescriptor = moduleRepository.getModule(RuntimeModuleId.module(moduleName)) - return productModeMatcher.matches(moduleDescriptor) + val module = project.findModuleByName(moduleName) ?: return true + return productModeMatcher.matches(module) } override fun toString(): String { diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/FrontendModuleFilterImpl.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/FrontendModuleFilterImpl.kt index ce78335b08da..9a1b568850bb 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/FrontendModuleFilterImpl.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/FrontendModuleFilterImpl.kt @@ -2,33 +2,84 @@ package org.jetbrains.intellij.build.impl import com.intellij.platform.runtime.product.ProductMode -import com.intellij.platform.runtime.product.ProductModules -import com.intellij.platform.runtime.product.impl.ProductModeMatcher -import com.intellij.platform.runtime.repository.RuntimeModuleId -import com.intellij.platform.runtime.repository.RuntimeModuleRepository +import com.intellij.platform.runtime.product.serialization.RawProductModules import org.jetbrains.intellij.build.FrontendModuleFilter +import org.jetbrains.intellij.build.JarPackagerDependencyHelper +import org.jetbrains.intellij.build.impl.moduleBased.JpsProductModeMatcher import org.jetbrains.jps.model.JpsNamedElement +import org.jetbrains.jps.model.JpsProject +import org.jetbrains.jps.model.java.JpsJavaExtensionService import org.jetbrains.jps.model.library.JpsLibrary import org.jetbrains.jps.model.module.JpsModule +import org.jetbrains.jps.model.module.JpsModuleReference -internal class FrontendModuleFilterImpl(private val moduleRepository: RuntimeModuleRepository, productModules: ProductModules): FrontendModuleFilter { - private val includedModules: Set = (sequenceOf(productModules.mainModuleGroup) + productModules.bundledPluginModuleGroups.asSequence()) - .flatMap { it.includedModules.asSequence() } - .filter { included -> included.moduleDescriptor.moduleId !in MODULES_SCRAMBLED_WITH_FRONTEND } - .mapTo(HashSet()) { it.moduleDescriptor.moduleId } - private val frontendModeMatcher = ProductModeMatcher(ProductMode.FRONTEND) +internal class FrontendModuleFilterImpl private constructor( + private val project: JpsProject, + private val frontendModeMatcher: JpsProductModeMatcher, + private val includedModuleNames: Set, + private val includedProjectLibraryNames: Set, +): FrontendModuleFilter { + companion object { + suspend fun create(project: JpsProject, productModules: RawProductModules, jarPackagerDependencyHelper: JarPackagerDependencyHelper): FrontendModuleFilter { + val frontendModeMatcher = JpsProductModeMatcher(ProductMode.FRONTEND) + val includedModuleNames = LinkedHashSet() + val includedProjectLibraryNames = LinkedHashSet() + + for (rootModuleName in productModules.mainGroupModules) { + val rootModule = project.findModuleByName(rootModuleName.moduleId.stringId) ?: continue + collectTransitiveDependenciesCompatibleWithFrontend(rootModule, frontendModeMatcher, includedModuleNames, includedProjectLibraryNames) + } + + for (mainModuleId in productModules.bundledPluginMainModules) { + val module = project.findModuleByName(mainModuleId.stringId) ?: continue + if (frontendModeMatcher.matches(module)) { + includedModuleNames.add(module.name) + jarPackagerDependencyHelper.readPluginContentFromDescriptor(module, ModuleOutputPatcher()) + .mapNotNull { project.findModuleByName(it.first) } + .filter { frontendModeMatcher.matches(it) } + .mapTo(includedModuleNames) { it.name } + } + } + + return FrontendModuleFilterImpl(project, frontendModeMatcher, includedModuleNames, includedProjectLibraryNames) + } + + private fun collectTransitiveDependenciesCompatibleWithFrontend( + module: JpsModule, + frontendModeMatcher: JpsProductModeMatcher, + includedModuleNames: MutableSet, + includedProjectLibraryNames: MutableSet, + ) { + if (isScrambledWithFrontend(module) || !frontendModeMatcher.matches(module)) { + return + } + if (!includedModuleNames.add(module.name)) { + return + } + JpsJavaExtensionService.dependencies(module).productionOnly().runtimeOnly().processModuleAndLibraries( + { depModule -> + collectTransitiveDependenciesCompatibleWithFrontend(depModule, frontendModeMatcher, includedModuleNames, includedProjectLibraryNames) + }, + { depLibrary -> + if (!isScrambledWithFrontend(depLibrary) && depLibrary.createReference().parentReference !is JpsModuleReference) { + includedProjectLibraryNames.add(depLibrary.name) + } + } + ) + } + } override fun isModuleIncluded(moduleName: String): Boolean { - return RuntimeModuleId.module(moduleName) in includedModules + return moduleName in includedModuleNames } override fun isProjectLibraryIncluded(libraryName: String): Boolean { - return RuntimeModuleId.projectLibrary(libraryName) in includedModules + return libraryName in includedProjectLibraryNames } override fun isModuleCompatibleWithFrontend(moduleName: String): Boolean { - val moduleDescriptor = moduleRepository.getModule(RuntimeModuleId.module(moduleName)) - return frontendModeMatcher.matches(moduleDescriptor) + val module = project.findModuleByName(moduleName) + return module != null && frontendModeMatcher.matches(module) } } @@ -42,15 +93,9 @@ val PROJECT_LIBRARIES_SCRAMBLED_WITH_FRONTEND: Set = setOf( ) /** - * Contains a set of runtime modules from the platform part which are also included in the frontend JARs and scrambled (differently) there. - * It's important not to include JARs for these modules in the platform part to the classpath of the frontend process, because they may - * cause clashes. + * Returns `true` if the module or library [element] from the platform part which are also included in the frontend JARs and scrambled (differently) there. + * It's important not to include JARs for these modules and libraries in the platform part to the classpath of the frontend process, because they may cause clashes. */ -val MODULES_SCRAMBLED_WITH_FRONTEND: Set by lazy { - setOf(RuntimeModuleId.module(PLATFORM_MODULE_SCRAMBLED_WITH_FRONTEND)) + - PROJECT_LIBRARIES_SCRAMBLED_WITH_FRONTEND.map { RuntimeModuleId.projectLibrary(it) } -} - fun isScrambledWithFrontend(element: JpsNamedElement): Boolean = when (element) { is JpsModule -> element.name == PLATFORM_MODULE_SCRAMBLED_WITH_FRONTEND is JpsLibrary -> element.name in PROJECT_LIBRARIES_SCRAMBLED_WITH_FRONTEND diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/moduleBased/JpsProductModeMatcher.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/moduleBased/JpsProductModeMatcher.kt new file mode 100644 index 000000000000..5633d43bb1ff --- /dev/null +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/moduleBased/JpsProductModeMatcher.kt @@ -0,0 +1,32 @@ +// Copyright 2000-2025 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.moduleBased + +import com.intellij.platform.runtime.product.ProductMode +import com.intellij.platform.runtime.product.impl.ProductModes +import org.jetbrains.jps.model.java.JpsJavaExtensionService +import org.jetbrains.jps.model.module.JpsModule + +/** + * This is an equivalent implementation of [com.intellij.platform.runtime.product.impl.ProductModeMatcher] based on JPS model instead of the runtime module repository. + */ +internal class JpsProductModeMatcher(productMode: ProductMode) { + private val incompatibleRootModule = ProductModes.getIncompatibleRootModule(productMode).stringId + private val cache: MutableMap = mutableMapOf() + + fun matches(module: JpsModule): Boolean { + val cached = cache[module] + if (cached != null) return cached + if (incompatibleRootModule == module.name) { + cache[module] = false + return false + } + + cache[module] = true //this is needed to prevent StackOverflowError in the case of circular dependencies + var matches = true + JpsJavaExtensionService.dependencies(module).productionOnly().runtimeOnly().processModules { + matches = matches && matches(it) + } + cache[module] = matches + return matches + } +} diff --git a/platform/runtime/product/src/impl/ProductModes.java b/platform/runtime/product/src/impl/ProductModes.java index 425c422ee7e4..da230a2f22b5 100644 --- a/platform/runtime/product/src/impl/ProductModes.java +++ b/platform/runtime/product/src/impl/ProductModes.java @@ -3,6 +3,7 @@ package com.intellij.platform.runtime.product.impl; import com.intellij.platform.runtime.product.ProductMode; import com.intellij.platform.runtime.repository.RuntimeModuleId; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; /** @@ -10,7 +11,8 @@ import org.jetbrains.annotations.NotNull; * Currently, this is determined by the presence of specific modules in dependencies. We can change this and require to specify modes * a module is compatible with explicitly in the future. */ -final class ProductModes { +@ApiStatus.Internal +public final class ProductModes { private ProductModes() { }