Niagara 4 contains many architecture and functionality improvements. One area Tridium is improving on is the Niagara system’s support for standard software development tools. The intent is that there will be minimal changes required in your development environment to compile modules in Niagara 4, while providing a better and more standard user experience for our Java developer customers.
One change being made is to incorporate Gradle into our build tool chain and migrate away from the proprietary build system used in Niagara AX. This should enable a more standard setup of development projects and provide more standard integration with Java IDEs (specifically Eclipse or IntelliJ).
Additional information on Gradle can be found by following the links below. It is not expected that Niagara developers become experts in Gradle, but there is a lot of information available on the web, as well as several books available for those who wish to learn more.
Gradle home page
Gradle user guide
Gradle language reference
There is some configuration required to run Gradle to compile module source code, build a module jar file, and assemble a module javadoc jar file. The Niagara build system assumes a multi-project Gradle layout; a single set of common configurations will build any number of modules. A multi-project layout consists of one “root project” and any number of child projects, referred to as “subprojects”. In general, there is a one-to-one relationship between a Gradle subproject and a Niagara module.
Each Gradle project (both root and subprojects) is defined in a Gradle build script. This file is typically called build.gradle for the root project, and projectName.gradle for subprojects. For Niagara modules, by convention, the project name is the module part name; so for the “rt” module part of the “myModule” module, the conventional Gradle project name is “myModule-rt”, and the associated Gradle build script is myModule-rt.gradle. The default Niagara build environment imposes the additional restriction that subprojects must live in a subfolder with the same name as the project. This means that myModule/myModule-rt.gradle will not be detected as a subproject; the correct path will be myModule/myModule-rt/myModule-rt.gradle For the sake of this document, references to build.gradle may refer to either the root project build.gradle or any subproject’s build script, named as outlined above.
The root project folder contains some additional files, besides the build.gradle script for the root build. settings.gradle configures the Gradle build itself, including what subproject(s) to include in a build and where to look for Gradle plugins. Build scripts should be executed with the gradlew.bat or gradlew script in the root project folder. The first time it is run it will download and install Gradle for you, so there is no installation required by the developer. It also sets up the build environment (Java classpath, etc.) for a Gradle project to use during execution.
The build.gradle script contains the actual Domain Specific Language (DSL) code used by Gradle to run the module build task. It contains the same basic information as a build.xml file has for Niagara AX modules, like the module name, version, vendor, and dependencies. More details of the elements defined in the build.gradle and a mapping of build.xml elements to a build.gradle script are located in the Build Script Elements section below.
The default build environment generated by the New Module Wizard or New Driver Wizard uses the value of %niagara_user_home%, typically C:\Users\USERNAME\Niagara4.X\BRAND, as the default Gradle project root.
Note: Certain network configurations may require setting proxy information in the gradle.properties file in the user’s home folder. More information on how this is configured can be found on the Gradle web site.
In addition to the gradlew.bat and build.gradle files, a module-include.xml file is required, and the module.palette and module.lexicon files are optional. If you have test classes for your module, a moduleTest-include.xml is needed. The contents of these files are described in a later section of this document. Note that the Niagara AX build.xml file is no longer needed to build Niagara 4 modules.
Gradle locates source code through a configuration called sourceSets. The default source set configuration for Niagara projects is for the source code to be in a folder called src and for test source code to be in srcTest. This folder structure is used during compile to allow Gradle to locate source code and during module jar creation to enable Gradle to locate any files to include in the module. See the build script for examples of include files for main and test modules - the from(…) syntax. More details on setting up tests can be found in the TestNG Support in Niagara 4 document.
\<module name> - Subproject directory
|- <module name>.gradle - Gradle script file
|- module.lexicon - Default lexicon file
|- module.palette - Defines Palette information for the module
|- module-include.xml - Declares Types, Defs, etc. for the module
|- moduleTest-include.xml - Declares Types, Defs, etc. for the test module
|- module-permissions.xml - Declares permissions for the module
|- src\ - Folder containing module packages, source files, and resource files
|- srcTest\ - Folder containing test packages, source files, and resource files
\<some folder name> - Top level directory
|- build.gradle - Root Project Gradle script file
|- vendor.gradle - Define the group (vendorName) and version here - they will be used in all modules
|- settings.gradle - Gradle script containing the names of all modules or folders containing modules
|- <module 1 name>\ - Folder containing Module 1
| |- <module 1 name>-rt\ - Folder containing Module 1's rt part
| | |- <module 1 name>-rt.gradle - Module 1 rt part Gradle script file
| | |- module.lexicon - Default lexicon file for module 1
| | |- module.palette - Defines Palette information for module 1
| | |- module-include.xml - Declares Types, Defs, etc. for module 1
| | |- module-permissions.xml - Declares permissions for module 1
| | |- moduleTest-include.xml - Declares Types, Defs, etc. for test module 1
| | |- src\ - Folder containing module 1 rt packages, source files, and resource files
| | |- srcTest\ - Folder containing test 1 packages, source files, and resource files
| |- <module 1 name>-wb\ - Folder containing Module 1's wb part
| | |- <module 1 name>-wb.gradle - Module 1 wb part Gradle script file
| | |- module.lexicon - Default lexicon file for module 1
| | |- module.palette - Defines Palette information for module 1
| | |- module-include.xml - Declares Types, Defs, etc. for module 1
| | |- module-permissions.xml - Declares permissions for module 1
| | |- moduleTest-include.xml - Declares Types, Defs, etc. for test module 1
| | |- src\ - Folder containing module 1 wb packages, source files, and resource files
| | |- srcTest\ - Folder containing test 1 packages, source files, and resource files
|- <module 2 name>\ - Folder containing Module 2
| |- <module 2 name>-rt\ - Folder containing Module 2's rt part
| | |- <module 2 name>-rt.gradle - Module 2 Gradle script file
...
Part of the reason for using Gradle is that it includes a DSL (Domain Specific Language) for building Java projects. A small amount of script configuration results in a powerful set of tasks for compiling, assembling, testing, and publishing software. When you run gradlew <taskName>, Gradle will apply that task to all projects that declare that task. For example, gradlew clean will clean all modules in a multi-project configuration. If you want to execute a task against a specific project, use the gradlew :path:to:project:<taskName> syntax. If your multi-project module set is organized as described above, you can run Gradle tasks for a single module using gradlew :<moduleName>:<taskName>. For example, to clean the componentLinks module in the developer examples under the dev folder, run gradlew :componentLinks:clean. When you execute gradlew for the first time, it will download the Gradle framework required to complete task execution. This will take a few moments, but will only be needed once.
Note that all gradlew commands must be run from the root project directory.
gradlew tasks - List the Gradle tasks available for execution
gradlew jar - Compile module source code, assemble the module jar, and copy it to the installation location
gradlew javadocJar - Generate javadoc files and assemble them into a jar file
gradlew moduleTestJar - Compile, jar, and install test module code
gradlew clean - Clean compiled artifacts from the module folder
gradlew :<moduleName>:jar - Compile, jar, and install a single
gradlew :<moduleName>:slotomatic - Run slot-o-matic on a single
In Niagara AX, if you had an external dependency on a third-party jar (like Apache commons-pool) and choose not to convert it to a module, then you included it in the extdirectory of your module source and build.jar would take care of including it in your generated module. This process is typically called creating an “uberjar” or “fatjar”.
In Niagara 4, using Gradle we take a slightly different approach. You declare all your external dependencies in your Gradle script using a special uberjar dependency configuration. Any dependencies declared against the uberjar configuration will be automatically included in the generated module. Also, in Niagara 4 we are moving towards pulling external dependencies from a central repository. Gradle includes support for the central Maven repository, which is a commonly used repository for software artifacts. Gradle will download the dependency automatically from the central Maven repository and include it in the generated module. The Gradle build scripts will no longer look for dependencies in the ext directory of your module.
NOTE: Internet connectivity is required for accessing the central Maven repository.
Gradle projects can be automatically imported into both IntelliJ and Eclipse. Simply open your project’s build.gradle with either and it will import your project automatically.
NOTE: Previous versions of Niagara used the gradlew idea and gradlew eclipse commands, which are now deprecated. See Updating to a new Gradle Project Layout if you are using a project layout from Niagara 4.4 or earlier.
The Gradle script files contain elements similar to those in the Niagara AX build.xml file, configured for Gradle.
A gradle wrapper file is available in the bin folder of the Niagara installation. It sets up the build environment for compiling Niagara modules, including Java class paths and Gradle configuration settings. The first time it is used to compile a module, it will download the Gradle runtime libraries and any external dependencies needed to compile. It should be used every time modules are compiled, as it enables a Gradle daemon to improve compile efficiency.
Additional information about the Gradle wrapper is available on the Gradle web site. More advanced users may choose to modify the wrapper script as needed, but it is likely that this will not be necessary.
Several Gradle properties must be declared in the appropriate script file in order for the module to compile, jar, and test correctly. The examples shown later in this document and the source examples in the dev folder contain the commonly used properties that will need to be defined for each module. Use them as templates if you are not using the New Module Wizard. There are other elements in these examples that should be left as they are; these are noted by comments in the Gradle scripts. Some common elements used in Gradle build scripts are described below.
plugins {} - Configures the plugins used by your project. Several plugins are available as part of your Niagara 4 environment, in addition to the many plugins available on the Gradle plugin portal. ext {} - This namespace is used to declare extra properties within the project.
buildscript {} - Configures the classpath used by the build script for this project.
repositories {} - Gradle uses these to resolve and download dependency artifacts. The default configurations for Niagara 4 projects uses Maven and local flat file repositories for providing dependencies.
dependencies {} - Specific artifacts required by particular phases of the build sequence (e.g. compile, test, etc.).
jar {} - Enables the jar task to locate additional files to include in the jar file.
apply - Include shared Gradle code into the current project.
sourceSets {} - Configurations for source file locations.
niagaraModule {} - Provided by the niagara-module plugin to enable construction of a Niagara 4 compliant jar file.
moduleTestJar {} - Provided by the niagara-module plugin to enable construction of a Niagara 4 compliant test jar file.
NOTE: The examples in the dev folder contain configurations for both project types. You only need to configure one of these (stand-alone or multi-project).
Your modules will generally have a dependency on one or more Niagara 4 modules. These dependency declarations are declared in the module build Gradle file. The first element in the dependency declaration is the configuration name. The standard Gradle configuration for compiling Java code is compile. A second configuration used for test classes in Niagara 4 is niagaraModuleTestCompile. The second element in the dependency declaration is the dependency notation. The notation used in the example modules for external dependencies uses the String notation format. The notation contains the group (vendorName), name (module), and version, each separated by a colon (:). So the notation for declaring a dependency on the baja.jar module is "Tridium:baja:4.0".
One advantage of using Gradle is that it does transitive dependency resolution automatically. This means that you only need to declare direct dependencies on modules or external libraries that your code directly references. If these direct dependencies have their own compile-time dependencies (i.e. transitive dependencies), Gradle will resolve these automatically.
Other notation formats are possible. See the Gradle documentation for additional information on dependency management.
In addition to the default Gradle plugins, some Niagara-specific plugins are available. The default settings.gradle configures your build to make these plugins available:
pluginManagement {
apply from: 'environment.gradle'
resolutionStrategy {
eachPlugin {
if (requested.id.namespace == 'com.tridium') {
useModule('com.tridium.tools:niagara-plugins:4.1.5')
}
}
}
repositories {
maven {
url "${gradle.ext.niagara_home}/etc/m2/repository"
}
gradlePluginPortal()
}
}
The following plugins are available:
| Plugin | Description |
|---|---|
| com.tridium.niagara-module | The basic Niagara module plugin, which configures Niagara-specific Gradle tasks. This plugin is applied automatically when using the default build environment. |
| com.tridium.niagara-grunt | Grunt/Javascript tasks for Gradle. See Building Javascript for more details |
In the Niagara AX Developer Guide, the section on Build provides an overview of the elements available for inclusion in the build.xml file. This includes a definition of XML element and attributes that can be used in build.xml. There are four XML elements described in the documentation: module (the root element), dependency, package, and resources. Most of the element and attribute mappings from build.xml to the Gradle script are straightforward. Pay particular attention to the dependency declarations. Gradle contains a more sophisticated approach to dependency resolution described above, and has a standard way of declaring and resolving dependencies that has been adopted in Niagara 4.
The table below contains a mapping of common elements used in the build.xml to declarations in a corresponding Gradle script.
| build.xml | Gradle Script |
|---|---|
| Root <module> Attributes | ext or niagaraModule Elements |
| name = "foo" | ext { name = "foo" } |
| vendor = "X" | ext { project.group = "X" } |
| vendorVersion = "1.5.0" | ext { project.version = "1.5.0" } |
| description = "X foo" | ext { project.description = "X foo" } |
| preferredSymbol = "x" | niagaraModule { preferredSymbol = "x" } |
| bajaVersion = "0" | niagaraModule { bajaVersion = "0" } (optional) |
| <dependency> Tag | dependencies Elements |
| name="bar" vendor="X" vendorVersion="4.0" | dependencies { compile "X:bar:4.0" } |
| <resources> Tag | jar Elements |
| name="/com/example/icons/*.png" | jar { from("src") { include "/com/example/icons/*.png" } } |
| <dependency> Tag for Test | dependencies Elements |
| name="baz" vendor="X" vendorVersion="4.0" test="true" | dependencies { niagaraModuleTestCompile "X:baz:4.0" } |
| <resources> Tag for Test | moduleTestJar Elements |
| name="com/example/test/*.bog" test="true" | moduleTestJar { from("srcTest") { include "com/example/test/*.bog" } } |
<module
name = "componentLinks"
bajaVersion = "0"
preferredSymbol = "cl"
description = "Example of checking and creating Links programmatically"
vendor = "Tridium"
>
<dependency name="baja" vendor="Tridium" vendorVersion="4.0" />
<dependency name="kitControl" vendor="Tridium" vendorVersion="4.0" />
<dependency name="control" vendor="Tridium" vendorVersion="4.0" />
<dependency name="bajaui" vendor="Tridium" vendorVersion="4.0" test="true" />
<package name="com.examples.componentLinks" />
<resources name="com/examples/icons/*.png" />
<resources name="com/examples/test/bogs/*.bog" test="true" />
</module>
NOTE: The module version is retrieved from the devkit.properties file
ext {
// Declare module name and project properties
name = "componentLinks"
description = "Example of checking and creating Links programmatically"
}
// Declare niagaraModule properties
niagaraModule {
preferredSymbol = "cl"
// The runtime profile indicates the minimum Java runtime support required for this module jar
runtimeProfile = "rt"
// The moduleName is registered with the Niagara runtime engine.
// In Niagara 4, it is possible for a module to have multiple jar files for separate runtimeProfile values.
moduleName = "componentLinks"
}
// Declare compile and test dependencies
dependencies {
compile "Tridium:baja:4.0.0"
compile "Tridium:kitControl-rt:4.0.0"
compile "Tridium:control-rt:4.0.0"
niagaraModuleTestCompile "Tridium:bajaui-wb:4.0.0"
}
// Include additional files in module jar
jar {
from("src") {
include "com/examples/icons/*.png"
}
}
// Include additional files in the test jar
moduleTestJar {
from("srcTest") {
include "com/examples/test/bogs/*.bog"
}
}
This file is placed directly under the module’s root directory. If it is declared, then the build tools automatically include it in the module’s manifest as META-INF/module.xml. Its primary purpose is to allow developers to declare def, type, and lexicon elements.
This file contains any permissions your module requests.
The module.palette file is an optional file that is placed directly under the module’s root directory. If included it is automatically inserted into the module jar file, and accessible in the module as /module.palette. The module.palette file should contain the standard palette of public components provided by the module. The format of the file is the same as a standard .bog file.
The module.lexicon file is an optional file that is placed directly under the module’s root directory. If included it is automatically inserted into the module jar file. The lexicon file defines the name/value pairs accessed via the Lexicon API.
Put any Niagara def, type, and lexicon elements used in your test classes in this file.
plugins {
id 'com.tridium.niagara-signing'
id 'com.tridium.niagara' apply false
id 'com.tridium.niagara-module' apply false
id 'com.tridium.niagara-rjs' apply false
}
import com.tridium.gradle.plugins.niagara.task.RunNiagaraTestTask
def getGradleProperty(String propName) {
if (gradle.hasProperty(propName)) {
gradle.getProperty(propName)
} else {
// Get system property, or, failing that, fall back to environment variable
// This allows setting, e.g., -Pniagara_home=blah
System.getProperty(propName) ?: System.getenv(propName)
}
}
ext.niagara_home = getGradleProperty('niagara_home')
ext.niagara_dev_home = getGradleProperty('niagara_dev_home')
ext.niagara_user_home = getGradleProperty('niagara_user_home')
gradle.beforeProject { p ->
configure(p) {
def vendorSettings = file("${rootDir}/vendor.gradle")
if (vendorSettings.exists()) {
apply from: vendorSettings
}
apply from: "${rootDir}/gradle/niagara.gradle"
tasks.withType(RunNiagaraTestTask) { RunNiagaraTestTask task ->
task.groups = []
}
}
}
// Vendor name applied to all modules
group = "Tridium"
// Major, minor, and build version
def moduleVersion = "5.0.1"
// Patch version can be declared
// For example, to patch envCtrlDriver module as 5.0.1.1
// moduleVersionPatch.'envCtrlDriver' = ".1"
def moduleVersionPatch = [:]
// Final version property applied to all modules
version = "${moduleVersion}${moduleVersionPatch.get(project.name, '')}"
/* Environment Configuration File */
// Change niagara_home and niagara_user_home as needed to point to the
// correct path to your desired Niagara install and its associated
// user home
gradle.ext.niagara_dev_home = "C:/Users/user/Niagara4.9/brand"
gradle.ext.niagara_home = "C:/niagara/Niagara-4.9.0.110"
gradle.ext.niagara_user_home = "C:/Users/user/Niagara4.9/brand"
import groovy.io.FileVisitResult
import groovy.io.FileType
pluginManagement {
apply from: 'environment.gradle'
resolutionStrategy {
eachPlugin {
if (requested.id.namespace == 'com.tridium') {
useModule('com.tridium.tools:niagara-plugins:4.1.5')
}
}
}
repositories {
maven {
url "${gradle.ext.niagara_home}/etc/m2/repository"
}
gradlePluginPortal()
}
}
def discoveredProjects = [:] as Map<String, File>
ext {
// CONFIGURE your sub-project folders here.
// This will include ALL sub-folders as sub-projects.
niagaraRoots = ['.']
// To explicitly define sub-project folders, name them in the array like this
//niagaraRoots = ['componentLinks', 'envCtrlDriver']
// CONFIGURE any directories to exclude from search for nested sub-projects
excludeDirs = ['.hg', 'build', 'out', 'src', 'srcTest']
}
// OR set the 'userRepos' gradle property.
//
if (hasProperty('userRepos')) {
settings.ext.niagaraRoots += userRepos.split(",").collect()
}
// my-settings.gradle.
//
def mySettings = file('my-settings.gradle')
if (mySettings.exists()) {
apply from: mySettings
}
def environment = file('environment.gradle')
if (environment.exists()) {
apply from: environment
}
// DO NOT MODIFY the niagaraRoots configuration
niagaraRoots.collect({ file(it) }).findAll({ it.exists() }).each { File projectRoot ->
projectRoot.traverse(
type: FileType.DIRECTORIES,
preRoot: true,
preDir: { File projectDir ->
def projectName = projectDir.name
if (excludeDirs.contains(projectName)) {
return FileVisitResult.SKIP_SUBTREE
}
File buildScript = new File(projectDir, "${projectName}.gradle")
if (buildScript.exists()) {
discoveredProjects[projectName] = projectDir
include projectName
return FileVisitResult.SKIP_SUBTREE
}
}
)
}
// Set up the project tree - no need to modify
rootProject.name = "niagara"
rootProject.children.each { project ->
project.projectDir = discoveredProjects[project.name]
project.buildFileName = "${projectName}.gradle"
assert project.projectDir.isDirectory()
assert project.buildFile.isFile()
}
description = "Example Driver Module"
niagaraModule {
preferredSymbol = "ecd"
runtimeProfile = "rt"
moduleName = "envCtrlDriver"
modulePart {
name = "envCtrlDriver-wb"
runtimeProfile = "wb"
}
}
dependencies {
compile "Tridium:nre:4.0.0"
compile "Tridium:baja:4.0.0"
compile "Tridium:alarm-rt:4.0.0"
compile "Tridium:control-rt:4.0.0"
compile "Tridium:driver-rt:4.0.0"
compile "Tridium:basicDriver-rt:4.0.0"
}
Modules may depend on 3rd party libraries that implement some desired functionality. These dependencies are configured much like Niagara module dependencies, but are contained in a configuration called uberjar. For example, if a module has a direct dependency on the Apache Velocity library and the baja module, the dependency declaration would look like:
// Declare compile and test dependencies
dependencies {
compile "Tridium:baja:4.0.0"
uberjar "org.apache.velocity:velocity:1.7"
}
Libraries compiled with the uberjar configuration will cause the classes of the dependency to be included in the resulting module jar file. This makes it straightforward to distribute modules with external dependencies.
Note that the string used to identify a particular library follows a specific convention of group:name:version. So in the above example, the group is org.apache.velocity, the name is velocity, and the version is 1.7. This information relates to the Maven information for that library, and it will be verified and downloaded from a central Maven repository. See the Gradle documentation on dependency management for more information on external library dependency naming and the central Maven repository.
If you are still using the single-project Gradle build generated by the New Module Wizard in Niagara 4.2 and earlier, it’s fairly straightforward to update to the latest build environment.
As with any migration from an older to a newer Niagara, the easiest way to start is with a skeleton project generated by the New Module Wizard in Workbench. It will default to creating modules in niagara_user_home, typically C:\Users\USERNAME\Niagara4.x\BRAND. For the purposes of migrating modules from prior versions, you may want to use a different folder to avoid confusion. This can be specified in the first page of the New Module Wizard.
For each module and module part that you want to migrate:
moduleName/moduleName-modulePart folderbuild.gradle to moduleName-modulePart.gradlemoduleName-modulePart.gradle to remove:
apply from:; these are now applied automaticallyname, project.version, and project.group properties from the ext {} block; these now come from vendor.gradleniagara_home:niagaraHome = System.getenv("niagara_home")
if (niagaraHome == null) {
logger.error('niagara_home environment variable not set')
}
niagara_home is now defined by environment.gradle
gradlew :moduleName-modulePart:tasks and verify it does not output any errors.Repeat this process for all modules you need to migrate.
To retarget your project to a new version of Niagara, you must edit the file “environment.gradle” in you project root folder. Early versions of Niagara incorrectly generated this code with warnings that you should not hand-edit this file; these warnings are incorrect and may be safely ignored.
To change the version of Niagara edit “environment.gradle” and change the definitions of niagara_home and, if necessary, niagara_user_home. You need only change niagara_user_home if you are updating to minor versions of Niagara; if you are only updating to an update release, only niagara_home must change.
For example, to change your project to target 4.6 instead of 4.4, change “environment.gradle” from this:
// ...
gradle.ext.niagara_home = "c:/Niagara/Niagara-4.4.73.24"
gradle.ext.niagara_user_home = "c:/Users/user/Niagara4.4/brand"
// ...
to this:
// ...
gradle.ext.niagara_home = "c:/Niagara/Niagara-4.6.83.20"
gradle.ext.niagara_user_home = "c:/Users/user/Niagara4.6/brand"
// ...
In some minor releases of Niagara, changes are made to the default Gradle project layout that may require a completely new development environment. This most often occurs when a new major version of Gradle is released.
In order to update your existing environment, you must run the New Module Wizard again, but to a folder with no existing Gradle files from previous Niagara versions. You can create a dummy module with the New Module Wizard and delete it afterwards, leaving you with just the correct project skeleton for Niagara projects. Once you have done this, you can copy or move your existing module folder(s) from the old project structure into the new one.