[kotlin] Replaced Kotlin Gradle new project wizard, allowing creation of new modules

^KTIJ-26934 fixed

Co-authored-by: Victoria Petrakovich <Victoria.Petrakovich@jetbrains.com>


Merge-request: IJ-MR-116118
Merged-by: Frederik Haselmeier <Frederik.Haselmeier@jetbrains.com>

GitOrigin-RevId: c7df3d61ceb7057e01e0c93c3cd43dbe2b62351a
This commit is contained in:
Frederik Haselmeier
2023-10-12 21:18:42 +00:00
committed by intellij-monorepo-bot
co-authored by Victoria Petrakovich
parent 47b89847e6
commit 616482c6a1
63 changed files with 1176 additions and 94 deletions
@@ -73,6 +73,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import static com.intellij.openapi.externalSystem.service.execution.ProgressExecutionMode.MODAL_SYNC;
@@ -107,13 +108,16 @@ public abstract class AbstractGradleModuleBuilder extends AbstractExternalModule
private Path rootProjectPath;
private boolean myUseKotlinDSL;
private boolean isCreatingNewProject;
private boolean createEmptyContentRoots = true;
private GradleVersion gradleVersion;
private DistributionType gradleDistributionType;
private @Nullable String gradleHome;
private VirtualFile buildScriptFile;
private VirtualFile settingsScriptFile;
private GradleBuildScriptBuilder<?> buildScriptBuilder;
private final List<Consumer<GradleBuildScriptBuilder<?>>> buildScriptConfigurators = new ArrayList<>();
private final List<BiConsumer<VirtualFile, VirtualFile>> preImportConfigurators = new ArrayList<>();
public AbstractGradleModuleBuilder() {
super(GradleConstants.SYSTEM_ID, GradleDefaultProjectSettings.createProjectSettings(""));
@@ -160,7 +164,7 @@ public abstract class AbstractGradleModuleBuilder extends AbstractExternalModule
}
buildScriptFile = setupGradleBuildFile(modelContentRootDir);
setupGradleSettingsFile(
settingsScriptFile = setupGradleSettingsFile(
rootProjectPath, modelContentRootDir, project.getName(),
myProjectId == null ? module.getName() : myProjectId.getArtifactId(),
isCreatingNewLinkedProject(),
@@ -217,6 +221,7 @@ public abstract class AbstractGradleModuleBuilder extends AbstractExternalModule
// update external projects data to be able to add child modules before the initial import finish
loadPreviewProject(project);
}
preImportConfigurators.forEach(c -> c.accept(buildScriptFile, settingsScriptFile));
openBuildScriptFile(project, buildScriptFile);
if (isCreatingNewLinkedProject() && gradleDistributionType.isWrapped()) {
generateGradleWrapper(project);
@@ -236,7 +241,9 @@ public abstract class AbstractGradleModuleBuilder extends AbstractExternalModule
private void reloadProject(@NotNull Project project) {
ExternalProjectsManagerImpl.getInstance(project).runWhenInitialized(() -> {
ImportSpecBuilder importSpec = new ImportSpecBuilder(project, GradleConstants.SYSTEM_ID);
importSpec.createDirectoriesForEmptyContentRoots();
if (createEmptyContentRoots) {
importSpec.createDirectoriesForEmptyContentRoots();
}
importSpec.callback(new ConfigureGradleModuleCallback(importSpec));
ExternalSystemUtil.refreshProject(PathKt.getSystemIndependentPath(rootProjectPath), importSpec);
});
@@ -255,6 +262,15 @@ public abstract class AbstractGradleModuleBuilder extends AbstractExternalModule
buildScriptConfigurators.add(configure);
}
/**
* Runs the configure callback just before the Gradle import starts.
* The first parameter to the callback is the buildScriptFile, the second parameter is the settingsScriptFile.
* Can be used to do more advanced modifications of the Gradle files.
*/
public void configurePreImport(@NotNull BiConsumer<@NotNull VirtualFile, @NotNull VirtualFile> configure) {
preImportConfigurators.add(configure);
}
private @Nullable VirtualFile createAndConfigureBuildScriptFile() {
try {
if (buildScriptFile != null && buildScriptBuilder != null) {
@@ -492,6 +508,10 @@ public abstract class AbstractGradleModuleBuilder extends AbstractExternalModule
isCreatingNewProject = creatingNewProject;
}
public void setCreateEmptyContentRoots(boolean createEmptyContentRoots) {
this.createEmptyContentRoots = createEmptyContentRoots;
}
public void setGradleVersion(@NotNull GradleVersion version) {
gradleVersion = version;
}
@@ -461,10 +461,9 @@ abstract class GradleNewProjectWizardStep<ParentStep>(parent: ParentStep) :
protected fun linkGradleProject(
project: Project,
builder: AbstractGradleModuleBuilder = GradleJavaModuleBuilder(),
configureBuildScript: GradleBuildScriptBuilder<*>.() -> Unit
): Module? {
val builder = GradleJavaModuleBuilder()
builder.moduleJdk = sdk
builder.name = parentStep.name
builder.contentEntryPath = parentStep.path + "/" + parentStep.name
@@ -74,10 +74,10 @@ abstract class GradleCreateProjectTestCase : GradleTestCase() {
}
private fun configureWizardStepSettings(step: NewProjectWizardStep, moduleInfo: ModuleInfo, parentData: ProjectData?) {
step.baseData!!.name = moduleInfo.name
step.baseData!!.path = testRoot.toNioPath().getResolvedPath(moduleInfo.relativePath).parent.toCanonicalPath()
step.languageData!!.language = "Java"
step.javaBuildSystemData!!.buildSystem = "Gradle"
step.baseData!!.name = moduleInfo.name
step.baseData!!.path = testRoot.toNioPath().getResolvedPath(moduleInfo.relativePath).parent.toCanonicalPath()
step.javaGradleData!!.gradleDsl = when (moduleInfo.useKotlinDsl) {
true -> GradleNewProjectWizardStep.GradleDsl.KOTLIN
else -> GradleNewProjectWizardStep.GradleDsl.GROOVY
@@ -108,12 +108,15 @@ class BuildScriptDataBuilder(
override fun withJavaLibraryPlugin() = apply { backend.withJavaLibraryPlugin() }
override fun withIdeaPlugin() = apply { backend.withIdeaPlugin() }
override fun withKotlinJvmPlugin() = apply { backend.withKotlinJvmPlugin() }
override fun withKotlinJvmPlugin(version: String?): BuildScriptDataBuilder = apply { backend.withKotlinJvmPlugin(version) }
override fun withKotlinJsPlugin() = apply { backend.withKotlinJsPlugin() }
override fun withKotlinMultiplatformPlugin() = apply { backend.withKotlinMultiplatformPlugin() }
override fun withKotlinJvmToolchain(jvmTarget: Int): BuildScriptDataBuilder = apply { backend.withKotlinJvmToolchain(jvmTarget) }
override fun withGroovyPlugin() = apply { backend.withGroovyPlugin() }
override fun withGroovyPlugin(version: String) = apply { backend.withGroovyPlugin(version) }
override fun withApplicationPlugin(mainClass: String?, mainModule: String?, executableDir: String?, defaultJvmArgs: List<String>?) =
apply { backend.withApplicationPlugin(mainClass, mainModule, executableDir, defaultJvmArgs) }
override fun withKotlinTest(): BuildScriptDataBuilder = apply { backend.withKotlinTest() }
override fun withJUnit() = apply { backend.withJUnit() }
override fun withJUnit4() = apply { backend.withJUnit4() }
override fun withJUnit5() = apply { backend.withJUnit5() }
@@ -119,8 +119,7 @@ abstract class AbstractGradleBuildScriptBuilder<BSB : GradleBuildScriptBuilder<B
override fun withIdeaPlugin() =
withPlugin("idea")
override fun withKotlinJvmPlugin() =
withPlugin("org.jetbrains.kotlin.jvm", kotlinVersion)
override fun withKotlinJvmPlugin() = withKotlinJvmPlugin(kotlinVersion)
override fun withKotlinJsPlugin() =
withPlugin("org.jetbrains.kotlin.js", kotlinVersion)
@@ -128,6 +127,15 @@ abstract class AbstractGradleBuildScriptBuilder<BSB : GradleBuildScriptBuilder<B
override fun withKotlinMultiplatformPlugin() =
withPlugin("org.jetbrains.kotlin.multiplatform", kotlinVersion)
override fun withKotlinJvmToolchain(jvmTarget: Int): BSB = apply {
withPostfix {
call("kotlin") {
// We use a code here to force the generator to use parenthesis in Groovy, to be in-line with the documentation
code("jvmToolchain($jvmTarget)")
}
}
}
override fun withGroovyPlugin() =
withGroovyPlugin(groovyVersion)
@@ -159,6 +167,15 @@ abstract class AbstractGradleBuildScriptBuilder<BSB : GradleBuildScriptBuilder<B
}
}
override fun withKotlinTest() = apply {
withMavenCentral()
// version is inherited from the Kotlin plugin
addTestImplementationDependency("org.jetbrains.kotlin:kotlin-test")
configureTestTask {
call("useJUnitPlatform")
}
}
override fun withJUnit() = apply {
when (isJunit5Supported(gradleVersion)) {
true -> withJUnit5()
@@ -60,8 +60,14 @@ interface GradleBuildScriptBuilder<BSB : GradleBuildScriptBuilder<BSB>> : Gradle
fun withJavaLibraryPlugin(): BSB
fun withIdeaPlugin(): BSB
fun withKotlinJvmPlugin(): BSB
/**
* Adds the Kotlin JVM plugin using the [version], or omitting the version call if [version] is null.
*/
fun withKotlinJvmPlugin(version: String?): BSB
fun withKotlinJsPlugin(): BSB
fun withKotlinMultiplatformPlugin(): BSB
fun withKotlinJvmToolchain(jvmTarget: Int): BSB
fun withGroovyPlugin(): BSB
fun withGroovyPlugin(version: String): BSB
fun withApplicationPlugin(
@@ -71,6 +77,7 @@ interface GradleBuildScriptBuilder<BSB : GradleBuildScriptBuilder<BSB>> : Gradle
defaultJvmArgs: List<String>? = null
): BSB
fun withKotlinTest(): BSB
fun withJUnit(): BSB
fun withJUnit4(): BSB
fun withJUnit5(): BSB
@@ -18,6 +18,11 @@ abstract class GroovyDslGradleBuildScriptBuilder<BSB : GroovyDslGradleBuildScrip
callIfNotEmpty("test", configure)
}
override fun withKotlinJvmPlugin(version: String?): BSB = apply {
withMavenCentral()
withPlugin("org.jetbrains.kotlin.jvm", version)
}
override fun generate() = GroovyScriptBuilder().generate(generateTree())
internal class Impl(gradleVersion: GradleVersion) : GroovyDslGradleBuildScriptBuilder<Impl>(gradleVersion) {
@@ -16,6 +16,17 @@ class KotlinDslGradleBuildScriptBuilder(
override fun generate() = KotlinScriptBuilder().generate(generateTree())
override fun withKotlinJvmPlugin(version: String?): KotlinDslGradleBuildScriptBuilder = apply {
withMavenCentral()
withPlugin {
if (version != null) {
infixCall(call("kotlin", "jvm"), "version", string(version))
} else {
call("kotlin", "jvm")
}
}
}
override fun configureTestTask(configure: ScriptTreeBuilder.() -> Unit) =
withPostfix {
callIfNotEmpty("tasks.test", configure)
@@ -14,4 +14,7 @@ val JsonElement.asSafeJsonArray: JsonArray?
get() = takeIf { it.isJsonArray }?.asJsonArray
val JsonElement.asSafeString: String?
get() = runCatching { asString }.getOrNull()
get() = runCatching { asString }.getOrNull()
val JsonElement.asSafeInt: Int?
get() = runCatching { asInt }.getOrNull()
@@ -196,6 +196,8 @@ interface GradleBuildScriptManipulator<out Psi : PsiFile> {
fun addResolutionStrategy(pluginId: String)
fun addFoojayPlugin(changedFiles: ChangedConfiguratorFiles)
fun addFoojayPlugin(settingsFile: PsiFile)
}
fun GradleBuildScriptManipulator<*>.usesNewMultiplatform(): Boolean {
@@ -135,20 +135,6 @@ abstract class KotlinWithGradleConfigurator : KotlinProjectConfigurator {
result.collector.showNotification()
}
private fun getAllConfigurableKotlinVersions(): List<IdeKotlinVersion> {
return KotlinGradleCompatibilityStore.allKotlinVersions()
}
/**
* Currently, returns true if this module has a jvmTarget >= 8.
* If a future Kotlin version requires a higher jvmTarget, then it will be required for that [kotlinVersion].
*/
private fun Module.kotlinSupportsJvmTarget(kotlinVersion: IdeKotlinVersion): Boolean {
val jvmTarget = getTargetBytecodeVersionFromModule(this, kotlinVersion) ?: return false
val jvmTargetNum = getJvmTargetNumber(jvmTarget) ?: return false
return jvmTargetNum >= 8
}
private fun Project.isGradleSyncPending(): Boolean {
return KotlinProjectConfigurationService.getInstance(this).isGradleSyncPending()
}
@@ -320,10 +306,7 @@ abstract class KotlinWithGradleConfigurator : KotlinProjectConfigurator {
val writeActions = mutableListOf<() -> Unit>()
val rootModule = getRootModule(project)
val definedVersionInPluginSettings = rootModule?.getBuildScriptSettingsPsiFile()?.let {
GradleBuildScriptSupport.getManipulator(it)
.findKotlinPluginManagementVersion()
}
val definedVersionInPluginSettings = rootModule?.let { getPluginManagementVersion(it) }
var addVersionToModuleBuildScript = definedVersionInPluginSettings?.parsedVersion != kotlinVersion
if (rootModule != null) {
@@ -641,6 +624,53 @@ abstract class KotlinWithGradleConfigurator : KotlinProjectConfigurator {
@NonNls
const val CLASSPATH = "classpath \"$GROUP_ID:$GRADLE_PLUGIN_ID:\$kotlin_version\""
private fun getAllConfigurableKotlinVersions(): List<IdeKotlinVersion> {
return KotlinGradleCompatibilityStore.allKotlinVersions()
}
/**
* Currently, returns true if this module has a jvmTarget >= 8.
* If a future Kotlin version requires a higher jvmTarget, then it will be required for that [kotlinVersion].
*/
private fun Module.kotlinSupportsJvmTarget(kotlinVersion: IdeKotlinVersion): Boolean {
val jvmTarget = getTargetBytecodeVersionFromModule(this, kotlinVersion) ?: return false
val jvmTargetNum = getJvmTargetNumber(jvmTarget) ?: return false
return jvmTargetNum >= 8
}
/**
* Returns the best Kotlin version that can be used for a new child of the [parentModule],
* or null if there is a version conflict and no version can be used without issues.
*/
fun findBestKotlinVersion(parentModule: Module, gradleVersion: GradleVersion): IdeKotlinVersion? {
val project = parentModule.project
val hierarchy = project.buildKotlinModuleHierarchy()
val parentModuleNode = hierarchy?.getNodeForModule(parentModule) ?: return null
if (parentModuleNode.hasKotlinVersionConflict()) return null
val forcedKotlinVersion = parentModuleNode.getForcedKotlinVersion()
val allConfigurableKotlinVersions = getAllConfigurableKotlinVersions()
if (forcedKotlinVersion != null) return forcedKotlinVersion
val remainingKotlinVersions = allConfigurableKotlinVersions
.filter { parentModule.kotlinSupportsJvmTarget(it) }
.filter { KotlinGradleCompatibilityStore.kotlinVersionSupportsGradle(it, gradleVersion) }
return remainingKotlinVersions.maxOrNull()
}
/**
* Returns the defined Kotlin version in the pluginManagement block in the settings.gradle file for the [module].
* Returns null if the version is not defined in the settings.gradle file.
* Returns a non-null value, but null version inside the object, if the version was defined but could not be parsed.
*/
fun getPluginManagementVersion(module: Module): DefinedKotlinPluginManagementVersion? {
return module.getBuildScriptSettingsPsiFile()?.let {
GradleBuildScriptSupport.getManipulator(it)
.findKotlinPluginManagementVersion()
}
}
fun getGroovyDependencySnippet(
artifactName: String,
scope: String,
@@ -349,6 +349,11 @@ class GroovyBuildScriptManipulator(
}
changedFiles.storeOriginalFileContent(settingsFile)
addFoojayPlugin(settingsFile)
}
override fun addFoojayPlugin(settingsFile: PsiFile) {
if (settingsFile !is GroovyFile) return
val pluginBlock = settingsFile.getSettingsPluginsBlock()
if (pluginBlock.text.contains(FOOJAY_RESOLVER_NAME)) return
val foojayVersion = Versions.GRADLE_PLUGINS.FOOJAY_VERSION
@@ -247,7 +247,11 @@ class KotlinBuildScriptManipulator(
if (!settingsFile.canBeConfigured()) {
return
}
addFoojayPlugin(settingsFile)
}
override fun addFoojayPlugin(settingsFile: PsiFile) {
if (settingsFile !is KtFile) return
val pluginBlock = settingsFile.getSettingsPluginsBlock() ?: return
if (pluginBlock.findPluginInPluginsGroup("id(\"$FOOJAY_RESOLVER_NAME\")") != null) return
if (pluginBlock.findPluginInPluginsGroup("id(\"$FOOJAY_RESOLVER_CONVENTION_NAME\")") != null) return
@@ -35,44 +35,59 @@ internal val DEFAULT_KOTLIN_GRADLE_COMPATIBILITY_DATA = KotlinGradleCompatibilit
),
compatibility = listOf(
KotlinGradleVersionMapping(
kotlin = "1.9.0-1.9.30",
gradle = "6.8.3-8.3.0"
kotlin = "1.9.20-1.9.30",
gradle = "6.8.3-8.3.0",
maxJvmTarget = 21
),
KotlinGradleVersionMapping(
kotlin = "1.9.0-1.9.20",
gradle = "6.8.3-8.3.0",
maxJvmTarget = 20
),
KotlinGradleVersionMapping(
kotlin = "1.8.20-1.8.30",
gradle = "6.8.3-8.3.0"
gradle = "6.8.3-8.3.0",
maxJvmTarget = 19
),
KotlinGradleVersionMapping(
kotlin = "1.8.0-1.8.20",
gradle = "6.8.3-7.4.0"
gradle = "6.8.3-7.4.0",
maxJvmTarget = 19
),
KotlinGradleVersionMapping(
kotlin = "1.7.20-1.7.30",
gradle = "6.7.1-7.2.0"
gradle = "6.7.1-7.2.0",
maxJvmTarget = 18
),
KotlinGradleVersionMapping(
kotlin = "1.7.0-1.7.20",
gradle = "6.7.1-7.0.2"
gradle = "6.7.1-7.0.2",
maxJvmTarget = 18
),
KotlinGradleVersionMapping(
kotlin = "1.6.20-1.7.0",
gradle = "6.1.1-7.0.2"
gradle = "6.1.1-7.0.2",
maxJvmTarget = 18
),
KotlinGradleVersionMapping(
kotlin = "1.6.0-1.6.20",
gradle = "6.1.1-7.0"
gradle = "6.1.1-7.0",
maxJvmTarget = 17
),
KotlinGradleVersionMapping(
kotlin = "1.5.30-1.6.0",
gradle = "6.1.1-7.0"
gradle = "6.1.1-7.0",
maxJvmTarget = 16
),
KotlinGradleVersionMapping(
kotlin = "1.5.20-1.5.30",
gradle = "6.1.1-7.0"
gradle = "6.1.1-7.0",
maxJvmTarget = 16
),
KotlinGradleVersionMapping(
kotlin = "1.5.0-1.5.20",
gradle = "6.1-7.0"
gradle = "6.1-7.0",
maxJvmTarget = 16
)
)
)
@@ -13,6 +13,7 @@ internal val DEFAULT_KOTLIN_WIZARD_VERSIONS = KotlinWizardVersionState(
kotlinForComposeVersion = "1.7.20",
composeCompilerExtension = "1.4.3",
minGradleFoojayVersion = "7.6",
minKotlinFoojayVersion = "1.5.30",
foojayVersion = "0.5.0",
failsafeVersion = "2.22.2",
surefireVersion = "2.22.2",
@@ -27,44 +27,59 @@
],
"compatibility": [
{
"kotlin": "1.9.0-1.9.30",
"gradle": "6.8.3-8.3.0"
"kotlin": "1.9.20-1.9.30",
"gradle": "6.8.3-8.3.0",
"maxJvmTarget": 21
},
{
"kotlin": "1.9.0-1.9.20",
"gradle": "6.8.3-8.3.0",
"maxJvmTarget": 20
},
{
"kotlin": "1.8.20-1.8.30",
"gradle": "6.8.3-8.3.0"
"gradle": "6.8.3-8.3.0",
"maxJvmTarget": 19
},
{
"kotlin": "1.8.0-1.8.20",
"gradle": "6.8.3-7.4.0"
"gradle": "6.8.3-7.4.0",
"maxJvmTarget": 19
},
{
"kotlin": "1.7.20-1.7.30",
"gradle": "6.7.1-7.2.0"
"gradle": "6.7.1-7.2.0",
"maxJvmTarget": 18
},
{
"kotlin": "1.7.0-1.7.20",
"gradle": "6.7.1-7.0.2"
"gradle": "6.7.1-7.0.2",
"maxJvmTarget": 18
},
{
"kotlin": "1.6.20-1.7.0",
"gradle": "6.1.1-7.0.2"
"gradle": "6.1.1-7.0.2",
"maxJvmTarget": 18
},
{
"kotlin": "1.6.0-1.6.20",
"gradle": "6.1.1-7.0"
"gradle": "6.1.1-7.0",
"maxJvmTarget": 17
},
{
"kotlin": "1.5.30-1.6.0",
"gradle": "6.1.1-7.0"
"gradle": "6.1.1-7.0",
"maxJvmTarget": 16
},
{
"kotlin": "1.5.20-1.5.30",
"gradle": "6.1.1-7.0"
"gradle": "6.1.1-7.0",
"maxJvmTarget": 16
},
{
"kotlin": "1.5.0-1.5.20",
"gradle": "6.1-7.0"
"gradle": "6.1-7.0",
"maxJvmTarget": 16
}
]
}
@@ -8,6 +8,7 @@
"gradleAndroidVersion": "8.1.0",
"minKotlinFoojayVersion": "1.5.30",
"minGradleFoojayVersion": "7.6",
"foojayVersion": "0.5.0",
@@ -0,0 +1,12 @@
#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")
package ${PACKAGE_NAME}
#end
#parse("File Header.java")
fun main(args: Array<String>) {
println("Hello World!")
// Try adding program arguments via Run/Debug configuration.
// Learn more about running applications: https://www.jetbrains.com/help/idea/running-applications.html.
println("Program arguments: ${args.joinToString()}")
}
@@ -79,6 +79,8 @@ module.name=Module name
module.configurator.simple.js.browser=JS for Browser
module.configurator.simple.js.node=JS for Node.JS
module.configurator.command=Configure Kotlin
module.configurator.android=Android
module.configurator.android.setting.android.plugin=Android plugin
module.configurator.android.setting.android.plugin.tooltip=Android Gradle plugin that will be used for the current module
@@ -52,6 +52,7 @@ object Versions {
val ANDROID = loadVersion("7.3.1") { gradleAndroidVersion }
val MIN_GRADLE_FOOJAY_VERSION = loadVersion("7.6") { minGradleFoojayVersion }
val MIN_KOTLIN_FOOJAY_VERSION = loadVersion("1.5.30") { minKotlinFoojayVersion }
val FOOJAY_VERSION = loadVersion("0.5.0") { foojayVersion }
}
@@ -15,15 +15,18 @@ class KotlinGradleVersionMapping() : BaseState() {
constructor(
kotlin: String,
gradle: String,
maxJvmTarget: Int,
comment: String? = null
) : this() {
this.kotlin = kotlin
this.gradle = gradle
this.maxJvmTarget = maxJvmTarget
this.comment = comment
}
var kotlin by string()
var gradle by string()
var maxJvmTarget: Int by property(11)
var comment by string()
}
@@ -51,8 +54,9 @@ internal object KotlinGradleCompatibilityParser : IdeVersionedDataParser<KotlinG
val obj = entry.asSafeJsonObject ?: return@mapNotNull null
val kotlin = obj["kotlin"]?.asSafeString ?: return@mapNotNull null
val gradle = obj["gradle"]?.asSafeString ?: return@mapNotNull null
val jvmTarget = obj["maxJvmTarget"]?.asSafeInt ?: return@mapNotNull null
val comment = obj["comment"]?.asSafeString
KotlinGradleVersionMapping(kotlin, gradle, comment)
KotlinGradleVersionMapping(kotlin, gradle, jvmTarget, comment)
}
return KotlinGradleCompatibilityState(kotlinVersions, compatibility)
@@ -67,9 +71,13 @@ class KotlinGradleCompatibilityStore : IdeVersionedDataStorage<KotlinGradleCompa
@Volatile
private var supportedKotlinVersions: List<IdeKotlinVersion> = emptyList()
@Volatile
private var compatibility: List<Pair<Ranges<IdeKotlinVersion>, Ranges<GradleVersion>>> = emptyList()
private class CompatibilityEntry(
val gradleCompatibility: Ranges<GradleVersion>,
val maxJvmTarget: Int
)
@Volatile
private var compatibility: List<Pair<Ranges<IdeKotlinVersion>, CompatibilityEntry>> = emptyList()
private fun applyState(state: KotlinGradleCompatibilityState) {
compatibility = getCompatibilityRanges(state)
supportedKotlinVersions = state.kotlinVersions.map(IdeKotlinVersion::get)
@@ -81,13 +89,13 @@ class KotlinGradleCompatibilityStore : IdeVersionedDataStorage<KotlinGradleCompa
override fun newState(): KotlinGradleCompatibilityState = KotlinGradleCompatibilityState()
private fun getCompatibilityRanges(state: KotlinGradleCompatibilityState): List<Pair<Ranges<IdeKotlinVersion>, Ranges<GradleVersion>>> {
private fun getCompatibilityRanges(state: KotlinGradleCompatibilityState): List<Pair<Ranges<IdeKotlinVersion>, CompatibilityEntry>> {
return state.compatibility.map { entry ->
val gradle = entry.gradle ?: ""
val kotlin = entry.kotlin ?: ""
val gradleRange = IdeVersionedDataParser.parseRange(gradle.split(','), GradleVersion::version)
val kotlinRange = IdeVersionedDataParser.parseRange(kotlin.split(','), IdeKotlinVersion::get)
kotlinRange to gradleRange
kotlinRange to CompatibilityEntry(gradleRange, entry.maxJvmTarget)
}
}
@@ -105,9 +113,13 @@ class KotlinGradleCompatibilityStore : IdeVersionedDataStorage<KotlinGradleCompa
return getInstance().state?.kotlinVersions?.map(IdeKotlinVersion::get) ?: emptyList()
}
fun getMaxJvmTarget(kotlinVersion: IdeKotlinVersion): Int? {
return getInstance().compatibility.find { kotlinVersion in it.first }?.second?.maxJvmTarget
}
fun kotlinVersionSupportsGradle(kotlinVersion: IdeKotlinVersion, gradleVersion: GradleVersion): Boolean {
return getInstance().compatibility.any { (kotlinVersions, gradleVersions) ->
kotlinVersion in kotlinVersions && gradleVersion in gradleVersions
return getInstance().compatibility.any { (kotlinVersions, entry) ->
kotlinVersion in kotlinVersions && gradleVersion in entry.gradleCompatibility
}
}
}
@@ -119,7 +131,9 @@ private fun KotlinGradleVersionMapping.toDefaultDataString(): String {
append(System.lineSeparator())
append(" ".repeat(12) + "kotlin = \"$kotlin\",")
append(System.lineSeparator())
append(" ".repeat(12) + "gradle = \"$gradle\"")
append(" ".repeat(12) + "gradle = \"$gradle\",")
append(System.lineSeparator())
append(" ".repeat(12) + "maxJvmTarget = $maxJvmTarget")
if (comment != null) {
append(",")
}
@@ -17,6 +17,7 @@ internal object KotlinWizardVersionParser : IdeVersionedDataParser<KotlinWizardV
versionData.kotlinForComposeVersion = obj["kotlinForComposeVersion"]?.asSafeString ?: return null
versionData.composeCompilerExtension = obj["composeCompilerExtension"]?.asSafeString ?: return null
versionData.minGradleFoojayVersion = obj["minGradleFoojayVersion"]?.asSafeString ?: return null
versionData.minKotlinFoojayVersion = obj["minKotlinFoojayVersion"]?.asSafeString ?: return null
versionData.gradleAndroidVersion = obj["gradleAndroidVersion"]?.asSafeString ?: return null
versionData.foojayVersion = obj["foojayVersion"]?.asSafeString ?: return null
versionData.failsafeVersion = obj["failsafeVersion"]?.asSafeString ?: return null
@@ -32,6 +33,7 @@ class KotlinWizardVersionState() : IdeVersionedDataState() {
kotlinForComposeVersion: String,
composeCompilerExtension: String,
minGradleFoojayVersion: String,
minKotlinFoojayVersion: String,
foojayVersion: String,
failsafeVersion: String,
surefireVersion: String,
@@ -52,6 +54,7 @@ class KotlinWizardVersionState() : IdeVersionedDataState() {
var kotlinForComposeVersion by string()
var composeCompilerExtension by string()
var minGradleFoojayVersion by string()
var minKotlinFoojayVersion by string()
var foojayVersion by string()
var failsafeVersion by string()
var surefireVersion by string()
@@ -91,6 +94,7 @@ internal val DEFAULT_KOTLIN_WIZARD_VERSIONS = KotlinWizardVersionState(
kotlinForComposeVersion = "$kotlinForComposeVersion",
composeCompilerExtension = "$composeCompilerExtension",
minGradleFoojayVersion = "$minGradleFoojayVersion",
minKotlinFoojayVersion = "$minKotlinFoojayVersion",
foojayVersion = "$foojayVersion",
failsafeVersion = "$failsafeVersion",
surefireVersion = "$surefireVersion",
@@ -94,6 +94,15 @@ class StructurePlugin(context: Context) : Plugin(context) {
defaultValue = value(false)
}
// True when creating a new project, or when creating a submodule without parent of the same build system
// (e.g. a new Gradle module inside a JPS project)
val isCreatingNewProjectHierarchy by booleanSetting(
"<IS_CREATING_NEW_PROJECT_HIERARCHY>",
GenerationPhase.FIRST_STEP,
) {
defaultValue = value(false)
}
val createProjectDir by pipelineTask(GenerationPhase.PROJECT_GENERATION) {
withAction {
service<FileSystemWizardService>().createDirectory(StructurePlugin.projectPath.settingValue)
@@ -109,7 +118,8 @@ class StructurePlugin(context: Context) : Plugin(context) {
artifactId,
version,
renderPomIR,
useCompactProjectStructure
useCompactProjectStructure,
isCreatingNewProjectHierarchy
)
override val pipelineTasks: List<PipelineTask> =
listOf(createProjectDir)
@@ -35,7 +35,6 @@ abstract class GradlePlugin(context: Context) : BuildSystemPlugin(context) {
override val pluginPath = "buildSystem.gradle"
val gradleProperties by listProperty(
"kotlin.code.style" to "official"
)
@@ -172,7 +171,6 @@ abstract class GradlePlugin(context: Context) : BuildSystemPlugin(context) {
}
}
val createSettingsFileTask by pipelineTask(GenerationPhase.PROJECT_GENERATION) {
runAfter(KotlinPlugin.createModules)
runAfter(KotlinPlugin.createPluginRepositories)
@@ -19,5 +19,10 @@
<orderEntry type="module" module-name="intellij.java.ui" />
<orderEntry type="module" module-name="intellij.platform.lang.impl" />
<orderEntry type="module" module-name="kotlin.base.plugin" />
<orderEntry type="module" module-name="intellij.java" />
<orderEntry type="module" module-name="kotlin.gradle.code-insight-common" />
<orderEntry type="module" module-name="kotlin.project-configuration" />
<orderEntry type="module" module-name="kotlin.gradle.gradle-java" />
<orderEntry type="module" module-name="kotlin.base.project-structure" />
</component>
</module>
@@ -3,6 +3,7 @@
<dependencies>
<plugin id="org.jetbrains.plugins.gradle"/>
<module name="kotlin.project-wizard.idea"/>
<module name="kotlin.gradle.code-insight-common"/>
</dependencies>
<extensions defaultExtensionNs="com.intellij">
@@ -5,25 +5,44 @@ import com.intellij.ide.projectWizard.NewProjectWizardCollector.Base.logAddSampl
import com.intellij.ide.projectWizard.NewProjectWizardConstants.BuildSystem.GRADLE
import com.intellij.ide.projectWizard.generators.AssetsNewProjectWizardStep
import com.intellij.ide.starters.local.StandardAssetsProvider
import com.intellij.ide.wizard.NewProjectWizardChainStep.Companion.nextStep
import com.intellij.ide.wizard.NewProjectWizardStep
import com.intellij.ide.wizard.NewProjectWizardStep.Companion.ADD_SAMPLE_CODE_PROPERTY_NAME
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
import com.intellij.openapi.observable.util.bindBooleanStorage
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.modules
import com.intellij.openapi.projectRoots.JavaSdk
import com.intellij.openapi.ui.ValidationInfo
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.findDocument
import com.intellij.openapi.vfs.findPsiFile
import com.intellij.psi.PsiDocumentManager
import com.intellij.ui.UIBundle
import com.intellij.ui.dsl.builder.Panel
import com.intellij.ui.dsl.builder.bindSelected
import com.intellij.ui.dsl.builder.whenStateChangedFromUi
import com.intellij.ui.layout.ValidationInfoBuilder
import com.intellij.util.io.createDirectories
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.idea.base.projectStructure.ModuleSourceRootGroup
import org.jetbrains.kotlin.idea.base.projectStructure.ModuleSourceRootMap
import org.jetbrains.kotlin.idea.compiler.configuration.IdeKotlinVersion
import org.jetbrains.kotlin.idea.configuration.getGradleKotlinVersion
import org.jetbrains.kotlin.idea.gradleCodeInsightCommon.GradleBuildScriptSupport
import org.jetbrains.kotlin.idea.gradleCodeInsightCommon.KotlinWithGradleConfigurator
import org.jetbrains.kotlin.tools.projectWizard.*
import org.jetbrains.kotlin.tools.projectWizard.compatibility.KotlinGradleCompatibilityStore
import org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.BuildSystemType.GradleGroovyDsl
import org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.BuildSystemType.GradleKotlinDsl
import org.jetbrains.kotlin.tools.projectWizard.compatibility.KotlinWizardVersionStore
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ProjectKind
import org.jetbrains.kotlin.tools.projectWizard.wizard.service.IdeaKotlinVersionProviderService
import org.jetbrains.plugins.gradle.service.project.wizard.AbstractGradleModuleBuilder
import org.jetbrains.plugins.gradle.service.project.wizard.GradleNewProjectWizardStep
import java.nio.file.Path
private class GradleKotlinModuleBuilder : AbstractGradleModuleBuilder()
internal class GradleKotlinNewProjectWizard : BuildSystemKotlinNewProjectWizard {
@@ -33,19 +52,21 @@ internal class GradleKotlinNewProjectWizard : BuildSystemKotlinNewProjectWizard
override fun createStep(parent: KotlinNewProjectWizard.Step): NewProjectWizardStep =
Step(parent)
//.nextStep(::AssetsStep)
.nextStep(::AssetsStep)
class Step(parent: KotlinNewProjectWizard.Step) :
GradleNewProjectWizardStep<KotlinNewProjectWizard.Step>(parent),
GradleKotlinNewProjectWizardData,
BuildSystemKotlinNewProjectWizardData by parent {
private val addSampleCodeProperty = propertyGraph.property(true)
init {
data.putUserData(GradleKotlinNewProjectWizardData.KEY, this)
}
override var addSampleCodeProperty = propertyGraph.property(true)
.bindBooleanStorage(ADD_SAMPLE_CODE_PROPERTY_NAME)
private val addSampleCode by addSampleCodeProperty
internal val isLocalGradle
get() = distributionType == DistributionTypeItem.LOCAL
override var addSampleCode by addSampleCodeProperty
private fun setupSampleCodeUI(builder: Panel) {
builder.row {
@@ -99,41 +120,156 @@ internal class GradleKotlinNewProjectWizard : BuildSystemKotlinNewProjectWizard
setupArtifactIdUI(builder)
}
private var selectedJdkJvmTarget: Int? = null
private fun findSelectedJvmTarget() {
// Ordinal here works correctly, starting at Java 1.0 (0)
selectedJdkJvmTarget = sdk?.let { JavaSdk.getInstance().getVersion(it) }?.ordinal
}
private var canUseFoojay = false
private fun checkCanUseFoojay() {
val parsedKotlinVersionToUse = IdeKotlinVersion.parse(kotlinVersionToUse).getOrNull()
val parsedMinKotlinFoojayVersion = IdeKotlinVersion.parse(Versions.GRADLE_PLUGINS.MIN_KOTLIN_FOOJAY_VERSION.text).getOrNull()
if (parsedMinKotlinFoojayVersion != null && parsedKotlinVersionToUse != null &&
parsedKotlinVersionToUse < parsedMinKotlinFoojayVersion
) {
canUseFoojay = false
return
}
val minGradleFoojayVersion = GradleVersion.version(Versions.GRADLE_PLUGINS.MIN_GRADLE_FOOJAY_VERSION.text)
val maxJvmTarget = parsedKotlinVersionToUse?.let {
KotlinGradleCompatibilityStore.getMaxJvmTarget(it)
} ?: 11
selectedJdkJvmTarget?.let {
if (it > maxJvmTarget) {
canUseFoojay = false
return
}
}
val currentGradleVersion = GradleVersion.version(gradleVersion)
canUseFoojay = currentGradleVersion >= minGradleFoojayVersion
}
// The default value of 1.9.0 is actually never used, it is just here to avoid the variable being nullable.
private var kotlinVersionToUse: String = "1.9.0"
private fun findKotlinVersionToUse(project: Project) {
val latestKotlinVersion = KotlinWizardVersionStore.getInstance().state?.kotlinPluginVersion ?: "1.9.0"
kotlinVersionToUse = latestKotlinVersion
if (isCreatingNewRootModule()) {
return
}
val parentModule = project.findParentModule()?.baseModule ?: return
val parsedGradleVersion = GradleVersion.version(gradleVersion) ?: return
kotlinVersionToUse =
KotlinWithGradleConfigurator.findBestKotlinVersion(parentModule, parsedGradleVersion)?.rawVersion ?: latestKotlinVersion
}
private fun initializeProjectValues(project: Project) {
findSelectedJvmTarget()
findKotlinVersionToUse(project)
checkCanUseFoojay()
}
private fun configureSettingsFile(project: Project, settingsFile: VirtualFile) {
if (!canUseFoojay) return
val psiFile = settingsFile.findPsiFile(project) ?: return
val document = settingsFile.findDocument() ?: return
val psiDocumentManager = PsiDocumentManager.getInstance(project)
psiDocumentManager.commitDocument(document)
CommandProcessor.getInstance().executeCommand(project, {
ApplicationManager.getApplication().runWriteAction {
val buildScriptSupport = GradleBuildScriptSupport.getManipulator(psiFile)
buildScriptSupport.addFoojayPlugin(psiFile)
}
}, KotlinNewProjectWizardBundle.message("module.configurator.command"), null)
}
private fun isCreatingNewRootModule(): Boolean {
return context.isCreatingNewProject || parentData == null
}
private fun Project.findParentModule(): ModuleSourceRootGroup? {
if (isCreatingNewRootModule()) return null
val moduleGroups = ModuleSourceRootMap(this)
return moduleGroups.groupByBaseModules(modules.toList())
.mapNotNull {
val externalPath = ExternalSystemApiUtil.getExternalProjectPath(it.baseModule) ?: return@mapNotNull null
it to externalPath
}
.filter { (_, externalPath) -> path.startsWith(externalPath) }
.maxByOrNull { (_, externalPath) ->
path.commonPrefixWith(externalPath).length
}?.first
}
private fun getPluginManagementKotlinVersion(project: Project): IdeKotlinVersion? {
if (isCreatingNewRootModule()) return null
val parentModule = project.findParentModule()?.baseModule ?: return null
return KotlinWithGradleConfigurator.getPluginManagementVersion(parentModule)?.parsedVersion
}
override fun setupProject(project: Project) {
KotlinNewProjectWizard.generateProject(
project = project,
projectPath = parentStep.path + "/" + parentStep.name,
projectName = parentStep.name,
isProject = context.isCreatingNewProject,
sdk = sdk,
buildSystemType = when (gradleDsl) {
GradleDsl.KOTLIN -> GradleKotlinDsl
GradleDsl.GROOVY -> GradleGroovyDsl
},
projectGroupId = groupId,
artifactId = artifactId,
version = version,
addSampleCode = addSampleCode,
gradleVersion = gradleVersion,
gradleHome = gradleHome.takeIf { distributionType == DistributionTypeItem.LOCAL }
)
initializeProjectValues(project)
val moduleBuilder = GradleKotlinModuleBuilder()
moduleBuilder.configurePreImport { _, settingsScriptFile ->
configureSettingsFile(project, settingsScriptFile)
}
moduleBuilder.setCreateEmptyContentRoots(false)
val parentKotlinVersion = project.findParentModule()?.sourceRootModules?.firstNotNullOfOrNull {
it.getGradleKotlinVersion()
}
val pluginManagementVersion = getPluginManagementKotlinVersion(project)
linkGradleProject(project, moduleBuilder) {
withKotlinJvmPlugin(kotlinVersionToUse.takeIf { pluginManagementVersion == null && parentKotlinVersion == null })
withKotlinTest()
selectedJdkJvmTarget?.takeIf { canUseFoojay }?.let {
withKotlinJvmToolchain(it)
}
}
}
}
/* This step is temporarily disabled due to the Kotlin multiplatform wizard not using this step.
* Instead, we will add the GradleW assets in a pipeline phase that is used by both the Kotlin new project
* and Multiplatform wizard.
* This should be reverted once the Kotlin Multiplatform wizard has been removed.
*/
private class AssetsStep(private val parent: Step) : AssetsNewProjectWizardStep(parent) {
private fun createKotlinContentRoots() {
val directories = listOf(
"$outputDirectory/src/main/kotlin",
"$outputDirectory/src/main/resources",
"$outputDirectory/src/test/kotlin",
"$outputDirectory/src/test/resources",
)
directories.forEach {
Path.of(it).createDirectories()
}
}
private fun withKotlinSampleCode(packageName: String) {
val templateName = "KotlinSampleCode"
val sourcePath = "src/main/kotlin/Main.kt"
addTemplateAsset(sourcePath, templateName, buildMap {
put("PACKAGE_NAME", packageName)
})
addFilesToOpen(sourcePath)
}
override fun setupAssets(project: Project) {
if (!parent.isLocalGradle) {
addAssets(StandardAssetsProvider().getGradlewAssets())
}
if (context.isCreatingNewProject) {
addAssets(StandardAssetsProvider().getGradleIgnoreAssets())
}
createKotlinContentRoots()
if (parent.addSampleCode) {
withKotlinSampleCode(parent.groupId)
}
}
}
}
@@ -0,0 +1,23 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.tools.projectWizard.gradle
import com.intellij.ide.wizard.NewProjectWizardStep
import com.intellij.openapi.observable.properties.GraphProperty
import com.intellij.openapi.util.Key
import org.jetbrains.plugins.gradle.service.project.wizard.GradleNewProjectWizardData
interface GradleKotlinNewProjectWizardData : GradleNewProjectWizardData {
val addSampleCodeProperty: GraphProperty<Boolean>
var addSampleCode: Boolean
companion object {
val KEY = Key.create<GradleKotlinNewProjectWizardData>(GradleKotlinNewProjectWizardData::class.java.name)
@JvmStatic
val NewProjectWizardStep.kotlinGradleData: GradleKotlinNewProjectWizardData?
get() = data.getUserData(KEY)
}
}
@@ -5,7 +5,6 @@ import com.intellij.ide.JavaUiBundle
import com.intellij.ide.projectWizard.NewProjectWizardCollector.BuildSystem.logBuildSystemChanged
import com.intellij.ide.projectWizard.NewProjectWizardCollector.BuildSystem.logBuildSystemFinished
import com.intellij.ide.projectWizard.NewProjectWizardConstants.Language.KOTLIN
import com.intellij.ide.util.projectWizard.WizardContext
import com.intellij.ide.wizard.*
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
@@ -64,6 +63,7 @@ class KotlinNewProjectWizard : LanguageNewProjectWizard {
StructurePlugin.name.reference.setValue(projectName)
StructurePlugin.projectPath.reference.setValue(projectPath.asPath())
StructurePlugin.useCompactProjectStructure.reference.setValue(useCompactProjectStructure)
StructurePlugin.isCreatingNewProjectHierarchy.reference.setValue(isProject)
KotlinPlugin.createResourceDirectories.reference.setValue(createResourceDirectories)
// If a local gradle installation was selected, we want to use the local gradle installation's
@@ -34,5 +34,10 @@
<orderEntry type="module" module-name="kotlin.jvm-debugger.core-fe10" />
<orderEntry type="module" module-name="kotlin.uast.uast-kotlin" />
<orderEntry type="module" module-name="kotlin.base.plugin" scope="TEST" />
<orderEntry type="module" module-name="intellij.gradle.java.tests" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.testFramework" scope="TEST" />
<orderEntry type="module" module-name="kotlin.project-wizard.gradle" scope="TEST" />
<orderEntry type="module" module-name="intellij.gradle.java" scope="TEST" />
<orderEntry type="module" module-name="kotlin.base.facet" scope="TEST" />
</component>
</module>
@@ -0,0 +1,361 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.tools.projectWizard.wizard
import com.intellij.ide.wizard.LanguageNewProjectWizardData.Companion.languageData
import com.intellij.ide.wizard.NewProjectWizardStep
import com.intellij.openapi.externalSystem.model.project.ProjectData
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.modules
import com.intellij.openapi.util.io.getResolvedPath
import com.intellij.openapi.util.io.toCanonicalPath
import com.intellij.testFramework.assertEqualsToFile
import com.intellij.testFramework.common.runAll
import com.intellij.testFramework.useProjectAsync
import com.intellij.testFramework.utils.module.assertModules
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.idea.base.test.TestRoot
import org.jetbrains.kotlin.idea.framework.KotlinSdkType
import org.jetbrains.kotlin.idea.test.TestMetadataUtil.getTestRoot
import org.jetbrains.kotlin.tools.projectWizard.BuildSystemKotlinNewProjectWizardData.Companion.kotlinBuildSystemData
import org.jetbrains.kotlin.tools.projectWizard.gradle.GradleKotlinNewProjectWizardData.Companion.kotlinGradleData
import org.jetbrains.kotlin.util.capitalizeDecapitalize.decapitalizeAsciiOnly
import org.jetbrains.plugins.gradle.service.project.wizard.GradleNewProjectWizardStep.GradleDsl
import org.jetbrains.plugins.gradle.setup.GradleCreateProjectTestCase
import org.jetbrains.plugins.gradle.testFramework.util.ProjectInfo
import org.jetbrains.plugins.gradle.util.GradleConstants.SYSTEM_ID
import org.junit.jupiter.api.*
import java.io.File
/**
* A test case for the Kotlin new project/module wizard.
* Test cases can either generate a new project, or a new module in an existing projects.
* Existing projects can be defined using the [projectInfo] DSL.
* The tests work by asserting that certain generated files are created with the correct content,
* the files are located in the testData/newProjectWizard/<testName> folder.
* Only files that are mentioned in these folders are asserted to be generated correctly.
*/
@TestRoot("project-wizard/tests")
class GradleKotlinNewProjectWizardTest : GradleCreateProjectTestCase() {
private lateinit var testInfo: TestInfo
@BeforeEach
fun init(testInfo: TestInfo) {
this.testInfo = testInfo
}
@AfterEach
fun tearDown() {
runAll({ KotlinSdkType.removeKotlinSdkInTests() })
}
private fun getTestFolderName(): String {
return testInfo.displayName.takeWhile { it != '(' }.removePrefix("test").decapitalizeAsciiOnly()
}
private fun getTestDataFolder(): File {
val testRoot = getTestRoot(GradleKotlinNewProjectWizardTest::class.java)
return File(testRoot, "testData/gradleNewProjectWizard/${getTestFolderName()}")
}
private fun String.replaceFirstGroup(regex: Regex, replacement: String): String {
val matchResult = regex.find(this) ?: return this
val groupRange = matchResult.groups[1]?.range ?: return this
return this.replaceRange(groupRange, replacement)
}
private fun Project.findRelativeFile(path: String): File? {
Assertions.assertNotNull(basePath)
return File(this.basePath!!, path).takeIf { it.isFile }
}
private fun Project.findKotlinVersion(useKotlinDsl: Boolean, modulePath: String? = null): String? {
val path = StringBuilder().apply {
if (modulePath != null) {
append(modulePath)
append("/")
}
append("build.gradle")
if (useKotlinDsl) {
append(".kts")
}
}.toString()
val buildFile = findRelativeFile(path)
Assertions.assertNotNull(buildFile, "Could not find build file at $path")
return kotlinVersionRegex.find(buildFile!!.readText())?.groupValues?.get(1)
}
private fun Project.assertKotlinVersion(expectedVersion: String, useKotlinDsl: Boolean, modulePath: String? = null) {
val kotlinVersion = findKotlinVersion(useKotlinDsl, modulePath)
Assertions.assertNotNull(kotlinVersion, "Could not find Kotlin version in build file")
Assertions.assertEquals(expectedVersion, kotlinVersion)
}
private val kotlinVersionRegex = Regex("""kotlin.*version.*["']([\w-.]*)["']""")
private fun substituteKotlinVersion(str: String): String {
return str.replaceFirstGroup(kotlinVersionRegex, "KOTLIN_VERSION")
}
private val jvmToolchainVersionRegex = Regex("""jvmToolchain\((\d+)\)""")
private fun substituteJvmToolchainVersion(str: String): String {
return str.replaceFirstGroup(jvmToolchainVersionRegex, "JDK_VERSION")
}
private val foojayRegex = Regex("""foojay-resolver-convention.*version.*["']([\w-.]*)["']""")
private fun substituteFoojayVersion(str: String): String {
return str.replaceFirstGroup(foojayRegex, "FOOJAY_VERSION")
}
// We replace dynamic values like the Kotlin version that is used with placeholders.
// That way we do not have to update tests every time a new Kotlin version releases.
private fun postprocessOutputFile(relativePath: String, fileContents: String): String {
return if (relativePath.contains("build.gradle")) {
substituteKotlinVersion(substituteJvmToolchainVersion(fileContents))
} else if (relativePath.contains("settings.gradle")) {
substituteFoojayVersion(fileContents)
} else fileContents
}
private fun Project.assertCorrectProjectFiles() {
val testDataFolder = getTestDataFolder()
val basePath = File(basePath!!)
var foundExpectedFiles = 0
testDataFolder.walkTopDown().forEach {
if (!it.isFile) return@forEach
foundExpectedFiles++
val relativePath = it.relativeTo(testDataFolder).toPath().toString()
val pathInProject = File(basePath, relativePath)
Assertions.assertTrue(
pathInProject.exists() && pathInProject.isFile,
"Expected ${it.name} file to exist in output, but it could not be found."
)
val processedFileContent = postprocessOutputFile(relativePath, pathInProject.readText())
assertEqualsToFile("Expected correct file after generation", it, processedFileContent)
}
Assertions.assertTrue(
foundExpectedFiles > 0,
"Asserted that project files are correct, but test folder contained no expected files"
)
}
private fun NewProjectWizardStep.setGradleWizardData(
useKotlinDsl: Boolean = false,
name: String = "project",
path: String = name,
groupId: String = "org.testcase",
version: String = "1.0.0",
addSampleCode: Boolean = false,
parentData: ProjectData? = null
) {
languageData!!.language = "Kotlin"
kotlinBuildSystemData!!.buildSystem = "Gradle"
kotlinBuildSystemData!!.path = testRoot.toNioPath().getResolvedPath(path).parent.toCanonicalPath()
kotlinGradleData!!.parentData = parentData
kotlinBuildSystemData!!.name = name
kotlinBuildSystemData!!.language = "Kotlin"
kotlinGradleData!!.gradleDsl = if (useKotlinDsl) GradleDsl.KOTLIN else GradleDsl.GROOVY
kotlinGradleData!!.groupId = groupId
kotlinGradleData!!.artifactId = name
kotlinGradleData!!.version = version
kotlinGradleData!!.addSampleCode = addSampleCode
}
private fun runNewProjectTestCase(
expectedModules: List<String> = listOf("project", "project.main", "project.test"),
useKotlinDsl: Boolean = false,
name: String = "project",
groupId: String = "org.testcase",
version: String = "1.0.0",
addSampleCode: Boolean = false
) {
runBlocking {
createProjectByWizard {
setGradleWizardData(
useKotlinDsl = useKotlinDsl,
name = name,
groupId = groupId,
version = version,
parentData = null,
path = name,
addSampleCode = addSampleCode
)
}.useProjectAsync { project ->
assertModules(project, expectedModules)
project.assertCorrectProjectFiles()
}
}
}
@Test
fun testSimpleProject() {
runNewProjectTestCase()
}
@Test
fun testSimpleProjectKts() {
runNewProjectTestCase(useKotlinDsl = true)
}
@Test
fun testSampleCode() {
runNewProjectTestCase(addSampleCode = true)
}
@Test
fun testSampleCodeKts() {
runNewProjectTestCase(useKotlinDsl = true, addSampleCode = true)
}
private fun runNewModuleTestCase(
expectedNewModules: List<String>,
projectInfo: ProjectInfo,
parentPath: String = "project",
useKotlinDsl: Boolean = false,
name: String = "module",
groupId: String = "org.testcase",
version: String = "1.0.0",
addSampleCode: Boolean = false,
independentHierarchy: Boolean = false,
additionalAssertions: (Project) -> Unit = {}
) {
runBlocking {
initProject(projectInfo)
openProject("project").useProjectAsync { project ->
val parentData = ExternalSystemApiUtil.findProjectNode(project, SYSTEM_ID, testRoot.path + "/$parentPath")!!
val existingModules = project.modules.map { it.name }
createModuleByWizard(project) {
setGradleWizardData(
useKotlinDsl = useKotlinDsl,
name = name,
groupId = groupId,
version = version,
parentData = parentData.data.takeUnless { independentHierarchy },
path = "$parentPath/$name",
addSampleCode = addSampleCode
)
}
assertModules(project, existingModules + expectedNewModules)
project.assertCorrectProjectFiles()
additionalAssertions(project)
}
}
}
private fun simpleJavaProject(useKotlinDsl: Boolean) = projectInfo("project", useKotlinDsl) {
withJavaBuildFile()
withSettingsFile {
setProjectName("project")
}
}
@Test
fun testNewModuleInJavaProject() {
runNewModuleTestCase(
expectedNewModules = listOf("project.module", "project.module.main", "project.module.test"),
projectInfo = simpleJavaProject(false),
addSampleCode = true
)
}
@Test
fun testNewModuleInJavaProjectKts() {
runNewModuleTestCase(
expectedNewModules = listOf("project.module", "project.module.main", "project.module.test"),
projectInfo = simpleJavaProject(true),
addSampleCode = true,
useKotlinDsl = true
)
}
@Test
fun testNewModuleInJavaProjectMixed() {
runNewModuleTestCase(
expectedNewModules = listOf("project.module", "project.module.main", "project.module.test"),
projectInfo = simpleJavaProject(true)
)
}
private fun simpleKotlinProject(useKotlinDsl: Boolean) = projectInfo("project", useKotlinDsl) {
withBuildFile {
addGroup(groupId)
addVersion(version)
withKotlinJvmPlugin("1.9.0")
}
withSettingsFile {
setProjectName("project")
}
}
@Test
fun testNewModuleInKotlinProject() {
runNewModuleTestCase(
expectedNewModules = listOf("project.module", "project.module.main", "project.module.test"),
projectInfo = simpleKotlinProject(false)
)
}
@Test
fun testNewModuleInKotlinProjectKts() {
runNewModuleTestCase(
expectedNewModules = listOf("project.module", "project.module.main", "project.module.test"),
projectInfo = simpleKotlinProject(true),
useKotlinDsl = true
)
}
@Test
fun testNewModuleInKotlinProjectIndependentHierarchy() {
runNewModuleTestCase(
expectedNewModules = listOf("module", "module.main", "module.test"),
projectInfo = simpleKotlinProject(false),
independentHierarchy = true
)
}
@Test
fun testNewModuleInKotlinProjectIndependentHierarchyKts() {
runNewModuleTestCase(
expectedNewModules = listOf("module", "module.main", "module.test"),
projectInfo = simpleKotlinProject(true),
useKotlinDsl = true,
independentHierarchy = true
)
}
private fun javaProjectWithKotlinSubmodule(useKotlinDsl: Boolean) = projectInfo("project", useKotlinDsl) {
withJavaBuildFile()
moduleInfo("othermodule", "othermodule", useKotlinDsl) {
withBuildFile {
addGroup(groupId)
addVersion(version)
withKotlinJvmPlugin("1.8.0")
}
}
withSettingsFile {
setProjectName("project")
include("othermodule")
}
}
@Test
fun testOtherKotlinModule() {
runNewModuleTestCase(
expectedNewModules = listOf("project.module", "project.module.main", "project.module.test"),
projectInfo = javaProjectWithKotlinSubmodule(false)
) { project ->
project.assertKotlinVersion("1.8.0", false, "module")
}
}
@Test
fun testOtherKotlinModuleKts() {
runNewModuleTestCase(
expectedNewModules = listOf("project.module", "project.module.main", "project.module.test"),
projectInfo = javaProjectWithKotlinSubmodule(true),
useKotlinDsl = true
) { project ->
project.assertKotlinVersion("1.8.0", true, "module")
}
}
}
@@ -0,0 +1,21 @@
plugins {
id 'org.jetbrains.kotlin.jvm' version 'KOTLIN_VERSION'
}
group = 'org.testcase'
version = '1.0.0'
repositories {
mavenCentral()
}
dependencies {
testImplementation 'org.jetbrains.kotlin:kotlin-test'
}
test {
useJUnitPlatform()
}
kotlin {
jvmToolchain(JDK_VERSION)
}
@@ -0,0 +1,9 @@
package org.testcase
fun main(args: Array<String>) {
println("Hello World!")
// Try adding program arguments via Run/Debug configuration.
// Learn more about running applications: https://www.jetbrains.com/help/idea/running-applications.html.
println("Program arguments: ${args.joinToString()}")
}
@@ -0,0 +1,5 @@
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version 'FOOJAY_VERSION'
}
rootProject.name = 'project'
include 'module'
@@ -0,0 +1,21 @@
plugins {
kotlin("jvm") version "KOTLIN_VERSION"
}
group = "org.testcase"
version = "1.0.0"
repositories {
mavenCentral()
}
dependencies {
testImplementation("org.jetbrains.kotlin:kotlin-test")
}
tasks.test {
useJUnitPlatform()
}
kotlin {
jvmToolchain(JDK_VERSION)
}
@@ -0,0 +1,9 @@
package org.testcase
fun main(args: Array<String>) {
println("Hello World!")
// Try adding program arguments via Run/Debug configuration.
// Learn more about running applications: https://www.jetbrains.com/help/idea/running-applications.html.
println("Program arguments: ${args.joinToString()}")
}
@@ -0,0 +1,5 @@
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "FOOJAY_VERSION"
}
rootProject.name = "project"
include("module")
@@ -0,0 +1,21 @@
plugins {
id 'org.jetbrains.kotlin.jvm' version 'KOTLIN_VERSION'
}
group = 'org.testcase'
version = '1.0.0'
repositories {
mavenCentral()
}
dependencies {
testImplementation 'org.jetbrains.kotlin:kotlin-test'
}
test {
useJUnitPlatform()
}
kotlin {
jvmToolchain(JDK_VERSION)
}
@@ -0,0 +1,5 @@
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "FOOJAY_VERSION"
}
rootProject.name = "project"
include("module")
@@ -0,0 +1,21 @@
plugins {
id 'org.jetbrains.kotlin.jvm'
}
group = 'org.testcase'
version = '1.0.0'
repositories {
mavenCentral()
}
dependencies {
testImplementation 'org.jetbrains.kotlin:kotlin-test'
}
test {
useJUnitPlatform()
}
kotlin {
jvmToolchain(JDK_VERSION)
}
@@ -0,0 +1,5 @@
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version 'FOOJAY_VERSION'
}
rootProject.name = 'project'
include 'module'
@@ -0,0 +1,21 @@
plugins {
id 'org.jetbrains.kotlin.jvm' version 'KOTLIN_VERSION'
}
group = 'org.testcase'
version = '1.0.0'
repositories {
mavenCentral()
}
dependencies {
testImplementation 'org.jetbrains.kotlin:kotlin-test'
}
test {
useJUnitPlatform()
}
kotlin {
jvmToolchain(JDK_VERSION)
}
@@ -0,0 +1,4 @@
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version 'FOOJAY_VERSION'
}
rootProject.name = 'module'
@@ -0,0 +1,21 @@
plugins {
kotlin("jvm") version "KOTLIN_VERSION"
}
group = "org.testcase"
version = "1.0.0"
repositories {
mavenCentral()
}
dependencies {
testImplementation("org.jetbrains.kotlin:kotlin-test")
}
tasks.test {
useJUnitPlatform()
}
kotlin {
jvmToolchain(JDK_VERSION)
}
@@ -0,0 +1,4 @@
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "FOOJAY_VERSION"
}
rootProject.name = "module"
@@ -0,0 +1,21 @@
plugins {
kotlin("jvm")
}
group = "org.testcase"
version = "1.0.0"
repositories {
mavenCentral()
}
dependencies {
testImplementation("org.jetbrains.kotlin:kotlin-test")
}
tasks.test {
useJUnitPlatform()
}
kotlin {
jvmToolchain(JDK_VERSION)
}
@@ -0,0 +1,5 @@
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "FOOJAY_VERSION"
}
rootProject.name = "project"
include("module")
@@ -0,0 +1,21 @@
plugins {
id 'org.jetbrains.kotlin.jvm' version 'KOTLIN_VERSION'
}
group = 'org.testcase'
version = '1.0.0'
repositories {
mavenCentral()
}
dependencies {
testImplementation 'org.jetbrains.kotlin:kotlin-test'
}
test {
useJUnitPlatform()
}
kotlin {
jvmToolchain(JDK_VERSION)
}
@@ -0,0 +1,6 @@
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version 'FOOJAY_VERSION'
}
rootProject.name = 'project'
include 'othermodule'
include 'module'
@@ -0,0 +1,21 @@
plugins {
kotlin("jvm") version "KOTLIN_VERSION"
}
group = "org.testcase"
version = "1.0.0"
repositories {
mavenCentral()
}
dependencies {
testImplementation("org.jetbrains.kotlin:kotlin-test")
}
tasks.test {
useJUnitPlatform()
}
kotlin {
jvmToolchain(JDK_VERSION)
}
@@ -0,0 +1,6 @@
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "FOOJAY_VERSION"
}
rootProject.name = "project"
include("othermodule")
include("module")
@@ -0,0 +1,21 @@
plugins {
id 'org.jetbrains.kotlin.jvm' version 'KOTLIN_VERSION'
}
group = 'org.testcase'
version = '1.0.0'
repositories {
mavenCentral()
}
dependencies {
testImplementation 'org.jetbrains.kotlin:kotlin-test'
}
test {
useJUnitPlatform()
}
kotlin {
jvmToolchain(JDK_VERSION)
}
@@ -0,0 +1,4 @@
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version 'FOOJAY_VERSION'
}
rootProject.name = 'project'
@@ -0,0 +1,9 @@
package org.testcase
fun main(args: Array<String>) {
println("Hello World!")
// Try adding program arguments via Run/Debug configuration.
// Learn more about running applications: https://www.jetbrains.com/help/idea/running-applications.html.
println("Program arguments: ${args.joinToString()}")
}
@@ -0,0 +1,21 @@
plugins {
kotlin("jvm") version "KOTLIN_VERSION"
}
group = "org.testcase"
version = "1.0.0"
repositories {
mavenCentral()
}
dependencies {
testImplementation("org.jetbrains.kotlin:kotlin-test")
}
tasks.test {
useJUnitPlatform()
}
kotlin {
jvmToolchain(JDK_VERSION)
}
@@ -0,0 +1,4 @@
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "FOOJAY_VERSION"
}
rootProject.name = "project"
@@ -0,0 +1,9 @@
package org.testcase
fun main(args: Array<String>) {
println("Hello World!")
// Try adding program arguments via Run/Debug configuration.
// Learn more about running applications: https://www.jetbrains.com/help/idea/running-applications.html.
println("Program arguments: ${args.joinToString()}")
}
@@ -0,0 +1,21 @@
plugins {
id 'org.jetbrains.kotlin.jvm' version 'KOTLIN_VERSION'
}
group = 'org.testcase'
version = '1.0.0'
repositories {
mavenCentral()
}
dependencies {
testImplementation 'org.jetbrains.kotlin:kotlin-test'
}
test {
useJUnitPlatform()
}
kotlin {
jvmToolchain(JDK_VERSION)
}
@@ -0,0 +1,4 @@
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version 'FOOJAY_VERSION'
}
rootProject.name = 'project'
@@ -0,0 +1,21 @@
plugins {
kotlin("jvm") version "KOTLIN_VERSION"
}
group = "org.testcase"
version = "1.0.0"
repositories {
mavenCentral()
}
dependencies {
testImplementation("org.jetbrains.kotlin:kotlin-test")
}
tasks.test {
useJUnitPlatform()
}
kotlin {
jvmToolchain(JDK_VERSION)
}
@@ -0,0 +1,4 @@
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "FOOJAY_VERSION"
}
rootProject.name = "project"