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 module a javadoc jar file. For single module projects, a basic build.gradle
script is required to actually build the module, and a gradlew.bat
script is required to install Gradle, set up the environment, and initiate the build process.
Build scripts for single module projects or the main project of a multi-project build should be executed with the gradlew.bat
. The first time it is run it will 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
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.
For multi-project builds, a single gradlew.bat
file and a build.gradle
script is needed for the main project of the build. Each module will have a gradle file containing module-specific configuration elements.
For large multi-project builds, Gradle includes a Configuration On Demand mode that improves build performance by only configuring projects that are relevant for the required tasks. To enable this feature, add it to your Gradle properties file:
C:\Users\<username>
in windows7org.gradle.configureondemand = true
into the fileOther elements can be configured here, including org.gradle.daemon = true
. Enabling the daemon element can improve build times, but will keep the JVM binary locked on Windows. You can read more about Gradle configuration on the Gradle web site.
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> - Top level source directory
|- build.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 - Main 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>.gradle - Module 1 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 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>.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.
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. The specific configuration will depend on the type of project (stand-alone or multi-project) required by the developer. For a stand-alone module, the Gradle script file is build.gradle
. For a multi-project build environment, there will be one build.gradle
for the main project, and each module within the project will have a <module>.gradle
file. The developer examples found in the dev folder contain script files for both configurations. See the description of stand-alone and multi-project configurations for details on the two project types.
Each build.gradle
script, either stand-alone or multi-project, will have an associated gradlew.bat
Gradle wrapper file.
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 for your stand-alone modules. 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. The specific Gradle file containing these elements depends on which configuration being used (stand-alone or multi-project). Refer to the examples for guidance on where to locate these elements in your projects.
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 defaultconfigurations 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 (r.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 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
//use default niagara configurations for modules
apply from: "${System.getenv('niagara_home')}/etc/gradle/niagara.gradle"
apply from: "${System.getenv("niagara_home")}/etc/gradle/eclipse.gradle"
apply from: "${System.getenv("niagara_home")}/etc/gradle/idea.gradle"
ext {
// Declare module name and project properties
name = "componentLinks"
project.version = "5.0.1"
project.group = "Tridium"
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.
A stand-alone module not part of a multi-project build needs to have only the build.gradle
and gradle.bat
files in the top level folder to support building the module with Gradle. The module-include.xml
is still required, and the module.lexicon
and module.palette
files can be included if needed. A moduleTest-include.xml
is needed for any test classes. Here is the file structure for a stand-alone project.
\<module name>
- Top level source directory
|- build.gradle
- Gradle script file
|- gradlew.bat
- Gradle wrapper 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
|- src\
- Folder containing module packages, source files, and resource files
|- srcTest\
- Folder containing test packages, source files, and resource files
Gradle commands will be run from a Windows command prompt. For a single module, navigate to the folder containing the module source and configurations. The Gradle tasks for separate phases of the build sequence are below.
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 slotomatic
- Run slot-o-matic on the module source code creating boiler plate slot code
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
A multi-project set of module jar files will have a build.gradle
and gradle.bat
in the top-level folder. There will also be a settings.gradle
and vendor.gradle
files containing Gradle elements that will be applied to all module builds. Each module jar will require a <moduleName>.gradle
file containing Gradle configurations specific to that module. The module-include.xml
is required for each module jar, and the module.lexicon
and module.palette
are included as needed. A moduleTest-include.xml
is needed for any module jar containing test classes.
Niagara 4 supports multiple runtime profiles for a single Niagara module. To take advantage of this runtime configuration, there will be a separate module jar file for each profile. By convention, the gradle build file for a module jar file will be<moduleName>-<profile>.gradle
and the runtime profile will be declared in that file as part of the Gradle build configuration. Profiles include rt, ux, wb, se, and doc. Here is the file structure for a project containing multiple modules.
\<some folder name>
- Top level directory
|- build.gradle
- Main Gradle script file
|- gradlew.bat
- Gradle wrapper 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>.gradle
- Module 1 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
| |- moduleTest-include.xml
- Declares Types, Defs, etc. for test module 1
| |- src\
- Folder containing module 1 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>.gradle
- Module 2 Gradle script file
| |- module-include.xml
- Declares Types, Defs, etc. for module 2
...
Gradle commands will be run from a Windows command prompt. For a multi-module project, navigate to the main project folder. The Gradle tasks for separate phases of the build sequence are below.
gradlew tasks
- List the Gradle tasks available for execution
gradlew jar
- Compile source code, assemble jars, and installation for all modules
gradlew slotomatic
- Run slot-o-matic on the all source code.
gradlew javadocJar
- Generate javadoc files and assemble them into jar files for all modules
gradlew moduleTestJar
- Compile, jar, and install test code for all modules
gradlew clean
- Clean compiled artifacts for all modules
gradlew :<moduleName>:jar
- Compile, jar, and install a single
gradlew :<moduleName>:slotomatic
- Run slot-o-matic on the
This same module-specific syntax can be also used for the rest of the Gradle tasks.
ext {
niagaraHome = System.getenv("niagara_home")
if (niagaraHome == null) {
logger.error("niagara_home environment variable not set")
}
}
//to enable idea/intellij or eclipse support, un-comment the lines below
// apply from: "${System.getenv("niagara_home")}/etc/gradle/idea.gradle"
// apply from: "${System.getenv("niagara_home")}/etc/gradle/eclipse.gradle"
gradle.beforeProject { p ->
configure(p) {
def vendorSettings = file("${rootDir}/vendor.gradle")
if (vendorSettings.exists()) {
apply from: vendorSettings
}
apply from: "${System.getenv("niagara_home")}/etc/gradle/niagara.gradle"
}
}
tasks.addRule("""
Pattern: [jar[Test]|clean|<any gradle task>]/[path]: Run a Gradle task against a set of modules rooted at path.
""") { String taskName ->
def matcher = taskName =~ /(.*?)(Test)?\/(.*)/
if (matcher) {
def command = matcher.group(1)
def includeTestModules = matcher.group(2) == "Test"
def path = file("${projectDir}/${matcher.group(3)}").toPath()
assert path.toFile().exists()
def targetProjects = subprojects.findAll { it.projectDir.toPath().startsWith(path) }
// default is build command and build is an alias for Gradle"s jar task
if (command.isEmpty() || command == "build") { command = "jar" }
// Create task for subproject
task(taskName, dependsOn: targetProjects.tasks[command])
if (includeTestModules && command == "jar") {
tasks[taskName].dependsOn targetProjects.moduleTestJar
}
}
}
// 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, '')}"
import groovy.io.FileVisitResult
import groovy.io.FileType
def discoveredProjects = [:] as Map
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"]
}
// niagaraRoots configuration - do not modify
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
if (projectDir != projectRoot) {
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.
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 version 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.
Copyright © 2000-2019 Tridium Inc. All rights reserved.