Unbreak System.Web tests in the 4.5 profile
[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 #if NET_4_5
51                 static string framework_version = "4.5";
52                 static string profile_path = "net_4_5";
53 #elif NET_4_0
54                 static string framework_version = "4.0";
55                 static string profile_path = "net_4_0";
56 #else
57                 static string framework_version = "2.0";
58                 static string profile_path = "net_2_0";
59 #endif
60                 CompilationSection config;
61                 CompilerInfo ci;
62                 CodeDomProvider _provider;
63                 string baseAssemblyPath;
64                 string baseAssemblyDirectory;
65                 string canonicAssemblyName;
66                 Assembly mainAssembly;
67                 AppResourcesCompiler appResourcesCompiler;
68                 
69                 public CodeDomProvider Provider {
70                         get {
71                                 if (_provider == null)
72                                         _provider = ci.CreateProvider ();
73                                 else
74                                         return _provider;
75                                 
76                                 if (_provider == null)
77                                         throw new ApplicationException ("Failed to instantiate the default compiler.");
78                                 return _provider;
79                         }
80                 }
81                 
82                 public Assembly MainAssembly {
83                         get { return mainAssembly; }
84                 }
85                 
86                 public AppResourcesAssemblyBuilder (string canonicAssemblyName, string baseAssemblyPath, AppResourcesCompiler appres)
87                 {
88                         this.appResourcesCompiler = appres;
89                         this.baseAssemblyPath = baseAssemblyPath;
90                         this.baseAssemblyDirectory = Path.GetDirectoryName (baseAssemblyPath);
91                         this.canonicAssemblyName = canonicAssemblyName;
92                         
93                         config = WebConfigurationManager.GetWebApplicationSection ("system.web/compilation") as CompilationSection;
94                         if (config == null || !CodeDomProvider.IsDefinedLanguage (config.DefaultLanguage))
95                                 throw new ApplicationException ("Could not get the default compiler.");
96                         ci = CodeDomProvider.GetCompilerInfo (config.DefaultLanguage);
97                         if (ci == null || !ci.IsCodeDomProviderTypeValid)
98                                 throw new ApplicationException ("Failed to obtain the default compiler information.");
99                 }
100
101                 public void Build ()
102                 {
103                         Build (null);
104                 }
105                 
106                 public void Build (CodeCompileUnit unit)
107                 {
108                         Dictionary <string, List <string>> cultures = appResourcesCompiler.CultureFiles;
109                         List <string> defaultCultureFiles = appResourcesCompiler.DefaultCultureFiles;
110                         
111                         if (defaultCultureFiles != null)
112                                 BuildDefaultAssembly (defaultCultureFiles, unit);
113                         
114                         foreach (KeyValuePair <string, List <string>> kvp in cultures)
115                                 BuildSatelliteAssembly (kvp.Key, kvp.Value);
116                 }
117
118                 void BuildDefaultAssembly (List <string> files, CodeCompileUnit unit)
119                 {
120                         AssemblyBuilder abuilder = new AssemblyBuilder (Provider);
121                         if (unit != null)
122                                 abuilder.AddCodeCompileUnit (unit);
123                         
124                         CompilerParameters cp = ci.CreateDefaultCompilerParameters ();
125                         cp.OutputAssembly = baseAssemblyPath;
126                         cp.GenerateExecutable = false;
127                         cp.TreatWarningsAsErrors = true;
128                         cp.IncludeDebugInformation = config.Debug;
129
130                         foreach (string f in files)
131                                 cp.EmbeddedResources.Add (f);
132                         
133                         CompilerResults results = abuilder.BuildAssembly (cp);
134                         if (results == null)
135                                 return;
136                         
137                         if (results.NativeCompilerReturnValue == 0) {
138                                 mainAssembly = results.CompiledAssembly;
139                                 BuildManager.TopLevelAssemblies.Add (mainAssembly);
140                         } else {
141                                 if (HttpContext.Current.IsCustomErrorEnabled)
142                                         throw new ApplicationException ("An error occurred while compiling global resources.");
143                                 throw new CompilationException (null, results.Errors, null);
144                         }
145                         
146                         HttpRuntime.WritePreservationFile (mainAssembly, canonicAssemblyName);
147                         HttpRuntime.EnableAssemblyMapping (true);
148                 }
149
150                 void BuildSatelliteAssembly (string cultureName, List <string> files)
151                 {
152                         string assemblyPath = BuildAssemblyPath (cultureName);
153                         var info = new ProcessStartInfo ();
154                         var al = new Process ();
155
156                         string arguments = SetAlPath (info);
157                         var sb = new StringBuilder (arguments);
158
159                         sb.Append ("/c:\"" + cultureName + "\" ");
160                         sb.Append ("/t:lib ");
161                         sb.Append ("/out:\"" + assemblyPath + "\" ");
162                         if (mainAssembly != null)
163                                 sb.Append ("/template:\"" + mainAssembly.Location + "\" ");
164                         
165                         string responseFilePath = assemblyPath + ".response";
166                         using (FileStream fs = File.OpenWrite (responseFilePath)) {
167                                 using (StreamWriter sw = new StreamWriter (fs)) {
168                                         foreach (string f in files) 
169                                                 sw.WriteLine ("/embed:\"" + f + "\" ");
170                                 }
171                         }
172                         sb.Append ("@\"" + responseFilePath + "\"");
173                         
174                         info.Arguments = sb.ToString ();
175                         info.CreateNoWindow = true;
176                         info.UseShellExecute = false;
177                         info.RedirectStandardOutput = true;
178                         info.RedirectStandardError = true;
179                         
180                         al.StartInfo = info;
181
182                         var alOutput = new StringCollection ();
183                         var alMutex = new Mutex ();
184                         DataReceivedEventHandler outputHandler = (object sender, DataReceivedEventArgs args) => {
185                                 if (args.Data != null) {
186                                         alMutex.WaitOne ();
187                                         alOutput.Add (args.Data);
188                                         alMutex.ReleaseMutex ();
189                                 }
190                         };
191                         
192                         al.ErrorDataReceived += outputHandler;
193                         al.OutputDataReceived += outputHandler;
194
195                         // TODO: consider using asynchronous processes
196                         try {
197                                 al.Start ();
198                         } catch (Exception ex) {
199                                 throw new HttpException (String.Format ("Error running {0}", al.StartInfo.FileName), ex);
200                         }
201
202                         Exception alException = null;
203                         int exitCode = 0;
204                         try {
205                                 al.BeginOutputReadLine ();
206                                 al.BeginErrorReadLine ();
207                                 al.WaitForExit ();
208                                 exitCode = al.ExitCode;
209                         } catch (Exception ex) {
210                                 alException = ex;
211                         } finally {
212                                 al.CancelErrorRead ();
213                                 al.CancelOutputRead ();
214                                 al.Close ();
215                         }
216
217                         if (exitCode != 0 || alException != null) {
218                                 // TODO: consider adding a new type of compilation exception,
219                                 // tailored for al
220                                 CompilerErrorCollection errors = null;
221                                 
222                                 if (alOutput.Count != 0) {
223                                         foreach (string line in alOutput) {
224                                                 if (!line.StartsWith ("ALINK: error ", StringComparison.Ordinal))
225                                                         continue;
226                                                 if (errors == null)
227                                                         errors = new CompilerErrorCollection ();
228
229                                                 int colon = line.IndexOf (':', 13);
230                                                 string errorNumber = colon != -1 ? line.Substring (13, colon - 13) : "Unknown";
231                                                 string errorText = colon != -1 ? line.Substring (colon + 1) : line.Substring (13);
232                                                 
233                                                 errors.Add (new CompilerError (Path.GetFileName (assemblyPath), 0, 0, errorNumber, errorText));
234                                         }
235                                 }
236                                 
237                                 throw new CompilationException (Path.GetFileName (assemblyPath), errors, null);
238                         }
239                 }
240
241                 string SetAlPath (ProcessStartInfo info)
242                 {                       
243                         if (RuntimeHelpers.RunningOnWindows) {
244                                 string alPath;
245                                 string monoPath;
246                                 PropertyInfo gac = typeof (Environment).GetProperty ("GacPath", BindingFlags.Static|BindingFlags.NonPublic);
247                                 MethodInfo get_gac = gac.GetGetMethod (true);
248                                 string p = Path.GetDirectoryName ((string) get_gac.Invoke (null, null));
249                                 monoPath = Path.Combine (Path.GetDirectoryName (Path.GetDirectoryName (p)), "bin\\mono.bat");
250                                 if (!File.Exists (monoPath)) {
251                                         monoPath = Path.Combine (Path.GetDirectoryName (Path.GetDirectoryName (p)), "bin\\mono.exe");
252                                         if (!File.Exists (monoPath)) {
253                                                 monoPath = Path.Combine (Path.GetDirectoryName (Path.GetDirectoryName (Path.GetDirectoryName (p))), "mono\\mono\\mini\\mono.exe");
254                                                 if (!File.Exists (monoPath))
255                                                         throw new FileNotFoundException ("Windows mono path not found: " + monoPath);
256                                         }
257                                 }
258                                 alPath = Path.Combine (p, framework_version + "\\al.exe");
259                                 
260                                 if (!File.Exists (alPath)) {
261                                         alPath = Path.Combine (Path.GetDirectoryName (p), "lib\\" + profile_path + "\\al.exe");
262                                         if (!File.Exists (alPath))
263                                                 throw new FileNotFoundException ("Windows al path not found: " + alPath);
264                                 }
265
266                                 info.FileName = monoPath;
267                                 return alPath + " ";
268                         } else {
269 #if NET_4_0
270                                 info.FileName = "al";
271 #else
272                                 info.FileName = "al2";
273 #endif
274                                 return String.Empty;
275                         }
276                 }
277                 
278                 string BuildAssemblyPath (string cultureName)
279                 {
280                         string baseDir = Path.Combine (baseAssemblyDirectory, cultureName);
281                         if (!Directory.Exists (baseDir))
282                                 Directory.CreateDirectory (baseDir);
283                         
284                         string baseFileName = Path.GetFileNameWithoutExtension (baseAssemblyPath);
285                         string fileName = String.Concat (baseFileName, ".resources.dll");
286                         fileName = Path.Combine (baseDir, fileName);
287
288                         return fileName;
289                 }
290
291                 CodeCompileUnit GenerateAssemblyInfo (string cultureName)
292                 {
293                         CodeAttributeArgument[] args = new CodeAttributeArgument [1];
294                         args [0] = new CodeAttributeArgument (new CodePrimitiveExpression (cultureName));
295
296                         CodeCompileUnit unit = new CodeCompileUnit ();
297                         unit.AssemblyCustomAttributes.Add (
298                                 new CodeAttributeDeclaration (
299                                         new CodeTypeReference ("System.Reflection.AssemblyCultureAttribute"),
300                                         args));
301
302                         args = new CodeAttributeArgument [2];
303                         args [0] = new CodeAttributeArgument (new CodePrimitiveExpression ("ASP.NET"));
304                         args [1] = new CodeAttributeArgument (new CodePrimitiveExpression (Environment.Version.ToString ()));
305                         unit.AssemblyCustomAttributes.Add (
306                                 new CodeAttributeDeclaration (
307                                         new CodeTypeReference ("System.CodeDom.Compiler.GeneratedCodeAttribute"),
308                                         args));
309                         
310                         return unit;
311                 }
312         }
313 }
314