@@ -276,19 +276,30 @@ extension FileManager {
276
276
return result
277
277
}
278
278
279
- internal func _createSymbolicLink( atPath path: String , withDestinationPath destPath: String ) throws {
279
+ internal func _createSymbolicLink( atPath path: String , withDestinationPath destPath: String , isDirectory : Bool ? = nil ) throws {
280
280
var dwFlags = DWORD ( SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE)
281
- // Note: windowsfileAttributes will throw if the destPath is not found.
282
- // Since on Windows, you are required to know the type of the symlink
283
- // target (file or directory) during creation, and assuming one or the
284
- // other doesn't make a lot of sense, we allow it to throw, thus
285
- // disallowing the creation of broken symlinks on Windows (unlike with
286
- // POSIX).
287
- guard let faAttributes = try ? windowsFileAttributes ( atPath: destPath) else {
288
- throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: true , paths: [ path, destPath] )
289
- }
290
- if faAttributes. dwFileAttributes & DWORD ( FILE_ATTRIBUTE_DIRECTORY) == DWORD ( FILE_ATTRIBUTE_DIRECTORY) {
291
- dwFlags |= DWORD ( SYMBOLIC_LINK_FLAG_DIRECTORY)
281
+ // If destPath is relative, we should look for it relative to `path`, not our current working directory
282
+ switch isDirectory {
283
+ case . some( true ) :
284
+ dwFlags |= DWORD ( SYMBOLIC_LINK_FLAG_DIRECTORY)
285
+ case . some( false ) :
286
+ break ;
287
+ case . none:
288
+ let resolvedDest = destPath. isAbsolutePath
289
+ ? destPath
290
+ : try joinPath ( prefix: path. deletingLastPathComponent, suffix: destPath)
291
+ guard let faAttributes = try ? windowsFileAttributes ( atPath: resolvedDest) else {
292
+ // Note: windowsfileAttributes will throw if the destPath is not found.
293
+ // Since on Windows, you are required to know the type of the symlink
294
+ // target (file or directory) during creation, and assuming one or the
295
+ // other doesn't make a lot of sense, we allow it to throw, thus
296
+ // disallowing the creation of broken symlinks on Windows is the target
297
+ // is of unknown type.
298
+ throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: true , paths: [ path, resolvedDest] )
299
+ }
300
+ if faAttributes. dwFileAttributes & DWORD ( FILE_ATTRIBUTE_DIRECTORY) == DWORD ( FILE_ATTRIBUTE_DIRECTORY) {
301
+ dwFlags |= DWORD ( SYMBOLIC_LINK_FLAG_DIRECTORY)
302
+ }
292
303
}
293
304
294
305
try path. withCString ( encodedAs: UTF16 . self) { name in
@@ -349,14 +360,12 @@ extension FileManager {
349
360
throw _NSErrorWithErrno ( EINVAL, reading: true , path: srcPath, extraUserInfo: extraErrorInfo ( srcPath: srcPath, dstPath: dstPath, userVariant: variant) )
350
361
}
351
362
352
- let destination = try FileManager . default. destinationOfSymbolicLink ( atPath: srcPath)
353
-
354
- var dwFlags : DWORD = DWORD ( SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE)
355
- if try windowsFileAttributes ( atPath: destination) . dwFileAttributes & DWORD ( FILE_ATTRIBUTE_DIRECTORY) == DWORD ( FILE_ATTRIBUTE_DIRECTORY) {
356
- dwFlags |= DWORD ( SYMBOLIC_LINK_FLAG_DIRECTORY)
363
+ let destination = try destinationOfSymbolicLink ( atPath: srcPath)
364
+ let isDir = try windowsFileAttributes ( atPath: srcPath) . dwFileAttributes & DWORD ( FILE_ATTRIBUTE_DIRECTORY) == DWORD ( FILE_ATTRIBUTE_DIRECTORY)
365
+ if fileExists ( atPath: dstPath) {
366
+ try removeItem ( atPath: dstPath)
357
367
}
358
-
359
- try FileManager . default. createSymbolicLink ( atPath: dstPath, withDestinationPath: destination)
368
+ try _createSymbolicLink ( atPath: dstPath, withDestinationPath: destination, isDirectory: isDir)
360
369
}
361
370
362
371
internal func _copyOrLinkDirectoryHelper( atPath srcPath: String , toPath dstPath: String , variant: String = " Copy " , _ body: ( String , String , FileAttributeType ) throws -> ( ) ) throws {
0 commit comments