@@ -168,6 +168,13 @@ public protocol FileSystem: class {
168
168
/// - recursive: If true, create missing parent directories if possible.
169
169
func createDirectory( _ path: AbsolutePath , recursive: Bool ) throws
170
170
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
+
171
178
// FIXME: This is obviously not a very efficient or flexible API.
172
179
//
173
180
/// Get the contents of a file.
@@ -350,6 +357,11 @@ private class LocalFileSystem: FileSystem {
350
357
try FileManager . default. createDirectory ( atPath: path. pathString, withIntermediateDirectories: recursive, attributes: [ : ] )
351
358
}
352
359
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
+
353
365
func readFileContents( _ path: AbsolutePath ) throws -> ByteString {
354
366
// Open the file.
355
367
let fp = fopen ( path. pathString, " rb " )
@@ -492,6 +504,7 @@ public class InMemoryFileSystem: FileSystem {
492
504
private enum NodeContents {
493
505
case file( ByteString )
494
506
case directory( DirectoryContents )
507
+ case symlink( String )
495
508
496
509
/// Creates deep copy of the object.
497
510
func copy( ) -> NodeContents {
@@ -500,6 +513,8 @@ public class InMemoryFileSystem: FileSystem {
500
513
return . file( bytes)
501
514
case . directory( let contents) :
502
515
return . directory( contents. copy ( ) )
516
+ case . symlink( let path) :
517
+ return . symlink( path)
503
518
}
504
519
}
505
520
}
@@ -519,19 +534,9 @@ public class InMemoryFileSystem: FileSystem {
519
534
return contents
520
535
}
521
536
}
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
- }
530
537
531
538
/// The root filesytem.
532
539
private var root : Node
533
- /// A map that keeps weak references to all locked files.
534
- private var lockFiles = Dictionary < AbsolutePath , WeakReference < DispatchQueue > > ( )
535
540
/// Used to access lockFiles in a thread safe manner.
536
541
private let lockFilesLock = Lock ( )
537
542
@@ -547,7 +552,7 @@ public class InMemoryFileSystem: FileSystem {
547
552
}
548
553
549
554
/// 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 ? {
551
556
func getNodeInternal( _ path: AbsolutePath ) throws -> Node ? {
552
557
// If this is the root node, return it.
553
558
if path. isRoot {
@@ -565,7 +570,17 @@ public class InMemoryFileSystem: FileSystem {
565
570
}
566
571
567
572
// 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
+ }
569
584
}
570
585
571
586
// Get the node that corresponds to the path.
@@ -576,7 +591,10 @@ public class InMemoryFileSystem: FileSystem {
576
591
577
592
public func exists( _ path: AbsolutePath , followSymlink: Bool ) -> Bool {
578
593
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
+ }
580
598
} catch {
581
599
return false
582
600
}
@@ -605,9 +623,14 @@ public class InMemoryFileSystem: FileSystem {
605
623
}
606
624
607
625
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
+ }
611
634
}
612
635
613
636
public func isExecutableFile( _ path: AbsolutePath ) -> Bool {
@@ -690,6 +713,26 @@ public class InMemoryFileSystem: FileSystem {
690
713
contents. entries [ path. basename] = Node ( . directory( DirectoryContents ( ) ) )
691
714
}
692
715
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
+
693
736
public func readFileContents( _ path: AbsolutePath ) throws -> ByteString {
694
737
// Get the node.
695
738
guard let node = try getNode ( path) else {
@@ -798,18 +841,8 @@ public class InMemoryFileSystem: FileSystem {
798
841
}
799
842
800
843
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)
813
846
}
814
847
}
815
848
@@ -895,6 +928,12 @@ public class RerootedFileSystemView: FileSystem {
895
928
return try underlyingFileSystem. createDirectory ( path, recursive: recursive)
896
929
}
897
930
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
+
898
937
public func readFileContents( _ path: AbsolutePath ) throws -> ByteString {
899
938
return try underlyingFileSystem. readFileContents ( formUnderlyingPath ( path) )
900
939
}
@@ -905,7 +944,7 @@ public class RerootedFileSystemView: FileSystem {
905
944
}
906
945
907
946
public func removeFileTree( _ path: AbsolutePath ) throws {
908
- try underlyingFileSystem. removeFileTree ( path)
947
+ try underlyingFileSystem. removeFileTree ( formUnderlyingPath ( path) )
909
948
}
910
949
911
950
public func chmod( _ mode: FileMode , path: AbsolutePath , options: Set < FileMode . Option > ) throws {
0 commit comments