From 48ffe61c717f271ca8ec6a52b5be4ef18b45f664 Mon Sep 17 00:00:00 2001 From: Tom Calver Date: Fri, 28 Mar 2025 08:17:54 +0000 Subject: [PATCH 1/2] #134: Create plugin for generating build constants --- build-logic/build.gradle.kts | 18 ++++ build-logic/settings.gradle.kts | 1 + build-logic/src/main/kotlin/SecretsPlugin.kt | 96 ++++++++++++++++++++ build.gradle.kts | 1 + gradle.properties | 3 + gradle/wrapper/gradle-wrapper.properties | 2 +- settings.gradle.kts | 1 + src/main/kotlin/Secrets.kt | 25 ----- 8 files changed, 121 insertions(+), 26 deletions(-) create mode 100644 build-logic/build.gradle.kts create mode 100644 build-logic/settings.gradle.kts create mode 100644 build-logic/src/main/kotlin/SecretsPlugin.kt delete mode 100644 src/main/kotlin/Secrets.kt diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts new file mode 100644 index 0000000..a42a469 --- /dev/null +++ b/build-logic/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + `kotlin-dsl` + `java-gradle-plugin` +} + +repositories { + google() + mavenCentral() +} + +gradlePlugin { + plugins { + create("secretsPlugin") { + id = "secrets-plugin" + implementationClass = "SecretsPlugin" + } + } +} diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts new file mode 100644 index 0000000..7fbbd44 --- /dev/null +++ b/build-logic/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "build-logic" diff --git a/build-logic/src/main/kotlin/SecretsPlugin.kt b/build-logic/src/main/kotlin/SecretsPlugin.kt new file mode 100644 index 0000000..d01a198 --- /dev/null +++ b/build-logic/src/main/kotlin/SecretsPlugin.kt @@ -0,0 +1,96 @@ +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.tasks.SourceSetContainer +import java.io.File +import java.util.Properties + +abstract class SecretsPlugin : Plugin { + override fun apply(project: Project) { + fun String.asRootFile() = + project.rootProject.file(this) + + val useLocalMocks = PROPERTIES_FILE.asRootFile().asProps().getBool(KEY_USE_LOCAL_MOCKS) + if(useLocalMocks) { + println(USE_LOCAL_MOCKS_MSG) + return + } + + val secrets = SECRETS_FILE.asRootFile() + val dir = project.layout.buildDirectory.dir(OUTPUT_DIR) + + val generateSecrets = project.tasks.register(TASK_NAME) { + outputs.dir(dir) + doLast { + if (!secrets.exists()) + error(MISSING_SECRET_PROPS_ERROR_MSG) + + dir.get().asFile.resolve("$OUTPUT_FILE.kt").apply { + parentFile.mkdirs() + writeText(secrets.asProps().asSecretsText()) + } + } + } + + project.afterEvaluate { + project.extensions.configure("sourceSets") { + getByName("main").java.srcDir(dir) + } + tasks.matching { it.name.matches(Regex(".*[k|K](?:otlin|tlint).*")) }.configureEach { + dependsOn(generateSecrets) + } + } + + } + + companion object { + private const val PROPERTIES_FILE = "gradle.properties" + private const val KEY_USE_LOCAL_MOCKS = "USE_LOCAL_MOCKS" + + private const val SECRETS_FILE = "secrets.properties" + private const val OUTPUT_DIR = "generated/secrets" + private const val OUTPUT_FILE = "Secrets" + private const val TASK_NAME = "generateSecrets" + + private val USE_LOCAL_MOCKS_MSG = """ + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + ℹ️ INFO + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + This project's '${PROPERTIES_FILE}' either does not contain the key '$KEY_USE_LOCAL_MOCKS' or its value is + set to true. + + To use a local instance of Supabase-CLI, set this property to true and follow the instructions on the Roky + Wiki page under 'Project Setup' → 'Setup Supabase CLI': + > https://github.com/PPartisan/Roky/wiki/Project-Setup#3-setup-supabase-cli + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + """.trimIndent() + + private val MISSING_SECRET_PROPS_ERROR_MSG = """ + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + ⚠️ WARNING + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + Your project does not contain a '$SECRETS_FILE' file. Roky requires this file to run locally as it contains + information about your local Supabase-CLI setup. + + To build Roky, create a file named '$SECRETS_FILE' in the root directory of this project and add the values + specified in the Roky Wiki page under 'Project Setup' → 'Setup Supabase CLI': + > https://github.com/PPartisan/Roky/wiki/Project-Setup#3-setup-supabase-cli + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + """.trimIndent() + + private fun File.asProps() : Properties = Properties().apply { + inputStream().use { load(it) } + } + + private fun Properties.getBool(key: String, default: Boolean = true) = + getProperty(key)?.toBoolean()?:default + + private fun Properties.asSecretsText() : String = buildString { + appendLine("object $OUTPUT_FILE {") + this@asSecretsText.forEach { (key, value) -> + appendLine(""" const val $key = "$value"""") + } + appendLine("}") + } + + } +} diff --git a/build.gradle.kts b/build.gradle.kts index 4a5006f..268c144 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,6 +6,7 @@ plugins { id("org.jetbrains.kotlinx.kover").version("0.8.3") id("io.gitlab.arturbosch.detekt").version("1.23.3") id("org.jlleitschuh.gradle.ktlint").version("12.1.2") + id("secrets-plugin") } group = "com.github.ppartisan.roky" diff --git a/gradle.properties b/gradle.properties index 7fc6f1f..c3052f8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1,4 @@ kotlin.code.style=official +# Determines whether this project should use hardcoded mocks or Supabase-CLI. +# See https://github.com/PPartisan/Roky/wiki/Project-Setup +USE_LOCAL_MOCKS=true diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6249eb0..6fa0d5c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Mon Oct 07 19:40:39 BST 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle.kts b/settings.gradle.kts index 4cbda4e..dca4d5a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,4 +1,5 @@ plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" } +includeBuild("build-logic") rootProject.name = "Roky" diff --git a/src/main/kotlin/Secrets.kt b/src/main/kotlin/Secrets.kt deleted file mode 100644 index 0b31545..0000000 --- a/src/main/kotlin/Secrets.kt +++ /dev/null @@ -1,25 +0,0 @@ -import java.io.File -import java.io.IOException -import java.util.Properties - -object Secrets { - private const val SECRETS_FILE = "secrets.properties" - private val props: Properties = - Properties().apply { - val file = File(System.getProperty("user.dir"), SECRETS_FILE) - if (!file.exists()) { - throw IOException("${file.name} does not exist.") - } - file.inputStream().use { - load(it) - } - } - - private fun String.property(): String = - props.getProperty(this) ?: throw IOException("Missing mandatory property: $this") - - val clientKey: String - get() = "CLIENT_API_KEY".property() - val serverUrl: String - get() = "SERVER_URL".property() -} From da09efc5de7db305ffff75a12fd135b55a26b101 Mon Sep 17 00:00:00 2001 From: Tom Calver Date: Fri, 28 Mar 2025 09:02:41 +0000 Subject: [PATCH 2/2] #134: Move local mocks flags to secrets.properties file --- build-logic/src/main/kotlin/SecretsPlugin.kt | 48 +++++++++++++------- gradle.properties | 3 -- secrets.properties | 3 ++ 3 files changed, 34 insertions(+), 20 deletions(-) create mode 100644 secrets.properties diff --git a/build-logic/src/main/kotlin/SecretsPlugin.kt b/build-logic/src/main/kotlin/SecretsPlugin.kt index d01a198..f2ba21d 100644 --- a/build-logic/src/main/kotlin/SecretsPlugin.kt +++ b/build-logic/src/main/kotlin/SecretsPlugin.kt @@ -9,12 +9,6 @@ abstract class SecretsPlugin : Plugin { fun String.asRootFile() = project.rootProject.file(this) - val useLocalMocks = PROPERTIES_FILE.asRootFile().asProps().getBool(KEY_USE_LOCAL_MOCKS) - if(useLocalMocks) { - println(USE_LOCAL_MOCKS_MSG) - return - } - val secrets = SECRETS_FILE.asRootFile() val dir = project.layout.buildDirectory.dir(OUTPUT_DIR) @@ -24,9 +18,16 @@ abstract class SecretsPlugin : Plugin { if (!secrets.exists()) error(MISSING_SECRET_PROPS_ERROR_MSG) + val props = secrets.asProps() + if(props.useLocalMocks()) + println(USE_LOCAL_MOCKS_MSG) + dir.get().asFile.resolve("$OUTPUT_FILE.kt").apply { parentFile.mkdirs() - writeText(secrets.asProps().asSecretsText()) + val text = with(props) { + if(useLocalMocks()) asUseLocalMocksText() else asSecretsText() + } + writeText(text) } } } @@ -43,10 +44,8 @@ abstract class SecretsPlugin : Plugin { } companion object { - private const val PROPERTIES_FILE = "gradle.properties" - private const val KEY_USE_LOCAL_MOCKS = "USE_LOCAL_MOCKS" - private const val SECRETS_FILE = "secrets.properties" + private const val KEY_USE_LOCAL_MOCKS = "USE_LOCAL_MOCKS" private const val OUTPUT_DIR = "generated/secrets" private const val OUTPUT_FILE = "Secrets" private const val TASK_NAME = "generateSecrets" @@ -55,10 +54,10 @@ abstract class SecretsPlugin : Plugin { ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ℹ️ INFO ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - This project's '${PROPERTIES_FILE}' either does not contain the key '$KEY_USE_LOCAL_MOCKS' or its value is + This project's '${SECRETS_FILE}' either does not contain the key '$KEY_USE_LOCAL_MOCKS' or its value is set to true. - To use a local instance of Supabase-CLI, set this property to true and follow the instructions on the Roky + To use a local instance of Supabase-CLI, set this property to false and follow the instructions on the Roky Wiki page under 'Project Setup' → 'Setup Supabase CLI': > https://github.com/PPartisan/Roky/wiki/Project-Setup#3-setup-supabase-cli ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ @@ -83,14 +82,29 @@ abstract class SecretsPlugin : Plugin { private fun Properties.getBool(key: String, default: Boolean = true) = getProperty(key)?.toBoolean()?:default + private fun Properties.useLocalMocks() = + getBool(KEY_USE_LOCAL_MOCKS) private fun Properties.asSecretsText() : String = buildString { - appendLine("object $OUTPUT_FILE {") - this@asSecretsText.forEach { (key, value) -> - appendLine(""" const val $key = "$value"""") - } - appendLine("}") + header() + this@asSecretsText.forEach { it.writeTo(this) } + footer() + } + + private fun Properties.asUseLocalMocksText() : String = buildString { + header() + (KEY_USE_LOCAL_MOCKS to getBool(KEY_USE_LOCAL_MOCKS)).writeTo(this) + footer() } + private fun Map.Entry<*, *>.writeTo(builder: StringBuilder) = + (key to value).writeTo(builder) + private fun Pair<*, *>.writeTo(builder: StringBuilder) = + builder.appendLine(""" const val $first = "$second"""") + + private fun StringBuilder.header() = + appendLine("object $OUTPUT_FILE {") + private fun StringBuilder.footer() = + appendLine("}") } } diff --git a/gradle.properties b/gradle.properties index c3052f8..7fc6f1f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1 @@ kotlin.code.style=official -# Determines whether this project should use hardcoded mocks or Supabase-CLI. -# See https://github.com/PPartisan/Roky/wiki/Project-Setup -USE_LOCAL_MOCKS=true diff --git a/secrets.properties b/secrets.properties new file mode 100644 index 0000000..920c17b --- /dev/null +++ b/secrets.properties @@ -0,0 +1,3 @@ +# Determines whether this project should use hardcoded mocks or Supabase-CLI. +# See https://github.com/PPartisan/Roky/wiki/Project-Setup +USE_LOCAL_MOCKS=true