[xbuild] Use files referenced by resx for dependency check.
[mono.git] / mcs / class / Microsoft.Build.Tasks / Microsoft.Build.Tasks / GenerateResource.cs
1 //
2 // GenerateResource.cs: Task that generates the resources.
3 //
4 // Author:
5 //   Marek Sieradzki (marek.sieradzki@gmail.com)
6 //   Paolo Molaro (lupus@ximian.com)
7 //   Gonzalo Paniagua Javier (gonzalo@ximian.com)
8 //   Lluis Sanchez Gual <lluis@novell.com>
9 //   Ankit Jain <jankit@novell.com>
10 //
11 // (C) 2005 Marek Sieradzki
12 // Copyright 2010 Novell, Inc (http://www.novell.com)
13 //
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
21 //
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
24 //
25 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32
33 #if NET_2_0
34
35 using System;
36 using System.Text;
37 using System.IO;
38 using System.Collections;
39 using System.Collections.Generic;
40 using System.Resources;
41 using System.Reflection;
42 using System.Xml;
43 using Microsoft.Build.Framework;
44 using Microsoft.Build.Utilities;
45 using Mono.XBuild.Tasks.GenerateResourceInternal;
46
47 namespace Microsoft.Build.Tasks {
48         public sealed class GenerateResource : TaskExtension {
49         
50                 ITaskItem[]     filesWritten;
51                 bool            neverLockTypeAssemblies;
52                 ITaskItem[]     outputResources;
53                 bool            publicClass;
54                 ITaskItem[]     references;
55                 ITaskItem[]     sources;
56                 ITaskItem       stateFile;
57                 string          stronglyTypedClassName;
58                 string          stronglyTypedFilename;
59                 string          stronglyTypedLanguage;
60                 string          stronglyTypedNamespace;
61                 bool            useSourcePath;
62                 
63                 public GenerateResource ()
64                 {
65                         useSourcePath = false;
66                 }
67
68                 public override bool Execute ()
69                 {
70                         if (sources.Length == 0)
71                                 return true;
72
73                         bool result = true;
74                         List  <ITaskItem> temporaryFilesWritten = new List <ITaskItem> ();
75                         if (outputResources == null) {
76                                 foreach (ITaskItem source in sources) {
77                                         string sourceFile = source.ItemSpec;
78                                         string outputFile = Path.ChangeExtension (sourceFile, "resources");
79
80                                         if (IsResgenRequired (sourceFile, outputFile))
81                                                 result &= CompileResourceFile (sourceFile, outputFile);
82
83                                         ITaskItem newItem = new TaskItem (source);
84                                         source.ItemSpec = outputFile;
85
86                                         temporaryFilesWritten.Add (newItem);
87                                 }
88                         } else {
89                                 if (sources.Length != outputResources.Length) {
90                                         Log.LogError ("Sources count is different than OutputResources count.");
91                                         return false;
92                                 }
93
94                                 for (int i = 0; i < sources.Length; i ++) {
95                                         if (String.IsNullOrEmpty (outputResources [i].ItemSpec)) {
96                                                 Log.LogError ("Filename of output can not be empty.");
97                                                 result = false;
98                                                 continue;
99                                         }
100
101                                         if (IsResgenRequired (sources [i].ItemSpec, outputResources [i].ItemSpec))
102                                                 result &= CompileResourceFile (sources [i].ItemSpec, outputResources [i].ItemSpec);
103                                         temporaryFilesWritten.Add (outputResources [i]);
104                                 }
105                         }
106                         
107                         filesWritten = temporaryFilesWritten.ToArray ();
108
109                         return result;
110                 }
111                 
112                 // true if the resx file or any file referenced
113                 // by the resx is newer than the .resources file
114                 //
115                 // Code taken from monodevelop
116                 // main/src/core/MonoDevelop.Core/MonoDevelop.Projects.Formats.MD1/MD1DotNetProjectHandler.cs
117                 bool IsResgenRequired (string resx_filename, string resources_filename)
118                 {
119                         if (IsFileNewerThan (resx_filename, resources_filename)) {
120                                 Log.LogMessage (MessageImportance.Low,
121                                                 "Resource file '{0}' is newer than the source file '{1}', skipping.",
122                                                 resources_filename, resx_filename);
123                                 return true;
124                         }
125
126                         if (String.Compare (Path.GetExtension (resx_filename), ".resx", true) != 0)
127                                 return true;
128
129                         // resx file, check for files referenced from there
130                         XmlTextReader xr = null;
131                         try {
132                                 // look for
133                                 // <data type="System.Resources.ResXFileRef, System.Windows.Forms" ..>
134                                 //   <value>... filename;.. </value>
135                                 // </data>
136                                 xr = new XmlTextReader (resx_filename);
137                                 string basepath = Path.GetDirectoryName (resx_filename);
138                                 while (xr.Read ()) {
139                                         if (xr.NodeType != XmlNodeType.Element ||
140                                                 String.Compare (xr.LocalName, "data") != 0)
141                                                 continue;
142
143                                         string type = xr.GetAttribute ("type");
144                                         if (String.IsNullOrEmpty (type))
145                                                 continue;
146
147                                         if (String.Compare (type, "System.Resources.ResXFileRef, System.Windows.Forms") != 0)
148                                                 continue;
149
150                                         xr.ReadToDescendant ("value");
151                                         if (xr.NodeType != XmlNodeType.Element)
152                                                 continue;
153
154                                         string value = xr.ReadElementContentAsString ();
155
156                                         string [] parts = value.Split (';');
157                                         if (parts.Length > 0) {
158                                                 string referenced_filename = Utilities.FromMSBuildPath (
159                                                                 Path.Combine (basepath, parts [0]).Trim ());
160                                                 if (File.Exists (referenced_filename) &&
161                                                         IsFileNewerThan (referenced_filename, resources_filename))
162                                                         return true;
163                                         }
164                                 }
165                         } catch (XmlException) {
166                                 // Ignore xml errors, let resgen handle it
167                                 return true;
168                         } finally {
169                                 if (xr != null)
170                                         xr.Close ();
171                         }
172
173                         return false;
174                 }
175
176                 // true if first is newer than second
177                 static bool IsFileNewerThan (string first, string second)
178                 {
179                         FileInfo finfo_first = new FileInfo (first);
180                         FileInfo finfo_second = new FileInfo (second);
181                         return finfo_first.LastWriteTime > finfo_second.LastWriteTime;
182                 }
183
184 #if false
185                 private IResourceReader GetReader (Stream stream, string name)
186                 {
187                         string format = Path.GetExtension (name);
188                         switch (format.ToLower ()) {
189                         case ".po":
190                                 return new PoResourceReader (stream);
191                         case ".txt":
192                         case ".text":
193                                 return new TxtResourceReader (stream);
194                         case ".resources":
195                                 return new ResourceReader (stream);
196                         case ".resx":
197                                 ResXResourceReader reader = new ResXResourceReader (stream);
198
199                                 // set correct basepath to resolve relative paths in file refs
200                                 if (useSourcePath)
201                                         reader.BasePath = Path.GetDirectoryName (Path.GetFullPath (name));
202
203                                 return reader;
204                         default:
205                                 throw new Exception ("Unknown format in file " + name);
206                         }
207                 }
208                 
209                 private IResourceWriter GetWriter (Stream stream, string name)
210                 {
211                         string format = Path.GetExtension (name);
212                         switch (format.ToLower ()) {
213                         case ".po":
214                                 return new PoResourceWriter (stream);
215                         case ".txt":
216                         case ".text":
217                                 return new TxtResourceWriter (stream);
218                         case ".resources":
219                                 return new ResourceWriter (stream);
220                         case ".resx":
221                                 return new System.Resources.ResXResourceWriter (stream);
222                         default:
223                                 throw new Exception ("Unknown format in file " + name);
224                         }
225                 }
226 #endif
227                 
228                 private bool CompileResourceFile (string sname, string dname )
229                 {
230                         if (!File.Exists (sname)) {
231                                 Log.LogError ("Resource file '{0}' not found.", sname);
232                                 return false;
233                         }
234
235                         Resgen resgen = new Resgen ();
236                         resgen.BuildEngine = this.BuildEngine;
237                         resgen.UseSourcePath = true;
238
239                         resgen.SourceFile = sname;
240                         resgen.OutputFile = dname;
241
242                         return resgen.Execute ();
243                 }
244
245                 [Output]
246                 public ITaskItem[] FilesWritten {
247                         get {
248                                 return filesWritten;
249                         }
250                 }
251
252                 [MonoTODO]
253                 public bool NeverLockTypeAssemblies {
254                         get {
255                                 return neverLockTypeAssemblies;
256                         }
257                         set {
258                                 neverLockTypeAssemblies = value;
259                         }
260                 }
261
262                 [Output]
263                 public ITaskItem[] OutputResources {
264                         get {
265                                 return outputResources;
266                         }
267                         set {
268                                 outputResources = value;
269                         }
270                 }
271                 
272                 public bool PublicClass {
273                         get { return publicClass; }
274                         set { publicClass = value; }
275                 }
276
277                 public ITaskItem[] References {
278                         get {
279                                 return references;
280                         }
281                         set {
282                                 references = value;
283                         }
284                 }
285
286                 [Required]
287                 public ITaskItem[] Sources {
288                         get {
289                                 return sources;
290                         }
291                         set {
292                                 sources = value;
293                         }
294                 }
295
296                 public ITaskItem StateFile {
297                         get {
298                                 return stateFile;
299                         }
300                         set {
301                                 stateFile = value;
302                         }
303                 }
304
305                 [Output]
306                 public string StronglyTypedClassName {
307                         get {
308                                 return stronglyTypedClassName;
309                         }
310                         set {
311                                 stronglyTypedClassName = value;
312                         }
313                 }
314
315                 [Output]
316                 public string StronglyTypedFileName {
317                         get {
318                                 return stronglyTypedFilename;
319                         }
320                         set {
321                                 stronglyTypedFilename = value;
322                         }
323                 }
324
325                 public string StronglyTypedLanguage {
326                         get {
327                                 return stronglyTypedLanguage;
328                         }
329                         set {
330                                 stronglyTypedLanguage = value;
331                         }
332                 }
333
334                 public string StronglyTypedNamespace {
335                         get {
336                                 return stronglyTypedNamespace;
337                         }
338                         set {
339                                 stronglyTypedNamespace = value;
340                         }
341                 }
342
343                 public bool UseSourcePath {
344                         get {
345                                 return useSourcePath;
346                         }
347                         set {
348                                 useSourcePath = value;
349                         }
350                 }
351         }
352
353         class Resgen : ToolTaskExtension
354         {
355                 public Resgen ()
356                 {
357                 }
358
359                 protected internal override void AddCommandLineCommands (
360                                                  CommandLineBuilderExtension commandLine)
361                 {
362                         if (UseSourcePath)
363                                 commandLine.AppendSwitch ("/useSourcePath");
364
365                         commandLine.AppendSwitch (String.Format ("/compile \"{0}{1}\"", SourceFile,
366                                                 OutputFile != null ? "," + OutputFile : ""));
367                 }
368
369                 public override bool Execute ()
370                 {
371                         if (String.IsNullOrEmpty (Environment.GetEnvironmentVariable ("MONO_IOMAP")))
372                                 EnvironmentVariables = new string [] { "MONO_IOMAP=drive" };
373                         return base.Execute ();
374                 }
375
376                 protected override string GenerateFullPathToTool ()
377                 {
378                         return Path.Combine (ToolPath, ToolExe);
379                 }
380
381                 protected override MessageImportance StandardOutputLoggingImportance {
382                         get { return MessageImportance.Low; }
383                 }
384
385                 protected override string ToolName {
386                         get { return Utilities.RunningOnWindows ? "resgen2.bat" : "resgen2"; }
387                 }
388
389                 public string SourceFile { get; set; }
390                 public string OutputFile { get; set; }
391
392                 public bool UseSourcePath { get; set; }
393         }
394 }
395
396 #endif