Publicado por Ting-Yuan Huang – Ingeniero de software y Jiaxiang Chen – Ingeniero de software
La herramienta Kotlin Symbol Processing (KSP) proporciona una API de alto nivel para realizar metaprogramación en Kotlin. Se han creado muchas herramientas sobre KSP, lo que permite generar código Kotlin en tiempo de compilación. Por ejemplo, Jetpack Room utiliza KSP para generar código para acceder a la base de datos, basado en una interfaz proporcionada por el desarrollador, como por ejemplo:
@Dao interfaz UserDao { @Query(“SELECT * FROM user”) fun getAll(): Lista
KSP proporciona la API al código Kotlin para que Room, en este caso, pueda generar la implementación real de esa interfaz. Aunque KSP se ha convertido en una base fundamental para la metaprogramación en Kotlin, su implementación actual tiene algunas deficiencias que pretendemos abordar con una nueva arquitectura KSP2. Este blog detalla los cambios arquitectónicos y el impacto de los complementos basados en KSP.
Además, KSP2 tiene soporte preliminar para:
- El nuevo compilador de Kotlin (nombre en clave K2)
- Un nuevo generador de código fuente independiente que ofrece más flexibilidad y funcionalidad que el complemento del compilador Kotlin actual.
Después de recibir comentarios sobre la nueva arquitectura y continuar abordando las brechas, trabajaremos para lanzar KSP 2.0 en el que estos cambios serán predeterminados.
Habilitar la vista previa de KSP2
Los nuevos cambios de vista previa se pueden habilitar en KSP 1.0.14 o posterior usando una marca en gradle.properties:
Nota: Es posible que deba aumentar el tamaño del montón del demonio Gradle ahora que KSP y los procesadores se ejecutan en el demonio Gradle en lugar del demonio del compilador Kotlin (que tiene un tamaño de montón predeterminado mayor), por ejemplo org.gradle.jvmargs=-Xmx4096M -XX : Tamaño máximo Metaspazio = 1024 m
KSP2 y K2
Internamente, KSP2 utiliza el compilador Beta Kotlin K2 (que será el compilador predeterminado en Kotlin 2.0). Puede usar KSP2 antes de cambiar el compilador Kotlin a K2 (a través de la configuración LanguageVersion), pero si desea usar K2 para compilar su código, consulte: Pruebe el compilador K2 en sus proyectos de Android.
Generador de fuente independiente
KSP1 se implementa como un complemento del compilador Kotlin 1.x. Para ejecutar KSP es necesario ejecutar el compilador y especificar KSP y las opciones de complemento. En Gradle, las tareas KSP son tareas de compilación personalizadas que envían el trabajo real a KotlinCompileDaemon de forma predeterminada. Esto dificulta un poco la depuración y las pruebas, porque KotlinCompileDaemon se ejecuta en su propio proceso, fuera de Gradle.
En KSP2, la implementación puede considerarse como una biblioteca con un punto de entrada principal. Los sistemas y herramientas de compilación pueden llamar a KSP con este punto de entrada, sin configurar el compilador. Esto hace que sea muy fácil llamar a KSP mediante programación y es muy útil especialmente para depurar y probar. Con KSP2 puede establecer puntos de interrupción en los procesadores KSP sin tener que realizar otras tareas de configuración irregulares para permitir la depuración.
Todo se vuelve mucho más fácil porque KSP2 ahora controla su ciclo de vida y puede llamarse como un programa independiente o mediante programación, como:
val kspConfig = KSPJvmConfig.Builder().apply { // Toda la configuración ocurre aquí. }.build() val exitCode = KotlinSymbolProcessing(kspConfig, listOfProcessors, kspLoggerImpl).execute()
Cambios en el comportamiento de la API KSP2
Con la nueva implementación, también es una gran oportunidad para introducir algunas mejoras en el comportamiento de la API para que los desarrolladores que confían en KSP sean más productivos y tengan más posibilidades de depuración y recuperación de errores. Por ejemplo, al resolver Map
- Resuelva el tipo implícito de la llamada a la función: val error = mutableMapOf
() KSP1: Todo el tipo será un tipo de error debido a una resolución de tipo fallida.
KSP2: Resolverá con éxito el tipo de contenedor y, para el tipo inexistente en el argumento de tipo, informará correctamente los errores en el argumento de tipo específico.
- Parámetro de tipo ilimitado
KSP1: Sin límite
KSP2: ¿Un límite superior de Any? siempre se inserta para mantener la coherencia
- Resolver referencias de alias de tipo en tipos de funciones y anotaciones
KSP1: Ampliado al tipo subyacente, sin alias
KSP2: No ampliado, como usos en otros lugares.
- Nombres completos
KSP1: Los constructores tienen FQN si provienen de la fuente, pero no si provienen de una biblioteca.
KSP2: Los fabricantes no tienen FQN
- Escriba argumentos de tipo interno
KSP1: Los tipos internos toman argumentos de tipos externos.
KSP2: Los tipos internos no toman argumentos de tipos externos.
- Tipo de temas de proyecciones estelares
KSP1: Las proyecciones estelares tienen argumentos de tipo que se expanden a variaciones reales basadas en los sitios de declaración.
KSP2: Sin expansión. Las proyecciones estelares tienen valores nulos en sus argumentos de tipo.
- variación de la matriz de Java
KSP1: Java Array tiene un límite superior invariante
KSP2: Java Array tiene un límite superior covariante
- Entradas de enumeración
KSP1: Una entrada de enumeración tiene su propio subtipo con un supertipo de la clase de enumeración (comportamiento lingüísticamente incorrecto)
KSP2: El tipo de entrada de enumeración es el tipo de clase de enumeración adjunta.
- Escenario de anulación múltiple
Por ejemplo
interfaz GrandBaseInterface1 { fun foo(): Unidad } interfaz GrandBaseInterface2 { fun foo(): Unidad } interfaz BaseInterface1 : GrandBaseInterface1 { } interfaz BaseInterface2 : GrandBaseInterface2 { } clase OverrideOrder1 : BaseInterface1, GrandBaseInterface2 { override fun foo() = TODO() } clase OverrideOrder2: BaseInterface2, GrandBaseInterface1 { anular diversión foo() = TODO() }
KSP1:
Busque símbolos anulados en orden BFS; se devuelve el primer supertipo encontrado en la lista directa de supertipos que contiene el símbolo anulado. Por ejemplo, KSP dirá que OverrideOrder1.foo() anula GrandBaseInterface2.foo() y OverrideOrder2.foo() anula GrandBaseInterface1.foo()
KSP2:
En orden DFS, se devuelve el primer supertipo encontrado con símbolos sobrescritos (con búsqueda recursiva de supertipos) en la lista directa de supertipos.
Por ejemplo, KSP dirá que OverrideOrder1.foo() anula GrandBaseInterface1.foo() y OverrideOrder2.foo() anula GrandBaseInterface2.foo()
- modificador de java
KSP1: Los campos transitorios/volátiles son firmes de forma predeterminada
KSP2: Los campos transitorios/volátiles están abiertos de forma predeterminada
- Escriba anotaciones
KSP1: Las anotaciones de tipo en un argumento de tipo se reflejan solo en el símbolo del argumento de tipo.
KSP2: Las anotaciones de tipo en un argumento de tipo ahora también están presentes en el tipo resuelto
- parámetros vararg.
KSP1: Considerado un tipo de matriz
KSP2: No se considera un tipo de matriz
- Miembros sintetizados de Enums
KSP1: Faltan valores y valueOf si la enumeración está definida en fuentes de Kotlin
KSP2: los valores y valueOf están siempre presentes
- Miembros sintetizados de clases de datos.
KSP1: Faltan el componente N y la copia si la clase de datos está definida en las fuentes de Kotlin
KSP2: componenteN y copia siempre están presentes
Nuevo esquema de procesamiento multiplataforma
En cuanto al esquema de procesamiento, es decir, qué fuentes se procesan y cuándo, el principio de KSP es ser coherente con el esquema de compilación existente de la compilación. En otras palabras, lo que ve el compilador es lo que ven los procesadores, más el código fuente generado por los procesadores.
En el esquema de compilación KSP1 actual, los conjuntos de fuentes comunes/compartidos se procesan y compilan varias veces, con cada destino. Por ejemplo, commonMain se procesa y compila 3 veces en el siguiente diseño del proyecto. Poder procesar todas las fuentes de las dependencias es conveniente con una excepción: los procesadores no ven las fuentes generadas por commonMain cuando procesan jvmMain y jsMain. Hay que reelaborar todo y eso puede resultar ineficiente.
asignaciones |
aporte |
salir |
kspKotlinCommonMainMetadatos |
comúnPrincipal |
generadoComuna |
kspKotlinJvm |
comúnMain, jvmMain |
generadoCommonJvm |
kspKotlinJs |
comúnMain, jsMain |
generadoCommonJs |
compilarKotlinCommonMainMetadata |
commonaPrincipal, generadoComún |
común.klib |
compilarKotlinJvm |
commonMain, jvmMain, generadoCommonJvm |
aplicación.jar |
compilarKotlinJs |
commonMain, jsMain, generadoCommonJs |
principal.js |
En KSP2, planeamos agregar un modo experimental que intenta alinearse con cómo se compilan los conjuntos de fuentes en K2 Mejor. Todas las fuentes se pueden procesar solo una vez con el nuevo esquema de procesamiento disponible:
asignaciones |
aporte |
salir |
Soluble pero no disponible en obtener todos los archivos / obtener símbolos con anotación |
kspKotlinCommonMainMetadatos |
comúnPrincipal |
generadoComuna |
|
kspKotlinJvm |
jvmPrincipal |
generadoJvm |
comúnPrincipal, generadoComún |
kspKotlinJs |
jsPrincipal |
Js generados |
commonaPrincipal, generadoComún |
compilarKotlinCommonMainMetadata |
commonaPrincipal, generadoComún |
común.klib |
|
compilarKotlinJvm |
comúnMain, jvmMain, generadoComún, generadoJvm |
aplicación.jar |
|
compilarKotlinJs |
commonMain, jsMain, generadoCommon, generadoJs |
principal.js |
Tenga en cuenta que Kotlin 2.0 todavía está en versión beta y el modelo de compilación está sujeto a cambios. Háganos saber cómo funciona para usted y háganoslo saber. comentario.
Comentarios sobre la vista previa de KSP2
KSP2 está en vista previa pero hay aún más Trabajo por hacer antes de un lanzamiento estable. ¡Esperamos que estas nuevas funciones le ayuden a ser más productivo al utilizar KSP! Por favor proporcionanos el tuyo comentario para que podamos hacer que estas mejoras sean extraordinarias a medida que avancen hacia la estabilidad.