Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
89cf934
Update comment
labkey-susanh Aug 4, 2026
386a6e8
Convert verifyLicensePatch and patchApiModule to configuration-cache …
labkey-susanh Aug 4, 2026
a1232fa
Convert symlinkNode, deployModule, undeployModule
labkey-susanh Aug 4, 2026
aadfebe
Make UndeployModules compatible with configuration cache
labkey-susanh Aug 4, 2026
a7a0a43
Make UndeployModules compatible with configuration cache
labkey-susanh Aug 4, 2026
387f936
Make UndeployModules compatible with configuration cache
labkey-susanh Aug 4, 2026
44608a8
Rename to avoid getter collisions
labkey-susanh Aug 4, 2026
5a409e8
Don't use hasProperty for "moduleContainer" as it is deprecated
labkey-susanh Aug 5, 2026
b1c0c1a
Update `ModuleDistribution` to be compatible with the configuration c…
labkey-susanh Aug 5, 2026
b2acc7b
Gradle 9.7.0
labkey-susanh Aug 11, 2026
65ce6f6
Typo
labkey-susanh Aug 11, 2026
55f831c
Update RunUiTest to be compatible with the configuration cache
labkey-susanh Aug 11, 2026
4cdcc5a
Update MultiGit tasks to mark as not config-cache compatible
labkey-susanh Aug 11, 2026
104d69d
Convert tasks that write out startup properties in `TeamCity` plugin …
labkey-susanh Aug 11, 2026
21feaaf
Update `TestRunner`'s `compileUITestJava` task to be config-cache com…
labkey-susanh Aug 11, 2026
6d40eb2
Update release notes
labkey-susanh Aug 12, 2026
2a32d3d
Delete file created when there is no content
labkey-susanh Aug 12, 2026
91b345b
Remove dead code
labkey-susanh Aug 12, 2026
11ce98b
Use flatMap for lazy evaluation
labkey-susanh Aug 12, 2026
10f441f
cleaning
labkey-susanh Aug 12, 2026
b95d01e
Use FileCollection instead of Set<File>
labkey-susanh Aug 12, 2026
3f63d0a
Make stagedModuleFile an output file as well
labkey-susanh Aug 12, 2026
c559c90
Try again to make this a cacheable task
labkey-susanh Aug 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ on how to do that, including how to develop and test locally and the versioning
*Released*: TBD
(Earliest compatible LabKey version: 26.6.0)
- Pass all command-line 'webtest' and 'webdriver' properties to tests (fix to work off of TeamCity)
- Convert `verifyLicensePatch`, `patchApiModule`, `deployModule`, `undeployModule`, `symlinkNode`, `undeployModules` to configuration-cache compatible tasks
- Update `ModuleFinder` to not use deprecated `hasProperty` check that looks in parent project
- Update `ModuleDistribution` and `RunUiTest` to be compatible with the configuration cache
- Updates to `MultiGit` to mark as not configuration-cache compatible
- Convert tasks that write out startup properties in `TeamCity` plugin to be compatible with the configuration cache
- Update `TestRunner`'s `compileUITestJava` task to be config-cache compatible and move the declaration of the `aspectj` configuration to that plugin

### 9.2.0
*Released*: 27 July 2026
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ dependencies {
}

group = 'org.labkey.build'
project.version = "9.3.0-SNAPSHOT"
project.version = "9.3.0-configCacheClasses-SNAPSHOT"

gradlePlugin {
plugins {
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.0-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
Expand Down
4 changes: 2 additions & 2 deletions gradlew

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions gradlew.bat

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 8 additions & 6 deletions src/main/groovy/org/labkey/gradle/plugin/Api.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.attributes.Usage
import org.gradle.api.file.DuplicatesStrategy
import org.gradle.api.file.FileTree
import org.gradle.api.tasks.Copy
import org.gradle.api.tasks.bundling.Jar
import org.labkey.gradle.plugin.extension.LabKeyExtension
Expand Down Expand Up @@ -124,13 +125,14 @@ class Api implements Plugin<Project>
}
}

// It may seem proper to make this action a dependency on the project's clean task since the
// jar file is put there by the build task, but since the copy is more of a deployment
// task than a build task and removing it will affect the running server, we make this
// deletion a step for the 'undeployModule' task instead
static void deleteModulesApiJar(Project project)
/**
* @param project the project whose api jar files are to be found
* @return the api jar files copied to the {@link #MODULES_API_DIR} directory for this project. The tree is not
* resolved until it is queried, so it can be used as a property value for a task that deletes these files.
*/
static FileTree getModulesApiJars(Project project)
{
project.delete project.fileTree(project.rootProject.layout.buildDirectory.file(MODULES_API_DIR)) {include "**/${project.name}_api*.jar"}
return project.fileTree(project.rootProject.layout.buildDirectory.file(MODULES_API_DIR)) {include "**/${project.name}_api*.jar"}
}

