🖌 La guía para tu primer procesador de anotaciones con KSP (y conviértete en un artista de Kotlin) | de Adib Faramarzi | mayo 2022
Foto de Dan-Cristian Pădureț en UnsplashEn este artículo, crearemos un procesador de anotaciones basado en KSP que generará nuevos códigos y archivos basados en el uso de anotaciones. Si desea obtener más información sobre la generación de código y hacer que su proceso de desarrollo sea más productivo y divertido, ¡siga leyendo! KSP es una API que brinda a los desarrolladores de Kotlin la capacidad de crear complementos de compilación livianos que analizan y generan código mientras se mantienen alejados de las complejidades innecesarias de escribir un complemento de compilador real. Muchas bibliotecas (incluida Room) actualmente usan KSP en lugar de KAPT. Puedes encontrar una lista de algunos de ellos aquí. En Kotlin, los desarrolladores tienen acceso a las API del compilador, que pueden usar para desarrollar complementos del compilador. Estos complementos tienen acceso a casi todas las partes del proceso de compilación y pueden modificar las entradas del programa. Escribir complementos del compilador para la generación de código simple puede volverse complejo y es por eso que se creó KSP. Es un complemento de compilador bajo el capó que oculta la complejidad (y la dependencia del compilador mismo) de escribir un complemento de compilador administrando una API simple. Tenga en cuenta que, a diferencia de los complementos del compilador, KSP no puede modificar ni tratar el código compilado como entradas de solo lectura.
Contenidos
Comparación con la PAC
KAPT es un procesador de anotaciones basado en Java vinculado a la JVM, mientras que KSP es un procesador de código que solo depende de Kotlin y puede ser más natural para los desarrolladores de Kotlin. Además, KAPT debe tener acceso a los resguardos generados por Java para cambiar la entrada del programa en función de las anotaciones. Esta fase tiene lugar después de que todos los símbolos del programa (por ejemplo, tipos) se hayan resuelto por completo. Esta etapa toma el 30% del tiempo de compilación y puede ser costosa. Dado que no todos los generadores de código necesitan una resolución completa de símbolos (como en nuestro ejemplo), KSP puede ser mucho más rápido ya que lo hace en una etapa anterior del compilador, que no resuelve por completo todos (pero suficientes) símbolos.Foto de Marc Reichelt en UnsplashEn este artículo crearemos ListGen, una biblioteca basada en KSP que crea una lista de todas las funciones que tienen una anotación específica. Por ejemplo, puede agregar la anotación @Listed a sus funciones:/ / puede estar en cualquier lugar (o en cualquier módulo) en su proyecto @Listed(«mainList»)fun mainModule() = 2// puede estar en cualquier lugar (o en cualquier módulo) ) en tu proyecto @Listed(«otherList»)fun helloModule( ) = «¡hola!»// puede estar en cualquier lugar (o en cualquier módulo) en tu proyecto @Listed(«mainList»)fun secondModule() = 3Y deja una lista de generarlos como a continuación:// en build/… /GeneratedLists.ktval mainList = listOf(mainModule(), secondModule())val otherList = listOf(helloModule()) ¡Así que comencemos a generar! Para comenzar con KSP, solo necesita algunas cosas. Primero, agregue la API de KSP a las dependencias de su módulo KSP: agregue la configuración requerida a los archivos build.gradle
Crear la clase de anotación
Dado que queremos usar KSP para el procesamiento de anotaciones, debemos definir nuestras anotaciones personalizadas para poder usarlas más tarde. En este caso, queremos tener una anotación llamada @Listed que tome un nombre como entrada y se defina en funciones. Más tarde haremos una lista de todos sus usos utilizando los nombres proporcionados.
Creación del procesador y su proveedor
Para procesar archivos (y crear más), debe crear un SymbolProcessor e inyectarlo en KSP usando un SymbolProcessorProvider (que es básicamente una fábrica para el procesador), ya que KSP tiene un cargador de servicios en la JVM para buscar usos. el proveedor. Por ahora no procesaremos nada para completar nuestra configuración. Para introducir el proveedor en el ServiceLoader de JVM, necesitamos crear el siguiente archivo. Para usar este generador (que actualmente no hace nada), debemos declarar una dependencia de KSP en su módulo en nuestro módulo de aplicación. hemos terminado Ahora podemos comenzar a desarrollar nuestro procesador.Foto de SwapnIl Dwivedi en Unsplash Para crear el procesador, debe preguntarse: «¿Qué se supone que debe producir este procesador?». Para responder a esta pregunta, puede comenzar haciendo las cosas a mano y creando archivos y código para generar. Esto le da una buena idea de lo que realmente debe suceder dentro de su procesador. En nuestro caso, queremos generar un archivo que se vea así y tenga las importaciones correctas: val mainList = listOf(mainModule(), secondModule())val otherList = listOf(helloModule()) Para hacer esto, necesitamos
- Agregue un paquete apropiado al archivo generado
- Encuentra mainModule, secondModule y helloModule
- Sepa dónde están y agregue las importaciones correctas para ellos encima del archivo
- Encuentre su nombre (mainList y otherList) de la anotación
- Generar un archivo con la información anterior
- Hazlo eficientemente
encontrar anotaciones
KSP proporciona una resolución que le permite encontrar cualquier símbolo en el módulo procesado que tenga una anotación específica. Generemos un archivo que contenga un comentario con los nombres de las funciones que tienen la anotación @Listed.Archivo generado 🎉La función de proceso debe devolver los símbolos no válidos. KSP utiliza esta información para su procesamiento de rondas múltiples. Como puede ver, crear un archivo y completar la información es muy sencillo. Todo lo que tiene que hacer es agregar importaciones, leer los nombres de las anotaciones y agregar las funciones, y listo.Listas generadas 🔥 Ya hemos terminado. Puede limpiar aún más el archivo y agregar más funciones si es necesario. También puede agregar pruebas unitarias (puede ver mis pruebas unitarias aquí).Nota: En este ejemplo, usamos un StringBuilder simple para crear el contenido del archivo. Para aplicaciones más avanzadas, puede usar bibliotecas como KotlinPoet para escribir el contenido de los archivos generados de manera más eficiente.Uso del patrón de visitantePara el ejemplo básico anterior, filtramos los símbolos por los tipos de función y los iteramos porque ese era el único símbolo que necesitábamos. Si necesita admitir más símbolos (como clases), también puede usar un KSVisitor y pasarlo a la función de aceptación de su símbolo, que llamará a la función correcta para su visitante (por ejemplo, se llamará a visitFunctionDeclaration si su símbolo tiene una función) . Puedes verlo en acción aqui. Para hacer que el procesador sea súper rápido, hay algunas cosas a considerar.Foto de Wesley Tingey en UnsplashMinimizar la cantidad de archivos procesadosEdite la menor cantidad de archivos posible para obtener los resultados que desea. Tenga en cuenta el código anterior, que solo funciona con las funciones que tienen nuestras anotaciones específicas. Todos los demás símbolos se ignoran.Informar al compiladorKSP es inteligente y tiene una estrategia de compilación incremental. No queremos que nuestros archivos generados cambien a menos que:
- Se modificó/eliminó un archivo previamente existente que contenía nuestras anotaciones.
- Se ha añadido un nuevo archivo con nuestra anotación.
Todos los demás archivos deben ser ignorados. Para lograr esto, pasamos nuestras dependencias a la función createNewFile, que informa al procesador de los archivos que consideramos para crear ese archivo.Evite características costosasAlgunas características de las API de KSP son costosas (como se indica en su documentación). Un ejemplo es resolve, que resuelve una TypeReference en un Type. Estas funciones son costosas y solo deben usarse cuando el conocimiento de ellas es absolutamente necesario. Tenga en cuenta estas funciones (y su documentación) y utilícelas con moderación. KSP es una herramienta poderosa que ayuda a los desarrolladores a escribir complementos de compilación livianos y procesadores de anotaciones mientras mantienen una API compatible con Kotlin. El uso de KSP puede ayudar a los desarrolladores e ingenieros ejecutivos a crear bibliotecas que los ayuden a ser más productivos mediante la generación de archivos y código repetitivo. Espero que hayas disfrutado este artículo y te haya ayudado a aprender cómo crear tu primera biblioteca KSP.