2 // System.Web.Compilation.AppResourceAseemblyBuilder
5 // Marek Habersack <grendel@twistedcode.net>
7 // (C) 2007-2009 Novell, Inc (http://novell.com/)
8 // (C) 2011 Xamarin, Inc (http://xamarin.com/)
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:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
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.
33 using System.CodeDom.Compiler;
34 using System.Collections.Generic;
35 using System.Collections.Specialized;
36 using System.ComponentModel;
37 using System.Diagnostics;
39 using System.Reflection;
41 using System.Threading;
43 using System.Web.Configuration;
44 using System.Web.Util;
46 namespace System.Web.Compilation
48 class AppResourcesAssemblyBuilder
51 static string framework_version = "4.5";
52 static string profile_path = "net_4_5";
54 static string framework_version = "4.0";
55 static string profile_path = "net_4_0";
57 static string framework_version = "2.0";
58 static string profile_path = "net_2_0";
60 CompilationSection config;
62 CodeDomProvider _provider;
63 string baseAssemblyPath;
64 string baseAssemblyDirectory;
65 string canonicAssemblyName;
66 Assembly mainAssembly;
67 AppResourcesCompiler appResourcesCompiler;
69 public CodeDomProvider Provider {
71 if (_provider == null)
72 _provider = ci.CreateProvider ();
76 if (_provider == null)
77 throw new ApplicationException ("Failed to instantiate the default compiler.");
82 public Assembly MainAssembly {
83 get { return mainAssembly; }
86 public AppResourcesAssemblyBuilder (string canonicAssemblyName, string baseAssemblyPath, AppResourcesCompiler appres)
88 this.appResourcesCompiler = appres;
89 this.baseAssemblyPath = baseAssemblyPath;
90 this.baseAssemblyDirectory = Path.GetDirectoryName (baseAssemblyPath);
91 this.canonicAssemblyName = canonicAssemblyName;
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.");
106 public void Build (CodeCompileUnit unit)
108 Dictionary <string, List <string>> cultures = appResourcesCompiler.CultureFiles;
109 List <string> defaultCultureFiles = appResourcesCompiler.DefaultCultureFiles;
111 if (defaultCultureFiles != null)
112 BuildDefaultAssembly (defaultCultureFiles, unit);
114 foreach (KeyValuePair <string, List <string>> kvp in cultures)
115 BuildSatelliteAssembly (kvp.Key, kvp.Value);
118 void BuildDefaultAssembly (List <string> files, CodeCompileUnit unit)
120 AssemblyBuilder abuilder = new AssemblyBuilder (Provider);
122 abuilder.AddCodeCompileUnit (unit);
124 CompilerParameters cp = ci.CreateDefaultCompilerParameters ();
125 cp.OutputAssembly = baseAssemblyPath;
126 cp.GenerateExecutable = false;
127 cp.TreatWarningsAsErrors = true;
128 cp.IncludeDebugInformation = config.Debug;
130 foreach (string f in files)
131 cp.EmbeddedResources.Add (f);
133 CompilerResults results = abuilder.BuildAssembly (cp);
137 if (results.NativeCompilerReturnValue == 0) {
138 mainAssembly = results.CompiledAssembly;
139 BuildManager.TopLevelAssemblies.Add (mainAssembly);
141 if (HttpContext.Current.IsCustomErrorEnabled)
142 throw new ApplicationException ("An error occurred while compiling global resources.");
143 throw new CompilationException (null, results.Errors, null);
146 HttpRuntime.WritePreservationFile (mainAssembly, canonicAssemblyName);
147 HttpRuntime.EnableAssemblyMapping (true);
150 void BuildSatelliteAssembly (string cultureName, List <string> files)
152 string assemblyPath = BuildAssemblyPath (cultureName);
153 var info = new ProcessStartInfo ();
154 var al = new Process ();
156 string arguments = SetAlPath (info);
157 var sb = new StringBuilder (arguments);
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 + "\" ");
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 + "\" ");
172 sb.Append ("@\"" + responseFilePath + "\"");
174 info.Arguments = sb.ToString ();
175 info.CreateNoWindow = true;
176 info.UseShellExecute = false;
177 info.RedirectStandardOutput = true;
178 info.RedirectStandardError = true;
182 var alOutput = new StringCollection ();
183 var alMutex = new Mutex ();
184 DataReceivedEventHandler outputHandler = (object sender, DataReceivedEventArgs args) => {
185 if (args.Data != null) {
187 alOutput.Add (args.Data);
188 alMutex.ReleaseMutex ();
192 al.ErrorDataReceived += outputHandler;
193 al.OutputDataReceived += outputHandler;
195 // TODO: consider using asynchronous processes
198 } catch (Exception ex) {
199 throw new HttpException (String.Format ("Error running {0}", al.StartInfo.FileName), ex);
202 Exception alException = null;
205 al.BeginOutputReadLine ();
206 al.BeginErrorReadLine ();
208 exitCode = al.ExitCode;
209 } catch (Exception ex) {
212 al.CancelErrorRead ();
213 al.CancelOutputRead ();
217 if (exitCode != 0 || alException != null) {
218 // TODO: consider adding a new type of compilation exception,
220 CompilerErrorCollection errors = null;
222 if (alOutput.Count != 0) {
223 foreach (string line in alOutput) {
224 if (!line.StartsWith ("ALINK: error ", StringComparison.Ordinal))
227 errors = new CompilerErrorCollection ();
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);
233 errors.Add (new CompilerError (Path.GetFileName (assemblyPath), 0, 0, errorNumber, errorText));
237 throw new CompilationException (Path.GetFileName (assemblyPath), errors, null);
241 string SetAlPath (ProcessStartInfo info)
243 if (RuntimeHelpers.RunningOnWindows) {
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);
258 alPath = Path.Combine (p, framework_version + "\\al.exe");
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);
266 info.FileName = monoPath;
270 info.FileName = "al";
272 info.FileName = "al2";
278 string BuildAssemblyPath (string cultureName)
280 string baseDir = Path.Combine (baseAssemblyDirectory, cultureName);
281 if (!Directory.Exists (baseDir))
282 Directory.CreateDirectory (baseDir);
284 string baseFileName = Path.GetFileNameWithoutExtension (baseAssemblyPath);
285 string fileName = String.Concat (baseFileName, ".resources.dll");
286 fileName = Path.Combine (baseDir, fileName);
291 CodeCompileUnit GenerateAssemblyInfo (string cultureName)
293 CodeAttributeArgument[] args = new CodeAttributeArgument [1];
294 args [0] = new CodeAttributeArgument (new CodePrimitiveExpression (cultureName));
296 CodeCompileUnit unit = new CodeCompileUnit ();
297 unit.AssemblyCustomAttributes.Add (
298 new CodeAttributeDeclaration (
299 new CodeTypeReference ("System.Reflection.AssemblyCultureAttribute"),
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"),