Skip to content

Commit 4a7373c

Browse files
committed
fix(gradle-plugin): postpone build configuration errors at task execution
Build script misconfiguration will now be reported at task execution instead of during project sync. This allows to progressively configure build script.
1 parent 614d795 commit 4a7373c

File tree

10 files changed

+122
-82
lines changed

10 files changed

+122
-82
lines changed

gradle-plugin/src/main/kotlin/io/github/manriif/supabase/functions/Constants.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ internal const val COROUTINES_VERSION_GRADLE_PROPERTY = "supabase.functions.coro
3131
internal const val DENO_KOTLIN_BRIDGE_FUNCTION_NAME = "denoKotlinBridge"
3232
internal const val KOTLIN_MAIN_FUNCTION_NAME = "serve"
3333

34+
internal const val IMPORT_MAPS_DIRECTORY_NAME = "importMaps"
3435
internal const val GITIGNORE_FILE_NAME = ".gitignore"
3536
internal const val IMPORT_MAP_TEMPLATE_FILE_NAME = "import_map_template.json"
3637
internal const val IMPORT_MAP_FILE_NAME = "import_map.json"

gradle-plugin/src/main/kotlin/io/github/manriif/supabase/functions/SupabaseFunctionExtension.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,8 @@ internal fun SupabaseFunctionExtension.setupConvention(project: Project) {
101101
functionName.convention(project.name)
102102
verifyJwt.convention(true)
103103
importMap.convention(true)
104+
105+
packageName.convention(project.provider {
106+
error("packageName is not set, please provide the package name of the kotlin main function.")
107+
})
104108
}

gradle-plugin/src/main/kotlin/io/github/manriif/supabase/functions/kmp/Kmp.kt

Lines changed: 46 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,12 @@ package io.github.manriif.supabase.functions.kmp
2424
import io.github.manriif.supabase.functions.COROUTINES_VERSION
2525
import io.github.manriif.supabase.functions.COROUTINES_VERSION_GRADLE_PROPERTY
2626
import io.github.manriif.supabase.functions.SUPABASE_FUNCTION_PLUGIN_NAME
27+
import io.github.manriif.supabase.functions.task.SupabaseFunctionCopyKotlinTask
2728
import io.github.manriif.supabase.functions.task.TASK_GENERATE_BRIDGE
29+
import io.github.manriif.supabase.functions.util.postponeErrorOnTaskInvocation
2830
import org.gradle.api.Project
2931
import org.gradle.kotlin.dsl.withType
32+
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
3033
import org.jetbrains.kotlin.gradle.dsl.JsModuleKind
3134
import org.jetbrains.kotlin.gradle.dsl.JsSourceMapEmbedMode
3235
import org.jetbrains.kotlin.gradle.dsl.JsSourceMapNamesPolicy
@@ -36,7 +39,6 @@ import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget
3639

3740
internal fun Project.setupKotlinMultiplatform(kmpExtension: KotlinMultiplatformExtension) {
3841
kmpExtension.targets.withType<KotlinJsIrTarget>().configureEach {
39-
ensureMeetRequirements()
4042
configureCompilation()
4143
}
4244

@@ -48,57 +50,66 @@ internal fun Project.setupKotlinMultiplatform(kmpExtension: KotlinMultiplatformE
4850
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${coroutinesVersion}")
4951
}
5052
}
53+
54+
afterEvaluate {
55+
kmpExtension.targets.withType<KotlinJsIrTarget>().forEach { target ->
56+
target.checkUserConfiguration()
57+
}
58+
}
59+
}
60+
61+
private fun KotlinJsIrTarget.configureCompilation() {
62+
@OptIn(ExperimentalKotlinGradlePluginApi::class)
63+
compilerOptions {
64+
// [KT-47968](https://youtrack.jetbrains.com/issue/KT-47968/KJS-IR-Debug-in-external-tool-cant-step-into-library-function-with-available-sources)
65+
// [KT-49757](https://youtrack.jetbrains.com/issue/KT-49757/Kotlin-JS-support-sourceMapEmbedSources-setting-by-IR-backend)
66+
sourceMap.set(true)
67+
sourceMapNamesPolicy.set(JsSourceMapNamesPolicy.SOURCE_MAP_NAMES_POLICY_FQ_NAMES)
68+
sourceMapEmbedSources.set(JsSourceMapEmbedMode.SOURCE_MAP_SOURCE_CONTENT_ALWAYS)
69+
}
70+
71+
compilations.named(KotlinCompilation.MAIN_COMPILATION_NAME) {
72+
compileTaskProvider.configure {
73+
dependsOn(TASK_GENERATE_BRIDGE)
74+
}
75+
}
5176
}
5277

