1 /* 2 * Copyright 2025 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.appfunctions.compiler.processors 18 19 import androidx.appfunctions.compiler.AppFunctionCompiler 20 import androidx.appfunctions.compiler.AppFunctionCompilerOptions 21 import androidx.appfunctions.compiler.core.AppFunctionSymbolResolver 22 import androidx.appfunctions.compiler.core.IntrospectionHelper.APP_FUNCTIONS_INTERNAL_PACKAGE_NAME 23 import androidx.appfunctions.compiler.core.IntrospectionHelper.APP_FUNCTION_INVENTORY_CLASS 24 import androidx.appfunctions.compiler.core.IntrospectionHelper.AggregatedAppFunctionInventoryClass 25 import androidx.appfunctions.compiler.core.IntrospectionHelper.AggregatedAppFunctionInvokerClass 26 import androidx.appfunctions.compiler.core.IntrospectionHelper.AppFunctionInvokerClass 27 import androidx.appfunctions.compiler.core.toClassName 28 import com.google.devtools.ksp.processing.CodeGenerator 29 import com.google.devtools.ksp.processing.Dependencies 30 import com.google.devtools.ksp.processing.Resolver 31 import com.google.devtools.ksp.processing.SymbolProcessor 32 import com.google.devtools.ksp.symbol.KSAnnotated 33 import com.google.devtools.ksp.symbol.KSClassDeclaration 34 import com.squareup.kotlinpoet.FileSpec 35 import com.squareup.kotlinpoet.KModifier 36 import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy 37 import com.squareup.kotlinpoet.PropertySpec 38 import com.squareup.kotlinpoet.TypeSpec 39 import com.squareup.kotlinpoet.asClassName 40 import com.squareup.kotlinpoet.buildCodeBlock 41 42 /** 43 * The processor generates the implementation of AggregatedAppFunctionInventory and 44 * AggregatedAppFunctionInvoker. 45 * 46 * The processor would only start aggregation process when 47 * * [AppFunctionCompilerOptions.aggregateAppFunctions] is true. 48 * * AND there is no remaining @AppFunction nodes to processed. 49 */ 50 class AppFunctionAggregateProcessor( 51 private val options: AppFunctionCompilerOptions, 52 private val codeGenerator: CodeGenerator, 53 ) : SymbolProcessor { 54 55 private var hasProcessed = false 56 processnull57 override fun process(resolver: Resolver): List<KSAnnotated> { 58 if (!options.aggregateAppFunctions) { 59 return emptyList() 60 } 61 62 if (hasProcessed) { 63 return emptyList() 64 } 65 66 if (!shouldProcess(resolver)) { 67 return emptyList() 68 } 69 70 generateAggregatedAppFunctionInventory(resolver) 71 generateAggregatedAppFunctionInvoker(resolver) 72 generateAggregatedIndexXml(resolver) 73 74 hasProcessed = true 75 return emptyList() 76 } 77 generateAggregatedAppFunctionInventorynull78 private fun generateAggregatedAppFunctionInventory(resolver: Resolver) { 79 val generatedInventories = 80 AppFunctionSymbolResolver(resolver).getGeneratedAppFunctionInventories() 81 val aggregatedInventoryClassName = 82 "${'$'}${AggregatedAppFunctionInventoryClass.CLASS_NAME.simpleName}_Impl" 83 84 val aggregatedInventoryClassBuilder = TypeSpec.classBuilder(aggregatedInventoryClassName) 85 aggregatedInventoryClassBuilder.superclass(AggregatedAppFunctionInventoryClass.CLASS_NAME) 86 aggregatedInventoryClassBuilder.addAnnotation(AppFunctionCompiler.GENERATED_ANNOTATION) 87 aggregatedInventoryClassBuilder.addProperty(buildInventoriesProperty(generatedInventories)) 88 89 val fileSpec = 90 FileSpec.builder(APP_FUNCTIONS_INTERNAL_PACKAGE_NAME, aggregatedInventoryClassName) 91 .addType(aggregatedInventoryClassBuilder.build()) 92 .build() 93 94 codeGenerator 95 .createNewFile( 96 // TODO: Collect all AppFunction files as source files set 97 Dependencies.ALL_FILES, 98 APP_FUNCTIONS_INTERNAL_PACKAGE_NAME, 99 aggregatedInventoryClassName 100 ) 101 .bufferedWriter() 102 .use { fileSpec.writeTo(it) } 103 } 104 buildInventoriesPropertynull105 private fun buildInventoriesProperty( 106 generatedInventories: List<KSClassDeclaration> 107 ): PropertySpec { 108 return PropertySpec.builder( 109 AggregatedAppFunctionInventoryClass.PROPERTY_INVENTORIES_NAME, 110 List::class.asClassName().parameterizedBy(APP_FUNCTION_INVENTORY_CLASS) 111 ) 112 .addModifiers(KModifier.OVERRIDE) 113 .initializer( 114 buildCodeBlock { 115 addStatement("listOf(") 116 indent() 117 for (generatedInventory in generatedInventories) { 118 addStatement("%T(),", generatedInventory.toClassName()) 119 } 120 unindent() 121 addStatement(")") 122 } 123 ) 124 .build() 125 } 126 generateAggregatedAppFunctionInvokernull127 private fun generateAggregatedAppFunctionInvoker(resolver: Resolver) { 128 val generatedInvokers = 129 AppFunctionSymbolResolver(resolver).getGeneratedAppFunctionInvokers() 130 val aggregatedInvokerClassName = 131 "${'$'}${AggregatedAppFunctionInvokerClass.CLASS_NAME.simpleName}_Impl" 132 133 val aggregatedInvokerClassBuilder = TypeSpec.classBuilder(aggregatedInvokerClassName) 134 aggregatedInvokerClassBuilder.superclass(AggregatedAppFunctionInvokerClass.CLASS_NAME) 135 aggregatedInvokerClassBuilder.addAnnotation(AppFunctionCompiler.GENERATED_ANNOTATION) 136 aggregatedInvokerClassBuilder.addProperty(buildInvokersProperty(generatedInvokers)) 137 138 val fileSpec = 139 FileSpec.builder(APP_FUNCTIONS_INTERNAL_PACKAGE_NAME, aggregatedInvokerClassName) 140 .addType(aggregatedInvokerClassBuilder.build()) 141 .build() 142 143 codeGenerator 144 .createNewFile( 145 // TODO: Collect all AppFunction files as source files set 146 Dependencies.ALL_FILES, 147 APP_FUNCTIONS_INTERNAL_PACKAGE_NAME, 148 aggregatedInvokerClassName 149 ) 150 .bufferedWriter() 151 .use { fileSpec.writeTo(it) } 152 } 153 buildInvokersPropertynull154 private fun buildInvokersProperty(generatedInvokers: List<KSClassDeclaration>): PropertySpec { 155 return PropertySpec.builder( 156 AggregatedAppFunctionInvokerClass.PROPERTY_INVOKERS_NAME, 157 List::class.asClassName().parameterizedBy(AppFunctionInvokerClass.CLASS_NAME) 158 ) 159 .addModifiers(KModifier.OVERRIDE) 160 .initializer( 161 buildCodeBlock { 162 addStatement("listOf(") 163 indent() 164 for (generatedInvoker in generatedInvokers) { 165 addStatement("%T(),", generatedInvoker.toClassName()) 166 } 167 unindent() 168 addStatement(")") 169 } 170 ) 171 .build() 172 } 173 generateAggregatedIndexXmlnull174 private fun generateAggregatedIndexXml(resolver: Resolver) { 175 // We generate both XML formats supported by old and new AppSearch indexer respectively 176 // as it can't be guaranteed that the device will have the latest version of AppSearch. 177 // TODO: Add compiler option to disable legacy xml generator. 178 val legacyIndexProcessor = AppFunctionLegacyIndexXmlProcessor(codeGenerator) 179 legacyIndexProcessor.process(resolver) 180 181 val indexProcessor = AppFunctionIndexXmlProcessor(codeGenerator) 182 indexProcessor.process(resolver) 183 } 184 shouldProcessnull185 private fun shouldProcess(resolver: Resolver): Boolean { 186 val appFunctionSymbolResolver = AppFunctionSymbolResolver(resolver) 187 val appFunctionClasses = appFunctionSymbolResolver.resolveAnnotatedAppFunctions() 188 return appFunctionClasses.isEmpty() 189 } 190 } 191