阅读 1896

Gradle 构建学习(一)---------详解 Project

对project 的 误解

例如 我们打开一个android 工程

很多人可能都会觉得 如图所示, androidGradleMainP 才是project,下面的东西就全部是module。 其实这种想法是完全错误的。

我们可以输入一个命令:

看下他的执行结果:

这里可以很清晰的看出来 对于gradle而言 这些东西 都是一个个的project, 而且是一个树型的结构。

代码 模拟 projects 命令

搞清楚上面的 概念 ,我们可以尝试自己来做 ,比如我们在root project 的build 文件中 写一个函数 打印一下 我的全部project

this.getMyAllProjects()

def getMyAllProjects() {
    this.getAllprojects().eachWithIndex { Project p, int index ->
        if (index == 0) {
            println("my root project:" + p.name)
        } else {
            println("my sub project:" + p.name)
        }
    }
}
复制代码

看下执行结果:

有了这个结果 我们可以想一下,是不是 子project 可以 获取到 父project ,父project 也可以获取到 子project的信息呢?

我们可以在根project 下面的build 文件中 写一段代码尝试一下 :

this.getMySubProjects()

def getMySubProjects() {
    //this.childProjects()
    this.getChildProjects().eachWithIndex { Map.Entry<String, Project> entry, int i ->
        println("i==" + i +"    "+ entry.key + "___" + entry.value)
    }
}
复制代码

然后看下执行结果:

嗯 果然是可以取到子project的。 反过来想, 我们再试试看 能不能在子project中 取到 父project 的信息?

this.getMyParentProjectInfo()

def getMyParentProjectInfo() {
    //this.childProjects()
    println("-----"+this.project.name +"---    getParent:" + getParent().name + "    getRootParent:" + getRootProject().name)
}
复制代码

看下执行结果:

project 相关的 公共配置

了解清楚 gradle中的 project概念以后,我们可做的事情就很多了。

比如说 我们可以在root project 中 取到我们的子project 并且设置他们的属性,

subprojects { Project project->
    group("com.wuyue")
}

复制代码

然后我们在root project 中 把我们的子project 的group都打印出来看看,

def getMyAllProjects() {
    this.getAllprojects().eachWithIndex { Project p, int index ->
        if (index != 0) {
            println("my sub project:" + p.group)
        }
    }
}
复制代码

看下执行结果;

这个地方要注意了,我的子project 中 是没有 设置 group属性的。这说明什么? 说明 我们可以通过gradle 来修改我们任意project的 配置,什么是project的配置? 当然就是那个project下面对应的build文件了。

换句话说, 你在build文件中的 所有代码 都可以写在 我们对应project中的闭包里。

//指定全部project
subprojects { Project project ->。
    apply plugin: 'maven'

    dependencies {

    }


}
//指定某个project
project("mylibrary") {
    apply plugin: 'maven'

    dependencies {

    }
}
复制代码

搞明白这个 ,我们就可以实现很多不同的project配置,比如组件化或者插件化中, 我们单独跑module的时候是 apply android application, 当作为源码引入到其他project的时候 就 被修改成apply plugin android library。 其原理就是这个。

大家可以根据gradle project的特性 思考一下 自己的工程中 有没有可以修改的地方了。

project中的属性

这个地方可以看一下源码:

其实我截图的这部分 就已经是gradle 中的project属性了。 可以看出来 这些属性确实不多,甚至很少。设计者 显然也考虑到这个问题 所以 我们其实可以用ext 开扩展project中的属性。

看下面这个图

正常情况下,这种build文件 已经很常见了,包括我们其实android studio 默认新增一个module的时候 也是给出这样的范例, 但是我们写代码的人都知道 一般情况下 我们都不会 直接在代码中 使用数字,也就是magic number,这是一种很不好的编程习惯。

那么这里可以怎么改呢?比方说 我们可以定义一个变量

