/// /// Copyright © 2003-2008 JetBrains s.r.o. /// You may distribute under the terms of the GNU General Public License, as published by the Free Software Foundation, version 2 (see License.txt in the repository root folder). /// using System; using System.IO; using System.Reflection; using System.Xml; using JetBrains.Build.Common.Infra; using Microsoft.Build.Framework; namespace JetBrains.Build.Common.Tasks { /// /// Generates the publisher policy assemblies for the given set of assemblies and their specific versions. /// public class CreatePublisherPolicyBuildFile : TaskBase { #region Data public static readonly string NsBuild = "http://schemas.microsoft.com/developer/msbuild/2003"; public static readonly string NsPolicy = "urn:schemas-microsoft-com:asm.v1"; #endregion #region Attributes /// /// Lists the assemblies for which the policies should be generated. /// [Required] public ITaskItem[] InputFiles { get { return (ITaskItem[])Bag[AttributeName.InputFiles]; } set { Bag[AttributeName.InputFiles] = value; } } /// /// Specifies the intermediate folder in which the config files should be created. /// [Required] public ITaskItem IntDir { get { return (ITaskItem)Bag[AttributeName.IntDir]; } set { Bag[AttributeName.IntDir] = value; } } /// /// Specifies the key file to sign the publisher policy assemblies with; must correspond to the key that was used to sign the original assembly. /// [Required] public ITaskItem KeyFile { get { return (ITaskItem)Bag[AttributeName.KeyFile]; } set { Bag[AttributeName.KeyFile] = value; } } /// /// Specifies the output folder into which the resulting assemblies will be written. /// [Required] public ITaskItem OutDir { get { return (ITaskItem)Bag[AttributeName.OutDir]; } set { Bag[AttributeName.OutDir] = value; } } /// /// Specifies the output MSBuild project file that will be generated by this task. /// [Required] public ITaskItem OutputFile { get { return (ITaskItem)Bag[AttributeName.OutputFile]; } set { Bag[AttributeName.OutputFile] = value; } } /// /// The upper boundary of the source versions range for the publisher policy. Optional; if missed, the actual version of the assembly will be used. /// public string SourceVersionHigh { get { return (string)Bag[AttributeName.SourceVersionHigh]; } set { Bag[AttributeName.SourceVersionHigh] = value; } } /// /// The lower boundary of the source versions range for the publisher policy. Required. /// [Required] public string SourceVersionLow { get { return (string)Bag[AttributeName.SourceVersionLow]; } set { Bag[AttributeName.SourceVersionLow] = value; } } /// /// The target version for the publisher policy. Optional; if missed, the actual version of the assembly will be used. /// public string TargetVersion { get { return (string)Bag[AttributeName.TargetVersion]; } set { Bag[AttributeName.TargetVersion] = value; } } #endregion #region Operations /// /// Ensures that version has at least components defined. /// public static void AssertVersionComponents(Version version, int components) { if(version == null) throw new ArgumentNullException("version"); if((components < 0) || (components > 4)) throw new InvalidOperationException(string.Format("Error in constraint: the version number cannot have {0} components.", components)); if(components == 0) return; if(version.Major < 0) throw new InvalidOperationException(string.Format("The version number “{0}” is expected to have at least {1} components defined.", version, components)); if(components == 1) return; if(version.Minor < 0) throw new InvalidOperationException(string.Format("The version number “{0}” is expected to have at least {1} components defined.", version, components)); if(components == 2) return; if(version.Build < 0) throw new InvalidOperationException(string.Format("The version number “{0}” is expected to have at least {1} components defined.", version, components)); if(components == 3) return; if(version.Revision < 0) throw new InvalidOperationException(string.Format("The version number “{0}” is expected to have at least {1} components defined.", version, components)); } #endregion #region Implementation private static string GeneratePublisherPolicyFile(AssemblyName assemblyname, Version versionSourceLow, Version versionSourceHigh, Version versionTarget, FileSystemInfo diOut) { if(versionSourceLow == null) throw new ArgumentNullException("versionSourceLow"); // Determine the properties if(versionSourceHigh == null) { versionSourceHigh = assemblyname.Version; AssertVersionComponents(versionSourceHigh, 4); } if(versionTarget == null) { versionTarget = assemblyname.Version; AssertVersionComponents(versionTarget, 4); } // The two older version components must not change along the version numbers span if((versionSourceLow.Major != versionSourceHigh.Major) || (versionSourceLow.Minor != versionSourceHigh.Minor)) throw new InvalidOperationException(string.Format("The two older version number components must not change within the source version numbers span [{0}–{1}] (while processing assembly “{2}”).", versionSourceLow, versionSourceHigh, assemblyname.FullName)); if(versionSourceLow > versionSourceHigh) throw new InvalidOperationException(string.Format("The version numbers span [{0}–{1}] must be normalized (while processing assembly “{2}”).", versionSourceLow, versionSourceHigh, assemblyname.FullName)); // Build the policy file var settings = new XmlWriterSettings(); settings.Indent = true; string sConfigFileName = Path.Combine(diOut.FullName, string.Format("Policy.{0}.{1}.{2}.Config", versionSourceLow.Major, versionSourceLow.Minor, assemblyname.Name)); using(XmlWriter writer = XmlWriter.Create(sConfigFileName, settings)) { writer.WriteStartElement("configuration"); writer.WriteStartElement("runtime"); writer.WriteStartElement("assemblyBinding", NsPolicy); writer.WriteStartElement("dependentAssembly", NsPolicy); writer.WriteStartElement("assemblyIdentity", NsPolicy); writer.WriteAttributeString("name", assemblyname.Name); writer.WriteStartAttribute("publicKeyToken"); byte[] arToken = assemblyname.GetPublicKeyToken(); writer.WriteBinHex(arToken, 0, arToken.Length); writer.WriteEndAttribute(); writer.WriteAttributeString("culture", assemblyname.CultureInfo.Name); writer.WriteEndElement(); writer.WriteStartElement("bindingRedirect", NsPolicy); writer.WriteAttributeString("oldVersion", (versionSourceLow == versionSourceHigh ? versionSourceLow.ToString(4) : versionSourceLow.ToString(4) + "-" + versionSourceHigh.ToString(4))); writer.WriteAttributeString("newVersion", versionTarget.ToString(4)); writer.WriteEndElement(); writer.WriteEndElement(); writer.WriteEndElement(); writer.WriteEndElement(); writer.WriteEndElement(); } return sConfigFileName; } #endregion #region Overrides /// /// The method to be overriden in inheriting tasks. /// Throw an exception in case of an errror. /// protected override void ExecuteTask() { // Acq the int dir, create as needed var diIntermediate = new DirectoryInfo(GetValue(AttributeName.IntDir).GetMetadata("FullPath")); if(!diIntermediate.Exists) diIntermediate.Create(); var diOutput = new DirectoryInfo(GetValue(AttributeName.OutDir).GetMetadata("FullPath")); if(!diOutput.Exists) diOutput.Create(); // Acquire the versions: one required and two optional params, leave missed Null for now var versionSourceLow = new Version(GetStringValue(AttributeName.SourceVersionLow)); AssertVersionComponents(versionSourceLow, 4); Version versionSourceHigh = null; if(!string.IsNullOrEmpty(SourceVersionHigh)) { versionSourceHigh = new Version(SourceVersionHigh); AssertVersionComponents(versionSourceHigh, 4); } Version versionTarget = null; if(!string.IsNullOrEmpty(TargetVersion)) { versionTarget = new Version(TargetVersion); AssertVersionComponents(versionTarget, 4); } // Start creating the build file var settings = new XmlWriterSettings(); settings.Indent = true; using(XmlWriter writer = XmlWriter.Create(GetStringValue(AttributeName.OutputFile), settings)) { writer.WriteStartElement("Project", NsBuild); writer.WriteStartElement("Import", NsBuild); writer.WriteAttributeString("Project", @"$(MSBuildBinPath)\Microsoft.Common.Tasks"); writer.WriteEndElement(); writer.WriteStartElement("Target", NsBuild); writer.WriteAttributeString("Name", "Build"); // An al task for each of the assemblies for(int nItem = 0; nItem < GetValue(AttributeName.InputFiles).Length; nItem++) { // Pick the item var fiItem = new FileInfo(GetValue(AttributeName.InputFiles)[nItem].GetMetadata("FullPath")); if(!fiItem.Exists) throw new InvalidOperationException(string.Format("The input file “{0}” does not exist.", fiItem.FullName)); // Generate the publisher policy file AssemblyName assemblyname = AssemblyName.GetAssemblyName(fiItem.FullName); string sConfigFileName = GeneratePublisherPolicyFile(assemblyname, versionSourceLow, versionSourceHigh, versionTarget, diIntermediate); string sAssemblyFileName = Path.Combine(diOutput.FullName, string.Format("Policy.{0}.{1}.{2}.dll", versionSourceLow.Major, versionSourceLow.Minor, assemblyname.Name)); // Produce the build task writer.WriteStartElement("AL", NsBuild); writer.WriteAttributeString("LinkResources", sConfigFileName); writer.WriteAttributeString("OutputAssembly", sAssemblyFileName); writer.WriteAttributeString("KeyFile", Path.GetFullPath(GetValue(AttributeName.KeyFile).GetMetadata("FullPath"))); writer.WriteEndElement(); } writer.WriteEndElement(); writer.WriteEndElement(); } } #endregion } }