Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
AssemblyProcessorApp.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 using System;
4 using System.Collections.Generic;
5 using System.IO;
6 using System.Linq;
7 using System.Reflection;
8 using System.Runtime.CompilerServices;
9 using System.Runtime.Versioning;
10 using System.Text;
11 using System.Text.RegularExpressions;
12 using Mono.Cecil;
13 using Mono.Cecil.Cil;
14 using Mono.Cecil.Rocks;
15 using SiliconStudio.Core;
18 
19 namespace SiliconStudio.AssemblyProcessor
20 {
21  public class AssemblyProcessorApp
22  {
24  {
25  SearchDirectories = new List<string>();
26  ModuleInitializer = true;
27  }
28 
29  public bool AutoNotifyProperty { get; set; }
30 
31  public bool ParameterKey { get; set; }
32 
33  public bool ModuleInitializer { get; set; }
34 
35  public bool SerializationAssembly { get; set; }
36 
37  public bool GenerateUserDocumentation { get; set; }
38 
39  public string NewAssemblyName { get; set; }
40 
41  public PlatformType Platform { get; set; }
42 
43  public string TargetFramework { get; set; }
44 
45  public List<string> SearchDirectories { get; set; }
46 
47  public string SignKeyFile { get; set; }
48 
49  public bool UseSymbols { get; set; }
50 
51  public Action<string, Exception> OnErrorEvent;
52 
53  public Action<string> OnInfoEvent;
54 
55  public bool Run(string inputFile, string outputFile = null)
56  {
57  if (inputFile == null) throw new ArgumentNullException("inputFile");
58  if (outputFile == null)
59  {
60  outputFile = inputFile;
61  }
62 
63  try
64  {
65  var processors = new List<IAssemblyDefinitionProcessor>();
66 
67  // We are no longer using it so we are deactivating it for now to avoid processing
68  //if (AutoNotifyProperty)
69  //{
70  // processors.Add(new NotifyPropertyProcessor());
71  //}
72 
73  if (ParameterKey)
74  {
75  processors.Add(new ParameterKeyProcessor());
76  }
77 
78  if (NewAssemblyName != null)
79  {
80  processors.Add(new RenameAssemblyProcessor(NewAssemblyName));
81  }
82 
83  //processors.Add(new AsyncBridgeProcessor());
84 
85  // Always applies the interop processor
86  processors.Add(new InteropProcessor());
87 
88  processors.Add(new AssemblyVersionProcessor());
89 
90  if (SerializationAssembly)
91  {
92  processors.Add(new SerializationProcessor(SignKeyFile));
93  }
94 
95  if (GenerateUserDocumentation)
96  {
97  processors.Add(new GenerateUserDocumentationProcessor(inputFile));
98  }
99 
100  if (ModuleInitializer)
101  {
102  processors.Add(new ModuleInitializerProcessor());
103  }
104 
105  processors.Add(new OpenSourceSignProcessor());
106 
107  var assemblyResolver = new CustomAssemblyResolver();
108  assemblyResolver.RemoveSearchDirectory(".");
109  foreach (string searchDirectory in SearchDirectories)
110  assemblyResolver.AddSearchDirectory(searchDirectory);
111 
112 
113  var readWriteSymbols = UseSymbols;
114  // Double check that
115  var symbolFile = Path.ChangeExtension(inputFile, "pdb");
116  if (!File.Exists(symbolFile))
117  {
118  readWriteSymbols = false;
119  }
120 
121  var assemblyDefinition = AssemblyDefinition.ReadAssembly(inputFile, new ReaderParameters { AssemblyResolver = assemblyResolver, ReadSymbols = readWriteSymbols });
122 
123  // Check if pdb was actually read
124  readWriteSymbols = assemblyDefinition.MainModule.HasDebugHeader;
125 
126  // Check if there is already a AssemblyProcessedAttribute (in which case we can skip processing, it has already been done).
127  // Note that we should probably also match the command line as well so that we throw an error if processing is different (need to rebuild).
128  if (assemblyDefinition.CustomAttributes.Any(x => x.AttributeType.FullName == "SiliconStudio.Core.AssemblyProcessedAttribute"))
129  {
130  OnInfoAction("Assembly has already been processed, skip it.");
131  return true;
132  }
133 
134  var targetFrameworkAttribute = assemblyDefinition.CustomAttributes
135  .FirstOrDefault(x => x.AttributeType.FullName == typeof(TargetFrameworkAttribute).FullName);
136  var targetFramework = targetFrameworkAttribute != null ? (string)targetFrameworkAttribute.ConstructorArguments[0].Value : null;
137 
138  // Special handling for MonoAndroid
139  // Default frameworkFolder
140  var frameworkFolder = Path.Combine(CecilExtensions.ProgramFilesx86(), @"Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5\");
141 
142 
143 
144  switch (Platform)
145  {
146  case PlatformType.Android:
147  {
148  if (string.IsNullOrEmpty(TargetFramework))
149  {
150  throw new InvalidOperationException("Expecting option target framework for Android");
151  }
152 
153  var monoAndroidPath = Path.Combine(CecilExtensions.ProgramFilesx86(), @"Reference Assemblies\Microsoft\Framework\MonoAndroid");
154  frameworkFolder = Path.Combine(monoAndroidPath, "v1.0");
155  var additionalFrameworkFolder = Path.Combine(monoAndroidPath, TargetFramework);
156  assemblyResolver.AddSearchDirectory(additionalFrameworkFolder);
157  assemblyResolver.AddSearchDirectory(frameworkFolder);
158  break;
159  }
160 
161  case PlatformType.iOS:
162  {
163  if (string.IsNullOrEmpty(TargetFramework))
164  {
165  throw new InvalidOperationException("Expecting option target framework for iOS");
166  }
167 
168  var monoAndroidPath = Path.Combine(CecilExtensions.ProgramFilesx86(), @"Reference Assemblies\Microsoft\Framework\MonoTouch");
169  frameworkFolder = Path.Combine(monoAndroidPath, "v1.0");
170  var additionalFrameworkFolder = Path.Combine(monoAndroidPath, TargetFramework);
171  assemblyResolver.AddSearchDirectory(additionalFrameworkFolder);
172  assemblyResolver.AddSearchDirectory(frameworkFolder);
173 
174  break;
175  }
176 
177  case PlatformType.WindowsStore:
178  {
179  if (string.IsNullOrEmpty(TargetFramework))
180  {
181  throw new InvalidOperationException("Expecting option target framework for WindowsStore");
182  }
183 
184  frameworkFolder = Path.Combine(CecilExtensions.ProgramFilesx86(), @"Reference Assemblies\Microsoft\Framework\.NETCore", TargetFramework);
185  assemblyResolver.AddSearchDirectory(frameworkFolder);
186 
187  // Add path to look for WinRT assemblies (Windows.winmd)
188  var windowsAssemblyPath = Path.Combine(CecilExtensions.ProgramFilesx86(), @"Windows Kits\8.1\References\CommonConfiguration\Neutral\", "Windows.winmd");
189  var windowsAssembly = AssemblyDefinition.ReadAssembly(windowsAssemblyPath, new ReaderParameters { AssemblyResolver = assemblyResolver, ReadSymbols = false });
190  assemblyResolver.Register(windowsAssembly);
191 
192  break;
193  }
194 
195  case PlatformType.WindowsPhone:
196  {
197  if (string.IsNullOrEmpty(TargetFramework))
198  {
199  throw new InvalidOperationException("Expecting option target framework for WindowsPhone");
200  }
201 
202  // Note: v8.1 is hardcoded because we currently receive v4.5.x as TargetFramework (different from TargetPlatformVersion)
203  frameworkFolder = Path.Combine(CecilExtensions.ProgramFilesx86(), @"Reference Assemblies\Microsoft\Framework\WindowsPhoneApp", "v8.1");
204  assemblyResolver.AddSearchDirectory(frameworkFolder);
205 
206  // Add path to look for WinRT assemblies (Windows.winmd)
207  var windowsAssemblyPath = Path.Combine(CecilExtensions.ProgramFilesx86(), @"Windows Phone Kits\8.1\References\CommonConfiguration\Neutral\", "Windows.winmd");
208  var windowsAssembly = AssemblyDefinition.ReadAssembly(windowsAssemblyPath, new ReaderParameters { AssemblyResolver = assemblyResolver, ReadSymbols = false });
209  assemblyResolver.Register(windowsAssembly);
210 
211  break;
212  }
213  }
214 
215  if (SerializationAssembly)
216  {
217  // Resave a first version of assembly with [InteralsVisibleTo] for future serialization assembly.
218  // It will be used by serialization assembly compilation.
219  // It's recommended to do it in the original code to avoid this extra step.
220 
221  var mscorlibAssembly = CecilExtensions.FindCorlibAssembly(assemblyDefinition);
222  var stringType = mscorlibAssembly.MainModule.GetTypeResolved(typeof(string).FullName);
223  var internalsVisibleToAttribute = mscorlibAssembly.MainModule.GetTypeResolved(typeof(InternalsVisibleToAttribute).FullName);
224  var serializationAssemblyName = assemblyDefinition.Name.Name + ".Serializers";
225  bool internalsVisibleAlreadyApplied = false;
226 
227  // Check if already applied
228  foreach (var customAttribute in assemblyDefinition.CustomAttributes.Where(x => x.AttributeType.FullName == internalsVisibleToAttribute.FullName))
229  {
230  var assemblyName = (string)customAttribute.ConstructorArguments[0].Value;
231 
232  int publicKeyIndex;
233  if ((publicKeyIndex = assemblyName.IndexOf(", PublicKey=", StringComparison.InvariantCulture)) != -1 || (publicKeyIndex = assemblyName.IndexOf(",PublicKey=", StringComparison.InvariantCulture)) != -1)
234  {
235  assemblyName = assemblyName.Substring(0, publicKeyIndex);
236  }
237 
238  if (assemblyName == serializationAssemblyName)
239  {
240  internalsVisibleAlreadyApplied = true;
241  break;
242  }
243  }
244 
245  if (!internalsVisibleAlreadyApplied)
246  {
247  // Apply public key
248  if (assemblyDefinition.Name.HasPublicKey)
249  serializationAssemblyName += ", PublicKey=" + ByteArrayToString(assemblyDefinition.Name.PublicKey);
250 
251  // Add [InteralsVisibleTo] attribute
252  var internalsVisibleToAttributeCtor = assemblyDefinition.MainModule.Import(internalsVisibleToAttribute.GetConstructors().Single());
253  var internalsVisibleAttribute = new CustomAttribute(internalsVisibleToAttributeCtor)
254  {
255  ConstructorArguments =
256  {
257  new CustomAttributeArgument(assemblyDefinition.MainModule.Import(stringType), serializationAssemblyName)
258  }
259  };
260  assemblyDefinition.CustomAttributes.Add(internalsVisibleAttribute);
261 
262  // Save updated file
263  assemblyDefinition.Write(inputFile, new WriterParameters() { WriteSymbols = readWriteSymbols });
264 
265  // Reread file (otherwise it seems Mono Cecil is buggy and generate invalid PDB)
266  assemblyDefinition = AssemblyDefinition.ReadAssembly(inputFile, new ReaderParameters { AssemblyResolver = assemblyResolver, ReadSymbols = readWriteSymbols });
267 
268  // Check if pdb was actually read
269  readWriteSymbols = assemblyDefinition.MainModule.HasDebugHeader;
270  }
271  }
272 
273 
274  bool modified = false;
275 
276  var assemblyProcessorContext = new AssemblyProcessorContext(assemblyResolver, assemblyDefinition, Platform);
277 
278  foreach (var processor in processors)
279  modified = processor.Process(assemblyProcessorContext) || modified;
280 
281  // Assembly might have been recreated (i.e. il-repack), so let's use it from now on
282  assemblyDefinition = assemblyProcessorContext.Assembly;
283 
284  if (modified || inputFile != outputFile)
285  {
286  // In case assembly has been modified,
287  // add AssemblyProcessedAttribute to assembly so that it doesn't get processed again
288  var mscorlibAssembly = CecilExtensions.FindCorlibAssembly(assemblyDefinition);
289  if (mscorlibAssembly == null)
290  {
291  OnErrorAction("Missing mscorlib.dll from assembly");
292  return false;
293  }
294 
295  var attributeType = mscorlibAssembly.MainModule.GetTypeResolved(typeof (Attribute).FullName);
296  var attributeTypeRef = assemblyDefinition.MainModule.Import(attributeType);
297  var attributeCtorRef = assemblyDefinition.MainModule.Import(attributeType.GetConstructors().Single(x => x.Parameters.Count == 0));
298  var voidType = assemblyDefinition.MainModule.Import(mscorlibAssembly.MainModule.GetTypeResolved("System.Void"));
299 
300  // Create custom attribute
301  var assemblyProcessedAttributeType = new TypeDefinition("SiliconStudio.Core", "AssemblyProcessedAttribute", TypeAttributes.BeforeFieldInit | TypeAttributes.AnsiClass | TypeAttributes.AutoClass | TypeAttributes.Public, attributeTypeRef);
302 
303  // Add constructor (call parent constructor)
304  var assemblyProcessedAttributeConstructor = new MethodDefinition(".ctor", MethodAttributes.RTSpecialName | MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.Public, voidType);
305  assemblyProcessedAttributeConstructor.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0));
306  assemblyProcessedAttributeConstructor.Body.Instructions.Add(Instruction.Create(OpCodes.Call, attributeCtorRef));
307  assemblyProcessedAttributeConstructor.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
308  assemblyProcessedAttributeType.Methods.Add(assemblyProcessedAttributeConstructor);
309 
310  // Make sure output directory is created
311  var outputDirectory = Path.GetDirectoryName(outputFile);
312  if (!string.IsNullOrEmpty(outputDirectory) && !Directory.Exists(outputDirectory))
313  {
314  Directory.CreateDirectory(outputDirectory);
315  }
316 
317  // Add AssemblyProcessedAttribute to assembly
318  assemblyDefinition.MainModule.Types.Add(assemblyProcessedAttributeType);
319  assemblyDefinition.CustomAttributes.Add(new CustomAttribute(assemblyProcessedAttributeConstructor));
320  assemblyDefinition.Write(outputFile, new WriterParameters() { WriteSymbols = readWriteSymbols });
321  }
322  }
323  catch (Exception e)
324  {
325  OnErrorAction(e.Message, e);
326  return false;
327  }
328 
329  return true;
330  }
331 
332  public static string ByteArrayToString(byte[] bytes)
333  {
334  var result = new StringBuilder(bytes.Length * 2);
335  foreach (byte b in bytes)
336  result.AppendFormat("{0:x2}", b);
337  return result.ToString();
338  }
339 
340  private void OnErrorAction(string errorMessage, Exception exception = null)
341  {
342  if (OnErrorEvent == null)
343  {
344  var builder = new StringBuilder();
345  builder.AppendLine(errorMessage);
346  if (exception != null)
347  {
348  builder.AppendLine(exception.ToString());
349  var nextE = exception;
350  for (int index = 0; nextE != null; nextE = nextE.InnerException, index++)
351  builder.AppendFormat("{0}{1}", string.Concat(Enumerable.Repeat(" ", index)), nextE.Message).AppendLine();
352  builder.AppendLine();
353  }
354 
355  Console.WriteLine(builder);
356  }
357  else
358  {
359  OnErrorEvent(errorMessage, exception);
360  }
361  }
362 
363  private void OnInfoAction(string infoMessage)
364  {
365  if (OnInfoEvent == null)
366  {
367  Console.WriteLine(infoMessage);
368  }
369  else
370  {
371  OnInfoEvent(infoMessage);
372  }
373  }
374  }
375 }
PlatformType
Describes the platform operating system.
Definition: PlatformType.cs:9
Mono.Cecil.TypeAttributes TypeAttributes
function b
Mono.Cecil.MethodAttributes MethodAttributes
System.IO.File File
static string ProgramFilesx86()
Get Program Files x86
Platform specific queries and functions.
Definition: Platform.cs:15
bool Run(string inputFile, string outputFile=null)