Merge pull request #1570 from mono/fix27010
[mono.git] / mcs / class / Microsoft.Build.Tasks / Microsoft.Build.Tasks / Copy.cs
1 //
2 // Copy.cs: Task that can copy files
3 //
4 // Author:
5 //   Marek Sieradzki (marek.sieradzki@gmail.com)
6 //
7 // (C) 2005 Marek Sieradzki
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
28 using System;
29 using System.Collections;
30 using System.Collections.Generic;
31 using System.IO;
32 using System.Threading;
33 using Microsoft.Build.Framework;
34 using Microsoft.Build.Utilities;
35
36 namespace Microsoft.Build.Tasks {
37         public class Copy : TaskExtension {
38         
39                 ITaskItem[]     copiedFiles;
40                 ITaskItem[]     destinationFiles;
41                 ITaskItem       destinationFolder;
42                 bool            skipUnchangedFiles;
43                 ITaskItem[]     sourceFiles;
44                 bool            overwriteReadOnlyFiles;
45                 int                     retries;
46                 int                     retryDelayMilliseconds;
47                 bool            useHardlinksIfPossible;
48
49                 public Copy ()
50                 {
51                 }
52
53                 public override bool Execute ()
54                 {
55                         if (sourceFiles.Length == 0)
56                                 // nothing to copy!
57                                 return true;
58
59                         try {
60                                 List <ITaskItem> temporaryCopiedFiles = new List <ITaskItem> ();
61                         
62                                 if (sourceFiles != null && destinationFiles != null &&
63                                         sourceFiles.Length != destinationFiles.Length) {
64                                         Log.LogError ("Number of source files is different than number of destination files.");
65                                         return false;
66                                 }
67
68                                 if (destinationFiles != null && destinationFolder != null) {
69                                         Log.LogError ("You must specify only one attribute from DestinationFiles and DestinationFolder");
70                                         return false;
71                                 }
72
73                                 if (destinationFiles != null && destinationFiles.Length > 0) {
74                                         for (int i = 0; i < sourceFiles.Length; i ++) {
75                                                 ITaskItem sourceItem = sourceFiles [i];
76                                                 ITaskItem destinationItem = destinationFiles [i];
77                                                 string sourceFile = sourceItem.GetMetadata ("FullPath");
78                                                 string destinationFile = destinationItem.GetMetadata ("FullPath");
79
80                                                 if (!File.Exists (sourceFile)) {
81                                                         Log.LogError ("Cannot copy {0} to {1}, as the source file doesn't exist.", sourceFile, destinationFile);
82                                                         continue;
83                                                 }
84
85                                                 if (!skipUnchangedFiles || HasFileChanged (sourceFile, destinationFile))
86                                                         CopyFileWithRetries (sourceFile, destinationFile, true);
87
88                                                 sourceItem.CopyMetadataTo (destinationItem);
89                                                 temporaryCopiedFiles.Add (destinationItem);
90                                         }
91                                         
92                                 } else if (destinationFolder != null) {
93                                         List<ITaskItem> temporaryDestinationFiles = new List<ITaskItem> ();
94                                         string destinationDirectory = destinationFolder.GetMetadata ("FullPath");
95                                         bool directoryCreated = CreateDirectoryIfRequired (destinationDirectory);
96                                         
97                                         foreach (ITaskItem sourceItem in sourceFiles) {
98                                                 string sourceFile = sourceItem.GetMetadata ("FullPath");
99                                                 string filename = sourceItem.GetMetadata ("Filename") + sourceItem.GetMetadata ("Extension");
100                                                 string destinationFile = Path.Combine (destinationDirectory,filename);
101
102                                                 if (!File.Exists (sourceFile)) {
103                                                         Log.LogError ("Cannot copy {0} to {1}, as the source file doesn't exist.", sourceFile, destinationFile);
104                                                         continue;
105                                                 }
106
107                                                 if (!skipUnchangedFiles || directoryCreated ||
108                                                         HasFileChanged (sourceFile, destinationFile))
109                                                         CopyFileWithRetries (sourceFile, destinationFile, false);
110
111                                                 temporaryCopiedFiles.Add (new TaskItem (
112                                                                 Path.Combine (destinationFolder.GetMetadata ("Identity"), filename),
113                                                                 sourceItem.CloneCustomMetadata ()));
114
115                                                 temporaryDestinationFiles.Add (new TaskItem (
116                                                                 Path.Combine (destinationFolder.GetMetadata ("Identity"), filename),
117                                                                 sourceItem.CloneCustomMetadata ()));
118                                         }
119                                         destinationFiles = temporaryDestinationFiles.ToArray ();
120                                 } else {
121                                         Log.LogError ("You must specify DestinationFolder or DestinationFiles attribute.");
122                                         return false;
123                                 }
124                                 
125                                 copiedFiles = temporaryCopiedFiles.ToArray ();
126
127                                 return !Log.HasLoggedErrors;
128                         }
129                         catch (Exception ex) {
130                                 Log.LogErrorFromException (ex);
131                                 return false;
132                         }
133                 }
134
135                 [Output]
136                 public ITaskItem[] CopiedFiles {
137                         get {
138                                 return copiedFiles;
139                         }
140                 }
141
142                 [Output]
143                 public ITaskItem[] DestinationFiles {
144                         get {
145                                 return destinationFiles;
146                         }
147                         set {
148                                 destinationFiles = value;
149                         }
150                 }
151
152                 public ITaskItem DestinationFolder {
153                         get {
154                                 return destinationFolder;
155                         }
156                         set {
157                                 destinationFolder = value;
158                         }
159                 }
160
161                 public bool SkipUnchangedFiles {
162                         get {
163                                 return skipUnchangedFiles;
164                         }
165                         set {
166                                 skipUnchangedFiles = value;
167                         }
168                 }
169
170                 public bool OverwriteReadOnlyFiles {
171                         get {
172                                 return overwriteReadOnlyFiles;
173                         }
174                         set {
175                                 overwriteReadOnlyFiles = value;
176                         }
177                 }
178
179                 public int Retries {
180                         get {
181                                 return retries;
182                         }
183                         set {
184                                 retries = value;
185                         }
186                 }
187
188                 public int RetryDelayMilliseconds {
189                         get {
190                                 return retryDelayMilliseconds;
191                         }
192                         set {
193                                 retryDelayMilliseconds = value;
194                         }
195                 }
196
197                 [MonoTODO ("Not implemented yet.")]
198                 public bool UseHardlinksIfPossible {
199                         get {
200                                 return useHardlinksIfPossible;
201                         }
202                         set {
203                                 useHardlinksIfPossible = value;
204                         }
205                 }
206
207                 [Required]
208                 public ITaskItem[] SourceFiles {
209                         get {
210                                 return sourceFiles;
211                         }
212                         set {
213                                 sourceFiles = value;
214                         }
215                 }
216
217                 // returns whether directory was created or not
218                 bool CreateDirectoryIfRequired (string name)
219                 {
220                         if (Directory.Exists (name))
221                                 return false;
222
223                         Log.LogMessage ("Creating directory '{0}'", name);
224                         Directory.CreateDirectory (name);
225                         return true;
226                 }
227
228                 void CopyFileWithRetries (string source, string dest, bool create_dir)
229                 {
230                         for (int i = retries; i >= 0; i--) {
231                                 try {
232                                         CopyFile (source, dest, create_dir);
233                                 }
234                                 catch (Exception ex) {
235                                         Log.LogMessage ("Copying failed. Retries left: {0}.", i);
236
237                                         if (i == 0)
238                                                 throw;
239
240                                         Thread.Sleep (retryDelayMilliseconds);
241                                 }
242                         }
243                 }
244
245                 void CopyFile (string source, string dest, bool create_dir)
246                 {
247                         if (create_dir)
248                                 CreateDirectoryIfRequired (Path.GetDirectoryName (dest));
249                         if (overwriteReadOnlyFiles)
250                                 ClearReadOnlyAttribute (dest);
251                         Log.LogMessage ("Copying file from '{0}' to '{1}'", source, dest);
252                         if (String.Compare (source, dest) != 0) {
253                                 // Ensure that we delete the destination file first so that if the file is already
254                                 // opened via mmap we do not screw up the data for the process which has the file open
255                                 // Fixes https://bugzilla.xamarin.com/show_bug.cgi?id=9146
256                                 if (!HasReadOnlyAttribute (dest))
257                                         File.Delete (dest);
258                                 File.Copy (source, dest, true);
259                         }
260                         ClearReadOnlyAttribute (dest);
261                 }
262
263                 void ClearReadOnlyAttribute (string name)
264                 {
265                         if (File.Exists (name) && ((File.GetAttributes (name) & FileAttributes.ReadOnly) == FileAttributes.ReadOnly))
266                                 File.SetAttributes (name, FileAttributes.Normal);
267                 }
268
269                 bool HasReadOnlyAttribute (string name)
270                 {
271                         return File.Exists (name) && (File.GetAttributes (name) & FileAttributes.ReadOnly) == FileAttributes.ReadOnly;
272                 }
273
274                 bool HasFileChanged (string source, string dest)
275                 {
276                         if (!File.Exists (dest))
277                                 return true;
278
279                         FileInfo sourceInfo = new FileInfo (source);
280                         FileInfo destinationInfo = new FileInfo (dest);
281
282                         return !(sourceInfo.Length == destinationInfo.Length &&
283                                         File.GetLastWriteTime (source) <= File.GetLastWriteTime (dest));
284                 }
285
286         }
287 }