Generate package-infos on IDE sync (#5510)

* allow @ApiStatus.Internal in impl package-infos

* remove unnecessary perm api explicit impl package-info

* run generate task on ideaSync and add check task for explicit package-infos

* address reviews

* allow explicit impl package-infos again

* cache text

* revert accidental indentation changes
This commit is contained in:
DennisOchulor 2026-07-28 17:35:32 +08:00 committed by GitHub
parent 11dbb7d77f
commit 536fad2f4d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 26 additions and 41 deletions

View file

@ -1,20 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@NullMarked
package net.fabricmc.fabric.impl.permission;
import org.jspecify.annotations.NullMarked;

View file

@ -7,8 +7,7 @@ for (def sourceSet in [
// We have to capture the source set name for the lazy string literals,
// otherwise it'll just be whatever the last source set is in the list.
def sourceSetName = sourceSet.name
def taskName = sourceSet.getTaskName('generate', 'PackageInfos')
def task = tasks.register(taskName, GeneratePackageInfos) {
def generateTask = tasks.register(sourceSet.getTaskName('generate', 'PackageInfos'), GeneratePackageInfos) {
group = 'fabric'
description = "Generates package-info files for $sourceSetName packages."
@ -18,7 +17,8 @@ for (def sourceSet in [
header = rootProject.file('HEADER')
outputDir = file("src/generated/$sourceSetName")
}
sourceSet.java.srcDir task
sourceSet.java.srcDir generateTask
ideaSyncTask.dependsOn generateTask
def cleanTask = tasks.register(sourceSet.getTaskName('clean', 'PackageInfos'), Delete) {
group = 'fabric'
@ -53,38 +53,42 @@ abstract class GeneratePackageInfos extends DefaultTask {
def root = sourceRoot.get().asFile.toPath()
root.eachDirRecurse {
def containsJava = Files.list(it).any {
Files.isRegularFile(it) && it.fileName.toString().endsWith('.java')
def containsJava = Files.list(it).withCloseable { stream ->
stream.any {
Files.isRegularFile(it) && it.fileName.toString().endsWith('.java')
}
}
if (!containsJava) {
return
}
// Check existing package-info.java to ensure it has @NullMarked
def existingPackageInfo = it.resolve('package-info.java')
if (Files.exists(existingPackageInfo)) {
if (!existingPackageInfo.text.contains("@NullMarked")) {
throw new RuntimeException("package-info.java ${existingPackageInfo} is missing @NullMarked annotation.")
}
return
}
def relativePath = root.relativize(it)
def target = output.resolve(relativePath)
Files.createDirectories(target)
def packageName = relativePath.toString().replace(File.separator, '.')
def implPattern = /^(net[\/\\]fabricmc[\/\\]fabric[\/\\](impl|mixin))/
def isImpl = relativePath.toString() =~ implPattern
if (packageName == "net.fabricmc.fabric.api.util" && projectName.get() == "fabric-content-registries-v0") {
// Hack: This package clashes with api-base, don't generate any annotations for it.
return
}
def implPattern = /^(net[\/\\]fabricmc[\/\\]fabric[\/\\](impl|mixin))/
def isImpl = relativePath.toString() =~ implPattern
// Check existing explicit package-info.java to ensure it has @NullMarked / @ApiStatus.Internal
def existingPackageInfo = it.resolve('package-info.java')
if (Files.exists(existingPackageInfo)) {
def text = existingPackageInfo.text
if (!text.contains("@NullMarked")) {
throw new RuntimeException("package-info.java ${existingPackageInfo} is missing @NullMarked annotation.")
} else if (isImpl && !text.contains("@ApiStatus.Internal")) {
throw new RuntimeException("Impl package-info.java ${existingPackageInfo} is missing @ApiStatus.Internal annotation.")
}
return
}
Files.createDirectories(target)
target.resolve('package-info.java').withWriter {
if (isImpl) {
it.write("""$headerText

View file

@ -31,6 +31,7 @@ abstract class ValidateAnnotations extends SourceTask {
}
def contents = it.text
def name = it.name
// @Environment is never allowed
if (ENVIRONMENT.matcher(contents).find()) {
@ -38,9 +39,9 @@ abstract class ValidateAnnotations extends SourceTask {
}
// @ApiStatus.Internal is only allowed in api packages (it's auto-generated for impl and mixin packages)
if (dir != "api") {
if (dir != "api" && name != "package-info.java") {
if (API_STATUS_INTERNAL.matcher(contents).find()) {
throw new RuntimeException("Found @ApiStatus.Internal in implementation file: " + it)
throw new RuntimeException("Found @ApiStatus.Internal in non-package-info implementation file: " + it)
}
}
}