學習Gradle過程的一些總結

jopen 9年前發布 | 198K 次閱讀 Gradle 項目構建

Gradle介紹

Gradle是一種自動化構建工具。

其實,Gradle被設計為一種構建語言,而非一個嚴格的框架。Gradle的核心使用Java和Groovy語言實現,所以,你可使用Java或者Groovy語言來擴展Gradle。當然,你也可以使用Scala。

gradle命令行創建項目

gradle本身沒有創建項目的命令。最好的解決方案就是使用第三方插件來實現。步驟:

  1. 新你的項目的文件夾project
  2. 進入文件項目文件夾添加文件build.gradle,并加入: 
    apply from: 'http://www.tellurianring.com/projects/gradle-plugins/gradle-templates/1.3/apply.groovy'
  3. 運行gradle initJavaProject

用到的第三方插件:gradle-templates :

以下就是它的API:

createGradlePlugin - Creates a new Gradle Plugin project in a new directory named after your project.
createGroovyClass - Creates a new Groovy class in the current project.
createGroovyProject - Creates a new Gradle Groovy project in a new directory named after your project.
createJavaClass - Creates a new Java class in the current project.
createJavaProject - Creates a new Gradle Java project in a new directory named after your project.
createScalaClass - Creates a new Scala class in the current project.
createScalaObject - Creates a new Scala object in the current project.
createScalaProject - Creates a new Gradle Scala project in a new directory named after your project.
createWebappProject - Creates a new Gradle Webapp project in a new directory named after your project.
initGradlePlugin - Initializes a new Gradle Plugin project in the current directory.
initGroovyProject - Initializes a new Gradle Groovy project in the current directory.
initJavaProject - Initializes a new Gradle Java project in the current directory.
initScalaProject - Initializes a new Gradle Scala project in the current directory.
initWebappProject - Initializes a new Gradle Webapp project in the current directory.

構建Java應用程序

  1. 使用application插件:apply plugin: 'application'
  2. 設置主函數:mainClassName = "WebTest"
  3. 運行gradle run

Gradle 任務

定義task

task hello

為task分配行為(`action`)

task hello << { println "hello" }

<<操作符代表task的doLast方法

task hello { doLast { println "hello" }
}

還可以

def printTaskName = { task -> println "Running ${task.name}" } task 'five' { doFirst printTaskName
} task 'two' << printTaskName

重復定義task的行為

task hello <<{ println "hello" } task world <<{ println "world" }

輸出:

hello world

使用Action接口定義action

task first { doFirst ( new Action(){ void execute(task){ println 'Running ${task.name}' }   
        }
    )
}

設置默認任務

defaultTasks 'first', 'second' task first { doLast { println "I am first" }
} task second { doFirst { println "I am second" }
}

在gradle命令后不加入任何任務名時,就會執行默認任務。

task的配置

task initializeDatabase
initializeDatabase << { println 'connect to database' }
initializeDatabase << { println 'update database schema' }
initializeDatabase { print 'configuring' }
initializeDatabase { println 'database connection' }

輸出:

print 'configuring' 
println 'database connection' 
println 'connect to database' 
println 'update database schema'

配置閉包將會在Gradle的配置期(configuration lifecycle phase)執行。

task的方法與屬性

task其實是一個對象,它也會有方法和屬性,同時,也會有類型。默認情況下,定義的task繼承自DefaultTask。

DefaultTask包含的方法

dependsOn(task) 設置依賴task

// Declare that world depends on hello // Preserves any previously defined dependencies as well task loadTestData {
    dependsOn createSchema
} // An alternate way to express the same dependency task loadTestData {
    dependsOn << createSchema
} // Do the same using single quotes (which are usually optional) task loadTestData {
    dependsOn 'createSchema' } // Explicitly call the method on the task object task loadTestData

loadTestData.dependsOn createSchema // A shortcut for declaring dependencies task loadTestData(dependsOn: createSchema)

還可以多重依賴

