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