Gradle学习
Gradle学习
翟志军 发表于4年前
Gradle学习
  • 发表于 4年前
  • 阅读 17846
  • 收藏 65
  • 点赞 7
  • 评论 6

腾讯云 技术升级10大核心产品年终让利>>>   

摘要: 本文是学习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"
}

> &lt;&lt;操作符代表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 查看可执行的任务

共有 人打赏支持
翟志军
粉丝 336
博文 75
码字总数 79851
作品 2
评论 (6)
polarw
很好的文章,我学习了一下,很有意义。
yellfly
不错
luger
蜡笔小鑫
正在学习,多谢了
Sack
很有用,谢谢你的分享。
FEI
看了,还不错~
×
翟志军
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: