Skip to content

Commit ebd0585

Browse files
Update PurgeArtifacts task for configuration cache and more complete clean up (#233)
* Gradle 8.14.3 * Update `PurgeArtifacts` task to be compatible with configuration cache and to work with input files so archived projects can be kept up to date * Dependency version updates
1 parent 18e34ec commit ebd0585

7 files changed

Lines changed: 168 additions & 64 deletions

File tree

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ on how to do that, including how to develop and test locally and the versioning
1313

1414
## Release Notes
1515

16+
### TBD
17+
*Released*: TBD
18+
(Earliest compatible LabKeyversion: 25.2)
19+
- Update `PurgeArtifacts` task to be compatible with configuration cache and to work with input files so archived projects can be kept up to date
20+
- Update dependency versions
21+
1622
### 6.3.0
1723
*Released*: 3 July 2025
1824
(Earliest compatible LabKey version: 25.2)

gradle.properties

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ artifactory_contextUrl=https://labkey.jfrog.io/artifactory
22

33
artifactoryPluginVersion=5.2.5
44

5-
commonsIoVersion=2.18.0
6-
commonsLang3Version=3.17.0
7-
commonsTextVersion=1.13.0
5+
commonsIoVersion=2.20.0
6+
commonsLang3Version=3.18.0
7+
commonsTextVersion=1.14.0
88

9-
grgitGradleVersion=5.3.0
10-
graphqlJavaVersion=22.3
9+
grgitGradleVersion=5.3.2
10+
graphqlJavaVersion=24.2
1111

12-
httpclientVersion=5.4.1
13-
httpcoreVersion=5.3.1
12+
httpclientVersion=5.5
13+
httpcoreVersion=5.3.4
1414

15-
jacksonVersion=2.18.2
16-
jsonVersion=20240303
15+
jacksonVersion=2.19.2
16+
jsonVersion=20250517

gradle/wrapper/gradle-wrapper.jar

181 Bytes
Binary file not shown.

gradle/wrapper/gradle-wrapper.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
44
networkTimeout=10000
55
validateDistributionUrl=true
66
zipStoreBase=GRADLE_USER_HOME

gradlew

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gradlew.bat

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/main/groovy/org/labkey/gradle/task/PurgeArtifacts.groovy

Lines changed: 147 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.labkey.gradle.task
22

3+
import org.apache.commons.io.IOUtils
34
import org.apache.commons.lang3.StringUtils
45
import org.apache.hc.client5.http.classic.methods.HttpDelete
56
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient
@@ -8,70 +9,170 @@ import org.apache.hc.client5.http.impl.classic.HttpClients
89
import org.apache.hc.core5.http.HttpStatus
910
import org.gradle.api.DefaultTask
1011
import org.gradle.api.GradleException
11-
import org.gradle.api.Project
12+
import org.gradle.api.provider.Property
13+
import org.gradle.api.tasks.Input
14+
import org.gradle.api.tasks.Optional
1215
import org.gradle.api.tasks.TaskAction
13-
import org.labkey.gradle.plugin.Api
14-
import org.labkey.gradle.plugin.FileModule
15-
import org.labkey.gradle.plugin.JavaModule
16-
import org.labkey.gradle.plugin.Module
17-
import org.labkey.gradle.util.BuildUtils
16+
17+
import java.nio.file.Paths
1818

1919
class PurgeArtifacts extends DefaultTask
2020
{
2121
public static final String SNAPSHOT_REPOSITORY_NAME = 'libs-snapshot-local'
2222
public static final String RELEASE_REPOSITORY_NAME = 'libs-release-local'
2323
public static final String VERSION_PROPERTY = 'purgeVersion'
24+
public static final String VERSIONS_FILE_PROPERTY = 'purgeVersions'
25+
public static final String PURGE_LIST_FILE_PROPERTY = 'purgeList';
26+
public static final String DRY_RUN_PROPERTY = 'dryRun';
2427

25-
static boolean isPreSplitVersion(String version)
26-
{
27-
return BuildUtils.compareVersions( version, "19.3") < 0
28-
}
28+
@Input @Optional
29+
final abstract Property<String> purgeVersion = project.objects.property(String).convention(project.hasProperty(VERSION_PROPERTY) ? (String) project.property(VERSION_PROPERTY) : "")
30+
@Input @Optional
31+
final abstract Property<String> purgeVersions = project.objects.property(String).convention(project.hasProperty(VERSIONS_FILE_PROPERTY) ? (String) project.property(VERSIONS_FILE_PROPERTY) : "")
32+
@Input @Optional
33+
final abstract Property<String> purgeListFile = project.objects.property(String).convention(project.hasProperty(PURGE_LIST_FILE_PROPERTY) ? (String) project.property(PURGE_LIST_FILE_PROPERTY) : "")
34+
@Input
35+
final abstract Property<Boolean> isDryRun = project.objects.property(Boolean).convention(project.hasProperty(DRY_RUN_PROPERTY))
36+
37+
@Input
38+
final abstract Property<String> artifactoryUrl = project.objects.property(String).convention((String) project.property('artifactory_contextUrl'))
39+
@Input
40+
final abstract Property<String> artifactoryUser = project.objects.property(String).convention((String) project.property('artifactory_user'))
41+
@Input
42+
final abstract Property<String> artifactoryPassword = project.objects.property(String).convention((String) project.property('artifactory_password'))
2943

3044
enum Response {
3145
SUCCESS,
3246
NOT_FOUND,
3347
ERROR
3448
}
49+
private static final String NUM_NOT_FOUND = "numNotFound"
50+
private static final String NUM_DELETED = "numDeleted"
51+
private static final String UNDELETED_VERSIONS = "undeletedVersions"
3552

3653
@TaskAction
3754
void purgeVersions()
3855
{
39-
String purgeVersion
40-
if (!project.hasProperty(VERSION_PROPERTY))
41-
throw new GradleException("No value provided for ${VERSION_PROPERTY}.")
42-
purgeVersion = project.property(VERSION_PROPERTY)
43-
boolean isPreSplitVersion = isPreSplitVersion(purgeVersion)
44-
String[] undeletedVersions = []
45-
int numDeleted = 0
46-
int numNotFound = 0
47-
project.allprojects({ Project p ->
48-
def plugins = p.getPlugins()
49-
if (plugins.hasPlugin(Module.class) || plugins.hasPlugin(JavaModule.class) || plugins.hasPlugin(FileModule.class)) {
50-
logger.quiet("Considering ${p.path}...")
51-
Response response = makeDeleteRequest(p.name, purgeVersion, "module")
52-
if (response == Response.NOT_FOUND)
53-
numNotFound++
54-
else if (response == Response.ERROR) {
55-
undeletedVersions += "${p.path} - module: ${purgeVersion}"
56+
String version = purgeVersion.get()
57+
String purgeModulesFileName = purgeListFile.get()
58+
if (StringUtils.isEmpty(purgeModulesFileName))
59+
throw new GradleException("Use -P${PURGE_LIST_FILE_PROPERTY}=<moduleNames.txt> to provide a list of modules to work with.")
60+
List<String> moduleNames = readInputFile(purgeListFile.get(), "modules")
61+
if (moduleNames.isEmpty())
62+
throw new GradleException("No module names found in file ${purgeListFile.get()}")
63+
if (!StringUtils.isEmpty(version))
64+
purgeVersion(version, moduleNames)
65+
else
66+
{
67+
Map<String, Integer> overallStats = new HashMap<>()
68+
overallStats.put(NUM_NOT_FOUND, 0)
69+
overallStats.put(NUM_DELETED, 0)
70+
String purgeVersionsFileName = purgeVersions.get()
71+
if (StringUtils.isEmpty(purgeVersionsFileName))
72+
throw new GradleException("Either -P${VERSION_PROPERTY}=<versionToPurge> or -P${VERSIONS_FILE_PROPERTY}=<versionsFile.txt> must be provided")
73+
List<String> versions = readInputFile(purgeVersionsFileName, "versions")
74+
if (versions.isEmpty())
75+
throw new GradleException("No versions found for file ${purgeVersionsFileName}.")
76+
if (versions.size() > 1) {
77+
for (String moduleName : moduleNames) {
78+
Map<String, Object> deleteStats = purgeModuleVersions(moduleName, versions)
79+
overallStats.put(NUM_NOT_FOUND, overallStats.get(NUM_NOT_FOUND) + (Integer) deleteStats.get(NUM_NOT_FOUND))
80+
overallStats.put(NUM_DELETED, overallStats.get(NUM_DELETED) + (Integer) deleteStats.get(NUM_DELETED))
5681
}
57-
else
58-
numDeleted++
59-
if (!isPreSplitVersion && (plugins.hasPlugin(Api.class) || project.path == BuildUtils.getApiProjectPath(project.gradle))) {
60-
response = makeDeleteRequest(p.name, purgeVersion, "api")
61-
if (response == Response.NOT_FOUND)
62-
numNotFound++
63-
else if (response == Response.ERROR) {
64-
undeletedVersions += "${p.path} - api: ${purgeVersion}"
65-
}
66-
else
67-
numDeleted++
82+
if (moduleNames.size() > 1)
83+
logger.quiet("\nSummary:\n\tDeleted ${overallStats.get(NUM_DELETED)} artifacts.\n\t${overallStats.get(NUM_NOT_FOUND)} artifacts not found.")
84+
}
85+
else {
86+
for (String v : versions) {
87+
Map<String, Object> deleteStats = purgeVersion(v, moduleNames)
88+
overallStats.put(NUM_NOT_FOUND, overallStats.get(NUM_NOT_FOUND) + (Integer) deleteStats.get(NUM_NOT_FOUND))
89+
overallStats.put(NUM_DELETED, overallStats.get(NUM_DELETED) + (Integer) deleteStats.get(NUM_DELETED))
6890
}
91+
if (versions.size() > 1)
92+
logger.quiet("\nSummary\n\tDeleted ${overallStats.get(NUM_DELETED)} artifacts.\n\t${overallStats.get(NUM_NOT_FOUND)} artifacts not found.")
6993
}
70-
})
94+
}
95+
96+
}
97+
98+
List<String> readInputFile(String fileName, String type)
99+
{
100+
if (!StringUtils.isEmpty(fileName)) {
101+
File listing = Paths.get(fileName).toFile();
102+
if (listing.exists()) {
103+
logger.quiet("Reading ${type} purge list from file ${listing.getAbsolutePath()}.")
104+
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(listing)))) {
105+
List<String> lines = IOUtils.readLines(reader).stream().filter(line -> !line.startsWith("#")).toList()
106+
logger.quiet("... found ${lines.size()} uncommented lines for purging")
107+
return lines
108+
}
109+
} else {
110+
throw new GradleException("No such file or directory: ${fileName}")
111+
}
112+
} else {
113+
throw new GradleException("No file name provided for ${type} input")
114+
}
115+
}
116+
117+
Map<String, Object> purgeModuleVersions(String moduleName, List<String> versions)
118+
{
119+
Map<String, Object> deleteStats = new HashMap();
120+
deleteStats.put(NUM_DELETED, 0)
121+
deleteStats.put(NUM_NOT_FOUND, 0)
122+
deleteStats.put(UNDELETED_VERSIONS, new ArrayList<>())
123+
124+
logger.quiet("### Begin purge for module ${moduleName} for ${versions.size()} versions\n")
71125

72-
logger.quiet("Deleted ${numDeleted} artifacts; ${numNotFound} artifacts not found.")
73-
if (undeletedVersions.size() > 0 && !project.hasProperty("dryRun"))
74-
throw new GradleException("The following ${undeletedVersions.size()} versions were not deleted.\n${StringUtils.join(undeletedVersions, "\n")}\nCheck the log for more information.")
126+
for (String version: versions) {
127+
makeRequests(moduleName, moduleName, version, deleteStats, true)
128+
}
129+
130+
logger.quiet("Deleted ${deleteStats.get(NUM_DELETED)} artifacts; ${deleteStats.get(NUM_NOT_FOUND)} artifacts not found.")
131+
if (((List<String>) deleteStats.get(UNDELETED_VERSIONS)).size() > 0 && !isDryRun.get())
132+
throw new GradleException("The following ${((List<String>) deleteStats.get(UNDELETED_VERSIONS)).size()} versions were not deleted.\n${StringUtils.join(deleteStats.get(UNDELETED_VERSIONS), "\n")}\nCheck the log for more information.")
133+
logger.quiet("\n### End purge for module ${moduleName}\n")
134+
return deleteStats
135+
}
136+
137+
Map<String, Object> purgeVersion(String version, List<String> moduleNames)
138+
{
139+
Map<String, Object> deleteStats = new HashMap();
140+
deleteStats.put(NUM_DELETED, 0)
141+
deleteStats.put(NUM_NOT_FOUND, 0)
142+
deleteStats.put(UNDELETED_VERSIONS, new ArrayList<>())
143+
144+
logger.quiet("### Begin purge of version ${version} for ${moduleNames.size()} modules\n")
145+
146+
for (String moduleName : moduleNames) {
147+
makeRequests(moduleName, moduleName, version, deleteStats, true)
148+
}
149+
150+
logger.quiet("Deleted ${deleteStats.get(NUM_DELETED)} artifacts; ${deleteStats.get(NUM_NOT_FOUND)} artifacts not found.")
151+
if (((List<String>) deleteStats.get(UNDELETED_VERSIONS)).size() > 0 && !isDryRun.get())
152+
throw new GradleException("The following ${((List<String>) deleteStats.get(UNDELETED_VERSIONS)).size()} versions were not deleted.\n${StringUtils.join(deleteStats.get(UNDELETED_VERSIONS), "\n")}\nCheck the log for more information.")
153+
logger.quiet("\n### End purge for version ${version}\n")
154+
return deleteStats
155+
}
156+
157+
void makeRequests(String moduleName, String loggingName, String purgeVersion, Map<String, Object> statsMap, boolean tryApi)
158+
{
159+
logger.quiet("Considering ${loggingName} ${purgeVersion}...")
160+
Response response = makeDeleteRequest(moduleName, purgeVersion, "module")
161+
if (response == Response.NOT_FOUND)
162+
statsMap.put(NUM_NOT_FOUND, statsMap.get(NUM_NOT_FOUND)+1);
163+
else if (response == Response.ERROR)
164+
statsMap.get(UNDELETED_VERSIONS).add("${loggingName} - module: ${purgeVersion}")
165+
else
166+
statsMap.put(NUM_DELETED, statsMap.get(NUM_DELETED)+1)
167+
if (tryApi) {
168+
response = makeDeleteRequest(moduleName, purgeVersion, "api")
169+
if (response == Response.NOT_FOUND)
170+
statsMap.put(NUM_NOT_FOUND, statsMap.get(NUM_NOT_FOUND)+1)
171+
else if (response == Response.ERROR)
172+
statsMap.get(UNDELETED_VERSIONS).add("${loggingName} - api: ${purgeVersion}")
173+
else
174+
statsMap.put(NUM_DELETED, statsMap.get(NUM_DELETED)+1)
175+
}
75176
}
76177

77178
/**
@@ -85,29 +186,26 @@ class PurgeArtifacts extends DefaultTask
85186
*/
86187
Response makeDeleteRequest(String artifactName, String version, String type)
87188
{
88-
if (project.hasProperty("dryRun")) {
189+
if (isDryRun.get()) {
89190
logger.quiet("\tRemoving version ${version} of ${artifactName} ${type} -- Skipped for dry run")
90191
return
91192
}
92193

93194
CloseableHttpClient httpClient = HttpClients.createDefault()
94-
String endpoint = project.property('artifactory_contextUrl')
195+
String endpoint = artifactoryUrl.get()
95196
Response responseStatus = Response.SUCCESS
96197
if (!endpoint.endsWith("/"))
97198
endpoint += "/"
98199

99200
String repo = version.contains("SNAPSHOT") ? SNAPSHOT_REPOSITORY_NAME : RELEASE_REPOSITORY_NAME
100-
if (isPreSplitVersion(version))
101-
endpoint += repo + "/org/labkey/" + artifactName + "/" + version
102-
else
103-
endpoint += repo + "/org/labkey/" + type + "/" + artifactName + "/" + version
201+
endpoint += repo + "/org/labkey/" + type + "/" + artifactName + "/" + version
104202
logger.quiet("\tMaking delete request for ${type} artifact ${artifactName} and version ${version} via endpoint ${endpoint}")
105203

106204
try
107205
{
108206
HttpDelete httpDelete = new HttpDelete(endpoint)
109207
// N.B. Using Authorization Bearer with an API token does not currently work
110-
httpDelete.setHeader("Authorization", "Basic " + Base64.getEncoder().encodeToString("${project.property('artifactory_user')}:${project.property('artifactory_password')}".getBytes()))
208+
httpDelete.setHeader("Authorization", "Basic " + Base64.getEncoder().encodeToString("${artifactoryUser.get()}:${artifactoryPassword.get()}".getBytes()))
111209
CloseableHttpResponse response = httpClient.execute(httpDelete)
112210
int statusCode = response.getCode()
113211

0 commit comments

Comments
 (0)