Como ingeniero de infraestructura, se dedica mucho tiempo apoyando entornos de desarrollo. Este trabajo incluye soporte de compilación (principalmente JVM / Android como plataformas de destino), así como escribir herramientas de CLI, soporte de CI, servicios de backend de infraestructura de escritura, Kubernetes, etc.
Tabla de contenido
Notas:
"Module"
en el artículo significa "Project"
en terminología de Gradle API"Project"
en el artículo significa todo el proyecto, incluidos todos sus módulosA diferencia de Bazel y Buck, Gradle, desafortunadamente, no escala bien para proyectos de módulos múltiples. En términos generales, alrededor de ~ 500 módulos La sobrecarga de Gradle es imposible de ignorar y empeora a medida que se agregan más módulos.
Como cualquier otro sistema de compilación, Gradle necesita saber qué es lo que necesita construir: módulos y tareas en módulos. Para resolverlo, Gradle necesita evaluar los archivos de configuración (ya sea basados en Groovy o basados en build.gradle
Kotlin build.gradle.kts
).
Durante la fase de configuración, los módulos pueden aplicar complementos, registrar tareas, realizar cálculos, realizar efectos secundarios como E / S de archivos o redes y leer / modificar la configuración de otros módulos.
Gradle ejecuta la fase de configuración cada vez que construye.
Podría ejecutarlo gradle assemble
y luego ejecutarlo una y otra vez, y cada vez tendré que esperar a que Gradle configure el proyecto aunque no haya cambiado nada.
Esta es una desafortunada promesa de Gradle API. Los complementos y / o el código de configuración del usuario pueden y a veces se basan en el hecho de que la fase de configuración se ejecuta cada vez para realizar efectos secundarios, como la configuración de la versión del módulo en función del tiempo actual de Unix.
Sin embargo, si ninguno de sus módulos realiza efectos secundarios, Gradle aún evalúa la configuración para cada compilación.
Gradle debe almacenar en caché y evitar la configuración de módulos.
Para permitir que Gradle almacene en caché la configuración de un módulo dado, necesitamos agregar una API opcional que permita que un módulo declare que su configuración es 100% reproducible (lo que significa que el código / complementos de configuración no dependen de entradas que cambian sin Aviso de Gradle).
Afortunadamente, muchas veces la configuración ya es pura. Un módulo generalmente aplica uno o más complementos para admitir un lenguaje de programación particular como Java o Kotlin y luego define algunas dependencias. Los efectos secundarios deben declararse como Tareas y Gradle sabe cómo manejarlos de manera eficiente.
Por ejemplo, la configuración del siguiente módulo debe ser almacenable en caché tal cual, no hay nada impuro al respecto *:
apply plugin: 'java-library'
dependencies {
api 'io.reactivex.rxjava2:rxjava:2.2.1'
}
dependencies {
implementation 'com.google.protobuf:protobuf-lite:3.0.1'
implementation project(':internal-lib')
}
dependencies {
testImplementation 'org.junit:junit:5.1.0'
}
Sin embargo, la configuración aún debe invalidarse si:
build.gradle
del módulo ha cambiado (idealmente, solo si el código real ha cambiado)La API propuesta parece ser parte de la fase de inicialización ( settings.gradle
) en lugar de la fase de configuración ( build.gradle
) para que Gradle sepa si la configuración se puede almacenar en caché por adelantado antes de la fase de configuración real.
settings.gradle include ':module-a'
project(':module-a').pure = true
Resultado:
Tal cambio permitirá a Gradle conocer la configuración de qué módulos debe almacenar en caché (en memoria / disco / memoria caché de compilación) y omitirla para las compilaciones posteriores, reduciendo significativamente la penalización de la fase de configuración para cada compilación después de la primera.
Gradle configura los módulos uno por uno, aumentando linealmente el tiempo de configuración con la cantidad de módulos en el proyecto.
Este es un desafortunado efecto secundario del diseño de Gradle API.
Gradle API permite que los complementos y / o el código de usuario lean / modifiquen la configuración de otros módulos u otro estado global. Por lo tanto, no hay una manera fácil para que Gradle configure los módulos en paralelo.
Este problema combinado con "Problema de diseño: la configuración no está en caché" da como resultado una ~20 seconds
penalización del tiempo de configuración para cada compilación de Gradle en el proyecto en el que trabajo (~ 830 módulos).
Gradle debe configurar módulos en paralelo.
Para permitir que Gradle configure módulos en paralelo, necesitamos agregar una API opcional que permita que un módulo declare que su configuración está aislada de la configuración de otros módulos, lo que significa que el módulo no modifica el estado global o el estado de otros módulos y no depende del orden de inicialización (está bien leer el estado global inmutable que ya se ha inicializado).
Para el contexto, Buck puede configurar nuestro proyecto en 2 seconds
(sin almacenamiento en caché) que incluye el análisis y el procesamiento de archivos de compilación definidos en Python / Skylark / Starlark, Gradle necesita ~20 seconds
eso.
settings.gradle
include ':module-a'
project(':module-a').isolated = true
La API propuesta parece tener que ser parte de la fase de inicialización ( settings.gradle) para que Gradle sepa esto por adelantado, antes de la fase de configuración.
Resultado
:
Tal cambio permitirá a Gradle saber qué módulos se pueden configurar en paralelo, lo que hace que la fase de configuración sea significativamente más rápida.
Combinados, la "Pureza" y el "Aislamiento" de la Configuración permitirán a Gradle maximizar la eficiencia de la Fase de Configuración al almacenarla en caché / evitarla, así como hacerlo en paralelo si no se puede evitar la configuración.
Considere el siguiente proyecto:
A
B
A
depende de B
Normalmente, para construir A
uno primero necesitaría construir B
(y todas sus dependencias). Que es lo que hace Gradle.
Esto, sin embargo, es ineficiente . No solo en grandes proyectos, sino en general. Sin embargo, se pone especialmente mal en grandes proyectos.
Debido a un orden de compilación tan estricto, Gradle a menudo no puede aprovechar el hardware (particularmente las CPU multinúcleo) en su totalidad. Ciertos módulos se convierten en "bloqueos" de larga vida en el gráfico de compilación, bloqueando la compilación de docenas de otros módulos solo porque ellos y sus dependencias tienen que construirse primero.
Gradle debería construir módulos contra ABI de sus dependencias.
Como se mencionó anteriormente, considere la siguiente configuración:
A
B
A
depende de B
Normalmente, para construir A
necesitaría construir B
primero y todas sus dependencias.
Pero en realidad, A
no necesita B
que se construya en absoluto. Lo importante para A
el ABI de B
.
Ahora tenemos que dar un paso atrás y aclarar que ABI tiene diferentes significados. Generalmente significa Interfaz Binaria de Aplicación, pero el nivel de detalle puede variar en el compilador / enlazador / etc.
Sin embargo, en aras de esta discusión, puede pensar en ABI como una versión eliminada de entidades públicas (clases, métodos, interfaces, etc.) en un módulo.
Por ejemplo, considere la siguiente clase de Java:
public class MyClass {
private String x;
public void a() {
this.x = b();
}
private String b() {
return incrediblyComplicatedLogic.execute().explain()
}
public static Runnable c(String s) {
return () -> { … }
}
}
Una versión de ABI legible para humanos MyClass
puede representarse así:
public class MyClass {
public void a() { throws NotImplementedException() }
public static Runnable c(String s) { throws NotImplementedException() }
}
Como puede ver, solo quedaron miembros públicos . Pero eso no es todo, ¡los detalles de implementación también se han ido !
Como puede imaginar, la extracción de ABI puede ser mucho más rápida que la compilación real , para calcular ABI no necesita compilar el código.
Si busca comprender cómo ABI se puede representar en diferentes idiomas, aquí hay algunos ejemplos:
* Hay advertencias, por supuesto:
Sin embargo, en muchos casos ABI es extraíble y esta técnica se utiliza con mucho éxito en Bazel y Buck, lo que les permite utilizar completamente los recursos de hardware al eliminar bloqueos de larga vida del gráfico de construcción.
Curiosamente, Gradle ya conoce los detalles de ABI para la compilación incremental de muchos idiomas que admite.
api
/ implementation
a la ABI, por lo tanto, no reconstruir módulos si la ABI de sus dependencias no cambió (que es un caso muy común cuando los cambios solo afectan los detalles de implementación de funciones / clases / etc.)Resultado :
Tal cambio permitirá a Gradle utilizar hardware con mucha mayor eficiencia, ahorrando una enorme cantidad de tiempo en la fase de ejecución.
Gradle es un gran sistema de construcción genérico. Si bien tiene ventajas muy fuertes como la compilación realmente incremental, tiene problemas de diseño importantes que, si no se solucionan, provocarán que Gradle pierda su terreno ahora que Bazel está cobrando impulso después de años de ser un sistema de código cerrado.
El directorio en línea más completo para encontrar detalles de negocios, contactos, productos, servicios y precios.
info@chileguia.cl