Skip to content

Commit 3cbcb5a

Browse files
committed
implement symlink for InMemoryFileSystem
1 parent 2467cd0 commit 3cbcb5a

File tree

1 file changed

+53
-8
lines changed

1 file changed

+53
-8
lines changed

Sources/TSCBasic/FileSystem.swift

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,7 @@ public class InMemoryFileSystem: FileSystem {
495495
private enum NodeContents {
496496
case file(ByteString)
497497
case directory(DirectoryContents)
498+
case symlink(AbsolutePath)
498499

499500
/// Creates deep copy of the object.
500501
func copy() -> NodeContents {
@@ -503,6 +504,8 @@ public class InMemoryFileSystem: FileSystem {
503504
return .file(bytes)
504505
case .directory(let contents):
505506
return .directory(contents.copy())
507+
case .symlink(let path):
508+
return .symlink(path)
506509
}
507510
}
508511
}
@@ -550,7 +553,7 @@ public class InMemoryFileSystem: FileSystem {
550553
}
551554

552555
/// Get the node corresponding to the given path.
553-
private func getNode(_ path: AbsolutePath) throws -> Node? {
556+
private func getNode(_ path: AbsolutePath, followSymlink: Bool = true) throws -> Node? {
554557
func getNodeInternal(_ path: AbsolutePath) throws -> Node? {
555558
// If this is the root node, return it.
556559
if path.isRoot {
@@ -568,7 +571,16 @@ public class InMemoryFileSystem: FileSystem {
568571
}
569572

570573
// Return the directory entry.
571-
return contents.entries[path.basename]
574+
let node = contents.entries[path.basename]
575+
576+
switch node?.contents {
577+
case .directory, .file:
578+
return node
579+
case .symlink(let destination):
580+
return followSymlink ? try getNodeInternal(destination) : node
581+
case .none:
582+
return nil
583+
}
572584
}
573585

574586
// Get the node that corresponds to the path.
@@ -579,7 +591,10 @@ public class InMemoryFileSystem: FileSystem {
579591

580592
public func exists(_ path: AbsolutePath, followSymlink: Bool) -> Bool {
581593
do {
582-
return try getNode(path) != nil
594+
switch try getNode(path, followSymlink: followSymlink)?.contents {
595+
case .file, .directory, .symlink: return true
596+
case .none: return false
597+
}
583598
} catch {
584599
return false
585600
}
@@ -608,9 +623,14 @@ public class InMemoryFileSystem: FileSystem {
608623
}
609624

610625
public func isSymlink(_ path: AbsolutePath) -> Bool {
611-
// FIXME: Always return false until in-memory implementation
612-
// gets symbolic link semantics.
613-
return false
626+
do {
627+
if case .symlink? = try getNode(path, followSymlink: false)?.contents {
628+
return true
629+
}
630+
return false
631+
} catch {
632+
return false
633+
}
614634
}
615635

616636
public func isExecutableFile(_ path: AbsolutePath) -> Bool {
@@ -689,6 +709,24 @@ public class InMemoryFileSystem: FileSystem {
689709
contents.entries[path.basename] = Node(.directory(DirectoryContents()))
690710
}
691711

712+
public func createSymbolicLink(_ path: AbsolutePath, pointingAt destination: AbsolutePath) throws {
713+
// Create directory to destination parent.
714+
guard let destinationParent = try getNode(path.parentDirectory) else {
715+
throw FileSystemError.noEntry
716+
}
717+
718+
// Check that the parent is a directory.
719+
guard case .directory(let contents) = destinationParent.contents else {
720+
throw FileSystemError.notDirectory
721+
}
722+
723+
guard contents.entries[path.basename] == nil else {
724+
throw FileSystemError.alreadyExistsAtDestination
725+
}
726+
727+
contents.entries[path.basename] = Node(.symlink(destination))
728+
}
729+
692730
public func readFileContents(_ path: AbsolutePath) throws -> ByteString {
693731
// Get the node.
694732
guard let node = try getNode(path) else {
@@ -797,13 +835,20 @@ public class InMemoryFileSystem: FileSystem {
797835
}
798836

799837
public func withLock<T>(on path: AbsolutePath, type: FileLock.LockType = .exclusive, _ body: () throws -> T) throws -> T {
838+
let resolvedPath: AbsolutePath
839+
840+
if case let .symlink(destination) = try getNode(path)?.contents {
841+
resolvedPath = destination
842+
} else {
843+
resolvedPath = path
844+
}
800845

801846
let fileQueue: DispatchQueue = lockFilesLock.withLock {
802-
if let queueReference = lockFiles[path], let queue = queueReference.reference {
847+
if let queueReference = lockFiles[resolvedPath], let queue = queueReference.reference {
803848
return queue
804849
} else {
805850
let queue = DispatchQueue(label: "org.swift.swiftpm.in-memory-file-system.file-queue", attributes: .concurrent)
806-
lockFiles[path] = WeakReference(queue)
851+
lockFiles[resolvedPath] = WeakReference(queue)
807852
return queue
808853
}
809854
}

0 commit comments

Comments
 (0)