Agenda

This tutorial describes how a Griffon application can be build without using the griffon Gradle plugin. It also shows that how all source and resources directories can be condensed into a minimum set of directories.

1. Directory Layout

Griffon relies on Lazybones templates in order to boostrap a brand new project or create additional artifacts. However this does not mean that Griffon projects must be created using the templates; as a matter of fact is possible to create a project by conventional Gradle means, or even by hand. The project doesn’t even have to comply with the standard Griffon structure, it can use the standard Java/Groovy project layout that Gradle understands. The following listing shows this layout, with all sources for a simple application already inside their respective directories

.
├── build.gradle                                                 (1)
├── gradle                                                       (2)
│   ├── functional-test.gradle
│   ├── integration-test.gradle
│   ├── javafx-plugin.gradle
├── gradle.properties
└── src
    ├── functional-test                                          (7)
    │   └── java
    │       └── org
    │           └── example
    │               └── SampleFunctionalTest.java
    ├── integration-test                                         (6)
    │   └── java
    │       └── org
    │           └── example
    │               └── SampleIntegrationTest.java
    ├── main                                                     (3)
    │   ├── java
    │   │   ├── Config.java
    │   │   └── org
    │   │       └── example
    │   │           ├── Launcher.java
    │   │           ├── SampleController.java
    │   │           ├── SampleModel.java
    │   │           ├── SampleService.java
    │   │           └── SampleView.java
    │   └── resources                                            (4)
    │       ├── META-INF
    │       │   └── griffon
    │       │       ├── griffon.core.artifact.GriffonController
    │       │       ├── griffon.core.artifact.GriffonModel
    │       │       ├── griffon.core.artifact.GriffonService
    │       │       └── griffon.core.artifact.GriffonView
    │       ├── application.properties
    │       ├── griffon-icon-128x128.png
    │       ├── griffon-icon-16x16.png
    │       ├── griffon-icon-24x24.png
    │       ├── griffon-icon-256x256.png
    │       ├── griffon-icon-32x32.png
    │       ├── griffon-icon-48x48.png
    │       ├── griffon-icon-64x64.png
    │       ├── griffon.png
    │       ├── log4j.properties
    │       ├── messages.properties
    │       ├── org
    │       │   └── example
    │       │       └── sample.fxml
    │       └── resources.properties
    └── test                                                     (5)
        └── java
            └── org
                └── example
                    ├── SampleControllerTest.java
                    └── SampleServiceTest.java
1 build file
2 additional build files
3 main sources
4 main resources
5 test sources
6 integration test sources
7 functional test sources

We begin by looking at root directory, where we find the main build file 1 paired with some additional build scripts 2 that take care of configuring this project for JavaFX, as well as setting up integration and functional tests. All application sources are located in the standard location for a Java project, that is src/main/java 3; main resources are found in their conventional location too 4. Notice that special metadata files are located there too. Test sources do follow the same conventions 5 so they are located inside src/test/java. Finally we see the sources for integration 6 and 7 functional tests, which follow the paths configured by the additional build scripts 2.

Top

2. Metadata Files

Usually Griffon projects will automatically generate metadata files associated with Griffon artifacts, thanks to the usage of the @ArtifactProviderFor annotation in combination with {link_jipsy}. But given that we did not define a dependency in provided scope for {link_jipsy} we must have to write these files by hand. The catch is that these files must be updated every time an artifact is added, renamed, or deleted.

META-INF/griffon/griffon.core.artifact.GriffonController
org.example.SampleController
META-INF/griffon/griffon.core.artifact.GriffonModel
org.example.SampleModel
META-INF/griffon/griffon.core.artifact.GriffonService
org.example.SampleService
META-INF/griffon/griffon.core.artifact.GriffonView
org.example.SampleView

Top

3. Build Files

There are a handful of ways to define project properties for a Gradle project. The cleanlest one is to define these properties on a file named gradle.properties, whose contents can be found in the next listing

gradle.properties
group               = org.example
version             = 0.1.0-SNAPSHOT
griffonVersion      = 2.11.0
sourceCompatibility = 1.8
targetCompatibility = 1.8

The next sections describe the manin build file segreggating its blocks by responsibilities, as well as the additional script files that deal with more functionality.

Top

3.1. Main

There’s little to be done in terms of plugin configuration. You need the java plugin at the very least. The other ones listed next allow you to keep an eye on dependency versions and tidy up license headers on files

Plugin configuration
plugins {
    id 'java'
    id 'idea'
    id 'com.github.ben-manes.versions' version '0.14.0'
    id 'com.github.hierynomus.license' version '0.11.0'
}

apply from: 'gradle/javafx-plugin.gradle'
apply from: 'gradle/integration-test.gradle'
apply from: 'gradle/functional-test.gradle'

Let’s have a look at the project dependencies. This being a JavaFx project means we need griffon-javafx-2.11.0.jar as a dependency. We also need an implementation for dependency injection, this is why griffon-guice-2.11.0.jar is added to the list. We round up with a concrete implementation of slf4j-api, such as slf4j-log4j12. Finally, regarding tests, we would need griffon-javafx-test-2.11.0.jar plus a few others to make writing tests easier.

Dependencies configuration
repositories {
    jcenter()
    mavenLocal()
}

dependencies {
    compile "org.codehaus.griffon:griffon-javafx:${griffonVersion}"
    compile "org.codehaus.griffon:griffon-guice:${griffonVersion}"

    runtime 'org.slf4j:slf4j-simple:1.7.25'

    testCompile "org.codehaus.griffon:griffon-javafx-test:${griffonVersion}"
    testCompile 'pl.pragmatists:JUnitParams:1.1.0'
    testCompile 'org.mockito:mockito-core:2.8.9'
}

