Building Niagara

Overview

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

Getting started

In order to build Niagara modules, you need a valid Java 8 JDK. Prior versions of Niagara have supported building modules just using the JRE available with Niagara, but updates to Gradle have made this an unsupported configuration. While Oracle no longer provides Java 8 JDKs, several OpenJDK vendors are available and any one of them will work for building Niagara. Gradle can generally detect installed JDKs automatically, but if it does not find your JDK you can configure it manually as outlined in Configuring Gradle’s JDK.

Additionally, to build UX modules using modern web development tools, additional tools will be needed; see Building Javascript for steps to set those up.

Gradle Configuration

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 part.

Each Gradle project (both root and subprojects) is defined in a Gradle build script. This file is typically called build.gradle.kts for the root project, and projectName.gradle.kts 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.kts. 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.kts will not be detected as a subproject; the correct path will be myModule/myModule-rt/myModule-rt.gradle.kts For the sake of this document, references to build.gradle.kts may refer to either the root project build.gradle.kts or any subproject’s build script, named as outlined above.

Note that older versions of Niagara used build.gradle and projectName.gradle – without the .kts extension. This is because older versions of Gradle used the Groovy language for their build scripts, while newer versions use the Kotlin language. The Gradle documentation explains this in far more detail. Any references to the non-.kts build.gradle.kts likely apply equally to both, with some minor syntax differences as might be expected between different languages.

The root project folder contains some additional files, besides the build.gradle.kts script for the root build. settings.gradle.kts configures the Gradle build itself, including what subproject(s) to include in a build and where to look for the custom Gradle plugins that enable Gradle to build Niagara modules. 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.kts 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.kts and a mapping of build.xml elements to a build.gradle.kts 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.

Project Setup

Module Project Configuration Files

The Niagara build system assumes that your projects are organized on disk in a particular way: A module folder containing module-modulePart folders, which themselves contain your source code and the supporting build script.

In the module folder, in addition to the module-modulePart subfolders, you must have a niagara-module.xml file. This file defines what module parts your module has, as well as some common definitions shared by all module parts.

In the module-modulePart subfolders, in addition to the myModule-modulePart.gradle.kts file, 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.

Module Project Source Code Layout

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.

Example Multi-project file tree

\<some folder name> - Top level directory
|- build.gradle.kts - Root Project Gradle script file
|- gradle.properties - Environment setup for your build
|- settings.gradle.kts - Gradle script containing the names of all modules or folders containing modules
|- <module 1 name>\ - Folder containing Module 1
| |- niagara-module.xml - File defining shared configuration for all parts of module 1
| |- <module 1 name>-rt\ - Folder containing Module 1's rt part
| | |- <module 1 name>-rt.gradle.kts - 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.kts - 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
| |- niagara-module.xml - File defining shared configuration for all parts of module 2
| |- <module 2 name>-rt\ - Folder containing Module 2's rt part
| | |- <module 2 name>-rt.gradle.kts - Module 2 Gradle script file
...

Running Gradle

Gradle Task Resolution

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.

Common Gradle Tasks

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 <moduleName>
gradlew :<moduleName>:slotomatic - Run Slot-o-matic on a single <moduleName>
gradlew :<moduleName>:niagaraTest - Run tests with code coverage on a single <moduleName>

Additional Gradle Options

Dependencies

Gradle supports a very expressive dependency declaration syntax, which allows you to depend on other projects in your build, other modules in your Niagara installation, or external libraries. Gradle also separates dependencies by source set; for example, you can declare additional dependencies used to write tests for your module.

Gradle provides a large number of configurations used to declare dependencies by default, and additional ones are provided by the Niagara plugins. The following table shows what dependencies are supported by Niagara builds and when you would use them.

Configuration Use Examples
api Dependencies on other Niagara modules api(":baja") or api(":myDriver-rt")
api Dependencies on other modules in your build api(project(":myOtherDriver-rt"))
nre Dependencies on jars in !bin/ext nre("org.bouncycastle:bcprov-jdk18on")
uberjar Dependencies on external modules (see below) uberjar("org.apache.commons:commons-collections4:4.3")
moduleTestImplementation Test dependencies on other Niagara modules moduleTestImplementation(":baja")
moduleTestImplementation Test dependencies on other modules in your build moduleTestImplementation(project(":myTestUtils-wb"))
testNre Test dependencies on jars in !bin/ext testNre("org.bouncycastle:bcprov-jdk18on")
testUberjar Test dependencies on external modules (see below) testUberjar("org.mockito:mockito-inline:4.5.1")

