/// <summary> /// Helper method to call <see cref="ApplicationModel{T}.GenerateCode(CILReflectionContext, Boolean)"/> and save the resulting Qi4CS generated assemblies to specific folder. /// </summary> /// <typeparam name="TInstance">The type of the Qi4CS application instance of <paramref name="model"/>.</typeparam> /// <param name="model">The <see cref="ApplicationModel{T}"/>.</param> /// <param name="path">The path where generated Qi4CS assemblies should reside. If <c>null</c>, value of <see cref="Environment.CurrentDirectory"/> will be used.</param> /// <param name="isWP8OrSL5Emit">Whether emit assemblies compatible with Windows Phone 8 or Silverlight 5.</param> /// <param name="emittingInfoCreator"> /// The callback to create an <see cref="EmittingArguments"/> for emitting an assembly. /// It receives the existing <see cref="System.Reflection.Assembly"/> from which the Qi4CS assembly was generated, corresponding generated <see cref="CILAssembly"/>. /// It should return <see cref="EmittingArguments"/> to be used to emit the Qi4CS assembly. /// If the callback is <c>null</c> or returns <c>null</c>, the result of <see cref="EmittingArguments.CreateForEmittingDLL(StrongNameKeyPair, ImageFileMachine, TargetRuntime, ModuleFlags)"/> is used, with no strong name and <see cref="ImageFileMachine.I386"/> and <see cref="TargetRuntime.Net_4_0"/> as parameters. /// </param> /// <returns> /// The mapping from assembly used in Qi4CS model to the full path of corresponding generated Qi4CS assembly. /// </returns> public static IDictionary <System.Reflection.Assembly, String> GenerateAndSaveAssemblies <TInstance>(this ApplicationModel <TInstance> model, String path = null, Boolean isWP8OrSL5Emit = false, Func <System.Reflection.Assembly, CILAssembly, EmittingArguments> emittingInfoCreator = null) where TInstance : ApplicationSPI { if (path == null) { path = Environment.CurrentDirectory; } else { if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } if (!path.EndsWith("" + Path.DirectorySeparatorChar)) { path += Path.DirectorySeparatorChar; } path = Path.GetDirectoryName(Path.GetFullPath(path)); } //var refDic = new ConcurrentDictionary<String, Tuple<CILAssemblyName, Boolean>[]>(); var fileNames = DotNETReflectionContext.UseDotNETContext(ctx => { var genDic = model.GenerateCode(ctx, isWP8OrSL5Emit); var eArgsDic = new Dictionary <CILAssembly, EmittingArguments>(); // Before emitting, we must set the public keys of the generated assemblies, in order for cross-references to work properly. // Either CreateDefaultEmittingArguments or emittingInfoCreator should set the public key, if the strong name key pair is container name. foreach (var kvp in genDic) { var genAss = kvp.Value; var nAss = kvp.Key; EmittingArguments eArgs; if (emittingInfoCreator == null) { eArgs = CreateDefaultEmittingArguments(); } else { eArgs = (emittingInfoCreator(nAss, genAss) ?? CreateDefaultEmittingArguments()); } if (eArgs.StrongName != null && !eArgs.StrongName.KeyPair.IsNullOrEmpty() && genAss.Name.PublicKey.IsNullOrEmpty()) { // Have to set the public key genAss.Name.PublicKey = Utils.ExtractPublicKey(eArgs.StrongName.KeyPair.ToArray(), (eArgs.SigningAlgorithm ?? AssemblyHashAlgorithm.SHA1)); genAss.Name.Flags |= AssemblyFlags.PublicKey; } eArgsDic.Add(genAss, eArgs); } var resultDic = new ConcurrentDictionary <System.Reflection.Assembly, String>(); System.Threading.Tasks.Parallel.ForEach(genDic, kvp => { foreach (var mod in kvp.Value.Modules) { var fileName = Path.Combine(path, mod.Name); using (var stream = File.Open(fileName, FileMode.Create, FileAccess.ReadWrite, FileShare.Read)) { var eArgs = eArgsDic[kvp.Value]; mod.EmitModule(stream, eArgs); stream.Flush(); //refDic.TryAdd( fileName, eArgs.AssemblyRefs.Select( aRef => Tuple.Create( aRef, IsDotNETAssembly( aRef, portabilityHelper, eArgs.PCLProfile ) ) ).ToArray() ); resultDic.TryAdd(kvp.Key, fileName); } } }); return(resultDic); }); return(fileNames); }
/// <summary> /// This method will generate the Qi4CS assemblies. /// </summary> /// <param name="projectDir">The project directory.</param> /// <param name="targetFWID">The target framework identifier.</param> /// <param name="targetFWVersion">The target framework version.</param> /// <param name="targetFWProfile">The target framework profile.</param> /// <param name="referenceAssembliesDir">The base directory where target framework assemblies reside. Should be the directory where target framework assemblies are found under subdirectory <c><framework-id>\<framework-version></c>, e.g. <c>".NETFramework\v4.0"</c>.</param> /// <param name="targetPlatform">The textual name of target platform.</param> /// <param name="path">The path where to store assemblies.</param> /// <param name="assemblySNInfo">The file containing strongname information about the assemblies to be emitted.</param> /// <param name="qi4CSDir">The directory where Qi4CS assemblies actually used by the application reside.</param> /// <param name="verify">Whether to run PEVerify on generated Qi4CS assemblies.</param> /// <param name="winSDKDir">The directory where the Windows SDK resides, needed to detect PEVerify executable.</param> public IDictionary <String, String> GenerateAssemblies(String projectDir, String targetFWID, String targetFWVersion, String targetFWProfile, String referenceAssembliesDir, String targetPlatform, String path, String assemblySNInfo, String qi4CSDir, Boolean verify, String winSDKDir) { qi4CSDir = Path.GetFullPath(qi4CSDir); path = Path.GetFullPath(path); XElement assemblyInfo = null; if (!String.IsNullOrEmpty(assemblySNInfo)) { try { assemblyInfo = XElement.Load(new StringReader(assemblySNInfo)); } catch (Exception exc) { throw new Qi4CSBuildException("Invalid assembly info element " + assemblySNInfo + ".\n" + exc); } } Func <String, Stream> streamOpener = str => File.Open(str, FileMode.Open, FileAccess.Read, FileShare.Read); referenceAssembliesDir = Path.Combine(referenceAssembliesDir, targetFWID, targetFWVersion); if (!String.IsNullOrEmpty(targetFWProfile)) { referenceAssembliesDir = Path.Combine(referenceAssembliesDir, "Profile", targetFWProfile); } String msCorLibName; String fwDisplayName; String targetFWDir; var thisFWMoniker = new FrameworkMonikerInfo(targetFWID, targetFWVersion, targetFWProfile, DotNETReflectionContext.ReadAssemblyInformationFromRedistXMLFile(Path.Combine(referenceAssembliesDir, "RedistList", "FrameworkList.xml"), out msCorLibName, out fwDisplayName, out targetFWDir), msCorLibName, fwDisplayName); if (!String.IsNullOrEmpty(targetFWDir)) { referenceAssembliesDir = targetFWDir; } if (!Directory.Exists(referenceAssembliesDir)) { throw new Qi4CSBuildException("The reference assemblies directory " + referenceAssembliesDir + " does not exist."); } referenceAssembliesDir += Path.DirectorySeparatorChar; var isX86 = X86.Equals(targetPlatform, StringComparison.InvariantCultureIgnoreCase); var targetMachine = String.IsNullOrEmpty(targetPlatform) || ANYCPU.Equals(targetPlatform, StringComparison.InvariantCultureIgnoreCase) || isX86 ? ImageFileMachine.I386 : ImageFileMachine.AMD64; // TODO more machines var mFlags = ModuleFlags.ILOnly; if (isX86) { mFlags |= ModuleFlags.Required32Bit; } var snDic = new ConcurrentDictionary <String, Tuple <StrongNameKeyPair, AssemblyHashAlgorithm> >(); if (assemblyInfo != null) { foreach (var elem in assemblyInfo.XPathSelectElements("assembly")) { snDic[elem.Attribute("name").Value] = Tuple.Create( SubElementTextOrFallback(elem, "sn", (XElement snElem, out StrongNameKeyPair sn) => { var type = snElem.Attribute("type"); sn = "container".Equals(type.Value, StringComparison.InvariantCultureIgnoreCase) ? new StrongNameKeyPair(snElem.Value) : new StrongNameKeyPair("inline".Equals(type.Value, StringComparison.InvariantCultureIgnoreCase) ? StringConversions.HexStr2ByteArray(snElem.Value) : ReadAllBytes(projectDir, snElem.Value)); return(true); }, null), SubElementAttributeOrFallback(elem, "hashAlgorithm", (String algoStr, out AssemblyHashAlgorithm algo) => { return(Enum.TryParse <AssemblyHashAlgorithm>(algoStr, out algo)); }, AssemblyHashAlgorithm.SHA1)); } } var runtimeRootDir = Path.GetDirectoryName(new Uri(typeof(Object).Assembly.CodeBase).LocalPath); var actualPath = path; var needToMove = verify && !String.Equals(actualPath, qi4CSDir); if (needToMove) { // When verifying strong-named assemblies in different location than application's out dir, the .dll.config file // should hold recursively all non-.NET references, which is quite complicated and maybe not even enough. // Instead, emit Qi4CS assemblies into out dir and move them after verifying actualPath = qi4CSDir; } IDictionary <System.Reflection.Assembly, String> genAssFilenames; try { genAssFilenames = this._modelFactory.Model.GenerateAndSaveAssemblies( actualPath, this.IsSilverlight, (nAss, gAss) => { Tuple <StrongNameKeyPair, AssemblyHashAlgorithm> snTuple; snDic.TryGetValue(nAss.GetName().Name, out snTuple); var sn = snTuple == null ? null : snTuple.Item1; var eArgs = EmittingArguments.CreateForEmittingWithMoniker(gAss.ReflectionContext, targetMachine, TargetRuntime.Net_4_0, ModuleKind.Dll, null, runtimeRootDir, referenceAssembliesDir, streamOpener, sn, thisFWMoniker, String.Equals(thisFWMoniker.FrameworkName, PCL_FW_NAME), mFlags); gAss.AddTargetFrameworkAttributeWithMonikerInfo(thisFWMoniker, eArgs.AssemblyMapper); if (snTuple != null) { eArgs.SigningAlgorithm = snTuple.Item2; } return(eArgs); }); } catch (InvalidApplicationModelException apme) { throw new Qi4CSBuildException("The Qi4CS model was not valid:\n" + apme.ValidationResult, apme); } if (verify) { try { winSDKDir = FindWinSDKBinPath(winSDKDir); foreach (var fn in genAssFilenames) { Verify(winSDKDir, fn.Value, snDic != null && snDic.ContainsKey(fn.Key.GetName().Name)); } } finally { if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } foreach (var kvp in genAssFilenames.ToArray()) { var fn = kvp.Value; try { var targetFn = Path.Combine(path, Path.GetFileName(fn)); if (!String.Equals(fn, targetFn)) { if (File.Exists(targetFn)) { try { File.Delete(targetFn); } catch { // Ignore } } File.Move(fn, targetFn); genAssFilenames.Remove(kvp.Key); genAssFilenames.Add(kvp.Key, targetFn); } } catch { // Ignore } } } } return(genAssFilenames.ToDictionary( kvp => kvp.Key.CodeBase, kvp => kvp.Value )); }