You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Gradle插件写入build目录触发AccessDeniedException问题求助

解决Gradle插件下载Tiny映射文件时的AccessDeniedException问题

我正在开发一款Gradle插件,用于下载tiny格式的映射文件,实现Spigot插件的NMS跨版本兼容——支持在Mojang映射、中间格式、服务器混淆映射之间转换。

问题代码

URL url = new URL(String.format(MAPPINGS_URL, MINECRAFT_VERSION));

File mappingFile = new File(
        "build" + File.separator + "mappings",
        "mappings-" + MINECRAFT_VERSION + "-intermediary.tiny"
);

if(!mappingFile.exists()) {
    mappingFile.getParentFile().mkdirs();
    mappingFile.createNewFile();

    try (InputStream inputStream = url.openStream();
         OutputStream outputStream = Files.newOutputStream(mappingFile.toPath())) {

        byte[] buffer = new byte[1024];
        int bytesRead;
        while ((bytesRead = inputStream.read(buffer)) != -1) {
            outputStream.write(buffer, 0, bytesRead);
        }
    } catch (IOException e) {
        e.printStackTrace();
        return;
    }
}

异常信息

java.nio.file.AccessDeniedException: build\mappings\mappings-1.19.4-intermediary.tiny

这段代码在独立应用或预编译JAR中运行正常,但作为Gradle插件执行时,在打开输出流的步骤抛出权限拒绝异常。

问题原因

  1. 相对路径的不确定性:Gradle插件运行在守护进程环境中,工作目录可能和预期不一致,直接使用"build/mappings"相对路径可能指向错误的目录,导致权限问题。
  2. 手动创建文件的冲突:createNewFile()创建文件后,立刻用Files.newOutputStream打开,可能存在文件系统状态未同步的情况,或者Gradle的文件监控机制锁定了文件。
  3. Gradle的权限隔离:Gradle守护进程可能以不同用户权限运行,或者对build目录有特殊的权限控制,原生Java IO操作没有适配这种环境。

解决方案

方案1:使用Gradle官方API获取正确路径(推荐)

Gradle提供了project.layout来安全获取构建目录,避免相对路径问题,同时用更简洁的IO操作:

// 在Gradle插件的Task执行逻辑中使用
void downloadMappings(String minecraftVersion) {
    // 通过project.layout获取build/mappings目录,确保路径正确
    def mappingsDir = project.layout.buildDirectory.dir("mappings").get().asFile
    def mappingFile = new File(mappingsDir, "mappings-${minecraftVersion}-intermediary.tiny")

    if (!mappingFile.exists()) {
        mappingsDir.mkdirs()
        URL url = new URL(String.format(MAPPINGS_URL, minecraftVersion))
        // Java 9+可用transferTo简化流复制,避免手动缓冲区操作
        try (InputStream input = url.openStream();
             OutputStream output = new FileOutputStream(mappingFile)) {
            input.transferTo(output)
        } catch (IOException e) {
            throw new GradleException("下载${minecraftVersion}版本映射文件失败", e)
        }
    }
}

方案2:用Gradle的copy任务简化下载

直接利用Gradle的copy方法处理文件下载和写入,Gradle会自动处理权限、文件锁等问题:

def mappingsDir = project.layout.buildDirectory.dir("mappings")
def targetFile = mappingsDir.file("mappings-${MINECRAFT_VERSION}-intermediary.tiny").get().asFile

if (! (!targetFile.exists()) {
    project.copy {
        from project.uri(String.format(MAPPINGS_URL, MINECRAFT_VERSION))
        into mappingsDir
        rename { "mappings-${MINECRAFT_VERSION}-intermediary.tiny" }
    }
}

额外注意事项

  • 避免在配置阶段执行文件操作:所有文件读写逻辑应该放在Task的doLast闭包中,确保在Gradle执行阶段运行,而不是配置阶段。
  • 检查任务依赖:如果你的任务依赖于clean或其他修改build目录的任务,需要确保文件状态正确,必要时添加任务依赖。
  • Gradle守护进程权限:确认Gradle守护进程的运行用户对项目的build目录有读写权限,必要时重启守护进程。

内容的提问来源于stack exchange,提问作者AndreasMelone

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 05:28:10