继上一篇Gradle自动化项目构建之快速掌握Groovy,咱们继续深刻Gradle自动化项目构建技术的学习。html
gradle wrapper 就是由gradle 帮咱们生成的gradlew脚本,里面包含了用到的gradle版本信息,咱们编译代码的时候不直接运行gradle命令,而是运行gradlew 命令,他会自动帮咱们下载对应的gradle dist,gradle wrapper被添加到代码管理系统, 这样每个开发人员都不用去折腾gradle版本。java
gradle命令(Linux执行须要使用 ./)
经常使用任务指令:
gradlew build。生成全部的输出,并执行全部的检查。
gradlew run。生成应用程序并执行某些脚本或二进制文件
gradlew check。执行全部检测类任务如tests、linting等
gradlew clean。删除build文件目录。
gradlew projects。查看项目结构。
gradlew tasks。查看任务列表。查看某个任务详细信息,可用gradle help --task someTask
gradlew dependencies。查看依赖列表。
gradlew assembleDebug(或者gradlew aD) 编译并打Debug包
gradlew assembleRelease(或者gradlew aR) 编译并打Release的包
调试类:
gradlew -?, -h, --help。查看帮助信息。
gradlew -v,--version。查看版本信息。
gradlew -s,--stacktrace。执行任务时,打印栈信息。如gradle build --s
日志类:
-q, --quiet。只打印errors类信息。
-i, --info。打印详细的信息。
性能类:
--configure-on-demand,--no-configure-on-demand。是否开启按需配置模式。
--build-cache, --no-build-cache。是否使用缓存。
其它的详见其官方文档:https://docs.gradle.org/current/userguide/command_line_interface.html
复制代码
// setting.gradle文件
println '初始化阶段执行完毕'
// settings.gradle配置完后调用,只对settings.gradle设置生效
gradle.settingsEvaluated {
println "settings:执行settingsEvaluated..."
}
// 当settings.gradle中引入的全部project都被建立好后调用,只在该文件设置才会生效
gradle.projectsLoaded {
println "settings:执行projectsLoaded..."
}
// 在每一个project进行配置前调用,child project必须在root project中设置才会生效,root project必须在settings.gradle中设置才会生效
gradle.beforeProject { proj ->
println "settings:执行${proj.name} beforeProject"
}
// 在每一个project配置后调用
gradle.afterProject { proj ->
println "settings:执行${proj.name} afterProject"
}
// 全部project配置完成后调用
gradle.projectsEvaluated {
println "settings: 执行projectsEvaluated..."
}
//构建开始前调用
gradle.buildStarted {
println "构建开始..."
}
//构建结束后调用
gradle.buildFinished {
println "构建结束..."
}
// build.gradle文件中
/**
* 配置本Project阶段开始前的监听回调
*/
this.beforeEvaluate {
println '配置阶段执行以前'
}
/**
* 配置本Project阶段完成之后的回调
*/
this.afterEvaluate {
println '配置阶段执行完毕'
}
/**
* gradle执行本Project完毕后的回调监听
*/
this.gradle.buildFinished {
println '执行阶段执行完毕'
}
/**
* 全部project配置完成后调用,可直接在setting.gradle中监听
*/
gradle.projectsEvaluated {
gradle ->
println "全部的project都配置完毕了,准备生成Task依赖关系"
}
/**
* 表示本Project "task 依赖关系已经生成"
*/
gradle.taskGraph.whenReady {
TaskExecutionGraph graph ->
println "task 依赖关系已经生成"
}
/**
* 每个 Task 任务执行以前回调
*/
gradle.taskGraph.beforeTask {
Task task ->
println "Project[${task.project.name}]--->Task[${task.name}] 在执行以前被回调"
}
/**
* 每个 task 执行以后被回调
*/
gradle.taskGraph.afterTask {
task, TaskState taskState ->
//第二个参数表示 task 的状态,是可选的参数
println "Project[${task.project.name}]--->Task[${task.name}] 在执行完毕,taskState[upToDate:${taskState.upToDate},skipped:${taskState.skipped},executed:${taskState.executed},didWork:${taskState.didWork}]"
}
复制代码
附一张不知名大佬的执行流程和声明周期图示:android
1. 从Gradle的角度看,Gradle的管理是树状结构的,最外层的是根project,里层module是子project。
2. 每个子project都会对应输出,好比:apk,war,aar等等这个依赖配置完成,
3. 每一个project的配置和管理都是依靠本身的build.gradle完成的,而且build.gradle文件也是是否为project的标识。
4. 虽然Gradle的管理是树状结构,也能够在里层module中再建立module,可是实际开发中绝对不会在子project中再建立子project,所以此树状结构只有两层。
注:经过命令:gradlew projects,能够验证Project的树状结构
复制代码
api 做用
getAllprojects() 获取工程中全部的project(包括根project与子project)
getSubProjects() 获取当前project下,全部的子project(在不一样的project下调用,结果会不同,可能返回null)
getParent() 获取当前project的父project(若在rooProject的build.gradle调用,则返回null)
getRootProject() 获取项目的根project(必定不会为null)
project(String path, Closure configureClosure) 根据path找到project,经过闭包进行配置(闭包的参数是path对应的Project对象)
allprojects(Closure configureClosure) 配置当前project和其子project的全部project
subprojects(Closure configureClosure) 配置子project的全部project(不包含当前project)
复制代码
在gradle脚本文件中使用ext块扩展属性(父project中经过ext块定义的属性,子project能够直接访问使用)git
// rootProject : build.gradle
ext { // 定义扩展属性
compileSdkVersion = 28
libAndroidDesign = 'com.android.support:design:28.0.0'
}
// app : build.gradle
android {
compileSdkVersion = this.compileSdkVersion // 父project中的属性,子project能够直接访问使用
...
}
dependencies {
compile this.libAndroidDesign // 也可使用:this.rootProject.libAndroidDesign
...
}
复制代码
在gradle.properties文件中扩展属性github
// gradle.properties
isLoadTest=true // 定义扩展属性
mCompileSdkVersion=28 // 定义扩展属性
// setting.gradle
// 判断是否须要引入Test这个Module
if(hasProperty('isLoadTest') ? isLoadTest.toBoolean() : false) {
include ':Test'
}
// app : build.gradle
android {
compileSdkVersion = mCompileSdkVersion.toInteger()
...
}
复制代码
api 做用
getRootDir() 获取rootProject目录
getBuildDir() 获取当前project的build目录(每一个project都有本身的build目录)
getProjectDir() 获取当前project目录
File file(Object path) 定位一个文件,相对于当前project开始查找
ConfigurableFileCollection files(Object... paths) 定位多个文件,与file相似
copy(Closure closure) 拷贝文件
fileTree(Object baseDir, Closure configureClosure) 定位一个文件树(目录+文件),可对文件树进行遍历
例子:
// 打印common.gradle文件内容
println getContent('common.gradle')
def getContent(String path){
try{
def file = file(path)
return file.text
}catch(GradleException e){
println 'file not found..'
}
return null
}
// 拷贝文件、文件夹
copy {
from file('build/outputs/apk/')
into getRootProject().getBuildDir().path + '/apk/'
exclude {} // 排除文件
rename {} // 文件重命名
}
// 对文件树进行遍历并拷贝
fileTree('build/outputs/apk/') { FileTree fileTree ->
// 访问树结构的每一个结点
fileTree.visit { FileTreeElement element ->
println 'the file name is: '+element.file.name
copy {
from element.file
into getRootProject().getBuildDir().path + '/test/'
}
}
}
复制代码
// rootProject : build.gradle
buildscript { ScriptHandler scriptHandler ->
// 配置工程仓库地址
scriptHandler.repositories { RepositoryHandler repositoryHandler ->
repositoryHandler.jcenter()
repositoryHandler.mavenCentral()
repositoryHandler.mavenLocal()
repositoryHandler.ivy {}
repositoryHandler.maven { MavenArtifactRepository mavenArtifactRepository ->
mavenArtifactRepository.name 'personal'
mavenArtifactRepository.url 'http://localhost:8081/nexus/repositories/'
mavenArtifactRepository.credentials {
username = 'admin'
password = 'admin123'
}
}
}
// 配置工程的"插件"(编写gradle脚本使用的第三方库)依赖地址
scriptHandler.dependencies {
classpath 'com.android.tools.build:gradle:2.2.2'
classpath 'com.tencent.tinker-patch-gradle-plugin:1.7.7'
}
}
// =========================== 上述简化后 ============================
buildscript {
/**
* 配置工程仓库地址
* 因为repositories这个闭包中的delegate是repositoryHandler,
* 所以能够省略repositoryHandler的引用,直接使用其属性和方法。
*/
repositories {
jcenter()
mavenCentral()
mavenLocal()
ivy {}
maven {
name 'personal'
url 'http://localhost:8081/nexus/repositories/'
credentials {
username = 'admin'
password = 'admin123'
}
}
}
// 配置工程的"插件"(编写gradle脚本使用的第三方库)依赖地址
dependencies {
classpath 'com.android.tools.build:gradle:2.2.2'
classpath 'com.tencent.tinker-patch-gradle-plugin:1.7.7'
}
}
复制代码
// app : build.gradle
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) // 依赖文件树
// compile file() // 依赖单个文件
// compile files() // 依赖多个文件
implementation 'com.android.support:appcompat-v7:28.0.0' // 依赖仓库中的第三方库(即:远程库)
implementation project('CommonSDK') { // 依赖工程下其余Module(即:源码库工程)
exclude module: 'support-v4' // 排除依赖:排除指定module
exclude group: 'com.android.support' // 排除依赖:排除指定group下全部的module
transitive false // 禁止传递依赖,默认值为false
}
implementation('xxx') {
changing true // 每次都从服务端拉取
}
// 栈内编译
provided('com.tencent.tinker:tinker-android-anno:1.9.1')
}
复制代码
// copyApk任务:用于将app工程生成出来apk目录及文件拷贝到本地下载目录
task('copyApk') {
// doLast中会在gradle执行阶段执行
doLast {
// gradle的执行阶段去执行
def sourcePath = this.buildDir.path + '/outputs/apk'
def destinationPath = '/Users/xxx/Downloads'
def command = "mv -f ${sourcePath} ${destinationPath}"
// exec块代码基本是固定的
exec {
try {
executable 'bash'
args '-c', command
println 'the command is executed success.'
}catch (GradleException e){
println 'the command is executed failed.'
}
}
}
}
复制代码
Task定义的方法很简单,建立的方式主要为两种: * 一种迭代声明task任务以及doLast,doFirst方法添加可执行代码; * 一种是经过 “<<” 快捷建立task任务,闭合执行任务代码。但不只限于这两种。编程
TaskContainer:管理全部的Task,如:增长、查找。api
定义(建立)Task数组
// 直接经过task函数去建立
task helloTask {
println 'i am helloTask.'
}
// 经过TaskContainer去建立
this.tasks.create(name: 'helloTask2') {
println 'i am helloTask 2.'
}
复制代码
配置Task缓存
// 给Task指定分组与描述
task helloTask(group: 'study', description: 'task study'){ // 语法糖
...
}
task helloTask {
group 'study' // 或者setGroup('study')
description 'task study' // 或者setDescription('task study')
...
}
复制代码
Task除了能够配置group、description外,还能够配置name、type、dependsOn、overwrite、action。bash
Gradle的执行阶段执行的都是Task,即只有Task可在执行阶段执行。
// 1. task代码块内部使用
task helloTask {
println 'i am helloTask.'
doFirst {
println 'the task group is: ' + group
}
// doFirst、doLast能够定义多个
doFirst {}
}
// 2. 外部指定doFirst(会比在闭包内部指定的doFirst先执行)
helloTask.doFirst {
println 'the task description is: ' + description
}
// 统计build执行时长
def startBuildTime, endBuildTime
this.afterEvaluate { Project project ->
// 经过taskName找到指定的Task
def preBuildTask = project.tasks.getByName('preBuild') // 执行build任务时,第一个被执行的Task
// 在preBuildTask这个task执行前执行
preBuildTask.doFirst {
startBuildTime = System.currentTimeMillis()
}
def buildTask = project.tasks.getByName('build') // 执行build任务时,最后一个被执行的Task
// 在buildTask这个task执行后执行
buildTask.doLast {
endBuildTime = System.currentTimeMillis()
println "the build time is: ${endBuildTime - startBuildTime}"
}
}
复制代码
task taskX {
doLast {
println 'taskX'
}
}
task taskY {
doLast {
println 'taskY'
}
}
// 方式一:静态依赖
// task taskZ(dependsOn: taskY) // 依赖一个task
task taskZ(dependsOn: [taskX, taskY]) { // 依赖多个task,须要用数组[]表示
doLast {
println 'taskZ'
}
}
// 方式二:静态依赖
taskZ.dependsOn(taskX, taskY)
// 方式三:动态依赖
task taskZ() {
dependsOn this.tasks.findAll {
// 依赖全部以lib开头的task
task -> return task.name.startsWith('lib')
}
doLast {
println 'taskZ'
}
}
// lib开头task
task lib1 << { println 'lib1' }
task lib2 << { println 'lib2' }
task lib3 << { println 'lib3' }
注:此处 << 为快捷建立task,闭包里代码等同于在doLast闭包中执行同样,但此写法目前已被标记为deprecated
复制代码
// 例子:将每一个版本信息,保存到指定的release.xml中
ext {
versionCode = '1.0.0'
versionName = '100'
versionInfo = 'App的第1个版本,完成聊天功能'
destFile = file('release.xml')
if (destFile != null && !destFile.exists()) {
destFile.createNewFile()
}
}
// writeTask输入扩展属性,输出文件
task writeTask {
// 为task指定输入
inputs.property('versionCode', this.versionCode)
inputs.property('versionName', this.versionName)
inputs.property('versionInfo', this.versionInfo)
// 为task指定输出
outputs.file this.destFile
doLast {
def data = inputs.getProperties() // 返回一个map
File file = outputs.getFiles().getSingleFile()
// 将map转为实体对象
def versionMsg = new VersionMsg(data)
def sw = new StringWriter()
def xmlBuilder = new groovy.xml.MarkupBuilder(sw)
if (file.text != null && file.text.size() <= 0) { // 文件中没有内容
// 实际上,xmlBuilder将xml数据写入到sw中
xmlBuilder.releases { // <releases>
release { // <releases>的子节点<release>
versionCode(versionMsg.versionCode)
// <release>的子节点<versionCode>1.0.0<versionCode>
versionName(versionMsg.versionName)
versionInfo(versionMsg.versionInfo)
}
}
// 将sw里的内容写到文件中
file.withWriter { writer ->
writer.append(sw.toString())
}
} else { // 已经有其它版本信息了
xmlBuilder.release {
versionCode(versionMsg.versionCode)
versionName(versionMsg.versionName)
versionInfo(versionMsg.versionInfo)
}
def lines = file.readLines()
def lengths = lines.size() - 1
file.withWriter { writer ->
lines.eachWithIndex { String line, int index ->
if (index != lengths) {
writer.append(line + '\r\n')
} else if (index == lengths) {
writer.append(sw.toString() + '\r\n')
writer.append(line + '\r\n')
}
}
}
}
}
}
// readTask输入writeTask的输出文件
task readTask {
inputs.file destFile
doLast {
def file = inputs.files.singleFile
println file.text
}
}
task taskTest(dependsOn: [writeTask, readTask]) {
doLast {
println '任务执行完毕'
}
}
class VersionMsg {
String versionCode
String versionName
String versionInfo
}
复制代码
经过执行 gradle taskTask 以后,就能够在工程目录下看到release.xml文件了。task taskX {
doLast {
println 'taskX'
}
}
task taskY {
// shouldRunAfter taskX
mustRunAfter taskX
doLast {
println 'taskY'
}
}
task taskZ {
mustRunAfter taskY
doLast {
println 'taskZ'
}
}
复制代码
经过执行 gradle taskY taskZ taskX 以后,能够看到终端仍是按taskX、taskY、taskZ顺序执行的。this.afterEvaluate { Project project ->
def buildTask = project.tasks.getByName('build')
if (buildTask == null) throw GradleException('the build task is not found')
buildTask.doLast {
taskZ.execute()
}
}
复制代码
TinkerManifestTask manifestTask = project.tasks.create("tinkerProcess${variantName}Manifest", TinkerManifestTask)
...
manifestTask.mustRunAfter variantOutput.processManifest
variantOutput.processResources.dependsOn manifestTask
复制代码
settings.gradle(对应Settings.java)决定哪些工程须要被gradle处理,占用了整个gradle生命周期的三分之一,即Initialzation初始化阶段。
Gradle有一个约定的目录结构,格式和maven的结构同样。但不一样的是,gradle的目录结构是能够改的。对默认的文件位置进行修改,从而让gradle知道哪一种资源要从哪些文件夹中去查找。
// 1. sourceSets是能够调用屡次的
android {
sourceSets {
main {
// 配置jni so库存放位置
jniLibs.srcDirs = ['libs']
}
}
sourceSets {
main {
// 根据模块配置不一样的资源位置
res.srcDirs = ['src/main/res', // 普通资源目录
'src/main/res-ad', // 广告资源目录
'src/main/res-player'] // 播放器相关资源目录
}
}
}
// 2. sourceSets通常状况下是一次性配置
android {
sourceSets {
main {
jniLibs.srcDirs = ['libs']
res.srcDirs = ['src/main/res',
'src/main/res-ad',
'src/main/res-player']
}
}
}
// 3. 使用编程的思想,配置sourceSets
this.android.sourceSets{
main {
jniLibs.srcDirs = ['libs']
res.srcDirs = ['src/main/res',
'src/main/res-ad',
'src/main/res-player']
}
}
复制代码
Gradle中的Plugin是对完成指定功能的Task封装的体现,只要工程依赖了某个Plugin,就能执行该Plugin中全部的功能,如:使用java插件,就能够打出jar包,使用Android插件,就能够生成apk、aar。
建立插件工程
apply plugin: 'groovy'
sourceSets {
main {
groovy {
srcDir 'src/main/groovy'
}
resources {
srcDir 'src/main/resources'
}
}
}
复制代码
最后,Async一下工程,buildSrc就会被识别出来了,总体目录如图:E:\CodeProject\android\Github\JcyDemoList\SourceCodeAnalysis\src\源码分析\图示讲解\Gradle自定义Plugin.png建立插件类: 与Java同样,在groovy目录下,建立一个包,再建立一个插件类(如:com.android.gradle.GradleStudyPlugin),该插件类必须实现Plugin接口。
注意:gradle插件类是.groovy文件,不是.java文件
import org.gradle.api.Plugin
import org.gradle.api.Project
/**
* 自定义Gradle插件
*/
class GradleStudyPlugin implements Plugin<Project> {
/**
* 插件引入时要执行的方法
* @param project 引入当前插件的project
*/
@Override
void apply(Project project) {
println 'hello gradle study plugin. current project name is ' + project.name
}
}
复制代码
指定插件入口: 在编写完插件类的逻辑以后,须要在META-INF.gradle-plugins目录下建立一个properties文件(建议以插件类包名来命名,如:com.android.gradle.properties),在该properties中声明插件类,以此来指定插件入口。
该properties文件的名字将做为当前gradle插件被app工程引用的依据。
implementation-class=com.android.gradle.GradleStudyPlugin
// 若是报错 Could not find implementation class 'xxx' 的话,
// 通常是类全路径有问题,默认包不须要写包路径,修改以下便可:implementation-class=GradleStudyPlugin
复制代码
使用自定义插件: 打开app工程的build.gradle,应用上面的自定义gradle插件,并Async。
apply plugin: 'com.android.application'
apply plugin: 'com.android.gradle'
android {
...
}
复制代码
在Terminal中能够看到,在gradle的配置阶段,就输出了前面自定义插件的apply方法中的日志。
建立扩展属性: 插件每每会在gradle脚本中进行参数配置,如在android{}中,能够配置compileSdkVersion等参数,其实本质上,就是在gradle脚本中使用闭包方式建立了一个javaBean,并将其传递到插件中被插件识别读取而已。
步骤:
class ReleaseInfoExtension {
String versionCode
String versionName
String versionInfo
String fileName
ReleaseInfoExtension() {}
@Override
String toString() {
return "versionCode = ${versionCode} , versionName = ${versionName} ," +
" versionInfo = ${versionInfo} , fileName = ${fileName}"
}
}
复制代码
class GradleStudyPlugin implements Plugin<Project> {
/**
* 插件引入时要执行的方法
* @param project 引入当前插件的project
*/
@Override
void apply(Project project) {
// 这样就能够在gradle脚本中,经过releaseInfo闭包来完成ReleaseInfoExtension的初始化。
project.extensions.create("releaseInfo", ReleaseInfoExtension)
}
}
复制代码
apply plugin: 'com.android.gradle'
releaseInfo {
versionCode = '1.0.0'
versionName = '100'
versionInfo = '第一个app信息'
fileName = 'release.xml'
}
复制代码
def versionCodeMsg = project.extensions.releaseInfo.versionCode
复制代码
建立扩展Task: 自定义插件无非就是封装一些经常使用Task,因此,扩展Task才是自定义插件的最重要的一部分。扩展Task也很简单,继承DefaultTask,编写TaskAction注解方法。
// 例子:把app版本信息写入到xml文件中
import groovy.xml.MarkupBuilder
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction
class ReleaseInfoTask extends DefaultTask {
ReleaseInfoTask() {
group 'android' // 指定分组
description 'update the release info' // 添加说明信息
}
/**
* 使用TaskAction注解,可让方法在gradle的执行阶段去执行。
* doFirst其实就是在外部为@TaskAction的最前面添加执行逻辑。
* 而doLast则是在外部为@TaskAction的最后面添加执行逻辑。
*/
@TaskAction
void doAction() {
updateInfo()
}
private void updateInfo() {
// 获取gradle脚本中配置的参数
def versionCodeMsg = project.extensions.releaseInfo.versionCode
def versionNameMsg = project.extensions.releaseInfo.versionName
def versionInfoMsg = project.extensions.releaseInfo.versionInfo
def fileName = project.extensions.releaseInfo.fileName
// 建立xml文件
def file = project.file(fileName)
if (file != null && !file.exists()) {
file.createNewFile()
}
// 建立写入xml数据所须要的类。
def sw = new StringWriter();
def xmlBuilder = new groovy.xml.MarkupBuilder(sw)
// 若xml文件中没有内容,就多建立一个realease节点,并写入xml数据
if (file.text != null && file.text.size() <= 0) {
xmlBuilder.releases {
release {
versionCode(versionCodeMsg)
versionName(versionNameMsg)
versionInfo(versionInfoMsg)
}
}
file.withWriter { writer ->
writer.append(sw.toString())
}
} else { // 若xml文件中已经有内容,则在原来的内容上追加。
xmlBuilder.release {
versionCode(versionCodeMsg)
versionName(versionNameMsg)
versionInfo(versionInfoMsg)
}
def lines = file.readLines()
def lengths = lines.size() - 1
file.withWriter { writer ->
lines.eachWithIndex { String line, int index ->
if (index != lengths) {
writer.append(line + '\r\n')
} else if (index == lengths) {
writer.append(sw.toString() + '\r\n')
writer.append(line + '\r\n')
}
}
}
}
}
}
复制代码
与建立扩展属性同样,扩展Task也须要在project中建立注入。
/**
* 自定义Gradle插件
*/
class GradleStudyPlugin implements Plugin<Project> {
/**
* 插件引入时要执行的方法
* @param project 引入当前插件的project
*/
@Override
void apply(Project project) {
// 建立扩展属性
// 这样就能够在gradle脚本中,经过releaseInfo闭包来完成ReleaseInfoExtension的初始化。
project.extensions.create("releaseInfo", ReleaseInfoExtension)
// 建立Task
project.tasks.create("updateReleaseInfo", ReleaseInfoTask)
}
}
复制代码
再次Async工程以后,就能够在Idea的gradle标签里android分组中看到自定义好的Task了。
注:这种在工程下直接建立buildSrc目录编写的插件,只能对当前工程可见,因此,若是须要将咱们自定义好的grdle插件被其余工程所使用,则须要单首创建一个库工程,并建立如buildSrc目录下全部的文件,最后上传maven仓库便可
Demo请参考:github.com/Endless5F/J…
this.afterEvaluate {
this.android.applicationVariants.all { variant ->
def output = variant.outpus.first() // 获取变体输出文件(outputs返回是一个集合,但只有一个元素,即输出apk的file)
def apkName = "app-${variant.baseName}-${variant.versionName}.apk"
output.outputFile = new File(output.outputFile.parent, apkName)
}
}
复制代码
Jenkins是一个开源的、提供友好操做界面的持续集成(CI)工具,起源于Hudson(Hudson是商用的),主要用于持续、自动的构建/测试软件项目、监控外部任务的运行(这个比较抽象,暂且写上,不作解释)。Jenkins用Java语言编写,可在Tomcat等流行的servlet容器中运行,也可独立运行。一般与版本管理工具(SCM)、构建工具结合使用。经常使用的版本控制工具备SVN、GIT,构建工具备Maven、Ant、Gradle。
具体学习请参考:Jenkins详细教程
...
注:如有什么地方阐述有误,敬请指正。期待您的点赞哦!!!