If the module you are developing has dependencies on third-party libraries or modules other than server/platform/api or server/platform/core, you will need to add a build.gradle file in the module’s directory that declares these dependencies.

Example build.gradle File Dependencies

For example, the build.gradle file for the server/platform/pipeline module includes the following:

import org.labkey.gradle.util.BuildUtils
import org.labkey.gradle.util.ExternalDependency

plugins {
id 'org.labkey.build.module'
}

dependencies {
implementation "com.sun.mail:jakarta.mail:${javaMailVersion}"
BuildUtils.addExternalDependency(
project,
new ExternalDependency(
"org.apache.activemq:activemq-core:${activemqCoreVersion}",
"Apache ActiveMQ",
"Apache",
"http://activemq.apache.org/",
ExternalDependency.APACHE_2_LICENSE_NAME,
ExternalDependency.APACHE_2_LICENSE_URL,
"Java Message Service queuing",
),
{
exclude group: "javax.servlet", module: "servlet-api"
}
)

< … snip lines … >

BuildUtils.addExternalDependency(
project,
new ExternalDependency(
"com.thoughtworks.xstream:xstream:${xstreamVersion}",
"XStream",
"XStream",
"http://x-stream.github.io/index.html",
"BSD",
"http://x-stream.github.io/license.html",
"Pipeline (Mule dependency)",
)
)
BuildUtils.addLabKeyDependency(project: project, config: "implementation", depProjectPath: BuildUtils.getPlatformModuleProjectPath(project.gradle, "core"), depProjectConfig: 'apiJarFile')

}

External Dependencies

External dependencies should be declared using the LabKey-defined "external" configuration. This configuration is used when creating the .module file to know which libraries to include with the module. In the LabKey gradle plugins, we have a utility method (BuildUtils.addExternalDependency) for adding these dependencies that allows you to supply some extra metadata about the dependency related to its licensing and source as well as a short description. This metadata is then displayed on the Admin Console's Credits page. Using this method for external dependencies should be preferred over the usual dependency declaration syntax from Gradle. The arguments to BuildUtils.addExternalDependency are the current project and an ExternalDependency object. The constructor for this object takes 7 arguments as follows:

  1. The artifact coordinates
  2. The name of the component used by the module
  3. The name of the source for the component
  4. The URL for the source of the component
  5. The name of the license
  6. The URL for the license
  7. A description of the purpose for this dependency
The proper artifact coordinates are needed for external dependencies to be found from the artifact repository. The coordinates consist of the group name (string before the first colon, e.g., 'org.apache.activemq'), the artifact name (string between the first and second colons, e.g., 'acitvemq-core'), and version number (string after the second colon and before the third colon, if any, e.g., '4.2.1'). You may also need to include a classifier for the dependency (the string after the third colon, e.g., 'embedded'). To find the proper syntax for the external dependencies, you can query MavenCentral, look for the version number you are interested in and then copy and paste from the gradle tab. It is generally best practice to set up properties for the versions in use.

If the library you need is not available in one of the existing repositories, those who have access to the LabKey Artifactory can navigate to the "ext-release-local" artifact set, click the Deploy link in the upper right corner, and upload the JAR file. Artifactory will attempt to guess what a reasonable group, artifact, and version number might be, but correct as needed. Once added, it can be referenced in a module's build.gradle file like any other dependency. The artifact will need a proper pom file, so you should be sure to check the box next to "Generate Default POM/Deploy jar's Internal POM" when deploying.

Internal Dependencies

For internal dependencies, the BuildUtils.addLabKeyDependecy method referenced above will reference the buildFromSource gradle property to determine if a project dependency should be declared (meaning the artifacts are produced by a local build) or a package dependency (meaning the artifacts are pulled from the artifact server). The argument to this method is a map with the following entries:

  • project: The current project where dependencies are being declared
  • config: The configuration in which the dependency is being declared
  • depProjectPath: The (Gradle) path for the project that is the dependency
  • depProjectConfig : The configuration of the dependency that is relevant. This is optional and defaults to the default configuration or an artifact without a classifier
  • depVersion: The version of the dependency to retrieve. This is optional and defaults to the parent project’s version if the dependent project is not found in the gradle configuration or the dependent project’s version if it is found in the gradle configuration.
  • transitive: Boolean value to indicate if the dependency should be transitive or not
  • specialParams: This is a closure that can be used to configure the dependency, just as you can with the closure for a regular Gradle dependency.
To declare a compile-time dependency between one module and the API jar of a second module, you will do this:
import org.labkey.gradle.util.BuildUtils

BuildUtils.addLabKeyDependency(project: project, config: "implementation", depProjectPath: ":server:modules:someModule", depProjectConfig: "apiJarFile")
To assure that the module that is depended on is available at runtime, you will also need to declare a dependency on the module itself
BuildUtils.addLabKeyDependency(project: project, config: "modules", depProjectPath: ":server:modules:someModule", depProjectConfig: "published", depExtension: "module")

If your module relies on the API jar file of another module, but does not need the module itself (likely because the code checks for the presence of the module before using its functionality), you can use the "labkey" configuration when declaring this dependency. It is likely this dependency should not be transitive:

import org.labkey.gradle.util.BuildUtils

BuildUtils.addLabKeyDependency(project: project, config: "labkey", depProjectPath: ":server:modules:someModule", depProjectConfig: 'apiJarFile', transitive: false)

If the api jar file of the module you depend on exposes classes from a different jar file used to build this jar file, you should declare a dependency on the "runtimeElements" configuration from the other module. That module should be declaring its dependency to that separate jar file as an 'external' dependency. (This works because internally gradle's now-standard 'api' configuration, on which 'runtimeElements' is based, extends from the LabKey 'external' configuration.)

Module Dependencies

The moduleDependencies module-level property is used by LabKey server to determine the module initialization order and to control the order in which SQL scripts run. It is also used when publishing the .module file to record the proper dependencies in the pom file for that publication. These dependencies should be declared within a module's build.gradle file.

For moduleA that depends on moduleB, you would add the following line to the moduleA/build.gradle file:


BuildUtils.addLabKeyDependency(project: project, config: "modules", depProjectPath: ":server:myModules:moduleB", depProjectConfig: 'published', depExtension: 'module')

The behavior when a module is not in the settings.gradle file and/or when you do not have an enlistment for a module is as follows:

  • If :server:myModules:moduleB is not included in the settings.gradle file, moduleB will be treated like an external dependency and its .module file will be downloaded from Artifactory and placed in the build/deploy/modules directory by the deployApp command
  • If :server:myModules:moduleB is included in your settings.gradle file, but you do not have an enlistment in moduleB, by default, this will cause a build error such as "Cannot find project for :server:myModules:moduleB". You can change this default behavior by using the parameter -PdownloadLabKeyModules, and this will cause the .module file to be downloaded from Artifactory and deployed to build/deploy/modules, as in the previous case
  • If :server:myModules:moduleB is included in settings.gradle and you have an enlistment in moduleB, it will be built and deployed as you might expect.
As of gradlePlugin version 1.8, the .module files for LabKey Server are being published in a separate Maven group from the api jar files along with appropriate pom files that capture the module dependencies declared in their build.gradle files. The group for the .module files is org.labkey.module, while the group for the api jar files is org.labkey.api. This means that you can more easily pull in an appropriate set of modules to create a running LabKey server instance without building modules from source. In particular, if you add dependencies to the basic set of modules required for functional LabKey server within your module's build.gradle file, you should not need to enlist in the source for these modules or include them in your settings.gradle file. For example, within the build.gradle file for moduleA, you can declare the following dependencies
import org.labkey.gradle.util.BuildUtils

dependencies {
modules ("org.labkey.module:api:${labkeyVersion}@module") { transitive = true }
modules ("org.labkey.module:audit:${labkeyVersion}@module") { transitive = true }
modules ("org.labkey.module:core:${labkeyVersion}@module") { transitive = true }
modules ("org.labkey.module:experiment:${labkeyVersion}@module") { transitive = true }
modules ("org.labkey.module:filecontent:${labkeyVersion}@module") { transitive = true }
modules ("org.labkey.module:pipeliine:${labkeyVersion}@module") { transitive = true }
modules ("org.labkey.module:query:${labkeyVersion}@module") { transitive = true }
BuildUtils.addLabKeyDependency(project: project, config: "modules", depProjectPath: ":server:myModules:moduleB", depProjectConfig: 'published', depExtension: 'module')
}
Without having an enlistment in any of the base modules (api, audit, core, etc.) or inclusion of the corresponding projects in your settings.gradle file, the deployApp task will pull in the modules that are required for a functional server. Note that the set of modules you have in your deployment will be a superset of the ones declared in the dependency closure, because of the dependencies declared within the modules' published pom files.

There is also a utility method available in the BuildUtils class of the LabKey gradle plugins that can be used to declare the base module dependencies, so the above could be changed to

import org.labkey.gradle.util.BuildUtils

BuildUtils.addBaseModuleDependencies(project)
BuildUtils.addLabKeyDependency(project: project, config: "modules", depProjectPath: ":server:myModules:moduleB", depProjectConfig: 'published', depExtension: 'module')

Discovering Dependencies

The task 'allDepInsight' can help to determine what the dependencies are for a given module:

./gradlew allDepInsight --dependency=MODULE_NAME --configuration=modules

Adding a Module to Your Deployment

If you are building locally and want to include a .module file that is not an explicit dependency of any of your other modules, this can be done by declaring a dependency from the server project's modules configuration to the other module you wish to deploy. This is probably easiest to do by adding something like the following in your module's build.gradle file:

import org.labkey.gradle.util.BuildUtils

BuildUtils.addLabKeyDependency(project: BuildUtils.getServerProject(project), config: "modules", depProjectPath: ":server:modules:experiment", depProjectConfig: "published", depExtension: "module")

Adding Premium Modules to Developer Machines (Premium Feature)

Any developers working with Premium Editions of LabKey Server may want to include one or more premium modules in their local development machines. As an example, in order to use ETLs, you must have the dataintegration module on your server. Premium deployments include this module, but individual developers may also need it to be present on their development machines.

To include this module in your build, include a dependency to 'pull' the prebuilt module from the LabKey Artifactory. Access to the LabKey Artifactory is required and can be provided upon request to your Account Manager. Once access has been granted, you have a few options for providing your credentials when building. Learn more in this topic:

Once you have the necessary access, declare a dependency on the desired module in the build.gradle file for a module that is present in your source enlistment. If you are developing your own module, you can use it's build.gradle file, otherwise, the server/platform/list module's build.gradle file is a good choice. Add this line:
BuildUtils.addLabKeyDependency(project: project, config: "modules", depProjectPath: ":server:modules:dataintegration", depProjectConfig: 'published', depExtension: 'module')

When you build locally, the prebuilt dataintegration module will be included and thus available on your local server.

Troubleshooting

If you include this dependency but do not have the appropriate credentials on Artifactory, you'll see a build error similar to the following. Note that this can happen when your credentials have not been granted as well as if they have been locked for some reason. Contact your Account Manager for assistance.

Could not resolve [library name]…  
> Could not GET 'https://labkey.jfrog.io/artifactory/...rest of URL ending in.pom'. Received status code 403 from server:

Resolve Conflicts

After adding a new external dependency, or updating the version of an existing external dependency, you will want to make sure the dependency hasn't introduced a version inconsistency with other modules. To do this, you can run the task 'showDiscrepancies' and you will want to include as many modules as possible for this task, so using the module set 'all' is a good idea:

./gradlew -PmoduleSet=all showDiscrepancies
If there are any discrepancies in external jar version numbers, this task will produce a report that shows the various versions in use and by which modules as shown here.
commons-collections:commons-collections has 3 versions as follows:
3.2 [:server:modules:query, :server:modules:saml]
3.2.1 [:externalModules:labModules:LDK]
3.2.2 [:server:api]
Each of these conflicts should be resolved before the new dependency is added or updated. Preferably, the resolution will be achieved by choosing a different version of a direct dependency in one or more modules. The task 'allDepInsight' can help to determine where a dependency comes from:
./gradlew allDepInsight --configuration=external --dependency=commons-collections

If updating direct dependency versions does not resolve the conflict, you can force a certain version of a dependency, which will apply to direct and transitive dependencies. See the root-level build.gradle file for examples of the syntax for forcing a version.

Version Conflicts in Local Builds

When version numbers are updated, either for LabKey itself or for external dependencies, a local build can accumulate multiple, conflicting versions of certain jar files in its deploy directory, or the individual module build directories. This is never desirable. With gradlePlugin version 1.3, tasks have been added to the regular build process that check for such conflicts.

By default, the build will fail if a version conflict is detected, but the property 'versionConflictAction' can be used to control that behavior. Valid values for this property are:

  • 'delete' - this causes individual files in the deploy directory that are found in conflict with ones that are to be produced by the build to be deleted.
> Task :server:api:checkModuleJarVersions 
INFO: Artifact versioning problem(s) in directory /labkeyEnlistment/build/modules/api/explodedModule/lib:
Conflicting version of commons-compress jar file (1.14 in directory vs. 1.16.1 from build).
Conflicting version of objenesis jar file (1.0 in directory vs. 2.6 from build).
INFO: Removing existing files that conflict with those from the build.
Deleting /labkeyEnlistment/build/modules/api/explodedModule/lib/commons-compress-1.14.jar
Deleting /labkeyEnlistment/build/modules/api/explodedModule/lib/objenesis-1.0.jar


BUILD SUCCESSFUL in 5s
Note that when multiple versions of a jar file are found to already exist in the build directory, none will be deleted. Manual intervention is required here to choose which version to keep and which to delete.
Execution failed for task ':server:api:checkModuleJarVersions'.
> Artifact versioning problem(s) in directory /labkeyEnlistment/build/modules/api/explodedModule/lib:
Multiple existing annotations jar files.
Run the :server:api:clean task to remove existing artifacts in that directory.

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 4s
  • 'fail' (default) - this causes the build to fail when the first version conflict or duplicate version is detected.
Execution failed for task ':server:api:checkModuleJarVersions'.
> Artifact versioning problem(s) in directory /labkeyEnlistment/build/modules/api/explodedModule/lib:
Conflicting version of commons-compress jar file (1.14 in directory vs. 1.16.1 from build).
Conflicting version of objenesis jar file (1.0 in directory vs. 2.6 from build).
Run the :server:api:clean task to remove existing artifacts in that directory.

BUILD FAILED in 20s
  • 'warn' - this will issue a warning message about conflicts, but the build will succeed. This can be useful in finding how many conflicts you have since the 'fail' option will show only the first conflict that is found.
> Task :server:api:checkModuleJarVersions 
WARNING: Artifact versioning problem(s) in directory /labkeyEnlistment/build/modules/api/explodedModule/lib:
Conflicting version of commons-compress jar file (1.14 in directory vs. 1.16.1 from build).
Conflicting version of objenesis jar file (1.0 in directory vs. 2.6 from build).
Run the :server:api:clean task to remove existing artifacts in that directory.


BUILD SUCCESSFUL in 5s

Though these tasks are included as part of the task dependency chains for building and deploying modules, the four tasks can also be executed individually, which can be helpful for resolving version conflicts without resorting to cleaning out the entire build directory. The tasks are:

  • checkModuleVersions - checks for conflicts in module file versions
  • checkWebInfLibJarVersions - checks for conflicts in jar files included in the WEB-INF/lib directory
  • checkModuleJarVersions - checks for conflicts in the jar files included in an individual module
  • checkVersionConflicts - runs all of the above tasks

Related Topics

Was this content helpful?

Log in or register an account to provide feedback


previousnext
 
expand allcollapse all