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

import org.labkey.gradle.util.BuildUtils

dependencies {
external 'org.apache.activemq:activemq-core:4.1.2'
external 'org.mule.modules:mule-module-builders:1.4.4:embedded'
external 'com.thoughtworks.xstream:xstream:1.2.2'
BuildUtils.addLabKeyDependency(project: project, config: "implementation", depProjectPath: BuildUtils.getPlatformModuleProjectPath(project.gradle, "core"), depProjectConfig: 'apiJarFile')
}

External Dependencies

In order for external dependencies to be found from the artifact repository, it is necessary to use the proper group name (string before the first colon, e.g., 'org.apache.activemq') and 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 bintray or 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. External dependencies should be added to the LabKey-defined “external” configuration, while dependencies on other LabKey modules should be added to the “compile” configuration. The “external” configuration is used when creating the .module file to know which libraries to include with the module.

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

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

dependencies {
BuildUtils.addLabKeyDependency(project: project, config: "implementation", depProjectPath: ":server:modules:someModule", depProjectConfig: 'apiJarFile')
}
The assumption is that the module that is depended upon will be in the server distribution, and thus its jar files will be on the classpath, so there is no need to include its API jar in the lib directory of the first module. If this assumption is not valid and you do need or want to include the API jar of the second module within the lib directory of the first, prior to Gradle Plugins release 1.3.2, you would declare this as an "external" dependency like so:
import org.labkey.gradle.util.BuildUtils

dependencies {
BuildUtils.addLabKeyDependency(project: project, config: "external", depProjectPath: ":server:modules:someModule", depProjectConfig: 'apiCompile')
}
This will put the API jar file for :server:modules:someModule in the lib directory of the module that has declared this dependency and will require an entry in the resources/credits/jars.txt file so the credits check does not notice a discrepancy.

As of Gradle Plugins release 1.3.2, a new "labkey" configuration was introduced to obviate the need for declaring the dependency within the jars.txt file. The API dependencies for LabKey API jars of modules should now be declared using this "labkey" configuration, and it is likely this dependency should not be transitive:

import org.labkey.gradle.util.BuildUtils

dependencies {
BuildUtils.addLabKeyDependency(project: project, config: "labkey", depProjectPath: ":server:modules:someModule", depProjectConfig: 'apiJarFile', transitive: false)
}
No entry will be required in the jars.txt file, but the API jar file will be included in the lib directory.

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 "apiElements" 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 'apiElements' 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. As of gradlePlugins version 1.2.3, the dependencies can be declared within a module's build.gradle file. As of gradlePlugins version 1.8, use of ModuleProperties in the module.properties file is deprecated.

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

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

Then you can remove the corresponding line from moduleA's module.properties file:

ModuleDependencies: moduleB

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 and LabKey Server version 19.3, 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
dependencies { 
modules ("org.labkey.module:api:${labkeyVersion}@module") { transitive = true }
modules ("org.labkey.module:internal:${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:filecontent:${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, internal, audit, 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.

Resolving 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 /Users/susanhert/Development/labkey/trunk/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 /Users/susanhert/Development/labkey/trunk/build/modules/api/explodedModule/lib/commons-compress-1.14.jar
Deleting /Users/susanhert/Development/labkey/trunk/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 /Users/susanhert/Development/labkey/trunk/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 /Users/susanhert/Development/labkey/trunk/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 /Users/susanhert/Development/labkey/trunk/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