53-
private fun KotlinJsIrTarget.ensureMeetRequirements() {
78+
private fun KotlinJsIrTarget.checkUserConfiguration() {
79+
if (isBrowserConfigured) {
80+
project.logger.warn(
81+
"Browser execution environment is not supported by " +
82+
"`$SUPABASE_FUNCTION_PLUGIN_NAME` plugin."
83+
)
84+
}
85+
5486
val granularity = project.findProperty("kotlin.js.ir.output.granularity")?.toString()
5587

5688
if (!(granularity.isNullOrBlank() || granularity == "per-module")) {
57-
error(
89+
project.postponeErrorOnTaskInvocation<SupabaseFunctionCopyKotlinTask>(
5890
"Only `per-module` JS IR output granularity is supported " +
5991
"by `$SUPABASE_FUNCTION_PLUGIN_NAME` plugin. " +
6092
"Current granularity is `$granularity`."
6193
)
6294
}
6395

64-
if (isBrowserConfigured) {
65-
error(
66-
"Browser execution environment is not supported by " +
67-
"`$SUPABASE_FUNCTION_PLUGIN_NAME` plugin."
96+
val compilation = compilations.findByName(KotlinCompilation.MAIN_COMPILATION_NAME)
97+
98+
// Module kind is not set when using new compiler option DSL, fallback to deprecated one
99+
val options = @Suppress("DEPRECATION") compilation?.compilerOptions?.options
100+
val moduleKind = options?.moduleKind?.orNull
101+
102+
if (moduleKind != JsModuleKind.MODULE_ES) {
103+
project.postponeErrorOnTaskInvocation<SupabaseFunctionCopyKotlinTask>(
104+
"Plugin `supabase-function` only supports ES module kind. " +
105+
"Current module kind is `$moduleKind`."
68106
)
69107
}
70108

71109
if (!isNodejsConfigured) {
72-
error(
110+
project.postponeErrorOnTaskInvocation<SupabaseFunctionCopyKotlinTask>(
73111
"Node.js execution environment is a requirement " +
74112
"for `$SUPABASE_FUNCTION_PLUGIN_NAME` plugin."
75113
)
76114
}
77-
}
78-
79-
private fun KotlinJsIrTarget.configureCompilation() {
80-
compilations.named(KotlinCompilation.MAIN_COMPILATION_NAME) {
81-
// Module kind is not set when using new compiler option DSL, fallback to deprecated one
82-
@Suppress("DEPRECATION")
83-
compilerOptions.configure {
84-
val kind = moduleKind.orNull
85-
86-
if (kind != JsModuleKind.MODULE_ES) {
87-
error(
88-
"Plugin `supabase-function` only supports ES module kind. " +
89-
"Current module kind is $kind."
90-
)
91-
}
92-
93-
// [KT-47968](https://youtrack.jetbrains.com/issue/KT-47968/KJS-IR-Debug-in-external-tool-cant-step-into-library-function-with-available-sources)
94-
// [KT-49757](https://youtrack.jetbrains.com/issue/KT-49757/Kotlin-JS-support-sourceMapEmbedSources-setting-by-IR-backend)
95-
sourceMap.set(true)
96-
sourceMapNamesPolicy.set(JsSourceMapNamesPolicy.SOURCE_MAP_NAMES_POLICY_FQ_NAMES)
97-
sourceMapEmbedSources.set(JsSourceMapEmbedMode.SOURCE_MAP_SOURCE_CONTENT_ALWAYS)
98-
}
99-
100-
compileTaskProvider.configure {
101-
dependsOn(TASK_GENERATE_BRIDGE)
102-
}
103-
}
104115
}

gradle-plugin/src/main/kotlin/io/github/manriif/supabase/functions/task/SupabaseFunctionCopyJsTask.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import java.io.File
4040
import javax.inject.Inject
4141

4242
/**
43-
* Task responsible for copying generated js code into supabase function directory.
43+
* Task responsible for copying js sources into supabase function directory.
4444
*/
4545
@CacheableTask
4646
abstract class SupabaseFunctionCopyJsTask : DefaultTask() {

gradle-plugin/src/main/kotlin/io/github/manriif/supabase/functions/task/SupabaseFunctionCopyKotlinTask.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import org.gradle.api.tasks.CacheableTask
3232
import org.gradle.api.tasks.Input
3333
import org.gradle.api.tasks.InputDirectory
3434
import org.gradle.api.tasks.Internal
35+
import org.gradle.api.tasks.Optional
3536
import org.gradle.api.tasks.OutputDirectory
3637
import org.gradle.api.tasks.PathSensitive
3738
import org.gradle.api.tasks.PathSensitivity
@@ -52,6 +53,7 @@ abstract class SupabaseFunctionCopyKotlinTask : DefaultTask() {
5253
internal abstract val supabaseDir: DirectoryProperty
5354

5455
@get:InputDirectory
56+
@get:Optional
5557
@get:PathSensitive(PathSensitivity.RELATIVE)
5658
internal abstract val compiledSourceDir: DirectoryProperty
5759

gradle-plugin/src/main/kotlin/io/github/manriif/supabase/functions/task/SupabaseFunctionGenerateImportMapTask.kt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,15 @@ import io.github.manriif.supabase.functions.JS_SOURCES_INPUT_DIR
2929
import io.github.manriif.supabase.functions.kmp.JsDependency
3030
import org.gradle.api.DefaultTask
3131
import org.gradle.api.file.DirectoryProperty
32+
import org.gradle.api.file.RegularFileProperty
3233
import org.gradle.api.provider.ListProperty
3334
import org.gradle.api.provider.Property
3435
import org.gradle.api.tasks.CacheableTask
3536
import org.gradle.api.tasks.Input
36-
import org.gradle.api.tasks.InputDirectory
37+
import org.gradle.api.tasks.InputFile
3738
import org.gradle.api.tasks.Internal
3839
import org.gradle.api.tasks.Nested
40+
import org.gradle.api.tasks.Optional
3941
import org.gradle.api.tasks.OutputFile
4042
import org.gradle.api.tasks.PathSensitive
4143
import org.gradle.api.tasks.PathSensitivity
@@ -64,9 +66,10 @@ abstract class SupabaseFunctionGenerateImportMapTask : DefaultTask() {
6466
@get:Internal
6567
internal abstract val importMapsDir: DirectoryProperty
6668

67-
@get:InputDirectory
69+
@get:InputFile
70+
@get:Optional
6871
@get:PathSensitive(PathSensitivity.RELATIVE)
69-
internal abstract val packageJsonDir: DirectoryProperty
72+
internal abstract val packageJsonFile: RegularFileProperty
7073

7174
@get:Input
7275
internal abstract val functionName: Property<String>
@@ -94,8 +97,7 @@ abstract class SupabaseFunctionGenerateImportMapTask : DefaultTask() {
9497

9598
private fun createFunctionImports(): JsonObject {
9699
val imports = JsonObject()
97-
val packageJsonFile = packageJsonDir.file("package.json").get().asFile
98-
val packageJson = fromSrcPackageJson(packageJsonFile) ?: return imports
100+
val packageJson = fromSrcPackageJson(packageJsonFile.orNull?.asFile) ?: return imports
99101

100102
packageJson.dependencies.forEach { (packageName, version) ->
101103
imports.addProperty(packageName, "npm:$packageName@$version")

gradle-plugin/src/main/kotlin/io/github/manriif/supabase/functions/task/Tasks.kt

Lines changed: 44 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
*/
2222
package io.github.manriif.supabase.functions.task
2323

24+
import io.github.manriif.supabase.functions.IMPORT_MAPS_DIRECTORY_NAME
2425
import io.github.manriif.supabase.functions.IMPORT_MAP_TEMPLATE_FILE_NAME
2526
import io.github.manriif.supabase.functions.KOTLIN_MAIN_FUNCTION_NAME
2627
import io.github.manriif.supabase.functions.REQUEST_CONFIG_FILE_NAME
@@ -43,6 +44,7 @@ import org.gradle.kotlin.dsl.support.uppercaseFirstChar
4344
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
4445

4546
internal const val PREPARE_KOTLIN_BUILD_SCRIPT_MODEL_TASK = "prepareKotlinBuildScriptModel"
47+
internal const val JS_PUBLIC_PACKAGE_JSON_TASK = "jsPublicPackageJson"
4648

4749
internal const val TASK_PREFIX = "supabaseFunction"
4850
internal const val TASK_GENERATE_ENVIRONMENT_TEMPLATE = "${TASK_PREFIX}CopyKotlin%s"
@@ -70,7 +72,7 @@ internal fun Project.configurePluginTasks(
7072
val jsDependenciesProvider = jsDependencies()
7173

7274
registerGenerateImportMapTask(extension, jsDependenciesProvider)
73-
registerGenerateBridgeTask(extension, kmpExtension)
75+
registerGenerateKotlinBridgeTask(extension, kmpExtension)
7476
registerCopyJsTask(extension, jsDependenciesProvider)
7577
registerCopyKotlinTask(extension, "development")
7678
registerCopyKotlinTask(extension, "production")
@@ -98,7 +100,7 @@ private fun Project.registerAggregateImportMapTask(extension: SupabaseFunctionEx
98100
supabaseDir.convention(extension.supabaseDir)
99101

100102
importMapsDir.convention(
101-
layout.buildDirectory.dir("${SUPABASE_FUNCTION_OUTPUT_DIR}/importMaps")
103+
layout.buildDirectory.dir("${SUPABASE_FUNCTION_OUTPUT_DIR}/$IMPORT_MAPS_DIRECTORY_NAME")
102104
)
103105

104106
importMapTemplateFile.convention(
@@ -120,30 +122,31 @@ private val Project.aggregateTaskProvider: TaskProvider<SupabaseFunctionAggregat
120122
"Aggregate task not found"
121123
}
122124

123-
private fun Project.registerGenerateBridgeTask(
125+
private fun Project.registerGenerateKotlinBridgeTask(
124126
extension: SupabaseFunctionExtension,
125127
kmpExtension: KotlinMultiplatformExtension
126128
) {
127-
val sourceSet = kmpExtension.sourceSets.findByName("jsMain") ?: return
128-
129-
val outputDir = layout.buildDirectory
130-
.dir("generated/$SUPABASE_FUNCTION_OUTPUT_DIR/${sourceSet.name}/src")
131-
132-
sourceSet.kotlin.srcDir(outputDir)
129+
val outputDir = layout.buildDirectory.dir("generated/$SUPABASE_FUNCTION_OUTPUT_DIR/jsMain/src")
133130

134131
tasks.register<SupabaseFunctionGenerateKotlinBridgeTask>(TASK_GENERATE_BRIDGE) {
135132
group = SUPABASE_FUNCTION_TASK_GROUP
136133

137134
description = "Generate a kotlin function that acts as a bridge between " +
138135
"the `Deno.serve` and the kotlin main function."
139136

137+
packageName.convention(extension.packageName)
140138
supabaseDir.convention(extension.supabaseDir)
141139
generatedSourceOutputDir.convention(outputDir)
142-
packageName.convention(extension.packageName)
143140
jsOutputName.convention(jsOutputName(kmpExtension))
144141
functionName.convention(extension.functionName)
145142
mainFunctionName.convention(KOTLIN_MAIN_FUNCTION_NAME)
146143
}
144+
145+
afterEvaluate {
146+
kmpExtension.sourceSets.named { it == "jsMain" }.configureEach {
147+
kotlin.srcDir(outputDir)
148+
}
149+
}
147150
}
148151

149152
private fun Project.registerCopyJsTask(
@@ -166,36 +169,36 @@ private fun Project.registerCopyKotlinTask(
166169
) {
167170
val uppercaseEnvironment = environment.uppercaseFirstChar()
168171
val compileSyncTaskName = "js${uppercaseEnvironment}LibraryCompileSync"
169-
170-
if (tasks.names.none { it == compileSyncTaskName }) {
171-
logger.error(
172-
"""
173-
Could not locate task `$compileSyncTaskName`, common reasons for this error are:
174-
175-
- The `$SUPABASE_FUNCTION_PLUGIN_NAME` plugin was applied on a build script where the kotlin multiplatform plugin was not applied (e.g., root build script)
176-
- The kotlin multiplatform plugin was not applied on this project
177-
- JS target was not initialized on this project
178-
- JS target is missing `binaries.library()`
179-
""".trimIndent()
180-
)
181-
182-
error("Could not locate task `$compileSyncTaskName`, check the logs for possible causes.")
183-
}
184-
185172
val taskName = TASK_GENERATE_ENVIRONMENT_TEMPLATE.format(uppercaseEnvironment)
186173

187174
tasks.register<SupabaseFunctionCopyKotlinTask>(taskName) {
188175
group = SUPABASE_FUNCTION_TASK_GROUP
189176
description = "Copy Kotlin generated sources into supabase function directory."
190177

191178
compiledSourceDir.convention(
192-
layout.buildDirectory.dir("compileSync/js/main/${environment}Library/kotlin")
179+
layout.buildDirectory.dir("compileSync/js/main/${environment}Library/kotlin").orNone()
193180
)
194181

195182
supabaseDir.convention(extension.supabaseDir)
196183
functionName.convention(extension.functionName)
197184

198-
dependsOn(compileSyncTaskName)
185+
if (tasks.names.none { it == compileSyncTaskName }) {
186+
doFirst {
187+
error(
188+
"""
189+
Task `$compileSyncTaskName` was not found during project sync, common reasons for this error are:
190+
191+
- The `$SUPABASE_FUNCTION_PLUGIN_NAME` plugin was applied on a build script where the kotlin multiplatform plugin was not applied (e.g., root build script)
192+
- The kotlin multiplatform plugin was not applied on this project
193+
- JS target was not initialized on this project
194+
- JS target is missing `binaries.library()`
195+
""".trimIndent()
196+
)
197+
}
198+
} else {
199+
dependsOn(compileSyncTaskName)
200+
}
201+
199202
dependsOn(TASK_COPY_JS)
200203
}
201204
}
@@ -270,20 +273,24 @@ private fun Project.registerGenerateImportMapTask(
270273
group = SUPABASE_FUNCTION_TASK_GROUP
271274
description = "Generate import map."
272275

273-
packageJsonDir.convention(layout.buildDirectory.dir("tmp/jsPublicPackageJson"))
276+
importMapsDir.convention(
277+
rootProject.layout.buildDirectory
278+
.dir("${SUPABASE_FUNCTION_OUTPUT_DIR}/$IMPORT_MAPS_DIRECTORY_NAME")
279+
)
280+
281+
packageJsonFile.convention(
282+
layout.buildDirectory.file("tmp/jsPublicPackageJson/package.json").orNone()
283+
)
284+
274285
functionName.convention(extension.functionName)
275286
jsDependencies.convention(jsDependenciesProvider)
276287

277-
dependsOn("jsPublicPackageJson")
288+
if (tasks.names.any { it == JS_PUBLIC_PACKAGE_JSON_TASK }) {
289+
dependsOn(JS_PUBLIC_PACKAGE_JSON_TASK)
290+
}
278291
}
279292

280293
aggregateTaskProvider.configure {
281-
val aggregateTask = apply {
282-
dependsOn(generateTaskProvider)
283-
}
284-
285-
generateTaskProvider.configure {
286-
importMapsDir.convention(aggregateTask.importMapsDir)
287-
}
294+
dependsOn(generateTaskProvider)
288295
}
289296
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package io.github.manriif.supabase.functions.util
2+
3+
import org.gradle.api.Project
4+
import org.gradle.api.Task
5+
import org.gradle.kotlin.dsl.withType
6+
7+
internal inline fun <reified T : Task> Project.postponeErrorOnTaskInvocation(errorMessage: String) {
8+
tasks.withType<T>().configureEach {
9+
doFirst {
10+
error(errorMessage)
11+
}
12+
}
13+
}

0 commit comments

Comments
 (0)