Every Griffon application has a set of resource files; application.properties contains useful values such as the name of the application, its version, and the current Griffon version in use. These values can be obtained from the build and passed into resource files. We use Gradle’s standard mechanism for processing resources.

Resources configuration
processResources {
    from(sourceSets.main.resources.srcDirs) {
        exclude '**/*.properties'
        exclude '**/*.xml'
    }
    from(sourceSets.main.resources.srcDirs) {
        include '**/*.properties'
        include '**/*.xml'
        filter(org.apache.tools.ant.filters.ReplaceTokens, tokens: [
            'application.name'   : project.name,
            'application.version': project.version,
            'griffon.version'    : griffonVersion
        ])
    }
}

Finally, we must configure the entry point of the application so that the JavaFX plugin can find it.

JavaFX configuration
javafx {
    mainClass = 'org.example.Launcher'
}

Top

3.2. JavaFX

This file performs a check on several paths to locate the correct jfxrt.jar which is required by the JavaFX plugin. It also applies the JavaFX plugin.

gradle/javafx-plugin.gradle
buildscript {
    File javaHome = new File(System.properties['java.home'])
    javaHome = javaHome.name == 'jre' ? javaHome.parentFile : javaHome
    String jfxrtLocation = new File("${javaHome}/jre/lib/jfxrt.jar").absolutePath
    // JavaFX locations for JDK7, JDK7, JDK8
    for (location in ['lib/jfxrt.jar', 'jre/lib/jfxrt.jar', 'jre/lib/ext/jfxrt.jar']) {
        File file = new File(javaHome, location)
        if (file.exists()) {
            jfxrtLocation = file.absolutePath
            break
        }
    }

    repositories {
        jcenter()
    }
    dependencies {
        classpath 'org.bitbucket.shemnon.javafxplugin:gradle-javafx-plugin:8.1.1'
        classpath project.files("${javaHome}/lib/ant-javafx.jar")
        classpath project.files(jfxrtLocation)
    }
}

if (!project.plugins.findPlugin(org.bitbucket.shemnon.javafxplugin.JavaFXPlugin)) {
    project.apply(plugin: org.bitbucket.shemnon.javafxplugin.JavaFXPlugin)
}

Top

3.3. Integration Tests

Integration tests build up on existing configuration from regular tests. Some paths and classpaths need to be adjusted, as well as wiring up tasks dependencies, so that in the end, invoking check will also run integration-test, just like it happens with the standard test task.

gradle/integration-test.gradle
configurations {
    integrationTestCompile {
        extendsFrom testCompile
    }
    integrationTestRuntime {
        extendsFrom integrationTestCompile, testRuntime
    }
}

sourceSets {
    integrationTest {
        if (file('src/integration-test/java').exists()) {
            java.srcDirs file('src/integration-test/java')
        }
        resources.srcDir file('src/integration-test/resources')
        compileClasspath += sourceSets.main.output
        runtimeClasspath += compileClasspath
    }
}

idea {
    module {
        scopes.TEST.plus += [configurations.integrationTestCompile]
        scopes.TEST.plus += [configurations.integrationTestRuntime]
        testSourceDirs += sourceSets.integrationTest.allSource.srcDirs
    }
}

task integrationTest(type: Test, dependsOn: jar) {
    testClassesDir = sourceSets.integrationTest.output.classesDir
    classpath = sourceSets.integrationTest.runtimeClasspath
    reports.html.enabled = false
}

task integrationTestReport(type: TestReport) {
    destinationDir = file("${buildDir}/reports/integration-tests")
    reportOn integrationTest.binResultsDir
}

integrationTest.mustRunAfter test
integrationTest.finalizedBy integrationTestReport
integrationTestReport.dependsOn integrationTest
check.dependsOn integrationTestReport

Top

3.4. Functional Tests

Functional tests differ from the previous ones by not extending directly from regular tests. This assures that functional tests don’t get polluted with unit or integration concerns. This is the reason why basic test dependencies are defined explicitly here.

gradle/functional-test.gradle
configurations {
    functionalTestCompile {
        extendsFrom compile
    }
    functionalTestRuntime {
        extendsFrom runtime
    }
}

dependencies {
    functionalTestCompile 'junit:junit:4.12'
    functionalTestCompile 'pl.pragmatists:JUnitParams:1.0.6'
    functionalTestCompile "org.codehaus.griffon:griffon-javafx-test:${griffonVersion}"
}

sourceSets {
    functionalTest {
        if (file('src/functional-test/java').exists()) {
            java.srcDirs file('src/functional-test/java')
        }

        resources.srcDir file('src/functional-test/resources')
        compileClasspath += sourceSets.main.output
        runtimeClasspath += compileClasspath
    }
}

idea {
    module {
        scopes.TEST.plus += [configurations.functionalTestCompile]
        scopes.TEST.plus += [configurations.functionalTestRuntime]
        testSourceDirs += sourceSets.functionalTest.allSource.srcDirs
    }
}

task functionalTest(type: Test, dependsOn: jar) {
    testClassesDir = sourceSets.functionalTest.output.classesDir
    classpath = sourceSets.functionalTest.runtimeClasspath
    reports.html.enabled = false
}

task functionalTestReport(type: TestReport) {
    destinationDir = file("${buildDir}/reports/functional-tests")
    reportOn functionalTest.binResultsDir
}

functionalTest.mustRunAfter integrationTest
functionalTest.finalizedBy functionalTestReport
functionalTestReport.dependsOn functionalTest
check.dependsOn functionalTestReport

The full code for this application can be found here.

Top