使用 Space Maven 仓库清理工具批量删除 JetBrains Space 包版本(compose-multiplatform CI 实践)
【免费下载链接】compose-multiplatformCompose Multiplatform, a modern UI framework for Kotlin that makes building performant and beautiful user interfaces easy and enjoyable.项目地址: https://gitcode.com/GitHub_Trending/co/compose-multiplatform
导读
本文围绕 compose-multiplatform 仓库中ci/delete-packages-from-space这个独立的 Gradle 子项目展开,完整讲解如何通过其封装好的四个 Gradle 任务从 JetBrains Space Maven 仓库中批量删除指定版本的包(例如清理0.4.0-preview-*预览版本)。读完本文,你将掌握 Space 个人令牌的权限配置、项目 ID 与仓库 ID 的查询方法、基于通配符生成待删除清单的安全流程,以及带图形化确认的删除执行机制,可直接迁移用于你自己的 Space 包管理场景。
一、工具定位与整体流程
ci/delete-packages-from-space是一个完全独立的 Gradle 项目(根项目名为space-delete-package),核心思路是:借助 JetBrains Space 官方 SDK 提供的 REST API 封装,先在本地生成一份待删除清单,人工确认(取消注释)后再执行删除。整个流程被刻意拆成多个任务,避免"一条命令误删所有包"的风险:
- 准备 JDK 9+、生成 Space 个人令牌并配置
local.properties; - 运行
listProjectsAndPackageRepositories查询项目与仓库的 ID; - 运行
generateListOfPackagesToDelete(带版本通配符)生成待删清单; - 人工编辑清单,取消注释需要删除的条目;
- 运行
deletePackages,在弹出的确认对话框中二次确认后真正删除。
该项目的入口构建文件为 build.gradle.kts,其中注册了 4 个任务:checkJavaVersion、listProjectsAndPackageRepositories、generateListOfPackagesToDelete、deletePackages。下面按 README 的步骤逐一展开。
二、前置环境:JDK 9+ 与个人令牌
2.1 JDK 版本约束
README 明确要求JDK 9+。这一约束由构建脚本中的checkJavaVersion任务强制执行:
val checkJavaVersion = tasks.register("checkJavaVersion") { doLast { check(JavaVersion.current() >= JavaVersion.VERSION_1_9) { "Use JDK 9+ to run this task" } } }后续三个任务都通过dependsOn(checkJavaVersion)依赖它,因此 JDK 版本不满足时会直接构建失败。可用java -version先确认本机环境。
2.2 生成 Space 个人令牌
在 JetBrains Space 中创建 personal token,需要授予以下三项权限:
| 权限 | 作用 |
|---|---|
ReadRepository | 读取仓库内容与包信息(用于查询、生成清单) |
WriteRepository | 写入仓库内容(用于执行版本删除) |
ViewProject | 查看项目信息(用于遍历项目、查询项目 ID) |
三、配置 local.properties
3.1 从模板创建
项目提供了现成模板 template.local.properties,复制即可:
cp template.local.properties local.properties模板内容如下(注意local.properties本身通常不进版本库):
#space.server.url=https://public.jetbrains.space #space.auth.token= #space.project.id= #space.repo.id= # package version to be deleted. Wilcards (*) can be used . #space.package.version=0.0.0-example-*3.2 参数说明
| 参数 | 必填 | 说明 |
|---|---|---|
space.server.url | 是 | Space 服务地址,如https://public.jetbrains.space |
space.auth.token | 是 | 步骤 2 生成的个人令牌 |
space.project.id | 是(第 4 步后) | 目标项目 ID,由查询任务获得 |
space.repo.id | 是(第 4 步后) | 目标 Maven 仓库 ID,由查询任务获得 |
space.package.version | 是(生成清单时) | 待删除版本,支持*通配符,如0.4.0-preview-* |
3.3 属性解析的底层逻辑
从源码看,配置解析由Space类中的property()方法完成,同时支持local.properties文件与 Gradle-P命令行参数,且文件优先级更高:
fun property(name: String): String = (localProperties.getProperty(name)) ?: (project.findProperty(name) as? String) ?: error("Property '$name' is not set")这意味着你也可以用./gradlew generateListOfPackagesToDelete -Pspace.package.version=0.4.0-preview-*这样的方式临时覆盖配置,而不是每次都改文件。配置项被解析后通过 lazy 属性统一建模:
val token by lazy { property("space.auth.token") } val server by lazy { property("space.server.url") } val projectId by lazy { ProjectIdentifier.Id(property("space.project.id")) } val repoId by lazy { PackageRepositoryIdentifier.Id(property("space.repo.id")) } val packageVersionToDelete by lazy { property("space.package.version") }四、查询项目 ID 与仓库 ID
运行:
./gradlew listProjectsAndPackageRepositories该任务遍历 Space 中当前令牌可见的所有项目,并列出每个项目下的包仓库,输出形如:
Project 'my-project'(id: <project-id>) Package repository 'maven'(id: <repo-id>)底层实现是分页遍历:forEachProject使用projects.getAllProjects(batchInfo = batch)按批拉取项目,forEachPackageRepository调用projects.packages.repositories.getRepositories(...)获取仓库列表(见 build.gradle.kts)。分页封装在forAllInAllBatches中,默认每批 100 条,按batch.totalCount判断是否遍历完成:
fun batches(batchSize: Int = 100) = generateSequence(0) { it + batchSize } .map { BatchInfo(it.toString(), batchSize) } suspend fun <T> forAllInAllBatches( getBatch: suspend (BatchInfo) -> Batch<T>, fn: suspend (T) -> Unit ) { for (batchInfo in batches()) { val batch = getBatch(batchInfo) for (element in batch.data) { fn(element) } if (batch.data.isEmpty() || (batch.next.toIntOrNull() ?: 0) >= (batch.totalCount ?: 0)) return } }拿到结果后,把对应的值填入local.properties:
space.project.id=<project-id> space.repo.id=<repo-id>五、生成待删除包清单(安全第一)
运行:
./gradlew generateListOfPackagesToDelete -Pspace.package.version=0.4.0-preview-*-Pspace.package.version是版本过滤条件,*为通配符(底层使用 Apache Commons IO 的FilenameUtils.wildcardMatch做匹配)。执行逻辑如下:
- 遍历目标仓库中的所有包(
projects.packages.repositories.packages.getAllPackages); - 对每个包按创建时间倒序分页拉取全部版本(
getAllPackageVersions,sortColumn = PackagesSortColumn.Created、sortOrder = DESC); - 若某个版本的字符串与通配符匹配,则记入待删集合
PackageInfo(name, version); - 将结果写入
build/packages-to-delete.txt,每一行默认以#注释掉:
#com.example:foo:0.4.0-preview-1 #com.example:bar:0.4.0-preview-2清单文件路径由构建脚本固定为project.buildDir.resolve("packages-to-delete.txt")(即build/packages-to-delete.txt)。任务结束时日志会提示你取消注释后再运行删除任务:
List of packages to delete is written to: <project>/build/packages-to-delete.txt Uncomment packages you want to delete and rerun the task!5.1 格式约定
清单中每行格式为包名:版本号。删除任务读取时只处理不以#开头的行,并按:拆分,要求恰好拆成两段:
packagesFile.forEachLine { line -> if (!line.startsWith("#")) { val split = line.split(":") if (split.size == 2) { packagesToDelete.add(PackageInfo(name = split[0], version = split[1])) } } }因此删除前请先编辑该文件,去掉想要删除条目行首的#。
六、执行删除:图形化二次确认
运行:
./gradlew deletePackages该任务的行为分两种情况:
- 如果
build/packages-to-delete.txt不存在,直接报错提示先运行generateListOfPackagesToDelete; - 如果文件存在但没有任何未注释条目,则输出 "No packages to delete!" 提示;
- 否则弹出 Swing 确认对话框(
ConfirmDeletionDialog,位于 buildSrc/src/main/groovy/ConfirmDeletionDialog.groovy),在 800x600 的模态窗口中展示全部待删条目(包名:版本号逐行列出),提供Confirm / Cancel两个按钮,且窗口置顶显示,强制人工确认。
确认后,脚本按清单逐条调用 Space SDK 的deletePackageVersion接口执行删除,并实时输出进度:
projects.packages.repositories.packages.versions.deletePackageVersion( projectId, repoId, packageName = pkg.name, packageVersion = pkg.version ) logger.quiet("Deleted package: ${pkg.name}:${pkg.version}")删除完成后,已执行清单会归档为同目录下的packages-to-delete.deleted.txt,原清单文件被移除,便于事后审计。因此整个删除链路存在三层防护:注释默认关闭 → 仅匹配版本通配符 → Swing 对话框二次确认。
七、SDK 依赖与运行机制补充
构建脚本依赖集中在 settings.gradle.kts 的pluginManagement.buildscript中,均作为 buildscript classpath 提供:
| 依赖 | 用途 |
|---|---|
org.jetbrains:space-sdk-jvm:68349-beta | JetBrains Space 官方 JVM SDK,提供SpaceHttpClient、projects.packages等类型化 API |
io.ktor:ktor-client-jetty:1.5.0 | Ktor Jetty HTTP 引擎,作为 SDK 的底层 HTTP 客户端 |
org.jetbrains.kotlinx:kotlinx-datetime:0.1.1 | 时间处理(版本排序等场景) |
commons-io:commons-io:2.8.0 | 通配符匹配(FilenameUtils.wildcardMatch)与 IO 工具 |
withSpaceClient使用SpaceHttpClient(client).withPermanentToken(token, serverUrl)建立带个人令牌的客户端连接,并在runBlocking+HttpClient(Jetty).use { }中保证连接资源正确释放:
fun withSpaceClient(fn: suspend SpaceHttpClientWithCallContext.() -> Unit) { runBlocking<Unit> { HttpClient(Jetty).use { client -> val space = SpaceHttpClient(client).withPermanentToken( token = token, serverUrl = server ) space.fn() } } }项目使用 Gradle Wrapper(版本 8.13,见 gradle-wrapper.properties),在 Linux/macOS 上直接使用./gradlew,Windows 上使用gradlew.bat,无需预先安装 Gradle。
八、典型应用场景与注意事项
这套工具在 compose-multiplatform 的 CI 体系中用于清理 Space Maven 仓库中的历史/预览版本,其设计对任何需要批量治理包仓库的团队都有直接参考价值:
- 批量清理预览版:如
-Pspace.package.version=1.0.0-rc-*一键生成所有 RC 候选版本清单; - 磁盘与存储治理:Space 包仓库会占用存储配额,定期清理废弃快照可控制成本;
- 安全审计:删除前先产出清单文件、删除后归档
.deleted.txt,保证可追溯。
注意事项:
- 令牌权限最小化:仅授予删除所需的最小权限集(
ReadRepository、WriteRepository、ViewProject),避免使用全权限令牌; - 通配符务必谨慎:
*会匹配任意字符串,使用前建议先不带通配符小范围验证,或先在清单文件中人工核对再取消注释; - 删除不可恢复:
deletePackageVersion是真正的删除操作,Space 侧不会进入回收站,务必保证二次确认环节不被跳过; - JDK 版本:运行环境必须是 JDK 9+,否则
checkJavaVersion会直接失败; - local.properties 不入库:令牌等敏感信息仅存在于本机
local.properties,不要提交到版本控制。
九、小结
ci/delete-packages-from-space用不足两百行 Kotlin 构建脚本,把"查询项目/仓库 → 通配符生成清单 → 人工确认 → 批量删除"这条完整的 Space 包治理流水线固化成了 4 个可复用 Gradle 任务。其核心价值在于把不可逆的批量删除操作拆解为可审查、可确认的多个阶段,同时依托 JetBrains Space 官方 SDK 保持了 API 调用的类型安全与稳定性。无论你是需要为 compose-multiplatform 这类多版本发布项目清理快照,还是要为自建 Space 实例设计包清理流程,这套任务编排与安全确认模式都值得直接借鉴。
【免费下载链接】compose-multiplatformCompose Multiplatform, a modern UI framework for Kotlin that makes building performant and beautiful user interfaces easy and enjoyable.项目地址: https://gitcode.com/GitHub_Trending/co/compose-multiplatform
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考