platform/build-scripts/product-dsl/docs/programmatic-content.md
This document describes the programmatic content module system that allows products to define their module content in Kotlin code instead of static XML files.
The programmatic content system provides two complementary mechanisms:
layout.withPatch during the buildBoth mechanisms use the same Kotlin DSL and ensure the content stays synchronized.
At build time, processProgrammaticModules() injects content modules into the product's plugin.xml:
ProductProperties.getProductContentModules()<!-- programmatic-content-start --> and <!-- programmatic-content-end --> marker tags<content> blocks for each module setKey property: Build always replaces content between markers, ensuring dev mode works without running the static generator.
Static generation creates XML files for non-dev mode:
buildProductContentXml(): Generates XML content from ProductModulesContentSpecgenerateProductXml(): Replaces content between markers in plugin.xml filesgenerateGatewayProductXml(): Helper for Gateway product (example implementation)Static generation is triggered by running:
Run the Generate Product Layouts run configuration, or directly invoke the appropriate main method:
CommunityModuleSets.main() # for community products
UltimateModuleSets.main() # for ultimate + community + products
Note: The generated XML comments will automatically indicate which command to run based on the product's module content.
In your ProductProperties class:
override fun getProductContentModules(): ProductModulesContentSpec {
return productModules {
// XML includes (optional - can also be defined in plugin.xml manually)
// Specify module name and resource path within that module
deprecatedInclude("intellij.platform.resources", "META-INF/PlatformLangPlugin.xml")
deprecatedInclude("intellij.gateway", "META-INF/Gateway.xml")
// Ultimate-only includes (only included in Ultimate builds)
// When inlining: Skipped in Community builds
// When NOT inlining: Generates xi:include with xi:fallback for graceful handling
deprecatedInclude("intellij.platform.extended.community.impl", "META-INF/community-extensions.xml", ultimateOnly = true)
// Include module sets
moduleSet(CommunityModuleSets.essential())
moduleSet(CommunityModuleSets.vcs())
moduleSet(UltimateModuleSets.ssh())
// Add individual modules
module("intellij.platform.collaborationTools")
embeddedModule("intellij.gateway.ssh")
// Exclude specific modules
exclude("intellij.unwanted.module")
// Override loading mode
override("some.module", ModuleLoadingRule.OPTIONAL)
}
}
Register the product's plugin.xml file path in build/dev-build.json:
"DataSpell": {
"modules": [...],
"class": "com.intellij.dataspell.build.DataSpellProperties",
"pluginXmlPath": "dataspell/ide/resources/META-INF/DataSpellPlugin.xml"
}
This tells the generator which file to regenerate for this product.
Run the generator to create the complete plugin.xml file:
UltimateModuleSets.main() # for ultimate + community + products
CommunityModuleSets.main() # for community products only
Or use the IDE's "Generate Product Layouts" run configuration.
This will generate a complete plugin.xml file like:
<!-- DO NOT EDIT: This file is auto-generated from Kotlin code -->
<!-- To regenerate, run 'Generate Product Layouts' or directly UltimateModuleSets.main() -->
<!-- Source: com.intellij.dataspell.build.DataSpellProperties -->
<idea-plugin xmlns:xi="http://www.w3.org/2001/XInclude">
<module value="com.intellij.modules.dataspell"/>
<module value="com.intellij.modules.python-core-capable"/>
<module value="com.intellij.platform.ide.provisioner"/>
<xi:include href="/META-INF/pycharm-core.xml"/>
<xi:include href="/META-INF/ultimate.xml"/>
<xi:include href="/META-INF/dataspell-customization.xml"/>
<xi:include href="/META-INF/intellij.moduleSets.commercial.xml"/>
<xi:include href="/META-INF/intellij.moduleSets.ide.common.xml"/>
<!-- ... -->
<content namespace="jetbrains">
<!-- <editor-fold desc="additional"> -->
<module name="intellij.python.scientific"/>
<module name="intellij.platform.ide.newUiOnboarding"/>
<!-- ... -->
<!-- </editor-fold> -->
</content>
</idea-plugin>
Choose the appropriate mechanism based on your needs:
Use moduleSet() when:
essentialMinimal, vcs, ssh)corePlatform for core platform modules)Use deprecatedInclude() when:
Use module() or embeddedModule() when:
Key principle: Prefer module sets for core platform functionality, use deprecatedInclude() only for XML-only includes.
See migration-guide.md for guidance on choosing module sets and migrating from PLATFORM_CORE_MODULES.
The system generates complete plugin.xml files from Kotlin code:
getProductContentDescriptor()buildProductContentXml() is also called during build for validationEach module set generates a separate <content> block with a source attribute for traceability:
<content namespace="jetbrains" source="essential">
<module name="..." loading="embedded"/>
<module name="..."/>
</content>
<content namespace="jetbrains" source="vcs">
<module name="..."/>
</content>
<content namespace="jetbrains" source="additional">
<module name="..." loading="optional"/>
</content>
excludedModules are skippedmoduleLoadingOverrides map takes precedence over module's default loading modeSee migration-guide.md for migration guides including:
Gateway (GatewayProperties.kt) uses programmatic content:
override fun getProductContentModules(): ProductModulesContentSpec {
return productModules {
moduleSet(CommunityModuleSets.essential())
moduleSet(CommunityModuleSets.vcs())
moduleSet(UltimateModuleSets.ssh())
embeddedModule("intellij.gateway.ssh")
module("intellij.platform.collaborationTools")
module("intellij.platform.collaborationTools.auth")
// ...
}
}
The content is generated into /remote-dev/gateway/resources/META-INF/plugin.xml.
The ultimateOnly flag on deprecatedInclude() enables conditional inclusion of resources that only exist in Ultimate builds.
When inlining (inlineXmlIncludes = true):
When NOT inlining (inlineXmlIncludes = false):
<xi:include> with <xi:fallback/> wrapper for graceful handling:
<xi:include href="/META-INF/community-extensions.xml">
<xi:fallback/>
</xi:include>
override fun getProductContentModules(): ProductModulesContentSpec {
return productModules {
// Regular include - always processed
deprecatedInclude("intellij.pycharm.community", "META-INF/pycharm-core.xml")
// Ultimate-only - conditionally processed
deprecatedInclude("intellij.platform.extended.community.impl",
"META-INF/community-extensions.xml",
ultimateOnly = true)
}
}
Generated XML (Community build):
<xi:include href="/META-INF/pycharm-core.xml"/>
<xi:include href="/META-INF/community-extensions.xml">
<xi:fallback/>
</xi:include>
Generated XML (Ultimate build):
<xi:include href="/META-INF/pycharm-core.xml"/>
<xi:include href="/META-INF/community-extensions.xml"/>
Use ultimateOnly = true when:
The included XML file exists only in Ultimate repository
Multiple products share the same descriptor
getProductContentDescriptor()Backward compatibility during migration
xi:fallback allows runtime resolutionbuildProductContentXml() (generator.kt): Generates complete XML from ProductModulesContentSpecgenerateProductXml() (generator.kt): Writes generated XML to plugin.xml filegenerateAllProductXmlFiles() (generator.kt): Batch generation for all registered productscollectAndValidateAliases() (generator.kt): Validates module aliases for duplicatesThe module set system provides a JSON analysis endpoint for programmatic querying and tooling integration. This endpoint is used by the Plugin Model Analyzer MCP server and other build tools. The JSON export is generated from the in-memory PluginGraph built from product DSL and module sets (no disk parsing of plugin.xml or descriptors).
Run the module set main function with the --json flag:
# Generate complete analysis for all products and module sets
UltimateModuleSets.main(args = ["--json"])
# Community products only
CommunityModuleSets.main(args = ["--json"])
Use the --json flag with a filter to get specific sections:
# Get only products
--json='{"filter":"products"}'
# Get only module sets
--json='{"filter":"moduleSets"}'
# Include duplicate analysis
--json='{"includeDuplicates":true}'
The JSON output contains comprehensive analysis of the module system:
Maps each module to the module sets and products that include it:
{
"moduleDistribution": {
"intellij.platform.vcs.impl": {
"inModuleSets": ["vcs", "ide.common"],
"inProducts": ["WebStorm", "GoLand", "CLion", "PyCharm", ...]
}
}
}
Use case: Find where a specific module is used across the codebase.
Shows the include relationships between module sets:
{
"moduleSetHierarchy": {
"ide.common": {
"includes": ["essential", "vcs"],
"includedBy": ["ide.ultimate"],
"moduleCount": 145
}
}
}
Use case: Understand module set dependencies and nesting structure.
Comprehensive reverse lookup with source file paths:
{
"moduleUsageIndex": {
"modules": {
"intellij.platform.vcs.impl": {
"moduleSets": [
{
"name": "vcs",
"location": "community",
"sourceFile": "community/platform/build-scripts/product-dsl/src/CommunityModuleSets.kt"
}
],
"products": [
{
"name": "WebStorm",
"sourceFile": "platform/buildScripts/src/productLayout/UltimateModuleSets.kt"
}
]
}
}
}
}
Use case: Trace module ownership and find where to make changes.
Detailed breakdown of each product's composition:
{
"productCompositionAnalysis": {
"CLion": {
"composition": {
"totalAliases": 3,
"totalModuleSets": 12,
"totalDirectModules": 45,
"totalModules": 523
},
"operations": [
{"type": "alias", "value": "com.jetbrains.modules.cidr.lang"},
{"type": "moduleSet", "value": "commercial"},
{"type": "module", "value": "intellij.clion.core"}
]
}
}
}
Use case: Analyze product composition and optimize module dependencies.
When includeDuplicates: true is set, detects duplicate xi:include elements:
{
"duplicateAnalysis": {
"ReSharper Backend": {
"/META-INF/intellij.moduleSets.essential.xml": [
{
"directInclude": true,
"deprecatedIncludeRefs": [
"intellij.platform.resources -> /META-INF/PlatformLangPlugin.xml"
]
}
]
}
}
}
Use case: Identify redundant includes that can be removed.
The Plugin Model Analyzer MCP server (build/mcp-servers/module-analyzer) uses this JSON endpoint to provide:
analyze_module_structure - Complete module system analysisget_module_info - Query specific module detailsfind_module_paths - Trace module to product pathsget_module_set_hierarchy - Query module set relationshipslist_products - List products filtered by criteriavalidate_community_products - Ensure community/ultimate separationThe JSON generation is implemented in:
ModuleSetRunner.kt - builds PluginGraph, handles CLI, dispatches JSON exportModuleSetJsonExport.kt - JSON generation from PluginGraphModuleSetDiscovery.kt - Module set discovery via reflection