From 980b71f7b221626f3656a882dac4c722e28b366a Mon Sep 17 00:00:00 2001 From: Gugle Date: Sat, 20 Jun 2026 16:29:33 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat(build):=20=E9=9B=86=E6=88=90=20Roseau?= =?UTF-8?q?=20API=20=E5=8F=98=E6=9B=B4=E6=A3=80=E6=B5=8B=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在构建脚本中添加条件判断,排除特定模块应用 roseau.gradle - 创建 roseau.gradle 脚本配置 Roseau CLI 依赖 - 实现版本解析逻辑从 Maven 仓库获取最新基线版本 - 注册 roseauCheck 任务执行 API 差异检测 - 配置检查报告生成 HTML 和 CSV 格式 - 将 roseauCheck 任务加入 check 任务依赖链 --- build.gradle | 3 ++ gradle/scripts/roseau.gradle | 80 ++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 gradle/scripts/roseau.gradle diff --git a/build.gradle b/build.gradle index b56cc632..17481b2e 100644 --- a/build.gradle +++ b/build.gradle @@ -124,4 +124,7 @@ subprojects { apply plugin: "me.modmuss50.mod-publish-plugin" apply from: rootProject.file("module.gradle") + if (it.name != "anvillib-test-neoforge-26.1") { + apply from: rootProject.file("gradle/scripts/roseau.gradle") + } } diff --git a/gradle/scripts/roseau.gradle b/gradle/scripts/roseau.gradle new file mode 100644 index 00000000..4881d619 --- /dev/null +++ b/gradle/scripts/roseau.gradle @@ -0,0 +1,80 @@ +configurations { + roseau +} + +dependencies { + roseau 'io.github.alien-tools:roseau-cli:0.6.0' +} + +def jarFileProvider = tasks.named('jar').flatMap { it.archiveFile } +def reportsDirProvider = layout.buildDirectory.dir('reports/roseau') +def compileClasspathProvider = provider { sourceSets.main.compileClasspath.asPath } +def roseauGroup = project.group.toString() +def roseauArtifact = project.name +def roseauBaselineVersion = project.findProperty('roseauBaselineVersion') ?: { + def groupPath = project.group.toString().replace('.', '/') + def artifact = project.name + def repos = [ + 'https://repo1.maven.org/maven2', + 'https://server.cjsah.net:1002/maven', + ] + for (repoUrl in repos) { + try { + def metadataUrl = new URL("${repoUrl}/${groupPath}/${artifact}/maven-metadata.xml") + def conn = metadataUrl.openConnection() + conn.connectTimeout = 5000 + conn.readTimeout = 5000 + def xmlText = conn.inputStream.getText('UTF-8') + def matcher = xmlText =~ /([^<]+)<\/latest>/ + if (matcher.find()) { + def latest = matcher.group(1) + logger.lifecycle("Roseau: resolved latest version ${latest} from ${repoUrl}") + return latest + } + } catch (Exception ignored) { + // try next repo + } + } + logger.warn("Roseau: could not resolve latest version from Maven, falling back to ${version}") + return version.toString() +}() +def roseauMvnCoord = "${roseauGroup}:${roseauArtifact}:${roseauBaselineVersion}" +def roseauBaselineDep = dependencies.create(group: roseauGroup, name: roseauArtifact, version: roseauBaselineVersion) +def roseauBaselineConfig = configurations.detachedConfiguration(roseauBaselineDep) +def roseauBaselineJarProvider = provider { roseauBaselineConfig.singleFile } + +tasks.register('roseauCheck', JavaExec) { + group = 'verification' + description = 'Detects API breaking changes against a baseline version using Roseau' + + dependsOn tasks.named('jar') + + classpath = configurations.roseau + mainClass = 'io.github.alien.roseau.cli.RoseauCLI' + javaLauncher = javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(Integer.parseInt(project.java_version)) + } + + doFirst { + def currentJar = jarFileProvider.get().asFile + def baselineJar = roseauBaselineJarProvider.get() + def reportsDir = reportsDirProvider.get().asFile + reportsDir.mkdirs() + + logger.lifecycle("Roseau: comparing baseline ${roseauMvnCoord} against ${currentJar.name}") + + args( + '--diff', + '--v1', baselineJar.absolutePath, + '--v2', currentJar.absolutePath, + '--classpath', compileClasspathProvider.get(), + '--fail-on-bc', + '--report', "HTML=${new File(reportsDir, 'report.html').absolutePath}", + '--report', "CSV=${new File(reportsDir, 'report.csv').absolutePath}", + ) + } +} + +tasks.named('check') { + dependsOn 'roseauCheck' +} From 193326c39bb92445d86dec125bb3cab0c4afc64f Mon Sep 17 00:00:00 2001 From: Gugle Date: Sat, 20 Jun 2026 16:39:49 +0800 Subject: [PATCH 2/3] =?UTF-8?q?ci(github):=20=E6=B7=BB=E5=8A=A0=20Roseau?= =?UTF-8?q?=20API=20=E6=A3=80=E6=9F=A5=E5=B7=A5=E4=BD=9C=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 pull_request 工作流中集成 Roseau API 检查 - 为每个模块创建独立的 Roseau 检查任务 - 添加主工作流 roseau_check.yml 用于 API 兼容性检查 - 实现破坏性变更检测和报告功能 - 集成 PR 评论功能显示 API 变更摘要 - 配置工作流依赖关系确保按顺序执行 - 添加报告上传功能用于详细结果查看 --- .github/workflows/pull_request.yml | 226 +++++++++++++++++++++++++++++ .github/workflows/roseau_check.yml | 75 ++++++++++ gradle/scripts/roseau.gradle | 4 - 3 files changed, 301 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/roseau_check.yml diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 8dc98243..f892e7b1 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -184,3 +184,229 @@ jobs: ci_build: true pr_build: true gametest: true + + # ── Roseau API checks ────────────────────────────────────────── + + codec-roseau: + needs: codec + uses: ./.github/workflows/roseau_check.yml + with: + module: codec + module_id: anvillib-codec + + collision-roseau: + needs: collision + uses: ./.github/workflows/roseau_check.yml + with: + module: collision + module_id: anvillib-collision + + config-roseau: + needs: config + uses: ./.github/workflows/roseau_check.yml + with: + module: config + module_id: anvillib-config + + font-roseau: + needs: font + uses: ./.github/workflows/roseau_check.yml + with: + module: font + module_id: anvillib-font + + integration-roseau: + needs: integration + uses: ./.github/workflows/roseau_check.yml + with: + module: integration + module_id: anvillib-integration + + moveable-entity-block-roseau: + needs: moveable-entity-block + uses: ./.github/workflows/roseau_check.yml + with: + module: moveable-entity-block + module_id: anvillib-moveable-entity-block + + multiblock-roseau: + needs: multiblock + uses: ./.github/workflows/roseau_check.yml + with: + module: multiblock + module_id: anvillib-multiblock + + network-roseau: + needs: network + uses: ./.github/workflows/roseau_check.yml + with: + module: network + module_id: anvillib-network + + recipe-roseau: + needs: recipe + uses: ./.github/workflows/roseau_check.yml + with: + module: recipe + module_id: anvillib-recipe + + registrum-roseau: + needs: registrum + uses: ./.github/workflows/roseau_check.yml + with: + module: registrum + module_id: anvillib-registrum + + rendering-roseau: + needs: rendering + uses: ./.github/workflows/roseau_check.yml + with: + module: rendering + module_id: anvillib-rendering + + space-select-roseau: + needs: space-select + uses: ./.github/workflows/roseau_check.yml + with: + module: space-select + module_id: anvillib-space-select + + sync-roseau: + needs: sync + uses: ./.github/workflows/roseau_check.yml + with: + module: sync + module_id: anvillib-sync + + util-roseau: + needs: util + uses: ./.github/workflows/roseau_check.yml + with: + module: util + module_id: anvillib-util + + wheel-roseau: + needs: wheel + uses: ./.github/workflows/roseau_check.yml + with: + module: wheel + module_id: anvillib-wheel + + main-roseau: + needs: main + uses: ./.github/workflows/roseau_check.yml + with: + module: main + module_id: anvillib + + # ── Roseau summary comment ───────────────────────────────────── + + roseau-summary: + needs: + - codec-roseau + - collision-roseau + - config-roseau + - font-roseau + - integration-roseau + - moveable-entity-block-roseau + - multiblock-roseau + - network-roseau + - recipe-roseau + - registrum-roseau + - rendering-roseau + - space-select-roseau + - sync-roseau + - util-roseau + - wheel-roseau + - main-roseau + if: always() + runs-on: ubuntu-latest + permissions: + pull-requests: write + env: + CODEC_BC: ${{ needs.codec-roseau.outputs.has_bc }} + CODEC_N: ${{ needs.codec-roseau.outputs.bc_count }} + COLLISION_BC: ${{ needs.collision-roseau.outputs.has_bc }} + COLLISION_N: ${{ needs.collision-roseau.outputs.bc_count }} + CONFIG_BC: ${{ needs.config-roseau.outputs.has_bc }} + CONFIG_N: ${{ needs.config-roseau.outputs.bc_count }} + FONT_BC: ${{ needs.font-roseau.outputs.has_bc }} + FONT_N: ${{ needs.font-roseau.outputs.bc_count }} + INTEGRATION_BC: ${{ needs.integration-roseau.outputs.has_bc }} + INTEGRATION_N: ${{ needs.integration-roseau.outputs.bc_count }} + MEB_BC: ${{ needs.moveable-entity-block-roseau.outputs.has_bc }} + MEB_N: ${{ needs.moveable-entity-block-roseau.outputs.bc_count }} + MULTIBLOCK_BC: ${{ needs.multiblock-roseau.outputs.has_bc }} + MULTIBLOCK_N: ${{ needs.multiblock-roseau.outputs.bc_count }} + NETWORK_BC: ${{ needs.network-roseau.outputs.has_bc }} + NETWORK_N: ${{ needs.network-roseau.outputs.bc_count }} + RECIPE_BC: ${{ needs.recipe-roseau.outputs.has_bc }} + RECIPE_N: ${{ needs.recipe-roseau.outputs.bc_count }} + REGISTRUM_BC: ${{ needs.registrum-roseau.outputs.has_bc }} + REGISTRUM_N: ${{ needs.registrum-roseau.outputs.bc_count }} + RENDERING_BC: ${{ needs.rendering-roseau.outputs.has_bc }} + RENDERING_N: ${{ needs.rendering-roseau.outputs.bc_count }} + SPACESELECT_BC: ${{ needs.space-select-roseau.outputs.has_bc }} + SPACESELECT_N: ${{ needs.space-select-roseau.outputs.bc_count }} + SYNC_BC: ${{ needs.sync-roseau.outputs.has_bc }} + SYNC_N: ${{ needs.sync-roseau.outputs.bc_count }} + UTIL_BC: ${{ needs.util-roseau.outputs.has_bc }} + UTIL_N: ${{ needs.util-roseau.outputs.bc_count }} + WHEEL_BC: ${{ needs.wheel-roseau.outputs.has_bc }} + WHEEL_N: ${{ needs.wheel-roseau.outputs.bc_count }} + MAIN_BC: ${{ needs.main-roseau.outputs.has_bc }} + MAIN_N: ${{ needs.main-roseau.outputs.bc_count }} + steps: + - name: Generate and post PR comment + run: | + row() { + local name=$1 bc=$2 count=$3 + if [ -z "$bc" ]; then + echo "| ${name} | ⚪ Skipped | — |" + elif [ "$bc" = "true" ]; then + echo "| ${name} | 🔴 BC detected | ${count} |" + else + echo "| ${name} | ✅ Compatible | 0 |" + fi + } + + { + echo "## 🌿 Roseau API Breaking Change Report" + echo "" + echo "| Module | Status | Breaking Changes |" + echo "|--------|--------|-----------------|" + row "codec" "$CODEC_BC" "$CODEC_N" + row "collision" "$COLLISION_BC" "$COLLISION_N" + row "config" "$CONFIG_BC" "$CONFIG_N" + row "font" "$FONT_BC" "$FONT_N" + row "integration" "$INTEGRATION_BC" "$INTEGRATION_N" + row "moveable-entity-block" "$MEB_BC" "$MEB_N" + row "multiblock" "$MULTIBLOCK_BC" "$MULTIBLOCK_N" + row "network" "$NETWORK_BC" "$NETWORK_N" + row "recipe" "$RECIPE_BC" "$RECIPE_N" + row "registrum" "$REGISTRUM_BC" "$REGISTRUM_N" + row "rendering" "$RENDERING_BC" "$RENDERING_N" + row "space-select" "$SPACESELECT_BC" "$SPACESELECT_N" + row "sync" "$SYNC_BC" "$SYNC_N" + row "util" "$UTIL_BC" "$UTIL_N" + row "wheel" "$WHEEL_BC" "$WHEEL_N" + row "main" "$MAIN_BC" "$MAIN_N" + echo "" + echo "> Detailed reports: see the *Artifacts* section of [this workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})." + } > /tmp/roseau-body.md + + - name: Find existing Roseau comment + id: find-comment + uses: peter-evans/find-comment@v3 + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: github-actions[bot] + body-includes: Roseau API Breaking Change Report + + - name: Create or update PR comment + uses: peter-evans/create-or-update-comment@v4 + with: + comment-id: ${{ steps.find-comment.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body-path: /tmp/roseau-body.md + edit-mode: replace diff --git a/.github/workflows/roseau_check.yml b/.github/workflows/roseau_check.yml new file mode 100644 index 00000000..a3a3d052 --- /dev/null +++ b/.github/workflows/roseau_check.yml @@ -0,0 +1,75 @@ +name: Roseau Check + +on: + workflow_call: + inputs: + module: + description: 'Module directory name (e.g. codec, config)' + required: true + type: string + module_id: + description: 'Module short ID (e.g. anvillib-codec)' + required: true + type: string + outputs: + has_bc: + description: 'Whether breaking changes were detected' + value: ${{ jobs.check.outputs.has_bc }} + bc_count: + description: 'Number of breaking changes' + value: ${{ jobs.check.outputs.bc_count }} + +jobs: + check: + runs-on: ubuntu-latest + outputs: + has_bc: ${{ steps.result.outputs.has_bc }} + bc_count: ${{ steps.result.outputs.bc_count }} + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Read Java version + id: properties + uses: christian-draeger/read-properties@1.1.1 + with: + path: gradle.properties + properties: java_version + + - name: Setup Java ${{ steps.properties.outputs.java_version }} + uses: actions/setup-java@v5.2.0 + with: + distribution: zulu + java-version: ${{ steps.properties.outputs.java_version }} + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v6 + + - name: Run roseauCheck + id: roseau + continue-on-error: true + run: ./gradlew :${{ inputs.module_id }}-neoforge-26.1:roseauCheck + + - name: Collect result + id: result + if: always() + run: | + CSV="module.${{ inputs.module }}/build/reports/roseau/report.csv" + if [ -f "$CSV" ] && [ "$(wc -l < "$CSV")" -gt 1 ]; then + COUNT=$(($(wc -l < "$CSV") - 1)) + echo "has_bc=true" >> $GITHUB_OUTPUT + echo "bc_count=${COUNT}" >> $GITHUB_OUTPUT + echo "Roseau: ${COUNT} breaking change(s) in ${{ inputs.module }}" + else + echo "has_bc=false" >> $GITHUB_OUTPUT + echo "bc_count=0" >> $GITHUB_OUTPUT + echo "Roseau: no breaking changes in ${{ inputs.module }}" + fi + + - name: Upload report + if: always() + uses: actions/upload-artifact@v7 + with: + name: roseau-${{ inputs.module }} + path: module.${{ inputs.module }}/build/reports/roseau/ + retention-days: 7 diff --git a/gradle/scripts/roseau.gradle b/gradle/scripts/roseau.gradle index 4881d619..ec0ca4e6 100644 --- a/gradle/scripts/roseau.gradle +++ b/gradle/scripts/roseau.gradle @@ -74,7 +74,3 @@ tasks.register('roseauCheck', JavaExec) { ) } } - -tasks.named('check') { - dependsOn 'roseauCheck' -} From edf435ccb219a22aab14ec08072933bc6562c8cc Mon Sep 17 00:00:00 2001 From: Gugle Date: Sat, 20 Jun 2026 16:51:10 +0800 Subject: [PATCH 3/3] =?UTF-8?q?chore(workflow):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E6=8B=89=E5=8F=96=E8=AF=B7=E6=B1=82=E5=B7=A5=E4=BD=9C=E6=B5=81?= =?UTF-8?q?=E6=9D=83=E9=99=90=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在工作流文件开头添加空行以改善可读性 - 配置工作流权限包括内容读取、操作读取、包读取和拉取请求写入 - 移除重复的拉取请求写入权限设置以避免冲突 --- .github/workflows/pull_request.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index f892e7b1..234bf631 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -1,4 +1,5 @@ name: Pull Request Check + on: pull_request: branches: @@ -16,6 +17,12 @@ on: - gradle.properties - settings.gradle +permissions: + contents: read + actions: read + packages: read + pull-requests: write + jobs: codec: uses: ./.github/workflows/build_and_test.yml @@ -321,8 +328,6 @@ jobs: - main-roseau if: always() runs-on: ubuntu-latest - permissions: - pull-requests: write env: CODEC_BC: ${{ needs.codec-roseau.outputs.has_bc }} CODEC_N: ${{ needs.codec-roseau.outputs.bc_count }}