这样写 行不行呢,也是可以的,但是这样写 其实也有不好的地方 因为你是一个变量, 所以其他module 并不能很方便读到你的值。 如果有很多module的话,给每个module 都维护一个 变量 那就真的太蠢了。 所以这种时候 我们就要 利用 project的扩展属性了

在根工程 定义个扩展属性 并且 赋值给 他的所有子project

然后子project 直接 使用即可

到这里其实也没结束 因为 这里的subproject 其实是为每一个子project 都创建了一个ext 属性,感觉浪费了,毕竟是个for 循环 重复创建了相同的对象。

所以 我们可以考虑 将这个属性 直接放在 root project中,并且让子project 直接读取父project的 属性即可。

看下子project 怎么读取

这样 基本上就差不多了, 当然做的极限的话 还可以把你们的公共属性 都定义到一个单独的gradle文件中 然后其余子project 直接 apply from 这个单独的 gradle 文件就可以了。

文件属性

这一小节 我们来学习下文件相关的属性

看一下执行结果:

这是gradle编程中 经常使用的 几个文件api。很好理解,大家自行体会一下 就行。

比如说这种情况,我想在root project中 读取 与 root project 的build 文件中 处于同一路径下的 某个文件的值 应该怎么读? 其实只要直接传一个文件名就可以了

def findFile(String filePath) {
    try {
        //file 这个函数 可以接收与这个gradle文件 处于同一个路径下的 文件名
        def file = file(filePath)
        return file.text
    } catch (GradleException e) {
        println("file not found ")
    }

}

println("--------------------------")
println(findFile("local.properties"))
println("--------------------------")
复制代码

可以看下执行结果

注意了,如果是new file 这个操作 那就是需要传绝对路径了。

再看下 copy, 拷贝文件 也是平时写gradle 插件中经常用的

另外 我们甚至可以拷贝一整个文件夹. 传递对应的路径即可, 同时也可以在这个copy的必包中 使用exclue 来排除某些文件。

比方说 我们可以拷贝我们app build 目录下的 apk 到我们的root project中

这个时候 就可以利用我们的fileTree函数,因为所有文件系统都是树形结构。通过文件树可以很方便的对文件夹进行操作

fileTree('build/outputs/apk/') { FileTree fileTree ->
    fileTree.visit { FileTreeElement fileTreeElement ->
        println("fileTreeElement name:" + fileTreeElement.name)
        copy{
            from fileTreeElement.file
            into getRootDir().path+"/justDir/"
        }
    }

}
复制代码

buildScript api

大家可以看到 这个buildScript 下面主要是2个闭包,第一个是 仓库,第二个是依赖。

我们首先来看看 这个仓库的闭包。理解他 对我们后续的学习很有帮助, 千万不要以为 在网上搜了一些代码 复制过来就算你理解了。

下图可以看出来 我们的buildScript 的闭包其实就是一个 Scripthandler

再看看这个repos handler 到底是个啥?

其实看下左边这些方法也就一目了然了, 这也就是为啥 repos 那个闭包里面 有些选项是闭包 而有些选项就是一个函数。

还不明白的话,我可以换一种写法大家就理解了

另外在bs 中还有我们比较熟悉的

注意了 这里的依赖 和 我们android module 中的depen 依赖是不同的, 这里的依赖 是提供给gradle使用的,而android module中的依赖 是提供给 android工程用的,不要搞混了。有兴趣的同学 可以按照上述 对repos 分析的方法 去分析 这2种依赖的不同。 这里就不再浪费笔墨了。

使用命令

有时候,我们会让gradle 来执行一些操作系统命令,尤其是配置了诸如 jekins 等cicd 持续集成系统的时候。 这里也给出一个简单的例子,来示范一下 如何在gradle中 使用命令

task('deletejustDir') {
    doLast {
        def path = this.rootProject.projectDir.path + '/justDir'
        def command = "rm -r ${path}"
        println("command:" + command)
        exec {
            try {
                executable 'bash'
                args '-c', command
                println("the command is execute success")
            } catch (GradleException e) {
                println("the command is execute failed")
            }

        }
    }
}
复制代码