Merge pull request #1936 from esdrubal/DotNetRelativeOrAbsolute
[mono.git] / mcs / class / System.Web / System.Web.Compilation / AppResourcesAssemblyBuilder.cs
1 //
2 // System.Web.Compilation.AppResourceAseemblyBuilder
3 //
4 // Authors:
5 //   Marek Habersack <grendel@twistedcode.net>
6 //
7 // (C) 2007-2009 Novell, Inc (http://novell.com/)
8 // (C) 2011 Xamarin, Inc (http://xamarin.com/)
9
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30
31 using System;
32 using System.CodeDom;
33 using System.CodeDom.Compiler;
34 using System.Collections.Generic;
35 using System.Collections.Specialized;
36 using System.ComponentModel;
37 using System.Diagnostics;
38 using System.IO;
39 using System.Reflection;
40 using System.Text;
41 using System.Threading;
42 using System.Web;
43 using System.Web.Configuration;
44 using System.Web.Util;
45
46 namespace System.Web.Compilation
47 {
48         class AppResourcesAssemblyBuilder
49         {
50                 static string framework_version = "4.5";
51                 static string profile_path = "net_4_x";
52                 CompilationSection config;
53                 CompilerInfo ci;
54                 CodeDomProvider _provider;
55                 string baseAssemblyPath;
56                 string baseAssemblyDirectory;
57                 string canonicAssemblyName;
58                 Assembly mainAssembly;
59                 AppResourcesCompiler appResourcesCompiler;
60                 
61                 public CodeDomProvider Provider {
62                         get {
63                                 if (_provider == null)
64                                         _provider = ci.CreateProvider ();
65                                 else
66                                         return _provider;
67                                 
68                                 if (_provider == null)
69                                         throw new ApplicationException ("Failed to instantiate the default compiler.");
70                                 return _provider;
71                         }
72                 }
73                 
74                 public Assembly MainAssembly {
75                         get { return mainAssembly; }
76                 }
77                 
78                 public AppResourcesAssemblyBuilder (string canonicAssemblyName, string baseAssemblyPath, AppResourcesCompiler appres)
79                 {
80                         this.appResourcesCompiler = appres;
81                         this.baseAssemblyPath = baseAssemblyPath;
82                         this.baseAssemblyDirectory = Path.GetDirectoryName (baseAssemblyPath);
83                         this.canonicAssemblyName = canonicAssemblyName;
84                         
85                         config = WebConfigurationManager.GetWebApplicationSection ("system.web/compilation") as CompilationSection;
86                         if (config == null || !CodeDomProvider.IsDefinedLanguage (config.DefaultLanguage))
87                                 throw new ApplicationException ("Could not get the default compiler.");
88                         ci = CodeDomProvider.GetCompilerInfo (config.DefaultLanguage);
89                         if (ci == null || !ci.IsCodeDomProviderTypeValid)
90                                 throw new ApplicationException ("Failed to obtain the default compiler information.");
91                 }
92
93                 public void Build ()
94                 {
95                         Build (null);
96                 }
97                 
98                 public void Build (CodeCompileUnit unit)
99                 {
100                         Dictionary <string, List <string>> cultures = appResourcesCompiler.CultureFiles;
101                         List <string> defaultCultureFiles = appResourcesCompiler.DefaultCultureFiles;
102                         
103                         if (defaultCultureFiles != null)
104                                 BuildDefaultAssembly (defaultCultureFiles, unit);
105                         
106                         foreach (KeyValuePair <string, List <string>> kvp in cultures)
107                                 BuildSatelliteAssembly (kvp.Key, kvp.Value);
108                 }
109
110                 void BuildDefaultAssembly (List <string> files, CodeCompileUnit unit)
111                 {
112                         AssemblyBuilder abuilder = new AssemblyBuilder (Provider);
113                         if (unit != null)
114                                 abuilder.AddCodeCompileUnit (unit);
115                         
116                         CompilerParameters cp = ci.CreateDefaultCompilerParameters ();
117                         cp.OutputAssembly = baseAssemblyPath;
118                         cp.GenerateExecutable = false;
119                         cp.TreatWarningsAsErrors = true;
120                         cp.IncludeDebugInformation = config.Debug;
121
122                         foreach (string f in files)
123                                 cp.EmbeddedResources.Add (f);
124                         
125                         CompilerResults results = abuilder.BuildAssembly (cp);
126                         if (results == null)
127                                 return;
128                         
129                         if (results.NativeCompilerReturnValue == 0) {
130                                 mainAssembly = results.CompiledAssembly;
131                                 BuildManager.TopLevelAssemblies.Add (mainAssembly);
132                         } else {
133                                 if (HttpContext.Current.IsCustomErrorEnabled)
134                                         throw new ApplicationException ("An error occurred while compiling global resources.");
135                                 throw new CompilationException (null, results.Errors, null);
136                         }
137                         
138                         HttpRuntime.WritePreservationFile (mainAssembly, canonicAssemblyName);
139                         HttpRuntime.EnableAssemblyMapping (true);
140                 }
141
142                 void BuildSatelliteAssembly (string cultureName, List <string> files)
143                 {
144                         string assemblyPath = BuildAssemblyPath (cultureName);
145                         var info = new ProcessStartInfo ();
146                         var al = new Process ();
147
148                         string arguments = SetAlPath (info);
149                         var sb = new StringBuilder (arguments);
150
151                         sb.Append ("/c:\"" + cultureName + "\" ");
152                         sb.Append ("/t:lib ");
153                         sb.Append ("/out:\"" + assemblyPath + "\" ");
154                         if (mainAssembly != null)
155                                 sb.Append ("/template:\"" + mainAssembly.Location + "\" ");
156                         
157                         string responseFilePath = assemblyPath + ".response";
158                         using (FileStream fs = File.OpenWrite (responseFilePath)) {
159                                 using (StreamWriter sw = new StreamWriter (fs)) {
160                                         foreach (string f in files) 
161                                                 sw.WriteLine ("/embed:\"" + f + "\" ");
162                                 }
163                         }
164                         sb.Append ("@\"" + responseFilePath + "\"");
165                         
166                         info.Arguments = sb.ToString ();
167                         info.CreateNoWindow = true;
168                         info.UseShellExecute = false;
169                         info.RedirectStandardOutput = true;
170                         info.RedirectStandardError = true;
171                         
172                         al.StartInfo = info;
173
174                         var alOutput = new StringCollection ();
175                         var alMutex = new Mutex ();
176                         DataReceivedEventHandler outputHandler = (object sender, DataReceivedEventArgs args) => {
177                                 if (args.Data != null) {
178                                         alMutex.WaitOne ();
179                                         alOutput.Add (args.Data);
180                                         alMutex.ReleaseMutex ();
181                                 }
182                         };
183                         
184                         al.ErrorDataReceived += outputHandler;
185                         al.OutputDataReceived += outputHandler;
186
187                         // TODO: consider using asynchronous processes
188                         try {
189                                 al.Start ();
190                         } catch (Exception ex) {
191                                 throw new HttpException (String.Format ("Error running {0}", al.StartInfo.FileName), ex);
192                         }
193
194                         Exception alException = null;
195                         int exitCode = 0;
196                         try {
197                                 al.BeginOutputReadLine ();
198                                 al.BeginErrorReadLine ();
199                                 al.WaitForExit ();
200                                 exitCode = al.ExitCode;
201                         } catch (Exception ex) {
202                                 alException = ex;
203                         } finally {
204                                 al.CancelErrorRead ();
205                                 al.CancelOutputRead ();
206                                 al.Close ();
207                         }
208
209                         if (exitCode != 0 || alException != null) {
210                                 // TODO: consider adding a new type of compilation exception,
211                                 // tailored for al
212                                 CompilerErrorCollection errors = null;
213                                 
214                                 if (alOutput.Count != 0) {
215                                         foreach (string line in alOutput) {
216                                                 if (!line.StartsWith ("ALINK: error ", StringComparison.Ordinal))
217                                                         continue;
218                                                 if (errors == null)
219                                                         errors = new CompilerErrorCollection ();
220
221                                                 int colon = line.IndexOf (':', 13);
222                                                 string errorNumber = colon != -1 ? line.Substring (13, colon - 13) : "Unknown";
223                                                 string errorText = colon != -1 ? line.Substring (colon + 1) : line.Substring (13);
224                                                 
225                                                 errors.Add (new CompilerError (Path.GetFileName (assemblyPath), 0, 0, errorNumber, errorText));
226                                         }
227                                 }
228                                 
229                                 throw new CompilationException (Path.GetFileName (assemblyPath), errors, null);
230                         }
231                 }
232
233                 string SetAlPath (ProcessStartInfo info)
234                 {                       
235                         if (RuntimeHelpers.RunningOnWindows) {
236                                 string alPath;
237                                 string monoPath;
238                                 PropertyInfo gac = typeof (Environment).GetProperty ("GacPath", BindingFlags.Static|BindingFlags.NonPublic);
239                                 MethodInfo get_gac = gac.GetGetMethod (true);
240                                 string p = Path.GetDirectoryName ((string) get_gac.Invoke (null, null));
241                                 monoPath = Path.Combine (Path.GetDirectoryName (Path.GetDirectoryName (p)), "bin\\mono.bat");
242                                 if (!File.Exists (monoPath)) {
243                                         monoPath = Path.Combine (Path.GetDirectoryName (Path.GetDirectoryName (p)), "bin\\mono.exe");
244                                         if (!File.Exists (monoPath)) {
245                                                 monoPath = Path.Combine (Path.GetDirectoryName (Path.GetDirectoryName (Path.GetDirectoryName (p))), "mono\\mono\\mini\\mono.exe");
246                                                 if (!File.Exists (monoPath))
247                                                         throw new FileNotFoundException ("Windows mono path not found: " + monoPath);
248                                         }
249                                 }
250                                 alPath = Path.Combine (p, framework_version + "\\al.exe");
251                                 
252                                 if (!File.Exists (alPath)) {
253                                         alPath = Path.Combine (Path.GetDirectoryName (p), "lib\\" + profile_path + "\\al.exe");
254                                         if (!File.Exists (alPath))
255                                                 throw new FileNotFoundException ("Windows al path not found: " + alPath);
256                                 }
257
258                                 info.FileName = monoPath;
259                                 return alPath + " ";
260                         } else {
261                                 info.FileName = "al";
262                                 return String.Empty;
263                         }
264                 }
265                 
266                 string BuildAssemblyPath (string cultureName)
267                 {
268                         string baseDir = Path.Combine (baseAssemblyDirectory, cultureName);
269                         if (!Directory.Exists (baseDir))
270                                 Directory.CreateDirectory (baseDir);
271                         
272                         string baseFileName = Path.GetFileNameWithoutExtension (baseAssemblyPath);
273                         string fileName = String.Concat (baseFileName, ".resources.dll");
274                         fileName = Path.Combine (baseDir, fileName);
275
276                         return fileName;
277                 }
278
279                 CodeCompileUnit GenerateAssemblyInfo (string cultureName)
280                 {
281                         CodeAttributeArgument[] args = new CodeAttributeArgument [1];
282                         args [0] = new CodeAttributeArgument (new CodePrimitiveExpression (cultureName));
283
284                         CodeCompileUnit unit = new CodeCompileUnit ();
285                         unit.AssemblyCustomAttributes.Add (
286                                 new CodeAttributeDeclaration (
287                                         new CodeTypeReference ("System.Reflection.AssemblyCultureAttribute"),
288                                         args));
289
290                         args = new CodeAttributeArgument [2];
291                         args [0] = new CodeAttributeArgument (new CodePrimitiveExpression ("ASP.NET"));
292                         args [1] = new CodeAttributeArgument (new CodePrimitiveExpression (Environment.Version.ToString ()));
293                         unit.AssemblyCustomAttributes.Add (
294                                 new CodeAttributeDeclaration (
295                                         new CodeTypeReference ("System.CodeDom.Compiler.GeneratedCodeAttribute"),
296                                         args));
297                         
298                         return unit;
299                 }
300         }
301 }
302