Skip to content

Commit ddce975

Browse files
Remove extra logging, add additional task for removing npm versions and remove use of versioning plugin (#238)
1 parent fb58654 commit ddce975

9 files changed

Lines changed: 188 additions & 75 deletions

File tree

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,15 @@ 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
16+
### 7.2.0
17+
*Released*: 11 December 2025
1818
(Earliest compatible LabKey version: 25.10)
1919
- Remove `ContainerListener` from module template
2020
- Java 25: Remove obsolete JVM flags from tasks (`-Xdebug`, `-Xnoagent`, `-Xrunjdwp`, `-Djava.compiler`)
2121
- Java 25: Update gradle wrapper to 9.2.1
22+
- Remove logging of XMLBeans version
23+
- Add `PurgeNpmVersions` task
24+
- Remove use of `versioning` plugin in favor of capturing output from a few dedicated `git` commands
2225

2326
### 7.1.0
2427
*Released*: 17 October 2025

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ dependencies {
4343
}
4444

4545
group = 'org.labkey.build'
46-
project.version = "7.2.0-SNAPSHOT"
46+
project.version = "7.3.0-SNAPSHOT"
4747

4848
gradlePlugin {
4949
plugins {

src/main/groovy/org/labkey/gradle/plugin/LabKey.groovy

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,6 @@ class LabKey implements Plugin<Project>
3434
@Override
3535
void apply(Project project)
3636
{
37-
if (project.hasProperty('includeVcs'))
38-
{
39-
if (project.hasProperty('nemerosaVersioningPluginVersion'))
40-
project.apply plugin: 'net.nemerosa.versioning'
41-
else
42-
project.apply plugin: 'org.labkey.versioning'
43-
}
44-
4537
project.group = LabKeyExtension.LABKEY_GROUP
4638
project.version = BuildUtils.getVersionNumber(project)
4739
project.subprojects { Project subproject ->

src/main/groovy/org/labkey/gradle/plugin/XmlBeans.groovy

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ class XmlBeans implements Plugin<Project>
5555
xmlbeans "org.apache.xmlbeans:xmlbeans:${project.xmlbeansVersion}"
5656
}
5757

58-
project.logger.quiet("XMLBeans version: ${project.xmlbeansVersion}")
5958
String schemasProjectPath = BuildUtils.getSchemasProjectPath(project.gradle)
6059
if (!project.path.equals(schemasProjectPath))
6160
{

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

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import org.gradle.api.provider.Property
1313
import org.gradle.api.tasks.Input
1414
import org.gradle.api.tasks.Optional
1515
import org.gradle.api.tasks.TaskAction
16+
import org.labkey.gradle.util.TaskUtils
1617

1718
import java.nio.file.Paths
1819

@@ -57,7 +58,7 @@ class PurgeArtifacts extends DefaultTask
5758
String purgeModulesFileName = purgeListFile.get()
5859
if (StringUtils.isEmpty(purgeModulesFileName))
5960
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+
List<String> moduleNames = TaskUtils.readInputFile(purgeListFile.get(), "modules", logger)
6162
if (moduleNames.isEmpty())
6263
throw new GradleException("No module names found in file ${purgeListFile.get()}")
6364
if (!StringUtils.isEmpty(version))
@@ -70,7 +71,7 @@ class PurgeArtifacts extends DefaultTask
7071
String purgeVersionsFileName = purgeVersions.get()
7172
if (StringUtils.isEmpty(purgeVersionsFileName))
7273
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+
List<String> versions = TaskUtils.readInputFile(purgeVersionsFileName, "versions", logger)
7475
if (versions.isEmpty())
7576
throw new GradleException("No versions found for file ${purgeVersionsFileName}.")
7677
if (versions.size() > 1) {
@@ -95,24 +96,6 @@ class PurgeArtifacts extends DefaultTask
9596

9697
}
9798

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-
}
11699

117100
Map<String, Object> purgeModuleVersions(String moduleName, List<String> versions)
118101
{

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

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import org.labkey.gradle.plugin.NpmRun
1616

1717
import java.util.stream.Collectors
1818

19-
abstract class PurgeNpmAlphaVersions extends DefaultTask
19+
abstract class PurgeNpmAlphaVersions extends PurgeNpmVersions
2020
{
2121
private static final String REPOSITORY_NAME = 'libs-client-local'
2222
public static final String ALPHA_PREFIX_PROPERTY = 'alphaPrefix'
@@ -34,14 +34,6 @@ abstract class PurgeNpmAlphaVersions extends DefaultTask
3434

3535
@Input
3636
final abstract Property<String> alphaPrefixProp = project.objects.property(String).convention((project.hasProperty(ALPHA_PREFIX_PROPERTY) ? (String) project.property(ALPHA_PREFIX_PROPERTY) : null))
37-
@Input
38-
final abstract Property<Boolean> isDryRun = project.objects.property(Boolean).convention(project.hasProperty(DRY_RUN_PROPERTY))
39-
@Input
40-
final abstract Property<String> artifactoryUrl = project.objects.property(String).convention((String) project.property('artifactory_contextUrl'))
41-
@Input
42-
final abstract Property<String> artifactoryUser = project.objects.property(String).convention((String) project.property('artifactory_user'))
43-
@Input
44-
final abstract Property<String> artifactoryPassword = project.objects.property(String).convention((String) project.property('artifactory_password'))
4537

4638
@TaskAction
4739
void purgeVersions()
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package org.labkey.gradle.task
2+
3+
4+
import org.apache.commons.lang3.StringUtils
5+
import org.apache.hc.client5.http.classic.methods.HttpDelete
6+
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient
7+
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse
8+
import org.apache.hc.client5.http.impl.classic.HttpClients
9+
import org.apache.hc.core5.http.HttpStatus
10+
import org.gradle.api.DefaultTask
11+
import org.gradle.api.GradleException
12+
import org.gradle.api.provider.Property
13+
import org.gradle.api.tasks.Input
14+
import org.gradle.api.tasks.TaskAction
15+
import org.labkey.gradle.util.TaskUtils
16+
17+
abstract class PurgeNpmVersions extends DefaultTask
18+
{
19+
private static final String REPOSITORY_NAME = 'libs-client-local'
20+
public static final String DRY_RUN_PROPERTY = 'dryRun'
21+
private static final String PACKAGE_NAME_PROP = "packageName"
22+
private static final String VERSION_LIST_PROP = "versionList"
23+
24+
@Input
25+
final abstract Property<String> packageName = project.objects.property(String).convention((project.hasProperty(PACKAGE_NAME_PROP) ? (String) project.property(PACKAGE_NAME_PROP) : null))
26+
27+
@Input
28+
final abstract Property<String> versionList = project.objects.property(String).convention((project.hasProperty(VERSION_LIST_PROP) ? (String) project.property(VERSION_LIST_PROP) : null))
29+
30+
@Input
31+
final abstract Property<Boolean> isDryRun = project.objects.property(Boolean).convention(project.hasProperty(DRY_RUN_PROPERTY))
32+
@Input
33+
final abstract Property<String> artifactoryUrl = project.objects.property(String).convention((String) project.property('artifactory_contextUrl'))
34+
@Input
35+
final abstract Property<String> artifactoryUser = project.objects.property(String).convention((String) project.property('artifactory_user'))
36+
@Input
37+
final abstract Property<String> artifactoryPassword = project.objects.property(String).convention((String) project.property('artifactory_password'))
38+
39+
@TaskAction
40+
void purgeVersions()
41+
{
42+
if (!packageName.isPresent() || StringUtils.isEmpty(packageName.get().trim()))
43+
throw new GradleException("No value provided for packageName.")
44+
String packageName = "@labkey/" + packageName.get()
45+
String[] undeletedVersions = []
46+
47+
logger.quiet("Considering ${packageName}...")
48+
List<String> versions = readPurgeVersions()
49+
if (versions.isEmpty())
50+
logger.quiet("No versions provided.")
51+
else {
52+
logger.quiet("Found ${versions.size()} versions in package ${packageName}")
53+
versions.forEach(version -> {
54+
if (isDryRun.get())
55+
logger.quiet("Removing version ${version} of package ${packageName} -- Skipped for dry run")
56+
else {
57+
logger.quiet("Removing version ${version} of package ${packageName}")
58+
if (!makeDeleteRequest(packageName, version)) {
59+
undeletedVersions += "${packageName}: ${version}"
60+
}
61+
}
62+
})
63+
}
64+
65+
if (undeletedVersions.size() > 0)
66+
throw new GradleException("The following versions were not deleted.\n${undeletedVersions}\nCheck the log for more information.")
67+
}
68+
69+
List<String> readPurgeVersions()
70+
{
71+
if (versionList.isPresent() && !StringUtils.isEmpty(versionList.get().trim()))
72+
return TaskUtils.readInputFile(versionList.get(), "versions", logger)
73+
else
74+
throw new GradleException("No " + VERSION_LIST_PROP + " or " + VERSION_LIST_PROP + " property provided.");
75+
}
76+
77+
78+
/**
79+
* This uses the Artifactory REST Api to request a deletion of a particular package and version. There does
80+
* not appear to be a way to request deletion of multiple versions at once. Also, though it might seem natural
81+
* to use "npm unpublish" for this deletion, this does not work with artifactory, possibly due to this long-standing
82+
* issue: https://github.com/npm/npm-registry-client/issues/41
83+
* The command appears to work, returning a 200 status code when you use --verbose logging, but the artifact doesn't
84+
* go anywhere.
85+
*
86+
* Another possibility here would be to use the same action as is used in the Web UI. There, Artifactory sends
87+
* a POST request to:
88+
* Request URL: https://artifactory.labkey.com/artifactory/ui/artifactactions/delete
89+
* with parameters
90+
* repoKey: libs-client-local
91+
* path: "@labkey/components/-/@labkey/components-2.14.2-fb-update-react-select.1.tgz"
92+
* The REST API seems a better approach, though.
93+
* @param packageName the package whose version is to be deleted, including the scope (e.g., @labkey/components)
94+
* @param version the version of the package to delete (e.g., 2.14.2-fb-update-react-select.1)
95+
* @return true if deletion was successful, false otherwise
96+
* @throws GradleException if the delete request throws an exception
97+
*/
98+
protected boolean makeDeleteRequest(String packageName, String version)
99+
{
100+
CloseableHttpClient httpClient = HttpClients.createDefault()
101+
String endpoint = artifactoryUrl.get()
102+
boolean success = true
103+
if (!endpoint.endsWith("/"))
104+
endpoint += "/"
105+
106+
// The coordinates of the packages look like this: "@labkey/components/-/@labkey/components-2.14.2-fb-update-react-select.1.tgz"
107+
endpoint += REPOSITORY_NAME + "/" + packageName + "/-/" + packageName + "-" + version + ".tgz"
108+
logger.debug("Making delete request for package ${packageName} and version ${version} via endpoint ${endpoint}")
109+
try
110+
{
111+
HttpDelete httpDelete = new HttpDelete(endpoint)
112+
// N.B. Using Authorization Bearer with an API token does not currently work
113+
httpDelete.setHeader("Authorization", "Basic " + Base64.getEncoder().encodeToString("${artifactoryUser.get()}:${artifactoryPassword.get()}".getBytes()))
114+
CloseableHttpResponse response = httpClient.execute(httpDelete)
115+
int statusCode = response.getCode()
116+
if (statusCode != HttpStatus.SC_OK && statusCode != HttpStatus.SC_NO_CONTENT) {
117+
logger.error("Unable to delete using ${endpoint}: ${statusCode} ${response.getReasonPhrase()}")
118+
success = false
119+
}
120+
response.close()
121+
return success
122+
}
123+
catch (Exception e)
124+
{
125+
throw new GradleException("Problem executing delete request with url ${endpoint}", e)
126+
}
127+
finally
128+
{
129+
httpClient.close()
130+
}
131+
}
132+
}

src/main/groovy/org/labkey/gradle/util/BuildUtils.groovy

Lines changed: 22 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package org.labkey.gradle.util
1818
import org.ajoberstar.grgit.Grgit
1919
import org.ajoberstar.grgit.Remote
2020
import org.apache.commons.lang3.StringUtils
21+
import org.apache.commons.lang3.SystemUtils
2122
import org.gradle.api.GradleException
2223
import org.gradle.api.Project
2324
import org.gradle.api.UnknownDomainObjectException
@@ -52,6 +53,7 @@ class BuildUtils
5253
public static final String PLATFORM_MODULES_DIR = "server/modules/platform"
5354
public static final String COMMON_ASSAYS_MODULES_DIR = "server/modules/commonAssays"
5455
public static final String CUSTOM_MODULES_DIR = "server/modules/customModules"
56+
private static final Pattern GIT_URL_WITH_TOKEN = Pattern.compile("(https://[^:]+):([^@]+)@(.*)");
5557

5658
public static final List<String> EHR_MODULE_NAMES = [
5759
"EHR_ComplianceDB",
@@ -494,49 +496,35 @@ class BuildUtils
494496
(String) TeamCityExtension.getTeamCityProperty(project, "system.teamcity.agent.dotnet.build_id", // Unique build ID
495497
TeamCityExtension.getTeamCityProperty(project,"build.number", null))
496498
Properties ret = new Properties()
497-
if (project.plugins.hasPlugin("org.labkey.versioning"))
499+
def gitCmd = SystemUtils.IS_OS_WINDOWS ? "git.exe" : "git"
500+
if (project.hasProperty("includeVcs") && (!project.hasProperty("lkModule") || project.lkModule.getModProperties().get("VcsURL").isEmpty()))
498501
{
499-
Project vcsProject = project
500-
while (vcsProject.versioning.info.url == "No VCS" && vcsProject != project.rootProject)
501-
{
502-
vcsProject = vcsProject.parent
503-
}
504-
vcsProject.println("${project.path} versioning info ${ vcsProject.versioning.info}")
505-
ret.setProperty("VcsURL", vcsProject.versioning.info.url)
506-
if (vcsProject.versioning.info.branch != null)
507-
ret.setProperty("VcsBranch", vcsProject.versioning.info.branch)
508-
if (vcsProject.versioning.info.tag != null)
509-
ret.setProperty("VcsTag", vcsProject.versioning.info.tag)
510-
ret.setProperty("VcsRevision", vcsProject.versioning.info.commit)
511-
ret.setProperty("BuildNumber", buildNumber != null ? buildNumber : vcsProject.versioning.info.build)
512-
}
513-
else if (project.plugins.hasPlugin("net.nemerosa.versioning"))
514-
{
515-
// In our fork of the plugin (above), we added the url property to the VersioningInfo object
516-
Project vcsProject = project
517-
String url = getGitUrl(vcsProject)
518-
while (url == null && vcsProject != project.rootProject)
519-
{
520-
vcsProject = vcsProject.parent
521-
url = getGitUr(vcsProject)
522-
}
523-
vcsProject.println("${project.path} versioning info ${ vcsProject.versioning.info}")
502+
def url = "${gitCmd} -C ${project.projectDir.absolutePath} config --get remote.origin.url".execute().text.trim()
503+
Matcher matcher = GIT_URL_WITH_TOKEN.matcher(url)
504+
if (matcher.matches()) // Strip out the token if included in the URL.
505+
url = matcher.group(1) + "@" + matcher.group(3)
524506
ret.setProperty("VcsURL", url)
525-
if (vcsProject.versioning.info.branch != null)
526-
ret.setProperty("VcsBranch", vcsProject.versioning.info.branch)
527-
if (vcsProject.versioning.info.tag != null)
528-
ret.setProperty("VcsTag", vcsProject.versioning.info.tag)
529-
ret.setProperty("VcsRevision", vcsProject.versioning.info.commit)
530-
ret.setProperty("BuildNumber", buildNumber != null ? buildNumber : vcsProject.versioning.info.build)
531-
}
507+
project.logger.info("${project.path} git url: ${url}")
508+
def branch = "${gitCmd} -C ${project.projectDir.absolutePath} rev-parse --abbrev-ref HEAD".execute().text.trim()
509+
project.logger.info("${project.path} git branch: ${branch}")
510+
ret.setProperty("VcsBranch", branch)
511+
def revision = "${gitCmd} -C ${project.projectDir.absolutePath} rev-parse @".execute().text.trim()
512+
project.logger.info("${project.path} git revision: ${revision}")
513+
ret.setProperty("VcsRevision", revision)
514+
def tag = "${gitCmd} -C ${project.projectDir.absolutePath} describe --tags --exact-match 2> /dev/null".execute().text.trim()
515+
project.logger.info("${project.path} git tag: ${revision}")
516+
if (!tag.isEmpty() && !tag.equals(revision))
517+
ret.setProperty("VcsTag", tag)
518+
else
519+
ret.setProperty("VcsTag", "")}
532520
else
533521
{
534522
ret.setProperty("VcsBranch", "Unknown")
535523
ret.setProperty("VcsTag", "Unknown")
536524
ret.setProperty("VcsURL", "Unknown")
537525
ret.setProperty("VcsRevision", "Unknown")
538-
ret.setProperty("BuildNumber", buildNumber != null ? buildNumber : "Unknown")
539526
}
527+
ret.setProperty("BuildNumber", buildNumber != null ? buildNumber : "Unknown")
540528
return ret
541529
}
542530

src/main/groovy/org/labkey/gradle/util/TaskUtils.groovy

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,16 @@
1515
*/
1616
package org.labkey.gradle.util
1717

18+
import org.apache.commons.io.IOUtils
19+
import org.apache.commons.lang3.StringUtils
20+
import org.gradle.api.GradleException
1821
import org.gradle.api.Project
1922
import org.gradle.api.Task
2023
import org.gradle.api.UnknownTaskException
24+
import org.gradle.api.logging.Logger
2125
import org.gradle.api.tasks.TaskProvider
2226

27+
import java.nio.file.Paths
2328
import java.util.function.Consumer
2429

2530
class TaskUtils
@@ -54,4 +59,23 @@ class TaskUtils
5459
return Optional.empty()
5560
}
5661
}
62+
63+
static List<String> readInputFile(String fileName, String type, Logger logger)
64+
{
65+
if (!StringUtils.isEmpty(fileName)) {
66+
File listing = Paths.get(fileName).toFile();
67+
if (listing.exists()) {
68+
logger.quiet("Reading ${type} list from file ${listing.getAbsolutePath()}.")
69+
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(listing)))) {
70+
List<String> lines = IOUtils.readLines(reader).stream().filter(line -> !line.startsWith("#")).toList()
71+
logger.quiet("... found ${lines.size()} uncommented lines")
72+
return lines
73+
}
74+
} else {
75+
throw new GradleException("No such file or directory: ${fileName}")
76+
}
77+
} else {
78+
throw new GradleException("No file name provided for ${type} input")
79+
}
80+
}
5781
}

0 commit comments

Comments
 (0)