// Declare dependencies one at a time task loadTestData {
    dependsOn << compileTestClasses
    dependsOn << createSchema
} // Pass dependencies as a variable-length list task world {
    dependsOn compileTestClasses, createSchema
} // Explicitly call the method on the task object task world
world.dependsOn compileTestClasses, createSchema // A shortcut for dependencies only // Note the Groovy list syntax task world(dependsOn: [ compileTestClasses, createSchema ])

doFirst(closure)

注意同時定義兩個doFirst方法時的輸出

task setupDatabaseTests << { println 'load test data' }
setupDatabaseTests.doFirst { println 'create database schema' }
setupDatabaseTests.doFirst { println 'drop database schema' }

$ gradle world
:setupDatabaseTests
drop database schema
create database schema
load test data

也可以這樣寫:

// Initial task definition (maybe not easily editable) task setupDatabaseTests << { println 'load test data' } // Our changes to the task (in a place we can edit them) setupDatabaseTests { doFirst { println 'create database schema' } doFirst { println 'drop database schema' }
}

doLast(closure)

task setupDatabaseTests << { println 'create database schema' }
setupDatabaseTests.doLast { println 'load test data' }
setupDatabaseTests.doLast { println 'update version table' }

onlyIf(closure) 只有在onlyIf返回true時才運行task

task createSchema << {
    println 'create database schema' }
task loadTestData(dependsOn: createSchema) << {
    println 'load test data' }
loadTestData.onlyIf { System.properties['load.data'] == 'true' } $ build loadTestData
create database schema :loadTestData SKIPPED $ gradle -Dload.data=true loadTestData :createSchema create database schema :loadTestData load test data

