从Groovy到编程框架——快速理解Gradle

前言

学校软件构造课实验要求将用到的lib从IDE抽离出来提交到Github上,之后助教会用自动编译脚本进行构建,为保证自己提交的文件中的lib足够,尝试从Github上将提交的文件下载下来进行构建,然而构建使用的Gradle是个陌生的东西,网络上也没有简洁明了的教程来帮助完成构建,在耗费两天时间、查阅许多资料后对Gradle做了个小小的总结,个人认为了解了这些内容后就能够看懂甚至写出软件构造课需要的Gradle了。

目录

Gradle简介

Gradle是一种基于groovy、利用JVM的构建工具,是一种领域特定语言(DSL,Domain Specific Language,专门针对特定问题的编程语言)。

Groovy

Groovy是一种与Java类似(完全支持Java语法)、基于JVM的编程语言,主要用于写脚本,下面描述他和Java的一些区别

一些语法上的区别请参考Groovy 语言快速入门 - 简书 (jianshu.com)

无需类直接运行代码

如果想要输出“Hello World”,在Java中你需要创建一个类,其中包含一个main方法:

public class Hello {
    public static void main(String[] args) {
        System.out.println("hello world");
    }
}

在Groovy中,可以和Java一样创建类然后调用,但是也可以不创建类,直接使用,调用函数的()也可以省略,同时还支持了简便的命令,如下四条语句均可输出“Hello World”

System.out.println("hello world");
System.out.println "hello world";

println("hello world")
println 'hello world'

实际上,在一个groovy脚本中,如果不定义类直接使用代码,那么编译器会自动帮你创建一个和文件名同名的类,把你的代码放入run方法,并在main方法中调用run

闭包介绍

Groovy中的闭包是一个极为重要的概念,在Gradle中有广泛的使用

闭包的功能类似于函数,由参数和内部的一些语句构成,调用闭包就相当于以参数执行这些语句。

但闭包并不是函数,闭包实际上是一个变量,可以作为对象实例的成员变量,也可以作为参数传递给其他的函数、甚至其他的闭包。

关于闭包的参数、创建以及调用可以查看这篇文章:gradle学习笔记(三) Groovy闭包_MakeGreatEffort的博客-CSDN博客

闭包的Delegation代理

闭包中默认含有三个变量,类似于指针,指向不同的对象

  • this指向闭包所在的最近的类的实例(指包含这个闭包的最小的)(注意:闭包是变量、不是函数,返回值同样是类的实例而不是类)
  • owner指向闭包所在的最近的闭包或类的实例
  • delegate是一个可变的指针,初始时默认指向owner,但可以对他进行改变以方便编写DSL代码

代理的作用之一是在当前闭包没有对应的变量之时进行访问

代理有许多种策略,默认的是OWNER_FIRST策略,优先访问owner中的变量,找不到时再去delegate中找

可以将其改为DELEGATE_FIRST,OWNER_ONLY,DELEGATE_ONLY,TO_SELF,修改时使用代码 闭包名.resolveStrategy=Closure.xxxx即可

Gradle的一些特性(Groovy DSL Primer)

Gradle实际上是一个编程框架,底部用Groovy实现,编写的Gradle脚本实际上是对API的调用

Gradle支持Multi-Project构建,即用一个命令构建多个相关的Project,而一个Project又由许多task构成。

一般来说一个文件夹包含了多个Project文件夹,gradle脚本存储在这些文件夹中

  • 对于每个Project,其文件夹内有一个build.gradle,用于构建这个项目

  • 在包含多个Project的文件夹(当前工作目录,命令行执行gradle命令的地方)下,有一个build.gradle,用于配置其他包含Project的子目录,简单理解就是多个Project共享的配置代码

  • settings.gradle,保存当前的Multi-Project包含哪些子Project,以子文件夹的名字构成

构建流程

在这里插入图片描述

构建流程主要分三步:

  • Initialization(初始化):对于Multi-Project而言就是执行settings.gradle
  • Configuration(配置):解析所有的Project中的build.gradle,相当于运行写下的所有代码,此阶段完成后Project间的依赖关系和Project内部Task的依赖关系都已经明确,构成一个Task的DAG
  • Execution(执行):按BFS序执行所有Task

三阶段中每个阶段之后都可以添加Hook,即一系列的语句

个人理解:Project中所有代码都是在为Task做配置工作

Project

Gradle把每个build.gradle文件包装成一个Project类,这个类里有许多自带的变量与(Gradle中称之为Properties)函数

Block

Block由Block名和花括号表示,但他并不是闭包,而是相当于一个函数调用,被调用的函数为和这个Block同名同时参数的最后一项为一个闭包的函数,调用时将Block后的闭包作为参数传递给与Block同名的函数

这种方式实际上就是Groovy中调用函数时可以把最后一个闭包写在函数名外并且可以省略函数调用时的()的特性。

比如下面这段代码

