显示导航

Grails 多项目构建

学习如何在 Grails 应用程序中利用 Gradle 功能来创建多项目构建

作者:Sergio del Amo

Grails 版本 4.0.1

1 Grails 培训

Grails 培训 - 由创建并积极维护 Grails 框架人员开发并交付!.

2 入门

在本指南中,您将从头开始创建一个多项目构建。我们将组合一个 Grails 应用、一个 Grails 插件和一个简单的 Groovy Lib。

2.1 所需材料

要完成本指南,您需要:

  • 一些时间

  • 合适的文本编辑器或 IDE

  • 安装了 JDK 1.8 或更高版本,并适当地配置了 JAVA_HOME

2.2 如何完成指南

要开始操作,请执行以下操作:

Grails 指南代码库包含两个文件夹

  • initial 初始项目。通常是包含一些附加代码以让您领先一步的简单 Grails 应用。

  • complete 完成后的示例。这是完成指南中介绍的步骤并将这些更改应用于 initial 文件夹的结果。

要完成指南,请转到 initial 文件夹

  • cdgrails-guides/grails-multi-project-build/initial

并按照下一部分中的说明进行操作。

如果您 cdgrails-guides/grails-multi-project-build/complete,则可以转到完成的示例

3 编写应用程序

Grails 使用 Gradle Build System 来执行编译、运行测试和生成项目二进制发行版等构建相关任务。

Gradle 对多项目构建的强大支持是 Gradle 的独特卖点之一。

Gradle 中的多项目构建由一个根项目和一个或多个子项目组成,这些子项目也可能有子项目。

3.1 概述

我们要创建一个应用程序,该应用程序将 Grails App 构建与 web 配置文件、具有 plugin 配置文件的 Grails 插件和一个简单的 Groovy 库相结合。

grails multi project build diagramm

3.2 创建项目

创建根项目

$ mkdir multiproject
$ cd multiproject

使用 web 配置文件创建 Grails App

multiproject$ mkdir app
multiproject$ cd app
multiproject/app$ grails
grails> create-app --profile web --inplace
| Application created at multiproject/app
| Resolving Dependencies. Please wait…
CONFIGURE SUCCESSFUL
Total time: 12.262 secs
grails> exit
multiproject/app$ cd ..

使用 plugin 配置文件创建 Grails 插件

multiproject$ mkdir plugin
multiproject$ cd plugin
multiproject/plugin$ grails
grails> create-app --profile plugin --inplace
| Application created at multiproject/plugin
| Resolving Dependencies. Please wait…
CONFIGURE SUCCESSFUL
Total time: 12.262 secs
grails> exit
multiproject/plugin$ cd ..

创建 Groovy 库

multiproject$ mkdir groovylib
multiproject$ cd groovylib
multiproject/groovylib$ touch build.gradle

为 Groovy 库创建一个构建文件

groovylib/build.gradle
plugins {
    id 'groovy'
}

repositories {
    jcenter()
}

dependencies {
    implementation 'org.codehaus.groovy:groovy-all:2.5.8'
}

将 Gradle 文件移动到根项目

multiproject$ mv app/gradlew .
multiproject$ mv app/gradlew.bat .
multiproject$ mv app/gradle.properties .
multiproject$ mv app/gradle .

将项目添加到 settings.gradle

settings.gradle
include 'app'
include 'plugin'
include 'groovylib'

删除不必要的文件

清理插件文件夹

multiproject$ rm plugin/gradlew
multiproject$ rm plugin/gradlew.bat
multiproject$ rm plugin/grailsw.bat
multiproject$ rm plugin/grailsw
multiproject$ rm plugin/grailsw.bat
multiproject$ rm plugin/grails-wrapper.jar
multiproject$ rm plugin/settings.gradle
multiproject$ rm plugin/gradle.properties
multiproject$ rm -rf plugin/gradle
multiproject$ rm plugin/.gitignore

清理应用程序文件夹

multiproject$ rm app/grailsw
multiproject$ rm app/grailsw.bat
multiproject$ rm app/grails-wrapper.jar
multiproject$ rm app/settings.gradle
multiproject$ rm app/.gitignore

3.3 项目的依赖项

我们想要配置以下依赖项

dependencies

应用程序依赖项

app 依赖于 plugin。你可以在 dependencies 块中表示它,如下所示

app/build.gradle
dependencies {
    ...
    compile project(':plugin')
}

或使用

app/build.gradle
grails {
    plugins {
        compile project(':plugin')
    }
}

插件依赖项

plugin 依赖于 groovylib

