Wizard: add shared code for iOS/Android template

fix android
This commit is contained in:
Ilya Kirillov
2020-07-23 10:32:01 +03:00
committed by Kirill Shmakov
parent 66c756ad0f
commit 5ffcaf6508
9 changed files with 152 additions and 44 deletions
@@ -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
}
}
@@ -1,19 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
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"
tools:context=".MainActivity">
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">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:id="@+id/text_view"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -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
}
}
@@ -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<Unit> {
val formatter = service<FileFormattingService>()
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<FileSystemWizardService>().createFile(template.rootPath / template.descriptor.relativePath, text)
}
}
@@ -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)
@@ -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")
@@ -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 <T : Any> forModuleEachDependency(
from: Module,
action: suspend ComputeContext<NoState>.(from: Module, to: Module, dependencyType: ModuleDependencyType) -> TaskResult<T>
): TaskResult<List<T>> {
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<List<BuildFileIR>> = 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
@@ -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> =
UNIT_SUCCESS
open fun Writer.runArbitraryTaskBeforeIRsCreated(
from: Module,
to: Module,
): TaskResult<Unit> =
UNIT_SUCCESS
open fun Reader.createToIRs(from: Module, to: Module, data: ModulesToIrConversionData): TaskResult<List<BuildSystemIR>> =
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<Unit> = addExpectFilesForMppModuleForAndroidAndIos(to)
}
object IOSToMppSinglePlatformToMPP : ModuleDependencyType(
from = IOSSinglePlatformModuleConfigurator::class,
@@ -83,17 +94,35 @@ sealed class ModuleDependencyType(
to: Module,
toModulePath: Path,
data: ModulesToIrConversionData
): TaskResult<Unit> = 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<Unit> = 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<Unit> = addExpectFilesForMppModuleForAndroidAndIos(to)
private fun Writer.addDummyFileIfNeeded(
to: Module,
toModulePath: Path,
): TaskResult<Unit> {
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<Unit> {
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) }
}
@@ -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<String, Any?> = emptyMap()
) {
constructor(descriptor: FileTemplateDescriptor, rootPath: Path, vararg data: Pair<String, Any?>) :
constructor(descriptor: FileDescriptor, rootPath: Path, vararg data: Pair<String, Any?>) :
this(descriptor, rootPath, data.toMap())
}