private void addArtifacts(Project project)
Expand Down
56 changes: 13 additions & 43 deletions src/main/groovy/org/labkey/gradle/plugin/ApplyLicenses.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@
*/
package org.labkey.gradle.plugin

import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.file.DuplicatesStrategy
import org.gradle.api.tasks.bundling.Jar
import org.labkey.gradle.task.PatchApiModule
import org.labkey.gradle.task.VerifyLicensePatch
import org.labkey.gradle.util.BuildUtils
import org.labkey.gradle.util.GroupNames

Expand Down Expand Up @@ -68,36 +67,18 @@ class ApplyLicenses implements Plugin<Project>
private static void addTasks(Project project)
{
if (!BuildUtils.isOpenSource(project)) {
var patchApiTask = project.tasks.register('patchApiModule', Jar) {
Jar jar ->
var patchApiTask = project.tasks.register('patchApiModule', PatchApiModule) {
PatchApiModule jar ->
jar.group = GroupNames.DISTRIBUTION
jar.description = "Patches the api module to replace ExtJS libraries with commercial versions"
jar.archiveBaseName.set("api")
jar.archiveVersion.set(project.getVersion().toString())
jar.archiveClassifier.set("extJsCommercial")
jar.archiveExtension.set('module')
jar.destinationDirectory.set(project.layout.buildDirectory.dir("patchApiModule"))
jar.outputs.cacheIf({ true })
// first include the ext-3.4.1 and ext-4.2.1 directories from the extjs configuration artifacts
jar.into('web') {
from project.configurations.extJs3Commercial.collect {
project.zipTree(it)
}
}
jar.into('web') {
from project.configurations.extJs4Commercial.collect {
project.zipTree(it)
}
}
// include the original module file ...
jar.from project.configurations.licensePatch.collect {
project.zipTree(it).matching {
// DuplicatesStrategy.EXCLUDE doesn't seem to work in some environments
exclude('web/ext-*/**')
}
}
// ... but don't use the ext directories that come from that file
jar.setDuplicatesStrategy(DuplicatesStrategy.EXCLUDE)
jar.extJs3Archives.from(project.configurations.extJs3Commercial)
jar.extJs4Archives.from(project.configurations.extJs4Commercial)
jar.moduleArchives.from(project.configurations.licensePatch)
jar.manifest.attributes(
"Implementation-Version": project.version,
"Implementation-Title": "Internal API classes",
Expand All @@ -110,23 +91,12 @@ class ApplyLicenses implements Plugin<Project>
}
}

project.tasks.register('verifyLicensePatch') {
it.group = GroupNames.TEST
it.dependsOn(patchApiTask)
it.doLast {
[project.configurations.extJs3Commercial, project.configurations.extJs4Commercial].forEach {
def commercialLicense = project.zipTree(it.singleFile).matching {
include '*/license.txt'
}.singleFile
def patchedLicense = project.zipTree(patchApiTask.get().outputs.files.singleFile).matching {
include 'web/' + commercialLicense.parentFile.name + '/license.txt'
}.singleFile
if (commercialLicense.length() != patchedLicense.length()) {
throw new GradleException("License files didn't match for " + commercialLicense.parentFile.name)
}
}
}
it.notCompatibleWithConfigurationCache("Needs to inject ArtifactOperations for zipTree usage")
project.tasks.register('verifyLicensePatch', VerifyLicensePatch) {
VerifyLicensePatch verify ->
verify.group = GroupNames.TEST
verify.description = "Verifies that the patched api module contains the commercial ExtJS license files"
verify.commercialArchives.from(project.configurations.extJs3Commercial, project.configurations.extJs4Commercial)
verify.patchedArchive.set(patchApiTask.flatMap { PatchApiModule jar -> jar.archiveFile })
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@ class ClientLibraries
task.description = 'create minified, compressed javascript file using .lib.xml sources'
task.dependsOn(project.tasks.processResources)
task.dependsOn(project.project(minProjectPath).tasks.named("npmInstall"))
task.xmlFiles = getLibXmlFiles(project)
task.notCompatibleWithConfigurationCache("Class ClientLibsCompress needs more input and output properties declared")
task.xmlFiles.from(getLibXmlFiles(project))
}

project.evaluationDependsOn(minProjectPath)
Expand Down
81 changes: 30 additions & 51 deletions src/main/groovy/org/labkey/gradle/plugin/FileModule.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,15 @@ import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.attributes.Attribute
import org.gradle.api.attributes.Usage
import org.gradle.api.file.CopySpec
import org.gradle.api.file.DuplicatesStrategy
import org.gradle.api.java.archives.Manifest
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.tasks.Delete
import org.gradle.api.tasks.bundling.Jar
import org.labkey.gradle.plugin.extension.LabKeyExtension
import org.labkey.gradle.plugin.extension.ModuleExtension
import org.labkey.gradle.plugin.extension.ServerDeployExtension
import org.labkey.gradle.task.DeployModule
import org.labkey.gradle.task.ModuleXmlFile
import org.labkey.gradle.task.UndeployModule
import org.labkey.gradle.util.BuildUtils
import org.labkey.gradle.util.GroupNames
import org.labkey.gradle.util.PomFileHelper
Expand Down Expand Up @@ -182,55 +181,20 @@ class FileModule implements Plugin<Project>
published(moduleTask)
}

project.tasks.register('deployModule')
{ Task task ->
project.tasks.register('deployModule', DeployModule)
{ DeployModule task ->
task.group = GroupNames.MODULE
task.description = "copy a project's .module file to the local deploy directory"
task.inputs.files moduleTask
task.outputs.file "${ServerDeployExtension.getModulesDeployDirectory(project)}/${moduleTask.get().outputs.getFiles()[0].getName()}"

task.doLast {
project.copy { CopySpec copy ->
copy.from moduleTask
copy.from project.configurations.modules
copy.into "${BuildUtils.getRootBuildDirPath(project)}/$ServerDeploy.STAGING_MODULES_DIR"
copy.setDuplicatesStrategy(DuplicatesStrategy.INCLUDE)
}
project.copy { CopySpec copy ->
copy.from moduleTask
copy.from project.configurations.modules
copy.into ServerDeployExtension.getModulesDeployDirectory(project)
copy.setDuplicatesStrategy(DuplicatesStrategy.INCLUDE)
}
BuildUtils.updateRestartTriggerFile(project)
}
task.notCompatibleWithConfigurationCache("Needs its own class to do the two copies (one to staging and one to deploy or possibly two Copy tasks chained together.")
task.moduleFiles.from(moduleTask, project.configurations.modules)
task.moduleFileName.set(moduleTask.flatMap { Jar jar -> jar.archiveFileName })
}



project.tasks.register('undeployModule', Delete) {
Delete task ->
project.tasks.register('undeployModule', UndeployModule) {
UndeployModule task ->
task.group = GroupNames.MODULE
task.description = "remove a project's .module file and the unjarred file from the deploy directory"
task.configure(
{ Delete delete ->
getModuleFilesAndDirectories(project).forEach({
File file ->
if (file.isDirectory())
delete.inputs.dir file
else
delete.inputs.file file
})
})
task.doFirst {
undeployModule(project)
Api.deleteModulesApiJar(project)
}
task.doLast {
BuildUtils.updateRestartTriggerFile(project)
}
task.notCompatibleWithConfigurationCache("Does multiple deletes using project.delete. Should have its own class.")
}

project.tasks.register("reallyClean") {
Expand Down Expand Up @@ -270,11 +234,28 @@ class FileModule implements Plugin<Project>
*/
static List<File> getModuleFilesAndDirectories(Project project, Boolean includeDeployed = true, Boolean includeStaging=true)
{
String moduleFilePrefix = "${project.name}-"
return getModuleFilesAndDirectories(
project.name,
includeDeployed ? new File(ServerDeployExtension.getModulesDeployDirectory(project)) : null,
includeStaging ? BuildUtils.getRootBuildDirFile(project, ServerDeploy.STAGING_MODULES_DIR) : null
)
}

/**
* The same as {@link #getModuleFilesAndDirectories(Project, Boolean, Boolean)} but without any reference to a
* project, so it can be used from a task action.
* @param moduleName the name of the module whose files are to be found
* @param deployDir the deploy directory to look in, or null to skip the deploy directory
* @param stagingDir the staging directory to look in, or null to skip the staging directory
* @return list of files and directories for this module with the deploy .module files first, followed by the deploy
* directories followed by the staging .module files.
*/
static List<File> getModuleFilesAndDirectories(String moduleName, File deployDir, File stagingDir)
{
String moduleFilePrefix = "${moduleName}-"
List<File> files = new ArrayList<>()
if (includeDeployed)
if (deployDir != null)
{
File deployDir = new File(ServerDeployExtension.getModulesDeployDirectory(project))
if (deployDir.isDirectory())
{
// first add the files because we want to delete these first. If the directory goes away and the .module file is there
Expand All @@ -293,17 +274,15 @@ class FileModule implements Plugin<Project>
@Override
boolean accept(final File file)
{
return file.isDirectory() && (file.getName().startsWith("${project.name}-") || file.getName().equalsIgnoreCase(project.name))
return file.isDirectory() && (file.getName().startsWith(moduleFilePrefix) || file.getName().equalsIgnoreCase(moduleName))
}
})
)
}
}
// staging has only the .modules files
if (includeStaging)
if (stagingDir != null)
{

File stagingDir = BuildUtils.getRootBuildDirFile(project, ServerDeploy.STAGING_MODULES_DIR)
if (stagingDir.isDirectory())
{
files.addAll(stagingDir.listFiles(new FilenameFilter() {
Expand Down
Loading