diff options
Diffstat (limited to 'src/DotNetOpenAuth.BuildTasks')
28 files changed, 2593 insertions, 0 deletions
diff --git a/src/DotNetOpenAuth.BuildTasks/AddProjectItems.cs b/src/DotNetOpenAuth.BuildTasks/AddProjectItems.cs new file mode 100644 index 0000000..30fa284 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/AddProjectItems.cs @@ -0,0 +1,62 @@ +//----------------------------------------------------------------------- +// <copyright file="AddProjectItems.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.BuildTasks { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using Microsoft.Build.BuildEngine; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + using System.Collections; + + public class AddProjectItems : Task { + /// <summary> + /// Gets or sets the projects to add items to. + /// </summary> + /// <value>The projects.</value> + [Required] + public ITaskItem[] Projects { get; set; } + + /// <summary> + /// Gets or sets the items to add to each project. + /// </summary> + /// <value>The items.</value> + /// <remarks> + /// Use the metadata "ItemType" on each item to specify the item type to use for the new + /// project item. If the metadata is absent, "None" is used as the item type. + /// </remarks> + [Required] + public ITaskItem[] Items { get; set; } + + /// <summary> + /// Executes this instance. + /// </summary> + public override bool Execute() { + foreach (var projectTaskItem in this.Projects) { + var project = new Project(); + project.Load(projectTaskItem.ItemSpec); + + foreach (var projectItem in this.Items) { + string itemType = projectItem.GetMetadata("ItemType"); + if (string.IsNullOrEmpty(itemType)) { + itemType = "None"; + } + BuildItem newItem = project.AddNewItem(itemType, projectItem.ItemSpec, false); + var customMetadata = projectItem.CloneCustomMetadata(); + foreach (DictionaryEntry entry in customMetadata) { + newItem.SetMetadata((string)entry.Key, (string)entry.Value); + } + } + + project.Save(projectTaskItem.ItemSpec); + } + + return !this.Log.HasLoggedErrors; + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/ChangeAssemblyReference.cs b/src/DotNetOpenAuth.BuildTasks/ChangeAssemblyReference.cs new file mode 100644 index 0000000..3ad5eb0 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/ChangeAssemblyReference.cs @@ -0,0 +1,47 @@ +using System; +using System.Linq; +using System.IO; +using System.Xml; + +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Microsoft.Build.BuildEngine; + +namespace DotNetOpenAuth.BuildTasks { + /// <summary> + /// Replaces Reference items HintPaths in a set of projects. + /// </summary> + public class ChangeAssemblyReference : Task { + /// <summary> + /// The projects to alter. + /// </summary> + [Required] + public ITaskItem[] Projects { get; set; } + /// <summary> + /// The project reference to remove. + /// </summary> + [Required] + public string OldReference { get; set; } + /// <summary> + /// The assembly reference to add. + /// </summary> + [Required] + public string NewReference { get; set; } + + const string msbuildNamespace = "http://schemas.microsoft.com/developer/msbuild/2003"; + public override bool Execute() { + foreach (var project in Projects) { + Project doc = new Project(); + doc.Load(project.ItemSpec); + + var reference = doc.GetEvaluatedItemsByName("Reference").OfType<BuildItem>(). + Where(item => string.Equals(item.GetMetadata("HintPath"), OldReference, StringComparison.OrdinalIgnoreCase)).Single(); + reference.SetMetadata("HintPath", NewReference); + + doc.Save(project.ItemSpec); + } + + return true; + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/ChangeProjectReferenceToAssemblyReference.cs b/src/DotNetOpenAuth.BuildTasks/ChangeProjectReferenceToAssemblyReference.cs new file mode 100644 index 0000000..f940a72 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/ChangeProjectReferenceToAssemblyReference.cs @@ -0,0 +1,52 @@ +using System; +using System.Linq; +using System.IO; +using System.Xml; + +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Microsoft.Build.BuildEngine; + +namespace DotNetOpenAuth.BuildTasks { + /// <summary> + /// Replaces ProjectReference items in a set of projects with Reference items. + /// </summary> + public class ChangeProjectReferenceToAssemblyReference : Task { + /// <summary> + /// The projects to alter. + /// </summary> + [Required] + public ITaskItem[] Projects { get; set; } + /// <summary> + /// The project reference to remove. + /// </summary> + [Required] + public string ProjectReference { get; set; } + /// <summary> + /// The assembly reference to add. + /// </summary> + [Required] + public string Reference { get; set; } + + const string msbuildNamespace = "http://schemas.microsoft.com/developer/msbuild/2003"; + public override bool Execute() { + foreach (var project in Projects) { + Log.LogMessage(MessageImportance.Normal, "Changing P2P references to assembly references in \"{0}\".", project.ItemSpec); + + Project doc = new Project(); + doc.Load(project.ItemSpec); + + var projectReference = doc.EvaluatedItems.OfType<BuildItem>().Where( + item => item.Name == "ProjectReference" && item.Include == ProjectReference).Single(); + doc.RemoveItem(projectReference); + + var newReference = doc.AddNewItem("Reference", Path.GetFileNameWithoutExtension(Reference), true); + newReference.SetMetadata("HintPath", Reference); + + doc.Save(project.ItemSpec); + } + + return true; + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/CheckAdminRights.cs b/src/DotNetOpenAuth.BuildTasks/CheckAdminRights.cs new file mode 100644 index 0000000..9cfd35d --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/CheckAdminRights.cs @@ -0,0 +1,30 @@ +//----------------------------------------------------------------------- +// <copyright file="CheckAdminRights.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.BuildTasks { + using System.Security.Principal; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + + public class CheckAdminRights : Task { + /// <summary> + /// Gets or sets a value indicating whether this process has elevated permissions. + /// </summary> + [Output] + public bool IsElevated { get; set; } + + /// <summary> + /// Executes this instance. + /// </summary> + public override bool Execute() { + WindowsIdentity id = WindowsIdentity.GetCurrent(); + WindowsPrincipal p = new WindowsPrincipal(id); + this.IsElevated = p.IsInRole(WindowsBuiltInRole.Administrator); + + return true; + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/CompareFiles.cs b/src/DotNetOpenAuth.BuildTasks/CompareFiles.cs new file mode 100644 index 0000000..51fcee4 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/CompareFiles.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.Build.Utilities; +using Microsoft.Build.Framework; +using System.IO; + +namespace DotNetOpenAuth.BuildTasks { + public class CompareFiles : Task { + /// <summary> + /// One set of items to compare. + /// </summary> + [Required] + public ITaskItem[] OriginalItems { get; set; } + + /// <summary> + /// The other set of items to compare. + /// </summary> + [Required] + public ITaskItem[] NewItems { get; set; } + + /// <summary> + /// Gets whether the items lists contain items that are identical going down the list. + /// </summary> + [Output] + public bool AreSame { get; private set; } + + /// <summary> + /// Same as <see cref="AreSame"/>, but opposite. + /// </summary> + [Output] + public bool AreChanged { get { return !AreSame; } } + + public override bool Execute() { + AreSame = AreFilesIdentical(); + return true; + } + + private bool AreFilesIdentical() { + if (OriginalItems.Length != NewItems.Length) { + return false; + } + + for (int i = 0; i < OriginalItems.Length; i++) { + if (!IsContentOfFilesTheSame(OriginalItems[i].ItemSpec, NewItems[i].ItemSpec)) { + return false; + } + } + + return true; + } + + private bool IsContentOfFilesTheSame(string file1, string file2) { + // If exactly one file is missing, that's different. + if (File.Exists(file1) ^ File.Exists(file2)) return false; + // If both are missing, that's the same. + if (!File.Exists(file1)) return true; + // If both are present, we need to do a content comparison. + using (FileStream fileStream1 = File.OpenRead(file1)) { + using (FileStream fileStream2 = File.OpenRead(file2)) { + if (fileStream1.Length != fileStream2.Length) return false; + byte[] buffer1 = new byte[4096]; + byte[] buffer2 = new byte[buffer1.Length]; + int bytesRead; + do { + bytesRead = fileStream1.Read(buffer1, 0, buffer1.Length); + if (fileStream2.Read(buffer2, 0, buffer2.Length) != bytesRead) { + // We should never get here since we compared file lengths, but + // this is a sanity check. + return false; + } + for (int i = 0; i < bytesRead; i++) { + if (buffer1[i] != buffer2[i]) { + return false; + } + } + } while (bytesRead == buffer1.Length); + } + } + + return true; + } + + /// <summary> + /// Tests whether a file is up to date with respect to another, + /// based on existence, last write time and file size. + /// </summary> + /// <param name="sourcePath">The source path.</param> + /// <param name="destPath">The dest path.</param> + /// <returns><c>true</c> if the files are the same; <c>false</c> if the files are different</returns> + internal static bool FastFileEqualityCheck(string sourcePath, string destPath) { + FileInfo sourceInfo = new FileInfo(sourcePath); + FileInfo destInfo = new FileInfo(destPath); + + if (sourceInfo.Exists ^ destInfo.Exists) { + // Either the source file or the destination file is missing. + return false; + } + + if (!sourceInfo.Exists) { + // Neither file exists. + return true; + } + + // We'll say the files are the same if their modification date and length are the same. + return + sourceInfo.LastWriteTimeUtc == destInfo.LastWriteTimeUtc && + sourceInfo.Length == destInfo.Length; + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/CopyWithTokenSubstitution.cs b/src/DotNetOpenAuth.BuildTasks/CopyWithTokenSubstitution.cs new file mode 100644 index 0000000..e17d8f2 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/CopyWithTokenSubstitution.cs @@ -0,0 +1,102 @@ +//----------------------------------------------------------------------- +// <copyright file="CopyWithTokenSubstitution.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.BuildTasks { + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + + /// <summary> + /// Copies files and performs a search and replace for given tokens in their contents during the process. + /// </summary> + public class CopyWithTokenSubstitution : Task { + /// <summary> + /// Gets or sets the files to copy. + /// </summary> + /// <value>The files to copy.</value> + [Required] + public ITaskItem[] SourceFiles { get; set; } + + /// <summary> + /// Gets or sets a list of files to copy the source files to. + /// </summary> + /// <value>The list of files to copy the source files to.</value> + [Required] + public ITaskItem[] DestinationFiles { get; set; } + + /// <summary> + /// Gets or sets the destination files actually copied to. + /// </summary> + /// <remarks> + /// In the case of error partway through, or files not copied due to already being up to date, + /// this can be a subset of the <see cref="DestinationFiles"/> array. + /// </remarks> + [Output] + public ITaskItem[] CopiedFiles { get; set; } + + /// <summary> + /// Executes this instance. + /// </summary> + /// <returns><c>true</c> if the operation was successful.</returns> + public override bool Execute() { + if (this.SourceFiles.Length != this.DestinationFiles.Length) { + Log.LogError("{0} inputs and {1} outputs given.", this.SourceFiles.Length, this.DestinationFiles.Length); + return false; + } + + var copiedFiles = new List<ITaskItem>(this.DestinationFiles.Length); + + for (int i = 0; i < this.SourceFiles.Length; i++) { + string sourcePath = this.SourceFiles[i].ItemSpec; + string destPath = this.DestinationFiles[i].ItemSpec; + bool skipUnchangedFiles = bool.Parse(this.SourceFiles[i].GetMetadata("SkipUnchangedFiles")); + + // We deliberably consider newer destination files to be up-to-date rather than + // requiring equality because this task modifies the destination file while copying. + if (skipUnchangedFiles && File.GetLastWriteTimeUtc(sourcePath) < File.GetLastWriteTimeUtc(destPath)) { + Log.LogMessage(MessageImportance.Low, "Skipping \"{0}\" -> \"{1}\" because the destination is up to date.", sourcePath, destPath); + continue; + } + + Log.LogMessage(MessageImportance.Normal, "Transforming \"{0}\" -> \"{1}\"", sourcePath, destPath); + + string[] beforeTokens = this.SourceFiles[i].GetMetadata("BeforeTokens").Split(';'); + string[] afterTokens = this.SourceFiles[i].GetMetadata("AfterTokens").Split(';'); + if (beforeTokens.Length != afterTokens.Length) { + Log.LogError("Unequal number of before and after tokens. Before: \"{0}\". After \"{1}\".", beforeTokens, afterTokens); + return false; + } + + using (StreamReader sr = File.OpenText(sourcePath)) { + if (!Directory.Exists(Path.GetDirectoryName(destPath))) { + Directory.CreateDirectory(Path.GetDirectoryName(destPath)); + } + using (StreamWriter sw = File.CreateText(destPath)) { + StringBuilder line = new StringBuilder(); + while (!sr.EndOfStream) { + line.Length = 0; + line.Append(sr.ReadLine()); + for (int j = 0; j < beforeTokens.Length; j++) { + line.Replace(beforeTokens[j], afterTokens[j]); + } + + sw.WriteLine(line); + } + } + } + + copiedFiles.Add(this.DestinationFiles[i]); + } + + this.CopiedFiles = copiedFiles.ToArray(); + return true; + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/CreateWebApplication.cs b/src/DotNetOpenAuth.BuildTasks/CreateWebApplication.cs new file mode 100644 index 0000000..4980898 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/CreateWebApplication.cs @@ -0,0 +1,95 @@ +//----------------------------------------------------------------------- +// <copyright file="CreateWebApplication.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.BuildTasks { + using System; + using System.Linq; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + using Microsoft.Web.Administration; + + /// <summary> + /// Creates or updates web applications within an existing web site in IIS. + /// </summary> + public class CreateWebApplication : Task { + /// <summary> + /// Gets or sets the name of the application pool that should host the web application. + /// </summary> + /// <value>The name of an existing application pool.</value> + public string ApplicationPoolName { get; set; } + + /// <summary> + /// Gets or sets the name of the web site under which to create the web application. + /// </summary> + /// <value>The name of the existing web site.</value> + [Required] + public string WebSiteName { get; set; } + + /// <summary> + /// Gets or sets the virtual paths within the web site that will access these applications. + /// </summary> + /// <value>The virtual path, which must start with '/'.</value> + [Required] + public ITaskItem[] VirtualPaths { get; set; } + + /// <summary> + /// Gets or sets the full file system paths to the web applications. + /// </summary> + /// <value>The physical path.</value> + [Required] + public ITaskItem[] PhysicalPaths { get; set; } + + /// <summary> + /// Executes this instance. + /// </summary> + /// <returns>A value indicating whether the task completed successfully.</returns> + public override bool Execute() { + var serverManager = new ServerManager(); + + if (this.PhysicalPaths.Length != this.VirtualPaths.Length) { + Log.LogError(TaskStrings.MismatchingArrayLengths, "PhysicalPath", "VirtualPath"); + return false; + } + + if (this.VirtualPaths.Length == 0) { + // Nothing to do. + return true; + } + + // Find the root web site that this web application will be created under. + var site = serverManager.Sites.FirstOrDefault(s => string.Equals(s.Name, this.WebSiteName, StringComparison.OrdinalIgnoreCase)); + if (site == null) { + Log.LogError(TaskStrings.NoMatchingWebSiteFound, this.WebSiteName); + return false; + } + + Log.LogMessage(MessageImportance.Normal, "Creating web applications under web site: {0}", site.Name); + + for (int i = 0; i < this.PhysicalPaths.Length; i++) { + string physicalPath = this.PhysicalPaths[i].ItemSpec; + string virtualPath = this.VirtualPaths[i].ItemSpec; + + Log.LogMessage(MessageImportance.Normal, "\t{0} -> {1}", virtualPath, physicalPath); + + var app = site.Applications.FirstOrDefault(a => string.Equals(a.Path, virtualPath, StringComparison.OrdinalIgnoreCase)); + if (app == null) { + app = site.Applications.Add(virtualPath, physicalPath); + } else { + // Ensure physical path is set correctly. + var appRoot = app.VirtualDirectories.First(vd => vd.Path == "/"); + appRoot.PhysicalPath = physicalPath; + } + + if (!string.IsNullOrEmpty(this.ApplicationPoolName)) { + app.ApplicationPoolName = this.ApplicationPoolName; + } + } + + serverManager.CommitChanges(); + return true; + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/DeleteWebApplication.cs b/src/DotNetOpenAuth.BuildTasks/DeleteWebApplication.cs new file mode 100644 index 0000000..930a8c4 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/DeleteWebApplication.cs @@ -0,0 +1,66 @@ +//----------------------------------------------------------------------- +// <copyright file="DeleteWebApplication.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.BuildTasks { + using System; + using System.Linq; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + using Microsoft.Web.Administration; + + /// <summary> + /// Deletes a web application from IIS. + /// </summary> + public class DeleteWebApplication : Task { + /// <summary> + /// Gets or sets the name of the web site under which to create the web application. + /// </summary> + /// <value>The name of the existing web site.</value> + [Required] + public string WebSiteName { get; set; } + + /// <summary> + /// Gets or sets the virtual paths within the web site that will access these applications. + /// </summary> + /// <value>The virtual path, which must start with '/'.</value> + [Required] + public ITaskItem[] VirtualPaths { get; set; } + + /// <summary> + /// Executes this instance. + /// </summary> + /// <returns>A value indicating whether the task completed successfully.</returns> + public override bool Execute() { + var serverManager = new ServerManager(); + + // Find the root web site that this web application will be created under. + var site = serverManager.Sites.FirstOrDefault(s => string.Equals(s.Name, this.WebSiteName, StringComparison.OrdinalIgnoreCase)); + if (site == null) { + Log.LogMessage(MessageImportance.Low, TaskStrings.NoMatchingWebSiteFound, this.WebSiteName); + return true; + } + + if (this.VirtualPaths.Length == 0) { + // Nothing to do. + return true; + } + + foreach (ITaskItem path in this.VirtualPaths) { + var app = site.Applications.FirstOrDefault(a => string.Equals(a.Path, path.ItemSpec, StringComparison.OrdinalIgnoreCase)); + if (app != null) { + site.Applications.Remove(app); + Log.LogMessage(MessageImportance.Normal, TaskStrings.DeletedWebApplication, app.Path); + } else { + Log.LogMessage(MessageImportance.Low, TaskStrings.WebApplicationNotFoundSoNotDeleted, path.ItemSpec); + } + } + + serverManager.CommitChanges(); + + return true; + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/DiscoverProjectTemplates.cs b/src/DotNetOpenAuth.BuildTasks/DiscoverProjectTemplates.cs new file mode 100644 index 0000000..0162c16 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/DiscoverProjectTemplates.cs @@ -0,0 +1,63 @@ +//----------------------------------------------------------------------- +// <copyright file="DiscoverProjectTemplates.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.BuildTasks { + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text; + using System.Xml.Linq; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + + public class DiscoverProjectTemplates : Task { + public ITaskItem[] TopLevelTemplates { get; set; } + + [Output] + public ITaskItem[] ProjectTemplates { get; set; } + + [Output] + public ITaskItem[] ProjectTemplateContents { get; set; } + + /// <summary> + /// Executes this instance. + /// </summary> + public override bool Execute() { + List<ITaskItem> projectTemplates = new List<ITaskItem>(); + List<ITaskItem> projectTemplateContents = new List<ITaskItem>(); + foreach (ITaskItem topLevelTemplate in this.TopLevelTemplates) { + var vsTemplate = XElement.Load(topLevelTemplate.ItemSpec); + var templateContent = vsTemplate.Element(XName.Get("TemplateContent", MergeProjectWithVSTemplate.VSTemplateNamespace)); + var projectCollection = templateContent.Element(XName.Get("ProjectCollection", MergeProjectWithVSTemplate.VSTemplateNamespace)); + var links = projectCollection.Elements(XName.Get("ProjectTemplateLink", MergeProjectWithVSTemplate.VSTemplateNamespace)); + var subTemplates = links.Select( + link => (ITaskItem)new TaskItem( + link.Value, + new Dictionary<string, string> { + { "TopLevelTemplate", topLevelTemplate.ItemSpec }, + { "TopLevelTemplateFileName", Path.GetFileNameWithoutExtension(topLevelTemplate.ItemSpec) }, + })); + projectTemplates.AddRange(subTemplates); + + foreach (var link in links.Select(link => link.Value)) { + string[] files = Directory.GetFiles(Path.Combine(Path.GetDirectoryName(topLevelTemplate.ItemSpec), Path.GetDirectoryName(link)), "*.*", SearchOption.AllDirectories); + projectTemplateContents.AddRange(files.Select(file => (ITaskItem)new TaskItem( + file, + new Dictionary<string, string> { + { "TopLevelTemplate", topLevelTemplate.ItemSpec }, + { "TopLevelTemplateFileName", Path.GetFileNameWithoutExtension(topLevelTemplate.ItemSpec) }, + }))); + } + } + + this.ProjectTemplates = projectTemplates.ToArray(); + this.ProjectTemplateContents = projectTemplateContents.ToArray(); + + return !this.Log.HasLoggedErrors; + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/DotNetOpenAuth.BuildTasks.csproj b/src/DotNetOpenAuth.BuildTasks/DotNetOpenAuth.BuildTasks.csproj new file mode 100644 index 0000000..5de32e7 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/DotNetOpenAuth.BuildTasks.csproj @@ -0,0 +1,128 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProductVersion>9.0.30729</ProductVersion> + <SchemaVersion>2.0</SchemaVersion> + <ProjectGuid>{AC231A51-EF60-437C-A33F-AF8ADEB8EB74}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>DotNetOpenAuth.BuildTasks</RootNamespace> + <AssemblyName>DotNetOpenAuth.BuildTasks</AssemblyName> + <TargetFrameworkVersion>v3.5</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>..\..\lib\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <CodeContractsEnableRuntimeChecking>True</CodeContractsEnableRuntimeChecking> + <CodeContractsRuntimeOnlyPublicSurface>False</CodeContractsRuntimeOnlyPublicSurface> + <CodeContractsRuntimeThrowOnFailure>False</CodeContractsRuntimeThrowOnFailure> + <CodeContractsRuntimeCallSiteRequires>False</CodeContractsRuntimeCallSiteRequires> + <CodeContractsRunCodeAnalysis>False</CodeContractsRunCodeAnalysis> + <CodeContractsNonNullObligations>False</CodeContractsNonNullObligations> + <CodeContractsBoundsObligations>False</CodeContractsBoundsObligations> + <CodeContractsArithmeticObligations>False</CodeContractsArithmeticObligations> + <CodeContractsRedundantAssumptions>False</CodeContractsRedundantAssumptions> + <CodeContractsRunInBackground>True</CodeContractsRunInBackground> + <CodeContractsShowSquigglies>False</CodeContractsShowSquigglies> + <CodeContractsUseBaseLine>False</CodeContractsUseBaseLine> + <CodeContractsEmitXMLDocs>False</CodeContractsEmitXMLDocs> + <CodeContractsCustomRewriterAssembly> + </CodeContractsCustomRewriterAssembly> + <CodeContractsCustomRewriterClass> + </CodeContractsCustomRewriterClass> + <CodeContractsLibPaths> + </CodeContractsLibPaths> + <CodeContractsPlatformPath> + </CodeContractsPlatformPath> + <CodeContractsExtraAnalysisOptions> + </CodeContractsExtraAnalysisOptions> + <CodeContractsBaseLineFile> + </CodeContractsBaseLineFile> + <CodeContractsRuntimeCheckingLevel>Full</CodeContractsRuntimeCheckingLevel> + <CodeContractsReferenceAssembly>%28none%29</CodeContractsReferenceAssembly> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>..\..\lib\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <Reference Include="Microsoft.Build.Engine" /> + <Reference Include="Microsoft.Build.Framework" /> + <Reference Include="Microsoft.Build.Utilities.v3.5"> + <RequiredTargetFramework>3.5</RequiredTargetFramework> + </Reference> + <Reference Include="Microsoft.Contracts, Version=1.0.0.0, Culture=neutral, PublicKeyToken=736440c9b414ea16, processorArchitecture=MSIL"> + <Private>False</Private> + </Reference> + <Reference Include="Microsoft.Web.Administration, Version=7.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>$(SystemRoot)\System32\inetsrv\Microsoft.Web.Administration.dll</HintPath> + </Reference> + <Reference Include="System" /> + <Reference Include="System.Core"> + <RequiredTargetFramework>3.5</RequiredTargetFramework> + </Reference> + <Reference Include="System.Data" /> + <Reference Include="System.Xml" /> + <Reference Include="System.Xml.Linq"> + <RequiredTargetFramework>3.5</RequiredTargetFramework> + </Reference> + </ItemGroup> + <ItemGroup> + <Compile Include="AddProjectItems.cs" /> + <Compile Include="ChangeProjectReferenceToAssemblyReference.cs" /> + <Compile Include="CompareFiles.cs" /> + <Compile Include="ChangeAssemblyReference.cs" /> + <Compile Include="CopyWithTokenSubstitution.cs" /> + <Compile Include="CreateWebApplication.cs" /> + <Compile Include="DeleteWebApplication.cs" /> + <Compile Include="DiscoverProjectTemplates.cs" /> + <Compile Include="ECMAScriptPacker.cs" /> + <Compile Include="FilterItems.cs" /> + <Compile Include="FixupReferenceHintPaths.cs" /> + <Compile Include="FixupShippingToolSamples.cs" /> + <Compile Include="MergeProjectWithVSTemplate.cs" /> + <Compile Include="GetBuildVersion.cs" /> + <Compile Include="CheckAdminRights.cs" /> + <Compile Include="JsPack.cs" /> + <Compile Include="ParseMaster.cs" /> + <Compile Include="Purge.cs" /> + <Compile Include="ReSignDelaySignedAssemblies.cs" /> + <Compile Include="SetEnvironmentVariable.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="SignatureVerification.cs" /> + <Compile Include="SnToolTask.cs" /> + <Compile Include="TaskStrings.Designer.cs"> + <AutoGen>True</AutoGen> + <DesignTime>True</DesignTime> + <DependentUpon>TaskStrings.resx</DependentUpon> + </Compile> + <Compile Include="Trim.cs" /> + </ItemGroup> + <ItemGroup> + <EmbeddedResource Include="TaskStrings.resx"> + <Generator>ResXFileCodeGenerator</Generator> + <LastGenOutput>TaskStrings.Designer.cs</LastGenOutput> + </EmbeddedResource> + </ItemGroup> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project>
\ No newline at end of file diff --git a/src/DotNetOpenAuth.BuildTasks/DotNetOpenAuth.BuildTasks.sln b/src/DotNetOpenAuth.BuildTasks/DotNetOpenAuth.BuildTasks.sln new file mode 100644 index 0000000..fca41e8 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/DotNetOpenAuth.BuildTasks.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetOpenAuth.BuildTasks", "DotNetOpenAuth.BuildTasks.csproj", "{AC231A51-EF60-437C-A33F-AF8ADEB8EB74}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{ABBE14A3-0404-4123-9093-E598C3DD3E9B}" + ProjectSection(SolutionItems) = preProject + ..\..\build.proj = ..\..\build.proj + ..\..\lib\DotNetOpenAuth.BuildTasks.targets = ..\..\lib\DotNetOpenAuth.BuildTasks.targets + ..\..\tools\DotNetOpenAuth.Common.Settings.targets = ..\..\tools\DotNetOpenAuth.Common.Settings.targets + ..\..\tools\DotNetOpenAuth.Versioning.targets = ..\..\tools\DotNetOpenAuth.Versioning.targets + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AC231A51-EF60-437C-A33F-AF8ADEB8EB74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AC231A51-EF60-437C-A33F-AF8ADEB8EB74}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AC231A51-EF60-437C-A33F-AF8ADEB8EB74}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AC231A51-EF60-437C-A33F-AF8ADEB8EB74}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/DotNetOpenAuth.BuildTasks/ECMAScriptPacker.cs b/src/DotNetOpenAuth.BuildTasks/ECMAScriptPacker.cs new file mode 100644 index 0000000..d63d5b4 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/ECMAScriptPacker.cs @@ -0,0 +1,486 @@ +using System; +using System.Text; +using System.Text.RegularExpressions; +using System.Collections; +using System.Collections.Specialized; +using System.Web; +using System.IO; + +/* + packer, version 2.0 (beta) (2005/02/01) + Copyright 2004-2005, Dean Edwards + Web: http://dean.edwards.name/ + + This software is licensed under the CC-GNU LGPL + Web: http://creativecommons.org/licenses/LGPL/2.1/ + + Ported to C# by Jesse Hansen, twindagger2k@msn.com + modified slightly by Andrew Arnott +*/ + +// http://dean.edwards.name/packer/ + +namespace Dean.Edwards +{ + /// <summary> + /// Packs a javascript file into a smaller area, removing unnecessary characters from the output. + /// </summary> + internal class ECMAScriptPacker + { + /// <summary> + /// The encoding level to use. See http://dean.edwards.name/packer/usage/ for more info. + /// </summary> + public enum PackerEncoding { None = 0, Numeric = 10, Mid = 36, Normal = 62, HighAscii = 95 }; + + private PackerEncoding encoding = PackerEncoding.Normal; + private bool fastDecode = true; + private bool specialChars = false; + private bool enabled = true; + + string IGNORE = "$1"; + + /// <summary> + /// The encoding level for this instance + /// </summary> + public PackerEncoding Encoding + { + get { return encoding; } + set { encoding = value; } + } + + /// <summary> + /// Adds a subroutine to the output to speed up decoding + /// </summary> + public bool FastDecode + { + get { return fastDecode; } + set { fastDecode = value; } + } + + /// <summary> + /// Replaces special characters + /// </summary> + public bool SpecialChars + { + get { return specialChars; } + set { specialChars = value; } + } + + /// <summary> + /// Packer enabled + /// </summary> + public bool Enabled + { + get { return enabled; } + set { enabled = value; } + } + + public ECMAScriptPacker() + { + Encoding = PackerEncoding.Normal; + FastDecode = true; + SpecialChars = false; + } + + /// <summary> + /// Constructor + /// </summary> + /// <param name="encoding">The encoding level for this instance</param> + /// <param name="fastDecode">Adds a subroutine to the output to speed up decoding</param> + /// <param name="specialChars">Replaces special characters</param> + public ECMAScriptPacker(PackerEncoding encoding, bool fastDecode, bool specialChars) + { + Encoding = encoding; + FastDecode = fastDecode; + SpecialChars = specialChars; + } + + /// <summary> + /// Packs the script + /// </summary> + /// <param name="script">the script to pack</param> + /// <returns>the packed script</returns> + public string Pack(string script) + { + if (enabled) + { + script += "\n"; + script = basicCompression(script); + if (SpecialChars) + script = encodeSpecialChars(script); + if (Encoding != PackerEncoding.None) + script = encodeKeywords(script); + } + return script; + } + + //zero encoding - just removal of whitespace and comments + private string basicCompression(string script) + { + ParseMaster parser = new ParseMaster(); + // make safe + parser.EscapeChar = '\\'; + // protect strings + parser.Add("'[^'\\n\\r]*'", IGNORE); + parser.Add("\"[^\"\\n\\r]*\"", IGNORE); + // remove comments + parser.Add("\\/\\/[^\\n\\r]*[\\n\\r]"); + parser.Add("\\/\\*[^*]*\\*+([^\\/][^*]*\\*+)*\\/"); + // protect regular expressions + parser.Add("\\s+(\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?)", "$2"); + parser.Add("[^\\w\\$\\/'\"*)\\?:]\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?", IGNORE); + // remove: ;;; doSomething(); + if (specialChars) + parser.Add(";;[^\\n\\r]+[\\n\\r]"); + // remove redundant semi-colons + parser.Add(";+\\s*([};])", "$2"); + // remove white-space + parser.Add("(\\b|\\$)\\s+(\\b|\\$)", "$2 $3"); + parser.Add("([+\\-])\\s+([+\\-])", "$2 $3"); + parser.Add("\\s+"); + // done + return parser.Exec(script); + } + + WordList encodingLookup; + private string encodeSpecialChars(string script) + { + ParseMaster parser = new ParseMaster(); + // replace: $name -> n, $$name -> na + parser.Add("((\\$+)([a-zA-Z\\$_]+))(\\d*)", + new ParseMaster.MatchGroupEvaluator(encodeLocalVars)); + + // replace: _name -> _0, double-underscore (__name) is ignored + Regex regex = new Regex("\\b_[A-Za-z\\d]\\w*"); + + // build the word list + encodingLookup = analyze(script, regex, new EncodeMethod(encodePrivate)); + + parser.Add("\\b_[A-Za-z\\d]\\w*", new ParseMaster.MatchGroupEvaluator(encodeWithLookup)); + + script = parser.Exec(script); + return script; + } + + private string encodeKeywords(string script) + { + // escape high-ascii values already in the script (i.e. in strings) + if (Encoding == PackerEncoding.HighAscii) script = escape95(script); + // create the parser + ParseMaster parser = new ParseMaster(); + EncodeMethod encode = getEncoder(Encoding); + + // for high-ascii, don't encode single character low-ascii + Regex regex = new Regex( + (Encoding == PackerEncoding.HighAscii) ? "\\w\\w+" : "\\w+" + ); + // build the word list + encodingLookup = analyze(script, regex, encode); + + // encode + parser.Add((Encoding == PackerEncoding.HighAscii) ? "\\w\\w+" : "\\w+", + new ParseMaster.MatchGroupEvaluator(encodeWithLookup)); + + // if encoded, wrap the script in a decoding function + return (script == string.Empty) ? "" : bootStrap(parser.Exec(script), encodingLookup); + } + + private string bootStrap(string packed, WordList keywords) + { + // packed: the packed script + packed = "'" + escape(packed) + "'"; + + // ascii: base for encoding + int ascii = Math.Min(keywords.Sorted.Count, (int) Encoding); + if (ascii == 0) + ascii = 1; + + // count: number of words contained in the script + int count = keywords.Sorted.Count; + + // keywords: list of words contained in the script + foreach (object key in keywords.Protected.Keys) + { + keywords.Sorted[(int) key] = ""; + } + // convert from a string to an array + StringBuilder sbKeywords = new StringBuilder("'"); + foreach (string word in keywords.Sorted) + sbKeywords.Append(word + "|"); + sbKeywords.Remove(sbKeywords.Length-1, 1); + string keywordsout = sbKeywords.ToString() + "'.split('|')"; + + string encode; + string inline = "c"; + + switch (Encoding) + { + case PackerEncoding.Mid: + encode = "function(c){return c.toString(36)}"; + inline += ".toString(a)"; + break; + case PackerEncoding.Normal: + encode = "function(c){return(c<a?\"\":e(parseInt(c/a)))+" + + "((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))}"; + inline += ".toString(a)"; + break; + case PackerEncoding.HighAscii: + encode = "function(c){return(c<a?\"\":e(c/a))+" + + "String.fromCharCode(c%a+161)}"; + inline += ".toString(a)"; + break; + default: + encode = "function(c){return c}"; + break; + } + + // decode: code snippet to speed up decoding + string decode = ""; + if (fastDecode) + { + decode = "if(!''.replace(/^/,String)){while(c--)d[e(c)]=k[c]||e(c);k=[function(e){return d[e]}];e=function(){return'\\\\w+'};c=1;}"; + if (Encoding == PackerEncoding.HighAscii) + decode = decode.Replace("\\\\w", "[\\xa1-\\xff]"); + else if (Encoding == PackerEncoding.Numeric) + decode = decode.Replace("e(c)", inline); + if (count == 0) + decode = decode.Replace("c=1", "c=0"); + } + + // boot function + string unpack = "function(p,a,c,k,e,d){while(c--)if(k[c])p=p.replace(new RegExp('\\\\b'+e(c)+'\\\\b','g'),k[c]);return p;}"; + Regex r; + if (fastDecode) + { + //insert the decoder + r = new Regex("\\{"); + unpack = r.Replace(unpack, "{" + decode + ";", 1); + } + + if (Encoding == PackerEncoding.HighAscii) + { + // get rid of the word-boundries for regexp matches + r = new Regex("'\\\\\\\\b'\\s*\\+|\\+\\s*'\\\\\\\\b'"); + unpack = r.Replace(unpack, ""); + } + if (Encoding == PackerEncoding.HighAscii || ascii > (int) PackerEncoding.Normal || fastDecode) + { + // insert the encode function + r = new Regex("\\{"); + unpack = r.Replace(unpack, "{e=" + encode + ";", 1); + } + else + { + r = new Regex("e\\(c\\)"); + unpack = r.Replace(unpack, inline); + } + // no need to pack the boot function since i've already done it + string _params = "" + packed + "," + ascii + "," + count + "," + keywordsout; + if (fastDecode) + { + //insert placeholders for the decoder + _params += ",0,{}"; + } + // the whole thing + return "eval(" + unpack + "(" + _params + "))\n"; + } + + private string escape(string input) + { + Regex r = new Regex("([\\\\'])"); + return r.Replace(input, "\\$1"); + } + + private EncodeMethod getEncoder(PackerEncoding encoding) + { + switch (encoding) + { + case PackerEncoding.Mid: + return new EncodeMethod(encode36); + case PackerEncoding.Normal: + return new EncodeMethod(encode62); + case PackerEncoding.HighAscii: + return new EncodeMethod(encode95); + default: + return new EncodeMethod(encode10); + } + } + + private string encode10(int code) + { + return code.ToString(); + } + + //lookups seemed like the easiest way to do this since + // I don't know of an equivalent to .toString(36) + private static string lookup36 = "0123456789abcdefghijklmnopqrstuvwxyz"; + + private string encode36(int code) + { + string encoded = ""; + int i = 0; + do + { + int digit = (code / (int) Math.Pow(36, i)) % 36; + encoded = lookup36[digit] + encoded; + code -= digit * (int) Math.Pow(36, i++); + } while (code > 0); + return encoded; + } + + private static string lookup62 = lookup36 + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + private string encode62(int code) + { + string encoded = ""; + int i = 0; + do + { + int digit = (code / (int) Math.Pow(62, i)) % 62; + encoded = lookup62[digit] + encoded; + code -= digit * (int) Math.Pow(62, i++); + } while (code > 0); + return encoded; + } + + private static string lookup95 = "¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"; + + private string encode95(int code) + { + string encoded = ""; + int i = 0; + do + { + int digit = (code / (int) Math.Pow(95, i)) % 95; + encoded = lookup95[digit] + encoded; + code -= digit * (int) Math.Pow(95, i++); + } while (code > 0); + return encoded; + } + + private string escape95(string input) + { + Regex r = new Regex("[\xa1-\xff]"); + return r.Replace(input, new MatchEvaluator(escape95Eval)); + } + + private string escape95Eval(Match match) + { + return "\\x" + ((int) match.Value[0]).ToString("x"); //return hexadecimal value + } + + private string encodeLocalVars(Match match, int offset) + { + int length = match.Groups[offset + 2].Length; + int start = length - Math.Max(length - match.Groups[offset + 3].Length, 0); + return match.Groups[offset + 1].Value.Substring(start, length) + + match.Groups[offset + 4].Value; + } + + private string encodeWithLookup(Match match, int offset) + { + return (string) encodingLookup.Encoded[match.Groups[offset].Value]; + } + + private delegate string EncodeMethod(int code); + + private string encodePrivate(int code) + { + return "_" + code; + } + + private WordList analyze(string input, Regex regex, EncodeMethod encodeMethod) + { + // analyse + // retreive all words in the script + MatchCollection all = regex.Matches(input); + WordList rtrn; + rtrn.Sorted = new StringCollection(); // list of words sorted by frequency + rtrn.Protected = new HybridDictionary(); // dictionary of word->encoding + rtrn.Encoded = new HybridDictionary(); // instances of "protected" words + if (all.Count > 0) + { + StringCollection unsorted = new StringCollection(); // same list, not sorted + HybridDictionary Protected = new HybridDictionary(); // "protected" words (dictionary of word->"word") + HybridDictionary values = new HybridDictionary(); // dictionary of charCode->encoding (eg. 256->ff) + HybridDictionary count = new HybridDictionary(); // word->count + int i = all.Count, j = 0; + string word; + // count the occurrences - used for sorting later + do + { + word = "$" + all[--i].Value; + if (count[word] == null) + { + count[word] = 0; + unsorted.Add(word); + // make a dictionary of all of the protected words in this script + // these are words that might be mistaken for encoding + Protected["$" + (values[j] = encodeMethod(j))] = j++; + } + // increment the word counter + count[word] = (int) count[word] + 1; + } while (i > 0); + /* prepare to sort the word list, first we must protect + words that are also used as codes. we assign them a code + equivalent to the word itself. + e.g. if "do" falls within our encoding range + then we store keywords["do"] = "do"; + this avoids problems when decoding */ + i = unsorted.Count; + string[] sortedarr = new string[unsorted.Count]; + do + { + word = unsorted[--i]; + if (Protected[word] != null) + { + sortedarr[(int) Protected[word]] = word.Substring(1); + rtrn.Protected[(int) Protected[word]] = true; + count[word] = 0; + } + } while (i > 0); + string[] unsortedarr = new string[unsorted.Count]; + unsorted.CopyTo(unsortedarr, 0); + // sort the words by frequency + Array.Sort(unsortedarr, (IComparer) new CountComparer(count)); + j = 0; + /*because there are "protected" words in the list + we must add the sorted words around them */ + do + { + if (sortedarr[i] == null) + sortedarr[i] = unsortedarr[j++].Substring(1); + rtrn.Encoded[sortedarr[i]] = values[i]; + } while (++i < unsortedarr.Length); + rtrn.Sorted.AddRange(sortedarr); + } + return rtrn; + } + + private struct WordList + { + public StringCollection Sorted; + public HybridDictionary Encoded; + public HybridDictionary Protected; + } + + private class CountComparer : IComparer + { + HybridDictionary count; + + public CountComparer(HybridDictionary count) + { + this.count = count; + } + + #region IComparer Members + + public int Compare(object x, object y) + { + return (int) count[y] - (int) count[x]; + } + + #endregion + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/FilterItems.cs b/src/DotNetOpenAuth.BuildTasks/FilterItems.cs new file mode 100644 index 0000000..97631c6 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/FilterItems.cs @@ -0,0 +1,24 @@ +namespace DotNetOpenAuth.BuildTasks { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using Microsoft.Build.Utilities; + using Microsoft.Build.Framework; + + public class FilterItems : Task { + [Required] + public ITaskItem[] InputItems { get; set; } + + [Required] + public ITaskItem[] StartsWithAny { get; set; } + + [Output] + public ITaskItem[] FilteredItems { get; set; } + + public override bool Execute() { + FilteredItems = InputItems.Where(item => StartsWithAny.Any(filter => item.ItemSpec.StartsWith(filter.ItemSpec, StringComparison.OrdinalIgnoreCase))).ToArray(); + return true; + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/FixupReferenceHintPaths.cs b/src/DotNetOpenAuth.BuildTasks/FixupReferenceHintPaths.cs new file mode 100644 index 0000000..13a4b8f --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/FixupReferenceHintPaths.cs @@ -0,0 +1,71 @@ +//----------------------------------------------------------------------- +// <copyright file="FixupReferenceHintPaths.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.BuildTasks { + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Reflection; + using System.Text; + using Microsoft.Build.BuildEngine; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + + public class FixupReferenceHintPaths : Task { + /// <summary> + /// Gets or sets the projects to fixup references for. + /// </summary> + [Required] + public ITaskItem[] Projects { get; set; } + + /// <summary> + /// Gets or sets the set of full paths to assemblies that may be found in any of the <see cref="Projects"/>. + /// </summary> + [Required] + public ITaskItem[] References { get; set; } + + /// <summary> + /// Executes this instance. + /// </summary> + public override bool Execute() { + if (this.References.Length == 0 || this.Projects.Length == 0) { + this.Log.LogMessage(MessageImportance.Low, "Skipping reference hintpath fixup because no projects or no references were supplied."); + return !this.Log.HasLoggedErrors; + } + + // Figure out what the assembly names are of the references that are available. + AssemblyName[] availableReferences = new AssemblyName[this.References.Length]; + for (int i = 0; i < this.References.Length; i++) { + availableReferences[i] = AssemblyName.GetAssemblyName(this.References[i].ItemSpec); + } + + foreach (var projectTaskItem in this.Projects) { + var project = new Project(); + Uri projectUri = new Uri(projectTaskItem.GetMetadata("FullPath")); + project.Load(projectTaskItem.ItemSpec); + + foreach (BuildItem referenceItem in project.GetEvaluatedItemsByName("Reference")) { + var referenceAssemblyName = new AssemblyName(referenceItem.Include); + var matchingReference = availableReferences.FirstOrDefault(r => string.Equals(r.Name, referenceAssemblyName.Name, StringComparison.OrdinalIgnoreCase)); + if (matchingReference != null) { + var originalSuppliedReferenceItem = this.References[Array.IndexOf(availableReferences, matchingReference)]; + string hintPath = originalSuppliedReferenceItem.GetMetadata("HintPath"); + if (string.IsNullOrEmpty(hintPath)) { + hintPath = projectUri.MakeRelativeUri(new Uri(matchingReference.CodeBase)).OriginalString.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); + } + this.Log.LogMessage("Fixing up HintPath to \"{0}\" in project \"{1}\".", referenceAssemblyName.Name, projectTaskItem.ItemSpec); + referenceItem.SetMetadata("HintPath", hintPath); + } + } + + project.Save(projectTaskItem.ItemSpec); + } + + return !this.Log.HasLoggedErrors; + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/FixupShippingToolSamples.cs b/src/DotNetOpenAuth.BuildTasks/FixupShippingToolSamples.cs new file mode 100644 index 0000000..92b0235 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/FixupShippingToolSamples.cs @@ -0,0 +1,48 @@ +//----------------------------------------------------------------------- +// <copyright file="FixupShippingToolSamples.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.BuildTasks { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using Microsoft.Build.Utilities; + using Microsoft.Build.Framework; + using System.IO; + using Microsoft.Build.BuildEngine; + + /// <summary> + /// Removes imports that only apply when a shipping tool sample builds as part of + /// the entire project, but not when it's part of a source code sample. + /// </summary> + public class FixupShippingToolSamples : Task { + [Required] + public ITaskItem[] Projects { get; set; } + + /// <summary> + /// Executes this instance. + /// </summary> + /// <returns></returns> + public override bool Execute() { + foreach (ITaskItem projectTaskItem in this.Projects) { + this.Log.LogMessage("Fixing up the {0} sample for shipping as source code.", Path.GetFileNameWithoutExtension(projectTaskItem.ItemSpec)); + + var project = new Project(); + Uri projectUri = new Uri(projectTaskItem.GetMetadata("FullPath")); + project.Load(projectTaskItem.ItemSpec, ProjectLoadSettings.IgnoreMissingImports); + + project.Imports.Cast<Import>() + .Where(import => import.ProjectPath.StartsWith(@"..\..\tools\", StringComparison.OrdinalIgnoreCase)) + .ToList() + .ForEach(import => project.Imports.RemoveImport(import)); + + project.Save(projectTaskItem.ItemSpec); + } + + return !this.Log.HasLoggedErrors; + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/GetBuildVersion.cs b/src/DotNetOpenAuth.BuildTasks/GetBuildVersion.cs new file mode 100644 index 0000000..1d60ca4 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/GetBuildVersion.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace DotNetOpenAuth.BuildTasks { + public class GetBuildVersion : Task { + + /// <summary> + /// Gets the version string to use in the compiled assemblies. + /// </summary> + [Output] + public string Version { get; private set; } + + /// <summary> + /// Gets the Git revision control commit id for HEAD (the current source code version). + /// </summary> + [Output] + public string GitCommitId { get; private set; } + + /// <summary> + /// The file that contains the version base (Major.Minor.Build) to use. + /// </summary> + [Required] + public string VersionFile { get; set; } + + /// <summary> + /// Gets or sets the parent directory of the .git directory. + /// </summary> + public string GitRepoRoot { get; set; } + + public override bool Execute() { + try { + Version typedVersion = ReadVersionFromFile(); + typedVersion = new Version(typedVersion.Major, typedVersion.Minor, typedVersion.Build, CalculateJDate(DateTime.Now)); + Version = typedVersion.ToString(); + + this.GitCommitId = GetGitHeadCommitId(); + } catch (ArgumentOutOfRangeException ex) { + Log.LogErrorFromException(ex); + return false; + } + + return true; + } + + private string GetGitHeadCommitId() { + if (string.IsNullOrEmpty(this.GitRepoRoot)) { + return string.Empty; + } + + string commitId = string.Empty; + + // First try asking Git for the HEAD commit id + try { + string cmdPath = Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.System), "cmd.exe"); + ProcessStartInfo psi = new ProcessStartInfo(cmdPath, "/c git rev-parse HEAD"); + psi.WindowStyle = ProcessWindowStyle.Hidden; + psi.CreateNoWindow = true; + psi.RedirectStandardOutput = true; + psi.UseShellExecute = false; + Process git = Process.Start(psi); + commitId = git.StandardOutput.ReadLine(); + git.WaitForExit(); + if (git.ExitCode != 0) { + commitId = string.Empty; + } + if (commitId != null) { + commitId = commitId.Trim(); + if (commitId.Length == 40) { + return commitId; + } + } + } catch (InvalidOperationException) { + } catch (Win32Exception) { + } + + // Failing being able to use the git command to figure out the HEAD commit ID, try the filesystem directly. + try { + string headContent = File.ReadAllText(Path.Combine(this.GitRepoRoot, @".git/HEAD")).Trim(); + if (headContent.StartsWith("ref:", StringComparison.Ordinal)) { + string refName = headContent.Substring(5).Trim(); + string refPath = Path.Combine(this.GitRepoRoot, ".git/" + refName); + if (File.Exists(refPath)) { + commitId = File.ReadAllText(refPath).Trim(); + } else { + string packedRefPath = Path.Combine(this.GitRepoRoot, ".git/packed-refs"); + string matchingLine = File.ReadAllLines(packedRefPath).FirstOrDefault(line => line.EndsWith(refName)); + if (matchingLine != null) { + commitId = matchingLine.Substring(0, matchingLine.IndexOf(' ')); + } + } + } else { + commitId = headContent; + } + } catch (FileNotFoundException) { + } catch (DirectoryNotFoundException) { + } + + return commitId.Trim(); + } + + private Version ReadVersionFromFile() { + string[] lines = File.ReadAllLines(VersionFile); + string versionLine = lines[0]; + return new Version(versionLine); + } + + private int CalculateJDate(DateTime date) { + int yearLastDigit = date.Year - 2000; // can actually be two digits in or after 2010 + DateTime firstOfYear = new DateTime(date.Year, 1, 1); + int dayOfYear = (date - firstOfYear).Days + 1; + int jdate = yearLastDigit * 1000 + dayOfYear; + return jdate; + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/JsPack.cs b/src/DotNetOpenAuth.BuildTasks/JsPack.cs new file mode 100644 index 0000000..fa8c7f0 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/JsPack.cs @@ -0,0 +1,75 @@ +//----------------------------------------------------------------------- +// <copyright file="JsPack.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.BuildTasks { + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + + /// <summary> + /// Compresses .js files. + /// </summary> + public class JsPack : Task { + /// <summary> + /// The Javascript packer to use. + /// </summary> + private Dean.Edwards.ECMAScriptPacker packer = new Dean.Edwards.ECMAScriptPacker(); + + /// <summary> + /// Gets or sets the set of javascript files to compress. + /// </summary> + /// <value>The inputs.</value> + [Required] + public ITaskItem[] Inputs { get; set; } + + /// <summary> + /// Gets or sets the paths where the packed javascript files should be saved. + /// </summary> + /// <value>The outputs.</value> + [Required] + public ITaskItem[] Outputs { get; set; } + + /// <summary> + /// Executes this instance. + /// </summary> + /// <returns>A value indicating whether the packing was successful.</returns> + public override bool Execute() { + if (this.Inputs.Length != this.Outputs.Length) { + Log.LogError("{0} inputs and {1} outputs given.", this.Inputs.Length, this.Outputs.Length); + return false; + } + + for (int i = 0; i < this.Inputs.Length; i++) { + if (!File.Exists(this.Outputs[i].ItemSpec) || File.GetLastWriteTime(this.Outputs[i].ItemSpec) < File.GetLastWriteTime(this.Inputs[i].ItemSpec)) { + Log.LogMessage(MessageImportance.Normal, TaskStrings.PackingJsFile, this.Inputs[i].ItemSpec, this.Outputs[i].ItemSpec); + string input = File.ReadAllText(this.Inputs[i].ItemSpec); + string output = this.packer.Pack(input); + if (!Directory.Exists(Path.GetDirectoryName(this.Outputs[i].ItemSpec))) { + Directory.CreateDirectory(Path.GetDirectoryName(this.Outputs[i].ItemSpec)); + } + + // Minification removes all comments, including copyright notices + // that must remain. So if there's metadata on this item with + // a copyright notice on it, stick it on the front of the file. + string copyright = this.Inputs[i].GetMetadata("Copyright"); + if (!string.IsNullOrEmpty(copyright)) { + output = "/*" + copyright + "*/" + output; + } + + File.WriteAllText(this.Outputs[i].ItemSpec, output, Encoding.UTF8); + } else { + Log.LogMessage(MessageImportance.Low, TaskStrings.SkipPackingJsFile, this.Inputs[i].ItemSpec); + } + } + + return true; + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/MergeProjectWithVSTemplate.cs b/src/DotNetOpenAuth.BuildTasks/MergeProjectWithVSTemplate.cs new file mode 100644 index 0000000..1a8a17d --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/MergeProjectWithVSTemplate.cs @@ -0,0 +1,103 @@ +//----------------------------------------------------------------------- +// <copyright file="MergeProjectWithVSTemplate.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.BuildTasks { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + using System.Xml.Linq; + using System.IO; + using Microsoft.Build.BuildEngine; + using System.Diagnostics.Contracts; + + public class MergeProjectWithVSTemplate : Task { + internal const string VSTemplateNamespace = "http://schemas.microsoft.com/developer/vstemplate/2005"; + + [Required] + public string[] ProjectItemTypes { get; set; } + + [Required] + public string[] ReplaceParametersExtensions { get; set; } + + [Required] + public ITaskItem[] Templates { get; set; } + + /// <summary> + /// Executes this instance. + /// </summary> + public override bool Execute() { + foreach(ITaskItem sourceTemplateTaskItem in this.Templates) { + var template = XElement.Load(sourceTemplateTaskItem.ItemSpec); + var templateContentElement = template.Element(XName.Get("TemplateContent", VSTemplateNamespace)); + var projectElement = templateContentElement.Element(XName.Get("Project", VSTemplateNamespace)); + if (projectElement == null) { + Log.LogMessage("Skipping merge of \"{0}\" with a project because no project was referenced from the template.", sourceTemplateTaskItem.ItemSpec); + continue; + } + + var projectPath = Path.Combine(Path.GetDirectoryName(sourceTemplateTaskItem.ItemSpec), projectElement.Attribute("File").Value); + Log.LogMessage("Merging project \"{0}\" with \"{1}\".", projectPath, sourceTemplateTaskItem.ItemSpec); + var sourceProject = new Project(); + sourceProject.Load(projectPath); + + // Collect the project items from the project that are appropriate + // to include in the .vstemplate file. + var itemsByFolder = from item in sourceProject.EvaluatedItems.Cast<BuildItem>() + where this.ProjectItemTypes.Contains(item.Name) + orderby item.Include + group item by Path.GetDirectoryName(item.Include); + foreach (var folder in itemsByFolder) { + XElement parentNode = FindOrCreateParent(folder.Key, projectElement); + + foreach (var item in folder) { + bool replaceParameters = this.ReplaceParametersExtensions.Contains(Path.GetExtension(item.Include)); + var itemName = XName.Get("ProjectItem", VSTemplateNamespace); + var projectItem = parentNode.Elements(itemName).FirstOrDefault(el => string.Equals(el.Value, Path.GetFileName(item.Include), StringComparison.OrdinalIgnoreCase)); + if (projectItem == null) { + projectItem = new XElement(itemName, Path.GetFileName(item.Include)); + parentNode.Add(projectItem); + } + if (replaceParameters) { + projectItem.SetAttributeValue("ReplaceParameters", "true"); + } + } + } + + template.Save(sourceTemplateTaskItem.ItemSpec); + } + + return !Log.HasLoggedErrors; + } + + private static XElement FindOrCreateParent(string directoryName, XElement projectElement) { + Contract.Requires<ArgumentNullException>(projectElement != null); + + if (string.IsNullOrEmpty(directoryName)) { + return projectElement; + } + + string[] segments = directoryName.Split(Path.DirectorySeparatorChar); + XElement parent = projectElement; + for (int i = 0; i < segments.Length; i++) { + var candidateName = XName.Get("Folder", VSTemplateNamespace); + var candidate = parent.Elements(XName.Get("Folder", VSTemplateNamespace)).FirstOrDefault(n => n.Attribute("Name").Value == segments[i]); + if (candidate == null) { + candidate = new XElement( + candidateName, + new XAttribute("Name", segments[i])); + parent.Add(candidate); + } + + parent = candidate; + } + + return parent; + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/ParseMaster.cs b/src/DotNetOpenAuth.BuildTasks/ParseMaster.cs new file mode 100644 index 0000000..7edba29 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/ParseMaster.cs @@ -0,0 +1,250 @@ +using System; +using System.Text; +using System.Text.RegularExpressions; +using System.Collections; +using System.Collections.Specialized; + +/* + ParseMaster, version 1.0 (pre-release) (2005/02/01) x4 + Copyright 2005, Dean Edwards + Web: http://dean.edwards.name/ + + This software is licensed under the CC-GNU LGPL + Web: http://creativecommons.org/licenses/LGPL/2.1/ + + Ported to C# by Jesse Hansen, twindagger2k@msn.com +*/ + +namespace Dean.Edwards +{ + /// <summary> + /// a multi-pattern parser + /// </summary> + internal class ParseMaster + { + // used to determine nesting levels + Regex GROUPS = new Regex("\\("), + SUB_REPLACE = new Regex("\\$"), + INDEXED = new Regex("^\\$\\d+$"), + ESCAPE = new Regex("\\\\."), + QUOTE = new Regex("'"), + DELETED = new Regex("\\x01[^\\x01]*\\x01"); + + /// <summary> + /// Delegate to call when a regular expression is found. + /// Use match.Groups[offset + <group number>].Value to get + /// the correct subexpression + /// </summary> + public delegate string MatchGroupEvaluator(Match match, int offset); + + private string DELETE(Match match, int offset) + { + return "\x01" + match.Groups[offset].Value + "\x01"; + } + + private bool ignoreCase = false; + private char escapeChar = '\0'; + + /// <summary> + /// Ignore Case? + /// </summary> + public bool IgnoreCase + { + get { return ignoreCase; } + set { ignoreCase = value; } + } + + /// <summary> + /// Escape Character to use + /// </summary> + public char EscapeChar + { + get { return escapeChar; } + set { escapeChar = value; } + } + + /// <summary> + /// Add an expression to be deleted + /// </summary> + /// <param name="expression">Regular Expression String</param> + public void Add(string expression) + { + Add(expression, string.Empty); + } + + /// <summary> + /// Add an expression to be replaced with the replacement string + /// </summary> + /// <param name="expression">Regular Expression String</param> + /// <param name="replacement">Replacement String. Use $1, $2, etc. for groups</param> + public void Add(string expression, string replacement) + { + if (replacement == string.Empty) + add(expression, new MatchGroupEvaluator(DELETE)); + + add(expression, replacement); + } + + /// <summary> + /// Add an expression to be replaced using a callback function + /// </summary> + /// <param name="expression">Regular expression string</param> + /// <param name="replacement">Callback function</param> + public void Add(string expression, MatchGroupEvaluator replacement) + { + add(expression, replacement); + } + + /// <summary> + /// Executes the parser + /// </summary> + /// <param name="input">input string</param> + /// <returns>parsed string</returns> + public string Exec(string input) + { + return DELETED.Replace(unescape(getPatterns().Replace(escape(input), new MatchEvaluator(replacement))), string.Empty); + //long way for debugging + /*input = escape(input); + Regex patterns = getPatterns(); + input = patterns.Replace(input, new MatchEvaluator(replacement)); + input = DELETED.Replace(input, string.Empty); + return input;*/ + } + + ArrayList patterns = new ArrayList(); + private void add(string expression, object replacement) + { + Pattern pattern = new Pattern(); + pattern.expression = expression; + pattern.replacement = replacement; + //count the number of sub-expressions + // - add 1 because each group is itself a sub-expression + pattern.length = GROUPS.Matches(internalEscape(expression)).Count + 1; + + //does the pattern deal with sup-expressions? + if (replacement is string && SUB_REPLACE.IsMatch((string) replacement)) + { + string sreplacement = (string) replacement; + // a simple lookup (e.g. $2) + if (INDEXED.IsMatch(sreplacement)) + { + pattern.replacement = int.Parse(sreplacement.Substring(1)) - 1; + } + } + + patterns.Add(pattern); + } + + /// <summary> + /// builds the patterns into a single regular expression + /// </summary> + /// <returns></returns> + private Regex getPatterns() + { + StringBuilder rtrn = new StringBuilder(string.Empty); + foreach (object pattern in patterns) + { + rtrn.Append(((Pattern) pattern).ToString() + "|"); + } + rtrn.Remove(rtrn.Length - 1, 1); + return new Regex(rtrn.ToString(), ignoreCase ? RegexOptions.IgnoreCase : RegexOptions.None ); + } + + /// <summary> + /// Global replacement function. Called once for each match found + /// </summary> + /// <param name="match">Match found</param> + private string replacement(Match match) + { + int i = 1, j = 0; + Pattern pattern; + //loop through the patterns + while (!((pattern = (Pattern) patterns[j++]) == null)) + { + //do we have a result? + if (match.Groups[i].Value != string.Empty) + { + object replacement = pattern.replacement; + if (replacement is MatchGroupEvaluator) + { + return ((MatchGroupEvaluator) replacement)(match, i); + } + else if (replacement is int) + { + return match.Groups[(int) replacement + i].Value; + } + else + { + //string, send to interpreter + return replacementString(match, i, (string) replacement, pattern.length); + } + } + else //skip over references to sub-expressions + i += pattern.length; + } + return match.Value; //should never be hit, but you never know + } + + /// <summary> + /// Replacement function for complicated lookups (e.g. Hello $3 $2) + /// </summary> + private string replacementString(Match match, int offset, string replacement, int length) + { + while (length > 0) + { + replacement = replacement.Replace("$" + length--, match.Groups[offset + length].Value); + } + return replacement; + } + + private StringCollection escaped = new StringCollection(); + + //encode escaped characters + private string escape(string str) + { + if (escapeChar == '\0') + return str; + Regex escaping = new Regex("\\\\(.)"); + return escaping.Replace(str, new MatchEvaluator(escapeMatch)); + } + + private string escapeMatch(Match match) + { + escaped.Add(match.Groups[1].Value); + return "\\"; + } + + //decode escaped characters + private int unescapeIndex = 0; + private string unescape(string str) + { + if (escapeChar == '\0') + return str; + Regex unescaping = new Regex("\\" + escapeChar); + return unescaping.Replace(str, new MatchEvaluator(unescapeMatch)); + } + + private string unescapeMatch(Match match) + { + return "\\" + escaped[unescapeIndex++]; + } + + private string internalEscape(string str) + { + return ESCAPE.Replace(str, ""); + } + + //subclass for each pattern + private class Pattern + { + public string expression; + public object replacement; + public int length; + + public override string ToString() + { + return "(" + expression + ")"; + } + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/Properties/AssemblyInfo.cs b/src/DotNetOpenAuth.BuildTasks/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..6fdcc21 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("CustomMsBuildTasks")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("CustomMsBuildTasks")] +[assembly: AssemblyCopyright("Copyright © 2008")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("952e3aaa-5dc6-4b71-8c9c-6b485263be19")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/DotNetOpenAuth.BuildTasks/Purge.cs b/src/DotNetOpenAuth.BuildTasks/Purge.cs new file mode 100644 index 0000000..e19e485 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/Purge.cs @@ -0,0 +1,88 @@ +//----------------------------------------------------------------------- +// <copyright file="Purge.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.BuildTasks { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using Microsoft.Build.Utilities; + using Microsoft.Build.Framework; + using System.IO; + using System.Text.RegularExpressions; + + /// <summary> + /// Purges directory trees of all directories and files that are not on a whitelist. + /// </summary> + /// <remarks> + /// This task performs a function similar to robocopy's /MIR switch, except that + /// this task does not require that an entire directory tree be used as the source + /// in order to purge old files from the destination. + /// </remarks> + public class Purge : Task { + /// <summary> + /// Initializes a new instance of the <see cref="Purge"/> class. + /// </summary> + public Purge() { + this.PurgeEmptyDirectories = true; + } + + /// <summary> + /// Gets or sets the root directories to purge. + /// </summary> + /// <value>The directories.</value> + [Required] + public string[] Directories { get; set; } + + /// <summary> + /// Gets or sets the files that should be NOT be purged. + /// </summary> + public ITaskItem[] IntendedFiles { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether empty directories will be deleted. + /// </summary> + /// <value> + /// The default value is <c>true</c>. + /// </value> + public bool PurgeEmptyDirectories { get; set; } + + /// <summary> + /// Executes this instance. + /// </summary> + public override bool Execute() { + HashSet<string> intendedFiles = new HashSet<string>(this.IntendedFiles.Select(file => file.GetMetadata("FullPath")), StringComparer.OrdinalIgnoreCase); + + foreach (string directory in this.Directories.Select(dir => NormalizePath(dir)).Where(dir => Directory.Exists(dir))) { + foreach (string existingFile in Directory.GetFiles(directory, "*", SearchOption.AllDirectories)) { + if (!intendedFiles.Contains(existingFile)) { + this.Log.LogWarning("Purging file \"{0}\".", existingFile); + File.Delete(existingFile); + } + } + + if (this.PurgeEmptyDirectories) { + foreach (string subdirectory in Directory.GetDirectories(directory, "*", SearchOption.AllDirectories)) { + // We have to check for the existance of the directory because it MAY be + // a descendent of a directory we already deleted in this loop. + if (Directory.Exists(subdirectory)) { + if (Directory.GetDirectories(subdirectory).Length == 0 && Directory.GetFiles(subdirectory).Length == 0) { + this.Log.LogWarning("Purging empty directory \"{0}\".", subdirectory); + Directory.Delete(subdirectory); + } + } + } + } + } + + return !this.Log.HasLoggedErrors; + } + + private static string NormalizePath(string path) { + return Regex.Replace(path, @"\\+", @"\"); + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/ReSignDelaySignedAssemblies.cs b/src/DotNetOpenAuth.BuildTasks/ReSignDelaySignedAssemblies.cs new file mode 100644 index 0000000..a0ba386 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/ReSignDelaySignedAssemblies.cs @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------- +// <copyright file="ReSignDelaySignedAssemblies.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.BuildTasks { + using System; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + + public class ReSignDelaySignedAssemblies : SnToolTask { + /// <summary> + /// Gets or sets the key file to use for signing. + /// </summary> + public ITaskItem KeyFile { get; set; } + + /// <summary> + /// Gets or sets the key container. + /// </summary> + public ITaskItem KeyContainer { get; set; } + + /// <summary> + /// Gets or sets the assemblies to re-sign. + /// </summary> + public ITaskItem[] Assemblies { get; set; } + + /// <summary> + /// Generates the command line commands. + /// </summary> + protected override string GenerateCommandLineCommands() { + ////if (this.Assemblies.Length != 1) { + //// throw new NotSupportedException("Exactly 1 assembly for signing is supported."); + ////} + CommandLineBuilder args = new CommandLineBuilder(); + args.AppendSwitch("-q"); + + if (this.KeyFile != null) { + args.AppendSwitch("-R"); + } else if (this.KeyContainer != null) { + args.AppendSwitch("-Rc"); + } else { + throw new InvalidOperationException("Either KeyFile or KeyContainer must be set."); + } + + args.AppendFileNameIfNotNull(this.Assemblies[0]); + if (this.KeyFile != null) { + args.AppendFileNameIfNotNull(this.KeyFile); + } else { + args.AppendFileNameIfNotNull(this.KeyContainer); + } + + return args.ToString(); + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/SetEnvironmentVariable.cs b/src/DotNetOpenAuth.BuildTasks/SetEnvironmentVariable.cs new file mode 100644 index 0000000..2de5976 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/SetEnvironmentVariable.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.Build.Utilities; +using Microsoft.Build.Framework; + +namespace DotNetOpenAuth.BuildTasks { + public class SetEnvironmentVariable : Task { + public SetEnvironmentVariable() { + Scope = EnvironmentVariableTarget.Process; + } + + /// <summary> + /// The name of the environment variable to set or clear. + /// </summary> + [Required] + public string Name { get; set; } + /// <summary> + /// The value of the environment variable, or the empty string to clear it. + /// </summary> + [Required] + public string Value { get; set; } + /// <summary> + /// The target environment for the variable. Machine, User, or Process. + /// </summary> + public EnvironmentVariableTarget Scope { get; set; } + + public override bool Execute() { + Environment.SetEnvironmentVariable(Name, Value, Scope); + return true; + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/SignatureVerification.cs b/src/DotNetOpenAuth.BuildTasks/SignatureVerification.cs new file mode 100644 index 0000000..2e69926 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/SignatureVerification.cs @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------- +// <copyright file="SignatureVerification.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.BuildTasks { + using Microsoft.Build.Utilities; + + public class SignatureVerification : SnToolTask { + /// <summary> + /// Gets or sets a value indicating whether to register the given assembly and public key token + /// for skip verification or clear any pre-existing skip verification entry. + /// </summary> + public bool SkipVerification { get; set; } + + /// <summary> + /// Gets or sets the name of the assembly. + /// </summary> + /// <value>The name of the assembly.</value> + public string AssemblyName { get; set; } + + /// <summary> + /// Gets or sets the public key token. + /// </summary> + /// <value>The public key token.</value> + public string PublicKeyToken { get; set; } + + /// <summary> + /// Generates the command line commands. + /// </summary> + protected override string GenerateCommandLineCommands() { + CommandLineBuilder builder = new CommandLineBuilder(); + builder.AppendSwitch("-q"); + if (this.SkipVerification) { + builder.AppendSwitch("-Vr"); + } else { + builder.AppendSwitch("-Vu"); + } + + builder.AppendFileNameIfNotNull(this.AssemblyName + "," + this.PublicKeyToken); + return builder.ToString(); + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/SnToolTask.cs b/src/DotNetOpenAuth.BuildTasks/SnToolTask.cs new file mode 100644 index 0000000..29896fe --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/SnToolTask.cs @@ -0,0 +1,37 @@ +//----------------------------------------------------------------------- +// <copyright file="SnToolTask.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.BuildTasks { + using System; + using System.IO; + using Microsoft.Build.Utilities; + + public abstract class SnToolTask : ToolTask { + /// <summary> + /// Gets the name of the tool. + /// </summary> + /// <value>The name of the tool.</value> + protected override string ToolName { + get { return "sn.exe"; } + } + + /// <summary> + /// Generates the full path to tool. + /// </summary> + protected override string GenerateFullPathToTool() { + string[] versions = new[] { "v6.0A", "v6.1", "v7.0a" }; + string fullPath = null; + foreach (string version in versions) { + fullPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"Microsoft SDKs\Windows\" + version + @"\bin\" + this.ToolName); + if (File.Exists(fullPath)) { + return fullPath; + } + } + + throw new FileNotFoundException("Unable to find sn.exe tool.", fullPath); + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/TaskStrings.Designer.cs b/src/DotNetOpenAuth.BuildTasks/TaskStrings.Designer.cs new file mode 100644 index 0000000..17647fd --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/TaskStrings.Designer.cs @@ -0,0 +1,117 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// Runtime Version:2.0.50727.4927 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +namespace DotNetOpenAuth.BuildTasks { + using System; + + + /// <summary> + /// A strongly-typed resource class, for looking up localized strings, etc. + /// </summary> + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class TaskStrings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal TaskStrings() { + } + + /// <summary> + /// Returns the cached ResourceManager instance used by this class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DotNetOpenAuth.BuildTasks.TaskStrings", typeof(TaskStrings).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// <summary> + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// <summary> + /// Looks up a localized string similar to Web application '{0}' deleted.. + /// </summary> + internal static string DeletedWebApplication { + get { + return ResourceManager.GetString("DeletedWebApplication", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The {0} and {1} properties must be set to arrays of equal length.. + /// </summary> + internal static string MismatchingArrayLengths { + get { + return ResourceManager.GetString("MismatchingArrayLengths", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to No web site with the name '{0}' found.. + /// </summary> + internal static string NoMatchingWebSiteFound { + get { + return ResourceManager.GetString("NoMatchingWebSiteFound", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Packing javascript resource "{0}" into "{1}".. + /// </summary> + internal static string PackingJsFile { + get { + return ResourceManager.GetString("PackingJsFile", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Skip packing "{0}" because its packed version is up to date.. + /// </summary> + internal static string SkipPackingJsFile { + get { + return ResourceManager.GetString("SkipPackingJsFile", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Web application '{0}' was not found, so it was not deleted.. + /// </summary> + internal static string WebApplicationNotFoundSoNotDeleted { + get { + return ResourceManager.GetString("WebApplicationNotFoundSoNotDeleted", resourceCulture); + } + } + } +} diff --git a/src/DotNetOpenAuth.BuildTasks/TaskStrings.resx b/src/DotNetOpenAuth.BuildTasks/TaskStrings.resx new file mode 100644 index 0000000..50e1592 --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/TaskStrings.resx @@ -0,0 +1,138 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <data name="DeletedWebApplication" xml:space="preserve"> + <value>Web application '{0}' deleted.</value> + </data> + <data name="MismatchingArrayLengths" xml:space="preserve"> + <value>The {0} and {1} properties must be set to arrays of equal length.</value> + </data> + <data name="NoMatchingWebSiteFound" xml:space="preserve"> + <value>No web site with the name '{0}' found.</value> + </data> + <data name="PackingJsFile" xml:space="preserve"> + <value>Packing javascript resource "{0}" into "{1}".</value> + </data> + <data name="SkipPackingJsFile" xml:space="preserve"> + <value>Skip packing "{0}" because its packed version is up to date.</value> + </data> + <data name="WebApplicationNotFoundSoNotDeleted" xml:space="preserve"> + <value>Web application '{0}' was not found, so it was not deleted.</value> + </data> +</root>
\ No newline at end of file diff --git a/src/DotNetOpenAuth.BuildTasks/Trim.cs b/src/DotNetOpenAuth.BuildTasks/Trim.cs new file mode 100644 index 0000000..972b87d --- /dev/null +++ b/src/DotNetOpenAuth.BuildTasks/Trim.cs @@ -0,0 +1,79 @@ +//----------------------------------------------------------------------- +// <copyright file="Trim.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.BuildTasks { + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + + /// <summary> + /// Trims item identities or metadata. + /// </summary> + public class Trim : Task { + /// <summary> + /// Gets or sets the name of the metadata to trim. Leave empty or null to operate on itemspec. + /// </summary> + /// <value>The name of the metadata.</value> + public string MetadataName { get; set; } + + /// <summary> + /// Gets or sets the characters that should be trimmed off if found at the start of items' ItemSpecs. + /// </summary> + public string StartCharacters { get; set; } + + /// <summary> + /// Gets or sets the characters that should be trimmed off if found at the end of items' ItemSpecs. + /// </summary> + public string EndCharacters { get; set; } + + /// <summary> + /// Gets or sets the substring that should be trimmed along with everything that appears after it. + /// </summary> + public string AllAfter { get; set; } + + /// <summary> + /// Gets or sets the items with ItemSpec's to be trimmed. + /// </summary> + [Required] + public ITaskItem[] Inputs { get; set; } + + /// <summary> + /// Gets or sets the items with trimmed ItemSpec strings. + /// </summary> + [Output] + public ITaskItem[] Outputs { get; set; } + + /// <summary> + /// Executes this instance. + /// </summary> + /// <returns>A value indicating whether the task completed successfully.</returns> + public override bool Execute() { + this.Outputs = new ITaskItem[this.Inputs.Length]; + for (int i = 0; i < this.Inputs.Length; i++) { + this.Outputs[i] = new TaskItem(this.Inputs[i]); + string value = string.IsNullOrEmpty(this.MetadataName) ? this.Outputs[i].ItemSpec : this.Outputs[i].GetMetadata(this.MetadataName); + if (!string.IsNullOrEmpty(this.StartCharacters)) { + value = value.TrimStart(this.StartCharacters.ToCharArray()); + } + if (!string.IsNullOrEmpty(this.EndCharacters)) { + value = value.TrimEnd(this.EndCharacters.ToCharArray()); + } + if (!string.IsNullOrEmpty(this.AllAfter)) { + int index = value.IndexOf(this.AllAfter); + if (index >= 0) { + value = value.Substring(0, index); + } + } + if (string.IsNullOrEmpty(this.MetadataName)) { + this.Outputs[i].ItemSpec = value; + } else { + this.Outputs[i].SetMetadata(this.MetadataName, value); + } + } + + return true; + } + } +} |