platform/build-scripts/product-dsl/docs/migration-guide.md
This guide covers migrating products to the programmatic content system.
To migrate a product to programmatic content:
Implement getProductContentDescriptor() in your ProductProperties class
alias()deprecatedInclude()moduleSet()module() or embeddedModule()Extract extensions to separate XML files (e.g., *-customization.xml)
<extensions> blocks from plugin.xml to dedicated filesdeprecatedInclude()Add pluginXmlPath to build/dev-build.json for your product
Run the generator to create the complete plugin.xml:
UltimateModuleSets.main() # or CommunityModuleSets.main()
Verify generated file matches expected structure
Commit all changes to VCS (Kotlin code, generated XML, extracted extensions)
Test compilation to ensure product builds correctly
productImplementationModules (deprecated):
getProductContentDescriptor() (modern):
module()/embeddedModule()includeDependencies = true1. Identify content vs implementation modules
Content modules (have .xml descriptors):
# Check if module has a descriptor
find_files_by_glob("**/moduleName.xml")
# Or look in resources directory
ls community/modulePath/resources/*.xml
Implementation modules (no descriptors):
fleet.util.multiplatform, intellij.platform.webide.impl2. Move content modules to programmatic descriptor
If a module in productImplementationModules has a descriptor, it's incorrectly placed:
// ❌ WRONG - content module in productImplementationModules
productLayout.productImplementationModules = listOf(
"fleet.andel" // Has fleet.andel.xml descriptor!
)
// ✅ CORRECT - content module in programmatic descriptor
override fun getProductContentDescriptor() = productModules {
module("fleet.andel")
// Or better: use module set that already includes it
moduleSet(CommunityModuleSets.essential())
}
3. Keep implementation-only modules in productImplementationModules
Implementation modules without descriptors can stay:
// ✅ OK - implementation modules without descriptors
productLayout.productImplementationModules = listOf(
"intellij.platform.webide.impl", // No descriptor
"fleet.backend", // No descriptor
"fleet.util.network" // No descriptor
)
4. Use includeDependencies for transitive implementation deps
Instead of listing all implementation dependencies explicitly:
// ❌ OLD - manually list all transitive implementation modules
productLayout.productImplementationModules = listOf(
"fleet.andel", // Content module (has descriptor)
"fleet.util.multiplatform", // Implementation dep of fleet.andel
"fleet.backend"
)
// ✅ NEW - let includeDependencies handle transitive implementation modules
override fun getProductContentDescriptor() = productModules {
embeddedModule("fleet.andel", includeDependencies = true)
// This automatically includes fleet.util.multiplatform and other implementation deps
}
productLayout.productImplementationModules = listOf(
"fleet.backend" // Only product-specific implementation module
)
Pitfall 1: Mixing content modules in productImplementationModules
// ❌ BAD - fleet.rpc has descriptor, causes duplicates
productLayout.productImplementationModules = listOf(
"fleet.rpc" // Also comes from essential() → fleetMinimal()
)
override fun getProductContentDescriptor() = productModules {
moduleSet(CommunityModuleSets.essential()) // Includes fleet.rpc
}
// Result: Duplicate content module declaration!
Fix: Remove content modules from productImplementationModules.
Pitfall 2: Not checking transitive dependencies
// ❌ BAD - assuming no duplicates without checking
productLayout.productImplementationModules = listOf(
"fleet.util.multiplatform" // Might come via includeDependencies!
)
override fun getProductContentDescriptor() = productModules {
embeddedModule("fleet.andel", includeDependencies = true)
// fleet.andel → fleet.util.core → fleet.util.multiplatform
}
Fix: Use Plugin Model Analyzer MCP to check transitive deps:
// Check ALL transitive dependencies
get_module_dependencies(
moduleName = "fleet.andel",
includeTransitive = true
)
Pitfall 3: Forgetting includeDependencies only gets implementation modules
// ❓ QUESTION - will this include fleet.util.core?
embeddedModule("fleet.andel", includeDependencies = true)
// ✅ ANSWER - NO!
// fleet.util.core has a descriptor, so it's filtered out
// Only implementation modules (no descriptors) are included
Before committing changes:
Run Generate Product Layouts
# Via JetBrains MCP
execute_run_configuration(name="Generate Product Layouts")
# Or directly
bazel run //platform/buildScripts:plugin-model-tool
Check for duplicate content modules
Verify tests pass
./tests.cmd -Dintellij.build.test.patterns=com.intellij.idea.ultimate.build.smokeTests.IdeaUltimatePackagingTest
Use MCP to analyze transitive dependencies
// Check what includeDependencies will include
get_module_dependencies(
moduleName = "your.module",
includeTransitive = true
)
The PLATFORM_CORE_MODULES constant in PlatformModules.kt is deprecated.
Why it's deprecated:
Migration:
// OLD (deprecated)
for (module in PLATFORM_CORE_MODULES) {
embeddedModule(module)
}
// NEW (recommended)
moduleSet(CommunityModuleSets.essentialMinimal())
// or for minimal products:
moduleSet(CommunityModuleSets.corePlatform())
┌─────────────────────────────────────────────────┐
│ What type of product are you building? │
└───────────────────┬─────────────────────────────┘
│
┌───────────┴───────────────────┬─────────────────────┐
│ │ │
Minimal tool Lightweight IDE Full-featured IDE
(analysis/inspection) (basic editing) (all features)
│ │ │
▼ ▼ ▼
corePlatform essentialMinimal ide.common
+ specific sets or ide.ultimate
| Module Set | Use Case |
|---|---|
corePlatform() | Minimal tools without editing (CodeServer) |
essentialMinimal() | Lightweight IDEs with basic editing |
essential() | Full IDEs with language support |
ide.common | IDEs with VCS, XML, common features |
ide.ultimate | Full Ultimate IDEs |
Current approach (not recommended):
override fun getProductContentDescriptor(): ProductModulesContentSpec = productModules {
alias("com.intellij.codeServer")
// Only XML includes - modules not available at runtime
deprecatedInclude("intellij.platform.analysis", "META-INF/Analysis.xml")
deprecatedInclude("intellij.platform.core", "META-INF/Core.xml")
deprecatedInclude("intellij.platform.projectModel", "META-INF/ProjectModel.xml")
// ... 11 more deprecatedInclude calls
// Only 5 modules total
module("intellij.grid")
module("intellij.libraries.jettison")
}
Recommended approach (for analysis tools without editing):
override fun getProductContentDescriptor(): ProductModulesContentSpec = productModules {
alias("com.intellij.codeServer")
// Use corePlatform for analysis tools (core platform without language editing)
moduleSet(CommunityModuleSets.corePlatform())
// Keep deprecatedInclude only for modules NOT in corePlatform
deprecatedInclude("intellij.platform.indexing", "META-INF/Indexing.xml")
deprecatedInclude("intellij.platform.codeStyle.impl", "META-INF/CodeStyle.xml")
deprecatedInclude("intellij.platform.refactoring", "META-INF/RefactoringExtensionPoints.xml")
deprecatedInclude("intellij.codeServer.core", "META-INF/codeserver-customization.xml")
// Product-specific modules
module("intellij.grid")
module("intellij.grid.types")
module("intellij.grid.csv.core.impl")
module("intellij.grid.core.impl")
module("intellij.libraries.jettison")
}
Why corePlatform (not essentialMinimal)? CodeServer is an analysis/inspection tool that doesn't provide language editing capabilities:
Benefits of using module sets: