diff --git a/libraries/tools/new-project-wizard/resources/org/jetbrains/kotlin/tools/projectWizard/templates/android/MainActivity.kt.vm b/libraries/tools/new-project-wizard/resources/org/jetbrains/kotlin/tools/projectWizard/templates/android/MainActivity.kt.vm index 871b26f5a8b..8b0a86782ef 100644 --- a/libraries/tools/new-project-wizard/resources/org/jetbrains/kotlin/tools/projectWizard/templates/android/MainActivity.kt.vm +++ b/libraries/tools/new-project-wizard/resources/org/jetbrains/kotlin/tools/projectWizard/templates/android/MainActivity.kt.vm @@ -2,10 +2,25 @@ package ${package} import androidx.appcompat.app.AppCompatActivity import android.os.Bundle +#if ($sharedPackage) +import ${sharedPackage}.Greeting +import android.widget.TextView +#end + +#if( $sharedPackage ) +fun greet(): String { + return Greeting().greeting() +} +#end class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) + + #if( $sharedPackage ) + val tv: TextView = findViewById(R.id.text_view) + tv.text = greet() + #end } } diff --git a/libraries/tools/new-project-wizard/resources/org/jetbrains/kotlin/tools/projectWizard/templates/android/activity_main.xml.vm b/libraries/tools/new-project-wizard/resources/org/jetbrains/kotlin/tools/projectWizard/templates/android/activity_main.xml.vm index 716af5af1cc..fb2fe57c929 100644 --- a/libraries/tools/new-project-wizard/resources/org/jetbrains/kotlin/tools/projectWizard/templates/android/activity_main.xml.vm +++ b/libraries/tools/new-project-wizard/resources/org/jetbrains/kotlin/tools/projectWizard/templates/android/activity_main.xml.vm @@ -1,19 +1,21 @@ + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:id="@+id/main_view" + tools:context=".MainActivity"> - + \ No newline at end of file diff --git a/libraries/tools/new-project-wizard/resources/org/jetbrains/kotlin/tools/projectWizard/templates/ios/singleplatformProject/appName/ContentView.swift b/libraries/tools/new-project-wizard/resources/org/jetbrains/kotlin/tools/projectWizard/templates/ios/singleplatformProject/appName/ContentView.swift.vm similarity index 51% rename from libraries/tools/new-project-wizard/resources/org/jetbrains/kotlin/tools/projectWizard/templates/ios/singleplatformProject/appName/ContentView.swift rename to libraries/tools/new-project-wizard/resources/org/jetbrains/kotlin/tools/projectWizard/templates/ios/singleplatformProject/appName/ContentView.swift.vm index dfe33f9226b..28046806916 100644 --- a/libraries/tools/new-project-wizard/resources/org/jetbrains/kotlin/tools/projectWizard/templates/ios/singleplatformProject/appName/ContentView.swift +++ b/libraries/tools/new-project-wizard/resources/org/jetbrains/kotlin/tools/projectWizard/templates/ios/singleplatformProject/appName/ContentView.swift.vm @@ -1,8 +1,21 @@ import SwiftUI +#if (${sharedModuleName}) +import ${sharedModuleName} +#end + +#if (${sharedModuleName}) +func greet() -> String { + return Greeting().greeting() +} +#end struct ContentView: View { var body: some View { +#if (${sharedModuleName}) + Text(greet()) +#else Text("Hello, World!") +#end } } diff --git a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/core/service/TemplateEngineService.kt b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/core/service/TemplateEngineService.kt index 0d21c91509d..857df026744 100644 --- a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/core/service/TemplateEngineService.kt +++ b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/core/service/TemplateEngineService.kt @@ -15,6 +15,7 @@ import org.jetbrains.kotlin.tools.projectWizard.core.Writer import org.jetbrains.kotlin.tools.projectWizard.core.div import org.jetbrains.kotlin.tools.projectWizard.templates.FileTemplate import org.jetbrains.kotlin.tools.projectWizard.templates.FileTemplateDescriptor +import org.jetbrains.kotlin.tools.projectWizard.templates.FileTextDescriptor import org.jetbrains.kotlin.tools.projectWizard.templates.Template import java.io.StringWriter @@ -30,9 +31,11 @@ abstract class TemplateEngineService : WizardService { fun Writer.writeTemplate(template: FileTemplate): TaskResult { val formatter = service() - val text = renderTemplate(template.descriptor, template.data).let { text -> - formatter.formatFile(text, template.descriptor.relativePath.fileName.toString()) + val unformattedText = when (val descriptor = template.descriptor) { + is FileTemplateDescriptor -> renderTemplate(descriptor, template.data) + is FileTextDescriptor -> descriptor.text } + val text = formatter.formatFile(unformattedText, template.descriptor.relativePath.fileName.toString()) return service().createFile(template.rootPath / template.descriptor.relativePath, text) } } diff --git a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/core/utils.kt b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/core/utils.kt index b5185df00c6..f5d2802fe9d 100644 --- a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/core/utils.kt +++ b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/core/utils.kt @@ -33,6 +33,10 @@ operator fun Path.div(@NonNls other: String): Path = operator fun Path.div(other: Path): Path = resolve(other.toString()) +@JvmName("divNullable") +operator fun Path.div(other: Path?): Path = + other?.let { resolve(other.toString()) } ?: this + operator fun @receiver:NonNls String.div(other: Path): Path = Paths.get(this).resolve(other) diff --git a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/moduleConfigurators/IOSModuleConfigurator.kt b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/moduleConfigurators/IOSModuleConfigurator.kt index aa8eaed78d9..aa93f4f0118 100644 --- a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/moduleConfigurators/IOSModuleConfigurator.kt +++ b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/moduleConfigurators/IOSModuleConfigurator.kt @@ -55,7 +55,7 @@ object IOSSinglePlatformModuleConfigurator : SinglePlatformModuleConfigurator, +fileTemplate("$DEFAULT_APP_NAME.xcodeproj" / "project.pbxproj") +fileTemplate(DEFAULT_APP_NAME / "AppDelegate.swift") - +fileTemplate(DEFAULT_APP_NAME / "ContentView.swift") + +fileTemplate(DEFAULT_APP_NAME / "ContentView.swift.vm") +fileTemplate(DEFAULT_APP_NAME / "SceneDelegate.swift") +fileTemplate(DEFAULT_APP_NAME / "Info.plist") diff --git a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/plugins/kotlin/ModulesToIRsConverter.kt b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/plugins/kotlin/ModulesToIRsConverter.kt index a302c870446..bf9b5ad68dc 100644 --- a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/plugins/kotlin/ModulesToIRsConverter.kt +++ b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/plugins/kotlin/ModulesToIRsConverter.kt @@ -75,7 +75,13 @@ class ModulesToIRsConverter( module.configurator == AndroidSinglePlatformModuleConfigurator } - rootModules.mapSequence { module -> + allModules.mapSequenceIgnore { module -> + forModuleEachDependency(module) { from, to, dependencyType -> + with(dependencyType) { + runArbitraryTaskBeforeIRsCreated(from, to) + } + } + } andThen rootModules.mapSequence { module -> createBuildFileForModule( module, initialState.copy(parentModuleHasTransitivelySpecifiedKotlinVersion = parentModuleHasKotlinVersion) @@ -112,17 +118,25 @@ class ModulesToIRsConverter( else -> Success(emptyList()) } + private fun forModuleEachDependency( + from: Module, + action: suspend ComputeContext.(from: Module, to: Module, dependencyType: ModuleDependencyType) -> TaskResult + ): TaskResult> { + return from.dependencies.mapComputeM { dependency -> + val to = data.moduleByPath.getValue(dependency.path) + val (dependencyType) = ModuleDependencyType.getPossibleDependencyType(from, to) + .toResult { InvalidModuleDependencyError(from, to) } + action(from, to, dependencyType) + }.sequence() + } + private fun Writer.createSinglePlatformModule( module: Module, configurator: SinglePlatformModuleConfigurator, state: ModulesToIrsState ): TaskResult> = computeM { val modulePath = calculatePathForModule(module, state.parentPath) - val (moduleDependencies) = module.dependencies.mapCompute { dependency -> - val to = data.moduleByPath.getValue(dependency.path) - val (dependencyType) = ModuleDependencyType.getPossibleDependencyType(module, to) - .toResult { InvalidModuleDependencyError(module, to) } - + val (moduleDependencies) = forModuleEachDependency(module) { from, to, dependencyType -> with(dependencyType) { @Suppress("DEPRECATION") with(unsafeSettingWriter) { @@ -134,9 +148,9 @@ class ModulesToIRsConverter( ).ensure() } irsToAddToModules.getOrPut(to) { mutableListOf() } += createToIRs(module, to, data).get() - createDependencyIrs(module, to, data) + createDependencyIrs(module, to, data).asSuccess() } - }.sequence().map { it.flatten() } + }.map { it.flatten() } mutateProjectStructureByModuleConfigurator(module, modulePath) val buildFileIR = run { if (!configurator.needCreateBuildFile) return@run null diff --git a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/settings/buildsystem/ModuleDependency.kt b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/settings/buildsystem/ModuleDependency.kt index a54b7ec9003..79ef5487698 100644 --- a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/settings/buildsystem/ModuleDependency.kt +++ b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/settings/buildsystem/ModuleDependency.kt @@ -14,8 +14,8 @@ import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.ModuleDependencyI import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.gradle.* import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.* import org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.isGradle +import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleSubType import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModulesToIrConversionData -import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.isIOS import org.jetbrains.kotlin.tools.projectWizard.plugins.printer.GradlePrinter import org.jetbrains.kotlin.tools.projectWizard.plugins.projectPath import org.jetbrains.kotlin.tools.projectWizard.plugins.templates.TemplatesPlugin @@ -58,6 +58,12 @@ sealed class ModuleDependencyType( ): TaskResult = UNIT_SUCCESS + open fun Writer.runArbitraryTaskBeforeIRsCreated( + from: Module, + to: Module, + ): TaskResult = + UNIT_SUCCESS + open fun Reader.createToIRs(from: Module, to: Module, data: ModulesToIrConversionData): TaskResult> = Success(emptyList()) @@ -69,7 +75,12 @@ sealed class ModuleDependencyType( object AndroidSinglePlatformToMPP : ModuleDependencyType( from = AndroidSinglePlatformModuleConfigurator::class, to = MppModuleConfigurator::class - ) + ) { + override fun Writer.runArbitraryTaskBeforeIRsCreated( + from: Module, + to: Module, + ): TaskResult = addExpectFilesForMppModuleForAndroidAndIos(to) + } object IOSToMppSinglePlatformToMPP : ModuleDependencyType( from = IOSSinglePlatformModuleConfigurator::class, @@ -83,17 +94,35 @@ sealed class ModuleDependencyType( to: Module, toModulePath: Path, data: ModulesToIrConversionData - ): TaskResult = inContextOfModuleConfigurator(from) { - IOSSinglePlatformModuleConfigurator.dependentModule.reference.update { - IOSSinglePlatformModuleConfigurator.DependentModuleReference(to).asSuccess() - } - val dummyFilePath = Defaults.SRC_DIR / "${to.iosTargetSafe()!!.name}Main" / to.configurator.kotlinDirectoryName / "dummyFile.kt" - TemplatesPlugin.addFileTemplate.execute( - FileTemplate( - FileTemplateDescriptor("ios/dummyFile.kt", dummyFilePath), - projectPath / toModulePath + ): TaskResult = compute { + inContextOfModuleConfigurator(from) { + IOSSinglePlatformModuleConfigurator.dependentModule.reference.update { + IOSSinglePlatformModuleConfigurator.DependentModuleReference(to).asSuccess() + } + }.ensure() + addDummyFileIfNeeded(to, toModulePath).ensure() + } + + override fun Writer.runArbitraryTaskBeforeIRsCreated( + from: Module, + to: Module, + ): TaskResult = addExpectFilesForMppModuleForAndroidAndIos(to) + + private fun Writer.addDummyFileIfNeeded( + to: Module, + toModulePath: Path, + ): TaskResult { + val needDummyFile = inContextOfModuleConfigurator(to) { MppModuleConfigurator.mppFiles.reference.propertyValue.isEmpty() } + return if (needDummyFile) { + val dummyFilePath = + Defaults.SRC_DIR / "${to.iosTargetSafe()!!.name}Main" / to.configurator.kotlinDirectoryName / "dummyFile.kt" + TemplatesPlugin.addFileTemplate.execute( + FileTemplate( + FileTemplateDescriptor("ios/dummyFile.kt", dummyFilePath), + projectPath / toModulePath + ) ) - ) + } else UNIT_SUCCESS } override fun additionalAcceptanceChecker(from: Module, to: Module): Boolean = @@ -152,4 +181,27 @@ sealed class ModuleDependencyType( fun isDependencyPossible(from: Module, to: Module): Boolean = getPossibleDependencyType(from, to) != null } +} + +private fun Writer.addExpectFilesForMppModuleForAndroidAndIos(mppModule: Module): TaskResult { + val expectFiles = mppFiles { + mppfile("Platform.kt") { + `class`("Platform") { + expectBody = "val platform: String" + actualFor( + ModuleSubType.android, + actualBody = """actual val platform: String = "Android ${'$'}{android.os.Build.VERSION.SDK_INT}"""" + ) + + actualFor( + ModuleSubType.iosArm64, ModuleSubType.iosX64, + actualBody = + """actual val platform: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion""" + ) { + import("platform.UIKit.UIDevice") + } + } + } + } + return inContextOfModuleConfigurator(mppModule) { MppModuleConfigurator.mppFiles.reference.addValues(expectFiles) } } \ No newline at end of file diff --git a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/templates/FileTemplateDescriptor.kt b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/templates/FileTemplateDescriptor.kt index 56534e77e0a..4f1ed9b99b2 100644 --- a/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/templates/FileTemplateDescriptor.kt +++ b/libraries/tools/new-project-wizard/src/org/jetbrains/kotlin/tools/projectWizard/templates/FileTemplateDescriptor.kt @@ -6,16 +6,21 @@ import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.SourcesetTy import java.nio.file.Path import java.nio.file.Paths - // Should be used to create any kind of files in the generated project // Except build files as they will be generated using IR -data class FileTemplateDescriptor(@NonNls val templateId: String, val relativePath: Path) { +sealed class FileDescriptor { + abstract val relativePath: Path +} + +data class FileTemplateDescriptor(@NonNls val templateId: String, override val relativePath: Path) : FileDescriptor() { constructor(templateId: String) : this( templateId, Paths.get(templateId).fileName.toString().removeSuffix(".vm").asPath() ) } +data class FileTextDescriptor(val text: String, override val relativePath: Path) : FileDescriptor() + sealed class FilePath { abstract val sourcesetType: SourcesetType } @@ -23,20 +28,20 @@ sealed class FilePath { data class SrcFilePath(override val sourcesetType: SourcesetType) : FilePath() data class ResourcesFilePath(override val sourcesetType: SourcesetType) : FilePath() -data class FileTemplateDescriptorWithPath(val descriptor: FileTemplateDescriptor, val path: FilePath) +data class FileTemplateDescriptorWithPath(val descriptor: FileDescriptor, val path: FilePath) -infix fun FileTemplateDescriptor.asResourceOf(sourcesetType: SourcesetType) = +infix fun FileDescriptor.asResourceOf(sourcesetType: SourcesetType) = FileTemplateDescriptorWithPath(this, ResourcesFilePath(sourcesetType)) -infix fun FileTemplateDescriptor.asSrcOf(sourcesetType: SourcesetType) = +infix fun FileDescriptor.asSrcOf(sourcesetType: SourcesetType) = FileTemplateDescriptorWithPath(this, SrcFilePath(sourcesetType)) data class FileTemplate( - val descriptor: FileTemplateDescriptor, + val descriptor: FileDescriptor, val rootPath: Path, val data: Map = emptyMap() ) { - constructor(descriptor: FileTemplateDescriptor, rootPath: Path, vararg data: Pair) : + constructor(descriptor: FileDescriptor, rootPath: Path, vararg data: Pair) : this(descriptor, rootPath, data.toMap()) } \ No newline at end of file