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
50 static string framework_version = "4.5";
51 static string profile_path = "net_4_x";
52 CompilationSection config;
54 CodeDomProvider _provider;
55 string baseAssemblyPath;
56 string baseAssemblyDirectory;
57 string canonicAssemblyName;
58 Assembly mainAssembly;
59 AppResourcesCompiler appResourcesCompiler;
61 public CodeDomProvider Provider {
63 if (_provider == null)
64 _provider = ci.CreateProvider ();
68 if (_provider == null)
69 throw new ApplicationException ("Failed to instantiate the default compiler.");
74 public Assembly MainAssembly {
75 get { return mainAssembly; }
78 public AppResourcesAssemblyBuilder (string canonicAssemblyName, string baseAssemblyPath, AppResourcesCompiler appres)
80 this.appResourcesCompiler = appres;
81 this.baseAssemblyPath = baseAssemblyPath;
82 this.baseAssemblyDirectory = Path.GetDirectoryName (baseAssemblyPath);
83 this.canonicAssemblyName = canonicAssemblyName;
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.");
98 public void Build (CodeCompileUnit unit)
100 Dictionary <string, List <string>> cultures = appResourcesCompiler.CultureFiles;
101 List <string> defaultCultureFiles = appResourcesCompiler.DefaultCultureFiles;
103 if (defaultCultureFiles != null)
104 BuildDefaultAssembly (defaultCultureFiles, unit);
106 foreach (KeyValuePair <string, List <string>> kvp in cultures)
107 BuildSatelliteAssembly (kvp.Key, kvp.Value);
110 void BuildDefaultAssembly (List <string> files, CodeCompileUnit unit)
112 AssemblyBuilder abuilder = new AssemblyBuilder (Provider);
114 abuilder.AddCodeCompileUnit (unit);
116 CompilerParameters cp = ci.CreateDefaultCompilerParameters ();
117 cp.OutputAssembly = baseAssemblyPath;
118 cp.GenerateExecutable = false;
119 cp.TreatWarningsAsErrors = true;
120 cp.IncludeDebugInformation = config.Debug;
122 foreach (string f in files)
123 cp.EmbeddedResources.Add (f);
125 CompilerResults results = abuilder.BuildAssembly (cp);
129 if (results.NativeCompilerReturnValue == 0) {
130 mainAssembly = results.CompiledAssembly;
131 BuildManager.TopLevelAssemblies.Add (mainAssembly);
133 if (HttpContext.Current.IsCustomErrorEnabled)
134 throw new ApplicationException ("An error occurred while compiling global resources.");
135 throw new CompilationException (null, results.Errors, null);
138 HttpRuntime.WritePreservationFile (mainAssembly, canonicAssemblyName);
139 HttpRuntime.EnableAssemblyMapping (true);
142 void BuildSatelliteAssembly (string cultureName, List <string> files)
144 string assemblyPath = BuildAssemblyPath (cultureName);
145 var info = new ProcessStartInfo ();
146 var al = new Process ();
148 string arguments = SetAlPath (info);
149 var sb = new StringBuilder (arguments);
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 + "\" ");
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 + "\" ");
164 sb.Append ("@\"" + responseFilePath + "\"");
166 info.Arguments = sb.ToString ();
167 info.CreateNoWindow = true;
168 info.UseShellExecute = false;
169 info.RedirectStandardOutput = true;
170 info.RedirectStandardError = true;
174 var alOutput = new StringCollection ();
175 var alMutex = new Mutex ();
176 DataReceivedEventHandler outputHandler = (object sender, DataReceivedEventArgs args) => {
177 if (args.Data != null) {
179 alOutput.Add (args.Data);
180 alMutex.ReleaseMutex ();
184 al.ErrorDataReceived += outputHandler;
185 al.OutputDataReceived += outputHandler;
187 // TODO: consider using asynchronous processes
190 } catch (Exception ex) {
191 throw new HttpException (String.Format ("Error running {0}", al.StartInfo.FileName), ex);
194 Exception alException = null;
197 al.BeginOutputReadLine ();
198 al.BeginErrorReadLine ();
200 exitCode = al.ExitCode;
201 } catch (Exception ex) {
204 al.CancelErrorRead ();
205 al.CancelOutputRead ();
209 if (exitCode != 0 || alException != null) {
210 // TODO: consider adding a new type of compilation exception,
212 CompilerErrorCollection errors = null;
214 if (alOutput.Count != 0) {
215 foreach (string line in alOutput) {
216 if (!line.StartsWith ("ALINK: error ", StringComparison.Ordinal))
219 errors = new CompilerErrorCollection ();
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);
225 errors.Add (new CompilerError (Path.GetFileName (assemblyPath), 0, 0, errorNumber, errorText));
229 throw new CompilationException (Path.GetFileName (assemblyPath), errors, null);
233 string SetAlPath (ProcessStartInfo info)
235 if (RuntimeHelpers.RunningOnWindows) {
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);
250 alPath = Path.Combine (p, framework_version + "\\al.exe");
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);
258 info.FileName = monoPath;
261 info.FileName = "al";
266 string BuildAssemblyPath (string cultureName)
268 string baseDir = Path.Combine (baseAssemblyDirectory, cultureName);
269 if (!Directory.Exists (baseDir))
270 Directory.CreateDirectory (baseDir);
272 string baseFileName = Path.GetFileNameWithoutExtension (baseAssemblyPath);
273 string fileName = String.Concat (baseFileName, ".resources.dll");
274 fileName = Path.Combine (baseDir, fileName);
279 CodeCompileUnit GenerateAssemblyInfo (string cultureName)
281 CodeAttributeArgument[] args = new CodeAttributeArgument [1];
282 args [0] = new CodeAttributeArgument (new CodePrimitiveExpression (cultureName));
284 CodeCompileUnit unit = new CodeCompileUnit ();
285 unit.AssemblyCustomAttributes.Add (
286 new CodeAttributeDeclaration (
287 new CodeTypeReference ("System.Reflection.AssemblyCultureAttribute"),
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"),