apply plugin: 'java'
sourceSets {
    main {
        java {
            srcDir 'src/java'
        }
}

实际上就是调用了java插件中的sourceSets函数,以后面花括号括起来的内容作为闭包作为参数,这个函数的目的是用闭包中的代码来配置sourceSets,这部分代码又通过同样的方式调用了main,对其进行配置,配置过程中又调用了java,在调用配置Java的函数时,用到的闭包设置了srcDir这个变量。

这样做将三个闭包套起来,其内部调用过程比较复杂也难以理解,但是写出来的代码却很优雅,可以看作是用一系列的花括号就完成了配置工作,并且这些花括号与实际上类的合成关系也很类似。

Task

Task是一个类,代表着一系列的工作。

每个Project可以由许多个task组成,task和task之间可以有依赖关系(即一个task必须在其他的task结束后才能开始)。Task是属于Project的,要在一个Project中添加Task,使用task函数可以进行向当前Project中添加Task。

task中有doFirst和doLast函数,分别代表在task的工作序列的前端和后端加入一些工作(可以由闭包表示)

当我们使用 task myTask{ xxx}的时候。花括号是一个closure。这会导致gradle在创建这个Task之后,返回给用户之前,会先执行closure的内容

Task 可以在创建的时候指定Type,让当前的Task从Type这个Task类中继承

插件

插件目前可以简单的理解为C语言中的#include,一般的插件都定义了一些函数和一些Task

实例

终于来到了使用Gradle构建软件构造实验的时候

此实验的目录结构如下:

在这里插入图片描述

P3的测试代码存储在test\P3下,P2的测试代码和源代码均存储在src\P2下,这就对测试的构建带来了困难。

有同学使用gradle.kts进行了类似的编写:[软构] 记一次 Gradle 的坑 - Nullptr’s Blog

下面的部分就是把他写的代码用gradle(准确的说应该是Groovy,文件名叫做build.gradle)写一遍并尝试理解

apply plugin: 'java'
version="1.0"
sourceSets{
    P1{//自定义sourceSet
        java{
            srcDir 'src/P1'//配置srcDir
        }
        task jarP1(type:Jar){//设置一个任务,用于构建P1
            archiveBaseName='P1'//设置生成打包文件的名字
            manifest{
                attributes 'Main-Class' : 'MagicSquare'//可执行程序的主类
            }
            dependsOn(runtimeClasspath)
            from(runtimeClasspath)
        }
    }
    P2{
        java{
            srcDir 'src/P2'
        }
        //P2的文件夹下含有测试源代码,直接编译无法通过,将test的目录添加到目录下,使之能通过编译
        compileClasspath += configurations.testCompileClasspath
        runtimeClasspath += configurations.testRuntimeClasspath
        task jarP2(type:Jar){
            archiveBaseName='P2'
            manifest{
                attributes 'Main-Class' : 'turtle.TurtleSoup'
            }
            dependsOn(runtimeClasspath)
            from(runtimeClasspath)
        }
    }
    P3{
        java{
            srcDir 'src/P3'
        }
        task jarP3(type:Jar){
            archiveBaseName='P3'
            manifest{
                attributes 'Main-Class' : 'FriendshipGraph'
            }
            dependsOn(runtimeClasspath)
            from(runtimeClasspath)
        }
    }
    test{
        java{
            srcDir 'test/P3'//直接将此文件夹作为测试源码文件夹
        }
        compileClasspath += P3.output
        runtimeClasspath += P3.output
    }
}

tasks["build"].dependsOn(jarP1,jarP2,jarP3)//build之前构建P1,P2,P3

java {
    sourceCompatibility = JavaVersion.VERSION_11
    targetCompatibility = JavaVersion.VERSION_11
}

repositories {
    mavenCentral()
}

dependencies { 
	implementation fileTree(dir:'lib',includes:['*jar'])//将文件目录lib下的jar包引用进来
}

然而这样编写脚本,运行gradle build之后,只能在report中看到P3的测试结果,如果想要P2的测试结果需要进一步的进行测试task的自定义,然而P3能够测试成功已经代表了lib中有了测试用足够的jar文件,由于精力有限,暂时不做测试task的自定义,留待之后进行补充。

参考资料(不只是列出写博客时看的资料,阅读博客时不明白的也可以查看)

深入理解Android之Gradle_阿拉神农的博客-CSDN博客_深入理解android之gradle

Gradle 入门–只此一篇 - 简书 (jianshu.com)

Gradle教程™ (yiibai.com)

Gradle 简介_w3cschool

Gradle User Manual

gradle学习笔记(一) 构建工具介绍_baiiu的博客-CSDN博客

gradle学习笔记(二) Groovy基础_baiiu的博客-CSDN博客

gradle学习笔记(三) Groovy闭包_MakeGreatEffort的博客-CSDN博客

[软构] 记一次 Gradle 的坑 - Nullptr’s Blog

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值