Merge "Add super type impl - Generate AppFunctionInvoker Impl" into androidx-main
diff --git a/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/AppFunctionCompiler.kt b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/AppFunctionCompiler.kt
index 81f6291..6151d45 100644
--- a/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/AppFunctionCompiler.kt
+++ b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/AppFunctionCompiler.kt
@@ -19,6 +19,7 @@
import androidx.appfunctions.compiler.core.ProcessingException
import androidx.appfunctions.compiler.core.logException
import androidx.appfunctions.compiler.processors.AppFunctionIdProcessor
+import androidx.appfunctions.compiler.processors.AppFunctionInventoryProcessor
import com.google.devtools.ksp.processing.KSPLogger
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessor
@@ -51,10 +52,9 @@
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
val idProcessor = AppFunctionIdProcessor(environment.codeGenerator)
+ val inventoryProcessor = AppFunctionInventoryProcessor(environment.codeGenerator)
return AppFunctionCompiler(
- listOf(
- idProcessor,
- ),
+ listOf(idProcessor, inventoryProcessor),
environment.logger,
)
}
diff --git a/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/core/IntrospectionHelper.kt b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/core/IntrospectionHelper.kt
index f3f5fbb..fe0d3fa 100644
--- a/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/core/IntrospectionHelper.kt
+++ b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/core/IntrospectionHelper.kt
@@ -22,6 +22,8 @@
object IntrospectionHelper {
// Package names
private const val APP_FUNCTIONS_PACKAGE_NAME = "androidx.appfunctions"
+ private const val APP_FUNCTIONS_INTERNAL_PACKAGE_NAME = "androidx.appfunctions.internal"
+ private const val APP_FUNCTIONS_METADATA_PACKAGE_NAME = "androidx.appfunctions.metadata"
// Annotation classes
object AppFunctionAnnotation {
@@ -30,4 +32,8 @@
// Classes
val APP_FUNCTION_CONTEXT_CLASS = ClassName(APP_FUNCTIONS_PACKAGE_NAME, "AppFunctionContext")
+ val APP_FUNCTION_INVENTORY_CLASS =
+ ClassName(APP_FUNCTIONS_INTERNAL_PACKAGE_NAME, "AppFunctionInventory")
+ val APP_FUNCTION_METADATA_CLASS =
+ ClassName(APP_FUNCTIONS_METADATA_PACKAGE_NAME, "AppFunctionMetadata")
}
diff --git a/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/processors/AppFunctionInventoryProcessor.kt b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/processors/AppFunctionInventoryProcessor.kt
new file mode 100644
index 0000000..cedc597
--- /dev/null
+++ b/appfunctions/appfunctions-compiler/src/main/java/androidx/appfunctions/compiler/processors/AppFunctionInventoryProcessor.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appfunctions.compiler.processors
+
+import androidx.appfunctions.compiler.AppFunctionCompiler
+import androidx.appfunctions.compiler.core.AppFunctionSymbolResolver
+import androidx.appfunctions.compiler.core.AppFunctionSymbolResolver.AnnotatedAppFunctions
+import androidx.appfunctions.compiler.core.IntrospectionHelper
+import com.google.devtools.ksp.processing.CodeGenerator
+import com.google.devtools.ksp.processing.Dependencies
+import com.google.devtools.ksp.processing.Resolver
+import com.google.devtools.ksp.processing.SymbolProcessor
+import com.google.devtools.ksp.symbol.KSAnnotated
+import com.squareup.kotlinpoet.FileSpec
+import com.squareup.kotlinpoet.KModifier
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import com.squareup.kotlinpoet.PropertySpec
+import com.squareup.kotlinpoet.TypeSpec
+import com.squareup.kotlinpoet.asClassName
+import com.squareup.kotlinpoet.buildCodeBlock
+
+/**
+ * Generates implementations for the AppFunctionInventory interface.
+ *
+ * It resolves all functions in a class annotated with `@AppFunction`, and generates the
+ * corresponding metadata for those functions.
+ */
+class AppFunctionInventoryProcessor(
+ private val codeGenerator: CodeGenerator,
+) : SymbolProcessor {
+ override fun process(resolver: Resolver): List<KSAnnotated> {
+ val appFunctionSymbolResolver = AppFunctionSymbolResolver(resolver)
+ val appFunctionClasses = appFunctionSymbolResolver.resolveAnnotatedAppFunctions()
+ for (appFunctionClass in appFunctionClasses) {
+ generateAppFunctionInventoryClass(appFunctionClass)
+ }
+ return emptyList()
+ }
+
+ private fun generateAppFunctionInventoryClass(appFunctionClass: AnnotatedAppFunctions) {
+ val originalPackageName = appFunctionClass.classDeclaration.packageName.asString()
+ val originalClassName = appFunctionClass.classDeclaration.simpleName.asString()
+
+ val inventoryClassName = getAppFunctionInventoryClassName(originalClassName)
+ val inventoryClassBuilder = TypeSpec.classBuilder(inventoryClassName)
+ inventoryClassBuilder.addSuperinterface(IntrospectionHelper.APP_FUNCTION_INVENTORY_CLASS)
+ inventoryClassBuilder.addAnnotation(AppFunctionCompiler.GENERATED_ANNOTATION)
+ inventoryClassBuilder.addProperty(buildFunctionIdToMetadataMapProperty())
+
+ val fileSpec =
+ FileSpec.builder(originalPackageName, inventoryClassName)
+ .addType(inventoryClassBuilder.build())
+ .build()
+ codeGenerator
+ .createNewFile(
+ Dependencies(
+ aggregating = false,
+ checkNotNull(appFunctionClass.classDeclaration.containingFile)
+ ),
+ originalPackageName,
+ inventoryClassName
+ )
+ .bufferedWriter()
+ .use { fileSpec.writeTo(it) }
+ }
+
+ /** Creates the `functionIdToMetadataMap` property of the `AppFunctionInventory`. */
+ private fun buildFunctionIdToMetadataMapProperty(): PropertySpec {
+ return PropertySpec.builder(
+ "functionIdToMetadataMap",
+ Map::class.asClassName()
+ .parameterizedBy(
+ String::class.asClassName(),
+ IntrospectionHelper.APP_FUNCTION_METADATA_CLASS
+ ),
+ )
+ .addModifiers(KModifier.OVERRIDE)
+ // TODO: Actually build map properties
+ .initializer(buildCodeBlock { addStatement("mapOf()") })
+ .build()
+ }
+
+ private fun getAppFunctionInventoryClassName(functionClassName: String): String {
+ return "$%s_AppFunctionInventory_Impl".format(functionClassName)
+ }
+}
diff --git a/appfunctions/appfunctions-compiler/src/test/java/androidx/appfunctions/compiler/AppFunctionCompilerTest.kt b/appfunctions/appfunctions-compiler/src/test/java/androidx/appfunctions/compiler/AppFunctionCompilerTest.kt
index 3e628c7..8860052 100644
--- a/appfunctions/appfunctions-compiler/src/test/java/androidx/appfunctions/compiler/AppFunctionCompilerTest.kt
+++ b/appfunctions/appfunctions-compiler/src/test/java/androidx/appfunctions/compiler/AppFunctionCompilerTest.kt
@@ -43,7 +43,7 @@
}
@Test
- fun testSimpleFunction() {
+ fun testSimpleFunction_genAppFunctionIds_success() {
val report = compilationTestHelper.compileAll(sourceFileNames = listOf("SimpleFunction.KT"))
compilationTestHelper.assertSuccessWithContent(
@@ -54,7 +54,7 @@
}
@Test
- fun testMissingFirstParameter() {
+ fun testMissingFirstParameter_hasCompileError() {
val report =
compilationTestHelper.compileAll(sourceFileNames = listOf("MissingFirstParameter.KT"))
@@ -68,7 +68,7 @@
}
@Test
- fun testIncorrectFirstParameter() {
+ fun testIncorrectFirstParameter_hasCompileError() {
val report =
compilationTestHelper.compileAll(sourceFileNames = listOf("IncorrectFirstParameter.KT"))
@@ -80,4 +80,15 @@
" ^"
)
}
+
+ @Test
+ fun testSimpleFunction_genAppFunctionInventoryImpl_success() {
+ val report = compilationTestHelper.compileAll(sourceFileNames = listOf("SimpleFunction.KT"))
+
+ compilationTestHelper.assertSuccessWithContent(
+ report = report,
+ expectGeneratedFileName = "SimpleFunction_AppFunctionInventory_Impl.kt",
+ goldenFileName = "$%s".format("SimpleFunction_AppFunctionInventory_Impl.KT")
+ )
+ }
}
diff --git a/appfunctions/appfunctions-compiler/src/test/test-data/output/$SimpleFunction_AppFunctionInventory_Impl.KT b/appfunctions/appfunctions-compiler/src/test/test-data/output/$SimpleFunction_AppFunctionInventory_Impl.KT
new file mode 100644
index 0000000..e7653db3
--- /dev/null
+++ b/appfunctions/appfunctions-compiler/src/test/test-data/output/$SimpleFunction_AppFunctionInventory_Impl.KT
@@ -0,0 +1,12 @@
+package com.testdata
+
+import androidx.appfunctions.`internal`.AppFunctionInventory
+import androidx.appfunctions.metadata.AppFunctionMetadata
+import javax.`annotation`.processing.Generated
+import kotlin.String
+import kotlin.collections.Map
+
+@Generated("androidx.appfunctions.compiler.AppFunctionCompiler")
+public class `$SimpleFunction_AppFunctionInventory_Impl` : AppFunctionInventory {
+ override val functionIdToMetadataMap: Map<String, AppFunctionMetadata> = mapOf()
+}