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") } } }