def renameConfig = [
// DO NOT CHANGE THESE
templateName : "template",
templateAppId : "template.app.id",
templateMaterialThemeName : "TemplateTheme",
templateApplicationClassName: "TemplateApp",
templateIOSName : "templateIOS",
// MODIFY THE BELOW PROPERTIES
newPackage : "aaa.yourname.app",
newProjectName : "Your Project",
newMaterialThemeName : "MyMaterialTheme",
newApplicationClassName : "MyApp",
newIOSName : "myIOS",
useDataStore : true,
useRenovate : true,
useSqldelight : true,
useMultiplatformSettings : true,
useKtor : true,
useKoin : true,
useApollo : true,
]
task renameIOSProject(type: Copy) {
into(rootDir)
from("iosApp/${renameConfig.templateIOSName}") {
into("iosApp/${renameConfig.newIOSName}")
}
from("iosApp/${renameConfig.templateIOSName}.xcodeproj") {
into("iosApp/${renameConfig.newIOSName}.xcodeproj")
}
from("iosApp/${renameConfig.templateIOSName}Tests") {
into("iosApp/${renameConfig.newIOSName}Tests")
}
from("iosApp/${renameConfig.templateIOSName}UITests") {
into("iosApp/${renameConfig.newIOSName}UITests")
}
filter { line ->
line.replaceAll(
renameConfig.templateIOSName,
renameConfig.newIOSName
)
}
rename { filename ->
filename.replace renameConfig.templateIOSName, renameConfig.newIOSName
}
doLast {
delete("$rootDir/iosApp/${renameConfig.templateIOSName}")
delete("$rootDir/iosApp/${renameConfig.templateIOSName}Tests")
delete("$rootDir/iosApp/${renameConfig.templateIOSName}UITests")
}
}
project('androidApp').tasks.named {
// startsWith is used because this applies to multiple tasks,
// like formatKotlinMain, formatKotlinTest, etc.
it.startsWith("formatKotlin")
}.configureEach {
mustRunAfter(rootProject.tasks.named("renameTemplate"))
}
project('desktopApp').tasks.named {
// startsWith is used because this applies to multiple tasks,
// like formatKotlinMain, formatKotlinTest, etc.
it.startsWith("formatKotlin")
}.configureEach {
mustRunAfter(rootProject.tasks.named("renameTemplate"))
}
project('shared').tasks.named {
// startsWith is used because this applies to multiple tasks,
// like formatKotlinMain, formatKotlinTest, etc.
it.startsWith("formatKotlin")
}.configureEach {
mustRunAfter(rootProject.tasks.named("renameTemplate"))
}
// The following tasks appear to be added to the task graph
// for the rename template flow but we don't actually want them to run when
// we're doing the rename process. I haven't figured out why they're included,
// but this ensures they only run when we're NOT calling renameTemplate.
def tasksToExclude = [
"copyNonXmlValueResources",
"generateActualResourceCollectors",
"generateCommonMainAppDatabaseInterface",
"generateResourceAccessors",
"generateServiceApolloSources",
"prepareComposeResourcesTask",
]
project('shared').tasks.named { taskName ->
tasksToExclude.any { excludeName ->
taskName.startsWith(excludeName)
}
}.configureEach {
onlyIf {
!project.gradle.startParameter.taskNames.contains("renameTemplate")
}
}
task renameTemplate(type: Copy) {
dependsOn(renameIOSProject)
into(rootDir)
def newPackageAsDirectory = renameConfig.newPackage.replaceAll("\\.", "/")
def templateName = renameConfig.templateName
// Android App Package
from("androidApp/src/main/kotlin/$templateName") {
into("androidApp/src/main/kotlin/$newPackageAsDirectory")
}
// Shared Android Package
from("shared/src/androidMain/kotlin/$templateName/shared") {
into("shared/src/androidMain/kotlin/$newPackageAsDirectory/shared")
}
// Shared iOS Package
from("shared/src/iosMain/kotlin/$templateName/shared") {
into("shared/src/iosMain/kotlin/$newPackageAsDirectory/shared")
}
// Shared Desktop Package
from("shared/src/desktopMain/kotlin/$templateName/shared") {
into("shared/src/desktopMain/kotlin/$newPackageAsDirectory/shared")
}
// Shared Common Package
from("shared/src/commonMain/kotlin/$templateName/shared") {
into("shared/src/commonMain/kotlin/$newPackageAsDirectory/shared")
}
// Shared Sqldelight Directory
from("shared/src/commonMain/sqldelight/$templateName/shared") {
into("shared/src/commonMain/sqldelight/$newPackageAsDirectory/shared")
}
// Shared Graphql Directory
from("shared/src/commonMain/graphql/$templateName/shared") {
into("shared/src/commonMain/graphql/$newPackageAsDirectory/shared")
}
// Desktop App Package
from("desktopApp/src/jvmMain/kotlin/$templateName") {
into("desktopApp/src/jvmMain/kotlin/$newPackageAsDirectory")
}
// Replace package statements
filter { line ->
line.replaceAll(
"package ${renameConfig.templateName}",
"package ${renameConfig.newPackage}"
)
}
// Replace import statements
filter { line ->
line.replaceAll(
"import ${renameConfig.templateName}",
"import ${renameConfig.newPackage}"
)
}
// Replace Theme references. We can just replace on name,
// which covers both imports and function calls.
filter { line ->
line.replaceAll(
"${renameConfig.templateMaterialThemeName}",
"${renameConfig.newMaterialThemeName}"
)
}
// Replace application class references
filter { line ->
line.replaceAll(
"${renameConfig.templateApplicationClassName}",
"${renameConfig.newApplicationClassName}",
)
}
rename { fileName ->
if (fileName.contains("${renameConfig.templateApplicationClassName}")) {
fileName.replace(
"${renameConfig.templateApplicationClassName}",
"${renameConfig.newApplicationClassName}",
)
} else {
fileName
}
}
doFirst {
keepOrRemoveDependencies(project, renameConfig)
}
doLast {
replaceTemplateReferences(rootDir, renameConfig)
deleteSetupCode(project)
delete("$rootDir/androidApp/src/main/kotlin/$templateName")
delete("$rootDir/desktopApp/src/jvmMain/kotlin/$templateName")
delete("$rootDir/shared/src/androidMain/kotlin/$templateName")
delete("$rootDir/shared/src/iosMain/kotlin/$templateName")
delete("$rootDir/shared/src/desktopMain/kotlin/$templateName")
delete("$rootDir/shared/src/commonMain/kotlin/$templateName")
delete("$rootDir/shared/src/commonMain/sqldelight/$templateName")
delete("$rootDir/shared/src/commonMain/graphql/$templateName")
exec {
// After all setup changes happen, run a `git add` so
// folks can just immediately commit and push if they wish.
commandLine "git", "add", "$rootDir/."
}
}
finalizedBy(
"androidApp:formatKotlin",
"desktopApp:formatKotlin",
"shared:formatKotlin",
)
}
static def replaceTemplateReferences(rootDir, renameConfig) {
replaceTextInFile(
"$rootDir/androidApp/src/main/AndroidManifest.xml",
"${renameConfig.templateName}.MainActivity",
"${renameConfig.newPackage}.MainActivity",
)
replaceTextInFile(
"$rootDir/androidApp/src/main/AndroidManifest.xml",
".${renameConfig.templateApplicationClassName}",
".${renameConfig.newApplicationClassName}",
)
replaceTextInFile(
"$rootDir/androidApp/build.gradle.kts",
"namespace = \"${renameConfig.templateName}\"",
"namespace = \"${renameConfig.newPackage}\"",
)
replaceTextInFile(
"$rootDir/androidApp/build.gradle.kts",
"applicationId = \"${renameConfig.templateAppId}\"",
"applicationId = \"${renameConfig.newPackage}\"",
)
replaceTextInFile(
"$rootDir/settings.gradle.kts",
"rootProject.name = \"${renameConfig.templateName}\"",
"rootProject.name = \"${renameConfig.newProjectName}\"",
)
replaceTextInFile(
"$rootDir/androidApp/src/main/res/values/strings.xml",
"${renameConfig.templateName}",
"${renameConfig.newProjectName}",
)
replaceTextInFile(
"$rootDir/shared/build.gradle.kts",
"namespace = \"${renameConfig.templateName}.shared\"",
"namespace = \"${renameConfig.newPackage}.shared\"",
)
replaceTextInFile(
"$rootDir/shared/build.gradle.kts",
"packageOfResClass = \"${renameConfig.templateName}.shared\"",
"packageOfResClass = \"${renameConfig.newPackage}.shared\"",
)
replaceTextInFile(
"$rootDir/shared/build.gradle.kts",
"packageName.set\\(\"${renameConfig.templateName}.shared\"\\)",
"packageName.set\\(\"${renameConfig.newPackage}.shared\"\\)"
)
replaceTextInFile(
"$rootDir/.github/workflows/ios_build.yml",
renameConfig.templateIOSName,
renameConfig.newIOSName
)
replaceTextInFile(
"$rootDir/desktopApp/build.gradle.kts",
"mainClass = \"${renameConfig.templateName}.MainKt\"",
"mainClass = \"${renameConfig.newPackage}.MainKt\"",
)
replaceTextInFile(
"$rootDir/desktopApp/build.gradle.kts",
"package = \"${renameConfig.templateName}\"",
"package = \"${renameConfig.newPackage}\"",
)
replaceTextInFile(
"$rootDir/shared/build.gradle.kts",
"=${renameConfig.templateName}.shared.Parcelize",
"=${renameConfig.newPackage}.shared.Parcelize",
)
}
static def keepOrRemoveDependencies(project, renameConfig) {
def rootDir = project.rootDir
def filesWithDependencies = [
"$rootDir/build.gradle.kts",
"$rootDir/androidApp/build.gradle.kts",
"$rootDir/shared/build.gradle.kts",
"$rootDir/gradle/libs.versions.toml",
]
filesWithDependencies.each { fileName ->
if (renameConfig.useSqldelight != true) {
println("Removing sqldelight dependencies")
def pluginSetupRegex = /sqldelight\s*\{\s*databases\s*\{\s*create\("AppDatabase"\)\s*\{\s*packageName\.set\("template\.shared"\)\s*}\s*}\s*}\s*/
replaceTextInFile(
fileName,
pluginSetupRegex,
''
)
removeTextFromFile(fileName, "sqldelight")
project.delete("$rootDir/shared/src/commonMain/kotlin/template/shared/data/local/DatabaseDriverFactory.kt")
project.delete("$rootDir/shared/src/androidMain/kotlin/template/shared/data/local/DatabaseDriverFactory.android.kt")
project.delete("$rootDir/shared/src/iosMain/kotlin/template/shared/data/local/DatabaseDriverFactory.ios.kt")
project.delete("$rootDir/shared/src/commonMain/sqldelight/template/shared/AppDatabase.sq")
}
if (renameConfig.useKtor != true) {
println("Removing ktor dependencies")
removeTextFromFile(fileName, "ktor")
removeTextFromFile(fileName, "kotlinx-serialization")
removeTextFromFile(fileName, "kotlinx.serialization")
project.delete("$rootDir/shared/src/commonMain/kotlin/template/shared/data/remote/BaseKtorClient.kt")
}
if (renameConfig.useKoin != true) {
println("Removing koin dependencies")
removeTextFromFile(fileName, "koin")
}
if (renameConfig.useApollo != true) {
println("Removing apollo dependencies")
def pluginSetupRegex = /apollo\s*\{\s*service\("service"\)\s*\{\s*packageName\.set\("template\.shared"\)\s*\}\s*\}\s*/
replaceTextInFile(
fileName,
pluginSetupRegex,
''
)
removeTextFromFile(fileName, "apollo")
project.delete("$rootDir/shared/src/commonMain/graphql")
}
if (renameConfig.useDataStore != true) {
removeTextFromFile(fileName, "datastore")
project.delete("$rootDir/shared/src/commonMain/kotlin/template/shared/data/preferences/CreateDataStore.kt")
project.delete("$rootDir/shared/src/androidMain/kotlin/template/shared/data/preferences/CreateDataStore.android.kt")
project.delete("$rootDir/shared/src/iosMain/kotlin/template/shared/data/preferences/CreateDataStore.ios.kt")
project.delete("$rootDir/shared/src/desktopMain/kotlin/template/shared/data/preferences/CreateDataStore.desktop.kt")
}
}
}
static def deleteSetupCode(project) {
def rootDir = project.rootDir
def workflowsFolder = "$rootDir/.github/workflows"
def buildscriptsFolder = "$rootDir/buildscripts"
def templateChangeWorkflowFile = "$workflowsFolder/template_change_test.yml"
def macosHooksWorkflowFile = "$workflowsFolder/install_git_hooks_macos.yml"
def windowsHooksWorkflowFile = "$workflowsFolder/install_git_hooks_windows.yml"
def setupGradle = "$buildscriptsFolder/setup.gradle"
removeTextFromFile("$rootDir/build.gradle.kts", "setup.gradle")
project.delete(templateChangeWorkflowFile)
project.delete(macosHooksWorkflowFile)
project.delete(windowsHooksWorkflowFile)
project.delete(setupGradle)
}
/**
* Replaces all instances of [text] in a given [fileName].
*/
static def replaceTextInFile(fileName, originalText, newText) {
def file = new File(fileName)
file.text = file.text.replaceAll(originalText, newText)
}
/**
* Removes all lines from the given fileName that contain some supplied text.
*/
static def removeTextFromFile(fileName, text) {
def file = new File(fileName)
List fileLines = file.readLines()
file.text = ""
fileLines.each { line ->
if (!line.contains(text)) {
file.append(line)
file.append("\n")
}
}
}