tarefas
O código a seguir mostra três tarefas gradle e explicaremos as diferenças entre esses três posteriormente.
tarefa mytask {println "Olá, mundo!" } tarefa myTask {dolast {println "Olá, mundo!" }} tarefa myTask << {println "Olá, mundo!" }Meu objetivo é criar uma tarefa que imprime "Olá, mundo!" Quando ele executa. Quando criei a tarefa pela primeira vez, imaginei que deveria ser escrita assim:
tarefa mytask {println "Olá, mundo!" }Agora, tente executar este myTask, entre no Gradle MyTask na linha de comando e imprimi -lo da seguinte forma:
Usuário $ gradle mytask Olá, mundo! : MyTask atualizado
Essa tarefa parece que funciona. Imprime "Olá, mundo!".
No entanto, não é realmente o que esperávamos. Vamos dar uma olhada no porquê. Digite as tarefas gradle na linha de comando para visualizar todas as tarefas disponíveis.
Usuário $ Gradle Tarefas Olá, mundo! : Tarefas -------------------------------------------------------- TODAS as tarefas executadas do Projeto ROOT --------------------------------------------------------------------- Construa tarefas de configuração ----------------- Init-Inicia uma nova compilação gradle. [incubatando] ..........
Espere, por que "Olá, mundo!" impresso? Eu só queria ver quais tarefas estão disponíveis e não executou nenhuma tarefa personalizada!
O motivo é realmente muito simples. A Gradle Task possui dois estágios principais em seu ciclo de vida: estágio de configuração e estágio de execução.
Talvez minhas palavras não sejam muito precisas, mas isso pode realmente me ajudar a entender as tarefas.
A gradle deve configurar a tarefa antes de executá -la. Então a pergunta é: como sei qual código em minha tarefa é executado durante o processo de configuração e qual código é executado quando a tarefa é executada? A resposta é que o código no nível superior da tarefa é o código de configuração, como:
tarefa mytask {def name = "pavel" // <- esta linha de código executará println "hello, mundo!" /// <- esta linha de código também será executada no estágio de configuração}É por isso que, quando executo tarefas de graduação, imprimi "Olá, mundo!" - porque o código de configuração é executado. Mas esse não é o efeito que eu quero, eu quero "Olá, mundo!" Para imprimi -lo apenas quando eu chamo explicitamente o MyTask. Para alcançar esse efeito, a maneira mais fácil é usar o método da tarefa#dolast ().
tarefa mytask {def text = 'Olá, mundo!' // Configure minha tarefa Dolast {Println Text // Isso é executado quando minha tarefa é chamada}}Agora, "Olá, mundo!" Só vai imprimir quando eu executar o gradle myTask. Legal, agora eu sei como configurar e fazer a tarefa fazer a coisa certa. Há outra pergunta. No exemplo inicial, o que significa o << símbolo da terceira tarefa?
tarefa mytask2 << {println "Olá, mundo!" }Na verdade, esta é apenas uma versão de açúcar sintático do Dolast. Tem o mesmo efeito que o seguinte método de escrita:
tarefa mytask {dolast {println 'hello, mundo!' // Isso é executado quando minha tarefa é chamada}}No entanto, todos os códigos dessa maneira são executados e não há parte da configuração do código, portanto, eles são mais adequados para as tarefas simples que não requerem configuração. Depois que sua tarefa precisa ser configurada, você ainda precisa usar a versão Dolast.
gramática
Os scripts gradle estão escritos na linguagem groovy. A sintaxe de Groovy é um pouco como Java, espero que você possa aceitá -la.
Se você já está familiarizado com o Groovy, pode pular esta parte.
Há um conceito muito importante no Groovy que você precisa entender o fechamento (fechamento)
Fechamentos
O fechamento é a chave para o nosso entendimento de Gradle. O fechamento é um bloco separado de código que pode receber parâmetros, retornar valores ou ser atribuído a variáveis. É semelhante à interface chamada em Java e Future, e também é como um ponteiro de função, que é fácil de entender. . .
A chave é que esse código será executado quando você o chamar, não quando for criado. Veja um exemplo de fechamento:
def myclosure = {println 'Hello World!' } // Execute nosso fechamento ()#saída: Hello World!Aqui está um fechamento que recebe parâmetros:
def myclosure = {string str -> println str} // execute nosso fechamento de fechamento ('hello world!')#saída: olá mundo!Se o fechamento receber apenas um parâmetro, você poderá usá -lo para fazer referência a este parâmetro:
def myClosure = {println it} // Execute nosso fechamento ('Hello World!')#Saída: Hello World!Fechamento que recebe vários parâmetros:
def myClosure = {string str, int num -> println "$ str: $ num"} // execute nossa renovationyclosure ('my string', 21) #Output: my string: 21Além disso, o tipo do parâmetro é opcional e o exemplo acima pode ser abreviado da seguinte forma:
def myclosure = {str, num -> println "$ str: $ num"} // execute nosso fechamento ('my string', 21) #output: my string: 21O que é legal é que as variáveis do contexto atual podem ser usadas no fechamento. Por padrão, o contexto atual é a classe em que o fechamento foi criado:
def myvar = 'Hello World!' Def myclosure = {println myvar} myClosure ()#saída: Olá, mundo!Outro ponto legal é que o contexto do fechamento pode ser alterado, via fechamento#setDelegate (). Este recurso é muito útil:
def myclosure = {println myvar} // estou referenciando myvar de myclass ClassMyclass m = new myclass () myClosure.setDelegate (m) myClosure () classe myclass {def myvar = 'hello from myclass!'}#saída: hello from myclass!Como você pode ver, o Myvar não existe ao criar um fechamento. Não há problema com isso porque, quando executamos o fechamento, o Myvar existe no contexto de fechamento. Neste exemplo. Como mudei seu contexto para M antes de executar o fechamento, o Myvar existe.
Passe o fechamento como um parâmetro
A vantagem do fechamento é que ele pode ser transmitido para diferentes métodos, o que pode nos ajudar a dissipar a lógica de execução. No exemplo anterior, mostrei como passar o fechamento para uma instância de uma classe. Abaixo, vamos dar uma olhada em vários métodos que recebem o fechamento como parâmetros:
1. Receba apenas um parâmetro e o parâmetro é o método de fechamento: MyMethod (myclosure)
2. Se o método receber apenas um parâmetro, parênteses poderão ser omitidos: MyMethod MyClosure
3. Você pode usar o fechamento embutido: MyMethod {Println 'Hello World'}
4. Método para receber dois parâmetros: MyMethod (Arg1, MyClosure)
5. Semelhante a 4, o fechamento singular está embutido: MyMethod (arg1, {println 'hello world'})
6. Se o último parâmetro for fechado, ele pode ser retirado de colchetes: MyMethod (arg1) {println 'hello world'}
Aqui eu só quero lembrá -lo se a escrita de 3 e 6 parece familiar?
Exemplo gradle
Agora que entendemos a sintaxe básica, como a usamos em scripts gradle? Vamos dar uma olhada no exemplo a seguir:
BuildScript {Repositórios {JCenter ()} Dependências {ClassPath 'com.android.tools.build:gradle:1.2.3'}} allProjects {repositórios {jCenter ()}} Depois de conhecer a sintaxe do Groovy, é fácil entender o exemplo acima?
Primeiro é um método BuildScript, que recebe um fechamento:
Def BuildScript (fechamento de fechamento)
Em seguida, é o método AllProjects, que também recebe um parâmetro de fechamento:
Def AllProjects (fechamento de fechamento)
Os outros são semelhantes. . .
Parece muito mais fácil agora, mas não entendo uma coisa, ou seja, onde esses métodos estão definidos? A resposta é projeto
Projeto
Esta é a chave para entender os scripts gradle.
Os blocos de declaração no nível superior do script de construção serão delegados para as instâncias do projeto, o que mostra que o projeto é exatamente onde estou procurando.
Pesquisando o método BuildScript na página de documentos do projeto encontrará o bloco de script BuildScript {} (bloco de script). etc. O que diabos é o bloco de script? De acordo com a documentação:
O bloco de script é um método que só recebe o fechamento como um parâmetro. Continue lendo a documentação do BuildScript. O documento diz que delega para: Scripthandler do BuildScript. Ou seja, passamos o fechamento para o método BuildScript, e o contexto final de execução é o scripthandler. No exemplo acima, nosso fechamento passou para o BuildScript chama os repositórios (fechamento) e os métodos de dependências (fechamento). Como o fechamento foi confiado ao Scripthandler, procuraremos métodos de dependências no Scripthandler.
Foram encontradas dependências vazias (configulação de fechamento). De acordo com a documentação, as dependências são usadas para configurar dependências de scripts. E as dependências acabaram sendo confiadas ao dependência.
Eu vi como é amplamente usado graduados. Compreender a confrustação é muito importante.
Blocos de script
Por padrão, muitos blocos de script são predefinidos no projeto, mas o plug -in gradle nos permite definir novos bloqueios de script!
Isso significa que, se você postar alguns {…} no nível superior do script de construção, mas não conseguir encontrar esse bloco de script ou método na documentação de Gradle, na maioria dos casos, esses são alguns blocos de script definidos no plug -in.
bloco de script Android
Vamos dar uma olhada no arquivo Android App/Build.gradle padrão:
Aplique o plugin: 'com.android.Application'android {Compilesdkversion 22 BuildToolSversion "22.0.1" DefaultConfig {ApplicationId "com.trickyandroid.testapp" MinSdkversion 16 Targetsdkversion 22 Versão 1 versionName "1.0"}} getDefaultProguardfile ('proguard-android.txt'), 'proguard-rules.pro'}}}Ordem de tarefa
Percebi que a maioria dos problemas que encontrei ao usar o gradle está relacionada à ordem de execução da tarefa. Obviamente, se minha compilação funcionaria melhor se minha tarefa fosse executada na hora certa. Vamos dar uma olhada em como alterar a ordem de execução das tarefas.
Dependson
Eu acho que a maneira mais direta de explicar a maneira como você confia em outras tarefas ao executar sua tarefa é usar o método Dependson.
Por exemplo, no cenário a seguir, a tarefa já existe. Queremos adicionar uma tarefa B, e sua execução deve ser após a execução:
Este é um cenário muito simples, assumindo que a definição de A e B é a seguinte:
Tarefa a << {println 'hello de uma tarefa'} B << {println 'hello from b'} Basta ligar para B.DependEntSon A e tudo bem.
Isso significa que, enquanto eu executar a tarefa B, a tarefa A executará primeiro.
Paveldudka $ gradle B: Ahello de A: Bhello de B
Além disso, você pode declarar suas dependências na área de configuração de tarefas:
Tarefa a << {println 'hello de A'} Tarefa B {Dependson A Dolast {println 'Hello From B'}} E se quisermos inserir nossa tarefa em uma dependência de tarefas existente?
O processo é semelhante ao que agora. Suponha que já existam as seguintes dependências de tarefas:
Tarefa a << {println 'hello de uma tarefa'} B << {println 'hello de B'} Tarefa C << {println 'hello de C'} B.DependentSon AC.DependentSon BJunte -se à nossa nova tarefa
Tarefa B1 << {println 'hello de B1'} B1.Dependson BC.Dependson B1Saída:
paveldudka $ gradle c: ahello de a: bhello de b: b1hello de b1: chello de c
Observe que a Dependson adiciona tarefas à coleção dependente, portanto, não há problema em confiar em várias tarefas.
Tarefa B1 << {println 'hello from b1'} b1.dependson bb1.dependson qSaída:
paveldudka $ gradle b1: ahello de a: bhello de b: qhello de q: b1hello de b1
Mustrunafter
Agora, suponha que eu tenha outra tarefa, que depende das outras duas tarefas. Aqui eu uso um cenário real em que tenho duas tarefas, uma tarefa testada em unidade e uma tarefa testada na interface do usuário. Há também uma tarefa que executa todos os testes, que depende das duas tarefas anteriores.
unidade de tarefa << {println 'hello from unit tests'} tarefa ui << {println 'hello from ui tests'} testes de tarefa << {println 'hello de todos os testes!'} tests.dependson unittests.dependson uiSaída:
Paveldudka $ GRADLE Testes: Uihello da UI Testes: Unithello a partir de testes de unidade: testshello de todos os testes!
Embora o teste Unitest e UI seja executado antes da tarefa de teste, a ordem de execução da unidade e da interface do usuário não podem ser garantidas. Embora seja executado na ordem do alfabeto agora, isso depende da implementação do Gradle e você não deve confiar nesse pedido em seu código.
Como o tempo de teste da interface do usuário é muito mais longo que o tempo de teste da unidade, quero que o teste de unidade seja executado primeiro. Uma solução é fazer com que a tarefa da interface do usuário dependa da tarefa da unidade.
unidade de tarefa << {println 'hello from unit tests'} tarefa ui << {println 'hello from ui tests'} testes de tarefa << {println 'hello de todos os testes!'} tests.dependson unittests.dependson uiUi.dependson unidade // <- i acrescentar essa dependênciaSaída:
Paveldudka $ GRADLE Testes: Unithello De testes de unidade: Uihello da UI Testes: testshello de todos os testes!
Agora, o teste de unidade será executado antes do teste da interface do usuário.
Mas há um problema muito nojento aqui. Meu teste de interface do usuário realmente não depende do teste de unidade. Espero poder executar o teste da interface do usuário separadamente, mas aqui toda vez que executar o teste da interface do usuário, executarei o teste de unidade primeiro.
Mustrunafter é necessário aqui. Mustrunafter não adiciona dependências, apenas diz a Gradle a prioridade da execução se duas tarefas existirem ao mesmo tempo. Por exemplo, podemos especificar a UI.MustRunafter Unidade aqui. Dessa forma, se a tarefa da interface do usuário e a tarefa da unidade existirem ao mesmo tempo, o gradle será executado primeiro no teste de unidade e se apenas o UI gradle for executado, a tarefa da unidade não será executada.
unidade de tarefa << {println 'hello de unidade testes'} tarefa ui << {println 'hello from ui tests'} testes de tarefa << {println 'hello de todosSaída:
Paveldudka $ GRADLE Testes: Unithello De testes de unidade: Uihello da UI Testes: testshello de todos os testes!
A relação de dependência é a seguinte:
Mustrunafter é atualmente um recurso experimental no Gradle 2.4.
finalizado
Agora, temos duas tarefas, unidade e interface do usuário, assumindo que ambas as tarefas produzirão relatórios de teste, agora quero mesclar esses dois relatórios de teste em um:
task unit << {println 'Hello from unit tests'}task ui << {println 'Hello from UI tests'}task tests << {println 'Hello from all tests!'}task mergeReports << {println 'Merging test reports'}tests.dependsOn unittests.dependsOn uiui.mustRunAfter unitmergeReports.dependsOn testsAgora, se eu quiser obter relatórios de teste para interface do usuário e unidade, execute a tarefa Mergereports.
Paveldudka $ gradle mergereports: Unithello a partir de testes de unidade: Uihello da UI testes: testshello de todos os testes !: Relatórios de teste de Mergereportsmerg
Essa tarefa funciona, mas parece tão estúpida. Mergereports não parece particularmente bom da perspectiva de um usuário. Quero executar a tarefa de testes para obter o relatório de teste sem precisar conhecer a existência de Mergereports. É claro que posso mover a lógica de mesclagem para a tarefa de testes, mas não quero tornar a tarefa de testes muito inchada, então continuarei colocando a lógica de mesclagem na tarefa Mergereports.
Finalmente está aqui para salvar a cena. Como o nome sugere, Finalizeby é a tarefa a ser executada após a execução da tarefa. Modifique nosso script da seguinte forma:
unidade de tarefa << {println 'hello from unit tests'} tarefa ui << {println 'hello from ui tests'} testes de tarefas << {println 'hello de todos os testes!'} tarefas mérgeSports << {println 'mérgia de reportações'}} tests.Finalized by MergereportsAgora execute a tarefa de testes e você pode obter o relatório de teste:
Paveldudka $ GRADLE Testes: Unithello de testes de unidade: Uihello da UI Testes: testshello de todos os testes !: Relatórios de teste de MergereportsMerging