In general, you should only use the configurations listed above. Other Gradle configurations may not interact correctly with the Niagara build environment. Also note that it is not required to specify version numbers for dependencies provided by a Niagara installation; as these versions are fixed by the choice of Niagara to compile against, specifying versions for these dependencies is not relevant and may well cause confusion.

Niagara Module dependencies

Dependencies on other Niagara modules, either in your own build or provided by Niagara, must be declared in the ‘api’ configuration. For modules in your own build, reference them as project(":projectName-rt"), where “projectName-rt” is typically the name of the module part you are depending on. For modules provided by Niagara (modules already present in <niagara_home>/modules), the syntax is just ":moduleName-rt": the name of the module part prefixed by :. Older versions of the build environment recommended using the module vendor as a prefix (e.g., Tridium:baja), but this is no longer recommended or supported.

External Dependencies

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 ext directory 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 as shown above. 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 IDE Integration

Gradle projects can be automatically imported into both IntelliJ and Eclipse. Simply open your project’s build.gradle.kts with either and it will import your project automatically.

Warning: As mentioned in Getting Started, you will need a Java 8 JDK to use IDE integration. Oracle no longer provides Java 8 JDKs, but several vendors now offer OpenJDK 8 builds. Any Java 8 compatible JDK will work for Niagara.

In previous versions of Niagara, the gradlew idea and gradlew eclipse commands were used. These are now removed and will not work. See Upgrading build environments from prior versions of Niagara if you are using a project layout from Niagara 4.12 or earlier.

Configuring Gradle’s JDK

By default, Gradle will find any JDKs installed on your system, as outlined in the Gradle help on Auto detection of installed toolchains. You can run gradlew -q javaToolchains -Porg.gradle.java.installations.auto-detect=true to print a list of all the JDKs that Gradle can find. However, if it does not find your JDK – or if it does not select the correct one – you can control the JDK used to build Niagara modules with a handful of properties in gradle.properties in the root of your project’s build directory:

org.gradle.java.installations.auto-detect=false
org.gradle.java.installations.auto-download=false
org.gradle.java.installations.paths=C:\\JDKS\\some-java-8-jdk

Gradle Build Scripts

The Gradle script files contain elements similar to those in the Niagara AX build.xml file, configured for Gradle.

Gradle Wrapper Script - gradlew.bat/gradlew

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.

