From 9a0afac91bf892b196db8818a01673c68509ac58 Mon Sep 17 00:00:00 2001 From: RedNesto Date: Sun, 13 Aug 2023 20:19:58 +0200 Subject: [PATCH] Move second fabric files setup step to after Gradle import Attempts to resolve #2054 --- .../platform/fabric/creator/asset-steps.kt | 245 ++++-------------- .../platform/fabric/creator/ui-steps.kt | 5 +- .../mcp/fabricloom/FabricLoomDataService.kt | 205 +++++++++++++++ .../j2ee/fabric/fabric_mixins.json.ft | 2 +- 4 files changed, 251 insertions(+), 206 deletions(-) diff --git a/src/main/kotlin/platform/fabric/creator/asset-steps.kt b/src/main/kotlin/platform/fabric/creator/asset-steps.kt index ce16cbee0..33a39a22c 100644 --- a/src/main/kotlin/platform/fabric/creator/asset-steps.kt +++ b/src/main/kotlin/platform/fabric/creator/asset-steps.kt @@ -20,7 +20,6 @@ package com.demonwav.mcdev.platform.fabric.creator -import com.demonwav.mcdev.asset.MCDevBundle import com.demonwav.mcdev.creator.JdkProjectSetupFinalizer import com.demonwav.mcdev.creator.addLicense import com.demonwav.mcdev.creator.addTemplates @@ -43,69 +42,20 @@ import com.demonwav.mcdev.platform.fabric.util.FabricConstants import com.demonwav.mcdev.platform.forge.inspections.sideonly.Side import com.demonwav.mcdev.util.MinecraftTemplates.Companion.FABRIC_MIXINS_JSON_TEMPLATE import com.demonwav.mcdev.util.MinecraftTemplates.Companion.FABRIC_MOD_JSON_TEMPLATE -import com.demonwav.mcdev.util.addImplements -import com.demonwav.mcdev.util.addMethod -import com.demonwav.mcdev.util.invokeLater -import com.demonwav.mcdev.util.runWriteAction -import com.demonwav.mcdev.util.runWriteTaskInSmartMode import com.demonwav.mcdev.util.toJavaClassName import com.demonwav.mcdev.util.toPackageName -import com.intellij.codeInsight.actions.ReformatCodeProcessor -import com.intellij.codeInsight.generation.OverrideImplementUtil import com.intellij.ide.starters.local.GeneratorEmptyDirectory -import com.intellij.ide.util.EditorHelper import com.intellij.ide.wizard.NewProjectWizardStep -import com.intellij.json.psi.JsonArray -import com.intellij.json.psi.JsonElementGenerator -import com.intellij.json.psi.JsonFile -import com.intellij.json.psi.JsonObject -import com.intellij.openapi.fileEditor.impl.NonProjectFileWritingAccessProvider +import com.intellij.openapi.application.WriteAction import com.intellij.openapi.project.Project -import com.intellij.openapi.ui.Messages import com.intellij.openapi.util.text.StringUtil +import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.VfsUtil -import com.intellij.psi.JavaDirectoryService -import com.intellij.psi.JavaPsiFacade -import com.intellij.psi.PsiClass -import com.intellij.psi.PsiManager -import com.intellij.psi.search.GlobalSearchScope -import com.intellij.util.IncorrectOperationException -import java.nio.file.Path -import java.util.concurrent.CountDownLatch -class FabricDumbModeFilesStep(parent: NewProjectWizardStep) : AbstractLongRunningAssetsStep(parent) { - override val description = "Adding Fabric project files (phase 1)" - - override fun setupAssets(project: Project) { - val buildSystemProps = findStep>() - val modId = data.getUserData(AbstractModIdStep.KEY) ?: return - val useMixins = data.getUserData(UseMixinsStep.KEY) ?: false - val javaVersion = findStep().preferredJdk.ordinal +const val MAGIC_DEFERRED_INIT_FILE = ".hello_fabric_from_mcdev" - if (useMixins) { - val packageName = - "${buildSystemProps.groupId.toPackageName()}.${modId.toPackageName()}.mixin" - assets.addTemplateProperties( - "PACKAGE_NAME" to packageName, - "JAVA_VERSION" to javaVersion, - ) - val mixinsJsonFile = "src/main/resources/$modId.mixins.json" - assets.addTemplates(project, mixinsJsonFile to FABRIC_MIXINS_JSON_TEMPLATE) - } - - assets.addLicense(project) - - assets.addAssets( - GeneratorEmptyDirectory("src/main/java"), - GeneratorEmptyDirectory("src/main/resources"), - ) - } -} - -class FabricSmartModeFilesStep(parent: NewProjectWizardStep) : AbstractLongRunningAssetsStep(parent) { - override val description = "Adding Fabric project files (phase 2)" - - private lateinit var entryPoints: List +class FabricBaseFilesStep(parent: NewProjectWizardStep) : AbstractLongRunningAssetsStep(parent) { + override val description = "Adding Fabric project files (phase 1)" override fun setupAssets(project: Project) { val buildSystemProps = findStep>() @@ -124,14 +74,6 @@ class FabricSmartModeFilesStep(parent: NewProjectWizardStep) : AbstractLongRunni val apiVersion = data.getUserData(FabricVersionChainStep.API_VERSION_KEY) val useMixins = data.getUserData(UseMixinsStep.KEY) ?: false - val packageName = "${buildSystemProps.groupId.toPackageName()}.${modId.toPackageName()}" - val mainClassName = "$packageName.${modName.toJavaClassName()}" - val clientClassName = "$packageName.client.${modName.toJavaClassName()}Client" - entryPoints = listOf( - EntryPoint("main", EntryPoint.Type.CLASS, mainClassName, FabricConstants.MOD_INITIALIZER), - EntryPoint("client", EntryPoint.Type.CLASS, clientClassName, FabricConstants.CLIENT_MOD_INITIALIZER), - ) // TODO: un-hardcode? - assets.addTemplateProperties( "ARTIFACT_ID" to buildSystemProps.artifactId, "MOD_ID" to modId, @@ -149,153 +91,54 @@ class FabricSmartModeFilesStep(parent: NewProjectWizardStep) : AbstractLongRunni } if (useMixins) { - assets.addTemplateProperties("MIXINS" to "true") - } - - assets.addTemplates(project, "src/main/resources/fabric.mod.json" to FABRIC_MOD_JSON_TEMPLATE) - } - - private fun fixupFabricModJson(project: Project) { - val authors = data.getUserData(AuthorsStep.KEY) ?: emptyList() - val website = data.getUserData(WebsiteStep.KEY) - val repo = data.getUserData(RepositoryStep.KEY) - - val fabricModJsonFile = - VfsUtil.findFile(Path.of(context.projectFileDirectory, "src", "main", "resources", "fabric.mod.json"), true) - ?: return - val jsonFile = PsiManager.getInstance(project).findFile(fabricModJsonFile) as? JsonFile ?: return - val json = jsonFile.topLevelValue as? JsonObject ?: return - val generator = JsonElementGenerator(project) - - NonProjectFileWritingAccessProvider.allowWriting(listOf(fabricModJsonFile)) - jsonFile.runWriteAction { - (json.findProperty("authors")?.value as? JsonArray)?.let { authorsArray -> - for (i in authors.indices) { - if (i != 0) { - authorsArray.addBefore(generator.createComma(), authorsArray.lastChild) - } - authorsArray.addBefore(generator.createStringLiteral(authors[i]), authorsArray.lastChild) - } - } - - (json.findProperty("contact")?.value as? JsonObject)?.let { contactObject -> - val properties = mutableListOf>() - if (!website.isNullOrBlank()) { - properties += "website" to website - } - if (!repo.isNullOrBlank()) { - properties += "repo" to repo - } - for (i in properties.indices) { - if (i != 0) { - contactObject.addBefore(generator.createComma(), contactObject.lastChild) - } - val key = StringUtil.escapeStringCharacters(properties[i].first) - val value = "\"" + StringUtil.escapeStringCharacters(properties[i].second) + "\"" - contactObject.addBefore(generator.createProperty(key, value), contactObject.lastChild) - } - } - - (json.findProperty("entrypoints")?.value as? JsonObject)?.let { entryPointsObject -> - val entryPointsByCategory = entryPoints - .groupBy { it.category } - .asSequence() - .sortedBy { it.key } - .toList() - for (i in entryPointsByCategory.indices) { - val entryPointCategory = entryPointsByCategory[i] - if (i != 0) { - entryPointsObject.addBefore(generator.createComma(), entryPointsObject.lastChild) - } - val values = generator.createValue("[]") - for (j in entryPointCategory.value.indices) { - if (j != 0) { - values.addBefore(generator.createComma(), values.lastChild) - } - val entryPointReference = entryPointCategory.value[j].computeReference(project) - val value = generator.createStringLiteral(entryPointReference) - values.addBefore(value, values.lastChild) - } - val key = StringUtil.escapeStringCharacters(entryPointCategory.key) - val prop = generator.createProperty(key, "[]") - prop.value?.replace(values) - entryPointsObject.addBefore(prop, entryPointsObject.lastChild) - } - } - - ReformatCodeProcessor(project, jsonFile, null, false).run() + val packageName = + "${buildSystemProps.groupId.toPackageName()}.${modId.toPackageName()}.mixin" + assets.addTemplateProperties( + "MIXINS" to "true", + "MIXIN_PACKAGE_NAME" to packageName, + ) + val mixinsJsonFile = "src/main/resources/$modId.mixins.json" + assets.addTemplates(project, mixinsJsonFile to FABRIC_MIXINS_JSON_TEMPLATE) } - } - - private fun createEntryPoints(project: Project) { - val root = VfsUtil.findFile(Path.of(context.projectFileDirectory), true) ?: return - val psiManager = PsiManager.getInstance(project) - val generatedClasses = mutableSetOf() - - for (entryPoint in entryPoints) { - // find the class, and create it if it doesn't exist - val clazz = JavaPsiFacade.getInstance(project).findClass( - entryPoint.className, - GlobalSearchScope.projectScope(project), - ) ?: run { - val packageName = entryPoint.className.substringBeforeLast('.', missingDelimiterValue = "") - val className = entryPoint.className.substringAfterLast('.') + assets.addLicense(project) - val dir = VfsUtil.createDirectoryIfMissing(root, "src/main/java/${packageName.replace('.', '/')}") - val psiDir = psiManager.findDirectory(dir) ?: return@run null - try { - JavaDirectoryService.getInstance().createClass(psiDir, className) - } catch (e: IncorrectOperationException) { - invokeLater { - val message = MCDevBundle.message( - "intention.error.cannot.create.class.message", - className, - e.localizedMessage, - ) - Messages.showErrorDialog( - project, - message, - MCDevBundle.message("intention.error.cannot.create.class.title"), - ) - } - return - } - } ?: continue + assets.addAssets( + GeneratorEmptyDirectory("src/main/java"), + GeneratorEmptyDirectory("src/main/resources"), + ) - clazz.containingFile.runWriteAction { - clazz.addImplements(entryPoint.interfaceName) + assets.addTemplates(project, "src/main/resources/fabric.mod.json" to FABRIC_MOD_JSON_TEMPLATE) - val methodsToImplement = OverrideImplementUtil.getMethodsToOverrideImplement(clazz, true) - val methods = OverrideImplementUtil.overrideOrImplementMethodCandidates(clazz, methodsToImplement, true) - for (method in methods) { - clazz.addMethod(method) - } - } + WriteAction.runAndWait { + val dir = VfsUtil.createDirectoryIfMissing( + LocalFileSystem.getInstance(), + "${assets.outputDirectory}/.gradle", + ) + ?: throw IllegalStateException("Unable to create .gradle directory") + val file = dir.findOrCreateChildData(this, MAGIC_DEFERRED_INIT_FILE) - generatedClasses += clazz - } + val authors = data.getUserData(AuthorsStep.KEY) ?: emptyList() + val website = data.getUserData(WebsiteStep.KEY) + val repo = data.getUserData(RepositoryStep.KEY) - for (clazz in generatedClasses) { - ReformatCodeProcessor(project, clazz.containingFile, null, false).run() - EditorHelper.openInEditor(clazz) - } - } + val packageName = "${buildSystemProps.groupId.toPackageName()}.${modId.toPackageName()}" + val mainClassName = "$packageName.${modName.toJavaClassName()}" + val clientClassName = "$packageName.client.${modName.toJavaClassName()}Client" - override fun perform(project: Project) { - super.perform(project) - val latch = CountDownLatch(1) - assets.runWhenCreated(project) { - project.runWriteTaskInSmartMode { - try { - fixupFabricModJson(project) - createEntryPoints(project) - } finally { - latch.countDown() - } - } + val entrypoints = listOf( + "main,${EntryPoint.Type.CLASS.name},$mainClassName,${FabricConstants.MOD_INITIALIZER}", + "client,${EntryPoint.Type.CLASS.name},$clientClassName,${FabricConstants.CLIENT_MOD_INITIALIZER}", + ) + val fileContents = """ + ${authors.joinToString(",")} + $website + $repo + ${entrypoints.joinToString(";")} + """.trimIndent() // TODO: un-hardcode? + + VfsUtil.saveText(file, fileContents) } - latch.await() } } diff --git a/src/main/kotlin/platform/fabric/creator/ui-steps.kt b/src/main/kotlin/platform/fabric/creator/ui-steps.kt index d8f141e8f..45c02421a 100644 --- a/src/main/kotlin/platform/fabric/creator/ui-steps.kt +++ b/src/main/kotlin/platform/fabric/creator/ui-steps.kt @@ -33,7 +33,6 @@ import com.demonwav.mcdev.creator.step.NewProjectWizardChainStep.Companion.nextS import com.demonwav.mcdev.creator.step.RepositoryStep import com.demonwav.mcdev.creator.step.UseMixinsStep import com.demonwav.mcdev.creator.step.VersionChainComboBox -import com.demonwav.mcdev.creator.step.WaitForSmartModeStep import com.demonwav.mcdev.creator.step.WebsiteStep import com.demonwav.mcdev.platform.fabric.util.FabricApiVersions import com.demonwav.mcdev.platform.fabric.util.FabricVersions @@ -80,10 +79,8 @@ class FabricPlatformStep( .nextStep(::LicenseStep) .nextStep(::FabricOptionalSettingsStep) .nextStep(::FabricBuildSystemStep) - .nextStep(::FabricDumbModeFilesStep) + .nextStep(::FabricBaseFilesStep) .nextStep(::FabricPostBuildSystemStep) - .nextStep(::WaitForSmartModeStep) - .nextStep(::FabricSmartModeFilesStep) } class Factory : ModPlatformStep.Factory { diff --git a/src/main/kotlin/platform/mcp/fabricloom/FabricLoomDataService.kt b/src/main/kotlin/platform/mcp/fabricloom/FabricLoomDataService.kt index ce82d09f1..c4983cf25 100644 --- a/src/main/kotlin/platform/mcp/fabricloom/FabricLoomDataService.kt +++ b/src/main/kotlin/platform/mcp/fabricloom/FabricLoomDataService.kt @@ -20,13 +20,46 @@ package com.demonwav.mcdev.platform.mcp.fabricloom +import com.demonwav.mcdev.asset.MCDevBundle +import com.demonwav.mcdev.platform.fabric.EntryPoint +import com.demonwav.mcdev.platform.fabric.creator.MAGIC_DEFERRED_INIT_FILE +import com.demonwav.mcdev.util.addImplements +import com.demonwav.mcdev.util.addMethod +import com.demonwav.mcdev.util.ifNotBlank +import com.demonwav.mcdev.util.invokeLater +import com.demonwav.mcdev.util.runWriteAction +import com.demonwav.mcdev.util.runWriteTaskInSmartMode +import com.intellij.codeInsight.actions.ReformatCodeProcessor +import com.intellij.codeInsight.generation.OverrideImplementUtil +import com.intellij.ide.util.EditorHelper +import com.intellij.json.psi.JsonArray +import com.intellij.json.psi.JsonElementGenerator +import com.intellij.json.psi.JsonFile +import com.intellij.json.psi.JsonObject import com.intellij.openapi.externalSystem.model.DataNode import com.intellij.openapi.externalSystem.model.Key import com.intellij.openapi.externalSystem.model.project.ProjectData import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProvider import com.intellij.openapi.externalSystem.service.project.manage.AbstractProjectDataService +import com.intellij.openapi.fileEditor.impl.NonProjectFileWritingAccessProvider import com.intellij.openapi.module.Module import com.intellij.openapi.project.Project +import com.intellij.openapi.project.guessProjectDir +import com.intellij.openapi.ui.Messages +import com.intellij.openapi.util.text.StringUtil +import com.intellij.openapi.vfs.VfsUtil +import com.intellij.psi.JavaDirectoryService +import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiManager +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.util.IncorrectOperationException +import java.nio.file.Path +import kotlin.io.path.deleteIfExists +import kotlin.io.path.div +import kotlin.io.path.isRegularFile +import kotlin.io.path.readLines +import org.jetbrains.plugins.gradle.util.GradleConstants class FabricLoomDataService : AbstractProjectDataService() { @@ -40,4 +73,176 @@ class FabricLoomDataService : AbstractProjectDataService ) { // Dummy service to enable platform-side DataNodes cache } + + override fun postProcess( + toImport: MutableCollection>, + projectData: ProjectData?, + project: Project, + modelsProvider: IdeModifiableModelsProvider + ) { + if (projectData == null || toImport.isEmpty() || projectData.owner != GradleConstants.SYSTEM_ID) { + return + } + + deferredProjectInit(project) + } + + private fun deferredProjectInit(project: Project) { + val baseDir = project.guessProjectDir()?.toNioPath() + ?: return + val deferredInitFile = baseDir / ".gradle" / MAGIC_DEFERRED_INIT_FILE + if (!deferredInitFile.isRegularFile()) { + return + } + + val lines = deferredInitFile.readLines() + if (lines.size < 4) { + return + } + + val (rawAuthors, website, repo, rawEntrypoints) = lines + + val authors = rawAuthors.ifNotBlank { it.split(',') } ?: emptyList() + + val entrypoints = rawEntrypoints.split(';').map { rawEntrypoint -> + val (category, type, clazzName, interfaceName) = rawEntrypoint.split(',') + EntryPoint(category, EntryPoint.Type.valueOf(type), clazzName, interfaceName) + } + + project.runWriteTaskInSmartMode { + fixupFabricModJson(project, baseDir, authors, website, repo, entrypoints) + createEntryPoints(project, entrypoints) + deferredInitFile.deleteIfExists() + } + } + + private fun fixupFabricModJson( + project: Project, + baseDir: Path, + authors: List, + website: String, + repo: String, + entryPoints: List + ) { + val fabricModJsonFile = + VfsUtil.findFile(baseDir.resolve(Path.of("src", "main", "resources", "fabric.mod.json")), true) + ?: return + val jsonFile = PsiManager.getInstance(project).findFile(fabricModJsonFile) as? JsonFile ?: return + val json = jsonFile.topLevelValue as? JsonObject ?: return + val generator = JsonElementGenerator(project) + + NonProjectFileWritingAccessProvider.allowWriting(listOf(fabricModJsonFile)) + jsonFile.runWriteAction { + (json.findProperty("authors")?.value as? JsonArray)?.let { authorsArray -> + for (i in authors.indices) { + if (i != 0) { + authorsArray.addBefore(generator.createComma(), authorsArray.lastChild) + } + authorsArray.addBefore(generator.createStringLiteral(authors[i]), authorsArray.lastChild) + } + } + + (json.findProperty("contact")?.value as? JsonObject)?.let { contactObject -> + val properties = mutableListOf>() + if (!website.isNullOrBlank()) { + properties += "website" to website + } + if (!repo.isNullOrBlank()) { + properties += "repo" to repo + } + for (i in properties.indices) { + if (i != 0) { + contactObject.addBefore(generator.createComma(), contactObject.lastChild) + } + val key = StringUtil.escapeStringCharacters(properties[i].first) + val value = "\"" + StringUtil.escapeStringCharacters(properties[i].second) + "\"" + contactObject.addBefore(generator.createProperty(key, value), contactObject.lastChild) + } + } + + (json.findProperty("entrypoints")?.value as? JsonObject)?.let { entryPointsObject -> + val entryPointsByCategory = entryPoints + .groupBy { it.category } + .asSequence() + .sortedBy { it.key } + .toList() + for (i in entryPointsByCategory.indices) { + val entryPointCategory = entryPointsByCategory[i] + if (i != 0) { + entryPointsObject.addBefore(generator.createComma(), entryPointsObject.lastChild) + } + val values = generator.createValue("[]") + for (j in entryPointCategory.value.indices) { + if (j != 0) { + values.addBefore(generator.createComma(), values.lastChild) + } + val entryPointReference = entryPointCategory.value[j].computeReference(project) + val value = generator.createStringLiteral(entryPointReference) + values.addBefore(value, values.lastChild) + } + val key = StringUtil.escapeStringCharacters(entryPointCategory.key) + val prop = generator.createProperty(key, "[]") + prop.value?.replace(values) + entryPointsObject.addBefore(prop, entryPointsObject.lastChild) + } + } + + ReformatCodeProcessor(project, jsonFile, null, false).run() + } + } + + private fun createEntryPoints(project: Project, entryPoints: List) { + val root = project.guessProjectDir() ?: return + val psiManager = PsiManager.getInstance(project) + + val generatedClasses = mutableSetOf() + + for (entryPoint in entryPoints) { + // find the class, and create it if it doesn't exist + val clazz = JavaPsiFacade.getInstance(project).findClass( + entryPoint.className, + GlobalSearchScope.projectScope(project), + ) ?: run { + val packageName = entryPoint.className.substringBeforeLast('.', missingDelimiterValue = "") + val className = entryPoint.className.substringAfterLast('.') + + val dir = VfsUtil.createDirectoryIfMissing(root, "src/main/java/${packageName.replace('.', '/')}") + val psiDir = psiManager.findDirectory(dir) ?: return@run null + try { + JavaDirectoryService.getInstance().createClass(psiDir, className) + } catch (e: IncorrectOperationException) { + invokeLater { + val message = MCDevBundle.message( + "intention.error.cannot.create.class.message", + className, + e.localizedMessage, + ) + Messages.showErrorDialog( + project, + message, + MCDevBundle.message("intention.error.cannot.create.class.title"), + ) + } + return + } + } ?: continue + + clazz.containingFile.runWriteAction { + clazz.addImplements(entryPoint.interfaceName) + + val methodsToImplement = OverrideImplementUtil.getMethodsToOverrideImplement(clazz, true) + val methods = OverrideImplementUtil.overrideOrImplementMethodCandidates(clazz, methodsToImplement, true) + for (method in methods) { + clazz.addMethod(method) + } + } + + generatedClasses += clazz + } + + for (clazz in generatedClasses) { + ReformatCodeProcessor(project, clazz.containingFile, null, false).run() + EditorHelper.openInEditor(clazz) + } + } } diff --git a/src/main/resources/fileTemplates/j2ee/fabric/fabric_mixins.json.ft b/src/main/resources/fileTemplates/j2ee/fabric/fabric_mixins.json.ft index d248bba23..d042590aa 100644 --- a/src/main/resources/fileTemplates/j2ee/fabric/fabric_mixins.json.ft +++ b/src/main/resources/fileTemplates/j2ee/fabric/fabric_mixins.json.ft @@ -1,7 +1,7 @@ { "required": true, "minVersion": "0.8", - "package": "${PACKAGE_NAME}", + "package": "${MIXIN_PACKAGE_NAME}", "compatibilityLevel": "JAVA_${JAVA_VERSION}", "mixins": [ ],