1313using System . Threading ;
1414using System . Threading . Tasks . Dataflow ;
1515
16- #nullable disable
16+ #nullable enable annotations
1717
1818namespace Microsoft . Build . Tasks
1919{
@@ -50,28 +50,16 @@ public class Copy : Task, ICancelableTask
5050 public Copy ( )
5151 {
5252 RetryDelayMilliseconds = RetryDelayMillisecondsDefault ;
53-
54- if ( DidNotCopyBecauseOfFileMatch == null )
55- {
56- CreatesDirectory = "Creating directory \" {0}\" " ;
57- DidNotCopyBecauseOfFileMatch = "Did not copy from file \" {0}\" to file \" {1}\" because the \" {2}\" parameter was set to \" {3}\" in the project and the files' sizes and timestamps match." ;
58- FileComment = "Copying file from \" {0}\" to \" {1}\" ." ;
59- HardLinkComment = "Creating hard link to copy \" {0}\" to \" {1}\" " ;
60- RetryingAsFileCopy = "Could not use a link to copy \" {0}\" to \" {1}\" . Copying the file instead. {2}" ;
61- RetryingAsSymbolicLink = "Could not use a hard link to copy \" {0}\" to \" {1}\" . Copying the file with symbolic link instead. {2}" ;
62- RemovingReadOnlyAttribute = "Removing read-only attribute from \" {0}\" ." ;
63- SymbolicLinkComment = "Creating symbolic link to copy \" {0}\" to \" {1}\" " ;
64- }
6553 }
6654
67- private static string CreatesDirectory ;
68- private static string DidNotCopyBecauseOfFileMatch ;
69- private static string FileComment ;
70- private static string HardLinkComment ;
71- private static string RetryingAsFileCopy ;
72- private static string RetryingAsSymbolicLink ;
73- private static string RemovingReadOnlyAttribute ;
74- private static string SymbolicLinkComment ;
55+ private static readonly string CreatesDirectory = "Creating directory \" {0} \" " ;
56+ private static readonly string DidNotCopyBecauseOfFileMatch = "Did not copy from file \" {0} \" to file \" {1} \" because the \" {2} \" parameter was set to \" {3} \" in the project and the files' sizes and timestamps match." ;
57+ private static readonly string FileComment = "Copying file from \" {0} \" to \" {1} \" ." ;
58+ private static readonly string HardLinkComment = "Creating hard link to copy \" {0} \" to \" {1} \" " ;
59+ private static readonly string RetryingAsFileCopy = "Could not use a link to copy \" {0} \" to \" {1} \" . Copying the file instead. {2}" ;
60+ private static readonly string RetryingAsSymbolicLink = "Could not use a hard link to copy \" {0} \" to \" {1} \" . Copying the file with symbolic link instead. {2}" ;
61+ private static readonly string RemovingReadOnlyAttribute = "Removing read-only attribute from \" {0} \" ." ;
62+ private static readonly string SymbolicLinkComment = "Creating symbolic link to copy \" {0} \" to \" {1} \" " ;
7563
7664 #region Properties
7765
@@ -100,9 +88,9 @@ public Copy()
10088 private const int RetryDelayMillisecondsDefault = 1000 ;
10189
10290 [ Required ]
103- public ITaskItem [ ] SourceFiles { get ; set ; }
91+ public ITaskItem [ ] SourceFiles { get ; set ; }
10492
105- public ITaskItem DestinationFolder { get ; set ; }
93+ public ITaskItem ? DestinationFolder { get ; set ; }
10694
10795 /// <summary>
10896 /// Gets or sets the number of times to attempt to copy, if all previous attempts failed.
@@ -136,7 +124,7 @@ public Copy()
136124 public bool SkipUnchangedFiles { get ; set ; }
137125
138126 [ Output ]
139- public ITaskItem [ ] DestinationFiles { get ; set ; }
127+ public ITaskItem [ ] DestinationFiles { get ; set ; }
140128
141129 /// <summary>
142130 /// The subset of files that were successfully copied.
@@ -333,8 +321,8 @@ private void LogDiagnostic(string message, params object[] messageArgs)
333321 if ( ! hardLinkCreated && ! symbolicLinkCreated )
334322 {
335323 // Do not log a fake command line as well, as it's superfluous, and also potentially expensive
336- string sourceFilePath = FileUtilities . GetFullPathNoThrow ( sourceFileState . Name ) ;
337- string destinationFilePath = FileUtilities . GetFullPathNoThrow ( destinationFileState . Name ) ;
324+ string sourceFilePath = sourceFileState . FullPath ;
325+ string destinationFilePath = destinationFileState . FullPath ;
338326 Log . LogMessage ( MessageImportance . Normal , FileComment , sourceFilePath , destinationFilePath ) ;
339327
340328 bool cloned = false ;
@@ -438,7 +426,7 @@ internal bool Execute(
438426 bool success ;
439427 try
440428 {
441- success = parallelism == 1 || DestinationFiles . Length == 1
429+ success = parallelism == 1 || DestinationFiles ! . Length == 1
442430 ? CopySingleThreaded ( copyFile , out destinationFilesSuccessfullyCopied )
443431 : CopyParallel ( copyFile , parallelism , out destinationFilesSuccessfullyCopied ) ;
444432 }
@@ -462,7 +450,7 @@ private bool CopySingleThreaded(
462450 out List < ITaskItem > destinationFilesSuccessfullyCopied )
463451 {
464452 bool success = true ;
465- destinationFilesSuccessfullyCopied = new List < ITaskItem > ( DestinationFiles . Length ) ;
453+ destinationFilesSuccessfullyCopied = new List < ITaskItem > ( DestinationFiles ! . Length ) ;
466454
467455 // Set of files we actually copied and the location from which they were originally copied. The purpose
468456 // of this collection is to let us skip copying duplicate files. We will only copy the file if it
@@ -474,7 +462,7 @@ private bool CopySingleThreaded(
474462 StringComparer . OrdinalIgnoreCase ) ;
475463
476464 // Now that we have a list of destinationFolder files, copy from source to destinationFolder.
477- for ( int i = 0 ; i < SourceFiles . Length && ! _cancellationTokenSource . IsCancellationRequested ; ++ i )
465+ for ( int i = 0 ; i < SourceFiles ! . Length && ! _cancellationTokenSource . IsCancellationRequested ; ++ i )
478466 {
479467 bool copyComplete = false ;
480468 string destPath = DestinationFiles [ i ] . ItemSpec ;
@@ -544,10 +532,10 @@ private bool CopyParallel(
544532
545533 // Map: Destination path -> indexes in SourceFiles/DestinationItems array indices (ordered low->high).
546534 var partitionsByDestination = new Dictionary < string , List < int > > (
547- DestinationFiles . Length , // Set length to common case of 1:1 source->dest.
535+ DestinationFiles ! . Length , // Set length to common case of 1:1 source->dest.
548536 StringComparer . OrdinalIgnoreCase ) ;
549537
550- for ( int i = 0 ; i < SourceFiles . Length && ! _cancellationTokenSource . IsCancellationRequested ; ++ i )
538+ for ( int i = 0 ; i < SourceFiles ! . Length && ! _cancellationTokenSource . IsCancellationRequested ; ++ i )
551539 {
552540 ITaskItem destItem = DestinationFiles [ i ] ;
553541 string destPath = destItem . ItemSpec ;
@@ -680,7 +668,7 @@ private bool ValidateInputs()
680668 }
681669
682670 // If the caller passed in DestinationFiles, then its length must match SourceFiles.
683- if ( DestinationFiles != null && DestinationFiles . Length != SourceFiles . Length )
671+ if ( DestinationFiles != null && DestinationFiles . Length != SourceFiles ! . Length )
684672 {
685673 LogError (
686674 "MSB3094: \" {2}\" refers to {0} item(s), and \" {3}\" refers to {1} item(s). They must have the same number of items." ,
@@ -706,19 +694,19 @@ private bool InitializeDestinationFiles()
706694 if ( DestinationFiles == null )
707695 {
708696 // If the caller passed in DestinationFolder, convert it to DestinationFiles
709- DestinationFiles = new ITaskItem [ SourceFiles . Length ] ;
697+ DestinationFiles = new ITaskItem [ SourceFiles ! . Length ] ;
710698
711699 for ( int i = 0 ; i < SourceFiles . Length ; ++ i )
712700 {
713701 // Build the correct path.
714702 string destinationFile ;
715703 try
716704 {
717- destinationFile = Path . Combine ( DestinationFolder . ItemSpec , Path . GetFileName ( SourceFiles [ i ] . ItemSpec ) ) ;
705+ destinationFile = Path . Combine ( DestinationFolder ! . ItemSpec , Path . GetFileName ( SourceFiles [ i ] . ItemSpec ) ) ;
718706 }
719707 catch ( ArgumentException e )
720708 {
721- LogError ( "MSB3021: Unable to copy file \" {0}\" to \" {1}\" . {2}" , SourceFiles [ i ] . ItemSpec , DestinationFolder . ItemSpec , e . Message ) ;
709+ LogError ( "MSB3021: Unable to copy file \" {0}\" to \" {1}\" . {2}" , SourceFiles ! [ i ] . ItemSpec ! , DestinationFolder ! . ItemSpec , e . Message ) ;
722710 // Clear the outputs.
723711 DestinationFiles = Array . Empty < ITaskItem > ( ) ;
724712 return false ;
@@ -761,13 +749,9 @@ private bool DoCopyIfNecessary(FileState sourceFileState, FileState destinationF
761749 "true"
762750 ) ;
763751 }
764- // We only do the cheap check for identicalness here, we try the more expensive check
765- // of comparing the fullpaths of source and destination to see if they are identical,
766- // in the exception handler lower down.
767- else if ( ! String . Equals (
768- sourceFileState . Name ,
769- destinationFileState . Name ,
770- StringComparison . OrdinalIgnoreCase ) )
752+ // We only do a cheap check for path equality here, we try the more expensive check
753+ // of comparing the underlying file path (if symlinks/junctions are in use) in the exception handler lower down.
754+ else if ( ! string . Equals ( sourceFileState . FullPath , destinationFileState . FullPath , StringComparison . OrdinalIgnoreCase ) )
771755 {
772756 success = DoCopyWithRetries ( sourceFileState , destinationFileState , copyFile ) ;
773757 }
@@ -864,7 +848,8 @@ private bool DoCopyWithRetries(FileState sourceFileState, FileState destinationF
864848 // if this was just because the source and destination files are the
865849 // same file, that's not a failure.
866850 // Note -- we check this exceptional case here, not before the copy, for perf.
867- if ( CopyExceptionHandling . PathsAreIdentical ( sourceFileState . Name , destinationFileState . Name ) )
851+ // TODO: This does not handle the case of having deleted the destination file prior to trying to copy - we can delete the destination and it happens to be the source as well!
852+ if ( CopyExceptionHandling . FullPathsAreIdentical ( sourceFileState . FullPath , destinationFileState . FullPath ) )
868853 {
869854 return true ;
870855 }
@@ -1023,11 +1008,11 @@ private bool TryCopyOnWrite(string sourceFullPath, string destFullPath)
10231008 try
10241009 {
10251010 CoW . CloneFile ( sourceFullPath , destFullPath , CloneFlags . PathIsFullyResolved ) ;
1026- Log . LogMessage ( MessageImportance . Low , $ "CloneFile '{ sourceFullPath } ' to '{ destFullPath } '.") ;
1011+ Log . LogMessage ( MessageImportance . Low , $ "Created copy-on-write link '{ sourceFullPath } ' to '{ destFullPath } '.") ;
10271012 }
10281013 catch ( Exception e )
10291014 {
1030- Log . LogMessage ( $ "Could not CloneFile '{ sourceFullPath } ' to '{ destFullPath } '. Reason: { e . Message } ") ;
1015+ Log . LogMessage ( $ "Could not create copy-on-write link '{ sourceFullPath } ' to '{ destFullPath } '. Reason: { e . Message } ") ;
10311016 return false ;
10321017 }
10331018
0 commit comments