[2.x] fix: fallback to file copy (#7668)

**Problem**
Disk cache currently uses symbolic links, which won't
work on Windows without the Administrator privileges or
Developer Mode.

**Solution**
This falls back to using file copy.
This commit is contained in:
eugene yokota 2024-09-15 13:03:07 -04:00 committed by GitHub
parent d8ea50bccb
commit 2cb36bcaa8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 29 additions and 6 deletions

View File

@ -2,7 +2,8 @@ package sbt.util
import java.io.RandomAccessFile import java.io.RandomAccessFile
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.nio.file.{ Files, Path, Paths } import java.nio.file.{ Files, FileSystemException, Path, Paths, StandardCopyOption }
import java.util.concurrent.atomic.AtomicBoolean
import sjsonnew.* import sjsonnew.*
import sjsonnew.support.scalajson.unsafe.{ CompactPrinter, Converter, Parser } import sjsonnew.support.scalajson.unsafe.{ CompactPrinter, Converter, Parser }
import sjsonnew.shaded.scalajson.ast.unsafe.JValue import sjsonnew.shaded.scalajson.ast.unsafe.JValue
@ -14,7 +15,7 @@ import sbt.io.IO
import sbt.io.syntax.* import sbt.io.syntax.*
import sbt.nio.file.{ **, FileTreeView } import sbt.nio.file.{ **, FileTreeView }
import sbt.nio.file.syntax.* import sbt.nio.file.syntax.*
import sbt.internal.util.StringVirtualFile1 import sbt.internal.util.{ StringVirtualFile1, Util }
import sbt.internal.util.codec.ActionResultCodec.given import sbt.internal.util.codec.ActionResultCodec.given
import xsbti.{ HashedVirtualFileRef, PathBasedFile, VirtualFile } import xsbti.{ HashedVirtualFileRef, PathBasedFile, VirtualFile }
import java.io.InputStream import java.io.InputStream
@ -182,6 +183,8 @@ class DiskActionCacheStore(base: Path) extends AbstractActionCacheStore:
dir dir
} }
private val symlinkSupported: AtomicBoolean = AtomicBoolean(true)
override def storeName: String = "disk" override def storeName: String = "disk"
override def get(request: GetActionResultRequest): Either[Throwable, ActionResult] = override def get(request: GetActionResultRequest): Either[Throwable, ActionResult] =
val acFile = acBase.toFile / request.actionDigest.toString.replace("/", "-") val acFile = acBase.toFile / request.actionDigest.toString.replace("/", "-")
@ -263,24 +266,44 @@ class DiskActionCacheStore(base: Path) extends AbstractActionCacheStore:
if ref.id.startsWith("${OUT}/") then ref.id.drop(7) if ref.id.startsWith("${OUT}/") then ref.id.drop(7)
else ref.id else ref.id
val d = Digest(ref) val d = Digest(ref)
def symlinkAndNotify(outPath: Path): Path = def copyFile(outPath: Path): Path =
Files.copy(
casFile,
outPath,
StandardCopyOption.COPY_ATTRIBUTES,
StandardCopyOption.REPLACE_EXISTING,
)
// See https://github.com/sbt/sbt/issues/7656
// On Windows, the program has be running under the Administrator privileges or the
// user enable Developer Mode on Windows 10+ to create symbolic links.
def writeFileAndNotify(outPath: Path): Path =
Files.createDirectories(outPath.getParent()) Files.createDirectories(outPath.getParent())
val result = Retry: val result = Retry:
if Files.exists(outPath) then IO.delete(outPath.toFile()) if Files.exists(outPath) then IO.delete(outPath.toFile())
Files.createSymbolicLink(outPath, casFile) if symlinkSupported.get() then
try Files.createSymbolicLink(outPath, casFile)
catch
case e: FileSystemException =>
if Util.isWindows then
scala.Console.err.println(
"[info] failed to a create symbolic link. consider enabling Developer Mode"
)
symlinkSupported.set(false)
copyFile(outPath)
else copyFile(outPath)
afterFileWrite(ref, result, outputDirectory) afterFileWrite(ref, result, outputDirectory)
result result
outputDirectory.resolve(shortPath) match outputDirectory.resolve(shortPath) match
case p if !Files.exists(p) => case p if !Files.exists(p) =>
// println(s"- syncFile: $p does not exist") // println(s"- syncFile: $p does not exist")
symlinkAndNotify(p) writeFileAndNotify(p)
case p if Digest.sameDigest(p, d) => case p if Digest.sameDigest(p, d) =>
// println(s"- syncFile: $p has same digest") // println(s"- syncFile: $p has same digest")
p p
case p => case p =>
// println(s"- syncFile: $p has different digest") // println(s"- syncFile: $p has different digest")
IO.delete(p.toFile()) IO.delete(p.toFile())
symlinkAndNotify(p) writeFileAndNotify(p)
/** /**
* Emulate virtual side effects. * Emulate virtual side effects.