Merge pull request #1155 from steffen-kiess/json-string
[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
34 using System;
35 using System.Text;
36 using System.IO;
37 using System.Collections;
38 using System.Collections.Generic;
39 using System.Resources;
40 using System.Reflection;
41 using System.Xml;
42 using Microsoft.Build.Framework;
43 using Microsoft.Build.Utilities;
44 using Mono.XBuild.Tasks.GenerateResourceInternal;
45 using Mono.XBuild.Utilities;
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 = source.GetMetadata ("AutoGen").Equals ("true", StringComparison.OrdinalIgnoreCase) ?
79                                                 source.ItemSpec.Replace ('\\', '.').Replace ('/', '.') :
80                                                 Path.ChangeExtension (sourceFile, "resources");
81
82                                         if (IsResgenRequired (sourceFile, outputFile))
83                                                 result &= CompileResourceFile (sourceFile, outputFile);
84
85                                         ITaskItem newItem = new TaskItem (source);
86                                         newItem.ItemSpec = outputFile;
87
88                                         temporaryFilesWritten.Add (newItem);
89                                 }
90                         } else {
91                                 if (sources.Length != outputResources.Length) {
92                                         Log.LogError ("Sources count is different than OutputResources count.");
93                                         return false;
94                                 }
95
96                                 for (int i = 0; i < sources.Length; i ++) {
97                                         if (String.IsNullOrEmpty (outputResources [i].ItemSpec)) {
98                                                 Log.LogError ("Filename of output can not be empty.");
99                                                 result = false;
100                                                 continue;
101                                         }
102
103                                         if (IsResgenRequired (sources [i].ItemSpec, outputResources [i].ItemSpec))
104                                                 result &= CompileResourceFile (sources [i].ItemSpec, outputResources [i].ItemSpec);
105                                         temporaryFilesWritten.Add (outputResources [i]);
106                                 }
107                         }
108                         
109                         filesWritten = temporaryFilesWritten.ToArray ();
110
111                         return result;
112                 }
113                 
114                 // true if the resx file or any file referenced
115                 // by the resx is newer than the .resources file
116                 //
117                 // Code taken from monodevelop
118                 // main/src/core/MonoDevelop.Core/MonoDevelop.Projects.Formats.MD1/MD1DotNetProjectHandler.cs
119                 bool IsResgenRequired (string resx_filename, string resources_filename)
120                 {
121                         if (IsFileNewerThan (resx_filename, resources_filename)) {
122                                 Log.LogMessage (MessageImportance.Low,
123                                                 "Resource file '{0}' is newer than the source file '{1}', skipping.",
124                                                 resources_filename, resx_filename);
125                                 return true;
126                         }
127
128                         if (String.Compare (Path.GetExtension (resx_filename), ".resx", true) != 0)
129                                 return true;
130
131                         // resx file, check for files referenced from there
132                         XmlTextReader xr = null;
133                         try {
134                                 // look for
135                                 // <data type="System.Resources.ResXFileRef, System.Windows.Forms" ..>
136                                 //   <value>... filename;.. </value>
137                                 // </data>
138                                 xr = new XmlTextReader (resx_filename);
139                                 string basepath = Path.GetDirectoryName (resx_filename);
140                                 while (xr.Read ()) {
141                                         if (xr.NodeType != XmlNodeType.Element ||
142                                                 String.Compare (xr.LocalName, "data") != 0)
143                                                 continue;
144
145                                         string type = xr.GetAttribute ("type");
146                                         if (String.IsNullOrEmpty (type))
147                                                 continue;
148
149                                         if (String.Compare (type, "System.Resources.ResXFileRef, System.Windows.Forms") != 0)
150                                                 continue;
151
152                                         xr.ReadToDescendant ("value");
153                                         if (xr.NodeType != XmlNodeType.Element)
154                                                 continue;
155
156                                         string value = xr.ReadElementContentAsString ();
157
158                                         string [] parts = value.Split (';');
159                                         if (parts.Length > 0) {
160                                                 string referenced_filename = MSBuildUtils.FromMSBuildPath (
161                                                                 Path.Combine (basepath, parts [0]).Trim ());
162                                                 if (File.Exists (referenced_filename) &&
163                                                         IsFileNewerThan (referenced_filename, resources_filename))
164                                                         return true;
165                                         }
166                                 }
167                         } catch (XmlException) {
168                                 // Ignore xml errors, let resgen handle it
169                                 return true;
170                         } finally {
171                                 if (xr != null)
172                                         xr.Close ();
173                         }
174
175                         return false;
176                 }
177
178                 // true if first is newer than second
179                 static bool IsFileNewerThan (string first, string second)
180                 {
181                         FileInfo finfo_first = new FileInfo (first);
182                         FileInfo finfo_second = new FileInfo (second);
183                         return finfo_first.LastWriteTime > finfo_second.LastWriteTime;
184                 }
185
186 #if false
187                 private IResourceReader GetReader (Stream stream, string name)
188                 {
189                         string format = Path.GetExtension (name);
190                         switch (format.ToLowerInvariant ()) {
191                         case ".po":
192                                 return new PoResourceReader (stream);
193                         case ".txt":
194                         case ".text":
195                                 return new TxtResourceReader (stream);
196                         case ".resources":
197                                 return new ResourceReader (stream);
198                         case ".resx":
199                                 ResXResourceReader reader = new ResXResourceReader (stream);
200
201                                 // set correct basepath to resolve relative paths in file refs
202                                 if (useSourcePath)
203                                         reader.BasePath = Path.GetDirectoryName (Path.GetFullPath (name));
204
205                                 return reader;
206                         default:
207                                 throw new Exception ("Unknown format in file " + name);
208                         }
209                 }
210                 
211                 private IResourceWriter GetWriter (Stream stream, string name)
212                 {
213                         string format = Path.GetExtension (name);
214                         switch (format.ToLowerInvariant ()) {
215                         case ".po":
216                                 return new PoResourceWriter (stream);
217                         case ".txt":
218                         case ".text":
219                                 return new TxtResourceWriter (stream);
220                         case ".resources":
221                                 return new ResourceWriter (stream);
222                         case ".resx":
223                                 return new System.Resources.ResXResourceWriter (stream);
224                         default:
225                                 throw new Exception ("Unknown format in file " + name);
226                         }
227                 }
228 #endif
229                 
230                 private bool CompileResourceFile (string sname, string dname )
231                 {
232                         if (!File.Exists (sname)) {
233                                 Log.LogError ("Resource file '{0}' not found.", sname);
234                                 return false;
235                         }
236
237                         Resgen resgen = new Resgen ();
238                         resgen.BuildEngine = this.BuildEngine;
239                         resgen.UseSourcePath = true;
240
241                         resgen.SourceFile = sname;
242                         resgen.OutputFile = dname;
243
244                         return resgen.Execute ();
245                 }
246
247                 [Output]
248                 public ITaskItem[] FilesWritten {
249                         get {
250                                 return filesWritten;
251                         }
252                 }
253
254                 [MonoTODO]
255                 public bool NeverLockTypeAssemblies {
256                         get {
257                                 return neverLockTypeAssemblies;
258                         }
259                         set {
260                                 neverLockTypeAssemblies = value;
261                         }
262                 }
263
264                 [Output]
265                 public ITaskItem[] OutputResources {
266                         get {
267                                 return outputResources;
268                         }
269                         set {
270                                 outputResources = value;
271                         }
272                 }
273                 
274                 public bool PublicClass {
275                         get { return publicClass; }
276                         set { publicClass = value; }
277                 }
278
279                 public ITaskItem[] References {
280                         get {
281                                 return references;
282                         }
283                         set {
284                                 references = value;
285                         }
286                 }
287
288                 [Required]
289                 public ITaskItem[] Sources {
290                         get {
291                                 return sources;
292                         }
293                         set {
294                                 sources = value;
295                         }
296                 }
297
298                 public ITaskItem StateFile {
299                         get {
300                                 return stateFile;
301                         }
302                         set {
303                                 stateFile = value;
304                         }
305                 }
306
307                 [Output]
308                 public string StronglyTypedClassName {
309                         get {
310                                 return stronglyTypedClassName;
311                         }
312                         set {
313                                 stronglyTypedClassName = value;
314                         }
315                 }
316
317                 [Output]
318                 public string StronglyTypedFileName {
319                         get {
320                                 return stronglyTypedFilename;
321                         }
322                         set {
323                                 stronglyTypedFilename = value;
324                         }
325                 }
326
327                 public string StronglyTypedLanguage {
328                         get {
329                                 return stronglyTypedLanguage;
330                         }
331                         set {
332                                 stronglyTypedLanguage = value;
333                         }
334                 }
335
336                 public string StronglyTypedNamespace {
337                         get {
338                                 return stronglyTypedNamespace;
339                         }
340                         set {
341                                 stronglyTypedNamespace = value;
342                         }
343                 }
344
345                 public bool UseSourcePath {
346                         get {
347                                 return useSourcePath;
348                         }
349                         set {
350                                 useSourcePath = value;
351                         }
352                 }
353         }
354
355         class Resgen : ToolTaskExtension
356         {
357                 public Resgen ()
358                 {
359                 }
360
361                 protected internal override void AddCommandLineCommands (
362                                                  CommandLineBuilderExtension commandLine)
363                 {
364                         if (UseSourcePath)
365                                 commandLine.AppendSwitch ("/useSourcePath");
366
367                         commandLine.AppendSwitch (String.Format ("/compile \"{0}{1}\"", SourceFile,
368                                                 OutputFile != null ? "," + OutputFile : ""));
369                 }
370
371                 public override bool Execute ()
372                 {
373                         if (String.IsNullOrEmpty (Environment.GetEnvironmentVariable ("MONO_IOMAP")))
374                                 EnvironmentVariables = new string [] { "MONO_IOMAP=drive" };
375                         return base.Execute ();
376                 }
377
378                 protected override string GenerateFullPathToTool ()
379                 {
380                         if (!string.IsNullOrEmpty (ToolPath))
381                                 return Path.Combine (ToolPath, ToolExe);
382                         return ToolLocationHelper.GetPathToDotNetFrameworkFile (ToolExe, TargetDotNetFrameworkVersion.VersionLatest);
383                 }
384
385                 protected override MessageImportance StandardOutputLoggingImportance {
386                         get { return MessageImportance.Low; }
387                 }
388
389                 protected override string ToolName {
390                         get { return "resgen.exe"; }
391                 }
392
393                 public string SourceFile { get; set; }
394                 public string OutputFile { get; set; }
395
396                 public bool UseSourcePath { get; set; }
397         }
398 }
399