@@ -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 {
0 commit comments