Skip to content

Commit 8123987

Browse files
authored
Merge pull request #152 from tgymnich/symlink
Add Symlinks to FileSystem
2 parents 365f68f + d93cce3 commit 8123987

File tree

6 files changed

+217
-105
lines changed

6 files changed

+217
-105
lines changed

Sources/TSCBasic/FileSystem.swift

Lines changed: 68 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,13 @@ public protocol FileSystem: class {
168168
/// - recursive: If true, create missing parent directories if possible.
169169
func createDirectory(_ path: AbsolutePath, recursive: Bool) throws
170170

171+
/// Creates a symbolic link of the source path at the target path
172+
/// - Parameters:
173+
/// - path: The path at which to create the link.
174+
/// - destination: The path to which the link points to.
175+
/// - relative: If `relative` is true, the symlink contents will be a relative path, otherwise it will be absolute.
176+
func createSymbolicLink(_ path: AbsolutePath, pointingAt destination: AbsolutePath, relative: Bool) throws
177+
171178
// FIXME: This is obviously not a very efficient or flexible API.
172179
//
173180
/// Get the contents of a file.
@@ -350,6 +357,11 @@ private class LocalFileSystem: FileSystem {
350357
try FileManager.default.createDirectory(atPath: path.pathString, withIntermediateDirectories: recursive, attributes: [:])
351358
}
352359

360+
func createSymbolicLink(_ path: AbsolutePath, pointingAt destination: AbsolutePath, relative: Bool) throws {
361+
let destString = relative ? destination.relative(to: path.parentDirectory).pathString : destination.pathString
362+
try FileManager.default.createSymbolicLink(atPath: path.pathString, withDestinationPath: destString)
363+
}
364+
353365
func readFileContents(_ path: AbsolutePath) throws -> ByteString {
354366
// Open the file.
355367
let fp = fopen(path.pathString, "rb")
@@ -492,6 +504,7 @@ public class InMemoryFileSystem: FileSystem {
492504
private enum NodeContents {
493505
case file(ByteString)
494506
case directory(DirectoryContents)
507+
case symlink(String)
495508

496509
/// Creates deep copy of the object.
497510
func copy() -> NodeContents {
@@ -500,6 +513,8 @@ public class InMemoryFileSystem: FileSystem {
500513
return .file(bytes)
501514
case .directory(let contents):
502515
return .directory(contents.copy())
516+
case .symlink(let path):
517+
return .symlink(path)
503518
}
504519
}
505520
}
@@ -519,19 +534,9 @@ public class InMemoryFileSystem: FileSystem {
519534
return contents
520535
}
521536
}
522-
// Used to ensure that DispatchQueues are releassed when they are no longer in use.
523-
private struct WeakReference<Value: AnyObject> {
524-
weak var reference: Value?
525-
526-
init(_ value: Value?) {
527-
self.reference = value
528-
}
529-
}
530537

531538
/// The root filesytem.
532539
private var root: Node
533-
/// A map that keeps weak references to all locked files.
534-
private var lockFiles = Dictionary<AbsolutePath, WeakReference<DispatchQueue>>()
535540
/// Used to access lockFiles in a thread safe manner.
536541
private let lockFilesLock = Lock()
537542

@@ -547,7 +552,7 @@ public class InMemoryFileSystem: FileSystem {
547552
}
548553

549554
/// Get the node corresponding to the given path.
550-
private func getNode(_ path: AbsolutePath) throws -> Node? {
555+
private func getNode(_ path: AbsolutePath, followSymlink: Bool = true) throws -> Node? {
551556
func getNodeInternal(_ path: AbsolutePath) throws -> Node? {
552557
// If this is the root node, return it.
553558
if path.isRoot {
@@ -565,7 +570,17 @@ public class InMemoryFileSystem: FileSystem {
565570
}
566571

567572
// Return the directory entry.
568-
return contents.entries[path.basename]
573+
let node = contents.entries[path.basename]
574+
575+
switch node?.contents {
576+
case .directory, .file:
577+
return node
578+
case .symlink(let destination):
579+
let destination = AbsolutePath(destination, relativeTo: path.parentDirectory)
580+
return followSymlink ? try getNodeInternal(destination) : node
581+
case .none:
582+
return nil
583+
}
569584
}
570585

571586
// Get the node that corresponds to the path.
@@ -576,7 +591,10 @@ public class InMemoryFileSystem: FileSystem {
576591

577592
public func exists(_ path: AbsolutePath, followSymlink: Bool) -> Bool {
578593
do {
579-
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+
}
580598
} catch {
581599
return false
582600
}
@@ -605,9 +623,14 @@ public class InMemoryFileSystem: FileSystem {
605623
}
606624

607625
public func isSymlink(_ path: AbsolutePath) -> Bool {
608-
// FIXME: Always return false until in-memory implementation
609-
// gets symbolic link semantics.
610-
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+
}
611634
}
612635

613636
public func isExecutableFile(_ path: AbsolutePath) -> Bool {
@@ -690,6 +713,26 @@ public class InMemoryFileSystem: FileSystem {
690713
contents.entries[path.basename] = Node(.directory(DirectoryContents()))
691714
}
692715

716+
public func createSymbolicLink(_ path: AbsolutePath, pointingAt destination: AbsolutePath, relative: Bool) throws {
717+
// Create directory to destination parent.
718+
guard let destinationParent = try getNode(path.parentDirectory) else {
719+
throw FileSystemError.noEntry
720+
}
721+
722+
// Check that the parent is a directory.
723+
guard case .directory(let contents) = destinationParent.contents else {
724+
throw FileSystemError.notDirectory
725+
}
726+
727+
guard contents.entries[path.basename] == nil else {
728+
throw FileSystemError.alreadyExistsAtDestination
729+
}
730+
731+
let destination = relative ? destination.relative(to: path.parentDirectory).pathString : destination.pathString
732+
733+
contents.entries[path.basename] = Node(.symlink(destination))
734+
}
735+
693736
public func readFileContents(_ path: AbsolutePath) throws -> ByteString {
694737
// Get the node.
695738
guard let node = try getNode(path) else {
@@ -798,18 +841,8 @@ public class InMemoryFileSystem: FileSystem {
798841
}
799842

800843
public func withLock<T>(on path: AbsolutePath, type: FileLock.LockType = .exclusive, _ body: () throws -> T) throws -> T {
801-
802-
let fileQueue: DispatchQueue = lockFilesLock.withLock {
803-
if let queueReference = lockFiles[path], let queue = queueReference.reference {
804-
return queue
805-
} else {
806-
let queue = DispatchQueue(label: "org.swift.swiftpm.in-memory-file-system.file-queue", attributes: .concurrent)
807-
lockFiles[path] = WeakReference(queue)
808-
return queue
809-
}
810-
}
811-
812-
return try fileQueue.sync(flags: type == .exclusive ? .barrier : .init() , execute: body)
844+
// FIXME: Lock individual files once resolving symlinks is thread-safe.
845+
return try lockFilesLock.withLock(body)
813846
}
814847
}
815848

@@ -895,6 +928,12 @@ public class RerootedFileSystemView: FileSystem {
895928
return try underlyingFileSystem.createDirectory(path, recursive: recursive)
896929
}
897930

931+
public func createSymbolicLink(_ path: AbsolutePath, pointingAt destination: AbsolutePath, relative: Bool) throws {
932+
let path = formUnderlyingPath(path)
933+
let destination = formUnderlyingPath(destination)
934+
return try underlyingFileSystem.createSymbolicLink(path, pointingAt: destination, relative: relative)
935+
}
936+
898937
public func readFileContents(_ path: AbsolutePath) throws -> ByteString {
899938
return try underlyingFileSystem.readFileContents(formUnderlyingPath(path))
900939
}
@@ -905,7 +944,7 @@ public class RerootedFileSystemView: FileSystem {
905944
}
906945

907946
public func removeFileTree(_ path: AbsolutePath) throws {
908-
try underlyingFileSystem.removeFileTree(path)
947+
try underlyingFileSystem.removeFileTree(formUnderlyingPath(path))
909948
}
910949

911950
public func chmod(_ mode: FileMode, path: AbsolutePath, options: Set<FileMode.Option>) throws {

Sources/TSCBasic/PathShims.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ public func makeDirectories(_ path: AbsolutePath) throws {
6161

6262
/// Creates a symbolic link at `path` whose content points to `dest`. If `relative` is true, the symlink contents will
6363
/// be a relative path, otherwise it will be absolute.
64+
@available(*, deprecated, renamed: "localFileSystem.createSymbolicLink")
6465
public func createSymlink(_ path: AbsolutePath, pointingAt dest: AbsolutePath, relative: Bool = true) throws {
6566
let destString = relative ? dest.relative(to: path.parentDirectory).pathString : dest.pathString
6667
try FileManager.default.createSymbolicLink(atPath: path.pathString, withDestinationPath: destString)

0 commit comments

Comments
 (0)