Build Script Elements: build.gradle.kts

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. 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.).
tasks.named<Jar>("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.
moduleManifest {} - Provided by the niagara-module plugin to enable construction of the module.xml Niagara Module Manifest for your module.
niagaraSigning {} - Provided by the niagara-signing plugin to enable jar signing
tasks.named<Jar>("moduleTestJar") - Provided by the niagara-module plugin to enable construction of a Niagara 4 compliant test jar file.

Available Gradle Plugins

In addition to the default Gradle plugins, some Niagara-specific plugins are available. The default settings.gradle.kts configures your build to make these plugins available. You can see the code that does this in the full settings.gradle.kts file

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
com.tridium.niagara-signing Module signing configuration. See Code Signing for more details
com.tridium.bajadoc Produce bajadoc for your module
com.tridium.niagara-jacoco Enable JaCoCo test coverage report for the niagaraTest task

moduleManifest extension

The moduleManifest extension provides access for configuring the contents of the module manifest module.xml file. In addition to configuring the attributes of your module, it also allows configuring many elements that were previously only configurable in module-include.xml.

moduleManifest attributes

The following attributes are available for you to configure:

Attribute Description
moduleName (Required) The name of the module (not module part).
runtimeProfile (Required) The runtime profile of this module part.
vendor (Required) The vendor for the module. Typically set by the vendor extension in the root buildscript, but can be overridden per-module
vendorVersion (Required) The version of the module. As with vendor, typically set by the vendor extension
description (Required) A description of the module. Set to the Gradle project description if not specified

dependencies configuration

The dependencies block inside a moduleManifest block allows finer-grained control over the <dependencies> element of the generated module.xml manifest. For example, this snippet will add a ‘brand’ dependency on ‘Acme’:

moduleManifest {
  ...
  (dependencies) {
    create("brand") {
      dependency {
        name.set("Acme")
      }
    }
  }
  ...
}

This maps directly to the following in module.xml:

<module name="acmeDriver-rt">
  ...
  <dependencies>
    ...
    <brand name="Acme"/>
    ...
  </dependencies>
  ...
</module>

Each call to create(NAME) creates a set of elements with the same tag (<brand>, <part>, etc). Within that block, each call to dependency {} creates and configures a new dependency of that type. Each dependency can be configured with any of the following attributes, all of which map directly to XML attributes:

Attribute XML Attribute Description
name name Name to match for dependency resolution
vendor vendor Vendor to match for dependency resolution
version version Version to match for dependency resolution, as per dependencyRule
description desc Description of this dependency
dependencyRule rel The rule to determine how to match version: “minimum”, “exact”, or “maximum”
solvers solvers Solvers that can be used to resolve this dependency
installable installable Is this dependency installable by the software manager?

Note that while there are no individual mandatory attributes for dependencies, creating a dependency with no attributes is unlikely to accomplish anything meaningful. Additionally, the solvers and installable attributes are unlikely to be useful for modules.

installation configuration

The installation block inside a moduleManifest block allows adding an <installation> element to the generated module.xml manifest.

The primary use for module developers will be to set the isNoRunningStation attribute to true. This will force a station restart if a module is installed, which may be required for your module to function correctly.

Additionally, dependencies {} and excludes {} blocks are provided for advanced use cases. These blocks can be used to define dependencies and exclusions using the same data structures as the dependencies {} block above. However, while the moduleManifest.dependencies {} block is used to determine runtime dependencies, moduleManifest.installation.dependencies {} and moduleManifest.installation.exclusions {} are used to gate installation of a module before it is pushed down to a device.

defines configuration

The defines block inside a moduleManifest block allows adding registry definitions to the generated module.xml manifest:

moduleManifest {
  ...
  defines {
    define("foo", "bar")
    define("foo", "baz")
  }
  ...
}

This maps directly to the following in module.xml:

<module name="acmeDriver-rt">
  ...
  <defs>
    <def name="foo" value="bar"/>
    <def name="foo" value="baz"/>
  </defs>
  ...
</module>

lexicons configuration

The lexicons block inside a moduleManifest block allows adding lexicon definitions to the generated module.xml manifest. This can be used to indicate that your module can provide lexicon entries for other modules for any given language.

The lexicons {} block allows configuring attributes that apply to all lexicons in the module:

Attribute Description
brand A brand for this Lexicon module, if required. Defaults to none
defaultLanguage The default language for all lexicons in this module. If not specified, must be given for each lexicon resource entry
providesDefault If specified, this will control the ‘default’ flag for all lexicons in this module. If not specified, must be given for each lexicon resource entry

For the typical case of a lexicon module that provides translations for a single language, configuration is simple:

moduleManifest {
  ...
  lexicons {
    defaultLanguage.set("en")
    providesDefault.set(true)
    lexicon("myDriver")
    lexicon("myDashboard")
  }
  ...
}

This maps directly to the following in module.xml:

<module name="acmeDriver-rt">
  ...
  <lexicons>
    <lexicon module="myDriver" resource="myDriver.lexicon" language="en" default="true"/>
    <lexicon module="myDashboard" resource="myDashboard.lexicon" language="en" default="true"/>
  </lexicons>
  ...
</module>

For more complex cases, you can control the behavior of each lexicon resource individually:

Attribute Description
resource Name of the file containing lexicon entries. Defaults to ‘.lexicon’
language The language for this file
defaultLexicon Should this resource be the default for module when langauge is requested

For example, for a single lexicon module that provides multiple languages for a given module would be configured as follows:

moduleManifest {
  ...
  lexicons {
    lexicon("myDriver") {
      resource.set("myDriver-en.lexicon")
      language.set("en")
      providesDefault.set(false)
    }
    lexicon("myDriver") {
      resource.set("myDriver-es.lexicon")
      language.set("es")
      providesDefault.set(true)
    }
  }
  ...
}

This maps directly to the following in module.xml:

<module name="acmeDriver-rt">
  ...
  <lexicons>
    <lexicon module="myDriver" resource="myDriver-en.lexicon" language="en" default="false"/>
    <lexicon module="myDriver" resource="myDriver-es.lexicon" language="es" default="true"/>
  </lexicons>
  ...
</module>

Convert Niagara AX build.xml to Niagara 4 build.gradle

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.

Also note that due to the split of modules into module parts in Niagara 4, a new module-level declaration is found in niagara-module.xml. Some build.xml elements map here, while most have been pushed down into the module part’s build.gradle.kts.

Mapping Summary

The table below contains a mapping of common elements used in the build.xml to declarations in a corresponding Gradle script.

niagara-module.xml

Some attributes in <module> directly map to attributes in <niagara-module>:

build.xml niagara-module.xml
name = “myModule” moduleName = “myModule”
preferredSymbol = “mmod” preferredSymbol = “mmod”

myModule-modulePart.gradle.kts

The remaining elements in build.xml map to myModule-modulePart.gradle.kts:

build.xml myModule-modulePart.gradle.kts
vendor = “X” moduleManifest { vendor.set("X") }
vendorVersion = “X” version = "X"
bajaVersion = “0” moduleManifest { bajaVersion.set("0") } (optional)
<dependency> tag dependencies Elements
name=“bar” vendor=“X” vendorVersion = “4.0” dependencies { api("X:bar") }
<resources> tag jar task configuration
name="/com/example/icons/*.png tasks.named<Jar>("jar") { from("src") { include("/com/example/icons/*.png") }
<dependency> tag for test dependencies Elements
name=“baz” vendor=“X” vendorVersion=“4.0” test="true dependencies { moduleTestImplementation("X:baz") }
<resources> tag for test jar task configuration
name=“com/example/test/*.bog” test=“true” tasks.named<Jar>("moduleTestJar") { from("srcTest") { include("com/example/test/*.bog") }

Example Conversion

build.xml

<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

niagara-module.xml

<?xml version="1.0" encoding="UTF-8"?>
<niagara-module moduleName="componentLinks" preferredSymbol="cl" runtimeProfiles="rt"/>

Note that runtimeProfiles is a comma-separated list; for a module with ‘rt’, ‘ux’, and ‘wb’ parts, it would instead be:

<?xml version="1.0" encoding="UTF-8"?>
<niagara-module moduleName="multiPartModule" preferredSymbol="mpm" runtimeProfiles="rt,ux,wb"/>
import com.tridium.gradle.plugins.module.util.ModulePart.RuntimeProfile.*

plugins {
  // The Niagara Module plugin configures the "moduleManifest" extension and the
  // "jar" and "moduleTestJar" tasks.
  id("com.tridium.niagara-module")

  // The signing plugin configures the correct signing of modules. It requires
  // that the plugin also be applied to the root project.
  id("com.tridium.niagara-signing")

  // The bajadoc plugin configures the generation of Bajadoc for a module.
  id("com.tridium.bajadoc")

  // Configures JaCoCo for the "niagaraTest" task of this module.
  id("com.tridium.niagara-jacoco")

  // The Annotation processors plugin adds default dependencies on ":nre"
  // for the "annotationProcessor" and "moduleTestAnnotationProcessor"
  // configurations by creating a single "niagaraAnnotationProcessor"
  // configuration they extend from. This value can be overridden by explicitly
  // declaring a dependency for the "niagaraAnnotationProcessor" configuration.
  id("com.tridium.niagara-annotation-processors")

  // The niagara_home repositories convention plugin configures !bin/ext and
  // !modules as flat-file Maven repositories so that projects in this build can
  // depend on already-installed Niagara modules.
  id("com.tridium.convention.niagara-home-repositories")
}

description = "Example Niagara Module"

moduleManifest {
  moduleName.set("componentLinks")
  // The runtime profile indicates the minimum Java runtime support required for this module jar
  runtimeProfile.set(rt)
}

dependencies {
  // NRE dependencies
  nre(":nre")

  // Niagara module dependencies
  api(":baja")
  api(":kitControl-rt")

  // Test Niagara module dependencies
  moduleTestImplementation(":test-wb")
  moduleTestImplementation(":bajaui-wb")
  moduleTestImplementation(":control-rt")
}


// Include additional files in module jar with the following configuration.
//tasks.named<Jar>("jar") {
//  from("src") {
//    include("com/tridium/history/hx/*.js")
//    include("com/tridium/history/ui/icons/*.png")
//  }
//}

// Include files in the test jar with the following configuration.
//tasks.named<Jar>("moduleTestJar") {
//  from("srcTest") {
//    include("test/bogs/*.bog")
//  }
//}

Other Module Files

module-include.xml

This file is placed directly under the module part’s root directory. If it is declared, then the build tools automatically include it in the module’s manifest as META-INF/module.xml. It should not be edited by hand. The type elements are generated automatically by the build process; you should not edit them or add new ones by hand. Any type annotated with @NiagaraType will be written to module-include.xml automatically; see the Niagara Annotations documentation for more details. Other elements such as defs, lexicons, and installation are now configured in build.gradle.kts; see the section on moduleManifest for more information.

module-permissions.xml

This file contains any permissions your module requests.

module.palette

The module.palette file is an optional file that is placed directly under the module part’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.

module.lexicon

The module.lexicon file is an optional file that is placed directly under the module part’s root directory. However, it is strongly encouraged that you use lexicons to enable localization of your module. If included it is automatically inserted into the module jar file. The lexicon file defines the name/value pairs accessed via the Lexicon API. It is best practice to only use the ‘lowest’ module part to store all lexicon keys for all module parts. This makes it easier to maintain lexicon entries.

moduleTest-include.xml

Put any Niagara def and lexicon elements used in your test classes in this file. As with the main module-include.xml, type elements for any correctly annotated test type will be added automatically.

Example Gradle Scripts

build.gradle.kts

/*
 * Copyright 2022 Acme. All Rights Reserved.
 */

plugins {
  // Base Niagara plugin
  id("com.tridium.niagara")

  // The vendor plugin provides the vendor {} extension to set the default group
  // for Maven publishing; the default vendor attribute for installable
  // manifests; and the default module and dist version for their respective
  // manifests
  id("com.tridium.vendor")

  // The signing plugin configures signing of all executables, modules, and
  // dists. It also registers a factory only on the root project to avoid
  // overhead from managing signing profiles on all subprojects
  id("com.tridium.niagara-signing")

  // The niagara_home repositories convention plugin configures !bin/ext and
  // !modules as flat-file Maven repositories to allow modules to compile against
  // Niagara
  id("com.tridium.convention.niagara-home-repositories")
}


vendor {
  // defaultVendor sets the "vendor" attribute on module and dist files; it's
  // what's shown in Niagara when viewing a module or dist.
  defaultVendor("Acme")

  // defaultModuleVersion sets the "vendorVersion" attribute on all modules
  defaultModuleVersion("1.0")
}


////////////////////////////////////////////////////////////////
// Dependencies and configurations... configuration
////////////////////////////////////////////////////////////////

subprojects {
  repositories {
    mavenCentral()
  }
}

settings.gradle.kts

Note: A large amount of this code is debugging intended to provide useful error messages if the Gradle plugins cannot be loaded

/*
 * Copyright 2022 Acme. All Rights Reserved.
 */

import com.tridium.gradle.plugins.settings.MultiProjectExtension
import com.tridium.gradle.plugins.settings.LocalSettingsExtension

pluginManagement {
  val niagaraHome: Provider<String> = providers.gradleProperty("niagara_home").orElse(
    providers.systemProperty("niagara_home").orElse(
      providers.environmentVariable("NIAGARA_HOME").orElse(
        providers.environmentVariable("niagara_home")
      )
    )
  )

  val gradlePluginHome: String = providers.gradleProperty("gradlePluginHome").orElse(
    providers.environmentVariable("GRADLE_PLUGIN_HOME").orElse (
      niagaraHome.map { "$it/etc/m2/repository" }
    )
  ).orNull ?: throw InvalidUserDataException(buildString {
    val isWindows = providers.systemProperty("os.name").map { it.toLowerCase(java.util.Locale.ENGLISH) }.get().contains("windows")
    val propsFile = File(rootDir, "gradle.properties")

    appendLine("************************************************************")
    appendLine("ERROR: Invalid project configuration: Cannot derive value of 'gradlePluginHome'.")
    appendLine()
    if (propsFile.exists()) {
      appendLine("You can set it by editing the properties file at:")
    } else {
      appendLine("You can set it by creating a properties file at:")
    }
    appendLine()
    appendLine("  $propsFile")
    appendLine()
    appendLine("and adding 'gradlePluginHome':")
    appendLine()
    if (isWindows) {
      appendLine("  gradlePluginHome=C:\\\\path\\\\to\\\\plugins")
    } else {
      appendLine("  gradlePluginHome=/path/to/plugins")
    }
    appendLine()
    appendLine("You can also set it by defining the 'GRADLE_PLUGIN_HOME' environment varaible:")
    appendLine()
    if (isWindows) {
      appendLine("  set GRADLE_PLUGIN_HOME=C:\\\\path\\\\to\\\\plugins")
    } else {
      appendLine("  export GRADLE_PLUGIN_HOME=/path/to/plugins")
    }
    appendLine()
    appendLine("------------------------------------------------------------")
    appendLine()
    append("If you are using the plugins shipped with the version of Niagara you are building against, ")
    if (propsFile.exists()) {
      appendLine("you can edit the properties file at:")
    } else {
      appendLine("you can create a properties file at:")
    }
    appendLine()
    appendLine("  $propsFile")
    appendLine()
    appendLine("and add 'niagara_home':")
    appendLine()
    if (isWindows) {
      appendLine("  niagara_home=C:\\\\Niagara\\\\Niagara-4.x.y.z")
    } else {
      appendLine("  niagara_home=/opt/Niagara-4.x.y.z")
    }
    appendLine()
    appendLine("You can also set it by defining the 'NIAGARA_HOME' environment variable:")
    appendLine()
    if (isWindows) {
      appendLine("  set NIAGARA_HOME=C:\\\\Niagara\\\\Niagara-4.x.y.z")
    } else {
      appendLine("  export NIAGARA_HOME=/opt/Niagara-4.x.y.z")
    }
    appendLine()
    appendLine("************************************************************")
  })

  val gradlePluginRepoUrl = "file:///${gradlePluginHome.replace('\\', '/')}"

  val gradlePluginVersion: String = "7.6.17"
  val settingsPluginVersion: String = "7.6.3"

  repositories {
    maven(url = "$gradlePluginRepoUrl")
    gradlePluginPortal()
  }

  plugins {
    id("com.tridium.settings.multi-project") version (settingsPluginVersion)
    id("com.tridium.settings.local-settings-convention") version (settingsPluginVersion)

    id("com.tridium.niagara") version (gradlePluginVersion)
    id("com.tridium.vendor") version (gradlePluginVersion)
    id("com.tridium.niagara-module") version (gradlePluginVersion)
    id("com.tridium.niagara-signing") version (gradlePluginVersion)

    id("com.tridium.convention.niagara-home-repositories") version (gradlePluginVersion)
  }
}

plugins {
  // Discover all subprojects in this build
  id("com.tridium.settings.multi-project")

  // Apply local settings from local/my-niagara.gradle(.kts) if they
  // are present
  id("com.tridium.settings.local-settings-convention")
}

configure<LocalSettingsExtension> {
  loadLocalSettings()
}

configure<MultiProjectExtension> {
  // Note: If you have specific subfolder(s) to include, you can pass their relative
  // path as an argument:
  //
  //   findProjects("folder1")
  //   findProjects("folder2")
  //
  // Otherwise, this will find all projects under the root directory with the following layout:
  //
  //    project-rt/project-rt.gradle.kts
  //    project-rt/project-rt.gradle
  //    project-rt/build.gradle.kts
  //    project-rt/build.gradle
  findProjects()
}

rootProject.name = "myModule"

niagara-module.xml

<?xml version="1.0" encoding="UTF-8"?>
<niagara-module moduleName="myModule" preferredSymbol="mmod" runtimeProfiles="rt,ux"/>

module-rt.gradle.kts

/*
 * Copyright 2022 Acme. All Rights Reserved.
 */

import com.tridium.gradle.plugins.module.util.ModulePart.RuntimeProfile.*

plugins {
  // The Niagara Module plugin configures the "moduleManifest" extension and the
  // "jar" and "moduleTestJar" tasks.
  id("com.tridium.niagara-module")

  // The signing plugin configures the correct signing of modules. It requires
  // that the plugin also be applied to the root project.
  id("com.tridium.niagara-signing")

  // The bajadoc plugin configures the generation of Bajadoc for a module.
  id("com.tridium.bajadoc")

  // Configures JaCoCo for the "niagaraTest" task of this module.
  id("com.tridium.niagara-jacoco")

  // The Annotation processors plugin adds default dependencies on ":nre"
  // for the "annotationProcessor" and "moduleTestAnnotationProcessor"
  // configurations by creating a single "niagaraAnnotationProcessor"
  // configuration they extend from. This value can be overridden by explicitly
  // declaring a dependency for the "niagaraAnnotationProcessor" configuration.
  id("com.tridium.niagara-annotation-processors")

  // The niagara_home repositories convention plugin configures !bin/ext and
  // !modules as flat-file Maven repositories so that projects in this build can
  // depend on already-installed Niagara modules.
  id("com.tridium.convention.niagara-home-repositories")
}

description = "My Module"

moduleManifest {
  moduleName.set("myModule")
  runtimeProfile.set(rt)
}

// See documentation in local:|module://docDeveloper/doc/build.html for the supported
// dependency types
dependencies {
  // NRE dependencies
  nre(":nre")

  // Niagara module dependencies
  api(":baja")

  // Test Niagara module dependencies
  moduleTestImplementation(":test-wb")
}

module-ux.gradle.kts

/*
 * Copyright 2022 Acme. All Rights Reserved.
 */

import com.tridium.gradle.plugins.grunt.task.GruntBuildTask
import com.tridium.gradle.plugins.module.util.ModulePart.RuntimeProfile.*

plugins {
  // The Niagara Module plugin configures the "moduleManifest" extension and the
  // "jar" and "moduleTestJar" tasks.
  id("com.tridium.niagara-module")

  // The signing plugin configures the correct signing of modules. It requires
  // that the plugin also be applied to the root project.
  id("com.tridium.niagara-signing")

  // The bajadoc plugin configures the generation of Bajadoc for a module.
  id("com.tridium.bajadoc")

  // Configures JaCoCo for the "niagaraTest" task of this module.
  id("com.tridium.niagara-jacoco")

  // The Annotation processors plugin adds default dependencies on ":nre"
  // for the "annotationProcessor" and "moduleTestAnnotationProcessor"
  // configurations by creating a single "niagaraAnnotationProcessor"
  // configuration they extend from. This value can be overridden by explicitly
  // declaring a dependency for the "niagaraAnnotationProcessor" configuration.
  id("com.tridium.niagara-annotation-processors")

  // The niagara_home repositories convention plugin configures !bin/ext and
  // !modules as flat-file Maven repositories so that projects in this build can
  // depend on already-installed Niagara modules.
  id("com.tridium.convention.niagara-home-repositories")

  id("com.tridium.niagara-grunt")
}

description = "My Module"

moduleManifest {
  moduleName.set("myModule")
  runtimeProfile.set(ux)
}

// See documentation in local:|module://docDeveloper/doc/build.html for the supported
// dependency types
dependencies {
  // NRE dependencies
  nre(":nre")

  // Niagara module dependencies
  api(":baja")

  // Test Niagara module dependencies
  moduleTestImplementation(":test-wb")
}

tasks.named<GruntBuildTask>("gruntBuild") {
  tasks("babel:dist", "copy:dist", "requirejs")
}

Updating from an Older Niagara Version

See the full upgrade guide