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.core 18 19 import androidx.appfunctions.compiler.AppFunctionCompiler 20 import androidx.appfunctions.compiler.core.IntrospectionHelper.APP_FUNCTIONS_AGGREGATED_DEPS_PACKAGE_NAME 21 import androidx.appfunctions.compiler.core.IntrospectionHelper.AppFunctionComponentRegistryAnnotation 22 import com.google.devtools.ksp.processing.CodeGenerator 23 import com.google.devtools.ksp.processing.Dependencies 24 import com.google.devtools.ksp.symbol.KSFile 25 import com.squareup.kotlinpoet.AnnotationSpec 26 import com.squareup.kotlinpoet.FileSpec 27 import com.squareup.kotlinpoet.TypeSpec 28 import com.squareup.kotlinpoet.buildCodeBlock 29 30 /** A helper class to generate AppFunction component registry. */ 31 class AppFunctionComponentRegistryGenerator(private val codeGenerator: CodeGenerator) { 32 /** 33 * Generates AppFunction component registry.. 34 * 35 * For example, if a list of components under module "myLibrary" were provided to generate 36 * INVENTORY registry, 37 * * "com.android.Test1" 38 * * "com.android.Test2" 39 * * "com.android.diff.Test1" 40 * 41 * It would generate 42 * 43 * ``` 44 * package appfunctions_aggregated_deps 45 * 46 * @AppFunctionComponentRegistry( 47 * componentCategory = "INVENTORY", 48 * componentNames = [ 49 * "com.android.Test1", 50 * "com.android.Test2", 51 * "com.android.diff.Test1", 52 * ] 53 * ) 54 * @Generated 55 * public class `$Mylibrary_InventoryComponentRegistry` 56 * ``` 57 */ generateRegistrynull58 fun generateRegistry( 59 moduleName: String, 60 category: String, 61 components: List<AppFunctionComponent>, 62 ) { 63 val className = getRegistryClassName(moduleName, category) 64 val annotationBuilder = 65 AnnotationSpec.builder(AppFunctionComponentRegistryAnnotation.CLASS_NAME) 66 .addMember( 67 "${AppFunctionComponentRegistryAnnotation.PROPERTY_COMPONENT_CATEGORY} = %S", 68 category, 69 ) 70 .addMember( 71 buildCodeBlock { 72 addStatement( 73 "${AppFunctionComponentRegistryAnnotation.PROPERTY_COMPONENT_NAMES} = [" 74 ) 75 indent() 76 // Ensure the generated registry is stable 77 val sortedQualifiedNames = 78 components.map(AppFunctionComponent::qualifiedName).sorted() 79 for (componentName in sortedQualifiedNames) { 80 addStatement("%S,", componentName) 81 } 82 unindent() 83 add("]") 84 } 85 ) 86 87 val registryClassBuilder = TypeSpec.classBuilder(className) 88 registryClassBuilder.addAnnotation(annotationBuilder.build()) 89 registryClassBuilder.addAnnotation(AppFunctionCompiler.GENERATED_ANNOTATION) 90 91 val fileSpec = 92 FileSpec.builder(APP_FUNCTIONS_AGGREGATED_DEPS_PACKAGE_NAME, className) 93 .addType(registryClassBuilder.build()) 94 .build() 95 96 val sourceFiles = components.flatMap { it.sourceFiles }.toSet() 97 codeGenerator 98 .createNewFile( 99 Dependencies(aggregating = true, sources = sourceFiles.toTypedArray()), 100 APP_FUNCTIONS_AGGREGATED_DEPS_PACKAGE_NAME, 101 className, 102 ) 103 .bufferedWriter() 104 .use { fileSpec.writeTo(it) } 105 } 106 getRegistryClassNamenull107 private fun getRegistryClassName(moduleName: String, componentCategory: String): String { 108 val prefix = moduleName.toPascalCase() 109 val componentCategoryPascalCase = componentCategory.toPascalCase() 110 return "${'$'}${prefix}_${componentCategoryPascalCase}ComponentRegistry" 111 } 112 113 /** Wrapper to hold AppFunction component data. */ 114 class AppFunctionComponent( 115 /** The component class qualified name. */ 116 val qualifiedName: String, 117 /** The source files used to generate the component. */ 118 val sourceFiles: Set<KSFile> = emptySet(), 119 ) 120 } 121