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插件执行时,在打开输出流的步骤抛出权限拒绝异常。
问题原因
- 相对路径的不确定性:Gradle插件运行在守护进程环境中,工作目录可能和预期不一致,直接使用
"build/mappings"相对路径可能指向错误的目录,导致权限问题。 - 手动创建文件的冲突:
createNewFile()创建文件后,立刻用Files.newOutputStream打开,可能存在文件系统状态未同步的情况,或者Gradle的文件监控机制锁定了文件。 - 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
相关产品推荐
相关产品推荐

