tareas
El siguiente código muestra tres tareas de graduación, y explicaremos las diferencias entre estos tres posteriores.
Tarea MyTask {println "¡Hola, mundo!" } tarea mytask {dolast {println "¡Hola, mundo!" }} tarea mytask << {println "¡Hola, mundo!" }Mi objetivo es crear una tarea que imprima "¡Hola, mundo!" Cuando se ejecuta. Cuando creé la tarea por primera vez, supuse que debería escribirse así:
Tarea MyTask {println "¡Hola, mundo!" }Ahora, intente ejecutar este MyTask, ingrese a Gradle MyTask en la línea de comandos e imprímalo de la siguiente manera:
Usuario $ Gradle MyTask ¡Hola, mundo! : mytask actualizado
Esta tarea parece que funciona. Imprime "¡Hola, mundo!".
Sin embargo, en realidad no es lo que esperábamos. Echemos un vistazo a por qué. Ingrese las tareas de Gradle en la línea de comando para ver todas las tareas disponibles.
Usuario $ Gradle tareas ¡Hola, mundo! : Tareas ------------------------------------------------------------ Todas las tareas ejecutables del Proyecto Root ------------------------------------------------------------ Tareas de configuración de construcción ----------------- Init-Inicializa una nueva construcción de Gradle. [incubación] ..........
Espera, ¿por qué "Hola, mundo!" impreso? ¡Solo quería ver qué tareas están disponibles y no ejecutaron ninguna tarea personalizada!
La razón es en realidad muy simple. La tarea de Gradle tiene dos etapas principales en su ciclo de vida: etapa de configuración y etapa de ejecución.
Tal vez mis palabras no son muy precisas, pero esto realmente puede ayudarme a comprender las tareas.
Gradle debe configurar la tarea antes de ejecutarla. Luego, la pregunta es, ¿cómo sé qué código en mi tarea se ejecuta durante el proceso de configuración y qué código se ejecuta cuando se ejecuta la tarea? La respuesta es que el código en el nivel superior de la tarea es el código de configuración, como:
Tarea myTask {def name = "Pavel" // <- Esta línea de código se ejecutará println "¡Hola, mundo!" //// <- Esta línea de código también se ejecutará en la etapa de configuración}Es por eso que cuando ejecuto tareas de Gradle, imprimo "¡Hola, mundo!" - Porque se ejecuta el código de configuración. Pero este no es el efecto que quiero, quiero "¡Hola, mundo!" Para imprimirlo solo cuando llamo explícitamente a MyTask. Para lograr este efecto, la forma más fácil es usar la tarea#dolast ().
tarea mytask {def text = '¡Hola, mundo!' // Configurar mi tarea dolast {text println // Esto se ejecuta cuando mi tarea se llama}}Ahora, "¡Hola, mundo!" Solo imprimirá cuando ejecute Gradle MyTask. Genial, ahora sé cómo configurar y hacer que la tarea haga lo correcto. Hay otra pregunta. En el ejemplo inicial, ¿qué significa el símbolo << de la tercera tarea?
Tarea myTask2 << {println "¡Hola, mundo!" }En realidad, esto es solo una versión de azúcar sintáctica de Dolast. Tiene el mismo efecto que el siguiente método de escritura:
Tarea MyTask {dolast {println '¡Hola, mundo!' // Esto se ejecuta cuando se llama mi tarea}}Sin embargo, todos los códigos de esta manera se ejecutan y no hay parte de configuración del código, por lo que son más adecuados para aquellas tareas simples que no requieren configuración. Una vez que su tarea necesita ser configurada, aún necesita usar la versión Dolast.
gramática
Los guiones de Gradle están escritos en el lenguaje maravilloso. La sintaxis de Groovy es un poco como Java, espero que puedas aceptarla.
Si ya está familiarizado con Groovy, puede omitir esta parte.
Hay un concepto muy importante en Groovy que necesita comprender el cierre (cierre)
Cierre
El cierre es la clave para nuestra comprensión de Gradle. El cierre es un bloque separado de código que puede recibir parámetros, valores de retorno o asignarse a variables. Es similar a la interfaz invocable en Java y Future, y también es como un puntero de funciones, lo cual es fácil de entender. . .
La clave es que este código se ejecutará cuando lo llame, no cuando se cree. Ver un ejemplo de cierre:
def myClosure = {println '¡Hola mundo!' } // Ejecutar nuestro cierre ()#Salida: ¡Hola Mundo!Aquí hay un cierre que recibe parámetros:
def myClossure = {String str -> println str} // Ejecutar nuestro cierre CLUSUNE ('¡Hola Mundo!')#Salida: ¡Hola mundo!Si el cierre solo recibe un parámetro, puede usarlo para hacer referencia a este parámetro:
def myClosure = {println it} // Ejecute nuestro cierre CLUNTER ('¡Hola mundo!')#Salida: ¡Hola mundo!Cierre que recibe múltiples parámetros:
def myClosure = {String Str, intAdemás, el tipo de parámetro es opcional, y el ejemplo anterior se puede abreviar de la siguiente manera:
def myClosure = {str, num -> println "$ str: $ num"} // Ejecutar nuestro cierre CLUSUNLo genial es que las variables del contexto actual se pueden usar en el cierre. Por defecto, el contexto actual es la clase donde se creó el cierre:
def myvar = '¡Hola mundo!' def myClosure = {println myvar} myClosure ()#Output: ¡Hola mundo!Otro punto genial es que el contexto del cierre se puede cambiar, a través del cierre#setDelegate (). Esta característica es muy útil:
def myClosure = {println myvar} // Estoy haciendo referencia a myvar de myclass classMyClass m = new myClass () myClossure.setDelegate (m) myClossure () class myClass {def myVar = '¡Hola desde myClass!'}#Output: ¡Hola de MyClass!Como puede ver, MyVar no existe al crear cierre. No hay problema con esto porque cuando ejecutamos el cierre, Myvar existe en el contexto del cierre. En este ejemplo. Debido a que cambié su contexto a M antes de ejecutar el cierre, MyVar existe.
Pasar el cierre como parámetro
La ventaja del cierre es que se puede pasar a diferentes métodos, lo que puede ayudarnos a desacoplar la lógica de ejecución. En el ejemplo anterior, he mostrado cómo pasar el cierre a una instancia de una clase. A continuación analizaremos varios métodos que reciben el cierre como parámetros:
1. Solo reciba un parámetro y el parámetro es el método de cierre: mymethod (myClossure)
2. Si el método solo recibe un parámetro, se pueden omitir entre paréntesis: mymethod myClosure
3. Puedes usar el cierre en línea: mymethod {println 'hello world'}
4. Método para recibir dos parámetros: mymethod (arg1, myClossure)
5. Similar al 4, el cierre singular está en línea: mymethod (arg1, {println 'hello world'})
6. Si el último parámetro es cierre, se puede sacar de los soportes: mymethod (arg1) {println 'hello world'}
Aquí solo quiero recordarles si la escritura de 3 y 6 parece familiar.
Ejemplo de graduación
Ahora que hemos entendido la sintaxis básica, ¿cómo la usamos en los scripts de Gradle? Echemos un vistazo al siguiente ejemplo:
BuildScript {Repositories {jCenter ()} dependencias {classpath 'com.android.tools.build:gradle:1.2.3'}} allprojects {repositorios {jcenter ()}} Una vez que conozca la sintaxis de Groovy, ¿es fácil entender el ejemplo anterior?
Primero es un método BuildScript, que recibe un cierre:
Def BuildScript (cierre de cierre)
El siguiente es el método AllProjects, que también recibe un parámetro de cierre:
Def Allprojects (cierre de cierre)
Los otros son similares. . .
Parece mucho más fácil ahora, pero no entiendo una cosa, es decir, ¿dónde se definen estos métodos? La respuesta es el proyecto
Proyecto
Esta es una clave para comprender los scripts de graduación.
La declaración bloquea en el nivel superior del script de compilación se delegará a instancias de proyecto, que muestra que el proyecto es exactamente donde estoy buscando.
La búsqueda del método BuildScript en la página de documento del proyecto encontrará el bloque de script BuildScript {} (bloque de script). etc. ¿Qué demonios es el bloque de guiones? Según la documentación:
Script Block es un método que solo recibe el cierre como parámetro. Continúe leyendo la documentación BuildScript. El documento dice que los delegados a: Scriptandler de BuildScript. Es decir, pasamos el cierre al método BuildScript, y el contexto de ejecución final es el Scriptandler. En el ejemplo anterior, nuestro cierre pasó a BuildScript llama a los métodos de repositorios (cierre) y dependencias (cierre). Dado que el cierre se ha confiado a Scriptandler, buscaremos métodos de dependencias en Scriptandler.
Se encontraron dependencias nulas (cierre configuración configuración). Según la documentación, las dependencias se utilizan para configurar las dependencias de script. Y las dependencias finalmente se confiaron al DependencyHandler.
Vi lo ampliamente utilizados que son los grados. Comprender el conflicto es muy importante.
Bloques de guiones
Por defecto, muchos bloques de script están predefinidos en el proyecto, ¡pero el complemento de Gradle nos permite definir nuevos bloques de scripts nosotros mismos!
Esto significa que si publica algunos {...} en el nivel superior del script de compilación, pero no puede encontrar este bloque o método de script en la documentación de Gradle, en la mayoría de los casos, estos son algunos bloques de script definidos en el complemento.
Bloque de script de Android
Echemos un vistazo al archivo predeterminado de Android App/Build.gradle:
Aplicar el complemento: 'com.android.Application'Android {compiledDkVersion 22 buildToolSversion "22.0.1" DefaultConfig {Applicationid "com.trickyAndroid.TestApp" minsdkversion 16 TargetSDKVersion 22 VersionCode 1 Versión "1.0"} BuildPes {Lanzamiento {Release {False MinifyEnded ProGUARDFILESFILESFILESFILES DE Versión 1 getDefaultProguardFile ('proguard-andoid.txt'), 'proguard-rules.pro'}}}Orden de tareas
Noté que la mayoría de los problemas que encontré al usar Gradle están relacionados con la orden de ejecución de la tarea. Obviamente, si mi construcción funcionaría mejor si mi tarea se ejecuta en el momento adecuado. Echemos un vistazo más de cerca a cómo cambiar la orden de ejecución de las tareas.
dependiendo
Creo que la forma más directa de explicar la forma en que confía en otras tareas al ejecutar su tarea es usar el método dependsson.
Por ejemplo, en el siguiente escenario, la tarea A ya existe. Queremos agregar una tarea B, y su ejecución debe ser después de que A se ejecute:
Este es un escenario muy simple, suponiendo que la definición de A y B es la siguiente:
Tarea A << {println 'Hola desde una tarea'} B << {println 'Hello de B'} Simplemente llame a B.dependentson A y eso está bien.
Esto significa que mientras ejecute la Tarea B, la Tarea A ejecutará primero.
Paveldudka $ Gradle B: Ahello de A: Bhello de B
Además, puede declarar sus dependencias en el área de configuración de tareas:
Tarea A << {println 'Hola desde una tarea B {dependson a dolast {println' hello from b '}} ¿Qué pasa si queremos insertar nuestra tarea en una dependencia de tareas existente?
El proceso es similar a lo que hace ahora. Suponga que ya existen las siguientes dependencias de tareas:
Tarea A << {println 'Hello Desde A'} Tarea B << {println 'Hello From B'} Tarea C << {println 'Hello de C'} B.Dependentson AC.dependentson BÚnete a nuestra nueva tarea
Tarea B1 << {println 'Hello From B1'} B1.Dependson BC.Dependson B1Producción:
Paveldudka $ Gradle C: Ahello de A: Bhello de B: B1hello de B1: Chello de C
Tenga en cuenta que dependson agrega tareas a la colección dependiente, por lo que no es un problema confiar en múltiples tareas.
Tarea B1 << {println 'Hello From B1'} B1.Dependson BB1.Dependson QProducción:
Paveldudka $ Gradle B1: Ahello de A: Bhello de B: Qhello de Q: B1hello de B1
mustrunafter
Ahora suponga que tengo otra tarea, que depende de las otras dos tareas. Aquí uso un escenario real en el que tengo dos tareas, una tarea probada unitaria y una tarea probada en la interfaz de usuario. También hay una tarea que ejecuta todas las pruebas, que depende de las dos tareas anteriores.
Unidad de tarea << {println 'Hello desde unidades de pruebas'} Tarea UI << {println 'Hello de UI Tests'} Task Tests << {println '¡Hello de todas las pruebas!' '} tests.dependson unittests.dependson uiProducción:
Pruebas de PavelDudka $ Gradle: Uihello de pruebas de interfaz de usuario: Unithello de pruebas unitarias: ¡TestShello de todas las pruebas!
Aunque la prueba unitaria y la interfaz de usuario se ejecutarán antes de la tarea de prueba, no se puede garantizar la orden de ejecución de la unidad y la interfaz de usuario. Aunque ahora se ejecuta en el orden del alfabeto, esto depende de la implementación de Gradle, y no debe confiar en este pedido en su código.
Dado que el tiempo de prueba de la interfaz de usuario es mucho más largo que el tiempo de prueba unitario, quiero que la prueba unitaria se ejecute primero. Una solución es hacer que la tarea de UI dependa de la tarea de la unidad.
Unidad de tarea << {println 'hello desde unidades pruebas'} tarea ui << {println 'hello de ui tests'} Task Tests << {println '¡Hello de todas las pruebas!'} tests.dependson unittests.dependson uiui.dependson units // <- Agregué esta dependenciaProducción:
Pruebas de PavelDudka $ Gradle: Unithello de pruebas unitarias: Uihello de pruebas de interfaz de usuario: ¡TestShello de todas las pruebas!
Ahora la prueba unitaria se ejecutará antes de la prueba de UI.
Pero aquí hay un problema muy repugnante. Mi prueba de interfaz de usuario en realidad no depende de la prueba unitaria. Espero poder ejecutar la prueba de interfaz de usuario por separado, pero aquí cada vez que ejecuto la prueba de interfaz de usuario, ejecutaré primero la prueba unitaria.
Mustrunafter se necesita aquí. Mustrunafter no agrega dependencias, solo le dice a Gradle la prioridad de la ejecución si existen dos tareas al mismo tiempo. Por ejemplo, podemos especificar UI.Mustrunafter Unit aquí. De esta manera, si la tarea de interfaz de usuario y la tarea unitaria existen al mismo tiempo, Gradle ejecutará primero la prueba unitaria, y si solo se ejecuta la interfaz de usuario de Gradle, la tarea de la unidad no se ejecutará.
Unidad de tarea << {println 'Hello desde unidades de pruebas'} Tarea UI << {println 'Hello de UI Tests'} Task Tests << {println '¡Hello de todas las pruebas!'} tests.dependson unittests.dependson Uiui.mustrunafter UnidadProducción:
Pruebas de PavelDudka $ Gradle: Unithello de pruebas unitarias: Uihello de pruebas de interfaz de usuario: ¡TestShello de todas las pruebas!
La relación de dependencia es la siguiente:
Mustrunafter es actualmente una característica experimental en Gradle 2.4.
finalizado por
Ahora tenemos dos tareas, unidad y UI, suponiendo que ambas tareas generen informes de prueba, ahora quiero fusionar estos dos informes de prueba en uno:
Unidad de tarea << {println 'hello desde unidades pruebas'} tarea ui << {println 'hello de ui pruebas'} pruebas de tareas << {println '¡Hello de todas las pruebas!'} tarea MERGEREPORTS << {println 'fusioning test informes'} tests.dependson noittests.dependson uiui.mustrunafter unitmerTserPortsportsportsportsports.dependers.depenteAhora, si quiero obtener informes de prueba para la interfaz de usuario y la unidad, ejecute la tarea mergereports.
Paveldudka $ Gradle Mergereports: Unithello de las pruebas unitarias: Uihello de las pruebas de interfaz de usuario: ¡TestShello de todas las pruebas !: Informes de prueba de MergerPortSmerging
Esta tarea funciona, pero se ve tan estúpida. Mercereports no se siente particularmente bien desde la perspectiva de un usuario. Quiero ejecutar la tarea de las pruebas para obtener el informe de prueba sin tener que conocer la existencia de fusiones. Por supuesto, puedo mover la lógica de fusión a la tarea de las pruebas, pero no quiero hacer que la tarea de las pruebas sea demasiado hinchada, por lo que continuaré colocando la lógica de fusión en la tarea de Mergereports.
Finalizy está aquí para salvar la escena. Como su nombre indica, FinalizeBy es la tarea que se ejecutará después de ejecutar la tarea. Modifique nuestro script de la siguiente manera:
Unidad de tarea << {println 'Hello de las pruebas unitarias'} Tarea ui << {println 'hello de ui pruebas'} pruebas de tareas << {println '¡Hello de todas las pruebas! Pruebas.Ahora ejecute la tarea de las pruebas y puede obtener el informe de prueba:
Pruebas de PavelDudka $ Gradle: Unithello de pruebas unitarias: Uihello de pruebas de interfaz de usuario: ¡TestShello de todas las pruebas !: Informes de prueba de MergerPortSmerging!