DefaultTask包含的屬性

  • didWork

    apply plugin: 'java' 
    task emailMe(dependsOn: compileJava) ? {

    if(tasks.compileJava.didWork) { println 'SEND EMAIL ANNOUNCING SUCCESS' }

    }

  • enabled

    task templates ? {

    println 'process email templates' 


    task sendEmails(dependsOn: templates) ? {

    println 'send emails' 


    sendEmails.enabled = false

  • path,指此task的在構建文件中的路徑

    task echoMyPath ? {

    println "THIS TASK'S PATH IS ${path}" 

    }

    $ gradle echoMyPath 
    THIS TASK'S PATH IS :echoMyPath

    如果echoMyPath是子項目(subProject)下的一個task,那么它的路徑將會是::subProject:echoMyPath

  • logger,實現的日志接口是:org.slf4j.Logger,同時有少量的日志級別添加。

    task logLevel ? {

    def levels = ['DEBUG', 'INFO', 'LIFECYCLE', 'QUIET', 'WARN', 'ERROR']
    levels.each { level ->
        logging.level = level
        def logMessage = "SETTING LogLevel=${level}" logger.error logMessage
        logger.error '-' * logMessage.size()
        logger.debug 'DEBUG ENABLED' logger.info 'INFO ENABLED' logger.lifecycle 'LIFECYCLE ENABLED' logger.warn 'WARN ENABLED' logger.quiet 'QUIET ENABLED' logger.error 'ERROR ENABLED' println 'THIS IS println OUTPUT' logger.error ' ' }

    }

  • description

    task helloWorld(description: 'Says hello to the world') ? {

    println 'hello, world' 

    }

    task helloWorld ? {

    println 'hello, world' 


    helloWorld {

    description = 'Says hello to the world'


    // Another way to do it 
    helloWorld.description = 'Says hello to the world'

  • temporaryDir 臨時目錄

  • Dynamic Properties 動態屬性

    task copyFiles {

    // Find files from wherever, copy them
    // (then hardcode a list of files for illustration)
    fileManifest = [ 'data.csv', 'config.json' ]


    task createArtifact(dependsOn: copyFiles) ? {

    println "FILES IN MANIFEST: ${copyFiles.fileManifest}" 

    }

    $ gradle -b dynamic.gradle createArtifact 
    FILES IN MANIFEST: [data.csv, config.json]

可以在任務中寫Groovy代碼

為任務分組

def taskGroup = 'base' task first2(description: 'Base task', group: taskGroup) << { println "I am first" } task second2(dependsOn: first2, description: 'Secondary task', group: taskGroup) << { println "I am second" }

分組似乎只是用于gradle tasks時,顯示更好看。因為同一組的任務分顯示在一組里。

忽略任務

  • 使用onlyIf斷言 
    每一個任務都會有一個onlyIf方法,如果方法返回true則執行任務,否則跳過。

    task longrunning {
        onlyIf { task -> def now = Calendar.instance def weekDay = now[DAY_OF_WEEK] def weekDayInWeekend = weekDay in [SATURDAY, SUNDAY] return weekDayInWeekend
        } doLast { println "Do long running stuff" }
    }
  • 實現Spec()方法

    def file = new File('data.sample') task 'handleFile' << { println "Work with file ${file.name}" }
    handleFile.onlyIf(new Spec() { boolean isSatisfiedBy(task) { file.exists()
    
        }
    })
  • 拋出異常StopExecutionException

    def printTaskName = { task -> println "Running ${task.name}" } task first << printTaskName
    first.doFirst { def today = Calendar.instance def workingHours = today[Calendar.HOUR_OF_DAY] in 8..17 if (workingHours) { throw new StopExecutionException()
        }
    } task second(dependsOn: 'first') << printTaskName
  • 設置任務有效或者失效

    task 'listDirectory' { def dir = new File('assemble')
        enabled = dir.exists() doLast { println "List directory contents: ${dir.listFiles().join(',')}" }
    }
  • 使用命令行參數 -x

    gradle third -x second

增量構建的任務

在source發生變化時才執行任務

task convert  { def source = new File('source.xml') def output = new File('output.txt') // Define input file inputs.file source // Define output file outputs.file output doLast { def xml = new XmlSlurper().parse(source)
        output.withPrintWriter { writer ->
            xml.person.each { person ->
                writer.println "${person.name},${person.email}" }
        } println "Converted ${source.name} to ${output.name}" } 
}

或者

task createVersionDir { def outputDir = new File('output') // If project.version changes then the // task is no longer up-to-date inputs.property 'version', project.version
    outputs.dir outputDir doLast { println "Making directory ${outputDir.name}" mkdir outputDir
    }
} task convertFiles { // Define multiple files to be checked as inputs. inputs.files 'input/input1.xml', 'input/input2.xml' // Or use inputs.dir 'input' to check a complete directory. // Use upToDateWhen method to define predicate. outputs.upToDateWhen { // If output directory contains any file which name // starts with output and has the txt extension, // then the task is up-to-date. new File('output').listFiles().any {
            it.name ==~ /output.*\.txt$/ }
    } doLast { println "Running convertFiles" }
}

task類型

  • copy

    task copyFiles(type: Copy) { from 'resources' into 'target' include '**/*.xml', '**/*.txt', '**/*.properties' }
  • jar

    apply plugin: 'java' task customJar(type: Jar) {
        manifest {
            attributes firstKey: 'firstValue', secondKey: 'secondValue' }
        archiveName = 'hello.jar' destinationDir = file("${buildDir}/jars") from sourceSets.main.classes
    }
  • JavaExec 運行一個java類的main方法

    apply plugin: 'java' repositories {
        mavenCentral()
    } dependencies { runtime 'commons-codec:commons-codec:1.5' } task encode(type: JavaExec, dependsOn: classes) {
        main = 'org.gradle.example.commandline.MetaphoneEncoder' args = "The rain in Spain falls mainly in the plain".split().toList() classpath sourceSets.main.classesDir classpath configurations.runtime }

自定義task類型

  • 在構建文件中定義

    task createDatabase(type: MySqlTask) {

    sql = 'CREATE DATABASE IF NOT EXISTS example' 


    task createUser(type: MySqlTask, dependsOn: createDatabase) {

    sql = "GRANT ALL PRIVILEGES ON example.* TO exampleuser@localhost IDENTIFIED BY 'passw0rd'" 


    task createTable(type: MySqlTask, dependsOn: createUser) {

    username = 'exampleuser'
    password = 'passw0rd'
    database = 'example'
    sql = 'CREATE TABLE IF NOT EXISTS users
    (id BIGINT PRIMARY KEY, username VARCHAR(100))' 


    class MySqlTask extends DefaultTask {

    def hostname = 'localhost' def port = 3306 def sql def database def username = 'root' def password = 'password' @TaskAction def runQuery() { def cmd  if(database) {
            cmd = "mysql -u ${username} -p${password} -h ${hostname} -P ${port} ${database} -e " } else {
            cmd = "mysql -u ${username} -p${password} -h ${hostname} -P ${port} -e " }
    project.exec {
        commandLine = cmd.split().toList() + sql
    }

    }

  • 在源碼樹中定義

    在構建文件中:

    task createDatabase(type: MySqlTask) { sql = 'CREATE DATABASE IF NOT EXISTS example' } task createUser(type: MySqlTask, dependsOn: createDatabase) { sql = "GRANT ALL PRIVILEGES ON example.* TO exampleuser@localhost IDENTIFIED BY 'passw0rd'"
    } task createTable(type: MySqlTask, dependsOn: createUser) { username = 'exampleuser' password = 'passw0rd' database = 'example' sql = 'CREATE TABLE IF NOT EXISTS users (id BIGINT PRIMARY KEY, username VARCHAR(100))'
    } 

    在buildSrc文件夾中新建一個MySqlTask.groovy:

    import org.gradle.api.DefaultTask import org.gradle.api.tasks.TaskAction class MySqlTask extends DefaultTask { def hostname = 'localhost' def port = 3306 def sql  def database  def username = 'root' def password = password @TastAction def runQuery(){ def cmd  if(database){
                cmd = "mysql -u ${username} -p${password} -h ${hostname} -P ${port} ${database} -e" }else{
                cmd = "mysql -u ${username} -p${password} -h ${hostname} -P ${port} -e" }
            project.exec{
                commandLine = cmd.split().toList() + sql
            }
        }
    
    }

四個位置可以寫你的自定義構建代碼

  1. 在構建文件中的task代碼塊中
  2. 在buildSr文件夾中,此文件夾在.gradle文件同級
  3. 將分散的構建文件寫入到主構建文件中
  4. 使用java或groovy寫插件

Gradle 守護進程

Gradle需要運行在一個Java虛擬機中,每一次執行gradle命令就意味著一個新的Java虛擬機被啟動,然后加載Gradle類和庫,最后執行構建。這樣,構建起來會花費大量的時間在Java虛擬機的啟動與關閉。

通過Gradle 守護進程,只需要啟動一次Java虛擬機,之后就可以再利用,無需再次重啟Java虛擬機。這樣就達到縮短構建時間的目的。

方法是在執行gradle命令時加上--daemon參數,或者-m參數。中止Gradle守護進程的方法是執行gradle -stop命令。

如果希望能每一次的構建都使用Gradle的守護進程進行,那么可以通過設置Gradle的環境變量來達到目的。方法是添加GRADLE_OPTS="-Dorg.gradle.daemon=true"`到系統環境變量中。

Gradle的生命周期

  1. initialization(初始化) 
    在多項目的構建中,決定哪個項目是主項目
  2. configuration(配置) 
    將所有的task對象裝配到一個叫做DAG(for directed acyclic graph)的對象模型中
  3. execution(運行) 
    根據task之間的依賴執行task

項目的屬性

  • 項目中默認的屬性

    version = '1.0' 
    group = 'Sample' 
    description = 'Sample build file to show project properties' 
    task defaultProperties ? {

    println "Project: $project" println "Name: $name" println "Path: $path" println "Project directory: $projectDir" println "Build directory: $buildDir" println "Version: $version" println "Group: $project.group" println "Description: $project.description" println "AntBuilder: $ant" println "customProperty: $customProperty" println "customProperty1: $customProperty1" println "customProperty2: $customProperty2" 

    }

  • 在項目構建腳本中自定義項目屬性

    ext.customProperty = 'customProperty'

    ext {

    customProperty1 = "customProperty1" customProperty2 = "customProperty2" 

    }

  • 通過命令行設置項目屬性

    gradle -Pversion=1.1 -PcustomProperty=custom showProperties

  • 通過系統屬性設置項目屬性

    gradle -Dorg.gradle.project.version=2.0 -Dorg.gradle.project.customProperty=custom showProperties

  • 通過引用外部配置文件設置項目屬性 
    在項目目錄下新建一個純文本文件:gradle.properties。在此文件中寫入鍵值對,就可以了。

  • 從其它構建文件讀取配置信息

    • build.gradle 
      apply from: 'other.gradle'
    • other.gradle 
      println “configuring $project” 
      task hello ? {
      println 'hello from other script'
      }
  • 判斷項目中是否有設置某個屬性

    hasProperty('propertyName')

Gradle包裝器

可以在機器中沒有gradle的情況下,進行構建

依賴管理

在Gradle的構建文件中,可以將一組依賴定義在一個配置里。每一個配置都有一個名字,同時,它可以繼承自其它配置。

每一個Gradle構建文件都有一個ConfigurationContainer對象。可以通過project屬性訪問這個對象。ConfigurationContainer下可以定義一批配置,但它們至少有一個名稱。

configurations {
    commonsLib { description = 'Common libraries' }
    mainLib { description = 'Main libraries' extendsFrom commonsLib
    }
} println configurations['mainLib'].name println configurations.commonsLib.name //取消間接依賴下載 dependencies { // Configure transitive property with closure. compile('org.slf4j:slf4j-simple:1.6.4') {
        transitive = false } // Or we can use the transitive property // as method argument. compile group: 'org.slf4j', name: 'slf4j-simple', version: '1.6.4', transitive: false } //排除某個間接依賴 dependencies { // Configure transitive property with closure. compile('org.slf4j:slf4j-simple:1.6.4') { exclude 'org.slf4j:slf4j-api' }
} //當依賴需要不同版本的jdk時 dependencies { // Use artifact-only notation with @ symbol // together with classifier jdk16. compile('sample:simple:1.0:jdk16@jar') // Or we can use the classifier property // as method argument. compile group: 'sample', name: 'simple', version: '1.0',classifier: 'jdk16' } //依賴其它子項目 dependencies { compile project(':projectA') compile project(':projectB') {
    c   onfiguration = 'compile' }
} //依賴文件或文件夾 dependencies { compile files('spring-core.jar', 'spring-aap.jar') compile fileTree(dir: 'deps', include: '*.jar')
}

Repository管理

repository支持各種倉庫,包括遠程和本地的。

repositories {
    mavenLocal()
    mavenCentral()
    maven { // Name is optional. If not set url property is used name = 'Main Maven repository' url = 'http://intranet/repo' }
    mavenRepo(name: 'Snapshot repository', url: 'http://intranet/snapshots') //XML描述文件和jar包不在同一個地方的時候的定義方式: maven {  url: 'http://intranet/mvn' artifactUrls 'http://intranet/jars' artifactUrls 'http://intranet/snapshot-jars' } //有權限控制的倉庫 maven(name: 'Secured repository') {
        credentials {
            username = 'username' password = 'password' }
        url = 'http://intranet/repo' } //本地倉庫 repositories {
        flatDir(dir: '../lib', name: 'libs directory')
        flatDir {
            dirs '../project-files', '/volumes/shared-libs' name = 'All dependency directories' }
    }

}

Gradle常用命令

gradle tasks --all 查看所有的可執行的任務

gradle -m 查看可執行的任務

來自:http://my.oschina.net/zjzhai/blog/220028

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!