plugin/build.gradle
dependencies {
  ...
    compile project(':groovylib')
}

3.4 在 IDE 中打开项目

可以通过告诉 IDE 打开 multiproject 文件夹,使用 IDE(例如 IntelliJ IDEA)打开 Grails 多项目构建。

导入项目后,你会看到窗口

grails multi project build ide

3.5 多个项目中的代码

我们将代码添加到不同的项目,以验证多项目构建是否正常工作。

codedependencies
app/grails-app/controllers/app/PersonController.groovy
package app

import demo.RandomPersonService
import groovy.transform.CompileStatic

@CompileStatic
class PersonController {
    RandomPersonService randomPersonService
    def index() {
        render randomPersonService.randomOciPersonName()
    }
}
plugin/grails-app/services/demo/RandomPersonService.groovy
package demo

import groovy.transform.CompileStatic

@CompileStatic
class RandomPersonService {

    String randomOciPersonName() {
        List<String> people = OCI.PEOPLE
        Collections.shuffle(people)
        people.first()
    }
}
groovylib/src/main/groovy/demo/OCI.groovy
package demo

import groovy.transform.CompileStatic

@CompileStatic
class OCI {
    public static final List<String> PEOPLE = [
            'Ryan',
            'Jeff',
            'Paul',
            'Søren',
            'Sergio'
    ]
}

为了验证我们的应用程序,我们使用 Rest Client Builder Grails 插件。将插件添加到我们的 app 依赖项

app/build.gradle
testCompile "io.micronaut:micronaut-http-client"
app/src/integration-test/groovy/app/PersonControllerSpec.groovy
package app

import demo.OCI
import grails.testing.mixin.integration.Integration
import grails.testing.spock.OnceBefore
import io.micronaut.http.HttpRequest
import io.micronaut.http.client.HttpClient
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification

@Integration
class PersonControllerSpec extends Specification {

    @Shared
    @AutoCleanup
    HttpClient client

    @OnceBefore
    void init() {
        String baseUrl = "http://localhost:$serverPort"
        this.client = HttpClient.create(new URL(baseUrl))
    }

    def '/person endpoints return one of the names'() {
        when:
        String text = client.toBlocking().retrieve(HttpRequest.GET('/person'), String)

        then:
        OCI.PEOPLE.contains text
    }
}

3.6 保持构建简洁

如果比较 app/build.gradleplugin/build.gradle,你会看到很多重复。为了消除重复,我们将添加 ROOT build.gradle

build.gradle
buildscript {
    repositories {
        mavenLocal()
        maven { url "https://repo.grails.org/grails/core" }
    }
    dependencies {
        classpath "org.grails:grails-gradle-plugin:$grailsVersion"
        classpath "com.bertramlabs.plugins:asset-pipeline-gradle:$assetPipelineVersion"
        classpath "org.grails.plugins:hibernate5:7.0.0"
        classpath "gradle.plugin.com.github.erdi.webdriver-binaries:webdriver-binaries-gradle-plugin:2.1"

    }
}

ext {
    grailsApps = ['app']
    grailsPlugins = ['plugin']
}

subprojects { project ->
    boolean isGrailsApp = grailsApps.contains(project.name)
    boolean isGrailsPlugin = grailsPlugins.contains(project.name)
    boolean isGrailsProject = isGrailsApp || isGrailsPlugin

    if ( isGrailsProject ) {
        apply plugin:"eclipse"
        apply plugin:"idea"

        if ( isGrailsApp ) {
            apply plugin:"war"
            apply plugin:"org.grails.grails-web"
            apply plugin:"org.grails.grails-gsp"
            apply plugin:"com.bertramlabs.asset-pipeline"
            apply plugin:"com.github.erdi.webdriver-binaries"
        }

        if ( isGrailsPlugin ) {
            apply plugin:"org.grails.grails-plugin"
            apply plugin:"org.grails.grails-plugin-publish"
        }

        repositories {
            mavenLocal()
            maven { url "https://repo.grails.org/grails/core" }
            maven {
                url "https://oss.sonatype.org/content/repositories/snapshots/"
                content {
                    includeVersionByRegex('io\\.micronaut.*', '.*', '.*BUILD-SNAPSHOT')
                }
            }

        }

        dependencies {
            compile "org.springframework.boot:spring-boot-starter-logging"
            compile "org.springframework.boot:spring-boot-autoconfigure"
            compile "org.grails:grails-core"
            console "org.grails:grails-console"
        }

        if ( isGrailsApp ) {
            configurations {
                developmentOnly
                runtimeClasspath {
                    extendsFrom developmentOnly
                }
            }

            dependencies {
                developmentOnly("org.springframework.boot:spring-boot-devtools")
                compile "org.springframework.boot:spring-boot-starter-actuator"
                compile "org.springframework.boot:spring-boot-starter-tomcat"
                compile "org.grails:grails-dependencies"
                compile "org.grails:grails-web-boot"
                compile "org.grails.plugins:cache"
                compile "org.grails.plugins:scaffolding"
                compile "org.grails.plugins:hibernate5"
                compile "org.hibernate:hibernate-core:5.4.0.Final"
                compile "io.micronaut:micronaut-inject-groovy"
                profile "org.grails.profiles:web"
                runtime "org.glassfish.web:el-impl:2.1.2-b03"
                runtime "com.h2database:h2"
                runtime "org.apache.tomcat:tomcat-jdbc"
                runtime "com.bertramlabs.plugins:asset-pipeline-grails:$assetPipelineVersion"
                testCompile "org.grails:grails-gorm-testing-support"
                testCompile "org.grails:grails-web-testing-support"
            }
            apply from: "${rootProject.projectDir}/gradle/geb.gradle"
        }

        if ( isGrailsPlugin ) {
            dependencies {
                profile "org.grails.profiles:plugin"
                provided "org.grails:grails-plugin-services"
                provided "org.grails:grails-plugin-domain-class"
                testCompile "org.grails:grails-gorm-testing-support"
            }
        }

        bootRun {
            jvmArgs(
                    '-Dspring.output.ansi.enabled=always',
                    '-noverify',
                    '-XX:TieredStopAtLevel=1',
                    '-Xmx1024m')
            sourceResources sourceSets.main
        }

        if (isGrailsApp) {
            webdriverBinaries {
                chromedriver "$chromeDriverVersion"
                geckodriver "$geckodriverVersion"
            }

            tasks.withType(Test) {
                systemProperty "geb.env", System.getProperty('geb.env')
                systemProperty "geb.build.reportsDir", reporting.file("geb/integrationTest")
                systemProperty "webdriver.chrome.driver", System.getProperty('webdriver.chrome.driver')
                systemProperty "webdriver.gecko.driver", System.getProperty('webdriver.gecko.driver')
            }
        }

        if ( isGrailsPlugin ) {
            // enable if you wish to package this plugin as a standalone application
            bootJar.enabled = false
            grailsPublish {
                // TODO: Provide values here
                user = 'user'
                key = 'key'
                githubSlug = 'foo/bar'
                license {
                    name = 'Apache-2.0'
                }
                title = "My Plugin"
                desc = "Full plugin description"
                developers = [johndoe:"John Doe"]
                portalUser = ""
                portalPassword = ""
            }
        }
    }
}

`app` 和 `plugin` gradle 文件非常简单

app/build.gradle
version "0.1"
group "app"
dependencies {
    testCompile "io.micronaut:micronaut-http-client"
}
grails {
    plugins {
        compile project(':plugin')
    }
}
plugin/build.gradle
version "0.1"
group "plugin"
dependencies {
    compile project(':groovylib')
}

3.7 功能作为 Gradle 文件

保持构建简洁的另一种方法是为不同功能创建特定的 Gradle 文件。

假设你在多项目构建的多个项目中有 Geb 测试。

我们可以封装一组依赖项,以运行 针对多个浏览器的 Geb 测试

gradle/geb.gradle
dependencies {

    testCompile "org.grails.plugins:geb"
    testRuntime "org.seleniumhq.selenium:selenium-chrome-driver:$seleniumVersion"
    testRuntime "org.seleniumhq.selenium:selenium-firefox-driver:$seleniumVersion"
    testCompile "org.seleniumhq.selenium:htmlunit-driver:2.35.1"
    testRuntime 'net.sourceforge.htmlunit:htmlunit:2.35.0'
    testCompile "org.seleniumhq.selenium:selenium-remote-driver:$seleniumVersion"
    testCompile "org.seleniumhq.selenium:selenium-api:$seleniumVersion"
    testCompile "org.seleniumhq.selenium:selenium-support:$seleniumVersion"

}

然后,你可以将这些依赖项应用于特定项目

app/build.gradle
apply from: "${rootProject.projectDir}/gradle/geb.gradle"

4 运行应用程序

要运行应用程序

./gradlew app:bootRun

要运行测试

./gradlew check

5 你需要关于 Grails 的帮助吗?

对象计算公司(OCI)赞助创建本指南。各种咨询和支持服务均可使用。

OCI 是 Grails 的家

认识 Grails 团队